Add support for OSC 8 hyperlinks (a VTE extension now supported by other

terminals such as iTerm2). Originally written by me then extended and
completed by first Will Noble and later Jeff Chiang. GitHub issues 911,
2621, 2890, 3240.
This commit is contained in:
nicm 2022-06-30 09:55:53 +00:00
parent b22edcf3a5
commit cdacc12ce3
15 changed files with 432 additions and 48 deletions

View File

@ -81,6 +81,7 @@ SRCS= alerts.c \
grid-reader.c \ grid-reader.c \
grid-view.c \ grid-view.c \
grid.c \ grid.c \
hyperlinks.c \
input-keys.c \ input-keys.c \
input.c \ input.c \
job.c \ job.c \

View File

@ -53,8 +53,8 @@ const struct cmd_entry cmd_clear_history_entry = {
.name = "clear-history", .name = "clear-history",
.alias = "clearhist", .alias = "clearhist",
.args = { "t:", 0, 0, NULL }, .args = { "Ht:", 0, 0, NULL },
.usage = CMD_TARGET_PANE_USAGE, .usage = "[-H] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@ -204,6 +204,8 @@ cmd_capture_pane_exec(struct cmd *self, struct cmdq_item *item)
if (cmd_get_entry(self) == &cmd_clear_history_entry) { if (cmd_get_entry(self) == &cmd_clear_history_entry) {
window_pane_reset_mode_all(wp); window_pane_reset_mode_all(wp);
grid_clear_history(wp->base.grid); grid_clear_history(wp->base.grid);
if (args_has(args, 'H'))
screen_reset_hyperlinks(wp->screen);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@ -144,7 +144,7 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx,
llen = 0; llen = 0;
if (sx < len * 6 || sy < 5) { if (sx < len * 6 || sy < 5) {
tty_attributes(tty, &fgc, &grid_default_cell, NULL); tty_attributes(tty, &fgc, &grid_default_cell, NULL, NULL);
if (sx >= len + llen + 1) { if (sx >= len + llen + 1) {
len += llen + 1; len += llen + 1;
tty_cursor(tty, xoff + px - len / 2, yoff + py); tty_cursor(tty, xoff + px - len / 2, yoff + py);
@ -161,7 +161,7 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx,
px -= len * 3; px -= len * 3;
py -= 2; py -= 2;
tty_attributes(tty, &bgc, &grid_default_cell, NULL); tty_attributes(tty, &bgc, &grid_default_cell, NULL, NULL);
for (ptr = buf; *ptr != '\0'; ptr++) { for (ptr = buf; *ptr != '\0'; ptr++) {
if (*ptr < '0' || *ptr > '9') if (*ptr < '0' || *ptr > '9')
continue; continue;
@ -179,7 +179,7 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx,
if (sy <= 6) if (sy <= 6)
goto out; goto out;
tty_attributes(tty, &fgc, &grid_default_cell, NULL); tty_attributes(tty, &fgc, &grid_default_cell, NULL, NULL);
if (rlen != 0 && sx >= rlen) { if (rlen != 0 && sx >= rlen) {
tty_cursor(tty, xoff + sx - rlen, yoff); tty_cursor(tty, xoff + sx - rlen, yoff);
tty_putn(tty, rbuf, rlen, rlen); tty_putn(tty, rbuf, rlen, rlen);

13
grid.c
View File

@ -37,7 +37,7 @@
/* Default grid cell data. */ /* Default grid cell data. */
const struct grid_cell grid_default_cell = { const struct grid_cell grid_default_cell = {
{ { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0 { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0
}; };
/* /*
@ -45,12 +45,12 @@ const struct grid_cell grid_default_cell = {
* appears in the grid - because of this, they are always extended cells. * appears in the grid - because of this, they are always extended cells.
*/ */
static const struct grid_cell grid_padding_cell = { static const struct grid_cell grid_padding_cell = {
{ { '!' }, 0, 0, 0 }, 0, GRID_FLAG_PADDING, 8, 8, 0 { { '!' }, 0, 0, 0 }, 0, GRID_FLAG_PADDING, 8, 8, 0, 0
}; };
/* Cleared grid cell data. */ /* Cleared grid cell data. */
static const struct grid_cell grid_cleared_cell = { static const struct grid_cell grid_cleared_cell = {
{ { ' ' }, 0, 1, 1 }, 0, GRID_FLAG_CLEARED, 8, 8, 0 { { ' ' }, 0, 1, 1 }, 0, GRID_FLAG_CLEARED, 8, 8, 0, 0
}; };
static const struct grid_cell_entry grid_cleared_entry = { static const struct grid_cell_entry grid_cleared_entry = {
GRID_FLAG_CLEARED, { .data = { 0, 8, 8, ' ' } } GRID_FLAG_CLEARED, { .data = { 0, 8, 8, ' ' } }
@ -90,6 +90,8 @@ grid_need_extended_cell(const struct grid_cell_entry *gce,
return (1); return (1);
if (gc->us != 0) /* only supports 256 or RGB */ if (gc->us != 0) /* only supports 256 or RGB */
return (1); return (1);
if (gc->link != 0)
return (1);
return (0); return (0);
} }
@ -131,6 +133,7 @@ grid_extended_cell(struct grid_line *gl, struct grid_cell_entry *gce,
gee->fg = gc->fg; gee->fg = gc->fg;
gee->bg = gc->bg; gee->bg = gc->bg;
gee->us = gc->us; gee->us = gc->us;
gee->link = gc->link;
return (gee); return (gee);
} }
@ -231,6 +234,8 @@ grid_cells_look_equal(const struct grid_cell *gc1, const struct grid_cell *gc2)
return (0); return (0);
if (gc1->attr != gc2->attr || gc1->flags != gc2->flags) if (gc1->attr != gc2->attr || gc1->flags != gc2->flags)
return (0); return (0);
if (gc1->link != gc2->link)
return (0);
return (1); return (1);
} }
@ -509,6 +514,7 @@ grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc)
gc->fg = gee->fg; gc->fg = gee->fg;
gc->bg = gee->bg; gc->bg = gee->bg;
gc->us = gee->us; gc->us = gee->us;
gc->link = gee->link;
utf8_to_data(gee->data, &gc->data); utf8_to_data(gee->data, &gc->data);
} }
return; return;
@ -524,6 +530,7 @@ grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc)
gc->bg |= COLOUR_FLAG_256; gc->bg |= COLOUR_FLAG_256;
gc->us = 0; gc->us = 0;
utf8_set(&gc->data, gce->data.data); utf8_set(&gc->data, gce->data.data);
gc->link = 0;
} }
/* Get cell for reading. */ /* Get cell for reading. */

225
hyperlinks.c Normal file
View File

@ -0,0 +1,225 @@
/* $OpenBSD$ */
/*
* Copyright (c) 2021 Will <author@will.party>
* Copyright (c) 2022 Jeff Chiang <pobomp@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <vis.h>
#include "tmux.h"
/*
* OSC 8 hyperlinks, described at:
*
* https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
*
* Each hyperlink and ID combination is assigned a number ("inner" in this
* file) which is stored in an extended grid cell and maps into a tree here.
*
* Each URI has one inner number and one external ID (which tmux uses to send
* the hyperlink to the terminal) and one internal ID (which is received from
* the sending application inside tmux).
*
* Anonymous hyperlinks are each unique and are not reused even if they have
* the same URI (terminals will not want to tie them together).
*/
#define MAX_HYPERLINKS 5000
static uint64_t hyperlinks_next_external_id = 1;
static u_int global_hyperlinks_count;
struct hyperlinks_uri {
struct hyperlinks *tree;
u_int inner;
const char *internal_id;
const char *external_id;
const char *uri;
TAILQ_ENTRY(hyperlinks_uri) list_entry;
RB_ENTRY(hyperlinks_uri) by_inner_entry;
RB_ENTRY(hyperlinks_uri) by_uri_entry; /* by internal ID and URI */
};
RB_HEAD(hyperlinks_by_uri_tree, hyperlinks_uri);
RB_HEAD(hyperlinks_by_inner_tree, hyperlinks_uri);
TAILQ_HEAD(hyperlinks_list, hyperlinks_uri);
static struct hyperlinks_list global_hyperlinks =
TAILQ_HEAD_INITIALIZER(global_hyperlinks);
struct hyperlinks {
u_int next_inner;
struct hyperlinks_by_inner_tree by_inner;
struct hyperlinks_by_uri_tree by_uri;
};
static int
hyperlinks_by_uri_cmp(struct hyperlinks_uri *left, struct hyperlinks_uri *right)
{
int r;
if (*left->internal_id == '\0' || *right->internal_id == '\0') {
/*
* If both URIs are anonymous, use the inner for comparison so
* that they do not match even if the URI is the same - each
* anonymous URI should be unique.
*/
if (*left->internal_id != '\0')
return (-1);
if (*right->internal_id != '\0')
return (1);
return (left->inner - right->inner);
}
r = strcmp(left->internal_id, right->internal_id);
if (r != 0)
return (r);
return (strcmp(left->uri, right->uri));
}
RB_PROTOTYPE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry,
hyperlinks_by_uri_cmp);
RB_GENERATE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry,
hyperlinks_by_uri_cmp);
static int
hyperlinks_by_inner_cmp(struct hyperlinks_uri *left,
struct hyperlinks_uri *right)
{
return (left->inner - right->inner);
}
RB_PROTOTYPE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry,
hyperlinks_by_inner_cmp);
RB_GENERATE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry,
hyperlinks_by_inner_cmp);
/* Remove a hyperlink. */
static void
hyperlinks_remove(struct hyperlinks_uri *hlu)
{
struct hyperlinks *hl = hlu->tree;
TAILQ_REMOVE(&global_hyperlinks, hlu, list_entry);
global_hyperlinks_count--;
RB_REMOVE(hyperlinks_by_inner_tree, &hl->by_inner, hlu);
RB_REMOVE(hyperlinks_by_uri_tree, &hl->by_uri, hlu);
free((void *)hlu->internal_id);
free((void *)hlu->external_id);
free((void *)hlu->uri);
free(hlu);
}
/* Store a new hyperlink or return if it already exists. */
u_int
hyperlinks_put(struct hyperlinks *hl, const char *uri_in,
const char *internal_id_in)
{
struct hyperlinks_uri find, *hlu;
char *uri, *internal_id, *external_id;
/*
* Anonymous URI are stored with an empty internal ID and the tree
* comparator will make sure they never match each other (so each
* anonymous URI is unique).
*/
if (internal_id_in == NULL)
internal_id_in = "";
utf8_stravis(&uri, uri_in, VIS_OCTAL|VIS_CSTYLE);
utf8_stravis(&internal_id, internal_id_in, VIS_OCTAL|VIS_CSTYLE);
if (*internal_id_in != '\0') {
find.uri = uri;
find.internal_id = internal_id;
hlu = RB_FIND(hyperlinks_by_uri_tree, &hl->by_uri, &find);
if (hlu != NULL) {
free (uri);
free (internal_id);
return (hlu->inner);
}
}
xasprintf(&external_id, "tmux%llX", hyperlinks_next_external_id++);
hlu = xcalloc(1, sizeof *hlu);
hlu->inner = hl->next_inner++;
hlu->internal_id = internal_id;
hlu->external_id = external_id;
hlu->uri = uri;
hlu->tree = hl;
RB_INSERT(hyperlinks_by_uri_tree, &hl->by_uri, hlu);
RB_INSERT(hyperlinks_by_inner_tree, &hl->by_inner, hlu);
TAILQ_INSERT_TAIL(&global_hyperlinks, hlu, list_entry);
if (++global_hyperlinks_count == MAX_HYPERLINKS)
hyperlinks_remove(TAILQ_FIRST(&global_hyperlinks));
return (hlu->inner);
}
/* Get hyperlink by inner number. */
int
hyperlinks_get(struct hyperlinks *hl, u_int inner, const char **uri_out,
const char **external_id_out)
{
struct hyperlinks_uri find, *hlu;
find.inner = inner;
hlu = RB_FIND(hyperlinks_by_inner_tree, &hl->by_inner, &find);
if (hlu == NULL)
return (0);
*external_id_out = hlu->external_id;
*uri_out = hlu->uri;
return (1);
}
/* Initialize hyperlink set. */
struct hyperlinks *
hyperlinks_init(void)
{
struct hyperlinks *hl;
hl = xcalloc(1, sizeof *hl);
hl->next_inner = 1;
RB_INIT(&hl->by_uri);
RB_INIT(&hl->by_inner);
return (hl);
}
/* Free all hyperlinks but not the set itself. */
void
hyperlinks_reset(struct hyperlinks *hl)
{
struct hyperlinks_uri *hlu, *hlu1;
RB_FOREACH_SAFE(hlu, hyperlinks_by_inner_tree, &hl->by_inner, hlu1)
hyperlinks_remove(hlu);
}
/* Free hyperlink set. */
void
hyperlinks_free(struct hyperlinks *hl)
{
hyperlinks_reset(hl);
free(hl);
}

45
input.c
View File

@ -135,6 +135,7 @@ static void input_set_state(struct input_ctx *,
static void input_reset_cell(struct input_ctx *); static void input_reset_cell(struct input_ctx *);
static void input_osc_4(struct input_ctx *, const char *); static void input_osc_4(struct input_ctx *, const char *);
static void input_osc_8(struct input_ctx *, const char *);
static void input_osc_10(struct input_ctx *, const char *); static void input_osc_10(struct input_ctx *, const char *);
static void input_osc_11(struct input_ctx *, const char *); static void input_osc_11(struct input_ctx *, const char *);
static void input_osc_12(struct input_ctx *, const char *); static void input_osc_12(struct input_ctx *, const char *);
@ -2318,6 +2319,9 @@ input_exit_osc(struct input_ctx *ictx)
} }
} }
break; break;
case 8:
input_osc_8(ictx, p);
break;
case 10: case 10:
input_osc_10(ictx, p); input_osc_10(ictx, p);
break; break;
@ -2562,6 +2566,47 @@ input_osc_4(struct input_ctx *ictx, const char *p)
free(copy); free(copy);
} }
/* Handle the OSC 8 sequence for embedding hyperlinks. */
static void
input_osc_8(struct input_ctx *ictx, const char *p)
{
struct hyperlinks *hl = ictx->ctx.s->hyperlinks;
struct grid_cell *gc = &ictx->cell.cell;
const char *start, *end, *uri;
char *id = NULL;
for (start = p; (end = strpbrk(start, ":;")) != NULL; start = end + 1) {
if (end - start >= 4 && strncmp(start, "id=", 3) == 0) {
if (id != NULL)
goto bad;
id = xstrndup(start + 3, end - start - 3);
}
/* The first ; is the end of parameters and start of the URI. */
if (*end == ';')
break;
}
if (end == NULL || *end != ';')
goto bad;
uri = end + 1;
if (*uri == '\0') {
gc->link = 0;
free(id);
return;
}
gc->link = hyperlinks_put(hl, uri, id);
if (id == NULL)
log_debug("hyperlink (anonymous) %s = %u", uri, gc->link);
else
log_debug("hyperlink (id=%s) %s = %u", id, uri, gc->link);
free(id);
return;
bad:
log_debug("bad OSC 8 %s", p);
free(id);
}
/* Handle the OSC 10 sequence for setting and querying foreground colour. */ /* Handle the OSC 10 sequence for setting and querying foreground colour. */
static void static void
input_osc_10(struct input_ctx *ictx, const char *p) input_osc_10(struct input_ctx *ictx, const char *p)

