[gnome-terminal] all: Add support for OSC 8 hyperlinks (HTML-like anchors)



commit 1c6f8db736efc62d9a9b38bfbc43ec03c8544696
Author: Egmont Koblinger <egmont gmail com>
Date:   Tue Apr 25 01:50:08 2017 +0200

    all: 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

 src/terminal-screen.c |   41 ++++++++++++++++++++++++--
 src/terminal-screen.h |    1 +
 src/terminal-util.c   |   47 ++++++++++++++++++++++++++++++
 src/terminal-util.h   |    2 +
 src/terminal-window.c |   75 ++++++++++++++++++++++++++++++++++++++++++++++--
 src/terminal.xml      |    3 ++
 6 files changed, 161 insertions(+), 8 deletions(-)
---
diff --git a/src/terminal-screen.c b/src/terminal-screen.c
index 9df4fc7..f6cc4a5 100644
--- a/src/terminal-screen.c
+++ b/src/terminal-screen.c
@@ -152,6 +152,8 @@ static void terminal_screen_icon_title_changed        (VteTerminal *vte_terminal
 
 static void update_color_scheme                      (TerminalScreen *screen);
 
+static char* terminal_screen_check_hyperlink   (TerminalScreen            *screen,
+                                                GdkEvent                  *event);
 static void terminal_screen_check_extra (TerminalScreen *screen,
                                          GdkEvent       *event,
                                          char           **number_info);
@@ -358,6 +360,8 @@ terminal_screen_init (TerminalScreen *screen)
 
   priv->child_pid = -1;
 
+  vte_terminal_set_allow_hyperlink (terminal, TRUE);
+
   for (i = 0; i < n_url_regexes; ++i)
     {
       TagData *tag_data;
@@ -1509,6 +1513,7 @@ terminal_screen_popup_info_unref (TerminalScreenPopupInfo *info)
 
   g_object_unref (info->screen);
   g_weak_ref_clear (&info->window_weak_ref);
+  g_free (info->hyperlink);
   g_free (info->url);
   g_free (info->number_info);
   g_slice_free (TerminalScreenPopupInfo, info);
@@ -1547,6 +1552,7 @@ terminal_screen_popup_menu (GtkWidget *widget)
 static void
 terminal_screen_do_popup (TerminalScreen *screen,
                           GdkEventButton *event,
+                          char *hyperlink,
                           char *url,
                           int url_flavor,
                           char *number_info)
@@ -1557,6 +1563,7 @@ terminal_screen_do_popup (TerminalScreen *screen,
   info->button = event->button;
   info->state = event->state & gtk_accelerator_get_default_mod_mask ();
   info->timestamp = event->time;
+  info->hyperlink = hyperlink; /* adopted */
   info->url = url; /* adopted */
   info->url_flavor = url_flavor;
   info->number_info = number_info; /* adopted */
@@ -1572,6 +1579,7 @@ terminal_screen_button_press (GtkWidget      *widget,
   TerminalScreen *screen = TERMINAL_SCREEN (widget);
   gboolean (* button_press_event) (GtkWidget*, GdkEventButton*) =
     GTK_WIDGET_CLASS (terminal_screen_parent_class)->button_press_event;
+  gs_free char *hyperlink = NULL;
   gs_free char *url = NULL;
   int url_flavor = 0;
   gs_free char *number_info = NULL;
@@ -1579,9 +1587,25 @@ terminal_screen_button_press (GtkWidget      *widget,
 
   state = event->state & gtk_accelerator_get_default_mod_mask ();
 
+  hyperlink = terminal_screen_check_hyperlink (screen, (GdkEvent*)event);
   url = terminal_screen_check_match (screen, (GdkEvent*)event, &url_flavor);
   terminal_screen_check_extra (screen, (GdkEvent*)event, &number_info);
 
+  if (hyperlink != NULL &&
+      (event->button == 1 || event->button == 2) &&
+      (state & GDK_CONTROL_MASK))
+    {
+      gboolean handled = FALSE;
+
+      g_signal_emit (screen, signals[MATCH_CLICKED], 0,
+                     hyperlink,
+                     FLAVOR_AS_IS,
+                     state,
+                     &handled);
+      if (handled)
+        return TRUE; /* don't do anything else such as select with the click */
+    }
+
   if (url != NULL &&
       (event->button == 1 || event->button == 2) &&
       (state & GDK_CONTROL_MASK))
@@ -1606,16 +1630,18 @@ terminal_screen_button_press (GtkWidget      *widget,
           if (button_press_event && button_press_event (widget, event))
             return TRUE;
 
-          terminal_screen_do_popup (screen, event, url, url_flavor, number_info);
-          url = NULL; /* adopted to the popup info */
+          terminal_screen_do_popup (screen, event, hyperlink, url, url_flavor, number_info);
+          hyperlink = NULL; /* adopted to the popup info */
+          url = NULL; /* ditto */
           number_info = NULL; /* ditto */
           return TRUE;
         }
       else if (!(event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)))
         {
           /* do popup on shift+right-click */
-          terminal_screen_do_popup (screen, event, url, url_flavor, number_info);
-          url = NULL; /* adopted to the popup info */
+          terminal_screen_do_popup (screen, event, hyperlink, url, url_flavor, number_info);
+          hyperlink = NULL; /* adopted to the popup info */
+          url = NULL; /* ditto */
           number_info = NULL; /* ditto */
           return TRUE;
         }
@@ -1954,6 +1980,13 @@ terminal_screen_get_cell_size (TerminalScreen *screen,
 }
 
 static char*
+terminal_screen_check_hyperlink (TerminalScreen *screen,
+                                 GdkEvent       *event)
+{
+  return vte_terminal_hyperlink_check_event (VTE_TERMINAL (screen), event);
+}
+
+static char*
 terminal_screen_check_match (TerminalScreen *screen,
                              GdkEvent       *event,
                              int       *flavor)
diff --git a/src/terminal-screen.h b/src/terminal-screen.h
index f3b4013..2e8f6dc 100644
--- a/src/terminal-screen.h
+++ b/src/terminal-screen.h
@@ -142,6 +142,7 @@ struct _TerminalScreenPopupInfo {
   TerminalScreen *screen;
   char *url;
   TerminalURLFlavor url_flavor;
+  char *hyperlink;
   char *number_info;
   guint button;
   guint state;
diff --git a/src/terminal-util.c b/src/terminal-util.c
index 9e63070..b8676a2 100644
--- a/src/terminal-util.c
+++ b/src/terminal-util.c
@@ -20,6 +20,7 @@
  */
 
 #include "config.h"
+#define _GNU_SOURCE /* for strchrnul */
 
 #include <string.h>
 #include <stdlib.h>
@@ -1140,3 +1141,49 @@ terminal_util_uri_is_allowed (const char *uri,
   }
   return TRUE;
 }
+
+/**
+ * terminal_util_hyperlink_uri_label:
+ * @uri: a URI
+ *
+ * Formats @uri to be displayed in a tooltip.
+ * Performs URI-decoding and converts IDN hostname to UTF-8.
+ *
+ * Returns: (transfer full): The human readable URI as plain text
+ */
+char *terminal_util_hyperlink_uri_label (const char *uri)
+{
+  char *unesc = NULL;
+  gboolean replace_hostname;
+
+  if (uri == NULL)
+    return NULL;
+
+  unesc = g_uri_unescape_string(uri, NULL);
+  if (unesc == NULL)
+    unesc = g_strdup(uri);
+
+  if (g_ascii_strncasecmp(unesc, "ftp://";, 6) == 0 ||
+      g_ascii_strncasecmp(unesc, "http://";, 7) == 0 ||
+      g_ascii_strncasecmp(unesc, "https://";, 8) == 0) {
+    gs_free char *unidn = NULL;
+    char *hostname = strchr(unesc, '/') + 2;
+    char *hostname_end = strchrnul(hostname, '/');
+    char save = *hostname_end;
+    *hostname_end = '\0';
+    unidn = g_hostname_to_unicode(hostname);
+    replace_hostname = unidn != NULL && g_ascii_strcasecmp(unidn, hostname) != 0;
+    *hostname_end = save;
+    if (replace_hostname) {
+      char *new_unesc = g_strdup_printf("%.*s%s%s",
+                                        (int) (hostname - unesc),
+                                        unesc,
+                                        unidn,
+                                        hostname_end);
+      g_free(unesc);
+      unesc = new_unesc;
+    }
+  }
+
+  return unesc;
+}
diff --git a/src/terminal-util.h b/src/terminal-util.h
index 581a8f7..278096a 100644
--- a/src/terminal-util.h
+++ b/src/terminal-util.h
@@ -98,6 +98,8 @@ char *terminal_util_number_info (const char *str);
 gboolean terminal_util_uri_is_allowed (const char *uri,
                                        GError **error);
 
+char *terminal_util_hyperlink_uri_label (const char *str);
+
 G_END_DECLS
 
 #endif /* TERMINAL_UTIL_H */
diff --git a/src/terminal-window.c b/src/terminal-window.c
index 2921c02..abd73d7 100644
--- a/src/terminal-window.c
+++ b/src/terminal-window.c
@@ -1951,6 +1951,38 @@ handle_tab_droped_on_desktop (GtkNotebook *source_notebook,
 /* Terminal screen popup menu handling */
 
 static void
+popup_open_hyperlink_callback (GtkAction *action,
+                               TerminalWindow *window)
+{
+  TerminalWindowPrivate *priv = window->priv;
+  TerminalScreenPopupInfo *info = priv->popup_info;
+
+  if (info == NULL)
+    return;
+
+  terminal_util_open_url (GTK_WIDGET (window), info->hyperlink, FLAVOR_AS_IS,
+                          gtk_get_current_event_time ());
+}
+
+static void
+popup_copy_hyperlink_callback (GtkAction *action,
+                               TerminalWindow *window)
+{
+  TerminalWindowPrivate *priv = window->priv;
+  TerminalScreenPopupInfo *info = priv->popup_info;
+  GtkClipboard *clipboard;
+
+  if (info == NULL)
+    return;
+
+  if (info->hyperlink == NULL)
+    return;
+
+  clipboard = gtk_widget_get_clipboard (GTK_WIDGET (window), GDK_SELECTION_CLIPBOARD);
+  gtk_clipboard_set_text (clipboard, info->hyperlink, -1);
+}
+
+static void
 popup_open_url_callback (GtkAction *action,
                          TerminalWindow *window)
 {
@@ -2054,7 +2086,7 @@ popup_clipboard_targets_received_cb (GtkClipboard *clipboard,
   TerminalScreen *screen = info->screen;
   GtkWidget *popup_menu;
   GtkAction *action;
-  gboolean can_paste, can_paste_uris, show_link, show_email_link, show_call_link, show_number_info;
+  gboolean can_paste, can_paste_uris, show_hyperlink, show_link, show_email_link, show_call_link, 
show_number_info;
 
   window = terminal_screen_popup_info_ref_window (info);
   if (window == NULL ||
@@ -2072,11 +2104,17 @@ popup_clipboard_targets_received_cb (GtkClipboard *clipboard,
 
   can_paste = targets != NULL && gtk_targets_include_text (targets, n_targets);
   can_paste_uris = targets != NULL && gtk_targets_include_uri (targets, n_targets);
-  show_link = info->url != NULL && (info->url_flavor == FLAVOR_AS_IS || info->url_flavor == 
FLAVOR_DEFAULT_TO_HTTP);
-  show_email_link = info->url != NULL && info->url_flavor == FLAVOR_EMAIL;
-  show_call_link = info->url != NULL && info->url_flavor == FLAVOR_VOIP_CALL;
+  show_hyperlink = info->hyperlink != NULL;
+  show_link = !show_hyperlink && info->url != NULL && (info->url_flavor == FLAVOR_AS_IS || info->url_flavor 
== FLAVOR_DEFAULT_TO_HTTP);
+  show_email_link = !show_hyperlink && info->url != NULL && info->url_flavor == FLAVOR_EMAIL;
+  show_call_link = !show_hyperlink && info->url != NULL && info->url_flavor == FLAVOR_VOIP_CALL;
   show_number_info = info->number_info != NULL;
 
+  action = gtk_action_group_get_action (priv->action_group, "PopupOpenHyperlink");
+  gtk_action_set_visible (action, show_hyperlink);
+  action = gtk_action_group_get_action (priv->action_group, "PopupCopyHyperlinkAddress");
+  gtk_action_set_visible (action, show_hyperlink);
+
   action = gtk_action_group_get_action (priv->action_group, "PopupSendEmail");
   gtk_action_set_visible (action, show_email_link);
   action = gtk_action_group_get_action (priv->action_group, "PopupCopyEmailAddress");
@@ -2089,6 +2127,7 @@ popup_clipboard_targets_received_cb (GtkClipboard *clipboard,
   gtk_action_set_visible (action, show_link);
   action = gtk_action_group_get_action (priv->action_group, "PopupCopyLinkAddress");
   gtk_action_set_visible (action, show_link);
+
   action = gtk_action_group_get_action (priv->action_group, "PopupNumberInfo");
   gtk_action_set_label (action, info->number_info);
   gtk_action_set_sensitive (action, FALSE);
@@ -2562,6 +2601,12 @@ terminal_window_init (TerminalWindow *window)
         G_CALLBACK (help_inspector_callback) },
 
       /* Popup menu */
+      { "PopupOpenHyperlink", NULL, N_("_Open Hyperlink"), NULL,
+        NULL,
+        G_CALLBACK (popup_open_hyperlink_callback) },
+      { "PopupCopyHyperlinkAddress", NULL, N_("_Copy Hyperlink Address"), NULL,
+        NULL,
+        G_CALLBACK (popup_copy_hyperlink_callback) },
       { "PopupSendEmail", NULL, N_("_Send Mail To…"), NULL,
         NULL,
         G_CALLBACK (popup_open_url_callback) },
@@ -3082,6 +3127,22 @@ screen_font_any_changed_cb (TerminalScreen *screen,
   terminal_window_update_size (window);
 }
 
+static void
+screen_hyperlink_hover_uri_changed (TerminalScreen *screen,
+                                    const char *uri,
+                                    const GdkRectangle *bbox G_GNUC_UNUSED,
+                                    TerminalWindow *window G_GNUC_UNUSED)
+{
+  gs_free char *label = NULL;
+
+  if (!gtk_widget_get_realized (GTK_WIDGET (screen)))
+    return;
+
+  label = terminal_util_hyperlink_uri_label (uri);
+
+  gtk_widget_set_tooltip_text (GTK_WIDGET (screen), label);
+}
+
 /* MDI container callbacks */
 
 static void
@@ -3444,6 +3505,8 @@ mdi_screen_added_cb (TerminalMdiContainer *container,
                     G_CALLBACK (screen_font_any_changed_cb), window);
   g_signal_connect (screen, "selection-changed",
                     G_CALLBACK (terminal_window_update_copy_sensitivity), window);
+  g_signal_connect (screen, "hyperlink-hover-uri-changed",
+                    G_CALLBACK (screen_hyperlink_hover_uri_changed), window);
 
   g_signal_connect (screen, "show-popup-menu",
                     G_CALLBACK (screen_show_popup_menu_callback), window);
@@ -3527,6 +3590,10 @@ mdi_screen_removed_cb (TerminalMdiContainer *container,
                                         G_CALLBACK (terminal_window_update_copy_sensitivity),
                                         window);
 
+  g_signal_handlers_disconnect_by_func (G_OBJECT (screen),
+                                        G_CALLBACK (screen_hyperlink_hover_uri_changed),
+                                        window);
+
   g_signal_handlers_disconnect_by_func (screen,
                                         G_CALLBACK (screen_show_popup_menu_callback),
                                         window);
diff --git a/src/terminal.xml b/src/terminal.xml
index d830149..e72e595 100644
--- a/src/terminal.xml
+++ b/src/terminal.xml
@@ -78,6 +78,9 @@
   </menubar>
 
   <popup name="Popup" action="Popup">
+    <menuitem action="PopupOpenHyperlink" />
+    <menuitem action="PopupCopyHyperlinkAddress" />
+    <separator />
     <menuitem action="PopupSendEmail" />
     <menuitem action="PopupCopyEmailAddress" />
     <menuitem action="PopupCall" />


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]