adji

Adji's a Decisive and Joyful Internet browser
git clone https://noxz.tech/git/adji.git
Log | Files | Tags | LICENSE

commit: d4cc6ec8531884c590614c6145d7404604b7010e
parent: 524da715999b0e6ef5203e4e89af83fb06b88db9
author: Chris Noxz <chris@noxz.tech>
date:   Fri, 17 Mar 2023 15:37:34 +0100
add feature to provide custom (xdg-open) schemes

This commit adds the ability for users to provide their own schemes for
redirection to the xdg-open program. For example, users can now redirect the
'mailto' scheme to be handled by a program defined by the xdg utilities. If the
browser identifies the URI as one of the provided schemes, the default action
to relocate to that URI will be aborted. However, this feature may not work
when the URI is requested using target='_blank' or the context menu option
'Open Link in New Window', as interception occurs only after the new window is
created.

To accommodate this new feature, additional NULL checks have been implemented
in for calls to the resolve_uri function. While some of these checks may be
unnecessary, they have been added as a precautionary measure to avoid potential
bugs in the future.
Madji.111++++
Mbrowser.c58+++++++++++++-------
Mbrowser.h10+++-
Mconfig.h1+
4 files changed, 59 insertions(+), 21 deletions(-)
diff --git a/adji.1 b/adji.1
@@ -203,6 +203,17 @@ ADJI_ZOOM_LEVEL
 This variable can be utilized to set the zoom level of WebKit's viewports. The
 default value is
 .BR 1.0 .
+.TP
+.B
+ADJI_XDG_SCHEMES
+Enables the user to define a list of schemes that should be redirected to the
+.B xdg-open
+program. The list of schemes should be comma-separated
+(e.g. \(lqmailto,doi\(rq). If no schemes are specified, the default behaviour
+is to not redirect any schemes to the
+.B xdg-open
+program. Custom user-defined schemes can also be used, as the user can define any sort of scheme except the ones already handled by
+.BR adji .
 .
 .SH FILES
 These paths will be constructed using XDG variables.
diff --git a/browser.c b/browser.c
@@ -25,18 +25,25 @@ client_create(const gchar *uri, WebKitWebView *related_wv, gboolean show,
               gboolean focus_tab)
 {
 	struct Client          *c;                  /* client to be */
-	gchar                  *u;                  /* translated uri */
+	gchar                  *u = NULL;           /* translated uri */
 	GtkWidget              *e,                  /* event box */
 	                       *t;                  /* tab box */
 
 	/* communicate uri through ipc if constructed */
 	if (uri != NULL && gl.ipc == IPC_CLIENT) {
-		if (ipc_send((u = resolve_uri(uri))) <= 0 || ipc_send("\n") <= 0)
+		if ((u = resolve_uri(uri)) == NULL || ipc_send(u) <= 0
+		    || ipc_send("\n") <= 0)
 			fprintf(stderr, __NAME__": Could not send message '%s'\n", u);
 		g_free(u);
 		return NULL; /* the request was handled elsewhere */
 	}
 
+	/* do not create a new client if existing uri was resolved to NULL, as it
+	 * was handled as an XDG-OPEN event. Still process uri == NULL for "create"
+	 * callback signals, such as "Open Link in New Window". */
+	if (uri != NULL && (u = resolve_uri(uri)) == NULL)
+		return NULL;
+
 	if (!(c = calloc(1, sizeof(struct Client))))
 		die(__NAME__ ": fatal: calloc failed\n");
 
@@ -182,12 +189,9 @@ client_create(const gchar *uri, WebKitWebView *related_wv, gboolean show,
 		CB(c->wv, "ready-to-show",              cb_wv_show, c);
 
 	/* finally load the uri */
-	if (uri != NULL) {
-		webkit_web_view_load_uri(
-		    WEBKIT_WEB_VIEW(c->wv), (u = resolve_uri(uri))
-		);
-		g_free(u);
-	}
+	if (u != NULL)
+		webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->wv), u);
+	g_free(u);
 
 	gl.clients++;
 	return c;
@@ -367,15 +371,16 @@ key_common(struct Client *c, GdkEventKey *event)
 		client_destroy(c);
 		return TRUE;
 	case GDK_KEY_w: /* go to home page */
-		webkit_web_view_load_uri(
-		    WEBKIT_WEB_VIEW(c->wv), (u = resolve_uri(CFG_S(HomeUri)))
-		);
+		if ((u = resolve_uri(CFG_S(HomeUri))) != NULL)
+			webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->wv), u);
 		g_free(u);
 		return TRUE;
 	case GDK_KEY_t: /* create new client/tab */
-		c = client_create((u = resolve_uri(CFG_S(HomeUri))), NULL, TRUE, TRUE);
-		g_free(u);
-		gtk_widget_grab_focus(c->entry);
+		if ((u = resolve_uri(CFG_S(HomeUri))) != NULL) {
+			c = client_create(u, NULL, TRUE, TRUE);
+			g_free(u);
+			gtk_widget_grab_focus(c->entry);
+		}
 		return TRUE;
 	case GDK_KEY_r: /* reload, while bypassing cache */
 		webkit_web_view_reload_bypass_cache(WEBKIT_WEB_VIEW(c->wv));