View File

@ -738,7 +738,7 @@ screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j)
} }
} }
tty_cell(tty, &gc, &grid_default_cell, NULL); tty_cell(tty, &gc, &grid_default_cell, NULL, NULL);
if (isolates) if (isolates)
tty_puts(tty, START_ISOLATE); tty_puts(tty, START_ISOLATE);
} }

View File

@ -90,6 +90,7 @@ screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit)
s->sel = NULL; s->sel = NULL;
s->write_list = NULL; s->write_list = NULL;
s->hyperlinks = NULL;
screen_reinit(s); screen_reinit(s);
} }
@ -119,6 +120,17 @@ screen_reinit(struct screen *s)
screen_clear_selection(s); screen_clear_selection(s);
screen_free_titles(s); screen_free_titles(s);
screen_reset_hyperlinks(s);
}
/* Reset hyperlinks of a screen. */
void
screen_reset_hyperlinks(struct screen *s)
{
if (s->hyperlinks == NULL)
s->hyperlinks = hyperlinks_init();
else
hyperlinks_reset(s->hyperlinks);
} }
/* Destroy a screen. */ /* Destroy a screen. */
@ -137,6 +149,8 @@ screen_free(struct screen *s)
grid_destroy(s->saved_grid); grid_destroy(s->saved_grid);
grid_destroy(s->grid); grid_destroy(s->grid);
if (s->hyperlinks != NULL)
hyperlinks_free(s->hyperlinks);
screen_free_titles(s); screen_free_titles(s);
} }

