Skip to content

Commit

Permalink
Merge pull request #802 from fwSmit/environment-variables
Browse files Browse the repository at this point in the history
Set environment variables for scripts
  • Loading branch information
tsipinakis authored Jan 30, 2021
2 parents e90f605 + bc3de38 commit 814e620
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 24 deletions.
21 changes: 16 additions & 5 deletions docs/dunst.5.pod
Original file line number Diff line number Diff line change
Expand Up @@ -869,11 +869,22 @@ Within rules you can specify a script to be run every time the rule is matched
by assigning the 'script' option to the name of the script to be run.

When the script is called details of the notification that triggered it will be
passed via command line parameters in the following order: appname, summary,
body, icon, urgency.

Where icon is the absolute path to the icon file if there is one and urgency is
one of "LOW", "NORMAL" or "CRITICAL".
passed via environment variables. The following variables are available:
B<DUNST_APP_NAME>, B<DUNST_SUMMARY>, B<DUNST_BODY>, B<DUNST_ICON_PATH>,
B<DUNST_URGENCY>, B<DUNST_ID>, B<DUNST_PROGRESS>, B<DUNST_CATEGORY>,
B<DUNST_STACK_TAG>, B<DUNST_URLS>, B<DUNST_TIMEOUT>, B<DUNST_TIMESTAMP>
and B<DUNST_STACK_TAG>.

Another, less recommended way to get notifcations details from a script is via
command line parameters. These are passed to the script in the following order:
B<appname>, B<summary>, B<body>, B<icon_path>, B<urgency>.

Where B<DUNST_ICON_PATH> or B<icon_path> is the absolute path to the icon file
if there is one. B<DUNST_URGENCY> or B<urgency> is one of "LOW", "NORMAL" or
"CRITICAL". B<DUNST_URLS> is a newline-separated list of urls associated with
the notification.

Note that some variables may be empty.

If the notification is suppressed, the script will not be run unless
B<always_run_scripts> is set to true.
Expand Down
39 changes: 30 additions & 9 deletions src/icon.c
Original file line number Diff line number Diff line change
Expand Up @@ -195,14 +195,14 @@ GdkPixbuf *get_pixbuf_from_file(const char *filename)
return pixbuf;
}

GdkPixbuf *get_pixbuf_from_icon(const char *iconname)
char *get_path_from_icon_name(const char *iconname)
{
if (STR_EMPTY(iconname))
return NULL;

const char *suffixes[] = { ".svg", ".svgz", ".png", ".xpm", NULL };
GdkPixbuf *pixbuf = NULL;
gchar *uri_path = NULL;
char *new_name = NULL;

if (g_str_has_prefix(iconname, "file://")) {
uri_path = g_filename_from_uri(iconname, NULL, NULL);
Expand All @@ -212,7 +212,7 @@ GdkPixbuf *get_pixbuf_from_icon(const char *iconname)

/* absolute path? */
if (iconname[0] == '/' || iconname[0] == '~') {
pixbuf = get_pixbuf_from_file(iconname);
new_name = g_strdup(iconname);
} else {
/* search in icon_path */
char *start = settings.icon_path,
Expand All @@ -224,26 +224,47 @@ GdkPixbuf *get_pixbuf_from_icon(const char *iconname)
current_folder = g_strndup(start, end - start);

for (const char **suf = suffixes; *suf; suf++) {
maybe_icon_path = g_strconcat(current_folder, "/", iconname, *suf, NULL);
if (is_readable_file(maybe_icon_path))
pixbuf = get_pixbuf_from_file(maybe_icon_path);
gchar *name_with_extension = g_strconcat(iconname, *suf, NULL);
maybe_icon_path = g_build_filename(current_folder, name_with_extension, NULL);
if (is_readable_file(maybe_icon_path)) {
new_name = g_strdup(maybe_icon_path);
}
g_free(name_with_extension);
g_free(maybe_icon_path);

if (pixbuf)
if (new_name)
break;
}

g_free(current_folder);
if (pixbuf)
if (new_name)
break;

start = end + 1;
} while (STR_FULL(end));
if (!pixbuf)
if (!new_name)
LOG_W("No icon found in path: '%s'", iconname);
}

g_free(uri_path);
return new_name;
}

