dunst/dunst.c
2012-08-18 21:45:33 +02:00

1650 lines
53 KiB
C

/* copyright 2012 Sascha Kruse and contributors (see LICENSE for licensing information) */
#define _GNU_SOURCE
#include <assert.h>
#include <unistd.h>
#include <time.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <fnmatch.h>
#include <getopt.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#ifdef XINERAMA
#include <X11/extensions/Xinerama.h>
#endif
#include <X11/extensions/scrnsaver.h>
#include <basedir.h>
#include <basedir_fs.h>
#include "dunst.h"
#include "draw.h"
#include "dunst_dbus.h"
#include "list.h"
#include "ini.h"
#include "utils.h"
#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])
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask)
#define FONT_HEIGHT_BORDER 2
#define DEFFONT "Monospace-11"
#define MSG 1
#define INFO 2
#define DEBUG 3
enum follow_mode { FOLLOW_NONE, FOLLOW_MOUSE, FOLLOW_KEYBOARD };
/* structs */
typedef struct _screen_info {
int scr;
dimension_t dim;
} screen_info;
typedef struct _notification_buffer {
char txt[BUFSIZ];
notification *n;
} notification_buffer;
/* global variables */
#include "config.h"
int height_limit;
list *rules = NULL;
/* index of colors fit to urgency level */
static ColorSet *colors[3];
static const char *color_strings[2][3];
static Atom utf8;
static DC *dc;
static Window win;
static time_t now;
static int visible = False;
static screen_info scr;
static dimension_t geometry;
static XScreenSaverInfo *screensaver_info;
static int font_h;
int dunst_grab_key_errored = False;
int next_notification_id = 1;
int depricated_mod = False;
int depricated_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 */
/* 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 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);
/* show warning notification */
void warn(const char *text, int urg);
void init_shortcut(keyboard_shortcut * shortcut);
KeySym string_to_mask(char *str);
static int GrabKeyXErrorHandler(Display * display, XErrorEvent * e)
{
dunst_grab_key_errored = True;
char err_buf[BUFSIZ];
XGetErrorText(display, e->error_code, err_buf, BUFSIZ);
fputs(err_buf, stderr);
fputs("\n", stderr);
if (e->error_code != BadAccess) {
exit(EXIT_FAILURE);
}
return 0;
}
int grab_key(keyboard_shortcut * ks)
{
Window root;
root = RootWindow(dc->dpy, DefaultScreen(dc->dpy));
dunst_grab_key_errored = False;
XFlush(dc->dpy);
XErrorHandler old_handler = XSetErrorHandler(GrabKeyXErrorHandler);
if (ks->is_valid)
XGrabKey(dc->dpy, ks->code, ks->mask, root,
True, GrabModeAsync, GrabModeAsync);
XFlush(dc->dpy);
XSync(dc->dpy, False);
XSetErrorHandler(old_handler);
if (dunst_grab_key_errored) {
fprintf(stderr, "Unable to grab key \"%s\"\n", ks->str);
return 1;
}
return 0;
}
void ungrab_key(keyboard_shortcut * ks)
{
Window root;
root = RootWindow(dc->dpy, DefaultScreen(dc->dpy));
if (ks->is_valid)
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 = "dunst";
n->summary = strdup(text);
if (n->summary == NULL)
die("Unable to allocate memory", EXIT_FAILURE);
n->body = "";
n->icon = "";
n->timeout = 0;
n->urgency = urg;
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 print_rule(rule_t * r)
{
if (r == NULL)
return;
dunst_printf(DEBUG, "%s %s %s %s %s %d %d %s %s %s\n",
r->name,
r->appname,
r->summary,
r->body,
r->icon, r->timeout, r->urgency, r->fg, r->bg, r->format);
}
void print_rules(void)
{
dunst_printf(DEBUG, "current rules:\n");
if (l_is_empty(rules)) {
dunst_printf(DEBUG, "no rules present\n");
return;
}
for (l_node * iter = rules->head; iter; iter = iter->next) {
print_rule((rule_t *) iter->data);
}
}
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;
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))
&& (!r->icon || !fnmatch(r->icon, n->icon, 0))) {
dunst_printf(DEBUG, "matched rule: %s\n", r->name);
n->timeout = r->timeout != -1 ? r->timeout : n->timeout;
n->urgency = r->urgency != -1 ? r->urgency : n->urgency;
n->color_strings[ColFG] =
r->fg ? r->fg : n->color_strings[ColFG];
n->color_strings[ColBG] =
r->bg ? r->bg : n->color_strings[ColBG];
n->format = r->format ? r->format : n->format;
}
}
}
void check_timeouts(void)
{
l_node *iter;
notification *current;
l_node *next;
/* nothing to do */
if (l_is_empty(displayed_notifications)) {
return;
}
iter = displayed_notifications->head;
while (iter != NULL) {
current = (notification *) iter->data;
/* don't timeout when user is idle */
if (is_idle()) {
current->start = now;
iter = iter->next;
continue;
}
/* skip hidden and sticky messages */
if (current->start == 0 || current->timeout == 0) {
iter = iter->next;
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;
}
}
}
void update_lists()
{
l_node *to_move;
notification *n;
int limit;
check_timeouts();
if (geometry.h == 0) {
limit = 0;
} else if (geometry.h == 1) {
limit = 1;
} else if (indicate_hidden) {
limit = geometry.h - 1;
} else {
limit = geometry.h;
}
/* move notifications from queue to displayed */
while (!l_is_empty(notification_queue)) {
if (limit > 0 && l_length(displayed_notifications) >= limit) {
/* the list is full */
break;
}
to_move = most_important(notification_queue);
n = (notification *) to_move->data;
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);
}
}
/* TODO get draw_txt_buf as argument */
int do_word_wrap(char *source, int max_width)
{
char *eol = source;
while (True) {
if ( *eol == '\0')
return 1;
if (textnw(dc, source, (eol - source) + 1) >= max_width) {
/* go back to previous space */
char *space = eol;
while (space > source && !isspace(*space))
space--;
if (space <= source) {
/* whe have a word longer than width, so we
* split mid-word. That one letter is
* collateral damage */
space = eol;
}
*space = '\0';
if (*(space + 1) == '\0')
return 1;
return 1 + do_word_wrap(space+1, max_width);
}
eol++;
}
}
void update_draw_txt_buf(notification * n, int max_width)
{
if (!n->draw_txt_buf.txt) {
int dup_len = strlen(" () ") + digit_count(INT_MAX);
int msg_len = strlen(n->msg) + 2; /* 2 == surrounding spaces */
int age_len = strlen(" ( h 60m 60s old ) ")
+ digit_count(INT_MAX); /* could be INT_MAX hours */
int line_length = dup_len + msg_len + age_len;
line_length += sizeof(" ( more ) ") + digit_count(INT_MAX);
n->draw_txt_buf.txt = calloc(line_length, sizeof(char));
n->draw_txt_buf.bufsize = line_length;
}
char *buf = n->draw_txt_buf.txt;
int bufsize = n->draw_txt_buf.bufsize;
char *next = buf;
memset(buf, '\0', bufsize);
/* print dup_count */
if (n->dup_count > 0) {
snprintf(next, bufsize - strlen(buf), "(%d) ", n->dup_count);
next = buf + strlen(buf);
}
/* print msg */
strncat(buf, n->msg, bufsize - strlen(buf));
next = buf + strlen(buf);
/* print age */
int hours, minutes, seconds;
time_t t_delta = now - n->timestamp;
if (show_age_threshold >= 0 && t_delta >= show_age_threshold) {
hours = t_delta / 3600;
minutes = t_delta / 60 % 60;
seconds = t_delta % 60;
if (hours > 0) {
snprintf(next, bufsize - strlen(buf),
" (%dh %dm %ds old)", hours, minutes, seconds);
} else if (minutes > 0) {
snprintf(next, bufsize - strlen(buf), " (%dm %ds old)",
minutes, seconds);
} else {
snprintf(next, bufsize - strlen(buf), " (%ds old)",
seconds);
}
}
n->draw_txt_buf.line_count =
do_word_wrap(n->draw_txt_buf.txt, max_width);
}
char *draw_txt_get_line(draw_txt * dt, int line)
{
if (line > dt->line_count) {
return NULL;
}
char *begin = dt->txt;
for (int i = 1; i < line; i++) {
begin += strlen(begin) + 1;
}
return begin;
}
int calculate_x_offset(int line_width, int text_width)
{
switch (align) {
case left:
return 0;
case center:
return (line_width - text_width) / 2;
case right:
return line_width - text_width;
default:
/* this can't happen */
return 0;
}
}
void draw_win(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;
} else if (geometry.mask & WidthValue) {
/* fixed width */
width = geometry.w;
} else {
/* across the screen */
width = 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;
}
/* 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);
assert(line != NULL);
width = MAX(width, textw(dc, line));
}
}
}
/* calculate height */
if (geometry.h == 0) {
height = line_cnt * line_height;
} else {
height = MIN(geometry.h, line_cnt) * line_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;
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);
}
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 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);
}
/* calculate window position */
if (geometry.mask & XNegative) {
x = (scr.dim.x + (scr.dim.w - width)) + geometry.x;
} else {
x = scr.dim.x + geometry.x;
}
if (geometry.mask & YNegative) {
y = scr.dim.y + (scr.dim.h + geometry.y) - height;
} else {
y = scr.dim.y + geometry.y;
}
/* move and map window */
XResizeWindow(dc->dpy, win, width, height);
XMoveWindow(dc->dpy, win, x, y);
mapdc(dc, win, width, height);
free(x_more.txt);
}
char
*fix_markup(char *str)
{
char *replace_buf, *start, *end;
if (str == NULL) {
return NULL;
}
str = string_replace("&quot;", "\"", str);
str = string_replace("&apos;", "'", str);
str = string_replace("&amp;", "&", str);
str = string_replace("&lt;", "<", str);
str = string_replace("&gt;", ">", str);
str = string_replace("\n", " ", str);
/* remove tags */
str = string_replace("<b>", "", str);
str = string_replace("</b>", "", str);
str = string_replace("<br>", " ", str);
str = string_replace("<br/>", " ", str);
str = string_replace("<br />", " ", str);
str = string_replace("<i>", "", str);
str = string_replace("</i>", "", str);
str = string_replace("<u>", "", str);
str = string_replace("</u>", "", str);
str = string_replace("</a>", "", str);
start = strstr(str, "<a href");
if (start != NULL) {
end = strstr(str, ">");
if (end != NULL) {
replace_buf = strndup(start, end - start + 1);
str = string_replace(replace_buf, "", str);
free(replace_buf);
}
}
start = strstr(str, "<img src");
if (start != NULL) {
end = strstr(str, "/>");
if (end != NULL) {
replace_buf = strndup(start, end - start + 2);
str = string_replace(replace_buf, "", str);
free(replace_buf);
}
}
return str;
}
void handle_mouse_click(XEvent ev)
{
l_node *iter = displayed_notifications->head;
notification *n;
if (ev.xbutton.button == Button3) {
move_all_to_history();
} else if (ev.xbutton.button == Button1) {
int i = ev.xbutton.y / font_h;
for (; i > 0; i--) {
/* if the user clicks on the "(x more)" message,
* keep iter at the last displayed message and
* remove that instead
*/
if (iter->next) {
iter = iter->next;
}
}
n = (notification *) iter->data;
close_notification(n, 2);
}
}
void handleXEvents(void)
{
XEvent ev;
while (XPending(dc->dpy) > 0) {
XNextEvent(dc->dpy, &ev);
switch (ev.type) {
case Expose:
if (ev.xexpose.count == 0)
draw_win();
mapdc(dc, win, scr.dim.w, font_h);
break;
case SelectionNotify:
if (ev.xselection.property == utf8)
break;
case VisibilityNotify:
if (ev.xvisibility.state != VisibilityUnobscured)
XRaiseWindow(dc->dpy, win);
break;
case ButtonPress:
if (ev.xbutton.window == win) {
handle_mouse_click(ev);
}
break;
case KeyPress:
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 (history_ks.str
&& XLookupKeysym(&ev.xkey, 0) == history_ks.sym
&& history_ks.mask == ev.xkey.state) {
history_pop();
}
if (close_all_ks.str
&& XLookupKeysym(&ev.xkey, 0) == close_all_ks.sym
&& close_all_ks.mask == ev.xkey.state) {
move_all_to_history();
}
}
}
}
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 (!l_is_empty(notification_queue)) {
node = notification_queue->head;
n = (notification *) node->data;
close_notification(n, 2);
}
}
void history_pop(void)
{
l_node *iter;
notification *data;
/* nothing to do */
if (l_is_empty(notification_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);
if (!visible) {
map_win();
}
}
void free_notification(notification * n)
{
if (n == NULL)
return;
free(n->appname);
free(n->summary);
free(n->body);
free(n->icon);
free(n->msg);
free(n->dbus_client);
free(n);
}
int init_notification(notification * n, int id)
{
const char *fg = NULL;
const char *bg = NULL;
if (n == NULL)
return -1;
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);
n->msg = string_replace("%b", n->body, n->msg);
n->msg = fix_markup(n->msg);
n->msg = strtrim(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->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->msg, n->msg) == 0) {
orig->dup_count++;
orig->start = now;
free_notification(n);
return orig->id;
}
}
/* urgency > CRIT -> array out of range */
n->urgency = n->urgency > CRIT ? CRIT : n->urgency;
if (n->color_strings[ColFG]) {
fg = n->color_strings[ColFG];
} else {
fg = color_strings[ColFG][n->urgency];
}
if (n->color_strings[ColBG]) {
bg = n->color_strings[ColBG];
} else {
bg = color_strings[ColBG][n->urgency];
}
n->colors = initcolor(dc, fg, bg);
n->timeout = n->timeout == -1 ? timeouts[n->urgency] : n->timeout;
n->start = 0;
n->timestamp = now;
n->redisplayed = False;
dunst_printf(MSG, "%s\n", n->msg);
dunst_printf(INFO,
"{\n appname: %s\n summary: %s\n body: %s\n icon: %s\n urgency: %d\n timeout: %d\n}",
n->appname, n->summary, n->body, n->icon,
n->urgency, n->timeout);
if (id == 0) {
n->id = ++next_notification_id;
} else {
close_notification_by_id(id, -1);
}
l_push(notification_queue, n);
return n->id;
}
/*
* reasons:
* -1 -> notification is a replacement, no NotificationClosed signal emitted
* 1 -> the notification expired
* 2 -> the notification was dismissed by the user_data
* 3 -> The notification was closed by a call to CloseNotification
*/
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;
if (n->id == id) {
l_move(displayed_notifications, notification_history,
iter);
target = n;
break;
}
}
for (iter = notification_queue->head; iter; iter = iter->next) {
notification *n = (notification *) iter->data;
if (n->id == id) {
l_move(notification_queue, notification_history, iter);
target = n;
break;
}
}
if (reason > 0 && reason < 4 && target != NULL) {
notificationClosed(target, reason);
}
return target == NULL;
}
int close_notification(notification * n, int reason)
{
if (n == NULL)
return -1;
return close_notification_by_id(n->id, reason);
}
KeySym string_to_mask(char *str)
{
if (!strcmp(str, "ctrl")) {
return ControlMask;
} else if (!strcmp(str, "mod4")) {
return Mod4Mask;
} else if (!strcmp(str, "mod3")) {
return Mod3Mask;
} else if (!strcmp(str, "mod2")) {
return Mod2Mask;
} else if (!strcmp(str, "mod1")) {
return Mod1Mask;
} else if (!strcmp(str, "shift")) {
return ShiftMask;
} else {
fprintf(stderr, "Warning: Unknown Modifier: %s\n", str);
return 0;
}
}
void init_shortcut(keyboard_shortcut * ks)
{
if (ks == NULL || ks->str == NULL)
return;
if (!strcmp(ks->str, "none") || (!strcmp(ks->str, ""))) {
ks->is_valid = False;
return;
}
char *str = strdup(ks->str);
char *str_begin = str;
if (str == NULL)
die("Unable to allocate memory", EXIT_FAILURE);
while (strstr(str, "+")) {
char *mod = str;
while (*str != '+')
str++;
*str = '\0';
str++;
strtrim(mod);
ks->mask = ks->mask | string_to_mask(mod);
}
strtrim(str);
ks->sym = XStringToKeysym(str);
ks->code = XKeysymToKeycode(dc->dpy, ks->sym);
if (ks->sym == NoSymbol || ks->code == NoSymbol) {
fprintf(stderr, "Warning: Unknown keyboard shortcut: %s\n",
ks->str);
ks->is_valid = False;
} else {
ks->is_valid = True;
}
free(str_begin);
}
rule_t *initrule(void)
{
rule_t *r = malloc(sizeof(rule_t));
r->name = NULL;
r->appname = NULL;
r->summary = NULL;
r->body = NULL;
r->icon = NULL;
r->timeout = -1;
r->urgency = -1;
r->fg = NULL;
r->bg = NULL;
r->format = NULL;
return r;
}
int is_idle(void)
{
XScreenSaverQueryInfo(dc->dpy, DefaultRootWindow(dc->dpy),
screensaver_info);
if (idle_threshold == 0) {
return False;
}
return screensaver_info->idle / 1000 > idle_threshold;
}
void run(void)
{
while (True) {
if (visible) {
dbus_poll(50);
} else {
dbus_poll(200);
}
now = time(&now);
/* move messages from notification_queue to displayed_notifications */
update_lists();
if (l_length(displayed_notifications) > 0) {
if (!visible) {
map_win();
} else {
draw_win();
}
} else {
if (visible) {
hide_win();
}
}
handleXEvents();
}
}
void hide_win()
{
ungrab_key(&close_ks);
ungrab_key(&close_all_ks);
XUngrabButton(dc->dpy, AnyButton, AnyModifier, win);
XUnmapWindow(dc->dpy, win);
XFlush(dc->dpy);
visible = False;
}
Window get_focused_window(void)
{
Window focused = 0;
Atom type;
int format;
unsigned long nitems, bytes_after;
unsigned char *prop_return = NULL;
Window root = RootWindow(dc->dpy, DefaultScreen(dc->dpy));
Atom netactivewindow =
XInternAtom(dc->dpy, "_NET_ACTIVE_WINDOW", False);
XGetWindowProperty(dc->dpy, root, netactivewindow, 0L,
sizeof(Window), False, XA_WINDOW,
&type, &format, &nitems, &bytes_after, &prop_return);
if (prop_return) {
focused = *(Window *) prop_return;
XFree(prop_return);
}
return focused;
}
int select_screen(XineramaScreenInfo * info, int info_len)
{
if (f_mode == FOLLOW_NONE) {
return scr.scr;
} else {
int x, y;
Window root = RootWindow(dc->dpy, DefaultScreen(dc->dpy));
if (f_mode == FOLLOW_MOUSE) {
int dummy;
unsigned int dummy_ui;
Window dummy_win;
XQueryPointer(dc->dpy, root, &dummy_win,
&dummy_win, &x, &y, &dummy,
&dummy, &dummy_ui);
}
if (f_mode == FOLLOW_KEYBOARD) {
Window focused = get_focused_window();
if (focused == 0) {
/* something went wrong. Fallback to default */
return scr.scr;
}
Window child_return;
XTranslateCoordinates(dc->dpy, focused, root,
0, 0, &x, &y, &child_return);
}
for (int i = 0; i < info_len; i++) {
if (INRECT(x, y, info[i].x_org,
info[i].y_org,
info[i].width, info[i].height)) {
return i;
}
}
/* something seems to be wrong. Fallback to default */
return scr.scr;
}
}
void update_screen_info()
{
#ifdef XINERAMA
int n;
int screen = scr.scr;
XineramaScreenInfo *info;
if ((info = XineramaQueryScreens(dc->dpy, &n))) {
screen = select_screen(info, n);
if (screen >= n) {
/* invalid monitor, fallback to default */
screen = 0;
}
scr.dim.x = info[screen].x_org;
scr.dim.y = info[screen].y_org;
scr.dim.h = info[screen].height;
scr.dim.w = info[screen].width;
XFree(info);
} else
#endif
{
scr.dim.x = 0;
scr.dim.y = 0;
scr.dim.w = DisplayWidth(dc->dpy, scr.scr);
scr.dim.h = DisplayHeight(dc->dpy, scr.scr);
}
}
void setup(void)
{
Window root;
XSetWindowAttributes wa;
notification_queue = l_init();
notification_history = l_init();
displayed_notifications = l_init();
if (scr.scr < 0) {
scr.scr = DefaultScreen(dc->dpy);
}
root = RootWindow(dc->dpy, DefaultScreen(dc->dpy));
utf8 = XInternAtom(dc->dpy, "UTF8_STRING", False);
/* menu geometry */
font_h = dc->font.height + FONT_HEIGHT_BORDER;
update_screen_info();
/* menu window */
wa.override_redirect = True;
wa.background_pixmap = ParentRelative;
wa.event_mask =
ExposureMask | KeyPressMask | VisibilityChangeMask |
ButtonPressMask;
win =
XCreateWindow(dc->dpy, root, scr.dim.x, scr.dim.y, scr.dim.w,
font_h, 0, DefaultDepth(dc->dpy,
DefaultScreen(dc->dpy)),
CopyFromParent, DefaultVisual(dc->dpy,
DefaultScreen(dc->dpy)),
CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
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)) {
return;
}
grab_key(&close_ks);
grab_key(&close_all_ks);
update_screen_info();
XMapRaised(dc->dpy, win);
XGrabButton(dc->dpy, AnyButton, AnyModifier, win, False,
BUTTONMASK, GrabModeAsync, GrabModeSync, None, None);
XFlush(dc->dpy);
draw_win();
visible = True;
}
void parse_follow_mode(const char *mode)
{
if (strcmp(mode, "mouse") == 0)
f_mode = FOLLOW_MOUSE;
else if (strcmp(mode, "keyboard") == 0)
f_mode = FOLLOW_KEYBOARD;
else if (strcmp(mode, "none") == 0)
f_mode = FOLLOW_NONE;
else {
fprintf(stderr, "Warning: unknown follow mode: \"%s\"\n", mode);
f_mode = FOLLOW_NONE;
}
}
void parse_cmdline(int argc, char *argv[])
{
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'},
{"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':
depricated_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;
default:
usage(EXIT_FAILURE);
break;
}
}
}
#ifndef STATIC_CONFIG
static int dunst_ini_get_boolean(const char *value)
{
switch (value[0]) {
case 'y':
case 'Y':
case 't':
case 'T':
case '1':
return True;
case 'n':
case 'N':
case 'f':
case 'F':
case '0':
return False;
default:
return False;
}
}
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 not found in rules, create new one */
dunst_printf(DEBUG, "adding rule %s\n", section);
rule = initrule();
rule->name = strdup(section);
l_push(rules, rule);
return rule;
}
static char *dunst_ini_get_string(const char *value)
{
char *s;
if (value[0] == '"')
s = strdup(value + 1);
else
s = strdup(value);
if (s[strlen(s) - 1] == '"')
s[strlen(s) - 1] = '\0';
return s;
}
static int
dunst_ini_handle(void *user_data, const char *section,
const char *name, const char *value)
{
if (strcmp(section, "global") == 0) {
if (strcmp(name, "font") == 0)
font = dunst_ini_get_string(value);
else if (strcmp(name, "format") == 0)
format = dunst_ini_get_string(value);
else if (strcmp(name, "sort") == 0)
sort = dunst_ini_get_boolean(value);
else if (strcmp(name, "indicate_hidden") == 0)
indicate_hidden = dunst_ini_get_boolean(value);
else if (strcmp(name, "word_wrap") == 0)
word_wrap = dunst_ini_get_boolean(value);
else if (strcmp(name, "idle_threshold") == 0)
idle_threshold = atoi(value);
else if (strcmp(name, "monitor") == 0)
scr.scr = atoi(value);
else if (strcmp(name, "follow") == 0) {
char *c = dunst_ini_get_string(value);
parse_follow_mode(c);
free(c);
} else if (strcmp(name, "geometry") == 0)
geom = dunst_ini_get_string(value);
else if (strcmp(name, "line_height") == 0)
line_height = atoi(value);
else if (strcmp(name, "modifier") == 0) {
depricated_dunstrc_shortcuts = True;
char *c = dunst_ini_get_string(value);
KeySym mod = string_to_mask(c);
free(c);
close_ks.mask = mod;
close_all_ks.mask = mod;
history_ks.mask = mod;
} else if (strcmp(name, "key") == 0) {
close_ks.str = dunst_ini_get_string(value);
} else if (strcmp(name, "all_key") == 0) {
close_all_ks.str = dunst_ini_get_string(value);
} else if (strcmp(name, "history_key") == 0) {
history_ks.str = dunst_ini_get_string(value);
} else if (strcmp(name, "alignment") == 0) {
if (strcmp(value, "left") == 0)
align = left;
else if (strcmp(value, "center") == 0)
align = center;
else if (strcmp(value, "right") == 0)
align = right;
/* FIXME warning on unknown alignment */
} else if (strcmp(name, "show_age_threshold") == 0)
show_age_threshold = atoi(value);
else if (strcmp(name, "sticky_history") == 0)
sticky_history = dunst_ini_get_boolean(value);
} else if (strcmp(section, "urgency_low") == 0) {
if (strcmp(name, "background") == 0)
lowbgcolor = dunst_ini_get_string(value);
else if (strcmp(name, "foreground") == 0)
lowfgcolor = dunst_ini_get_string(value);
else if (strcmp(name, "timeout") == 0)
timeouts[LOW] = atoi(value);
} else if (strcmp(section, "urgency_normal") == 0) {
if (strcmp(name, "background") == 0)
normbgcolor = dunst_ini_get_string(value);
else if (strcmp(name, "foreground") == 0)
normfgcolor = dunst_ini_get_string(value);
else if (strcmp(name, "timeout") == 0)
timeouts[NORM] = atoi(value);
} else if (strcmp(section, "urgency_critical") == 0) {
if (strcmp(name, "background") == 0)
critbgcolor = dunst_ini_get_string(value);
else if (strcmp(name, "foreground") == 0)
critfgcolor = dunst_ini_get_string(value);
else if (strcmp(name, "timeout") == 0)
timeouts[CRIT] = atoi(value);
} else if (strcmp(section, "shortcuts") == 0) {
if (strcmp(name, "close") == 0)
close_ks.str = dunst_ini_get_string(value);
else if (strcmp(name, "close_all") == 0)
close_all_ks.str = dunst_ini_get_string(value);
else if (strcmp(name, "history") == 0)
history_ks.str = dunst_ini_get_string(value);
} else {
rule_t *current_rule = dunst_rules_find_or_create(section);
if (strcmp(name, "appname") == 0)
current_rule->appname = dunst_ini_get_string(value);
else if (strcmp(name, "summary") == 0)
current_rule->summary = dunst_ini_get_string(value);
else if (strcmp(name, "body") == 0)
current_rule->body = dunst_ini_get_string(value);
else if (strcmp(name, "icon") == 0)
current_rule->icon = dunst_ini_get_string(value);
else if (strcmp(name, "timeout") == 0)
current_rule->timeout = atoi(value);
else if (strcmp(name, "urgency") == 0) {
const char *urg = value;
if (strcmp(urg, "low") == 0)
current_rule->urgency = LOW;
else if (strcmp(urg, "normal") == 0)
current_rule->urgency = NORM;
else if (strcmp(urg, "critical") == 0)
current_rule->urgency = CRIT;
else
fprintf(stderr,
"unknown urgency: %s, ignoring\n", urg);
} else if (strcmp(name, "foreground") == 0)
current_rule->fg = dunst_ini_get_string(value);
else if (strcmp(name, "background") == 0)
current_rule->bg = dunst_ini_get_string(value);
else if (strcmp(name, "format") == 0)
current_rule->format = dunst_ini_get_string(value);
}
return 1;
}
void parse_dunstrc(char *cmdline_config_path)
{
xdgHandle xdg;
FILE *config_file = NULL;
xdgInitHandle(&xdg);
if (cmdline_config_path != NULL) {
config_file = fopen(cmdline_config_path, "r");
}
if (config_file == NULL) {
config_file = xdgConfigOpen("dunst/dunstrc", "r", &xdg);
}
if (config_file == NULL) {
/* Fall back to just "dunstrc", which was used before 2012-06-23
* (before v0.2). */
config_file = xdgConfigOpen("dunstrc", "r", &xdg);
if (config_file == NULL) {
puts("no dunstrc found -> skipping\n");
xdgWipeHandle(&xdg);
return;
}
}
if (ini_parse_file(config_file, dunst_ini_handle, NULL) < 0) {
puts("dunstrc could not be parsed -> skipping\n");
}
fclose(config_file);
xdgWipeHandle(&xdg);
print_rules();
}
#endif /* STATIC_CONFIG */
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;
}
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]);
}
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();
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();
initdbus();
initfont(dc, font);
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;
setup();
if (depricated_mod)
warn("-mod is depricated. Use \"-key mod+key\" instead\n",
CRIT);
if (depricated_dunstrc_shortcuts)
warn("You are using depricated 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);
exit(exit_status);
}
void print_version(void)
{
printf("Dunst - Dmenuish notification daemon version: %s\n", VERSION);
exit(EXIT_SUCCESS);
}
/* vim: set ts=8 sw=8 tw=0: */