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->dbus_client = g_strdup(sender); | ||||
|         n->dbus_valid = true; | ||||
| 
 | ||||
|         { | ||||
|                 GVariantIter *iter = g_variant_iter_new(parameters); | ||||
| @ -282,7 +283,7 @@ static void on_notify(GDBusConnection *connection, | ||||
|         // The message got discarded
 | ||||
|         if (id == 0) { | ||||
|                 signal_notification_closed(n, 2); | ||||
|                 notification_free(n); | ||||
|                 notification_unref(n); | ||||
|         } | ||||
| 
 | ||||
|         wake_up(); | ||||
| @ -316,6 +317,12 @@ static void on_get_server_information(GDBusConnection *connection, | ||||
| 
 | ||||
| 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) { | ||||
|                 LOG_W("Closing notification with reason '%d' not supported. " | ||||
|                       "Closing it with reason '%d'.", reason, REASON_UNDEF); | ||||
| @ -337,6 +344,8 @@ void signal_notification_closed(struct notification *n, enum reason reason) | ||||
|                                       body, | ||||
|                                       &err); | ||||
| 
 | ||||
|         n->dbus_valid = false; | ||||
| 
 | ||||
|         if (err) { | ||||
|                 LOG_W("Unable to close notification: %s", err->message); | ||||
|                 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) | ||||
| { | ||||
|         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); | ||||
|         GError *err = NULL; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										246
									
								
								src/menu.c
									
									
									
									
									
								
							
							
						
						
									
										246
									
								
								src/menu.c
									
									
									
									
									
								
							| @ -23,6 +23,12 @@ | ||||
| static bool is_initialized = false; | ||||
| static regex_t cregex; | ||||
| 
 | ||||
| struct notification_lock { | ||||
|         struct notification *n; | ||||
|         gint64 timeout; | ||||
| }; | ||||
| static gpointer context_menu_thread(gpointer data); | ||||
| 
 | ||||
| static int regex_init(void) | ||||
| { | ||||
|         if (is_initialized) | ||||
| @ -96,37 +102,45 @@ char *extract_urls(const char *to_match) | ||||
|  */ | ||||
| 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
 | ||||
|         const char *end = strstr(in, "] "); | ||||
|         if (*in == '[' && end) | ||||
|         if (*in == '[' && (end = strstr(in, "] "))) | ||||
|                 url = g_strdup(end + 2); | ||||
|         else | ||||
|                 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) { | ||||
|                 g_free(url); | ||||
|                 int status; | ||||
|                 waitpid(browser_pid1, &status, 0); | ||||
|         } else { | ||||
|                 int browser_pid2 = fork(); | ||||
|                 if (browser_pid2) { | ||||
|                         exit(0); | ||||
|                 } else { | ||||
|                         char *browser_cmd = g_strconcat(settings.browser, " ", url, NULL); | ||||
|                         char **cmd = g_strsplit(browser_cmd, " ", 0); | ||||
|                         execvp(cmd[0], cmd); | ||||
|                         // execvp won't return if it's successful
 | ||||
|                         // so, if we're here, it's definitely an error
 | ||||
|                         fprintf(stderr, "Warning: failed to execute '%s': %s\n", | ||||
|                                         settings.browser, | ||||
|                                         strerror(errno)); | ||||
|                         exit(EXIT_FAILURE); | ||||
|                 } | ||||
|         memcpy(argv, settings.browser_cmd, argc * sizeof(char*)); | ||||
|         argv[argc-2] = url; | ||||
|         argv[argc-1] = NULL; | ||||
| 
 | ||||
|         GError *err = NULL; | ||||
|         g_spawn_async(NULL, | ||||
|                       argv, | ||||
|                       NULL, | ||||
|                       G_SPAWN_DEFAULT | ||||
|                         | G_SPAWN_SEARCH_PATH | ||||
|                         | G_SPAWN_STDOUT_TO_DEV_NULL | ||||
|                         | G_SPAWN_STDERR_TO_DEV_NULL, | ||||
|                       NULL, | ||||
|                       NULL, | ||||
|                       NULL, | ||||
|                       &err); | ||||
| 
 | ||||
|         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 | ||||
|  * by the menu. | ||||
| /**
 | ||||
|  * Dispatch whatever has been returned by dmenu. | ||||
|  * 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) | ||||
| { | ||||
|         if (!input) | ||||
|                 return; | ||||
| 
 | ||||
|         char *in = g_strdup(input); | ||||
|         g_strstrip(in); | ||||
|         if (in[0] == '#') { | ||||
| 
 | ||||
|         if (in[0] == '#') | ||||
|                 invoke_action(in + 1); | ||||
|         } else { | ||||
|         else if (in[0] != '\0') | ||||
|                 open_browser(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 | ||||
|  * select urls/actions/etc | ||||
|  */ | ||||
| void context_menu(void) | ||||
| { | ||||
|         if (!settings.dmenu_cmd) { | ||||
|                 LOG_C("Unable to open dmenu: No dmenu command set."); | ||||
|                 return; | ||||
|         GError *err = NULL; | ||||
|         g_thread_unref(g_thread_try_new("dmenu", | ||||
|                                         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_output; | ||||
| 
 | ||||
|         GList *locked_notifications = NULL; | ||||
| 
 | ||||
|         for (const GList *iter = queues_get_displayed(); iter; | ||||
|              iter = iter->next) { | ||||
|                 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) | ||||
|                         dmenu_input = string_append(dmenu_input, n->urls, "\n"); | ||||
| 
 | ||||
| @ -212,65 +316,27 @@ void context_menu(void) | ||||
|                                           "\n"); | ||||
|         } | ||||
| 
 | ||||
|         if (!dmenu_input) | ||||
|                 return; | ||||
| 
 | ||||
|         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); | ||||
|         dmenu_output = invoke_dmenu(dmenu_input); | ||||
|         dispatch_menu_result(dmenu_output); | ||||
| 
 | ||||
|         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: */ | ||||
|  | ||||
| @ -42,6 +42,10 @@ const char *enum_to_string_fullscreen(enum behavior_fullscreen in) | ||||
|         } | ||||
| } | ||||
| 
 | ||||
| struct _notification_private { | ||||
|         gint refcount; | ||||
| }; | ||||
| 
 | ||||
| /* see notification.h */ | ||||
| void notification_print(const struct notification *n) | ||||
| { | ||||
| @ -206,12 +210,35 @@ void rawimage_free(struct raw_image *i) | ||||
|         g_free(i); | ||||
| } | ||||
| 
 | ||||
| static void notification_private_free(NotificationPrivate *p) | ||||
| { | ||||
|         g_free(p); | ||||
| } | ||||
| 
 | ||||
| /* 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) | ||||
|                 return; | ||||
| 
 | ||||
|         assert(n->priv->refcount > 0); | ||||
|         if (!g_atomic_int_dec_and_test(&n->priv->refcount)) | ||||
|                 return; | ||||
| 
 | ||||
|         g_free(n->appname); | ||||
|         g_free(n->summary); | ||||
|         g_free(n->body); | ||||
| @ -228,6 +255,8 @@ void notification_free(struct notification *n) | ||||
|         actions_free(n->actions); | ||||
|         rawimage_free(n->raw_icon); | ||||
| 
 | ||||
|         notification_private_free(n->priv); | ||||
| 
 | ||||
|         g_free(n); | ||||
| } | ||||
| 
 | ||||
| @ -255,11 +284,21 @@ void notification_replace_single_field(char **haystack, | ||||
|         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 */ | ||||
| struct notification *notification_create(void) | ||||
| { | ||||
|         struct notification *n = g_malloc0(sizeof(struct notification)); | ||||
| 
 | ||||
|         n->priv = notification_private_create(); | ||||
| 
 | ||||
|         /* Unparameterized default values */ | ||||
|         n->first_render = true; | ||||
|         n->markup = settings.markup; | ||||
| @ -274,6 +313,7 @@ struct notification *notification_create(void) | ||||
|         n->progress = -1; | ||||
| 
 | ||||
|         n->script_run = false; | ||||
|         n->dbus_valid = false; | ||||
| 
 | ||||
|         n->fullscreen = FS_SHOW; | ||||
| 
 | ||||
|  | ||||
| @ -42,9 +42,13 @@ struct actions { | ||||
|         gsize count; | ||||
| }; | ||||
| 
 | ||||
| typedef struct _notification_private NotificationPrivate; | ||||
| 
 | ||||
| struct notification { | ||||
|         NotificationPrivate *priv; | ||||
|         int id; | ||||
|         char *dbus_client; | ||||
|         bool dbus_valid; | ||||
| 
 | ||||
|         char *appname; | ||||
|         char *summary; | ||||
| @ -90,11 +94,23 @@ struct notification { | ||||
|  *  - the default (if it's not needed to be freed later) | ||||
|  *  - its undefined representation (NULL, -1) | ||||
|  * | ||||
|  * The reference counter is set to 1. | ||||
|  * | ||||
|  * This function is guaranteed to return a valid pointer. | ||||
|  * @returns The generated notification | ||||
|  */ | ||||
| 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 | ||||
|  * and generate derived fields. | ||||
| @ -118,11 +134,11 @@ void actions_free(struct actions *a); | ||||
| 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. | ||||
|  | ||||
| @ -30,7 +30,6 @@ static struct section *new_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 const char *get_value(const char *section, const char *key); | ||||
| static char *clean_value(const char *value); | ||||
| 
 | ||||
| static int cmdline_argc; | ||||
| 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; | ||||
|         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].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) | ||||
| @ -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) | ||||
| { | ||||
|         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 ) | ||||
|                                         n->start = time_monotonic_now(); | ||||
| 
 | ||||
|                                 notification_free(orig); | ||||
|                                 notification_unref(orig); | ||||
|                                 return true; | ||||
|                         } | ||||
|                 } | ||||
| @ -218,7 +218,7 @@ bool queues_notification_replace_id(struct notification *new) | ||||
|                                         notification_run_script(new); | ||||
|                                 } | ||||
| 
 | ||||
