diff --git a/Makefile b/Makefile index dcac48e..a901aed 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ include config.mk -SRC = draw.c dunst.c list.c dunst_dbus.c utils.c options.c +SRC = draw.c dunst.c container.c dunst_dbus.c utils.c options.c OBJ = ${SRC:.c=.o} all: doc options dunst service @@ -24,9 +24,13 @@ config.h: @echo creating $@ from config.def.h @cp config.def.h $@ -dunst: draw.o dunst.o list.o dunst_dbus.o utils.o options.o +dunst: ${OBJ} @echo CC -o $@ - @${CC} ${CFLAGS} -o $@ dunst.o draw.o list.o dunst_dbus.o options.o utils.o ${LDFLAGS} + @${CC} ${CFLAGS} -o $@ ${OBJ} ${LDFLAGS} + +debug: ${OBJ} + @echo CC -o $@ + @${CC} ${CFLAGS} -O0 -o dunst ${OBJ} ${LDFLAGS} clean: @echo cleaning @@ -34,6 +38,7 @@ clean: @rm -f dunst @rm -f dunst.1 @rm -f org.knopwob.dunst.service + @rm -f core doc: dunst.1 dunst.1: README.pod diff --git a/README.pod b/README.pod index 2cf685c..10a9662 100644 --- a/README.pod +++ b/README.pod @@ -145,6 +145,21 @@ The progress value can be set with a hint, too. =back +=head1 MISCELLANEOUS + +Dunst can be paused by sending a notification with a summary of "DUNST_COMMAND_PAUSE" +and resumed with a summary of "DUNST_COMMAND_RESUME". Alternatively you can send SIGUSR1 and SIGUSR2 to pause and unpause respectivly. For Example: + +=item killall -SIGUSR1 dunst # pause + +=item killall -SIGUSR2 dunst # resume + +=back + +When paused dunst will not display any notifications but keep all notifications in a queue. +This can for example be wrapped around a screen locker (i3lock, slock) to prevent flickering +of notifications through the lock and to read all missed notifications after returning to the computer. + =head1 CONFIGURATION An example configuration file is included (usually /usr/share/dunst/dunstrc). diff --git a/config.def.h b/config.def.h index cd702de..45c0a87 100644 --- a/config.def.h +++ b/config.def.h @@ -27,10 +27,21 @@ int line_height = 0; /* if line height < font height, it will be raised to fon int separator_height = 2; /* height of the separator line between two notifications */ enum separator_color sep_color = AUTO; /* AUTO or FOREGROUND */ +/* show a notification on startup + * This is mainly for crash detection since dbus restarts dunst + * automatically after a crash, so crashes might get unnotices otherwise + * */ +int startup_notification = False; + /* monitor to display notifications on */ int monitor = 0; +/* path to dmenu */ +char *dmenu = "/usr/bin/dmenu"; + +char *browser = "/usr/bin/firefox"; + /* follow focus to different monitor and display notifications there? * possible values: * FOLLOW_NONE @@ -54,6 +65,9 @@ keyboard_shortcut close_all_ks = {.str = "none", keyboard_shortcut history_ks = {.str = "none", .code = 0, .sym = NoSymbol,.is_valid = False}; /* ignore this */ +keyboard_shortcut context_ks = {.str = "none", + .code = 0, .sym = NoSymbol,.is_valid = False}; /* ignore this */ + rule_t default_rules[] = { /* name can be any unique string. It is used to identify the rule in dunstrc to override it there */ diff --git a/container.c b/container.c new file mode 100644 index 0000000..2db3e80 --- /dev/null +++ b/container.c @@ -0,0 +1,177 @@ +#include "stdlib.h" +#include "stdio.h" +#include + +#include "container.h" + +void n_stack_push(n_stack **s, notification *n) +{ + if (!n || !s) + return; + + n_stack *new = malloc(sizeof(n_stack)); + new->n = n; + new->next = *s; + *s = new; +} + +notification *n_stack_pop(n_stack **s) +{ + if (!s || !*s) + return NULL; + + n_stack *head = *s; + *s = (*s)->next; + + notification *n = head->n; + free(head); + + return n; +} + +void n_stack_remove(n_stack **s, notification *n) +{ + if (!s || !*s || !n) + return; + + /* head is n */ + if ((*s)->n == n) { + n_stack *tmp = *s; + *s = (*s)->next; + free(tmp); + return; + } + + for (n_stack *iter = *s; iter->next; iter = iter->next) { + if (iter->next->n == n) { + n_stack *tmp = iter->next; + iter->next = iter->next->next; + free(tmp); + return; + } + } +} + +int n_stack_len(n_stack **s) +{ + if (!s || !*s) + return 0; + + n_stack *cur = *s; + int count = 0; + + while (cur) { + cur = cur->next; + count++; + } + + return count; +} + +int cmp_notification(notification *a, notification *b) +{ + if (a == NULL && b == NULL) + return 0; + else if (a == NULL) + return -1; + else if (b == NULL) + return 1; + + if (a->urgency != b->urgency) { + return a->urgency - b->urgency; + } else { + return b->timestamp - a->timestamp; + } +} + +void n_queue_enqueue(n_queue **q, notification *n) +{ + if (!n || !q) + return; + + + n_queue *new = malloc(sizeof(n_queue)); + new->n = n; + new->next = NULL; + + if (!(*q)) { + /* queue was empty */ + *q = new; + return; + } + + + /* new head */ + if (cmp_notification(new->n, (*q)->n) > 0) { + new->next = *q; + *q = new; + return; + } + + /* in between */ + n_queue *cur = *q; + while (cur->next) { + if (cmp_notification(new->n, cur->next->n) > 0) { + new->next = cur->next; + cur->next = new; + return; + } + + cur = cur->next; + } + + /* last */ + cur->next = new; +} + +void n_queue_remove(n_queue **q, notification *n) +{ + n_stack_remove(q, n); +} + +notification *n_queue_dequeue(n_queue **q) +{ + return n_stack_pop(q); +} + +int n_queue_len(n_queue **q) +{ + return n_stack_len(q); +} + +str_array *str_array_malloc(void) +{ + str_array *a = malloc(sizeof(str_array)); + a->count = 0; + a->strs = NULL; + return a; +} + +void str_array_append(str_array *a, char *str) +{ + if (!a) + return; + a->count++; + a->strs = realloc(a->strs, a->count); + (a->strs)[a->count-1] = str; +} + +void str_array_dup_append(str_array *a, const char *str) +{ + if (!a) + return; + char *dup = strdup(str); + str_array_append(a, dup); +} + +void str_array_deep_free(str_array *a) +{ + if (!a) + return; + for (int i = 0; i < a->count; i++) { + free((a->strs)[i]); + } + free(a); +} + +/* vim: set ts=8 sw=8 tw=0: */ diff --git a/container.h b/container.h new file mode 100644 index 0000000..1e8e0b5 --- /dev/null +++ b/container.h @@ -0,0 +1,84 @@ +#ifndef _LIST_H +#define _LIST_H + +#include "dunst.h" + +typedef struct _n_stack { + notification *n; + struct _n_stack *next; +} n_stack; + +typedef n_stack n_queue; + +typedef struct _str_array { + int count; + char **strs; +} str_array; + +str_array *str_array_malloc(void); +void str_array_dup_append(str_array *a, const char *str); +void str_array_append(str_array *a, char *str); +void str_array_deep_free(str_array *a); + +int cmp_notification(notification *a, notification *b); + +/************ + * stack + */ + +/** + * push notification onto stack + * creates a new stack if *s == NULL + */ +void n_stack_push(n_stack **s, notification *n); + +/** + * remove and return notification from stack + * sets *s to NULL if stack is empty + */ +notification *n_stack_pop(n_stack **s); + +/** + * remove notification from stack + */ + +void n_stack_remove(n_stack **q, notification *n); + +/** + * return length of stack + */ + +int n_stack_len(n_stack **s); + +/*************** + * queue + */ + +/** + * enqueue notification into queue + * creates a new queue if *q == NULL + */ + +void n_queue_enqueue(n_queue **q, notification *n); + +/** + * dequeues the next element from the queue. + * returns NULL if queue is empty + */ + +notification *n_queue_dequeue(n_queue **q); + +/** + * remove notification from queue + */ + +void n_queue_remove(n_queue **q, notification *n); + +/** + * return length of queue + */ + +int n_queue_len(n_queue **q); + +#endif +/* vim: set ts=8 sw=8 tw=0: */ diff --git a/draw.c b/draw.c index 66ea483..663e88a 100644 --- a/draw.c +++ b/draw.c @@ -34,6 +34,7 @@ DEALINGS IN THE SOFTWARE. #include #include #include +#include #include #include #include @@ -45,7 +46,7 @@ DEALINGS IN THE SOFTWARE. #define MIN(a, b) ((a) < (b) ? (a) : (b)) void -drawrect(DC * dc, int x, int y, unsigned int w, unsigned int h, Bool fill, +drawrect(DC * dc, int x, int y, unsigned int w, unsigned int h, bool fill, unsigned long color) { XSetForeground(dc->dpy, dc->gc, color); @@ -71,7 +72,7 @@ void drawtext(DC * dc, const char *text, ColorSet * col) if (mn < n) for (n = MAX(mn - 3, 0); n < mn; buf[n++] = '.') ; - drawrect(dc, 0, 0, dc->w, dc->h, True, col->BG); + drawrect(dc, 0, 0, dc->w, dc->h, true, col->BG); drawtextn(dc, buf, mn, col); } @@ -234,12 +235,12 @@ void initfont(DC * dc, const char *fontstr) return; } -void -setopacity(DC *dc, Window win, unsigned long opacity) +void setopacity(DC * dc, Window win, unsigned long opacity) { - Atom _NET_WM_WINDOW_OPACITY = XInternAtom(dc->dpy, "_NET_WM_WINDOW_OPACITY", False); - XChangeProperty(dc->dpy, win, _NET_WM_WINDOW_OPACITY, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&opacity, 1L); + Atom _NET_WM_WINDOW_OPACITY = + XInternAtom(dc->dpy, "_NET_WM_WINDOW_OPACITY", false); + XChangeProperty(dc->dpy, win, _NET_WM_WINDOW_OPACITY, XA_CARDINAL, 32, + PropModeReplace, (unsigned char *)&opacity, 1L); } void mapdc(DC * dc, Window win, unsigned int w, unsigned int h) diff --git a/draw.h b/draw.h index 645f86c..6746cbb 100644 --- a/draw.h +++ b/draw.h @@ -33,11 +33,13 @@ DEALINGS IN THE SOFTWARE. #ifndef DRAW_H #define DRAW_H +#include + #include typedef struct { int x, y, w, h; - Bool invert; + bool invert; Display *dpy; GC gc; Pixmap canvas; @@ -59,7 +61,7 @@ typedef struct { unsigned long BG; } ColorSet; -void drawrect(DC * dc, int x, int y, unsigned int w, unsigned int h, Bool fill, +void drawrect(DC * dc, int x, int y, unsigned int w, unsigned int h, bool fill, unsigned long color); void drawtext(DC * dc, const char *text, ColorSet * col); void drawtextn(DC * dc, const char *text, size_t n, ColorSet * col); @@ -70,7 +72,7 @@ unsigned long getcolor(DC * dc, const char *colstr); ColorSet *initcolor(DC * dc, const char *foreground, const char *background); DC *initdc(void); void initfont(DC * dc, const char *fontstr); -void setopacity(DC *dc, Window win, unsigned long opacity); +void setopacity(DC * dc, Window win, unsigned long opacity); void mapdc(DC * dc, Window win, unsigned int w, unsigned int h); void resizedc(DC * dc, unsigned int w, unsigned int h); int textnw(DC * dc, const char *text, size_t len); diff --git a/dunst.c b/dunst.c index ba34682..3f5f9a0 100644 --- a/dunst.c +++ b/dunst.c @@ -7,11 +7,13 @@ #include #include #include +#include #include #include #include +#include #include -#include +#include #include #include #include @@ -26,12 +28,10 @@ #include "dunst.h" #include "draw.h" #include "dunst_dbus.h" -#include "list.h" +#include "container.h" #include "utils.h" -#ifndef STATIC_CONFIG #include "options.h" -#endif #define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh)) #define LENGTH(X) (sizeof X / sizeof X[0]) @@ -42,18 +42,21 @@ #define DEFFONT "Monospace-11" +#ifndef VERSION +#define VERSION 0 +#endif + #define MSG 1 #define INFO 2 #define DEBUG 3 - /* global variables */ #include "config.h" int height_limit; -list *rules = NULL; +rule_array rules; /* index of colors fit to urgency level */ static ColorSet *colors[3]; static const char *color_strings[2][3]; @@ -61,54 +64,165 @@ static Atom utf8; static DC *dc; static Window win; static time_t now; -static int visible = False; +static bool visible = false; static screen_info scr; static dimension_t geometry; static XScreenSaverInfo *screensaver_info; static int font_h; -static int print_notifications = False; +static bool print_notifications = false; static dimension_t window_dim; +static bool pause_display = false; +static char **dmenu_cmd; +static char **browser_cmd; -int dunst_grab_errored = False; +static r_line_cache line_cache; + +bool dunst_grab_errored = false; int next_notification_id = 1; -int deprecated_mod = False; -int deprecated_dunstrc_shortcuts = False; - /* notification lists */ -list *notification_queue = NULL; /* all new notifications get into here */ -list *displayed_notifications = NULL; /* currently displayed notifications */ -list *notification_history = NULL; /* history of displayed notifications */ +n_queue *queue = NULL; /* all new notifications get into here */ +n_queue *displayed = NULL; /* currently displayed notifications */ +n_stack *history = NULL; /* history of displayed notifications */ /* misc funtions */ void apply_rules(notification * n); void check_timeouts(void); -void draw_notifications(void); char *fix_markup(char *str); void handle_mouse_click(XEvent ev); void handleXEvents(void); void history_pop(void); -rule_t *initrule(void); -int is_idle(void); +void initrule(rule_t *r); +bool is_idle(void); void run(void); void setup(void); void update_screen_info(); void usage(int exit_status); -void hide_window(); -l_node *most_important(list * l); void draw_win(void); void hide_win(void); void move_all_to_history(void); void print_version(void); +str_array *extract_urls(const char *str); +void context_menu(void); -/* show warning notification */ -void warn(const char *text, int urg); +void r_line_cache_init(r_line_cache *c); +void r_line_cache_append(r_line_cache *c, const char *s, ColorSet *col, bool continues); +void r_line_cache_reset(r_line_cache *c); void init_shortcut(keyboard_shortcut * shortcut); KeySym string_to_mask(char *str); -static void print_notification(notification *n) +str_array *extract_urls( const char * to_match) +{ + static bool is_initialized = false; + static regex_t cregex; + + if (!is_initialized) { + char *regex = "((http|ftp|https)(://))?(www\\.)?[[:alnum:]_-]+\\.[^[:space:]]+"; + int ret = regcomp(&cregex, regex, REG_EXTENDED|REG_ICASE); + if (ret != 0) { + printf("failed to compile regex\n"); + return NULL; + } + } + + str_array *urls = str_array_malloc(); + + const char * p = to_match; + regmatch_t m; + + while (1) { + int nomatch = regexec (&cregex, p, 1, &m, 0); + if (nomatch) { + return urls; + } + int start; + int finish; + if (m.rm_so == -1) { + break; + } + start = m.rm_so + (p - to_match); + finish = m.rm_eo + (p - to_match); + + char *match = strndup(to_match+start, finish-start); + + str_array_append(urls, match); + + p += m.rm_eo; + } + return 0; + + return urls; +} + +void context_menu(void) { + char *dmenu_input = NULL; + + n_queue *iter = displayed; + + while (iter) { + for (int i = 0; i < iter->n->urls->count; i++) { + dmenu_input = string_append(dmenu_input, + (iter->n->urls->strs)[i], "\n"); + } + iter = iter->next; + } + + if (!dmenu_input) + return; + + int child_io[2]; + int parent_io[2]; + pipe(child_io); + pipe(parent_io); + int pid = fork(); + + if (pid == 0) { + close(child_io[1]); + close(parent_io[0]); + close(0); + dup(child_io[0]); + close(1); + dup(parent_io[1]); + execvp(dmenu_cmd[0], dmenu_cmd); + } else { + close(child_io[0]); + close(parent_io[1]); + write(child_io[1], dmenu_input, strlen(dmenu_input)); + close(child_io[1]); + + char buf[1024]; + size_t len = read(parent_io[0], buf, 1023); + if (len == 0) + return; + buf[len - 1] = '\0'; + } + + close(child_io[1]); + + int browser_pid = fork(); + + if (browser_pid == 0) { + execvp(browser_cmd[0], browser_cmd); + } else { + return; + } +} + +void pause_signal_handler(int sig) +{ + if (sig == SIGUSR1) { + pause_display = true; + } + if (sig == SIGUSR2) { + pause_display = false; + } + + signal (sig, pause_signal_handler); +} + +static void print_notification(notification * n) { printf("{\n"); printf("\tappname: %s\n", n->appname); @@ -118,12 +232,18 @@ static void print_notification(notification *n) printf("\turgency: %d\n", n->urgency); printf("\tformatted: %s\n", n->msg); printf("\tid: %d\n", n->id); + printf("urls\n"); + printf("\t{\n"); + for (int i = 0; i < n->urls->count; i++) { + printf("\t\t%s\n", (n->urls->strs)[i]); + } + printf("\t}\n"); printf("}\n"); } static int GrabXErrorHandler(Display * display, XErrorEvent * e) { - dunst_grab_errored = True; + dunst_grab_errored = true; char err_buf[BUFSIZ]; XGetErrorText(display, e->error_code, err_buf, BUFSIZ); fputs(err_buf, stderr); @@ -138,7 +258,7 @@ static int GrabXErrorHandler(Display * display, XErrorEvent * e) static void setup_error_handler(void) { - dunst_grab_errored = False; + dunst_grab_errored = false; XFlush(dc->dpy); XSetErrorHandler(GrabXErrorHandler); @@ -147,7 +267,7 @@ static void setup_error_handler(void) static int tear_down_error_handler(void) { XFlush(dc->dpy); - XSync(dc->dpy, False); + XSync(dc->dpy, false); XSetErrorHandler(NULL); return dunst_grab_errored; } @@ -155,7 +275,7 @@ static int tear_down_error_handler(void) int grab_key(keyboard_shortcut * ks) { if (!ks->is_valid) - return 1; + return 1; Window root; root = RootWindow(dc->dpy, DefaultScreen(dc->dpy)); @@ -163,11 +283,11 @@ int grab_key(keyboard_shortcut * ks) if (ks->is_valid) XGrabKey(dc->dpy, ks->code, ks->mask, root, - True, GrabModeAsync, GrabModeAsync); + true, GrabModeAsync, GrabModeAsync); if (tear_down_error_handler()) { fprintf(stderr, "Unable to grab key \"%s\"\n", ks->str); - ks->is_valid = False; + ks->is_valid = false; return 1; } return 0; @@ -181,83 +301,11 @@ void ungrab_key(keyboard_shortcut * ks) XUngrabKey(dc->dpy, ks->code, ks->mask, root); } -void warn(const char *text, int urg) -{ - notification *n = malloc(sizeof(notification)); - - if (n == NULL) - die("Unable to allocate memory", EXIT_FAILURE); - - n->appname = strdup("dunst"); - n->summary = strdup(text); - if (n->summary == NULL) - die("Unable to allocate memory", EXIT_FAILURE); - n->body = strdup(""); - n->icon = strdup(""); - n->timeout = 0; - n->urgency = urg; - n->progress = 0; - n->dbus_client = NULL; - n->color_strings[ColFG] = NULL; - n->color_strings[ColBG] = NULL; - init_notification(n, 0); - map_win(); -} - -int cmp_notification(void *a, void *b) -{ - if (a == NULL && b == NULL) - return 0; - else if (a == NULL) - return -1; - else if (b == NULL) - return 1; - - notification *na = (notification *) a; - notification *nb = (notification *) b; - if (na->urgency != nb->urgency) { - return na->urgency - nb->urgency; - } else { - return nb->timestamp - na->timestamp; - } -} - -l_node *most_important(list * l) -{ - - if (l == NULL || l_is_empty(l)) { - return NULL; - } - - if (sort) { - notification *max; - l_node *node_max; - notification *data; - - max = l->head->data; - node_max = l->head; - for (l_node * iter = l->head; iter; iter = iter->next) { - data = (notification *) iter->data; - if (cmp_notification(max, data) < 0) { - max = data; - node_max = iter; - } - } - return node_max; - } else { - return l->head; - } -} - void apply_rules(notification * n) { - if (l_is_empty(rules) || n == NULL) { - return; - } - - for (l_node * iter = rules->head; iter; iter = iter->next) { - rule_t *r = (rule_t *) iter->data; + for (int i = 0; i < rules.count; i++) { + rule_t *r = &(rules.rules[i]); if ((!r->appname || !fnmatch(r->appname, n->appname, 0)) && (!r->summary || !fnmatch(r->summary, n->summary, 0)) && (!r->body || !fnmatch(r->body, n->body, 0)) @@ -275,54 +323,48 @@ void apply_rules(notification * n) void check_timeouts(void) { - l_node *iter; - notification *current; - l_node *next; - /* nothing to do */ - if (l_is_empty(displayed_notifications)) { + if (!displayed) return; - } - iter = displayed_notifications->head; - while (iter != NULL) { - current = (notification *) iter->data; + for (n_queue *iter = displayed; iter; iter = iter->next) { + notification *n = iter->n; /* don't timeout when user is idle */ if (is_idle()) { - current->start = now; - iter = iter->next; + n->start = now; continue; } /* skip hidden and sticky messages */ - if (current->start == 0 || current->timeout == 0) { - iter = iter->next; + if (n->start == 0 || n->timeout == 0) { continue; } /* remove old message */ - if (difftime(now, current->start) > current->timeout) { - /* l_move changes iter->next, so we need to store it beforehand */ - next = iter->next; - close_notification(current, 1); - iter = next; - continue; - - } else { - iter = iter->next; + if (difftime(now, n->start) > n->timeout) { + /* close_notification may conflict with iter, so restart */ + close_notification(n, 1); + check_timeouts(); + return; } } } void update_lists() { - l_node *to_move; - notification *n; int limit; check_timeouts(); + if (pause_display) { + while (displayed) { + notification *n = n_queue_dequeue(&displayed); + n_queue_enqueue(&queue, n); + } + return; + } + if (geometry.h == 0) { limit = 0; } else if (geometry.h == 1) { @@ -333,36 +375,60 @@ void update_lists() limit = geometry.h; } - /* move notifications from queue to displayed */ - while (!l_is_empty(notification_queue)) { - if (limit > 0 && l_length(displayed_notifications) >= limit) { + /* move notifications from queue to displayed */ + while (queue) { + + if (limit > 0 && n_queue_len(&displayed) >= limit) { /* the list is full */ break; } - to_move = most_important(notification_queue); - if (!to_move) { - return; - } - n = (notification *) to_move->data; + notification *n = n_queue_dequeue(&queue); + if (!n) return; n->start = now; - /* TODO get notifications pushed back into - * notification_queue if there's a more important - * message waiting there - */ - - l_move(notification_queue, displayed_notifications, to_move); - - l_sort(displayed_notifications, cmp_notification); - + n_queue_enqueue(&displayed, n); } } -/* TODO get draw_txt_buf as argument */ +void r_line_cache_init(r_line_cache *c) +{ + c->count = 0; + c->size = 0; + c->lines = NULL; +} + +void r_line_cache_append(r_line_cache *c, const char *s, ColorSet *col, bool continues) +{ + if (!c || !s) + return; + + /* resize cache if it's too small */ + if (c->count >= c->size) { + c->size++; + c->lines = realloc(c->lines, c->size * sizeof(r_line)); + } + + c->count++; + c->lines[c->count-1].colors = col; + c->lines[c->count-1].str = strdup(s); + c->lines[c->count-1].continues = continues; +} + +void r_line_cache_reset(r_line_cache *c) +{ + for (int i = 0; i < c->count; i++) { + if (c->lines[i].str) + free(c->lines[i].str); + c->lines[i].str = NULL; + c->lines[i].colors = NULL; + } + c->count = 0; +} + int do_word_wrap(char *source, int max_width) { @@ -373,16 +439,16 @@ int do_word_wrap(char *source, int max_width) char *eol = source; - while (True) { + while (true) { if (*eol == '\0') return 1; if (*eol == '\n') { *eol = '\0'; return 1 + do_word_wrap(eol + 1, max_width); } - if (*eol == '\\' && *(eol+1) == 'n') { + if (*eol == '\\' && *(eol + 1) == 'n') { *eol = ' '; - *(eol+1) = '\0'; + *(eol + 1) = '\0'; return 1 + do_word_wrap(eol + 2, max_width); } @@ -405,20 +471,17 @@ int do_word_wrap(char *source, int max_width) return 1 + do_word_wrap(space + 1, max_width); } } - eol++; + eol++; } } -void update_draw_txt_buf(notification * n, int max_width) +void add_notification_to_line_cache(notification *n, int max_width) { rstrip(n->msg); char *msg = n->msg; - while(isspace(*msg)) + while (isspace(*msg)) msg++; - if (n->draw_txt_buf.txt) - free(n->draw_txt_buf.txt); - char *buf; /* print dup_count */ @@ -448,9 +511,10 @@ void update_draw_txt_buf(notification * n, int max_width) char *new_buf; if (hours > 0) { asprintf(&new_buf, "%s (%dh %dm %ds old)", buf, hours, - minutes, seconds); + minutes, seconds); } else if (minutes > 0) { - asprintf(&new_buf, "%s (%dm %ds old)", buf, minutes, seconds); + asprintf(&new_buf, "%s (%dm %ds old)", buf, minutes, + seconds); } else { asprintf(&new_buf, "%s (%ds old)", buf, seconds); } @@ -459,22 +523,18 @@ void update_draw_txt_buf(notification * n, int max_width) buf = new_buf; } - n->draw_txt_buf.line_count = do_word_wrap(buf, max_width); - n->draw_txt_buf.txt = buf; -} -char *draw_txt_get_line(draw_txt * dt, int line) -{ - if (line > dt->line_count) { - return NULL; + int linecnt = do_word_wrap(buf, max_width); + n->line_count = linecnt; + char *cur = buf; + for (int i = 0; i < linecnt; i++) { + r_line_cache_append(&line_cache, cur, n->colors, i+1 != linecnt); + + while (*cur != '\0') + cur++; + cur++; } - - char *begin = dt->txt; - for (int i = 1; i < line; i++) { - begin += strlen(begin) + 1; - } - - return begin; + free(buf); } int calculate_x_offset(int line_width, int text_width) @@ -485,7 +545,8 @@ int calculate_x_offset(int line_width, int text_width) /* If the text is wider than the frame, bouncing is enabled and word_wrap disabled */ if (line_width < text_width && bounce_freq > 0.0001 && !word_wrap) { gettimeofday(&t, NULL); - pos = ((t.tv_sec % 100) * 1e6 + t.tv_usec) / (1e6 / bounce_freq); + pos = + ((t.tv_sec % 100) * 1e6 + t.tv_usec) / (1e6 / bounce_freq); return (1 + sinf(2 * 3.14159 * pos)) * leftover / 2; } switch (align) { @@ -547,144 +608,28 @@ unsigned long calculate_foreground_color(unsigned long source_color) return color.pixel; } -void draw_win(void) +int calculate_width(void) { - int width, x, y, height; - - line_height = MAX(line_height, font_h); - - update_screen_info(); - - /* calculate width */ if (geometry.mask & WidthValue && geometry.w == 0) { /* dynamic width */ - width = 0; + return 0; } else if (geometry.mask & WidthValue) { /* fixed width */ - width = geometry.w; + if (geometry.negative_width) { + return scr.dim.w - geometry.w; + } else { + return geometry.w; + } } else { /* across the screen */ - width = scr.dim.w; + return scr.dim.w; } +} - /* update draw_txt_bufs and line_cnt */ - int line_cnt = 0; - for (l_node * iter = displayed_notifications->head; iter; - iter = iter->next) { - notification *n = (notification *) iter->data; - update_draw_txt_buf(n, width); - line_cnt += n->draw_txt_buf.line_count; - } - - - - /* calculate height */ - if (geometry.h == 0) { - height = line_cnt * line_height; - } else { - height = MAX(geometry.h, (line_cnt * line_height)); - } - - height += (l_length(displayed_notifications) - 1) * separator_height; - - - /* add "(x more)" */ - draw_txt x_more; - x_more.txt = NULL; - - char *print_to; - int more = l_length(notification_queue); - if (indicate_hidden && more > 0) { - - int x_more_len = strlen(" ( more) ") + digit_count(more); - - if (geometry.h != 1) { - /* add additional line */ - x_more.txt = calloc(x_more_len, sizeof(char)); - height += line_height; - line_cnt++; - - print_to = x_more.txt; - - } else { - /* append "(x more)" message to notification text */ - notification *n = - (notification *) displayed_notifications->head->data; - print_to = - draw_txt_get_line(&n->draw_txt_buf, - n->draw_txt_buf.line_count); - for (; *print_to != '\0'; print_to++) ; - } - snprintf(print_to, x_more_len, "(%d more)", more); - } - - /* if we have a dynamic width, calculate the actual width */ - if (width == 0) { - for (l_node * iter = displayed_notifications->head; iter; - iter = iter->next) { - notification *n = (notification *) iter->data; - for (int i = 0; i < n->draw_txt_buf.line_count; i++) { - char *line = - draw_txt_get_line(&n->draw_txt_buf, i+1); - assert(line != NULL); - width = MAX(width, textw(dc, line)); - } - } - } - - assert(line_height > 0); - assert(font_h > 0); - assert(width > 0); - assert(height > 0); - assert(line_cnt > 0); - - resizedc(dc, width, height); - - /* draw buffers */ - dc->y = 0; - ColorSet *last_color; - assert(displayed_notifications->head != NULL); - for (l_node * iter = displayed_notifications->head; iter; - iter = iter->next) { - - notification *n = (notification *) iter->data; - last_color = n->colors; - - for (int i = 0; i < n->draw_txt_buf.line_count; i++) { - - char *line = draw_txt_get_line(&n->draw_txt_buf, i + 1); - dc->x = 0; - drawrect(dc, 0, 0, width, line_height, True, - n->colors->BG); - - dc->x = calculate_x_offset(width, textw(dc, line)); - dc->y += (line_height - font_h) / 2; - drawtextn(dc, line, strlen(line), n->colors); - dc->y += line_height - ((line_height - font_h) / 2); - } - - /* draw separator */ - if (separator_height > 0) { - dc -> x = 0; - double color; - if (sep_color == AUTO) - color = calculate_foreground_color(n->colors->BG); - else - color = n->colors->FG; - - drawrect(dc, 0, 0, width, separator_height, True, color); - dc->y += separator_height; - } - } - - /* draw x_more */ - if (x_more.txt) { - dc->x = 0; - drawrect(dc, 0, 0, width, line_height, True, last_color->BG); - dc->x = calculate_x_offset(width, textw(dc, x_more.txt)); - drawtext(dc, x_more.txt, last_color); - } +void move_and_map(int width, int height) +{ + int x,y; /* calculate window position */ if (geometry.mask & XNegative) { x = (scr.dim.x + (scr.dim.w - width)) + geometry.x; @@ -700,8 +645,7 @@ void draw_win(void) /* move and map window */ if (x != window_dim.x || y != window_dim.y - || width != window_dim.w - || height != window_dim.h) { + || width != window_dim.w || height != window_dim.h) { XResizeWindow(dc->dpy, win, width, height); XMoveWindow(dc->dpy, win, x, y); @@ -714,7 +658,100 @@ void draw_win(void) mapdc(dc, win, width, height); - free(x_more.txt); +} + +void fill_line_cache(int width) +{ + /* create cache with all lines */ + for (n_queue *iter = displayed; iter; iter = iter->next) { + add_notification_to_line_cache(iter->n, width); + } + + assert(line_cache.count > 0); + + /* add (x more) */ + int queue_cnt = n_queue_len(&queue); + if (indicate_hidden && queue_cnt > 0) { + if (geometry.h != 1) { + char *tmp; + asprintf(&tmp, "(%d more)", queue_cnt); + ColorSet *last_colors = + line_cache.lines[line_cache.count-1].colors; + r_line_cache_append(&line_cache, tmp, last_colors, false); + free(tmp); + } else { + char *old = line_cache.lines[0].str; + char *new; + asprintf(&new, "%s (%d more)", old, queue_cnt); + free(old); + line_cache.lines[0].str = new; + } + } +} + + +void draw_win(void) +{ + + r_line_cache_reset(&line_cache); + update_screen_info(); + int width = calculate_width(); + + + line_height = MAX(line_height, font_h); + + + fill_line_cache(width); + + + /* if we have a dynamic width, calculate the actual width */ + if (width == 0) { + for (int i = 0; i < line_cache.count; i++) { + char *line = line_cache.lines[i].str; + width = MAX(width, textw(dc, line)); + } + } + + /* resize dc to correct width */ + + int height = (line_cache.count * line_height) + + (separator_height * (n_queue_len(&displayed) - 1)); + + + resizedc(dc, width, height); + + dc->y = 0; + + for (int i = 0; i < line_cache.count; i++) { + dc->x = 0; + + r_line line = line_cache.lines[i]; + + + /* draw background */ + drawrect(dc, 0, 0, width, line_height, true, line.colors->BG); + + /* draw text */ + dc->x = calculate_x_offset(width, textw(dc, line.str)); + dc->y += (line_height - font_h) / 2; + drawtextn(dc, line.str, strlen(line.str), line.colors); + dc->y += line_height - ((line_height - font_h) / 2); + + /* draw separator */ + if (separator_height > 0 && i < line_cache.count - 1 && !line.continues) { + dc->x = 0; + double color; + if (sep_color == AUTO) + color = calculate_foreground_color(line.colors->BG); + else + color = line.colors->FG; + drawrect(dc, 0, 0, width, separator_height, true, color); + dc->y += separator_height; + } + + } + + move_and_map(width, height); } char @@ -776,18 +813,17 @@ void handle_mouse_click(XEvent ev) if (ev.xbutton.button == Button1) { int y = 0; - notification *n; - l_node *iter = displayed_notifications->head; - assert(iter); - for (; iter; iter = iter->next) { - n = (notification *) iter->data; - int height = font_h * n->draw_txt_buf.line_count; + notification *n = NULL; + for (n_queue *iter = displayed; iter; iter = iter->next) { + n = iter->n; + int height = MAX(font_h, line_height) * n->line_count; if (ev.xbutton.y > y && ev.xbutton.y < y + height) break; else y += height; } - close_notification(n, 2); + if (n) + close_notification(n, 2); } } @@ -798,9 +834,10 @@ void handleXEvents(void) XNextEvent(dc->dpy, &ev); switch (ev.type) { case Expose: - if (ev.xexpose.count == 0) + if (ev.xexpose.count == 0 && visible) { draw_win(); - mapdc(dc, win, scr.dim.w, font_h); + mapdc(dc, win, scr.dim.w, font_h); + } break; case SelectionNotify: if (ev.xselection.property == utf8) @@ -818,10 +855,8 @@ void handleXEvents(void) if (close_ks.str && XLookupKeysym(&ev.xkey, 0) == close_ks.sym && close_ks.mask == ev.xkey.state) { - if (!l_is_empty(displayed_notifications)) { - notification *n = (notification *) - displayed_notifications->head->data; - close_notification(n, 2); + if (displayed) { + close_notification(displayed->n, 2); } } if (history_ks.str @@ -834,45 +869,39 @@ void handleXEvents(void) && close_all_ks.mask == ev.xkey.state) { move_all_to_history(); } + if (context_ks.str + && XLookupKeysym(&ev.xkey, 0) == context_ks.sym + && context_ks.mask == ev.xkey.state) { + context_menu(); + } } } } void move_all_to_history() { - l_node *node; - notification *n; - - while (!l_is_empty(displayed_notifications)) { - node = displayed_notifications->head; - n = (notification *) node->data; - close_notification(n, 2); + while (displayed) { + close_notification(displayed->n, 2); } - while (!l_is_empty(notification_queue)) { - node = notification_queue->head; - n = (notification *) node->data; - close_notification(n, 2); + + notification *n = n_queue_dequeue(&queue); + while (n) { + n_stack_push(&history, n); + n = n_queue_dequeue(&queue); } } void history_pop(void) { - l_node *iter; - notification *data; - /* nothing to do */ - if (l_is_empty(notification_history)) { + if (!history) return; - } - for (iter = notification_history->head; iter->next; iter = iter->next) ; - data = (notification *) iter->data; - data->redisplayed = True; - data->start = 0; - if (sticky_history) { - data->timeout = 0; - } - l_move(notification_history, notification_queue, iter); + notification *n = n_stack_pop(&history); + n->redisplayed = true; + n->start = 0; + n->timeout = sticky_history ? 0 : n->timeout; + n_queue_enqueue(&queue, n); if (!visible) { map_win(); @@ -899,43 +928,57 @@ int init_notification(notification * n, int id) if (n == NULL) return -1; + + if (strcmp("DUNST_COMMAND_PAUSE", n->summary) == 0) { + pause_display = true; + return 0; + } + + if (strcmp("DUNST_COMMAND_RESUME", n->summary) == 0) { + pause_display = false; + return 0; + } + n->format = format; apply_rules(n); n->msg = string_replace("%a", n->appname, strdup(n->format)); n->msg = string_replace("%s", n->summary, n->msg); - n->msg = string_replace("%i", n->icon, n->msg); - n->msg = string_replace("%I", basename(n->icon), n->msg); + if (n->icon) { + n->msg = string_replace("%I", basename(n->icon), n->msg); + n->msg = string_replace("%i", n->icon, n->msg); + } n->msg = string_replace("%b", n->body, n->msg); if (n->progress) { char pg[10]; - sprintf(pg, "[%3d%%]", n->progress-1); + sprintf(pg, "[%3d%%]", n->progress - 1); n->msg = string_replace("%p", pg, n->msg); } else { n->msg = string_replace("%p", "", n->msg); } n->msg = fix_markup(n->msg); + n->msg = rstrip(n->msg); n->dup_count = 0; - n->draw_txt_buf.txt = NULL; /* check if n is a duplicate */ - for (l_node * iter = notification_queue->head; iter; iter = iter->next) { - notification *orig = (notification *) iter->data; - if (strcmp(orig->appname, n->appname) == 0 && strcmp(orig->msg, n->msg) == 0) { + for (n_queue *iter = queue; iter; iter = iter->next) { + notification *orig = iter->n; + if (strcmp(orig->appname, n->appname) == 0 + && strcmp(orig->msg, n->msg) == 0) { orig->dup_count++; free_notification(n); return orig->id; } } - for (l_node * iter = displayed_notifications->head; iter; - iter = iter->next) { - notification *orig = (notification *) iter->data; - if (strcmp(orig->appname, n->appname) == 0 && strcmp(orig->msg, n->msg) == 0) { + for (n_queue *iter = displayed; iter; iter = iter->next) { + notification *orig = iter->n; + if (strcmp(orig->appname, n->appname) == 0 + && strcmp(orig->msg, n->msg) == 0) { orig->dup_count++; orig->start = now; free_notification(n); @@ -965,7 +1008,7 @@ int init_notification(notification * n, int id) n->timestamp = now; - n->redisplayed = False; + n->redisplayed = false; if (id == 0) { n->id = ++next_notification_id; @@ -974,17 +1017,23 @@ int init_notification(notification * n, int id) n->id = id; } - if(strlen(n->msg) == 0) { + if (strlen(n->msg) == 0) { close_notification(n, 2); printf("skipping notification: %s %s\n", n->body, n->summary); } else { - l_push(notification_queue, n); + n_queue_enqueue(&queue, n); } + char *tmp; + asprintf(&tmp, "%s %s", n->summary, n->body); + + n->urls = extract_urls(tmp); + + free(tmp); + if (print_notifications) print_notification(n); - return n->id; } @@ -997,23 +1046,23 @@ int init_notification(notification * n, int id) */ int close_notification_by_id(int id, int reason) { - l_node *iter; notification *target = NULL; - for (iter = displayed_notifications->head; iter; iter = iter->next) { - notification *n = (notification *) iter->data; + for (n_queue *iter = displayed; iter; iter = iter->next) { + notification *n = iter->n; if (n->id == id) { - l_move(displayed_notifications, notification_history, - iter); + n_queue_remove(&displayed, n); + n_stack_push(&history, n); target = n; break; } } - for (iter = notification_queue->head; iter; iter = iter->next) { - notification *n = (notification *) iter->data; + for (n_queue *iter = queue; iter; iter = iter->next) { + notification *n = iter->n; if (n->id == id) { - l_move(notification_queue, notification_history, iter); + n_queue_remove(&queue, n); + n_stack_push(&history, n); target = n; break; } @@ -1060,7 +1109,7 @@ void init_shortcut(keyboard_shortcut * ks) return; if (!strcmp(ks->str, "none") || (!strcmp(ks->str, ""))) { - ks->is_valid = False; + ks->is_valid = false; return; } @@ -1096,21 +1145,19 @@ void init_shortcut(keyboard_shortcut * ks) } } - if (ks->sym == NoSymbol || ks->code == NoSymbol) { fprintf(stderr, "Warning: Unknown keyboard shortcut: %s\n", ks->str); - ks->is_valid = False; + ks->is_valid = false; } else { - ks->is_valid = True; + ks->is_valid = true; } free(str_begin); } -rule_t *initrule(void) +void initrule(rule_t *r) { - rule_t *r = malloc(sizeof(rule_t)); r->name = NULL; r->appname = NULL; r->summary = NULL; @@ -1121,23 +1168,21 @@ rule_t *initrule(void) r->fg = NULL; r->bg = NULL; r->format = NULL; - - return r; } -int is_idle(void) +bool is_idle(void) { XScreenSaverQueryInfo(dc->dpy, DefaultRootWindow(dc->dpy), screensaver_info); if (idle_threshold == 0) { - return False; + return false; } return screensaver_info->idle / 1000 > idle_threshold; } void run(void) { - while (True) { + while (true) { if (visible) { dbus_poll(50); } else { @@ -1147,7 +1192,7 @@ void run(void) /* move messages from notification_queue to displayed_notifications */ update_lists(); - if (l_length(displayed_notifications) > 0) { + if (displayed) { if (!visible) { map_win(); } else { @@ -1166,11 +1211,12 @@ void hide_win() { ungrab_key(&close_ks); ungrab_key(&close_all_ks); + ungrab_key(&context_ks); XUngrabButton(dc->dpy, AnyButton, AnyModifier, win); XUnmapWindow(dc->dpy, win); XFlush(dc->dpy); - visible = False; + visible = false; } Window get_focused_window(void) @@ -1182,10 +1228,10 @@ Window get_focused_window(void) unsigned char *prop_return = NULL; Window root = RootWindow(dc->dpy, DefaultScreen(dc->dpy)); Atom netactivewindow = - XInternAtom(dc->dpy, "_NET_ACTIVE_WINDOW", False); + XInternAtom(dc->dpy, "_NET_ACTIVE_WINDOW", false); XGetWindowProperty(dc->dpy, root, netactivewindow, 0L, - sizeof(Window), False, XA_WINDOW, + sizeof(Window), false, XA_WINDOW, &type, &format, &nitems, &bytes_after, &prop_return); if (prop_return) { focused = *(Window *) prop_return; @@ -1272,26 +1318,72 @@ void update_screen_info() void setup(void) { - Window root; - XSetWindowAttributes wa; - notification_queue = l_init(); - notification_history = l_init(); - displayed_notifications = l_init(); + /* initialize dc, font, keyboard, colors */ + dc = initdc(); + + initfont(dc, font); + + init_shortcut(&close_ks); + init_shortcut(&close_all_ks); + init_shortcut(&history_ks); + init_shortcut(&context_ks); + + grab_key(&close_ks); + ungrab_key(&close_ks); + grab_key(&close_all_ks); + ungrab_key(&close_all_ks); + grab_key(&history_ks); + ungrab_key(&history_ks); + grab_key(&context_ks); + ungrab_key(&context_ks); + + colors[LOW] = initcolor(dc, lowfgcolor, lowbgcolor); + colors[NORM] = initcolor(dc, normfgcolor, normbgcolor); + colors[CRIT] = initcolor(dc, critfgcolor, critbgcolor); + + color_strings[ColFG][LOW] = lowfgcolor; + color_strings[ColFG][NORM] = normfgcolor; + color_strings[ColFG][CRIT] = critfgcolor; + + color_strings[ColBG][LOW] = lowbgcolor; + color_strings[ColBG][NORM] = normbgcolor; + color_strings[ColBG][CRIT] = critbgcolor; + + /* parse and set geometry and monitor position */ + if (geom[0] == '-') { + geometry.negative_width = true; + geom++; + } else { + geometry.negative_width = false; + } + + geometry.mask = XParseGeometry(geom, + &geometry.x, &geometry.y, + &geometry.w, &geometry.h); + + window_dim.x = 0; + window_dim.y = 0; + window_dim.w = 0; + window_dim.h = 0; + + screensaver_info = XScreenSaverAllocInfo(); + + scr.scr = monitor; if (scr.scr < 0) { scr.scr = DefaultScreen(dc->dpy); } + + /* initialize window */ + Window root; + XSetWindowAttributes wa; + root = RootWindow(dc->dpy, DefaultScreen(dc->dpy)); - - utf8 = XInternAtom(dc->dpy, "UTF8_STRING", False); - - /* menu geometry */ + utf8 = XInternAtom(dc->dpy, "UTF8_STRING", false); font_h = dc->font.height + FONT_HEIGHT_BORDER; - update_screen_info(); - /* menu window */ - wa.override_redirect = True; + wa.override_redirect = true; wa.background_pixmap = ParentRelative; wa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask | @@ -1304,21 +1396,23 @@ void setup(void) DefaultScreen(dc->dpy)), CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa); transparency = transparency > 100 ? 100 : transparency; - setopacity(dc, win, (unsigned long)((100 - transparency) * (0xffffffff/100))); + setopacity(dc, win, + (unsigned long)((100 - transparency) * (0xffffffff / 100))); grab_key(&history_ks); } void map_win(void) { /* window is already mapped or there's nothing to show */ - if (visible || l_is_empty(displayed_notifications)) { + if (visible || !displayed) { return; } grab_key(&close_ks); grab_key(&close_all_ks); + grab_key(&context_ks); setup_error_handler(); - XGrabButton(dc->dpy, AnyButton, AnyModifier, win, False, + XGrabButton(dc->dpy, AnyButton, AnyModifier, win, false, BUTTONMASK, GrabModeAsync, GrabModeSync, None, None); if (tear_down_error_handler()) { fprintf(stderr, "Unable to grab mouse button(s)\n"); @@ -1328,7 +1422,7 @@ void map_win(void) XMapRaised(dc->dpy, win); draw_win(); XFlush(dc->dpy); - visible = True; + visible = true; } void parse_follow_mode(const char *mode) @@ -1346,169 +1440,10 @@ void parse_follow_mode(const char *mode) } -void parse_cmdline(int argc, char *argv[]) +void load_options(char *cmdline_config_path) { - int c; - while (1) { - static struct option long_options[] = { - {"help", no_argument, NULL, 'h'}, - {"fn", required_argument, NULL, 'F'}, - {"nb", required_argument, NULL, 'n'}, - {"nf", required_argument, NULL, 'N'}, - {"lb", required_argument, NULL, 'l'}, - {"lf", required_argument, NULL, 'L'}, - {"cb", required_argument, NULL, 'c'}, - {"cf", required_argument, NULL, 'C'}, - {"to", required_argument, NULL, 't'}, - {"lto", required_argument, NULL, '0'}, - {"nto", required_argument, NULL, '1'}, - {"cto", required_argument, NULL, '2'}, - {"format", required_argument, NULL, 'f'}, - {"key", required_argument, NULL, 'k'}, - {"history_key", required_argument, NULL, 'K'}, - {"all_key", required_argument, NULL, 'A'}, - {"geometry", required_argument, NULL, 'g'}, - {"config", required_argument, NULL, 'r'}, - {"mod", required_argument, NULL, 'M'}, - {"mon", required_argument, NULL, 'm'}, - {"ns", no_argument, NULL, 'x'}, - {"follow", required_argument, NULL, 'o'}, - {"line_height", required_argument, NULL, 'H'}, - {"lh", required_argument, NULL, 'H'}, - {"print", no_argument, NULL, 'V'}, - {"version", no_argument, NULL, 'v'}, - {0, 0, 0, 0} - }; - - int option_index = 0; - - c = getopt_long_only(argc, argv, "bhsv", long_options, - &option_index); - - if (c == -1) { - break; - } - - KeySym mod = 0; - switch (c) { - case 0: - break; - case 'h': - usage(EXIT_SUCCESS); - break; - case 'F': - font = optarg; - break; - case 'n': - normbgcolor = optarg; - break; - case 'N': - normfgcolor = optarg; - break; - case 'l': - lowbgcolor = optarg; - break; - case 'L': - lowfgcolor = optarg; - break; - case 'c': - critbgcolor = optarg; - break; - case 'C': - critfgcolor = optarg; - break; - case 't': - timeouts[0] = atoi(optarg); - timeouts[1] = timeouts[0]; - break; - case '0': - timeouts[0] = atoi(optarg); - break; - case '1': - timeouts[1] = atoi(optarg); - break; - case '2': - timeouts[2] = atoi(optarg); - break; - case 'm': - scr.scr = atoi(optarg); - break; - case 'f': - format = optarg; - break; - case 'M': - deprecated_mod = True; - mod = string_to_mask(optarg); - close_ks.mask = mod; - close_all_ks.mask = mod; - history_ks.mask = mod; - break; - case 'k': - close_ks.str = optarg; - break; - case 'K': - history_ks.str = optarg; - break; - case 'A': - close_all_ks.str = optarg; - break; - case 'g': - geom = optarg; - break; - case 's': - sort = True; - break; - case 'r': - /* this option is parsed elsewhere. This is just to supress - * error message */ - break; - case 'x': - sort = False; - break; - case 'o': - parse_follow_mode(optarg); - break; - case 'H': - line_height = atoi(optarg); - break; - case 'v': - print_version(); - break; - case 'V': - print_notifications = True; - break; - default: - usage(EXIT_FAILURE); - break; - } - } -} #ifndef STATIC_CONFIG -static rule_t *dunst_rules_find_or_create(const char *section) -{ - l_node *iter; - rule_t *rule; - - /* find rule */ - for (iter = rules->head; iter; iter = iter->next) { - rule_t *r = (rule_t *) iter->data; - if (strcmp(r->name, section) == 0) { - return r; - } - } - - rule = initrule(); - rule->name = strdup(section); - - l_push(rules, rule); - - return rule; -} - -void parse_dunstrc(char *cmdline_config_path) -{ - xdgHandle xdg; FILE *config_file = NULL; @@ -1532,40 +1467,56 @@ void parse_dunstrc(char *cmdline_config_path) } load_ini_file(config_file); +#endif - font = ini_get_string("global", "font", font); - format = ini_get_string("global", "format", format); - sort = ini_get_bool("global", "sort", sort); - indicate_hidden = ini_get_bool("global", "indicate_hidden", indicate_hidden); - word_wrap = ini_get_bool("global", "word_wrap", word_wrap); - idle_threshold = ini_get_int("global", "idle_threshold", idle_threshold); - monitor = ini_get_int("global", "monitor", monitor); + font = + option_get_string("global", "font", "-fn", font, + "The font dunst should use."); + format = + option_get_string("global", "format", "-format", format, + "The format template for the notifictions"); + sort = + option_get_bool("global", "sort", "-sort", sort, + "Sort notifications by urgency and date?"); + indicate_hidden = + option_get_bool("global", "indicate_hidden", "-indicate_hidden", + indicate_hidden, + "Show how many notificaitons are hidden?"); + word_wrap = + option_get_bool("global", "word_wrap", "-word_wrap", word_wrap, + "Truncating long lines or do word wrap"); + idle_threshold = + option_get_int("global", "idle_threshold", "-idle_threshold", + idle_threshold, + "Don't timeout notifications if user is longer idle than threshold"); + monitor = + option_get_int("global", "monitor", "-mon", monitor, + "On which monitor should the notifications be displayed"); { - char *c = ini_get_string("global", "follow", ""); + char *c = + option_get_string("global", "follow", "-follow", "", + "Follow mouse, keyboard or none?"); if (strlen(c) > 0) { parse_follow_mode(c); free(c); } } - geom = ini_get_string("global", "geometry", geom); - line_height = ini_get_int("global", "line_height", line_height); + geom = + option_get_string("global", "geometry", "-geom/-geometry", geom, + "Geometry for the window"); + line_height = + option_get_int("global", "line_height", "-lh/-line_height", + line_height, + "Add additional padding above and beneath text"); + bounce_freq = + option_get_double("global", "bounce_freq", "-bounce_freq", + bounce_freq, + "Make long text bounce from side to side"); { - char *c = ini_get_string("global", "modifier", ""); - if (strlen(c) > 0) { - deprecated_dunstrc_shortcuts = True; - KeySym mod = string_to_mask(c); - close_ks.mask = mod; - close_all_ks.mask = mod; - close_all_ks.mask = mod; - free(c); - } - } - close_ks.str = ini_get_string("global", "key", close_ks.str); - close_all_ks.str = ini_get_string("global", "key", close_all_ks.str); - history_ks.str = ini_get_string("global", "key", history_ks.str); - bounce_freq = ini_get_double("global", "bounce_freq", bounce_freq); - { - char *c = ini_get_string("global", "alignment", ""); + char *c = + option_get_string("global", "alignment", + "-align/-alignment", "", + "Align notifications left/center/right"); if (strlen(c) > 0) { if (strcmp(c, "left") == 0) align = left; @@ -1574,41 +1525,106 @@ void parse_dunstrc(char *cmdline_config_path) else if (strcmp(c, "right") == 0) align = right; else - fprintf(stderr, "Warning: unknown allignment\n"); + fprintf(stderr, + "Warning: unknown allignment\n"); free(c); } } - show_age_threshold = ini_get_int("global", "show_age_threshold", show_age_threshold); - sticky_history = ini_get_bool("global", "sticky_history", sticky_history); - separator_height = ini_get_int("global", "separator_height", separator_height); - transparency = ini_get_int("global", "transparency", transparency); + show_age_threshold = + option_get_int("global", "show_age_threshold", + "-show_age_threshold", show_age_threshold, + "When should the age of the notification be displayed?"); + sticky_history = + option_get_bool("global", "sticky_history", "-sticky_history", + sticky_history, + "Don't timeout notifications popped up from history"); + separator_height = + option_get_int("global", "separator_height", + "-sep_height/-separator_height", separator_height, + "height of the separator line"); + transparency = + option_get_int("global", "transparency", "-transparency", + transparency, "Transparency. range 0-100"); { - char *c = ini_get_string("global", "separator_color", ""); + char *c = + option_get_string("global", "separator_color", + "-sep_color/-separator_color", "", + "Color of the separator line (or 'auto')"); if (strlen(c) > 0) { if (strcmp(c, "auto") == 0) sep_color = AUTO; else if (strcmp(c, "foreground") == 0) sep_color = FOREGROUND; else - fprintf(stderr, "Warning: Unknown separator color\n"); + fprintf(stderr, + "Warning: Unknown separator color\n"); free(c); } } - lowbgcolor = ini_get_string("urgency_low", "background", lowbgcolor); - lowfgcolor = ini_get_string("urgency_low", "foreground", lowfgcolor); - timeouts[LOW] = ini_get_int("urgency_low", "timeout", timeouts[LOW]); - normbgcolor = ini_get_string("urgency_normal", "background", normbgcolor); - normfgcolor = ini_get_string("urgency_normal", "foreground", normfgcolor); - timeouts[NORM] = ini_get_int("urgency_normal", "timeout", timeouts[NORM]); - critbgcolor = ini_get_string("urgency_critical", "background", critbgcolor); - critfgcolor = ini_get_string("urgency_critical", "foreground", critfgcolor); - timeouts[CRIT] = ini_get_int("urgency_critical", "timeout", timeouts[CRIT]); + startup_notification = option_get_bool("global", "startup_notification", + "-startup_notification", false, "print notification on startup"); - close_ks.str = ini_get_string("shortcuts", "close", close_ks.str); - close_all_ks.str = ini_get_string("shortcuts", "close_all", close_all_ks.str); - history_ks.str = ini_get_string("shortcuts", "history", history_ks.str); + dmenu = option_get_string("global", "dmenu", "-dmenu", dmenu, "path to dmenu"); + dmenu_cmd = string_to_argv(dmenu); + + browser = option_get_string("global", "browser", "-browser", browser, "path to browser"); + browser_cmd = string_to_argv(browser); + + lowbgcolor = + option_get_string("urgency_low", "background", "-lb", lowbgcolor, + "Background color for notifcations with low urgency"); + lowfgcolor = + option_get_string("urgency_low", "foreground", "-lf", lowfgcolor, + "Foreground color for notifications with low urgency"); + timeouts[LOW] = + option_get_int("urgency_low", "timeout", "-lto", timeouts[LOW], + "Timeout for notifications with low urgency"); + normbgcolor = + option_get_string("urgency_normal", "background", "-nb", + normbgcolor, + "Background color for notifications with normal urgency"); + normfgcolor = + option_get_string("urgency_normal", "foreground", "-nf", + normfgcolor, + "Foreground color for notifications with normal urgency"); + timeouts[NORM] = + option_get_int("urgency_normal", "timeout", "-nto", timeouts[NORM], + "Timeout for notifications with normal urgency"); + critbgcolor = + option_get_string("urgency_critical", "background", "-cb", + critbgcolor, + "Background color for notifications with critical urgency"); + critfgcolor = + option_get_string("urgency_critical", "foreground", "-cf", + critfgcolor, + "Foreground color for notifications with ciritical urgency"); + timeouts[CRIT] = + option_get_int("urgency_critical", "timeout", "-cto", + timeouts[CRIT], + "Timeout for notifications with critical urgency"); + + close_ks.str = + option_get_string("shortcuts", "close", "-key", close_ks.str, + "Shortcut for closing one notification"); + close_all_ks.str = + option_get_string("shortcuts", "close_all", "-all_key", + close_all_ks.str, + "Shortcut for closing all notifications"); + history_ks.str = + option_get_string("shortcuts", "history", "-history_key", + history_ks.str, + "Shortcut to pop the last notification from history"); + + context_ks.str = + option_get_string("shortcuts", "context", "-context_key", + context_ks.str, + "Shortcut for context menu"); + + print_notifications = + cmdline_get_bool("-print", false, + "Print notifications to cmdline (DEBUG)"); char *cur_section = NULL; for (;;) { @@ -1616,143 +1632,125 @@ void parse_dunstrc(char *cmdline_config_path) if (!cur_section) break; if (strcmp(cur_section, "global") == 0 - || strcmp(cur_section, "shortcuts") == 0 - || strcmp(cur_section, "urgency_low") == 0 - || strcmp(cur_section, "urgency_normal") == 0 - || strcmp(cur_section, "urgency_critical") == 0) + || strcmp(cur_section, "shortcuts") == 0 + || strcmp(cur_section, "urgency_low") == 0 + || strcmp(cur_section, "urgency_normal") == 0 + || strcmp(cur_section, "urgency_critical") == 0) continue; - rule_t *current_rule = dunst_rules_find_or_create(cur_section); - current_rule->appname = ini_get_string( - cur_section, "appname", current_rule->appname); - current_rule->summary = ini_get_string( - cur_section, "summary", current_rule->summary); - current_rule->body = ini_get_string( - cur_section, "body", current_rule->body); - current_rule->icon = ini_get_string( - cur_section, "icon", current_rule->icon); - current_rule->timeout = ini_get_int( - cur_section, "timeout", current_rule->timeout); + /* check for existing rule with same name */ + rule_t *r = NULL; + for (int i = 0; i < rules.count; i++) + if (rules.rules[i].name && + strcmp(rules.rules[i].name, cur_section) == 0) + r = &(rules.rules[i]); + + if (r == NULL) { + rules.count++; + rules.rules = realloc(rules.rules, + rules.count * sizeof(rule_t)); + r = &(rules.rules[rules.count-1]); + initrule(r); + } + + r->appname = ini_get_string(cur_section, "appname", r->appname); + r->summary = ini_get_string(cur_section, "summary", r->summary); + r->body = ini_get_string(cur_section, "body", r->body); + r->icon = ini_get_string(cur_section, "icon", r->icon); + r->timeout = ini_get_int(cur_section, "timeout", r->timeout); { char *urg = ini_get_string(cur_section, "urgency", ""); if (strlen(urg) > 0) { if (strcmp(urg, "low") == 0) - current_rule->urgency = LOW; + r->urgency = LOW; else if (strcmp(urg, "normal") == 0) - current_rule->urgency = NORM; + r->urgency = NORM; else if (strcmp(urg, "critical") == 0) - current_rule->urgency = CRIT; + r->urgency = CRIT; else fprintf(stderr, - "unknown urgency: %s, ignoring\n", urg); + "unknown urgency: %s, ignoring\n", + urg); free(urg); } } - current_rule->fg = ini_get_string( - cur_section, "foreground", current_rule->fg); - current_rule->bg = ini_get_string( - cur_section, "background", current_rule->bg); - current_rule->format = ini_get_string( - cur_section, "format", current_rule->format); + r->fg = ini_get_string(cur_section, "foreground", r->fg); + r->bg = ini_get_string(cur_section, "background", r->bg); + r->format = ini_get_string(cur_section, "format", r->format); } +#ifndef STATIC_CONFIG fclose(config_file); free_ini(); xdgWipeHandle(&xdg); +#endif } - -char *parse_cmdline_for_config_file(int argc, char *argv[]) -{ - for (int i = 0; i < argc; i++) { - if (strstr(argv[i], "-config") != 0) { - if (i + 1 == argc) { - printf - ("Invalid commandline: -config needs argument\n"); - } - return argv[++i]; - } - } - return NULL; -} -#endif /* STATIC_CONFIG */ - int main(int argc, char *argv[]) { now = time(&now); - rules = l_init(); - for (int i = 0; i < LENGTH(default_rules); i++) { - l_push(rules, &default_rules[i]); + r_line_cache_init(&line_cache); + + + rules.count = LENGTH(default_rules); + rules.rules = calloc(rules.count, sizeof(rule_t)); + memcpy(rules.rules, default_rules, sizeof(rule_t) * rules.count); + + cmdline_load(argc, argv); + + if (cmdline_get_bool("-v/-version", false, "Print version") + || cmdline_get_bool("--version", false, "Print version")) { + print_version(); } - scr.scr = monitor; -#ifndef STATIC_CONFIG + char *cmdline_config_path; - cmdline_config_path = parse_cmdline_for_config_file(argc, argv); - parse_dunstrc(cmdline_config_path); -#endif - parse_cmdline(argc, argv); - dc = initdc(); + cmdline_config_path = + cmdline_get_string("-conf/-config", NULL, + "Path to configuration file"); + load_options(cmdline_config_path); - init_shortcut(&close_ks); - init_shortcut(&close_all_ks); - init_shortcut(&history_ks); - - - geometry.mask = XParseGeometry(geom, - &geometry.x, &geometry.y, - &geometry.w, &geometry.h); - - screensaver_info = XScreenSaverAllocInfo(); + if (cmdline_get_bool("-h/-help", false, "Print help") + || cmdline_get_bool("--help", false, "Print help")) { + usage(EXIT_SUCCESS); + } initdbus(); - initfont(dc, font); - - grab_key(&close_ks); - ungrab_key(&close_ks); - grab_key(&close_all_ks); - ungrab_key(&close_all_ks); - grab_key(&history_ks); - ungrab_key(&history_ks); - - colors[LOW] = initcolor(dc, lowfgcolor, lowbgcolor); - colors[NORM] = initcolor(dc, normfgcolor, normbgcolor); - colors[CRIT] = initcolor(dc, critfgcolor, critbgcolor); - - color_strings[ColFG][LOW] = lowfgcolor; - color_strings[ColFG][NORM] = normfgcolor; - color_strings[ColFG][CRIT] = critfgcolor; - - color_strings[ColBG][LOW] = lowbgcolor; - color_strings[ColBG][NORM] = normbgcolor; - color_strings[ColBG][CRIT] = critbgcolor; - - window_dim.x = 0; - window_dim.y = 0; - window_dim.w = 0; - window_dim.h = 0; setup(); + signal (SIGUSR1, pause_signal_handler); + signal (SIGUSR2, pause_signal_handler); + + if (startup_notification) { + notification *n = malloc(sizeof (notification)); + n->appname = "dunst"; + n->summary = "startup"; + n->body = "dunst is up and running"; + n->urgency = LOW; + n->icon = NULL; + n->msg = NULL; + n->dbus_client = NULL; + n->color_strings[0] = NULL; + n->color_strings[1] = NULL; + init_notification(n, 0); + } - if (deprecated_mod) - warn("-mod is deprecated. Use \"-key mod+key\" instead\n", - CRIT); - if (deprecated_dunstrc_shortcuts) - warn("You are using deprecated settings. Please update your dunstrc. SEE [shortcuts]", CRIT); run(); return 0; } void usage(int exit_status) { - fputs - ("usage: dunst [-h/--help] [-v] [-geometry geom] [-lh height] [-fn font] [-format fmt]\n[-nb color] [-nf color] [-lb color] [-lf color] [-cb color] [ -cf color]\n[-to secs] [-lto secs] [-cto secs] [-nto secs] [-key key] [-history_key key] [-all_key key] [-mon n] [-follow none/mouse/keyboard] [-config dunstrc]\n", - stderr); + fputs("usage:\n", stderr); + char *us = cmdline_create_usage(); + fputs(us, stderr); + fputs("\n", stderr); exit(exit_status); } void print_version(void) { - printf("Dunst - a dmenu-ish notification-daemon, version: %s\n", VERSION); + printf("Dunst - a dmenu-ish notification-daemon, version: %s\n", + VERSION); exit(EXIT_SUCCESS); } diff --git a/dunst.h b/dunst.h index 270e598..b68a2ef 100644 --- a/dunst.h +++ b/dunst.h @@ -2,6 +2,8 @@ #pragma once +#include + #include "draw.h" #define LOW 0 @@ -12,7 +14,6 @@ #define ColFG 1 #define ColBG 0 - enum alignment { left, center, right }; enum separator_color { FOREGROUND, AUTO }; enum follow_mode { FOLLOW_NONE, FOLLOW_MOUSE, FOLLOW_KEYBOARD }; @@ -23,6 +24,7 @@ typedef struct _dimension_t { unsigned int h; unsigned int w; int mask; + int negative_width; } dimension_t; typedef struct _screen_info { @@ -30,30 +32,26 @@ typedef struct _screen_info { dimension_t dim; } screen_info; -typedef struct _draw_txt { - char *txt; - int line_count; -} draw_txt; - typedef struct _notification { char *appname; char *summary; char *body; char *icon; char *msg; - draw_txt draw_txt_buf; const char *format; char *dbus_client; time_t start; time_t timestamp; int timeout; int urgency; - int redisplayed; /* has been displayed before? */ + bool redisplayed; /* has been displayed before? */ int id; int dup_count; ColorSet *colors; char *color_strings[2]; int progress; /* percentage + 1, 0 to hide */ + int line_count; + struct { int count; char **strs; } *urls; } notification; typedef struct _notification_buffer { @@ -83,9 +81,27 @@ typedef struct _keyboard_shortcut { KeyCode code; KeySym sym; KeySym mask; - int is_valid; + bool is_valid; } keyboard_shortcut; +typedef struct _r_line { + ColorSet *colors; + char *str; + bool continues; +} r_line; + +typedef struct r_line_cache { + int count; + int size; + r_line *lines; +} r_line_cache; + +typedef struct _rule_array { + int count; + rule_t *rules; +} rule_array; + + extern int verbosity; /* return id of notification */ diff --git a/dunst_dbus.c b/dunst_dbus.c index 8590af5..1280b58 100644 --- a/dunst_dbus.c +++ b/dunst_dbus.c @@ -3,7 +3,7 @@ #include #include "dunst.h" -#include "list.h" +#include "container.h" #include "dunst_dbus.h" @@ -131,37 +131,36 @@ void dbus_poll(int timeout) dbus_connection_read_write(dbus_conn, timeout); dbus_msg = dbus_connection_pop_message(dbus_conn); - /* we don't have a new message */ - if (dbus_msg == NULL) { - return; - } - if (dbus_message_is_method_call - (dbus_msg, "org.freedesktop.DBus.Introspectable", "Introspect")) { - dbus_introspect(dbus_msg); - } + while (dbus_msg) { + if (dbus_message_is_method_call + (dbus_msg, "org.freedesktop.DBus.Introspectable", "Introspect")) { + dbus_introspect(dbus_msg); + } - if (dbus_message_is_method_call(dbus_msg, - "org.freedesktop.Notifications", - "Notify")) { - notify(dbus_msg); + if (dbus_message_is_method_call(dbus_msg, + "org.freedesktop.Notifications", + "Notify")) { + notify(dbus_msg); + } + if (dbus_message_is_method_call(dbus_msg, + "org.freedesktop.Notifications", + "GetCapabilities")) { + getCapabilities(dbus_msg); + } + if (dbus_message_is_method_call(dbus_msg, + "org.freedesktop.Notifications", + "GetServerInformation")) { + getServerInformation(dbus_msg); + } + if (dbus_message_is_method_call(dbus_msg, + "org.freedesktop.Notifications", + "CloseNotification")) { + closeNotification(dbus_msg); + } + dbus_message_unref(dbus_msg); + dbus_msg = dbus_connection_pop_message(dbus_conn); } - if (dbus_message_is_method_call(dbus_msg, - "org.freedesktop.Notifications", - "GetCapabilities")) { - getCapabilities(dbus_msg); - } - if (dbus_message_is_method_call(dbus_msg, - "org.freedesktop.Notifications", - "GetServerInformation")) { - getServerInformation(dbus_msg); - } - if (dbus_message_is_method_call(dbus_msg, - "org.freedesktop.Notifications", - "CloseNotification")) { - closeNotification(dbus_msg); - } - dbus_message_unref(dbus_msg); } void getCapabilities(DBusMessage * dmsg) @@ -336,11 +335,17 @@ void notify(DBusMessage * dmsg) continue; } dbus_message_iter_get_basic(&hint, &hint_name); - _extract_hint(DBUS_TYPE_BYTE, "urgency", hint_name, &hint, &urgency); - _extract_hint(DBUS_TYPE_STRING, "fgcolor", hint_name, &hint, &fgcolor); - _extract_hint(DBUS_TYPE_STRING, "bgcolor", hint_name, &hint, &bgcolor); - _extract_hint(DBUS_TYPE_INT32, "value", hint_name, &hint, &progress); - if (!progress) _extract_hint(DBUS_TYPE_UINT32, "value", hint_name, &hint, &progress); + _extract_hint(DBUS_TYPE_BYTE, "urgency", hint_name, + &hint, &urgency); + _extract_hint(DBUS_TYPE_STRING, "fgcolor", hint_name, + &hint, &fgcolor); + _extract_hint(DBUS_TYPE_STRING, "bgcolor", hint_name, + &hint, &bgcolor); + _extract_hint(DBUS_TYPE_INT32, "value", hint_name, + &hint, &progress); + if (!progress) + _extract_hint(DBUS_TYPE_UINT32, "value", + hint_name, &hint, &progress); dbus_message_iter_next(&hint); } dbus_message_iter_next(&hints); @@ -358,7 +363,7 @@ void notify(DBusMessage * dmsg) n->body = body != NULL ? strdup(body) : ""; n->icon = icon != NULL ? strdup(icon) : ""; n->timeout = expires; - n->progress = (progress < 0 || progress > 100) ? 0 : progress+1; + n->progress = (progress < 0 || progress > 100) ? 0 : progress + 1; n->urgency = urgency; n->dbus_client = strdup(dbus_message_get_sender(dmsg)); for (i = 0; i < ColLast; i++) { @@ -366,8 +371,10 @@ void notify(DBusMessage * dmsg) } n->color_strings[ColFG] = fgcolor == NULL ? NULL : strdup(fgcolor); n->color_strings[ColBG] = bgcolor == NULL ? NULL : strdup(bgcolor); + id = init_notification(n, replaces_id); - map_win(); + if (id > 0) + map_win(); reply = dbus_message_new_method_return(dmsg); diff --git a/dunstrc b/dunstrc index 9955fe2..e1c5c72 100644 --- a/dunstrc +++ b/dunstrc @@ -43,6 +43,8 @@ # the window expands to the longest message displayed. # A positive x is measured from the left, a negative from the # right side of the screen. Y is measured from the top and down respectevly. + # The width can be negative. In this case the actual width is the + # screen width minus the width defined in within the geometry option. geometry = "0x3-30+20" # The transparency of the window. range: [0; 100] @@ -87,6 +89,17 @@ # that fits nicely to the background color. separator_color = auto + # print a notification on startup + # This is mainly for error detection, since dbus (re-)starts dunst + # automatically after a crash. + startup_notification = false + + # dmenu path + dmenu = /usr/bin/dmenu -p dunst: + + # browser for opening urls in context menu + browser = /usr/bin/firefox -new-tab + [shortcuts] # shortcuts are specified as [modifier+][modifier+]...key @@ -104,6 +117,9 @@ # On the US keyboard layout 'grave' is normally above TAB and left of '1'. history = ctrl+grave + # context menu + context = ctrl+shift+period + [urgency_low] # IMPORTANT: colors have to be defined in quotation marks. # Otherwise the '#' and following would be interpreted as a comment. diff --git a/list.c b/list.c deleted file mode 100644 index 395aef0..0000000 --- a/list.c +++ /dev/null @@ -1,266 +0,0 @@ -#include "stdlib.h" -#include "stdio.h" - -#include "list.h" - -int l_push(list * l, void *data) -{ - l_node *n; - int ret; - - /* invalid input */ - if (!l || !data) { - return -1; - } - - n = l_init_node(data); - if (!n) { - /* something went wrong */ - return -2; - } - - ret = l_node_push(l, n); - - return ret; -} - -int l_node_push(list * l, l_node * n) -{ - l_node *end; - - /* invalid input */ - if (!l || !n) { - return -1; - } - - n->next = NULL; - - /* empty list */ - if (!l->head) { - l->head = n; - - return 0; - } - - for (end = l->head; end->next; end = end->next) ; - - if (end != n) { - end->next = n; - } - - return 0; -} - -void *l_pop(list * l) -{ - l_node *penultimate; - void *data; - - /* invalid input */ - if (!l) { - return NULL; - } - - /* empty list */ - if (!l->head) { - return NULL; - } - - /* len(l) == 1 */ - if (!l->head->next) { - data = l->head->data; - free(l->head); - l->head = NULL; - - return data; - } - - for (penultimate = l->head; - penultimate->next->next; penultimate = penultimate->next) ; - data = penultimate->next->data; - free(penultimate->next); - penultimate->next = NULL; - - return data; -} - -int l_insert(l_node * node, void *data) -{ - int ret; - l_node *to_be_inserted; - - /* invalid input */ - if (!node || !data) { - return -1; - } - - to_be_inserted = l_init_node(data); - if (!to_be_inserted) { - return -2; - } - - ret = l_node_insert(node, to_be_inserted); - - return ret; -} - -int l_node_insert(l_node * node, l_node * to_be_inserted) -{ - l_node *tmp; - - /* invalid input */ - if (!node || !to_be_inserted) { - return -1; - } - - tmp = node->next; - node->next = to_be_inserted; - to_be_inserted->next = tmp; - - return 0; -} - -void *l_remove(list * l, l_node * node) -{ - void *data; - if (l != NULL) { - l_node_remove(l, node); - } - - if (node == NULL) { - return NULL; - } - - data = node->data; - free(node); - - return data; -} - -l_node *l_node_remove(list * l, l_node * node) -{ - l_node *prev; - l_node *next; - - /* invalid input */ - if (!l || !node) { - return NULL; - } - - /* empty list */ - if (!l->head) { - return NULL; - } - - /* node is head */ - if (l->head == node) { - l->head = node->next; - node->next = NULL; - return node; - } - - /* find the predecessor of node */ - for (prev = l->head; - prev->next && prev->next != node; prev = prev->next) ; - - /* node not in list */ - if (prev->next != node) { - return node; - } - - next = node->next; - prev->next = next; - - return node; -} - -l_node *l_init_node(void *data) -{ - l_node *node; - - node = malloc(sizeof(l_node)); - if (!node) { - return NULL; - } - - node->data = data; - node->next = NULL; - - return node; -} - -int l_length(list * l) -{ - l_node *iter; - int count; - - if (!l || !l->head) { - return 0; - } - - count = 0; - iter = l->head; - while (iter) { - count++; - iter = iter->next; - } - - return count; -} - -int l_is_empty(list * l) -{ - return l->head == NULL; -} - -int l_move(list * from, list * to, l_node * node) -{ - - if (!from || !to || !node) { - return -1; - } - node = l_node_remove(from, node); - return l_node_push(to, node); -} - -list *l_init(void) -{ - list *l = malloc(sizeof(list)); - l->head = NULL; - - return l; -} - -void l_sort(list * l, int (*f) (void *, void *)) -{ - list *old_list; - - if (!l || l_length(l) < 2) { - /* nothing to do */ - return; - } - - old_list = l_init(); - - old_list->head = l->head; - l->head = NULL; - - while (!l_is_empty(old_list)) { - l_node *iter; - l_node *max; - - /* find max in old_list */ - max = old_list->head; - for (iter = old_list->head; iter; iter = iter->next) { - if (f(max->data, iter->data) < 0) { - max = iter; - } - } - - l_move(old_list, l, max); - } - - free(old_list); -} - -/* vim: set ts=8 sw=8 tw=0: */ diff --git a/list.h b/list.h deleted file mode 100644 index 800ee9c..0000000 --- a/list.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef _LIST_H -#define _LIST_H - -typedef struct _l_node { - struct _l_node *next; - void *data; -} l_node; - -typedef struct _list { - l_node *head; -} list; - -/* append to end of list */ -int l_push(list * l, void *data); - -/* same as append but with a l_node */ -int l_node_push(list * l, l_node * n); - -/* remove and return last element of list */ -void *l_pop(list * l); - -/* insert after node. */ -int l_insert(l_node * node, void *data); - -/* - * same as insert, but using a node_t - */ -int l_node_insert(l_node * node, l_node * to_be_inserted); - -/* - * remove l_node from list and return a pointer to its data - */ -void *l_remove(list * l, l_node * node); - -/* - * same as l_remove but returns the node instead of the data - */ -l_node *l_node_remove(list * l, l_node * node); - -/* - * initialize a node - */ -l_node *l_init_node(void *data); - -/* return the length of the list */ -int l_length(list * l); - -/* is list empty */ -int l_is_empty(list * l); - -/* move node from 'from' to 'to' */ -int l_move(list * from, list * to, l_node * node); - -void l_sort(list * l, int (*f) (void *, void *)); - -list *l_init(void); -#endif - -/* vim: set ts=8 sw=8 tw=0: */ diff --git a/options.c b/options.c index 78b0c55..ab90b57 100644 --- a/options.c +++ b/options.c @@ -1,4 +1,6 @@ /* copyright 2012 Sascha Kruse and contributors (see LICENSE for licensing information) */ +#define _GNU_SOURCE + #include #include #include @@ -9,21 +11,17 @@ #include "utils.h" typedef struct _entry_t { - char *key; - char *value; + char *key; + char *value; } entry_t; typedef struct _section_t { - char *name; - int entry_count; - entry_t *entries; + char *name; + int entry_count; + entry_t *entries; } section_t; - - - - -static int section_count; +static int section_count = 0; static section_t *sections; static section_t *new_section(char *name); @@ -32,19 +30,27 @@ static void add_entry(char *section_name, char *key, char *value); static char *get_value(char *section, char *key); static char *clean_value(char *value); +static int cmdline_argc; +static char **cmdline_argv; + +static char *usage_str = NULL; +static void cmdline_usage_append(char *key, char *type, char *description); + +static int cmdline_find_option(char *key); + section_t *new_section(char *name) { section_count++; - sections = realloc(sections, sizeof(section_t) *section_count); + sections = realloc(sections, sizeof(section_t) * section_count); sections[section_count - 1].name = strdup(name); sections[section_count - 1].entries = NULL; sections[section_count - 1].entry_count = 0; - return §ions[section_count -1]; + return §ions[section_count - 1]; } void free_ini(void) { - for(int i = 0; i < section_count; i++) { + for (int i = 0; i < section_count; i++) { for (int j = 0; j < sections[i].entry_count; j++) { free(sections[i].entries[j].key); free(sections[i].entries[j].value); @@ -97,10 +103,13 @@ char *get_value(char *section, char *key) char *ini_get_string(char *section, char *key, const char *def) { char *value = get_value(section, key); - if (value == NULL) - return def; - else + if (value) return strdup(value); + + if (def == NULL) + return NULL; + else + return strdup(def); } int ini_get_int(char *section, char *key, int def) @@ -112,7 +121,7 @@ int ini_get_int(char *section, char *key, int def) return atoi(value); } -double ini_get_double(char *section, char *key, int def) +double ini_get_double(char *section, char *key, double def) { char *value = get_value(section, key); if (value == NULL) @@ -135,7 +144,7 @@ char *next_section(char *section) if (i + 1 >= section_count) return NULL; else - return sections[i+1].name; + return sections[i + 1].name; } } return NULL; @@ -182,7 +191,7 @@ char *clean_value(char *value) } -int load_ini_file(FILE *fp) +int load_ini_file(FILE * fp) { char line[BUFSIZ]; @@ -199,7 +208,9 @@ int load_ini_file(FILE *fp) if (*start == '[') { char *end = strstr(start + 1, "]"); if (!end) { - printf("Warning: invalid config file at line %d\n", line_num); + printf + ("Warning: invalid config file at line %d\n", + line_num); printf("Missing ']'\n"); continue; } @@ -208,27 +219,30 @@ int load_ini_file(FILE *fp) if (current_section) free(current_section); - current_section = (strdup(start+1)); + current_section = (strdup(start + 1)); new_section(current_section); continue; } char *equal = strstr(start + 1, "="); if (!equal) { - printf("Warning: invalid config file at line %d\n", line_num); + printf("Warning: invalid config file at line %d\n", + line_num); printf("Missing '='\n"); continue; } *equal = '\0'; char *key = rstrip(start); - char *value = lskip(equal+1); + char *value = lskip(equal + 1); char *quote = strstr(value, "\""); if (quote) { char *closing_quote = strstr(quote + 1, "\""); if (!closing_quote) { - printf("Warning: invalid config file at line %d\n", line_num); + printf + ("Warning: invalid config file at line %d\n", + line_num); printf("Missing '\"'\n"); continue; } @@ -244,7 +258,8 @@ int load_ini_file(FILE *fp) value = rstrip(value); if (!current_section) { - printf("Warning: invalid config file at line: %d\n", line_num); + printf("Warning: invalid config file at line: %d\n", + line_num); printf("Key value pair without a section\n"); continue; } @@ -255,4 +270,196 @@ int load_ini_file(FILE *fp) free(current_section); return 0; } + +void cmdline_load(int argc, char *argv[]) +{ + cmdline_argc = argc; + cmdline_argv = argv; +} + +int cmdline_find_option(char *key) +{ + if (!key) { + return -1; + } + char *key1 = strdup(key); + char *key2 = strstr(key1, "/"); + + if (key2) { + *key2 = '\0'; + key2++; + } + + /* look for first key */ + for (int i = 0; i < cmdline_argc; i++) { + if (strcmp(key1, cmdline_argv[i]) == 0) { + free(key1); + return i; + } + } + + /* look for second key if one was specified */ + if (key2) { + for (int i = 0; i < cmdline_argc; i++) { + if (strcmp(key2, cmdline_argv[i]) == 0) { + free(key1); + return i; + } + } + } + + free(key1); + return -1; +} + +static char *cmdline_get_value(char *key) +{ + int idx = cmdline_find_option(key); + if (idx < 0) { + return NULL; + } + + if (idx + 1 >= cmdline_argc || cmdline_argv[idx + 1][0] == '-') { + /* the argument is missing */ + fprintf(stderr, "Warning: %s, missing argument. Ignoring\n", + key); + return NULL; + } + return cmdline_argv[idx + 1]; +} + +char *cmdline_get_string(char *key, const char *def, char *description) +{ + cmdline_usage_append(key, "string", description); + char *str = cmdline_get_value(key); + + if (str) + return strdup(str); + if (def == NULL) + return NULL; + else + return strdup(def); +} + +int cmdline_get_int(char *key, int def, char *description) +{ + cmdline_usage_append(key, "double", description); + char *str = cmdline_get_value(key); + + if (str == NULL) + return def; + else + return atoi(str); +} + +double cmdline_get_double(char *key, double def, char *description) +{ + cmdline_usage_append(key, "double", description); + char *str = cmdline_get_value(key); + if (str == NULL) + return def; + else + return atof(str); +} + +int cmdline_get_bool(char *key, int def, char *description) +{ + cmdline_usage_append(key, "", description); + int idx = cmdline_find_option(key); + if (idx > 0) + return true; + else + return def; +} + +char *option_get_string(char *ini_section, char *ini_key, char *cmdline_key, + const char *def, char *description) +{ + char *val = NULL; + + if (cmdline_key) { + val = cmdline_get_string(cmdline_key, NULL, description); + } + + if (val) { + return val; + } else { + return ini_get_string(ini_section, ini_key, def); + } + +} + +int option_get_int(char *ini_section, char *ini_key, char *cmdline_key, int def, + char *description) +{ + /* *str is only used to check wether the cmdline option is actually set. */ + char *str = cmdline_get_value(cmdline_key); + + /* we call cmdline_get_int even when the option isn't set in order to + * add the usage info */ + int val = cmdline_get_int(cmdline_key, def, description); + + if (!str) + return ini_get_int(ini_section, ini_key, def); + else + return val; +} + +double option_get_double(char *ini_section, char *ini_key, char *cmdline_key, + double def, char *description) +{ + char *str = cmdline_get_value(cmdline_key); + double val = cmdline_get_double(cmdline_key, def, description); + + if (!str) + return ini_get_int(ini_section, ini_key, def); + else + return val; +} + +int option_get_bool(char *ini_section, char *ini_key, char *cmdline_key, + int def, char *description) +{ + int val = false; + + if (cmdline_key) + val = cmdline_get_bool(cmdline_key, false, description); + + if (cmdline_key && val) { + /* this can only be true if the value has been set, + * so we can return */ + return true; + } + + return ini_get_bool(ini_section, ini_key, def); +} + +void cmdline_usage_append(char *key, char *type, char *description) +{ + char *key_type; + if (type && strlen(type) > 0) + asprintf(&key_type, "%s (%s)", key, type); + else + asprintf(&key_type, "%s", key); + + if (!usage_str) { + asprintf(&usage_str, "%-40s - %s\n", key_type, description); + free(key_type); + return; + } + + char *tmp; + asprintf(&tmp, "%s%-40s - %s\n", usage_str, key_type, description); + free(key_type); + + free(usage_str); + usage_str = tmp; + +} + +char *cmdline_create_usage(void) +{ + return strdup(usage_str); +} + /* vim: set ts=8 sw=8 tw=0: */ diff --git a/options.h b/options.h index 2d2d968..13cf490 100644 --- a/options.h +++ b/options.h @@ -3,14 +3,30 @@ #include - int load_ini_file(FILE *); char *ini_get_string(char *section, char *key, const char *def); int ini_get_int(char *section, char *key, int def); -double ini_get_double(char *section, char *key, int def); +double ini_get_double(char *section, char *key, double def); int ini_get_bool(char *section, char *key, int def); void free_ini(void); +void cmdline_load(int argc, char *argv[]); +/* for all cmdline_get_* key can be either "-key" or "-key/-longkey" */ +char *cmdline_get_string(char *key, const char *def, char *description); +int cmdline_get_int(char *key, int def, char *description); +double cmdline_get_double(char *key, double def, char *description); +int cmdline_get_bool(char *key, int def, char *description); +char *cmdline_create_usage(void); + +char *option_get_string(char *ini_section, char *ini_key, char *cmdline_key, + const char *def, char *description); +int option_get_int(char *ini_section, char *ini_key, char *cmdline_key, int def, + char *description); +double option_get_double(char *ini_section, char *ini_key, char *cmdline_key, + double def, char *description); +int option_get_bool(char *ini_section, char *ini_key, char *cmdline_key, + int def, char *description); + /* returns the next known section. * if section == NULL returns first section. * returns NULL if no more sections are available diff --git a/utils.c b/utils.c index e587429..a915336 100644 --- a/utils.c +++ b/utils.c @@ -1,3 +1,5 @@ +#define _GNU_SOURCE + #include #include #include @@ -6,7 +8,6 @@ #include "utils.h" #include "dunst.h" - char *rstrip(char *str) { char *end; @@ -20,7 +21,7 @@ char *rstrip(char *str) char *lskip(char *s) { - for(; *s && isspace(*s); s++); + for (; *s && isspace(*s); s++) ; return s; } @@ -44,17 +45,47 @@ char *string_replace(const char *needle, const char *replacement, sprintf(tmp + strlen(tmp), "%s%s", replacement, start + strlen(needle)); free(haystack); - if (strstr(tmp, needle)) { - return string_replace(needle, replacement, tmp); - } else { - return tmp; + return tmp; +} + +char *string_append(char *a, const char *b, const char *sep) +{ + if (!a) + return strdup(b); + + char *new; + if (!sep) + asprintf(&new, "%s%s", a, b); + else + asprintf(&new, "%s%s%s", a, sep, b); + free(a); + + return new; + +} + +char **string_to_argv(const char *s) +{ + char *str = strdup(s); + char **argv = NULL; + char *p = strtok (str, " "); + int n_spaces = 0; + + while (p) { + argv = realloc (argv, sizeof (char*) * ++n_spaces); + argv[n_spaces-1] = p; + p = strtok (NULL, " "); } + argv = realloc (argv, sizeof (char*) * (n_spaces+1)); + argv[n_spaces] = NULL; + + return argv; } int digit_count(int i) { int len = 0; - if ( i == 0) { + if (i == 0) { return 1; } diff --git a/utils.h b/utils.h index 8995b01..c93479d 100644 --- a/utils.h +++ b/utils.h @@ -8,8 +8,12 @@ char *lskip(char *str); char *string_replace(const char *needle, const char *replacement, char *haystack); +char *string_append(char *a, const char *b, const char *sep); + +char **string_to_argv(const char *s); + /* exit with an error message */ -void die(char * msg, int exit_value); +void die(char *msg, int exit_value); int digit_count(int i);