Commit c9e7cbab authored by Egmont Koblinger's avatar Egmont Koblinger

widget,emulation: Add support for OSC 8 hyperlinks (HTML-like anchors)

The escape sequences
  OSC 8 ; params ; URI BEL
  OSC 8 ; params ; URI ST
turn subsequent characters into an HTML-like anchor to the given address.
Terminate the hyperlink with the same escape sequence with an empty URI.

Params is an optional colon-separated list of key=value assignments. Cells
having the same URI and "id" parameter, or cells lacking an "id" that were
printed in a single OSC 8 run are underlined together on mouseover.

For further details, see
https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda.

https://bugzilla.gnome.org/show_bug.cgi?id=779734
parent 83674b1b
......@@ -324,6 +324,7 @@ class Window : Gtk.ApplicationWindow
if (App.Options.word_char_exceptions != null)
terminal.set_word_char_exceptions(App.Options.word_char_exceptions);
terminal.set_allow_hyperlink(!App.Options.no_hyperlink);
terminal.set_audible_bell(App.Options.audible);
terminal.set_cjk_ambiguous_width(App.Options.get_cjk_ambiguous_width());
terminal.set_cursor_blink_mode(App.Options.get_cursor_blink_mode());
......@@ -628,6 +629,9 @@ class Window : Gtk.ApplicationWindow
#if VALA_0_24
if (event != null) {
var hyperlink = terminal.hyperlink_check_event(event);
if (hyperlink != null)
menu.append("Copy _Hyperlink", "win.copy-match::" + hyperlink);
var match = terminal.match_check_event(event, null);
if (match != null)
menu.append("Copy _Match", "win.copy-match::" + match);
......@@ -834,6 +838,7 @@ class App : Gtk.Application
public static bool no_context_menu = false;
public static bool no_double_buffer = false;
public static bool no_geometry_hints = false;
public static bool no_hyperlink = false;
public static bool no_pcre = false;
public static bool no_rewrap = false;
public static bool no_shell = false;
......@@ -1021,6 +1026,8 @@ class App : Gtk.Application
"Disable double-buffering", null },
{ "no-geometry-hints", 'G', 0, OptionArg.NONE, ref no_geometry_hints,
"Allow the terminal to be resized to any dimension, not constrained to fit to an integer multiple of characters", null },
{ "no-hyperlink", 'H', 0, OptionArg.NONE, ref no_hyperlink,
"Disable hyperlinks", null },
{ "no-rewrap", 'R', 0, OptionArg.NONE, ref no_rewrap,
"Disable rewrapping on resize", null },
{ "no-shell", 'S', 0, OptionArg.NONE, ref no_shell,
......
......@@ -102,6 +102,10 @@
<title>Index of new symbols in 0.48</title>
<xi:include href="xml/api-index-0.48.xml"><xi:fallback /></xi:include>
</index>
<index id="api-index-0-50" role="0.50">
<title>Index of new symbols in 0.50</title>
<xi:include href="xml/api-index-0.50.xml"><xi:fallback /></xi:include>
</index>
<xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
......
......@@ -24,6 +24,8 @@ vte_terminal_set_audible_bell
vte_terminal_get_audible_bell
vte_terminal_set_allow_bold
vte_terminal_get_allow_bold
vte_terminal_set_allow_hyperlink
vte_terminal_get_allow_hyperlink
vte_terminal_set_scroll_on_output
vte_terminal_set_scroll_on_keystroke
vte_terminal_set_rewrap_on_resize
......@@ -54,6 +56,7 @@ vte_terminal_get_text
vte_terminal_get_text_include_trailing_spaces
vte_terminal_get_text_range
vte_terminal_get_cursor_position
vte_terminal_hyperlink_check_event
vte_terminal_match_add_regex
vte_terminal_match_remove
vte_terminal_match_remove_all
......
This diff is collapsed.
......@@ -52,7 +52,8 @@ _vte_debug_init(void)
{ "widget-size", VTE_DEBUG_WIDGET_SIZE },
{ "style", VTE_DEBUG_STYLE },
{ "resize", VTE_DEBUG_RESIZE },
{ "regex", VTE_DEBUG_REGEX }
{ "regex", VTE_DEBUG_REGEX },
{ "hyperlink", VTE_DEBUG_HYPERLINK },
};
_vte_debug_flags = g_parse_debug_string (g_getenv("VTE_DEBUG"),
......
......@@ -61,7 +61,8 @@ typedef enum {
VTE_DEBUG_WIDGET_SIZE = 1 << 21,
VTE_DEBUG_STYLE = 1 << 22,
VTE_DEBUG_RESIZE = 1 << 23,
VTE_DEBUG_REGEX = 1 << 24
VTE_DEBUG_REGEX = 1 << 24,
VTE_DEBUG_HYPERLINK = 1 << 25,
} VteDebugFlags;
void _vte_debug_init(void);
......
VOID:INT,INT
VOID:OBJECT,OBJECT
VOID:STRING,BOXED
VOID:STRING,UINT
VOID:UINT,UINT
This diff is collapsed.
......@@ -32,13 +32,15 @@
G_BEGIN_DECLS
typedef guint32 hyperlink_idx_t;
typedef struct _VteVisualPosition {
long row, col;
} VteVisualPosition;
typedef struct _VteCellAttrChange {
gsize text_end_offset; /* offset of first character no longer using this attr */
VteCellAttr attr;
VteStreamCellAttr attr;
} VteCellAttrChange;
......@@ -56,7 +58,20 @@ struct _VteRing {
gulong writable, mask;
VteRowData *array;
/* Storage */
/* Storage:
*
* row_stream contains records of VteRowRecord for each physical row.
* (This stream is regenerated when the contents rewrap on resize.)
*
* text_stream is the text in UTF-8.
*
* attr_stream contains entries that consist of:
* - a VteCellAttrChange.
* - a string of attr.hyperlink_length length containing the (typically empty) hyperlink data.
* As far as the ring is concerned, this hyperlink data is opaque. Only the caller cares that
* if nonempty, it actually contains the ID and URI separated with a semicolon. Not NUL terminated.
* - 2 bytes repeating attr.hyperlink_length so that we can walk backwards.
*/
VteStream *attr_stream, *text_stream, *row_stream;
gsize last_attr_text_start_offset;
VteCellAttr last_attr;
......@@ -67,6 +82,16 @@ struct _VteRing {
gboolean has_streams;
gulong visible_rows; /* to keep at least a screenful of lines in memory, bug 646098 comment 12 */
GPtrArray *hyperlinks; /* The hyperlink pool. Contains GString* items.
[0] points to an empty GString, [1] to [VTE_HYPERLINK_COUNT_MAX] contain the id;uri pairs. */
char hyperlink_buf[VTE_HYPERLINK_TOTAL_LENGTH_MAX + 1]; /* One more hyperlink buffer to get the value if it's not placed in the pool. */
hyperlink_idx_t hyperlink_highest_used_idx; /* 0 if no hyperlinks at all in the pool. */
hyperlink_idx_t hyperlink_current_idx; /* The hyperlink idx used for newly created cells.
Must not be GC'd even if doesn't occur onscreen. */
hyperlink_idx_t hyperlink_hover_idx; /* The hyperlink idx of the hovered cell.
An idx is allocated on hover even if the cell is scrolled out to the streams. */
gulong hyperlink_maybe_gc_counter; /* Do a GC when it reaches 65536. */
};
#define _vte_ring_contains(__ring, __position) \
......@@ -81,6 +106,9 @@ VteRowData *_vte_ring_index_writable (VteRing *ring, gulong position);
void _vte_ring_init (VteRing *ring, gulong max_rows, gboolean has_streams);
void _vte_ring_fini (VteRing *ring);
void _vte_ring_hyperlink_maybe_gc (VteRing *ring, gulong increment);
hyperlink_idx_t _vte_ring_get_hyperlink_idx (VteRing *ring, const char *hyperlink);
hyperlink_idx_t _vte_ring_get_hyperlink_at_position (VteRing *ring, gulong position, int col, bool update_hover_idx, const char **hyperlink);
long _vte_ring_reset (VteRing *ring);
void _vte_ring_resize (VteRing *ring, gulong max_rows);
void _vte_ring_shrink (VteRing *ring, gulong max_len);
......
This diff is collapsed.
......@@ -289,6 +289,12 @@ void vte_terminal_set_allow_bold(VteTerminal *terminal,
_VTE_PUBLIC
gboolean vte_terminal_get_allow_bold(VteTerminal *terminal) _VTE_GNUC_NONNULL(1);
_VTE_PUBLIC
void vte_terminal_set_allow_hyperlink(VteTerminal *terminal,
gboolean allow_hyperlink) _VTE_GNUC_NONNULL(1);
_VTE_PUBLIC
gboolean vte_terminal_get_allow_hyperlink(VteTerminal *terminal) _VTE_GNUC_NONNULL(1);
/* Check if the terminal is the current selection owner. */
_VTE_PUBLIC
gboolean vte_terminal_get_has_selection(VteTerminal *terminal) _VTE_GNUC_NONNULL(1);
......@@ -342,6 +348,10 @@ void vte_terminal_get_cursor_position(VteTerminal *terminal,
glong *column,
glong *row) _VTE_GNUC_NONNULL(1);
_VTE_PUBLIC
char *vte_terminal_hyperlink_check_event(VteTerminal *terminal,
GdkEvent *event) _VTE_GNUC_NONNULL(1) _VTE_GNUC_NONNULL(2) G_GNUC_MALLOC;
/* Add a matching expression, returning the tag the widget assigns to that
* expression. */
_VTE_PUBLIC
......
......@@ -143,6 +143,7 @@ button_pressed(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
VteTerminal *terminal;
char *match;
char *hyperlink;
int tag;
gboolean has_extra_match;
char *extra_match = NULL;
......@@ -151,6 +152,14 @@ button_pressed(GtkWidget *widget, GdkEventButton *event, gpointer data)
case 3:
terminal = VTE_TERMINAL(widget);
hyperlink = vte_terminal_hyperlink_check_event(terminal,
(GdkEvent*)event);
if (hyperlink)
g_print("Hyperlink: %s\n", hyperlink);
else
g_print("Not hyperlink\n");
g_free(hyperlink);
match = vte_terminal_match_check_event(terminal,
(GdkEvent*)event,
&tag);
......@@ -621,7 +630,8 @@ main(int argc, char **argv)
icon_title = FALSE, shell = TRUE,
reverse = FALSE, use_geometry_hints = TRUE,
use_scrolled_window = FALSE,
show_object_notifications = FALSE, rewrap = TRUE;
show_object_notifications = FALSE, rewrap = TRUE,
hyperlink = TRUE;
char *geometry = NULL;
gint lines = -1;
const char *message = "Launching interactive shell...\r\n";
......@@ -661,6 +671,11 @@ main(int argc, char **argv)
G_OPTION_ARG_NONE, &use_gregex,
"Use GRegex instead of PCRE2", NULL
},
{
"no-hyperlink", 'H', G_OPTION_FLAG_REVERSE,
G_OPTION_ARG_NONE, &hyperlink,
"Disable hyperlinks inside the terminal", NULL
},
{
"no-rewrap", 'R', G_OPTION_FLAG_REVERSE,
G_OPTION_ARG_NONE, &rewrap,
......@@ -1060,6 +1075,8 @@ main(int argc, char **argv)
pango_font_description_free(desc);
}
vte_terminal_set_allow_hyperlink(terminal, hyperlink);
/* Match "abcdefg". */
if (!no_builtin_dingus) {
add_dingus (terminal, (char **) builtin_dingus);
......
......@@ -66,6 +66,8 @@
#define VTE_SCROLLBACK_INIT 512
#define VTE_DEFAULT_CURSOR GDK_XTERM
#define VTE_MOUSING_CURSOR GDK_LEFT_PTR
#define VTE_HYPERLINK_CURSOR GDK_HAND2
#define VTE_HYPERLINK_CURSOR_DEBUG GDK_SPIDER
#define VTE_TAB_MAX 999
#define VTE_ADJUSTMENT_PRIORITY G_PRIORITY_DEFAULT_IDLE
#define VTE_INPUT_RETRY_PRIORITY G_PRIORITY_HIGH
......@@ -93,3 +95,31 @@
#define VTE_FONT_SCALE_MIN (.25)
#define VTE_FONT_SCALE_MAX (4.)
/* Maximum length of a URI in the OSC 8 escape sequences. There's no de jure limit,
* 2000-ish the de facto standard, and Internet Explorer supports 2083.
* See also the comment of VTE_HYPERLINK_TOTAL_LENGTH_MAX. */
#define VTE_HYPERLINK_URI_LENGTH_MAX 2083
/* Maximum number of URIs in the ring for a given screen (as in "normal" vs "alternate" screen)
* at a time. Idx 0 is a placeholder for no hyperlink, URIs have indexes from 1 to
* VTE_HYPERLINK_COUNT_MAX inclusive, plus one more technical idx is also required, see below.
* This is just a safety cap because the number of URIs is bound by the number of cells in the ring
* (excluding the stream) which should be way lower than this at sane window sizes.
* Make sure there are enough bits to store them in VteCellAttr.hyperlink_idx.
* Also make sure _vte_ring_hyperlink_gc() can allocate a large enough bitmap. */
#define VTE_HYPERLINK_COUNT_MAX ((1 << 20) - 2)
/* Used when thawing a row from the stream in order to display it, to denote
* hyperlinks whose target is currently irrelevant.
* Make sure there are enough bits to store this in VteCellAttr.hyperlink_idx */
#define VTE_HYPERLINK_IDX_TARGET_IN_STREAM (VTE_HYPERLINK_COUNT_MAX + 1)
/* Max length allowed in the id= parameter of an OSC 8 sequence.
* See also the comment of VTE_HYPERLINK_TOTAL_LENGTH_MAX. */
#define VTE_HYPERLINK_ID_LENGTH_MAX 250
/* Max length of all the hyperlink data stored in the streams as a string.
* Currently the hyperlink data is the ID and URI and a separator in between.
* Make sure there are enough bits to store this in VteStreamCellAttr.hyperlink_length */
#define VTE_HYPERLINK_TOTAL_LENGTH_MAX (VTE_HYPERLINK_ID_LENGTH_MAX + 1 + VTE_HYPERLINK_URI_LENGTH_MAX)
......@@ -417,6 +417,9 @@ vte_terminal_get_property (GObject *object,
case PROP_ALLOW_BOLD:
g_value_set_boolean (value, vte_terminal_get_allow_bold (terminal));
break;
case PROP_ALLOW_HYPERLINK:
g_value_set_boolean (value, vte_terminal_get_allow_hyperlink (terminal));
break;
case PROP_AUDIBLE_BELL:
g_value_set_boolean (value, vte_terminal_get_audible_bell (terminal));
break;
......@@ -450,6 +453,9 @@ vte_terminal_get_property (GObject *object,
case PROP_FONT_SCALE:
g_value_set_double (value, vte_terminal_get_font_scale (terminal));
break;
case PROP_HYPERLINK_HOVER_URI:
g_value_set_string (value, impl->m_hyperlink_hover_uri);
break;
case PROP_ICON_TITLE:
g_value_set_string (value, vte_terminal_get_icon_title (terminal));
break;
......@@ -512,6 +518,9 @@ vte_terminal_set_property (GObject *object,
case PROP_ALLOW_BOLD:
vte_terminal_set_allow_bold (terminal, g_value_get_boolean (value));
break;
case PROP_ALLOW_HYPERLINK:
vte_terminal_set_allow_hyperlink (terminal, g_value_get_boolean (value));
break;
case PROP_AUDIBLE_BELL:
vte_terminal_set_audible_bell (terminal, g_value_get_boolean (value));
break;
......@@ -567,6 +576,7 @@ vte_terminal_set_property (GObject *object,
/* Not writable */
case PROP_CURRENT_DIRECTORY_URI:
case PROP_CURRENT_FILE_URI:
case PROP_HYPERLINK_HOVER_URI:
case PROP_ICON_TITLE:
case PROP_WINDOW_TITLE:
g_assert_not_reached ();
......@@ -797,6 +807,33 @@ vte_terminal_class_init(VteTerminalClass *klass)
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
/**
* VteTerminal::hyperlink-hover-changed:
* @vteterminal: the object which received the signal
* @uri: the nonempty target URI under the mouse, or NULL
* @bbox: the bounding box of the hyperlink anchor text, or NULL
*
* Emitted when the hovered hyperlink changes.
*
* @uri and @bbox are owned by VTE, must not be modified, and might
* change after the signal handlers returns.
*
* The signal is not re-emitted when the bounding box changes for the
* same hyperlink. This might change in a future VTE version without notice.
*
* Since: 0.50
*/
signals[SIGNAL_HYPERLINK_HOVER_URI_CHANGED] =
g_signal_new(I_("hyperlink-hover-uri-changed"),
G_OBJECT_CLASS_TYPE(klass),
G_SIGNAL_RUN_LAST,
0,
NULL,
NULL,
_vte_marshal_VOID__STRING_BOXED,
G_TYPE_NONE,
2, G_TYPE_STRING, GDK_TYPE_RECTANGLE | G_SIGNAL_TYPE_STATIC_SCOPE);
/**
* VteTerminal::encoding-changed:
* @vteterminal: the object which received the signal
......@@ -1218,6 +1255,18 @@ vte_terminal_class_init(VteTerminalClass *klass)
TRUE,
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
/**
* VteTerminal:allow-hyperlink:
*
* Controls whether or not hyperlinks (OSC 8 escape sequence) are recognized and displayed.
*
* Since: 0.50
*/
pspecs[PROP_ALLOW_HYPERLINK] =
g_param_spec_boolean ("allow-hyperlink", NULL, NULL,
TRUE,
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
/**
* VteTerminal:audible-bell:
*
......@@ -1454,6 +1503,18 @@ vte_terminal_class_init(VteTerminalClass *klass)
NULL,
(GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
/**
* VteTerminal:hyperlink-hover-uri:
*
* The currently hovered hyperlink URI, or %NULL if unset.
*
* Since: 0.50
*/
pspecs[PROP_HYPERLINK_HOVER_URI] =
g_param_spec_string ("hyperlink-hover-uri", NULL, NULL,
NULL,
(GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
/**
* VteTerminal:word-char-exceptions:
*
......@@ -1803,6 +1864,30 @@ vte_terminal_match_check_event(VteTerminal *terminal,
return IMPL(terminal)->regex_match_check(event, tag);
}
/**
* vte_terminal_hyperlink_check_event:
* @terminal: a #VteTerminal
* @event: a #GdkEvent
*
* Returns a nonempty string: the target of the explicit hyperlink (printed using the OSC 8
* escape sequence) at the position of the event, or %NULL.
*
* Proper use of the escape sequence should result in URI-encoded URIs with a proper scheme
* like "http://", "https://", "file://", "mailto:" etc. This is, however, not enforced by VTE.
* The caller must tolerate the returned string potentially not being a valid URI.
*
* Returns: (transfer full): a newly allocated string containing the target of the hyperlink
*
* Since: 0.50
*/
char *
vte_terminal_hyperlink_check_event(VteTerminal *terminal,
GdkEvent *event)
{
g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE);
return IMPL(terminal)->hyperlink_check(event);
}
/**
* vte_terminal_event_check_regex_simple:
* @terminal: a #VteTerminal
......@@ -2743,6 +2828,42 @@ vte_terminal_set_allow_bold(VteTerminal *terminal,
g_object_notify_by_pspec(G_OBJECT(terminal), pspecs[PROP_ALLOW_BOLD]);
}
/**
* vte_terminal_get_allow_hyperlink:
* @terminal: a #VteTerminal
*
* Checks whether or not hyperlinks (OSC 8 escape sequence) are allowed.
*
* Returns: %TRUE if hyperlinks are enabled, %FALSE if not
*
* Since: 0.50
*/
gboolean
vte_terminal_get_allow_hyperlink(VteTerminal *terminal)
{
g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE);
return IMPL(terminal)->m_allow_hyperlink;
}
/**
* vte_terminal_set_allow_hyperlink:
* @terminal: a #VteTerminal
* @allow_hyperlink: %TRUE if the terminal should allow hyperlinks
*
* Controls whether or not hyperlinks (OSC 8 escape sequence) are allowed.
*
* Since: 0.50
*/
void
vte_terminal_set_allow_hyperlink(VteTerminal *terminal,
gboolean allow_hyperlink)
{
g_return_if_fail(VTE_IS_TERMINAL(terminal));
if (IMPL(terminal)->set_allow_hyperlink(allow_hyperlink != FALSE))
g_object_notify_by_pspec(G_OBJECT(terminal), pspecs[PROP_ALLOW_HYPERLINK]);
}
/**
* vte_terminal_get_audible_bell:
* @terminal: a #VteTerminal
......
......@@ -39,6 +39,7 @@ enum {
SIGNAL_DEICONIFY_WINDOW,
SIGNAL_ENCODING_CHANGED,
SIGNAL_EOF,
SIGNAL_HYPERLINK_HOVER_URI_CHANGED,
SIGNAL_ICON_TITLE_CHANGED,
SIGNAL_ICONIFY_WINDOW,
SIGNAL_INCREASE_FONT_SIZE,
......@@ -63,6 +64,7 @@ extern guint signals[LAST_SIGNAL];
enum {
PROP_0,
PROP_ALLOW_BOLD,
PROP_ALLOW_HYPERLINK,
PROP_AUDIBLE_BELL,
PROP_BACKSPACE_BINDING,
PROP_CJK_AMBIGUOUS_WIDTH,
......@@ -74,6 +76,7 @@ enum {
PROP_ENCODING,
PROP_FONT_DESC,
PROP_FONT_SCALE,
PROP_HYPERLINK_HOVER_URI,
PROP_ICON_TITLE,
PROP_INPUT_ENABLED,
PROP_MOUSE_POINTER_AUTOHIDE,
......
......@@ -479,6 +479,7 @@ public:
gboolean m_mouse_cursor_visible; /* derived value really containing if it's actually visible */
GdkCursor* m_mouse_default_cursor;
GdkCursor* m_mouse_mousing_cursor;
GdkCursor* m_mouse_hyperlink_cursor;
GdkCursor* m_mouse_inviso_cursor;
/* Input method support. */
......@@ -530,6 +531,12 @@ public:
guint m_hscroll_policy : 1; /* unused */
guint m_vscroll_policy : 1;
/* Hyperlinks */
gboolean m_allow_hyperlink;
hyperlink_idx_t m_hyperlink_hover_idx;
const char *m_hyperlink_hover_uri; /* data is owned by the ring */
long m_hyperlink_auto_id;
public:
// FIXMEchpe inline!
......@@ -692,6 +699,7 @@ public:
bool italic,
bool underline,
bool strikethrough,
bool hyperlink,
bool hilite,
bool boxed,
int column_width,
......@@ -887,7 +895,7 @@ public:
bool cell_is_selected(vte::grid::column_t col,
vte::grid::row_t) const;
void reset_default_attributes();
void reset_default_attributes(bool reset_hyperlink);
void ensure_font();
void update_font();
......@@ -955,12 +963,17 @@ public:
guint rows);
void emit_copy_clipboard();
void emit_paste_clipboard();
void emit_hyperlink_hover_uri_changed(const GdkRectangle *bbox);
void clear_tabstop(int column); // FIXMEchpe vte::grid::column_t ?
bool get_tabstop(int column);
void set_tabstop(int column);
void set_default_tabstops();
void hyperlink_invalidate_and_get_bbox(hyperlink_idx_t idx, GdkRectangle *bbox);
void hyperlink_hilite_update(vte::view::coords const& pos);
void hyperlink_hilite(vte::view::coords const& pos);
void match_contents_clear();
void match_contents_refresh();
void set_cursor_from_regex_match(struct vte_match_regex *regex);
......@@ -975,6 +988,8 @@ public:
long *column,
long *row);
char *hyperlink_check(GdkEvent *event);
bool regex_match_check_extra(GdkEvent *event,
VteRegex **regexes,
gsize n_regexes,
......@@ -1076,6 +1091,7 @@ public:
bool set_audible_bell(bool setting);
bool set_allow_bold(bool setting);
bool set_allow_hyperlink(bool setting);
bool set_backspace_binding(VteEraseBinding binding);
bool set_background_alpha(double alpha);
bool set_cjk_ambiguous_width(int width);
......@@ -1176,6 +1192,7 @@ public:
inline void seq_send_secondary_device_attributes();
inline void set_current_directory_uri_changed(char* uri /* adopted */);
inline void set_current_file_uri_changed(char* uri /* adopted */);
inline void set_current_hyperlink(char* hyperlink_params /* adopted */, char* uri /* adopted */);
inline void set_keypad_mode(VteKeymode mode);
inline void seq_erase_in_display(long param);
inline void seq_erase_in_line(long param);
......
......@@ -21,6 +21,8 @@
#ifndef vterowdata_h_included
#define vterowdata_h_included
#include <string.h>
#include "vteunistr.h"
#include "vtemacros.h"
#include "vtedefines.hh"
......@@ -30,13 +32,17 @@ G_BEGIN_DECLS
#define VTE_TAB_WIDTH_BITS 4 /* Has to be able to store the value of 8. */
#define VTE_TAB_WIDTH_MAX ((1 << VTE_TAB_WIDTH_BITS) - 1)
#define VTE_CELL_ATTR_COMMON_BYTES 8 /* The number of common bytes in VteCellAttr and VteStreamCellAttr */
/*
* VteCellAttr: A single cell style attributes
*
* Ordered by most commonly changed attributes, to
* optimize the compact representation.
*
* When adding new attributes, remember to update basic_cell below too.
* When adding new attributes, keep in sync with VteStreamCellAttr and
* update VTE_CELL_ATTR_COMMON_BYTES accordingly.
* Also don't forget to update basic_cell below!
*/
typedef struct _VteCellAttr {
......@@ -59,9 +65,38 @@ typedef struct _VteCellAttr {
guint64 dim: 1; /* also known as faint, half intensity etc. */
guint64 invisible: 1;
/* 1 bit unused */
guint64 padding_unused_1: 1;
/* 8-byte boundary */
guint32 hyperlink_idx; /* a unique hyperlink index at a time for the ring's cells,
0 means not a hyperlink, VTE_HYPERLINK_IDX_TARGET_IN_STREAM
means the target is irrelevant/unknown at the moment.
If bitpacking, choose a size big enough to hold a different idx
for every cell in the ring but not yet in the stream
(currently the height rounded up to the next power of two, times width)
for supported VTE sizes, and update VTE_HYPERLINK_IDX_TARGET_IN_STREAM. */
guint32 padding_unused_2;
} VteCellAttr;
G_STATIC_ASSERT (sizeof (VteCellAttr) == 8);
G_STATIC_ASSERT (sizeof (VteCellAttr) == 16);
G_STATIC_ASSERT (offsetof (VteCellAttr, hyperlink_idx) == VTE_CELL_ATTR_COMMON_BYTES);
/*
* VteStreamCellAttr: Variant of VteCellAttr to be stored in attr_stream.
*
* When adding new attributes, keep in sync with VteCellAttr and
* update VTE_CELL_ATTR_COMMON_BYTES accordingly.
*/
typedef struct _VTE_GNUC_PACKED _VteStreamCellAttr {
guint64 fragment: 1;
guint64 columns: VTE_TAB_WIDTH_BITS;
guint64 remaining_main_attributes: 59; /* All the non-hyperlink related attributes from VteCellAttr.
We don't individually access them in the stream, so there's
no point in repeating each field separately. */
/* 8-byte boundary */
guint16 hyperlink_length; /* make sure it fits VTE_HYPERLINK_TOTAL_LENGTH_MAX */
} VteStreamCellAttr;
G_STATIC_ASSERT (sizeof (VteStreamCellAttr) == 10);
G_STATIC_ASSERT (offsetof (VteStreamCellAttr, hyperlink_length) == VTE_CELL_ATTR_COMMON_BYTES);
/*
* VteCell: A single cell's data
......@@ -71,7 +106,7 @@ typedef struct _VTE_GNUC_PACKED _VteCell {
vteunistr c;
VteCellAttr attr;
} VteCell;
G_STATIC_ASSERT (sizeof (VteCell) == 12);
G_STATIC_ASSERT (sizeof (VteCell) == 20);
static const VteCell basic_cell = {
0,
......@@ -90,7 +125,10 @@ static const VteCell basic_cell = {
0, /* blink */
0, /* half */
0 /* invisible */
0, /* invisible */
0, /* padding_unused_1 */
0, /* hyperlink_idx */
0, /* padding_unused_2 */
}
};
......@@ -135,6 +173,15 @@ _vte_row_data_get_writable (VteRowData *row, gulong col)
return &row->cells[col];
}
/*
* Copy the common attributes from VteCellAttr to VteStreamCellAttr or vice versa.
*/
static inline void
_attrcpy (void *dst, void *src)
{
memcpy(dst, src, VTE_CELL_ATTR_COMMON_BYTES);
}
void _vte_row_data_init (VteRowData *row);
void _vte_row_data_clear (VteRowData *row);
void _vte_row_data_fini (VteRowData *row);
......
/*
* Copyright (C) 2001-2004 Red Hat, Inc.
*
......@@ -387,6 +388,17 @@ VteTerminalPrivate::seq_switch_screen(VteScreen *new_screen)
{
/* if (new_screen == m_screen) return; ? */
/* The two screens use different hyperlink pools, so carrying on the idx
* wouldn't make sense and could lead to crashes.
* Ideally we'd carry the target URI itself, but I'm just lazy.
* Also, run a GC before we switch away from that screen. */
m_hyperlink_hover_idx = _vte_ring_get_hyperlink_at_position(m_screen->row_data, -1, -1, true, NULL);
g_assert (m_hyperlink_hover_idx == 0);
m_hyperlink_hover_uri = NULL;
emit_hyperlink_hover_uri_changed(NULL); /* FIXME only emit if really changed */
m_defaults.attr.hyperlink_idx = _vte_ring_get_hyperlink_idx(m_screen->row_data, NULL);
g_assert (m_defaults.attr.hyperlink_idx == 0);
/* cursor.row includes insert_delta, adjust accordingly */
auto cr = m_screen->cursor.row - m_screen->insert_delta;
m_screen = new_screen;
......@@ -2127,7 +2139,7 @@ vte_sequence_handler_character_attributes (VteTerminalPrivate *that, GValueArray
param = g_value_get_long(value);
switch (param) {
case 0:
that->reset_default_attributes();
that->reset_default_attributes(false);
break;
case 1:
that->m_defaults.attr.bold = 1;
......@@ -2271,7 +2283,7 @@ vte_sequence_handler_character_attributes (VteTerminalPrivate *that, GValueArray
}
/* If we had no parameters, default to the defaults. */
if (i == 0) {
that->reset_default_attributes();
that->reset_default_attributes(false);
}
/* Save the new colors. */
that->m_color_defaults.attr.fore = that->m_defaults.attr.fore;
......@@ -2450,10 +2462,106 @@ VteTerminalPrivate::set_current_file_uri_changed(char* uri /* adopted */)
m_current_file_uri_changed = uri;
}
/* Handle OSC 8 hyperlinks.
* See bug 779734 and https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda. */
static void
vte_sequence_handler_set_current_hyperlink (VteTerminalPrivate *that, GValueArray *params)
{
/* Accept but ignore to prepare for the forthcoming hyperlink feature (bug #779734) */
GValue *value;
char *hyperlink_params;
char *uri;
hyperlink_params = NULL;
uri = NULL;
if (params != NULL && params->n_values > 1) {
value = g_value_array_get_nth(params, 0);
if (G_VALUE_HOLDS_POINTER(value)) {
hyperlink_params = that->ucs4_to_utf8((const guchar *)g_value_get_pointer (value));
} else if (G_VALUE_HOLDS_STRING(value)) {
/* Copy the string into the buffer. */
hyperlink_params = g_value_dup_string(value);
}
value = g_value_array_get_nth(params, 1);
if (G_VALUE_HOLDS_POINTER(value)) {
uri = that->ucs4_to_utf8((const guchar *)g_value_get_pointer (value));
} else if (G_VALUE_HOLDS_STRING(value)) {
/* Copy the string into the buffer. */
uri = g_value_dup_string(value);
}
}
that->set_current_hyperlink(hyperlink_params, uri);
}
void
VteTerminalPrivate::set_current_hyperlink(char *hyperlink_params /* adopted */, char* uri /* adopted */)
{
guint idx;
char *id = NULL;
char idbuf[24];
if (!m_allow_hyperlink)
return;
/* Get the "id" parameter */
if (hyperlink_params) {
if (strncmp(hyperlink_params, "id=", 3) == 0) {
id = hyperlink_params + 3;
} else {
id = strstr(hyperlink_params, ":id=");
if (id)
id += 4;