2019-06-15 22:22:44 +02:00
|
|
|
|
#include "osc.h"
|
2019-07-18 19:26:24 +02:00
|
|
|
|
|
2021-02-13 13:41:27 +01:00
|
|
|
|
#include <stdlib.h>
|
2019-07-18 19:26:24 +02:00
|
|
|
|
#include <string.h>
|
2019-07-05 09:46:48 +02:00
|
|
|
|
#include <ctype.h>
|
2020-08-08 20:34:30 +01:00
|
|
|
|
#include <errno.h>
|
2019-06-15 22:22:44 +02:00
|
|
|
|
|
|
|
|
|
|
#define LOG_MODULE "osc"
|
2019-07-03 20:21:03 +02:00
|
|
|
|
#define LOG_ENABLE_DBG 0
|
2019-06-15 22:22:44 +02:00
|
|
|
|
#include "log.h"
|
2019-07-19 11:13:07 +02:00
|
|
|
|
#include "base64.h"
|
2020-11-26 18:08:28 +01:00
|
|
|
|
#include "config.h"
|
2020-06-14 09:34:46 +02:00
|
|
|
|
#include "grid.h"
|
2021-10-20 17:05:36 +01:00
|
|
|
|
#include "macros.h"
|
2020-12-10 18:06:24 +01:00
|
|
|
|
#include "notify.h"
|
2019-07-05 10:16:56 +02:00
|
|
|
|
#include "render.h"
|
2019-07-19 11:13:07 +02:00
|
|
|
|
#include "selection.h"
|
2019-07-18 19:26:24 +02:00
|
|
|
|
#include "terminal.h"
|
2020-10-28 19:11:22 +01:00
|
|
|
|
#include "uri.h"
|
2021-02-14 13:28:42 +01:00
|
|
|
|
#include "util.h"
|
2019-07-18 19:26:24 +02:00
|
|
|
|
#include "vt.h"
|
2020-08-08 20:34:30 +01:00
|
|
|
|
#include "xmalloc.h"
|
2021-01-14 21:30:06 +00:00
|
|
|
|
#include "xsnprintf.h"
|
2019-07-18 19:26:24 +02:00
|
|
|
|
|
2019-11-29 23:59:24 +01:00
|
|
|
|
#define UNHANDLED() LOG_DBG("unhandled: OSC: %.*s", (int)term->vt.osc.idx, term->vt.osc.data)
|
2019-07-30 21:42:46 +02:00
|
|
|
|
|
2019-07-19 11:13:07 +02:00
|
|
|
|
static void
|
2019-07-19 14:20:38 +02:00
|
|
|
|
osc_to_clipboard(struct terminal *term, const char *target,
|
|
|
|
|
|
const char *base64_data)
|
2019-07-19 11:13:07 +02:00
|
|
|
|
{
|
2019-11-28 20:56:55 +01:00
|
|
|
|
bool to_clipboard = false;
|
|
|
|
|
|
bool to_primary = false;
|
|
|
|
|
|
|
|
|
|
|
|
if (target[0] == '\0')
|
|
|
|
|
|
to_clipboard = true;
|
|
|
|
|
|
|
2019-07-19 14:20:38 +02:00
|
|
|
|
for (const char *t = target; *t != '\0'; t++) {
|
|
|
|
|
|
switch (*t) {
|
2019-11-28 20:56:55 +01:00
|
|
|
|
case 'c':
|
|
|
|
|
|
to_clipboard = true;
|
2019-07-19 11:13:07 +02:00
|
|
|
|
break;
|
|
|
|
|
|
|
2019-11-19 14:05:31 +01:00
|
|
|
|
case 's':
|
2019-11-28 20:56:55 +01:00
|
|
|
|
case 'p':
|
|
|
|
|
|
to_primary = true;
|
2019-08-09 21:27:51 +02:00
|
|
|
|
break;
|
|
|
|
|
|
|
2019-07-19 14:20:38 +02:00
|
|
|
|
default:
|
|
|
|
|
|
LOG_WARN("unimplemented: clipboard target '%c'", *t);
|
2019-07-19 11:13:07 +02:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-09-09 18:46:39 +02:00
|
|
|
|
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);
|
|
|
|
|
|
|
2019-11-28 20:56:55 +01:00
|
|
|
|
if (to_clipboard) {
|
2020-08-08 20:34:30 +01:00
|
|
|
|
char *copy = xstrdup(decoded);
|
2020-07-09 11:20:46 +02:00
|
|
|
|
if (!text_to_clipboard(seat, term, copy, seat->kbd.serial))
|
2019-11-28 20:56:55 +01:00
|
|
|
|
free(copy);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (to_primary) {
|
2020-08-08 20:34:30 +01:00
|
|
|
|
char *copy = xstrdup(decoded);
|
2020-07-09 11:20:46 +02:00
|
|
|
|
if (!text_to_primary(seat, term, copy, seat->kbd.serial))
|
2019-11-28 20:56:55 +01:00
|
|
|
|
free(copy);
|
|
|
|
|
|
}
|
2020-07-08 19:28:08 +02:00
|
|
|
|
|
2019-07-19 14:20:38 +02:00
|
|
|
|
free(decoded);
|
|
|
|
|
|
}
|
2019-07-19 11:13:07 +02:00
|
|
|
|
|
2019-07-19 14:20:38 +02:00
|
|
|
|
struct clip_context {
|
2020-07-08 19:28:08 +02:00
|
|
|
|
struct seat *seat;
|
2019-07-19 14:20:38 +02:00
|
|
|
|
struct terminal *term;
|
|
|
|
|
|
uint8_t buf[3];
|
|
|
|
|
|
int idx;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2020-10-28 19:11:22 +01:00
|
|
|
|
from_clipboard_cb(char *text, size_t size, void *user)
|
2019-07-19 14:20:38 +02:00
|
|
|
|
{
|
|
|
|
|
|
struct clip_context *ctx = user;
|
|
|
|
|
|
struct terminal *term = ctx->term;
|
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(ctx->idx >= 0 && ctx->idx <= 2);
|
2019-07-19 14:20:38 +02:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(ctx->idx <= 3);
|
2019-07-19 14:20:38 +02:00
|
|
|
|
if (ctx->idx == 3) {
|
|
|
|
|
|
char *chunk = base64_encode(ctx->buf, 3);
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(chunk != NULL);
|
|
|
|
|
|
xassert(strlen(chunk) == 4);
|
2019-07-19 14:20:38 +02:00
|
|
|
|
|
2019-11-03 00:27:39 +01:00
|
|
|
|
term_to_slave(term, chunk, 4);
|
2019-07-19 14:20:38 +02:00
|
|
|
|
free(chunk);
|
|
|
|
|
|
|
|
|
|
|
|
ctx->idx = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (left == 0)
|
2019-07-19 11:13:07 +02:00
|
|
|
|
return;
|
2019-07-19 14:20:38 +02:00
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(ctx->idx == 0);
|
2019-08-03 21:30:06 +02:00
|
|
|
|
|
2019-07-19 14:20:38 +02:00
|
|
|
|
int remaining = left % 3;
|
|
|
|
|
|
for (int i = remaining; i > 0; i--)
|
2019-08-03 21:30:06 +02:00
|
|
|
|
ctx->buf[ctx->idx++] = text[size - i];
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(ctx->idx == remaining);
|
2019-07-19 14:20:38 +02:00
|
|
|
|
|
|
|
|
|
|
char *chunk = base64_encode((const uint8_t *)t, left / 3 * 3);
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(chunk != NULL);
|
|
|
|
|
|
xassert(strlen(chunk) % 4 == 0);
|
2019-11-03 00:27:39 +01:00
|
|
|
|
term_to_slave(term, chunk, strlen(chunk));
|
2019-07-19 14:20:38 +02:00
|
|
|
|
free(chunk);
|
|
|
|
|
|
}
|
2020-07-08 19:28:08 +02:00
|
|
|
|
|
2019-11-05 08:49:32 +01: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_to_slave(term, res, 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-20 12:48:37 +01:00
|
|
|
|
if (term->vt.osc.bel)
|
|
|
|
|
|
term_to_slave(term, "\a", 1);
|
|
|
|
|
|
else
|
|
|
|
|
|
term_to_slave(term, "\033\\", 2);
|
|
|
|
|
|
|
2019-11-05 08:49:32 +01:00
|
|
|
|
free(ctx);
|
|
|
|
|
|
}
|
2020-07-08 19:28:08 +02:00
|
|
|
|
|
2019-07-19 14:20:38 +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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-09-09 18:46:58 +02:00
|
|
|
|
/* 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;
|
|
|
|
|
|
|
2019-11-03 00:27:39 +01:00
|
|
|
|
term_to_slave(term, "\033]52;", 5);
|
|
|
|
|
|
term_to_slave(term, &src, 1);
|
|
|
|
|
|
term_to_slave(term, ";", 1);
|
2019-07-19 14:20:38 +02:00
|
|
|
|
|
2020-08-08 20:34:30 +01:00
|
|
|
|
struct clip_context *ctx = xmalloc(sizeof(*ctx));
|
2020-07-08 19:28:08 +02:00
|
|
|
|
*ctx = (struct clip_context) {.seat = seat, .term = term};
|
2019-07-19 14:20:38 +02:00
|
|
|
|
|
2020-09-09 18:46:58 +02:00
|
|
|
|
if (from_clipboard) {
|
2019-11-05 08:49:32 +01:00
|
|
|
|
text_from_clipboard(
|
2020-07-09 11:20:46 +02:00
|
|
|
|
seat, term, &from_clipboard_cb, &from_clipboard_done, ctx);
|
2020-09-09 18:46:58 +02:00
|
|
|
|
}
|
2019-07-19 14:20:38 +02:00
|
|
|
|
|
2020-09-09 18:46:58 +02:00
|
|
|
|
if (from_primary) {
|
2020-07-08 19:28:08 +02:00
|
|
|
|
text_from_primary(
|
|
|
|
|
|
seat, term, &from_clipboard_cb, &from_clipboard_done, ctx);
|
2019-07-19 11:13:07 +02:00
|
|
|
|
}
|
2019-07-19 14:20:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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++;
|
2019-07-19 11:13:07 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-19 14:20:38 +02:00
|
|
|
|
LOG_DBG("clipboard: target = %s data = %s", string, p);
|
|
|
|
|
|
|
2022-04-06 01:24:34 +01:00
|
|
|
|
if (p[0] == '?' && p[1] == '\0')
|
2019-07-19 14:20:38 +02:00
|
|
|
|
osc_from_clipboard(term, string);
|
|
|
|
|
|
else
|
|
|
|
|
|
osc_to_clipboard(term, string, p);
|
2019-07-19 11:13:07 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-22 19:10:15 +02:00
|
|
|
|
static void
|
|
|
|
|
|
osc_flash(struct terminal *term)
|
|
|
|
|
|
{
|
|
|
|
|
|
/* Our own private - flash */
|
2019-07-30 22:08:58 +02:00
|
|
|
|
term_flash(term, 50);
|
2019-07-22 19:10:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-28 17:29:03 +02:00
|
|
|
|
static bool
|
2021-04-05 22:05:50 +02:00
|
|
|
|
parse_legacy_color(const char *string, uint32_t *color, bool *_have_alpha,
|
|
|
|
|
|
uint16_t *_alpha)
|
2019-08-28 17:29:03 +02:00
|
|
|
|
{
|
2021-04-05 22:05:50 +02:00
|
|
|
|
bool have_alpha = false;
|
|
|
|
|
|
uint16_t alpha = 0xffff;
|
|
|
|
|
|
|
|
|
|
|
|
if (string[0] == '[') {
|
2021-04-07 08:00:39 +02:00
|
|
|
|
/* e.g. \E]11;[50]#00ff00 */
|
2021-04-05 22:05:50 +02:00
|
|
|
|
const char *start = &string[1];
|
|
|
|
|
|
|
2021-04-07 08:00:39 +02:00
|
|
|
|
errno = 0;
|
|
|
|
|
|
char *end;
|
|
|
|
|
|
unsigned long percent = strtoul(start, &end, 10);
|
|
|
|
|
|
|
|
|
|
|
|
if (errno != 0 || *end != ']')
|
2021-04-05 22:05:50 +02:00
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
have_alpha = true;
|
2021-04-07 08:00:39 +02:00
|
|
|
|
alpha = (0xffff * min(percent, 100) + 50) / 100;
|
2021-04-05 22:05:50 +02:00
|
|
|
|
|
|
|
|
|
|
string = end + 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-28 17:29:03 +02:00
|
|
|
|
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 */
|
2020-05-02 22:57:12 +02:00
|
|
|
|
uint8_t r = 256 * (rgb[0] / 65536.);
|
|
|
|
|
|
uint8_t g = 256 * (rgb[1] / 65536.);
|
|
|
|
|
|
uint8_t b = 256 * (rgb[2] / 65536.);
|
2019-08-28 17:29:03 +02:00
|
|
|
|
|
2021-04-05 22:05:50 +02:00
|
|
|
|
LOG_DBG("legacy: %02x%02x%02x (alpha=%04x)", r, g, b,
|
|
|
|
|
|
have_alpha ? alpha : 0xffff);
|
|
|
|
|
|
|
2019-08-28 17:29:03 +02:00
|
|
|
|
*color = r << 16 | g << 8 | b;
|
2021-04-05 22:05:50 +02:00
|
|
|
|
|
|
|
|
|
|
if (_have_alpha != NULL)
|
|
|
|
|
|
*_have_alpha = have_alpha;
|
|
|
|
|
|
if (_alpha != NULL)
|
|
|
|
|
|
*_alpha = alpha;
|
2019-08-28 17:29:03 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-21 17:57:02 +02:00
|
|
|
|
static bool
|
2021-04-05 22:05:50 +02:00
|
|
|
|
parse_rgb(const char *string, uint32_t *color, bool *_have_alpha,
|
|
|
|
|
|
uint16_t *_alpha)
|
2019-08-21 17:57:02 +02:00
|
|
|
|
{
|
|
|
|
|
|
size_t len = strlen(string);
|
2021-04-05 22:05:50 +02:00
|
|
|
|
bool have_alpha = len >= 4 && strncmp(string, "rgba", 4) == 0;
|
2019-08-21 17:57:02 +02:00
|
|
|
|
|
2021-04-05 22:05:50 +02:00
|
|
|
|
/* Verify we have the minimum required length (for "") */
|
|
|
|
|
|
if (have_alpha) {
|
2021-10-20 17:05:36 +01:00
|
|
|
|
if (len < STRLEN("rgba:x/x/x/x"))
|
2021-04-05 22:05:50 +02:00
|
|
|
|
return false;
|
|
|
|
|
|
} else {
|
2021-10-20 17:05:36 +01:00
|
|
|
|
if (len < STRLEN("rgb:x/x/x"))
|
2021-04-05 22:05:50 +02:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2019-08-21 17:57:02 +02:00
|
|
|
|
|
2021-04-05 22:05:50 +02:00
|
|
|
|
/* 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;
|
|
|
|
|
|
}
|
2019-08-21 17:57:02 +02:00
|
|
|
|
|
2021-04-05 22:05:50 +02:00
|
|
|
|
int rgb[4];
|
|
|
|
|
|
int digits[4];
|
2019-08-21 17:57:02 +02:00
|
|
|
|
|
2021-04-05 22:05:50 +02:00
|
|
|
|
for (size_t i = 0; i < (have_alpha ? 4 : 3); i++) {
|
2019-08-21 17:57:02 +02:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-04-05 22:05:50 +02:00
|
|
|
|
if (i >= (have_alpha ? 3 : 2))
|
2019-08-21 17:57:02 +02:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
if (len == 0 || *string != '/')
|
|
|
|
|
|
return false;
|
|
|
|
|
|
string++; len--;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Re-scale to 8-bit */
|
2020-05-02 23:07:26 +02:00
|
|
|
|
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])));
|
2019-08-21 17:57:02 +02:00
|
|
|
|
|
2021-04-05 22:31:09 +02:00
|
|
|
|
uint16_t alpha = 0xffff;
|
2021-04-05 22:05:50 +02:00
|
|
|
|
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;
|
|
|
|
|
|
|
2019-08-21 17:57:02 +02:00
|
|
|
|
*color = r << 16 | g << 8 | b;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-12-21 19:42:59 +01:00
|
|
|
|
static void
|
|
|
|
|
|
osc_set_pwd(struct terminal *term, char *string)
|
|
|
|
|
|
{
|
|
|
|
|
|
LOG_DBG("PWD: URI: %s", string);
|
|
|
|
|
|
|
2020-10-28 19:11:22 +01:00
|
|
|
|
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);
|
2020-04-03 20:15:54 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-28 19:11:22 +01:00
|
|
|
|
if (strcmp(scheme, "file") == 0 && hostname_is_localhost(host)) {
|
|
|
|
|
|
LOG_DBG("OSC7: pwd: %s", path);
|
|
|
|
|
|
free(term->cwd);
|
|
|
|
|
|
term->cwd = path;
|
|
|
|
|
|
} else
|
|
|
|
|
|
free(path);
|
2019-12-21 19:42:59 +01:00
|
|
|
|
|
2020-10-28 19:11:22 +01:00
|
|
|
|
free(scheme);
|
|
|
|
|
|
free(host);
|
2019-12-21 19:42:59 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-13 12:41:57 +01:00
|
|
|
|
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).
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2021-02-13 13:41:27 +01:00
|
|
|
|
char *params = string;
|
2021-02-13 12:41:57 +01:00
|
|
|
|
char *params_end = strchr(params, ';');
|
|
|
|
|
|
if (params_end == NULL)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
*params_end = '\0';
|
|
|
|
|
|
const char *uri = params_end + 1;
|
2021-02-13 13:41:27 +01:00
|
|
|
|
uint64_t id = (uint64_t)rand() << 32 | rand();
|
2021-02-13 12:41:57 +01:00
|
|
|
|
|
2021-02-13 13:41:27 +01:00
|
|
|
|
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)
|
2021-02-14 13:28:42 +01:00
|
|
|
|
id = sdbm_hash(value);
|
2021-02-13 13:41:27 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-14 13:28:42 +01:00
|
|
|
|
LOG_DBG("OSC-8: URL=%s, id=%" PRIu64, uri, id);
|
|
|
|
|
|
|
2021-02-13 12:41:57 +01:00
|
|
|
|
if (uri[0] == '\0')
|
|
|
|
|
|
term_osc8_close(term);
|
|
|
|
|
|
else
|
2021-02-13 13:44:07 +01:00
|
|
|
|
term_osc8_open(term, id, uri);
|
2021-02-13 12:41:57 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-12-19 07:28:10 +01:00
|
|
|
|
static void
|
|
|
|
|
|
osc_notify(struct terminal *term, char *string)
|
|
|
|
|
|
{
|
2020-12-08 19:19:55 +01:00
|
|
|
|
/*
|
|
|
|
|
|
* The 'notify' perl extension
|
|
|
|
|
|
* (https://pub.phyks.me/scripts/urxvt/notify) is very simple:
|
|
|
|
|
|
*
|
|
|
|
|
|
* #!/usr/bin/perl
|
2021-11-20 16:29:57 +01:00
|
|
|
|
*
|
2020-12-08 19:19:55 +01:00
|
|
|
|
* 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.
|
|
|
|
|
|
*/
|
2019-12-19 07:28:10 +01:00
|
|
|
|
char *ctx = NULL;
|
2020-12-08 19:19:55 +01:00
|
|
|
|
const char *title = strtok_r(string, ";", &ctx);
|
|
|
|
|
|
const char *msg = strtok_r(NULL, "\x00", &ctx);
|
|
|
|
|
|
|
2021-06-02 17:50:29 +02:00
|
|
|
|
notify_notify(term, title, msg != NULL ? msg : "");
|
2019-12-19 07:28:10 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-07 16:32:18 +02:00
|
|
|
|
void
|
2019-06-15 22:22:44 +02:00
|
|
|
|
osc_dispatch(struct terminal *term)
|
|
|
|
|
|
{
|
2019-07-18 19:26:24 +02:00
|
|
|
|
unsigned param = 0;
|
2019-07-05 09:46:48 +02:00
|
|
|
|
int data_ofs = 0;
|
|
|
|
|
|
|
2019-08-21 17:57:02 +02:00
|
|
|
|
for (size_t i = 0; i < term->vt.osc.idx; i++, data_ofs++) {
|
2019-07-18 19:26:24 +02:00
|
|
|
|
char c = term->vt.osc.data[i];
|
2019-07-05 09:46:48 +02:00
|
|
|
|
|
|
|
|
|
|
if (c == ';') {
|
2019-08-21 17:57:02 +02:00
|
|
|
|
data_ofs++;
|
2019-07-05 09:46:48 +02:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!isdigit(c)) {
|
2019-07-30 21:42:46 +02:00
|
|
|
|
UNHANDLED();
|
|
|
|
|
|
return;
|
2019-07-05 09:46:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-05 19:04:09 +02:00
|
|
|
|
param *= 10;
|
2019-07-05 09:46:48 +02:00
|
|
|
|
param += c - '0';
|
|
|
|
|
|
}
|
2019-07-30 21:42:46 +02:00
|
|
|
|
|
2021-07-24 01:58:34 +01:00
|
|
|
|
LOG_DBG("OSC: %.*s (param = %d)",
|
2019-07-05 09:46:48 +02:00
|
|
|
|
(int)term->vt.osc.idx, term->vt.osc.data, param);
|
|
|
|
|
|
|
2019-07-19 14:20:38 +02:00
|
|
|
|
char *string = (char *)&term->vt.osc.data[data_ofs];
|
2019-07-05 09:46:48 +02:00
|
|
|
|
|
|
|
|
|
|
switch (param) {
|
2019-07-21 17:35:53 +02:00
|
|
|
|
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 */
|
2019-07-23 17:55:25 +02:00
|
|
|
|
|
2019-08-21 17:57:02 +02:00
|
|
|
|
case 4: {
|
|
|
|
|
|
/* Set color<idx> */
|
|
|
|
|
|
|
2020-04-04 14:27:44 +02:00
|
|
|
|
string--;
|
2020-06-09 17:33:26 +02:00
|
|
|
|
if (*string != ';')
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
|
xassert(*string == ';');
|
2019-08-21 17:57:02 +02:00
|
|
|
|
|
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';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-04-05 21:06:48 +02:00
|
|
|
|
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 */
|
2022-04-06 01:24:34 +01:00
|
|
|
|
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;
|
2021-10-20 12:48:37 +01:00
|
|
|
|
const char *terminator = term->vt.osc.bel ? "\a" : "\033\\";
|
2020-04-04 14:27:44 +02:00
|
|
|
|
|
|
|
|
|
|
char reply[32];
|
2021-10-20 12:48:37 +01:00
|
|
|
|
size_t n = xsnprintf(
|
2022-03-13 09:05:55 +01:00
|
|
|
|
reply, sizeof(reply),
|
|
|
|
|
|
"\033]4;%u;rgb:%02hhx%02hhx/%02hhx%02hhx/%02hhx%02hhx%s",
|
|
|
|
|
|
idx, r, r, g, g, b, b, terminator);
|
2021-01-14 21:30:06 +00:00
|
|
|
|
term_to_slave(term, reply, n);
|
2020-04-04 14:27:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
uint32_t color;
|
2021-04-05 22:05:50 +02:00
|
|
|
|
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;
|
|
|
|
|
|
|
2020-06-11 17:13:32 +02:00
|
|
|
|
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;
|
2022-02-10 18:27:20 +01:00
|
|
|
|
|
|
|
|
|
|
/* Dirty visible, affected cells */
|
|
|
|
|
|
for (int r = 0; r < term->rows; r++) {
|
|
|
|
|
|
struct row *row = grid_row_in_view(term->grid, r);
|
|
|
|
|
|
struct cell *cell = &row->cells[0];
|
|
|
|
|
|
|
|
|
|
|
|
for (int c = 0; c < term->cols; c++, cell++) {
|
|
|
|
|
|
bool dirty = false;
|
|
|
|
|
|
|
|
|
|
|
|
switch (cell->attrs.fg_src) {
|
|
|
|
|
|
case COLOR_BASE16:
|
|
|
|
|
|
case COLOR_BASE256:
|
|
|
|
|
|
if (cell->attrs.fg == idx)
|
|
|
|
|
|
dirty = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case COLOR_DEFAULT:
|
|
|
|
|
|
case COLOR_RGB:
|
|
|
|
|
|
/* Not affected */
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch (cell->attrs.bg_src) {
|
|
|
|
|
|
case COLOR_BASE16:
|
|
|
|
|
|
case COLOR_BASE256:
|
|
|
|
|
|
if (cell->attrs.bg == idx)
|
|
|
|
|
|
dirty = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case COLOR_DEFAULT:
|
|
|
|
|
|
case COLOR_RGB:
|
|
|
|
|
|
/* Not affected */
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (dirty) {
|
|
|
|
|
|
cell->attrs.clean = 0;
|
|
|
|
|
|
row->dirty = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2020-04-04 14:27:44 +02:00
|
|
|
|
}
|
2019-08-21 17:57:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-12-21 19:42:59 +01:00
|
|
|
|
case 7:
|
|
|
|
|
|
/* Update terminal's understanding of PWD */
|
|
|
|
|
|
osc_set_pwd(term, string);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2021-02-13 12:41:57 +01:00
|
|
|
|
case 8:
|
|
|
|
|
|
osc_uri(term, string);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2021-06-02 17:50:29 +02:00
|
|
|
|
case 9:
|
|
|
|
|
|
/* iTerm2 Growl notifications */
|
|
|
|
|
|
osc_notify(term, string);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2019-08-21 17:57:02 +02:00
|
|
|
|
case 10:
|
2021-04-07 08:09:40 +02:00
|
|
|
|
case 11:
|
|
|
|
|
|
case 17:
|
|
|
|
|
|
case 19: {
|
|
|
|
|
|
/* Set default foreground/background/highlight-bg/highlight-fg color */
|
2019-08-21 17:57:02 +02:00
|
|
|
|
|
|
|
|
|
|
/* Client queried for current value */
|
2022-04-06 01:24:34 +01:00
|
|
|
|
if (string[0] == '?' && string[1] == '\0') {
|
2019-08-21 17:57:02 +02:00
|
|
|
|
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;
|
2021-10-20 12:48:37 +01:00
|
|
|
|
const char *terminator = term->vt.osc.bel ? "\a" : "\033\\";
|
2019-08-21 17:57:02 +02:00
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* Reply in XParseColor format
|
2020-08-02 23:54:04 +01:00
|
|
|
|
* E.g. for color 0xdcdccc we reply "\033]10;rgb:dc/dc/cc\033\\"
|
2019-08-21 17:57:02 +02:00
|
|
|
|
*/
|
|
|
|
|
|
char reply[32];
|
2021-01-14 21:30:06 +00:00
|
|
|
|
size_t n = xsnprintf(
|
2022-03-13 09:05:55 +01:00
|
|
|
|
reply, sizeof(reply),
|
|
|
|
|
|
"\033]%u;rgb:%02hhx%02hhx/%02hhx%02hhx/%02hhx%02hhx%s",
|
|
|
|
|
|
param, r, r, g, g, b, b, terminator);
|
2019-08-21 17:57:02 +02:00
|
|
|
|
|
2021-01-14 21:30:06 +00:00
|
|
|
|
term_to_slave(term, reply, n);
|
2019-08-21 17:57:02 +02:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t color;
|
2021-04-05 22:05:50 +02:00
|
|
|
|
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))
|
|
|
|
|
|
{
|
2019-08-21 17:57:02 +02:00
|
|
|
|
break;
|
2021-04-05 22:05:50 +02:00
|
|
|
|
}
|
2019-08-21 17:57:02 +02:00
|
|
|
|
|
2019-08-21 18:54:12 +02:00
|
|
|
|
LOG_DBG("change color definition for %s to %06x",
|
2021-04-07 08:09:40 +02:00
|
|
|
|
param == 10 ? "foreground" :
|
|
|
|
|
|
param == 11 ? "background" :
|
|
|
|
|
|
param == 17 ? "selection background" :
|
|
|
|
|
|
"selection foreground",
|
|
|
|
|
|
color);
|
2019-08-21 18:54:12 +02:00
|
|
|
|
|
2019-08-21 17:57:02 +02:00
|
|
|
|
switch (param) {
|
2021-04-05 22:05:50 +02:00
|
|
|
|
case 10:
|
|
|
|
|
|
term->colors.fg = color;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 11:
|
|
|
|
|
|
term->colors.bg = color;
|
osc: update font subpixel mode, and window opaque compositor hint, on alpha changes
When background alpha is changed at runtime (using OSC-11), we (may)
have to update the opaque hint we send to the compositor.
We must also update the subpixel mode used when rendering font
glyphs.
Why?
When the window is fully opaque, we use wl_surface_set_opaque_region()
on the entire surface, to hint to the compositor that it doesn’t have
to blend the window content with whatever is behind the
window. Obviously, if alpha is changed from opaque, to transparent (or
semi-transparent), that hint must be removed.
Sub-pixel mode is harder to explain, but in short, we can’t do
subpixel hinting with a (semi-)transparent background. Thus, similar
to the opaque hint, subpixel antialiasing must be enabled/disabled
when background alpha is changed.
2023-05-25 18:39:32 +02:00
|
|
|
|
if (have_alpha) {
|
|
|
|
|
|
const bool changed = term->colors.alpha != alpha;
|
2021-04-05 22:05:50 +02:00
|
|
|
|
term->colors.alpha = alpha;
|
osc: update font subpixel mode, and window opaque compositor hint, on alpha changes
When background alpha is changed at runtime (using OSC-11), we (may)
have to update the opaque hint we send to the compositor.
We must also update the subpixel mode used when rendering font
glyphs.
Why?
When the window is fully opaque, we use wl_surface_set_opaque_region()
on the entire surface, to hint to the compositor that it doesn’t have
to blend the window content with whatever is behind the
window. Obviously, if alpha is changed from opaque, to transparent (or
semi-transparent), that hint must be removed.
Sub-pixel mode is harder to explain, but in short, we can’t do
subpixel hinting with a (semi-)transparent background. Thus, similar
to the opaque hint, subpixel antialiasing must be enabled/disabled
when background alpha is changed.
2023-05-25 18:39:32 +02:00
|
|
|
|
|
|
|
|
|
|
if (changed) {
|
|
|
|
|
|
wayl_win_alpha_changed(term->window);
|
|
|
|
|
|
term_font_subpixel_changed(term);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-04-05 22:05:50 +02:00
|
|
|
|
break;
|
2021-04-07 08:09:40 +02:00
|
|
|
|
|
|
|
|
|
|
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;
|
2019-08-21 17:57:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-08-18 07:20:57 +02:00
|
|
|
|
term_damage_view(term);
|
2020-09-29 10:05:52 +02:00
|
|
|
|
term_damage_margins(term);
|
2019-08-21 17:57:02 +02:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case 12: /* Set cursor color */
|
2020-01-20 18:36:19 +01:00
|
|
|
|
|
|
|
|
|
|
/* Client queried for current value */
|
2022-04-06 01:24:34 +01:00
|
|
|
|
if (string[0] == '?' && string[1] == '\0') {
|
2020-05-02 22:58:30 +02:00
|
|
|
|
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;
|
2021-10-20 12:48:37 +01:00
|
|
|
|
const char *terminator = term->vt.osc.bel ? "\a" : "\033\\";
|
2020-01-20 18:36:19 +01:00
|
|
|
|
|
|
|
|
|
|
char reply[32];
|
2021-10-20 12:48:37 +01:00
|
|
|
|
size_t n = xsnprintf(
|
|
|
|
|
|
reply, sizeof(reply), "\033]12;rgb:%02x/%02x/%02x%s",
|
|
|
|
|
|
r, g, b, terminator);
|
|
|
|
|
|
|
2021-01-14 21:30:06 +00:00
|
|
|
|
term_to_slave(term, reply, n);
|
2020-01-20 18:36:19 +01:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t color;
|
2021-04-05 22:05:50 +02:00
|
|
|
|
|
|
|
|
|
|
if (string[0] == '#' || string[0] == '['
|
|
|
|
|
|
? !parse_legacy_color(string, &color, NULL, NULL)
|
|
|
|
|
|
: !parse_rgb(string, &color, NULL, NULL))
|
|
|
|
|
|
{
|
2020-01-20 18:36:19 +01:00
|
|
|
|
break;
|
2021-04-05 22:05:50 +02:00
|
|
|
|
}
|
2020-01-20 18:36:19 +01:00
|
|
|
|
|
2020-10-20 21:03:51 +02:00
|
|
|
|
LOG_DBG("change cursor color to %06x", color);
|
2020-01-20 18:36:19 +01:00
|
|
|
|
|
2020-05-02 22:58:50 +02:00
|
|
|
|
if (color == 0)
|
|
|
|
|
|
term->cursor_color.cursor = 0; /* Invert fg/bg */
|
|
|
|
|
|
else
|
|
|
|
|
|
term->cursor_color.cursor = 1u << 31 | color;
|
2020-09-29 10:05:52 +02:00
|
|
|
|
|
|
|
|
|
|
term_damage_cursor(term);
|
2019-08-21 17:57:02 +02:00
|
|
|
|
break;
|
|
|
|
|
|
|
2022-01-01 13:58:42 +01:00
|
|
|
|
case 22: /* Set mouse cursor */
|
|
|
|
|
|
term_set_user_mouse_cursor(term, string);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2019-07-23 17:55:25 +02:00
|
|
|
|
case 30: /* Set tab title */
|
|
|
|
|
|
break;
|
2019-07-30 21:42:46 +02:00
|
|
|
|
|
2019-07-23 17:55:25 +02:00
|
|
|
|
case 52: /* Copy to/from clipboard/primary */
|
|
|
|
|
|
osc_selection(term, string);
|
|
|
|
|
|
break;
|
2019-07-05 09:46:48 +02:00
|
|
|
|
|
2019-08-21 17:57:02 +02:00
|
|
|
|
case 104: {
|
|
|
|
|
|
/* Reset Color Number 'c' (whole table if no parameter) */
|
|
|
|
|
|
|
2022-04-06 01:24:34 +01:00
|
|
|
|
if (string[0] == '\0') {
|
2019-08-21 18:54:12 +02:00
|
|
|
|
LOG_DBG("resetting all colors");
|
2021-12-25 17:13:50 +01:00
|
|
|
|
for (size_t i = 0; i < ALEN(term->colors.table); i++)
|
2021-04-07 08:07:43 +02:00
|
|
|
|
term->colors.table[i] = term->conf->colors.table[i];
|
2020-04-04 14:30:50 +02:00
|
|
|
|
}
|
2019-08-21 17:57:02 +02:00
|
|
|
|
|
2020-04-04 14:30:50 +02:00
|
|
|
|
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';
|
2019-08-21 17:57:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-04-05 21:06:48 +02:00
|
|
|
|
if (idx >= ALEN(term->colors.table)) {
|
|
|
|
|
|
LOG_WARN("invalid OSC 104 color index: %u", idx);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-04-04 14:30:50 +02:00
|
|
|
|
LOG_DBG("resetting color #%u", idx);
|
2021-04-07 08:07:43 +02:00
|
|
|
|
term->colors.table[idx] = term->conf->colors.table[idx];
|
2019-08-21 17:57:02 +02:00
|
|
|
|
}
|
2021-12-25 17:13:50 +01:00
|
|
|
|
|
2019-08-21 17:57:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-12-25 17:13:50 +01:00
|
|
|
|
term_damage_view(term);
|
2019-08-21 17:57:02 +02:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-05 19:04:34 +02:00
|
|
|
|
case 105: /* Reset Special Color Number 'c' */
|
2019-08-21 17:57:02 +02:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 110: /* Reset default text foreground color */
|
2021-04-07 08:07:43 +02:00
|
|
|
|
LOG_DBG("resetting foreground color");
|
|
|
|
|
|
term->colors.fg = term->conf->colors.fg;
|
2020-08-18 07:20:57 +02:00
|
|
|
|
term_damage_view(term);
|
2019-08-21 17:57:02 +02:00
|
|
|
|
break;
|
|
|
|
|
|
|
2019-08-21 18:54:12 +02:00
|
|
|
|
case 111: /* Reset default text background color */
|
2021-04-07 08:07:43 +02:00
|
|
|
|
LOG_DBG("resetting background color");
|
|
|
|
|
|
term->colors.bg = term->conf->colors.bg;
|
2021-04-07 19:04:25 +02:00
|
|
|
|
term->colors.alpha = term->conf->colors.alpha;
|
2020-08-18 07:20:57 +02:00
|
|
|
|
term_damage_view(term);
|
2020-09-29 10:05:52 +02:00
|
|
|
|
term_damage_margins(term);
|
2019-07-05 19:04:34 +02:00
|
|
|
|
break;
|
|
|
|
|
|
|
2020-01-20 18:36:19 +01:00
|
|
|
|
case 112:
|
|
|
|
|
|
LOG_DBG("resetting cursor color");
|
2020-11-26 18:08:28 +01:00
|
|
|
|
term->cursor_color.text = term->conf->cursor.color.text;
|
|
|
|
|
|
term->cursor_color.cursor = term->conf->cursor.color.cursor;
|
2020-09-29 10:05:52 +02:00
|
|
|
|
term_damage_cursor(term);
|
2020-01-20 18:36:19 +01:00
|
|
|
|
break;
|
|
|
|
|
|
|
2021-04-07 08:09:40 +02:00
|
|
|
|
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->prompt_marker = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 'B':
|
|
|
|
|
|
LOG_DBG("FTCS_COMMAND_START");
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 'C':
|
|
|
|
|
|
LOG_DBG("FTCS_COMMAND_EXECUTED");
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 'D':
|
|
|
|
|
|
LOG_DBG("FTCS_COMMAND_FINISHED");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2019-07-22 19:10:15 +02:00
|
|
|
|
case 555:
|
|
|
|
|
|
osc_flash(term);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2020-12-08 19:19:55 +01:00
|
|
|
|
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();
|
2019-12-19 07:28:10 +01:00
|
|
|
|
break;
|
2020-12-08 19:19:55 +01:00
|
|
|
|
}
|
2019-12-19 07:28:10 +01:00
|
|
|
|
|
2019-07-05 09:46:48 +02:00
|
|
|
|
default:
|
2019-07-30 21:42:46 +02:00
|
|
|
|
UNHANDLED();
|
2019-07-07 16:32:18 +02:00
|
|
|
|
break;
|
2019-07-05 09:46:48 +02:00
|
|
|
|
}
|
2019-06-15 22:22:44 +02:00
|
|
|
|
}
|
2019-07-19 08:59:35 +02:00
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
|
osc_ensure_size(struct terminal *term, size_t required_size)
|
|
|
|
|
|
{
|
2022-03-02 01:55:25 +00:00
|
|
|
|
if (likely(required_size <= term->vt.osc.size))
|
2019-07-19 08:59:35 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
|
2022-03-02 01:55:25 +00:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-02 01:55:25 +00:00
|
|
|
|
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;
|
|
|
|
|
|
}
|