foot/osc.c

1717 lines
48 KiB
C
Raw Normal View History

#include "osc.h"
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <sys/epoll.h>
#define LOG_MODULE "osc"
2025-01-02 09:12:11 +01:00
#define LOG_ENABLE_DBG 0
#include "log.h"
#include "base64.h"
#include "config.h"
#include "macros.h"
#include "notify.h"
#include "selection.h"
#include "terminal.h"
#include "uri.h"
#include "util.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;
}
}
2020-07-08 19:28:08 +02:00
/* 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;
}
const bool copy_allowed = term->conf->security.osc52 == OSC52_ENABLED
|| term->conf->security.osc52 == OSC52_COPY_ENABLED;
if (!copy_allowed) {
LOG_DBG("ignoring copy request: disabled in configuration");
return;
}
char *decoded = base64_decode(base64_data, NULL);
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);
}
2020-07-08 19:28:08 +02:00
free(decoded);
}
struct clip_context {
2020-07-08 19:28:08 +02:00
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_paste_data_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_paste_data_to_slave(term, chunk, strlen(chunk));
free(chunk);
}
2020-07-08 19:28:08 +02:00
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_paste_data_to_slave(term, res, 4);
}
if (term->vt.osc.bel)
term_paste_data_to_slave(term, "\a", 1);
else
term_paste_data_to_slave(term, "\033\\", 2);
term->is_sending_paste_data = false;
/* Make sure we send any queued up non-paste data */
if (tll_length(term->ptmx_buffers) > 0)
fdm_event_add(term->fdm, term->ptmx, EPOLLOUT);
free(ctx);
}
2020-07-08 19:28:08 +02:00
static void
osc_from_clipboard(struct terminal *term, const char *source)
{
2020-07-08 19:28:08 +02:00
/* 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;
}
const bool paste_allowed = term->conf->security.osc52 == OSC52_ENABLED
|| term->conf->security.osc52 == OSC52_PASTE_ENABLED;
if (!paste_allowed) {
LOG_DBG("ignoring paste request: disabled in configuration");
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;
if (term->is_sending_paste_data) {
/* FIXME: we should wait for the paste to end, then continue
with the OSC-52 reply */
term_to_slave(term, "\033]52;", 5);
term_to_slave(term, &src, 1);
term_to_slave(term, ";", 1);
if (term->vt.osc.bel)
term_to_slave(term, "\a", 1);
else
term_to_slave(term, "\033\\", 2);
return;
}
term->is_sending_paste_data = true;
term_paste_data_to_slave(term, "\033]52;", 5);
term_paste_data_to_slave(term, &src, 1);
term_paste_data_to_slave(term, ";", 1);
struct clip_context *ctx = xmalloc(sizeof(*ctx));
2020-07-08 19:28:08 +02:00
*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) {
2020-07-08 19:28:08 +02:00
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 (p[0] == '?' && p[1] == '\0')
osc_from_clipboard(term, string);
else
osc_to_clipboard(term, string, p);
}
static void
osc_flash(struct terminal *term)
{
/* Our own private - flash */
2019-07-30 22:08:58 +02:00
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) {
if (len < STRLEN("rgba:x/x/x/x"))
return false;
} else {
if (len < STRLEN("rgb:x/x/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 (streq(scheme, "file") && 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 (streq(key, "id"))
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);
if (title == NULL)
return;
if (mbsntoc32(NULL, title, strlen(title), 0) == (size_t)-1) {
LOG_WARN("%s: notification title is not valid UTF-8, ignoring", title);
return;
}
if (msg != NULL && mbsntoc32(NULL, msg, strlen(msg), 0) == (size_t)-1) {
LOG_WARN("%s: notification message is not valid UTF-8, ignoring", msg);
return;
}
char *msgdup = NULL;
if (msg != NULL)
msgdup = xstrdup(msg);
2024-07-23 06:59:46 +02:00
notify_notify(term, &(struct notification){
.title = xstrdup(title),
.body = msgdup,
.expire_time = -1,
.focus = true,
});
}
IGNORE_WARNING("-Wpedantic")
static bool
verify_kitty_id_is_valid(const char *id)
{
const size_t len = strlen(id);
for (size_t i = 0; i < len; i++) {
switch (id[i]) {
case 'a' ... 'z':
case 'A' ... 'Z':
case '0' ... '9':
case '_':
case '-':
case '+':
case '.':
break;
default:
return false;
}
}
return true;
}
UNIGNORE_WARNINGS
static void
kitty_notification(struct terminal *term, char *string)
{
/* https://sw.kovidgoyal.net/kitty/desktop-notifications */
char *payload_raw = strchr(string, ';');
if (payload_raw == NULL)
return;
char *parameters = string;
*payload_raw = '\0';
payload_raw++;
char *id = NULL; /* The 'i' parameter */
char *app_id = NULL; /* The 'f' parameter */
char *icon_cache_id = NULL; /* The 'g' parameter */
char *symbolic_icon = NULL; /* The 'n' parameter */
char *category = NULL; /* The 't' parameter */
char *sound_name = NULL; /* The 's' parameter */
char *payload = NULL;
bool focus = true; /* The 'a' parameter */
bool report_activated = false; /* The 'a' parameter */
bool report_closed = false; /* The 'c' parameter */
bool done = true; /* The 'd' parameter */
bool base64 = false; /* The 'e' parameter */
int32_t expire_time = -1; /* The 'w' parameter */
size_t payload_size;
enum {
PAYLOAD_TITLE,
PAYLOAD_BODY,
PAYLOAD_CLOSE,
PAYLOAD_ALIVE,
PAYLOAD_ICON,
PAYLOAD_BUTTON,
} payload_type = PAYLOAD_TITLE; /* The 'p' parameter */
enum notify_when when = NOTIFY_ALWAYS;
enum notify_urgency urgency = NOTIFY_URGENCY_NORMAL;
bool have_a = false;
bool have_c = false;
bool have_o = false;
bool have_u = false;
bool have_w = false;
char *ctx = NULL;
for (char *param = strtok_r(parameters, ":", &ctx);
param != NULL;
param = strtok_r(NULL, ":", &ctx))
{
/* All parameters are on the form X=value, where X is always
exactly one character */
2024-07-23 06:59:46 +02:00
if (param[0] == '\0' || param[1] != '=')
continue;
char *value = &param[2];
switch (param[0]) {
case 'a': {
/* notification activation action: focus|report|-focus|-report */
have_a = true;
char *a_ctx = NULL;
2024-07-23 06:59:46 +02:00
for (const char *v = strtok_r(value, ",", &a_ctx);
v != NULL;
v = strtok_r(NULL, ",", &a_ctx))
{
bool reverse = v[0] == '-';
if (reverse)
v++;
2024-07-24 16:04:14 +02:00
if (streq(v, "focus"))
focus = !reverse;
2024-07-24 16:04:14 +02:00
else if (streq(v, "report"))
report_activated = !reverse;
}
break;
}
case 'c':
if (value[0] == '1' && value[1] == '\0')
report_closed = true;
else if (value[0] == '0' && value[1] == '\0')
report_closed = false;
have_c = true;
break;
case 'd':
/* done: 0|1 */
if (value[0] == '0' && value[1] == '\0')
done = false;
else if (value[0] == '1' && value[1] == '\0')
done = true;
break;
case 'e':
2024-07-23 12:12:38 +02:00
/* base64 (payload encoding): 0=utf8, 1=base64(utf8) */
if (value[0] == '0' && value[1] == '\0')
base64 = false;
else if (value[0] == '1' && value[1] == '\0')
base64 = true;
break;
case 'i':
/* id */
if (verify_kitty_id_is_valid(value)) {
free(id);
id = xstrdup(value);
} else
LOG_WARN("OSC-99: ignoring invalid 'i' identifier");
break;
case 'p':
/* payload content: title|body */
2024-07-24 16:04:14 +02:00
if (streq(value, "title"))
payload_type = PAYLOAD_TITLE;
2024-07-24 16:04:14 +02:00
else if (streq(value, "body"))
payload_type = PAYLOAD_BODY;
else if (streq(value, "close"))
payload_type = PAYLOAD_CLOSE;
else if (streq(value, "alive"))
payload_type = PAYLOAD_ALIVE;
2024-07-24 16:04:14 +02:00
else if (streq(value, "icon"))
payload_type = PAYLOAD_ICON;
else if (streq(value, "buttons"))
payload_type = PAYLOAD_BUTTON;
2024-07-24 16:04:14 +02:00
else if (streq(value, "?")) {
/* Query capabilities */
const char *reply_id = id != NULL ? id : "0";
const char *p_caps = "title,body,?,close,alive,icon,buttons";
const char *a_caps = "focus,report";
const char *u_caps = "0,1,2";
char when_caps[64];
strcpy(when_caps, "unfocused");
if (!term->conf->desktop_notifications.inhibit_when_focused)
strcat(when_caps, ",always");
const char *terminator = term->vt.osc.bel ? "\a" : "\033\\";
char reply[128];
size_t n = xsnprintf(
reply, sizeof(reply),
"\033]99;i=%s:p=?;p=%s:a=%s:o=%s:u=%s:c=1:w=1:s=system,silent,error,warn,warning,info,question%s",
reply_id, p_caps, a_caps, when_caps, u_caps, terminator);
xassert(n < sizeof(reply));
term_to_slave(term, reply, n);
goto out;
}
break;
case 'o':
/* honor when: always|unfocused|invisible */
have_o = true;
2024-07-24 16:04:14 +02:00
if (streq(value, "always"))
when = NOTIFY_ALWAYS;
2024-07-24 16:04:14 +02:00
else if (streq(value, "unfocused"))
when = NOTIFY_UNFOCUSED;
2024-07-24 16:04:14 +02:00
else if (streq(value, "invisible"))
when = NOTIFY_INVISIBLE;
break;
case 'u':
/* urgency: 0=low, 1=normal, 2=critical */
have_u = true;
if (value[0] == '0' && value[1] == '\0')
urgency = NOTIFY_URGENCY_LOW;
else if (value[0] == '1' && value[1] == '\0')
urgency = NOTIFY_URGENCY_NORMAL;
else if (value[0] == '2' && value[1] == '\0')
urgency = NOTIFY_URGENCY_CRITICAL;
break;
case 'w': {
/* Notification timeout */
errno = 0;
char *end = NULL;
long timeout = strtol(value, &end, 10);
if (errno == 0 && *end == '\0' && timeout <= INT32_MAX) {
expire_time = timeout;
have_w = true;
}
break;
}
case 'f': {
/* App-name */
char *decoded = base64_decode(value, NULL);
if (decoded != NULL) {
free(app_id);
app_id = decoded;
}
break;
}
case 't': {
/* Type (category) */
char *decoded = base64_decode(value, NULL);
if (decoded != NULL) {
if (category == NULL)
category = decoded;
else {
/* Append, comma separated */
char *old_category = category;
category = xstrjoin3(old_category, ",", decoded);
free(decoded);
free(old_category);
}
}
break;
}
case 's': {
/* Sound */
char *decoded = base64_decode(value, NULL);
if (decoded != NULL) {
free(sound_name);
sound_name = decoded;
const char *translated_name = NULL;
if (streq(decoded, "error"))
translated_name = "dialog-error";
else if (streq(decoded, "warn") || streq(decoded, "warning"))
translated_name = "dialog-warning";
else if (streq(decoded, "info"))
translated_name = "dialog-information";
else if (streq(decoded, "question"))
translated_name = "dialog-question";
if (translated_name != NULL) {
free(sound_name);
sound_name = xstrdup(translated_name);
}
}
break;
}
case 'g':
/* graphical ID (see 'n' and 'p=icon') */
free(icon_cache_id);
icon_cache_id = xstrdup(value);
break;
case 'n': {
/* Symbolic icon name, may used with 'g' */
/*
* Sigh, protocol says 'n' can be used multiple times, and
* that the terminal picks the first one that it can
* resolve.
*
* We can't resolve any icons at all. So, enter
* heuristics... let's pick the *shortest* symbolic
* name. The idea is that icon *names* are typically
* shorter than .desktop names, and macOS bundle
* identifiers.
*/
char *maybe_new_symbolic_icon = base64_decode(value, NULL);
if (maybe_new_symbolic_icon == NULL)
break;
if (symbolic_icon == NULL ||
strlen(maybe_new_symbolic_icon) < strlen(symbolic_icon))
{
free(symbolic_icon);
symbolic_icon = maybe_new_symbolic_icon;
/* Translate OSC-99 "special" names */
if (symbolic_icon != NULL) {
const char *translated_name = NULL;
if (streq(symbolic_icon, "error"))
translated_name = "dialog-error";
else if (streq(symbolic_icon, "warn") ||
streq(symbolic_icon, "warning"))
translated_name = "dialog-warning";
else if (streq(symbolic_icon, "info"))
translated_name = "dialog-information";
else if (streq(symbolic_icon, "question"))
translated_name = "dialog-question";
else if (streq(symbolic_icon, "help"))
translated_name = "system-help";
else if (streq(symbolic_icon, "file-manager"))
translated_name = "system-file-manager";
else if (streq(symbolic_icon, "system-monitor"))
translated_name = "utilities-system-monitor";
else if (streq(symbolic_icon, "text-editor"))
translated_name = "text-editor";
if (translated_name != NULL) {
free(symbolic_icon);
symbolic_icon = xstrdup(translated_name);
}
}
} else {
free(maybe_new_symbolic_icon);
}
2024-07-23 06:59:46 +02:00
break;
}
}
}
if (base64) {
payload = base64_decode(payload_raw, &payload_size);
if (payload == NULL)
goto out;
} else {
payload = xstrdup(payload_raw);
payload_size = strlen(payload);
}
/* Append metadata to previous notification chunk */
struct notification *notif = &term->kitty_notification;
if (!((id == NULL && notif->id == NULL) ||
(id != NULL && notif->id != NULL && streq(id, notif->id))) ||
!notif->may_be_programatically_closed) /* Free:d notification has this as false... */
{
/* ID mismatch, ignore previous notification state */
notify_free(term, notif);
notif->id = id;
notif->when = when;
notif->urgency = urgency;
notif->expire_time = expire_time;
notif->focus = focus;
notif->may_be_programatically_closed = true;
notif->report_activated = report_activated;
notif->report_closed = report_closed;
2024-07-23 06:59:46 +02:00
id = NULL; /* Prevent double free */
}
if (have_a) {
notif->focus = focus;
notif->report_activated = report_activated;
}
if (have_c)
notif->report_closed = report_closed;
if (have_o)
notif->when = when;
if (have_u)
notif->urgency = urgency;
if (have_w)
notif->expire_time = expire_time;
if (icon_cache_id != NULL) {
free(notif->icon_cache_id);
notif->icon_cache_id = icon_cache_id;
icon_cache_id = NULL; /* Prevent double free */
}
if (symbolic_icon != NULL) {
free(notif->icon_symbolic_name);
notif->icon_symbolic_name = symbolic_icon;
symbolic_icon = NULL;
2024-07-23 06:59:46 +02:00
}
if (app_id != NULL) {
free(notif->app_id);
notif->app_id = app_id;
app_id = NULL; /* Prevent double free */
}
if (category != NULL) {
if (notif->category == NULL) {
notif->category = category;
category = NULL; /* Prevent double free */
} else {
/* Append, comma separated */
char *new_category = xstrjoin3(notif->category, ",", category);
free(notif->category);
notif->category = new_category;
}
}
if (sound_name != NULL) {
notif->muted = streq(sound_name, "silent");
if (notif->muted || streq(sound_name, "system")) {
free(notif->sound_name);
notif->sound_name = NULL;
} else {
free(notif->sound_name);
notif->sound_name = sound_name;
sound_name = NULL; /* Prevent double free */
}
}
/* Handled chunked payload - append to existing metadata */
switch (payload_type) {
case PAYLOAD_TITLE:
case PAYLOAD_BODY: {
char **ptr = payload_type == PAYLOAD_TITLE
? &notif->title
: &notif->body;
if (*ptr == NULL) {
*ptr = payload;
payload = NULL;
} else {
char *old = *ptr;
*ptr = xstrjoin(old, payload);
free(old);
}
break;
}
case PAYLOAD_CLOSE:
case PAYLOAD_ALIVE:
/* Ignore payload */
break;
case PAYLOAD_ICON:
if (notif->icon_data == NULL) {
notif->icon_data = (uint8_t *)payload;
notif->icon_data_sz = payload_size;
payload = NULL;
} else {
notif->icon_data = xrealloc(
notif->icon_data, notif->icon_data_sz + payload_size);
memcpy(&notif->icon_data[notif->icon_data_sz], payload, payload_size);
notif->icon_data_sz += payload_size;
}
break;
case PAYLOAD_BUTTON: {
char *ctx = NULL;
for (const char *button = strtok_r(payload, "\u2028", &ctx);
button != NULL;
button = strtok_r(NULL, "\u2028", &ctx))
{
if (button[0] != '\0') {
tll_push_back(notif->actions, xstrdup(button));
}
}
break;
}
}
if (done) {
/* Update icon cache, if necessary */
if (notif->icon_cache_id != NULL &&
(notif->icon_symbolic_name != NULL || notif->icon_data != NULL))
{
notify_icon_del(term, notif->icon_cache_id);
notify_icon_add(term, notif->icon_cache_id,
notif->icon_symbolic_name,
notif->icon_data, notif->icon_data_sz);
/* Don't need this anymore */
free(notif->icon_symbolic_name);
free(notif->icon_data);
notif->icon_symbolic_name = NULL;
notif->icon_data = NULL;
notif->icon_data_sz = 0;
}
if (payload_type == PAYLOAD_CLOSE) {
if (notif->id != NULL)
notify_close(term, notif->id);
} else if (payload_type == PAYLOAD_ALIVE) {
char *alive_ids = NULL;
tll_foreach(term->active_notifications, it) {
/* TODO: check with kitty: use "0" for all
notifications with no ID? */
const char *item_id = it->item.id != NULL ? it->item.id : "0";
if (alive_ids == NULL)
alive_ids = xstrdup(item_id);
else {
char *old_alive_ids = alive_ids;
alive_ids = xstrjoin3(old_alive_ids, ",", item_id);
free(old_alive_ids);
}
}
char *reply = xasprintf(
"\033]99;i=%s:p=alive;%s\033\\",
notif->id != NULL ? notif->id : "0",
alive_ids != NULL ? alive_ids : "");
term_to_slave(term, reply, strlen(reply));
free(reply);
free(alive_ids);
} else {
/*
* Show notification.
*
* The checks for title|body is to handle notifications that
* only load icon data into the icon cache
*/
if (notif->title != NULL || notif->body != NULL) {
notify_notify(term, notif);
}
}
notify_free(term, notif);
}
2024-07-23 06:59:46 +02:00
out:
free(id);
free(app_id);
free(icon_cache_id);
free(symbolic_icon);
2024-07-23 06:59:46 +02:00
free(payload);
free(category);
free(sound_name);
}
static void
kitty_text_size(struct terminal *term, char *string)
{
char *text = strchr(string, ';');
if (text == NULL)
return;
char *parameters = string;
*text = '\0';
text++;
char32_t *wchars = ambstoc32(text);
if (wchars == NULL)
return;
int forced_width = 0;
char *ctx = NULL;
for (char *param = strtok_r(parameters, ":", &ctx);
param != NULL;
param = strtok_r(NULL, ":", &ctx))
{
/* All parameters are on the form X=value, where X is always
exactly one character */
if (param[0] == '\0' || param[1] != '=')
continue;
char *value = &param[2];
switch (param[0]) {
case 'w': {
errno = 0;
char *end = NULL;
unsigned long w = strtoul(value, &end, 10);
if (*end == '\0' && errno == 0 && w <= 7) {
forced_width = (int)w;
break;
} else
LOG_ERR("OSC-66: invalid 'w' value, ignoring");
break;
}
case 's':
case 'n':
case 'd':
case 'v':
LOG_WARN("OSC-66: unsupported: '%c' parameter, ignoring", param[0]);
break;
}
}
const size_t len = c32len(wchars);
if (forced_width == 0) {
/*
* w=0 means we split the text up as we'd normally do... Since
* we don't support any other parameters of the text-sizing
* protocol, that means we just process the string as if it
* has been printed without this OSC.
*/
for (size_t i = 0; i < len; i++)
term_process_and_print_non_ascii(term, wchars[i]);
free(wchars);
return;
}
size_t max_cp_width = 0;
size_t all_cp_width = 0;
for (size_t i = 0; i < len; i++) {
const size_t cp_width = c32width(wchars[i]);
all_cp_width += cp_width;
max_cp_width = max(max_cp_width, cp_width);
}
size_t calculated_width = 0;
switch (term->conf->tweak.grapheme_width_method) {
case GRAPHEME_WIDTH_WCSWIDTH: calculated_width = all_cp_width; break;
case GRAPHEME_WIDTH_MAX: calculated_width = max_cp_width; break;
case GRAPHEME_WIDTH_DOUBLE: calculated_width = min(max_cp_width, 2); break;
}
const size_t width = forced_width == 0 ? calculated_width : forced_width;
LOG_DBG("len=%zu, forced=%d, calculated=%zu, using=%zu",
len, forced_width, calculated_width, width);
#if 0
if (len == 1 && calculated_width == forced_width) {
/*
* Optimization: if there's a single codepoint, and either
* w=0, or the 'w' matches the calculated width, print
* codepoint directly instead of creating a combining
* character.
*/
term_print(term, wchars[0], width);
free(wchars);
return;
}
#endif
uint32_t key = composed_key_from_chars(wchars, len);
const struct composed *composed = composed_lookup_without_collision(
term->composed, &key, wchars, len - 1, wchars[len - 1], forced_width);
if (composed == NULL) {
struct composed *new_cc = xmalloc(sizeof(*new_cc));
new_cc->chars = wchars;
new_cc->count = len;
new_cc->key = key;
new_cc->width = width;
new_cc->forced_width = forced_width;
term->composed_count++;
composed_insert(&term->composed, new_cc);
composed = new_cc;
} else if (composed->width == width) {
free(wchars);
}
term_print(
term, CELL_COMB_CHARS_LO + composed->key,
composed->forced_width > 0 ? composed->forced_width : composed->width,
false);
}
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';
}
2021-07-24 01:58:34 +01:00
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: /* icon + title */
term_set_window_title(term, string);
break;
case 1: /* icon */
break;
case 2: /* title */
term_set_window_title(term, string);
break;
case 4: {
/* Set color<idx> */
2020-04-04 14:27:44 +02:00
string--;
if (*string != ';')
break;
xassert(*string == ';');
2020-04-04 14:27:44 +02:00
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;
}
2020-04-04 14:27:44 +02:00
/* Client queried for current value */
if (s_color[0] == '?' && s_color[1] == '\0') {
2020-04-04 14:27:44 +02:00
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;
const char *terminator = term->vt.osc.bel ? "\a" : "\033\\";
2020-04-04 14:27:44 +02:00
char reply[32];
size_t n = xsnprintf(
reply, sizeof(reply),
"\033]4;%u;rgb:%02hhx%02hhx/%02hhx%02hhx/%02hhx%02hhx%s",
idx, r, r, g, g, b, b, terminator);
term_to_slave(term, reply, n);
2020-04-04 14:27:44 +02:00
}
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);
2020-04-04 14:27:44 +02:00
if (!color_is_valid)
continue;
LOG_DBG("change color definition for #%u from %06x to %06x",
idx, term->colors.table[idx], color);
2020-04-04 14:27:44 +02:00
term->colors.table[idx] = color;
term_damage_color(term, COLOR_BASE256, idx);
2020-04-04 14:27:44 +02:00
}
}
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 */
const char *sep = strchr(string, ';');
if (sep != NULL) {
errno = 0;
char *end = NULL;
strtoul(string, &end, 10);
if (end == sep && errno == 0) {
/* Ignore ConEmu/Windows Terminal escape */
break;
}
}
osc_notify(term, string);
break;
}
case 10: /* fg */
case 11: /* bg */
case 12: /* cursor */
case 17: /* highlight (selection) fg */
case 19: { /* highlight (selection) bg */
/* Set default foreground/background/highlight-bg/highlight-fg color */
/* Client queried for current value */
if (string[0] == '?' && string[1] == '\0') {
uint32_t color = param == 10
? term->colors.fg
: param == 11
? term->colors.bg
: param == 12
? term->colors.cursor_bg
: param == 17
? term->colors.selection_bg
: term->colors.selection_fg;
uint8_t r = (color >> 16) & 0xff;
uint8_t g = (color >> 8) & 0xff;
uint8_t b = (color >> 0) & 0xff;
const char *terminator = term->vt.osc.bel ? "\a" : "\033\\";
/*
* 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:%02hhx%02hhx/%02hhx%02hhx/%02hhx%02hhx%s",
param, r, r, g, g, b, b, terminator);
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 == 12 ? "cursor" :
param == 17 ? "selection background" :
"selection foreground",
color);
switch (param) {
case 10:
term->colors.fg = color;
term_damage_color(term, COLOR_DEFAULT, 0);
break;
case 11:
term->colors.bg = color;
if (have_alpha) {
const bool changed = term->colors.alpha != alpha;
term->colors.alpha = alpha;
if (changed) {
wayl_win_alpha_changed(term->window);
term_font_subpixel_changed(term);
}
}
term_damage_color(term, COLOR_DEFAULT, 0);
term_damage_margins(term);
break;
case 12:
term->colors.cursor_bg = 1u << 31 | color;
term_damage_cursor(term);
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;
}
break;
}
case 22: /* Set mouse cursor */
term_set_user_mouse_cursor(term, string);
break;
case 30: /* Set tab title */
break;
case 52: /* Copy to/from clipboard/primary */
osc_selection(term, string);
break;
case 66: /* text-size protocol (kitty) */
kitty_text_size(term, string);
break;
case 99: /* Kitty notifications */
kitty_notification(term, string);
break;
case 104: {
/* Reset Color Number 'c' (whole table if no parameter) */
if (string[0] == '\0') {
LOG_DBG("resetting all colors");
for (size_t i = 0; i < ALEN(term->colors.table); i++)
term->colors.table[i] = term->conf->colors.table[i];
term_damage_view(term);
}
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);
term->colors.table[idx] = term->conf->colors.table[idx];
term_damage_color(term, COLOR_BASE256, 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_color(term, COLOR_DEFAULT, 0);
break;
case 111: { /* Reset default text background color */
LOG_DBG("resetting background color");
bool alpha_changed = term->colors.alpha != term->conf->colors.alpha;
term->colors.bg = term->conf->colors.bg;
term->colors.alpha = term->conf->colors.alpha;
if (alpha_changed) {
wayl_win_alpha_changed(term->window);
term_font_subpixel_changed(term);
}
term_damage_color(term, COLOR_DEFAULT, 0);
term_damage_margins(term);
break;
}
case 112:
LOG_DBG("resetting cursor color");
term->colors.cursor_fg = term->conf->colors.cursor.text;
term->colors.cursor_bg = term->conf->colors.cursor.cursor;
if (term->conf->colors.use_custom.cursor) {
term->colors.cursor_fg |= 1u << 31;
term->colors.cursor_bg |= 1u << 31;
}
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;
osc: add support for OSC 133;A (prompt markers) This patch adds support for the OSC-133;A sequence, introduced by FinalTerm and implemented by iTerm2, Kitty and more. See https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html. The shell emits the OSC just before printing the prompt. This lets the terminal know where, in the scrollback, there are prompts. We implement this using a simple boolean in the row struct ("this row has a prompt"). The prompt marker must be reflowed along with the text on window resizes. In an ideal world, erasing, or overwriting the cell where the OSC was emitted, would remove the prompt mark. Since we don't store this information in the cell struct, we can't do that. The best we can do is reset it in erase_line(). This works well enough in the "normal" screen, when used with a "normal" shell. It doesn't really work in fullscreen apps, on the alt screen. But that doesn't matter since we don't support jumping between prompts on the alt screen anyway. To be able to jump between prompts, two new key bindings have been added: prompt-prev and prompt-next, bound to ctrl+shift+z and ctrl+shift+x respectively. prompt-prev will jump to the previous, not currently visible, prompt, by moving the viewport, ensuring the prompt is at the top of the screen. prompt-next jumps to the next prompt, visible or not. Again, by moving the viewport to ensure the prompt is at the top of the screen. If we're at the bottom of the scrollback, the viewport is instead moved as far down as possible. Closes #30
2022-06-15 18:44:23 +02:00
case 133:
/*
* Shell integration; see
* https://iterm2.com/documentation-escape-codes.html (Shell
* Integration/FinalTerm)
*
* [PROMPT]prompt% [COMMAND_START] ls -l
* [COMMAND_EXECUTED]
* -rw-r--r-- 1 user group 127 May 1 2016 filename
* [COMMAND_FINISHED]
*/
switch (string[0]) {
case 'A':
LOG_DBG("FTCS_PROMPT: %dx%d",
term->grid->cursor.point.row,
term->grid->cursor.point.col);
term->grid->cur_row->shell_integration.prompt_marker = true;
osc: add support for OSC 133;A (prompt markers) This patch adds support for the OSC-133;A sequence, introduced by FinalTerm and implemented by iTerm2, Kitty and more. See https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html. The shell emits the OSC just before printing the prompt. This lets the terminal know where, in the scrollback, there are prompts. We implement this using a simple boolean in the row struct ("this row has a prompt"). The prompt marker must be reflowed along with the text on window resizes. In an ideal world, erasing, or overwriting the cell where the OSC was emitted, would remove the prompt mark. Since we don't store this information in the cell struct, we can't do that. The best we can do is reset it in erase_line(). This works well enough in the "normal" screen, when used with a "normal" shell. It doesn't really work in fullscreen apps, on the alt screen. But that doesn't matter since we don't support jumping between prompts on the alt screen anyway. To be able to jump between prompts, two new key bindings have been added: prompt-prev and prompt-next, bound to ctrl+shift+z and ctrl+shift+x respectively. prompt-prev will jump to the previous, not currently visible, prompt, by moving the viewport, ensuring the prompt is at the top of the screen. prompt-next jumps to the next prompt, visible or not. Again, by moving the viewport to ensure the prompt is at the top of the screen. If we're at the bottom of the scrollback, the viewport is instead moved as far down as possible. Closes #30
2022-06-15 18:44:23 +02:00
break;
case 'B':
LOG_DBG("FTCS_COMMAND_START");
break;
case 'C':
LOG_DBG("FTCS_COMMAND_EXECUTED: %dx%d",
term->grid->cursor.point.row,
term->grid->cursor.point.col);
term->grid->cur_row->shell_integration.cmd_start = term->grid->cursor.point.col;
osc: add support for OSC 133;A (prompt markers) This patch adds support for the OSC-133;A sequence, introduced by FinalTerm and implemented by iTerm2, Kitty and more. See https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html. The shell emits the OSC just before printing the prompt. This lets the terminal know where, in the scrollback, there are prompts. We implement this using a simple boolean in the row struct ("this row has a prompt"). The prompt marker must be reflowed along with the text on window resizes. In an ideal world, erasing, or overwriting the cell where the OSC was emitted, would remove the prompt mark. Since we don't store this information in the cell struct, we can't do that. The best we can do is reset it in erase_line(). This works well enough in the "normal" screen, when used with a "normal" shell. It doesn't really work in fullscreen apps, on the alt screen. But that doesn't matter since we don't support jumping between prompts on the alt screen anyway. To be able to jump between prompts, two new key bindings have been added: prompt-prev and prompt-next, bound to ctrl+shift+z and ctrl+shift+x respectively. prompt-prev will jump to the previous, not currently visible, prompt, by moving the viewport, ensuring the prompt is at the top of the screen. prompt-next jumps to the next prompt, visible or not. Again, by moving the viewport to ensure the prompt is at the top of the screen. If we're at the bottom of the scrollback, the viewport is instead moved as far down as possible. Closes #30
2022-06-15 18:44:23 +02:00
break;
case 'D':
LOG_DBG("FTCS_COMMAND_FINISHED: %dx%d",
term->grid->cursor.point.row,
term->grid->cursor.point.col);
term->grid->cur_row->shell_integration.cmd_end = term->grid->cursor.point.col;
osc: add support for OSC 133;A (prompt markers) This patch adds support for the OSC-133;A sequence, introduced by FinalTerm and implemented by iTerm2, Kitty and more. See https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html. The shell emits the OSC just before printing the prompt. This lets the terminal know where, in the scrollback, there are prompts. We implement this using a simple boolean in the row struct ("this row has a prompt"). The prompt marker must be reflowed along with the text on window resizes. In an ideal world, erasing, or overwriting the cell where the OSC was emitted, would remove the prompt mark. Since we don't store this information in the cell struct, we can't do that. The best we can do is reset it in erase_line(). This works well enough in the "normal" screen, when used with a "normal" shell. It doesn't really work in fullscreen apps, on the alt screen. But that doesn't matter since we don't support jumping between prompts on the alt screen anyway. To be able to jump between prompts, two new key bindings have been added: prompt-prev and prompt-next, bound to ctrl+shift+z and ctrl+shift+x respectively. prompt-prev will jump to the previous, not currently visible, prompt, by moving the viewport, ensuring the prompt is at the top of the screen. prompt-next jumps to the next prompt, visible or not. Again, by moving the viewport to ensure the prompt is at the top of the screen. If we're at the bottom of the scrollback, the viewport is instead moved as far down as possible. Closes #30
2022-06-15 18:44:23 +02:00
break;
}
break;
case 176:
if (string[0] == '?' && string[1] == '\0') {
#if 0 /* Disabled for now, see #1894 */
const char *terminator = term->vt.osc.bel ? "\a" : "\033\\";
char *reply = xasprintf(
"\033]176;%s%s",
term->app_id != NULL ? term->app_id : term->conf->app_id,
terminator);
term_to_slave(term, reply, strlen(reply));
free(reply);
#else
LOG_WARN("OSC-176 app-id query ignored");
#endif
break;
}
term_set_app_id(term, string);
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;
}
}
2019-07-19 08:59:35 +02:00
bool
osc_ensure_size(struct terminal *term, size_t required_size)
{
if (likely(required_size <= term->vt.osc.size))
2019-07-19 08:59:35 +02:00
return true;
const size_t pow2_max = ~(SIZE_MAX >> 1);
if (unlikely(required_size > pow2_max)) {
LOG_ERR("required OSC buffer size (%zu) exceeds limit (%zu)",
required_size, pow2_max);
return false;
}
size_t new_size = max(term->vt.osc.size, 4096);
while (new_size < required_size) {
new_size <<= 1;
}
2019-07-19 08:59:35 +02:00
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;
}
LOG_DBG("resized OSC buffer: %zu", new_size);
2019-07-19 08:59:35 +02:00
term->vt.osc.data = new_data;
term->vt.osc.size = new_size;
return true;
}