diff --git a/src/caps.cc b/src/caps.cc
index 4757126..af30e40 100644
--- a/src/caps.cc
+++ b/src/caps.cc
@@ -229,6 +229,7 @@ const char _vte_xterm_capability_strings[] =
         ENTRY(OSC "4;%s" BEL, "change-color-bel")
         ENTRY(OSC "6;%s" BEL, "set-current-file-uri")
         ENTRY(OSC "7;%s" BEL, "set-current-directory-uri")
+        ENTRY(OSC "8;%s" BEL, "set-current-hyperlink")
         ENTRY(OSC "10;%s" BEL, "change-foreground-color-bel")
         ENTRY(OSC "11;%s" BEL, "change-background-color-bel")
         ENTRY(OSC "12;%s" BEL, "change-cursor-background-color-bel")
@@ -266,6 +267,7 @@ const char _vte_xterm_capability_strings[] =
         ENTRY(OSC "4;%s" ST, "change-color-st")
         ENTRY(OSC "6;%s" ST, "set-current-file-uri")
         ENTRY(OSC "7;%s" ST, "set-current-directory-uri")
+        ENTRY(OSC "8;%s" ST, "set-current-hyperlink")
         ENTRY(OSC "10;%s" ST, "change-foreground-color-st")
         ENTRY(OSC "11;%s" ST, "change-background-color-st")
         ENTRY(OSC "12;%s" ST, "change-cursor-background-color-st")
diff --git a/src/ring.cc b/src/ring.cc
index d23f191..b3e8e6b 100644
--- a/src/ring.cc
+++ b/src/ring.cc
@@ -72,7 +72,7 @@ _vte_ring_init (VteRing *ring, gulong max_rows, gboolean has_streams)
 	}
 
 	ring->last_attr_text_start_offset = 0;
-	ring->last_attr.i = basic_cell.i.attr;
+	memcpy(&ring->last_attr.s, &basic_cell.cell.attr, sizeof(VteCellAttr));
 	ring->utf8_buffer = g_string_sized_new (128);
 
 	_vte_row_data_init (&ring->cached_row);
@@ -167,7 +167,7 @@ _vte_ring_freeze_row (VteRing *ring, gulong position, const VteRowData *row)
 		if (G_LIKELY (!attr.s.fragment)) {
 			VteCellAttrChange attr_change;
 
-			if (ring->last_attr.i != attr.i) {
+			if (memcmp(&ring->last_attr.s, &attr.s, sizeof(VteCellAttr)) != 0) {
 				ring->last_attr_text_start_offset = record.text_start_offset + buffer->len;
 				memset(&attr_change, 0, sizeof (attr_change));
 				attr_change.text_end_offset = ring->last_attr_text_start_offset;
@@ -305,7 +305,7 @@ _vte_ring_thaw_row (VteRing *ring, gulong position, VteRowData *row, gboolean do
 				}
 			} else {
 				ring->last_attr_text_start_offset = 0;
-				ring->last_attr.i = basic_cell.i.attr;
+				memcpy(&ring->last_attr.s, &basic_cell.cell.attr, sizeof(VteCellAttr));
 			}
 		}
 		_vte_stream_truncate (ring->row_stream, position * sizeof (record));
@@ -326,7 +326,7 @@ _vte_ring_reset_streams (VteRing *ring, gulong position)
 	}
 
 	ring->last_attr_text_start_offset = 0;
-	ring->last_attr.i = basic_cell.i.attr;
+	memcpy(&ring->last_attr.s, &basic_cell.cell.attr, sizeof(VteCellAttr));
 }
 
 long
diff --git a/src/vte.cc b/src/vte.cc
index d8fc4fc..0a04ea3 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -1818,6 +1818,29 @@ VteTerminalPrivate::regex_match_check(GdkEvent *event,
         return regex_match_check(col, row - (long)m_screen->scroll_delta, tag);
 }
 
