From 30bbf529ad3073fd449c7f9331a889b114fad1e1 Mon Sep 17 00:00:00 2001 From: Wynn Wolf Arbor Date: Sat, 28 Mar 2020 15:10:55 +0100 Subject: Add OSC 8 hyperlink support This commit adds support for the OSC 8 hyperlink escape sequence [1]. As this is not a mature feature and there seem to be outstanding security concerns [2], the setting that controls whether or not OSC 8 is interpreted is disabled by default. Just like gnome-terminal, weltschmerz will display a tooltip with the canonicalized URI when hovering over a hyperlink. [1] https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda [2] https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda#security --- Makefile | 2 +- config.vala | 2 ++ meson.build | 2 +- terminal.ui | 25 ++++++++++++++++++++++++- terminal.vala | 37 ++++++++++++++++++++++++++++--------- utils.vala | 27 +++++++++++++++++++++++++++ weltschmerz.1 | 16 +++++++++++++--- 7 files changed, 96 insertions(+), 15 deletions(-) create mode 100644 utils.vala diff --git a/Makefile b/Makefile index 5822595..cfc8ec9 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ VALAC ?= valac weltschmerz: weltschmerz.vala terminal.vala config.vala resources.c ${VALAC} -X -lm --pkg posix --pkg gtk+-3.0 --pkg vte-2.91 --gresources resources.xml \ - weltschmerz.vala terminal.vala config.vala configreader.vala resources.c + weltschmerz.vala terminal.vala config.vala configreader.vala utils.vala resources.c resources.c: resources.xml terminal.ui terminal.css glib-compile-resources $< --target=$@ --generate-source diff --git a/config.vala b/config.vala index 991d015..be3f353 100644 --- a/config.vala +++ b/config.vala @@ -5,6 +5,7 @@ class Config { public Pango.FontDescription font; public int scrollback; public bool scrollbar; + public bool allow_hyperlinks; public Gdk.RGBA? foreground; public Gdk.RGBA? background; @@ -46,6 +47,7 @@ class Config { font = Pango.FontDescription.from_string(reader.read_string("misc", "font", "Monospace 12")); scrollback = reader.read_integer("misc", "scrollback", 10000); scrollbar = reader.read_boolean("misc", "scrollbar", true); + allow_hyperlinks = reader.read_boolean("misc", "allow-hyperlinks", false); foreground = reader.read_colour("colours", "foreground", null); background = reader.read_colour("colours", "background", null); diff --git a/meson.build b/meson.build index 485ce8e..dc83f80 100644 --- a/meson.build +++ b/meson.build @@ -11,7 +11,7 @@ dependencies = [ dependency('vte-2.91'), ] -sources = files('weltschmerz.vala', 'terminal.vala', 'config.vala', 'configreader.vala') +sources = files('weltschmerz.vala', 'terminal.vala', 'config.vala', 'configreader.vala', 'utils.vala') sources += gnome.compile_resources('resources', 'resources.xml') executable('weltschmerz', sources, dependencies: dependencies, install: true) diff --git a/terminal.ui b/terminal.ui index c82a04d..4ba9555 100644 --- a/terminal.ui +++ b/terminal.ui @@ -3,6 +3,28 @@ + + True + False + edit-copy + + + True + False + + + _Copy hyperlink + True + False + Copy the hyperlink's URI to the clipboard + True + copy_hyperlink_image + False + True + + + + True False @@ -21,7 +43,7 @@ copy_url_image False True - + @@ -58,6 +80,7 @@ + diff --git a/terminal.vala b/terminal.vala index 99c21ce..5f4f4e0 100644 --- a/terminal.vala +++ b/terminal.vala @@ -25,14 +25,17 @@ class Terminal : Gtk.Overlay { [GtkChild] Gtk.Label infobar_label; [GtkChild] Gtk.Menu standard_context_menu; [GtkChild] Gtk.Menu url_context_menu; + [GtkChild] Gtk.Menu hyperlink_context_menu; [GtkChild] Gtk.MenuItem copy_item; [GtkChild] Gtk.Revealer search_revealer; [GtkChild] Gtk.ScrolledWindow scrolled_window; [GtkChild] Gtk.SearchEntry search_entry; [GtkChild] Vte.Terminal vte; + Gtk.Clipboard clipboard; bool has_search; string url_match; + string hyperlink_match; uint? infobar_timeout_id; double scroll_delta; @@ -45,8 +48,11 @@ class Terminal : Gtk.Overlay { vte.search_set_wrap_around(true); url_context_menu.attach_to_widget(vte, null); + hyperlink_context_menu.attach_to_widget(vte, null); standard_context_menu.attach_to_widget(vte, null); + clipboard = Gtk.Clipboard.get_default(window.get_display()); + try { var regex = new Vte.Regex.for_match(URL_REGEX, URL_REGEX.length, PCRE2_CASELESS | PCRE2_MULTILINE); vte.match_add_regex(regex, 0); @@ -69,6 +75,8 @@ class Terminal : Gtk.Overlay { Gtk.PolicyType policy = conf.scrollbar ? Gtk.PolicyType.AUTOMATIC : Gtk.PolicyType.NEVER; scrolled_window.set_policy(policy, policy); + vte.set_allow_hyperlink(conf.allow_hyperlinks); + vte.set_font(conf.font); var geometry = Gdk.Geometry() { // This must be kept in sync with the padding size in terminal.css @@ -262,9 +270,9 @@ class Terminal : Gtk.Overlay { search_button_down.set_sensitive(has_search); } - void url_match_open() { + void uri_open(string uri) { try { - AppInfo.launch_default_for_uri(url_match, get_display().get_app_launch_context()); + AppInfo.launch_default_for_uri(uri, get_display().get_app_launch_context()); } catch (Error e) { warning(e.message); infobar_show(e.message, Gtk.MessageType.ERROR); @@ -272,13 +280,12 @@ class Terminal : Gtk.Overlay { } [GtkCallback] - void url_match_copy() { - if (url_match == null) { - return; + void uri_copy() { + if (url_match != null) { + clipboard.set_text(url_match, -1); + } else if (hyperlink_match != null) { + clipboard.set_text(hyperlink_match, -1); } - - Gtk.Clipboard clipboard = Gtk.Clipboard.get_default(window.get_display()); - clipboard.set_text(url_match, -1); } void adjust_font_scale(double scale) { @@ -309,9 +316,12 @@ class Terminal : Gtk.Overlay { bool vte_button_press(Gdk.EventButton event) { if (match_button(event, 0, Gdk.BUTTON_SECONDARY)) { url_match = vte.match_check_event(event, null); + hyperlink_match = vte.hyperlink_check_event(event); if (url_match != null) { url_context_menu.popup_at_pointer(event); + } else if (hyperlink_match != null) { + hyperlink_context_menu.popup_at_pointer(event); } else { copy_item.set_sensitive(vte.get_has_selection()); standard_context_menu.popup_at_pointer(event); @@ -326,8 +336,12 @@ class Terminal : Gtk.Overlay { bool vte_button_release(Gdk.EventButton event) { if (match_button(event, 0, Gdk.BUTTON_PRIMARY)) { url_match = vte.match_check_event(event, null); + hyperlink_match = vte.hyperlink_check_event(event); + if (url_match != null && !vte.get_has_selection()) { - url_match_open(); + uri_open(url_match); + } else if (hyperlink_match != null && !vte.get_has_selection()) { + uri_open(hyperlink_match); } } @@ -397,6 +411,11 @@ class Terminal : Gtk.Overlay { return false; } + [GtkCallback] + void vte_hyperlink_hover(string? uri, Gdk.Rectangle? box) { + vte.set_tooltip_text(Utils.normalize_uri(uri)); + } + [GtkCallback] void window_set_title() { string title = null; diff --git a/utils.vala b/utils.vala new file mode 100644 index 0000000..845728b --- /dev/null +++ b/utils.vala @@ -0,0 +1,27 @@ +class Utils { + public static string? normalize_uri(string? uri) { + if (uri == null) + return null; + + string? u = Uri.unescape_string(uri, null); + + if (u == null) + u = uri; + + if (u.has_prefix("ftp://") || u.has_prefix("http://") || u.has_prefix("https://")) { + int a = u.index_of("/", 0) + 2; // character after second slash + int b = u.index_of("/", a); // next slash after hostname... + if (b < 0) + b = u.length; // ... or end of string + + string hostname = u.slice(a, b); + string? canon = Hostname.to_unicode(hostname); + + if (canon != null && hostname != canon) { + u = u.splice(a, b, canon); + } + } + + return u; + } +} diff --git a/weltschmerz.1 b/weltschmerz.1 index f754253..f2481c8 100644 --- a/weltschmerz.1 +++ b/weltschmerz.1 @@ -1,4 +1,4 @@ -.Dd January 17, 2020 +.Dd March 28, 2020 .Dt WELTSCHMERZ 1 .Os .Sh NAME @@ -13,8 +13,8 @@ .Sh DESCRIPTION .Nm is a terminal emulator using the VTE widget. -It supports clickable URLs, contains basic search functionality, and can -reload its configuration whilst running. +It supports clickable URLs and hyperlinks, contains basic search functionality, +and can reload its configuration whilst running. .Pp .Nm executes the given command, or the program specified in the @@ -82,6 +82,16 @@ The options for the .Em misc group are as follows: .Bl -tag -width Ds +.It Sy allow-hyperlinks +When set to +.Sy true , +.Nm +will highlight and process OSC 8 hyperlinks. +When set to +.Sy false , +hyperlink support will be disabled completely. +The default is +.Sy false . .It Sy autohide-mouse When set to .Sy true , -- cgit v1.2.3-2-gb3c3