GdkPixbuf *get_pixbuf_from_icon(const char *iconname)
{
char *path = get_path_from_icon_name(iconname);
if (!path) {
return NULL;
}

GdkPixbuf *pixbuf = NULL;

pixbuf = get_pixbuf_from_file(path);
g_free(path);

if (!pixbuf)
LOG_W("No icon found in path: '%s'", iconname);

return pixbuf;
}

Expand Down
11 changes: 11 additions & 0 deletions src/icon.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf);
*/
GdkPixbuf *get_pixbuf_from_file(const char *filename);

/** Retrieve a path from an icon name.
*
* @param iconname A string describing a `file://` URL, an arbitary filename
* or an icon name, which then gets searched for in the
* settings.icon_path
*
* @return a newly allocated string with the icon path
* @retval NULL: file does not exist, not readable, etc..
*/
char *get_path_from_icon_name(const char *iconname);

/** Retrieve an icon by its name sent via the notification bus, scaled according to settings
*
* @param iconname A string describing a `file://` URL, an arbitary filename
Expand Down
30 changes: 25 additions & 5 deletions src/notification.c
Original file line number Diff line number Diff line change
Expand Up @@ -129,22 +129,42 @@ void notification_run_script(struct notification *n)
int status;
waitpid(pid1, &status, 0);
} else {
// second fork to prevent zombie processes
int pid2 = fork();
if (pid2) {
exit(0);
} else {
int ret = execlp(script,
// Set environment variables
gchar *n_id_str = g_strdup_printf("%i", n->id);
gchar *n_progress_str = g_strdup_printf("%i", n->progress);
gchar *n_timeout_str = g_strdup_printf("%li", n->timeout/1000);
gchar *n_timestamp_str = g_strdup_printf("%li", n->timestamp / 1000);
char* icon_path = get_path_from_icon_name(icon);
safe_setenv("DUNST_APP_NAME", appname);
safe_setenv("DUNST_SUMMARY", summary);
safe_setenv("DUNST_BODY", body);
safe_setenv("DUNST_ICON_PATH", icon_path);
safe_setenv("DUNST_URGENCY", urgency);
safe_setenv("DUNST_ID", n_id_str);
safe_setenv("DUNST_PROGRESS", n_progress_str);
safe_setenv("DUNST_CATEGORY", n->category);
safe_setenv("DUNST_STACK_TAG", n->stack_tag);
safe_setenv("DUNST_URLS", n->urls);
safe_setenv("DUNST_TIMEOUT", n_timeout_str);
safe_setenv("DUNST_TIMESTAMP", n_timestamp_str);
safe_setenv("DUNST_STACK_TAG", n->stack_tag);

execlp(script,
script,
appname,
summary,
body,
icon,
urgency,
(char *)NULL);
if (ret != 0) {
LOG_W("Unable to run script: %s", strerror(errno));
exit(EXIT_FAILURE);
}

LOG_W("Unable to run script %s: %s", n->scripts[i], strerror(errno));
exit(EXIT_FAILURE);
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/notification.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ struct notification {
char *iconname; /**< plain icon information (may be a path or just a name)
Use this to compare the icon name with rules.*/

gint64 start; /**< begin of current display */
gint64 timestamp; /**< arrival time */
gint64 timeout; /**< time to display */
gint64 start; /**< begin of current display (in milliseconds) */
gint64 timestamp; /**< arrival time (in milliseconds) */
gint64 timeout; /**< time to display (in milliseconds) */
int locked; /**< If non-zero the notification is locked **/

GHashTable *actions;
Expand Down
12 changes: 12 additions & 0 deletions src/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -247,4 +247,16 @@ const char *user_get_home(void)
return home_directory;
}

bool safe_setenv(const char* key, const char* value){
if (!key)
return false;

if (!value)
setenv(key, "", 1);
else
setenv(key, value, 1);

return true;
}

/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
12 changes: 12 additions & 0 deletions src/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <glib.h>
#include <string.h>
#include <stdbool.h>

//! Test if a string is NULL or empty
#define STR_EMPTY(s) (!s || (*s == '\0'))
Expand Down Expand Up @@ -137,5 +138,16 @@ gint64 time_monotonic_now(void);
*/
const char *user_get_home(void);

