Skip to content

Commit

Permalink
Add support for OSC 8 hyperlinks (HTML-like anchors)
Browse files Browse the repository at this point in the history
backport of 1c6f8db736efc62d9a9b38bfbc43ec03c8544696
from gnome-terminal
  • Loading branch information
geo-stark committed Mar 11, 2023
1 parent 285a0fc commit bcac21e
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 13 deletions.
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ AM_PROG_LIBTOOL
GLIB_REQUIRED=2.50.0
GIO_REQUIRED=2.50.0
GTK_REQUIRED=3.22.0
VTE_REQUIRED=0.48
VTE_REQUIRED=0.49.1
DCONF_REQUIRED=0.13.4

PKG_CHECK_MODULES([TERM],
Expand Down
50 changes: 42 additions & 8 deletions src/terminal-screen.c
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ 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 gboolean terminal_screen_format_title (TerminalScreen *screen, const char *raw_title, char **old_cooked_title);

static void terminal_screen_cook_title (TerminalScreen *screen);
Expand Down Expand Up @@ -351,6 +354,8 @@ terminal_screen_init (TerminalScreen *screen)
vte_terminal_set_bold_is_bright (VTE_TERMINAL (screen), TRUE);
#endif

vte_terminal_set_allow_hyperlink (VTE_TERMINAL (screen), TRUE);

priv->child_pid = -1;

priv->font_scale = PANGO_SCALE_MEDIUM;
Expand Down Expand Up @@ -1657,6 +1662,7 @@ terminal_screen_popup_info_unref (TerminalScreenPopupInfo *info)
return;

g_object_unref (info->screen);
g_free (info->hyperlink);
g_free (info->url);
g_slice_free (TerminalScreenPopupInfo, info);
}
Expand Down Expand Up @@ -1684,24 +1690,39 @@ 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;
char *hyperlink;
char *url;
int url_flavor = 0;
int url_flavor = FLAVOR_AS_IS;
guint state;

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);