+char *
+VteTerminalPrivate::get_hyperlink(GdkEvent *event)
+{
+        long col, row;
+        VteRowData const* row_data;
+        VteCell const* cell;
+
+        if (!rowcol_from_event(event, &col, &row))
+                return NULL;
+
+        row_data = find_row_data(row);
+        if (!row_data)
+                return NULL;
+        cell = _vte_row_data_get (row_data, col);
+        if (!cell)
+                return NULL;
+
+        if (cell->attr.hyperlink[0] != '\0')
+                return g_strdup(cell->attr.hyperlink);
+        else
+                return NULL;
+}
+
 bool
 VteTerminalPrivate::regex_match_check_extra(GdkEvent *event,
                                             VteRegex **regexes,
@@ -5985,7 +6008,8 @@ vte_terminal_cellattr_equal(VteCellAttr const *attr1,
 	        attr1->strikethrough == attr2->strikethrough &&
 	        attr1->reverse       == attr2->reverse   &&
 	        attr1->blink         == attr2->blink     &&
-	        attr1->invisible     == attr2->invisible);
+	        attr1->invisible     == attr2->invisible &&
+	        strcmp(attr1->hyperlink, attr2->hyperlink) == 0);
 }
 
 /*
@@ -8561,6 +8585,7 @@ VteTerminalPrivate::draw_cells(struct _vte_draw_text_request *items,
                                bool italic,
                                bool underline,
                                bool strikethrough,
+                               bool hyperlink,
                                bool hilite,
                                bool boxed,
                                int column_width,
@@ -8614,7 +8639,7 @@ VteTerminalPrivate::draw_cells(struct _vte_draw_text_request *items,
 			_vte_draw_get_style(bold, italic));
 
 	/* Draw whatever SFX are required. */
