You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

768 lines
20 KiB

  1. #include "osc.h"
  2. #include <string.h>
  3. #include <ctype.h>
  4. #include <errno.h>
  5. #include <limits.h>
  6. #include <unistd.h>
  7. #define LOG_MODULE "osc"
  8. #define LOG_ENABLE_DBG 0
  9. #include "log.h"
  10. #include "base64.h"
  11. #include "grid.h"
  12. #include "render.h"
  13. #include "selection.h"
  14. #include "terminal.h"
  15. #include "vt.h"
  16. #include "xmalloc.h"
  17. #define UNHANDLED() LOG_DBG("unhandled: OSC: %.*s", (int)term->vt.osc.idx, term->vt.osc.data)
  18. static void
  19. osc_to_clipboard(struct terminal *term, const char *target,
  20. const char *base64_data)
  21. {
  22. bool to_clipboard = false;
  23. bool to_primary = false;
  24. if (target[0] == '\0')
  25. to_clipboard = true;
  26. for (const char *t = target; *t != '\0'; t++) {
  27. switch (*t) {
  28. case 'c':
  29. to_clipboard = true;
  30. break;
  31. case 's':
  32. case 'p':
  33. to_primary = true;
  34. break;
  35. default:
  36. LOG_WARN("unimplemented: clipboard target '%c'", *t);
  37. break;
  38. }
  39. }
  40. /* Find a seat in which the terminal has focus */
  41. struct seat *seat = NULL;
  42. tll_foreach(term->wl->seats, it) {
  43. if (it->item.kbd_focus == term) {
  44. seat = &it->item;
  45. break;
  46. }
  47. }
  48. if (seat == NULL) {
  49. LOG_WARN("OSC52: client tried to write to clipboard data while window was unfocused");
  50. return;
  51. }
  52. char *decoded = base64_decode(base64_data);
  53. if (decoded == NULL) {
  54. if (errno == EINVAL)
  55. LOG_WARN("OSC: invalid clipboard data: %s", base64_data);
  56. else
  57. LOG_ERRNO("base64_decode() failed");
  58. if (to_clipboard)
  59. selection_clipboard_unset(seat);
  60. if (to_primary)
  61. selection_primary_unset(seat);
  62. return;
  63. }
  64. LOG_DBG("decoded: %s", decoded);
  65. if (to_clipboard) {
  66. char *copy = xstrdup(decoded);
  67. if (!text_to_clipboard(seat, term, copy, seat->kbd.serial))
  68. free(copy);
  69. }
  70. if (to_primary) {
  71. char *copy = xstrdup(decoded);
  72. if (!text_to_primary(seat, term, copy, seat->kbd.serial))
  73. free(copy);
  74. }
  75. free(decoded);
  76. }
  77. struct clip_context {
  78. struct seat *seat;
  79. struct terminal *term;
  80. uint8_t buf[3];
  81. int idx;
  82. };
  83. static void
  84. from_clipboard_cb(const char *text, size_t size, void *user)
  85. {
  86. struct clip_context *ctx = user;
  87. struct terminal *term = ctx->term;
  88. assert(ctx->idx >= 0 && ctx->idx <= 2);
  89. const char *t = text;
  90. size_t left = size;
  91. if (ctx->idx > 0) {
  92. for (size_t i = ctx->idx; i < 3 && left > 0; i++, t++, left--)
  93. ctx->buf[ctx->idx++] = *t;
  94. assert(ctx->idx <= 3);
  95. if (ctx->idx == 3) {
  96. char *chunk = base64_encode(ctx->buf, 3);
  97. assert(chunk != NULL);
  98. assert(strlen(chunk) == 4);
  99. term_to_slave(term, chunk, 4);
  100. free(chunk);
  101. ctx->idx = 0;
  102. }
  103. }
  104. if (left == 0)
  105. return;
  106. assert(ctx->idx == 0);
  107. int remaining = left % 3;
  108. for (int i = remaining; i > 0; i--)
  109. ctx->buf[ctx->idx++] = text[size - i];
  110. assert(ctx->idx == remaining);
  111. char *chunk = base64_encode((const uint8_t *)t, left / 3 * 3);
  112. assert(chunk != NULL);
  113. assert(strlen(chunk) % 4 == 0);
  114. term_to_slave(term, chunk, strlen(chunk));
  115. free(chunk);
  116. }
  117. static void
  118. from_clipboard_done(void *user)
  119. {
  120. struct clip_context *ctx = user;
  121. struct terminal *term = ctx->term;
  122. if (ctx->idx > 0) {
  123. char res[4];
  124. base64_encode_final(ctx->buf, ctx->idx, res);
  125. term_to_slave(term, res, 4);
  126. }
  127. term_to_slave(term, "\033\\", 2);
  128. free(ctx);
  129. }
  130. static void
  131. osc_from_clipboard(struct terminal *term, const char *source)
  132. {
  133. /* Find a seat in which the terminal has focus */
  134. struct seat *seat = NULL;
  135. tll_foreach(term->wl->seats, it) {
  136. if (it->item.kbd_focus == term) {
  137. seat = &it->item;
  138. break;
  139. }
  140. }
  141. if (seat == NULL) {
  142. LOG_WARN("OSC52: client tried to read clipboard data while window was unfocused");
  143. return;
  144. }
  145. /* Use clipboard if no source has been specified */
  146. char src = source[0] == '\0' ? 'c' : 0;
  147. bool from_clipboard = src == 'c';
  148. bool from_primary = false;
  149. for (const char *s = source;
  150. *s != '\0' && !from_clipboard && !from_primary;
  151. s++)
  152. {
  153. if (*s == 'c' || *s == 'p' || *s == 's') {
  154. src = *s;
  155. switch (src) {
  156. case 'c':
  157. from_clipboard = selection_clipboard_has_data(seat);
  158. break;
  159. case 's':
  160. case 'p':
  161. from_primary = selection_primary_has_data(seat);
  162. break;
  163. }
  164. } else
  165. LOG_WARN("unimplemented: clipboard source '%c'", *s);
  166. }
  167. if (!from_clipboard && !from_primary)
  168. return;
  169. term_to_slave(term, "\033]52;", 5);
  170. term_to_slave(term, &src, 1);
  171. term_to_slave(term, ";", 1);
  172. struct clip_context *ctx = xmalloc(sizeof(*ctx));
  173. *ctx = (struct clip_context) {.seat = seat, .term = term};
  174. if (from_clipboard) {
  175. text_from_clipboard(
  176. seat, term, &from_clipboard_cb, &from_clipboard_done, ctx);
  177. }
  178. if (from_primary) {
  179. text_from_primary(
  180. seat, term, &from_clipboard_cb, &from_clipboard_done, ctx);
  181. }
  182. }
  183. static void
  184. osc_selection(struct terminal *term, char *string)
  185. {
  186. char *p = string;
  187. bool clipboard_done = false;
  188. /* The first parameter is a string of clipbard sources/targets */
  189. while (*p != '\0' && !clipboard_done) {
  190. switch (*p) {
  191. case ';':
  192. clipboard_done = true;
  193. *p = '\0';
  194. break;
  195. }
  196. p++;
  197. }
  198. LOG_DBG("clipboard: target = %s data = %s", string, p);
  199. if (strlen(p) == 1 && p[0] == '?')
  200. osc_from_clipboard(term, string);
  201. else
  202. osc_to_clipboard(term, string, p);
  203. }
  204. static void
  205. osc_flash(struct terminal *term)
  206. {
  207. /* Our own private - flash */
  208. term_flash(term, 50);
  209. }
  210. static bool
  211. parse_legacy_color(const char *string, uint32_t *color)
  212. {
  213. if (string[0] != '#')
  214. return false;
  215. string++;
  216. const size_t len = strlen(string);
  217. if (len % 3 != 0)
  218. return false;
  219. const int digits = len / 3;
  220. int rgb[3];
  221. for (size_t i = 0; i < 3; i++) {
  222. rgb[i] = 0;
  223. for (size_t j = 0; j < digits; j++) {
  224. size_t idx = i * digits + j;
  225. char c = string[idx];
  226. rgb[i] <<= 4;
  227. if (!isxdigit(c))
  228. rgb[i] |= 0;
  229. else
  230. rgb[i] |= c >= '0' && c <= '9' ? c - '0' :
  231. c >= 'a' && c <= 'f' ? c - 'a' + 10 : c - 'A' + 10;
  232. }
  233. /* Values with less than 16 bits represent the *most
  234. * significant bits*. I.e. the values are *not* scaled */
  235. rgb[i] <<= 16 - (4 * digits);
  236. }
  237. /* Re-scale to 8-bit */
  238. uint8_t r = 256 * (rgb[0] / 65536.);
  239. uint8_t g = 256 * (rgb[1] / 65536.);
  240. uint8_t b = 256 * (rgb[2] / 65536.);
  241. LOG_DBG("legacy: %02x%02x%02x", r, g, b);
  242. *color = r << 16 | g << 8 | b;
  243. return true;
  244. }
  245. static bool
  246. parse_rgb(const char *string, uint32_t *color)
  247. {
  248. size_t len = strlen(string);
  249. /* Verify we have the minimum required length (for "rgb:x/x/x") */
  250. if (len < 3 /* 'rgb' */ + 1 /* ':' */ + 2 /* '/' */ + 3 * 1 /* 3 * 'x' */)
  251. return false;
  252. /* Verify prefix is "rgb:" */
  253. if (string[0] != 'r' || string[1] != 'g' || string[2] != 'b' || string[3] != ':')
  254. return false;
  255. string += 4;
  256. len -= 4;
  257. int rgb[3];
  258. int digits[3];
  259. for (size_t i = 0; i < 3; i++) {
  260. for (rgb[i] = 0, digits[i] = 0;
  261. len > 0 && *string != '/';
  262. len--, string++, digits[i]++)
  263. {
  264. char c = *string;
  265. rgb[i] <<= 4;
  266. if (!isxdigit(c))
  267. rgb[i] |= 0;
  268. else
  269. rgb[i] |= c >= '0' && c <= '9' ? c - '0' :
  270. c >= 'a' && c <= 'f' ? c - 'a' + 10 : c - 'A' + 10;
  271. }
  272. if (i >= 2)
  273. break;
  274. if (len == 0 || *string != '/')
  275. return false;
  276. string++; len--;
  277. }
  278. /* Re-scale to 8-bit */
  279. uint8_t r = 256 * (rgb[0] / (double)(1 << (4 * digits[0])));
  280. uint8_t g = 256 * (rgb[1] / (double)(1 << (4 * digits[1])));
  281. uint8_t b = 256 * (rgb[2] / (double)(1 << (4 * digits[2])));
  282. LOG_DBG("rgb: %02x%02x%02x", r, g, b);
  283. *color = r << 16 | g << 8 | b;
  284. return true;
  285. }
  286. static uint8_t
  287. nibble2hex(char c)
  288. {
  289. switch (c) {
  290. case '0': case '1': case '2': case '3': case '4':
  291. case '5': case '6': case '7': case '8': case '9':
  292. return c - '0';
  293. case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
  294. return c - 'a' + 10;
  295. case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
  296. return c - 'A' + 10;
  297. }
  298. assert(false);
  299. return 0;
  300. }
  301. static void
  302. osc_set_pwd(struct terminal *term, char *string)
  303. {
  304. LOG_DBG("PWD: URI: %s", string);
  305. if (memcmp(string, "file://", 7) != 0)
  306. return;
  307. string += 7;
  308. const char *hostname = string;
  309. char *hostname_end = strchr(string, '/');
  310. if (hostname_end == NULL)
  311. return;
  312. char this_host[HOST_NAME_MAX];
  313. if (gethostname(this_host, sizeof(this_host)) < 0)
  314. this_host[0] = '\0';
  315. /* Ignore this CWD if the hostname isn't 'localhost' or our gethostname() */
  316. size_t hostname_len = hostname_end - hostname;
  317. if (strncmp(hostname, "", hostname_len) != 0 &&
  318. strncmp(hostname, "localhost", hostname_len) != 0 &&
  319. strncmp(hostname, this_host, hostname_len) != 0)
  320. {
  321. LOG_DBG("ignoring OSC 7: hostname mismatch: %.*s != %s",
  322. (int)hostname_len, hostname, this_host);
  323. return;
  324. }
  325. /* Decode %xx encoded characters */
  326. const char *path = hostname_end;
  327. char *pwd = xmalloc(strlen(path) + 1);
  328. char *p = pwd;
  329. while (true) {
  330. /* Find next '%' */
  331. const char *next = strchr(path, '%');
  332. if (next == NULL) {
  333. strcpy(p, path);
  334. break;
  335. }
  336. /* Copy everything leading up to the '%' */
  337. size_t prefix_len = next - path;
  338. memcpy(p, path, prefix_len);
  339. p += prefix_len;
  340. if (isxdigit(next[1]) && isxdigit(next[2])) {
  341. *p++ = nibble2hex(next[1]) << 4 | nibble2hex(next[2]);
  342. *p = '\0';
  343. path = next + 3;
  344. } else {
  345. *p++ = *next;
  346. *p = '\0';
  347. path = next + 1;
  348. }
  349. }
  350. LOG_DBG("PWD: decoded: %s", pwd);
  351. free(term->cwd);
  352. term->cwd = pwd;
  353. }
  354. #if 0
  355. static void
  356. osc_notify(struct terminal *term, char *string)
  357. {
  358. char *ctx = NULL;
  359. const char *cmd = strtok_r(string, ";", &ctx);
  360. const char *title = strtok_r(NULL, ";", &ctx);
  361. const char *msg = strtok_r(NULL, ";", &ctx);
  362. LOG_DBG("cmd: \"%s\", title: \"%s\", msg: \"%s\"",
  363. cmd, title, msg);
  364. if (cmd == NULL || strcmp(cmd, "notify") != 0 || title == NULL || msg == NULL)
  365. return;
  366. }
  367. #endif
  368. static void
  369. update_color_in_grids(struct terminal *term, uint32_t old_color,
  370. uint32_t new_color)
  371. {
  372. /*
  373. * Update color of already rendered cells.
  374. *
  375. * Note that we do *not* store the original palette
  376. * index. Therefore, the best we can do is compare colors - if
  377. * they match, assume "our" palette index was the one used to
  378. * render the cell.
  379. *
  380. * There are a couple of cases where this isn't necessarily true:
  381. * - user has configured the 16 base colors with non-unique
  382. * colors. - the client has used 24-bit escapes for colors
  383. *
  384. * In general though, if the client configures the palette, it is
  385. * very likely only using index:ed coloring (i.e. not 24-bit
  386. * direct colors), and I hope that it is unusual with palettes
  387. * where all the colors aren't unique.
  388. *
  389. * TODO(?): for performance reasons, we only update the current
  390. * screen rows (of both grids). I.e. scrollback is *not* updated.
  391. */
  392. for (size_t i = 0; i < 2; i++) {
  393. struct grid *grid = i == 0 ? &term->normal : &term->alt;
  394. for (size_t r = 0; r < term->rows; r++) {
  395. struct row *row = grid_row(grid, r);
  396. assert(row != NULL);
  397. for (size_t c = 0; c < term->grid->num_cols; c++) {
  398. struct cell *cell = &row->cells[c];
  399. if (cell->attrs.have_fg &&
  400. cell->attrs.fg == old_color)
  401. {
  402. cell->attrs.fg = new_color;
  403. cell->attrs.clean = 0;
  404. row->dirty = true;
  405. }
  406. if ( cell->attrs.have_bg &&
  407. cell->attrs.bg == old_color)
  408. {
  409. cell->attrs.bg = new_color;
  410. cell->attrs.clean = 0;
  411. row->dirty = true;
  412. }
  413. }
  414. }
  415. }
  416. }
  417. void
  418. osc_dispatch(struct terminal *term)
  419. {
  420. unsigned param = 0;
  421. int data_ofs = 0;
  422. for (size_t i = 0; i < term->vt.osc.idx; i++, data_ofs++) {
  423. char c = term->vt.osc.data[i];
  424. if (c == ';') {
  425. data_ofs++;
  426. break;
  427. }
  428. if (!isdigit(c)) {
  429. UNHANDLED();
  430. return;
  431. }
  432. param *= 10;
  433. param += c - '0';
  434. }
  435. LOG_DBG("OCS: %.*s (param = %d)",
  436. (int)term->vt.osc.idx, term->vt.osc.data, param);
  437. char *string = (char *)&term->vt.osc.data[data_ofs];
  438. switch (param) {
  439. case 0: term_set_window_title(term, string); break; /* icon + title */
  440. case 1: break; /* icon */
  441. case 2: term_set_window_title(term, string); break; /* title */
  442. case 4: {
  443. /* Set color<idx> */
  444. string--;
  445. if (*string != ';')
  446. break;
  447. assert(*string == ';');
  448. for (const char *s_idx = strtok(string, ";"), *s_color = strtok(NULL, ";");
  449. s_idx != NULL && s_color != NULL;
  450. s_idx = strtok(NULL, ";"), s_color = strtok(NULL, ";"))
  451. {
  452. /* Parse <idx> parameter */
  453. unsigned idx = 0;
  454. for (; *s_idx != '\0'; s_idx++) {
  455. char c = *s_idx;
  456. idx *= 10;
  457. idx += c - '0';
  458. }
  459. /* Client queried for current value */
  460. if (strlen(s_color) == 1 && s_color[0] == '?') {
  461. uint32_t color = term->colors.table[idx];
  462. uint8_t r = (color >> 16) & 0xff;
  463. uint8_t g = (color >> 8) & 0xff;
  464. uint8_t b = (color >> 0) & 0xff;
  465. char reply[32];
  466. snprintf(reply, sizeof(reply), "\033]4;%u;rgb:%02x/%02x/%02x\033\\",
  467. idx, r, g, b);
  468. term_to_slave(term, reply, strlen(reply));
  469. }
  470. else {
  471. uint32_t color;
  472. bool color_is_valid = s_color[0] == '#'
  473. ? parse_legacy_color(s_color, &color)
  474. : parse_rgb(s_color, &color);
  475. if (!color_is_valid)
  476. continue;
  477. LOG_DBG("change color definition for #%u from %06x to %06x",
  478. idx, term->colors.table[idx], color);
  479. update_color_in_grids(term, term->colors.table[idx], color);
  480. term->colors.table[idx] = color;
  481. }
  482. }
  483. break;
  484. }
  485. case 7:
  486. /* Update terminal's understanding of PWD */
  487. osc_set_pwd(term, string);
  488. break;
  489. case 10:
  490. case 11: {
  491. /* Set default foreground/background color */
  492. /* Client queried for current value */
  493. if (strlen(string) == 1 && string[0] == '?') {
  494. uint32_t color = param == 10 ? term->colors.fg : term->colors.bg;
  495. uint8_t r = (color >> 16) & 0xff;
  496. uint8_t g = (color >> 8) & 0xff;
  497. uint8_t b = (color >> 0) & 0xff;
  498. /*
  499. * Reply in XParseColor format
  500. * E.g. for color 0xdcdccc we reply "\033]10;rgb:dc/dc/cc\033\\"
  501. */
  502. char reply[32];
  503. snprintf(
  504. reply, sizeof(reply), "\033]%u;rgb:%02x/%02x/%02x\033\\",
  505. param, r, g, b);
  506. term_to_slave(term, reply, strlen(reply));
  507. break;
  508. }
  509. uint32_t color;
  510. if (string[0] == '#' ? !parse_legacy_color(string, &color) : !parse_rgb(string, &color))
  511. break;
  512. LOG_DBG("change color definition for %s to %06x",
  513. param == 10 ? "foreground" : "background", color);
  514. switch (param) {
  515. case 10: term->colors.fg = color; break;
  516. case 11: term->colors.bg = color; break;
  517. }
  518. term_damage_view(term);
  519. term_damage_margins(term);
  520. break;
  521. }
  522. case 12: /* Set cursor color */
  523. /* Client queried for current value */
  524. if (strlen(string) == 1 && string[0] == '?') {
  525. uint8_t r = (term->cursor_color.cursor >> 16) & 0xff;
  526. uint8_t g = (term->cursor_color.cursor >> 8) & 0xff;
  527. uint8_t b = (term->cursor_color.cursor >> 0) & 0xff;
  528. char reply[32];
  529. snprintf(reply, sizeof(reply), "\033]12;rgb:%02x/%02x/%02x\033\\", r, g, b);
  530. term_to_slave(term, reply, strlen(reply));
  531. break;
  532. }
  533. uint32_t color;
  534. if (string[0] == '#' ? !parse_legacy_color(string, &color) : !parse_rgb(string, &color))
  535. break;
  536. LOG_INFO("change cursor color to %06x", color);
  537. if (color == 0)
  538. term->cursor_color.cursor = 0; /* Invert fg/bg */
  539. else
  540. term->cursor_color.cursor = 1u << 31 | color;
  541. term_damage_cursor(term);
  542. break;
  543. case 30: /* Set tab title */
  544. break;
  545. case 52: /* Copy to/from clipboard/primary */
  546. osc_selection(term, string);
  547. break;
  548. case 104: {
  549. /* Reset Color Number 'c' (whole table if no parameter) */
  550. if (strlen(string) == 0) {
  551. LOG_DBG("resetting all colors");
  552. for (size_t i = 0; i < 256; i++) {
  553. update_color_in_grids(
  554. term, term->colors.table[i], term->colors.default_table[i]);
  555. term->colors.table[i] = term->colors.default_table[i];
  556. }
  557. }
  558. else {
  559. for (const char *s_idx = strtok(string, ";");
  560. s_idx != NULL;
  561. s_idx = strtok(NULL, ";"))
  562. {
  563. unsigned idx = 0;
  564. for (; *s_idx != '\0'; s_idx++) {
  565. char c = *s_idx;
  566. idx *= 10;
  567. idx += c - '0';
  568. }
  569. LOG_DBG("resetting color #%u", idx);
  570. update_color_in_grids(
  571. term, term->colors.table[idx], term->colors.default_table[idx]);
  572. term->colors.table[idx] = term->colors.default_table[idx];
  573. }
  574. }
  575. break;
  576. }
  577. case 105: /* Reset Special Color Number 'c' */
  578. break;
  579. case 110: /* Reset default text foreground color */
  580. LOG_DBG("resetting foreground");
  581. term->colors.fg = term->colors.default_fg;
  582. term_damage_view(term);
  583. break;
  584. case 111: /* Reset default text background color */
  585. LOG_DBG("resetting background");
  586. term->colors.bg = term->colors.default_bg;
  587. term_damage_view(term);
  588. term_damage_margins(term);
  589. break;
  590. case 112:
  591. LOG_DBG("resetting cursor color");
  592. term->cursor_color.text = term->default_cursor_color.text;
  593. term->cursor_color.cursor = term->default_cursor_color.cursor;
  594. term_damage_cursor(term);
  595. break;
  596. case 555:
  597. osc_flash(term);
  598. break;
  599. #if 0
  600. case 777:
  601. osc_notify(term, string);
  602. break;
  603. #endif
  604. default:
  605. UNHANDLED();
  606. break;
  607. }
  608. }
  609. bool
  610. osc_ensure_size(struct terminal *term, size_t required_size)
  611. {
  612. if (required_size <= term->vt.osc.size)
  613. return true;
  614. size_t new_size = (required_size + 127) / 128 * 128;
  615. assert(new_size > 0);
  616. uint8_t *new_data = realloc(term->vt.osc.data, new_size);
  617. if (new_data == NULL) {
  618. LOG_ERRNO("failed to increase size of OSC buffer");
  619. return false;
  620. }
  621. term->vt.osc.data = new_data;
  622. term->vt.osc.size = new_size;
  623. return true;
  624. }