aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorWynn Wolf Arbor2020-03-28 15:10:55 +0100
committerWynn Wolf Arbor2020-04-13 15:45:26 +0200
commit30bbf529ad3073fd449c7f9331a889b114fad1e1 (patch)
treede43218ab0a4e4fc12816251144b55010e8c01a4
parentbed4cae59bfd5ac100caa3a4ac795f33598b6ab0 (diff)
downloadweltschmerz-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--Makefile2
-rw-r--r--config.vala2
-rw-r--r--meson.build2
-rw-r--r--terminal.ui25
-rw-r--r--terminal.vala37
-rw-r--r--utils.vala27
-rw-r--r--weltschmerz.116
7 files changed, 96 insertions, 15 deletions
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 @@
<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 ,