pax_global_header00006660000000000000000000000064132237451200014511gustar00rootroot0000000000000052 comment=10ee1782d93dfcdf61f847eb571f486ce157a924 dunst-1.3.0/000077500000000000000000000000001322374512000126475ustar00rootroot00000000000000dunst-1.3.0/.github/000077500000000000000000000000001322374512000142075ustar00rootroot00000000000000dunst-1.3.0/.github/ISSUE_TEMPLATE.md000066400000000000000000000011441322374512000167140ustar00rootroot00000000000000 ### Installation info - Version: `` - Install type: `` - Distro and version: ` ` dunst-1.3.0/.gitignore000066400000000000000000000001431322374512000146350ustar00rootroot00000000000000dunst *.o core vgcore.* dunst.1 org.knopwob.dunst.service dunst.systemd.service dunstify test/test dunst-1.3.0/.travis.yml000066400000000000000000000011671322374512000147650ustar00rootroot00000000000000addons: apt: packages: - libdbus-1-dev - libx11-dev - libxrandr-dev - libxinerama-dev - libxss-dev - libxdg-basedir-dev - libglib2.0-dev - libpango1.0-dev - libcairo2-dev - libnotify-dev - libgtk-3-dev - valgrind dist: trusty sudo: false language: c before_install: - pip install --user cpp-coveralls script: - CFLAGS="-fprofile-arcs -ftest-coverage -Werror" make all dunstify test-valgrind - coveralls compiler: - gcc - clang notifications: irc: channels: - "chat.freenode.net#dunst" on_success: change on_failure: always dunst-1.3.0/.valgrind.suppressions000066400000000000000000000002361322374512000172330ustar00rootroot00000000000000{ xdgBaseDir_leak # see https://github.com/devnev/libxdg-basedir/pull/6 Memcheck:Leak fun:malloc ... fun:xdgInitHandle ... fun:main } dunst-1.3.0/AUTHORS000066400000000000000000000001761322374512000137230ustar00rootroot00000000000000Sascha Kruse (http://github.com/knopwob) contributors: See `git shortlog` for a list of contributors and their contributions dunst-1.3.0/CHANGELOG.md000066400000000000000000000134501322374512000144630ustar00rootroot00000000000000# Dunst changelog ## 1.3.0 (2018-01-05) ### Added - `ellipsize` option to control how long lines should be ellipsized when `word_wrap` is set to `false` (#374) - A beginning tilde of a path is now expanded to the home of the current user (#351) - The image-path hint is now respected, as GApplications send their icon only via this link (#447) - The (legacy) image\_data hint is now respected (#353) - If dunst can't acquire the DBus name, dunst prints the PID of the process holding the name (#458 #460) - Increased accuracy of timeouts by using microseconds internally (#379 #291) - Support for specifying timeout values in milliseconds, minutes, hours, or days. (#379) - Support for HTML img tags (via context menu) (#428) ### Fixed - `new_icon` rule being ignored on notifications that had a raw icon (#423) - Format strings being replaced recursively in some cases (#322 #365) - DBus related memory leaks (#397) - Crash on X11 servers with RandR support less than 1.5. (#413 #364) - Silently reading the default config file, if `-conf` did not specify a valid file (#452) - Notification window flickering when a notification is replaced (#320 #415) - Inaccurate timeout in some cases (#291 #379) ### Changed - Transient hints are now handled (#343 #310) An additional rule option (`match_transient` and `set_transient`) is added to optionally reset the transient setting - HTML links are now referred to by their text in the context menu rather than numbers (#428) - `icon_folders` setting renamed to `icon_path` (#170) - `config.def.h` and `config.h` got merged (#371) - The dependency on GTK3+ has been removed. Instead of GTK3+, dunst now requires gdk-pixbuf which had been a transient dependency before. (#334 #376) - The `_GNU_SOURCE` macros had been removed to make dunst portable to nonGNU systems (#403) - Internal refactorings of the notification queue handling. (#411) - Dunst does now install the systemd and dbus service files into their proper location given by pkg-config. Use `SERVICEDIR_(DBUS|SYSTEMD)` params to overwrite them. (#463) ## 1.2.0 - 2017-07-12 ### Added - `always_run_script` option to run script even if a notification is suppressed - Support for more icon file types - Support for raw icons - `hide_duplicate_count` option to hide the number of duplicate notifications - Support for per-urgency frame colours - `markup` setting for more fine-grained control over how markup is handled - `history_ignore` rule action to exclude a notification from being added to the history - Support for setting the dpi value dunst will use for font rendering via the `Xft.dpi` X resource - Experimental support for per-monitor dpi calculation - `max_icon_size` option to scale down icons if they exceed a certain size - Middle click on notifications can be used to trigger actions - Systemd service file, installed by default - `%n` format flag for getting progress value without any extra characters ### Changed - Text and icons are now centred vertically - Notifications aren't considered duplicate if urgency or icons differ - The maximum length of a notification is limited to 5000 characters - The frame width and color settings were moved to the global section as `frame_width` and `frame_color` respectively - Dropped Xinerama in favour of RandR, Xinerama can be enabled with the `-force_xinerama` option if needed ### Deprecated - `allow_markup` is deprecated with `markup` as its replacement - The urgency specific command line flags have been deprecated with no replacement, respond to issue #328 on the bug tracker if you depend on them ### Fixed - Infinite loop if there are 2 configuration file sections with the same name - URLs with dashes and underscores in them are now parsed properly - Many memory leaks - Category based rules were applied without actually matching - dmenu command not parsing quoted arguments correctly - Icon alignment with dynamic width - Issue when loading configuration files with very long lines - '\n' is no longer expanded to a newline inside notification text - Notification window wasn't redrawn if obscured on systems without a compositor - `ignore_newline` now works regardless of the markup setting - dmenu process being left as a zombie if no option was selected - Crash when opening urls parsed from `` tags ## 1.1.0 - 2014-07-29 - fix nasty memory leak - icon support (still work in progress) - fix issue where keybindings aren't working when numlock is activated ## 1.0.0 - 2013-04-15 - use pango/cairo as drawing backend - make use of pangos ability to parse markup - support for actions via context menu - indicator for actions/urls found - use blocking I/O. No more waking up the CPU multiple times per second to check for new dbus messages ## 0.5.0 - 2013-01-26 - 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 ## 0.4.0 - 2012-09-27 - 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 ## 0.3.1 - 2012-08-02 - fix -mon option ## 0.3.0 - 2012-07-30 - 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 ## 0.2.0 - 2012-06-26 - 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.3.0/LICENSE000066400000000000000000000030161322374512000136540ustar00rootroot00000000000000Copyright © 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.3.0/Makefile000066400000000000000000000101231322374512000143040ustar00rootroot00000000000000# dunst - Notification-daemon # See LICENSE file for copyright and license details. include config.mk VERSION := "1.3.0 (2018-01-05)" ifneq ($(wildcard ./.git/.),) VERSION := $(shell git describe --tags) endif ifeq (,${SYSTEMD}) # Check for systemctl to avoid discrepancies on systems, where # systemd is installed, but systemd.pc is in another package systemctl := $(shell command -v systemctl >/dev/null && echo systemctl) ifeq (systemctl,${systemctl}) SYSTEMD := 1 else SYSTEMD := 0 endif endif SERVICEDIR_DBUS ?= $(shell pkg-config dbus-1 --variable=session_bus_services_dir) SERVICEDIR_DBUS := ${SERVICEDIR_DBUS} ifeq (,${SERVICEDIR_DBUS}) $(error "Failed to query pkg-config for package 'dbus-1'!") endif ifneq (0,${SYSTEMD}) SERVICEDIR_SYSTEMD ?= $(shell pkg-config systemd --variable=systemduserunitdir) SERVICEDIR_SYSTEMD := ${SERVICEDIR_SYSTEMD} ifeq (,${SERVICEDIR_SYSTEMD}) $(error "Failed to query pkg-config for package 'systemd'!") endif endif LIBS := $(shell pkg-config --libs ${pkg_config_packs}) INCS := $(shell pkg-config --cflags ${pkg_config_packs}) ifneq (clean, $(MAKECMDGOALS)) ifeq ($(and $(INCS),$(LIBS)),) $(error "pkg-config failed!") endif endif CFLAGS += -I. ${INCS} LDFLAGS+= -L. ${LIBS} SRC := $(sort $(shell find src/ -name '*.c')) OBJ := ${SRC:.c=.o} TEST_SRC := $(sort $(shell find test/ -name '*.c')) TEST_OBJ := $(TEST_SRC:.c=.o) .PHONY: all debug all: doc dunst service debug: CFLAGS += ${CFLAGS_DEBUG} debug: LDFLAGS += ${LDFLAGS_DEBUG} debug: CPPFLAGS += ${CPPFLAGS_DEBUG} debug: all .c.o: ${CC} -o $@ -c $< ${CFLAGS} ${OBJ}: config.mk dunst: ${OBJ} main.o ${CC} ${CFLAGS} -o $@ ${OBJ} main.o ${LDFLAGS} dunstify: dunstify.o ${CC} ${CFLAGS} -o $@ dunstify.o ${LDFLAGS} .PHONY: test test-valgrind test: test/test cd test && ./test test-valgrind: test/test cd ./test \ && valgrind \ --suppressions=../.valgrind.suppressions \ --leak-check=full \ --show-leak-kinds=definite \ --errors-for-leak-kinds=definite \ --error-exitcode=123 \ ./test test/test: ${OBJ} ${TEST_OBJ} ${CC} ${CFLAGS} -o $@ ${TEST_OBJ} ${OBJ} ${LDFLAGS} .PHONY: doc doc: docs/dunst.1 docs/dunst.1: docs/dunst.pod pod2man --name=dunst -c "Dunst Reference" --section=1 --release=${VERSION} $< > $@ .PHONY: service service-dbus service-systemd service: service-dbus service-dbus: @sed "s|##PREFIX##|$(PREFIX)|" org.knopwob.dunst.service.in > org.knopwob.dunst.service ifneq (0,${SYSTEMD}) service: service-systemd service-systemd: @sed "s|##PREFIX##|$(PREFIX)|" dunst.systemd.service.in > dunst.systemd.service endif .PHONY: clean clean-dunst clean-dunstify clean-doc clean-tests clean: clean-dunst clean-dunstify clean-doc clean-tests clean-dunst: rm -f dunst ${OBJ} main.o rm -f org.knopwob.dunst.service rm -f dunst.systemd.service clean-dunstify: rm -f dunstify.o rm -f dunstify clean-doc: rm -f docs/dunst.1 clean-tests: rm -f test/test test/*.o .PHONY: install install-dunst install-doc \ install-service install-service-dbus install-service-systemd \ uninstall \ uninstall-service uninstall-service-dbus uninstall-service-systemd install: install-dunst install-doc install-service install-dunst: dunst doc install -Dm755 dunst ${DESTDIR}${PREFIX}/bin/dunst install -Dm644 docs/dunst.1 ${DESTDIR}${MANPREFIX}/man1/dunst.1 install-doc: install -Dm644 dunstrc ${DESTDIR}${PREFIX}/share/dunst/dunstrc install-service: service install-service-dbus install-service-dbus: install -Dm644 org.knopwob.dunst.service ${DESTDIR}${SERVICEDIR_DBUS}/org.knopwob.dunst.service ifneq (0,${SYSTEMD}) install-service: install-service-systemd install-service-systemd: install -Dm644 dunst.systemd.service ${DESTDIR}${SERVICEDIR_SYSTEMD}/dunst.service endif uninstall: uninstall-service rm -f ${DESTDIR}${PREFIX}/bin/dunst rm -f ${DESTDIR}${MANPREFIX}/man1/dunst.1 rm -rf ${DESTDIR}${PREFIX}/share/dunst uninstall-service: uninstall-service-dbus uninstall-service-dbus: rm -f ${DESTDIR}${SERVICEDIR_DBUS}/org.knopwob.dunst.service ifneq (0,${SYSTEMD}) uninstall-service: uninstall-service-systemd uninstall-service-systemd: rm -f ${DESTDIR}${SERVICEDIR_SYSTEMD}/dunst.service endif dunst-1.3.0/README.md000066400000000000000000000043601322374512000141310ustar00rootroot00000000000000[![Build Status](https://travis-ci.org/dunst-project/dunst.svg?branch=master)](https://travis-ci.org/dunst-project/dunst) [![Coverage Status](https://coveralls.io/repos/github/dunst-project/dunst/badge.svg?branch=coveralls)](https://coveralls.io/github/dunst-project/dunst?branch=master) ## Dunst * [Wiki][wiki] * [Description](#description) * [Compiling](#compiling) * [Copyright](#copyright) ## Description Dunst is a highly configurable and lightweight notification daemon. ## Installation ### Dependencies Dunst has a number of build dependencies that must be present before attempting configuration. The names are different depending on [distribution](https://github.com/dunst-project/dunst/wiki/Dependencies): - dbus - libxinerama - libxrandr - libxss - libxdg-basedir - glib - pango/cairo - libgtk-3-dev ### Building ``` git clone https://github.com/dunst-project/dunst.git cd dunst make sudo make install ``` ### Make parameters - `PREFIX=`: Set the prefix of the installation. (Default: `/usr/local`) - `MANPREFIX=`: Set the prefix of the manpage. (Default: `${PREFIX}/share/man`) - `SYSTEMD=(0|1)`: Enable/Disable the systemd unit. (Default: detected via `pkg-config`) - `SERVICEDIR_SYSTEMD=`: The path to put the systemd user service file. Unused, if `SYSTEMD=0`. (Default: detected via `pkg-config`) - `SERVICEDIR_DBUS=`: The path to put the dbus service file. (Default: detected via `pkg-config`) **Make sure to run all make calls with the same parameter set. So when building with `make PREFIX=/usr`, you have to install it with `make PREFIX=/usr install`, too.** Checkout the [wiki][wiki] for more information. ## Bug reports Please use the [issue tracker][issue-tracker] provided by GitHub to send us bug reports or feature requests. You can also join us on the IRC channel `#dunst` on Freenode. ## Maintainers Nikos Tsipinakis Jonathan Lusso ## Author written by Sascha Kruse ## Copyright copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) If you feel that copyrights are violated, please send me an email. [issue-tracker]: https://github.com/dunst-project/dunst/issues [wiki]: https://github.com/dunst-project/dunst/wiki dunst-1.3.0/RELEASE_NOTES000066400000000000000000000207741322374512000146340ustar00rootroot00000000000000=================================================================================== Release Notes For v1.3.0 =================================================================================== Version 1.3 is supposed to be fully backwards compatible with 1.2. For users: * Behavioural changes Dunst respects the timeout with millisecond accuracy now. Notifications with a one second timeout are not shown up to three seconds. Additionally you can specify timeout values in milliseconds, seconds, minutes, hours or days using the ms, s, h, or d suffix in the config value respectively. Transient notifications time out ignoring the `idle_threshold` setting and are not saved in history. This can be overridden with a rule containing `set_transient = no`. In the same vein there is the `match_transient` condition to match transient notifications via rules. A prefixed tilde (`~/`) in path settings (browser, dmenu, script) is interpreted as the home folder of the user. * Configuration Options `icon_folders` got deprecated and renamed to `icon_path`. `icon_folders` is still supported, but will get removed in future. The option `ellipsize` got introduced. It controls where to ellipsize the text of an overlong notification if `word_wrap = no`. For maintainers: * Dependencies The GTK3+ dependency got removed. Instead of this gdk-pixbuf is required explicitly. This had been a transient dependency before. In the Makefile, libxrandr is now specified to require version 1.5 or newer. The dependency on libxrandr >= 1.5 is not new, Dunst 1.2.0 required it too but there was no active check for it. * Installation process The internals of dunst's make installation process have slightly changed. The install routine won't install the service files for DBus and systemd in a hardcoded subdirectory of $PREFIX. It'll now query the `dbus-1` and `systemd` pkg-config packages for those paths and will put it there. To overwrite the pkg-config values, you can manually specify another path. Use `SERVICEDIR_(DBUS|SYSTEMD)` vars as parameters to your make calls. For all introduced variables, see [the README.md]. * Portability GNU-specific functions have been disabled to make dunst portable to nongnu libc's. For a full list of changes see [CHANGELOG.md]. =================================================================================== Release Notes For v1.2.0 =================================================================================== After about 3 years of inactivity, dunst is back under active development. Version 1.2 is supposed to be fully backwards compatible with 1.1 but due to the number of changes and the time since the last release there may be some overlooked breakages. If one is found it should be reported to the bug tracker. For users: * Markup The `allow_markup` setting has been deprecated in favour of `markup` which is a multi-value setting that can be used to control much more accurately how markup in notifications should be handled. Currently it only supports `no`, `strip` and `full` as values but it is planned to be expanded soon. To preserve backwards compatibility, `allow_markup` is still supported but users are encouraged to update their configuration files since it will be removed after a few major releases. * DPI handling The DPI value used is now retrieved from the `Xft.dpi` X resource if available. If not, the default value 96 will be used. Additionally, as an experiment a per-monitor dpi setting, which tries to calculate an acceptable dpi values for each monitor, has been added to the experimental section of the configuration file. * RandR and Xinerama Dunst switched from using the Xinerama extension to provide multi-monitor support to using the more modern RandR extension. While this change won't affect the majority of users, some legacy drivers do not support RandR. In that case, the `force_xinerama` option was added as a way to fall back to the old method. The downside of forcing Xinerama to be used is that dunst won't be able to detect when a monitor is added or removed meaning that follow mode might break if the screen layout changes. * Frame settings All the settings in the frame section of the configuration file have been deprecated and have been moved into the global section. The `color` and `size` settings became `frame_color` and `frame_size` respectively. As with `allow_markup`, the old format still works but it'll be removed in one of the next major releases. * Deprecation of urgency-specific command line flags The urgency specific command line flags (`-li, -ni, -ci, -lf, -nf, -cf, -lb, -nb, -cb, -lfr, -nfr, -cfr, -lto, -nto, -cto`) have been deprecated with no plan for a replacement. If you rely on them please respond to issue #328 on the bug tracker with your use case. For maintainers: * The project homepage has been changed to https://dunst-project.org * The main repository has been changed to https://github.com/dunst-project/dunst * Dependency changes: - Dependency on libraries that were unused in the code but were mentioned as dependencies has been dropped. Dunst no longer depends on: libfreetype, libxft and libxext. - Added dependency on libxradnr and libgtk2.0. For a full list of changes see CHANGELOG.md. =================================================================================== Release Notes For v1.0.0 =================================================================================== PACKAGE 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 =================================================================================== Release 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.3.0/config.h000066400000000000000000000120501322374512000142630ustar00rootroot00000000000000/* see example dunstrc for additional explanations about these options */ settings_t defaults = { .font = "-*-terminus-medium-r-*-*-16-*-*-*-*-*-*-*", .markup = MARKUP_NO, .normbgcolor = "#1793D1", .normfgcolor = "#DDDDDD", .critbgcolor = "#ffaaaa", .critfgcolor = "#000000", .lowbgcolor = "#aaaaff", .lowfgcolor = "#000000", .format = "%s %b", /* default format */ .timeouts = { 10*G_USEC_PER_SEC, 10*G_USEC_PER_SEC, 0 }, /* low, normal, critical */ .icons = { "dialog-information", "dialog-information", "dialog-warning" }, /* low, normal, critical */ .transparency = 0, /* transparency */ .geom = "0x0", /* geometry */ .title = "Dunst", /* the title of dunst notification windows */ .class = "Dunst", /* the class of dunst notification windows */ .shrink = false, /* shrinking */ .sort = true, /* sort messages by urgency */ .indicate_hidden = true, /* show count of hidden messages */ .idle_threshold = 0, /* don't timeout notifications when idle for x seconds */ .show_age_threshold = -1, /* show age of notification, when notification is older than x seconds */ .align = left, /* text alignment [left/center/right] */ .sticky_history = true, .history_length = 20, /* max amount of notifications kept in history */ .show_indicators = true, .word_wrap = false, .ellipsize = middle, .ignore_newline = false, .line_height = 0, /* if line height < font height, it will be raised to font height */ .notification_height = 0, /* if notification height < font height and padding, it will be raised */ .separator_height = 2, /* height of the separator line between two notifications */ .padding = 0, .h_padding = 0, /* horizontal padding */ .sep_color = AUTO, /* AUTO, FOREGROUND, FRAME, CUSTOM */ .sep_custom_color_str = NULL,/* custom color if sep_color is set to CUSTOM */ .frame_width = 0, .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 * */ .startup_notification = false, /* monitor to display notifications on */ .monitor = 0, /* path to dmenu */ .dmenu = "/usr/bin/dmenu", .browser = "/usr/bin/firefox", .max_icon_size = 0, /* paths to default icons */ .icon_path = "/usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/", /* follow focus to different monitor and display notifications there? * possible values: * FOLLOW_NONE * FOLLOW_MOUSE * FOLLOW_KEYBOARD * * everything else than FOLLOW_NONE overrides 'monitor' */ .f_mode = FOLLOW_NONE, /* keyboard shortcuts * use for example "ctrl+shift+space" * use "none" to disable */ .close_ks = {.str = "none", .code = 0,.sym = NoSymbol,.is_valid = false }, /* ignore this */ .close_all_ks = {.str = "none", .code = 0,.sym = NoSymbol,.is_valid = false }, /* ignore this */ .history_ks = {.str = "none", .code = 0,.sym = NoSymbol,.is_valid = false }, /* ignore this */ .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 */ /* an empty rule with no effect */ { .name = "empty", .appname = NULL, .summary = NULL, .body = NULL, .icon = NULL, .category = NULL, .msg_urgency = -1, .timeout = -1, .urgency = -1, .markup = MARKUP_NULL, .history_ignore = 1, .match_transient = 1, .set_transient = -1, .new_icon = NULL, .fg = NULL, .bg = NULL, .format = NULL, .script = NULL, }, /* ignore transient hints in history by default */ { .name = "ignore_transient_in_history", .appname = NULL, .summary = NULL, .body = NULL, .icon = NULL, .category = NULL, .msg_urgency = -1, .timeout = -1, .urgency = -1, .markup = MARKUP_NULL, .history_ignore = 1, .match_transient = 1, .set_transient = -1, .new_icon = NULL, .fg = NULL, .bg = NULL, .format = NULL, .script = NULL, }, }; /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ dunst-1.3.0/config.mk000066400000000000000000000023011322374512000144410ustar00rootroot00000000000000# paths PREFIX ?= /usr/local MANPREFIX = ${PREFIX}/share/man # Disable systemd service file installation, # if you don't want to use systemd albeit installed #SYSTEMD ?= 0 # uncomment to disable parsing of dunstrc # or use "CFLAGS=-DSTATIC_CONFIG make" to build #STATIC= -DSTATIC_CONFIG # Warning: This is deprecated behavior # flags CPPFLAGS += -D_DEFAULT_SOURCE -DVERSION=\"${VERSION}\" CFLAGS += -g --std=gnu99 -pedantic -Wall -Wno-overlength-strings -Os ${STATIC} ${CPPFLAGS} LDFLAGS += -lm -L${X11LIB} CPPFLAGS_DEBUG := -DDEBUG_BUILD CFLAGS_DEBUG := -O0 LDFLAGS_DEBUG := pkg_config_packs := dbus-1 \ gio-2.0 \ gdk-pixbuf-2.0 \ "glib-2.0 >= 2.36" \ pangocairo \ x11 \ xinerama \ "xrandr >= 1.5" \ xscrnsaver # check if we need libxdg-basedir ifeq (,$(findstring STATIC_CONFIG,$(CFLAGS))) pkg_config_packs += libxdg-basedir else $(warning STATIC_CONFIG is deprecated behavior. It will get removed in future releases) endif # dunstify also needs libnotify ifneq (,$(findstring dunstify,${MAKECMDGOALS})) pkg_config_packs += libnotify endif dunst-1.3.0/contrib/000077500000000000000000000000001322374512000143075ustar00rootroot00000000000000dunst-1.3.0/contrib/dunst_espeak.sh000077500000000000000000000001041322374512000173260ustar00rootroot00000000000000#!/bin/bash summary="$2" body="$3" echo "$summary $body" | espeak dunst-1.3.0/docs/000077500000000000000000000000001322374512000135775ustar00rootroot00000000000000dunst-1.3.0/docs/dunst.pod000066400000000000000000000531151322374512000154450ustar00rootroot00000000000000=head1 NAME dunst - A customizable and lightweight notification-daemon =head1 SYNOPSIS dunst [-conf file] [-font font] [-geometry geom] [-format fmt] [-follow mode] [-monitor n] [-history_length n] ... =head1 DESCRIPTION Dunst is a highly configurable and lightweight notification daemon. =head1 COMMAND LINE OPTIONS =over 4 =item B<-h/--help> List all command line flags =item B<-conf/-config file> Use alternative config file. =item B<-v/--version> Print version information. =item B<-print> Print notifications to stdout. This might be useful for logging, setting up rules or using the output in other scripts. =back =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. The configuration is divided into sections in an ini-like format. The 'global' section contains most general settings while the 'shortcuts' sections contains all keyboard configuration and the 'experimental' section all the features that have not yet been tested thoroughly. Any section that is not one of the above is assumed to be a rule, see RULES for more details. For backwards compatibility reasons the section name 'frame' is considered bound and can't be used as a rule. =head2 Command line Each configuration option in the global section can be overridden from the command line by adding a single dash in front of it's name. For example the font option can be overridden by running $ dunst -font "LiberationSans Mono 4" Configuration options that take boolean values can only currently be set to "true" through the command line via the same method. e.g. $ dunst -shrink This is a known limitation of the way command line parameters are parsed and will be changed in the future. Available settings per section: =head2 Global section =over 4 =item B (default: 0) Specifies on which monitor the notifications should be displayed in, count starts at 0. See the B setting. =item B (values: [none/mouse/keyboard] default: none) Defines where the notifications should be placed in a multi-monitor setup. All values except I override the B setting. =over 4 =item B The notifications will be placed on the monitor specified by the B setting. =item B The notifications will be placed on the monitor that the mouse is currently in. =item B The notifications will be placed on the monitor that contains the window with keyboard focus. =back =item B (format: [{width}][x{height}][+/-{x}[+/-{y}]], default: "0x0+0-0") The geometry of the window the notifications will be displayed in. =over 4 =item B The width of the notification window in pixels. A negative value sets the width to the screen width B. If the width is omitted then the window expands to cover the whole screen. If it's 0 the window expands to the width of the longest message being displayed. =item B The number of notifications that can appear at one time. When this limit is reached any additional notifications will be queued and displayed when the currently displayed ones either time out or are manually dismissed. If B is true, then the specified limit is reduced by 1 and the last notification is a message informing how many hidden notifications are waiting to be displayed. See the B entry for more information. The physical(pixel) height of the notifications vary depending on the number of lines that need to be displayed. See B for changing the physical height. =item B Respectively the horizontal and vertical offset in pixels from the corner of the screen that the notification should be drawn at. For the horizontal(x) offset, a positive value is measured from the left of the screen while a negative one from the right. For the vertical(y) offset, a positive value is measured from the top while a negative from the bottom. It's important to note that the positive and negative sign B affect the position even if the offset is 0. For example, a horizontal offset of +0 puts the notification on the left border of the screen while a horizontal offset of -0 at the right border. The same goes for the vertical offset. =back =item B (values: [true/false], default: true) If this is set to true, a notification indicating how many notifications are not being displayed due to the notification limit (see B) will be shown B. Meaning that if this is enabled the number of visible notifications will be 1 less than what is specified in geometry, the last slot will be taken by the hidden count. =item B (values: [true/false], default: false) Shrink window if it's smaller than the width. Will be ignored if width is 0. This is used mainly in order to have the shrinking benefit of dynamic width (see geometry) while also having an upper bound on how long a notification can get before wrapping. =item B (default: 0) A 0-100 range on how transparent the notification window should be, with 0 being fully opaque and 100 invisible. This setting will only work if a compositor is running. =item B (default: 0) The minimum height of the notification window in pixels. If the text and padding cannot fit in within the height specified by this value, the height will be increased as needed. =item B (default: 2) The height in pixels of the separator between notifications, if set to 0 there will be no separating line between notifications. =item B (default: 0) The distance in pixels from the content to the separator/border of the window in the vertical axis =item B (default: 0) The distance in pixels from the content to the border of the window in the horizontal axis =item B (default: 0) Defines width in pixels of frame around the notification window. Set to 0 to disable. =item B (default: #888888) Defines color of the frame around the notification window. See COLORS. =item B (values: [auto/foreground/frame/#RRGGBB] default: auto) Sets the color of the separator line between two notifications. =over 4 =item B Dunst tries to find a color that fits the rest of the notification color scheme automatically. =item B The color will be set to the same as the foreground color of the topmost notification that's being separated. =item B The color will be set to the frame color of the notification with the highest urgency between the 2 notifications that are being separated. =item B Any other value is interpreted as a color, see COLORS =back =item B (values: [true/false], default: true) If set to true, display notifications with higher urgency above the others. =item B (default: 0) Don't timeout notifications if user is idle longer than this time. See TIME FORMAT for valid times. Set to 0 to disable. Transient notifications will ignore this setting and timeout anyway. Use a rule overwriting with 'set_transient = no' to disable this behavior. =item B (default: "Monospace 8") Defines the font or font set used. Optionally set the size as a decimal number after the font name and space. Multiple font options can be separated with commas. This options is parsed as a Pango font description. =item B (default: 0) The amount of extra spacing between text lines in pixels. Set to 0 to disable. =item B (values: [full/strip/no], default: no) Defines how markup in notifications is handled. It's important to note that markup in the format option will be parsed regardless of what this is set to. Possible values: =over 4 =item B Allow a small subset of html markup in notifications bold italic strikethrough underline For a complete reference see =item B This setting is provided for compatibility with some broken clients that send markup even though it's not enabled on the server. Dunst will try to strip the markup but the parsing is simplistic so using this option outside of matching rules for specific applications B. See RULES =item B Disable markup parsing, incoming notifications will be treated as plain text. Dunst will not advertise that it can parse markup if this is set as a global setting. =back =item B (default: "%s %b") Specifies how the various attributes of the notification should be formatted on the notification window. Regardless of the status of the B setting, any markup tags that are present in the format will be parsed. Note that because of that, if a literal ampersand (&) is needed it needs to be escaped as '&' If '\n' is present anywhere in the format, it will be replaced with a literal newline. If any of the following strings are present, they will be replaced with the equivalent notification attribute. =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%]) =item B<%n> progress value without any extra characters =item B<%%> Literal % =back If any of these exists in the format but hasn't been specified in the notification (e.g. no icon has been set), the placeholders will simply be removed from the format. =item B (values: [left/center/right], default: left) Defines how the text should be aligned within the notification. =item B (default: -1) Show age of message if message is older than this time. See TIME FORMAT for valid times. Set to -1 to disable. =item B (values: [true/false], default: false) Specifies how very long lines should be handled If it's set to false, long lines will be truncated an ellipsised. If it's set to true, long lines will be broken into multiple lines expanding the notification window height as necessary for them to fit. =item B (values: [start/middle/end], default: middle) If word_wrap is set to false, specifies where truncated lines should be ellipsized. =item B (values: [true/false], default: false) If set to true, replace newline characters in notifications with whitespace. =item B (values: [true/false], default: true) If set to true, duplicate notifications will be stacked together instead of being displayed separately. Two notifications are considered duplicate if the name of the program that sent it, summary, body, icon and urgency are all identical. =item B (values: [true/false], default: false) Hide the count of stacked duplicate notifications. =item B (values: [true/false], default: true) Show an indicator if a notification contains actions and/or open-able URLs. See ACTIONS below for further details. =item B (values: [left/right/off], default: off) Defines the position of the icon in the notification window. Setting it to off disables icons. =item B (default: 0) Defines the maximum size in pixels for the icons. If the icon is smaller than the specified value it won't be affected. If it's larger then it will be scaled down so that the larger axis is equivalent to the specified size. Set to 0 to disable icon scaling. (default) If B is set to off, this setting is ignored. =item B (default: "/usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/") Can be set to a colon-separated list of paths to search for icons to use with notifications. Dunst doesn't currently do any type of icon lookup outside of these directories. =item B (values: [true/false], default: true) If set to true, notifications that have been recalled from history will not time out automatically. =item B (default: 20) Maximum number of notifications that will be kept in history. After that limit is reached, older notifications will be deleted once a new one arrives. See HISTORY. =item B (default: "/usr/bin/dmenu") The command that will be run when opening the context menu. Should be either a dmenu command or a dmenu-compatible menu. =item B (default: "/usr/bin/firefox") The command that will be run when opening a URL. The URL to be opened will be appended to the end of the value of this setting. =item B (values: [true/false] default: true] Always run rule-defined scripts, even if the notification is suppressed with format = "". See SCRIPTING. =item B (default: "Dunst") Defines the title of notification windows spawned by dunst. (_NET_WM_NAME property). There should be no need to modify this setting for regular use. =item B<class> (default: "Dunst") Defines the class of notification windows spawned by dunst. (First part of WM_CLASS). There should be no need to modify this setting for regular use. =item B<startup_notification> (values: [true/false], default: false) Display a notification on startup. This is usually used for debugging and there shouldn't be any need to use this option. =item B<force_xinerama> (values: [true/false], default: false) Use the Xinerama extension instead of RandR for multi-monitor support. This setting is provided for compatibility with older nVidia drivers that do not support RandR and using it on systems that support RandR is highly discouraged. By enabling this setting dunst will not be able to detect when a monitor is connected or disconnected which might break follow mode if the screen layout changes. =back =head2 Shortcut section Keyboard shortcuts are defined in the following format: "Modifier+key" where the modifier is one of ctrl,mod1,mod2,mod3,mod4 and key is any keyboard key. =over 4 =item B<close> B<command line flag>: -key <key> Specifies the keyboard shortcut for closing a notification. =item B<close_all> B<command line flag>: -all_key <key> Specifies the keyboard shortcut for closing all currently displayed notifications. =item B<history> B<command line flag>: -history_key <key> Specifies the keyboard shortcut for recalling a single notification from history. =item B<context> B<command line flag>: -context_key <key> Specifies the keyboard shortcut that opens the context menu. =back =head2 Urgency sections The urgency sections work in a similar way to rules and can be used to specify attributes for the different urgency levels of notifications (low, normal, critical). Currently only the background, foreground, timeout, frame_color and icon attributes can be modified. The urgency sections are urgency_low, urgency_normal, urgency_critical for low, normal and critical urgency respectively. See the example configuration file for examples. Additionally, you can override these settings via the following command line flags: Please note these flags may be removed in the future. See issue #328 in the bug tracker for discussions (See REPORTING BUGS). =over 4 =item B<-li/ni/ci icon> Defines the icon for low, normal and critical notifications respectively. Where I<icon> is a path to an image file containing the icon. =item B<-lf/nf/cf color> Defines the foreground color for low, normal and critical notifications respectively. See COLORS for the value format. =item B<-lb/nb/cb color> Defines the background color for low, normal and critical notifications respectively. See COLORS for the value format. =item B<-lfr/nfr/cfr color> Defines the frame color for low, normal and critical notifications respectively. See COLORS for more information =item B<-lto/nto/cto secs> Defines the timeout time for low, normal and critical notifications respectively. See TIME FORMAT for valid times. =back =head1 HISTORY Dunst saves a number of notifications (specified by B<history_length>) in memory. These notifications can be recalled (i.e. redesiplayed) by pressing the B<history_key> (see the shortcuts section), whether these notifications will time out like if they have been just send depends on the value of the B<sticky_history> setting. Past notifications are redisplayed in a first-in-last-out order, meaning that pressing the history key once will bring up the most recent notification that had been closed/timed out. =head1 RULES Rules allow the conditional modification of notifications. They are defined by creating a section in the configuration file that has any name that is not already used internally (i.e. any name other than 'global', 'experimental', 'frame', 'shortcuts', 'urgency_low', 'urgency_normal' and 'urgency_critical'). There are 2 parts in configuring a rule: Defining the filters that control when a rule should apply and then the actions that should be taken when the rule is matched. =over 4 =item B<filtering> Notifications can be matched for any of the following attributes: appname, summary, body, icon, category, match_transient and msg_urgency where each is the respective notification attribute to be matched and 'msg_urgency' is the urgency of the notification, it is named so to not conflict with trying to modify the urgency. To define a matching rule simply assign the specified value to the value that should be matched, for example: appname="notify-send" Matches only messages that were send via notify-send. If multiple filter expressions are present, all of them have to match for the rule to be applied (logical AND). Shell-like globing is supported. =item B<modifying> The following attributes can be overridden: timeout, urgency, foreground, background, new_icon, set_transient, format where, as with the filtering attributes, each one corresponds to the respective notification attribute to be modified. As with filtering, to make a rule modify an attribute simply assign it in the rule definition. If the format is set to an empty string, the notification will not be suppressed. =back =head2 SCRIPTING Within rules you can specify a script to be run every time the rule is matched by assigning the 'script' option to the name of the script to be run. When the script is called details of the notification that triggered it will be passed via command line parameters in the following order: appname, summary, body, icon, urgency. Where icon is the absolute path to the icon file if there is one and urgency is one of "LOW", "NORMAL" or "CRITICAL". If the notification is suppressed, the script will not be run unless B<always_run_scripts> is set to true. If '~/' occurs at the beginning of the script parameter, it will get replaced by the users' home directory. If the value is not an absolute path, the directories in the PATH variable will be searched for an executable of the same name. =head1 COLORS Colors are interpreted as X11 color values. This includes both verbatim color names such as "Yellow", "Blue", "White", etc as well as #RGB and #RRGGBB values. B<NOTE>: '#' is interpreted as a comment, to use it the entire value needs to be in quotes like so: separator_color="#123456" =head2 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 ACTIONS Dunst allows notifiers (i.e.: programs that send the notifications) to specify actions. Dunst has support for both displaying indicators for these, and interacting with these actions. If "show_indicators" is true and a notification has an action, an "(A)" will be prepended to the notification format. Likewise, an "(U)" is preneded to notifications with URLs. It is possible to interact with notifications that have actions regardless of this setting, though it may not be obvious which notifications HAVE actions. The "context" keybinding is used to interact with these actions, by showing a menu of possible actions. This feature requires "dmenu" or a dmenu drop-in replacement present. Alternatively, you can invoke an action with a middle click on the notification. If there is exactly one associated action, or one is marked as default, that one is invoked. If there are multiple, the context menu is shown. The same applies to URLs when there are no actions. =head1 TIME FORMAT A time can be any decimal integer value suffixed with a time unit. If no unit given, seconds ("s") is taken as default. Time units understood by dunst are "ms", "s", "m", "h" and "d". Example time: "1000ms" "10m" =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 respectively. 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 FILES $XDG_CONFIG_HOME/dunst/dunstrc -or- $HOME/.config/dunst/dunstrc =head1 AUTHORS Written by Sascha Kruse <knopwob@googlemail.com> =head1 REPORTING BUGS Bugs and suggestions should be reported on GitHub at https://github.com/dunst-project/dunst/issues =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.3.0/docs/dunst_layout.png�������������������������������������������������������������������0000664�0000000�0000000�00000055700�13223745120�0017046�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� 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-[hU<CT6mڶ}vvvxq)5e;Lc՝E=0sLT*3kKWFpt ڵÔ)SPPP`tQ$jb�K,C /^,Dvv|xꩧ,_.<<\ 6L7n@񢰰PܺuK̜9Sk:)"˗uj-3fcԾZbagg'agg'j]Y>cO_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#<btgM};vz666] ! aaaZ˩T*&z!lllD=ҥKEiiʼnKXrr<x]v~[rСӧOb]WZ-w67Dt:0K2z]vݽm5T:y%7""7ބ4&QCt~ID@,P'""jP'""j$NDDD$9K'""j$3t:Q#t:Q# t"""j$Y:Q#3ԉ)w:1ЉY:NDD@g5@'""bsNDD@g1DDD t""":gDDD t:NDD@'""b7!NDD@g5nwc~nڷwÈNDbܹصk6-ٳ' 19K'.^OO\m{ѣ9`P'3[F\.=d2֮] GGGjΝ;___١M6A~~V=ᰵEǎg,_vvvprr‘#Gj4[nh׮Lꊳg�rrr ˑ �8s \]]̺U*f̘;;;888`ժUUf21dƍpvvR'RRR'zYYBCCѵkW"**Jz\II �Dz :C !fǡh��~~~1cq%t ,Ъ+//YYY㑛L,^s̑n::t@ii)BCCunߨQp!�={`mm{�~G<sUs琔|w�p!cԨQ6mTv5;w?8�`ʕ駟 . ;;[zeː$$$ )) :ϘV jO&@d�qEܾ}[tEkׯ !(..rZz8stʕ+k׮:ߊ�!F=zBwU*NNN*u� BTJ7o,|}}=z:{Us<P_P' ,>eȦ sW0o<<y7o�( U[Gu[hJ%ZmEEEׯN:'''\p...|2郴4hկT*qM��T*r] Cbј<yuTk(**R1h4<S<ND5;_nܸq4iQVVׯ `c899Ij!KT6lڶmC޽ujΝqvFFF6Eii)<uz:ߩS'sf?7:C,�[F˖-7x캦O_iiiPTHNNƸq}Q|r9�0rH,_F#((999A``Vy푖f9rzB&OYf… y&�̞=ٳnSK;ĈY@si߾}W^J899hK뻭VEttpss֢_~bn߹s� !8u� Ο?b1m4akk+:t "##ʣD۶mz_]!EXXVJ .]tb͚5RYQQ5kb֬YHLy,=&陸)a={b׮]pwwgcP<<(m@t=jt2w0D t" Jdd$agg3gJ_m _L)"P&jXII O.٠_D2̉蜥E}HLLDRRΞ=˗KeT`ƔWaBrr2qAvs*55=� 55cƌwA>}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Ժ-Uoxx8D<xu?ׅ|W 5cu]0yyyppp��}(,,�wF^��W^ŠApEI탛�ӧ@N >u+#VEPTRrjqaJT*q-X[[�T*Ѫxʽ;|[r;99!==eeePB sK۩S'dffJMA_ u>͚l>}:^uAR!99ƍz0g"77s1i]۷GZZ !i rf?Ou֘0aE}; +++5|<c<V?|ܦl{Ն*)) &z'9K'b׋x]뫠 \rYYY3g90jnCL۔m]gM>jc{J1c <<&}S<p66d{s\2ȉ3t"۔@'x r":C6#90'im8D , gD t>D 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%<K_0z[:>Q�F鎃;4y,.;*5zj�qaX8yΨmX]_:&b܆�M__ QOV#<L]m=䟋]GB)nBZߦov��U;]d2gk6 nmuvz'G��0ERM!�o~i�j~~w<�EFo|dm�sɿ:}`]:H]L_P�ӠhngbNý2~+�@֏ z W=.Є<PS ĉB5\H*~ {}�no5WpKA3PV24;ߺJ /Z|[K�H}}c`>XP>{O|c6[Ϡ"%5Rv@&ھK/tuٰ pL8S L P[`FLaċWoegT8o6y3Ղ ,oWfFr>Hltŝ91�|=ˡh1((-kӺn^<n\`دEmߢpTbܵ;�N]ͪ*qYȿSPu�/JP[r�⟽Ҍӣ�� WTv-/`Bm׊"}6̢;%&/lv frlܚŃqNgd; FC]S="v vV$Xxx?L86 A{:-s8e> ɌM=/xc'Rqܱˬ:e?on3?ޟ8G/ĩߎZyo/}Yt:>Ͻ/5Bw1<vLG~u/a J۱#XᔴLȆq,Y }9+ ZOljon _UQ{eںb}1̤esJB?|uKŃ582Qo6qߡ')9V[sv @(kD#q'Ƃ =ÏFcx&Va~8x8CZ%4 s�J -f94jN40':QPc1ЉNDDD t"""?GD ƙؕx~D`r%O1ЉNDDD t"""b1ЉNDDD t2GZ̊:멋:M]җuѮ7CmyE_|U∨_G.C^%ld'ܑoQ=l_>z�`c bp'YJߕg\_e+/9+^\!3zmlֱ+¤:uza|ox:ұd62Gm];ZX\,gFMgz^,*O߶T7&e<;yZw~!ӾR:JaoY7{^{[=3w1Щ !SsDl^*)xp;_b�Cq ϼ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;b�4j1XpNNѸ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?ض-.4<Qmbo?:zu'슀^3l| %2pRniN69pAZXp/M {YMAv:?"iG8f6_.o@Ohݿv\6å%f='w,C\7pqQ 34o\۞n;Y r+[vD/9UL\:pU{K q{~0r@ڿXgg� 遥x o=:-/vBTԪ{C]u*mhrrʁh{9w'G᭱a`5}۹!|:ST;BjܦBh0( whλe5ZiS}cT V#c<< RWUQ?�>u_h8dl�sO䢃ǫ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~8dq�zkoI`,`jq%T}gBe6�=:<?yQe_D|^"c ]4iOC~ օ"_7&Y z^2MϤ>JY25F`g.:WNcĻU9[?C魣ǴFcMnj6dxiR0<Q !vJƞ`Xi  ixʝNT?qvND t"""bDDD t"""b}A'"bDDD@'""bDDD@'Ŭ0q>2.k:jl'K؋_޵/k+!c*q U[<76?o~Æ!bF]KNlK#+|a{ضX}�ƶ'N688 ;yS v?_ pBKԉ~?K5zj`_0D&w\p5.;^ug}=fkcHT<PvZ ~g{^nB#⧛]_ ݽo܉c�`1ILN}mzUváH|G?�L\4e?lǁ,?2ގG?x "q zS(|';OW0|y޺,J|Ͳ'1U0iF|v�; 9e;Ouu+'_1gmDԱ?:q9kcowg,''.*spk' EV l8xy2~@a;P5 `rI]A}[�֝Cg l*,?ގ׳?VF �' w_yv'?&9�,:o@A�׻3w}&DžNپ K n%bSe׫19Je-#D(L}VPdP�Tg]�F5qyw d>xG[ɀRO[j ɀ)I} SEuDuro�Z+dwerk~cW7XɛiN>�()ԵTv-e/j�wZumV,-׵]�`d tb3\aOXe\^>z5i|¢zus9͕>E7uK;*|3t2sql yB|F-5"I68 I2q�wpዱAvn8%-Gl2Kֆ \&-'^|k6\=|_Eufÿsö̻ �EO1 Zux?6w20oz5m]/ކ%3SD)ΟMCP^--7b⃷+D'Z+{ RCkk?OX c4P<NDD@'j:8;'":*qDD t""":1ЉNDDD t""":1IF ""zSq&v%DDD tzW#"b1ЉNTҿ^EXӠڬD@!#jl ꖱ`/\cϸ'Ik;-]~*x 3GB^Z_ZԻ6yA,K/ I@'sWplc\@w'GCLⓗ0fKVۇ/g, Ke5"<^);w`[\V m|l] kn^mKqb_]/|9I;`x]sթ3�qmO|j-&Σ0N�Vms|wK(-=1pzb�sv[w,7T4wN2l F@.eͰ6Z?"c0 DwAbZeC^ xLXC!C*W> '#/J�`|޿hc1q)΀ '�Pl=7UVVr4nϿ-�6O_SkOs�J3`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=aKn1xu<YWfF\aezGzBdl~-aԺgum7v;�شE@vʊ(s܆Q2}- 0?5l ߳Kc'/ ,?bT: xKތ_D|NImʶF@9rN~):8,=N狊8I0V{}>ok޵;fņ6.}�T�8? !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;5<N@'""":gDDD_[:W|U8ڈI'bS ga~p~]?c)w&(&&lC/|F:g =ƣzQ?7yooOl gDDM"U?bsvNĠb[1ЉD tΉNDuۋ蜝CND (s"b@'"ێ^t;1ԉNDP'bsvN@"":_sΉDlCb1ԉ蜝CND "%19;윈@'"":19;׵a im$*� mJT vF_ܟBz;?Zbv(gMpv+U?y3glS"Л.-NKY3=nxBT"�@ٮSkq)KVlÑ*ew zAq�`쌰}= -y\d:? JM>136;-(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<hG:1蜝ÂD t",<%:1ԩ :O1$3tA\esvNDP':1 }FM=9;%Љ@7:gDD ub D.5@윈@? 9U>@'"}HM=9;'"bgă@윈t:1Љń9=Oױ3t""<CT6m4 ѭ[7kSLAAA.22̙3RsvvvhӦ |||ڵk\np2LfII �DzF'"W^App0nݺ#G_~֭[C!>>(--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<S/@x{H�G��5IDAT{Lq=d1dիm۶BR !Æ ƍ"00PzlHH/_s<v̘1"''=}666ZW)**666Zh4.#EvvIu&]>|��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Є;wN�BSN �:$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<CT6mW㘧݉NM^߾}SOaÆ fב,cEff&/^9sh=Gbb"pY,_\*[n:xdddZ>|Ǐ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>""{m�eY+ld7&s^8_%=Lb$' 1?^8A0aLIrk(nuVcFa8)9]'|?o99ϭ<Ow~m6🣝=n8AX,�@OOl6 �`„ ()sUՊ@ hնhhEvc8}4��l6?SoHOO;1i"NӘr_MbvggimmUl󡯯e0akRTe ^Yͅ狚VQQe˖)եꛡR|߱sNXi8pdZꫯFƌx뭷ݍ˗//hoo¢EJ\Bz+Ӊkcڵp:I''N^|9ڊ@ �˥uww;w܁6ZJu^ӉƍDL6/јU800 =jGyD,L2E}]k֏l"b٤Rzzz~iӦIjjw}R__i0*̔L`0>o.iii1HBHNNl6ٱc EEE"yyyRWW:WD%|'|"EEEbXFq hp8ND4:f 9NЉ:q@S4xLִ;?twl8#D"D7MP_(:]�4n%+V7Ϗ-\aa!~�@{{;L&:::��.\@aazh+g(m<y2nݪ-Zyz"镩էF?P(5k(el۶-VĶqiF3rL4I@FF~7kH-DiX?9EХukQ)2Hl79455�+�cǎ'4\xڥohkkũSpѸ?#镩էFۼi&v1ՋئcPQQ{OuC= k( 9Q̷iEJ$W""Eb w!q:""p8䥗^s犈ȳ>+-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(vko<m!: +zK$W$<-RKK�gϊș3g\x1zE-r_0UVf ٸqELW^,{zzdJ[n/Z۠/kPD^rrr_9_zm!m6x<(++ӝzeƳҞ={pelٲe ;rى6TWWlTyعs'^x^t4g1sLB!<Өe'}Ʉ]vi~ah\r'1y;2r'k'9oCy~2?o<lܸ) uuuYYYIy ??YOC|kQRRχ4^|B':E\́F7T眜<Sػw//$~Fiihh@yy-O0?kE'V__6l؀)SfaJZ"≾IMeO/]z �_͋8SlhE':Ֆ-[pq466~_IK$[<iS8^]|A9s+҉b^D2~x%*UiE'=#^PDp'Zd^^ҍD3Ӌl۫F9J^`"")F x^dffj"(Nzzz`Zyl8ۍ��ٌ0aB500�3JY}zzuԫ �\v Ÿv/$S4ɓ Ot\|iDp'PO/=ut())iw"ۜ9sϏ':QUUV\.%-nF_'ܹsyQNKv"i+ IMMf;v *[׌)Q.~_v矼XN5tٳ8p�;c)//GIIoSN#NDF>F[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<y2&Ou֡Wybd9=n8ٳZ5k~g7<Jzq}}}ذaL͆۷+i---X`v;.?0T^8ۋcXd ۧHjkk׋SNѣ~m455.]7aÆ8wn7n7<6mڤzbͧ#G V^##ٲe ?F+i< ֬YߏW";;/viH)јgO<l?+SNfeܹs~6m\pA)S,oԩr9eJAAw -VuPL߾{׿[rrr kZ[KqD4̙3 .ĢE��{ŧ~�V+/�zzz`Z' &  )O7gZ`Xl62urY"L;qi�lF__Ro;a׮]7|ŋ+K,#Gp5�@VV._ڪ#//>}}}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<H-??3gD(O?Zv {r'""8NDck{a-Oyaƍ|B'"IPTTǃ,CXM x^QRRχ4>~! 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<cb�٬*GUvxݬVf1b2Lt}�d``@WPc#씌}msʝƔb<yR_|~wܹ+V0V0ՊB޽3gDZZf̘]vaQÒӋhp<OH٪c/]t1n%%%\C'")̙9N?7nh2~kԋp8yf̞=�0{ll޼C7ofɎ w`"SP{ rXn-8p�s:H%v]~O>"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.3.0/docs/dunst_layout.xcf�������������������������������������������������������������������0000664�0000000�0000000�00000144415�13223745120�0017044�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������gimp xcf file���������������������B��B����������������������������gimp-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) ������������� ������@�� ��+��<��C4��I1��a����������������������"Indicator for Actions/URLs/dup...���������������������� �������������������� ���������� ���������� ���������� ����������������������������������������������gimp-text-layer������(markup "<markup><span size=\"12288\"><span foreground=\"#000000\"><span font=\"Sans\">Indicator for Actions/URLs/duplicates</span></span></span></markup>") (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�� ~������������X��C��%�� X�������P��(�P��� �������:��� ���������h,��u����s��&����q����� ~������{#��A �������������� ��*��,����,������*��,��������������������4������������������*��+����,��^����*��,����������������z"��P"��)��������������u����q��Jm��r�������������� ����*�:9������q��xy��s������h,��Cd��������{#�������� ~��S��*��,����V��U��,������*��,���� ��m��`������[��\����������������2I��*��,�����,������*��,������K�����r��q�z"��)����������*K�C����@��?�q��r����������XA�}�<�1�~����i�����2�����%��� �2������~���������� �~������B������� ����Cd��B�u������v���������������S������������������� ����m��`��*��,������*��+����`�������#Q����2I��`������������������.��/���� ���K���*��+�� ����*��*����}++z����g���*K�C��悹��~ !������}|����F��XA�}��u��,i��v��� �1���=��=������I����"�����s��&��| ��Cd���{#��A ����r!��S���,������'����m������4������2I���,��^���� ��K���z"��P"��)��o)-��*K���q��Jm��r��mO��XA�n����r����������9����������3���������padding���������������������� �������������������� ���������� ���������� ���������� �������������������N������������������� ��������gimp-text-layer������e(markup "<markup><span foreground=\"#000000\"><span font=\"Sans\"><span size=\"12288\">padding</span></span></span></markup>") (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��&��u��u����h,��v����A �������� ~����*��+����*��,��*��,������ ��(��*������4��������������������*��*��^��*��+��*��+��������(��*����P"��������������v��Jm��u��u��������}���,�.���)����)�o ����l���������frame width/color���������������������� �������������������� ���������� ���������� ���������� ������������������D������������������� ��������gimp-text-layer������o(markup "<markup><span foreground=\"#000000\"><span font=\"Sans\"><span size=\"12288\">frame width/color</span></span></span></markup>") (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) ������������������l�����������������l��������������@�P�4���:�-��������5������q�&��|Ny>��| ��'��(���u������A ��cc��r!��g�if�i�����������������'����I��H���*��,��������4����������  �������������^�������� ��w�w����*��+��������P"��������o)-��ns�tm������)������Jm��������mO��%.�/$����u��r���h�h�h�-�������2������~����h,��B�s��������q�� ~���{#������������ ��`��,��*��,����*��,��������`���������������������,��*��,����*��,�������C��z"�������������}��q��������� �1�w����6����������D��������� line_height���������������������� �������������������� ���������� ���������� ���������� ���������������o���_������������������� ��������gimp-text-layer������i(markup "<markup><span foreground=\"#000000\"><span font=\"Sans\"><span size=\"12288\">line_height</span></span></span></markup>") (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����������4�������D���������������@������������ ���������������h,��| ��h,��| ����v��h,������ ~��r!�� ~��r!������ ~�������� ��'������ ��'������(��*���� ������������������������������������ � ����� ����(��*��������������o)-������o)-������������������mO������mO����}�����4�.�9����o ��7�7�7���������������)��r� ����"����������D���������separator ...���������������������� �������������������� ���������� ���������� ���������� �����������������^���������������������������gimp-text-layer������t(markup "<markup><span foreground=\"#000000\"><span font=\"Sans\"><span size=\"12288\">separator\nwidth/color</span></span></span></markup>") (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�&����q��S��r!����A ����A ��������m��'����*��+����������*��,����2I��������4����4����������K�� ��*��*��^����^����*��,����ߧ*K��o)-����P"����P"��)������XA��mO��v��Jm����Jm��r�������=��=������������������2����������~����'��(���u��h,��B�s��������g�if�i������� ~���{#��������I��H���*��,������ ��`��,��*��,����*��,��  �������������`��������������w�w����*��+���������,��*��,����*��,��ns�tm������)�����C��z"��������%.�/$����u��r�����}��q�������!�1���o�o�o�G�q���������� ����"�������������������Age of notification ...���������������������� �������������������� ���������� ���������� ���������� ���������������<�����������������������������gimp-text-layer������(markup "<markup><span foreground=\"#000000\"><span font=\"Sans\"><span size=\"12288\">Age of notification\n(show_age_threshold)</span></span></span></markup>") (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���������������#��+��+������������#��'��*����������d�P���:����:9������xy��v��| ����h,��������r!������ ~������V��U��(��*��'����*��,������ ��*��,����[��\�������������������������(��*�� ��*��,��������*��,����r��q���o)-������������)�@��?�}��mO������������r� �.�9��9�o �q����1����1�q���1�H��Cd��h,���'��(��&��v��|���S�� ~���g�if�i��A ����r!���m���� ��*��,�I��H� ���(��*��'����2I���������  ��4�������J��K������*��,��w�w��^��(��*�� ��q��*K��������ns�tm��P"����o)���XA��������%.�/$��Jm��}��m���2�.�9��)� �o ��������D�V�8���2�������"����$���s��&������h,��������{#��A �������� ~��������,��������*��,���� ����������4��������������������,��^������*��,������������z"��P"��)����������������q��Jm��r������������� �� ������ �� ������ ��� ���h,��q�| ��Cd��h,���������� ~����r!��S�� ~������������ ����'����m���� ��*��,����*������������2I���������������������� ��K������*��,����*-��)��������o)-��*K����������O��r��������mO��XA�������������6��������~��������l�u��K�����,��������+��K���m�u����~�����E����������"�������������*������Number of notifications ...���������������������� �������������������� ���������� ���������� ���������� ���������������������������������������������gimp-text-layer������(markup "<markup><span foreground=\"#000000\"><span font=\"Sans\"><span size=\"12288\">Number of notifications\nthat are waiting to get displayed\n(indicate_hidden)</span></span></span></markup>") (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��| ��q�����q��������cc����r!����ꂻ����������������*��+��'������*��,������z����������������������������� �� ����������*��*�� ����*��,������[��~ !����������o)-������������,i��������v��mO���������8���� ��(���� ��'��h,��&��&��q�| ��'��(��� ~��A ����A ����r!��g�if�i����� ����� �����'����I��H�������4����4������  �������^����^���� ��w�w��)������P"��)��P"����o)-��ns�tm��r������Jm��r��Jm����mO��%.�/$�����%������ ���������q��������H����h,��u����s��&��| ��h����� ~������{#��A ����r!��������� ��*��,����,������'���������������������4��������J��������*��+����,��^���� � ���q������������z"��P"��)��o)-������������u����q��Jm��r��mO�������� � � �?���V�3�����2������������� ���h,������s��&������ ~������������{#��A ���������� ��*��,����������,��������*��������������������4������������*��,����������,��^������*��������)��������z"��P"��)������������r��������q��Jm��r�����H��������!�����&������h,��v�����v��A �������� ~�������������������� ��(��*����*��,��(��*��4��������������������������^������������(��*����*��,��(��*��P"����)����������)����ㄶ��Jm����r��������}��r����}��.��.������o ��o �M�����~��������� �����l��,����u��u��| ��h,��K�� ~��������r!�� ~���� ����*��,��*��,��'������ ����������������������������*��+��*��+�� ������K����������o)-������m������u��u��mO�������;�~��� � � �����h,��Cd�-��� ~��S�-�,���� ��m�/�������2I�-�,������K�-�������*K�-�������XA�E������ �������� �����| ��u����Cd��v����&��|�r!����ǀ����S������A �z��F�r!�'������*��,����m��*��+�����"�X�'������������2I��������4��Y �� ����*��+����K��*��*����^��7:�� ��o)-��)��܂����*K������P"��B��o)�mO��r��u����XA��v����Jm��O��m�"���h�(����(���j�������� �� �� ��u�������*��,�������*��+�-���O��u������f����������3��� �������v���������horizontal_padding����������������������������� �������������������� ���������� ���������� ���������� ������������������k���������������������������gimp-text-layer������(markup "<markup><span foreground=\"#000000\"><span font=\"Sans\"><span size=\"12288\">horizontal_padding</span></span></span></markup>") (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��������?V��C(�������v�����?j��Aa�������@��7�������� ����*�� ���h,����q�����h,��&���� ~��������c��� ~����A ������ ��*��,������9�*��,���� ��������������������������������4��������*��,�����;��*��,��������^�������������g��������)��P"��������������������r��Jm��������N����+������(�����v��&��u��u����h,��v����A �������� ~����*��+����*��,��*��,������ ��(��*������4��������������������*��*��^��*��+��*��+��������(��*����P"��������������v��Jm��u��u��������}���,�.���)����)�o ����;�����������������text���������������������� �������������������� ���������� ���������� ���������� ��������������������������������������������������C�������������D��I ��I��I%����������E ��E��E-��E=��EM��E]��Em��E}��E��E��E��E��E��E��E��E��F ��F��F-��F=��FM��F]��Fm��F}��F��F��F��F��F��F��F��F��G ��G��G-��G=��GM��G]��Gm��G}��G��G��G��G��G��G��G��G��H ��H��H-��H=��HM��H]��Hm��H}��H��H��H��H��H��H��H��H������������������������������������������������������������ �� �� �� ���������������������������������������������������������� �� �� �� ���������������������������������������������������������� �� �� �� ���������������������������������������������������������� �� �� �� ���������������������������������������������������������� �� �� �� ���������������������������������������������������������� �� �� �� ���������������������������������������������������������� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� � � � ��������������}���}�������>���>��������������arrows���������������������� �������������������� ���������� ���������� ���������� ��������������������������������������������������I�������������J���a{��a��a����������K ��K��K��K��K��K��K��K��L ��LA��Mi��N��O��O��Pk��P{��P��P��P��R:��RJ��R��R��S��S��S!��T��Ub��U��U��U��U��U��V]��WX��W��X ��Y��Z%��Z5��ZE��[��[��\b��\r��]��^��^}��^��^��^��_5��_E��`��`��`��`��`��`��`��a��a��a[��ak���������� ��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��������������������������������������������������� �� �� �� ��������9��>��=��>��>��>��=��>�� ����������>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>���������9�<���;���:����:����9����8����9����8����8����7����7����6����5����5����4����4����4����3����3����2����2����1�� ��1�� ��0�� ��/�� ��/�� ��/�� ��/�� ��.�� ��.�� ��'���.�� ��.�� ��.�� ��.�� ��'���=��=��=��������� ��<��=��<�����������<��=��<��<��=��<��<��=��<��<��=��<��<��=��<��<��=��<��<��=��<��=��<��<��=��<��<��=��<��<��=��<��<��=��<��<��=����;�;�;�0�6��������� .�;�;�;�;�;�;�:�:�;�;�;�;�:�:�;�;�;�;�;�:�:�<����z��������� �� �� �� ����������������)��>��=��>��>��>��=��>��>��>��=��>��>�� �������5��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=�� ���������������6��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��2� �������@� ��������� �� �� �� ���������������� ��=����3����3����3����3����3����3� 2����3����3����3����3����3����3����2���2���1�����1�����0������0����9����9��<��=��'�������/��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��������� ��=��>��=��>��=��=��>��=��=��>��=��>��=��������������������������� �� �� �� �������� ��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��=�����������=��<��=��<��=��<��=��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��=��<��=��~�������(��=��=��=� '� � �>��>��=��=��>��=��=��>��=��=��+�������8��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=�� �������@��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��,���������>��>��>��>��=��>��>��>��>��>��>��>��>��=��>��>��>��>��>��>��>��>��=��>��>��>��>��>��>��>��>��=��>��>��>��>��>��>��>��>��=��>��>��>��>��>��>��>��=�� ��������� �� �� �� ��������6��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��W�����������������=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=�������������������>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=���������3��>��>��>��>��>��>��>��=��>��>��>��>�� �������@��>��>��>��=��>��>��>��>��>��>��>��>��=��>��>��>��>��o� �� �� �� ������������������������'��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=�� ���������������(��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=�������������=��>��=��<��������� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� ����>��=��=��>��=��>��=��=��>��=�� y� �� �� �� �� � � � ��������������}���}�������>���>������,����������dunst_doc_base.png���������������������� �������������������� ���������� ���������� ���������� ����������������Y���t�����������������������������bZ������,��������bz������������,�����b��m��s��})��������2��x��'��U����6��P��t��\������4��L�����ê<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<((((+((( (:((((((((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(((((((<(<(<(<(<(<(<(<(<(�ê<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U UVUUVUUU UwUUUUUUUwU UUU UlUU\\UUUUUUlU UUU U^UU؄UUUUUU^UUU UWUUڂUUUUUUWU UUn UWUUV\\VUUUUUWU UUVU U^UUnmUVUUVUU^U UUU UlUUUUU[[UUlU UbUUU UvUUeUUeUghUUwU UUUUU UVUUVUU<U<U<U<U<U<U<U<U<U<U<U<U<U<U<UUUUUUUUUUUUUU UUUUUUU U UUUUUfUUUUUUUVvUUXUUXUUUUXUUUU UUUXUVUUXUXUUUUXUVUUUU UWXUUUUUUXUXUUUUUUUUUU UwYUUUUUUWUUjUUUUUUUU UUUUUUUhUUUUUUUUU<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 wzwwwww wƑwwwwwwwww www wwwzwwwwwww www wzww֦wwwwwwwww wxwwԩwwwwww~w ww wxww}yxwwwww~w wwwx wzwwwwwzwww www wwwwwwywww w|www wőww}www}www wwwww wywwww<w<w<w<w<w<w<w<w<w<w<w<w<w<w<wwwwwwwwwwwwww wwwwwww w w݅wwwwwwwwwwwzwwyww{wwwwywwww wwwxw{wwxwwwwwxw{wwww wxwwwwwwxwwwwwwwwwww wwwwwww{wwwwwwwwww wwwwwwwwwwwwwwww<w<w<w<w<w<w<w<w<w�(((((f((;݆(((;݆(((((((\*A(((\*A( (.((7(((z+(((z+((B((-((((S(((S((z+(((((p05(((p05((fQ(((((F(((F((I((((((>((=(( (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 UUUwUUwYUUrUUWUUUUUUUUUUUhhUAU�ww(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}UUZUU�ww 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((<(*((;(*((;(*( (ĉ*(ĉ*(ĉ*((=(*((;(*((;(*(<U<U<U<U<U<U<U%UUU%UUUUU%UUUUU U\XUUX\ggUUUUfUUUUUU\ UԇUUUXUUZUUXUUUUvVUU Uq\\qUXUXUUXUUXUXUUUUXUU UUUXUXUUUUXUXUUUUVUU UUUWUUUUWUUrUUwUU U^\^UUhhUUUUhUUUUUU<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<UUUU UUUUU UUUUU UU UUUUUfUUjUZZUUUUUVvUUXUUXUUUUUUU UUUXUVUUXUXUUXUXXUUUU UWXUUUUUUXUXUUXUXXUUVUU UwYUUUUUUWUU퉈UUUoUU UUUUUUUhUUlUZZUUU<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U U\XUUXXUUXXUUX\UUeUUggUUggU UԇUUԇUUԇUUUUXUUXUUXU Uq\\qq\\qq\\qUUXUXUUXUXUUXUXU UUUUUUUYUXUUXUXUUXUXU<w<w<w<w<w<w<w%www%wwwww%wwwww wxwwy}wwwwwwwwww wwwөw{׹ww{ww{wwwww w wywxwwwxwwxwwwww۽xww wxwwxwwwwwxwwwww}ww www{ֻwwww{wwwwyww wzww}wwwwwwwwww<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<wwww wwwww wwwww ww w݅wwwwww~wywwwwwzwwyww{wwwwwww wwwxw{wwxwwwxwxwwww wxwwwwwwxwwwxwxwwww wwwwwww{wwwwwww wwwwwwwww~wywww<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w wxwwxwwxwwywwww}ww}w wwwөwwөwwөww{ww{׹ww{׹w wyyywwxwwwxwwwxww wxwxwxwwxwwwxwwwxww((=((=(((9((*K(u((((oe((oe((-((0((((v((A((5w((B((/_((/_((0F((/`( (B((7,K((:((@((;(*((;(*((((;(*((@((ĉ*((((Ն/*((;(*((;(*((6((;(*((Ն/*((N((((z+m((.]((.]((9((-_((z+m((,>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<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 UVU+U UwUU%U UlUUU$U U^UUU UUUggUUUU^zU UWUUU UijUUXUUZUU؊X}U UWUUU UVVUUXUXUUXUUU U^UUU UUUUUXUXUUUUXUU UlUUU UUUUUWUUUU֭\UU UvUUUUUUUhhUUUU\U UVU+U<U<U<U<U<U<U wwwwwwwww}ww{ֻww{ֻw wzwwzwwzwwwwww}ww}w*wxw w)ww 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 wzw+w wƑww%w wwww$w wzwww www}wwwww wxwww www{׹ww{wwzw wxwww wyywwxwwwxwww wzwww wwwwwxwwwwwxww wwww wwwww{ֻwwwwyww wőwwwwwww}wwwww wyw+w<w<w<w<w<w<w(0`((~((9((((-_((9( (k((+T֤((0((K((,((((1((,((:((ߍ((F(,(( ((y(�B(+(<(Q(<(5(<(-(<()(<()(<(-(<(5(<(R(<(,((UXUUrUU֭\UUUUWUU֭\U UlUUZUUeUUUU\UUUUhUU\UUvUUݐUUU+UVU U}UyU�BUVU<UwU<UlU<U^U<UWU<UWU<U^U<UlU<UwU<UVUUw}wwwwywwww{wwyw www~wwwwwwwwwwwwwwőwwےwwxw+wyw wwyw�Bww=ww<ww<ww<w~w<w~w<ww<ww<ww<www(B((._((~((-_((5((((2i((K((1((R(<(,((� @(UwUUWUUrUUWUUlUUUUhhUUUUhUUwU<UVUU� @Uwyww{ֻwwww{wwwwww}wwwwwww<www� @w@(� @(@U� @U@w� @w((((((((((((((((((((((((((((((((((((((((((((((((((Z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(UZ(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(wZ(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<(<(<(<(<(<(<(<(<(<(<(<(<(�<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�@(�@U�@w�@(�@U�@w�@(�@U�@w�((((((((((((((((((((((((((�(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�������h�������K���4�������%�����������������bg���������������������� �������������������� ���������� ���������� ���������� ��������������������������������������������������ý��������������������������������� ����)��9��I��Y��i��y��ʼn��ř��ũ��Ź���������� ����)��9��I��Y��i��y��Ɖ��ƙ��Ʃ��ƹ���������� ����)��9��I��Y��i��y��lj��Ǚ��ǩ��ǹ���������� ����)��9��I��Y��i��y��ȉ��ș��ȩ��ȹ������������������������������������ � � � ����������������������������� � � � ����������������������������� � � � ����������������������������� � � � ����������������������������� � � � ����������������������������� � � � ����������������������������� � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �    �������������}���}�������>���>�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/dunst.systemd.service.in����������������������������������������������������������������0000664�0000000�0000000�00000000346�13223745120�0017465�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[Unit] Description=Dunst notification daemon Documentation=man:dunst(1) PartOf=graphical-session.target [Service] Type=dbus BusName=org.freedesktop.Notifications ExecStart=##PREFIX##/bin/dunst [Install] WantedBy=default.target ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/dunstify.c������������������������������������������������������������������������������0000664�0000000�0000000�00000022711�13223745120�0014663�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <glib.h> #include <libnotify/notify.h> #include <locale.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> #include <gdk-pixbuf/gdk-pixbuf.h> 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 gchar *raw_icon_path = 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" }, { "raw_icon", 'I', 0, G_OPTION_ARG_STRING, &raw_icon_path, "Path to the icon to be sent as raw image data", "RAW_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", (char *)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; } void 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 = strchr(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 = strchr(str, ':'); if (!name || *(name+1) == '\0') { g_printerr("Malformed hint. Expected \"type:name:value\", got \"%s\"", str); return; } *name = '\0'; name++; char *value = strchr(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[]) { setlocale(LC_ALL, ""); #if !GLIB_CHECK_VERSION(2,35,0) g_type_init(); #endif 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 (raw_icon_path) { GdkPixbuf *raw_icon = gdk_pixbuf_new_from_file(raw_icon_path, &err); if(err) { g_printerr("Unable to get raw icon: %s\n", err->message); die(1); } notify_notification_set_image_from_pixbuf(n, raw_icon); } 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 (printid) g_print("%d\n", get_id(n)); if (block || action_strs) g_main_loop_run(l); g_object_unref(G_OBJECT (n)); die(0); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �������������������������������������������������������dunst-1.3.0/dunstrc���������������������������������������������������������������������������������0000664�0000000�0000000�00000024436�13223745120�0014265�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[global] ### Display ### # 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 window manager that exports the # _NET_ACTIVE_WINDOW property. # This should be the case for almost all modern window managers. # # If this option is set to mouse or keyboard, the monitor option # will be ignored. follow = mouse # The geometry of the window: # [{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 respectively. # 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" # Show how many messages are currently hidden (because of geometry). indicate_hidden = yes # Shrink window if it's smaller than the width. Will be ignored if # width is 0. shrink = no # The transparency of the window. Range: [0; 100]. # This option will only work if a compositing window manager is # present (e.g. xcompmgr, compiz, etc.). transparency = 0 # The height of the entire notification. If the height is smaller # than the font height and padding combined, it will be raised # to the font height and padding. notification_height = 0 # Draw a line of "separator_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 # Defines width in pixels of frame around the notification window. # Set to 0 to disable. frame_width = 3 # Defines color of the frame around the notification window. frame_color = "#aaaaaa" # 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 # Sort messages by urgency. sort = yes # 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. # Transient notifications ignore this setting. idle_threshold = 120 ### Text ### font = Monospace 8 # The spacing between lines. If the height is smaller than the # font height, it will get raised to the font height. line_height = 0 # Possible values are: # full: Allow a small subset of html markup in notifications: # <b>bold</b> # <i>italic</i> # <s>strikethrough</s> # <u>underline</u> # # For a complete reference see # <http://developer.gnome.org/pango/stable/PangoMarkupFormat.html>. # # strip: This setting is provided for compatibility with some broken # clients that send markup even though it's not enabled on the # server. Dunst will try to strip the markup but the parsing is # simplistic so using this option outside of matching rules for # specific applications *IS GREATLY DISCOURAGED*. # # no: Disable markup parsing, incoming notifications will be treated as # plain text. Dunst will not advertise that it has the body-markup # capability if this is set as a global setting. # # It's important to note that markup inside the format option will be parsed # regardless of what this is set to. markup = full # 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 # %n progress value if set without any extra characters # %% Literal % # Markup is allowed format = "<b>%s</b>\n%b" # Alignment of message text. # Possible values are "left", "center" and "right". alignment = left # 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 # When word_wrap is set to no, specify where to ellipsize long lines. # Possible values are "start", "middle" and "end". ellipsize = middle # Ignore newlines '\n' in notifications. ignore_newline = no # Merge multiple notifications with the same content stack_duplicates = true # Hide the count of merged notifications with the same content hide_duplicate_count = false # Display indicators for URLs (U) and actions (A). show_indicators = yes ### Icons ### # Align icons left/right/off icon_position = off # Scale larger icons down to this size, set to 0 to disable max_icon_size = 32 # Paths to default icons. icon_path = /usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/ ### History ### # Should a notification popped up from history be sticky or timeout # as if it would normally do. sticky_history = yes # Maximum amount of notifications kept in history history_length = 20 ### Misc/Advanced ### # dmenu path. dmenu = /usr/bin/dmenu -p dunst: # Browser for opening urls in context menu. browser = /usr/bin/firefox -new-tab # Always run rule-defined scripts, even if the notification is suppressed always_run_script = true # Define the title of the windows spawned by dunst title = Dunst # Define the class of the windows spawned by dunst class = Dunst # Print a notification on startup. # This is mainly for error detection, since dbus (re-)starts dunst # automatically after a crash. startup_notification = false ### Legacy # Use the Xinerama extension instead of RandR for multi-monitor support. # This setting is provided for compatibility with older nVidia drivers that # do not support RandR and using it on systems that support RandR is highly # discouraged. # # By enabling this setting dunst will not be able to detect when a monitor # is connected or disconnected which might break follow mode if the screen # layout changes. force_xinerama = false # Experimental features that may or may not work correctly. Do not expect them # to have a consistent behaviour across releases. [experimental] # Calculate the dpi to use on a per-monitor basis. # If this setting is enabled the Xft.dpi value will be ignored and instead # dunst will attempt to calculate an appropriate dpi value for each monitor # using the resolution and physical size. This might be useful in setups # where there are multiple screens with very different dpi values. per_monitor_dpi = false [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". Make sure this key actually exists on your keyboard layout, # e.g. check output of 'xmodmap -pke' 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 # Icon for notifications with low urgency, uncomment to enable #icon = /path/to/icon [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 # Icon for notifications with normal urgency, uncomment to enable #icon = /path/to/icon [urgency_critical] background = "#900000" foreground = "#ffffff" frame_color = "#ff0000" timeout = 0 # Icon for notifications with critical urgency, uncomment to enable #icon = /path/to/icon # 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", "icon", "category", # "msg_urgency" and you can override the "timeout", "urgency", "foreground", # "background", "new_icon" 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 = "" #[history-ignore] # # This notification will not be saved in history # summary = "foobar" # history_ignore = yes #[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 # # vim: ft=cfg ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/main.c����������������������������������������������������������������������������������0000664�0000000�0000000�00000000241�13223745120�0013734�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "src/dunst.h" int main(int argc, char *argv[]) { return dunst_main(argc, argv); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/org.knopwob.dunst.service.in������������������������������������������������������������0000664�0000000�0000000�00000000152�13223745120�0020235�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[D-BUS Service] Name=org.freedesktop.Notifications Exec=##PREFIX##/bin/dunst SystemdService=dunst.service ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/������������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13223745120�0013436�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/dbus.c������������������������������������������������������������������������������0000664�0000000�0000000�00000057476�13223745120�0014562�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "dbus.h" #include <gio/gio.h> #include <glib.h> #include <stdio.h> #include <stdlib.h> #include "dunst.h" #include "notification.h" #include "queues.h" #include "settings.h" #include "utils.h" #define FDN_PATH "/org/freedesktop/Notifications" #define FDN_IFAC "org.freedesktop.Notifications" #define FDN_NAME "org.freedesktop.Notifications" GDBusConnection *dbus_conn; static GDBusNodeInfo *introspection_data = NULL; static const char *introspection_xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" "<node name=\""FDN_PATH"\">" " <interface name=\""FDN_IFAC"\">" " <method name=\"GetCapabilities\">" " <arg direction=\"out\" name=\"capabilities\" type=\"as\"/>" " </method>" " <method name=\"Notify\">" " <arg direction=\"in\" name=\"app_name\" type=\"s\"/>" " <arg direction=\"in\" name=\"replaces_id\" type=\"u\"/>" " <arg direction=\"in\" name=\"app_icon\" type=\"s\"/>" " <arg direction=\"in\" name=\"summary\" type=\"s\"/>" " <arg direction=\"in\" name=\"body\" type=\"s\"/>" " <arg direction=\"in\" name=\"actions\" type=\"as\"/>" " <arg direction=\"in\" name=\"hints\" type=\"a{sv}\"/>" " <arg direction=\"in\" name=\"expire_timeout\" type=\"i\"/>" " <arg direction=\"out\" name=\"id\" type=\"u\"/>" " </method>" " <method name=\"CloseNotification\">" " <arg direction=\"in\" name=\"id\" type=\"u\"/>" " </method>" " <method name=\"GetServerInformation\">" " <arg direction=\"out\" name=\"name\" type=\"s\"/>" " <arg direction=\"out\" name=\"vendor\" type=\"s\"/>" " <arg direction=\"out\" name=\"version\" type=\"s\"/>" " <arg direction=\"out\" name=\"spec_version\" type=\"s\"/>" " </method>" " <signal name=\"NotificationClosed\">" " <arg name=\"id\" type=\"u\"/>" " <arg name=\"reason\" type=\"u\"/>" " </signal>" " <signal name=\"ActionInvoked\">" " <arg name=\"id\" type=\"u\"/>" " <arg name=\"action_key\" type=\"s\"/>" " </signal>" " </interface>" "</node>"; static void on_get_capabilities(GDBusConnection *connection, const gchar *sender, const GVariant *parameters, GDBusMethodInvocation *invocation); static void on_notify(GDBusConnection *connection, const gchar *sender, GVariant *parameters, GDBusMethodInvocation *invocation); static void on_close_notification(GDBusConnection *connection, const gchar *sender, GVariant *parameters, GDBusMethodInvocation *invocation); static void on_get_server_information(GDBusConnection *connection, const gchar *sender, const GVariant *parameters, GDBusMethodInvocation *invocation); static RawImage *get_raw_image_from_data_hint(GVariant *icon_data); 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) { on_get_capabilities(connection, sender, parameters, invocation); } else if (g_strcmp0(method_name, "Notify") == 0) { on_notify(connection, sender, parameters, invocation); } else if (g_strcmp0(method_name, "CloseNotification") == 0) { on_close_notification(connection, sender, parameters, invocation); } else if (g_strcmp0(method_name, "GetServerInformation") == 0) { on_get_server_information(connection, sender, parameters, invocation); } else { fprintf(stderr, "WARNING: sender: %s; unknown method_name: %s\n", sender, method_name); } } static void on_get_capabilities(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-hyperlinks"); if (settings.markup != MARKUP_NO) 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); } static notification *dbus_message_to_notification(const gchar *sender, GVariant *parameters) { gchar *appname = NULL; guint replaces_id = 0; gchar *icon = NULL; gchar *summary = NULL; gchar *body = NULL; Actions *actions = g_malloc0(sizeof(Actions)); gint timeout = -1; /* hints */ gint urgency = 1; gint progress = -1; gboolean transient = 0; gchar *fgcolor = NULL; gchar *bgcolor = NULL; gchar *category = NULL; RawImage *raw_icon = NULL; { 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); g_variant_unref(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); g_variant_unref(dict_value); } dict_value = g_variant_lookup_value(content, "bgcolor", G_VARIANT_TYPE_STRING); if (dict_value) { bgcolor = g_variant_dup_string(dict_value, NULL); g_variant_unref(dict_value); } dict_value = g_variant_lookup_value(content, "category", G_VARIANT_TYPE_STRING); if (dict_value) { category = g_variant_dup_string(dict_value, NULL); g_variant_unref(dict_value); } dict_value = g_variant_lookup_value(content, "image-path", G_VARIANT_TYPE_STRING); if (dict_value) { g_free(icon); icon = g_variant_dup_string(dict_value, NULL); g_variant_unref(dict_value); } dict_value = g_variant_lookup_value(content, "image-data", G_VARIANT_TYPE("(iiibiiay)")); if (!dict_value) dict_value = g_variant_lookup_value(content, "image_data", G_VARIANT_TYPE("(iiibiiay)")); if (!dict_value) dict_value = g_variant_lookup_value(content, "icon_data", G_VARIANT_TYPE("(iiibiiay)")); if (dict_value) { raw_icon = get_raw_image_from_data_hint(dict_value); g_variant_unref(dict_value); } /* Check for transient hints * * According to the spec, the transient hint should be boolean. * But notify-send does not support hints of type 'boolean'. * So let's check for int and boolean until notify-send is fixed. */ if((dict_value = g_variant_lookup_value(content, "transient", G_VARIANT_TYPE_BOOLEAN))) { transient = g_variant_get_boolean(dict_value); g_variant_unref(dict_value); } else if((dict_value = g_variant_lookup_value(content, "transient", G_VARIANT_TYPE_UINT32))) { transient = g_variant_get_uint32(dict_value) > 0; g_variant_unref(dict_value); } else if((dict_value = g_variant_lookup_value(content, "transient", G_VARIANT_TYPE_INT32))) { transient = g_variant_get_int32(dict_value) > 0; g_variant_unref(dict_value); } if((dict_value = g_variant_lookup_value(content, "value", G_VARIANT_TYPE_INT32))) { progress = g_variant_get_int32(dict_value); g_variant_unref(dict_value); } else if((dict_value = g_variant_lookup_value(content, "value", G_VARIANT_TYPE_UINT32))) { progress = g_variant_get_uint32(dict_value); g_variant_unref(dict_value); } } break; case 7: if (g_variant_is_of_type(content, G_VARIANT_TYPE_INT32)) timeout = g_variant_get_int32(content); break; } g_variant_unref(content); idx++; } g_variant_iter_free(iter); } fflush(stdout); notification *n = notification_create(); n->id = replaces_id; n->appname = appname; n->summary = summary; n->body = body; n->icon = icon; n->raw_icon = raw_icon; n->timeout = timeout < 0 ? -1 : timeout * 1000; n->progress = progress; n->urgency = urgency; n->category = category; n->dbus_client = g_strdup(sender); n->transient = transient; if (actions->count < 1) { actions_free(actions); actions = NULL; } n->actions = actions; n->colors[ColFG] = fgcolor; n->colors[ColBG] = bgcolor; notification_init(n); return n; } static void on_notify(GDBusConnection *connection, const gchar *sender, GVariant *parameters, GDBusMethodInvocation *invocation) { notification *n = dbus_message_to_notification(sender, parameters); int id = queues_notification_insert(n); GVariant *reply = g_variant_new("(u)", id); g_dbus_method_invocation_return_value(invocation, reply); g_dbus_connection_flush(connection, NULL, NULL, NULL); // The message got discarded if (id == 0) { signal_notification_closed(n, 2); notification_free(n); } wake_up(); } static void on_close_notification(GDBusConnection *connection, const gchar *sender, GVariant *parameters, GDBusMethodInvocation *invocation) { guint32 id; g_variant_get(parameters, "(u)", &id); queues_notification_close_id(id, REASON_SIG); wake_up(); g_dbus_method_invocation_return_value(invocation, NULL); g_dbus_connection_flush(connection, NULL, NULL, NULL); } static void on_get_server_information(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 signal_notification_closed(notification *n, enum reason reason) { if (reason < REASON_MIN || REASON_MAX < reason) { fprintf(stderr, "ERROR: Closing notification with reason '%d' not supported. " "Closing it with reason '%d'.\n", reason, REASON_UNDEF); reason = REASON_UNDEF; } if (!dbus_conn) { fprintf(stderr, "ERROR: Tried to close notification but dbus connection not set!\n"); return; } GVariant *body = g_variant_new("(uu)", n->id, reason); GError *err = NULL; g_dbus_connection_emit_signal(dbus_conn, n->dbus_client, FDN_PATH, FDN_IFAC, "NotificationClosed", body, &err); if (err) { fprintf(stderr, "Unable to close notification: %s\n", err->message); g_error_free(err); } } void signal_action_invoked(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, FDN_PATH, FDN_IFAC, "ActionInvoked", body, &err); if (err) { fprintf(stderr, "Unable to invoke action: %s\n", err->message); g_error_free(err); } } static const GDBusInterfaceVTable interface_vtable = { handle_method_call }; static void on_bus_acquired(GDBusConnection *connection, const gchar *name, gpointer user_data) { guint registration_id; GError *err = NULL; registration_id = g_dbus_connection_register_object(connection, FDN_PATH, introspection_data->interfaces[0], &interface_vtable, NULL, NULL, &err); if (registration_id == 0) { fprintf(stderr, "Unable to register dbus connection: %s\n", err->message); exit(1); } } static void on_name_acquired(GDBusConnection *connection, const gchar *name, gpointer user_data) { dbus_conn = connection; } /* * Get the PID of the current process, which acquired FDN DBus Name. * * Returns: valid PID, else -1 */ static int dbus_get_fdn_pid(GDBusConnection *connection) { char *owner = NULL; GError *error = NULL; int pid = -1; GDBusProxy *proxy_fdn; GDBusProxy *proxy_dbus; if (!connection) return pid; proxy_fdn = g_dbus_proxy_new_sync( connection, /* do not trigger a start of the notification daemon */ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, NULL, /* info */ FDN_NAME, FDN_PATH, FDN_IFAC, NULL, /* cancelable */ &error); if (error) { g_error_free(error); return pid; } owner = g_dbus_proxy_get_name_owner(proxy_fdn); proxy_dbus = g_dbus_proxy_new_sync( connection, G_DBUS_PROXY_FLAGS_NONE, NULL, /* info */ "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", NULL, /* cancelable */ &error); if (error) { g_error_free(error); return pid; } GVariant *pidinfo = g_dbus_proxy_call_sync( proxy_dbus, "org.freedesktop.DBus.GetConnectionUnixProcessID", g_variant_new("(s)", owner), G_DBUS_CALL_FLAGS_NONE, /* It's not worth to wait for the PID * longer than half a second when dying */ 500, NULL, &error); if (error) { g_error_free(error); return pid; } g_variant_get(pidinfo, "(u)", &pid); g_object_unref(proxy_fdn); g_object_unref(proxy_dbus); g_free(owner); if (pidinfo) g_variant_unref(pidinfo); return pid; } static void on_name_lost(GDBusConnection *connection, const gchar *name, gpointer user_data) { if (connection) { int pid = dbus_get_fdn_pid(connection); if (pid > 0) fprintf(stderr, "Cannot acquire '"FDN_NAME"': " "Name is acquired by PID '%d'.\n", pid); else fprintf(stderr, "Cannot acquire '"FDN_NAME"'.\n"); } else { fprintf(stderr, "Cannot connect to DBus.\n"); } exit(1); } static RawImage *get_raw_image_from_data_hint(GVariant *icon_data) { RawImage *image = g_malloc(sizeof(RawImage)); GVariant *data_variant; gsize expected_len; g_variant_get(icon_data, "(iiibii@ay)", &image->width, &image->height, &image->rowstride, &image->has_alpha, &image->bits_per_sample, &image->n_channels, &data_variant); expected_len = (image->height - 1) * image->rowstride + image->width * ((image->n_channels * image->bits_per_sample + 7) / 8); if (expected_len != g_variant_get_size (data_variant)) { fprintf(stderr, "Expected image data to be of length %" G_GSIZE_FORMAT " but got a " "length of %" G_GSIZE_FORMAT, expected_len, g_variant_get_size (data_variant)); g_free(image); g_variant_unref(data_variant); return NULL; } image->data = (guchar *) g_memdup(g_variant_get_data(data_variant), g_variant_get_size(data_variant)); g_variant_unref(data_variant); return image; } int initdbus(void) { guint owner_id; #if !GLIB_CHECK_VERSION(2,35,0) g_type_init(); #endif introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, NULL); owner_id = g_bus_own_name(G_BUS_TYPE_SESSION, FDN_NAME, 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) { if (introspection_data) g_dbus_node_info_unref(introspection_data); g_bus_unown_name(owner_id); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/dbus.h������������������������������������������������������������������������������0000664�0000000�0000000�00000001154�13223745120�0014545�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_DBUS_H #define DUNST_DBUS_H #include "notification.h" enum reason { REASON_MIN = 1, REASON_TIME = 1, REASON_USER = 2, REASON_SIG = 3, REASON_UNDEF = 4, REASON_MAX = 4, }; int initdbus(void); void dbus_tear_down(int id); /* void dbus_poll(int timeout); */ void signal_notification_closed(notification *n, enum reason reason); void signal_action_invoked(notification *n, const char *identifier); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/dunst.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000013143�13223745120�0014741�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2012 - 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #define XLIB_ILLEGAL_ACCESS #include "dunst.h" #include <X11/Xlib.h> #include <glib-unix.h> #include <glib.h> #include <signal.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include "dbus.h" #include "menu.h" #include "notification.h" #include "option_parser.h" #include "queues.h" #include "settings.h" #include "x11/screen.h" #include "x11/x.h" #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 */ GMainLoop *mainloop = NULL; GSList *rules = NULL; /* misc funtions */ static gboolean run(void *data); void wake_up(void) { run(NULL); } static gboolean run(void *data) { queues_check_timeouts(x_is_idle()); queues_update(); static gint64 next_timeout = 0; if (!xctx.visible && queues_length_displayed() > 0) { x_win_show(); } if (xctx.visible && queues_length_displayed() == 0) { x_win_hide(); } if (xctx.visible) { x_win_draw(); } if (xctx.visible) { gint64 now = g_get_monotonic_time(); gint64 sleep = queues_get_next_datachange(now); gint64 timeout_at = now + sleep; if (sleep >= 0) { if (next_timeout < now || timeout_at < next_timeout) { g_timeout_add(sleep/1000, run, NULL); next_timeout = timeout_at; } } } /* If the execution got triggered by g_timeout_add, * we have to remove the timeout (which is actually a * recurring interval), as we have set a new one * by ourselves. */ return G_SOURCE_REMOVE; } gboolean pause_signal(gpointer data) { queues_pause_on(); wake_up(); return G_SOURCE_CONTINUE; } gboolean unpause_signal(gpointer data) { queues_pause_off(); wake_up(); return G_SOURCE_CONTINUE; } gboolean quit_signal(gpointer data) { g_main_loop_quit(mainloop); return G_SOURCE_CONTINUE; } static void teardown(void) { regex_teardown(); teardown_queues(); x_free(); } int dunst_main(int argc, char *argv[]) { queues_init(); 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(); if (settings.startup_notification) { notification *n = notification_create(); n->id = 0; n->appname = g_strdup("dunst"); n->summary = g_strdup("startup"); n->body = g_strdup("dunst is up and running"); n->progress = -1; n->timeout = 10 * G_USEC_PER_SEC; n->markup = MARKUP_NO; n->urgency = URG_LOW; notification_init(n); queues_notification_insert(n); // we do not call wakeup now, wake_up does not work here yet } 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); guint pause_src = g_unix_signal_add(SIGUSR1, pause_signal, NULL); guint unpause_src = g_unix_signal_add(SIGUSR2, unpause_signal, NULL); /* register SIGINT/SIGTERM handler for * graceful termination */ guint term_src = g_unix_signal_add(SIGTERM, quit_signal, NULL); guint int_src = g_unix_signal_add(SIGINT, quit_signal, NULL); run(NULL); g_main_loop_run(mainloop); g_main_loop_unref(mainloop); /* remove signal handler watches */ g_source_remove(pause_src); g_source_remove(unpause_src); g_source_remove(term_src); g_source_remove(int_src); g_source_destroy(x11_source); dbus_tear_down(owner_id); teardown(); return 0; } void usage(int exit_status) { puts("usage:\n"); const char *us = cmdline_create_usage(); puts(us); exit(exit_status); } void print_version(void) { printf ("Dunst - A customizable and lightweight notification-daemon %s\n", VERSION); exit(EXIT_SUCCESS); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/dunst.h�����������������������������������������������������������������������������0000664�0000000�0000000�00000001322�13223745120�0014742�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_DUNST_H #define DUNST_DUNST_H #include <glib.h> #include <stdbool.h> #include <stdio.h> #include "notification.h" #define PERR(msg, errnum) printf("(%d) %s : %s\n", __LINE__, (msg), (strerror(errnum))) #define ColLast 3 #define ColFrame 2 #define ColFG 1 #define ColBG 0 extern GSList *rules; extern const char *colors[3][3]; void wake_up(void); int dunst_main(int argc, char *argv[]); void usage(int exit_status); void print_version(void); char *extract_urls(const char *str); void context_menu(void); void pause_signal_handler(int sig); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/markup.c����������������������������������������������������������������������������0000664�0000000�0000000�00000022377�13223745120�0015114�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "markup.h" #include <assert.h> #include <stdbool.h> #include <string.h> #include <stdio.h> #include "settings.h" #include "utils.h" static char *markup_quote(char *str) { assert(str != 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); return str; } static char *markup_unquote(char *str) { assert(str != 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); return str; } static char *markup_br2nl(char *str) { assert(str != NULL); str = string_replace_all("<br>", "\n", str); str = string_replace_all("<br/>", "\n", str); str = string_replace_all("<br />", "\n", str); return str; } /* * Remove HTML hyperlinks of a string. * * @str: The string to replace a tags * @urls: (nullable): If any href-attributes found, an '\n' concatenated * string of the URLs in format '[<text between tags>] <href>' */ void markup_strip_a(char **str, char **urls) { char *tag1 = NULL; if (urls) *urls = NULL; while ((tag1 = strstr(*str, "<a"))) { // use href=" as stated in the notification spec char *href = strstr(tag1, "href=\""); char *tag1_end = strstr(tag1, ">"); char *tag2 = strstr(tag1, "</a>"); // the tag is broken, ignore it if (!tag1_end) { fprintf(stderr, "WARNING: Given link is broken: '%s'\n", tag1); string_replace_at(*str, tag1-*str, strlen(tag1), ""); break; } if (tag2 && tag2 < tag1_end) { int repl_len = (tag2 - tag1) + strlen("</a>"); fprintf(stderr, "WARNING: Given link is broken: '%.*s.'\n", repl_len, tag1); string_replace_at(*str, tag1-*str, repl_len, ""); break; } // search contents of href attribute char *plain_url = NULL; if (href && href < tag1_end) { // shift href to the actual begin of the value href = href+6; const char *quote = strstr(href, "\""); if (quote && quote < tag1_end) { plain_url = g_strndup(href, quote-href); } } // text between a tags int text_len; if (tag2) text_len = tag2 - (tag1_end+1); else text_len = strlen(tag1_end+1); char *text = g_strndup(tag1_end+1, text_len); int repl_len = text_len + (tag1_end-tag1) + 1; repl_len += tag2 ? strlen("</a>") : 0; *str = string_replace_at(*str, tag1-*str, repl_len, text); // if there had been a href attribute, // add it to the URLs if (plain_url && urls) { text = string_replace_all("]", "", text); text = string_replace_all("[", "", text); char *url = g_strdup_printf("[%s] %s", text, plain_url); *urls = string_append(*urls, url, "\n"); g_free(url); } g_free(plain_url); g_free(text); } } /* * Remove img-tags of a string. If alt attribute given, use this as replacement. * * @str: The string to replace img tags * @urls: (nullable): If any src-attributes found, an '\n' concatenated string of * the URLs in format '[<alt>] <src>' */ void markup_strip_img(char **str, char **urls) { const char *start = *str; if (urls) *urls = NULL; while ((start = strstr(*str, "<img"))) { const char *end = strstr(start, ">"); // the tag is broken, ignore it if (!end) { fprintf(stderr, "WARNING: Given image is broken: '%s'\n", start); string_replace_at(*str, start-*str, strlen(start), ""); break; } // use attribute=" as stated in the notification spec const char *alt_s = strstr(start, "alt=\""); const char *src_s = strstr(start, "src=\""); char *text_alt = NULL; char *text_src = NULL; const char *src_e = NULL, *alt_e = NULL; if (alt_s) alt_e = strstr(alt_s + strlen("alt=\""), "\""); if (src_s) src_e = strstr(src_s + strlen("src=\""), "\""); // Move pointer to the actual start alt_s = alt_s ? alt_s + strlen("alt=\"") : NULL; src_s = src_s ? src_s + strlen("src=\"") : NULL; /* check if alt and src attribute are given * If both given, check the alignment of all pointers */ if ( alt_s && alt_e && src_s && src_e && ( (alt_s < src_s && alt_e < src_s-strlen("src=\"") && src_e < end) ||(src_s < alt_s && src_e < alt_s-strlen("alt=\"") && alt_e < end)) ) { text_alt = g_strndup(alt_s, alt_e-alt_s); text_src = g_strndup(src_s, src_e-src_s); /* check if single valid alt attribute is available */ } else if (alt_s && alt_e && alt_e < end && (!src_s || src_s < alt_s || alt_e < src_s - strlen("src=\""))) { text_alt = g_strndup(alt_s, alt_e-alt_s); /* check if single valid src attribute is available */ } else if (src_s && src_e && src_e < end && (!alt_s || alt_s < src_s || src_e < alt_s - strlen("alt=\""))) { text_src = g_strndup(src_s, src_e-src_s); } else { fprintf(stderr, "WARNING: Given image argument is broken: '%.*s'\n", (int)(end-start), start); } // replacement text for alt int repl_len = end - start + 1; if (!text_alt) text_alt = g_strdup("[image]"); *str = string_replace_at(*str, start-*str, repl_len, text_alt); // if there had been a href attribute, // add it to the URLs if (text_src && urls) { text_alt = string_replace_all("]", "", text_alt); text_alt = string_replace_all("[", "", text_alt); char *url = g_strdup_printf("[%s] %s", text_alt, text_src); *urls = string_append(*urls, url, "\n"); g_free(url); } g_free(text_src); g_free(text_alt); } } /* * Strip any markup from text; turn it in to plain text. * * For well-formed markup, the following two commands should be * roughly equivalent: * * out = markup_strip(in); * pango_parse_markup(in, -1, 0, NULL, &out, NULL, NULL); * * However, `pango_parse_markup()` balks at invalid markup; * `markup_strip()` shouldn't care if there is invalid markup. */ char *markup_strip(char *str) { if (str == NULL) { return NULL; } /* strip all tags */ string_strip_delimited(str, '<', '>'); /* unquote the remainder */ str = markup_unquote(str); return str; } /* * Transform the string in accordance with `markup_mode` and * `settings.ignore_newline` */ char *markup_transform(char *str, enum markup_mode markup_mode) { if (str == NULL) { return NULL; } switch (markup_mode) { case MARKUP_NULL: /* `assert(false)`, but with a meaningful error message */ assert(markup_mode != MARKUP_NULL); break; case MARKUP_NO: str = markup_quote(str); break; case MARKUP_STRIP: str = markup_br2nl(str); str = markup_strip(str); str = markup_quote(str); break; case MARKUP_FULL: str = markup_br2nl(str); markup_strip_a(&str, NULL); markup_strip_img(&str, NULL); break; } if (settings.ignore_newline) { str = string_replace_all("\n", " ", str); } return str; } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/markup.h����������������������������������������������������������������������������0000664�0000000�0000000�00000000646�13223745120�0015114�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_MARKUP_H #define DUNST_MARKUP_H #include "settings.h" char *markup_strip(char *str); void markup_strip_a(char **str, char **urls); void markup_strip_img(char **str, char **urls); char *markup_transform(char *str, enum markup_mode markup_mode); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ������������������������������������������������������������������������������������������dunst-1.3.0/src/menu.c������������������������������������������������������������������������������0000664�0000000�0000000�00000016371�13223745120�0014556�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "menu.h" #include <errno.h> #include <glib.h> #include <regex.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/wait.h> #include <unistd.h> #include "dbus.h" #include "dunst.h" #include "notification.h" #include "queues.h" #include "settings.h" #include "utils.h" static bool is_initialized = false; static regex_t cregex; static int regex_init(void) { if (is_initialized) return 1; char *regex = "\\b(https?://|ftps?://|news://|mailto:|file://|www\\.)" "[-[:alnum:]_\\@;/?:&=%$.+!*\x27,~#]*" "(\\([-[:alnum:]_\\@;/?:&=%$.+!*\x27,~#]*\\)|[-[:alnum:]_\\@;/?:&=%$+*~])+"; int ret = regcomp(&cregex, regex, REG_EXTENDED | REG_ICASE); if (ret != 0) { fputs("failed to compile regex", stderr); return 0; } else { is_initialized = true; return 1; } } void regex_teardown(void) { if (is_initialized) { regfree(&cregex); is_initialized = false; } } /* * Exctract all urls from a given string. * * Return: a string of urls separated by \n * */ char *extract_urls(const char *to_match) { char *urls = NULL; if (!regex_init()) return 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 = g_strndup(to_match + start, finish - start); urls = string_append(urls, match, "\n"); g_free(match); p += m.rm_eo; } return urls; } /* * Open url in browser. * */ void open_browser(const char *in) { char *url = NULL; // If any, remove leading [ linktext ] from URL const char *end = strstr(in, "] "); if (*in == '[' && end) url = g_strdup(end + 2); else url = g_strdup(in); int browser_pid1 = fork(); if (browser_pid1) { g_free(url); int status; waitpid(browser_pid1, &status, 0); } else { int browser_pid2 = fork(); if (browser_pid2) { exit(0); } else { char *browser_cmd = 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 (const GList *iter = queues_get_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) { signal_action_invoked(invoked, action_identifier); } } /* * Dispatch whatever has been returned * by the menu. */ void dispatch_menu_result(const char *input) { char *in = g_strdup(input); g_strstrip(in); if (in[0] == '#') { invoke_action(in + 1); } else { open_browser(in); } g_free(in); } /* * Open the context menu that let's the user * select urls/actions/etc */ void context_menu(void) { if (settings.dmenu_cmd == NULL) { fprintf(stderr, "dmenu command not set properly. Cowardly refusing to open the context menu.\n"); return; } char *dmenu_input = NULL; for (const GList *iter = queues_get_displayed(); iter; iter = iter->next) { notification *n = iter->data; if (n->urls) 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); g_free(dmenu_input); return; } if (pipe(parent_io) != 0) { PERR("pipe()", errno); g_free(dmenu_input); return; } int pid = fork(); if (pid == 0) { close(child_io[1]); close(parent_io[0]); close(0); if (dup(child_io[0]) == -1) { 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); waitpid(pid, NULL, 0); if (len == 0) { g_free(dmenu_input); return; } } close(parent_io[0]); dispatch_menu_result(buf); g_free(dmenu_input); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/menu.h������������������������������������������������������������������������������0000664�0000000�0000000�00000000533�13223745120�0014554�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_MENU_H #define DUNST_MENU_H char *extract_urls(const char *to_match); void open_browser(const char *in); void invoke_action(const char *action); void regex_teardown(void); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/notification.c����������������������������������������������������������������������0000664�0000000�0000000�00000044362�13223745120�0016301�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "notification.h" #include <assert.h> #include <errno.h> #include <glib.h> #include <libgen.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/wait.h> #include <unistd.h> #include "dbus.h" #include "dunst.h" #include "markup.h" #include "menu.h" #include "queues.h" #include "rules.h" #include "settings.h" #include "utils.h" #include "x11/x.h" static void notification_extract_urls(notification *n); static void notification_format_message(notification *n); static void notification_dmenu_string(notification *n); /* * 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("\traw_icon set: %s\n", (n->raw_icon ? "true" : "false")); printf("\tcategory: %s\n", n->category); printf("\ttimeout: %ld\n", n->timeout/1000); printf("\turgency: %s\n", notification_urgency_to_string(n->urgency)); printf("\ttransient: %d\n", n->transient); printf("\tformatted: '%s'\n", n->msg); printf("\tfg: %s\n", n->colors[ColFG]); printf("\tbg: %s\n", n->colors[ColBG]); printf("\tframe: %s\n", n->colors[ColFrame]); printf("\tid: %d\n", n->id); if (n->urls) { char *urls = string_replace_all("\n", "\t\t\n", g_strdup(n->urls)); printf("\turls:\n"); printf("\t{\n"); printf("\t\t%s\n", urls); printf("\t}\n"); g_free(urls); } 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("\t}\n"); printf("\tactions_dmenu: %s\n", n->actions->dmenu_str); } 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 : ""; const char *urgency = notification_urgency_to_string(n->urgency); 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 convert an urgency to a string */ const char *notification_urgency_to_string(enum urgency urgency) { switch (urgency) { case URG_NONE: return "NONE"; case URG_LOW: return "LOW"; case URG_NORM: return "NORMAL"; case URG_CRIT: return "CRITICAL"; default: return "UNDEF"; } } /* * 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); } int notification_is_duplicate(const notification *a, const notification *b) { //Comparing raw icons is not supported, assume they are not identical if (settings.icon_position != icons_off && (a->raw_icon != NULL || b->raw_icon != NULL)) return false; return strcmp(a->appname, b->appname) == 0 && strcmp(a->summary, b->summary) == 0 && strcmp(a->body, b->body) == 0 && (settings.icon_position != icons_off ? strcmp(a->icon, b->icon) == 0 : 1) && a->urgency == b->urgency; } /* * Free the actions element * @a: (nullable): Pointer to #Actions */ void actions_free(Actions *a) { if (!a) return; g_strfreev(a->actions); g_free(a->dmenu_str); g_free(a); } /* * Free a #RawImage * @i: (nullable): pointer to #RawImage */ void rawimage_free(RawImage *i) { if (!i) return; g_free(i->data); g_free(i); } /* * Free the memory used by the given notification. */ void notification_free(notification *n) { assert(n != NULL); g_free(n->appname); g_free(n->summary); g_free(n->body); g_free(n->icon); g_free(n->msg); g_free(n->dbus_client); g_free(n->category); g_free(n->text_to_render); g_free(n->urls); g_free(n->colors[ColFG]); g_free(n->colors[ColBG]); g_free(n->colors[ColFrame]); actions_free(n->actions); rawimage_free(n->raw_icon); g_free(n); } /* * Replace the two chars where **needle points * with a quoted "replacement", according to the markup settings. * * The needle is a double pointer and gets updated upon return * to point to the first char, which occurs after replacement. * */ void notification_replace_single_field(char **haystack, char **needle, const char *replacement, enum markup_mode markup_mode) { assert(*needle[0] == '%'); // needle has to point into haystack (but not on the last char) assert(*needle >= *haystack); assert(*needle - *haystack < strlen(*haystack) - 1); int pos = *needle - *haystack; char *input = markup_transform(g_strdup(replacement), markup_mode); *haystack = string_replace_at(*haystack, pos, 2, input); // point the needle to the next char // which was originally in haystack *needle = *haystack + pos + strlen(input); g_free(input); } /* * Create notification struct and initialise all fields with either * - the default (if it's not needed to be freed later) * - its undefined representation (NULL, -1) * * This function is guaranteed to return a valid pointer. * @Returns: The generated notification */ notification *notification_create(void) { notification *n = g_malloc0(sizeof(notification)); /* Unparameterized default values */ n->first_render = true; n->markup = settings.markup; n->format = settings.format; n->timestamp = g_get_monotonic_time(); n->urgency = URG_NORM; n->timeout = -1; n->transient = false; n->progress = -1; return n; } /* * Sanitize values of notification, apply all matching rules * and generate derived fields. * * @n: the notification to sanitize */ void notification_init(notification *n) { /* default to empty string to avoid further NULL faults */ n->appname = n->appname ? n->appname : g_strdup("unknown"); n->summary = n->summary ? n->summary : g_strdup(""); n->body = n->body ? n->body : g_strdup(""); n->category = n->category ? n->category : g_strdup(""); /* sanitize urgency */ if (n->urgency < URG_MIN) n->urgency = URG_LOW; if (n->urgency > URG_MAX) n->urgency = URG_CRIT; /* Timeout processing */ if (n->timeout < 0) n->timeout = settings.timeouts[n->urgency]; /* Icon handling */ if (n->icon && strlen(n->icon) <= 0) g_clear_pointer(&n->icon, g_free); if (!n->raw_icon && !n->icon) n->icon = g_strdup(settings.icons[n->urgency]); /* Color hints */ if (!n->colors[ColFG]) n->colors[ColFG] = g_strdup(xctx.colors[ColFG][n->urgency]); if (!n->colors[ColBG]) n->colors[ColBG] = g_strdup(xctx.colors[ColBG][n->urgency]); if (!n->colors[ColFrame]) n->colors[ColFrame] = g_strdup(xctx.colors[ColFrame][n->urgency]); /* Sanitize misc hints */ if (n->progress < 0 || n->progress > 100) n->progress = -1; /* Process rules */ rule_apply_all(n); /* UPDATE derived fields */ notification_extract_urls(n); notification_dmenu_string(n); notification_format_message(n); } static void notification_format_message(notification *n) { g_clear_pointer(&n->msg, g_free); n->msg = string_replace_all("\\n", "\n", g_strdup(n->format)); /* replace all formatter */ for(char *substr = strchr(n->msg, '%'); substr; substr = strchr(substr, '%')) { char pg[16]; char *icon_tmp; switch(substr[1]) { case 'a': notification_replace_single_field( &n->msg, &substr, n->appname, MARKUP_NO); break; case 's': notification_replace_single_field( &n->msg, &substr, n->summary, n->markup); break; case 'b': notification_replace_single_field( &n->msg, &substr, n->body, n->markup); break; case 'I': icon_tmp = g_strdup(n->icon); notification_replace_single_field( &n->msg, &substr, icon_tmp ? basename(icon_tmp) : "", MARKUP_NO); g_free(icon_tmp); break; case 'i': notification_replace_single_field( &n->msg, &substr, n->icon ? n->icon : "", MARKUP_NO); break; case 'p': if (n->progress != -1) sprintf(pg, "[%3d%%]", n->progress); notification_replace_single_field( &n->msg, &substr, n->progress != -1 ? pg : "", MARKUP_NO); break; case 'n': if (n->progress != -1) sprintf(pg, "%d", n->progress); notification_replace_single_field( &n->msg, &substr, n->progress != -1 ? pg : "", MARKUP_NO); break; case '%': notification_replace_single_field( &n->msg, &substr, "%", MARKUP_NO); break; case '\0': fprintf(stderr, "WARNING: format_string has trailing %% character." "To escape it use %%%%."); break; default: fprintf(stderr, "WARNING: format_string %%%c" " is unknown\n", substr[1]); // shift substr pointer forward, // as we can't interpret the format string substr++; break; } } n->msg = g_strchomp(n->msg); /* truncate overlong messages */ if (strlen(n->msg) > DUNST_NOTIF_MAX_CHARS) { char *buffer = g_malloc(DUNST_NOTIF_MAX_CHARS); strncpy(buffer, n->msg, DUNST_NOTIF_MAX_CHARS); buffer[DUNST_NOTIF_MAX_CHARS-1] = '\0'; g_free(n->msg); n->msg = buffer; } } static void notification_extract_urls(notification *n) { g_clear_pointer(&n->urls, g_free); char *urls_in = string_append(g_strdup(n->summary), n->body, " "); char *urls_a = NULL; char *urls_img = NULL; markup_strip_a(&urls_in, &urls_a); markup_strip_img(&urls_in, &urls_img); // remove links and images first to not confuse // plain urls extraction char *urls_text = extract_urls(urls_in); n->urls = string_append(n->urls, urls_a, "\n"); n->urls = string_append(n->urls, urls_img, "\n"); n->urls = string_append(n->urls, urls_text, "\n"); g_free(urls_in); g_free(urls_a); g_free(urls_img); g_free(urls_text); } static void notification_dmenu_string(notification *n) { if (n->actions) { g_clear_pointer(&n->actions->dmenu_str, g_free); 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); char *act_str = g_strdup_printf("#%s [%s]", human_readable, n->appname); if (act_str) { n->actions->dmenu_str = string_append(n->actions->dmenu_str, act_str, "\n"); g_free(act_str); } } } } void notification_update_text_to_render(notification *n) { g_clear_pointer(&n->text_to_render, g_free); char *buf = NULL; char *msg = g_strchomp(n->msg); /* print dup_count and msg */ if ((n->dup_count > 0 && !settings.hide_duplicate_count) && (n->actions || n->urls) && settings.show_indicators) { buf = g_strdup_printf("(%d%s%s) %s", n->dup_count, n->actions ? "A" : "", n->urls ? "U" : "", msg); } else if ((n->actions || n->urls) && settings.show_indicators) { buf = g_strdup_printf("(%s%s) %s", n->actions ? "A" : "", n->urls ? "U" : "", msg); } else if (n->dup_count > 0 && !settings.hide_duplicate_count) { buf = g_strdup_printf("(%d) %s", n->dup_count, msg); } else { buf = g_strdup(msg); } /* print age */ gint64 hours, minutes, seconds; gint64 t_delta = g_get_monotonic_time() - n->timestamp; if (settings.show_age_threshold >= 0 && t_delta >= settings.show_age_threshold) { hours = t_delta / G_USEC_PER_SEC / 3600; minutes = t_delta / G_USEC_PER_SEC / 60 % 60; seconds = t_delta / G_USEC_PER_SEC % 60; char *new_buf; if (hours > 0) { new_buf = g_strdup_printf("%s (%ldh %ldm %lds old)", buf, hours, minutes, seconds); } else if (minutes > 0) { new_buf = g_strdup_printf("%s (%ldm %lds old)", buf, minutes, seconds); } else { new_buf = g_strdup_printf("%s (%lds old)", buf, seconds); } g_free(buf); buf = new_buf; } n->text_to_render = buf; } /* * If the notification has exactly one action, or one is marked as default, * invoke it. If there are multiple and no default, open the context menu. If * there are no actions, proceed similarly with urls. */ void notification_do_action(notification *n) { if (n->actions) { if (n->actions->count == 2) { signal_action_invoked(n, n->actions->actions[0]); return; } for (int i = 0; i < n->actions->count; i += 2) { if (strcmp(n->actions->actions[i], "default") == 0) { signal_action_invoked(n, n->actions->actions[i]); return; } } context_menu(); } else if (n->urls) { if (strstr(n->urls, "\n") == NULL) open_browser(n->urls); else context_menu(); } } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/notification.h����������������������������������������������������������������������0000664�0000000�0000000�00000006021�13223745120�0016274�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_NOTIFICATION_H #define DUNST_NOTIFICATION_H #include <glib.h> #include <stdbool.h> #include "settings.h" #define DUNST_NOTIF_MAX_CHARS 5000 enum urgency { URG_NONE = -1, URG_MIN = 0, URG_LOW = 0, URG_NORM = 1, URG_CRIT = 2, URG_MAX = 2, }; typedef struct _raw_image { int width; int height; int rowstride; int has_alpha; int bits_per_sample; int n_channels; unsigned char *data; } RawImage; typedef struct _actions { char **actions; char *dmenu_str; gsize count; } Actions; typedef struct _notification { int id; char *dbus_client; char *appname; char *summary; char *body; char *category; enum urgency urgency; char *icon; /* plain icon information (may be a path or just a name) */ RawImage *raw_icon; /* passed icon data of notification, takes precedence over icon */ gint64 start; /* begin of current display */ gint64 timestamp; /* arrival time */ gint64 timeout; /* time to display */ Actions *actions; enum markup_mode markup; const char *format; const char *script; char *colors[3]; /* Hints */ bool transient; /* timeout albeit user is idle */ int progress; /* percentage (-1: undefined) */ int history_ignore; /* push to history or free directly */ /* internal */ bool redisplayed; /* has been displayed before? */ bool first_render; /* markup has been rendered before? */ int dup_count; /* amount of duplicate notifications stacked onto this */ int displayed_height; /* derived fields */ char *msg; /* formatted message */ char *text_to_render; /* formatted message (with age and action indicators) */ char *urls; /* urllist delimited by '\n' */ } notification; notification *notification_create(void); void notification_init(notification *n); void actions_free(Actions *a); void rawimage_free(RawImage *i); void notification_free(notification *n); int notification_cmp(const void *a, const void *b); int notification_cmp_data(const void *a, const void *b, void *data); int notification_is_duplicate(const notification *a, const notification *b); void notification_run_script(notification *n); void notification_print(notification *n); void notification_replace_single_field(char **haystack, char **needle, const char *replacement, enum markup_mode markup_mode); void notification_update_text_to_render(notification *n); void notification_do_action(notification *n); const char *notification_urgency_to_string(enum urgency urgency); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/option_parser.c���������������������������������������������������������������������0000664�0000000�0000000�00000037102�13223745120�0016471�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "option_parser.h" #include <glib.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.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(const char *name); static section_t *get_section(const char *name); static void add_entry(const char *section_name, const char *key, const char *value); static const char *get_value(const char *section, const char *key); static char *clean_value(const char *value); static int cmdline_argc; static char **cmdline_argv; static char *usage_str = NULL; static void cmdline_usage_append(const char *key, const char *type, const char *description); static int cmdline_find_option(const char *key); section_t *new_section(const char *name) { for (int i = 0; i < section_count; i++) { if (!strcmp(name, sections[i].name)) { die("Duplicated section in dunstrc detected.\n", -1); } } section_count++; sections = g_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++) { g_free(sections[i].entries[j].key); g_free(sections[i].entries[j].value); } g_free(sections[i].entries); g_free(sections[i].name); } g_free(sections); section_count = 0; sections = NULL; } section_t *get_section(const 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(const char *section_name, const char *key, const 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 = g_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); } const char *get_value(const char *section, const 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_path(const char *section, const char *key, const char *def) { return string_to_path(ini_get_string(section, key, def)); } char *ini_get_string(const char *section, const char *key, const char *def) { const char *value = get_value(section, key); if (value) return g_strdup(value); return def ? g_strdup(def) : NULL; } gint64 ini_get_time(const char *section, const char *key, gint64 def) { const char *timestring = get_value(section, key); gint64 val = def; if (timestring) { val = string_to_time(timestring); } return val; } int ini_get_int(const char *section, const char *key, int def) { const char *value = get_value(section, key); if (value == NULL) return def; else return atoi(value); } double ini_get_double(const char *section, const char *key, double def) { const char *value = get_value(section, key); if (value == NULL) return def; else return atof(value); } bool ini_is_set(const char *ini_section, const char *ini_key) { return get_value(ini_section, ini_key) != NULL; } const char *next_section(const 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(const char *section, const char *key, int def) { const 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 def; } } } char *clean_value(const char *value) { char *s; if (value[0] == '"') s = g_strdup(value + 1); else s = g_strdup(value); if (s[strlen(s) - 1] == '"') s[strlen(s) - 1] = '\0'; return s; } int load_ini_file(FILE *fp) { if (!fp) return 1; char *line = NULL; size_t line_len = 0; int line_num = 0; char *current_section = NULL; while (getline(&line, &line_len, fp) != -1) { line_num++; char *start = g_strstrip(line); if (*start == ';' || *start == '#' || strlen(start) == 0) continue; if (*start == '[') { char *end = strchr(start + 1, ']'); if (!end) { fprintf(stderr, "Warning: invalid config file at line %d\n", line_num); fprintf(stderr, "Missing ']'\n"); continue; } *end = '\0'; g_free(current_section); current_section = (g_strdup(start + 1)); new_section(current_section); continue; } char *equal = strchr(start + 1, '='); if (!equal) { fprintf(stderr, "Warning: invalid config file at line %d\n", line_num); fprintf(stderr, "Missing '='\n"); continue; } *equal = '\0'; char *key = g_strstrip(start); char *value = g_strstrip(equal + 1); char *quote = strchr(value, '"'); if (quote) { char *closing_quote = strchr(quote + 1, '"'); if (!closing_quote) { fprintf(stderr, "Warning: invalid config file at line %d\n", line_num); fprintf(stderr, "Missing '\"'\n"); continue; } } else { char *comment = strpbrk(value, "#;"); if (comment) *comment = '\0'; } value = g_strstrip(value); if (!current_section) { fprintf(stderr, "Warning: invalid config file at line %d\n", line_num); fprintf(stderr, "Key value pair without a section\n"); continue; } add_entry(current_section, key, value); } free(line); g_free(current_section); return 0; } void cmdline_load(int argc, char *argv[]) { cmdline_argc = argc; cmdline_argv = argv; } int cmdline_find_option(const char *key) { if (!key) { return -1; } char *key1 = g_strdup(key); char *key2 = strchr(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) { g_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) { g_free(key1); return i; } } } g_free(key1); return -1; } static const char *cmdline_get_value(const 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(const char *key, const char *def, const char *description) { cmdline_usage_append(key, "string", description); const char *str = cmdline_get_value(key); if (str) return g_strdup(str); if (def == NULL) return NULL; else return g_strdup(def); } char *cmdline_get_path(const char *key, const char *def, const char *description) { cmdline_usage_append(key, "string", description); const char *str = cmdline_get_value(key); if (str) return string_to_path(g_strdup(str)); else return string_to_path(g_strdup(def)); } gint64 cmdline_get_time(const char *key, gint64 def, const char *description) { cmdline_usage_append(key, "time", description); const char *timestring = cmdline_get_value(key); gint64 val = def; if (timestring) { val = string_to_time(timestring); } return val; } int cmdline_get_int(const char *key, int def, const char *description) { cmdline_usage_append(key, "int", description); const char *str = cmdline_get_value(key); if (str == NULL) return def; else return atoi(str); } double cmdline_get_double(const char *key, double def, const char *description) { cmdline_usage_append(key, "double", description); const char *str = cmdline_get_value(key); if (str == NULL) return def; else return atof(str); } int cmdline_get_bool(const char *key, int def, const char *description) { cmdline_usage_append(key, "", description); int idx = cmdline_find_option(key); if (idx > 0) return true; else return def; } bool cmdline_is_set(const char *key) { return cmdline_get_value(key) != NULL; } char *option_get_path(const char *ini_section, const char *ini_key, const char *cmdline_key, const char *def, const char *description) { char *val = NULL; if (cmdline_key) { val = cmdline_get_path(cmdline_key, NULL, description); } if (val) { return val; } else { return ini_get_path(ini_section, ini_key, def); } } char *option_get_string(const char *ini_section, const char *ini_key, const char *cmdline_key, const char *def, const 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); } } gint64 option_get_time(const char *ini_section, const char *ini_key, const char *cmdline_key, gint64 def, const char *description) { gint64 ini_val = ini_get_time(ini_section, ini_key, def); return cmdline_get_time(cmdline_key, ini_val, description); } int option_get_int(const char *ini_section, const char *ini_key, const char *cmdline_key, int def, const char *description) { /* *str is only used to check wether the cmdline option is actually set. */ const 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(const char *ini_section, const char *ini_key, const char *cmdline_key, double def, const char *description) { const char *str = cmdline_get_value(cmdline_key); double val = cmdline_get_double(cmdline_key, def, description); if (!str) return ini_get_double(ini_section, ini_key, def); else return val; } int option_get_bool(const char *ini_section, const char *ini_key, const char *cmdline_key, int def, const 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(const char *key, const char *type, const 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); g_free(key_type); return; } char *tmp; tmp = g_strdup_printf("%s%-40s - %s\n", usage_str, key_type, description); g_free(key_type); g_free(usage_str); usage_str = tmp; } const char *cmdline_create_usage(void) { return usage_str; } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/option_parser.h���������������������������������������������������������������������0000664�0000000�0000000�00000005536�13223745120�0016504�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_OPTION_PARSER_H #define DUNST_OPTION_PARSER_H #include <glib.h> #include <stdbool.h> #include <stdio.h> int load_ini_file(FILE *); char *ini_get_path(const char *section, const char *key, const char *def); char *ini_get_string(const char *section, const char *key, const char *def); gint64 ini_get_time(const char *section, const char *key, gint64 def); int ini_get_int(const char *section, const char *key, int def); double ini_get_double(const char *section, const char *key, double def); int ini_get_bool(const char *section, const char *key, int def); bool ini_is_set(const char *ini_section, const char *ini_key); 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(const char *key, const char *def, const char *description); char *cmdline_get_path(const char *key, const char *def, const char *description); int cmdline_get_int(const char *key, int def, const char *description); double cmdline_get_double(const char *key, double def, const char *description); int cmdline_get_bool(const char *key, int def, const char *description); bool cmdline_is_set(const char *key); const char *cmdline_create_usage(void); char *option_get_string(const char *ini_section, const char *ini_key, const char *cmdline_key, const char *def, const char *description); char *option_get_path(const char *ini_section, const char *ini_key, const char *cmdline_key, const char *def, const char *description); gint64 option_get_time(const char *ini_section, const char *ini_key, const char *cmdline_key, gint64 def, const char *description); int option_get_int(const char *ini_section, const char *ini_key, const char *cmdline_key, int def, const char *description); double option_get_double(const char *ini_section, const char *ini_key, const char *cmdline_key, double def, const char *description); int option_get_bool(const char *ini_section, const char *ini_key, const char *cmdline_key, int def, const char *description); /* returns the next known section. * if section == NULL returns first section. * returns NULL if no more sections are available */ const char *next_section(const char *section); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/queues.c����������������������������������������������������������������������������0000664�0000000�0000000�00000027237�13223745120�0015124�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "queues.h" #include <assert.h> #include <glib.h> #include <stdio.h> #include <string.h> #include "notification.h" #include "settings.h" /* notification lists */ static GQueue *waiting = NULL; /* all new notifications get into here */ static GQueue *displayed = NULL; /* currently displayed notifications */ static GQueue *history = NULL; /* history of displayed notifications */ unsigned int displayed_limit = 0; int next_notification_id = 1; bool pause_displayed = false; static bool queues_stack_duplicate(notification *n); void queues_init(void) { history = g_queue_new(); displayed = g_queue_new(); waiting = g_queue_new(); } void queues_displayed_limit(unsigned int limit) { displayed_limit = limit; } /* misc getter functions */ const GList *queues_get_displayed() { return g_queue_peek_head_link(displayed); } unsigned int queues_length_waiting() { return waiting->length; } unsigned int queues_length_displayed() { return displayed->length; } unsigned int queues_length_history() { return history->length; } int queues_notification_insert(notification *n) { /* do not display the message, if the message is empty */ if (strlen(n->msg) == 0) { if (settings.always_run_script) { notification_run_script(n); } printf("skipping notification: %s %s\n", n->body, n->summary); return 0; } /* Do not insert the message if it's a command */ if (strcmp("DUNST_COMMAND_PAUSE", n->summary) == 0) { pause_displayed = true; return 0; } if (strcmp("DUNST_COMMAND_RESUME", n->summary) == 0) { pause_displayed = false; return 0; } if (n->id == 0) { n->id = ++next_notification_id; if (!settings.stack_duplicates || !queues_stack_duplicate(n)) g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL); } else { if (!queues_notification_replace_id(n)) g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL); } if (settings.print_notifications) notification_print(n); return n->id; } /* * Replaces duplicate notification and stacks it * * Returns %true, if notification got stacked * Returns %false, if notification did not get stacked */ static bool queues_stack_duplicate(notification *n) { for (GList *iter = g_queue_peek_head_link(displayed); iter; iter = iter->next) { notification *orig = iter->data; if (notification_is_duplicate(orig, n)) { /* If the progress differs, probably notify-send was used to update the notification * So only count it as a duplicate, if the progress was not the same. * */ if (orig->progress == n->progress) { orig->dup_count++; } else { orig->progress = n->progress; } iter->data = n; n->start = g_get_monotonic_time(); n->dup_count = orig->dup_count; signal_notification_closed(orig, 1); notification_free(orig); return true; } } for (GList *iter = g_queue_peek_head_link(waiting); iter; iter = iter->next) { notification *orig = iter->data; if (notification_is_duplicate(orig, n)) { /* If the progress differs, probably notify-send was used to update the notification * So only count it as a duplicate, if the progress was not the same. * */ if (orig->progress == n->progress) { orig->dup_count++; } else { orig->progress = n->progress; } iter->data = n; n->dup_count = orig->dup_count; signal_notification_closed(orig, 1); notification_free(orig); return true; } } return false; } bool queues_notification_replace_id(notification *new) { for (GList *iter = g_queue_peek_head_link(displayed); iter; iter = iter->next) { notification *old = iter->data; if (old->id == new->id) { iter->data = new; new->start = g_get_monotonic_time(); new->dup_count = old->dup_count; notification_run_script(new); notification_free(old); return true; } } for (GList *iter = g_queue_peek_head_link(waiting); iter; iter = iter->next) { notification *old = iter->data; if (old->id == new->id) { iter->data = new; new->dup_count = old->dup_count; notification_free(old); return true; } } return false; } int queues_notification_close_id(int id, enum reason 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); target = n; break; } } for (GList *iter = g_queue_peek_head_link(waiting); iter; iter = iter->next) { notification *n = iter->data; if (n->id == id) { assert(target == NULL); g_queue_remove(waiting, n); target = n; break; } } if (target) { //Don't notify clients if notification was pulled from history if (!target->redisplayed) signal_notification_closed(target, reason); queues_history_push(target); } return reason; } int queues_notification_close(notification *n, enum reason reason) { assert(n != NULL); return queues_notification_close_id(n->id, reason); } void queues_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(waiting, n); } void queues_history_push(notification *n) { if (!n->history_ignore) { if (settings.history_length > 0 && history->length >= settings.history_length) { notification *to_free = g_queue_pop_head(history); notification_free(to_free); } g_queue_push_tail(history, n); } else { notification_free(n); } } void queues_history_push_all(void) { while (displayed->length > 0) { queues_notification_close(g_queue_peek_head_link(displayed)->data, REASON_USER); } while (waiting->length > 0) { queues_notification_close(g_queue_peek_head_link(waiting)->data, REASON_USER); } } void queues_check_timeouts(bool idle) { /* nothing to do */ if (displayed->length == 0) return; GList *iter = g_queue_peek_head_link(displayed); while (iter) { notification *n = iter->data; /* * Update iter to the next item before we either exit the * current iteration of the loop or potentially delete the * notification which would invalidate the pointer. */ iter = iter->next; /* don't timeout when user is idle */ if (idle && !n->transient) { n->start = g_get_monotonic_time(); continue; } /* skip hidden and sticky messages */ if (n->start == 0 || n->timeout == 0) { continue; } /* remove old message */ if (g_get_monotonic_time() - n->start > n->timeout) { queues_notification_close(n, REASON_TIME); } } } void queues_update() { if (pause_displayed) { while (displayed->length > 0) { g_queue_insert_sorted( waiting, g_queue_pop_head(displayed), notification_cmp_data, NULL); } return; } /* move notifications from queue to displayed */ while (waiting->length > 0) { if (displayed_limit > 0 && displayed->length >= displayed_limit) { /* the list is full */ break; } notification *n = g_queue_pop_head(waiting); if (!n) return; n->start = g_get_monotonic_time(); if (!n->redisplayed && n->script) { notification_run_script(n); } g_queue_insert_sorted(displayed, n, notification_cmp_data, NULL); } } gint64 queues_get_next_datachange(gint64 time) { gint64 sleep = G_MAXINT64; for (GList *iter = g_queue_peek_head_link(displayed); iter; iter = iter->next) { notification *n = iter->data; gint64 ttl = n->timeout - (time - n->start); if (n->timeout > 0) { if (ttl > 0) sleep = MIN(sleep, ttl); else // while we're processing, the notification already timed out return 0; } if (settings.show_age_threshold >= 0) { gint64 age = time - n->timestamp; if (age > settings.show_age_threshold) // sleep exactly until the next shift of the second happens sleep = MIN(sleep, ((G_USEC_PER_SEC) - (age % (G_USEC_PER_SEC)))); else if (ttl > settings.show_age_threshold) sleep = MIN(sleep, settings.show_age_threshold); } } return sleep != G_MAXINT64 ? sleep : -1; } void queues_pause_on(void) { pause_displayed = true; } void queues_pause_off(void) { pause_displayed = false; } bool queues_pause_status(void) { return pause_displayed; } static void teardown_notification(gpointer data) { notification *n = data; notification_free(n); } void teardown_queues(void) { g_queue_free_full(history, teardown_notification); g_queue_free_full(displayed, teardown_notification); g_queue_free_full(waiting, teardown_notification); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/queues.h����������������������������������������������������������������������������0000664�0000000�0000000�00000006520�13223745120�0015121�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_QUEUE_H #define DUNST_QUEUE_H #include "dbus.h" #include "notification.h" /* * Initialise neccessary queues */ void queues_init(void); /* * Set maximum notification count to display * and store in displayed queue */ void queues_displayed_limit(unsigned int limit); /* * Return read only list of notifications */ const GList *queues_get_displayed(); /* * Returns the current amount of notifications, * which are shown, waiting or already in history */ unsigned int queues_length_waiting(); unsigned int queues_length_displayed(); unsigned int queues_length_history(); /* * Insert a fully initialized notification into queues * Respects stack_duplicates, and notification replacement * * If n->id != 0, n replaces notification with id n->id * If n->id == 0, n gets a new id assigned * * Returns the assigned notification id * If returned id == 0, the message was dismissed */ int queues_notification_insert(notification *n); /* * Replace the notification which matches the id field of * the new notification. The given notification is inserted * right in the same position as the old notification. * * Returns true, if a matching notification has been found * and is replaced. Else false. */ bool queues_notification_replace_id(notification *new); /* * Close the notification that has n->id == id * * Sends a signal and pushes it automatically to history. * * After closing, call wake_up to synchronize the queues with the UI * (which closes the notification on screen) */ int queues_notification_close_id(int id, enum reason reason); /* Close the given notification. SEE queues_notification_close_id. * * @n: (transfer full): The notification to close * @reason: The reason to close * */ int queues_notification_close(notification *n, enum reason reason); /* * Pushed the latest notification of history to the displayed queue * and removes it from history */ void queues_history_pop(void); /* * Push a single notification to history * The given notification has to be removed its queue * * @n: (transfer full): The notification to push to history */ void queues_history_push(notification *n); /* * Push all waiting and displayed notifications to history */ void queues_history_push_all(void); /* * Check timeout of each notification and close it, if neccessary */ void queues_check_timeouts(bool idle); /* * Move inserted notifications from waiting queue to displayed queue * and show them. In displayed queue, the amount of elements is limited * to the amount set via queues_displayed_limit */ void queues_update(); /* * Return the distance to the next event in the queue, * which forces an update visible to the user * * This may be: * * - notification hits timeout * - notification's age second changes * - notification's age threshold is hit */ gint64 queues_get_next_datachange(gint64 time); /* * Pause queue-management of dunst * pause_on = paused (no notifications displayed) * pause_off = running * * Calling update_lists is neccessary */ void queues_pause_on(void); void queues_pause_off(void); bool queues_pause_status(void); /* * Remove all notifications from all lists * and free the notifications */ void teardown_queues(void); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/rules.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000005354�13223745120�0014743�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "rules.h" #include <fnmatch.h> #include <glib.h> #include "dunst.h" /* * Apply rule to notification. */ void rule_apply(rule_t *r, notification *n) { if (r->timeout != -1) n->timeout = r->timeout; if (r->urgency != URG_NONE) n->urgency = r->urgency; if (r->history_ignore != -1) n->history_ignore = r->history_ignore; if (r->set_transient != -1) n->transient = r->set_transient; if (r->markup != MARKUP_NULL) n->markup = r->markup; if (r->new_icon) { g_free(n->icon); n->icon = g_strdup(r->new_icon); rawimage_free(n->raw_icon); n->raw_icon = NULL; } if (r->fg) { g_free(n->colors[ColFG]); n->colors[ColFG] = g_strdup(r->fg); } if (r->bg) { g_free(n->colors[ColBG]); n->colors[ColBG] = g_strdup(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->category = NULL; r->msg_urgency = URG_NONE; r->timeout = -1; r->urgency = URG_NONE; r->markup = MARKUP_NULL; r->new_icon = NULL; r->history_ignore = false; r->match_transient = -1; r->set_transient = -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)) && (!r->category || !fnmatch(r->category, n->category, 0)) && (r->match_transient == -1 || (r->match_transient == n->transient)) && (r->msg_urgency == URG_NONE || r->msg_urgency == n->urgency)); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/rules.h�����������������������������������������������������������������������������0000664�0000000�0000000�00000001762�13223745120�0014747�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_RULES_H #define DUNST_RULES_H #include <glib.h> #include <stdbool.h> #include "notification.h" #include "settings.h" typedef struct _rule_t { char *name; /* filters */ char *appname; char *summary; char *body; char *icon; char *category; int msg_urgency; /* actions */ gint64 timeout; enum urgency urgency; enum markup_mode markup; int history_ignore; int match_transient; int set_transient; char *new_icon; 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); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ��������������dunst-1.3.0/src/settings.c��������������������������������������������������������������������������0000664�0000000�0000000�00000063112�13223745120�0015445�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "settings.h" #include <glib.h> #include <stdio.h> #include <string.h> #ifndef STATIC_CONFIG #include <basedir.h> #include <basedir_fs.h> #endif #include "rules.h" // put before config.h to fix missing include #include "config.h" #include "dunst.h" #include "notification.h" #include "option_parser.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; } } static enum markup_mode parse_markup_mode(const char *mode) { if (strcmp(mode, "strip") == 0) { return MARKUP_STRIP; } else if (strcmp(mode, "no") == 0) { return MARKUP_NO; } else if (strcmp(mode, "full") == 0 || strcmp(mode, "yes") == 0) { return MARKUP_FULL; } else { fprintf(stderr, "Warning: unknown markup mode: \"%s\"\n", mode); return MARKUP_NO; } } static enum urgency ini_get_urgency(const char *section, const char *key, const int def) { int ret = def; char *urg = ini_get_string(section, key, ""); if (strlen(urg) > 0) { if (strcmp(urg, "low") == 0) ret = URG_LOW; else if (strcmp(urg, "normal") == 0) ret = URG_NORM; else if (strcmp(urg, "critical") == 0) ret = URG_CRIT; else fprintf(stderr, "unknown urgency: %s, ignoring\n", urg); } g_free(urg); return ret; } void load_settings(char *cmdline_config_path) { #ifndef STATIC_CONFIG xdgHandle xdg; FILE *config_file = NULL; xdgInitHandle(&xdg); if (cmdline_config_path != NULL) { if (0 == strcmp(cmdline_config_path, "-")) { config_file = stdin; } else { config_file = fopen(cmdline_config_path, "r"); } if(!config_file) { char *msg = g_strdup_printf( "Cannot find config file: '%s'\n", cmdline_config_path); die(msg, 1); } } 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); #else fprintf(stderr, "Warning: dunstrc parsing disabled. " "Using STATIC_CONFIG is deprecated behavior.\n"); #endif settings.per_monitor_dpi = option_get_bool( "experimental", "per_monitor_dpi", NULL, false, "" ); settings.force_xinerama = option_get_bool( "global", "force_xinerama", "-force_xinerama", false, "Force the use of the Xinerama extension" ); settings.font = option_get_string( "global", "font", "-font/-fn", defaults.font, "The font dunst should use." ); { // Check if allow_markup set if (ini_is_set("global", "allow_markup")) { bool allow_markup = option_get_bool( "global", "allow_markup", NULL, false, "Allow markup in notifications" ); settings.markup = (allow_markup ? MARKUP_FULL : MARKUP_STRIP); fprintf(stderr, "Warning: 'allow_markup' is deprecated, please use 'markup' instead.\n"); } char *c = option_get_string( "global", "markup", "-markup", NULL, "Specify how markup should be handled" ); //Use markup if set //Use default if settings.markup not set yet // (=>c empty&&!allow_markup) if (c) { settings.markup = parse_markup_mode(c); } else if (!settings.markup) { settings.markup = defaults.markup; } g_free(c); } settings.format = option_get_string( "global", "format", "-format", defaults.format, "The format template for the notifications" ); settings.sort = option_get_bool( "global", "sort", "-sort", defaults.sort, "Sort notifications by urgency and date?" ); settings.indicate_hidden = option_get_bool( "global", "indicate_hidden", "-indicate_hidden", defaults.indicate_hidden, "Show how many notificaitons are hidden?" ); settings.word_wrap = option_get_bool( "global", "word_wrap", "-word_wrap", defaults.word_wrap, "Truncating long lines or do word wrap" ); { char *c = option_get_string( "global", "ellipsize", "-ellipsize", "", "Ellipsize truncated lines on the start/middle/end" ); if (strlen(c) == 0) { settings.ellipsize = defaults.ellipsize; } else if (strcmp(c, "start") == 0) { settings.ellipsize = start; } else if (strcmp(c, "middle") == 0) { settings.ellipsize = middle; } else if (strcmp(c, "end") == 0) { settings.ellipsize = end; } else { fprintf(stderr, "Warning: unknown ellipsize value: \"%s\"\n", c); settings.ellipsize = defaults.ellipsize; } g_free(c); } settings.ignore_newline = option_get_bool( "global", "ignore_newline", "-ignore_newline", defaults.ignore_newline, "Ignore newline characters in notifications" ); settings.idle_threshold = option_get_time( "global", "idle_threshold", "-idle_threshold", defaults.idle_threshold, "Don't timeout notifications if user is longer idle than threshold" ); settings.monitor = option_get_int( "global", "monitor", "-mon/-monitor", defaults.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); g_free(c); } } settings.title = option_get_string( "global", "title", "-t/-title", defaults.title, "Define the title of windows spawned by dunst." ); settings.class = option_get_string( "global", "class", "-c/-class", defaults.class, "Define the class of windows spawned by dunst." ); settings.geom = option_get_string( "global", "geometry", "-geom/-geometry", defaults.geom, "Geometry for the window" ); settings.shrink = option_get_bool( "global", "shrink", "-shrink", defaults.shrink, "Shrink window if it's smaller than the width" ); settings.line_height = option_get_int( "global", "line_height", "-lh/-line_height", defaults.line_height, "Add spacing between lines of text" ); settings.notification_height = option_get_int( "global", "notification_height", "-nh/-notification_height", defaults.notification_height, "Define height of the window" ); { char *c = option_get_string( "global", "alignment", "-align/-alignment", "", "Text alignment 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 alignment\n"); g_free(c); } } settings.show_age_threshold = option_get_time( "global", "show_age_threshold", "-show_age_threshold", defaults.show_age_threshold, "When should the age of the notification be displayed?" ); settings.hide_duplicate_count = option_get_bool( "global", "hide_duplicate_count", "-hide_duplicate_count", false, "Hide the count of merged notifications with the same content" ); settings.sticky_history = option_get_bool( "global", "sticky_history", "-sticky_history", defaults.sticky_history, "Don't timeout notifications popped up from history" ); settings.history_length = option_get_int( "global", "history_length", "-history_length", defaults.history_length, "Max amount of notifications kept in history" ); settings.show_indicators = option_get_bool( "global", "show_indicators", "-show_indicators", defaults.show_indicators, "Show indicators for actions \"(A)\" and URLs \"(U)\"" ); settings.separator_height = option_get_int( "global", "separator_height", "-sep_height/-separator_height", defaults.separator_height, "height of the separator line" ); settings.padding = option_get_int( "global", "padding", "-padding", defaults.padding, "Padding between text and separator" ); settings.h_padding = option_get_int( "global", "horizontal_padding", "-horizontal_padding", defaults.h_padding, "horizontal padding" ); settings.transparency = option_get_int( "global", "transparency", "-transparency", defaults.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); } } g_free(c); } settings.stack_duplicates = option_get_bool( "global", "stack_duplicates", "-stack_duplicates", true, "Merge multiple notifications with the same content" ); settings.startup_notification = option_get_bool( "global", "startup_notification", "-startup_notification", false, "print notification on startup" ); settings.dmenu = option_get_path( "global", "dmenu", "-dmenu", defaults.dmenu, "path to dmenu" ); { GError *error = NULL; if (!g_shell_parse_argv(settings.dmenu, NULL, &settings.dmenu_cmd, &error)) { fprintf(stderr, "Unable to parse dmenu command: %s\n", error->message); fprintf(stderr, "dmenu functionality will be disabled.\n"); g_error_free(error); settings.dmenu_cmd = NULL; } } settings.browser = option_get_path( "global", "browser", "-browser", defaults.browser, "path to browser" ); { char *c = option_get_string( "global", "icon_position", "-icon_position", "off", "Align icons left/right/off" ); if (strlen(c) > 0) { if (strcmp(c, "left") == 0) settings.icon_position = icons_left; else if (strcmp(c, "right") == 0) settings.icon_position = icons_right; else if (strcmp(c, "off") == 0) settings.icon_position = icons_off; else fprintf(stderr, "Warning: unknown icon position: %s\n", c); g_free(c); } } settings.max_icon_size = option_get_int( "global", "max_icon_size", "-max_icon_size", defaults.max_icon_size, "Scale larger icons down to this size, set to 0 to disable" ); // If the deprecated icon_folders option is used, // read it and generate its usage string. if (ini_is_set("global", "icon_folders") || cmdline_is_set("-icon_folders")) { settings.icon_path = option_get_string( "global", "icon_folders", "-icon_folders", defaults.icon_path, "folders to default icons (deprecated, please use 'icon_path' instead)" ); fprintf(stderr, "Warning: 'icon_folders' is deprecated, please use 'icon_path' instead.\n"); } // Read value and generate usage string for icon_path. // If icon_path is set, override icon_folder. // if not, but icon_folder is set, use that instead of the compile time default. settings.icon_path = option_get_string( "global", "icon_path", "-icon_path", settings.icon_path ? settings.icon_path : defaults.icon_path, "paths to default icons" ); { // Backwards compatibility with the legacy 'frame' section. if (ini_is_set("frame", "width")) { settings.frame_width = option_get_int( "frame", "width", NULL, defaults.frame_width, "Width of frame around the window" ); fprintf(stderr, "Warning: The frame section is deprecated, width has been renamed to frame_width and moved to the global section.\n"); } settings.frame_width = option_get_int( "global", "frame_width", "-frame_width", settings.frame_width ? settings.frame_width : defaults.frame_width, "Width of frame around the window" ); if (ini_is_set("frame", "color")) { settings.frame_color = option_get_string( "frame", "color", NULL, defaults.frame_color, "Color of the frame around the window" ); fprintf(stderr, "Warning: The frame section is deprecated, color has been renamed to frame_color and moved to the global section.\n"); } settings.frame_color = option_get_string( "global", "frame_color", "-frame_color", settings.frame_color ? settings.frame_color : defaults.frame_color, "Color of the frame around the window" ); } settings.lowbgcolor = option_get_string( "urgency_low", "background", "-lb", defaults.lowbgcolor, "Background color for notifications with low urgency" ); settings.lowfgcolor = option_get_string( "urgency_low", "foreground", "-lf", defaults.lowfgcolor, "Foreground color for notifications with low urgency" ); settings.lowframecolor = option_get_string( "urgency_low", "frame_color", "-lfr", NULL, "Frame color for notifications with low urgency" ); settings.timeouts[URG_LOW] = option_get_time( "urgency_low", "timeout", "-lto", defaults.timeouts[URG_LOW], "Timeout for notifications with low urgency" ); settings.icons[URG_LOW] = option_get_string( "urgency_low", "icon", "-li", defaults.icons[URG_LOW], "Icon for notifications with low urgency" ); settings.normbgcolor = option_get_string( "urgency_normal", "background", "-nb", defaults.normbgcolor, "Background color for notifications with normal urgency" ); settings.normfgcolor = option_get_string( "urgency_normal", "foreground", "-nf", defaults.normfgcolor, "Foreground color for notifications with normal urgency" ); settings.normframecolor = option_get_string( "urgency_normal", "frame_color", "-nfr", NULL, "Frame color for notifications with normal urgency" ); settings.timeouts[URG_NORM] = option_get_time( "urgency_normal", "timeout", "-nto", defaults.timeouts[URG_NORM], "Timeout for notifications with normal urgency" ); settings.icons[URG_NORM] = option_get_string( "urgency_normal", "icon", "-ni", defaults.icons[URG_NORM], "Icon for notifications with normal urgency" ); settings.critbgcolor = option_get_string( "urgency_critical", "background", "-cb", defaults.critbgcolor, "Background color for notifications with critical urgency" ); settings.critfgcolor = option_get_string( "urgency_critical", "foreground", "-cf", defaults.critfgcolor, "Foreground color for notifications with ciritical urgency" ); settings.critframecolor = option_get_string( "urgency_critical", "frame_color", "-cfr", NULL, "Frame color for notifications with critical urgency" ); settings.timeouts[URG_CRIT] = option_get_time( "urgency_critical", "timeout", "-cto", defaults.timeouts[URG_CRIT], "Timeout for notifications with critical urgency" ); settings.icons[URG_CRIT] = option_get_string( "urgency_critical", "icon", "-ci", defaults.icons[URG_CRIT], "Icon for notifications with critical urgency" ); settings.close_ks.str = option_get_string( "shortcuts", "close", "-key", defaults.close_ks.str, "Shortcut for closing one notification" ); settings.close_all_ks.str = option_get_string( "shortcuts", "close_all", "-all_key", defaults.close_all_ks.str, "Shortcut for closing all notifications" ); settings.history_ks.str = option_get_string( "shortcuts", "history", "-history_key", defaults.history_ks.str, "Shortcut to pop the last notification from history" ); settings.context_ks.str = option_get_string( "shortcuts", "context", "-context_key", defaults.context_ks.str, "Shortcut for context menu" ); settings.print_notifications = cmdline_get_bool( "-print", false, "Print notifications to cmdline (DEBUG)" ); settings.always_run_script = option_get_bool( "global", "always_run_script", "-always_run_script", true, "Always run rule-defined scripts, even if the notification is suppressed with format = \"\"." ); /* push hardcoded default rules into rules list */ for (int i = 0; i < G_N_ELEMENTS(default_rules); i++) { rules = g_slist_insert(rules, &(default_rules[i]), -1); } const char *cur_section = NULL; for (;;) { cur_section = next_section(cur_section); if (!cur_section) break; if (strcmp(cur_section, "global") == 0 || strcmp(cur_section, "frame") == 0 || strcmp(cur_section, "experimental") == 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, -1); } 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->category = ini_get_string(cur_section, "category", r->category); r->timeout = ini_get_time(cur_section, "timeout", r->timeout); { char *c = ini_get_string( cur_section, "markup", NULL ); if (c != NULL) { r->markup = parse_markup_mode(c); g_free(c); } } r->urgency = ini_get_urgency(cur_section, "urgency", r->urgency); r->msg_urgency = ini_get_urgency(cur_section, "msg_urgency", r->msg_urgency); 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->new_icon = ini_get_string(cur_section, "new_icon", r->new_icon); r->history_ignore = ini_get_bool(cur_section, "history_ignore", r->history_ignore); r->match_transient = ini_get_bool(cur_section, "match_transient", r->match_transient); r->set_transient = ini_get_bool(cur_section, "set_transient", r->set_transient); r->script = ini_get_path(cur_section, "script", NULL); } #ifndef STATIC_CONFIG if (config_file) { fclose(config_file); free_ini(); xdgWipeHandle(&xdg); } #endif } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/settings.h��������������������������������������������������������������������������0000664�0000000�0000000�00000004502�13223745120�0015450�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_SETTINGS_H #define DUNST_SETTINGS_H #include <stdbool.h> #include "x11/x.h" enum alignment { left, center, right }; enum ellipsize { start, middle, end }; enum icon_position_t { icons_left, icons_right, icons_off }; enum separator_color { FOREGROUND, AUTO, FRAME, CUSTOM }; enum follow_mode { FOLLOW_NONE, FOLLOW_MOUSE, FOLLOW_KEYBOARD }; enum markup_mode { MARKUP_NULL, MARKUP_NO, MARKUP_STRIP, MARKUP_FULL }; typedef struct _settings { bool print_notifications; bool per_monitor_dpi; enum markup_mode markup; bool stack_duplicates; bool hide_duplicate_count; char *font; char *normbgcolor; char *normfgcolor; char *normframecolor; char *critbgcolor; char *critfgcolor; char *critframecolor; char *lowbgcolor; char *lowfgcolor; char *lowframecolor; char *format; gint64 timeouts[3]; char *icons[3]; unsigned int transparency; char *geom; char *title; char *class; int shrink; int sort; int indicate_hidden; gint64 idle_threshold; gint64 show_age_threshold; enum alignment align; int sticky_history; int history_length; int show_indicators; int word_wrap; enum ellipsize ellipsize; int ignore_newline; int line_height; int notification_height; int separator_height; int padding; int h_padding; enum separator_color sep_color; char *sep_custom_color_str; int frame_width; char *frame_color; int startup_notification; int monitor; char *dmenu; char **dmenu_cmd; char *browser; enum icon_position_t icon_position; int max_icon_size; char *icon_path; enum follow_mode f_mode; bool always_run_script; keyboard_shortcut close_ks; keyboard_shortcut close_all_ks; keyboard_shortcut history_ks; keyboard_shortcut context_ks; bool force_xinerama; } settings_t; extern settings_t settings; void load_settings(char *cmdline_config_path); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/utils.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000011105�13223745120�0014740�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "utils.h" #include <assert.h> #include <errno.h> #include <glib.h> #include <stdio.h> #include <stdlib.h> #include <string.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_at(char *buf, int pos, int len, const char *repl) { char *tmp; int size, buf_len, repl_len; buf_len = strlen(buf); repl_len = strlen(repl); size = (buf_len - len) + repl_len + 1; if (repl_len <= len) { tmp = buf; } else { tmp = g_malloc(size); memcpy(tmp, buf, pos); } memcpy(tmp + pos, repl, repl_len); memmove(tmp + pos + repl_len, buf + pos + len, buf_len - (pos + len) + 1); if (tmp != buf) { g_free(buf); } return tmp; } char *string_replace(const char *needle, const char *replacement, char *haystack) { char *start; start = strstr(haystack, needle); if (start == NULL) { return haystack; } return string_replace_at(haystack, (start - haystack), strlen(needle), replacement); } char *string_replace_all(const char *needle, const char *replacement, char *haystack) { char *start; int needle_pos; int needle_len, repl_len; needle_len = strlen(needle); if (needle_len == 0) { return haystack; } start = strstr(haystack, needle); repl_len = strlen(replacement); while (start != NULL) { needle_pos = start - haystack; haystack = string_replace_at(haystack, needle_pos, needle_len, replacement); start = strstr(haystack + needle_pos + repl_len, needle); } return haystack; } char *string_append(char *a, const char *b, const char *sep) { if (!a || *a == '\0') { g_free(a); return g_strdup(b); } if (!b || *b == '\0') return a; char *new; if (!sep) new = g_strconcat(a, b, NULL); else new = g_strconcat(a, sep, b, NULL); g_free(a); return new; } void string_strip_delimited(char *str, char a, char b) { int iread=-1, iwrite=0, copen=0; while (str[++iread] != 0) { if (str[iread] == a) { ++copen; } else if (str[iread] == b && copen > 0) { --copen; } else if (copen == 0) { str[iwrite++] = str[iread]; } } str[iwrite] = 0; } char *string_to_path(char *string) { if (string && 0 == strncmp(string, "~/", 2)) { char *home = g_strconcat(getenv("HOME"), "/", NULL); string = string_replace("~/", home, string); g_free(home); } return string; } gint64 string_to_time(const char *string) { assert(string); errno = 0; char *endptr; gint64 val = strtoll(string, &endptr, 10); if (errno != 0) { fprintf(stderr, "ERROR: Time: '%s': %s.\n", string, strerror(errno)); return 0; } else if (string == endptr) { fprintf(stderr, "ERROR: Time: No digits found.\n"); return 0; } else if (errno != 0 && val == 0) { fprintf(stderr, "ERROR: Time: '%s' unknown error.\n", string); return 0; } else if (errno == 0 && !*endptr) { return val * G_USEC_PER_SEC; } // endptr may point to a separating space while (*endptr == ' ') endptr++; if (0 == strncmp(endptr, "ms", 2)) return val * 1000; else if (0 == strncmp(endptr, "s", 1)) return val * G_USEC_PER_SEC; else if (0 == strncmp(endptr, "m", 1)) return val * G_USEC_PER_SEC * 60; else if (0 == strncmp(endptr, "h", 1)) return val * G_USEC_PER_SEC * 60 * 60; else if (0 == strncmp(endptr, "d", 1)) return val * G_USEC_PER_SEC * 60 * 60 * 24; else return 0; } void die(char *text, int exit_value) { fputs(text, stderr); exit(exit_value); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/utils.h�����������������������������������������������������������������������������0000664�0000000�0000000�00000002435�13223745120�0014753�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_UTILS_H #define DUNST_UTILS_H #include <glib.h> /* replace all occurrences 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 <len> characters with <repl> at position <pos> of the string <buf> */ char *string_replace_at(char *buf, int pos, int len, const char *repl); /* 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); /* strip content between two delimiter characters (inplace) */ void string_strip_delimited(char *str, char a, char b); /* exit with an error message */ void die(char *msg, int exit_value); /* replace tilde and path-specific values with its equivalents */ char *string_to_path(char *string); /* convert time units (ms, s, m) to internal gint64 microseconds */ gint64 string_to_time(const char *string); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/x11/��������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13223745120�0014047�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/x11/screen.c������������������������������������������������������������������������0000664�0000000�0000000�00000022723�13223745120�0015500�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "screen.h" #include <X11/X.h> #include <X11/Xatom.h> #include <X11/Xlib.h> #include <X11/Xresource.h> #include <X11/extensions/Xinerama.h> #include <X11/extensions/Xrandr.h> #include <X11/extensions/randr.h> #include <assert.h> #include <glib.h> #include <locale.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "src/settings.h" #include "x.h" screen_info *screens; int screens_len; bool dunst_follow_errored = false; int randr_event_base = 0; static int randr_major_version = 0; static int randr_minor_version = 0; void randr_init(); void randr_update(); void xinerama_update(); void screen_update_fallback(); static void x_follow_setup_error_handler(void); static int x_follow_tear_down_error_handler(void); static int FollowXErrorHandler(Display *display, XErrorEvent *e); static Window get_focused_window(void); static double get_xft_dpi_value() { static double dpi = -1; //Only run this once, we don't expect dpi changes during runtime if (dpi <= -1) { XrmInitialize(); char *xRMS = XResourceManagerString(xctx.dpy); if (xRMS == NULL) { dpi = 0; return 0; } XrmDatabase xDB = XrmGetStringDatabase(xRMS); char *xrmType; XrmValue xrmValue; if (XrmGetResource(xDB, "Xft.dpi", "Xft.dpi", &xrmType, &xrmValue)) { dpi = strtod(xrmValue.addr, NULL); } else { dpi = 0; } XrmDestroyDatabase(xDB); } return dpi; } void init_screens() { if (!settings.force_xinerama) { randr_init(); randr_update(); } else { xinerama_update(); } } void alloc_screen_ar(int n) { assert(n > 0); if (n <= screens_len) return; screens = g_realloc(screens, n * sizeof(screen_info)); memset(screens, 0, n * sizeof(screen_info)); screens_len = n; } void randr_init() { int randr_error_base = 0; if (!XRRQueryExtension(xctx.dpy, &randr_event_base, &randr_error_base)) { fprintf(stderr, "Could not initialize the RandR extension, falling back to single monitor mode.\n"); return; } XRRQueryVersion(xctx.dpy, &randr_major_version, &randr_minor_version); XRRSelectInput(xctx.dpy, RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)), RRScreenChangeNotifyMask); } void randr_update() { if (randr_major_version < 1 || (randr_major_version == 1 && randr_minor_version < 5)) { fprintf(stderr, "Server RandR version too low (%i.%i). Falling back to single monitor mode\n", randr_major_version, randr_minor_version); screen_update_fallback(); return; } int n = 0; XRRMonitorInfo *m = XRRGetMonitors(xctx.dpy, RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)), true, &n); if (n < 1) { fprintf(stderr, "Get monitors reported %i monitors, falling back to single monitor mode\n", n); screen_update_fallback(); return; } assert(m); alloc_screen_ar(n); for (int i = 0; i < n; i++) { screens[i].scr = i; screens[i].dim.x = m[i].x; screens[i].dim.y = m[i].y; screens[i].dim.w = m[i].width; screens[i].dim.h = m[i].height; screens[i].dim.mmh = m[i].mheight; } XRRFreeMonitors(m); } static int autodetect_dpi(screen_info *scr) { return (double)scr->dim.h * 25.4 / (double)scr->dim.mmh; } void screen_check_event(XEvent event) { if (event.type == randr_event_base + RRScreenChangeNotify) randr_update(); } void xinerama_update() { int n; XineramaScreenInfo *info = XineramaQueryScreens(xctx.dpy, &n); if (!info) { fprintf(stderr, "(Xinerama) Could not get screen info, falling back to single monitor mode\n"); screen_update_fallback(); return; } alloc_screen_ar(n); for (int i = 0; i < n; i++) { screens[i].scr = i; screens[i].dim.x = info[i].x_org; screens[i].dim.y = info[i].y_org; screens[i].dim.h = info[i].height; screens[i].dim.w = info[i].width; } XFree(info); } void screen_update_fallback() { alloc_screen_ar(1); int screen; if (settings.monitor >= 0) screen = settings.monitor; else screen = DefaultScreen(xctx.dpy); screens[0].dim.w = DisplayWidth(xctx.dpy, screen); screens[0].dim.h = DisplayHeight(xctx.dpy, screen); } /* * Select the screen on which the Window * should be displayed. */ screen_info *get_active_screen() { int ret = 0; if (settings.monitor > 0 && settings.monitor < screens_len) { ret = settings.monitor; goto sc_cleanup; } x_follow_setup_error_handler(); if (settings.f_mode == FOLLOW_NONE) { ret = XDefaultScreen(xctx.dpy); goto sc_cleanup; } 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 */ ret = XDefaultScreen(xctx.dpy); goto sc_cleanup; } Window child_return; XTranslateCoordinates(xctx.dpy, focused, root, 0, 0, &x, &y, &child_return); } for (int i = 0; i < screens_len; i++) { if (INRECT(x, y, screens[i].dim.x, screens[i].dim.y, screens[i].dim.w, screens[i].dim.h)) { ret = i; } } if (ret > 0) goto sc_cleanup; /* something seems to be wrong. Fallback to default */ ret = XDefaultScreen(xctx.dpy); goto sc_cleanup; } sc_cleanup: x_follow_tear_down_error_handler(); assert(screens); assert(ret >= 0 && ret < screens_len); return &screens[ret]; } double get_dpi_for_screen(screen_info *scr) { double dpi = 0; if ((!settings.force_xinerama && settings.per_monitor_dpi && (dpi = autodetect_dpi(scr)))) return dpi; else if ((dpi = get_xft_dpi_value())) return dpi; else return 96; } /* * 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; } static void x_follow_setup_error_handler(void) { dunst_follow_errored = false; XFlush(xctx.dpy); XSetErrorHandler(FollowXErrorHandler); } static int x_follow_tear_down_error_handler(void) { XFlush(xctx.dpy); XSync(xctx.dpy, false); XSetErrorHandler(NULL); return dunst_follow_errored; } static int FollowXErrorHandler(Display *display, XErrorEvent *e) { dunst_follow_errored = true; char err_buf[BUFSIZ]; XGetErrorText(display, e->error_code, err_buf, BUFSIZ); fputs(err_buf, stderr); fputs("\n", stderr); return 0; } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ���������������������������������������������dunst-1.3.0/src/x11/screen.h������������������������������������������������������������������������0000664�0000000�0000000�00000001362�13223745120�0015501�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_SCREEN_H #define DUNST_SCREEN_H #include <X11/Xlib.h> #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 mmh; unsigned int w; int mask; int negative_width; } dimension_t; typedef struct _screen_info { int scr; dimension_t dim; } screen_info; void init_screens(); void screen_check_event(XEvent event); screen_info *get_active_screen(); double get_dpi_for_screen(screen_info *scr); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/x11/x.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000134071�13223745120�0014470�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "x.h" #include <X11/X.h> #include <X11/XKBlib.h> #include <X11/Xatom.h> #include <X11/Xlib.h> #include <X11/Xutil.h> #include <assert.h> #include <cairo-xlib.h> #include <cairo.h> #include <gdk-pixbuf/gdk-pixbuf.h> #include <glib-object.h> #include <locale.h> #include <math.h> #include <pango/pango-attributes.h> #include <pango/pango-font.h> #include <pango/pango-layout.h> #include <pango/pango-types.h> #include <pango/pangocairo.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "src/dbus.h" #include "src/dunst.h" #include "src/markup.h" #include "src/notification.h" #include "src/queues.h" #include "src/settings.h" #include "src/utils.h" #include "screen.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; color_t frame; char *text; PangoAttrList *attr; cairo_surface_t *icon; notification *n; } colored_layout; cairo_ctx_t cairo_ctx; /* FIXME refactor setup teardown handlers into one setup and one teardown */ 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_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(colored_layout *cl, colored_layout *cl_next) { switch (settings.sep_color) { case FRAME: if (cl_next->n->urgency > cl->n->urgency) return cl_next->frame; else return cl->frame; case CUSTOM: return x_string_to_color_t(settings.sep_custom_color_str); case FOREGROUND: return cl->fg; case AUTO: return calculate_foreground_color(cl->bg); default: printf("Unknown separator color type. Please file a Bugreport.\n"); return cl->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); } 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); if (cl->icon) cairo_surface_destroy(cl->icon); g_free(cl); } static bool have_dynamic_width(void) { return (xctx.geometry.mask & WidthValue && xctx.geometry.w == 0); } static bool does_file_exist(const char *filename) { return (access(filename, F_OK) != -1); } static bool is_readable_file(const char *filename) { return (access(filename, R_OK) != -1); } const char *get_filename_ext(const char *filename) { const char *dot = strrchr(filename, '.'); if (!dot || dot == filename) return ""; return dot + 1; } 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 = get_active_screen(); 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 += 2 * settings.frame_width; dim.h += (g_slist_length(layouts) - 1) * settings.separator_height; int text_width = 0, total_width = 0; for (GSList *iter = layouts; iter; iter = iter->next) { colored_layout *cl = iter->data; int w=0,h=0; pango_layout_get_pixel_size(cl->l, &w, &h); if (cl->icon) { h = MAX(cairo_image_surface_get_height(cl->icon), h); w += cairo_image_surface_get_width(cl->icon) + settings.h_padding; } h = MAX(settings.notification_height, h + settings.padding * 2); dim.h += h; text_width = MAX(w, text_width); if (have_dynamic_width() || settings.shrink) { /* dynamic width */ total_width = MAX(text_width + 2 * settings.h_padding, total_width); /* subtract height from the unwrapped text */ dim.h -= h; if (total_width > scr->dim.w) { /* set width to screen width */ dim.w = scr->dim.w - xctx.geometry.x * 2; } else if (have_dynamic_width() || (total_width < xctx.geometry.w && settings.shrink)) { /* set width to text width */ dim.w = total_width + 2 * settings.frame_width; } /* re-setup the layout */ w = dim.w; w -= 2 * settings.h_padding; w -= 2 * settings.frame_width; if (cl->icon) w -= cairo_image_surface_get_width(cl->icon) + settings.h_padding; r_setup_pango_layout(cl->l, w); /* re-read information */ pango_layout_get_pixel_size(cl->l, &w, &h); if (cl->icon) { h = MAX(cairo_image_surface_get_height(cl->icon), h); w += cairo_image_surface_get_width(cl->icon) + settings.h_padding; } h = MAX(settings.notification_height, h + settings.padding * 2); 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 cairo_status_t read_from_buf(void *closure, unsigned char *data, unsigned int size) { GByteArray *buf = (GByteArray *)closure; unsigned int cpy = MIN(size, buf->len); memcpy(data, buf->data, cpy); g_byte_array_remove_range(buf, 0, cpy); return CAIRO_STATUS_SUCCESS; } static cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf) { /* * Export the gdk pixbuf into buffer as a png and import the png buffer * via cairo again as a cairo_surface_t. * It looks counterintuitive, as there is gdk_cairo_set_source_pixbuf, * which does the job faster. But this would require gtk3 as a dependency * for a single function call. See discussion in #334 and #376. */ cairo_surface_t *icon_surface = NULL; GByteArray *buffer; char *bufstr; gsize buflen; gdk_pixbuf_save_to_buffer(pixbuf, &bufstr, &buflen, "png", NULL, NULL); buffer = g_byte_array_new_take((guint8*)bufstr, buflen); icon_surface = cairo_image_surface_create_from_png_stream(read_from_buf, buffer); g_byte_array_free(buffer, TRUE); return icon_surface; } static GdkPixbuf *get_pixbuf_from_file(const char *icon_path) { GdkPixbuf *pixbuf = NULL; if (is_readable_file(icon_path)) { GError *error = NULL; pixbuf = gdk_pixbuf_new_from_file(icon_path, &error); if (pixbuf == NULL) g_free(error); } return pixbuf; } static GdkPixbuf *get_pixbuf_from_path(char *icon_path) { GdkPixbuf *pixbuf = NULL; gchar *uri_path = NULL; if (strlen(icon_path) > 0) { if (g_str_has_prefix(icon_path, "file://")) { uri_path = g_filename_from_uri(icon_path, NULL, NULL); if (uri_path != NULL) { icon_path = uri_path; } } /* absolute path? */ if (icon_path[0] == '/' || icon_path[0] == '~') { pixbuf = get_pixbuf_from_file(icon_path); } /* search in icon_path */ if (pixbuf == NULL) { char *start = settings.icon_path, *end, *current_folder, *maybe_icon_path; do { end = strchr(start, ':'); if (end == NULL) end = strchr(settings.icon_path, '\0'); /* end = end of string */ current_folder = g_strndup(start, end - start); /* try svg */ maybe_icon_path = g_strconcat(current_folder, "/", icon_path, ".svg", NULL); if (!does_file_exist(maybe_icon_path)) { g_free(maybe_icon_path); /* fallback to png */ maybe_icon_path = g_strconcat(current_folder, "/", icon_path, ".png", NULL); } g_free(current_folder); pixbuf = get_pixbuf_from_file(maybe_icon_path); g_free(maybe_icon_path); if (pixbuf != NULL) { return pixbuf; } start = end + 1; } while (*(end) != '\0'); } if (pixbuf == NULL) { fprintf(stderr, "Could not load icon: '%s'\n", icon_path); } if (uri_path != NULL) { g_free(uri_path); } } return pixbuf; } static GdkPixbuf *get_pixbuf_from_raw_image(const RawImage *raw_image) { GdkPixbuf *pixbuf = NULL; pixbuf = gdk_pixbuf_new_from_data(raw_image->data, GDK_COLORSPACE_RGB, raw_image->has_alpha, raw_image->bits_per_sample, raw_image->width, raw_image->height, raw_image->rowstride, NULL, NULL); return pixbuf; } static PangoLayout *create_layout(cairo_t *c) { screen_info *screen = get_active_screen(); PangoContext *context = pango_cairo_create_context(c); pango_cairo_context_set_resolution(context, get_dpi_for_screen(screen)); PangoLayout *layout = pango_layout_new(context); g_object_unref(context); return layout; } static colored_layout *r_init_shared(cairo_t *c, notification *n) { colored_layout *cl = g_malloc(sizeof(colored_layout)); cl->l = create_layout(c); if (!settings.word_wrap) { PangoEllipsizeMode ellipsize; switch (settings.ellipsize) { case start: ellipsize = PANGO_ELLIPSIZE_START; break; case middle: ellipsize = PANGO_ELLIPSIZE_MIDDLE; break; case end: ellipsize = PANGO_ELLIPSIZE_END; break; default: assert(false); } pango_layout_set_ellipsize(cl->l, ellipsize); } GdkPixbuf *pixbuf = NULL; if (n->raw_icon && settings.icon_position != icons_off) { pixbuf = get_pixbuf_from_raw_image(n->raw_icon); } else if (n->icon && settings.icon_position != icons_off) { pixbuf = get_pixbuf_from_path(n->icon); } if (pixbuf != NULL) { int w = gdk_pixbuf_get_width(pixbuf); int h = gdk_pixbuf_get_height(pixbuf); int larger = w > h ? w : h; if (settings.max_icon_size && larger > settings.max_icon_size) { GdkPixbuf *scaled; if (w >= h) { scaled = gdk_pixbuf_scale_simple(pixbuf, settings.max_icon_size, (int) ((double) settings.max_icon_size / w * h), GDK_INTERP_BILINEAR); } else { scaled = gdk_pixbuf_scale_simple(pixbuf, (int) ((double) settings.max_icon_size / h * w), settings.max_icon_size, GDK_INTERP_BILINEAR); } g_object_unref(pixbuf); pixbuf = scaled; } cl->icon = gdk_pixbuf_to_cairo_surface(pixbuf); g_object_unref(pixbuf); } else { cl->icon = NULL; } if (cl->icon && cairo_surface_status(cl->icon) != CAIRO_STATUS_SUCCESS) { cairo_surface_destroy(cl->icon); cl->icon = NULL; } cl->fg = x_string_to_color_t(n->colors[ColFG]); cl->bg = x_string_to_color_t(n->colors[ColBG]); cl->frame = x_string_to_color_t(n->colors[ColFrame]); cl->n = n; 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; if (cl->icon) width -= cairo_image_surface_get_width(cl->icon) + settings.h_padding; 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 = markup_strip(n->text_to_render); cl->text = NULL; cl->attr = NULL; pango_layout_set_text(cl->l, n->text_to_render, -1); if (n->first_render) { printf("Error parsing markup: %s\n", err->message); } g_error_free(err); } pango_layout_get_pixel_size(cl->l, NULL, &(n->displayed_height)); if (cl->icon) n->displayed_height = MAX(cairo_image_surface_get_height(cl->icon), n->displayed_height); n->displayed_height = MAX(settings.notification_height, n->displayed_height + settings.padding * 2); n->first_render = false; return cl; } static GSList *r_create_layouts(cairo_t *c) { GSList *layouts = NULL; int qlen = queues_length_waiting(); bool xmore_is_needed = qlen > 0 && settings.indicate_hidden; notification *last = NULL; for (const GList *iter = queues_get_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, colored_layout *cl_next, dimension_t dim, bool first, bool last) { int h; int h_text = 0; pango_layout_get_pixel_size(cl->l, NULL, &h); if (cl->icon) { h_text = h; h = MAX(cairo_image_surface_get_height(cl->icon), h); } int bg_x = 0; int bg_y = dim.y; int bg_width = dim.w; int bg_height = MAX(settings.notification_height, (2 * settings.padding) + h); double bg_half_height = settings.notification_height/2.0; int pango_offset = (int) floor(h/2.0); if (first) bg_height += settings.frame_width; if (last) bg_height += settings.frame_width; else bg_height += settings.separator_height; cairo_set_source_rgb(c, cl->frame.r, cl->frame.g, cl->frame.b); cairo_rectangle(c, bg_x, bg_y, bg_width, bg_height); cairo_fill(c); /* adding frame */ bg_x += settings.frame_width; if (first) { dim.y += settings.frame_width; bg_y += settings.frame_width; bg_height -= settings.frame_width; if (!last) bg_height -= settings.separator_height; } 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); bool use_padding = settings.notification_height <= (2 * settings.padding) + h; if (use_padding) dim.y += settings.padding; else dim.y += (int) (ceil(bg_half_height) - pango_offset); if (cl->icon && settings.icon_position == icons_left) { cairo_move_to(c, settings.frame_width + cairo_image_surface_get_width(cl->icon) + 2 * settings.h_padding, bg_y + settings.padding + h/2 - h_text/2); } else if (cl->icon && settings.icon_position == icons_right) { cairo_move_to(c, settings.frame_width + settings.h_padding, bg_y + settings.padding + h/2 - h_text/2); } else { cairo_move_to(c, settings.frame_width + settings.h_padding, bg_y + settings.padding); } 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); if (use_padding) dim.y += h + settings.padding; else dim.y += (int)(floor(bg_half_height) + pango_offset); if (settings.separator_height > 0 && !last) { color_t sep_color = x_get_separator_color(cl, cl_next); cairo_set_source_rgb(c, sep_color.r, sep_color.g, sep_color.b); if (settings.sep_color == FRAME) // Draw over the borders on both sides to avoid // the wrong color in the corners. cairo_rectangle(c, 0, dim.y, dim.w, settings.separator_height); else cairo_rectangle(c, settings.frame_width, dim.y + settings.frame_width, 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); if (cl->icon) { unsigned int image_width = cairo_image_surface_get_width(cl->icon), image_height = cairo_image_surface_get_height(cl->icon), image_x, image_y = bg_y + settings.padding + h/2 - image_height/2; if (settings.icon_position == icons_left) { image_x = settings.frame_width + settings.h_padding; } else { image_x = bg_width - settings.h_padding - image_width + settings.frame_width; } cairo_set_source_surface(c, cl->icon, image_x, image_y); cairo_rectangle(c, image_x, image_y, image_width, image_height); cairo_fill(c); } 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_move_to(c, 0, 0); bool first = true; for (GSList *iter = layouts; iter; iter = iter->next) { if (iter->next) dim = x_render_layout(c, iter->data, iter->next->data, dim, first, iter->next == NULL); else dim = x_render_layout(c, iter->data, NULL, 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); XFlush(xctx.dpy); 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 = get_active_screen(); xctx.cur_screen = scr->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); } /* * Returns the modifier which is NumLock. */ static KeySym x_numlock_mod() { static KeyCode nl = 0; KeySym sym = 0; XModifierKeymap *map = XGetModifierMapping(xctx.dpy); if (!nl) nl = XKeysymToKeycode(xctx.dpy, XStringToKeysym("Num_Lock")); for (int mod = 0; mod < 8; mod++) { for (int j = 0; j < map->max_keypermod; j++) { if (map->modifiermap[mod*map->max_keypermod+j] == nl) { /* In theory, one could use `1 << mod`, but this * could count as 'using implementation details', * so use this large switch. */ switch (mod) { case ShiftMapIndex: sym = ShiftMask; goto end; case LockMapIndex: sym = LockMask; goto end; case ControlMapIndex: sym = ControlMask; goto end; case Mod1MapIndex: sym = Mod1Mask; goto end; case Mod2MapIndex: sym = Mod2Mask; goto end; case Mod3MapIndex: sym = Mod3Mask; goto end; case Mod4MapIndex: sym = Mod4Mask; goto end; case Mod5MapIndex: sym = Mod5Mask; goto end; } } } } end: XFreeModifiermap(map); return sym; } /* * 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; unsigned int state; while (XPending(xctx.dpy) > 0) { XNextEvent(xctx.dpy, &ev); switch (ev.type) { case Expose: if (ev.xexpose.count == 0 && xctx.visible) { x_win_draw(); } break; case SelectionNotify: if (ev.xselection.property == xctx.utf8) break; case ButtonRelease: if (ev.xbutton.window == xctx.win) { x_handle_click(ev); wake_up(); } break; case KeyPress: state = ev.xkey.state; /* NumLock is also encoded in the state. Remove it. */ state &= ~x_numlock_mod(); if (settings.close_ks.str && XLookupKeysym(&ev.xkey, 0) == settings.close_ks.sym && settings.close_ks.mask == state) { const GList *displayed = queues_get_displayed(); if (displayed && displayed->data) { queues_notification_close(displayed->data, REASON_USER); wake_up(); } } if (settings.history_ks.str && XLookupKeysym(&ev.xkey, 0) == settings.history_ks.sym && settings.history_ks.mask == state) { queues_history_pop(); wake_up(); } if (settings.close_all_ks.str && XLookupKeysym(&ev.xkey, 0) == settings.close_all_ks.sym && settings.close_all_ks.mask == state) { queues_history_push_all(); wake_up(); } if (settings.context_ks.str && XLookupKeysym(&ev.xkey, 0) == settings.context_ks.sym && settings.context_ks.mask == state) { context_menu(); wake_up(); } break; case FocusIn: case FocusOut: wake_up(); break; case PropertyNotify: /* Ignore PropertyNotify, when we're still on the * same screen. PropertyNotify is only neccessary * to detect a focus change to another screen */ if( settings.f_mode != FOLLOW_NONE && get_active_screen()->scr != xctx.cur_screen) wake_up(); break; default: screen_check_event(ev); 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 > settings.idle_threshold / 1000; } /* TODO move to x_mainloop_* */ /* * Handle incoming mouse click events */ static void x_handle_click(XEvent ev) { if (ev.xbutton.button == Button3) { queues_history_push_all(); return; } if (ev.xbutton.button == Button1 || ev.xbutton.button == Button2) { int y = settings.separator_height; notification *n = NULL; int first = true; for (const GList *iter = queues_get_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) { if (ev.xbutton.button == Button1) queues_notification_close(n, REASON_USER); else notification_do_action(n); } } } void x_free(void) { cairo_surface_destroy(cairo_ctx.surface); cairo_destroy(cairo_ctx.context); if (xctx.dpy) XCloseDisplay(xctx.dpy); } /* * 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.colors[ColFG][URG_LOW] = settings.lowfgcolor; xctx.colors[ColFG][URG_NORM] = settings.normfgcolor; xctx.colors[ColFG][URG_CRIT] = settings.critfgcolor; xctx.colors[ColBG][URG_LOW] = settings.lowbgcolor; xctx.colors[ColBG][URG_NORM] = settings.normbgcolor; xctx.colors[ColBG][URG_CRIT] = settings.critbgcolor; if (settings.lowframecolor) xctx.colors[ColFrame][URG_LOW] = settings.lowframecolor; else xctx.colors[ColFrame][URG_LOW] = settings.frame_color; if (settings.normframecolor) xctx.colors[ColFrame][URG_NORM] = settings.normframecolor; else xctx.colors[ColFrame][URG_NORM] = settings.frame_color; if (settings.critframecolor) xctx.colors[ColFrame][URG_CRIT] = settings.critframecolor; else xctx.colors[ColFrame][URG_CRIT] = settings.frame_color; /* 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); /* calculate maximum notification count and push information to queue */ if (xctx.geometry.h == 0) { queues_displayed_limit(0); } else if (xctx.geometry.h == 1) { queues_displayed_limit(1); } else if (settings.indicate_hidden) { queues_displayed_limit(xctx.geometry.h - 1); } else { queues_displayed_limit(xctx.geometry.h); } xctx.screensaver_info = XScreenSaverAllocInfo(); init_screens(); x_win_setup(); x_cairo_setup(); x_shortcut_grab(&settings.history_ks); } static void x_set_wm(Window win) { Atom data[2]; /* set window title */ char *title = settings.title != NULL ? settings.title : "Dunst"; Atom _net_wm_title = XInternAtom(xctx.dpy, "_NET_WM_NAME", false); XStoreName(xctx.dpy, win, title); XChangeProperty(xctx.dpy, win, _net_wm_title, XInternAtom(xctx.dpy, "UTF8_STRING", false), 8, PropModeReplace, (unsigned char *)title, strlen(title)); /* set window class */ char *class = settings.class != NULL ? settings.class : "Dunst"; XClassHint classhint = { class, "Dunst" }; XSetClassHint(xctx.dpy, win, &classhint); /* set window type */ Atom net_wm_window_type = XInternAtom(xctx.dpy, "_NET_WM_WINDOW_TYPE", false); data[0] = XInternAtom(xctx.dpy, "_NET_WM_WINDOW_TYPE_NOTIFICATION", false); data[1] = XInternAtom(xctx.dpy, "_NET_WM_WINDOW_TYPE_UTILITY", false); XChangeProperty(xctx.dpy, win, net_wm_window_type, XA_ATOM, 32, PropModeReplace, (unsigned char *)data, 2L); /* set state above */ Atom net_wm_state = XInternAtom(xctx.dpy, "_NET_WM_STATE", false); data[0] = XInternAtom(xctx.dpy, "_NET_WM_STATE_ABOVE", false); XChangeProperty(xctx.dpy, win, net_wm_state, XA_ATOM, 32, PropModeReplace, (unsigned char *) data, 1L); } /* * 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 | ButtonReleaseMask | FocusChangeMask| StructureNotifyMask; screen_info *scr = get_active_screen(); 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); x_set_wm(xctx.win); 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 || queues_length_displayed() == 0) { 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); XGrabKey(xctx.dpy, ks->code, ks->mask | x_numlock_mod(), 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); XUngrabKey(xctx.dpy, ks->code, ks->mask | x_numlock_mod(), 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; while (strchr(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; } g_free(str_begin); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/src/x11/x.h�����������������������������������������������������������������������������0000664�0000000�0000000�00000003170�13223745120�0014470�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_X_H #define DUNST_X_H #include <X11/X.h> #include <X11/Xlib.h> #include <X11/extensions/scrnsaver.h> #include <glib.h> #include <stdbool.h> #include "screen.h" #define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) #define FONT_HEIGHT_BORDER 2 #define DEFFONT "Monospace-11" 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; int cur_screen; Window win; bool visible; dimension_t geometry; const char *colors[3][3]; XScreenSaverInfo *screensaver_info; dimension_t window_dim; 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); void x_free(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 tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/test/�����������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13223745120�0013626�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/test/data/������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13223745120�0014537�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/test/data/dunstrc.default���������������������������������������������������������������0000777�0000000�0000000�00000000000�13223745120�0021615�2../../dunstrc���������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/test/data/test-ini����������������������������������������������������������������������0000664�0000000�0000000�00000001201�13223745120�0016210�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#General comment [bool] booltrue = true #This is a test inline comment booltrue_capital = TRUE #This is a comment boolfalse = false boolfalse_capital = FALSE boolyes = yes boolyes_capital = YES boolno = no boolno_capital = NO boolbin0 = 0 boolbin1 = 1 boolinvalid = invalidbool [string] simple = A simple string quoted = "A quoted string" quoted_with_quotes = "A string "with quotes"" [path] expand_tilde = ~/.path/to/tilde [int] simple = 5 negative = -10 decimal = 2.71828 leading_zeroes = 007 multi_char = 1024 [double] simple = 1 decimal = 1.5 negative = -1.2 zeroes = 0.005 long = 3.141592653589793 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/test/functional-tests/������������������������������������������������������������������0000775�0000000�0000000�00000000000�13223745120�0017130�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/test/functional-tests/dunstrc.default���������������������������������������������������0000664�0000000�0000000�00000001766�13223745120�0022172�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[global] font = Monospace 8 allow_markup = yes format = "<b>%s</b>\n%b" sort = yes indicate_hidden = yes alignment = left 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.3.0/test/functional-tests/dunstrc.ignore_newline��������������������������������������������0000664�0000000�0000000�00000001767�13223745120�0023553�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[global] font = Monospace 8 allow_markup = yes format = "<b>%s</b>\n%b" sort = yes indicate_hidden = yes alignment = left 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.3.0/test/functional-tests/dunstrc.ignore_newline_no_wrap������������������������������������0000664�0000000�0000000�00000001766�13223745120�0025277�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[global] font = Monospace 8 allow_markup = yes format = "<b>%s</b>\n%b" sort = yes indicate_hidden = yes alignment = left 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.3.0/test/functional-tests/dunstrc.markup����������������������������������������������������0000664�0000000�0000000�00000001757�13223745120�0022045�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[global] font = Monospace 8 allow_markup = yes format = "%s\n%b" sort = yes indicate_hidden = yes alignment = left 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.3.0/test/functional-tests/dunstrc.nomarkup��������������������������������������������������0000664�0000000�0000000�00000001774�13223745120�0022401�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[global] font = Monospace 8 allow_markup = no format = "<b>%s</b>\n<i>%b</i>" sort = yes indicate_hidden = yes alignment = left 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.3.0/test/functional-tests/dunstrc.nowrap����������������������������������������������������0000664�0000000�0000000�00000001761�13223745120�0022047�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[global] font = Monospace-10 allow_markup = yes format = "%s\n%b" sort = yes indicate_hidden = yes alignment = left 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.3.0/test/functional-tests/dunstrc.run_script������������������������������������������������0000664�0000000�0000000�00000002067�13223745120�0022731�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[global] font = Monospace 8 allow_markup = yes format = "<b>%s</b>\n%b" sort = yes indicate_hidden = yes alignment = left 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.3.0/test/functional-tests/dunstrc.show_age��������������������������������������������������0000664�0000000�0000000�00000001765�13223745120�0022341�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[global] font = Monospace 8 allow_markup = yes format = "<b>%s</b>\n%b" sort = yes indicate_hidden = yes alignment = left 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.3.0/test/functional-tests/script_test.sh����������������������������������������������������0000775�0000000�0000000�00000000060�13223745120�0022026�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/bash notify-send "Success" "ooooh yeah" ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/test/functional-tests/test.sh�����������������������������������������������������������0000775�0000000�0000000�00000013316�13223745120�0020452�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/bash function keypress { echo "press enter to continue..." read key } function basic_notifications { ../../dunstify -a "dunst tester" "normal" "<i>italic body</i>" ../../dunstify -a "dunst tester" -u c "critical" "<b>bold body</b>" ../../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" "<b>bold</b> <i>italic</i>" ../../dunstify -a "dunst tester" "<b>broken markup</i>" keypress killall dunst ../../dunst -config dunstrc.nomarkup "200x0+10+10" & ../../dunstify -a "dunst tester" -u c "NO Markup Tests" ../../dunstify -a "dunst tester" "<b>bold</b><i>italic</i>" ../../dunstify -a "dunst tester" "<b>broken markup</i>" 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.3.0/test/greatest.h�������������������������������������������������������������������������0000664�0000000�0000000�00000152167�13223745120�0015631�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2011-2016 Scott Vokes <vokes.s@gmail.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef GREATEST_H #define GREATEST_H #ifdef __cplusplus extern "C" { #endif /* 1.2.1 */ #define GREATEST_VERSION_MAJOR 1 #define GREATEST_VERSION_MINOR 2 #define GREATEST_VERSION_PATCH 1 /* A unit testing system for C, contained in 1 file. * It doesn't use dynamic allocation or depend on anything * beyond ANSI C89. * * An up-to-date version can be found at: * https://github.com/silentbicycle/greatest/ */ /********************************************************************* * Minimal test runner template *********************************************************************/ #if 0 #include "greatest.h" TEST foo_should_foo(void) { PASS(); } static void setup_cb(void *data) { printf("setup callback for each test case\n"); } static void teardown_cb(void *data) { printf("teardown callback for each test case\n"); } SUITE(suite) { /* Optional setup/teardown callbacks which will be run before/after * every test case. If using a test suite, they will be cleared when * the suite finishes. */ SET_SETUP(setup_cb, voidp_to_callback_data); SET_TEARDOWN(teardown_cb, voidp_to_callback_data); RUN_TEST(foo_should_foo); } /* Add definitions that need to be in the test runner's main file. */ GREATEST_MAIN_DEFS(); /* Set up, run suite(s) of tests, report pass/fail/skip stats. */ int run_tests(void) { GREATEST_INIT(); /* init. greatest internals */ /* List of suites to run (if any). */ RUN_SUITE(suite); /* Tests can also be run directly, without using test suites. */ RUN_TEST(foo_should_foo); GREATEST_PRINT_REPORT(); /* display results */ return greatest_all_passed(); } /* main(), for a standalone command-line test runner. * This replaces run_tests above, and adds command line option * handling and exiting with a pass/fail status. */ int main(int argc, char **argv) { GREATEST_MAIN_BEGIN(); /* init & parse command-line args */ RUN_SUITE(suite); GREATEST_MAIN_END(); /* display results */ } #endif /*********************************************************************/ #include <stdlib.h> #include <stdio.h> #include <string.h> #include <ctype.h> /*********** * Options * ***********/ /* Default column width for non-verbose output. */ #ifndef GREATEST_DEFAULT_WIDTH #define GREATEST_DEFAULT_WIDTH 72 #endif /* FILE *, for test logging. */ #ifndef GREATEST_STDOUT #define GREATEST_STDOUT stdout #endif /* Remove GREATEST_ prefix from most commonly used symbols? */ #ifndef GREATEST_USE_ABBREVS #define GREATEST_USE_ABBREVS 1 #endif /* Set to 0 to disable all use of setjmp/longjmp. */ #ifndef GREATEST_USE_LONGJMP #define GREATEST_USE_LONGJMP 1 #endif #if GREATEST_USE_LONGJMP #include <setjmp.h> #endif /* Set to 0 to disable all use of time.h / clock(). */ #ifndef GREATEST_USE_TIME #define GREATEST_USE_TIME 1 #endif #if GREATEST_USE_TIME #include <time.h> #endif /* Floating point type, for ASSERT_IN_RANGE. */ #ifndef GREATEST_FLOAT #define GREATEST_FLOAT double #define GREATEST_FLOAT_FMT "%g" #endif /********* * Types * *********/ /* Info for the current running suite. */ typedef struct greatest_suite_info { unsigned int tests_run; unsigned int passed; unsigned int failed; unsigned int skipped; #if GREATEST_USE_TIME /* timers, pre/post running suite and individual tests */ clock_t pre_suite; clock_t post_suite; clock_t pre_test; clock_t post_test; #endif } greatest_suite_info; /* Type for a suite function. */ typedef void (greatest_suite_cb)(void); /* Types for setup/teardown callbacks. If non-NULL, these will be run * and passed the pointer to their additional data. */ typedef void (greatest_setup_cb)(void *udata); typedef void (greatest_teardown_cb)(void *udata); /* Type for an equality comparison between two pointers of the same type. * Should return non-0 if equal, otherwise 0. * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */ typedef int greatest_equal_cb(const void *exp, const void *got, void *udata); /* Type for a callback that prints a value pointed to by T. * Return value has the same meaning as printf's. * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */ typedef int greatest_printf_cb(const void *t, void *udata); /* Callbacks for an arbitrary type; needed for type-specific * comparisons via GREATEST_ASSERT_EQUAL_T[m].*/ typedef struct greatest_type_info { greatest_equal_cb *equal; greatest_printf_cb *print; } greatest_type_info; typedef struct greatest_memory_cmp_env { const unsigned char *exp; const unsigned char *got; size_t size; } greatest_memory_cmp_env; /* Callbacks for string and raw memory types. */ extern greatest_type_info greatest_type_info_string; extern greatest_type_info greatest_type_info_memory; typedef enum { GREATEST_FLAG_FIRST_FAIL = 0x01, GREATEST_FLAG_LIST_ONLY = 0x02 } greatest_flag_t; /* Struct containing all test runner state. */ typedef struct greatest_run_info { unsigned char flags; unsigned char verbosity; unsigned int tests_run; /* total test count */ /* overall pass/fail/skip counts */ unsigned int passed; unsigned int failed; unsigned int skipped; unsigned int assertions; /* currently running test suite */ greatest_suite_info suite; /* info to print about the most recent failure */ const char *fail_file; unsigned int fail_line; const char *msg; /* current setup/teardown hooks and userdata */ greatest_setup_cb *setup; void *setup_udata; greatest_teardown_cb *teardown; void *teardown_udata; /* formatting info for ".....s...F"-style output */ unsigned int col; unsigned int width; /* only run a specific suite or test */ const char *suite_filter; const char *test_filter; #if GREATEST_USE_TIME /* overall timers */ clock_t begin; clock_t end; #endif #if GREATEST_USE_LONGJMP jmp_buf jump_dest; #endif } greatest_run_info; struct greatest_report_t { /* overall pass/fail/skip counts */ unsigned int passed; unsigned int failed; unsigned int skipped; unsigned int assertions; }; /* Global var for the current testing context. * Initialized by GREATEST_MAIN_DEFS(). */ extern greatest_run_info greatest_info; /* Type for ASSERT_ENUM_EQ's ENUM_STR argument. */ typedef const char *greatest_enum_str_fun(int value); /********************** * Exported functions * **********************/ /* These are used internally by greatest. */ void greatest_do_pass(const char *name); void greatest_do_fail(const char *name); void greatest_do_skip(const char *name); int greatest_pre_test(const char *name); void greatest_post_test(const char *name, int res); void greatest_usage(const char *name); int greatest_do_assert_equal_t(const void *exp, const void *got, greatest_type_info *type_info, void *udata); /* These are part of the public greatest API. */ void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata); void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata); int greatest_all_passed(void); void greatest_set_test_filter(const char *name); void greatest_set_suite_filter(const char *name); void greatest_get_report(struct greatest_report_t *report); unsigned int greatest_get_verbosity(void); void greatest_set_verbosity(unsigned int verbosity); void greatest_set_flag(greatest_flag_t flag); /******************** * Language Support * ********************/ /* If __VA_ARGS__ (C99) is supported, allow parametric testing * without needing to manually manage the argument struct. */ #if __STDC_VERSION__ >= 19901L || _MSC_VER >= 1800 #define GREATEST_VA_ARGS #endif /********** * Macros * **********/ /* Define a suite. */ #define GREATEST_SUITE(NAME) void NAME(void); void NAME(void) /* Declare a suite, provided by another compilation unit. */ #define GREATEST_SUITE_EXTERN(NAME) void NAME(void) /* Start defining a test function. * The arguments are not included, to allow parametric testing. */ #define GREATEST_TEST static enum greatest_test_res /* PASS/FAIL/SKIP result from a test. Used internally. */ typedef enum greatest_test_res { GREATEST_TEST_RES_PASS = 0, GREATEST_TEST_RES_FAIL = -1, GREATEST_TEST_RES_SKIP = 1 } greatest_test_res; /* Run a suite. */ #define GREATEST_RUN_SUITE(S_NAME) greatest_run_suite(S_NAME, #S_NAME) /* Run a test in the current suite. */ #define GREATEST_RUN_TEST(TEST) \ do { \ if (greatest_pre_test(#TEST) == 1) { \ enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ if (res == GREATEST_TEST_RES_PASS) { \ res = TEST(); \ } \ greatest_post_test(#TEST, res); \ } else if (GREATEST_LIST_ONLY()) { \ fprintf(GREATEST_STDOUT, " %s\n", #TEST); \ } \ } while (0) /* Ignore a test, don't warn about it being unused. */ #define GREATEST_IGNORE_TEST(TEST) (void)TEST /* Run a test in the current suite with one void * argument, * which can be a pointer to a struct with multiple arguments. */ #define GREATEST_RUN_TEST1(TEST, ENV) \ do { \ if (greatest_pre_test(#TEST) == 1) { \ int res = TEST(ENV); \ greatest_post_test(#TEST, res); \ } else if (GREATEST_LIST_ONLY()) { \ fprintf(GREATEST_STDOUT, " %s\n", #TEST); \ } \ } while (0) #ifdef GREATEST_VA_ARGS #define GREATEST_RUN_TESTp(TEST, ...) \ do { \ if (greatest_pre_test(#TEST) == 1) { \ int res = TEST(__VA_ARGS__); \ greatest_post_test(#TEST, res); \ } else if (GREATEST_LIST_ONLY()) { \ fprintf(GREATEST_STDOUT, " %s\n", #TEST); \ } \ } while (0) #endif /* Check if the test runner is in verbose mode. */ #define GREATEST_IS_VERBOSE() ((greatest_info.verbosity) > 0) #define GREATEST_LIST_ONLY() \ (greatest_info.flags & GREATEST_FLAG_LIST_ONLY) #define GREATEST_FIRST_FAIL() \ (greatest_info.flags & GREATEST_FLAG_FIRST_FAIL) #define GREATEST_FAILURE_ABORT() \ (greatest_info.suite.failed > 0 && GREATEST_FIRST_FAIL()) /* Message-less forms of tests defined below. */ #define GREATEST_PASS() GREATEST_PASSm(NULL) #define GREATEST_FAIL() GREATEST_FAILm(NULL) #define GREATEST_SKIP() GREATEST_SKIPm(NULL) #define GREATEST_ASSERT(COND) \ GREATEST_ASSERTm(#COND, COND) #define GREATEST_ASSERT_OR_LONGJMP(COND) \ GREATEST_ASSERT_OR_LONGJMPm(#COND, COND) #define GREATEST_ASSERT_FALSE(COND) \ GREATEST_ASSERT_FALSEm(#COND, COND) #define GREATEST_ASSERT_EQ(EXP, GOT) \ GREATEST_ASSERT_EQm(#EXP " != " #GOT, EXP, GOT) #define GREATEST_ASSERT_EQ_FMT(EXP, GOT, FMT) \ GREATEST_ASSERT_EQ_FMTm(#EXP " != " #GOT, EXP, GOT, FMT) #define GREATEST_ASSERT_IN_RANGE(EXP, GOT, TOL) \ GREATEST_ASSERT_IN_RANGEm(#EXP " != " #GOT " +/- " #TOL, EXP, GOT, TOL) #define GREATEST_ASSERT_EQUAL_T(EXP, GOT, TYPE_INFO, UDATA) \ GREATEST_ASSERT_EQUAL_Tm(#EXP " != " #GOT, EXP, GOT, TYPE_INFO, UDATA) #define GREATEST_ASSERT_STR_EQ(EXP, GOT) \ GREATEST_ASSERT_STR_EQm(#EXP " != " #GOT, EXP, GOT) #define GREATEST_ASSERT_STRN_EQ(EXP, GOT, SIZE) \ GREATEST_ASSERT_STRN_EQm(#EXP " != " #GOT, EXP, GOT, SIZE) #define GREATEST_ASSERT_MEM_EQ(EXP, GOT, SIZE) \ GREATEST_ASSERT_MEM_EQm(#EXP " != " #GOT, EXP, GOT, SIZE) #define GREATEST_ASSERT_ENUM_EQ(EXP, GOT, ENUM_STR) \ GREATEST_ASSERT_ENUM_EQm(#EXP " != " #GOT, EXP, GOT, ENUM_STR) /* The following forms take an additional message argument first, * to be displayed by the test runner. */ /* Fail if a condition is not true, with message. */ #define GREATEST_ASSERTm(MSG, COND) \ do { \ greatest_info.assertions++; \ if (!(COND)) { GREATEST_FAILm(MSG); } \ } while (0) /* Fail if a condition is not true, longjmping out of test. */ #define GREATEST_ASSERT_OR_LONGJMPm(MSG, COND) \ do { \ greatest_info.assertions++; \ if (!(COND)) { GREATEST_FAIL_WITH_LONGJMPm(MSG); } \ } while (0) /* Fail if a condition is not false, with message. */ #define GREATEST_ASSERT_FALSEm(MSG, COND) \ do { \ greatest_info.assertions++; \ if ((COND)) { GREATEST_FAILm(MSG); } \ } while (0) /* Fail if EXP != GOT (equality comparison by ==). */ #define GREATEST_ASSERT_EQm(MSG, EXP, GOT) \ do { \ greatest_info.assertions++; \ if ((EXP) != (GOT)) { GREATEST_FAILm(MSG); } \ } while (0) /* Fail if EXP != GOT (equality comparison by ==). * Warning: EXP and GOT will be evaluated more than once on failure. */ #define GREATEST_ASSERT_EQ_FMTm(MSG, EXP, GOT, FMT) \ do { \ const char *greatest_FMT = ( FMT ); \ greatest_info.assertions++; \ if ((EXP) != (GOT)) { \ fprintf(GREATEST_STDOUT, "\nExpected: "); \ fprintf(GREATEST_STDOUT, greatest_FMT, EXP); \ fprintf(GREATEST_STDOUT, "\n Got: "); \ fprintf(GREATEST_STDOUT, greatest_FMT, GOT); \ fprintf(GREATEST_STDOUT, "\n"); \ GREATEST_FAILm(MSG); \ } \ } while (0) /* Fail if EXP is not equal to GOT, printing enum IDs. */ #define GREATEST_ASSERT_ENUM_EQm(MSG, EXP, GOT, ENUM_STR) \ do { \ int greatest_EXP = (int)(EXP); \ int greatest_GOT = (int)(GOT); \ greatest_enum_str_fun *greatest_ENUM_STR = ENUM_STR; \ if (greatest_EXP != greatest_GOT) { \ fprintf(GREATEST_STDOUT, "\nExpected: %s", \ greatest_ENUM_STR(greatest_EXP)); \ fprintf(GREATEST_STDOUT, "\n Got: %s\n", \ greatest_ENUM_STR(greatest_GOT)); \ GREATEST_FAILm(MSG); \ } \ } while (0) \ /* Fail if GOT not in range of EXP +|- TOL. */ #define GREATEST_ASSERT_IN_RANGEm(MSG, EXP, GOT, TOL) \ do { \ GREATEST_FLOAT greatest_EXP = (EXP); \ GREATEST_FLOAT greatest_GOT = (GOT); \ GREATEST_FLOAT greatest_TOL = (TOL); \ greatest_info.assertions++; \ if ((greatest_EXP > greatest_GOT && \ greatest_EXP - greatest_GOT > greatest_TOL) || \ (greatest_EXP < greatest_GOT && \ greatest_GOT - greatest_EXP > greatest_TOL)) { \ fprintf(GREATEST_STDOUT, \ "\nExpected: " GREATEST_FLOAT_FMT \ " +/- " GREATEST_FLOAT_FMT \ "\n Got: " GREATEST_FLOAT_FMT \ "\n", \ greatest_EXP, greatest_TOL, greatest_GOT); \ GREATEST_FAILm(MSG); \ } \ } while (0) /* Fail if EXP is not equal to GOT, according to strcmp. */ #define GREATEST_ASSERT_STR_EQm(MSG, EXP, GOT) \ do { \ GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \ &greatest_type_info_string, NULL); \ } while (0) \ /* Fail if EXP is not equal to GOT, according to strcmp. */ #define GREATEST_ASSERT_STRN_EQm(MSG, EXP, GOT, SIZE) \ do { \ size_t size = SIZE; \ GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \ &greatest_type_info_string, &size); \ } while (0) \ /* Fail if EXP is not equal to GOT, according to memcmp. */ #define GREATEST_ASSERT_MEM_EQm(MSG, EXP, GOT, SIZE) \ do { \ greatest_memory_cmp_env env; \ env.exp = (const unsigned char *)EXP; \ env.got = (const unsigned char *)GOT; \ env.size = SIZE; \ GREATEST_ASSERT_EQUAL_Tm(MSG, env.exp, env.got, \ &greatest_type_info_memory, &env); \ } while (0) \ /* Fail if EXP is not equal to GOT, according to a comparison * callback in TYPE_INFO. If they are not equal, optionally use a * print callback in TYPE_INFO to print them. */ #define GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, TYPE_INFO, UDATA) \ do { \ greatest_type_info *type_info = (TYPE_INFO); \ greatest_info.assertions++; \ if (!greatest_do_assert_equal_t(EXP, GOT, \ type_info, UDATA)) { \ if (type_info == NULL || type_info->equal == NULL) { \ GREATEST_FAILm("type_info->equal callback missing!"); \ } else { \ GREATEST_FAILm(MSG); \ } \ } \ } while (0) \ /* Pass. */ #define GREATEST_PASSm(MSG) \ do { \ greatest_info.msg = MSG; \ return GREATEST_TEST_RES_PASS; \ } while (0) /* Fail. */ #define GREATEST_FAILm(MSG) \ do { \ greatest_info.fail_file = __FILE__; \ greatest_info.fail_line = __LINE__; \ greatest_info.msg = MSG; \ return GREATEST_TEST_RES_FAIL; \ } while (0) /* Optional GREATEST_FAILm variant that longjmps. */ #if GREATEST_USE_LONGJMP #define GREATEST_FAIL_WITH_LONGJMP() GREATEST_FAIL_WITH_LONGJMPm(NULL) #define GREATEST_FAIL_WITH_LONGJMPm(MSG) \ do { \ greatest_info.fail_file = __FILE__; \ greatest_info.fail_line = __LINE__; \ greatest_info.msg = MSG; \ longjmp(greatest_info.jump_dest, GREATEST_TEST_RES_FAIL); \ } while (0) #endif /* Skip the current test. */ #define GREATEST_SKIPm(MSG) \ do { \ greatest_info.msg = MSG; \ return GREATEST_TEST_RES_SKIP; \ } while (0) /* Check the result of a subfunction using ASSERT, etc. */ #define GREATEST_CHECK_CALL(RES) \ do { \ enum greatest_test_res greatest_RES = RES; \ if (greatest_RES != GREATEST_TEST_RES_PASS) { \ return greatest_RES; \ } \ } while (0) \ #if GREATEST_USE_TIME #define GREATEST_SET_TIME(NAME) \ NAME = clock(); \ if (NAME == (clock_t) -1) { \ fprintf(GREATEST_STDOUT, \ "clock error: %s\n", #NAME); \ exit(EXIT_FAILURE); \ } #define GREATEST_CLOCK_DIFF(C1, C2) \ fprintf(GREATEST_STDOUT, " (%lu ticks, %.3f sec)", \ (long unsigned int) (C2) - (long unsigned int)(C1), \ (double)((C2) - (C1)) / (1.0 * (double)CLOCKS_PER_SEC)) #else #define GREATEST_SET_TIME(UNUSED) #define GREATEST_CLOCK_DIFF(UNUSED1, UNUSED2) #endif #if GREATEST_USE_LONGJMP #define GREATEST_SAVE_CONTEXT() \ /* setjmp returns 0 (GREATEST_TEST_RES_PASS) on first call */ \ /* so the test runs, then RES_FAIL from FAIL_WITH_LONGJMP. */ \ ((enum greatest_test_res)(setjmp(greatest_info.jump_dest))) #else #define GREATEST_SAVE_CONTEXT() \ /*a no-op, since setjmp/longjmp aren't being used */ \ GREATEST_TEST_RES_PASS #endif /* Include several function definitions in the main test file. */ #define GREATEST_MAIN_DEFS() \ \ /* Is FILTER a subset of NAME? */ \ static int greatest_name_match(const char *name, \ const char *filter) { \ size_t offset = 0; \ size_t filter_len = strlen(filter); \ while (name[offset] != '\0') { \ if (name[offset] == filter[0]) { \ if (0 == strncmp(&name[offset], filter, filter_len)) { \ return 1; \ } \ } \ offset++; \ } \ \ return 0; \ } \ \ int greatest_pre_test(const char *name) { \ if (!GREATEST_LIST_ONLY() \ && (!GREATEST_FIRST_FAIL() || greatest_info.suite.failed == 0) \ && (greatest_info.test_filter == NULL || \ greatest_name_match(name, greatest_info.test_filter))) { \ GREATEST_SET_TIME(greatest_info.suite.pre_test); \ if (greatest_info.setup) { \ greatest_info.setup(greatest_info.setup_udata); \ } \ return 1; /* test should be run */ \ } else { \ return 0; /* skipped */ \ } \ } \ \ void greatest_post_test(const char *name, int res) { \ GREATEST_SET_TIME(greatest_info.suite.post_test); \ if (greatest_info.teardown) { \ void *udata = greatest_info.teardown_udata; \ greatest_info.teardown(udata); \ } \ \ if (res <= GREATEST_TEST_RES_FAIL) { \ greatest_do_fail(name); \ } else if (res >= GREATEST_TEST_RES_SKIP) { \ greatest_do_skip(name); \ } else if (res == GREATEST_TEST_RES_PASS) { \ greatest_do_pass(name); \ } \ greatest_info.suite.tests_run++; \ greatest_info.col++; \ if (GREATEST_IS_VERBOSE()) { \ GREATEST_CLOCK_DIFF(greatest_info.suite.pre_test, \ greatest_info.suite.post_test); \ fprintf(GREATEST_STDOUT, "\n"); \ } else if (greatest_info.col % greatest_info.width == 0) { \ fprintf(GREATEST_STDOUT, "\n"); \ greatest_info.col = 0; \ } \ if (GREATEST_STDOUT == stdout) fflush(stdout); \ } \ \ static void report_suite(void) { \ if (greatest_info.suite.tests_run > 0) { \ fprintf(GREATEST_STDOUT, \ "\n%u test%s - %u passed, %u failed, %u skipped", \ greatest_info.suite.tests_run, \ greatest_info.suite.tests_run == 1 ? "" : "s", \ greatest_info.suite.passed, \ greatest_info.suite.failed, \ greatest_info.suite.skipped); \ GREATEST_CLOCK_DIFF(greatest_info.suite.pre_suite, \ greatest_info.suite.post_suite); \ fprintf(GREATEST_STDOUT, "\n"); \ } \ } \ \ static void update_counts_and_reset_suite(void) { \ greatest_info.setup = NULL; \ greatest_info.setup_udata = NULL; \ greatest_info.teardown = NULL; \ greatest_info.teardown_udata = NULL; \ greatest_info.passed += greatest_info.suite.passed; \ greatest_info.failed += greatest_info.suite.failed; \ greatest_info.skipped += greatest_info.suite.skipped; \ greatest_info.tests_run += greatest_info.suite.tests_run; \ memset(&greatest_info.suite, 0, sizeof(greatest_info.suite)); \ greatest_info.col = 0; \ } \ \ static void greatest_run_suite(greatest_suite_cb *suite_cb, \ const char *suite_name) { \ if (greatest_info.suite_filter && \ !greatest_name_match(suite_name, greatest_info.suite_filter)) { \ return; \ } \ update_counts_and_reset_suite(); \ if (GREATEST_FIRST_FAIL() && greatest_info.failed > 0) { return; } \ fprintf(GREATEST_STDOUT, "\n* Suite %s:\n", suite_name); \ GREATEST_SET_TIME(greatest_info.suite.pre_suite); \ suite_cb(); \ GREATEST_SET_TIME(greatest_info.suite.post_suite); \ report_suite(); \ } \ \ void greatest_do_pass(const char *name) { \ if (GREATEST_IS_VERBOSE()) { \ fprintf(GREATEST_STDOUT, "PASS %s: %s", \ name, greatest_info.msg ? greatest_info.msg : ""); \ } else { \ fprintf(GREATEST_STDOUT, "."); \ } \ greatest_info.suite.passed++; \ } \ \ void greatest_do_fail(const char *name) { \ if (GREATEST_IS_VERBOSE()) { \ fprintf(GREATEST_STDOUT, \ "FAIL %s: %s (%s:%u)", \ name, greatest_info.msg ? greatest_info.msg : "", \ greatest_info.fail_file, greatest_info.fail_line); \ } else { \ fprintf(GREATEST_STDOUT, "F"); \ greatest_info.col++; \ /* add linebreak if in line of '.'s */ \ if (greatest_info.col != 0) { \ fprintf(GREATEST_STDOUT, "\n"); \ greatest_info.col = 0; \ } \ fprintf(GREATEST_STDOUT, "FAIL %s: %s (%s:%u)\n", \ name, \ greatest_info.msg ? greatest_info.msg : "", \ greatest_info.fail_file, greatest_info.fail_line); \ } \ greatest_info.suite.failed++; \ } \ \ void greatest_do_skip(const char *name) { \ if (GREATEST_IS_VERBOSE()) { \ fprintf(GREATEST_STDOUT, "SKIP %s: %s", \ name, \ greatest_info.msg ? \ greatest_info.msg : "" ); \ } else { \ fprintf(GREATEST_STDOUT, "s"); \ } \ greatest_info.suite.skipped++; \ } \ \ int greatest_do_assert_equal_t(const void *exp, const void *got, \ greatest_type_info *type_info, void *udata) { \ int eq = 0; \ if (type_info == NULL || type_info->equal == NULL) { \ return 0; \ } \ eq = type_info->equal(exp, got, udata); \ if (!eq) { \ if (type_info->print != NULL) { \ fprintf(GREATEST_STDOUT, "\nExpected: "); \ (void)type_info->print(exp, udata); \ fprintf(GREATEST_STDOUT, "\n Got: "); \ (void)type_info->print(got, udata); \ fprintf(GREATEST_STDOUT, "\n"); \ } else { \ fprintf(GREATEST_STDOUT, \ "GREATEST_ASSERT_EQUAL_T failure at %s:%u\n", \ greatest_info.fail_file, \ greatest_info.fail_line); \ } \ } \ return eq; \ } \ \ void greatest_usage(const char *name) { \ fprintf(GREATEST_STDOUT, \ "Usage: %s [-hlfv] [-s SUITE] [-t TEST]\n" \ " -h, --help print this Help\n" \ " -l List suites and their tests, then exit\n" \ " -f Stop runner after first failure\n" \ " -v Verbose output\n" \ " -s SUITE only run suites containing string SUITE\n" \ " -t TEST only run tests containing string TEST\n", \ name); \ } \ \ static void greatest_parse_args(int argc, char **argv) { \ int i = 0; \ for (i = 1; i < argc; i++) { \ if (0 == strncmp("-t", argv[i], 2)) { \ if (argc <= i + 1) { \ greatest_usage(argv[0]); \ exit(EXIT_FAILURE); \ } \ greatest_info.test_filter = argv[i+1]; \ i++; \ } else if (0 == strncmp("-s", argv[i], 2)) { \ if (argc <= i + 1) { \ greatest_usage(argv[0]); \ exit(EXIT_FAILURE); \ } \ greatest_info.suite_filter = argv[i+1]; \ i++; \ } else if (0 == strncmp("-f", argv[i], 2)) { \ greatest_info.flags |= GREATEST_FLAG_FIRST_FAIL; \ } else if (0 == strncmp("-v", argv[i], 2)) { \ greatest_info.verbosity++; \ } else if (0 == strncmp("-l", argv[i], 2)) { \ greatest_info.flags |= GREATEST_FLAG_LIST_ONLY; \ } else if (0 == strncmp("-h", argv[i], 2) || \ 0 == strncmp("--help", argv[i], 6)) { \ greatest_usage(argv[0]); \ exit(EXIT_SUCCESS); \ } else if (0 == strncmp("--", argv[i], 2)) { \ break; \ } else { \ fprintf(GREATEST_STDOUT, \ "Unknown argument '%s'\n", argv[i]); \ greatest_usage(argv[0]); \ exit(EXIT_FAILURE); \ } \ } \ } \ \ int greatest_all_passed(void) { return (greatest_info.failed == 0); } \ \ void greatest_set_test_filter(const char *name) { \ greatest_info.test_filter = name; \ } \ \ void greatest_set_suite_filter(const char *name) { \ greatest_info.suite_filter = name; \ } \ \ void greatest_get_report(struct greatest_report_t *report) { \ if (report) { \ report->passed = greatest_info.passed; \ report->failed = greatest_info.failed; \ report->skipped = greatest_info.skipped; \ report->assertions = greatest_info.assertions; \ } \ } \ \ unsigned int greatest_get_verbosity(void) { \ return greatest_info.verbosity; \ } \ \ void greatest_set_verbosity(unsigned int verbosity) { \ greatest_info.verbosity = (unsigned char)verbosity; \ } \ \ void greatest_set_flag(greatest_flag_t flag) { \ greatest_info.flags |= flag; \ } \ \ void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata) { \ greatest_info.setup = cb; \ greatest_info.setup_udata = udata; \ } \ \ void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, \ void *udata) { \ greatest_info.teardown = cb; \ greatest_info.teardown_udata = udata; \ } \ \ static int greatest_string_equal_cb(const void *exp, const void *got, \ void *udata) { \ size_t *size = (size_t *)udata; \ return (size != NULL \ ? (0 == strncmp((const char *)exp, (const char *)got, *size)) \ : (0 == strcmp((const char *)exp, (const char *)got))); \ } \ \ static int greatest_string_printf_cb(const void *t, void *udata) { \ (void)udata; /* note: does not check \0 termination. */ \ return fprintf(GREATEST_STDOUT, "%s", (const char *)t); \ } \ \ greatest_type_info greatest_type_info_string = { \ greatest_string_equal_cb, \ greatest_string_printf_cb, \ }; \ \ static int greatest_memory_equal_cb(const void *exp, const void *got, \ void *udata) { \ greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \ return (0 == memcmp(exp, got, env->size)); \ } \ \ static int greatest_memory_printf_cb(const void *t, void *udata) { \ greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \ unsigned char *buf = (unsigned char *)t, diff_mark = ' '; \ FILE *out = GREATEST_STDOUT; \ size_t i, line_i, line_len = 0; \ int len = 0; /* format hexdump with differences highlighted */ \ for (i = 0; i < env->size; i+= line_len) { \ diff_mark = ' '; \ line_len = env->size - i; \ if (line_len > 16) { line_len = 16; } \ for (line_i = i; line_i < i + line_len; line_i++) { \ if (env->exp[line_i] != env->got[line_i]) diff_mark = 'X'; \ } \ len += fprintf(out, "\n%04x %c ", (unsigned int)i, diff_mark); \ for (line_i = i; line_i < i + line_len; line_i++) { \ int m = env->exp[line_i] == env->got[line_i]; /* match? */ \ len += fprintf(out, "%02x%c", buf[line_i], m ? ' ' : '<'); \ } \ for (line_i = 0; line_i < 16 - line_len; line_i++) { \ len += fprintf(out, " "); \ } \ fprintf(out, " "); \ for (line_i = i; line_i < i + line_len; line_i++) { \ unsigned char c = buf[line_i]; \ len += fprintf(out, "%c", isprint(c) ? c : '.'); \ } \ } \ len += fprintf(out, "\n"); \ return len; \ } \ \ greatest_type_info greatest_type_info_memory = { \ greatest_memory_equal_cb, \ greatest_memory_printf_cb, \ }; \ \ greatest_run_info greatest_info /* Init internals. */ #define GREATEST_INIT() \ do { \ /* Suppress unused function warning if features aren't used */ \ (void)greatest_run_suite; \ (void)greatest_parse_args; \ \ memset(&greatest_info, 0, sizeof(greatest_info)); \ greatest_info.width = GREATEST_DEFAULT_WIDTH; \ GREATEST_SET_TIME(greatest_info.begin); \ } while (0) \ /* Handle command-line arguments, etc. */ #define GREATEST_MAIN_BEGIN() \ do { \ GREATEST_INIT(); \ greatest_parse_args(argc, argv); \ } while (0) /* Report passes, failures, skipped tests, the number of * assertions, and the overall run time. */ #define GREATEST_PRINT_REPORT() \ do { \ if (!GREATEST_LIST_ONLY()) { \ update_counts_and_reset_suite(); \ GREATEST_SET_TIME(greatest_info.end); \ fprintf(GREATEST_STDOUT, \ "\nTotal: %u test%s", \ greatest_info.tests_run, \ greatest_info.tests_run == 1 ? "" : "s"); \ GREATEST_CLOCK_DIFF(greatest_info.begin, \ greatest_info.end); \ fprintf(GREATEST_STDOUT, ", %u assertion%s\n", \ greatest_info.assertions, \ greatest_info.assertions == 1 ? "" : "s"); \ fprintf(GREATEST_STDOUT, \ "Pass: %u, fail: %u, skip: %u.\n", \ greatest_info.passed, \ greatest_info.failed, greatest_info.skipped); \ } \ } while (0) /* Report results, exit with exit status based on results. */ #define GREATEST_MAIN_END() \ do { \ GREATEST_PRINT_REPORT(); \ return (greatest_all_passed() ? EXIT_SUCCESS : EXIT_FAILURE); \ } while (0) /* Make abbreviations without the GREATEST_ prefix for the * most commonly used symbols. */ #if GREATEST_USE_ABBREVS #define TEST GREATEST_TEST #define SUITE GREATEST_SUITE #define SUITE_EXTERN GREATEST_SUITE_EXTERN #define RUN_TEST GREATEST_RUN_TEST #define RUN_TEST1 GREATEST_RUN_TEST1 #define RUN_SUITE GREATEST_RUN_SUITE #define IGNORE_TEST GREATEST_IGNORE_TEST #define ASSERT GREATEST_ASSERT #define ASSERTm GREATEST_ASSERTm #define ASSERT_FALSE GREATEST_ASSERT_FALSE #define ASSERT_EQ GREATEST_ASSERT_EQ #define ASSERT_EQ_FMT GREATEST_ASSERT_EQ_FMT #define ASSERT_IN_RANGE GREATEST_ASSERT_IN_RANGE #define ASSERT_EQUAL_T GREATEST_ASSERT_EQUAL_T #define ASSERT_STR_EQ GREATEST_ASSERT_STR_EQ #define ASSERT_STRN_EQ GREATEST_ASSERT_STRN_EQ #define ASSERT_MEM_EQ GREATEST_ASSERT_MEM_EQ #define ASSERT_ENUM_EQ GREATEST_ASSERT_ENUM_EQ #define ASSERT_FALSEm GREATEST_ASSERT_FALSEm #define ASSERT_EQm GREATEST_ASSERT_EQm #define ASSERT_EQ_FMTm GREATEST_ASSERT_EQ_FMTm #define ASSERT_IN_RANGEm GREATEST_ASSERT_IN_RANGEm #define ASSERT_EQUAL_Tm GREATEST_ASSERT_EQUAL_Tm #define ASSERT_STR_EQm GREATEST_ASSERT_STR_EQm #define ASSERT_STRN_EQm GREATEST_ASSERT_STRN_EQm #define ASSERT_MEM_EQm GREATEST_ASSERT_MEM_EQm #define ASSERT_ENUM_EQm GREATEST_ASSERT_ENUM_EQm #define PASS GREATEST_PASS #define FAIL GREATEST_FAIL #define SKIP GREATEST_SKIP #define PASSm GREATEST_PASSm #define FAILm GREATEST_FAILm #define SKIPm GREATEST_SKIPm #define SET_SETUP GREATEST_SET_SETUP_CB #define SET_TEARDOWN GREATEST_SET_TEARDOWN_CB #define CHECK_CALL GREATEST_CHECK_CALL #ifdef GREATEST_VA_ARGS #define RUN_TESTp GREATEST_RUN_TESTp #endif #if GREATEST_USE_LONGJMP #define ASSERT_OR_LONGJMP GREATEST_ASSERT_OR_LONGJMP #define ASSERT_OR_LONGJMPm GREATEST_ASSERT_OR_LONGJMPm #define FAIL_WITH_LONGJMP GREATEST_FAIL_WITH_LONGJMP #define FAIL_WITH_LONGJMPm GREATEST_FAIL_WITH_LONGJMPm #endif #endif /* USE_ABBREVS */ #ifdef __cplusplus } #endif #endif ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/test/markup.c���������������������������������������������������������������������������0000664�0000000�0000000�00000014443�13223745120�0015277�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "greatest.h" #include <stdbool.h> #include <glib.h> #include "src/markup.h" TEST test_markup_strip(void) { char *ptr; ASSERT_STR_EQ(""", (ptr=markup_strip(g_strdup("&quot;")))); g_free(ptr); ASSERT_STR_EQ("'", (ptr=markup_strip(g_strdup("&apos;")))); g_free(ptr); ASSERT_STR_EQ("<", (ptr=markup_strip(g_strdup("&lt;")))); g_free(ptr); ASSERT_STR_EQ(">", (ptr=markup_strip(g_strdup("&gt;")))); g_free(ptr); ASSERT_STR_EQ("&", (ptr=markup_strip(g_strdup("&amp;")))); g_free(ptr); ASSERT_STR_EQ(">A ", (ptr=markup_strip(g_strdup(">A <img> <string")))); g_free(ptr); PASS(); } TEST test_markup_transform(void) { char *ptr; settings.ignore_newline = false; ASSERT_STR_EQ("<i>foo</i><br>bar\nbaz", (ptr=markup_transform(g_strdup("<i>foo</i><br>bar\nbaz"), MARKUP_NO))); g_free(ptr); ASSERT_STR_EQ("foo\nbar\nbaz", (ptr=markup_transform(g_strdup("<i>foo</i><br>bar\nbaz"), MARKUP_STRIP))); g_free(ptr); ASSERT_STR_EQ("<i>foo</i>\nbar\nbaz", (ptr=markup_transform(g_strdup("<i>foo</i><br>bar\nbaz"), MARKUP_FULL))); g_free(ptr); settings.ignore_newline = true; ASSERT_STR_EQ("<i>foo</i><br>bar baz", (ptr=markup_transform(g_strdup("<i>foo</i><br>bar\nbaz"), MARKUP_NO))); g_free(ptr); ASSERT_STR_EQ("foo bar baz", (ptr=markup_transform(g_strdup("<i>foo</i><br>bar\nbaz"), MARKUP_STRIP))); g_free(ptr); ASSERT_STR_EQ("<i>foo</i> bar baz", (ptr=markup_transform(g_strdup("<i>foo</i><br>bar\nbaz"), MARKUP_FULL))); g_free(ptr); // Test replacement of img and a tags, not renderable by pango ASSERT_STR_EQ("foo bar bar baz", (ptr=markup_transform(g_strdup("<img alt=\"foo bar\"><br>bar\nbaz"), MARKUP_FULL))); g_free(ptr); ASSERT_STR_EQ("test ", (ptr=markup_transform(g_strdup("test <img alt=\"foo bar\""), MARKUP_FULL))); g_free(ptr); ASSERT_STR_EQ("test [image] image", (ptr=markup_transform(g_strdup("test <img src=\"nothing.jpg\"> image"), MARKUP_FULL))); g_free(ptr); ASSERT_STR_EQ("bar baz", (ptr=markup_transform(g_strdup("<a href=\"asdf\">bar</a> baz"), MARKUP_FULL))); g_free(ptr); PASS(); } TEST helper_markup_strip_a (const char *in, const char *exp, const char *urls) { // out_urls is a return parameter and the content should be ignored char *out_urls = (char *)0x04; //Chosen by a fair dice roll char *out = g_strdup(in); char *msg = g_strconcat("url: ", in, NULL); markup_strip_a(&out, &out_urls); ASSERT_STR_EQm(msg, exp, out); if (urls) { ASSERT_STR_EQm(msg, urls, out_urls); } else { ASSERT_EQm(msg, urls, out_urls); } g_free(out_urls); g_free(out); g_free(msg); PASS(); } TEST test_markup_strip_a(void) { RUN_TESTp(helper_markup_strip_a, "<a href=\"https://url.com\">valid</a> link", "valid link", "[valid] https://url.com"); RUN_TESTp(helper_markup_strip_a, "<a href=\"\">valid</a> link", "valid link", "[valid] "); RUN_TESTp(helper_markup_strip_a, "<a>valid</a> link", "valid link", NULL); RUN_TESTp(helper_markup_strip_a, "<a href=\"https://url.com\">valid link", "valid link", "[valid link] https://url.com"); RUN_TESTp(helper_markup_strip_a, "<a href=\"https://url.com\" invalid</a> link", " link", NULL); RUN_TESTp(helper_markup_strip_a, "<a invalid</a> link", " link", NULL); PASS(); } TEST helper_markup_strip_img (const char *in, const char *exp, const char *urls) { // out_urls is a return parameter and the content should be ignored char *out_urls = (char *)0x04; //Chosen by a fair dice roll char *out = g_strdup(in); char *msg = g_strconcat("url: ", in, NULL); markup_strip_img(&out, &out_urls); ASSERT_STR_EQm(msg, exp, out); if (urls) { ASSERT_STR_EQm(msg, urls, out_urls); } else { ASSERT_EQm(msg, urls, out_urls); } g_free(out_urls); g_free(out); g_free(msg); PASS(); } TEST test_markup_strip_img(void) { RUN_TESTp(helper_markup_strip_img, "v <img> img", "v [image] img", NULL); RUN_TESTp(helper_markup_strip_img, "v <img alt=\"valid\" alt=\"invalid\"> img", "v valid img", NULL); RUN_TESTp(helper_markup_strip_img, "v <img src=\"url.com\"> img", "v [image] img", "[image] url.com"); RUN_TESTp(helper_markup_strip_img, "v <img alt=\"valid\" src=\"url.com\"> img", "v valid img", "[valid] url.com"); RUN_TESTp(helper_markup_strip_img, "v <img src=\"url.com\" alt=\"valid\"> img", "v valid img", "[valid] url.com"); RUN_TESTp(helper_markup_strip_img, "v <img src=\"url.com\" alt=\"valid\" alt=\"i\"> img", "v valid img", "[valid] url.com"); RUN_TESTp(helper_markup_strip_img, "i <img alt=\"invalid src=\"https://url.com\"> img", "i [image] img", "[image] https://url.com"); RUN_TESTp(helper_markup_strip_img, "i <img alt=\"broken\" src=\"https://url.com > img", "i broken img", NULL); RUN_TESTp(helper_markup_strip_img, "i <img alt=\"invalid src=\"https://url.com > img", "i [image] img", NULL); RUN_TESTp(helper_markup_strip_img, "i <img src=\"url.com alt=\"broken\"> img", "i broken img", NULL); RUN_TESTp(helper_markup_strip_img, "i <img src=\"url.com\" alt=\"invalid > img", "i [image] img", "[image] url.com"); RUN_TESTp(helper_markup_strip_img, "i <img src=\"url.com alt=\"invalid > img", "i [image] img", NULL); RUN_TESTp(helper_markup_strip_img, "i <img src=\"url.com\" alt=\"invalid\" img", "i ", NULL); PASS(); } SUITE(suite_markup) { RUN_TEST(test_markup_strip); RUN_TEST(test_markup_strip_a); RUN_TEST(test_markup_strip_img); RUN_TEST(test_markup_transform); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/test/notification.c���������������������������������������������������������������������0000664�0000000�0000000�00000007620�13223745120�0016465�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "greatest.h" #include "src/notification.h" #include "src/option_parser.h" #include "src/settings.h" #include <glib.h> TEST test_notification_is_duplicate_field(char **field, notification *a, notification *b) { ASSERT(notification_is_duplicate(a, b)); char *tmp = *field; (*field) = "Something different"; ASSERT_FALSE(notification_is_duplicate(a, b)); (*field) = tmp; PASS(); } TEST test_notification_is_duplicate(void *notifications) { notification **n = (notification**)notifications; notification *a = n[0]; notification *b = n[1]; ASSERT(notification_is_duplicate(a, b)); CHECK_CALL(test_notification_is_duplicate_field(&(b->appname), a, b)); CHECK_CALL(test_notification_is_duplicate_field(&(b->summary), a, b)); CHECK_CALL(test_notification_is_duplicate_field(&(b->body), a, b)); ASSERT(notification_is_duplicate(a, b)); char *tmp = b->icon; enum icon_position_t icon_setting_tmp = settings.icon_position; b->icon = "Test1"; settings.icon_position = icons_off; ASSERT(notification_is_duplicate(a, b)); //Setting pointer to a random value since we are checking for null b->raw_icon = (RawImage*)0xff; ASSERT(notification_is_duplicate(a, b)); b->raw_icon = NULL; settings.icon_position = icons_left; ASSERT_FALSE(notification_is_duplicate(a, b)); b->raw_icon = (RawImage*)0xff; ASSERT_FALSE(notification_is_duplicate(a, b)); b->raw_icon = NULL; settings.icon_position = icons_right; ASSERT_FALSE(notification_is_duplicate(a, b)); b->raw_icon = (RawImage*)0xff; ASSERT_FALSE(notification_is_duplicate(a, b)); b->raw_icon = NULL; b->icon = tmp; settings.icon_position = icon_setting_tmp; ASSERT(notification_is_duplicate(a, b)); b->urgency = URG_LOW; ASSERT_FALSE(notification_is_duplicate(a, b)); b->urgency = URG_NORM; ASSERT(notification_is_duplicate(a, b)); b->urgency = URG_CRIT; ASSERT_FALSE(notification_is_duplicate(a, b)); PASS(); } TEST test_notification_replace_single_field(void) { char *str = g_malloc(128 * sizeof(char)); char *substr = NULL; strcpy(str, "Markup %a preserved"); substr = strchr(str, '%'); notification_replace_single_field(&str, &substr, "and & <i>is</i>", MARKUP_FULL); ASSERT_STR_EQ("Markup and & <i>is</i> preserved", str); ASSERT_EQ(26, substr - str); strcpy(str, "Markup %a escaped"); substr = strchr(str, '%'); notification_replace_single_field(&str, &substr, "and & <i>is</i>", MARKUP_NO); ASSERT_STR_EQ("Markup and & <i>is</i> escaped", str); ASSERT_EQ(38, substr - str); strcpy(str, "Markup %a"); substr = strchr(str, '%'); notification_replace_single_field(&str, &substr, "<i>is removed</i> and & escaped", MARKUP_STRIP); ASSERT_STR_EQ("Markup is removed and & escaped", str); ASSERT_EQ(35, substr - str); g_free(str); PASS(); } SUITE(suite_notification) { cmdline_load(0, NULL); load_settings("data/dunstrc.default"); notification *a = notification_create(); a->appname = "Test"; a->summary = "Summary"; a->body = "Body"; a->icon = "Icon"; a->urgency = URG_NORM; notification *b = notification_create(); memcpy(b, a, sizeof(*b)); //2 equal notifications to be passed for duplicate checking, notification *n[2] = {a, b}; RUN_TEST1(test_notification_is_duplicate, (void*) n); g_free(a); g_free(b); RUN_TEST(test_notification_replace_single_field); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ����������������������������������������������������������������������������������������������������������������dunst-1.3.0/test/option_parser.c��������������������������������������������������������������������0000664�0000000�0000000�00000030053�13223745120�0016657�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "greatest.h" #include <stdbool.h> #include <glib.h> #include "src/option_parser.h" TEST test_next_section(void) { const char *section = NULL; ASSERT_STR_EQ("bool", (section = next_section(section))); ASSERT_STR_EQ("string", (section = next_section(section))); ASSERT_STR_EQ("path", (section = next_section(section))); ASSERT_STR_EQ("int", (section = next_section(section))); ASSERT_STR_EQ("double", (section = next_section(section))); PASS(); } TEST test_ini_get_bool(void) { char *bool_section = "bool"; ASSERT(ini_get_bool(bool_section, "booltrue", false)); ASSERT(ini_get_bool(bool_section, "booltrue_capital", false)); ASSERT_FALSE(ini_get_bool(bool_section, "boolfalse", true)); ASSERT_FALSE(ini_get_bool(bool_section, "boolfalse_capital", true)); ASSERT(ini_get_bool(bool_section, "boolyes", false)); ASSERT(ini_get_bool(bool_section, "boolyes_capital", false)); ASSERT_FALSE(ini_get_bool(bool_section, "boolno", true)); ASSERT_FALSE(ini_get_bool(bool_section, "boolno_capital", true)); ASSERT(ini_get_bool(bool_section, "boolbin1", false)); ASSERT_FALSE(ini_get_bool(bool_section, "boolbin0", true)); ASSERT(ini_get_bool(bool_section, "boolinvalid", true)); ASSERT_FALSE(ini_get_bool(bool_section, "boolinvalid", false)); ASSERT(ini_get_bool(bool_section, "nonexistent", true)); ASSERT_FALSE(ini_get_bool(bool_section, "nonexistent", false)); PASS(); } TEST test_ini_get_string(void) { char *string_section = "string"; char *ptr; ASSERT_STR_EQ("A simple string", (ptr = ini_get_string(string_section, "simple", ""))); free(ptr); ASSERT_STR_EQ("A quoted string", (ptr = ini_get_string(string_section, "quoted", ""))); free(ptr); ASSERT_STR_EQ("A string \"with quotes\"", (ptr = ini_get_string(string_section, "quoted_with_quotes", ""))); free(ptr); ASSERT_STR_EQ("default value", (ptr = ini_get_string(string_section, "nonexistent", "default value"))); free(ptr); PASS(); } TEST test_ini_get_path(void) { char *section = "path"; char *ptr, *exp; char *home = getenv("HOME"); // return default, if nonexistent key ASSERT_EQ(NULL, (ptr = ini_get_path(section, "nonexistent", NULL))); ASSERT_STR_EQ("default", (ptr = ini_get_path(section, "nonexistent", "default"))); g_free(ptr); // return path with replaced home ASSERT_STR_EQ((exp = g_strconcat(home, "/.path/to/tilde", NULL)), (ptr = ini_get_path(section, "expand_tilde", NULL))); g_free(ptr); g_free(exp); PASS(); } TEST test_ini_get_int(void) { char *int_section = "int"; ASSERT_EQ(5, ini_get_int(int_section, "simple", 0)); ASSERT_EQ(-10, ini_get_int(int_section, "negative", 0)); ASSERT_EQ(2, ini_get_int(int_section, "decimal", 0)); ASSERT_EQ(7, ini_get_int(int_section, "leading_zeroes", 0)); ASSERT_EQ(1024, ini_get_int(int_section, "multi_char", 0)); ASSERT_EQ(10, ini_get_int(int_section, "nonexistent", 10)); PASS(); } TEST test_ini_get_double(void) { char *double_section = "double"; ASSERT_EQ(1, ini_get_double(double_section, "simple", 0)); ASSERT_EQ(1.5, ini_get_double(double_section, "decimal", 0)); ASSERT_EQ(-1.2, ini_get_double(double_section, "negative", 0)); ASSERT_EQ(0.005, ini_get_double(double_section, "zeroes", 0)); ASSERT_EQ(3.141592653589793, ini_get_double(double_section, "long", 0)); ASSERT_EQ(10.5, ini_get_double(double_section, "nonexistent", 10.5)); PASS(); } TEST test_cmdline_get_path(void) { char *ptr, *exp; char *home = getenv("HOME"); // return default, if nonexistent key ASSERT_EQ(NULL, (ptr = cmdline_get_path("-nonexistent", NULL, "desc"))); ASSERT_STR_EQ("default", (ptr = cmdline_get_path("-nonexistent", "default", "desc"))); g_free(ptr); // return path with replaced home ASSERT_STR_EQ((exp = g_strconcat(home, "/path/from/cmdline", NULL)), (ptr = cmdline_get_path("-path", NULL, "desc"))); g_free(ptr); g_free(exp); PASS(); } TEST test_cmdline_get_string(void) { char *ptr; ASSERT_STR_EQ("A simple string from the cmdline", (ptr =cmdline_get_string("-string", "", ""))); free(ptr); ASSERT_STR_EQ("Single_word_string", (ptr = cmdline_get_string("-str/-s", "", ""))); free(ptr); ASSERT_STR_EQ("Default", (ptr = cmdline_get_string("-nonexistent", "Default", ""))); free(ptr); PASS(); } TEST test_cmdline_get_int(void) { ASSERT_EQ(3, cmdline_get_int("-int", 0, "")); ASSERT_EQ(2, cmdline_get_int("-int2/-i", 0, "")); ASSERT_EQ(-7, cmdline_get_int("-negative", 0, "")); ASSERT_EQ(4, cmdline_get_int("-zeroes", 0, "")); ASSERT_EQ(2, cmdline_get_int("-intdecim", 0, "")); ASSERT_EQ(10, cmdline_get_int("-nonexistent", 10, "")); PASS(); } TEST test_cmdline_get_double(void) { ASSERT_EQ(2, cmdline_get_double("-simple_double", 0, "")); ASSERT_EQ(5.2, cmdline_get_double("-double", 0, "")); ASSERT_EQ(3.14, cmdline_get_double("-nonexistent", 3.14, "")); PASS(); } TEST test_cmdline_get_bool(void) { ASSERT(cmdline_get_bool("-bool", false, "")); ASSERT(cmdline_get_bool("-shortbool/-b", false, "")); ASSERT(cmdline_get_bool("-boolnd/-n", true, "")); ASSERT_FALSE(cmdline_get_bool("-boolnd/-n", false, "")); PASS(); } TEST test_cmdline_create_usage(void) { g_free(cmdline_get_string("-msgstring/-ms", "", "A string to test usage creation")); cmdline_get_int("-msgint/-mi", 0, "An int to test usage creation"); cmdline_get_double("-msgdouble/-md", 0, "A double to test usage creation"); cmdline_get_bool("-msgbool/-mb", false, "A bool to test usage creation"); const char *usage = cmdline_create_usage(); ASSERT(strstr(usage, "-msgstring/-ms")); ASSERT(strstr(usage, "A string to test usage creation")); ASSERT(strstr(usage, "-msgint/-mi")); ASSERT(strstr(usage, "An int to test usage creation")); ASSERT(strstr(usage, "-msgdouble/-md")); ASSERT(strstr(usage, "A double to test usage creation")); ASSERT(strstr(usage, "-msgbool/-mb")); ASSERT(strstr(usage, "A bool to test usage creation")); PASS(); } TEST test_option_get_string(void) { char *string_section = "string"; char *ptr; ASSERT_STR_EQ("A simple string", (ptr =option_get_string(string_section, "simple", "-nonexistent", "", ""))); free(ptr); ASSERT_STR_EQ("Single_word_string", (ptr = option_get_string(string_section, "simple", "-str/-s", "", ""))); free(ptr); ASSERT_STR_EQ("A simple string from the cmdline", (ptr = option_get_string(string_section, "simple", "-string", "", ""))); free(ptr); ASSERT_STR_EQ("A simple string from the cmdline", (ptr = option_get_string(string_section, "simple", "-string/-s", "", ""))); free(ptr); ASSERT_STR_EQ("Single_word_string", (ptr = option_get_string(string_section, "simple", "-s", "", ""))); free(ptr); ASSERT_STR_EQ("Default", (ptr = option_get_string(string_section, "nonexistent", "-nonexistent", "Default", ""))); free(ptr); PASS(); } TEST test_option_get_path(void) { char *section = "path"; char *ptr, *exp; char *home = getenv("HOME"); // invalid ini, invalid cmdline ASSERT_EQ(NULL, (ptr = option_get_path(section, "nonexistent", "-nonexistent", NULL, "desc"))); ASSERT_STR_EQ("default", (ptr = option_get_path(section, "nonexistent", "-nonexistent", "default", "desc"))); free(ptr); // valid ini, invalid cmdline ASSERT_STR_EQ((exp = g_strconcat(home, "/.path/to/tilde", NULL)), (ptr = option_get_path(section, "expand_tilde", "-nonexistent", NULL, "desc"))); g_free(exp); g_free(ptr); // valid ini, valid cmdline ASSERT_STR_EQ((exp = g_strconcat(home, "/path/from/cmdline", NULL)), (ptr = option_get_path(section, "expand_tilde", "-path", NULL, "desc"))); g_free(exp); g_free(ptr); // invalid ini, valid cmdline ASSERT_STR_EQ((exp = g_strconcat(home, "/path/from/cmdline", NULL)), (ptr = option_get_path(section, "nonexistent", "-path", NULL, "desc"))); g_free(exp); g_free(ptr); PASS(); } TEST test_option_get_int(void) { char *int_section = "int"; ASSERT_EQ(3, option_get_int(int_section, "negative", "-int", 0, "")); ASSERT_EQ(2, option_get_int(int_section, "simple", "-int2/-i", 0, "")); ASSERT_EQ(-7, option_get_int(int_section, "decimal", "-negative", 0, "")); ASSERT_EQ(4, option_get_int(int_section, "simple", "-zeroes", 0, "")); ASSERT_EQ(2, option_get_int(int_section, "simple", "-intdecim", 0, "")); ASSERT_EQ(5, option_get_int(int_section, "simple", "-nonexistent", 0, "")); ASSERT_EQ(-10, option_get_int(int_section, "negative", "-nonexistent", 0, "")); ASSERT_EQ(2, option_get_int(int_section, "decimal", "-nonexistent", 0, "")); ASSERT_EQ(7, option_get_int(int_section, "leading_zeroes", "-nonexistent", 0, "")); ASSERT_EQ(1024, option_get_int(int_section, "multi_char", "-nonexistent", 0, "")); ASSERT_EQ(3, option_get_int(int_section, "nonexistent", "-nonexistent", 3, "")); PASS(); } TEST test_option_get_double(void) { char *double_section = "double"; ASSERT_EQ(2, option_get_double(double_section, "simple", "-simple_double", 0, "")); ASSERT_EQ(5.2, option_get_double(double_section, "simple", "-double", 0, "")); ASSERT_EQ(0.005, option_get_double(double_section, "zeroes", "-nonexistent", 0, "")); ASSERT_EQ(10.5, option_get_double(double_section, "nonexistent", "-nonexistent", 10.5, "")); PASS(); } TEST test_option_get_bool(void) { char *bool_section = "bool"; ASSERT(option_get_bool(bool_section, "boolfalse", "-bool/-b", false, "")); ASSERT(option_get_bool(bool_section, "boolbin1", "-nonexistent", false, "")); ASSERT_FALSE(option_get_bool(bool_section, "boolbin0", "-nonexistent", false, "")); ASSERT_FALSE(option_get_bool(bool_section, "nonexistent", "-nonexistent", false, "")); PASS(); } SUITE(suite_option_parser) { FILE *config_file = fopen("data/test-ini", "r"); if (config_file == NULL) { fputs("\nTest config file 'data/test-ini' couldn't be opened, failing.\n", stderr); exit(1); } load_ini_file(config_file); RUN_TEST(test_next_section); RUN_TEST(test_ini_get_bool); RUN_TEST(test_ini_get_string); RUN_TEST(test_ini_get_path); RUN_TEST(test_ini_get_int); RUN_TEST(test_ini_get_double); char cmdline[] = "dunst -bool -b " "-string \"A simple string from the cmdline\" -s Single_word_string " "-int 3 -i 2 -negative -7 -zeroes 04 -intdecim 2.5 " "-path ~/path/from/cmdline " "-simple_double 2 -double 5.2" ; int argc; char **argv; g_shell_parse_argv(&cmdline[0], &argc, &argv, NULL); cmdline_load(argc, argv); RUN_TEST(test_cmdline_get_string); RUN_TEST(test_cmdline_get_path); RUN_TEST(test_cmdline_get_int); RUN_TEST(test_cmdline_get_double); RUN_TEST(test_cmdline_get_bool); RUN_TEST(test_cmdline_create_usage); RUN_TEST(test_option_get_string); RUN_TEST(test_option_get_path); RUN_TEST(test_option_get_int); RUN_TEST(test_option_get_double); RUN_TEST(test_option_get_bool); free_ini(); g_strfreev(argv); fclose(config_file); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.3.0/test/test.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000000731�13223745120�0014752�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "greatest.h" SUITE_EXTERN(suite_utils); SUITE_EXTERN(suite_option_parser); SUITE_EXTERN(suite_notification); SUITE_EXTERN(suite_markup); GREATEST_MAIN_DEFS(); int main(int argc, char *argv[]) { GREATEST_MAIN_BEGIN(); RUN_SUITE(suite_utils); RUN_SUITE(suite_option_parser); RUN_SUITE(suite_notification); RUN_SUITE(suite_markup); GREATEST_MAIN_END(); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ���������������������������������������dunst-1.3.0/test/utils.c����������������������������������������������������������������������������0000664�0000000�0000000�00000013431�13223745120�0015134�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "greatest.h" #include "src/utils.h" #include <glib.h> TEST test_string_replace_char(void) { char *text = malloc(128 * sizeof(char)); strcpy(text, "a aa aaa"); ASSERT_STR_EQ("b bb bbb", string_replace_char('a', 'b', text)); strcpy(text, "Nothing to replace"); ASSERT_STR_EQ("Nothing to replace", string_replace_char('s', 'a', text)); strcpy(text, ""); ASSERT_STR_EQ("", string_replace_char('a', 'b', text)); free(text); PASS(); } /* * We trust that string_replace_all and string_replace properly reallocate * memory if the result is longer than the given string, no real way to test for * that far as I know. */ TEST test_string_replace_all(void) { char *text = malloc(128 * sizeof(char)); strcpy(text, "aaaaa"); ASSERT_STR_EQ("bbbbb", (text = string_replace_all("a", "b", text))); strcpy(text, ""); ASSERT_STR_EQ("", (text = string_replace_all("a", "b", text))); strcpy(text, "Nothing to replace"); ASSERT_STR_EQ((text = string_replace_all("z", "a", text)), "Nothing to replace"); strcpy(text, "Reverse this"); ASSERT_STR_EQ("Reverse sith", (text = string_replace_all("this", "sith", text))); strcpy(text, "abcdabc"); ASSERT_STR_EQ("xyzabcdxyzabc", (text = string_replace_all("a", "xyza", text))); free(text); PASS(); } TEST test_string_replace(void) { char *text = malloc(128 * sizeof(char)); strcpy(text, "aaaaa"); ASSERT_STR_EQ("baaaa", (text = string_replace("a", "b", text)) ); strcpy(text, ""); ASSERT_STR_EQ((text = string_replace("a", "b", text)), ""); strcpy(text, "Nothing to replace"); ASSERT_STR_EQ((text = string_replace("z", "a", text)), "Nothing to replace"); strcpy(text, "Reverse this"); ASSERT_STR_EQ("Reverse sith", (text = string_replace("this", "sith", text))); strcpy(text, "abcdabc"); ASSERT_STR_EQ("xyzabcdabc", (text = string_replace("a", "xyza", text))); free(text); PASS(); } TEST test_string_append(void) { char *exp; ASSERT_STR_EQ("text_sep_bit", (exp = string_append(g_strdup("text"), "bit", "_sep_"))); g_free(exp); ASSERT_STR_EQ("textbit", (exp = string_append(g_strdup("text"), "bit", NULL))); g_free(exp); ASSERT_STR_EQ("textbit", (exp = string_append(g_strdup("text"), "bit", ""))); g_free(exp); ASSERT_STR_EQ("text", (exp = string_append(g_strdup("text"), "", NULL))); g_free(exp); ASSERT_STR_EQ("text", (exp = string_append(g_strdup("text"), "", "_sep_"))); g_free(exp); ASSERT_STR_EQ("b", (exp = string_append(g_strdup(""), "b", NULL))); g_free(exp); ASSERT_STR_EQ("b", (exp = string_append(NULL, "b", "_sep_"))); g_free(exp); ASSERT_STR_EQ("a", (exp = string_append(g_strdup("a"), "", NULL))); g_free(exp); ASSERT_STR_EQ("a", (exp = string_append(g_strdup("a"), NULL, "_sep_"))); g_free(exp); ASSERT_STR_EQ("", (exp = string_append(g_strdup(""), "", "_sep_"))); g_free(exp); ASSERT_EQ(NULL, (exp = string_append(NULL, NULL, "_sep_"))); g_free(exp); PASS(); } TEST test_string_strip_delimited(void) { char *text = malloc(128 * sizeof(char)); strcpy(text, "A <simple> string_strip_delimited test"); string_strip_delimited(text, '<', '>'); ASSERT_STR_EQ("A string_strip_delimited test", text); strcpy(text, "Remove <blink>html <b><i>tags</i></b></blink>"); string_strip_delimited(text, '<', '>'); ASSERT_STR_EQ("Remove html tags", text); strcpy(text, "Calls|with|identical|delimiters|are|handled|properly"); string_strip_delimited(text, '|', '|'); ASSERT_STR_EQ("Calls", text); strcpy(text, "<Return empty string if there is nothing left>"); string_strip_delimited(text, '<', '>'); ASSERT_STR_EQ("", text); strcpy(text, "Nothing is done if there are no delimiters in the string"); string_strip_delimited(text, '<', '>'); ASSERT_STR_EQ("Nothing is done if there are no delimiters in the string", text); free(text); PASS(); } TEST test_string_to_path(void) { char *ptr, *exp; char *home = getenv("HOME"); exp = "/usr/local/bin/script"; ASSERT_STR_EQ(exp, (ptr = string_to_path(g_strdup(exp)))); free(ptr); exp = "~path/with/wrong/tilde"; ASSERT_STR_EQ(exp, (ptr = string_to_path(g_strdup(exp)))); free(ptr); ASSERT_STR_EQ((exp = g_strconcat(home, "/.path/with/tilde", NULL)), (ptr = string_to_path(g_strdup("~/.path/with/tilde")))); free(exp); free(ptr); ASSERT_STR_EQ((exp = g_strconcat(home, "/.path/with/tilde and some space", NULL)), (ptr = string_to_path(g_strdup("~/.path/with/tilde and some space")))); free(exp); free(ptr); PASS(); } TEST test_string_to_time(void) { char *input[] = { "5000 ms", "5000ms", "100", "10s", "2m", "11h", "9d", " 5 ms ", NULL }; gint64 exp[] = { 5000, 5000, 100000, 10000, 120000, 39600000, 777600000, 5, 0}; int i = 0; while (input[i]){ ASSERT_EQ_FMT(string_to_time(input[i]), exp[i]*1000, "%ld"); i++; } PASS(); } SUITE(suite_utils) { RUN_TEST(test_string_replace_char); RUN_TEST(test_string_replace_all); RUN_TEST(test_string_replace); RUN_TEST(test_string_append); RUN_TEST(test_string_strip_delimited); RUN_TEST(test_string_to_path); RUN_TEST(test_string_to_time); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������