|                                 notification_free(old); | ||||
|                                 notification_unref(old); | ||||
|                                 return true; | ||||
|                         } | ||||
|                 } | ||||
| @ -278,12 +278,12 @@ void queues_history_push(struct notification *n) | ||||
|         if (!n->history_ignore) { | ||||
|                 if (settings.history_length > 0 && history->length >= settings.history_length) { | ||||
|                         struct notification *to_free = g_queue_pop_head(history); | ||||
|                         notification_free(to_free); | ||||
|                         notification_unref(to_free); | ||||
|                 } | ||||
| 
 | ||||
|                 g_queue_push_tail(history, n); | ||||
|         } else { | ||||
|                 notification_free(n); | ||||
|                 notification_unref(n); | ||||
|         } | ||||
| } | ||||
| 
 | ||||
| @ -488,7 +488,7 @@ bool queues_pause_status(void) | ||||
| static void teardown_notification(gpointer data) | ||||
| { | ||||
|         struct notification *n = data; | ||||
|         notification_free(n); | ||||
|         notification_unref(n); | ||||
| } | ||||
| 
 | ||||
| /* see queues.h */ | ||||
|  | ||||
| @ -448,6 +448,16 @@ void load_settings(char *cmdline_config_path) | ||||
|                 "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( | ||||
|                         "global", | ||||
|  | ||||
| @ -75,6 +75,7 @@ struct settings { | ||||
|         char *dmenu; | ||||
|         char **dmenu_cmd; | ||||
|         char *browser; | ||||
|         char **browser_cmd; | ||||
|         enum icon_position icon_position; | ||||
|         int max_icon_size; | ||||
|         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) | ||||
| { | ||||
|         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) */ | ||||
| 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 */ | ||||
| char *string_to_path(char *string); | ||||
| 
 | ||||
