2019-06-15 22:22:44 +02:00
|
|
|
#include "osc.h"
|
2019-07-18 19:26:24 +02:00
|
|
|
|
|
|
|
|
#include <string.h>
|
2019-07-05 09:46:48 +02:00
|
|
|
#include <ctype.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"
|
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"
|
|
|
|
|
#include "vt.h"
|
|
|
|
|
|
2019-07-30 21:42:46 +02:00
|
|
|
#define UNHANDLED() LOG_ERR("unhandled: OSC: %.*s", (int)term->vt.osc.idx, term->vt.osc.data)
|
|
|
|
|
|
2019-07-18 19:26:24 +02:00
|
|
|
static void
|
|
|
|
|
osc_query(struct terminal *term, unsigned param)
|
|
|
|
|
{
|
|
|
|
|
switch (param) {
|
|
|
|
|
case 10:
|
|
|
|
|
case 11: {
|
2019-07-21 10:58:09 +02:00
|
|
|
uint32_t color = param == 10 ? term->colors.fg : term->colors.bg;
|
2019-07-19 10:18:22 +02:00
|
|
|
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 "\e]10;rgb:dc/dc/cc\e\\"
|
|
|
|
|
*/
|
|
|
|
|
char reply[32];
|
2019-07-18 19:26:24 +02:00
|
|
|
snprintf(
|
2019-07-19 10:18:22 +02:00
|
|
|
reply, sizeof(reply), "\033]%u;rgb:%02x/%02x/%02x\033\\",
|
|
|
|
|
param, r, g, b);
|
|
|
|
|
|
2019-07-18 19:26:24 +02:00
|
|
|
vt_to_slave(term, reply, strlen(reply));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
default:
|
2019-07-30 21:42:46 +02:00
|
|
|
UNHANDLED();
|
2019-07-18 19:26:24 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-05 09:46:48 +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-07-19 14:20:38 +02:00
|
|
|
char *decoded = base64_decode(base64_data);
|
|
|
|
|
LOG_DBG("decoded: %s", decoded);
|
2019-07-19 11:13:07 +02:00
|
|
|
|
2019-07-19 14:20:38 +02:00
|
|
|
if (decoded == NULL) {
|
|
|
|
|
LOG_WARN("OSC: invalid clipboard data: %s", base64_data);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-07-19 11:13:07 +02:00
|
|
|
|
2019-07-19 14:20:38 +02:00
|
|
|
for (const char *t = target; *t != '\0'; t++) {
|
|
|
|
|
switch (*t) {
|
|
|
|
|
case 'c': {
|
|
|
|
|
char *copy = strdup(decoded);
|
|
|
|
|
if (!text_to_clipboard(term, copy, term->input_serial))
|
|
|
|
|
free(copy);
|
2019-07-19 11:13:07 +02:00
|
|
|
break;
|
2019-07-19 14:20:38 +02:00
|
|
|
}
|
2019-07-19 11:13:07 +02:00
|
|
|
|
2019-08-09 21:27:51 +02:00
|
|
|
case 'p': {
|
|
|
|
|
char *copy = strdup(decoded);
|
|
|
|
|
if (!text_to_primary(term, copy, term->input_serial))
|
|
|
|
|
free(copy);
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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 {
|
|
|
|
|
struct terminal *term;
|
|
|
|
|
uint8_t buf[3];
|
|
|
|
|
int idx;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
from_clipboard_cb(const char *text, size_t size, void *user)
|
|
|
|
|
{
|
|
|
|
|
struct clip_context *ctx = user;
|
|
|
|
|
struct terminal *term = ctx->term;
|
|
|
|
|
|
|
|
|
|
assert(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;
|
|
|
|
|
|
|
|
|
|
assert(ctx->idx <= 3);
|
|
|
|
|
if (ctx->idx == 3) {
|
|
|
|
|
char *chunk = base64_encode(ctx->buf, 3);
|
|
|
|
|
assert(chunk != NULL);
|
|
|
|
|
assert(strlen(chunk) == 4);
|
|
|
|
|
|
|
|
|
|
vt_to_slave(term, chunk, 4);
|
|
|
|
|
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
|
|
|
|
2019-08-03 21:30:06 +02:00
|
|
|
assert(ctx->idx == 0);
|
|
|
|
|
|
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];
|
|
|
|
|
assert(ctx->idx == remaining);
|
2019-07-19 14:20:38 +02:00
|
|
|
|
|
|
|
|
char *chunk = base64_encode((const uint8_t *)t, left / 3 * 3);
|
|
|
|
|
assert(chunk != NULL);
|
|
|
|
|
assert(strlen(chunk) % 4 == 0);
|
|
|
|
|
vt_to_slave(term, chunk, strlen(chunk));
|
|
|
|
|
free(chunk);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
osc_from_clipboard(struct terminal *term, const char *source)
|
|
|
|
|
{
|
|
|
|
|
char src = 0;
|
|
|
|
|
|
|
|
|
|
for (const char *s = source; *s != '\0'; s++) {
|
|
|
|
|
if (*s == 'c') {
|
|
|
|
|
src = 'c';
|
|
|
|
|
break;
|
|
|
|
|
} else if (*s == 'p') {
|
|
|
|
|
src = 'p';
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-07-19 11:13:07 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-19 14:20:38 +02:00
|
|
|
if (src == 0)
|
|
|
|
|
return;
|
2019-07-19 11:13:07 +02:00
|
|
|
|
2019-07-19 14:20:38 +02:00
|
|
|
vt_to_slave(term, "\033]52;", 5);
|
|
|
|
|
vt_to_slave(term, &src, 1);
|
|
|
|
|
vt_to_slave(term, ";", 1);
|
|
|
|
|
|
|
|
|
|
struct clip_context ctx = {
|
|
|
|
|
.term = term,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
switch (src) {
|
|
|
|
|
case 'c':
|
|
|
|
|
text_from_clipboard(term, term->input_serial, &from_clipboard_cb, &ctx);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'p':
|
2019-08-09 21:27:51 +02:00
|
|
|
text_from_primary(term, &from_clipboard_cb, &ctx);
|
2019-07-19 14:20:38 +02:00
|
|
|
break;
|
2019-07-19 11:13:07 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-19 14:20:38 +02:00
|
|
|
if (ctx.idx > 0) {
|
|
|
|
|
char res[4];
|
|
|
|
|
base64_encode_final(ctx.buf, ctx.idx, res);
|
|
|
|
|
vt_to_slave(term, res, 4);
|
2019-07-19 11:13:07 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-19 14:20:38 +02:00
|
|
|
vt_to_slave(term, "\033\\", 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
if (strlen(p) == 1 && p[0] == '?')
|
|
|
|
|
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-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;
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < term->vt.osc.idx; i++) {
|
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 == ';') {
|
|
|
|
|
data_ofs = i + 1;
|
|
|
|
|
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
|
|
|
|
2019-07-05 09:46:48 +02:00
|
|
|
LOG_DBG("OCS: %.*s (param = %d)",
|
|
|
|
|
(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
|
|
|
|
2019-07-18 19:26:24 +02:00
|
|
|
if (strlen(string) == 1 && string[0] == '?') {
|
|
|
|
|
osc_query(term, param);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
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-07-05 19:04:34 +02:00
|
|
|
case 104: /* Reset Color Number 'c' */
|
|
|
|
|
case 105: /* Reset Special Color Number 'c' */
|
2019-07-16 10:20:20 +02:00
|
|
|
case 112: /* Reset text cursor color */
|
2019-07-05 19:04:34 +02:00
|
|
|
break;
|
|
|
|
|
|
2019-07-22 19:10:15 +02:00
|
|
|
case 555:
|
|
|
|
|
osc_flash(term);
|
|
|
|
|
break;
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
if (required_size <= term->vt.osc.size)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
size_t new_size = (required_size + 127) / 128 * 128;
|
|
|
|
|
assert(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;
|
|
|
|
|
}
|