Merge pull request #549 from bebehei/threaded-dmenu
Threaded dmenu Create a refcounting mechanism for notifications and "lock" them while displayed in dmenu. Fixes #456
This commit is contained in:
commit
8ba4983ce0
17
src/dbus.c
17
src/dbus.c
@ -137,6 +137,7 @@ static struct notification *dbus_message_to_notification(const gchar *sender, GV
|
|||||||
|
|
||||||
n->actions = g_malloc0(sizeof(struct actions));
|
n->actions = g_malloc0(sizeof(struct actions));
|
||||||
n->dbus_client = g_strdup(sender);
|
n->dbus_client = g_strdup(sender);
|
||||||
|
n->dbus_valid = true;
|
||||||
|
|
||||||
{
|
{
|
||||||
GVariantIter *iter = g_variant_iter_new(parameters);
|
GVariantIter *iter = g_variant_iter_new(parameters);
|
||||||
@ -282,7 +283,7 @@ static void on_notify(GDBusConnection *connection,
|
|||||||
// The message got discarded
|
// The message got discarded
|
||||||
if (id == 0) {
|
if (id == 0) {
|
||||||
signal_notification_closed(n, 2);
|
signal_notification_closed(n, 2);
|
||||||
notification_free(n);
|
notification_unref(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
wake_up();
|
wake_up();
|
||||||
@ -316,6 +317,12 @@ static void on_get_server_information(GDBusConnection *connection,
|
|||||||
|
|
||||||
void signal_notification_closed(struct notification *n, enum reason reason)
|
void signal_notification_closed(struct notification *n, enum reason reason)
|
||||||
{
|
{
|
||||||
|
if (!n->dbus_valid) {
|
||||||
|
LOG_W("Closing notification '%s' not supported. "
|
||||||
|
"Notification already closed.", n->summary);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (reason < REASON_MIN || REASON_MAX < reason) {
|
if (reason < REASON_MIN || REASON_MAX < reason) {
|
||||||
LOG_W("Closing notification with reason '%d' not supported. "
|
LOG_W("Closing notification with reason '%d' not supported. "
|
||||||
"Closing it with reason '%d'.", reason, REASON_UNDEF);
|
"Closing it with reason '%d'.", reason, REASON_UNDEF);
|
||||||
@ -337,6 +344,8 @@ void signal_notification_closed(struct notification *n, enum reason reason)
|
|||||||
body,
|
body,
|
||||||
&err);
|
&err);
|
||||||
|
|
||||||
|
n->dbus_valid = false;
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
LOG_W("Unable to close notification: %s", err->message);
|
LOG_W("Unable to close notification: %s", err->message);
|
||||||
g_error_free(err);
|
g_error_free(err);
|
||||||
@ -346,6 +355,12 @@ void signal_notification_closed(struct notification *n, enum reason reason)
|
|||||||
|
|
||||||
void signal_action_invoked(const struct notification *n, const char *identifier)
|
void signal_action_invoked(const struct notification *n, const char *identifier)
|
||||||
{
|
{
|
||||||
|
if (!n->dbus_valid) {
|
||||||
|
LOG_W("Invoking action '%s' not supported. "
|
||||||
|
"Notification already closed.", identifier);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
GVariant *body = g_variant_new("(us)", n->id, identifier);
|
GVariant *body = g_variant_new("(us)", n->id, identifier);
|
||||||
GError *err = NULL;
|
GError *err = NULL;
|
||||||
|
|
||||||
|
246
src/menu.c
246
src/menu.c
@ -23,6 +23,12 @@
|
|||||||
static bool is_initialized = false;
|
static bool is_initialized = false;
|
||||||
static regex_t cregex;
|
static regex_t cregex;
|
||||||
|
|
||||||
|
struct notification_lock {
|
||||||
|
struct notification *n;
|
||||||
|
gint64 timeout;
|
||||||
|
};
|
||||||
|
static gpointer context_menu_thread(gpointer data);
|
||||||
|
|
||||||
static int regex_init(void)
|
static int regex_init(void)
|
||||||
{
|
{
|
||||||
if (is_initialized)
|
if (is_initialized)
|
||||||
@ -96,37 +102,45 @@ char *extract_urls(const char *to_match)
|
|||||||
*/
|
*/
|
||||||
void open_browser(const char *in)
|
void open_browser(const char *in)
|
||||||
{
|
{
|
||||||
char *url = NULL;
|
if (!settings.browser_cmd) {
|
||||||
|
LOG_C("Unable to open browser: No browser command set.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *url, *end;
|
||||||
// If any, remove leading [ linktext ] from URL
|
// If any, remove leading [ linktext ] from URL
|
||||||
const char *end = strstr(in, "] ");
|
if (*in == '[' && (end = strstr(in, "] ")))
|
||||||
if (*in == '[' && end)
|
|
||||||
url = g_strdup(end + 2);
|
url = g_strdup(end + 2);
|
||||||
else
|
else
|
||||||
url = g_strdup(in);
|
url = g_strdup(in);
|
||||||
|
|
||||||
int browser_pid1 = fork();
|
int argc = 2+g_strv_length(settings.browser_cmd);
|
||||||
|
char **argv = g_malloc_n(argc, sizeof(char*));
|
||||||
|
|
||||||
if (browser_pid1) {
|
memcpy(argv, settings.browser_cmd, argc * sizeof(char*));
|
||||||
g_free(url);
|
argv[argc-2] = url;
|
||||||
int status;
|
argv[argc-1] = NULL;
|
||||||
waitpid(browser_pid1, &status, 0);
|
|
||||||
} else {
|
GError *err = NULL;
|
||||||
int browser_pid2 = fork();
|
g_spawn_async(NULL,
|
||||||
if (browser_pid2) {
|
argv,
|
||||||
exit(0);
|
NULL,
|
||||||
} else {
|
G_SPAWN_DEFAULT
|
||||||
char *browser_cmd = g_strconcat(settings.browser, " ", url, NULL);
|
| G_SPAWN_SEARCH_PATH
|
||||||
char **cmd = g_strsplit(browser_cmd, " ", 0);
|
| G_SPAWN_STDOUT_TO_DEV_NULL
|
||||||
execvp(cmd[0], cmd);
|
| G_SPAWN_STDERR_TO_DEV_NULL,
|
||||||
// execvp won't return if it's successful
|
NULL,
|
||||||
// so, if we're here, it's definitely an error
|
NULL,
|
||||||
fprintf(stderr, "Warning: failed to execute '%s': %s\n",
|
NULL,
|
||||||
settings.browser,
|
&err);
|
||||||
strerror(errno));
|
|
||||||
exit(EXIT_FAILURE);
|
if (err) {
|
||||||
}
|
LOG_C("Cannot spawn browser: %s", err->message);
|
||||||
|
g_error_free(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_free(argv);
|
||||||
|
g_free(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -171,38 +185,128 @@ void invoke_action(const char *action)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Dispatch whatever has been returned
|
* Dispatch whatever has been returned by dmenu.
|
||||||
* by the menu.
|
* If the given result of dmenu is empty or NULL, nothing will be done.
|
||||||
|
*
|
||||||
|
* @param input The result from dmenu.
|
||||||
*/
|
*/
|
||||||
void dispatch_menu_result(const char *input)
|
void dispatch_menu_result(const char *input)
|
||||||
{
|
{
|
||||||
|
if (!input)
|
||||||
|
return;
|
||||||
|
|
||||||
char *in = g_strdup(input);
|
char *in = g_strdup(input);
|
||||||
g_strstrip(in);
|
g_strstrip(in);
|
||||||
if (in[0] == '#') {
|
|
||||||
|
if (in[0] == '#')
|
||||||
invoke_action(in + 1);
|
invoke_action(in + 1);
|
||||||
} else {
|
else if (in[0] != '\0')
|
||||||
open_browser(in);
|
open_browser(in);
|
||||||
}
|
|
||||||
g_free(in);
|
g_free(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Call dmenu with the specified input. Blocks until dmenu is finished.
|
||||||
|
*
|
||||||
|
* @param dmenu_input The input string to feed into dmenu
|
||||||
|
* @returns the selected string from dmenu
|
||||||
|
*/
|
||||||
|
char *invoke_dmenu(const char *dmenu_input)
|
||||||
|
{
|
||||||
|
if (!settings.dmenu_cmd) {
|
||||||
|
LOG_C("Unable to open dmenu: No dmenu command set.");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dmenu_input || *dmenu_input == '\0')
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
gint dunst_to_dmenu;
|
||||||
|
gint dmenu_to_dunst;
|
||||||
|
GError *err = NULL;
|
||||||
|
char buf[1024];
|
||||||
|
char *ret = NULL;
|
||||||
|
|
||||||
|
g_spawn_async_with_pipes(NULL,
|
||||||
|
settings.dmenu_cmd,
|
||||||
|
NULL,
|
||||||
|
G_SPAWN_DEFAULT
|
||||||
|
| G_SPAWN_SEARCH_PATH,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
&dunst_to_dmenu,
|
||||||
|
&dmenu_to_dunst,
|
||||||
|
NULL,
|
||||||
|
&err);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
LOG_C("Cannot spawn dmenu: %s", err->message);
|
||||||
|
g_error_free(err);
|
||||||
|
} else {
|
||||||
|
size_t wlen = strlen(dmenu_input);
|
||||||
|
if (write(dunst_to_dmenu, dmenu_input, wlen) != wlen) {
|
||||||
|
LOG_W("Cannot feed dmenu with input: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
close(dunst_to_dmenu);
|
||||||
|
|
||||||
|
ssize_t rlen = read(dmenu_to_dunst, buf, sizeof(buf));
|
||||||
|
close(dmenu_to_dunst);
|
||||||
|
|
||||||
|
if (rlen > 0)
|
||||||
|
ret = g_strndup(buf, rlen);
|
||||||
|
else
|
||||||
|
LOG_W("Didn't receive input from dmenu.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Open the context menu that let's the user
|
* Open the context menu that let's the user
|
||||||
* select urls/actions/etc
|
* select urls/actions/etc
|
||||||
*/
|
*/
|
||||||
void context_menu(void)
|
void context_menu(void)
|
||||||
{
|
{
|
||||||
if (!settings.dmenu_cmd) {
|
GError *err = NULL;
|
||||||
LOG_C("Unable to open dmenu: No dmenu command set.");
|
g_thread_unref(g_thread_try_new("dmenu",
|
||||||
return;
|
context_menu_thread,
|
||||||
|
NULL,
|
||||||
|
&err));
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
LOG_C("Cannot start thread to call dmenu: %s", err->message);
|
||||||
|
g_error_free(err);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static gpointer context_menu_thread(gpointer data)
|
||||||
|
{
|
||||||
char *dmenu_input = NULL;
|
char *dmenu_input = NULL;
|
||||||
|
char *dmenu_output;
|
||||||
|
|
||||||
|
GList *locked_notifications = NULL;
|
||||||
|
|
||||||
for (const GList *iter = queues_get_displayed(); iter;
|
for (const GList *iter = queues_get_displayed(); iter;
|
||||||
iter = iter->next) {
|
iter = iter->next) {
|
||||||
struct notification *n = iter->data;
|
struct notification *n = iter->data;
|
||||||
|
|
||||||
|
|
||||||
|
// Reference and lock the notification if we need it
|
||||||
|
if (n->urls || n->actions) {
|
||||||
|
notification_ref(n);
|
||||||
|
|
||||||
|
struct notification_lock *nl =
|
||||||
|
g_malloc(sizeof(struct notification_lock));
|
||||||
|
|
||||||
|
nl->n = n;
|
||||||
|
nl->timeout = n->timeout;
|
||||||
|
n->timeout = 0;
|
||||||
|
|
||||||
|
locked_notifications = g_list_prepend(locked_notifications, nl);
|
||||||
|
}
|
||||||
|
|
||||||
if (n->urls)
|
if (n->urls)
|
||||||
dmenu_input = string_append(dmenu_input, n->urls, "\n");
|
dmenu_input = string_append(dmenu_input, n->urls, "\n");
|
||||||
|
|
||||||
@ -212,65 +316,27 @@ void context_menu(void)
|
|||||||
"\n");
|
"\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dmenu_input)
|
dmenu_output = invoke_dmenu(dmenu_input);
|
||||||
return;
|
dispatch_menu_result(dmenu_output);
|
||||||
|
|
||||||
char buf[1024] = {0};
|
|
||||||
int child_io[2];
|
|
||||||
int parent_io[2];
|
|
||||||
if (pipe(child_io) != 0) {
|
|
||||||
LOG_W("pipe(): error in child: %s", strerror(errno));
|
|
||||||
g_free(dmenu_input);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (pipe(parent_io) != 0) {
|
|
||||||
LOG_W("pipe(): error in parent: %s", strerror(errno));
|
|
||||||
g_free(dmenu_input);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int pid = fork();
|
|
||||||
|
|
||||||
if (pid == 0) {
|
|
||||||
close(child_io[1]);
|
|
||||||
close(parent_io[0]);
|
|
||||||
close(0);
|
|
||||||
if (dup(child_io[0]) == -1) {
|
|
||||||
LOG_W("dup(): error in child: %s", strerror(errno));
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
close(1);
|
|
||||||
if (dup(parent_io[1]) == -1) {
|
|
||||||
LOG_W("dup(): error in parent: %s", strerror(errno));
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
execvp(settings.dmenu_cmd[0], settings.dmenu_cmd);
|
|
||||||
fprintf(stderr, "Warning: failed to execute '%s': %s\n",
|
|
||||||
settings.dmenu,
|
|
||||||
strerror(errno));
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
} else {
|
|
||||||
close(child_io[0]);
|
|
||||||
close(parent_io[1]);
|
|
||||||
size_t wlen = strlen(dmenu_input);
|
|
||||||
if (write(child_io[1], dmenu_input, wlen) != wlen) {
|
|
||||||
LOG_W("write(): error: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
close(child_io[1]);
|
|
||||||
|
|
||||||
size_t len = read(parent_io[0], buf, 1023);
|
|
||||||
|
|
||||||
waitpid(pid, NULL, 0);
|
|
||||||
|
|
||||||
if (len == 0) {
|
|
||||||
g_free(dmenu_input);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close(parent_io[0]);
|
|
||||||
|
|
||||||
dispatch_menu_result(buf);
|
|
||||||
|
|
||||||
g_free(dmenu_input);
|
g_free(dmenu_input);
|
||||||
|
g_free(dmenu_output);
|
||||||
|
|
||||||
|
// unref all notifications
|
||||||
|
for (GList *iter = locked_notifications;
|
||||||
|
iter;
|
||||||
|
iter = iter->next) {
|
||||||
|
|
||||||
|
struct notification_lock *nl = iter->data;
|
||||||
|
struct notification *n = nl->n;
|
||||||
|
|
||||||
|
n->timeout = nl->timeout;
|
||||||
|
|
||||||
|
g_free(nl);
|
||||||
|
notification_unref(n);
|
||||||
|
}
|
||||||
|
g_list_free(locked_notifications);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
|
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
|
||||||
|
@ -42,6 +42,10 @@ const char *enum_to_string_fullscreen(enum behavior_fullscreen in)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct _notification_private {
|
||||||
|
gint refcount;
|
||||||
|
};
|
||||||
|
|
||||||
/* see notification.h */
|
/* see notification.h */
|
||||||
void notification_print(const struct notification *n)
|
void notification_print(const struct notification *n)
|
||||||
{
|
{
|
||||||
@ -206,12 +210,35 @@ void rawimage_free(struct raw_image *i)
|
|||||||
g_free(i);
|
g_free(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void notification_private_free(NotificationPrivate *p)
|
||||||
|
{
|
||||||
|
g_free(p);
|
||||||
|
}
|
||||||
|
|
||||||
/* see notification.h */
|
/* see notification.h */
|
||||||
void notification_free(struct notification *n)
|
gint notification_refcount_get(struct notification *n)
|
||||||
|
{
|
||||||
|
assert(n->priv->refcount > 0);
|
||||||
|
return g_atomic_int_get(&n->priv->refcount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* see notification.h */
|
||||||
|
void notification_ref(struct notification *n)
|
||||||
|
{
|
||||||
|
assert(n->priv->refcount > 0);
|
||||||
|
g_atomic_int_inc(&n->priv->refcount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* see notification.h */
|
||||||
|
void notification_unref(struct notification *n)
|
||||||
{
|
{
|
||||||
if (!n)
|
if (!n)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
assert(n->priv->refcount > 0);
|
||||||
|
if (!g_atomic_int_dec_and_test(&n->priv->refcount))
|
||||||
|
return;
|
||||||
|
|
||||||
g_free(n->appname);
|
g_free(n->appname);
|
||||||
g_free(n->summary);
|
g_free(n->summary);
|
||||||
g_free(n->body);
|
g_free(n->body);
|
||||||
@ -228,6 +255,8 @@ void notification_free(struct notification *n)
|
|||||||
actions_free(n->actions);
|
actions_free(n->actions);
|
||||||
rawimage_free(n->raw_icon);
|
rawimage_free(n->raw_icon);
|
||||||
|
|
||||||
|
notification_private_free(n->priv);
|
||||||
|
|
||||||
g_free(n);
|
g_free(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,11 +284,21 @@ void notification_replace_single_field(char **haystack,
|
|||||||
g_free(input);
|
g_free(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static NotificationPrivate *notification_private_create(void)
|
||||||
|
{
|
||||||
|
NotificationPrivate *priv = g_malloc0(sizeof(NotificationPrivate));
|
||||||
|
g_atomic_int_set(&priv->refcount, 1);
|
||||||
|
|
||||||
|
return priv;
|
||||||
|
}
|
||||||
|
|
||||||
/* see notification.h */
|
/* see notification.h */
|
||||||
struct notification *notification_create(void)
|
struct notification *notification_create(void)
|
||||||
{
|
{
|
||||||
struct notification *n = g_malloc0(sizeof(struct notification));
|
struct notification *n = g_malloc0(sizeof(struct notification));
|
||||||
|
|
||||||
|
n->priv = notification_private_create();
|
||||||
|
|
||||||
/* Unparameterized default values */
|
/* Unparameterized default values */
|
||||||
n->first_render = true;
|
n->first_render = true;
|
||||||
n->markup = settings.markup;
|
n->markup = settings.markup;
|
||||||
@ -274,6 +313,7 @@ struct notification *notification_create(void)
|
|||||||
n->progress = -1;
|
n->progress = -1;
|
||||||
|
|
||||||
n->script_run = false;
|
n->script_run = false;
|
||||||
|
n->dbus_valid = false;
|
||||||
|
|
||||||
n->fullscreen = FS_SHOW;
|
n->fullscreen = FS_SHOW;
|
||||||
|
|
||||||
|
@ -42,9 +42,13 @@ struct actions {
|
|||||||
gsize count;
|
gsize count;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef struct _notification_private NotificationPrivate;
|
||||||
|
|
||||||
struct notification {
|
struct notification {
|
||||||
|
NotificationPrivate *priv;
|
||||||
int id;
|
int id;
|
||||||
char *dbus_client;
|
char *dbus_client;
|
||||||
|
bool dbus_valid;
|
||||||
|
|
||||||
char *appname;
|
char *appname;
|
||||||
char *summary;
|
char *summary;
|
||||||
@ -90,11 +94,23 @@ struct notification {
|
|||||||
* - the default (if it's not needed to be freed later)
|
* - the default (if it's not needed to be freed later)
|
||||||
* - its undefined representation (NULL, -1)
|
* - its undefined representation (NULL, -1)
|
||||||
*
|
*
|
||||||
|
* The reference counter is set to 1.
|
||||||
|
*
|
||||||
* This function is guaranteed to return a valid pointer.
|
* This function is guaranteed to return a valid pointer.
|
||||||
* @returns The generated notification
|
* @returns The generated notification
|
||||||
*/
|
*/
|
||||||
struct notification *notification_create(void);
|
struct notification *notification_create(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the current reference count of the notification
|
||||||
|
*/
|
||||||
|
gint notification_refcount_get(struct notification *n);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increase the reference counter of the notification.
|
||||||
|
*/
|
||||||
|
void notification_ref(struct notification *n);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitize values of notification, apply all matching rules
|
* Sanitize values of notification, apply all matching rules
|
||||||
* and generate derived fields.
|
* and generate derived fields.
|
||||||
@ -118,11 +134,11 @@ void actions_free(struct actions *a);
|
|||||||
void rawimage_free(struct raw_image *i);
|
void rawimage_free(struct raw_image *i);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Free the memory used by the given notification.
|
* Decrease the reference counter of the notification.
|
||||||
*
|
*
|
||||||
* @param n (nullable): pointer to #notification
|
* If the reference count drops to 0, the object gets freed.
|
||||||
*/
|
*/
|
||||||
void notification_free(struct notification *n);
|
void notification_unref(struct notification *n);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to compare two given notifications.
|
* Helper function to compare two given notifications.
|
||||||
|
@ -30,7 +30,6 @@ static struct section *new_section(const char *name);
|
|||||||
static struct section *get_section(const char *name);
|
static struct section *get_section(const char *name);
|
||||||
static void add_entry(const char *section_name, const char *key, const char *value);
|
static void add_entry(const char *section_name, const char *key, const char *value);
|
||||||
static const char *get_value(const char *section, const char *key);
|
static const char *get_value(const char *section, const char *key);
|
||||||
static char *clean_value(const char *value);
|
|
||||||
|
|
||||||
static int cmdline_argc;
|
static int cmdline_argc;
|
||||||
static char **cmdline_argv;
|
static char **cmdline_argv;
|
||||||
@ -90,7 +89,7 @@ void add_entry(const char *section_name, const char *key, const char *value)
|
|||||||
int len = s->entry_count;
|
int len = s->entry_count;
|
||||||
s->entries = g_realloc(s->entries, sizeof(struct entry) * len);
|
s->entries = g_realloc(s->entries, sizeof(struct entry) * len);
|
||||||
s->entries[s->entry_count - 1].key = g_strdup(key);
|
s->entries[s->entry_count - 1].key = g_strdup(key);
|
||||||
s->entries[s->entry_count - 1].value = clean_value(value);
|
s->entries[s->entry_count - 1].value = string_strip_quotes(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *get_value(const char *section, const char *key)
|
const char *get_value(const char *section, const char *key)
|
||||||
@ -201,21 +200,6 @@ int ini_get_bool(const char *section, const char *key, int def)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char *clean_value(const char *value)
|
|
||||||
{
|
|
||||||
char *s;
|
|
||||||
|
|
||||||
if (value[0] == '"')
|
|
||||||
s = g_strdup(value + 1);
|
|
||||||
else
|
|
||||||
s = g_strdup(value);
|
|
||||||
|
|
||||||
if (s[strlen(s) - 1] == '"')
|
|
||||||
s[strlen(s) - 1] = '\0';
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
int load_ini_file(FILE *fp)
|
int load_ini_file(FILE *fp)
|
||||||
{
|
{
|
||||||
if (!fp)
|
if (!fp)
|
||||||
|
10
src/queues.c
10
src/queues.c
@ -191,7 +191,7 @@ static bool queues_stack_duplicate(struct notification *n)
|
|||||||
if ( allqueues[i] == displayed )
|
if ( allqueues[i] == displayed )
|
||||||
n->start = time_monotonic_now();
|
n->start = time_monotonic_now();
|
||||||
|
|
||||||
notification_free(orig);
|
notification_unref(orig);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,7 +218,7 @@ bool queues_notification_replace_id(struct notification *new)
|
|||||||
notification_run_script(new);
|
notification_run_script(new);
|
||||||
}
|
}
|
||||||
|
|
||||||
notification_free(old);
|
notification_unref(old);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -278,12 +278,12 @@ void queues_history_push(struct notification *n)
|
|||||||
if (!n->history_ignore) {
|
if (!n->history_ignore) {
|
||||||
if (settings.history_length > 0 && history->length >= settings.history_length) {
|
if (settings.history_length > 0 && history->length >= settings.history_length) {
|
||||||
struct notification *to_free = g_queue_pop_head(history);
|
struct notification *to_free = g_queue_pop_head(history);
|
||||||
notification_free(to_free);
|
notification_unref(to_free);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_queue_push_tail(history, n);
|
g_queue_push_tail(history, n);
|
||||||
} else {
|
} else {
|
||||||
notification_free(n);
|
notification_unref(n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -488,7 +488,7 @@ bool queues_pause_status(void)
|
|||||||
static void teardown_notification(gpointer data)
|
static void teardown_notification(gpointer data)
|
||||||
{
|
{
|
||||||
struct notification *n = data;
|
struct notification *n = data;
|
||||||
notification_free(n);
|
notification_unref(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* see queues.h */
|
/* see queues.h */
|
||||||
|
@ -448,6 +448,16 @@ void load_settings(char *cmdline_config_path)
|
|||||||
"path to browser"
|
"path to browser"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
if (!g_shell_parse_argv(settings.browser, NULL, &settings.browser_cmd, &error)) {
|
||||||
|
LOG_W("Unable to parse browser command: '%s'."
|
||||||
|
" URL functionality will be disabled.", error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
settings.browser_cmd = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
char *c = option_get_string(
|
char *c = option_get_string(
|
||||||
"global",
|
"global",
|
||||||
|
@ -75,6 +75,7 @@ struct settings {
|
|||||||
char *dmenu;
|
char *dmenu;
|
||||||
char **dmenu_cmd;
|
char **dmenu_cmd;
|
||||||
char *browser;
|
char *browser;
|
||||||
|
char **browser_cmd;
|
||||||
enum icon_position icon_position;
|
enum icon_position icon_position;
|
||||||
int max_icon_size;
|
int max_icon_size;
|
||||||
char *icon_path;
|
char *icon_path;
|
||||||
|
17
src/utils.c
17
src/utils.c
@ -97,6 +97,23 @@ char *string_append(char *a, const char *b, const char *sep)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* see utils.h */
|
||||||
|
char *string_strip_quotes(const char *value)
|
||||||
|
{
|
||||||
|
if (!value)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
size_t len = strlen(value);
|
||||||
|
char *s;
|
||||||
|
|
||||||
|
if (value[0] == '"' && value[len-1] == '"')
|
||||||
|
s = g_strndup(value + 1, len-2);
|
||||||
|
else
|
||||||
|
s = g_strdup(value);
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
void string_strip_delimited(char *str, char a, char b)
|
void string_strip_delimited(char *str, char a, char b)
|
||||||
{
|
{
|
||||||
int iread=-1, iwrite=0, copen=0;
|
int iread=-1, iwrite=0, copen=0;
|
||||||
|
@ -21,6 +21,14 @@ char *string_append(char *a, const char *b, const char *sep);
|
|||||||
/* strip content between two delimiter characters (inplace) */
|
/* strip content between two delimiter characters (inplace) */
|
||||||
void string_strip_delimited(char *str, char a, char b);
|
void string_strip_delimited(char *str, char a, char b);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strip quotes from a string. Won't touch inner quotes.
|
||||||
|
*
|
||||||
|
* @param value The string to strip the quotes from
|
||||||
|
* @returns A copy of the string value with the outer quotes removed (if any)
|
||||||
|
*/
|
||||||
|
char *string_strip_quotes(const char *value);
|
||||||
|
|
||||||
/* replace tilde and path-specific values with its equivalents */
|
/* replace tilde and path-specific values with its equivalents */
|
||||||
char *string_to_path(char *string);
|
char *string_to_path(char *string);
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
simple = A simple string
|
simple = A simple string
|
||||||
quoted = "A quoted string"
|
quoted = "A quoted string"
|
||||||
quoted_with_quotes = "A string "with quotes""
|
quoted_with_quotes = "A string "with quotes""
|
||||||
|
unquoted_with_quotes = A" string with quotes"
|
||||||
|
|
||||||
[path]
|
[path]
|
||||||
expand_tilde = ~/.path/to/tilde
|
expand_tilde = ~/.path/to/tilde
|
||||||
|
@ -98,29 +98,52 @@ TEST test_notification_replace_single_field(void)
|
|||||||
PASS();
|
PASS();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST test_notification_referencing(void)
|
||||||
|
{
|
||||||
|
struct notification *n = notification_create();
|
||||||
|
ASSERT(notification_refcount_get(n) == 1);
|
||||||
|
|
||||||
|
notification_ref(n);
|
||||||
|
ASSERT(notification_refcount_get(n) == 2);
|
||||||
|
|
||||||
|
notification_unref(n);
|
||||||
|
ASSERT(notification_refcount_get(n) == 1);
|
||||||
|
|
||||||
|
// Now we have to rely on valgrind to test, that
|
||||||
|
// it gets actually freed
|
||||||
|
notification_unref(n);
|
||||||
|
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
SUITE(suite_notification)
|
SUITE(suite_notification)
|
||||||
{
|
{
|
||||||
cmdline_load(0, NULL);
|
cmdline_load(0, NULL);
|
||||||
load_settings("data/dunstrc.default");
|
load_settings("data/dunstrc.default");
|
||||||
|
|
||||||
struct notification *a = notification_create();
|
struct notification *a = notification_create();
|
||||||
a->appname = "Test";
|
a->appname = g_strdup("Test");
|
||||||
a->summary = "Summary";
|
a->summary = g_strdup("Summary");
|
||||||
a->body = "Body";
|
a->body = g_strdup("Body");
|
||||||
a->icon = "Icon";
|
a->icon = g_strdup("Icon");
|
||||||
a->urgency = URG_NORM;
|
a->urgency = URG_NORM;
|
||||||
|
|
||||||
struct notification *b = notification_create();
|
struct notification *b = notification_create();
|
||||||
memcpy(b, a, sizeof(*b));
|
b->appname = g_strdup("Test");
|
||||||
|
b->summary = g_strdup("Summary");
|
||||||
|
b->body = g_strdup("Body");
|
||||||
|
b->icon = g_strdup("Icon");
|
||||||
|
b->urgency = URG_NORM;
|
||||||
|
|
||||||
//2 equal notifications to be passed for duplicate checking,
|
//2 equal notifications to be passed for duplicate checking,
|
||||||
struct notification *n[2] = {a, b};
|
struct notification *n[2] = {a, b};
|
||||||
|
|
||||||
RUN_TEST1(test_notification_is_duplicate, (void*) n);
|
RUN_TEST1(test_notification_is_duplicate, (void*) n);
|
||||||
g_free(a);
|
notification_unref(a);
|
||||||
g_free(b);
|
notification_unref(b);
|
||||||
|
|
||||||
RUN_TEST(test_notification_replace_single_field);
|
RUN_TEST(test_notification_replace_single_field);
|
||||||
|
RUN_TEST(test_notification_referencing);
|
||||||
|
|
||||||
g_clear_pointer(&settings.icon_path, g_free);
|
g_clear_pointer(&settings.icon_path, g_free);
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,8 @@ TEST test_ini_get_string(void)
|
|||||||
free(ptr);
|
free(ptr);
|
||||||
ASSERT_STR_EQ("A string \"with quotes\"", (ptr = ini_get_string(string_section, "quoted_with_quotes", "")));
|
ASSERT_STR_EQ("A string \"with quotes\"", (ptr = ini_get_string(string_section, "quoted_with_quotes", "")));
|
||||||
free(ptr);
|
free(ptr);
|
||||||
|
ASSERT_STR_EQ("A\" string with quotes\"", (ptr = ini_get_string(string_section, "unquoted_with_quotes", "")));
|
||||||
|
free(ptr);
|
||||||
|
|
||||||
ASSERT_STR_EQ("default value", (ptr = ini_get_string(string_section, "nonexistent", "default value")));
|
ASSERT_STR_EQ("default value", (ptr = ini_get_string(string_section, "nonexistent", "default value")));
|
||||||
free(ptr);
|
free(ptr);
|
||||||
|
27
test/utils.c
27
test/utils.c
@ -104,6 +104,32 @@ TEST test_string_append(void)
|
|||||||
PASS();
|
PASS();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST test_string_strip_quotes(void)
|
||||||
|
{
|
||||||
|
char *exp = string_strip_quotes(NULL);
|
||||||
|
ASSERT_FALSE(exp);
|
||||||
|
|
||||||
|
ASSERT_STR_EQ("NewString", (exp = string_strip_quotes("NewString")));
|
||||||
|
g_free(exp);
|
||||||
|
|
||||||
|
ASSERT_STR_EQ("becomes unquoted", (exp = string_strip_quotes("\"becomes unquoted\"")));
|
||||||
|
g_free(exp);
|
||||||
|
|
||||||
|
ASSERT_STR_EQ("\"stays quoted", (exp = string_strip_quotes("\"stays quoted")));
|
||||||
|
g_free(exp);
|
||||||
|
|
||||||
|
ASSERT_STR_EQ("stays quoted\"", (exp = string_strip_quotes("stays quoted\"")));
|
||||||
|
g_free(exp);
|
||||||
|
|
||||||
|
ASSERT_STR_EQ("stays \"quoted\"", (exp = string_strip_quotes("stays \"quoted\"")));
|
||||||
|
g_free(exp);
|
||||||
|
|
||||||
|
ASSERT_STR_EQ(" \"stays quoted\"", (exp = string_strip_quotes(" \"stays quoted\"")));
|
||||||
|
g_free(exp);
|
||||||
|
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
TEST test_string_strip_delimited(void)
|
TEST test_string_strip_delimited(void)
|
||||||
{
|
{
|
||||||
char *text = malloc(128 * sizeof(char));
|
char *text = malloc(128 * sizeof(char));
|
||||||
@ -178,6 +204,7 @@ SUITE(suite_utils)
|
|||||||
RUN_TEST(test_string_replace_all);
|
RUN_TEST(test_string_replace_all);
|
||||||
RUN_TEST(test_string_replace);
|
RUN_TEST(test_string_replace);
|
||||||
RUN_TEST(test_string_append);
|
RUN_TEST(test_string_append);
|
||||||
|
RUN_TEST(test_string_strip_quotes);
|
||||||
RUN_TEST(test_string_strip_delimited);
|
RUN_TEST(test_string_strip_delimited);
|
||||||
RUN_TEST(test_string_to_path);
|
RUN_TEST(test_string_to_path);
|
||||||
RUN_TEST(test_string_to_time);
|
RUN_TEST(test_string_to_time);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user