|  | ||||
| @ -22,6 +22,7 @@ | ||||
| 	simple = A simple string | ||||
| 	quoted = "A quoted string" | ||||
| 	quoted_with_quotes = "A string "with quotes"" | ||||
| 	unquoted_with_quotes = A" string with quotes" | ||||
| 
 | ||||
| [path] | ||||
| 	expand_tilde    = ~/.path/to/tilde | ||||
|  | ||||
| @ -98,29 +98,52 @@ TEST test_notification_replace_single_field(void) | ||||
|         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) | ||||
| { | ||||
|         cmdline_load(0, NULL); | ||||
|         load_settings("data/dunstrc.default"); | ||||
| 
 | ||||
|         struct notification *a = notification_create(); | ||||
|         a->appname = "Test"; | ||||
|         a->summary = "Summary"; | ||||
|         a->body = "Body"; | ||||
|         a->icon = "Icon"; | ||||
|         a->appname = g_strdup("Test"); | ||||
|         a->summary = g_strdup("Summary"); | ||||
|         a->body = g_strdup("Body"); | ||||
|         a->icon = g_strdup("Icon"); | ||||
|         a->urgency = URG_NORM; | ||||
| 
 | ||||
|         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,
 | ||||
|         struct notification *n[2] = {a, b}; | ||||
| 
 | ||||
|         RUN_TEST1(test_notification_is_duplicate, (void*) n); | ||||
|         g_free(a); | ||||
|         g_free(b); | ||||
|         notification_unref(a); | ||||
|         notification_unref(b); | ||||
| 
 | ||||
|         RUN_TEST(test_notification_replace_single_field); | ||||
|         RUN_TEST(test_notification_referencing); | ||||
| 
 | ||||
|         g_clear_pointer(&settings.icon_path, g_free); | ||||
| } | ||||
|  | ||||
| @ -53,6 +53,8 @@ TEST test_ini_get_string(void) | ||||
|         free(ptr); | ||||
|         ASSERT_STR_EQ("A string \"with quotes\"", (ptr = ini_get_string(string_section, "quoted_with_quotes", ""))); | ||||
|         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"))); | ||||
|         free(ptr); | ||||
|  | ||||
							
								
								
									
										27
									
								
								test/utils.c
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								test/utils.c
									
									
									
									
									
								
							| @ -104,6 +104,32 @@ TEST test_string_append(void) | ||||
|         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) | ||||
| { | ||||
|         char *text = malloc(128 * sizeof(char)); | ||||
| @ -178,6 +204,7 @@ SUITE(suite_utils) | ||||
|         RUN_TEST(test_string_replace_all); | ||||
|         RUN_TEST(test_string_replace); | ||||
|         RUN_TEST(test_string_append); | ||||
|         RUN_TEST(test_string_strip_quotes); | ||||
|         RUN_TEST(test_string_strip_delimited); | ||||
|         RUN_TEST(test_string_to_path); | ||||
|         RUN_TEST(test_string_to_time); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Benedikt Heine
						Benedikt Heine