if (url != NULL &&
(event->button == 1 || event->button == 2) &&
(state & GDK_CONTROL_MASK))
// left or middle button with Ctrl
if ((event->button == 1 || event->button == 2) &&
(state & GDK_CONTROL_MASK))
{
gboolean handled = FALSE;

if (hyperlink != NULL)
g_signal_emit (screen, signals[MATCH_CLICKED], 0,
hyperlink,
FLAVOR_AS_IS,
state,
&handled);

if (handled) {
g_free (url);
g_free (hyperlink);
return TRUE; /* don't do anything else such as select with the click */
}

#ifdef ENABLE_SKEY
if (url_flavor != FLAVOR_SKEY ||
terminal_profile_get_property_boolean (screen->priv->profile, TERMINAL_PROFILE_USE_SKEY))
#endif
if (url != NULL)
{
g_signal_emit (screen, signals[MATCH_CLICKED], 0,
url,
Expand All @@ -1710,12 +1731,14 @@ terminal_screen_button_press (GtkWidget *widget,
&handled);
}

g_free (url);

if (handled)
if (handled) {
g_free (url);
g_free (hyperlink);
return TRUE; /* don't do anything else such as select with the click */
}
}

// right button with no Ctrl, Alt or Shift
if (event->button == 3 &&
(state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)) == 0)
{
Expand All @@ -1726,6 +1749,7 @@ terminal_screen_button_press (GtkWidget *widget,
info->state = state;
info->timestamp = event->time;
info->url = url; /* adopted */
info->hyperlink = hyperlink; /* adopted */
info->flavor = url_flavor;

g_signal_emit (screen, signals[SHOW_POPUP_MENU], 0, info);
Expand All @@ -1734,6 +1758,9 @@ terminal_screen_button_press (GtkWidget *widget,
return TRUE;
}

g_free (url);
g_free (hyperlink);

/* default behavior is to let the terminal widget deal with it */
if (button_press_event)
return button_press_event (widget, event);
Expand Down Expand Up @@ -2350,6 +2377,13 @@ terminal_screen_url_match_remove (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,
Expand Down
1 change: 1 addition & 0 deletions src/terminal-screen.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ struct _TerminalScreenPopupInfo
TerminalWindow *window;
TerminalScreen *screen;
char *url;
char *hyperlink;
TerminalURLFlavor flavor;
guint button;
guint state;
Expand Down
50 changes: 50 additions & 0 deletions src/terminal-util.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include <config.h>

#define _GNU_SOURCE /* for strchrnul */
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
Expand Down Expand Up @@ -707,6 +708,55 @@ terminal_util_add_proxy_env (GHashTable *env_table)
g_object_unref (settings_socks);
}

/**
* 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)
{
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;
}
g_free(unidn);
}
return unesc;
}

/* Bidirectional object/widget binding */

typedef struct
Expand Down
2 changes: 2 additions & 0 deletions src/terminal-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ void terminal_util_bind_object_property_to_widget (GObject *object,

void terminal_util_x11_clear_demands_attention (GdkWindow *window);

char *terminal_util_hyperlink_uri_label (const char *str);

G_END_DECLS

#endif /* TERMINAL_UTIL_H */
78 changes: 74 additions & 4 deletions src/terminal-window.c
Original file line number Diff line number Diff line change
Expand Up @@ -1365,6 +1365,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)
Expand Down Expand Up @@ -1476,7 +1508,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_input_method_menu;
gboolean can_paste, can_paste_uris, show_hyperlink, show_link, show_email_link, show_call_link, show_input_method_menu;
int n_pages;

if (!gtk_widget_get_realized (GTK_WIDGET (screen)))
Expand All @@ -1494,11 +1526,16 @@ 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->flavor == FLAVOR_AS_IS || info->flavor == FLAVOR_DEFAULT_TO_HTTP);
show_email_link = info->url != NULL && info->flavor == FLAVOR_EMAIL;
show_call_link = info->url != NULL && info->flavor == FLAVOR_VOIP_CALL;
show_hyperlink = info->hyperlink != NULL;
show_link = !show_hyperlink && info->url != NULL && (info->flavor == FLAVOR_AS_IS || info->flavor == FLAVOR_DEFAULT_TO_HTTP);
show_email_link = !show_hyperlink && info->url != NULL && info->flavor == FLAVOR_EMAIL;
show_call_link = !show_hyperlink && info->url != NULL && info->flavor == FLAVOR_VOIP_CALL;

G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
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");
Expand Down Expand Up @@ -2083,6 +2120,16 @@ terminal_window_init (TerminalWindow *window)
},

/* 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,
Expand Down Expand Up @@ -2562,6 +2609,23 @@ sync_screen_icon_title_set (TerminalScreen *screen,
/* Re-setting the right title will be done by the notify::title handler which comes after this one */
}

static void
screen_hyperlink_hover_uri_changed (TerminalScreen *screen,
const char *uri,
const GdkRectangle *bbox G_GNUC_UNUSED,
TerminalWindow *window G_GNUC_UNUSED)
{
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);
g_free(label);
}

/* Notebook callbacks */

static void
Expand Down Expand Up @@ -3166,6 +3230,8 @@ notebook_page_added_callback (GtkWidget *notebook,
G_CALLBACK (sync_screen_icon_title_set), 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);
Expand Down Expand Up @@ -3246,6 +3312,10 @@ notebook_page_removed_callback (GtkWidget *notebook,
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);
Expand Down
3 changes: 3 additions & 0 deletions src/terminal.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@
</menubar>

<popup name="Popup" action="Popup">
<menuitem action="PopupOpenHyperlink" />
<menuitem action="PopupCopyHyperlinkAddress" />
<separator />
<menuitem action="PopupSendEmail" />
<menuitem action="PopupCopyEmailAddress" />
<menuitem action="PopupCall" />
Expand Down

0 comments on commit bcac21e

Please sign in to comment.