/**
* Try to set an environment variable safely. If an environment variable with
* name `key` exists, it will be overwritten.
* If `value` is null, `key` will be set to an empty string.
*
* @param key (nullable) The environment variable to change
* @param value (nullable) The value to change it to.
* @returns: A bool that is true when it succeeds
*/
bool safe_setenv(const char* key, const char* value);

#endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
76 changes: 74 additions & 2 deletions test/icon.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,67 @@

extern const char *base;

TEST test_get_path_from_icon_null(void){
char *result = get_path_from_icon_name(NULL);
ASSERT_EQ(result, NULL);
PASS();
}

TEST test_get_path_from_icon_sorting(void)
{
const char *iconpath = ICONPREFIX;

const char* icon_names[] = { "onlypng", "onlysvg", "icon1" };
const char* icon_paths[] = { "valid/onlypng.png", "valid/onlysvg.svg", "invalid/icon1.svg" };
ASSERT_EQm("Test is incorrect", G_N_ELEMENTS(icon_names), G_N_ELEMENTS(icon_paths));

for (int i = 0; i < G_N_ELEMENTS(icon_names); i++){
gchar *path = g_build_filename(base, iconpath, icon_paths[i], NULL);
char *result = get_path_from_icon_name(icon_names[i]);
ASSERT(result);
ASSERT_EQ(strcmp(result, path), 0);
g_free(path);
g_free(result);
}

PASS();
}

TEST test_get_path_from_icon_name(void)
{
const char *iconpath = ICONPREFIX;

const char* icon_name = "onlypng";
const char* expected_suffix = ".png";
char* full_name = g_strconcat(icon_name, expected_suffix, NULL);

gchar *path = g_build_filename(base, iconpath, "valid", full_name, NULL);
char *result = get_path_from_icon_name(icon_name);

ASSERT(result);
ASSERT_EQ(strcmp(result, path), 0);

g_free(full_name);
g_free(path);
g_free(result);
PASS();
}

TEST test_get_path_from_icon_name_full(void)
{
const char *iconpath = ICONPREFIX;

gchar *path = g_build_filename(base, iconpath, "valid", "icon1.svg", NULL);

char *result = get_path_from_icon_name(path);
ASSERT(result);
ASSERT_EQ(strcmp(result, path), 0);

g_free(path);
g_free(result);
PASS();
}

TEST test_get_pixbuf_from_file_tilde(void)
{
const char *home = g_get_home_dir();
Expand Down Expand Up @@ -62,8 +123,8 @@ TEST test_get_pixbuf_from_icon_invalid(void)
TEST test_get_pixbuf_from_icon_both(void)
{
GdkPixbuf *pixbuf = get_pixbuf_from_icon("icon1");
ASSERT(pixbuf);
ASSERTm("SVG pixbuf hasn't precedence", IS_ICON_SVG(pixbuf));
// the first icon found is invalid, so the pixbuf is empty
ASSERT(!pixbuf);
g_clear_pointer(&pixbuf, g_object_unref);

PASS();
Expand Down Expand Up @@ -170,12 +231,23 @@ TEST test_get_pixbuf_from_icon_both_is_scaled(void)

SUITE(suite_icon)
{
// set only valid icons in the path
settings.icon_path = g_strconcat(
base, ICONPREFIX "/valid"
":", base, ICONPREFIX "/both",
NULL);
RUN_TEST(test_get_path_from_icon_name);

g_clear_pointer(&settings.icon_path, g_free);
settings.icon_path = g_strconcat(
base, ICONPREFIX "/invalid"
":", base, ICONPREFIX "/valid"
":", base, ICONPREFIX "/both",
NULL);

RUN_TEST(test_get_path_from_icon_null);
RUN_TEST(test_get_path_from_icon_sorting);
RUN_TEST(test_get_path_from_icon_name_full);
RUN_TEST(test_get_pixbuf_from_file_tilde);
RUN_TEST(test_get_pixbuf_from_file_absolute);
RUN_TEST(test_get_pixbuf_from_icon_invalid);
Expand Down

0 comments on commit 814e620

Please sign in to comment.