Initial commit
This commit is contained in:
commit
7f9dd0e3e7
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
dnotify
|
||||||
|
*.o
|
||||||
|
core
|
||||||
|
vgcore.*
|
27
LICENSE.dmenu
Normal file
27
LICENSE.dmenu
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
MIT/X Consortium License
|
||||||
|
|
||||||
|
© 2010-2011 Connor Lane Smith <cls@lubutu.com>
|
||||||
|
© 2006-2011 Anselm R Garbe <anselm@garbe.us>
|
||||||
|
© 2009 Gottox <gottox@s01.de>
|
||||||
|
© 2009 Markus Schnalke <meillo@marmaro.de>
|
||||||
|
© 2009 Evan Gates <evan.gates@gmail.com>
|
||||||
|
© 2006-2008 Sander van Dijk <a dot h dot vandijk at gmail dot com>
|
||||||
|
© 2006-2007 Michał Janeczek <janeczek at gmail dot com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
48
Makefile
Normal file
48
Makefile
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# dunst - Notification-daemon
|
||||||
|
# See LICENSE file for copyright and license details.
|
||||||
|
|
||||||
|
include config.mk
|
||||||
|
|
||||||
|
SRC = draw.c dunst.c
|
||||||
|
OBJ = ${SRC:.c=.o}
|
||||||
|
|
||||||
|
all: options dunst
|
||||||
|
|
||||||
|
options:
|
||||||
|
@echo dunst build options:
|
||||||
|
@echo "CFLAGS = ${CFLAGS}"
|
||||||
|
@echo "LDFLAGS = ${LDFLAGS}"
|
||||||
|
@echo "CC = ${CC}"
|
||||||
|
|
||||||
|
.c.o:
|
||||||
|
@echo CC -c $<
|
||||||
|
@${CC} -c $< ${CFLAGS}
|
||||||
|
|
||||||
|
${OBJ}: config.mk
|
||||||
|
|
||||||
|
dunst: dunst.o draw.o
|
||||||
|
@echo CC -o $@
|
||||||
|
@${CC} ${CFLAGS} -o $@ dunst.o draw.o ${LDFLAGS}
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@echo cleaning
|
||||||
|
@rm -f ${OBJ}
|
||||||
|
@rm -f dunst
|
||||||
|
|
||||||
|
install: all
|
||||||
|
@echo installing executables to ${DESTDIR}${PREFIX}/bin
|
||||||
|
@mkdir -p ${DESTDIR}${PREFIX}/bin
|
||||||
|
@cp -f dunst ${DESTDIR}${PREFIX}/bin
|
||||||
|
@chmod 755 ${DESTDIR}${PREFIX}/bin/dunst
|
||||||
|
@echo installing manual pages to ${DESTDIR}${MANPREFIX}/man1
|
||||||
|
@mkdir -p ${DESTDIR}${MANPREFIX}/man1
|
||||||
|
@cp -f dunst.1 ${DESTDIR}${MANPREFIX}/man1/
|
||||||
|
@chmod 644 ${DESTDIR}${MANPREFIX}/man1/dunst.1
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
@echo removing executables from ${DESTDIR}${PREFIX}/bin
|
||||||
|
@rm -f ${DESTDIR}${PREFIX}/bin/dunst
|
||||||
|
@echo removing manual page from ${DESTDIR}${MANPREFIX}/man1
|
||||||
|
@rm -f ${DESTDIR}${MANPREFIX}/man1/dunst
|
||||||
|
|
||||||
|
.PHONY: all options clean dist install uninstall
|
22
config.mk
Normal file
22
config.mk
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# paths
|
||||||
|
PREFIX = /usr/local
|
||||||
|
MANPREFIX = ${PREFIX}/share/man
|
||||||
|
|
||||||
|
X11INC = /usr/X11R6/include
|
||||||
|
X11LIB = /usr/X11R6/lib
|
||||||
|
|
||||||
|
# Xinerama, comment if you don't want it
|
||||||
|
XINERAMALIBS = -lXinerama
|
||||||
|
XINERAMAFLAGS = -DXINERAMA
|
||||||
|
|
||||||
|
# includes and libs
|
||||||
|
INCS = -I${X11INC} -I/usr/lib/dbus-1.0/include -I/usr/include/dbus-1.0
|
||||||
|
LIBS = -L${X11LIB} -lX11 -ldbus-1 -lpthread -lrt ${XINERAMALIBS}
|
||||||
|
|
||||||
|
# flags
|
||||||
|
CPPFLAGS = -D_BSD_SOURCE -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}
|
||||||
|
CFLAGS = -g -ansi -pedantic -Wall -Os ${INCS} ${CPPFLAGS}
|
||||||
|
LDFLAGS = -s ${LIBS}
|
||||||
|
|
||||||
|
# compiler and linker
|
||||||
|
CC = cc
|
176
draw.c
Normal file
176
draw.c
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
/* See LICENSE file for copyright and license details. */
|
||||||
|
#include <locale.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include "draw.h"
|
||||||
|
|
||||||
|
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||||
|
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||||
|
#define DEFAULTFN "fixed"
|
||||||
|
|
||||||
|
static Bool loadfont(DC *dc, const char *fontstr);
|
||||||
|
|
||||||
|
void
|
||||||
|
drawrect(DC *dc, int x, int y, unsigned int w, unsigned int h, Bool fill, unsigned long color) {
|
||||||
|
XSetForeground(dc->dpy, dc->gc, color);
|
||||||
|
if(fill)
|
||||||
|
XFillRectangle(dc->dpy, dc->canvas, dc->gc, dc->x + x, dc->y + y, w, h);
|
||||||
|
else
|
||||||
|
XDrawRectangle(dc->dpy, dc->canvas, dc->gc, dc->x + x, dc->y + y, w-1, h-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
drawtext(DC *dc, const char *text, unsigned long col[ColLast]) {
|
||||||
|
char buf[BUFSIZ];
|
||||||
|
size_t mn, n = strlen(text);
|
||||||
|
|
||||||
|
/* shorten text if necessary */
|
||||||
|
for(mn = MIN(n, sizeof buf); textnw(dc, text, mn) + dc->font.height/2 > dc->w; mn--)
|
||||||
|
if(mn == 0)
|
||||||
|
return;
|
||||||
|
memcpy(buf, text, mn);
|
||||||
|
if(mn < n)
|
||||||
|
for(n = MAX(mn-3, 0); n < mn; buf[n++] = '.');
|
||||||
|
|
||||||
|
drawrect(dc, 0, 0, dc->w, dc->h, True, BG(dc, col));
|
||||||
|
drawtextn(dc, buf, mn, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
drawtextn(DC *dc, const char *text, size_t n, unsigned long col[ColLast]) {
|
||||||
|
int x = dc->x + dc->font.height/2;
|
||||||
|
int y = dc->y + dc->font.ascent+1;
|
||||||
|
|
||||||
|
XSetForeground(dc->dpy, dc->gc, FG(dc, col));
|
||||||
|
if(dc->font.set)
|
||||||
|
XmbDrawString(dc->dpy, dc->canvas, dc->font.set, dc->gc, x, y, text, n);
|
||||||
|
else {
|
||||||
|
XSetFont(dc->dpy, dc->gc, dc->font.xfont->fid);
|
||||||
|
XDrawString(dc->dpy, dc->canvas, dc->gc, x, y, text, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
eprintf(const char *fmt, ...) {
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vfprintf(stderr, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
if(fmt[0] != '\0' && fmt[strlen(fmt)-1] == ':') {
|
||||||
|
fputc(' ', stderr);
|
||||||
|
perror(NULL);
|
||||||
|
}
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
freedc(DC *dc) {
|
||||||
|
if(dc->font.set)
|
||||||
|
XFreeFontSet(dc->dpy, dc->font.set);
|
||||||
|
if(dc->font.xfont)
|
||||||
|
XFreeFont(dc->dpy, dc->font.xfont);
|
||||||
|
if(dc->canvas)
|
||||||
|
XFreePixmap(dc->dpy, dc->canvas);
|
||||||
|
XFreeGC(dc->dpy, dc->gc);
|
||||||
|
XCloseDisplay(dc->dpy);
|
||||||
|
free(dc);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long
|
||||||
|
getcolor(DC *dc, const char *colstr) {
|
||||||
|
Colormap cmap = DefaultColormap(dc->dpy, DefaultScreen(dc->dpy));
|
||||||
|
XColor color;
|
||||||
|
|
||||||
|
if(!XAllocNamedColor(dc->dpy, cmap, colstr, &color, &color))
|
||||||
|
eprintf("cannot allocate color '%s'\n", colstr);
|
||||||
|
return color.pixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
DC *
|
||||||
|
initdc(void) {
|
||||||
|
DC *dc;
|
||||||
|
|
||||||
|
if(!setlocale(LC_CTYPE, "") || !XSupportsLocale())
|
||||||
|
fputs("no locale support\n", stderr);
|
||||||
|
if(!(dc = calloc(1, sizeof *dc)))
|
||||||
|
eprintf("cannot malloc %u bytes:", sizeof *dc);
|
||||||
|
if(!(dc->dpy = XOpenDisplay(NULL)))
|
||||||
|
eprintf("cannot open display\n");
|
||||||
|
|
||||||
|
dc->gc = XCreateGC(dc->dpy, DefaultRootWindow(dc->dpy), 0, NULL);
|
||||||
|
XSetLineAttributes(dc->dpy, dc->gc, 1, LineSolid, CapButt, JoinMiter);
|
||||||
|
return dc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
initfont(DC *dc, const char *fontstr) {
|
||||||
|
if(!loadfont(dc, fontstr ? fontstr : DEFAULTFN)) {
|
||||||
|
if(fontstr != NULL)
|
||||||
|
fprintf(stderr, "cannot load font '%s'\n", fontstr);
|
||||||
|
if(fontstr == NULL || !loadfont(dc, DEFAULTFN))
|
||||||
|
eprintf("cannot load font '%s'\n", DEFAULTFN);
|
||||||
|
}
|
||||||
|
dc->font.height = dc->font.ascent + dc->font.descent;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool
|
||||||
|
loadfont(DC *dc, const char *fontstr) {
|
||||||
|
char *def, **missing, **names;
|
||||||
|
int i, n = 1;
|
||||||
|
XFontStruct **xfonts;
|
||||||
|
|
||||||
|
if(!*fontstr)
|
||||||
|
return False;
|
||||||
|
if((dc->font.set = XCreateFontSet(dc->dpy, fontstr, &missing, &n, &def)))
|
||||||
|
n = XFontsOfFontSet(dc->font.set, &xfonts, &names);
|
||||||
|
else if((dc->font.xfont = XLoadQueryFont(dc->dpy, fontstr)))
|
||||||
|
xfonts = &dc->font.xfont;
|
||||||
|
else
|
||||||
|
n = 0;
|
||||||
|
|
||||||
|
for(i = 0; i < n; i++) {
|
||||||
|
dc->font.ascent = MAX(dc->font.ascent, xfonts[i]->ascent);
|
||||||
|
dc->font.descent = MAX(dc->font.descent, xfonts[i]->descent);
|
||||||
|
dc->font.width = MAX(dc->font.width, xfonts[i]->max_bounds.width);
|
||||||
|
}
|
||||||
|
if(missing)
|
||||||
|
XFreeStringList(missing);
|
||||||
|
return (dc->font.set || dc->font.xfont);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mapdc(DC *dc, Window win, unsigned int w, unsigned int h) {
|
||||||
|
XCopyArea(dc->dpy, dc->canvas, win, dc->gc, 0, 0, w, h, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
resizedc(DC *dc, unsigned int w, unsigned int h) {
|
||||||
|
if(dc->canvas)
|
||||||
|
XFreePixmap(dc->dpy, dc->canvas);
|
||||||
|
|
||||||
|
dc->w = w;
|
||||||
|
dc->h = h;
|
||||||
|
dc->canvas = XCreatePixmap(dc->dpy, DefaultRootWindow(dc->dpy), w, h,
|
||||||
|
DefaultDepth(dc->dpy, DefaultScreen(dc->dpy)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
textnw(DC *dc, const char *text, size_t len) {
|
||||||
|
if(dc->font.set) {
|
||||||
|
XRectangle r;
|
||||||
|
|
||||||
|
XmbTextExtents(dc->font.set, text, len, NULL, &r);
|
||||||
|
return r.width;
|
||||||
|
}
|
||||||
|
return XTextWidth(dc->font.xfont, text, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
textw(DC *dc, const char *text) {
|
||||||
|
return textnw(dc, text, strlen(text)) + dc->font.height;
|
||||||
|
}
|
35
draw.h
Normal file
35
draw.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/* See LICENSE file for copyright and license details. */
|
||||||
|
|
||||||
|
#define FG(dc, col) ((col)[(dc)->invert ? ColBG : ColFG])
|
||||||
|
#define BG(dc, col) ((col)[(dc)->invert ? ColFG : ColBG])
|
||||||
|
|
||||||
|
enum { ColBG, ColFG, ColBorder, ColLast };
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int x, y, w, h;
|
||||||
|
Bool invert;
|
||||||
|
Display *dpy;
|
||||||
|
GC gc;
|
||||||
|
Pixmap canvas;
|
||||||
|
struct {
|
||||||
|
int ascent;
|
||||||
|
int descent;
|
||||||
|
int height;
|
||||||
|
int width;
|
||||||
|
XFontSet set;
|
||||||
|
XFontStruct *xfont;
|
||||||
|
} font;
|
||||||
|
} DC; /* draw context */
|
||||||
|
|
||||||
|
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, unsigned long col[ColLast]);
|
||||||
|
void drawtextn(DC *dc, const char *text, size_t n, unsigned long col[ColLast]);
|
||||||
|
void eprintf(const char *fmt, ...);
|
||||||
|
void freedc(DC *dc);
|
||||||
|
unsigned long getcolor(DC *dc, const char *colstr);
|
||||||
|
DC *initdc(void);
|
||||||
|
void initfont(DC *dc, const char *fontstr);
|
||||||
|
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);
|
||||||
|
int textw(DC *dc, const char *text);
|
54
dunst.1
Normal file
54
dunst.1
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
.TH DNOTIFY 1
|
||||||
|
.SH NAME
|
||||||
|
dunst \- dmenu\-ish universal notification system
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B dunst
|
||||||
|
.RB [ \-b ]
|
||||||
|
.RB [ \-fn
|
||||||
|
.IR font ]
|
||||||
|
.RB [ \-nb
|
||||||
|
.IR color ]
|
||||||
|
.RB [ \-nf
|
||||||
|
.IR color ]
|
||||||
|
.RB [ \-to
|
||||||
|
.IR secs ]
|
||||||
|
.P
|
||||||
|
.SH DESCRIPTION
|
||||||
|
.B dunst
|
||||||
|
is a lightweight notification\-daemon for the libnotify. It can also be used as a standalone notification system. Dnotify displays messages received via dbus or as commandline argument in a fashion similar to dmenu and additionally prints them to stdout. Notifications can be closed via mouseclick.
|
||||||
|
.SH OPTIONS
|
||||||
|
.TP
|
||||||
|
.B \-h/\-\-help
|
||||||
|
display help message.
|
||||||
|
.TP
|
||||||
|
.B \-b
|
||||||
|
dunst appears at the bottom of the screen.
|
||||||
|
.TP
|
||||||
|
.BI \-fn/ " font"
|
||||||
|
defines the font or font set used.
|
||||||
|
.TP
|
||||||
|
.BI \-nb/\-bg " color"
|
||||||
|
defines the background color. #RGB, #RRGGBB and X color names are supported.
|
||||||
|
.TP
|
||||||
|
.BI \-nf/\-fg " color"
|
||||||
|
defines the background color.
|
||||||
|
.TP
|
||||||
|
.BI \-msg " msg"
|
||||||
|
display msg instead of listening to notifications. This option can be used multiple times.
|
||||||
|
.TP
|
||||||
|
.BI \-to " secs"
|
||||||
|
display each message for secs seconds.
|
||||||
|
.SH AUTHOR
|
||||||
|
written by Sascha Kruse <knopwob@googlemail.com>
|
||||||
|
.SH COPYRIGHT
|
||||||
|
Parts of the code are taken from dmenu (especially draw.c and draw.h).
|
||||||
|
Read LICENCE.dmenu and look at http://tools.suckless.org/dmenu.
|
||||||
|
.TP
|
||||||
|
Some snippets in dunst_dbus.c are taken from twmn. See http://github.com/sboli/twmn.
|
||||||
|
.TP
|
||||||
|
If you feel that copyrights are violated, please send me an e-mail to knopwob@googlemail.com.
|
||||||
|
.SH SEE also
|
||||||
|
.IR dwm (1),
|
||||||
|
.IR dmenu (1),
|
||||||
|
.IR twmn (1),
|
||||||
|
.IR notify-send (1)
|
303
dunst.c
Normal file
303
dunst.c
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
#include <unistd.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/Xatom.h>
|
||||||
|
#include <X11/Xutil.h>
|
||||||
|
#ifdef XINERAMA
|
||||||
|
#include <X11/extensions/Xinerama.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "draw.h"
|
||||||
|
|
||||||
|
#define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh))
|
||||||
|
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
||||||
|
#define MAX(a,b) ((a) > (b) ? (a) : (b))
|
||||||
|
#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask)
|
||||||
|
|
||||||
|
/* structs */
|
||||||
|
typedef struct _msg_queue_t {
|
||||||
|
char *msg;
|
||||||
|
struct _msg_queue_t *next;
|
||||||
|
time_t start;
|
||||||
|
} msg_queue_t;
|
||||||
|
|
||||||
|
|
||||||
|
/* global variables */
|
||||||
|
static int bh, mw, mh;
|
||||||
|
static int lines = 0;
|
||||||
|
static const char *font = NULL;
|
||||||
|
static const char *normbgcolor = "#cccccc";
|
||||||
|
static const char *normfgcolor = "#000000";
|
||||||
|
static const char *selbgcolor = "#0066ff";
|
||||||
|
static const char *selfgcolor = "#ffffff";
|
||||||
|
static unsigned long normcol[ColLast];
|
||||||
|
static unsigned long selcol[ColLast];
|
||||||
|
static Atom utf8;
|
||||||
|
static Bool topbar = True;
|
||||||
|
static DC *dc;
|
||||||
|
static Window win;
|
||||||
|
static double global_timeout = 10;
|
||||||
|
static msg_queue_t *msgqueuehead = NULL;
|
||||||
|
static time_t now;
|
||||||
|
static int loop = True;
|
||||||
|
static int visible = False;
|
||||||
|
|
||||||
|
|
||||||
|
/* list functions */
|
||||||
|
msg_queue_t *append(msg_queue_t *queue, char *msg);
|
||||||
|
msg_queue_t *pop(msg_queue_t *queue);
|
||||||
|
|
||||||
|
|
||||||
|
/* misc funtions */
|
||||||
|
void drawmsg(const char *msg);
|
||||||
|
void handleXEvents(void);
|
||||||
|
void hide_win(void);
|
||||||
|
void run(void);
|
||||||
|
void setup(void);
|
||||||
|
void show_win(void);
|
||||||
|
void usage(int exit_status);
|
||||||
|
|
||||||
|
#include "dunst_dbus.h"
|
||||||
|
|
||||||
|
msg_queue_t*
|
||||||
|
append(msg_queue_t *queue, char *msg) {
|
||||||
|
msg_queue_t *new = malloc(sizeof(msg_queue_t));
|
||||||
|
msg_queue_t *last;
|
||||||
|
new->msg = msg;
|
||||||
|
printf("%s\n", new->msg);
|
||||||
|
new->next = NULL;
|
||||||
|
if(queue == NULL) {
|
||||||
|
new->start = now;
|
||||||
|
return new;
|
||||||
|
}
|
||||||
|
for(last = queue; last->next; last = last->next);
|
||||||
|
last->next = new;
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_queue_t*
|
||||||
|
pop(msg_queue_t *queue) {
|
||||||
|
msg_queue_t *new_head;
|
||||||
|
if(queue == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if(queue->next == NULL) {
|
||||||
|
free(queue->msg);
|
||||||
|
free(queue);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
new_head = queue->next;
|
||||||
|
new_head->start = now;
|
||||||
|
drawmsg(new_head->msg);
|
||||||
|
free(queue);
|
||||||
|
return new_head;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
drawmsg(const char *msg) {
|
||||||
|
dc->x = 0;
|
||||||
|
dc->y = 0;
|
||||||
|
dc->h = 0;
|
||||||
|
drawrect(dc, 0, 0, mw, mh, True, BG(dc, normcol));
|
||||||
|
drawtext(dc, msg, normcol);
|
||||||
|
|
||||||
|
mapdc(dc, win, mw, mh);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
handleXEvents(void) {
|
||||||
|
XEvent ev;
|
||||||
|
while(XPending(dc->dpy) > 0) {
|
||||||
|
XNextEvent(dc->dpy, &ev);
|
||||||
|
switch(ev.type) {
|
||||||
|
case Expose:
|
||||||
|
if(ev.xexpose.count == 0)
|
||||||
|
mapdc(dc, win, mw, mh);
|
||||||
|
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 && msgqueuehead != NULL) {
|
||||||
|
msgqueuehead = pop(msgqueuehead);
|
||||||
|
if(msgqueuehead == NULL) {
|
||||||
|
hide_win();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
hide_win(void) {
|
||||||
|
if(!visible) {
|
||||||
|
/* window is already hidden */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
XUngrabButton(dc->dpy, AnyButton, AnyModifier, win);
|
||||||
|
XUnmapWindow(dc->dpy, win);
|
||||||
|
XFlush(dc->dpy);
|
||||||
|
visible = False;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
run(void) {
|
||||||
|
|
||||||
|
while(True) {
|
||||||
|
/* dbus_poll blocks for max 2 seconds, if no events are present */
|
||||||
|
if(loop) {
|
||||||
|
dbus_poll();
|
||||||
|
}
|
||||||
|
now = time(&now);
|
||||||
|
if(msgqueuehead != NULL) {
|
||||||
|
show_win();
|
||||||
|
if(difftime(now, msgqueuehead->start) > global_timeout) {
|
||||||
|
msgqueuehead = pop(msgqueuehead);
|
||||||
|
if(msgqueuehead == NULL) {
|
||||||
|
hide_win();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
handleXEvents();
|
||||||
|
} else if (!loop) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
setup(void) {
|
||||||
|
int x, y, screen = DefaultScreen(dc->dpy);
|
||||||
|
Window root = RootWindow(dc->dpy, screen);
|
||||||
|
XSetWindowAttributes wa;
|
||||||
|
#ifdef XINERAMA
|
||||||
|
int n;
|
||||||
|
XineramaScreenInfo *info;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
normcol[ColBG] = getcolor(dc, normbgcolor);
|
||||||
|
normcol[ColFG] = getcolor(dc, normfgcolor);
|
||||||
|
selcol[ColBG] = getcolor(dc, selbgcolor);
|
||||||
|
selcol[ColFG] = getcolor(dc, selfgcolor);
|
||||||
|
|
||||||
|
utf8 = XInternAtom(dc->dpy, "UTF8_STRING", False);
|
||||||
|
|
||||||
|
/* menu geometry */
|
||||||
|
bh = dc->font.height + 2;
|
||||||
|
lines = MAX(lines, 0);
|
||||||
|
mh = (lines + 1) * bh;
|
||||||
|
#ifdef XINERAMA
|
||||||
|
if((info = XineramaQueryScreens(dc->dpy, &n))) {
|
||||||
|
int i, di;
|
||||||
|
unsigned int du;
|
||||||
|
Window dw;
|
||||||
|
|
||||||
|
XQueryPointer(dc->dpy, root, &dw, &dw, &x, &y, &di, &di, &du);
|
||||||
|
for(i = 0; i < n-1; i++)
|
||||||
|
if(INRECT(x, y, info[i].x_org, info[i].y_org, info[i].width, info[i].height))
|
||||||
|
break;
|
||||||
|
x = info[i].x_org;
|
||||||
|
y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
|
||||||
|
mw = info[i].width;
|
||||||
|
XFree(info);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
x = 0;
|
||||||
|
y = topbar ? 0 : DisplayHeight(dc->dpy, screen) - mh;
|
||||||
|
mw = DisplayWidth(dc->dpy, screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* menu window */
|
||||||
|
wa.override_redirect = True;
|
||||||
|
wa.background_pixmap = ParentRelative;
|
||||||
|
wa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask | ButtonPressMask;
|
||||||
|
win = XCreateWindow(dc->dpy, root, x, y, mw, mh, 0,
|
||||||
|
DefaultDepth(dc->dpy, screen), CopyFromParent,
|
||||||
|
DefaultVisual(dc->dpy, screen),
|
||||||
|
CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
|
||||||
|
|
||||||
|
XMapRaised(dc->dpy, win);
|
||||||
|
resizedc(dc, mw, mh);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
show_win(void) {
|
||||||
|
if(visible == True) {
|
||||||
|
/* window is already visible */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(msgqueuehead == NULL) {
|
||||||
|
/* there's nothing to show */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
XMapRaised(dc->dpy, win);
|
||||||
|
XGrabButton(dc->dpy, AnyButton, AnyModifier, win, False,
|
||||||
|
BUTTONMASK, GrabModeAsync, GrabModeSync, None, None);
|
||||||
|
XFlush(dc->dpy);
|
||||||
|
drawmsg(msgqueuehead->msg);
|
||||||
|
visible = True;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char *argv[]) {
|
||||||
|
|
||||||
|
int i;
|
||||||
|
|
||||||
|
now = time(&now);
|
||||||
|
|
||||||
|
for(i = 1; i < argc; i++) {
|
||||||
|
if(!strcmp(argv[i], "-b"))
|
||||||
|
topbar = False;
|
||||||
|
else if(!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
|
||||||
|
usage(EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
else if(!strcmp(argv[i], "-fn"))
|
||||||
|
font = argv[++i];
|
||||||
|
else if(!strcmp(argv[i], "-nb") || !strcmp(argv[i], "-bg"))
|
||||||
|
normbgcolor = argv[++i];
|
||||||
|
else if(!strcmp(argv[i], "-nf") || !strcmp(argv[i], "-fg"))
|
||||||
|
normfgcolor = argv[++i];
|
||||||
|
else if(!strcmp(argv[i], "-to"))
|
||||||
|
global_timeout = atoi(argv[++i]);
|
||||||
|
else if(!strcmp(argv[i], "-msg")) {
|
||||||
|
if(i+1 == argc) {
|
||||||
|
usage(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
msgqueuehead = append(msgqueuehead, argv[++i]);
|
||||||
|
loop = False;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
usage(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(loop) {
|
||||||
|
initdbus();
|
||||||
|
}
|
||||||
|
dc = initdc();
|
||||||
|
initfont(dc, font);
|
||||||
|
setup();
|
||||||
|
if(msgqueuehead != NULL) {
|
||||||
|
show_win();
|
||||||
|
}
|
||||||
|
run();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
usage(int exit_status) {
|
||||||
|
fputs("usage: dunst [-h/--help] [-b] [-fn font]\n[-nb/-bg color] [-nf/-fg color] [-to secs] [-msg msg]\n", stderr);
|
||||||
|
exit(exit_status);
|
||||||
|
}
|
210
dunst_dbus.c
Normal file
210
dunst_dbus.c
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
#include <dbus/dbus.h>
|
||||||
|
|
||||||
|
#define DBUS_POLL_TIMEOUT 1000
|
||||||
|
|
||||||
|
DBusError dbus_err;
|
||||||
|
DBusConnection *dbus_conn;
|
||||||
|
dbus_uint32_t dbus_serial = 0;
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
initdbus(void) {
|
||||||
|
int ret;
|
||||||
|
dbus_error_init(&dbus_err);
|
||||||
|
dbus_conn = dbus_bus_get(DBUS_BUS_SESSION, &dbus_err);
|
||||||
|
if(dbus_error_is_set(&dbus_err)) {
|
||||||
|
fprintf(stderr, "Connection Error (%s)\n", dbus_err.message);
|
||||||
|
dbus_error_free(&dbus_err);
|
||||||
|
}
|
||||||
|
if(dbus_conn == NULL) {
|
||||||
|
fprintf(stderr, "dbus_con == NULL\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = dbus_bus_request_name(dbus_conn, "org.freedesktop.Notifications",
|
||||||
|
DBUS_NAME_FLAG_REPLACE_EXISTING, &dbus_err);
|
||||||
|
if(dbus_error_is_set(&dbus_err)) {
|
||||||
|
fprintf(stderr, "Name Error (%s)\n", dbus_err.message);
|
||||||
|
}
|
||||||
|
if(DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) {
|
||||||
|
fprintf(stderr, "There's already another notification-daemon running\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
dbus_bus_add_match(dbus_conn,
|
||||||
|
"type='signal',interface='org.freedesktop.Notifications'",
|
||||||
|
&dbus_err);
|
||||||
|
if(dbus_error_is_set(&dbus_err)) {
|
||||||
|
fprintf(stderr, "Match error (%s)\n", dbus_err.message);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
dbus_poll(void) {
|
||||||
|
DBusMessage *dbus_msg;
|
||||||
|
|
||||||
|
/* make timeout smaller if we are displaying a message
|
||||||
|
* to improve responsivness for mouse clicks
|
||||||
|
*/
|
||||||
|
if(msgqueuehead == NULL) {
|
||||||
|
dbus_connection_read_write(dbus_conn, DBUS_POLL_TIMEOUT);
|
||||||
|
} else {
|
||||||
|
dbus_connection_read_write(dbus_conn, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
getCapabilities(DBusMessage *dmsg) {
|
||||||
|
DBusMessage* reply;
|
||||||
|
DBusMessageIter args;
|
||||||
|
DBusMessageIter subargs;
|
||||||
|
|
||||||
|
const char *caps[1] = {"body"};
|
||||||
|
dbus_serial++;
|
||||||
|
|
||||||
|
reply = dbus_message_new_method_return(dmsg);
|
||||||
|
if(!reply) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dbus_message_iter_init_append(reply, &args);
|
||||||
|
|
||||||
|
if(!dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &subargs )
|
||||||
|
|| !dbus_message_iter_append_basic(&subargs, DBUS_TYPE_STRING, caps)
|
||||||
|
|| !dbus_message_iter_close_container(&args, &subargs)
|
||||||
|
|| !dbus_connection_send(dbus_conn, reply, &dbus_serial)) {
|
||||||
|
fprintf(stderr, "Unable to reply");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dbus_connection_flush(dbus_conn);
|
||||||
|
dbus_message_unref(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
closeNotification(DBusMessage *dmsg) {
|
||||||
|
fprintf(stderr, "closeNotification to be implemented\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
getServerInformation(DBusMessage *dmsg) {
|
||||||
|
DBusMessage *reply;
|
||||||
|
DBusMessageIter args;
|
||||||
|
char *param = "";
|
||||||
|
const char *info[4] = {"dunst", "dunst", "2011", "2011"};
|
||||||
|
|
||||||
|
|
||||||
|
if (!dbus_message_iter_init(dmsg, &args)) {
|
||||||
|
}
|
||||||
|
else if (DBUS_TYPE_STRING != dbus_message_iter_get_arg_type(&args)) {
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dbus_message_iter_get_basic(&args, ¶m);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
reply = dbus_message_new_method_return(dmsg);
|
||||||
|
|
||||||
|
dbus_message_iter_init_append(reply, &args);
|
||||||
|
if(!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &info[0])
|
||||||
|
|| !dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &info[1])
|
||||||
|
|| !dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &info[2])
|
||||||
|
|| !dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &info[3])) {
|
||||||
|
fprintf(stderr, "Unable to fill arguments");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dbus_serial++;
|
||||||
|
if (!dbus_connection_send(dbus_conn, reply, &dbus_serial)) {
|
||||||
|
fprintf(stderr, "Out Of Memory!\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
dbus_connection_flush(dbus_conn);
|
||||||
|
|
||||||
|
dbus_message_unref(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
notify(DBusMessage *dmsg) {
|
||||||
|
DBusMessage *reply;
|
||||||
|
DBusMessageIter args;
|
||||||
|
|
||||||
|
int id = 23;
|
||||||
|
const char *appname;
|
||||||
|
const char *summary;
|
||||||
|
const char *body;
|
||||||
|
const char *icon;
|
||||||
|
char *msg;
|
||||||
|
dbus_uint32_t nid=0;
|
||||||
|
dbus_int32_t expires=-1;
|
||||||
|
|
||||||
|
dbus_serial++;
|
||||||
|
dbus_message_iter_init(dmsg, &args);
|
||||||
|
dbus_message_iter_get_basic(&args, &appname);
|
||||||
|
dbus_message_iter_next( &args );
|
||||||
|
dbus_message_iter_get_basic(&args, &nid);
|
||||||
|
dbus_message_iter_next( &args );
|
||||||
|
dbus_message_iter_get_basic(&args, &icon);
|
||||||
|
dbus_message_iter_next( &args );
|
||||||
|
dbus_message_iter_get_basic(&args, &summary);
|
||||||
|
dbus_message_iter_next( &args );
|
||||||
|
dbus_message_iter_get_basic(&args, &body);
|
||||||
|
dbus_message_iter_next( &args );
|
||||||
|
dbus_message_iter_next( &args );
|
||||||
|
dbus_message_iter_next( &args );
|
||||||
|
dbus_message_iter_get_basic(&args, &expires);
|
||||||
|
|
||||||
|
|
||||||
|
if(strlen(body) > 0) {
|
||||||
|
msg = malloc(
|
||||||
|
strlen(appname)
|
||||||
|
+strlen(summary)
|
||||||
|
+strlen(body)
|
||||||
|
+strlen(": -- ")
|
||||||
|
+5);
|
||||||
|
sprintf(msg, "%s: %s -- %s", appname, summary, body);
|
||||||
|
} else {
|
||||||
|
msg = malloc(
|
||||||
|
strlen(appname)
|
||||||
|
+strlen(summary)
|
||||||
|
+strlen(": ")
|
||||||
|
+5);
|
||||||
|
sprintf(msg, "%s: %s", appname, summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
msgqueuehead = append(msgqueuehead, msg);
|
||||||
|
|
||||||
|
reply = dbus_message_new_method_return(dmsg);
|
||||||
|
|
||||||
|
dbus_message_iter_init_append(reply, &args);
|
||||||
|
dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &id);
|
||||||
|
dbus_connection_send(dbus_conn, reply, &dbus_serial);
|
||||||
|
dbus_message_unref(reply);
|
||||||
|
}
|
10
dunst_dbus.h
Normal file
10
dunst_dbus.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#include <dbus/dbus.h>
|
||||||
|
|
||||||
|
void initdbus(void);
|
||||||
|
void dbus_poll(void);
|
||||||
|
void notify(DBusMessage *msg);
|
||||||
|
void getCapabilities(DBusMessage *dmsg);
|
||||||
|
void closeNotification(DBusMessage *dmsg);
|
||||||
|
void getServerInformation(DBusMessage *dmsg);
|
||||||
|
|
||||||
|
#include "dunst_dbus.c"
|
Loading…
x
Reference in New Issue
Block a user