Skip to content

Commit

Permalink
Add support for user actions in the link menu
Browse files Browse the repository at this point in the history
Allows the user to define additional entries in the link menu which will
execute the given program/script. Each actions is defined using the
"link_action" option. The link URL is stored in the $url enviroment
variable and the current page in $origin, so the user can customize how
do the handling.

Here is a simple example to add three new entries:

    link_action="Debug variables:echo url=$url origin=$origin"
    link_action="Open in MPV:mpv $url"
    link_action="Open in Firefox:firefox $url"

The command is spawned in a forked process using the system() call,
which uses the shell to expand any variable. In particular, the $url
variable is set to the current URL being opened.

Fixes: #3
  • Loading branch information
rodarima committed Dec 17, 2024
1 parent 0f49ee5 commit 140d9eb
Show file tree
Hide file tree
Showing 14 changed files with 233 additions and 16 deletions.
2 changes: 2 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ dillo-3.2.0 [Not released yet]
- Reload current page on SIGUSR1 signal
- Print library versions and enabled features with dillo -v.
- Allow image formats to be ignored with the "ignore_image_formats" option.
- Add the "link_action" option to define custom menu entries to open links
with external programs or scripts.
Patches: Rodrigo Arias Mallo
+- Add primitive support for SVG using the nanosvg.h library.
- Add support for ch, rem, vw, vh, vmin and vmax CSS units.
Expand Down
11 changes: 11 additions & 0 deletions dillorc
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@
# jump to a given position.
#scrollbar_page_mode=NO

# Define custom actions for the link menu. The format is <label>:<cmd>. The
# command will be executed in the system shell using the system() call. You can
# implement your own handling logic in a script or program. The following
# environment variables are set:
# $url: URL being opened
# $origin: URL of the current document
# Examples:
# link_action="Debug variables:echo url=$url origin=$origin"
# link_action="Open in MPV:mpv $url"
# link_action="Open in Firefox:firefox $url"

#-------------------------------------------------------------------------
# RENDERING SECTION
#-------------------------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ dillo_SOURCES = \
bw.c \
cookies.c \
cookies.h \
actions.c \
actions.h \
hsts.c \
hsts.h \
auth.c \
Expand Down
65 changes: 65 additions & 0 deletions src/actions.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* File: actions.c
*
* Copyright (C) 2024 Rodrigo Arias Mallo <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*/

#include "actions.h"
#include "msg.h"
#include "../dlib/dlib.h"
#include <errno.h>

static Dlist *link_actions = NULL;

void
action_parse(char *line)
{
char *label = strtok(line, ":");

if (label == NULL || strlen(label) == 0) {
MSG("Missing action label, ignoring '%s'\n", line);
return;
}

//MSG("Got label='%s'\n", label);

char *cmd = strtok(NULL, "");

if (cmd == NULL || strlen(cmd) == 0) {
MSG("Missing action command, ignoring '%s'\n", line);
return;
}

//MSG("Got action label='%s' cmd='%s'\n", label, cmd);

Action *action = dMalloc(sizeof(Action));
action->label = dStrdup(label);
action->cmd = dStrdup(cmd);

dList_append(link_actions, action);
}

void
a_Actions_init(void)
{
int n = dList_length(prefs.link_actions);

link_actions = dList_new(n);

for (int i = 0; i < n; i++) {
char *line = dList_nth_data(prefs.link_actions, i);
if (line)
action_parse(line);
}
}

Dlist *
a_Actions_link_get(void)
{
return link_actions;
}
32 changes: 32 additions & 0 deletions src/actions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* File: actions.h
*
* Copyright (C) 2024 Rodrigo Arias Mallo <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*/

#ifndef ACTIONS_H
#define ACTIONS_H

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

#include "dlib/dlib.h"

typedef struct {
char *label;
char *cmd;
} Action;

void a_Actions_init(void);
Dlist *a_Actions_link_get(void);

#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* ACTIONS_H*/
2 changes: 2 additions & 0 deletions src/dillo.cc
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
#include "capi.h"
#include "dicache.h"
#include "cookies.h"
#include "actions.h"
#include "hsts.h"
#include "domain.h"
#include "auth.h"
Expand Down Expand Up @@ -489,6 +490,7 @@ int main(int argc, char **argv)
a_Dicache_init();
a_Bw_init();
a_Cookies_init();
a_Actions_init();
a_Hsts_init(Paths::getPrefsFP(PATHS_HSTS_PRELOAD));
a_Auth_init();
a_UIcmd_init();
Expand Down
2 changes: 1 addition & 1 deletion src/html.cc
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,7 @@ bool DilloHtml::HtmlLinkReceiver::press (Widget *widget, int link, int img,
a_UIcmd_page_popup(bw, bw->num_page_bugs != 0, html->cssUrls);
ret = true;
} else {
a_UIcmd_link_popup(bw, html->links->get(link));
a_UIcmd_link_popup(bw, html->links->get(link), html->page_url);
ret = true;
}
}
Expand Down
121 changes: 110 additions & 11 deletions src/menu.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@
#include <FL/Fl.H>
#include <FL/Fl_Menu_Item.H>

