Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for OSC 8 hyperlinks (HTML-like anchors) #427

Merged
merged 3 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
74 changes: 54 additions & 20 deletions src/terminal-screen.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
typedef struct
{
int tag;
TerminalURLFlavour flavor;
TerminalURLFlavor flavor;
} TagData;

struct _TerminalScreenPrivate
Expand Down 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 All @@ -161,7 +164,7 @@ static guint signals[LAST_SIGNAL] = { 0 };
typedef struct
{
const char *pattern;
TerminalURLFlavour flavor;
TerminalURLFlavor flavor;
guint32 flags;
} TerminalRegexPattern;

Expand All @@ -175,7 +178,7 @@ static const TerminalRegexPattern url_regex_patterns[] =
};

static VteRegex **url_regexes;
static TerminalURLFlavour *url_regex_flavors;
static TerminalURLFlavor *url_regex_flavors;
static guint n_url_regexes;

static void terminal_screen_url_match_remove (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 @@ -574,7 +579,7 @@ terminal_screen_class_init (TerminalScreenClass *klass)
/* Precompile the regexes */
n_url_regexes = G_N_ELEMENTS (url_regex_patterns);
url_regexes = g_new0 (VteRegex*, n_url_regexes);
url_regex_flavors = g_new0 (TerminalURLFlavour, n_url_regexes);
url_regex_flavors = g_new0 (TerminalURLFlavor, n_url_regexes);

for (i = 0; i < n_url_regexes; ++i)
{
Expand Down Expand Up @@ -1657,7 +1662,8 @@ terminal_screen_popup_info_unref (TerminalScreenPopupInfo *info)
return;

g_object_unref (info->screen);
g_free (info->string);
g_free (info->hyperlink);
g_free (info->url);
g_slice_free (TerminalScreenPopupInfo, info);
}

Expand All @@ -1684,38 +1690,55 @@ 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 *matched_string;
int matched_flavor = 0;
char *hyperlink;
char *url;
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);

matched_string = terminal_screen_check_match (screen, (GdkEvent*)event, &matched_flavor);

if (matched_string != 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 (matched_flavor != FLAVOR_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,
matched_string,
matched_flavor,
url,
url_flavor,
state,
&handled);
}

g_free (matched_string);

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 @@ -1725,15 +1748,19 @@ terminal_screen_button_press (GtkWidget *widget,
info->button = event->button;
info->state = state;
info->timestamp = event->time;
info->string = matched_string; /* adopted */
info->flavour = matched_flavor;
info->url = url; /* adopted */
info->hyperlink = hyperlink; /* adopted */
info->flavor = url_flavor;

g_signal_emit (screen, signals[SHOW_POPUP_MENU], 0, info);
terminal_screen_popup_info_unref (info);

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
9 changes: 5 additions & 4 deletions src/terminal-screen.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ typedef enum
FLAVOR_VOIP_CALL,
FLAVOR_EMAIL,
FLAVOR_SKEY
} TerminalURLFlavour;
} TerminalURLFlavor;
geo-stark marked this conversation as resolved.
Show resolved Hide resolved

/* Forward decls */
typedef struct _TerminalScreenPopupInfo TerminalScreenPopupInfo;
Expand Down Expand Up @@ -69,7 +69,7 @@ struct _TerminalScreenClass
TerminalScreenPopupInfo *info);
gboolean (* match_clicked) (TerminalScreen *screen,
const char *url,
int flavour,
int url_flavor,
guint state);
void (* close_screen) (TerminalScreen *screen);
};
Expand Down Expand Up @@ -147,8 +147,9 @@ struct _TerminalScreenPopupInfo
int ref_count;
TerminalWindow *window;
TerminalScreen *screen;
char *string;
TerminalURLFlavour flavour;
char *url;
char *hyperlink;
TerminalURLFlavor flavor;
guint button;
guint state;
guint32 timestamp;
Expand Down
71 changes: 70 additions & 1 deletion 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 @@ -181,7 +182,7 @@ terminal_util_set_atk_name_description (GtkWidget *widget,
void
terminal_util_open_url (GtkWidget *parent,
const char *orig_url,
TerminalURLFlavour flavor,
TerminalURLFlavor flavor,
guint32 user_time)
{
GError *error = NULL;
Expand Down Expand Up @@ -707,6 +708,74 @@ 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)
geo-stark marked this conversation as resolved.
Show resolved Hide resolved
{
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);
}
if (g_ascii_strncasecmp(unesc, "mailto:", 7) == 0)
{
const char *hostname = strchr(unesc, '@');
if (hostname != NULL)
{
const char *unidn = g_hostname_to_unicode(++hostname);
replace_hostname = unidn != NULL && g_ascii_strcasecmp(unidn, hostname) != 0;
if (replace_hostname)
{
char *new_unesc = g_strdup_printf("%.*s%s",
(int) (hostname - unesc),
unesc,
unidn);
g_free(unesc);
unesc = new_unesc;
}
g_free(unidn);
}
}
return unesc;
}

/* Bidirectional object/widget binding */

typedef struct
Expand Down
4 changes: 3 additions & 1 deletion src/terminal-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ void terminal_util_set_atk_name_description (GtkWidget *widget,

void terminal_util_open_url (GtkWidget *parent,
const char *orig_url,
TerminalURLFlavour flavor,
TerminalURLFlavor flavor,
guint32 user_time);

char *terminal_util_resolve_relative_path (const char *path,
Expand Down 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 */
Loading