Merge pull request #802 from fwSmit/environment-variables

Set environment variables for scripts
This commit is contained in:
Nikos Tsipinakis 2021-01-30 18:06:36 +02:00 committed by GitHub
commit 814e620247
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 182 additions and 23 deletions

View File

@ -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.
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>.
Where icon is the absolute path to the icon file if there is one and urgency is
one of "LOW", "NORMAL" or "CRITICAL".
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.

View File

@ -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);
@ -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,
@ -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;
}

View File

@ -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

View File

@ -129,11 +129,32 @@ 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,
@ -141,10 +162,9 @@ void notification_run_script(struct notification *n)
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);
}
}
}

View File

@ -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;

View File

@ -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: */

View File

@ -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'))
@ -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: */

View File

@ -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();
@ -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();
@ -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);