pax_global_header00006660000000000000000000000064121327044420014511gustar00rootroot0000000000000052 comment=908ae0af46696a352b6429af6ed10e237d5ca2a5 dunst-1.0.0/000077500000000000000000000000001213270444200126445ustar00rootroot00000000000000dunst-1.0.0/.clang_complete000066400000000000000000000004471213270444200156260ustar00rootroot00000000000000-DVERSION="foo" -I/usr/include/dbus-1.0 -I/usr/include/freetype2 -I/usr/include/glib-2.0 -I/usr/include/libpng15 -I/usr/include/pango-1.0 -I/usr/include/pixman-1 -I/usr/lib/dbus-1.0/include -I/usr/lib/glib-2.0/include -I/usr/include/cairo -I/usr/include/pixman-1 -I/usr/include/libdrm -pthread dunst-1.0.0/.gitignore000066400000000000000000000001141213270444200146300ustar00rootroot00000000000000dunst *.o core vgcore.* config.h dunst.1 org.knopwob.dunst.service dunstify dunst-1.0.0/AUTHORS000066400000000000000000000001761213270444200137200ustar00rootroot00000000000000Sascha Kruse (http://github.com/knopwob) contributors: See `git shortlog` for a list of contributors and their contributions dunst-1.0.0/CHANGELOG000066400000000000000000000023271213270444200140620ustar00rootroot00000000000000v0.5.0: - new default dunstrc - frames for window - trigger scripts on matching notifications - context menu for urls (using dmenu) - pause and resume function - use own code for ini parsing (this removes inih) - progress hints v0.4.0: - separator between notifications - word wrap long lines - real transparance - bouncing text (alternative to word_wrap) - new option for line height - better multihead support - don't die when keybindings can't be grabbed - bugfix: forgetting geometry - (optional) static configuration v0.3.1: - fix -mon option v0.3.0: - full support for Desktop Notification Specification (mandatory parts) - option to select monitor on which notifications are shown - follow focus - oneline mode - text alignment - show age of notifications - sticky history - filter duplicate messages - keybinding to close all notifications - new way to specify keybindings - cleanup / bugfixes etc. - added dunst.service v0.2.0: - introduction of dunstrc - removed static configuration via config.h - don't timeout when user is idle - xft-support - history (a.k.a. redisplay old notifications) dunst-1.0.0/INSTALL000066400000000000000000000005221213270444200136740ustar00rootroot00000000000000The following dependencies are needed: dbus libxinerama libxft libxss libxdg-basedir glib pango/cairo On Debian and Debian-based distros you'll probably also need the *-dev packages. With all dependencies present you can build dunst with: $ make and install with: $ sudo make PREFIX=/usr install dunst-1.0.0/LICENSE000066400000000000000000000030161213270444200136510ustar00rootroot00000000000000Copyright © 2013, Sascha Kruse and contributors All rights reserved. All files (unless otherwise noted) are licensed under the BSD license: Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Sascha Kruse nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY Sascha Kruse ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Sascha Kruse BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. dunst-1.0.0/Makefile000066400000000000000000000041331213270444200143050ustar00rootroot00000000000000# dunst - Notification-daemon # See LICENSE file for copyright and license details. include config.mk SRC = x.c \ dunst.c \ dbus.c \ utils.c \ option_parser.c \ settings.c \ rules.c \ menu.c \ notification.c OBJ = ${SRC:.c=.o} all: doc options dunst service dunstify options: @echo dunst build options: @echo "CFLAGS = ${CFLAGS}" @echo "LDFLAGS = ${LDFLAGS}" @echo "CC = ${CC}" .c.o: @echo CC -c $< @${CC} -c $< ${CFLAGS} ${OBJ}: config.h config.mk config.h: @echo creating $@ from config.def.h @cp config.def.h $@ dunst: ${OBJ} @echo CC -o $@ @${CC} ${CFLAGS} -o $@ ${OBJ} ${LDFLAGS} dunstify: @${CC} -o $@ dunstify.c -std=c99 $(shell pkg-config --libs --cflags glib-2.0 libnotify) debug: ${OBJ} @echo CC -o $@ @${CC} ${CFLAGS} -O0 -o dunst ${OBJ} ${LDFLAGS} clean: @echo cleaning @rm -f ${OBJ} @rm -f dunst @rm -f dunst.1 @rm -f org.knopwob.dunst.service @rm -f core @rm -f dunstify doc: dunst.1 dunst.1: README.pod pod2man --name=dunst -c "Dunst Reference" --section=1 --release=${VERSION} $< > $@ service: @sed "s|##PREFIX##|$(PREFIX)|" org.knopwob.dunst.service.in > org.knopwob.dunst.service 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 @mkdir -p "${DESTDIR}${PREFIX}/share/dunst" @ cp -f dunstrc ${DESTDIR}${PREFIX}/share/dunst @mkdir -p "${DESTDIR}${PREFIX}/share/dbus-1/services/" @cp -vf org.knopwob.dunst.service "${DESTDIR}${PREFIX}/share/dbus-1/services/org.knopwob.dunst.service" 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 @rm -f ${DESTDIR}${PREFIX}/share/dbus-1/service/org.knopwob.dunst.service .PHONY: all options clean dist install uninstall dunst-1.0.0/README.pod000066400000000000000000000105271213270444200143120ustar00rootroot00000000000000=head1 NAME dunst - A customizable and lightweight notification-daemon =head1 SYNOPSIS dunst [-geometry geom] [-fn font] [-nf/nb/lf/lb/cf/cb color] [-to/nto/lto/cto secs] [-format fmt] [-key key] [-mod mod] [-mon n] [-v] =head1 DESCRIPTION Dunst is a highly configurable and lightweight notification daemon. =head1 OPTIONS =over 4 =item B<-h/--help> display help message. =item B<-fn font> defines the font or font set used. =item B<-lb/nb/cb color> defines the background color of low/normal/critical messages. =item B<-lf/nf/cf color> defines the foreground color of low/normal/critical messages. =item B<-to secs> defines the default timeout. Can be (partially) overridden by the following options. =item B<-lto/nto/cto secs> timeouts for low/normal/critical messages. =item B<-key key> remove notification by pressing key. =item B<-all_key key> remove all notifications by pressing key. =item B<-history_key key> redisplay last notification by pressing key. =item B<-format fmt> defines the format of the message. See FORMAT. =item B<-mon n> show the notification on monitor n. =item B<-follow mode> display notifications on focused monitor. Possible options are "mouse" to follow the mouse cursor, "keyboard" to follow the window with the keyboard focus and "none" to disable focus following. "mouse" and "keyboard" overwrite the -mon option. =item B<-s> sort messages by urgency. =item B<-ns> don't sort messages by urgency. =item B<-geometry [{width}]x{height}][+/-{x}+/-{y}]> The geometry of the message window. The height is measured in lines everything else in pixels. If the width is omitted but the height is given ("-geometry x2"), the message window expands over the whole screen (dmenu-like). If width is 0, 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. see also EXAMPLES show the notification on monitor n. =item B<-lh/-line_height> height The height of a single line in pixel. If the height is smaller than the font height, it will get raised to the font height. =item B<-print> Print notifications to stdout. This might be useful for logging, setting up rules or using the output in other scripts. =item B<-v/--version> print version information. =item B<-config file> use alternative config file. =back =head1 FORMAT fmt is a string containing placeholders. The placeholders will be replaced with the corresponding text, or if the text is not present, nothing. Possible placeholders are: =over 4 =item B<%a> appname =item B<%s> summary =item B<%b> body =item B<%i> iconname (including its path) =item B<%I> iconname (without its path) =item B<%p> progress value ([ 0%] to [100%]) =back =head1 COLORS Every option that needs a color as argument accepts #RGB, #RRGGBB and X color names. =head1 NOTIFY-SEND dunst is able to get different colors for a message via notify-send. In order to do that you have to add a hint via the -h option. The progress value can be set with a hint, too. =over 4 =item notify-send -h string:fgcolor:#ff4444 =item notify-send -h string:bgcolor:#4444ff -h string:fgcolor:#ff4444 =item notify-send -h int:value:42 "Working ..." =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: =over 4 =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). To change the configuration, copy this file to ~/.config/dunst/dunstrc and edit it accordingly. =head1 AUTHOR written by Sascha Kruse =head1 COPYRIGHT copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) If you feel that copyrights are violated, please send me an email. =head1 SEE ALSO dwm(1), dmenu(1), twmn(1), notify-send(1) dunst-1.0.0/RELEASE_NOTES.0.4.0000066400000000000000000000011271213270444200152560ustar00rootroot00000000000000Release Notes For v0.4.0 Since dunst has lost its ability to show notifications independend of dbus/libnotify a long time ago I think it is time that the describtion reflects that. Even though this breaks the acronym. So if you're a packager please update the package description to read something like: short: "Dunst - a dmenu-ish notification-daemon" long: "Dunst is a highly configurable and lightweight notification-daemon" Release Tarballs are now available at: http://www.knopwob.org/public/dunst-release/ For more information have a look at the CHANGELOG and the new options in dunstrc. dunst-1.0.0/RELEASE_NOTES.1.0.0000066400000000000000000000044061213270444200152560ustar00rootroot00000000000000PACKAGE MAINTAINERS: There are new dependencies introduced with this version: *glib *pango/cairo * The drawing backend was moved from Xlib to Cairo/Pango. This change requires some user intervention since Pango uses different font strings. For example "Monospace-12" becomes "Monospace 12". Font sizes might also get interpreted differently. * Markup Markup within the notification can be interpreted by pango. Examples are italic and bold. If the Markup can't be parsed by pango, the tags are stripped from the notification and the text is displayed as plain text. An error message gets printed to stdout describing why the markup could not be parsed. To make use of markup the option allow_markup must be set in dunstrc. If this option is not set, dunst falls back to the old behaviour (stripping all tags from the text and display plain text). * Actions are now supported. If a client adds an action to a notification this gets indicated by an (A) infront of the notification. In this case the context menu shortcut can be used to invoke this action. * Indicator for URLs. If dunst detects an URL within the notification this gets indicated by an (U) infront of the notification. As with actions the URL can be opened with the context menu shortcut. (This requires the browser option to be set in the dunstrc). * dunstify ( a drop-in replacement for notify-send) Since notify-send lacks some features I need to for testing, I've written dunstify. It supports the same option as notify-send + The abillity to print the id of the notification to stdout and to replace or close existing notifications. example: id=$(dunstify -p "Replace" "this should get replaced after the sleep") sleep 5 dunstify -r $id "replacement" see dunstify --help for additional info. Since dunstify depends on non-public parts of libnotify it may break on every other libnotify version than that version that I use. Because of this it does not get build and installed by default. It can be build with "make dunstify". An installation target does not exist. Please don't open bug reports when dunstify doesn't work with your version of libnotify dunst-1.0.0/config.def.h000066400000000000000000000074341213270444200150270ustar00rootroot00000000000000/* see example dunstrc for additional explanations about these options */ char *font = "-*-terminus-medium-r-*-*-16-*-*-*-*-*-*-*"; bool allow_markup = false; char *normbgcolor = "#1793D1"; char *normfgcolor = "#DDDDDD"; char *critbgcolor = "#ffaaaa"; char *critfgcolor = "#000000"; char *lowbgcolor = "#aaaaff"; char *lowfgcolor = "#000000"; char *format = "%s %b"; /* default format */ int timeouts[] = { 10, 10, 0 }; /* low, normal, critical */ unsigned int transparency = 0; /* transparency */ char *geom = "0x0"; /* geometry */ int sort = True; /* sort messages by urgency */ int indicate_hidden = True; /* show count of hidden messages */ int idle_threshold = 0; /* don't timeout notifications when idle for x seconds */ int show_age_threshold = -1; /* show age of notification, when notification is older than x seconds */ enum alignment align = left; /* text alignment [left/center/right] */ float bounce_freq = 1; /* determines the bounce frequency (if activated) */ int sticky_history = True; int verbosity = 0; int word_wrap = False; int ignore_newline = False; int line_height = 0; /* if line height < font height, it will be raised to font height */ int separator_height = 2; /* height of the separator line between two notifications */ int padding = 0; int h_padding = 0; /* horizontal padding */ enum separator_color sep_color = AUTO; /* AUTO, FOREGROUND, FRAME, CUSTOM */ char *sep_custom_color_str = NULL; /* custom color if sep_color is set to CUSTOM */ int frame_width = 0; char *frame_color = "#888888"; /* 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 * FOLLOW_MOUSE * FOLLOW_KEYBOARD * * everything else than FOLLOW_NONE overrides 'monitor' */ enum follow_mode f_mode = FOLLOW_NONE; /* keyboard shortcuts * use for example "ctrl+shift+space" * use "none" to disable */ keyboard_shortcut close_ks = {.str = "none", .code = 0,.sym = NoSymbol,.is_valid = False }; /* ignore this */ keyboard_shortcut close_all_ks = {.str = "none", .code = 0,.sym = NoSymbol,.is_valid = False }; /* ignore this */ 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 */ /* name, appname, summary, body, icon, timeout, urgency, fg, bg, format, script */ {"empty", NULL, NULL, NULL, NULL, -1, -1, NULL, NULL, NULL, NULL}, /* { "rule1", "notify-send", NULL, NULL, NULL, -1, -1, NULL, NULL, "%s %b", NULL }, */ /* { "rule2", "Pidgin", "*says*, NULL, NULL, -1, CRITICAL, NULL, NULL, NULL, NULL }, */ /* { "rule3", "Pidgin", "*signed on*", NULL, NULL, -1, LOW, NULL, NULL, NULL, NULL }, */ /* { "rule4", "Pidgin", "*signed off*", NULL, NULL, -1, LOW, NULL, NULL, NULL, NULL }, */ /* { "rule5", NULL, "*foobar*", NULL, NULL, -1, -1, NULL, "#00FF00", NULL, NULL }, */ }; dunst-1.0.0/config.mk000066400000000000000000000025541213270444200144500ustar00rootroot00000000000000# paths PREFIX ?= /usr/local MANPREFIX = ${PREFIX}/share/man # In dist tarballs, the version is stored in the VERSION files. VERSION := '$(shell [ -f VERSION ] && cat VERSION)' ifeq ('',$(VERSION)) VERSION := $(shell git describe) endif # Xinerama, comment if you don't want it XINERAMALIBS = -lXinerama XINERAMAFLAGS = -DXINERAMA # uncomment to disable parsing of dunstrc # or use "CFLAGS=-DSTATIC_CONFIG make" to build #STATIC= -DSTATIC_CONFIG PKG_CONFIG:=$(shell which pkg-config) ifeq (${PKG_CONFIG}, ${EMPTY}) $(error "Failed to find pkg-config, please make sure it is installed") endif # flags CPPFLAGS += -D_BSD_SOURCE -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} ${INIFLAGS} CFLAGS += -g --std=c99 -pedantic -Wall -Wno-overlength-strings -Os ${STATIC} ${CPPFLAGS} ${EXTRACFLAGS} pkg_config_packs:="dbus-1 x11 freetype2 xext xft xscrnsaver glib-2.0 gio-2.0 pango cairo pangocairo" # check if we need libxdg-basedir ifeq (,$(findstring STATIC_CONFIG,$(CFLAGS))) pkg_config_packs += libxdg-basedir endif # includes and libs INCS := $(shell ${PKG_CONFIG} --cflags ${pkg_config_packs}) CFLAGS += ${INCS} LDFLAGS += -lm -L${X11LIB} -lXss ${XINERAMALIBS} $(shell ${PKG_CONFIG} --libs ${pkg_config_packs}) # only make this an fatal error when where not cleaning ifneq (clean, $(MAKECMDGOALS)) ifeq (${INCS}, ${EMPTY}) $(error "pkg-config failed, see errors above") endif endif dunst-1.0.0/contrib/000077500000000000000000000000001213270444200143045ustar00rootroot00000000000000dunst-1.0.0/contrib/dunst_espeak.sh000077500000000000000000000001041213270444200173230ustar00rootroot00000000000000#!/bin/bash summary="$2" body="$3" echo "$summary $body" | espeak dunst-1.0.0/dbus.c000066400000000000000000000412121213270444200137450ustar00rootroot00000000000000/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include #include #include "dunst.h" #include "dbus.h" #include "notification.h" GDBusConnection *dbus_conn; static GDBusNodeInfo *introspection_data = NULL; static const char *introspection_xml = "" "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " ""; static void onGetCapabilities(GDBusConnection * connection, const gchar * sender, const GVariant * parameters, GDBusMethodInvocation * invocation); static void onNotify(GDBusConnection * connection, const gchar * sender, GVariant * parameters, GDBusMethodInvocation * invocation); static void onCloseNotification(GDBusConnection * connection, const gchar * sender, GVariant * parameters, GDBusMethodInvocation * invocation); static void onGetServerInformation(GDBusConnection * connection, const gchar * sender, const GVariant * parameters, GDBusMethodInvocation * invocation); void handle_method_call(GDBusConnection * connection, const gchar * sender, const gchar * object_path, const gchar * interface_name, const gchar * method_name, GVariant * parameters, GDBusMethodInvocation * invocation, gpointer user_data) { if (g_strcmp0(method_name, "GetCapabilities") == 0) { onGetCapabilities(connection, sender, parameters, invocation); } else if (g_strcmp0(method_name, "Notify") == 0) { onNotify(connection, sender, parameters, invocation); } else if (g_strcmp0(method_name, "CloseNotification") == 0) { onCloseNotification(connection, sender, parameters, invocation); } else if (g_strcmp0(method_name, "GetServerInformation") == 0) { onGetServerInformation(connection, sender, parameters, invocation); } else { printf("WARNING: sender: %s; unknown method_name: %s\n", sender, method_name); } } static void onGetCapabilities(GDBusConnection * connection, const gchar * sender, const GVariant * parameters, GDBusMethodInvocation * invocation) { GVariantBuilder *builder; GVariant *value; builder = g_variant_builder_new(G_VARIANT_TYPE("as")); g_variant_builder_add(builder, "s", "actions"); g_variant_builder_add(builder, "s", "body"); g_variant_builder_add(builder, "s", "body-markup"); value = g_variant_new("(as)", builder); g_variant_builder_unref(builder); g_dbus_method_invocation_return_value(invocation, value); g_dbus_connection_flush(connection, NULL, NULL, NULL); g_variant_unref(value); } static void onNotify(GDBusConnection * connection, const gchar * sender, GVariant * parameters, GDBusMethodInvocation * invocation) { gchar *appname = NULL; guint replaces_id = 0; gchar *icon = NULL; gchar *summary = NULL; gchar *body = NULL; Actions *actions = malloc(sizeof(Actions)); gint timeout = -1; /* hints */ gint urgency = 1; gint progress = -1; gchar *fgcolor = NULL; gchar *bgcolor = NULL; actions->actions = NULL; actions->count = 0; { GVariantIter *iter = g_variant_iter_new(parameters); GVariant *content; GVariant *dict_value; int idx = 0; while ((content = g_variant_iter_next_value(iter))) { switch (idx) { case 0: if (g_variant_is_of_type (content, G_VARIANT_TYPE_STRING)) appname = g_variant_dup_string(content, NULL); break; case 1: if (g_variant_is_of_type (content, G_VARIANT_TYPE_UINT32)) replaces_id = g_variant_get_uint32(content); break; case 2: if (g_variant_is_of_type (content, G_VARIANT_TYPE_STRING)) icon = g_variant_dup_string(content, NULL); break; case 3: if (g_variant_is_of_type (content, G_VARIANT_TYPE_STRING)) summary = g_variant_dup_string(content, NULL); break; case 4: if (g_variant_is_of_type (content, G_VARIANT_TYPE_STRING)) body = g_variant_dup_string(content, NULL); break; case 5: if (g_variant_is_of_type (content, G_VARIANT_TYPE_STRING_ARRAY)) actions->actions = g_variant_dup_strv(content, &(actions-> count)); break; case 6: if (g_variant_is_of_type (content, G_VARIANT_TYPE_DICTIONARY)) { dict_value = g_variant_lookup_value(content, "urgency", G_VARIANT_TYPE_BYTE); if (dict_value) urgency = g_variant_get_byte (dict_value); dict_value = g_variant_lookup_value(content, "fgcolor", G_VARIANT_TYPE_STRING); if (dict_value) fgcolor = g_variant_dup_string (dict_value, NULL); dict_value = g_variant_lookup_value(content, "bgcolor", G_VARIANT_TYPE_STRING); if (dict_value) bgcolor = g_variant_dup_string (dict_value, NULL); dict_value = g_variant_lookup_value(content, "value", G_VARIANT_TYPE_INT32); if (dict_value) { progress = g_variant_get_int32(dict_value); } else { dict_value = g_variant_lookup_value(content, "value", G_VARIANT_TYPE_UINT32); if (dict_value) progress = g_variant_get_uint32(dict_value); } } break; case 7: if (g_variant_is_of_type (content, G_VARIANT_TYPE_INT32)) timeout = g_variant_get_int32(content); break; } idx++; } g_variant_iter_free(iter); } fflush(stdout); if (timeout > 0) { /* do some rounding */ timeout = (timeout + 500) / 1000; if (timeout < 1) { timeout = 1; } } notification *n = malloc(sizeof(notification)); n->appname = appname; n->summary = summary; n->body = body; n->icon = icon; n->timeout = timeout; n->progress = (progress < 0 || progress > 100) ? 0 : progress + 1; n->urgency = urgency; n->dbus_client = strdup(sender); if (actions->count > 0) { n->actions = actions; } else { n->actions = NULL; free(actions); } for (int i = 0; i < ColLast; i++) { n->color_strings[i] = NULL; } n->color_strings[ColFG] = fgcolor; n->color_strings[ColBG] = bgcolor; int id = notification_init(n, replaces_id); wake_up(); GVariant *reply = g_variant_new("(u)", id); g_dbus_method_invocation_return_value(invocation, reply); g_dbus_connection_flush(connection, NULL, NULL, NULL); run(NULL); } static void onCloseNotification(GDBusConnection * connection, const gchar * sender, GVariant * parameters, GDBusMethodInvocation * invocation) { guint32 id; g_variant_get(parameters, "(u)", &id); notification_close_by_id(id, 3); g_dbus_method_invocation_return_value(invocation, NULL); g_dbus_connection_flush(connection, NULL, NULL, NULL); } static void onGetServerInformation(GDBusConnection * connection, const gchar * sender, const GVariant * parameters, GDBusMethodInvocation * invocation) { GVariant *value; value = g_variant_new("(ssss)", "dunst", "knopwob", VERSION, "1.2"); g_dbus_method_invocation_return_value(invocation, value); g_dbus_connection_flush(connection, NULL, NULL, NULL); } void notificationClosed(notification * n, int reason) { if (!dbus_conn) { printf("DEBUG: notificationClosed but not (yet) connected\n"); return; } GVariant *body = g_variant_new("(uu)", n->id, reason); GError *err = NULL; g_dbus_connection_emit_signal(dbus_conn, n->dbus_client, "/org/freedesktop/Notifications", "org.freedesktop.Notifications", "NotificationClosed", body, &err); if (err) { printf("notificationClosed ERROR\n"); } } void actionInvoked(notification * n, const char *identifier) { GVariant *body = g_variant_new("(us)", n->id, identifier); GError *err = NULL; g_dbus_connection_emit_signal(dbus_conn, n->dbus_client, "/org/freedesktop/Notifications", "org.freedesktop.Notifications", "ActionInvoked", body, &err); if (err) { printf("ActionInvoked ERROR\n"); } } static const GDBusInterfaceVTable interface_vtable = { handle_method_call }; static void on_bus_acquired(GDBusConnection * connection, const gchar * name, gpointer user_data) { guint registration_id; registration_id = g_dbus_connection_register_object(connection, "/org/freedesktop/Notifications", introspection_data-> interfaces[0], &interface_vtable, NULL, NULL, NULL); if (!registration_id > 0) { fprintf(stderr, "Unable to register\n"); exit(1); } } static void on_name_acquired(GDBusConnection * connection, const gchar * name, gpointer user_data) { dbus_conn = connection; } static void on_name_lost(GDBusConnection * connection, const gchar * name, gpointer user_data) { fprintf(stderr, "Name Lost. Is Another notification daemon running?\n"); exit(1); } int initdbus(void) { guint owner_id; g_type_init(); introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, NULL); owner_id = g_bus_own_name(G_BUS_TYPE_SESSION, "org.freedesktop.Notifications", G_BUS_NAME_OWNER_FLAGS_NONE, on_bus_acquired, on_name_acquired, on_name_lost, NULL, NULL); return owner_id; } void dbus_tear_down(int owner_id) { g_bus_unown_name(owner_id); } /* vim: set ts=8 sw=8 tw=0: */ dunst-1.0.0/dbus.h000066400000000000000000000006551213270444200137600ustar00rootroot00000000000000/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef _DUNST_DBUS_H #define _DUNST_DBUS_H #include #include "notification.h" int initdbus(void); void dbus_tear_down(int id); /* void dbus_poll(int timeout); */ void notificationClosed(notification * n, int reason); void actionInvoked(notification * n, const char *identifier); #endif /* vim: set ts=8 sw=8 tw=0: */ dunst-1.0.0/docs/000077500000000000000000000000001213270444200135745ustar00rootroot00000000000000dunst-1.0.0/docs/dunst_explainations.png000066400000000000000000000557001213270444200204040ustar00rootroot00000000000000PNG  IHDRߊbKGD pHYs  tIME S| IDATxy\TUgfD1D }QI+S,{%-5^ Z FkAXګe!.Qfc@3{9s9wfȄDDDԠDDD t"""bDDD t"""bDDD t"""bDDD t"""bDDD t"""bDDD t"""bDDD t"""bNdXwcfcQ xܹsk.!,^wϞ=ѳgg]Nbbխ2L׾}{=ϟW܎B/Q+/;v  ?siý{b&ogϞE1iҤMaF37JHIIU*f̘;;;888`ժUT]QQQRٹs ;;;i>>>ת|&XpppQRRkr\i4rޚYn˖-4i&N-[hUcO_uQ|碸XdeeSJeW^m۶*m5>]~],ZH 8P궱|l%Kר(1bqq /9s&D4Y<BTJ]\\Djjt;99d=DrrQpmѥKݻWY2/^4z_-U]O?-111Fp2f.]͛7 ___a\+ر4X)an+.Rb3gH\"vjT"FI]c+Å BvZ2Ƥ@D=Ϥ[n?Q`N{W>}˟8qBx{{ ;;;*[lرbf͚JJJU5HOOC_~@7e;LcՍac۬6ǫR B( !!ˍj"bzW=Tk3equQVV&_L&3knʾZ+Wf͚U6kL\r`;OSgƼHh4b޽A!Jm۶W^՚Z˝:uJٳںsQevc666@wj W^Zg/m"jSǏGPPrrrj;yd̚5 .\͛7$uhٲ%oh-۾}{iٳg#;;٘={6j?5s۶m0a'N (--}ȑ#ի:t =vĉ9s&RSSQ\\T̚5K؝0tPi_-؀zj 33׿AAA0ax>}:^uAR!99ƍ3&D XL6Mڊ:jWTb…K.VYF*۷oիNNN"::Zk٨(Ѷm[ĬYf:Ej u>rC#eȦ sW0o<}pe@=giOCO˃۷Gaa!탛ӧOk|1y"SQQQW2t2CW3aT*q-X[[묋 "2OSSY.\кb+:rSt :^K~zQ^AAA0aTfJef3grssKb1G~₰0>#̟?ZSO='|Rk`tC˄[nE}; +++9{<5),)) ;D_pdeeaΜ9fY LPf|>rvvT*ƌp+)wؚB8;rL:lW"br0D t, u,J/u}<2Dpo-8$kTpY:üe26nggg(Jxzz"%%E*h4GnЮ];L2F&VƒL>vvvppp@dddgu*L&1Pg5n8C!??F´iӤuСCGFFJKKjԺ-Uoxx8Du+#VEPTRrjqaJT*q-X[[T*Ѫxʽ;|[r;99!==eeePB sK۩S'dffJMA_ u>͚l>}:^uAR!99ƍz0g"77s1i]۷GZZ !i rf?Ou֘0aE}; +++5|jc{J1c <<&}SD rbS,&&2 111l(3۰6kö1ʟu|d KÇ,;ިfAN@'"b ^)T<-{ǝ] 7yM}Įqڭ'YnL67@9C' p$CH2~#:1ԉ8'b1ȉ蜥PXsʫk "91Љ~AN@'&ε,Y:Q<L p>o) N1DD@'""":qNDD tbNDD t_W"":qNDD tb):Ǣ$* 61-fbV1Ӝ/c ͉T6ݏRmKqtqn-mzGolro mYudg%pgJFՙ}8匥au ΆDD ƿkKSH\;QuI ʳەjoՁ]aUz_ޟSeي% 23kO>=kXΊJq%/[?َ-wZ]d]&o~/ܾ%HoPXCŋ b͆/Sʨ8 ;yS:}3 -QS'N .譫}~=nCSvߜ{g-c4eV⇨iZW^,G<`%GoX0xiN`DmZ'KOwC O7Z(Q;п ~ēa)bte%`٤f04mcaӲ |_" ws̠exxacXΊ}-:;:#hMұP#h=!.X%QF鎃;4y,.;*5zjqaX8yΨmX]_:&b܆M__ QOV#<L]m=䟋]GB)nBZߦovU;]d2gk6 nmuvz'G0ERM!o~ij~~w<EFo|dmsɿ:}`]:H]L_PӠhngbNý2~+@֏ z W=.ЄXP>{O|c6[Ϡ"%5Rv@&ھK/tuٰ pL8S L P[`FLaċWoegT8o6y3Ղ ,oWfFr>Hltŝ91|=ˡh1((-kӺn^z`c bp'YJߕg\_e+/9+^\!3zmlֱ+¤:uza|ox:ұd62Gm];ZX\,gFMgz^,*O߶T7&e<;yZw~!ӾR:JaoY7{^{[=3w1Щ !SsDl^*)xp;_bCq ϼa&K_8VVy\.B0} iAP{crKE?2օ xm6!xď;1p&yz<4a(N܆GGN!mnLKbuKbfM {'lR:wdeyꁯ`O ܱK@*ܟICǡ2*Ej̤мE!/q_-=խOEX8O_ q\WXq1+aU4D>!7fS(1f:}%0!׹v GۢoLi "9ejuF?k.-aL붱뫍K@o6=WGcr5Z uGV(L"jSY1%~1QxkCEb]QujO}4B@r rm&^\EߘW[/ƌ\pݸRR~лմ ø6.Qm +B#_2z-X VfxǤu IDATQ!TH<-_Ń=,Z'4= X _ǖUza^\ #}p18˞Es |E%h.W`dԶBvwƮ->^ 9ukl&Cm]"Лcc;b4j1XpNNѸX^gX.FE巅P+&)]nt'oI`,nKc|xGo۲!z-??yQbhLۓbtĤ1jx1ԩ=fEhe%Kg%k2! eߡ' mMs> 0mxm=0~~~lЉi1+ \YmKqi*ۓND/i~lOW#"b1ЉNDDD t"""b1ЉNDDD t"""b1Љ^%NDD@'""":3i1+,>Sjl}ny$9fh2G tjzO6lSoᭌ?nAW֢ΔM^<d‘{e&-lzRXwMUJ-0mu\JǒpJk6ξX ['0p֗۞^8;hFoxi z۷FsĘ}(TI y9ؖrG*Vmp/7[R,4?۳J3ųn(`{5jC9~˯u-aۦbfϘܻ5B笱̼}4wL(v|Ŭw\x!*` Exxz6ɻV@ M-R2U!}[Sl۽+ngƛ>5 !>a$O|6b7EkX Ki7oI?" x vxg^ƺ}񾓀Sc}Cq ϼaFn#rQ$~x e2o;)ɹ{"U:hᱷ]_Q Jٳ_Ω֨] mHCߑ*~him|jսn;CoVm¤Z?ȐSC`߾u;"d*{iR0;'~*P)EϷLZ?ض-.4u_h8dlsO䢃ǫ1dmwJMZߘ6d^1b:}ȏܝ\xQ8{[#̫˛c]9fT[rīOb5|[CO&cW?`h`>2ܻW25Fx 2~!僻*{βl&9Y~t{e {PigL;>1^jg*jmJ km_/䣕 膜VYZfzEUjL 8EzSq*^~!m^wī ,?bT[#b~8dqzkoI`,`jq%T}gBe6=:JY25F`g.:WNcĻU9[?C魣ǴFcMnj6dxiR02.k:jl'K؋_޵/k+!c*q U[<76?o~Æ!bF]KNlK#+|a{ضX}ƶ'N688 ;yS v?_ pBKԉ~?K5zj`_0D&w\p5.;^ug}=fkcHTxG[ɀRO[j ɀ)I} SEuDuroZ+dwerk~cW7XɛiN>()ԵTv-e/jwZumV,-׵]`d tb3\aOXe\^>z5i|¢zus9͕>E7uK;*|3t2sql yB|F-5"I68 I2qwpዱAvn8%-Gl2Kֆ \&-'^|k6\=|_Eufÿsö̻ EO1 Zux?6w20oz5m]/ކ%3SD)ΟMCP^--7b⃷+D'Z+{ RCkk?OX c4P '#/J`|޿hc1q)΀ 'Pl=7UVVr4nϿ-6O_SkOsJ3`D tK?:zu'슀^1̐)]ޟbu~KpA kxaQegKKx{&NX~͸0dmθ0o'2d;-8a:&B 8]|hH?vF~"M_{VLJNDCng@K29zt<[^FBh`^1dRh HfT:@#*}`P@0ᲇ^Zh(Z:MqP k9ZE{W"^>P{VfPZxO."Щn DYiY<(ݽ﯃e~EXl(c/jmZ0nh.o>wQeb5|[V}z!6=aKn1xuok޵;fņ6.}T8? !vJ?ڰ&CO빎p}޴Lի/^ak6w̧1' C'bV@Xjs MsCB'b9#cZu$"?=tFoї&":A&!&& ADD 8"""bף9D@!O1Щ;19;osNv'bgDD@ү 3鈈NÐf@;ېNT+!CDD9;&6K'":1Љ^t;5136;-(XoVmٌn#'"CVYNFNYe)|NFw_l8y/+qN t׳?Vl| %2pReٷni=.-18c¼aCl[#<历jXˑTXj~m'C叢Wh uBAݖͬ;"QPw ٕYd][#̫˛c]9fr ZV6L"ԉt,=ٮi1+U&6/As/ؾˇ+ Q};=un3t6Di]n=aʱz7ޚem5enH#8CZ%4 : tÜ3H"b79_8N@윈Aö&:1ԉ9CDĀ!9]s:Du|gdJ#݉90DD@'"h&蜝1X@';bsvNDP':1P}@M=9;A?b1L@'"""zt;gD|RS!8K6> } ???6"g s"-&&l:$D|.R6S}OЉQB@&!|EJ<B t""bs"""Љb9CDDP':g@'"}HM=9;'"bgă@윈t:1Љń9=Oױ3t"">(--Ehh=gbRf̘l\t ;wƂ ?|0?Fcp|BheͲeː$$$ )) zSBcS(ťK3gH\"v5RSS)))ťuݾ}[tEk/>]Tֽ{wHNNcs;Yݻwww}T>}YfP(ppp˗޽ rrr 1bڵkL6m //Oky''',SYnnvCk;tܩ㇣ƥꫯ`ڵ2eV jBj/\wΝƍäI:_^e2_N#==]k;[NDDF@@N>?‡ƦO_iiiPTHNNƸq㴖 BNNrrr &Hehݺ5Zl,2۷GZZZ}={6ٳg# J&۱cի">>^*S"::Z kkkѯ_?{nrJѡCakk+O.}^z +++$Ədh(Ѷm[ĬYfEEE b?JN7gOnMSk? >??ܴ|guRÃ*P]1Q:_݈tjsDbb"zqF8;;CT)))R9ʯ|m~8['PT1cUViok6>}TWddd<q`){ѣC\\:|5 ӦMjr !i&L<:ۦ.o&?Ns!)) Z]4ܔ~ GVVHEԄٳrΜ9WW*}&O&o>)]QQQFO+jV.,'.((JFʯ[ǎ?tcԷM41?-j...Uz WPISSSk|"# @5k,qF!|t7߬Nj/^^^";;[\|Y|P(Lt}ehP[ tCR*NM˷~+B5J;bB!w}WMd;ؼynC$''fc&w5\2h/..?gv'O&U)wqq[lׯ_eeezjjvZG'GJJIzmy@'UXX(\\\ݻwyh׮(((dI;Vl޼uTdΜe]rɅM &`Ȑ!}As~8@8㵖 4~3grss9sMDȆIT6lڶmC޽accSeΝ;kYFFVcs='t7Ly}}n΅+?-]_MB]ɏiӦ [[[ѡCiR)o!&8K#6mڈիW !XjhӦٿ!!!bȑ";;[dgg ///|P>}ĠAʖ-[& &222ŢEt˔6S8d֭1Є;wNBSN :$CI_02J%.\(t"lmmŚ5k̚06_a㯭# ׆0o<`ԩxᇱf͚&={Į]$@{ﰡqvvT*ƌp:ܹsM;_8C'Јcϧ1ЉjDD@'"#b34 bNT+q 1ЉDDD@'r%":t":DD t"""bQ35D tNDDD t""":`ND@'" ;":1Љ8;#"b_1ЉN/DDD t"Έ〈NDDD t"fgDD@'FŽ݉NDDD t"""bYOD3ݻwmKLL :tY%gD tl4^om;w.vUk_}v?>/RmmhD@Fvj2{Exzz ܥKпe^"bS#zj@R5d-:v={`尳9ldd$agg3gjW <<ݺuCv0ehkڵptt\{ 00ppp@`` JJJ5 ryWL7JHII1+QjZ*2k׮ETTTv9mڴZ6f|ǏFѹ-˖-Cjj*$DDDhXzgqqq8t1j(L6"W.+W⧟~B||<.\l3f@vv6.]Ν;cF6f_y֭[8r~>yɫY"Q>\"v*n޼uu@\~]!DqqqZMMMn鶛8st|*.EԽ{wu$''k BT֨~}Yv=DrrQ}wmѥKkvoGGG-.]T3QS"|s:Z>""{meY+ld7&s^8_%=Lb$' 1?^8A0aLIrk(nuVcFa8)9]'|?o99ϭo.iii1HBHNNl6ٱc EEE"yyyRWW:WD%|'|"EEEbXFq hp8ND4:f 9NЉ:q@S4xLִ;?twl8#D"D7MP_(:]4n%+V7Ϗ-\aa!~@{{;L&:::.\@aazh+g(m+-0EM/J[ss}9;1-Zen:7mЉħUҥK'PHDDV^^ 8 R8qB{1l@lk0*fYfL& &)jׯ_fI t|啧qEgXT qE`0(%fze2Zbɓ'LvʡCTi-kp)W-DMH1&ՊB޽3gDZZf̘]vaJQ#L_˗gFˋ7b^z}j٪|/]XmZ~^|E;0k,퍧-D\Ca%`e$hp8yf̞=0{ll޼Y;z &ojtttq6FL+SOy…z֭ӬV6H|\??0RRRPSS%K(vkoF[Q7nvMpP'D&MD4[DDDЉFM:q@'"""DD):q@'"""DDDЉ:q@'"둛{?GFF~`p"^@KKmGAAG|Ӈ:u*ۇҘE't"Aa6a6?ֺ\rfO T^ iCY.q@'">˖-ҥKz{{QYY ݎɓ'c֭聁Fzz:VXebݺu}}}oCvvvڤ`-77>/jZEE-[EWW~ND4}GXhjXDtR|Ӊjtttժ<*++rJ477# "fNk׮ڵkt:&'N^|9ڊ@ ˥uww;w܁6ZJu^C/cWh(..cǎݲI~ j*l!7n~iӦIjjw}R__`0(UUU)RUU%`05t}zo߾]bCc = IMMf;v(i RTT$)))'uuusM_8dq (aeeeLqʝhs\D[[QVVNao~! 46੧޽{Gt;8јЀrվGVxUF;+,,/hoodBGG… (,,ԬVDe'32'כFN[køqsNd2*=R(š5k~ڶm-̟?_5t"%K+௿w}Qh3Á&W_}TǎÓO>YWf@#ڑ}駟000`H6mBKK <n7>|1> Μ93/n[ƒK(R͕:zjR:tHN8y饗dܹ""Ç{dDdG{KV?+W+= ^G"D4L4I:;;UN<)eeebۥP:dE`0(EDD_.?f@ -9q"X,Hcc_a---@Ξ=+""gΜrEݺE4Kvd"ŚrWP{===zjnݪJb?6_,CDc믿g] rphs\X~=z{{sϡ;v`njAFDt;b|V[. NDD8t"""NDD8Q &ͥIENDB`dunst-1.0.0/docs/dunst_explainations.xcf000066400000000000000000001444151213270444200204020ustar00rootroot00000000000000gimp xcf fileBBgimp-image-grid(style solid) (fgcolor (color-rgba 0.000000 0.000000 0.000000 1.000000)) (bgcolor (color-rgba 1.000000 1.000000 1.000000 1.000000)) (xspacing 10.000000) (yspacing 10.000000) (spacing-unit inches) (xoffset 0.000000) (yoffset 0.000000) (offset-unit inches)  @ +<C4I1a"Indicator for Actions/URLs/dup...     gimp-text-layer(markup "Indicator for Actions/URLs/duplicates") (font "Sans") (font-size 18.000000) (font-size-unit pixels) (antialias yes) (language "en-us") (base-direction ltr) (color (color-rgb 0.000000 0.000000 0.000000)) (justify left) (box-mode dynamic) (box-unit pixels) (hinting yes)  < r ~XC% XP(P : h,us&q ~{#A  *,,*,4*+,^*,z"P")uqJmr *:9qxysh,Cd{# ~S*,VU,*, m`[\2I*,,*,Krqz")*KC@?qrXA}<1~i2% 2~ ~B CdBuvS m`*,*+`#Q2I`./ K*+ **}++zg*KC悹~ !}|FXA}u,iv 1==I"s&| Cd{#A r!S,'m42I,^ Kz"P")o)-*KqJmrmOXAnr93padding     N gimp-text-layere(markup "padding") (font "Sans") (font-size 18.000000) (font-size-unit pixels) (antialias yes) (language "en-us") (base-direction ltr) (color (color-rgb 0.000000 0.000000 0.000000)) (justify left) (box-mode dynamic) (box-unit pixels) (hinting yes) 3 3 H(%v&uuh,vA  ~*+*,*, (*4**^*+*+(*P"vJmuu},.))o lframe width/color     D gimp-text-layero(markup "frame width/color") (font "Sans") (font-size 18.000000) (font-size-unit pixels) (antialias yes) (language "en-us") (base-direction ltr) (color (color-rgb 0.000000 0.000000 0.000000)) (justify left) (box-mode dynamic) (box-unit pixels) (hinting yes) ll@P4:-5q&|Ny>| '(uA ccr!gifi'IH*,4  ^ ww*+P"o)-nstm)JmmO%./$urhhh-2~h,Bsq ~{# `,*,*,`,*,*,Cz"}q 1w6D line_height     o_ gimp-text-layeri(markup "line_height") (font "Sans") (font-size 18.000000) (font-size-unit pixels) (antialias yes) (language "en-us") (base-direction ltr) (color (color-rgb 0.000000 0.000000 0.000000)) (justify left) (box-mode dynamic) (box-unit pixels) (hinting yes) D4D@ h,| h,| vh, ~r! ~r! ~ ' '(*   (*o)-o)-mOmO}4.9o 777)r "Dseparator ...     ^gimp-text-layert(markup "separator\nwidth/color") (font "Sans") (font-size 18.000000) (font-size-unit pixels) (antialias yes) (language "en-us") (base-direction ltr) (color (color-rgb 0.000000 0.000000 0.000000)) (justify left) (box-mode dynamic) (box-unit pixels) (hinting yes) D D =Cd| v&q&qSr!A A m'*+*,2I44K **^^*,ߧ*Ko)-P"P")XAmOvJmJmr==2~'(uh,Bsgifi ~{#IH*, `,*,*,  `ww*+,*,*,nstm)Cz"%./$ur}q!1oooGq "Age of notification ...     <gimp-text-layer(markup "Age of notification\n(show_age_threshold)") (font "Sans") (font-size 18.000000) (font-size-unit pixels) (antialias yes) (language "en-us") (base-direction ltr) (color (color-rgb 0.000000 0.000000 0.000000)) (justify left) (box-mode dynamic) (box-width 11.000000) (box-height 6.000000) (box-unit pixels) (hinting yes) #s#++#'*dP::9xyv| h,r! ~VU(*'*, *,[\(* *,*,rqo)-)@?}mOr .99o q11q1HCdh,'(&v|S ~gifiA r!m *,IH (*'2I  4JK*,ww^(* q*KnstmP"o)XA%./$Jm}m2.9) o DV82"$s&h,{#A  ~,*, 4,^*,z"P")qJmr    h,q| Cdh, ~r!S ~ 'm *,*2I K*,*-)o)-*KOrmOXA6~luK,+Kmu~E"*Number of notifications ...     gimp-text-layer(markup "Number of notifications\nthat are waiting to get displayed\n(indicate_hidden)") (font "Sans") (font-size 18.000000) (font-size-unit pixels) (antialias yes) (language "en-us") (base-direction ltr) (color (color-rgb 0.000000 0.000000 0.000000)) (justify left) (box-mode dynamic) (box-width 11.000000) (box-height 6.000000) (box-unit pixels) (hinting yes) .X*.t<<*.4*9<0   \P:W,|Ny>v| qqccr!ꂻ*+'*,z  ** *,[~ !o)-,ivmO8 ( 'h,&&q| '( ~A A r!gifi  'IH44  ^^ ww)P")P"o)-nstmrJmrJmmO%./$% qHh,us&| h ~{#A r! *,,'4J*+,^ qz"P")o)-uqJmrmO   ?V32 h,s& ~{#A  *,,*4*,,^*)z"P")rqJmrH!&h,vvA  ~ (**,(*4^(**,(*P"))ㄶJmr}r}..o o M~ l,uu| h,K ~r! ~ *,*,' *+*+ Ko)-muumO;~   h,Cd- ~S-, m/2I-,K-*K-XAE  | uCdv&|r!ǀSA zFr!'*,m*+"X'2I4Y *+K**^7: o)-)܂*KP"Bo)mOruXAvJmOm"h((j u*,*+-Ouf3 vhorizontal_padding     kgimp-text-layer(markup "horizontal_padding") (font "Sans") (font-size 18.000000) (font-size-unit pixels) (antialias yes) (language "en-us") (base-direction ltr) (color (color-rgb 0.000000 0.000000 0.000000)) (justify left) (box-mode dynamic) (box-width 11.000000) (box-height 6.000000) (box-unit pixels) (hinting yes) ?>v?VC(v?jAa@7 * h,qh,& ~c ~A  *,9*, 4*,;*,^g)P"rJmN+(v&uuh,vA  ~*+*,*, (*4**^*+*+(*P"vJmuu},.))o ;text     CDI II%E EE-E=EME]EmE}EEEEEEEEF FF-F=FMF]FmF}FFFFFFFFG GG-G=GMG]GmG}GGGGGGGGH HH-H=HMH]HmH}HHHHHHHH                                                            }}>>arrows     IJa{aaK KKKKKKKL LAMiNOOPkP{PPPR:RJRRSSS!TUbUUUUUV]WXWX YZ%Z5ZE[[\b\r]^^}^^^_5_E```````aaa[ak >>=>>>=>>>=>>>=>>>=>>>=>    9>=>>>=> >=>>>=>>>=>>>=>>>=>>>=>>>=>>>=>>>=>>>=>>>=>>>=>>>=>>>=>9<;::989887765544433221 1 0 / / / / . . '. . . . '=== <=<<=<<=<<=<<=<<=<<=<<=<=<<=<<=<<=<<=<<=;;;06 .;;;;;;::;;;;::;;;;;::<z    )>=>>>=>>>=>> 5=====<=========<=========<=========<=========<=========<======= 6========================2 @      =333333 233333322110099<='/=<=========<=========<=========<=========<=========<=========<= =>=>==>==>=>=     =<=<=<=<=<=<=<===<=<=<==<=<=<=<=<=<=<=<==<=<=<=<=<=<=<=<=<==<=~(=== '  >>==>==>==+8>==>=>==>==>=>== @==>=>==>==>=>==>==>=>==>==>=>==>==>=>==>==>=>=,>>>>=>>>>>>>>=>>>>>>>>=>>>>>>>>=>>>>>>>>=>>>>>>>=     6=<=<=<=<=<=<=<==<=<=<=<=<=<=<=<=<=W==>==>==>==>==>==>==>==>==>==>==>==>=>==>==>==>==>==>==>==>==>=>==>=>==>==>=>==>==>=>==>==>=>==>==>=>==>==>=>==>==>=>==>==>=>=3>>>>>>>=>>>> @>>>=>>>>>>>>=>>>>o    '>==>==>==>==>==>==>==>= (>==>=>==>==>=>==>==>=>==>==>=>==>==>=>==>==>=>==>==>=>==>===>=<                           >==>=>==>= y        }}>>,dunst_doc_base.png     YtbZ,bz,bms})2x'U6Pt\4Lê<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<((((+((( (:((((((((Q( ((( (l((,P((((((5( ((( (V((@t((((((-((( (3((v?(((((()( ((7 (3(()K+1((((()( ((-( (V((5(,((((-( ((( (k((Z((P(6+a((5( (U((( (:((}((1(1d((R( (^(((((((,((<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(((((((((((((( ((((((( ( (((Y((0(((Y((((B((1I((/`((((1I(((( (@((5((((;(*((((5(((((( (Ն/*((((((;(*(((((((((( (z+m((((((-_((h(((((((( (O((((((1((g(((((((<(<(<(<(<(<(<(<(<(ê((=(( (H(((H((((((1f((p,( (((p,( (B((((/`((~;( (((~;((@((((;(*(()Z( ((()Z((Ն/*((*((;(*((B( (((B((z+m((~((._(((((((O((K((2i(A(UU(UUfUU|ڌUUU|ڌUUUUUUUiVsUUUiVsU U`UUnUUU辉VUUU辉VUUUUVUUUUVUUUVUUvXUUUUUXhUUUXhUUhUUUUU}߆UUU}߆UUUUUUUU>UU=UU UUUUUUUUUUggUUvVU UUUvVU UVvUUUUXUUXU UUUXUUUUUUXUXUUVU UUUVUUWXUUUUXUXUUwU UUUwUUwYUUrUUWUUUUUUUUUUUhhUAUww(wwfwwؑwwwؑww߅wwwwwzwwwzw wwwwwwÕw wwÕwwwwwxwwwywwwywwwwwww̎xwww̎xww~wwwww݆www݆wwwwwwww>ww=ww wwwwww݅wwww}www wwww wzwwww{׹ww۽xw www۽xwwwwwwxwww}w www}wwxwwwwxwwwyw wwwywwwwww{ֻwwwwwwwwwww}wAw(( (( (( (($( (((()|(+e((k(+D((((3((;((((j?((v{8((I(wI((((J((((((D((((;*(;*((((;*((((((C((?( (;*(;*((+((;*( ((j=((y3((H(wH(({((I((()~(+((p(+D((A((4((F((((((((((((((((((((((Y((0((qf((1f((0(+;(6(((1I((0_((/_((/`((/`(((v((((5((((<(*((;(*((;(*((;(*((;+@(((((((=(*((;(*((;(*((;(*(()ۖ( ((((((0`((.]((._((-_((]( (h(((((0((pf((2i((1((7)( (g((((F((((((D((UU UU UU UU$U UUUUWUZiUUjUZZUUUUjUU|UUUU_UUtnUUUUUUUUUUUUUZUUUUXUXXUUUUXXUUUUUUZUUYU UXUXXUUVUUXXUUVUUUU^UUXUUUUUoUUUUUWUZUUlUZZUUUUlUU}UUUUUUUUUUUUUUUUUUUUUUUUeUUgUUggUUfU\XUpUUUXUUXUUXUUXUUXUU܉UԎUUUUXUVUUXUXUUXUXUUXUXUUXUXUUy\YUUUUUUUYUXUUXUXUUXUXUUXUXUUVU UUUUUUXUUWUUWUUWUUkU UjUUUUUeUUgUUhhUUhUUpU UUUUUUUVΦUU}UUZUUww ww ww ww$w wwww}w}ww~wywwwwwwwwww{wwwwwwwwwwwwwwwywwwwxwxwwwwxwwwwwwywwxw wxwxwwwwxwwywwwwzwwxwwwwwwwwww}www~wywwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww}ww}wwwxwwwwyww{ww{ָww{׹ww{wwwwwwwxw{wwxwwwxwwwxwwwxwwwywwwwwwwxwwwxwwwxwwwxwww}w wwwwww}wwzշww{ֻww{ww~w wwwwwwww}ww}wwwww wwwwwxww{wwwwyww@(м݆((((((((((((((\*A((((kp(kp((.((4W(MS4((z+((((((((((B((?((.k<((S((((((((((z+((((((p05(qC(((((((fQ((((Fy((ک(R(((((((I((((+(;(?(:(+(((((!(((((Y((( (0((-((H( (1I((B((( (0_((0F((((p,( (5((((@((( (<(*((((((~;( (((Ն/*((( (=(*((6(((()Z( (((z+m((h( (0`((9((h((B( (((O((g((0((,((g((('(F(:(((@UڌUUUUUUUUUUUUUUiVsUUUUruUruUU`UUZU]kUU辉VUUUUUUUUUUUUYUU`XUUڂUUVUUUUUUUvXUUUUUUXhUm~UUUUUUUhUUUUeUU߆UUUUUUUUUUUU\U;UyU:UUUUUU!UUUUUUUU UeUU^zUUU UXUUVvUUU UXUU؊X}UUUUvVU UXUVUUUUU UXUXUUUUUUXU UUUWXUUU UYUXUUXUUUUVU UUUwYUUjU UXUU֭\UUjUUwU UUUUUUUeUU\UUUUU'UU:U}UU@wؑwwwwwww߅wwwwwwwzwwwwwwwww{wzwwÕwwwwwwwwwwwwwywwxwwڣwwzwwwwwwwwwwwwwڎxwwwwwwww~wwww}ww݆wwwwwwwwwwwww:wxw:wwwwww!wwwwwww݅w wwwwww wywwzwww w{wwzwwwww wxw{wwwww wxwwwwwww۽xw wwwxwww wxwwwxwwww}w wwwwww w}wwywwwwyw wwwwwwwwwwwwww&wxw:www(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w<(<(<(<(<(<(<(%(((%(((((%((((( (+9((*K1f((u((0((((H((+ (v((A(/`((5w((/`((((p,(( (7,K(;(*((:((;(*((((~;(( (ĉ*(;(*((((;(*((*(()Z(( (N((._((((-_((~((B(( (,>V((2i((((1((K((((<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(((( ((((( ((((( (( (((Y((0((k(+D(((((B((1I((/`((I(wI(((( (@((5((((;(*((;*(;*(((( (Ն/*((((((;(*((;*(;*((+(( (z+m((((((-_((H(wH(({(( (O((((((1((p(+D((A(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<( (+9((*9((*9((*K((0((1f((1f( (v((Av((Av((A((0_((/`((/`( (7,K7,K7,K((<(*((;(*((;(*( (ĉ*(ĉ*(ĉ*((=(*((;(*((;(*(V((((O((qf((qf((,((1((O(((((6((((((((((((((3((((((u(((,((((((oe((J((^^((B((5w((/Û((((((/_((;*((++((@((:((((((((;(*((;*((((((Ն/*((((s((-()((;(*((I((((((z+m(((((8<((ʙ*a((.]((4((((((O((((((V((F((qf(9((=((((((((((((:((29((((((l((7-p((0((((-((0((-((V((3(()((0_((((0F((/`((0F((3((5,l((<(*((((((;(*((((3((2((=(*((*((6( (;(*((6( (V((=(UU=UU=UUUXUUX\UUUUUfUUfUU^zUUfUUUUԇUUUUZUUVvUUXUUXUU؊X}UUXU UVvUUq\\qUUXUUUUXUXUUXUXUUUUXUXUUUUUUUUWXUUXUXUUXUXUUXUUXUXUUWXUUUUUUwYUUWUUWUU֭\UUWUUwYUU^\^UUUUޙUUgUUgUU\UUhUUUUUUU6UUUUUUUUUUUUUUjUUUUUUUUU^UUUUUUfUUUUijUUVvUUZUUbUUUUUUXUUXXUUVVUUUUXUUUUUUUUXUXUUXXUUUUUUWXUUUUjUUVUXUUXUXUUUUUUUUwYUUUUUtXUUXUUWUUlUUUUUUUUUUUUUUUUgU9UU=UUUUUUVUUUUUUwUUjXUUUUUUlUU\]UUeUUUU^zUUfUU^zUU^UUWUUXUUXUUUU؊X}UUXUU؊X}UUWUUZ\UUXUXUUUUUUXUXUUUUWUUiUUYUXUUUUXU UXUXUUXU U^UU~Uww=ww=wwwxwwԂywww݅ww|ww|wwwwww݅wwwwөww{wwzww{ֶww{ֶwwzww{w wzwwywwxwwwwxwwwxwwwwwxwwwwwxwwwwxwwxwwwxwwwxwwxwwwxwwwwwwwwzոwwzոwwyww{wwwwzwwwwww}ww}wwwwwwwwwww6wwwwwwwwwwwwwwwwww݅wwwwwwwwwww|wwwwwwzww{wwwwwwww{ֶwwxwwyywwwwxwwwwwwwwxwwwxwwwwwwxwwww~wwwwwwxwwwwwwwwwwwwwwxww|wwzոwwwwwwwwwwwwww٫wwww}w9ww=wwwwwwzwwwwwwƑwwxwwwwwwwwz֖wwwwwwwwwwwwzwwxwwww{wwwwzww{wwzwwxwwywwxwwwwwwwxwwwwwxwwwwxwwwwwxw wxwwwxw wzwww(((( (((((((((((((((Y((0((+9((*K((Y((H((11I((/`((v((A((((((1I((p,((5((((;(*((7,K((((((5((((~;((((((;(*((ĉ*(((((((((()Z((((((-_((N((((h((((( (B((((((1((,>V(((g((((((((2(((=((((((Z((((((((((1v(((Y((0((((Y((((((()(((B((1I((/`( (B((1I(((((((.k(@((5((((;(*((@((5(((((((J(Ն/*((((((;(*((Ն/*((((((,((((()(z+m((((((-_((z+m((((((6+a(((-`O((((((1((O((((((1d(((P(((((+(.(((((Q(.(((((5((H((1f((((0((-((p,( (/`((((/`(()((~;((;(*((((;(*(()(()Z((;(*((*((;(*((-((UUUU UUUUUUUUUUUUUUUUUfUU\XUUX\UUUUUUgXUUXUUԇUUUUUUUUXUUvVUUXUVUUXUXUUq\\qUUUUUUXUVUUXUUUUUUXUXUUUUUUUUUUUUVUUUUUUWUUUUUUjUUUUU UwUUUUUUhUU^\^UUUUUUUUUUUhUUU=UUUUUUcUUUUUUUUUUbUUUUUfUUUUUUUUUUUXUUUVvUUXUUXU UVvUUXUUUUUUU^UUUXUVUUXUXUUUUXUVUUUUU\UWXUUUUUUXUXUUWXUUUUUUVUUVUUUVͤUwYUUUUUUWUUwYUUUUUU[[UUU^`UUUUUUhUUUUUUUUghUUUUUUUUVU.UUUUUwU.UUUUUlUUUUggUUUUfUU^UUvVU UXUUUUXUUWUUXUUXUXUUUUXUXUUWUUVUUXUXUUUUXUXUU^UUwwww wwwwwwwwwwwwwwwwwwwxwwywwwwwwyww{wwwwөwwwwwwywwwwxw{wwxwwwywwwwwwxw{ww۽xwwwwwwxwwwxwwwwwwwwww}wwwwww{wwwwwwwwwww wywwwwwwwwzwwwwwwwwwwwwww=wwwwww|wwwwwwwwwxw݅wwwwww݅wwwwwwwwwwwwzwwyww{w wzwwywwwwwwwwwwxw{wwxwwwwwxw{wwwwwzwxwwwwwwxwwwxwwwwwwwwzwww|wwwwwww{wwwwwwwwywww{wwwwwwwwwwwwww}wwwܵwwwwww/wwwwww.wwwwwwwww}wwwwwwwww w{׹wwww{ww~ww۽xwwxwwwwwxwww~ww}wwxwwwwwxwwwww(((=P((('(j( (+(.(*,( (4( (f((((-((((9(((D(((([/`((^^((0F( (9((?((4((\((9((@;(*((++((( (T((`((4((((T((;(*((((((6( (((((.D((R((((2._((((((9( (T((b((b;((V((T((ϒi((((((,((V((4(jڠ((((V((C(((UUU|\UUU'UbU U\U.UXVU UjU UgUUUU^zUUUUXUUUUUUU왞XUUijUU؊X}U UtUUUUhUUĐUUtUUXUXUUVVUUU UUUɐUUlUUkUUUUpXUXUUUUUUXU UqUUfUUWUU]UUqUUWWUUUUUU֭\U U]UUfUUhoUUdUU]UUhUUUUUU\UUUUWUlUUUUUUUUUwwwzwww'w{w ww.ww ww w}wwwwwwwwxwwwܚwwww{׹wwwwzw wͬwwסwwwwwwͬwwxwwwyywww wwwwwwwwwwwxwwwwwwwxw www}wwx٤wwzwwwwx{ֻwwwwwwyw wzww}ww~ww|wwzww}wwwwwwwwwwxwwwwwwwwww((((((((((((((((((((($(_*(%(((((+(&(V($((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((U(U(U(U(U(U(U(U(U(UU$UpU&U(U(UVU&U^U$U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(w(w(w(w(w(w(w(w(w(ww$ww&w(w(w(wzw$w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w (N((N((N((((0`((._((._( (,>V((,>V((,>V((((0((2i((2i(+(F( ()(( (<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(((+( (:((%( (l((($( (V((( (((1f((u((-( (3((( (^^((/`((5w((0F( (3((( (++((;(*((:((( (V((( (((((;(*((((6(( (k((( (((((._((((9(( (:(((((((2i((((,(((+(<(<(<(<(<(<( UUUUUUUUUXUUWUUWU U^\^UU^\^UU^\^UUUUeUUhhUUhhU+UU U)U}U U>dunst-1.0.0/dunst.c000066400000000000000000000237421213270444200141550ustar00rootroot00000000000000/* copyright 2012 - 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #define _GNU_SOURCE #define XLIB_ILLEGAL_ACCESS #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef XINERAMA #include #endif #include #include "dunst.h" #include "x.h" #include "dbus.h" #include "utils.h" #include "rules.h" #include "notification.h" #include "option_parser.h" #include "settings.h" #define LENGTH(X) (sizeof X / sizeof X[0]) #ifndef VERSION #define VERSION "version info needed" #endif #define MSG 1 #define INFO 2 #define DEBUG 3 typedef struct _x11_source { GSource source; Display *dpy; Window w; } x11_source_t; /* index of colors fit to urgency level */ bool pause_display = false; GMainLoop *mainloop = NULL; /* notification lists */ GQueue *queue = NULL; /* all new notifications get into here */ GQueue *displayed = NULL; /* currently displayed notifications */ GQueue *history = NULL; /* history of displayed notifications */ GSList *rules = NULL; /* misc funtions */ void check_timeouts(void) { /* nothing to do */ if (displayed->length == 0) return; for (GList * iter = g_queue_peek_head_link(displayed); iter; iter = iter->next) { notification *n = iter->data; /* don't timeout when user is idle */ if (x_is_idle()) { n->start = time(NULL); continue; } /* skip hidden and sticky messages */ if (n->start == 0 || n->timeout == 0) { continue; } /* remove old message */ if (difftime(time(NULL), n->start) > n->timeout) { /* close_notification may conflict with iter, so restart */ notification_close(n, 1); check_timeouts(); return; } } } void update_lists() { int limit; check_timeouts(); if (pause_display) { while (displayed->length > 0) { g_queue_insert_sorted(queue, g_queue_pop_head(queue), notification_cmp_data, NULL); } return; } if (xctx.geometry.h == 0) { limit = 0; } else if (xctx.geometry.h == 1) { limit = 1; } else if (settings.indicate_hidden) { limit = xctx.geometry.h - 1; } else { limit = xctx.geometry.h; } /* move notifications from queue to displayed */ while (queue->length > 0) { if (limit > 0 && displayed->length >= limit) { /* the list is full */ break; } notification *n = g_queue_pop_head(queue); if (!n) return; n->start = time(NULL); if (!n->redisplayed && n->script) { notification_run_script(n); } g_queue_insert_sorted(displayed, n, notification_cmp_data, NULL); } } void move_all_to_history() { while (displayed->length > 0) { notification_close(g_queue_peek_head_link(displayed)->data, 2); } notification *n = g_queue_pop_head(queue); while (n) { g_queue_push_tail(history, n); n = g_queue_pop_head(queue); } } void history_pop(void) { if (g_queue_is_empty(history)) return; notification *n = g_queue_pop_tail(history); n->redisplayed = true; n->start = 0; n->timeout = settings.sticky_history ? 0 : n->timeout; g_queue_push_head(queue, n); if (!xctx.visible) { wake_up(); } } void wake_up(void) { run(NULL); } static int get_sleep_time(void) { if (settings.show_age_threshold == 0) { /* we need to update every second */ return 1000; } bool have_ttl = false; int min_ttl = 0; int max_age = 0; for (GList *iter = g_queue_peek_head_link(displayed); iter; iter = iter->next) { notification *n = iter->data; max_age = MAX(max_age, notification_get_age(n)); int ttl = notification_get_ttl(n); if (ttl > 0) { if (have_ttl) { min_ttl = MIN(min_ttl, ttl); } else { min_ttl = ttl; have_ttl = true; } } } int min_timeout; int show_age_timeout = settings.show_age_threshold - max_age; if (show_age_timeout < 1) { return 1000; } if (!have_ttl) { min_timeout = show_age_timeout; } else { min_timeout = MIN(show_age_timeout, min_ttl); } /* show_age_timeout might be negative */ if (min_timeout < 1) { return 1000; } else { /* add 501 milliseconds to make sure we wake are in the second * after the next notification times out. Otherwise we'll wake * up, but the notification won't get closed until we get woken * up again (which might be multiple seconds later */ return min_timeout * 1000 + 501; } } gboolean run(void *data) { update_lists(); static int timeout_cnt = 0; static int next_timeout = 0; if (data) { timeout_cnt--; } if (displayed->length > 0 && !xctx.visible) { x_win_show(); } if (displayed->length == 0 && xctx.visible) { x_win_hide(); } if (xctx.visible) { x_win_draw(); } if (xctx.visible) { int now = time(NULL) * 1000; int sleep = get_sleep_time(); if (sleep > 0) { int timeout_at = now + sleep; if (timeout_cnt == 0 || timeout_at < next_timeout) { g_timeout_add(sleep, run, mainloop); next_timeout = timeout_at; timeout_cnt++; } } } /* always return false to delete timers */ return false; } int main(int argc, char *argv[]) { history = g_queue_new(); displayed = g_queue_new(); queue = g_queue_new(); cmdline_load(argc, argv); if (cmdline_get_bool("-v/-version", false, "Print version") || cmdline_get_bool("--version", false, "Print version")) { print_version(); } char *cmdline_config_path; cmdline_config_path = cmdline_get_string("-conf/-config", NULL, "Path to configuration file"); load_settings(cmdline_config_path); if (cmdline_get_bool("-h/-help", false, "Print help") || cmdline_get_bool("--help", false, "Print help")) { usage(EXIT_SUCCESS); } int owner_id = initdbus(); x_setup(); signal(SIGUSR1, pause_signal_handler); signal(SIGUSR2, pause_signal_handler); if (settings.startup_notification) { notification *n = malloc(sizeof(notification)); n->appname = "dunst"; n->summary = "startup"; n->body = "dunst is up and running"; n->progress = 0; n->timeout = 10; n->urgency = LOW; n->icon = NULL; n->msg = NULL; n->dbus_client = NULL; n->color_strings[0] = NULL; n->color_strings[1] = NULL; n->actions = NULL; n->urls = NULL; notification_init(n, 0); } mainloop = g_main_loop_new(NULL, FALSE); GPollFD dpy_pollfd = { xctx.dpy->fd, G_IO_IN | G_IO_HUP | G_IO_ERR, 0 }; GSourceFuncs x11_source_funcs = { x_mainloop_fd_prepare, x_mainloop_fd_check, x_mainloop_fd_dispatch, NULL, NULL, NULL }; GSource *x11_source = g_source_new(&x11_source_funcs, sizeof(x11_source_t)); ((x11_source_t *) x11_source)->dpy = xctx.dpy; ((x11_source_t *) x11_source)->w = xctx.win; g_source_add_poll(x11_source, &dpy_pollfd); g_source_attach(x11_source, NULL); run(NULL); g_main_loop_run(mainloop); dbus_tear_down(owner_id); return 0; } void pause_signal_handler(int sig) { if (sig == SIGUSR1) { pause_display = true; } if (sig == SIGUSR2) { pause_display = false; } signal(sig, pause_signal_handler); } void usage(int exit_status) { 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 customizable and lightweight notification-daemon %s\n", VERSION); exit(EXIT_SUCCESS); } /* vim: set ts=8 sw=8 tw=0: */ dunst-1.0.0/dunst.h000066400000000000000000000021471213270444200141560ustar00rootroot00000000000000/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #pragma once #include #include #include "x.h" #define ERR(msg) printf("%s : %d\n", (msg), __LINE__) #define PERR(msg, errnum) printf("(%d) %s : %s\n", __LINE__, (msg), (strerror(errnum))) #define LENGTH(X) (sizeof X / sizeof X[0]) #define ColLast 2 #define ColFG 1 #define ColBG 0 enum alignment { left, center, right }; enum separator_color { FOREGROUND, AUTO, FRAME, CUSTOM }; enum follow_mode { FOLLOW_NONE, FOLLOW_MOUSE, FOLLOW_KEYBOARD }; extern int verbosity; extern GQueue *queue; extern GQueue *displayed; extern GQueue *history; extern GSList *rules; extern bool pause_display; extern const char *color_strings[2][3]; /* return id of notification */ gboolean run(void *data); void wake_up(void); void check_timeouts(void); void history_pop(void); void usage(int exit_status); void move_all_to_history(void); void print_version(void); char *extract_urls(const char *str); void context_menu(void); void wake_up(void); void pause_signal_handler(int sig); /* vim: set ts=8 sw=8 tw=0: */ dunst-1.0.0/dunstify.c000066400000000000000000000210531213270444200146560ustar00rootroot00000000000000#include #include #include #include #include static gchar *appname = "dunstify"; static gchar *summary = NULL; static gchar *body = NULL; static NotifyUrgency urgency = NOTIFY_URGENCY_NORMAL; static gchar *urgency_str = NULL; static gchar **hint_strs = NULL; static gchar **action_strs = NULL; static gint timeout = NOTIFY_EXPIRES_DEFAULT; static gchar *icon = NULL; static gboolean capabilities = false; static gboolean serverinfo = false; static gboolean printid = false; static guint32 replace_id = 0; static guint32 close_id = 0; static gboolean block = false; static GOptionEntry entries[] = { { "appname", 'a', 0, G_OPTION_ARG_STRING, &appname, "Name of your application", "NAME" }, { "urgency", 'u', 0, G_OPTION_ARG_STRING, &urgency_str, "The urgency of this notification", "URG" }, { "hints", 'h', 0, G_OPTION_ARG_STRING_ARRAY, &hint_strs, "User specified hints", "HINT" }, { "action", 'A', 0, G_OPTION_ARG_STRING_ARRAY, &action_strs, "Actions the user can invoke", "ACTION" }, { "timeout", 't', 0, G_OPTION_ARG_INT, &timeout, "The time until the notification expires", "TIMEOUT" }, { "icon", 'i', 0, G_OPTION_ARG_STRING, &icon, "An Icon that should be displayed with the notification", "ICON" }, { "capabilities", 'c', 0, G_OPTION_ARG_NONE, &capabilities, "Print the server capabilities and exit", NULL}, { "serverinfo", 's', 0, G_OPTION_ARG_NONE, &serverinfo, "Print server information and exit", NULL}, { "printid", 'p', 0, G_OPTION_ARG_NONE, &printid, "Print id, which can be used to update/replace this notification", NULL}, { "replace", 'r', 0, G_OPTION_ARG_INT, &replace_id, "Set id of this notification.", "ID"}, { "close", 'C', 0, G_OPTION_ARG_INT, &close_id, "Set id of this notification.", "ID"}, { "block", 'b', 0, G_OPTION_ARG_NONE, &block, "Block until notification is closed and print close reason", NULL}, { NULL } }; void die(int exit_value) { if (notify_is_initted()) notify_uninit(); exit(exit_value); } void print_capabilities(void) { GList *caps = notify_get_server_caps(); for (GList *iter = caps; iter; iter = iter->next) { if (strlen(iter->data) > 0) { g_print("%s\n", iter->data); } } } void print_serverinfo(void) { char *name; char *vendor; char *version; char *spec_version; if (!notify_get_server_info(&name, &vendor, &version, &spec_version)) { g_printerr("Unable to get server information"); exit(1); } g_print("name:%s\nvendor:%s\nversion:%s\nspec_version:%s\n", name, vendor, version, spec_version); } void parse_commandline(int argc, char *argv[]) { GError *error = NULL; GOptionContext *context; context = g_option_context_new("- Dunstify"); g_option_context_add_main_entries(context, entries, NULL); if (!g_option_context_parse(context, &argc, &argv, &error)){ g_printerr("Invalid commandline: %s\n", error->message); exit(1); } g_option_context_free(context); if (capabilities) { print_capabilities(); die(0); } if (serverinfo) { print_serverinfo(); die(0); } if (argc < 2 && close_id < 1) { g_printerr("I need at least a summary\n"); die(1); } else if (argc < 2) { summary = g_strdup("These are not the summaries you are looking for"); } else { summary = g_strdup(argv[1]); } if (argc > 2) { body = g_strdup(argv[2]); } if (urgency_str) { switch (urgency_str[0]) { case 'l': case 'L': case '0': urgency = NOTIFY_URGENCY_LOW; break; case 'n': case 'N': case '1': urgency = NOTIFY_URGENCY_NORMAL; break; case 'c': case 'C': case '2': urgency = NOTIFY_URGENCY_CRITICAL; break; default: g_printerr("Unknown urgency: %s\n", urgency_str); g_printerr("Assuming normal urgency\n"); break; } } } typedef struct _NotifyNotificationPrivate { guint32 id; char *app_name; char *summary; char *body; /* NULL to use icon data. Anything else to have server lookup icon */ char *icon_name; /* * -1 = use server default * 0 = never timeout * > 0 = Number of milliseconds before we timeout */ gint timeout; GSList *actions; GHashTable *action_map; GHashTable *hints; gboolean has_nondefault_actions; gboolean updates_pending; gulong proxy_signal_handler; gint closed_reason; } knickers; int get_id(NotifyNotification *n) { knickers *kn = n->priv; /* I'm sorry for taking a peek */ return kn->id; } int put_id(NotifyNotification *n, guint32 id) { knickers *kn = n->priv; /* And know I'm putting stuff into * your knickers. I'm sorry. * I'm so sorry. * */ kn->id = id; } void actioned(NotifyNotification *n, char *a, gpointer foo) { notify_notification_close(n, NULL); g_print("%s\n", a); die(0); } void closed(NotifyNotification *n, gpointer foo) { g_print("%d\n", notify_notification_get_closed_reason(n)); die(0); } void add_action(NotifyNotification *n, char *str) { char *action = str; char *label = strstr(str, ","); if (!label || *(label+1) == '\0') { g_printerr("Malformed action. Excpected \"action,label\", got \"%s\"", str); return; } *label = '\0'; label++; notify_notification_add_action(n, action, label, actioned, NULL, NULL); } void add_hint(NotifyNotification *n, char *str) { char *type = str; char *name = strstr(str, ":"); if (!name || *(name+1) == '\0') { g_printerr("Malformed hint. Expected \"type:name:value\", got \"%s\"", str); return; } *name = '\0'; name++; char *value = strstr(name, ":"); if (!value || *(value+1) == '\0') { g_printerr("Malformed hint. Expected \"type:name:value\", got \"%s\"", str); return; } *value = '\0'; value++; if (strcmp(type, "int") == 0) notify_notification_set_hint_int32(n, name, atoi(value)); else if (strcmp(type, "double") == 0) notify_notification_set_hint_double(n, name, atof(value)); else if (strcmp(type, "string") == 0) notify_notification_set_hint_string(n, name, value); else if (strcmp(type, "byte") == 0) { gint h_byte = g_ascii_strtoull(value, NULL, 10); if (h_byte < 0 || h_byte > 0xFF) g_printerr("Not a byte: \"%s\"", value); else notify_notification_set_hint_byte(n, name, (guchar) h_byte); } else g_printerr("Malformed hint. Expected a type of int, double, string or byte, got %s\n", type); } int main(int argc, char *argv[]) { g_type_init(); parse_commandline(argc, argv); if (!notify_init(appname)) { g_printerr("Unable to initialize libnotify\n"); die(1); } NotifyNotification *n; n = notify_notification_new(summary, body, icon); notify_notification_set_timeout(n, timeout); notify_notification_set_urgency(n, urgency); GError *err = NULL; if (close_id > 0) { put_id(n, close_id); notify_notification_close(n, &err); if (err) { g_printerr("Unable to close notification: %s\n", err->message); die(1); } die(0); } if (replace_id > 0) { put_id(n, replace_id); } GMainLoop *l = NULL; if (block || action_strs) l = g_main_loop_new(NULL, false); g_signal_connect(n, "closed", G_CALLBACK(closed), NULL); if (action_strs) for (int i = 0; action_strs[i]; i++) { add_action(n, action_strs[i]); } if (hint_strs) for (int i = 0; hint_strs[i]; i++) { add_hint(n, hint_strs[i]); } notify_notification_show(n, &err); if (err) { g_printerr("Unable to send notification: %s\n", err->message); die(1); } if (block || action_strs) g_main_loop_run(l); if (printid) { g_print("%d\n", get_id(n)); } die(0); } dunst-1.0.0/dunstrc000066400000000000000000000147331213270444200142610ustar00rootroot00000000000000[global] font = Monospace 8 # allow a small subset of html markup: # bold # italic # strikethrough # underline # # for a complete reference see http://developer.gnome.org/pango/stable/PangoMarkupFormat.html # If markup is not allowed, those tags will be stripped out of the message. allow_markup = yes # The format of the message. Possible variables are: # %a appname # %s summary # %b body # %i iconname (including its path) # %I iconname (without its path) # %p progress value if set ([ 0%] to [100%]) or nothing # Markup is allowed format = "%s\n%b" # Sort messages by urgency sort = yes # Show how many messages are currently hidden (because of geometry) indicate_hidden = yes # alignment of message text. # Possible values are "left", "center" and "right" alignment = left # The frequency with wich text that is longer than the notification # window allows bounces back and forth. # This option conflicts with 'word_wrap'. # Set to 0 to disable bounce_freq = 0 # show age of message if message is older than show_age_threshold seconds. # set to -1 to disable show_age_threshold = 60 # split notifications into multiple lines if they don't fit into geometry word_wrap = yes # ignore newlines '\n' in notifications ignore_newline = no # the geometry of the window # geometry [{width}]x{height}][+/-{x}+/-{y}] # The geometry of the message window. # The height is measured in number of notifications everything else in pixels. If the width # is omitted but the height is given ("-geometry x2"), the message window # expands over the whole screen (dmenu-like). If width is 0, # 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 = "300x5-30+20" # The transparency of the window. range: [0; 100] # This option will only work if a compositing windowmanager is present (e.g. xcompmgr, compiz, etc..) transparency = 0 # Don't remove messages, if the user is idle (no mouse or keyboard input) # for longer than idle_threshold seconds. # Set to 0 to disable. idle_threshold = 120 # Which monitor should the notifications be displayed on. monitor = 0 # Display notification on focused monitor. Possible modes are: # mouse: follow mouse pointer # keyboard: follow window with keyboard focus # none: don't follow anything # # "keyboard" needs a windowmanager that exports the _NET_ACTIVE_WINDOW property. # This should be the case for almost all modern windowmanagers. # # If this option is set to mouse or keyboard, the monitor option will be # ignored. follow = mouse # should a notification popped up from history be sticky or # timeout as if it would normally do. sticky_history = yes # The height of a single line. If the height is smaller than the font height, # it will get raised to the font height. # This adds empty space above and under the text. line_height = 0 # Draw a line of 'separatpr_height' pixel height between two notifications. # Set to 0 to disable separator_height = 2 # padding between text and separator padding = 8 # horizontal padding horizontal_padding = 8 # Define a color for the separator. # possible values are: # * auto: dunst tries to find a color fitting to the background # * foreground: use the same color as the foreground # * frame: use the same color as the frame. # * anything else will be interpreted as a X color separator_color = frame # 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 [frame] width = 3 color = "#aaaaaa" [shortcuts] # shortcuts are specified as [modifier+][modifier+]...key # available modifiers are 'ctrl', 'mod1' (the alt-key), 'mod2', 'mod3' # and 'mod4' (windows-key) # xev might be helpful to find names for keys # close notification close = ctrl+space # close all notifications close_all = ctrl+shift+space # redisplay last message(s) # 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. background = "#222222" foreground = "#888888" timeout = 10 [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 [urgency_critical] background = "#900000" foreground = "#ffffff" timeout = 0 # Every section that isn't one of the above is interpreted as a rules # to override settings for certain messages. # Messages can be matched by 'appname', 'summary', 'body' or 'icon' # and you can override the 'timeout', 'urgency', 'foreground', 'background' # and 'format'. # Shell-like globbing will get expanded. # # SCRIPTING # you can specify a script that gets run when the rule matches by setting # the 'script' option. # The script will be called as follows: # script appname summary body icon urgency # where urgency can be "LOW", "NORMAL" or "CRITICAL". # # NOTE: if you don't want a notification to be displayed, set the format to "" # NOTE: It might be helpful to run dunst -print in a terminal in order to find # fitting options for rules. #[espeak] # summary = "*" # script = dunst_espeak.sh #[script-test] # summary = "*script*" # script = dunst_test.sh #[ignore] ## This notification will not be displayed # summary = "foobar" # format = "" #[signed_on] # appname = Pidgin # summary = "*signed on*" # urgency = low # #[signed_off] # appname = Pidgin # summary = *signed off* # urgency = low # #[says] # appname = Pidgin # summary = *says* # urgency = critical # #[twitter] # appname = Pidgin # summary = *twitter.com* # urgency = normal # dunst-1.0.0/menu.c000066400000000000000000000154501213270444200137610ustar00rootroot00000000000000/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "dunst.h" #include "utils.h" #include "settings.h" #include "dbus.h" /* * Exctract all urls from a given string. * * Return: a string of urls separated by \n * */ char *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:]\"<>]{2,}"; int ret = regcomp(&cregex, regex, REG_EXTENDED | REG_ICASE); if (ret != 0) { printf("failed to compile regex\n"); return NULL; } else { is_initialized = true; } } char *urls = NULL; 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); urls = string_append(urls, match, "\n"); p += m.rm_eo; } return urls; } /* * Open url in browser. * */ void open_browser(const char *url) { int browser_pid1 = fork(); if (browser_pid1) { int status; waitpid(browser_pid1, &status, 0); } else { int browser_pid2 = fork(); if (browser_pid2) { exit(0); } else { char *browser_cmd = string_append(settings.browser, url, " "); char **cmd = g_strsplit(browser_cmd, " ", 0); execvp(cmd[0], cmd); } } } /* * Notify the corresponding client * that an action has been invoked */ void invoke_action(const char *action) { notification *invoked = NULL; char *action_identifier = NULL; char *appname_begin = strchr(action, '['); if (!appname_begin) { printf("invalid action: %s\n", action); return; } appname_begin++; int appname_len = strlen(appname_begin) - 1; // remove ] int action_len = strlen(action) - appname_len - 3; // remove space, [, ] for (GList * iter = g_queue_peek_head_link(displayed); iter; iter = iter->next) { notification *n = iter->data; if (g_str_has_prefix(appname_begin, n->appname) && strlen(n->appname) == appname_len) { if (!n->actions) continue; for (int i = 0; i < n->actions->count; i += 2) { char *a_identifier = n->actions->actions[i]; char *name = n->actions->actions[i + 1]; if (g_str_has_prefix(action, name) && strlen(name) == action_len) { invoked = n; action_identifier = a_identifier; break; } } } } if (invoked && action_identifier) { actionInvoked(invoked, action_identifier); } } /* * Dispatch whatever has been returned * by the menu. */ void dispatch_menu_result(const char *input) { char *in = strdup(input); g_strstrip(in); switch (in[0]) { case '#': invoke_action(in + 1); break; case '[': // named url. skip name and continue in = strchr(in, ']'); if (in == NULL) break; default: { // test and open url char *maybe_url = extract_urls(in); if (maybe_url) { open_browser(maybe_url); free(maybe_url); break; } } } free(in); } /* * Open the context menu that let's the user * select urls/actions/etc */ void context_menu(void) { char *dmenu_input = NULL; for (GList * iter = g_queue_peek_head_link(displayed); iter; iter = iter->next) { notification *n = iter->data; dmenu_input = string_append(dmenu_input, n->urls, "\n"); if (n->actions) dmenu_input = string_append(dmenu_input, n->actions->dmenu_str, "\n"); } if (!dmenu_input) return; char buf[1024] = {0}; int child_io[2]; int parent_io[2]; if (pipe(child_io) != 0) { PERR("pipe()", errno); return; } if (pipe(parent_io) != 0) { PERR("pipe()", errno); return; } int pid = fork(); if (pid == 0) { close(child_io[1]); close(parent_io[0]); close(0); if (dup(child_io[0]) == -1) { PERR("dup()", errno); exit(EXIT_FAILURE); } close(1); if (dup(parent_io[1]) == -1) { PERR("dup()", errno); exit(EXIT_FAILURE); } execvp(settings.dmenu_cmd[0], settings.dmenu_cmd); } else { close(child_io[0]); close(parent_io[1]); size_t wlen = strlen(dmenu_input); if (write(child_io[1], dmenu_input, wlen) != wlen) { PERR("write()", errno); } close(child_io[1]); size_t len = read(parent_io[0], buf, 1023); if (len == 0) return; int status; waitpid(pid, &status, 0); } close(parent_io[0]); dispatch_menu_result(buf); } /* vim: set ts=8 sw=8 tw=0: */ dunst-1.0.0/menu.h000066400000000000000000000003701213270444200137610ustar00rootroot00000000000000/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "dunst.h" #include char *extract_urls(const char *to_match); void open_browser(const char *url); void invoke_action(const char *action); dunst-1.0.0/notification.c000066400000000000000000000405361213270444200155060ustar00rootroot00000000000000/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include "dbus.h" #include "x.h" #include "notification.h" #include "dunst.h" #include "utils.h" #include "settings.h" #include "rules.h" #include "menu.h" int next_notification_id = 1; /* * print a human readable representation * of the given notification to stdout. */ void notification_print(notification * n) { printf("{\n"); printf("\tappname: '%s'\n", n->appname); printf("\tsummary: '%s'\n", n->summary); printf("\tbody: '%s'\n", n->body); printf("\ticon: '%s'\n", n->icon); printf("\turgency: %d\n", n->urgency); printf("\tformatted: '%s'\n", n->msg); printf("\tfg: %s\n", n->color_strings[ColFG]); printf("\tbg: %s\n", n->color_strings[ColBG]); printf("\tid: %d\n", n->id); if (n->urls) { printf("\turls\n"); printf("\t{\n"); printf("%s\n", n->urls); printf("\t}\n"); } if (n->actions) { printf("\tactions:\n"); printf("\t{\n"); for (int i = 0; i < n->actions->count; i += 2) { printf("\t\t [%s,%s]\n", n->actions->actions[i], n->actions->actions[i + 1]); } printf("actions_dmenu: %s\n", n->actions->dmenu_str); printf("\t]\n"); } printf("\tscript: %s\n", n->script); printf("}\n"); } /* * Run the script associated with the * given notification. */ void notification_run_script(notification * n) { if (!n->script || strlen(n->script) < 1) return; char *appname = n->appname ? n->appname : ""; char *summary = n->summary ? n->summary : ""; char *body = n->body ? n->body : ""; char *icon = n->icon ? n->icon : ""; char *urgency; switch (n->urgency) { case LOW: urgency = "LOW"; break; case NORM: urgency = "NORMAL"; break; case CRIT: urgency = "CRITICAL"; break; default: urgency = "NORMAL"; break; } int pid1 = fork(); if (pid1) { int status; waitpid(pid1, &status, 0); } else { int pid2 = fork(); if (pid2) { exit(0); } else { int ret = execlp(n->script, n->script, appname, summary, body, icon, urgency, (char *)NULL); if (ret != 0) { PERR("Unable to run script", errno); exit(EXIT_FAILURE); } } } } /* * Helper function to compare to given * notifications. */ int notification_cmp(const void *va, const void *vb) { notification *a = (notification *) va; notification *b = (notification *) vb; if (!settings.sort) return 1; if (a->urgency != b->urgency) { return b->urgency - a->urgency; } else { return a->id - b->id; } } /* * Wrapper for notification_cmp to match glib's * compare functions signature. */ int notification_cmp_data(const void *va, const void *vb, void *data) { return notification_cmp(va, vb); } /* * Free the memory used by the given notification. */ void notification_free(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); } /* * Strip any markup from text */ char *notification_fix_markup(char *str) { char *replace_buf, *start, *end; if (str == NULL) { return NULL; } str = string_replace_all(""", "\"", str); str = string_replace_all("'", "'", str); str = string_replace_all("&", "&", str); str = string_replace_all("<", "<", str); str = string_replace_all(">", ">", str); /* remove tags */ str = string_replace_all("", "", str); str = string_replace_all("", "", str); str = string_replace_all("
", " ", str); str = string_replace_all("
", " ", str); str = string_replace_all("
", " ", str); str = string_replace_all("", "", str); str = string_replace_all("", "", str); str = string_replace_all("", "", str); str = string_replace_all("", "", str); str = string_replace_all("", "", str); while ((start = strstr(str, ""); if (end != NULL) { replace_buf = strndup(start, end - start + 1); str = string_replace(replace_buf, "", str); free(replace_buf); } else { break; } } while ((start = strstr(str, ""); if (end != NULL) { replace_buf = strndup(start, end - start + 2); str = string_replace(replace_buf, "", str); free(replace_buf); } else { break; } } return str; } char *notification_extract_markup_urls(char **str_ptr) { char *start, *end, *replace_buf, *str, *urls = NULL, *url, *index_buf; int linkno = 1; str = *str_ptr; while ((start = strstr(str, ""); if (end != NULL) { replace_buf = strndup(start, end - start + 1); url = extract_urls(replace_buf); if (url != NULL) { str = string_replace(replace_buf, "[", str); index_buf = g_strdup_printf("[#%d]", linkno++); if (urls == NULL) { urls = g_strconcat(index_buf, " ", url, NULL); } else { char *tmp = urls; urls = g_strconcat(tmp, "\n", index_buf, " ", url, NULL); free(tmp); } index_buf[0] = ' '; str = string_replace("", index_buf, str); free(index_buf); free(url); } else { str = string_replace(replace_buf, "", str); str = string_replace("", "", str); } free(replace_buf); } else { break; } } *str_ptr = str; return urls; } /* * Initialize the given notification and add it to * the queue. Replace notification with id if id > 0. */ int notification_init(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->script = NULL; n->text_to_render = NULL; n->format = settings.format; rule_apply_all(n); n->urls = notification_extract_markup_urls(&(n->body)); n->msg = string_replace("%a", n->appname, g_strdup(n->format)); n->msg = string_replace("%s", n->summary, 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); n->msg = string_replace("%p", pg, n->msg); } else { n->msg = string_replace("%p", "", n->msg); } if (!settings.allow_markup) n->msg = notification_fix_markup(n->msg); else if (!settings.ignore_newline) { n->msg = string_replace("
", "\n", n->msg); n->msg = string_replace("
", "\n", n->msg); } while (strstr(n->msg, "\\n") != NULL) n->msg = string_replace("\\n", "\n", n->msg); if (settings.ignore_newline) while (strstr(n->msg, "\n") != NULL) n->msg = string_replace("\n", " ", n->msg); n->msg = g_strstrip(n->msg); n->dup_count = 0; /* check if n is a duplicate */ for (GList * iter = g_queue_peek_head_link(queue); iter; iter = iter->next) { notification *orig = iter->data; if (strcmp(orig->appname, n->appname) == 0 && strcmp(orig->msg, n->msg) == 0) { orig->dup_count++; notification_free(n); wake_up(); return orig->id; } } for (GList * iter = g_queue_peek_head_link(displayed); iter; iter = iter->next) { notification *orig = iter->data; if (strcmp(orig->appname, n->appname) == 0 && strcmp(orig->msg, n->msg) == 0) { orig->dup_count++; orig->start = time(NULL); notification_free(n); wake_up(); return orig->id; } } /* urgency > CRIT -> array out of range */ n->urgency = n->urgency > CRIT ? CRIT : n->urgency; if (!n->color_strings[ColFG]) { n->color_strings[ColFG] = xctx.color_strings[ColFG][n->urgency]; } if (!n->color_strings[ColBG]) { n->color_strings[ColBG] = xctx.color_strings[ColBG][n->urgency]; } n->timeout = n->timeout == -1 ? settings.timeouts[n->urgency] : n->timeout; n->start = 0; n->timestamp = time(NULL); n->redisplayed = false; if (id == 0) { n->id = ++next_notification_id; } else { notification_close_by_id(id, -1); n->id = id; } if (strlen(n->msg) == 0) { notification_close(n, 2); printf("skipping notification: %s %s\n", n->body, n->summary); } else { g_queue_insert_sorted(queue, n, notification_cmp_data, NULL); } char *tmp = g_strconcat(n->summary, " ", n->body, NULL); char *tmp_urls = extract_urls(tmp); if (tmp_urls != NULL) { if (n->urls != NULL) { n->urls = string_append(n->urls, tmp_urls, "\n"); free(tmp_urls); } else { n->urls = tmp_urls; } } if (n->actions) { n->actions->dmenu_str = NULL; for (int i = 0; i < n->actions->count; i += 2) { char *human_readable = n->actions->actions[i + 1]; string_replace_char('[', '(', human_readable); // kill square brackets string_replace_char(']', ')', human_readable); n->actions->dmenu_str = string_append(n->actions->dmenu_str, g_strdup_printf("#%s [%s]", human_readable, n->appname), "\n"); } } free(tmp); if (settings.print_notifications) notification_print(n); return n->id; } /* * Close the notification that has 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 notification_close_by_id(int id, int reason) { notification *target = NULL; for (GList * iter = g_queue_peek_head_link(displayed); iter; iter = iter->next) { notification *n = iter->data; if (n->id == id) { g_queue_remove(displayed, n); g_queue_push_tail(history, n); target = n; break; } } for (GList * iter = g_queue_peek_head_link(queue); iter; iter = iter->next) { notification *n = iter->data; if (n->id == id) { g_queue_remove(queue, n); g_queue_push_tail(history, n); target = n; break; } } if (reason > 0 && reason < 4 && target != NULL) { notificationClosed(target, reason); } wake_up(); return reason; } /* * Close the given notification. SEE notification_close_by_id. */ int notification_close(notification * n, int reason) { if (n == NULL) return -1; return notification_close_by_id(n->id, reason); } void notification_update_text_to_render(notification *n) { if (n->text_to_render) { free(n->text_to_render); n->text_to_render = NULL; } char *buf = NULL; char *msg = g_strstrip(n->msg); /* print dup_count and msg */ if (n->dup_count > 0 && (n->actions || n->urls)) { buf = g_strdup_printf("(%d%s%s) %s", n->dup_count, n->actions ? "A" : "", n->urls ? "U" : "", msg); } else if (n->actions || n->urls) { buf = g_strdup_printf("(%s%s) %s", n->actions ? "A" : "", n->urls ? "U" : "", msg); } else if (n->dup_count > 0) { buf = g_strdup_printf("(%d) %s", n->dup_count, msg); } else { buf = g_strdup(msg); } /* print age */ int hours, minutes, seconds; time_t t_delta = time(NULL) - n->timestamp; if (settings.show_age_threshold >= 0 && t_delta >= settings.show_age_threshold) { hours = t_delta / 3600; minutes = t_delta / 60 % 60; seconds = t_delta % 60; char *new_buf; if (hours > 0) { new_buf = g_strdup_printf("%s (%dh %dm %ds old)", buf, hours, minutes, seconds); } else if (minutes > 0) { new_buf = g_strdup_printf("%s (%dm %ds old)", buf, minutes, seconds); } else { new_buf = g_strdup_printf("%s (%ds old)", buf, seconds); } free(buf); buf = new_buf; } n->text_to_render = buf; } int notification_get_ttl(notification *n) { if (n->timeout == 0) { return 0; } else { return n->timeout - (time(NULL) - n->start); } } int notification_get_age(notification *n) { return time(NULL) - n->timestamp; } /* vim: set ts=8 sw=8 tw=0: */ dunst-1.0.0/notification.h000066400000000000000000000027551213270444200155140ustar00rootroot00000000000000/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #pragma once #include "x.h" #define LOW 0 #define NORM 1 #define CRIT 2 typedef struct _actions { char **actions; char *dmenu_str; gsize count; } Actions; typedef struct _notification { char *appname; char *summary; char *body; char *icon; char *msg; /* formatted message */ char *text_to_render; const char *format; char *dbus_client; time_t start; time_t timestamp; int timeout; int urgency; bool redisplayed; /* has been displayed before? */ int id; int dup_count; int displayed_height; char *color_strings[2]; int progress; /* percentage + 1, 0 to hide */ int line_count; const char *script; char *urls; Actions *actions; } notification; int notification_init(notification * n, int id); int notification_close_by_id(int id, int reason); int notification_cmp(const void *a, const void *b); int notification_cmp_data(const void *a, const void *b, void *data); void notification_run_script(notification * n); int notification_close(notification * n, int reason); void notification_print(notification * n); char *notification_fix_markup(char *str); void notification_update_text_to_render(notification *n); int notification_get_ttl(notification *n); int notification_get_age(notification *n); dunst-1.0.0/option_parser.c000066400000000000000000000311131213270444200156730ustar00rootroot00000000000000/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #define _GNU_SOURCE #include #include #include #include #include #include #include "option_parser.h" #include "utils.h" typedef struct _entry_t { char *key; char *value; } entry_t; typedef struct _section_t { char *name; int entry_count; entry_t *entries; } section_t; static int section_count = 0; static section_t *sections; static section_t *new_section(char *name); static section_t *get_section(char *name); 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[section_count - 1].name = g_strdup(name); sections[section_count - 1].entries = NULL; sections[section_count - 1].entry_count = 0; return §ions[section_count - 1]; } void free_ini(void) { 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); } free(sections[i].entries); free(sections[i].name); } free(sections); } section_t *get_section(char *name) { for (int i = 0; i < section_count; i++) { if (strcmp(sections[i].name, name) == 0) return §ions[i]; } return NULL; } void add_entry(char *section_name, char *key, char *value) { section_t *s = get_section(section_name); if (s == NULL) { s = new_section(section_name); } s->entry_count++; int len = s->entry_count; s->entries = realloc(s->entries, sizeof(entry_t) * len); s->entries[s->entry_count - 1].key = g_strdup(key); s->entries[s->entry_count - 1].value = clean_value(value); } char *get_value(char *section, char *key) { section_t *s = get_section(section); if (!s) { return NULL; } for (int i = 0; i < s->entry_count; i++) { if (strcmp(s->entries[i].key, key) == 0) { return s->entries[i].value; } } return NULL; } char *ini_get_string(char *section, char *key, const char *def) { char *value = get_value(section, key); if (value) return g_strdup(value); return def ? g_strdup(def) : NULL; } int ini_get_int(char *section, char *key, int def) { char *value = get_value(section, key); if (value == NULL) return def; else return atoi(value); } double ini_get_double(char *section, char *key, double def) { char *value = get_value(section, key); if (value == NULL) return def; else return atof(value); } char *next_section(char *section) { if (section_count == 0) return NULL; if (section == NULL) { return sections[0].name; } for (int i = 0; i < section_count; i++) { if (strcmp(section, sections[i].name) == 0) { if (i + 1 >= section_count) return NULL; else return sections[i + 1].name; } } return NULL; } int ini_get_bool(char *section, char *key, int def) { char *value = get_value(section, key); if (value == NULL) return def; else { 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; } } } char *clean_value(char *value) { char *s; if (value[0] == '"') s = g_strdup(value + 1); else s = g_strdup(value); if (s[strlen(s) - 1] == '"') s[strlen(s) - 1] = '\0'; return s; } int load_ini_file(FILE * fp) { char line[BUFSIZ]; if (!fp) return 1; int line_num = 0; char *current_section = NULL; while (fgets(line, sizeof(line), fp) != NULL) { line_num++; char *start = g_strstrip(line); if (*start == ';' || *start == '#' || strlen(start) == 0) continue; if (*start == '[') { char *end = strstr(start + 1, "]"); if (!end) { printf ("Warning: invalid config file at line %d\n", line_num); printf("Missing ']'\n"); continue; } *end = '\0'; if (current_section) free(current_section); current_section = (g_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("Missing '='\n"); continue; } *equal = '\0'; char *key = g_strstrip(start); char *value = g_strstrip(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("Missing '\"'\n"); continue; } closing_quote = '\0'; } else { char *comment = strstr(value, "#"); if (!comment) comment = strstr(value, ";"); if (comment) comment = '\0'; } value = g_strstrip(value); if (!current_section) { printf("Warning: invalid config file at line: %d\n", line_num); printf("Key value pair without a section\n"); continue; } add_entry(current_section, key, value); } if (current_section) 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 = g_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) { /* 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 g_strdup(str); if (def == NULL) return NULL; else return g_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) key_type = g_strdup_printf("%s (%s)", key, type); else key_type = g_strdup(key); if (!usage_str) { usage_str = g_strdup_printf("%-40s - %s\n", key_type, description); free(key_type); return; } char *tmp; tmp = g_strdup_printf("%s%-40s - %s\n", usage_str, key_type, description); free(key_type); free(usage_str); usage_str = tmp; } char *cmdline_create_usage(void) { return g_strdup(usage_str); } /* vim: set ts=8 sw=8 tw=0: */ dunst-1.0.0/option_parser.h000066400000000000000000000027771213270444200157160ustar00rootroot00000000000000/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #pragma once #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, 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 */ char *next_section(char *section); /* vim: set ts=8 sw=8 tw=0: */ dunst-1.0.0/org.knopwob.dunst.service.in000066400000000000000000000001151213270444200202310ustar00rootroot00000000000000[D-BUS Service] Name=org.freedesktop.Notifications Exec=##PREFIX##/bin/dunst dunst-1.0.0/rules.c000066400000000000000000000033021213270444200141400ustar00rootroot00000000000000/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include #include #include "dunst.h" #include "rules.h" /* * Apply rule to notification. */ void rule_apply(rule_t * r, notification * n) { if (r->timeout != -1) n->timeout = r->timeout; if (r->urgency != -1) n->urgency = r->urgency; if (r->fg) n->color_strings[ColFG] = r->fg; if (r->bg) n->color_strings[ColBG] = r->bg; if (r->format) n->format = r->format; if (r->script) n->script = r->script; } /* * Check all rules if they match n and apply. */ void rule_apply_all(notification * n) { for (GSList * iter = rules; iter; iter = iter->next) { rule_t *r = iter->data; if (rule_matches_notification(r, n)) { rule_apply(r, n); } } } /* * Initialize rule with default values. */ void rule_init(rule_t * r) { 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; } /* * Check whether rule should be applied to n. */ bool rule_matches_notification(rule_t * r, notification * n) { return ((!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))); } /* vim: set ts=8 sw=8 tw=0: */ dunst-1.0.0/rules.h000066400000000000000000000012621213270444200141500ustar00rootroot00000000000000/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #pragma once #include #include "dunst.h" #include "notification.h" typedef struct _rule_t { char *name; /* filters */ char *appname; char *summary; char *body; char *icon; /* actions */ int timeout; int urgency; char *fg; char *bg; const char *format; const char *script; } rule_t; extern GSList *rules; void rule_init(rule_t * r); void rule_apply(rule_t * r, notification * n); void rule_apply_all(notification * n); bool rule_matches_notification(rule_t * r, notification * n); dunst-1.0.0/settings.c000066400000000000000000000335151213270444200146570ustar00rootroot00000000000000/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include #ifndef STATIC_CONFIG #include #include #endif #include "dunst.h" #include "rules.h" #include "option_parser.h" #include "settings.h" #include "config.h" #include "utils.h" settings_t settings; static void parse_follow_mode(const char *mode) { if (strcmp(mode, "mouse") == 0) settings.f_mode = FOLLOW_MOUSE; else if (strcmp(mode, "keyboard") == 0) settings.f_mode = FOLLOW_KEYBOARD; else if (strcmp(mode, "none") == 0) settings.f_mode = FOLLOW_NONE; else { fprintf(stderr, "Warning: unknown follow mode: \"%s\"\n", mode); settings.f_mode = FOLLOW_NONE; } } void load_settings(char *cmdline_config_path) { #ifndef STATIC_CONFIG 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 2013-06-23 * (before v0.2). */ config_file = xdgConfigOpen("dunstrc", "r", &xdg); if (config_file == NULL) { puts("no dunstrc found -> skipping\n"); xdgWipeHandle(&xdg); } } load_ini_file(config_file); #endif settings.font = option_get_string("global", "font", "-fn", font, "The font dunst should use."); settings.allow_markup = option_get_bool("global", "allow_markup", "-markup", allow_markup, "Allow markups."); settings.format = option_get_string("global", "format", "-format", format, "The format template for the notifictions"); settings.sort = option_get_bool("global", "sort", "-sort", sort, "Sort notifications by urgency and date?"); settings.indicate_hidden = option_get_bool("global", "indicate_hidden", "-indicate_hidden", indicate_hidden, "Show how many notificaitons are hidden?"); settings.word_wrap = option_get_bool("global", "word_wrap", "-word_wrap", word_wrap, "Truncating long lines or do word wrap"); settings.ignore_newline = option_get_bool("global", "ignore_newline", "-ignore_newline", ignore_newline, "Ignore newline characters in notifications"); settings.idle_threshold = option_get_int("global", "idle_threshold", "-idle_threshold", idle_threshold, "Don't timeout notifications if user is longer idle than threshold"); settings.monitor = option_get_int("global", "monitor", "-mon", monitor, "On which monitor should the notifications be displayed"); { char *c = option_get_string("global", "follow", "-follow", "", "Follow mouse, keyboard or none?"); if (strlen(c) > 0) { parse_follow_mode(c); free(c); } } settings.geom = option_get_string("global", "geometry", "-geom/-geometry", geom, "Geometry for the window"); settings.line_height = option_get_int("global", "line_height", "-lh/-line_height", line_height, "Add additional padding above and beneath text"); settings.bounce_freq = option_get_double("global", "bounce_freq", "-bounce_freq", bounce_freq, "Make long text bounce from side to side"); { char *c = option_get_string("global", "alignment", "-align/-alignment", "", "Align notifications left/center/right"); if (strlen(c) > 0) { if (strcmp(c, "left") == 0) settings.align = left; else if (strcmp(c, "center") == 0) settings.align = center; else if (strcmp(c, "right") == 0) settings.align = right; else fprintf(stderr, "Warning: unknown allignment\n"); free(c); } } settings.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?"); settings.sticky_history = option_get_bool("global", "sticky_history", "-sticky_history", sticky_history, "Don't timeout notifications popped up from history"); settings.separator_height = option_get_int("global", "separator_height", "-sep_height/-separator_height", separator_height, "height of the separator line"); settings.padding = option_get_int("global", "padding", "-padding", padding, "Padding between text and separator"); settings.h_padding = option_get_int("global", "horizontal_padding", "-horizontal_padding", h_padding, "horizontal padding"); settings.transparency = option_get_int("global", "transparency", "-transparency", transparency, "Transparency. range 0-100"); { 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) settings.sep_color = AUTO; else if (strcmp(c, "foreground") == 0) settings.sep_color = FOREGROUND; else if (strcmp(c, "frame") == 0) settings.sep_color = FRAME; else { settings.sep_color = CUSTOM; settings.sep_custom_color_str = g_strdup(c); } free(c); } } settings.startup_notification = option_get_bool("global", "startup_notification", "-startup_notification", false, "print notification on startup"); settings.dmenu = option_get_string("global", "dmenu", "-dmenu", dmenu, "path to dmenu"); settings.dmenu_cmd = string_to_argv(settings.dmenu); settings.browser = option_get_string("global", "browser", "-browser", browser, "path to browser"); settings.frame_width = option_get_int("frame", "width", "-frame_width", frame_width, "Width of frame around window"); settings.frame_color = option_get_string("frame", "color", "-frame_color", frame_color, "Color of the frame around window"); settings.lowbgcolor = option_get_string("urgency_low", "background", "-lb", lowbgcolor, "Background color for notifcations with low urgency"); settings.lowfgcolor = option_get_string("urgency_low", "foreground", "-lf", lowfgcolor, "Foreground color for notifications with low urgency"); settings.timeouts[LOW] = option_get_int("urgency_low", "timeout", "-lto", timeouts[LOW], "Timeout for notifications with low urgency"); settings.normbgcolor = option_get_string("urgency_normal", "background", "-nb", normbgcolor, "Background color for notifications with normal urgency"); settings.normfgcolor = option_get_string("urgency_normal", "foreground", "-nf", normfgcolor, "Foreground color for notifications with normal urgency"); settings.timeouts[NORM] = option_get_int("urgency_normal", "timeout", "-nto", timeouts[NORM], "Timeout for notifications with normal urgency"); settings.critbgcolor = option_get_string("urgency_critical", "background", "-cb", critbgcolor, "Background color for notifications with critical urgency"); settings.critfgcolor = option_get_string("urgency_critical", "foreground", "-cf", critfgcolor, "Foreground color for notifications with ciritical urgency"); settings.timeouts[CRIT] = option_get_int("urgency_critical", "timeout", "-cto", timeouts[CRIT], "Timeout for notifications with critical urgency"); settings.close_ks.str = option_get_string("shortcuts", "close", "-key", close_ks.str, "Shortcut for closing one notification"); settings.close_all_ks.str = option_get_string("shortcuts", "close_all", "-all_key", close_all_ks.str, "Shortcut for closing all notifications"); settings.history_ks.str = option_get_string("shortcuts", "history", "-history_key", history_ks.str, "Shortcut to pop the last notification from history"); settings.context_ks.str = option_get_string("shortcuts", "context", "-context_key", context_ks.str, "Shortcut for context menu"); settings.print_notifications = cmdline_get_bool("-print", false, "Print notifications to cmdline (DEBUG)"); /* push hardcoded default rules into rules list */ for (int i = 0; i < LENGTH(default_rules); i++) { rules = g_slist_insert(rules, &(default_rules[i]), 0); } char *cur_section = NULL; for (;;) { cur_section = next_section(cur_section); 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) continue; /* check for existing rule with same name */ rule_t *r = NULL; for (GSList * iter = rules; iter; iter = iter->next) { rule_t *match = iter->data; if (match->name && strcmp(match->name, cur_section) == 0) r = match; } if (r == NULL) { r = g_malloc(sizeof(rule_t)); rule_init(r); rules = g_slist_insert(rules, r, 0); } r->name = g_strdup(cur_section); 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) r->urgency = LOW; else if (strcmp(urg, "normal") == 0) r->urgency = NORM; else if (strcmp(urg, "critical") == 0) r->urgency = CRIT; else fprintf(stderr, "unknown urgency: %s, ignoring\n", urg); free(urg); } } 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); r->script = ini_get_string(cur_section, "script", NULL); } #ifndef STATIC_CONFIG if (config_file) { fclose(config_file); free_ini(); xdgWipeHandle(&xdg); } #endif } /* vim: set ts=8 sw=8 tw=0: */ dunst-1.0.0/settings.h000066400000000000000000000025661213270444200146660ustar00rootroot00000000000000/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #pragma once typedef struct _settings { bool print_notifications; bool allow_markup; char *font; char *normbgcolor; char *normfgcolor; char *critbgcolor; char *critfgcolor; char *lowbgcolor; char *lowfgcolor; char *format; int timeouts[3]; unsigned int transparency; char *geom; int sort; int indicate_hidden; int idle_threshold; int show_age_threshold; enum alignment align; float bounce_freq; int sticky_history; int verbosity; int word_wrap; int ignore_newline; int line_height; int separator_height; int padding; int h_padding; enum separator_color sep_color; char *sep_custom_color_str; char *sep_color_str; int frame_width; char *frame_color; int startup_notification; int monitor; char *dmenu; char **dmenu_cmd; char *browser; enum follow_mode f_mode; keyboard_shortcut close_ks; keyboard_shortcut close_all_ks; keyboard_shortcut history_ks; keyboard_shortcut context_ks; } settings_t; extern settings_t settings; void load_settings(char *cmdline_config_path); dunst-1.0.0/test/000077500000000000000000000000001213270444200136235ustar00rootroot00000000000000dunst-1.0.0/test/dunstrc.default000066400000000000000000000020121213270444200166460ustar00rootroot00000000000000[global] font = Monospace 8 allow_markup = yes format = "%s\n%b" sort = yes indicate_hidden = yes alignment = left bounce_freq = 0 show_age_threshold = 60 word_wrap = yes ignore_newline = no geometry = "300x5-30+20" transparency = 0 idle_threshold = 120 monitor = 0 follow = mouse sticky_history = yes line_height = 0 separator_height = 2 padding = 8 horizontal_padding = 8 separator_color = frame startup_notification = false dmenu = /usr/bin/dmenu -p dunst browser = /usr/bin/firefox -new-tab [frame] width = 3 color = "#aaaaaa" [shortcuts] close = ctrl+space close_all = ctrl+shift+space history = ctrl+grave context = ctrl+shift+period [urgency_low] background = "#222222" foreground = "#888888" timeout = 10 [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 [urgency_critical] background = "#900000" foreground = "#ffffff" timeout = 0 dunst-1.0.0/test/dunstrc.ignore_newline000066400000000000000000000020131213270444200202270ustar00rootroot00000000000000[global] font = Monospace 8 allow_markup = yes format = "%s\n%b" sort = yes indicate_hidden = yes alignment = left bounce_freq = 0 show_age_threshold = 60 word_wrap = yes ignore_newline = yes geometry = "200x0-30+20" transparency = 0 idle_threshold = 120 monitor = 0 follow = mouse sticky_history = yes line_height = 0 separator_height = 2 padding = 8 horizontal_padding = 8 separator_color = frame startup_notification = false dmenu = /usr/bin/dmenu -p dunst browser = /usr/bin/firefox -new-tab [frame] width = 3 color = "#aaaaaa" [shortcuts] close = ctrl+space close_all = ctrl+shift+space history = ctrl+grave context = ctrl+shift+period [urgency_low] background = "#222222" foreground = "#888888" timeout = 10 [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 [urgency_critical] background = "#900000" foreground = "#ffffff" timeout = 0 dunst-1.0.0/test/dunstrc.ignore_newline_no_wrap000066400000000000000000000020121213270444200217530ustar00rootroot00000000000000[global] font = Monospace 8 allow_markup = yes format = "%s\n%b" sort = yes indicate_hidden = yes alignment = left bounce_freq = 0 show_age_threshold = 60 word_wrap = no ignore_newline = yes geometry = "250x0-30+20" transparency = 0 idle_threshold = 120 monitor = 0 follow = mouse sticky_history = yes line_height = 0 separator_height = 2 padding = 8 horizontal_padding = 8 separator_color = frame startup_notification = false dmenu = /usr/bin/dmenu -p dunst browser = /usr/bin/firefox -new-tab [frame] width = 3 color = "#aaaaaa" [shortcuts] close = ctrl+space close_all = ctrl+shift+space history = ctrl+grave context = ctrl+shift+period [urgency_low] background = "#222222" foreground = "#888888" timeout = 10 [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 [urgency_critical] background = "#900000" foreground = "#ffffff" timeout = 0 dunst-1.0.0/test/dunstrc.markup000066400000000000000000000020031213270444200165210ustar00rootroot00000000000000[global] font = Monospace 8 allow_markup = yes format = "%s\n%b" sort = yes indicate_hidden = yes alignment = left bounce_freq = 0 show_age_threshold = 60 word_wrap = yes ignore_newline = no geometry = "300x5-30+20" transparency = 0 idle_threshold = 120 monitor = 0 follow = mouse sticky_history = yes line_height = 0 separator_height = 2 padding = 8 horizontal_padding = 8 separator_color = frame startup_notification = false dmenu = /usr/bin/dmenu -p dunst browser = /usr/bin/firefox -new-tab [frame] width = 3 color = "#aaaaaa" [shortcuts] close = ctrl+space close_all = ctrl+shift+space history = ctrl+grave context = ctrl+shift+period [urgency_low] background = "#222222" foreground = "#888888" timeout = 10 [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 [urgency_critical] background = "#900000" foreground = "#ffffff" timeout = 0 dunst-1.0.0/test/dunstrc.nomarkup000066400000000000000000000020201213270444200170550ustar00rootroot00000000000000[global] font = Monospace 8 allow_markup = no format = "%s\n%b" sort = yes indicate_hidden = yes alignment = left bounce_freq = 0 show_age_threshold = 60 word_wrap = yes ignore_newline = no geometry = "300x5-30+20" transparency = 0 idle_threshold = 120 monitor = 0 follow = mouse sticky_history = yes line_height = 0 separator_height = 2 padding = 8 horizontal_padding = 8 separator_color = frame startup_notification = false dmenu = /usr/bin/dmenu -p dunst browser = /usr/bin/firefox -new-tab [frame] width = 3 color = "#aaaaaa" [shortcuts] close = ctrl+space close_all = ctrl+shift+space history = ctrl+grave context = ctrl+shift+period [urgency_low] background = "#222222" foreground = "#888888" timeout = 10 [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 [urgency_critical] background = "#900000" foreground = "#ffffff" timeout = 0 dunst-1.0.0/test/dunstrc.nowrap000066400000000000000000000020051213270444200165320ustar00rootroot00000000000000[global] font = Monospace-10 allow_markup = yes format = "%s\n%b" sort = yes indicate_hidden = yes alignment = left bounce_freq = 0 show_age_threshold = 60 word_wrap = no ignore_newline = no geometry = "300x5-30+20" transparency = 0 idle_threshold = 120 monitor = 0 follow = mouse sticky_history = yes line_height = 0 separator_height = 2; padding = 8 horizontal_padding = 8 separator_color = frame startup_notification = false dmenu = /usr/bin/dmenu -p dunst: browser = /usr/bin/firefox -new-tab [frame] width = 3 color = "#aaaaaa" [shortcuts] close = ctrl+space close_all = ctrl+shift+space history = ctrl+grave context = ctrl+shift+period [urgency_low] background = "#222222" foreground = "#888888" timeout = 10 [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 [urgency_critical] background = "#900000" foreground = "#ffffff" timeout = 0 dunst-1.0.0/test/dunstrc.run_script000066400000000000000000000021131213270444200174140ustar00rootroot00000000000000[global] font = Monospace 8 allow_markup = yes format = "%s\n%b" sort = yes indicate_hidden = yes alignment = left bounce_freq = 0 show_age_threshold = 60 word_wrap = yes ignore_newline = no geometry = "300x5-30+20" transparency = 0 idle_threshold = 120 monitor = 0 follow = mouse sticky_history = yes line_height = 0 separator_height = 2 padding = 8 horizontal_padding = 8 separator_color = frame startup_notification = false dmenu = /usr/bin/dmenu -p dunst browser = /usr/bin/firefox -new-tab [frame] width = 3 color = "#aaaaaa" [shortcuts] close = ctrl+space close_all = ctrl+shift+space history = ctrl+grave context = ctrl+shift+period [urgency_low] background = "#222222" foreground = "#888888" timeout = 10 [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 [urgency_critical] background = "#900000" foreground = "#ffffff" timeout = 0 [script test] summary = trigger script = script_test.sh dunst-1.0.0/test/dunstrc.show_age000066400000000000000000000020111213270444200170150ustar00rootroot00000000000000[global] font = Monospace 8 allow_markup = yes format = "%s\n%b" sort = yes indicate_hidden = yes alignment = left bounce_freq = 0 show_age_threshold = 2 word_wrap = yes ignore_newline = no geometry = "300x5-30+20" transparency = 0 idle_threshold = 120 monitor = 0 follow = mouse sticky_history = yes line_height = 0 separator_height = 2 padding = 8 horizontal_padding = 8 separator_color = frame startup_notification = false dmenu = /usr/bin/dmenu -p dunst browser = /usr/bin/firefox -new-tab [frame] width = 3 color = "#aaaaaa" [shortcuts] close = ctrl+space close_all = ctrl+shift+space history = ctrl+grave context = ctrl+shift+period [urgency_low] background = "#222222" foreground = "#888888" timeout = 10 [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 [urgency_critical] background = "#900000" foreground = "#ffffff" timeout = 0 dunst-1.0.0/test/script_test.sh000077500000000000000000000000601213270444200165210ustar00rootroot00000000000000#!/bin/bash notify-send "Success" "ooooh yeah" dunst-1.0.0/test/test.sh000077500000000000000000000130651213270444200151460ustar00rootroot00000000000000#!/bin/bash function keypress { echo "press enter to continue..." read key } function basic_notifications { ../dunstify -a "dunst tester" "normal" "italic body" ../dunstify -a "dunst tester" -u c "critical" "bold body" ../dunstify -a "dunst tester" "long body" "This is a notification with a very long body" ../dunstify -a "dunst tester" "duplucate" ../dunstify -a "dunst tester" "duplucate" ../dunstify -a "dunst tester" "duplucate" ../dunstify -a "dunst tester" "url" "www.google.de" } function show_age { echo "###################################" echo "show age" echo "###################################" killall dunst ../dunst -config dunstrc.show_age & ../dunstify -a "dunst tester" -u c "Show Age" "These should print their age after 2 seconds" basic_notifications keypress } function run_script { echo "###################################" echo "run script" echo "###################################" killall dunst PATH=".:$PATH" ../dunst -config dunstrc.run_script & ../dunstify -a "dunst tester" -u c \ "Run Script" "After Keypress, 2 other notification should pop up. THis needs notify-send installed" keypress ../dunstify -a "dunst tester" -u c "trigger" "this should trigger a notification" keypress } function ignore_newline { echo "###################################" echo "ignore newline" echo "###################################" killall dunst ../dunst -config dunstrc.ignore_newline_no_wrap & ../dunstify -a "dunst tester" -u c "Ignore Newline No Wrap" "There should be no newline anywhere" ../dunstify -a "dunst tester" -u c "Th\nis\n\n\n is\n fu\nll of \n" "\nnew\nlines" basic_notifications keypress killall dunst ../dunst -config dunstrc.ignore_newline & ../dunstify -a "dunst tester" -u c "Ignore Newline" \ "The only newlines you should encounter here are wordwraps. That's why I'm so long." ../dunstify -a "dunst tester" -u c "Th\nis\n\n\n is\n fu\nll of \n" "\nnew\nlines" basic_notifications keypress } function replace { echo "###################################" echo "replace" echo "###################################" killall dunst ../dunst -config dunstrc.default & id=$(../dunstify -a "dunst tester" -p "Replace" "this should get replaces after keypress") keypress ../dunstify -a "dunst tester" -r $id "Success?" "I hope this is not a new notification" keypress } function markup { echo "###################################" echo "markup" echo "###################################" killall dunst ../dunst -config dunstrc.markup "200x0+10+10" & ../dunstify -a "dunst tester" "Markup Tests" -u "c" ../dunstify -a "dunst tester" "bold italic" ../dunstify -a "dunst tester" "broken markup" keypress killall dunst ../dunst -config dunstrc.nomarkup "200x0+10+10" & ../dunstify -a "dunst tester" -u c "NO Markup Tests" ../dunstify -a "dunst tester" "bolditalic" ../dunstify -a "dunst tester" "broken markup" keypress } function corners { echo "###################################" echo "corners" echo "###################################" killall dunst ../dunst -config dunstrc.default -geom "200x0+10+10" & ../dunstify -a "dunst tester" -u c "upper left" basic_notifications keypress killall dunst ../dunst -config dunstrc.default -geom "200x0-10+10" & ../dunstify -a "dunst tester" -u c "upper right" basic_notifications keypress killall dunst ../dunst -config dunstrc.default -geom "200x0-10-10" & ../dunstify -a "dunst tester" -u c "lower right" basic_notifications keypress killall dunst ../dunst -config dunstrc.default -geom "200x0+10-10" & ../dunstify -a "dunst tester" -u c "lower left" basic_notifications keypress } function geometry { echo "###################################" echo "geometry" echo "###################################" killall dunst ../dunst -config dunstrc.default -geom "0x0" & ../dunstify -a "dunst tester" -u c "0x0" basic_notifications keypress killall dunst ../dunst -config dunstrc.default -geom "200x0" & ../dunstify -a "dunst tester" -u c "200x0" basic_notifications keypress killall dunst ../dunst -config dunstrc.default -geom "200x2" & ../dunstify -a "dunst tester" -u c "200x2" basic_notifications keypress killall dunst ../dunst -config dunstrc.default -geom "200x1" & ../dunstify -a "dunst tester" -u c "200x1" basic_notifications keypress killall dunst ../dunst -config dunstrc.default -geom "0x1" & ../dunstify -a "dunst tester" -u c "0x1" basic_notifications keypress killall dunst ../dunst -config dunstrc.default -geom "-300x1" & ../dunstify -a "dunst tester" -u c "-300x1" basic_notifications keypress killall dunst ../dunst -config dunstrc.default -geom "-300x1-20-20" & ../dunstify -a "dunst tester" -u c "-300x1-20-20" basic_notifications keypress killall dunst ../dunst -config dunstrc.default -geom "x1" & ../dunstify -a "dunst tester" -u c "x1-20-20" "across the screen" basic_notifications keypress } if [ -n "$1" ]; then while [ -n "$1" ]; do $1 shift done else geometry corners show_age run_script ignore_newline replace markup fi killall dunst dunst-1.0.0/utils.c000066400000000000000000000047531213270444200141610ustar00rootroot00000000000000/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #define _GNU_SOURCE #include #include #include #include #include #include "utils.h" #include "dunst.h" char *string_replace_char(char needle, char replacement, char *haystack) { char *current = haystack; while ((current = strchr (current, needle)) != NULL) *current++ = replacement; return haystack; } char *string_replace_all(const char *needle, const char *replacement, char *haystack) { char *start; start = strstr(haystack, needle); while (start != NULL) { haystack = string_replace(needle, replacement, haystack); start = strstr(haystack, needle); } return haystack; } char *string_replace(const char *needle, const char *replacement, char *haystack) { char *tmp, *start; int size; start = strstr(haystack, needle); if (start == NULL) { return haystack; } size = (strlen(haystack) - strlen(needle)) + strlen(replacement) + 1; tmp = calloc(sizeof(char), size); memset(tmp, '\0', size); strncpy(tmp, haystack, start - haystack); tmp[start - haystack] = '\0'; sprintf(tmp + strlen(tmp), "%s%s", replacement, start + strlen(needle)); free(haystack); return tmp; } char *string_append(char *a, const char *b, const char *sep) { if (!a) return g_strdup(b); char *new; if (!sep) new = g_strconcat(a, b, NULL); else new = g_strconcat(a, sep, b, NULL); 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) { i = ABS(i); int len = 1; while (i > 0) { len++; i /= 10; } return len; } void die(char *text, int exit_value) { fputs(text, stderr); exit(exit_value); } /* vim: set ts=8 sw=8 tw=0: */ dunst-1.0.0/utils.h000066400000000000000000000015421213270444200141570ustar00rootroot00000000000000/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef UTIL_H #define UTIL_H /* replace all occurences of the character needle with the character replacement in haystack */ char *string_replace_char(char needle, char replacement, char *haystack); /* replace all occurrences of needle with replacement in haystack */ char *string_replace_all(const char *needle, const char *replacement, char *haystack); /* replace needle with replacement in haystack */ 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); int digit_count(int i); #endif /* vim: set ts=8 sw=8 tw=0: */ dunst-1.0.0/x.c000066400000000000000000000754701213270444200132740ustar00rootroot00000000000000/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "x.h" #include "utils.h" #include "dunst.h" #include "settings.h" #include "notification.h" #define WIDTH 400 #define HEIGHT 400 xctx_t xctx; bool dunst_grab_errored = false; typedef struct _cairo_ctx { cairo_status_t status; cairo_surface_t *surface; cairo_t *context; PangoFontDescription *desc; } cairo_ctx_t; typedef struct _colored_layout { PangoLayout *l; color_t fg; color_t bg; char *text; PangoAttrList *attr; } colored_layout; cairo_ctx_t cairo_ctx; static color_t frame_color; static void x_shortcut_setup_error_handler(void); static int x_shortcut_tear_down_error_handler(void); static void x_win_move(int width, int height); static void setopacity(Window win, unsigned long opacity); static void x_handle_click(XEvent ev); static void x_screen_info(screen_info * scr); static void x_win_setup(void); static color_t x_color_hex_to_double(int hexValue) { color_t color; color.r = ((hexValue >> 16) & 0xFF) / 255.0; color.g = ((hexValue >> 8) & 0xFF) / 255.0; color.b = ((hexValue) & 0xFF) / 255.0; return color; } static color_t x_string_to_color_t(const char *str) { char *end; long int val = strtol(str+1, &end, 16); if (*end != '\0' && *(end+1) != '\0') { printf("WARNING: Invalid color string: \"%s\"\n", str); } return x_color_hex_to_double(val); } static double _apply_delta(double base, double delta) { base += delta; if (base > 1) base = 1; if (base < 0) base = 0; return base; } static color_t calculate_foreground_color(color_t bg) { double c_delta = 0.1; color_t color = bg; /* do we need to darken or brighten the colors? */ bool darken = (bg.r + bg.g + bg.b) / 3 > 0.5; int signedness = darken ? -1 : 1; color.r = _apply_delta(color.r, c_delta * signedness); color.g = _apply_delta(color.g, c_delta * signedness); color.b = _apply_delta(color.b, c_delta * signedness); return color; } static color_t x_get_separator_color(color_t fg, color_t bg) { switch (settings.sep_color) { case FRAME: return x_string_to_color_t(settings.frame_color); case CUSTOM: return x_string_to_color_t(settings.sep_custom_color_str); case FOREGROUND: return fg; case AUTO: return calculate_foreground_color(bg); default: printf("Unknown separator color type. Please file a Bugreport.\n"); return fg; } } static void x_cairo_setup(void) { cairo_ctx.surface = cairo_xlib_surface_create(xctx.dpy, xctx.win, DefaultVisual(xctx.dpy, 0), WIDTH, HEIGHT); cairo_ctx.context = cairo_create(cairo_ctx.surface); cairo_ctx.desc = pango_font_description_from_string(settings.font); frame_color = x_string_to_color_t(settings.frame_color); } static void r_setup_pango_layout(PangoLayout *layout, int width) { pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR); pango_layout_set_width(layout, width * PANGO_SCALE); pango_layout_set_font_description(layout, cairo_ctx.desc); pango_layout_set_spacing(layout, settings.line_height * PANGO_SCALE); PangoAlignment align; switch (settings.align) { case left: default: align = PANGO_ALIGN_LEFT; break; case center: align = PANGO_ALIGN_CENTER; break; case right: align = PANGO_ALIGN_RIGHT; break; } pango_layout_set_alignment(layout, align); } static void free_colored_layout(void *data) { colored_layout *cl = data; g_object_unref(cl->l); pango_attr_list_unref(cl->attr); g_free(cl->text); g_free(cl); } static bool have_dynamic_width(void) { return (xctx.geometry.mask & WidthValue && xctx.geometry.w == 0); } static dimension_t calculate_dimensions(GSList *layouts) { dimension_t dim; dim.w = 0; dim.h = 0; dim.x = 0; dim.y = 0; dim.mask = xctx.geometry.mask; screen_info scr; x_screen_info(&scr); if (have_dynamic_width()) { /* dynamic width */ dim.w = 0; } else if (xctx.geometry.mask & WidthValue) { /* fixed width */ if (xctx.geometry.negative_width) { dim.w = scr.dim.w - xctx.geometry.w; } else { dim.w = xctx.geometry.w; } } else { /* across the screen */ dim.w = scr.dim.w; } dim.h += (g_slist_length(layouts) - 1) * settings.separator_height; dim.h += g_slist_length(layouts) * settings.padding * 2; int text_width = 0; for (GSList *iter = layouts; iter; iter = iter->next) { colored_layout *cl = iter->data; int w,h; pango_layout_get_pixel_size(cl->l, &w, &h); dim.h += h; text_width = MAX(w, text_width); } if (dim.w <= 0) { dim.w = text_width + 2 * settings.h_padding; dim.w += 2 * settings.frame_width; } return dim; } static colored_layout *r_init_shared(cairo_t *c, notification *n) { colored_layout *cl = malloc(sizeof(colored_layout)); cl->l = pango_cairo_create_layout(c); if (!settings.word_wrap) { pango_layout_set_ellipsize(cl->l, PANGO_ELLIPSIZE_MIDDLE); } cl->fg = x_string_to_color_t(n->color_strings[ColFG]); cl->bg = x_string_to_color_t(n->color_strings[ColBG]); dimension_t dim = calculate_dimensions(NULL); int width = dim.w; if (have_dynamic_width()) { r_setup_pango_layout(cl->l, -1); } else { width -= 2 * settings.h_padding; width -= 2 * settings.frame_width; r_setup_pango_layout(cl->l, width); } return cl; } static colored_layout *r_create_layout_for_xmore(cairo_t *c, notification *n, int qlen) { colored_layout *cl = r_init_shared(c, n); cl->text = g_strdup_printf("(%d more)", qlen); cl->attr = NULL; pango_layout_set_text(cl->l, cl->text, -1); return cl; } static colored_layout *r_create_layout_from_notification(cairo_t *c, notification *n) { colored_layout *cl = r_init_shared(c, n); /* markup */ GError *err = NULL; pango_parse_markup(n->text_to_render, -1, 0, &(cl->attr), &(cl->text), NULL, &err); if (!err) { pango_layout_set_text(cl->l, cl->text, -1); pango_layout_set_attributes(cl->l, cl->attr); } else { /* remove markup and display plain message instead */ n->text_to_render = notification_fix_markup(n->text_to_render); cl->text = NULL; cl->attr = NULL; pango_layout_set_text(cl->l, n->text_to_render, -1); printf("Error parsing markup: %s\n", err->message); g_error_free(err); } pango_layout_get_pixel_size(cl->l, NULL, &(n->displayed_height)); n->displayed_height += 2 * settings.padding; return cl; } static GSList *r_create_layouts(cairo_t *c) { GSList *layouts = NULL; int qlen = g_list_length(g_queue_peek_head_link(queue)); bool xmore_is_needed = qlen > 0 && settings.indicate_hidden; notification *last = NULL; for (GList *iter = g_queue_peek_head_link(displayed); iter; iter = iter->next) { notification *n = iter->data; last = n; notification_update_text_to_render(n); if (!iter->next && xmore_is_needed && xctx.geometry.h == 1) { char *new_ttr = g_strdup_printf("%s (%d more)", n->text_to_render, qlen); g_free(n->text_to_render); n->text_to_render = new_ttr; } layouts = g_slist_append(layouts, r_create_layout_from_notification(c, n)); } if (xmore_is_needed && xctx.geometry.h != 1) { /* append xmore message as new message */ layouts = g_slist_append(layouts, r_create_layout_for_xmore(c, last, qlen)); } return layouts; } static void r_free_layouts(GSList *layouts) { g_slist_free_full(layouts, free_colored_layout); } static dimension_t x_render_layout(cairo_t *c, colored_layout *cl, dimension_t dim, bool first, bool last) { int h; pango_layout_get_pixel_size(cl->l, NULL, &h); int bg_x = 0; int bg_y = dim.y; int bg_width = dim.w; int bg_height = (2 * settings.padding) + h; /* adding frame */ bg_x += settings.frame_width; if (first) { bg_y += settings.frame_width; bg_height -= settings.frame_width; } bg_width -= 2 * settings.frame_width; if (last) bg_height -= settings.frame_width; cairo_set_source_rgb(c, cl->bg.r, cl->bg.g, cl->bg.b); cairo_rectangle(c, bg_x, bg_y, bg_width, bg_height); cairo_fill(c); dim.y += settings.padding; cairo_move_to(c, settings.h_padding, dim.y); cairo_set_source_rgb(c, cl->fg.r, cl->fg.g, cl->fg.b); pango_cairo_update_layout(c, cl->l); pango_cairo_show_layout(c, cl->l); dim.y += h + settings.padding; color_t sep_color = x_get_separator_color(cl->fg, cl->bg); if (settings.separator_height > 0 && !last) { cairo_set_source_rgb(c, sep_color.r, sep_color.g, sep_color.b); cairo_rectangle(c, settings.frame_width, dim.y, dim.w - 2 * settings.frame_width , settings.separator_height); cairo_fill(c); dim.y += settings.separator_height; } cairo_move_to(c, settings.h_padding, dim.y); return dim; } void x_win_draw(void) { GSList *layouts = r_create_layouts(cairo_ctx.context); dimension_t dim = calculate_dimensions(layouts); int width = dim.w; int height = dim.h; cairo_t *c; cairo_surface_t *image_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); c = cairo_create(image_surface); x_win_move(width, height); cairo_xlib_surface_set_size(cairo_ctx.surface, width, height); cairo_set_source_rgb(c, frame_color.r, frame_color.g, frame_color.b); cairo_rectangle(c, 0.0, 0.0, width, height); cairo_fill(c); cairo_move_to(c, 0, 0); bool first = true; for (GSList *iter = layouts; iter; iter = iter->next) { colored_layout *cl = iter->data; dim = x_render_layout(c, cl, dim, first, iter->next == NULL); first = false; } cairo_set_source_surface(cairo_ctx.context, image_surface, 0, 0); cairo_paint(cairo_ctx.context); cairo_show_page(cairo_ctx.context); cairo_destroy(c); cairo_surface_destroy(image_surface); r_free_layouts(layouts); } static void x_win_move(int width, int height) { int x, y; screen_info scr; x_screen_info(&scr); /* calculate window position */ if (xctx.geometry.mask & XNegative) { x = (scr.dim.x + (scr.dim.w - width)) + xctx.geometry.x; } else { x = scr.dim.x + xctx.geometry.x; } if (xctx.geometry.mask & YNegative) { y = scr.dim.y + (scr.dim.h + xctx.geometry.y) - height; } else { y = scr.dim.y + xctx.geometry.y; } /* move and resize */ if (x != xctx.window_dim.x || y != xctx.window_dim.y) { XMoveWindow(xctx.dpy, xctx.win, x, y); } if (width != xctx.window_dim.w || height != xctx.window_dim.h) { XResizeWindow(xctx.dpy, xctx.win, width, height); } xctx.window_dim.x = x; xctx.window_dim.y = y; xctx.window_dim.h = height; xctx.window_dim.w = width; } static void setopacity(Window win, unsigned long opacity) { Atom _NET_WM_WINDOW_OPACITY = XInternAtom(xctx.dpy, "_NET_WM_WINDOW_OPACITY", false); XChangeProperty(xctx.dpy, win, _NET_WM_WINDOW_OPACITY, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&opacity, 1L); } /* * Helper function to use glib's mainloop mechanic * with Xlib */ gboolean x_mainloop_fd_prepare(GSource * source, gint * timeout) { if (timeout) *timeout = -1; else g_print("BUG: x_mainloop_fd_prepare: timeout == NULL\n"); return false; } /* * Helper function to use glib's mainloop mechanic * with Xlib */ gboolean x_mainloop_fd_check(GSource * source) { return XPending(xctx.dpy) > 0; } /* * Main Dispatcher for XEvents */ gboolean x_mainloop_fd_dispatch(GSource * source, GSourceFunc callback, gpointer user_data) { XEvent ev; while (XPending(xctx.dpy) > 0) { XNextEvent(xctx.dpy, &ev); switch (ev.type) { case Expose: if (ev.xexpose.count == 0 && xctx.visible) { } break; case SelectionNotify: if (ev.xselection.property == xctx.utf8) break; case VisibilityNotify: if (ev.xvisibility.state != VisibilityUnobscured) XRaiseWindow(xctx.dpy, xctx.win); break; case ButtonPress: if (ev.xbutton.window == xctx.win) { x_handle_click(ev); } break; case KeyPress: if (settings.close_ks.str && XLookupKeysym(&ev.xkey, 0) == settings.close_ks.sym && settings.close_ks.mask == ev.xkey.state) { if (displayed) { notification *n = g_queue_peek_head(displayed); if (n) notification_close(n, 2); } } if (settings.history_ks.str && XLookupKeysym(&ev.xkey, 0) == settings.history_ks.sym && settings.history_ks.mask == ev.xkey.state) { history_pop(); } if (settings.close_all_ks.str && XLookupKeysym(&ev.xkey, 0) == settings.close_all_ks.sym && settings.close_all_ks.mask == ev.xkey.state) { move_all_to_history(); } if (settings.context_ks.str && XLookupKeysym(&ev.xkey, 0) == settings.context_ks.sym && settings.context_ks.mask == ev.xkey.state) { context_menu(); } break; case FocusIn: case FocusOut: case PropertyNotify: wake_up(); break; } } return true; } /* * Check whether the user is currently idle. */ bool x_is_idle(void) { XScreenSaverQueryInfo(xctx.dpy, DefaultRootWindow(xctx.dpy), xctx.screensaver_info); if (settings.idle_threshold == 0) { return false; } return xctx.screensaver_info->idle / 1000 > settings.idle_threshold; } /* TODO move to x_mainloop_* */ /* * Handle incoming mouse click events */ static void x_handle_click(XEvent ev) { if (ev.xbutton.button == Button3) { move_all_to_history(); return; } if (ev.xbutton.button == Button1) { int y = settings.separator_height; notification *n = NULL; int first = true; for (GList * iter = g_queue_peek_head_link(displayed); iter; iter = iter->next) { n = iter->data; if (ev.xbutton.y > y && ev.xbutton.y < y + n->displayed_height) break; y += n->displayed_height + settings.separator_height; if (first) y += settings.frame_width; } if (n) notification_close(n, 2); } } /* * Return the window that currently has * the keyboard focus. */ static 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(xctx.dpy, DefaultScreen(xctx.dpy)); Atom netactivewindow = XInternAtom(xctx.dpy, "_NET_ACTIVE_WINDOW", false); XGetWindowProperty(xctx.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; } #ifdef XINERAMA /* * Select the screen on which the Window * should be displayed. */ static int select_screen(XineramaScreenInfo * info, int info_len) { if (settings.f_mode == FOLLOW_NONE) { return settings.monitor >= 0 ? settings.monitor : XDefaultScreen(xctx.dpy); } else { int x, y; assert(settings.f_mode == FOLLOW_MOUSE || settings.f_mode == FOLLOW_KEYBOARD); Window root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)); if (settings.f_mode == FOLLOW_MOUSE) { int dummy; unsigned int dummy_ui; Window dummy_win; XQueryPointer(xctx.dpy, root, &dummy_win, &dummy_win, &x, &y, &dummy, &dummy, &dummy_ui); } if (settings.f_mode == FOLLOW_KEYBOARD) { Window focused = get_focused_window(); if (focused == 0) { /* something went wrong. Fallback to default */ return settings.monitor >= 0 ? settings.monitor : XDefaultScreen(xctx.dpy); } Window child_return; XTranslateCoordinates(xctx.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 settings.monitor >= 0 ? settings.monitor : XDefaultScreen(xctx.dpy); } } #endif /* * Update the information about the monitor * geometry. */ static void x_screen_info(screen_info * scr) { #ifdef XINERAMA int n; XineramaScreenInfo *info; if ((info = XineramaQueryScreens(xctx.dpy, &n))) { int 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; int screen; if (settings.monitor >= 0) screen = settings.monitor; else screen = DefaultScreen(xctx.dpy); scr->dim.w = DisplayWidth(xctx.dpy, screen); scr->dim.h = DisplayHeight(xctx.dpy, screen); } } /* * Setup X11 stuff */ void x_setup(void) { /* initialize xctx.dc, font, keyboard, colors */ if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) fputs("no locale support\n", stderr); if (!(xctx.dpy = XOpenDisplay(NULL))) { die("cannot open display\n", EXIT_FAILURE); } x_shortcut_init(&settings.close_ks); x_shortcut_init(&settings.close_all_ks); x_shortcut_init(&settings.history_ks); x_shortcut_init(&settings.context_ks); x_shortcut_grab(&settings.close_ks); x_shortcut_ungrab(&settings.close_ks); x_shortcut_grab(&settings.close_all_ks); x_shortcut_ungrab(&settings.close_all_ks); x_shortcut_grab(&settings.history_ks); x_shortcut_ungrab(&settings.history_ks); x_shortcut_grab(&settings.context_ks); x_shortcut_ungrab(&settings.context_ks); xctx.color_strings[ColFG][LOW] = settings.lowfgcolor; xctx.color_strings[ColFG][NORM] = settings.normfgcolor; xctx.color_strings[ColFG][CRIT] = settings.critfgcolor; xctx.color_strings[ColBG][LOW] = settings.lowbgcolor; xctx.color_strings[ColBG][NORM] = settings.normbgcolor; xctx.color_strings[ColBG][CRIT] = settings.critbgcolor; /* parse and set xctx.geometry and monitor position */ if (settings.geom[0] == '-') { xctx.geometry.negative_width = true; settings.geom++; } else { xctx.geometry.negative_width = false; } xctx.geometry.mask = XParseGeometry(settings.geom, &xctx.geometry.x, &xctx.geometry.y, &xctx.geometry.w, &xctx.geometry.h); xctx.screensaver_info = XScreenSaverAllocInfo(); x_win_setup(); x_cairo_setup(); x_shortcut_grab(&settings.history_ks); } /* * Setup the window */ static void x_win_setup(void) { Window root; XSetWindowAttributes wa; xctx.window_dim.x = 0; xctx.window_dim.y = 0; xctx.window_dim.w = 0; xctx.window_dim.h = 0; root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)); xctx.utf8 = XInternAtom(xctx.dpy, "UTF8_STRING", false); wa.override_redirect = true; wa.background_pixmap = ParentRelative; wa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask | ButtonPressMask | FocusChangeMask| StructureNotifyMask; screen_info scr; x_screen_info(&scr); xctx.win = XCreateWindow(xctx.dpy, root, scr.dim.x, scr.dim.y, scr.dim.w, 1, 0, DefaultDepth(xctx.dpy, DefaultScreen(xctx.dpy)), CopyFromParent, DefaultVisual(xctx.dpy, DefaultScreen(xctx.dpy)), CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa); settings.transparency = settings.transparency > 100 ? 100 : settings.transparency; setopacity(xctx.win, (unsigned long)((100 - settings.transparency) * (0xffffffff / 100))); if (settings.f_mode != FOLLOW_NONE) { long root_event_mask = FocusChangeMask | PropertyChangeMask; XSelectInput(xctx.dpy, root, root_event_mask); } } /* * Show the window and grab shortcuts. */ void x_win_show(void) { /* window is already mapped or there's nothing to show */ if (xctx.visible || g_queue_is_empty(displayed)) { return; } x_shortcut_grab(&settings.close_ks); x_shortcut_grab(&settings.close_all_ks); x_shortcut_grab(&settings.context_ks); x_shortcut_setup_error_handler(); XGrabButton(xctx.dpy, AnyButton, AnyModifier, xctx.win, false, BUTTONMASK, GrabModeAsync, GrabModeSync, None, None); if (x_shortcut_tear_down_error_handler()) { fprintf(stderr, "Unable to grab mouse button(s)\n"); } XMapRaised(xctx.dpy, xctx.win); xctx.visible = true; } /* * Hide the window and ungrab unused keyboard_shortcuts */ void x_win_hide() { x_shortcut_ungrab(&settings.close_ks); x_shortcut_ungrab(&settings.close_all_ks); x_shortcut_ungrab(&settings.context_ks); XUngrabButton(xctx.dpy, AnyButton, AnyModifier, xctx.win); XUnmapWindow(xctx.dpy, xctx.win); XFlush(xctx.dpy); xctx.visible = false; } /* * Parse a string into a modifier mask. */ KeySym x_shortcut_string_to_mask(const 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; } } /* * Error handler for grabbing mouse and keyboard errors. */ static int GrabXErrorHandler(Display * display, XErrorEvent * e) { dunst_grab_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; } /* * Setup the Error handler. */ static void x_shortcut_setup_error_handler(void) { dunst_grab_errored = false; XFlush(xctx.dpy); XSetErrorHandler(GrabXErrorHandler); } /* * Tear down the Error handler. */ static int x_shortcut_tear_down_error_handler(void) { XFlush(xctx.dpy); XSync(xctx.dpy, false); XSetErrorHandler(NULL); return dunst_grab_errored; } /* * Grab the given keyboard shortcut. */ int x_shortcut_grab(keyboard_shortcut * ks) { if (!ks->is_valid) return 1; Window root; root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)); x_shortcut_setup_error_handler(); if (ks->is_valid) XGrabKey(xctx.dpy, ks->code, ks->mask, root, true, GrabModeAsync, GrabModeAsync); if (x_shortcut_tear_down_error_handler()) { fprintf(stderr, "Unable to grab key \"%s\"\n", ks->str); ks->is_valid = false; return 1; } return 0; } /* * Ungrab the given keyboard shortcut. */ void x_shortcut_ungrab(keyboard_shortcut * ks) { Window root; root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)); if (ks->is_valid) XUngrabKey(xctx.dpy, ks->code, ks->mask, root); } /* * Initialize the keyboard shortcut. */ void x_shortcut_init(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 = g_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++; g_strchomp(mod); ks->mask = ks->mask | x_shortcut_string_to_mask(mod); } g_strstrip(str); ks->sym = XStringToKeysym(str); /* find matching keycode for ks->sym */ int min_keysym, max_keysym; XDisplayKeycodes(xctx.dpy, &min_keysym, &max_keysym); ks->code = NoSymbol; for (int i = min_keysym; i <= max_keysym; i++) { if (XkbKeycodeToKeysym(xctx.dpy, i, 0, 0) == ks->sym || XkbKeycodeToKeysym(xctx.dpy, i, 0, 1) == ks->sym) { ks->code = i; break; } } 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); } /* vim: set ts=8 sw=8 tw=0: */ dunst-1.0.0/x.h000066400000000000000000000040731213270444200132700ustar00rootroot00000000000000/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DRAW_H #define DRAW_H #include #include "glib.h" #include #include #include #include #ifdef XINERAMA #include #endif #include #include #define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) #define FONT_HEIGHT_BORDER 2 #define DEFFONT "Monospace-11" #define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh)) typedef struct _dimension_t { int x; int y; unsigned int h; unsigned int w; int mask; int negative_width; } dimension_t; typedef struct _screen_info { int scr; dimension_t dim; } screen_info; typedef struct _keyboard_shortcut { const char *str; KeyCode code; KeySym sym; KeySym mask; bool is_valid; } keyboard_shortcut; typedef struct _xctx { Atom utf8; Display *dpy; Window win; bool visible; dimension_t geometry; const char *color_strings[2][3]; XScreenSaverInfo *screensaver_info; dimension_t window_dim; unsigned long framec; unsigned long sep_custom_col; } xctx_t; typedef struct _color_t { double r; double g; double b; } color_t; extern xctx_t xctx; /* window */ void x_win_draw(void); void x_win_hide(void); void x_win_show(void); /* shortcut */ void x_shortcut_init(keyboard_shortcut * shortcut); void x_shortcut_ungrab(keyboard_shortcut * ks); int x_shortcut_grab(keyboard_shortcut * ks); KeySym x_shortcut_string_to_mask(const char *str); /* X misc */ bool x_is_idle(void); void x_setup(void); gboolean x_mainloop_fd_dispatch(GSource * source, GSourceFunc callback, gpointer user_data); gboolean x_mainloop_fd_check(GSource * source); gboolean x_mainloop_fd_prepare(GSource * source, gint * timeout); #endif /* vim: set ts=8 sw=8 tw=0: */