#include <unistd.h>
#include <errno.h>
#include "lout/misc.hh" /* SimpleVector */
#include "msg.h"
#include "menu.hh"
#include "actions.h"
#include "uicmd.hh"
#include "history.h"
#include "html.hh"
Expand Down Expand Up @@ -430,38 +433,134 @@ void a_Menu_page_popup(BrowserWindow *bw, const DilloUrl *url,
a_Timeout_add(0.0, Menu_popup_cb, (void*)&page_data);
}

static Fl_Menu_Item link_menu[] = {
static Fl_Menu_Item link_menu_[] = {
{"Open link in new tab", 0, Menu_open_url_nt_cb,0,0,0,0,0,0},
{"Open link in new window", 0, Menu_open_url_nw_cb,0,FL_MENU_DIVIDER,0,0,
0,0},
{"Bookmark this link", 0, Menu_add_bookmark_cb,0,0,0,0,0,0},
{"Copy link location", 0, Menu_copy_urlstr_cb,0,FL_MENU_DIVIDER,0,0,0,0},
{"Save link as...", 0, Menu_save_link_cb,0,0,0,0,0,0},
{"Save link as...", 0, Menu_save_link_cb,0,FL_MENU_DIVIDER,0,0,0,0},
{0,0,0,0,0,0,0,0,0}
};

static void Menu_set_link_menu_user_data(void *user_data)
/* As we can only provide a pointer to the link menu items, we need to
* create an auxiliary structure to hold the current URL and the program
* that should run on each item. */
struct link_menu_item {
const DilloUrl *url;
const DilloUrl *origin;
Action *action;
};

/**
* Open URL following a custom action
*/
static void Menu_open_url_action_cb(Fl_Widget*, void *user_data)
{
/* Don't use popup_url because it is used for the image URL when coming from
* the image menu. We should get rid of the global variables and pass them
* via the user_data. */

struct link_menu_item *mitem = (struct link_menu_item *) user_data;
const DilloUrl *url = mitem->url;
const DilloUrl *origin = mitem->origin;
Action *action = mitem->action;

/* Set the environment variables */
setenv("url", URL_STR(url), 1);
setenv("origin", URL_STR(origin), 1);

if (fork() == 0) {
/* Child */
errno = 0;
int ret = system(action->cmd);
if (ret == -1) {
MSG("Cannot run '%s': %s\n", action->cmd, strerror(errno));
exit(1);
} else if (ret != 0) {
MSG("Command exited with '%d': %s\n", ret, action->cmd);
exit(1);
} else {
/* All good, terminate the child */
exit(0);
}
}
}

static Fl_Menu_Item *get_link_menu(void)
{
int i;
static Fl_Menu_Item *link_menu = NULL;
static struct link_menu_item *link_menu_item = NULL;

/* Already initialized */
if (link_menu != NULL)
return link_menu;

for (i = 0; link_menu[i].label(); i++)
link_menu[i].user_data(user_data);
Dlist *actions = a_Actions_link_get();
int nactions = dList_length(actions);

/* Count static menu entries */
int nstatic = 0;
while (link_menu_[nstatic].text)
nstatic++;

int ntotal = nstatic + nactions;
link_menu = (Fl_Menu_Item *) calloc(ntotal + 1, sizeof(Fl_Menu_Item));
link_menu_item = (struct link_menu_item *) calloc(nactions, sizeof(struct link_menu_item));

/* Just copy the static entries */
for (int i = 0; i < nstatic; i++) {
memcpy(&link_menu[i], &link_menu_[i], sizeof(Fl_Menu_Item));
}

/* And append the dynamic ones */
for (int i = 0; i < nactions; i++) {
Action *action = (Action *) dList_nth_data(actions, i);
struct link_menu_item *mitem = &link_menu_item[i];
mitem->url = NULL; /* Not known yet */
mitem->action = action;

Fl_Menu_Item *item = &link_menu[nstatic + i];
item->text = action->label;
item->callback_ = Menu_open_url_action_cb;
item->user_data_ = mitem;
}

return link_menu;
}

static void Menu_set_link_menu_user_data(const DilloUrl *url, const DilloUrl *page_url)
{
Fl_Menu_Item *link_menu = get_link_menu();
for (int i = 0; link_menu[i].label(); i++) {
if (link_menu[i].callback_ == Menu_open_url_action_cb) {
struct link_menu_item *mitem = (struct link_menu_item *) link_menu[i].user_data_;
/* Set the url and origin */
mitem->url = url;
mitem->origin = page_url;
} else {
link_menu[i].user_data_ = (void *) url;
}
}
}

/**
* Link popup menu (construction & popup)
*/
void a_Menu_link_popup(BrowserWindow *bw, const DilloUrl *url)
void a_Menu_link_popup(BrowserWindow *bw, const DilloUrl *url, const DilloUrl *page_url)
{
static Menu_popup_data_t link_data = {"Link menu", NULL, link_menu};
static Menu_popup_data_t link_data = {"Link menu", NULL, NULL};

popup_x = Fl::event_x();
popup_y = Fl::event_y();
popup_bw = bw;
a_Url_free(popup_url);
popup_url = a_Url_dup(url);

Menu_set_link_menu_user_data(popup_url);
Fl_Menu_Item *link_menu = get_link_menu();
link_data.menu = link_menu;

Menu_set_link_menu_user_data(popup_url, page_url);

a_Timeout_add(0.0, Menu_popup_cb, (void*)&link_data);
}
Expand All @@ -484,7 +583,7 @@ void a_Menu_image_popup(BrowserWindow *bw, const DilloUrl *url,
{"Bookmark this image", 0, Menu_add_bookmark_cb,0,0,0,0,0,0},
{"Copy image location", 0,Menu_copy_urlstr_cb,0,FL_MENU_DIVIDER,0,0,0,0},
{"Save image as...", 0, Menu_save_link_cb, 0, FL_MENU_DIVIDER,0,0,0,0},
{"Link menu", 0, Menu_nop_cb, link_menu, FL_SUBMENU_POINTER,0,0,0,0},
{"Link menu", 0, Menu_nop_cb, get_link_menu(), FL_SUBMENU_POINTER,0,0,0,0},
{0,0,0,0,0,0,0,0,0}
};
static Menu_popup_data_t image_data = {"Image menu", NULL, pm};
Expand Down Expand Up @@ -517,7 +616,7 @@ void a_Menu_image_popup(BrowserWindow *bw, const DilloUrl *url,

if (link_url) {
pm[7].activate();
Menu_set_link_menu_user_data(popup_link_url);
Menu_set_link_menu_user_data(popup_link_url, popup_page_url);
} else {
pm[7].deactivate();
}
Expand Down
3 changes: 2 additions & 1 deletion src/menu.hh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ extern "C" {

void a_Menu_page_popup(BrowserWindow *bw, const DilloUrl *url,
bool_t has_bugs, void *v_cssUrls);
void a_Menu_link_popup(BrowserWindow *bw, const DilloUrl *url);
void a_Menu_link_popup(BrowserWindow *bw, const DilloUrl *url,
const DilloUrl *page_url);
void a_Menu_image_popup(BrowserWindow *bw, const DilloUrl *url,
bool_t loaded_img, DilloUrl *page_url,
DilloUrl *link_url);
Expand Down
1 change: 1 addition & 0 deletions src/prefs.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ void a_Prefs_init(void)
prefs.scroll_switches_tabs = TRUE;
prefs.scroll_switches_tabs_reverse = FALSE;
prefs.no_proxy = dStrdup(PREFS_NO_PROXY);
prefs.link_actions = dList_new(16);
prefs.panel_size = P_medium;
prefs.parse_embedded_css=TRUE;
prefs.save_dir = dStrdup(PREFS_SAVE_DIR);
Expand Down
1 change: 1 addition & 0 deletions src/prefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ typedef struct {
int penalty_hyphen, penalty_hyphen_2;
int penalty_em_dash_left, penalty_em_dash_right, penalty_em_dash_right_2;
int stretchability_factor;
Dlist *link_actions;
} DilloPrefs;

/** Global Data */
Expand Down
1 change: 1 addition & 0 deletions src/prefsparser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ void PrefsParser::parse(FILE *fp)
PREFS_FRACTION_100, 0 },
{ "stretchability_factor", &prefs.stretchability_factor,
PREFS_FRACTION_100, 0 },
{ "link_action", &prefs.link_actions, PREFS_STRINGS, 0 },
{ "zoom_factor", &prefs.zoom_factor, PREFS_DOUBLE, 0 }
};
// changing the LC_NUMERIC locale (temporarily) to C
Expand Down
4 changes: 2 additions & 2 deletions src/uicmd.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1269,9 +1269,9 @@ void a_UIcmd_page_popup(void *vbw, bool_t has_bugs, void *v_cssUrls)
/*
* Popup the link menu
*/
void a_UIcmd_link_popup(void *vbw, const DilloUrl *url)
void a_UIcmd_link_popup(void *vbw, const DilloUrl *url, const DilloUrl *page_url)
{
a_Menu_link_popup((BrowserWindow*)vbw, url);
a_Menu_link_popup((BrowserWindow*)vbw, url, page_url);
}

/*
Expand Down
2 changes: 1 addition & 1 deletion src/uicmd.hh
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ void a_UIcmd_findbar_toggle(BrowserWindow *bw, int on);
void a_UIcmd_focus_main_area(BrowserWindow *bw);
void a_UIcmd_focus_location(void *vbw);
void a_UIcmd_page_popup(void *vbw, bool_t has_bugs, void *v_cssUrls);
void a_UIcmd_link_popup(void *vbw, const DilloUrl *url);
void a_UIcmd_link_popup(void *vbw, const DilloUrl *url, const DilloUrl *page_url);
void a_UIcmd_image_popup(void *vbw, const DilloUrl *url, bool_t loaded_img,
DilloUrl *page_url, DilloUrl *link_url);
void a_UIcmd_form_popup(void *vbw, const DilloUrl *url, void *vform,
Expand Down

0 comments on commit 140d9eb

Please sign in to comment.