@@ -486,9 +491,8 @@ key_entry(struct Client *c, GdkEventKey *event)
 		if (t[0] != ':') { /* if not a command */
 			/* store current uri before loading new uri */
 			l = WV_GET_URI(c->wv);
-			webkit_web_view_load_uri(
-			    WEBKIT_WEB_VIEW(c->wv), (u = resolve_uri(t))
-			);
+			if ((u = resolve_uri(t)) != NULL)
+				webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->wv), u);
 			g_free(u);
 			/* fix: notify::uri won't be raised if current uri is unchanged */
 			if (g_strcmp0(l, WV_GET_URI(c->wv)) == 0)
@@ -1069,13 +1073,16 @@ toggle_tls_error_policy(struct Client *c)
 gchar *
 resolve_uri(const gchar *t)
 {
-	gchar                  *u,                  /* uri to return */
+	gchar                  *u = NULL,           /* uri to return */
 	                       *l = NULL;           /* temporary string */
 	const gchar            *s;
 
 	/* check if valid scheme, and if so just create a copy of the text */
 	if ((s = g_uri_peek_scheme(t)) && g_strv_contains(CFG_L(UriSchemes), s))
 		u = g_strdup(t);
+	/* if no match with, test xdg schemes (schemes that are redirected) */
+	else if (s && CFG_L(XdgSchemes) && g_strv_contains(CFG_L(XdgSchemes), s))
+		xdg_open(s, t);
 	/* if path is local, use the file scheme */
 	else if ((l = realpath(t, NULL)) != NULL)
 		u = g_strdup_printf("file://%s", l);
@@ -1223,6 +1230,16 @@ web_view_crashed(struct Client *c)
 	g_free(t);
 }
 
+void
+xdg_open(const gchar *s, const gchar *t)
+{
+	char                    b[URI_MAX];         /* command line buffer */
+
+	/* make sure to send the scheme the way it was matched */
+	sprintf(b, "%s %s%s &", XDG_OPEN, s, t+strlen(s));
+	system(b);
+}
+
 int
 main(int argc, char **argv)
 {
@@ -1256,8 +1273,9 @@ main(int argc, char **argv)
 		for (i = optind; i < argc; i++)
 			client_create(argv[i], NULL, TRUE, TRUE);
 
-	/* don't load gtk for ipc clients, they have done their bit */
-	if (gl.ipc != IPC_CLIENT)
+	/* don't load gtk for ipc clients, they have done their bit, and make sure
+	 * there are at least one client to view */
+	if (gl.ipc != IPC_CLIENT && gl.clients > 0)
 		gtk_main();
 
 	exit(EXIT_SUCCESS);
diff --git a/browser.h b/browser.h
@@ -78,6 +78,7 @@
 #define MSG_TLS_CERTIFICATE_GENERIC_ERROR                                      \
                             ">  Some error occurred validating the certificate"\
                             ".<br>"
+#define XDG_OPEN            "xdg-open"
 
 enum javascript_policy {
 	JSP_DISABLE             = 0,
@@ -133,6 +134,7 @@ enum config_name {
 	UcDir,      /* user (cascading) style sheets directory */
 	UsDir,      /* user scripts directory */
 	WeDir,      /* web extension directory */
+	XdgSchemes,
 	ZoomLevel,
 	/* must be last to represent number of config items */
 	LastConfig
@@ -226,6 +228,7 @@ static void update_load_progress(struct Client *);
 static void update_title(struct Client *);
 static void uri_changed(struct Client *);
 static void web_view_crashed(struct Client *);
+static void xdg_open(const gchar *, const gchar *);
 
 /* callback functions */
 static gboolean cb_context_menu(WebKitWebView *, WebKitContextMenu *,
@@ -448,10 +451,15 @@ WebKitWebView *
 cb_wv_create(WebKitWebView *web_view,
              WebKitNavigationAction *navigation_action, gpointer data)
 {
+	struct Client *c;
+
 	(void)navigation_action;
 	(void)data;
 
-	return WEBKIT_WEB_VIEW(client_create(NULL, web_view, FALSE, FALSE)->wv);
+	if ((c = client_create(NULL, web_view, FALSE, FALSE)) == NULL)
+		return NULL;
+
+	return WEBKIT_WEB_VIEW(c->wv);
 }
 
 gboolean
diff --git a/config.h b/config.h
@@ -48,6 +48,7 @@ static Config cfg[LastConfig] = {
 	[UsDir]                 = { NULL,                                           CFG_STRING, {.s = "scripts" }},
 	[UcDir]                 = { NULL,                                           CFG_STRING, {.s = "styles" }},
 	[WeDir]                 = { NULL,                                           CFG_STRING, {.s = "web_extensions" }},
+	[XdgSchemes]            = { __NAME_UPPERCASE__"_XDG_SCHEMES",               CFG_LIST,   {.l = NULL }},
 	[ZoomLevel]             = { __NAME_UPPERCASE__"_ZOOM_LEVEL",                CFG_FLOAT,  {.f = 1.0 }},
 };