diff options
author | Wynn Wolf Arbor | 2020-03-28 15:10:55 +0100 |
---|---|---|
committer | Wynn Wolf Arbor | 2020-04-13 15:45:26 +0200 |
commit | 30bbf529ad3073fd449c7f9331a889b114fad1e1 (patch) | |
tree | de43218ab0a4e4fc12816251144b55010e8c01a4 | |
parent | bed4cae59bfd5ac100caa3a4ac795f33598b6ab0 (diff) | |
download | weltschmerz-30bbf529ad3073fd449c7f9331a889b114fad1e1.tar.gz |
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
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | config.vala | 2 | ||||
-rw-r--r-- | meson.build | 2 | ||||
-rw-r--r-- | terminal.ui | 25 | ||||
-rw-r--r-- | terminal.vala | 37 | ||||
-rw-r--r-- | utils.vala | 27 | ||||
-rw-r--r-- | weltschmerz.1 | 16 |
7 files changed, 96 insertions, 15 deletions
@@ -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 @@ <interface> <requires lib="gtk+" version="3.20"/> <requires lib="vte-2.91" version="0.54"/> + <object class="GtkImage" id="copy_hyperlink_image"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">edit-copy</property> + </object> + <object class="GtkMenu" id="hyperlink_context_menu"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkImageMenuItem" id="copy_hyperlink_item"> + <property name="label" translatable="yes">_Copy hyperlink</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="tooltip_text" translatable="yes">Copy the hyperlink's URI to the clipboard</property> + <property name="use_underline">True</property> + <property name="image">copy_hyperlink_image</property> + <property name="use_stock">False</property> + <property name="always_show_image">True</property> + <signal name="activate" handler="uri_copy" swapped="no"/> + </object> + </child> + </object> <object class="GtkImage" id="copy_url_image"> <property name="visible">True</property> <property name="can_focus">False</property> @@ -21,7 +43,7 @@ <property name="image">copy_url_image</property> <property name="use_stock">False</property> <property name="always_show_image">True</property> - <signal name="activate" handler="url_match_copy" swapped="no"/> + <signal name="activate" handler="uri_copy" swapped="no"/> </object> </child> </object> @@ -58,6 +80,7 @@ <signal name="button-press-event" handler="vte_button_press" swapped="no"/> <signal name="button-release-event" handler="vte_button_release" swapped="no"/> <signal name="child-exited" handler="gtk_main_quit" swapped="no"/> + <signal name="hyperlink-hover-uri-changed" handler="vte_hyperlink_hover" swapped="no"/> <signal name="key-press-event" handler="vte_key_press" swapped="no"/> <signal name="scroll-event" handler="vte_scroll" swapped="no"/> <signal name="window-title-changed" handler="window_set_title" swapped="no"/> 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); } } @@ -398,6 +412,11 @@ class Terminal : Gtk.Overlay { } [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; if (vte.window_title.length > 0) { 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 , |