-	if (underline | strikethrough | hilite | boxed) {
+	if (underline | strikethrough | hyperlink | hilite | boxed) {
 		i = 0;
 		do {
 			x = items[i].x;
@@ -8640,6 +8665,16 @@ VteTerminalPrivate::draw_cells(struct _vte_draw_text_request *items,
                                                        VTE_LINE_WIDTH,
                                                        &fg, VTE_DRAW_OPAQUE);
 			}
+			if (hyperlink) {
+				for (double j = 0.125; j < columns; j += 0.5) {
+				        _vte_draw_fill_rectangle(m_draw,
+				                                 x + j * column_width,
+				                                 y + row_height - 1,
+				                                 column_width * 0.25,
+				                                 1,
+				                                 &fg, VTE_DRAW_OPAQUE);
+				}
+			}
 			if (hilite) {
                                 _vte_draw_draw_line(m_draw,
 						x,
@@ -8879,6 +8914,7 @@ VteTerminalPrivate::draw_cells_with_attributes(struct _vte_draw_text_request *it
 					cells[j].attr.italic,
 					cells[j].attr.underline,
 					cells[j].attr.strikethrough,
+					cells[j].attr.hyperlink[0] != '\0',
 					FALSE, FALSE, column_width, height);
 		j += g_unichar_to_utf8(items[i].c, scratch_buf);
 	}
@@ -8905,7 +8941,7 @@ VteTerminalPrivate::draw_rows(VteScreen *screen_,
         vte::grid::column_t i, j;
         long x, y;
 	guint fore, nfore, back, nback;
-	gboolean underline, nunderline, bold, nbold, italic, nitalic, hilite, nhilite,
+	gboolean underline, nunderline, bold, nbold, italic, nitalic, hyperlink, nhyperlink, hilite, nhilite,
 		 selected, nselected, strikethrough, nstrikethrough;
 	guint item_count;
 	const VteCell *cell;
@@ -9040,7 +9076,8 @@ VteTerminalPrivate::draw_rows(VteScreen *screen_,
 			while (cell->c == 0 || cell->attr.invisible ||
 					(cell->c == ' ' &&
 					 !cell->attr.underline &&
-					 !cell->attr.strikethrough) ||
+					 !cell->attr.strikethrough &&
+					 cell->attr.hyperlink[0] == '\0') ||
 					cell->attr.fragment) {
 				if (++i >= end_column) {
 					goto fg_skip_row;
@@ -9055,6 +9092,7 @@ VteTerminalPrivate::draw_rows(VteScreen *screen_,
 			determine_colors(cell, selected, &fore, &back);
 			underline = cell->attr.underline;
 			strikethrough = cell->attr.strikethrough;
+			hyperlink = (cell->attr.hyperlink[0] != '\0');
 			bold = cell->attr.bold;
 			italic = cell->attr.italic;
 			if (m_show_match) {
@@ -9089,7 +9127,7 @@ VteTerminalPrivate::draw_rows(VteScreen *screen_,
 						/* only break the run if we
 						 * are drawing attributes
 						 */
-						if (underline || strikethrough || hilite) {
+						if (underline || strikethrough || hyperlink || hilite) {
 							break;
 						} else {
 							j++;
@@ -9121,6 +9159,10 @@ VteTerminalPrivate::draw_rows(VteScreen *screen_,
 					if (nstrikethrough != strikethrough) {
 						break;
 					}
+					nhyperlink = (cell->attr.hyperlink[0] != '\0');
+					if (nhyperlink != hyperlink) {
+						break;
+					}
 					/* Break up matched/not-matched text. */
 					nhilite = false;
 					if (m_show_match) {
@@ -9172,7 +9214,7 @@ fg_draw:
 					item_count,
 					fore, back, FALSE, FALSE,
 					bold, italic, underline,
-					strikethrough, hilite, FALSE,
+					strikethrough, hyperlink, hilite, FALSE,
 					column_width, row_height);
 			item_count = 1;
 			/* We'll need to continue at the first cell which didn't
@@ -9377,6 +9419,7 @@ VteTerminalPrivate::paint_cursor()
                                                         cell->attr.italic,
                                                         cell->attr.underline,
                                                         cell->attr.strikethrough,
+                                                        cell->attr.hyperlink[0] != '\0',
                                                         FALSE,
                                                         FALSE,
                                                         width,
@@ -9463,6 +9506,7 @@ VteTerminalPrivate::paint_im_preedit_string()
 						FALSE,
 						FALSE,
 						FALSE,
+						FALSE,
 						TRUE,
 						width, height);
 		}
diff --git a/src/vte/vteterminal.h b/src/vte/vteterminal.h
index 3b22c4a..9fcdec1 100644
--- a/src/vte/vteterminal.h
+++ b/src/vte/vteterminal.h
@@ -371,6 +371,9 @@ char *vte_terminal_match_check_event(VteTerminal *terminal,
                                      GdkEvent *event,
                                      int *tag) _VTE_GNUC_NONNULL(1) _VTE_GNUC_NONNULL(2) G_GNUC_MALLOC;
 _VTE_PUBLIC
+char *vte_terminal_get_hyperlink(VteTerminal *terminal,
+                                 GdkEvent *event) _VTE_GNUC_NONNULL(1) _VTE_GNUC_NONNULL(2);
+_VTE_PUBLIC
 gboolean vte_terminal_event_check_regex_simple(VteTerminal *terminal,
                                                GdkEvent *event,
                                                VteRegex **regexes,
diff --git a/src/vteapp.c b/src/vteapp.c
index 71d0e61..3ac80ee 100644
--- a/src/vteapp.c
+++ b/src/vteapp.c
@@ -143,6 +143,7 @@ button_pressed(GtkWidget *widget, GdkEventButton *event, gpointer data)
 {
 	VteTerminal *terminal;
 	char *match;
+	char *hyperlink;
 	int tag;
 	GtkBorder padding;
         gboolean has_extra_match;
@@ -188,6 +189,15 @@ button_pressed(GtkWidget *widget, GdkEventButton *event, gpointer data)
                 else
                         g_print("Extra regex didn't match\n");
                 g_free(extra_match);
+
+		hyperlink = vte_terminal_get_hyperlink(terminal,
+		                                       (GdkEvent*)event);
+		if (hyperlink)
+			g_print("Hyperlink: %s\n", hyperlink);
+		else
+			g_print("Not hyperlink\n");
+		g_free(hyperlink);
+
 		break;
 	case 1:
 	case 2:
diff --git a/src/vtegtk.cc b/src/vtegtk.cc
index 21282e1..6e7a8f2 100644
--- a/src/vtegtk.cc
+++ b/src/vtegtk.cc
@@ -1804,6 +1804,23 @@ vte_terminal_match_check_event(VteTerminal *terminal,
 }
 
 /**
+ * vte_terminal_get_hyperlink:
+ * @terminal: a #VteTerminal
+ * @event: a #GdkEvent
+ *
+ * Blahblah...
+ *
+ * Returns: (transfer full): blah
+ */
+char *
+vte_terminal_get_hyperlink(VteTerminal *terminal,
+                           GdkEvent *event)
+{
+        g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE);
+        return IMPL(terminal)->get_hyperlink(event);
+}
+
+/**
  * vte_terminal_event_check_regex_simple:
  * @terminal: a #VteTerminal
  * @event: a #GdkEvent
diff --git a/src/vteinternal.hh b/src/vteinternal.hh
index ff5b93c..8fd4e99 100644
--- a/src/vteinternal.hh
+++ b/src/vteinternal.hh
@@ -690,6 +690,7 @@ public:
                         bool italic,
                         bool underline,
                         bool strikethrough,
+                        bool hyperlink,
                         bool hilite,
                         bool boxed,
                         int column_width,
@@ -985,6 +986,7 @@ public:
                                 int *tag);
         char *regex_match_check(GdkEvent *event,
                                 int *tag);
+        char *get_hyperlink(GdkEvent *event);
         void regex_match_remove(int tag);
         void regex_match_remove_all();
         void regex_match_set_cursor(int tag,
@@ -1173,6 +1175,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* uri);
         inline void set_keypad_mode(VteKeymode mode);
         inline void seq_erase_in_display(long param);
         inline void seq_erase_in_line(long param);
diff --git a/src/vterowdata.h b/src/vterowdata.h
index c9f710e..500629d 100644
--- a/src/vterowdata.h
+++ b/src/vterowdata.h
@@ -59,13 +59,14 @@ typedef struct _VteCellAttr {
 	guint64 dim: 1;		/* also known as faint, half intensity etc. */
 
 	guint64 invisible: 1;
-        /* 1 bit unused */
+        guint64 unused: 1;      /* 1 bit unused */
+        gchar hyperlink[256];
 } VteCellAttr;
-G_STATIC_ASSERT (sizeof (VteCellAttr) == 8);
+G_STATIC_ASSERT (sizeof (VteCellAttr) == 8+256);
 
 typedef union _VteIntCellAttr {
 	VteCellAttr s;
-	guint64 i;
+	guint64 i_dontuseme;
 } VteIntCellAttr;
 G_STATIC_ASSERT (sizeof (VteCellAttr) == sizeof (VteIntCellAttr));
 
@@ -77,14 +78,14 @@ typedef struct _VTE_GNUC_PACKED _VteCell {
 	vteunistr c;
 	VteCellAttr attr;
 } VteCell;
-G_STATIC_ASSERT (sizeof (VteCell) == 12);
+G_STATIC_ASSERT (sizeof (VteCell) == 12+256);
 
 typedef union _VteIntCell {
 	VteCell cell;
 	struct _VTE_GNUC_PACKED {
 		guint32 c;
 		guint64 attr;
-	} i;
+	} i_dontuseme;
 } VteIntCell;
 G_STATIC_ASSERT (sizeof (VteCell) == sizeof (VteIntCell));
 
@@ -106,7 +107,24 @@ static const VteIntCell basic_cell = {
 			0, /* blink */
 			0, /* half */
 
-			0  /* invisible */
+			0, /* invisible */
+			0, /* unused */
+			"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+			"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+			"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+			"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+			"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+			"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+			"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+			"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+			"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+			"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+			"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+			"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+			"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+			"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+			"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+			"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" /* hyperlink */
 		}
 	}
 };
diff --git a/src/vteseq-n.gperf b/src/vteseq-n.gperf
index 573f045..3e60835 100644
--- a/src/vteseq-n.gperf
+++ b/src/vteseq-n.gperf
@@ -169,5 +169,6 @@ struct vteseq_n_struct {
 #"reset-mouse-cursor-foreground-color", VTE_SEQUENCE_HANDLER_NULL
 "set-current-directory-uri", VTE_SEQUENCE_HANDLER(vte_sequence_handler_set_current_directory_uri)
 "set-current-file-uri", VTE_SEQUENCE_HANDLER(vte_sequence_handler_set_current_file_uri)
+"set-current-hyperlink", VTE_SEQUENCE_HANDLER(vte_sequence_handler_set_current_hyperlink)
 "urxvt-777", VTE_SEQUENCE_HANDLER(vte_sequence_handler_urxvt_777)
 "iterm2-1337", VTE_SEQUENCE_HANDLER(vte_sequence_handler_iterm2_1337)
diff --git a/src/vteseq.cc b/src/vteseq.cc
index 25e697b..4b5c6e3 100644
--- a/src/vteseq.cc
+++ b/src/vteseq.cc
@@ -2086,6 +2086,7 @@ vte_sequence_handler_character_attributes (VteTerminalPrivate *that, GValueArray
 	unsigned int i;
 	GValue *value;
 	long param;
+	char hyperlink_save[256];
 	/* The default parameter is zero. */
 	param = 0;
 	/* Step through each numeric parameter. */
@@ -2126,7 +2127,9 @@ vte_sequence_handler_character_attributes (VteTerminalPrivate *that, GValueArray
 		param = g_value_get_long(value);
 		switch (param) {
 		case 0:
+			strcpy(hyperlink_save, that->m_defaults.attr.hyperlink);
 			that->reset_default_attributes();
+			strcpy(that->m_defaults.attr.hyperlink, hyperlink_save);
 			break;
 		case 1:
                         that->m_defaults.attr.bold = 1;
@@ -2270,7 +2273,9 @@ vte_sequence_handler_character_attributes (VteTerminalPrivate *that, GValueArray
 	}
 	/* If we had no parameters, default to the defaults. */
 	if (i == 0) {
+		strcpy(hyperlink_save, that->m_defaults.attr.hyperlink);
 		that->reset_default_attributes();
+		strcpy(that->m_defaults.attr.hyperlink, hyperlink_save);
 	}
 	/* Save the new colors. */
         that->m_color_defaults.attr.fore = that->m_defaults.attr.fore;
@@ -2449,6 +2454,36 @@ VteTerminalPrivate::set_current_file_uri_changed(char* uri /* adopted */)
         m_current_file_uri_changed = uri;
 }
 
+static void
+vte_sequence_handler_set_current_hyperlink (VteTerminalPrivate *that, GValueArray *params)
+{
+        GValue *value;
+        char *uri;
+
+        uri = NULL;
+        if (params != NULL && params->n_values > 0) {
+                value = g_value_array_get_nth(params, 0);
+
+                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(uri);
+}
+
+void
+VteTerminalPrivate::set_current_hyperlink(char* uri)
+{
+        memset(m_defaults.attr.hyperlink, 0, 256);
+        if (uri != NULL && strlen(uri) < 256)
+                strcpy(m_defaults.attr.hyperlink, uri);
+        g_free(uri);
+}
+
 /* Restrict the scrolling region. */
 static void
 vte_sequence_handler_set_scrolling_region_from_start (VteTerminalPrivate *that, GValueArray *params)
