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. 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 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, passed via environment variables. The following variables are available:
body, icon, urgency. 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 Another, less recommended way to get notifcations details from a script is via
one of "LOW", "NORMAL" or "CRITICAL". 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 If the notification is suppressed, the script will not be run unless
B<always_run_scripts> is set to true. B<always_run_scripts> is set to true.

View File

@ -195,14 +195,14 @@ GdkPixbuf *get_pixbuf_from_file(const char *filename)
return pixbuf; return pixbuf;
} }
GdkPixbuf *get_pixbuf_from_icon(const char *iconname) char *get_path_from_icon_name(const char *iconname)
{ {
if (STR_EMPTY(iconname)) if (STR_EMPTY(iconname))
return NULL; return NULL;
const char *suffixes[] = { ".svg", ".svgz", ".png", ".xpm", NULL }; const char *suffixes[] = { ".svg", ".svgz", ".png", ".xpm", NULL };
GdkPixbuf *pixbuf = NULL;
gchar *uri_path = NULL; gchar *uri_path = NULL;
char *new_name = NULL;
if (g_str_has_prefix(iconname, "file://")) { if (g_str_has_prefix(iconname, "file://")) {
uri_path = g_filename_from_uri(iconname, NULL, NULL); uri_path = g_filename_from_uri(iconname, NULL, NULL);
@ -212,7 +212,7 @@ GdkPixbuf *get_pixbuf_from_icon(const char *iconname)
/* absolute path? */ /* absolute path? */
if (iconname[0] == '/' || iconname[0] == '~') { if (iconname[0] == '/' || iconname[0] == '~') {
pixbuf = get_pixbuf_from_file(iconname); new_name = g_strdup(iconname);
} else { } else {
/* search in icon_path */ /* search in icon_path */
char *start = settings.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); current_folder = g_strndup(start, end - start);
for (const char **suf = suffixes; *suf; suf++) { for (const char **suf = suffixes; *suf; suf++) {
maybe_icon_path = g_strconcat(current_folder, "/", iconname, *suf, NULL); gchar *name_with_extension = g_strconcat(iconname, *suf, NULL);
if (is_readable_file(maybe_icon_path)) maybe_icon_path = g_build_filename(current_folder, name_with_extension, NULL);
pixbuf = get_pixbuf_from_file(maybe_icon_path); if (is_readable_file(maybe_icon_path)) {
new_name = g_strdup(maybe_icon_path);
}
g_free(name_with_extension);
g_free(maybe_icon_path); g_free(maybe_icon_path);
if (pixbuf) if (new_name)
break; break;
} }
g_free(current_folder); g_free(current_folder);
if (pixbuf) if (new_name)
break; break;
start = end + 1; start = end + 1;
} while (STR_FULL(end)); } while (STR_FULL(end));
if (!pixbuf) if (!new_name)
LOG_W("No icon found in path: '%s'", iconname); LOG_W("No icon found in path: '%s'", iconname);
} }
g_free(uri_path); 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; 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); 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 /** 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 * @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; int status;
waitpid(pid1, &status, 0); waitpid(pid1, &status, 0);
} else { } else {
// second fork to prevent zombie processes
int pid2 = fork(); int pid2 = fork();
if (pid2) { if (pid2) {
exit(0); exit(0);
} else { } 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, script,
appname, appname,
summary, summary,
@ -141,10 +162,9 @@ void notification_run_script(struct notification *n)
icon, icon,
urgency, urgency,
(char *)NULL); (char *)NULL);
if (ret != 0) {
LOG_W("Unable to run script: %s", strerror(errno)); LOG_W("Unable to run script %s: %s", n->scripts[i], strerror(errno));
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
}
} }
} }
} }

View File

@ -55,9 +55,9 @@ struct notification {
char *iconname; /**< plain icon information (may be a path or just a name) char *iconname; /**< plain icon information (may be a path or just a name)
Use this to compare the icon name with rules.*/ Use this to compare the icon name with rules.*/
gint64 start; /**< begin of current display */ gint64 start; /**< begin of current display (in milliseconds) */
gint64 timestamp; /**< arrival time */ gint64 timestamp; /**< arrival time (in milliseconds) */
gint64 timeout; /**< time to display */ gint64 timeout; /**< time to display (in milliseconds) */
int locked; /**< If non-zero the notification is locked **/ int locked; /**< If non-zero the notification is locked **/
GHashTable *actions; GHashTable *actions;

View File

@ -247,4 +247,16 @@ const char *user_get_home(void)
return home_directory; 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: */ /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -4,6 +4,7 @@
#include <glib.h> #include <glib.h>
#include <string.h> #include <string.h>
#include <stdbool.h>
//! Test if a string is NULL or empty //! Test if a string is NULL or empty
#define STR_EMPTY(s) (!s || (*s == '\0')) #define STR_EMPTY(s) (!s || (*s == '\0'))
@ -137,5 +138,16 @@ gint64 time_monotonic_now(void);
*/ */
const char *user_get_home(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 #endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -12,6 +12,67 @@
extern const char *base; 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) TEST test_get_pixbuf_from_file_tilde(void)
{ {
const char *home = g_get_home_dir(); 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) TEST test_get_pixbuf_from_icon_both(void)
{ {
GdkPixbuf *pixbuf = get_pixbuf_from_icon("icon1"); GdkPixbuf *pixbuf = get_pixbuf_from_icon("icon1");
ASSERT(pixbuf); // the first icon found is invalid, so the pixbuf is empty
ASSERTm("SVG pixbuf hasn't precedence", IS_ICON_SVG(pixbuf)); ASSERT(!pixbuf);
g_clear_pointer(&pixbuf, g_object_unref); g_clear_pointer(&pixbuf, g_object_unref);
PASS(); PASS();
@ -170,12 +231,23 @@ TEST test_get_pixbuf_from_icon_both_is_scaled(void)
SUITE(suite_icon) 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( settings.icon_path = g_strconcat(
base, ICONPREFIX "/invalid" base, ICONPREFIX "/invalid"
":", base, ICONPREFIX "/valid" ":", base, ICONPREFIX "/valid"
":", base, ICONPREFIX "/both", ":", base, ICONPREFIX "/both",
NULL); 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_tilde);
RUN_TEST(test_get_pixbuf_from_file_absolute); RUN_TEST(test_get_pixbuf_from_file_absolute);
RUN_TEST(test_get_pixbuf_from_icon_invalid); RUN_TEST(test_get_pixbuf_from_icon_invalid);