View File

@ -211,7 +211,6 @@ server_start(struct tmuxproc *client, int flags, struct event_base *base,
RB_INIT(&sessions); RB_INIT(&sessions);
key_bindings_init(); key_bindings_init();
TAILQ_INIT(&message_log); TAILQ_INIT(&message_log);
gettimeofday(&start_time, NULL); gettimeofday(&start_time, NULL);
server_fd = server_create_socket(flags, &cause); server_fd = server_create_socket(flags, &cause);

View File

@ -30,7 +30,7 @@
/* Default style. */ /* Default style. */
static struct style style_default = { static struct style style_default = {
{ { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0 }, { { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0 },
0, 0,
8, 8,

11
tmux.1
View File

@ -3656,6 +3656,8 @@ Allows setting the cursor style.
Supports extended keys. Supports extended keys.
.It focus .It focus
Supports focus reporting. Supports focus reporting.
.It hyperlinks
Supports OSC 8 hyperlinks.
.It ignorefkeys .It ignorefkeys
Ignore function keys from Ignore function keys from
.Xr terminfo 5 .Xr terminfo 5
@ -6122,9 +6124,14 @@ a format for each shortcut key; both are evaluated once for each line.
starts without the preview. starts without the preview.
This command works only if at least one client is attached. This command works only if at least one client is attached.
.Tg clearhist .Tg clearhist
.It Ic clear-history Op Fl t Ar target-pane .It Xo Ic clear-history
.Op Fl H
.Op Fl t Ar target-pane
.Xc
.D1 Pq alias: Ic clearhist .D1 Pq alias: Ic clearhist
Remove and free the history for the specified pane. Remove and free the history for the specified pane.
.Fl H
also removes all hyperlinks.
.Tg deleteb .Tg deleteb
.It Ic delete-buffer Op Fl b Ar buffer-name .It Ic delete-buffer Op Fl b Ar buffer-name
.D1 Pq alias: Ic deleteb .D1 Pq alias: Ic deleteb
@ -6412,6 +6419,8 @@ Disable and enable focus reporting.
These are set automatically if the These are set automatically if the
.Em XT .Em XT
capability is present. capability is present.
.It Em \&Hls
Set or clear a hyperlink annotation.
.It Em \&Rect .It Em \&Rect
Tell Tell
.Nm .Nm

23
tmux.h
View File

@ -49,6 +49,8 @@ struct control_state;
struct environ; struct environ;
struct format_job_tree; struct format_job_tree;
struct format_tree; struct format_tree;
struct hyperlinks_uri;
struct hyperlinks;
struct input_ctx; struct input_ctx;
struct job; struct job;
struct menu_data; struct menu_data;
@ -365,6 +367,7 @@ enum tty_code_code {
TTYC_ENFCS, TTYC_ENFCS,
TTYC_ENMG, TTYC_ENMG,
TTYC_FSL, TTYC_FSL,
TTYC_HLS,
TTYC_HOME, TTYC_HOME,
TTYC_HPA, TTYC_HPA,
TTYC_ICH, TTYC_ICH,
@ -689,6 +692,7 @@ struct grid_cell {
int fg; int fg;
int bg; int bg;
int us; int us;
u_int link;
}; };
/* Grid extended cell entry. */ /* Grid extended cell entry. */
@ -699,6 +703,7 @@ struct grid_extd_entry {
int fg; int fg;
int bg; int bg;
int us; int us;
u_int link;
} __packed; } __packed;
/* Grid cell entry. */ /* Grid cell entry. */
@ -850,6 +855,8 @@ struct screen {
struct screen_sel *sel; struct screen_sel *sel;
struct screen_write_cline *write_list; struct screen_write_cline *write_list;
struct hyperlinks *hyperlinks;
}; };
/* Screen write context. */ /* Screen write context. */
@ -2246,7 +2253,8 @@ void tty_update_window_offset(struct window *);
void tty_update_client_offset(struct client *); void tty_update_client_offset(struct client *);
void tty_raw(struct tty *, const char *); void tty_raw(struct tty *, const char *);
void tty_attributes(struct tty *, const struct grid_cell *, void tty_attributes(struct tty *, const struct grid_cell *,
const struct grid_cell *, struct colour_palette *); const struct grid_cell *, struct colour_palette *,
struct hyperlinks *);
void tty_reset(struct tty *); void tty_reset(struct tty *);
void tty_region_off(struct tty *); void tty_region_off(struct tty *);
void tty_margin_off(struct tty *); void tty_margin_off(struct tty *);
@ -2263,7 +2271,8 @@ void tty_puts(struct tty *, const char *);
void tty_putc(struct tty *, u_char); void tty_putc(struct tty *, u_char);
void tty_putn(struct tty *, const void *, size_t, u_int); void tty_putn(struct tty *, const void *, size_t, u_int);
void tty_cell(struct tty *, const struct grid_cell *, void tty_cell(struct tty *, const struct grid_cell *,
const struct grid_cell *, struct colour_palette *); const struct grid_cell *, struct colour_palette *,
struct hyperlinks *);
int tty_init(struct tty *, struct client *); int tty_init(struct tty *, struct client *);
void tty_resize(struct tty *); void tty_resize(struct tty *);
void tty_set_size(struct tty *, u_int, u_int, u_int, u_int); void tty_set_size(struct tty *, u_int, u_int, u_int, u_int);
@ -2893,6 +2902,7 @@ void screen_init(struct screen *, u_int, u_int, u_int);
void screen_reinit(struct screen *); void screen_reinit(struct screen *);
void screen_free(struct screen *); void screen_free(struct screen *);
void screen_reset_tabs(struct screen *); void screen_reset_tabs(struct screen *);
void screen_reset_hyperlinks(struct screen *);
void screen_set_cursor_style(u_int, enum screen_cursor_style *, int *); void screen_set_cursor_style(u_int, enum screen_cursor_style *, int *);
void screen_set_cursor_colour(struct screen *, int); void screen_set_cursor_colour(struct screen *, int);
int screen_set_title(struct screen *, const char *); int screen_set_title(struct screen *, const char *);
@ -3298,4 +3308,13 @@ void server_acl_user_deny_write(uid_t);
int server_acl_join(struct client *); int server_acl_join(struct client *);
uid_t server_acl_get_uid(struct server_acl_user *); uid_t server_acl_get_uid(struct server_acl_user *);
/* hyperlink.c */
u_int hyperlinks_put(struct hyperlinks *, const char *,
const char *);
int hyperlinks_get(struct hyperlinks *, u_int,
const char **, const char **);
struct hyperlinks *hyperlinks_init(void);
void hyperlinks_reset(struct hyperlinks *);
void hyperlinks_free(struct hyperlinks *);
#endif /* TMUX_H */ #endif /* TMUX_H */

View File

@ -87,6 +87,17 @@ static const struct tty_feature tty_feature_clipboard = {
0 0
}; };
/* Terminal supports OSC 8 hyperlinks. */
static const char *tty_feature_hyperlinks_capabilities[] = {
"*:Hls=\\E]8;%?%p1%l%tid=%p1%s%;;%p2%s\\E\\\\",
NULL
};
static const struct tty_feature tty_feature_hyperlinks = {
"hyperlinks",
tty_feature_hyperlinks_capabilities,
0
};
/* /*
* Terminal supports RGB colour. This replaces setab and setaf also since * Terminal supports RGB colour. This replaces setab and setaf also since
* terminals with RGB have versions that do not allow setting colours from the * terminals with RGB have versions that do not allow setting colours from the
@ -330,6 +341,7 @@ static const struct tty_feature *tty_features[] = {
&tty_feature_bpaste, &tty_feature_bpaste,
&tty_feature_ccolour, &tty_feature_ccolour,
&tty_feature_clipboard, &tty_feature_clipboard,
&tty_feature_hyperlinks,
&tty_feature_cstyle, &tty_feature_cstyle,
&tty_feature_extkeys, &tty_feature_extkeys,
&tty_feature_focus, &tty_feature_focus,
@ -444,14 +456,14 @@ tty_default_features(int *feat, const char *name, u_int version)
}, },
{ .name = "tmux", { .name = "tmux",
.features = TTY_FEATURES_BASE_MODERN_XTERM .features = TTY_FEATURES_BASE_MODERN_XTERM
",ccolour,cstyle,focus,overline,usstyle" ",ccolour,cstyle,focus,overline,usstyle,hyperlinks"
}, },
{ .name = "rxvt-unicode", { .name = "rxvt-unicode",
.features = "256,bpaste,ccolour,cstyle,mouse,title,ignorefkeys" .features = "256,bpaste,ccolour,cstyle,mouse,title,ignorefkeys"
}, },
{ .name = "iTerm2", { .name = "iTerm2",
.features = TTY_FEATURES_BASE_MODERN_XTERM .features = TTY_FEATURES_BASE_MODERN_XTERM
",cstyle,extkeys,margins,usstyle,sync,osc7" ",cstyle,extkeys,margins,usstyle,sync,osc7,hyperlinks"
}, },
{ .name = "XTerm", { .name = "XTerm",
/* /*

View File

@ -100,6 +100,7 @@ static const struct tty_term_code_entry tty_term_codes[] = {
[TTYC_ENFCS] = { TTYCODE_STRING, "Enfcs" }, [TTYC_ENFCS] = { TTYCODE_STRING, "Enfcs" },
[TTYC_ENMG] = { TTYCODE_STRING, "Enmg" }, [TTYC_ENMG] = { TTYCODE_STRING, "Enmg" },
[TTYC_FSL] = { TTYCODE_STRING, "fsl" }, [TTYC_FSL] = { TTYCODE_STRING, "fsl" },
[TTYC_HLS] = { TTYCODE_STRING, "Hls" },
[TTYC_HOME] = { TTYCODE_STRING, "home" }, [TTYC_HOME] = { TTYCODE_STRING, "home" },
[TTYC_HPA] = { TTYCODE_STRING, "hpa" }, [TTYC_HPA] = { TTYCODE_STRING, "hpa" },
[TTYC_ICH1] = { TTYCODE_STRING, "ich1" }, [TTYC_ICH1] = { TTYCODE_STRING, "ich1" },

114
tty.c
View File

@ -69,7 +69,7 @@ static void tty_emulate_repeat(struct tty *, enum tty_code_code,
static void tty_repeat_space(struct tty *, u_int); static void tty_repeat_space(struct tty *, u_int);
static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int); static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int);
static void tty_default_attributes(struct tty *, const struct grid_cell *, static void tty_default_attributes(struct tty *, const struct grid_cell *,
struct colour_palette *, u_int); struct colour_palette *, u_int, struct hyperlinks *);
static int tty_check_overlay(struct tty *, u_int, u_int); static int tty_check_overlay(struct tty *, u_int, u_int);
static void tty_check_overlay_range(struct tty *, u_int, u_int, u_int, static void tty_check_overlay_range(struct tty *, u_int, u_int, u_int,
struct overlay_ranges *); struct overlay_ranges *);
@ -1455,7 +1455,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
tty_term_has(tty->term, TTYC_EL1) && tty_term_has(tty->term, TTYC_EL1) &&
!tty_fake_bce(tty, defaults, 8) && !tty_fake_bce(tty, defaults, 8) &&
c->overlay_check == NULL) { c->overlay_check == NULL) {
tty_default_attributes(tty, defaults, palette, 8); tty_default_attributes(tty, defaults, palette, 8,
s->hyperlinks);
tty_cursor(tty, nx - 1, aty); tty_cursor(tty, nx - 1, aty);
tty_putcode(tty, TTYC_EL1); tty_putcode(tty, TTYC_EL1);
cleared = 1; cleared = 1;
@ -1480,9 +1481,11 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
gcp->fg != last.fg || gcp->fg != last.fg ||
gcp->bg != last.bg || gcp->bg != last.bg ||
gcp->us != last.us || gcp->us != last.us ||
gcp->link != last.link ||
ux + width + gcp->data.width > nx || ux + width + gcp->data.width > nx ||
(sizeof buf) - len < gcp->data.size)) { (sizeof buf) - len < gcp->data.size)) {
tty_attributes(tty, &last, defaults, palette); tty_attributes(tty, &last, defaults, palette,
s->hyperlinks);
if (last.flags & GRID_FLAG_CLEARED) { if (last.flags & GRID_FLAG_CLEARED) {
log_debug("%s: %zu cleared", __func__, len); log_debug("%s: %zu cleared", __func__, len);
tty_clear_line(tty, defaults, aty, atx + ux, tty_clear_line(tty, defaults, aty, atx + ux,
@ -1515,7 +1518,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
ux += gcp->data.width; ux += gcp->data.width;
} else if (hidden != 0 || ux + gcp->data.width > nx) { } else if (hidden != 0 || ux + gcp->data.width > nx) {
if (~gcp->flags & GRID_FLAG_PADDING) { if (~gcp->flags & GRID_FLAG_PADDING) {
tty_attributes(tty, &last, defaults, palette); tty_attributes(tty, &last, defaults, palette,
s->hyperlinks);
for (j = 0; j < OVERLAY_MAX_RANGES; j++) { for (j = 0; j < OVERLAY_MAX_RANGES; j++) {
if (r.nx[j] == 0) if (r.nx[j] == 0)
continue; continue;
@ -1532,7 +1536,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
} }
} }
} else if (gcp->attr & GRID_ATTR_CHARSET) { } else if (gcp->attr & GRID_ATTR_CHARSET) {
tty_attributes(tty, &last, defaults, palette); tty_attributes(tty, &last, defaults, palette,
s->hyperlinks);
tty_cursor(tty, atx + ux, aty); tty_cursor(tty, atx + ux, aty);
for (j = 0; j < gcp->data.size; j++) for (j = 0; j < gcp->data.size; j++)
tty_putc(tty, gcp->data.data[j]); tty_putc(tty, gcp->data.data[j]);
@ -1544,7 +1549,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
} }
} }
if (len != 0 && ((~last.flags & GRID_FLAG_CLEARED) || last.bg != 8)) { if (len != 0 && ((~last.flags & GRID_FLAG_CLEARED) || last.bg != 8)) {
tty_attributes(tty, &last, defaults, palette); tty_attributes(tty, &last, defaults, palette, s->hyperlinks);
if (last.flags & GRID_FLAG_CLEARED) { if (last.flags & GRID_FLAG_CLEARED) {
log_debug("%s: %zu cleared (end)", __func__, len); log_debug("%s: %zu cleared (end)", __func__, len);
tty_clear_line(tty, defaults, aty, atx + ux, width, tty_clear_line(tty, defaults, aty, atx + ux, width,
@ -1560,7 +1565,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
if (!cleared && ux < nx) { if (!cleared && ux < nx) {
log_debug("%s: %u to end of line (%zu cleared)", __func__, log_debug("%s: %u to end of line (%zu cleared)", __func__,
nx - ux, len); nx - ux, len);
tty_default_attributes(tty, defaults, palette, 8); tty_default_attributes(tty, defaults, palette, 8,
s->hyperlinks);
tty_clear_line(tty, defaults, aty, atx + ux, nx - ux, 8); tty_clear_line(tty, defaults, aty, atx + ux, nx - ux, 8);
} }
@ -1646,7 +1652,8 @@ tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx)
return; return;
} }
tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg,
ctx->s->hyperlinks);
tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy);
@ -1668,7 +1675,8 @@ tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx)
return; return;
} }
tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg,
ctx->s->hyperlinks);
tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy);
@ -1678,7 +1686,8 @@ tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx)
void void
tty_cmd_clearcharacter(struct tty *tty, const struct tty_ctx *ctx) tty_cmd_clearcharacter(struct tty *tty, const struct tty_ctx *ctx)
{ {
tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg,
ctx->s->hyperlinks);
tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, ctx->num, ctx->bg); tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, ctx->num, ctx->bg);
} }
@ -1700,7 +1709,8 @@ tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx)
return; return;
} }
tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg,
ctx->s->hyperlinks);
tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
tty_margin_off(tty); tty_margin_off(tty);
@ -1727,7 +1737,8 @@ tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx)
return; return;
} }
tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg,
ctx->s->hyperlinks);
tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
tty_margin_off(tty); tty_margin_off(tty);
@ -1740,7 +1751,8 @@ tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx)
void void
tty_cmd_clearline(struct tty *tty, const struct tty_ctx *ctx) tty_cmd_clearline(struct tty *tty, const struct tty_ctx *ctx)
{ {
tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg,
ctx->s->hyperlinks);
tty_clear_pane_line(tty, ctx, ctx->ocy, 0, ctx->sx, ctx->bg); tty_clear_pane_line(tty, ctx, ctx->ocy, 0, ctx->sx, ctx->bg);
} }
@ -1750,7 +1762,8 @@ tty_cmd_clearendofline(struct tty *tty, const struct tty_ctx *ctx)
{ {
u_int nx = ctx->sx - ctx->ocx; u_int nx = ctx->sx - ctx->ocx;
tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg,
ctx->s->hyperlinks);
tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, nx, ctx->bg); tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, nx, ctx->bg);
} }
@ -1758,7 +1771,8 @@ tty_cmd_clearendofline(struct tty *tty, const struct tty_ctx *ctx)
void void
tty_cmd_clearstartofline(struct tty *tty, const struct tty_ctx *ctx) tty_cmd_clearstartofline(struct tty *tty, const struct tty_ctx *ctx)
{ {
tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg,
ctx->s->hyperlinks);
tty_clear_pane_line(tty, ctx, ctx->ocy, 0, ctx->ocx + 1, ctx->bg); tty_clear_pane_line(tty, ctx, ctx->ocy, 0, ctx->ocx + 1, ctx->bg);
} }
@ -1784,7 +1798,8 @@ tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx)
return; return;
} }
tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg,
ctx->s->hyperlinks);
tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
tty_margin_pane(tty, ctx); tty_margin_pane(tty, ctx);
@ -1815,7 +1830,8 @@ tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx)
return; return;
} }
tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg,
ctx->s->hyperlinks);
tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
tty_margin_pane(tty, ctx); tty_margin_pane(tty, ctx);
@ -1855,7 +1871,8 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx)
return; return;
} }
tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg,
ctx->s->hyperlinks);
tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
tty_margin_pane(tty, ctx); tty_margin_pane(tty, ctx);
@ -1895,7 +1912,8 @@ tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx)
return; return;
} }
tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg,
ctx->s->hyperlinks);
tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
tty_margin_pane(tty, ctx); tty_margin_pane(tty, ctx);
@ -1914,7 +1932,8 @@ tty_cmd_clearendofscreen(struct tty *tty, const struct tty_ctx *ctx)
{ {
u_int px, py, nx, ny; u_int px, py, nx, ny;
tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg,
ctx->s->hyperlinks);
tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_region_pane(tty, ctx, 0, ctx->sy - 1);
tty_margin_off(tty); tty_margin_off(tty);
@ -1938,7 +1957,8 @@ tty_cmd_clearstartofscreen(struct tty *tty, const struct tty_ctx *ctx)
{ {
u_int px, py, nx, ny; u_int px, py, nx, ny;
tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg,
ctx->s->hyperlinks);
tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_region_pane(tty, ctx, 0, ctx->sy - 1);
tty_margin_off(tty); tty_margin_off(tty);
@ -1962,7 +1982,8 @@ tty_cmd_clearscreen(struct tty *tty, const struct tty_ctx *ctx)
{ {
u_int px, py, nx, ny; u_int px, py, nx, ny;
tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg,
ctx->s->hyperlinks);
tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_region_pane(tty, ctx, 0, ctx->sy - 1);
tty_margin_off(tty); tty_margin_off(tty);
@ -1985,7 +2006,8 @@ tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx)
return; return;
} }
tty_attributes(tty, &grid_default_cell, &ctx->defaults, ctx->palette); tty_attributes(tty, &grid_default_cell, &ctx->defaults, ctx->palette,
ctx->s->hyperlinks);
tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_region_pane(tty, ctx, 0, ctx->sy - 1);
tty_margin_off(tty); tty_margin_off(tty);
@ -2031,7 +2053,8 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx)
tty_margin_off(tty); tty_margin_off(tty);
tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy);
tty_cell(tty, ctx->cell, &ctx->defaults, ctx->palette); tty_cell(tty, ctx->cell, &ctx->defaults, ctx->palette,
ctx->s->hyperlinks);
} }
void void
@ -2062,7 +2085,7 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx)
tty_margin_off(tty); tty_margin_off(tty);
tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy);
tty_attributes(tty, ctx->cell, &ctx->defaults, ctx->palette); tty_attributes(tty, ctx->cell, &ctx->defaults, ctx->palette, ctx->s->hyperlinks);
/* Get tty position from pane position for overlay check. */ /* Get tty position from pane position for overlay check. */
px = ctx->xoff + ctx->ocx - ctx->wox; px = ctx->xoff + ctx->ocx - ctx->wox;
@ -2136,7 +2159,8 @@ tty_cmd_syncstart(struct tty *tty, const struct tty_ctx *ctx)
void void
tty_cell(struct tty *tty, const struct grid_cell *gc, tty_cell(struct tty *tty, const struct grid_cell *gc,
const struct grid_cell *defaults, struct colour_palette *palette) const struct grid_cell *defaults, struct colour_palette *palette,
struct hyperlinks *hl)
{ {
const struct grid_cell *gcp; const struct grid_cell *gcp;
@ -2152,11 +2176,11 @@ tty_cell(struct tty *tty, const struct grid_cell *gc,
/* Check the output codeset and apply attributes. */ /* Check the output codeset and apply attributes. */
gcp = tty_check_codeset(tty, gc); gcp = tty_check_codeset(tty, gc);
tty_attributes(tty, gcp, defaults, palette); tty_attributes(tty, gcp, defaults, palette, hl);
/* If it is a single character, write with putc to handle ACS. */ /* If it is a single character, write with putc to handle ACS. */
if (gcp->data.size == 1) { if (gcp->data.size == 1) {
tty_attributes(tty, gcp, defaults, palette); tty_attributes(tty, gcp, defaults, palette, hl);
if (*gcp->data.data < 0x20 || *gcp->data.data == 0x7f) if (*gcp->data.data < 0x20 || *gcp->data.data == 0x7f)
return; return;
tty_putc(tty, *gcp->data.data); tty_putc(tty, *gcp->data.data);
@ -2173,6 +2197,8 @@ tty_reset(struct tty *tty)
struct grid_cell *gc = &tty->cell; struct grid_cell *gc = &tty->cell;
if (!grid_cells_equal(gc, &grid_default_cell)) { if (!grid_cells_equal(gc, &grid_default_cell)) {
if (gc->link != 0)
tty_putcode_ptr2(tty, TTYC_HLS, "", "");
if ((gc->attr & GRID_ATTR_CHARSET) && tty_acs_needed(tty)) if ((gc->attr & GRID_ATTR_CHARSET) && tty_acs_needed(tty))
tty_putcode(tty, TTYC_RMACS); tty_putcode(tty, TTYC_RMACS);
tty_putcode(tty, TTYC_SGR0); tty_putcode(tty, TTYC_SGR0);
@ -2462,9 +2488,29 @@ out:
tty->cy = cy; tty->cy = cy;
} }
static void
tty_hyperlink(struct tty *tty, const struct grid_cell *gc,
struct hyperlinks *hl)
{
const char *uri, *id;
if (gc->link == tty->cell.link)
return;
tty->cell.link = gc->link;
if (hl == NULL)
return;
if (gc->link == 0 || !hyperlinks_get(hl, gc->link, &uri, &id))
tty_putcode_ptr2(tty, TTYC_HLS, "", "");
else
tty_putcode_ptr2(tty, TTYC_HLS, id, uri);
}
void void
tty_attributes(struct tty *tty, const struct grid_cell *gc, tty_attributes(struct tty *tty, const struct grid_cell *gc,
const struct grid_cell *defaults, struct colour_palette *palette) const struct grid_cell *defaults, struct colour_palette *palette,
struct hyperlinks *hl)
{ {
struct grid_cell *tc = &tty->cell, gc2; struct grid_cell *tc = &tty->cell, gc2;
int changed; int changed;
@ -2482,7 +2528,8 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc,
if (gc2.attr == tty->last_cell.attr && if (gc2.attr == tty->last_cell.attr &&
gc2.fg == tty->last_cell.fg && gc2.fg == tty->last_cell.fg &&
gc2.bg == tty->last_cell.bg && gc2.bg == tty->last_cell.bg &&
gc2.us == tty->last_cell.us) gc2.us == tty->last_cell.us &&
gc2.link == tty->last_cell.link)
return; return;
/* /*
@ -2559,6 +2606,9 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc,
if ((changed & GRID_ATTR_CHARSET) && tty_acs_needed(tty)) if ((changed & GRID_ATTR_CHARSET) && tty_acs_needed(tty))
tty_putcode(tty, TTYC_SMACS); tty_putcode(tty, TTYC_SMACS);
/* Set hyperlink if any. */
tty_hyperlink(tty, gc, hl);
memcpy(&tty->last_cell, &gc2, sizeof tty->last_cell); memcpy(&tty->last_cell, &gc2, sizeof tty->last_cell);
} }
@ -2924,13 +2974,13 @@ tty_default_colours(struct grid_cell *gc, struct window_pane *wp)
static void static void
tty_default_attributes(struct tty *tty, const struct grid_cell *defaults, tty_default_attributes(struct tty *tty, const struct grid_cell *defaults,
struct colour_palette *palette, u_int bg) struct colour_palette *palette, u_int bg, struct hyperlinks *hl)
{ {
struct grid_cell gc; struct grid_cell gc;
memcpy(&gc, &grid_default_cell, sizeof gc); memcpy(&gc, &grid_default_cell, sizeof gc);
gc.bg = bg; gc.bg = bg;
tty_attributes(tty, &gc, defaults, palette); tty_attributes(tty, &gc, defaults, palette, hl);
} }
static void static void