From 649935a610d4f97ad99f98fed2facffdd5be5265 Mon Sep 17 00:00:00 2001 From: George Stark Date: Tue, 31 Jan 2023 12:37:30 +0300 Subject: Add support for OSC 8 hyperlinks (HTML-like anchors) backport of 1c6f8db736efc62d9a9b38bfbc43ec03c8544696 from gnome-terminal --- src/terminal-screen.c | 50 +++++++++++++++++++++++++++------ src/terminal-screen.h | 1 + src/terminal-util.c | 50 +++++++++++++++++++++++++++++++++ src/terminal-util.h | 2 ++ src/terminal-window.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++--- src/terminal.xml | 3 ++ 6 files changed, 172 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/terminal-screen.c b/src/terminal-screen.c index defe083..7282f6d 100644 --- a/src/terminal-screen.c +++ b/src/terminal-screen.c @@ -137,6 +137,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); @@ -353,6 +356,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; @@ -1680,6 +1685,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); } @@ -1707,24 +1713,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, @@ -1733,12 +1754,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) { @@ -1749,6 +1772,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); @@ -1757,6 +1781,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); @@ -2380,6 +2407,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, diff --git a/src/terminal-screen.h b/src/terminal-screen.h index c5cdb5d..ba1c218 100644 --- a/src/terminal-screen.h +++ b/src/terminal-screen.h @@ -148,6 +148,7 @@ struct _TerminalScreenPopupInfo TerminalWindow *window; TerminalScreen *screen; char *url; + char *hyperlink; TerminalURLFlavor flavor; guint button; guint state; diff --git a/src/terminal-util.c b/src/terminal-util.c index ede72da..d0f1864 100644 --- a/src/terminal-util.c +++ b/src/terminal-util.c @@ -22,6 +22,7 @@ #include +#define _GNU_SOURCE /* for strchrnul */ #include #include #include @@ -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 diff --git a/src/terminal-util.h b/src/terminal-util.h index 190672f..f1ccd43 100644 --- a/src/terminal-util.h +++ b/src/terminal-util.h @@ -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 */ diff --git a/src/terminal-window.c b/src/terminal-window.c index d23fd87..6517d8d 100644 --- a/src/terminal-window.c +++ b/src/terminal-window.c @@ -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) @@ -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))) @@ -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"); @@ -2116,6 +2153,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, @@ -2596,6 +2643,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 @@ -3209,6 +3273,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); @@ -3289,6 +3355,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); diff --git a/src/terminal.xml b/src/terminal.xml index 263dce4..281dc90 100644 --- a/src/terminal.xml +++ b/src/terminal.xml @@ -83,6 +83,9 @@ + + + -- cgit v1.2.1