diff -Nru dunst-1.2.0/CHANGELOG.md dunst-1.3.0/CHANGELOG.md --- dunst-1.2.0/CHANGELOG.md 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/CHANGELOG.md 2018-01-05 18:56:16.000000000 +0000 @@ -1,5 +1,41 @@ # 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 diff -Nru dunst-1.2.0/config.def.h dunst-1.3.0/config.def.h --- dunst-1.2.0/config.def.h 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/config.def.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,101 +0,0 @@ -/* see example dunstrc for additional explanations about these options */ - -char *font = "-*-terminus-medium-r-*-*-16-*-*-*-*-*-*-*"; -char *markup = "no"; -char *normbgcolor = "#1793D1"; -char *normfgcolor = "#DDDDDD"; -char *critbgcolor = "#ffaaaa"; -char *critfgcolor = "#000000"; -char *lowbgcolor = "#aaaaff"; -char *lowfgcolor = "#000000"; -char *format = "%s %b"; /* default format */ - -int timeouts[] = { 10, 10, 0 }; /* low, normal, critical */ -char *icons[] = { "info", "info", "emblem-important" }; /* low, normal, critical */ - -unsigned int transparency = 0; /* transparency */ -char *geom = "0x0"; /* geometry */ -char *title = "Dunst"; /* the title of dunst notification windows */ -char *class = "Dunst"; /* the class of dunst notification windows */ -int shrink = false; /* shrinking */ -int sort = true; /* sort messages by urgency */ -int indicate_hidden = true; /* show count of hidden messages */ -int idle_threshold = 0; /* don't timeout notifications when idle for x seconds */ -int show_age_threshold = -1; /* show age of notification, when notification is older than x seconds */ -enum alignment align = left; /* text alignment [left/center/right] */ -int sticky_history = true; -int history_length = 20; /* max amount of notifications kept in history */ -int show_indicators = true; -int word_wrap = false; -int ignore_newline = false; -int line_height = 0; /* if line height < font height, it will be raised to font height */ -int notification_height = 0; /* if notification height < font height and padding, it will be raised */ - -int separator_height = 2; /* height of the separator line between two notifications */ -int padding = 0; -int h_padding = 0; /* horizontal padding */ -enum separator_color sep_color = AUTO; /* AUTO, FOREGROUND, FRAME, CUSTOM */ -char *sep_custom_color_str = NULL; /* custom color if sep_color is set to CUSTOM */ - -int frame_width = 0; -char *frame_color = "#888888"; -/* show a notification on startup - * This is mainly for crash detection since dbus restarts dunst - * automatically after a crash, so crashes might get unnotices otherwise - * */ -int startup_notification = false; - -/* monitor to display notifications on */ -int monitor = 0; - -/* path to dmenu */ -char *dmenu = "/usr/bin/dmenu"; - -char *browser = "/usr/bin/firefox"; - -int max_icon_size = 0; - -/* paths to default icons */ -char *icon_folders = "/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' - */ -enum follow_mode f_mode = FOLLOW_NONE; - -/* keyboard shortcuts - * use for example "ctrl+shift+space" - * use "none" to disable - */ -keyboard_shortcut close_ks = {.str = "none", - .code = 0,.sym = NoSymbol,.is_valid = false -}; /* ignore this */ - -keyboard_shortcut close_all_ks = {.str = "none", - .code = 0,.sym = NoSymbol,.is_valid = false -}; /* ignore this */ - -keyboard_shortcut history_ks = {.str = "none", - .code = 0,.sym = NoSymbol,.is_valid = false -}; /* ignore this */ - -keyboard_shortcut context_ks = {.str = "none", - .code = 0,.sym = NoSymbol,.is_valid = false -}; /* ignore this */ - -rule_t default_rules[] = { - /* name can be any unique string. It is used to identify the rule in dunstrc to override it there */ - - /* name, appname, summary, body, icon, category, msg_urgency, timeout, urgency, markup, history_ignore, new_icon, fg, bg, format, script */ - { "empty", NULL, NULL, NULL, NULL, NULL, -1, -1, -1, MARKUP_NULL, false, NULL, NULL, NULL, NULL, NULL}, - /* { "rule1", "notify-send", NULL, NULL, NULL, NULL, -1, -1, -1, MARKUP_NULL, false, NULL, NULL, NULL, "%s %b", NULL}, */ - /* { "rule2", "Pidgin", "*says*, NULL, NULL, NULL, -1, -1, CRITICAL, MARKUP_NULL, false, NULL, NULL, NULL, NULL, NULL}, */ - /* { "rule3", "Pidgin", "*signed on*", NULL, NULL, NULL, -1, -1, LOW, MARKUP_NULL, false, NULL, NULL, NULL, NULL, NULL}, */ - /* { "rule4", "Pidgin", "*signed off*", NULL, NULL, NULL, -1, -1, LOW, MARKUP_NULL, false, NULL, NULL, NULL, NULL, NULL}, */ - /* { "rule5", NULL, "*foobar*", NULL, NULL, NULL, -1, -1, -1, MARKUP_NULL, false, NULL, NULL, "#00FF00", NULL, NULL}, */ -}; diff -Nru dunst-1.2.0/config.h dunst-1.3.0/config.h --- dunst-1.2.0/config.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.3.0/config.h 2018-01-05 18:56:16.000000000 +0000 @@ -0,0 +1,148 @@ +/* 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: */ diff -Nru dunst-1.2.0/config.mk dunst-1.3.0/config.mk --- dunst-1.2.0/config.mk 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/config.mk 2018-01-05 18:56:16.000000000 +0000 @@ -2,51 +2,41 @@ PREFIX ?= /usr/local MANPREFIX = ${PREFIX}/share/man -# In dist tarballs, the version is stored in the VERSION files. -VERSION := "1.2.0 (2017-07-12)" -ifneq ($(wildcard ./.git/.),) -VERSION := $(shell git describe --tags) -endif - -# Specifies which X extension dunst should use for multi monitor support -# Possible values are randr, xinerama or none -# * xrandr is the recommended/default value and should be the best option in most cases -# * xinerama is the legacy equivalent to randr but does not support some more -# advanced features like per-monitor dpi calculation it should probably one -# be used if xrandr cannot be used for some reason. -# * none disables multi-monitor support and dunst will assume only one monitor. -MULTIMON ?= xrandr +# 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 - -PKG_CONFIG:=$(shell which pkg-config) -ifeq (${PKG_CONFIG}, ${EMPTY}) - $(error "Failed to find pkg-config, please make sure it is installed") -endif +#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} -pkg_config_packs := dbus-1 x11 xscrnsaver \ - "glib-2.0 >= 2.36" gio-2.0 \ - pangocairo gdk-2.0 xrandr xinerama +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 -# includes and libs -INCS := $(shell ${PKG_CONFIG} --cflags ${pkg_config_packs}) -CFLAGS += ${INCS} -LDFLAGS += -lm -L${X11LIB} -lXss $(shell ${PKG_CONFIG} --libs ${pkg_config_packs}) - -# only make this an fatal error when where not cleaning -ifneq (clean, $(MAKECMDGOALS)) -ifeq (${INCS}, ${EMPTY}) -$(error "pkg-config failed, see errors above") -endif +# dunstify also needs libnotify +ifneq (,$(findstring dunstify,${MAKECMDGOALS})) + pkg_config_packs += libnotify endif diff -Nru dunst-1.2.0/debian/changelog dunst-1.3.0/debian/changelog --- dunst-1.2.0/debian/changelog 2017-10-10 20:28:17.000000000 +0000 +++ dunst-1.3.0/debian/changelog 2018-01-29 15:53:39.000000000 +0000 @@ -1,3 +1,20 @@ +dunst (1.3.0-2) unstable; urgency=medium + + * Makefile: correct dependencies to fix race condition (Closes: #888760) + + -- Michael Stapelberg Mon, 29 Jan 2018 16:53:39 +0100 + +dunst (1.3.0-1) unstable; urgency=medium + + * Enable pristine-tar in debian/gbp.conf to reflect status quo + * Update Vcs-* tags after moving to salsa + * New upstream version 1.3.0 + * refresh patches + * add build-dep on systemd so that dunst installs the service file + * priority extra was replaced by priority optional + + -- Michael Stapelberg Sun, 28 Jan 2018 09:50:17 +0100 + dunst (1.2.0-2) unstable; urgency=medium * rm debian/org.knopwob.dunst.service to not shadow upstream (Closes: #878164) diff -Nru dunst-1.2.0/debian/control dunst-1.3.0/debian/control --- dunst-1.2.0/debian/control 2017-10-10 20:28:17.000000000 +0000 +++ dunst-1.3.0/debian/control 2018-01-29 15:53:39.000000000 +0000 @@ -1,6 +1,6 @@ Source: dunst Section: x11 -Priority: extra +Priority: optional Maintainer: Michael Stapelberg Build-Depends: debhelper (>= 10), libdbus-1-dev, @@ -14,11 +14,12 @@ libnotify-dev, libxrandr-dev, libgtk2.0-dev, - dpkg-dev (>= 1.16.1.1) + dpkg-dev (>= 1.16.1.1), + systemd Standards-Version: 4.1.1 Homepage: https://dunst-project.org/ -Vcs-Git: https://anonscm.debian.org/git/collab-maint/dunst.git -Vcs-Browser: https://anonscm.debian.org/cgit/collab-maint/dunst.git/ +Vcs-Git: https://salsa.debian.org/debian/dunst.git +Vcs-Browser: https://salsa.debian.org/debian/dunst Package: dunst Architecture: any diff -Nru dunst-1.2.0/debian/gbp.conf dunst-1.3.0/debian/gbp.conf --- dunst-1.2.0/debian/gbp.conf 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.3.0/debian/gbp.conf 2018-01-29 15:53:39.000000000 +0000 @@ -0,0 +1,2 @@ +[DEFAULT] +pristine-tar = True diff -Nru dunst-1.2.0/debian/patches/example_path.patch dunst-1.3.0/debian/patches/example_path.patch --- dunst-1.2.0/debian/patches/example_path.patch 2017-10-10 20:28:17.000000000 +0000 +++ dunst-1.3.0/debian/patches/example_path.patch 2018-01-29 15:53:39.000000000 +0000 @@ -8,17 +8,15 @@ =================================================================== --- dunst.orig/Makefile +++ dunst/Makefile -@@ -58,8 +58,8 @@ install-dunst: dunst doc - install -m644 docs/dunst.1 ${DESTDIR}${MANPREFIX}/man1 +@@ -130,7 +130,7 @@ install-dunst: dunst doc + install -Dm644 docs/dunst.1 ${DESTDIR}${MANPREFIX}/man1/dunst.1 install-doc: -- mkdir -p ${DESTDIR}${PREFIX}/share/dunst -- install -m644 dunstrc ${DESTDIR}${PREFIX}/share/dunst -+ mkdir -p ${DESTDIR}${PREFIX}/share/doc/dunst -+ install -m644 dunstrc ${DESTDIR}${PREFIX}/share/doc/dunst/dunstrc.example +- install -Dm644 dunstrc ${DESTDIR}${PREFIX}/share/dunst/dunstrc ++ install -Dm644 dunstrc ${DESTDIR}${PREFIX}/share/doc/dunst/dunstrc - install-service: service - mkdir -p ${DESTDIR}${PREFIX}/share/dbus-1/services/ + install-service: service install-service-dbus + install-service-dbus: Index: dunst/docs/dunst.pod =================================================================== --- dunst.orig/docs/dunst.pod @@ -28,7 +26,7 @@ =head1 CONFIGURATION -An example configuration file is included (usually /usr/share/dunst/dunstrc). -+An example configuration file is included (usually /usr/share/doc/dunst/dunstrc.example.gz). ++An example configuration file is included (usually /usr/share/doc/dunst/dunstrc.gz). To change the configuration, copy this file to ~/.config/dunst/dunstrc and edit it accordingly. diff -Nru dunst-1.2.0/debian/patches/makefile.patch dunst-1.3.0/debian/patches/makefile.patch --- dunst-1.2.0/debian/patches/makefile.patch 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.3.0/debian/patches/makefile.patch 2018-01-29 15:53:39.000000000 +0000 @@ -0,0 +1,27 @@ +Description: correct dependencies to fix race condition +Author: Michael Stapelberg +Forwarded: https://github.com/dunst-project/dunst/pull/488 +Bug-Debian: https://bugs.debian.org/888760 + +--- + +Index: dunst/Makefile +=================================================================== +--- dunst.orig/Makefile ++++ dunst/Makefile +@@ -132,12 +132,12 @@ install-dunst: dunst doc + install-doc: + install -Dm644 dunstrc ${DESTDIR}${PREFIX}/share/doc/dunst/dunstrc + +-install-service: service install-service-dbus +-install-service-dbus: ++install-service: install-service-dbus ++install-service-dbus: 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-service-systemd: service-systemd + install -Dm644 dunst.systemd.service ${DESTDIR}${SERVICEDIR_SYSTEMD}/dunst.service + endif + diff -Nru dunst-1.2.0/debian/patches/series dunst-1.3.0/debian/patches/series --- dunst-1.2.0/debian/patches/series 2017-10-10 20:28:17.000000000 +0000 +++ dunst-1.3.0/debian/patches/series 2018-01-29 15:53:39.000000000 +0000 @@ -1 +1,2 @@ example_path.patch +makefile.patch diff -Nru dunst-1.2.0/docs/dunst.pod dunst-1.3.0/docs/dunst.pod --- dunst-1.2.0/docs/dunst.pod 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/docs/dunst.pod 2018-01-05 18:56:16.000000000 +0000 @@ -230,10 +230,14 @@ =item B (default: 0) -Don't timeout notifications if user is idle longer than this value (in seconds). +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 @@ -321,6 +325,8 @@ =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 @@ -333,7 +339,8 @@ =item B (default: -1) -Show age of message if message is older than this value (in seconds). +Show age of message if message is older than this time. +See TIME FORMAT for valid times. Set to -1 to disable. @@ -346,6 +353,11 @@ 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. @@ -383,7 +395,7 @@ 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/") +=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. @@ -523,8 +535,9 @@ =item B<-lto/nto/cto secs> -Defines the timeout time(in seconds) for low, normal and critical notifications +Defines the timeout time for low, normal and critical notifications respectively. +See TIME FORMAT for valid times. =back @@ -556,9 +569,10 @@ =item B Notifications can be matched for any of the following attributes: appname, -summary, body, icon, category 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. +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: @@ -574,8 +588,8 @@ =item B The following attributes can be overridden: timeout, urgency, foreground, -background, new_icon, format where, as with the filtering attributes, each one -corresponds to the respective notification attribute to be modified. +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. @@ -588,9 +602,7 @@ =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. (If the -value is not an absolute path, the directories in the PATH variable will be -searched for an executable of the same name). +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, @@ -602,6 +614,10 @@ If the notification is suppressed, the script will not be run unless B 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 @@ -648,6 +664,15 @@ 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 diff -Nru dunst-1.2.0/dunstify.c dunst-1.3.0/dunstify.c --- dunst-1.2.0/dunstify.c 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/dunstify.c 2018-01-05 18:56:16.000000000 +0000 @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -24,19 +25,19 @@ 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}, + { "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 } }; @@ -252,6 +253,7 @@ int main(int argc, char *argv[]) { + setlocale(LC_ALL, ""); #if !GLIB_CHECK_VERSION(2,35,0) g_type_init(); #endif @@ -296,9 +298,10 @@ GMainLoop *l = NULL; - if (block || action_strs) + 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++) { @@ -317,12 +320,13 @@ die(1); } + if (printid) + g_print("%d\n", get_id(n)); + if (block || action_strs) g_main_loop_run(l); - if (printid) { - g_print("%d\n", get_id(n)); - } + g_object_unref(G_OBJECT (n)); die(0); } diff -Nru dunst-1.2.0/dunstrc dunst-1.3.0/dunstrc --- dunst-1.2.0/dunstrc 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/dunstrc 2018-01-05 18:56:16.000000000 +0000 @@ -80,6 +80,7 @@ # 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 ### @@ -122,6 +123,7 @@ # %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 = "%s\n%b" @@ -138,6 +140,10 @@ # 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 @@ -159,7 +165,7 @@ max_icon_size = 32 # Paths to default icons. - icon_folders = /usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/ + icon_path = /usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/ ### History ### diff -Nru dunst-1.2.0/.github/ISSUE_TEMPLATE.md dunst-1.3.0/.github/ISSUE_TEMPLATE.md --- dunst-1.2.0/.github/ISSUE_TEMPLATE.md 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.3.0/.github/ISSUE_TEMPLATE.md 2018-01-05 18:56:16.000000000 +0000 @@ -0,0 +1,24 @@ + + + + + +### Installation info + + + +- Version: `` +- Install type: `` +- Distro and version: ` ` diff -Nru dunst-1.2.0/.gitignore dunst-1.3.0/.gitignore --- dunst-1.2.0/.gitignore 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/.gitignore 2018-01-05 18:56:16.000000000 +0000 @@ -2,7 +2,6 @@ *.o core vgcore.* -config.h dunst.1 org.knopwob.dunst.service dunst.systemd.service diff -Nru dunst-1.2.0/Makefile dunst-1.3.0/Makefile --- dunst-1.2.0/Makefile 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/Makefile 2018-01-05 18:56:16.000000000 +0000 @@ -3,27 +3,107 @@ include config.mk -CFLAGS += -I. -LDFLAGS += -L. +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 -SRC := $(shell find src/ -name '*.c') +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.h config.mk - -config.h: config.def.h - @if test -s $@; then echo $< is newer than $@, merge and save $@. If you haven\'t edited $@ you can just delete it && exit 1; fi - @echo creating $@ from $< - @cp $< $@ +${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 @@ -36,57 +116,42 @@ clean-doc: rm -f docs/dunst.1 -clean: clean-dunst clean-dunstify clean-doc test-clean - -distclean: clean clean-config - -clean-config: - rm -f config.h - -doc: docs/dunst.1 -docs/dunst.1: docs/dunst.pod - pod2man --name=dunst -c "Dunst Reference" --section=1 --release=${VERSION} $< > $@ +clean-tests: + rm -f test/test test/*.o -service: - @sed "s|##PREFIX##|$(PREFIX)|" org.knopwob.dunst.service.in > org.knopwob.dunst.service - @sed "s|##PREFIX##|$(PREFIX)|" dunst.systemd.service.in > dunst.systemd.service +.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 - mkdir -p ${DESTDIR}${PREFIX}/bin - install -m755 dunst ${DESTDIR}${PREFIX}/bin - mkdir -p ${DESTDIR}${MANPREFIX}/man1 - install -m644 docs/dunst.1 ${DESTDIR}${MANPREFIX}/man1 + install -Dm755 dunst ${DESTDIR}${PREFIX}/bin/dunst + install -Dm644 docs/dunst.1 ${DESTDIR}${MANPREFIX}/man1/dunst.1 install-doc: - mkdir -p ${DESTDIR}${PREFIX}/share/dunst - install -m644 dunstrc ${DESTDIR}${PREFIX}/share/dunst + install -Dm644 dunstrc ${DESTDIR}${PREFIX}/share/dunst/dunstrc -install-service: service - mkdir -p ${DESTDIR}${PREFIX}/share/dbus-1/services/ - install -m644 org.knopwob.dunst.service ${DESTDIR}${PREFIX}/share/dbus-1/services - install -Dm644 dunst.systemd.service ${DESTDIR}${PREFIX}/lib/systemd/user/dunst.service +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 -install: install-dunst install-doc install-service - -uninstall: +uninstall: uninstall-service rm -f ${DESTDIR}${PREFIX}/bin/dunst rm -f ${DESTDIR}${MANPREFIX}/man1/dunst.1 - rm -f ${DESTDIR}${PREFIX}/share/dbus-1/services/org.knopwob.dunst.service rm -rf ${DESTDIR}${PREFIX}/share/dunst -test: test/test - cd test && ./test - -TEST_SRC := $(shell find test/ -name '*.c') -TEST_OBJ := $(TEST_SRC:.c=.o) - -test/test: ${OBJ} ${TEST_OBJ} - ${CC} ${CFLAGS} -o $@ ${TEST_OBJ} ${OBJ} ${LDFLAGS} - -test-clean: - rm -f test/test test/*.o - -dunstify: dunstify.o - ${CC} ${CFLAGS} -o $@ dunstify.o $(shell pkg-config --libs --cflags glib-2.0 libnotify gdk-2.0) - -.PHONY: all clean dist install uninstall +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 diff -Nru dunst-1.2.0/README.md dunst-1.3.0/README.md --- dunst-1.2.0/README.md 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/README.md 2018-01-05 18:56:16.000000000 +0000 @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/dunst-project/dunst.svg?branch=master)](https://travis-ci.org/dunst-project/dunst) +[![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 @@ -12,7 +12,9 @@ Dunst is a highly configurable and lightweight notification daemon. -## Compiling +## 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): @@ -23,7 +25,26 @@ - libxdg-basedir - glib - pango/cairo -- libgtk2.0 +- 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. @@ -31,7 +52,7 @@ 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. -## Mantainers +## Maintainers Nikos Tsipinakis diff -Nru dunst-1.2.0/RELEASE_NOTES dunst-1.3.0/RELEASE_NOTES --- dunst-1.2.0/RELEASE_NOTES 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/RELEASE_NOTES 2018-01-05 18:56:16.000000000 +0000 @@ -1,4 +1,65 @@ =================================================================================== +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 =================================================================================== diff -Nru dunst-1.2.0/src/dbus.c dunst-1.3.0/src/dbus.c --- dunst-1.2.0/src/dbus.c 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/dbus.c 2018-01-05 18:56:16.000000000 +0000 @@ -8,17 +8,22 @@ #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 = "" - "" - " " + "" + " " " " " " @@ -59,30 +64,32 @@ " " ""; -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) +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); @@ -91,18 +98,17 @@ } 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); + 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) +static void on_get_capabilities(GDBusConnection *connection, + const gchar *sender, + const GVariant *parameters, + GDBusMethodInvocation *invocation) { GVariantBuilder *builder; GVariant *value; @@ -112,7 +118,7 @@ g_variant_builder_add(builder, "s", "body"); g_variant_builder_add(builder, "s", "body-hyperlinks"); - if(settings.markup != MARKUP_NO) + if (settings.markup != MARKUP_NO) g_variant_builder_add(builder, "s", "body-markup"); value = g_variant_new("(as)", builder); @@ -122,9 +128,7 @@ g_dbus_connection_flush(connection, NULL, NULL, NULL); } -static void on_notify(GDBusConnection * connection, - const gchar * sender, - GVariant * parameters, GDBusMethodInvocation * invocation) +static notification *dbus_message_to_notification(const gchar *sender, GVariant *parameters) { gchar *appname = NULL; @@ -138,6 +142,7 @@ /* hints */ gint urgency = 1; gint progress = -1; + gboolean transient = 0; gchar *fgcolor = NULL; gchar *bgcolor = NULL; gchar *category = NULL; @@ -152,125 +157,101 @@ switch (idx) { case 0: - if (g_variant_is_of_type - (content, G_VARIANT_TYPE_STRING)) - appname = - g_variant_dup_string(content, NULL); + 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); + 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); + 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); + 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); + 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)); + 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)) { + if (g_variant_is_of_type(content, G_VARIANT_TYPE_DICTIONARY)) { - dict_value = - g_variant_lookup_value(content, - "urgency", - G_VARIANT_TYPE_BYTE); - if (dict_value) - urgency = - g_variant_get_byte - (dict_value); - - dict_value = - g_variant_lookup_value(content, - "fgcolor", - G_VARIANT_TYPE_STRING); - if (dict_value) - fgcolor = - g_variant_dup_string - (dict_value, NULL); - - dict_value = - g_variant_lookup_value(content, - "bgcolor", - G_VARIANT_TYPE_STRING); - if (dict_value) - bgcolor = - g_variant_dup_string - (dict_value, NULL); - - dict_value = - g_variant_lookup_value(content, - "category", - G_VARIANT_TYPE_STRING); + 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) { - category = - g_variant_dup_string( - dict_value, NULL); + fgcolor = 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, - "icon_data", - G_VARIANT_TYPE("(iiibiiay)")); + 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) { - raw_icon = - get_raw_image_from_data_hint( - dict_value); + category = g_variant_dup_string(dict_value, NULL); + g_variant_unref(dict_value); } - dict_value = - g_variant_lookup_value(content, - "value", - G_VARIANT_TYPE_INT32); + 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) { - progress = - g_variant_get_int32(dict_value); - } else { - dict_value = - g_variant_lookup_value(content, - "value", - G_VARIANT_TYPE_UINT32); - - if (dict_value) - progress = - g_variant_get_uint32(dict_value); + 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)) + if (g_variant_is_of_type(content, G_VARIANT_TYPE_INT32)) timeout = g_variant_get_int32(content); break; } @@ -283,66 +264,72 @@ fflush(stdout); - if (timeout > 0) { - /* do some rounding */ - timeout = (timeout + 500) / 1000; - if (timeout < 1) { - timeout = 1; - } - } - 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; - n->markup = settings.markup; - n->progress = (progress < 0 || progress > 100) ? 0 : progress + 1; + n->timeout = timeout < 0 ? -1 : timeout * 1000; + n->progress = progress; n->urgency = urgency; n->category = category; n->dbus_client = g_strdup(sender); - if (actions->count > 0) { - n->actions = actions; - } else { - n->actions = NULL; - g_strfreev(actions->actions); - g_free(actions); - } + n->transient = transient; - for (int i = 0; i < ColLast; i++) { - n->color_strings[i] = NULL; + if (actions->count < 1) { + actions_free(actions); + actions = NULL; } - n->color_strings[ColFG] = fgcolor; - n->color_strings[ColBG] = bgcolor; + n->actions = actions; - int id = notification_init(n, replaces_id); - wake_up(); + 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); - run(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) +static void on_close_notification(GDBusConnection *connection, + const gchar *sender, + GVariant *parameters, + GDBusMethodInvocation *invocation) { guint32 id; g_variant_get(parameters, "(u)", &id); - notification_close_by_id(id, 3); + 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) +static void on_get_server_information(GDBusConnection *connection, + const gchar *sender, + const GVariant *parameters, + GDBusMethodInvocation *invocation) { GVariant *value; @@ -352,8 +339,14 @@ g_dbus_connection_flush(connection, NULL, NULL, NULL); } -void notification_closed(notification * n, int reason) +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; @@ -364,9 +357,11 @@ g_dbus_connection_emit_signal(dbus_conn, n->dbus_client, - "/org/freedesktop/Notifications", - "org.freedesktop.Notifications", - "NotificationClosed", body, &err); + FDN_PATH, + FDN_IFAC, + "NotificationClosed", + body, + &err); if (err) { fprintf(stderr, "Unable to close notification: %s\n", err->message); @@ -375,16 +370,18 @@ } -void action_invoked(notification * n, const char *identifier) +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, - "/org/freedesktop/Notifications", - "org.freedesktop.Notifications", - "ActionInvoked", body, &err); + FDN_PATH, + FDN_IFAC, + "ActionInvoked", + body, + &err); if (err) { fprintf(stderr, "Unable to invoke action: %s\n", err->message); @@ -396,18 +393,21 @@ handle_method_call }; -static void on_bus_acquired(GDBusConnection * connection, - const gchar * name, gpointer user_data) +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, - "/org/freedesktop/Notifications", + FDN_PATH, introspection_data->interfaces[0], &interface_vtable, - NULL, NULL, &err); + NULL, + NULL, + &err); if (registration_id == 0) { fprintf(stderr, "Unable to register dbus connection: %s\n", err->message); @@ -415,51 +415,143 @@ } } -static void on_name_acquired(GDBusConnection * connection, - const gchar * name, gpointer user_data) +static void on_name_acquired(GDBusConnection *connection, + const gchar *name, + gpointer user_data) { dbus_conn = connection; } -static void on_name_lost(GDBusConnection * connection, - const gchar * name, gpointer user_data) +/* + * 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) { - fprintf(stderr, "Name Lost. Is Another notification daemon running?\n"); + 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) +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); - return NULL; - } + 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)); + image->data = (guchar *) g_memdup(g_variant_get_data(data_variant), + g_variant_get_size(data_variant)); + g_variant_unref(data_variant); - return image; + return image; } int initdbus(void) @@ -474,10 +566,13 @@ NULL); owner_id = g_bus_own_name(G_BUS_TYPE_SESSION, - "org.freedesktop.Notifications", + FDN_NAME, G_BUS_NAME_OWNER_FLAGS_NONE, on_bus_acquired, - on_name_acquired, on_name_lost, NULL, NULL); + on_name_acquired, + on_name_lost, + NULL, + NULL); return owner_id; } diff -Nru dunst-1.2.0/src/dbus.h dunst-1.3.0/src/dbus.h --- dunst-1.2.0/src/dbus.h 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/dbus.h 2018-01-05 18:56:16.000000000 +0000 @@ -5,11 +5,20 @@ #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 notification_closed(notification * n, int reason); -void action_invoked(notification * n, const char *identifier); +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: */ diff -Nru dunst-1.2.0/src/dunst.c dunst-1.3.0/src/dunst.c --- dunst-1.2.0/src/dunst.c 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/dunst.c 2018-01-05 18:56:16.000000000 +0000 @@ -1,6 +1,5 @@ /* copyright 2012 - 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ -#define _GNU_SOURCE #define XLIB_ILLEGAL_ACCESS #include "dunst.h" @@ -12,17 +11,15 @@ #include #include #include -#include #include "dbus.h" #include "menu.h" #include "notification.h" #include "option_parser.h" +#include "queues.h" #include "settings.h" -#include "x11/x.h" #include "x11/screen.h" - -#define LENGTH(X) (sizeof X / sizeof X[0]) +#include "x11/x.h" #ifndef VERSION #define VERSION "version info needed" @@ -39,227 +36,62 @@ } x11_source_t; /* index of colors fit to urgency level */ -bool pause_display = false; GMainLoop *mainloop = NULL; -/* notification lists */ -GQueue *queue = NULL; /* all new notifications get into here */ -GQueue *displayed = NULL; /* currently displayed notifications */ -GQueue *history = NULL; /* history of displayed notifications */ GSList *rules = NULL; /* misc funtions */ - -void check_timeouts(void) -{ - /* nothing to do */ - if (displayed->length == 0) - return; - - for (GList * iter = g_queue_peek_head_link(displayed); iter; - iter = iter->next) { - notification *n = iter->data; - - /* don't timeout when user is idle */ - if (x_is_idle()) { - n->start = time(NULL); - continue; - } - - /* skip hidden and sticky messages */ - if (n->start == 0 || n->timeout == 0) { - continue; - } - - /* remove old message */ - if (difftime(time(NULL), n->start) > n->timeout) { - /* close_notification may conflict with iter, so restart */ - notification_close(n, 1); - check_timeouts(); - return; - } - } -} - -void update_lists() -{ - int limit; - - check_timeouts(); - - if (pause_display) { - while (displayed->length > 0) { - g_queue_insert_sorted(queue, g_queue_pop_head(displayed), - notification_cmp_data, NULL); - } - return; - } - - if (xctx.geometry.h == 0) { - limit = 0; - } else if (xctx.geometry.h == 1) { - limit = 1; - } else if (settings.indicate_hidden) { - limit = xctx.geometry.h - 1; - } else { - limit = xctx.geometry.h; - } - - /* move notifications from queue to displayed */ - while (queue->length > 0) { - - if (limit > 0 && displayed->length >= limit) { - /* the list is full */ - break; - } - - notification *n = g_queue_pop_head(queue); - - if (!n) - return; - n->start = time(NULL); - if (!n->redisplayed && n->script) { - notification_run_script(n); - } - - g_queue_insert_sorted(displayed, n, notification_cmp_data, - NULL); - } -} - -void move_all_to_history() -{ - while (displayed->length > 0) { - notification_close(g_queue_peek_head_link(displayed)->data, 2); - } - - while (queue->length > 0) { - notification_close(g_queue_peek_head_link(queue)->data, 2); - } -} - -void history_pop(void) -{ - if (g_queue_is_empty(history)) - return; - - notification *n = g_queue_pop_tail(history); - n->redisplayed = true; - n->start = 0; - n->timeout = settings.sticky_history ? 0 : n->timeout; - g_queue_push_head(queue, n); - - wake_up(); -} - -void history_push(notification *n) -{ - if (settings.history_length > 0 && history->length >= settings.history_length) { - notification *to_free = g_queue_pop_head(history); - notification_free(to_free); - } - - if (!n->history_ignore) - g_queue_push_tail(history, n); -} +static gboolean run(void *data); void wake_up(void) { run(NULL); } -static int get_sleep_time(void) +static gboolean run(void *data) { + queues_check_timeouts(x_is_idle()); + queues_update(); - if (settings.show_age_threshold == 0) { - /* we need to update every second */ - return 1; - } + static gint64 next_timeout = 0; - bool have_ttl = false; - int min_ttl = 0; - int max_age = 0; - for (GList *iter = g_queue_peek_head_link(displayed); iter; - iter = iter->next) { - notification *n = iter->data; - - max_age = MAX(max_age, notification_get_age(n)); - int ttl = notification_get_ttl(n); - if (ttl >= 0) { - if (have_ttl) { - min_ttl = MIN(min_ttl, ttl); - } else { - min_ttl = ttl; - have_ttl = true; - } - } + if (!xctx.visible && queues_length_displayed() > 0) { + x_win_show(); } - int min_timeout; - int show_age_timeout = settings.show_age_threshold - max_age; - - if (show_age_timeout < 1) { - return 1; - } - - if (!have_ttl) { - min_timeout = show_age_timeout; - } else { - min_timeout = MIN(show_age_timeout, min_ttl); - } - - /* show_age_timeout might be negative */ - if (min_timeout < 1) { - return 1; - } else { - return min_timeout; - } -} - -gboolean run(void *data) -{ - update_lists(); - static int timeout_cnt = 0; - static int next_timeout = 0; - - if (data) { - timeout_cnt--; - } - - if (displayed->length > 0 && !xctx.visible && !pause_display) { - x_win_show(); - } - - if (xctx.visible && (pause_display || displayed->length == 0)) { - x_win_hide(); + if (xctx.visible && queues_length_displayed() == 0) { + x_win_hide(); } if (xctx.visible) { - x_win_draw(); + x_win_draw(); } if (xctx.visible) { - int now = time(NULL); - int sleep = get_sleep_time(); - - if (sleep > 0) { - int timeout_at = now + sleep; - if (timeout_cnt == 0 || timeout_at < next_timeout) { - g_timeout_add_seconds(sleep, run, mainloop); + 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; - timeout_cnt++; } } } - /* always return false to delete timers */ - return false; + /* 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) { - pause_display = true; + queues_pause_on(); wake_up(); return G_SOURCE_CONTINUE; @@ -267,7 +99,7 @@ gboolean unpause_signal(gpointer data) { - pause_display = false; + queues_pause_off(); wake_up(); return G_SOURCE_CONTINUE; @@ -280,19 +112,11 @@ return G_SOURCE_CONTINUE; } -static void teardown_notification(gpointer data) -{ - notification *n = data; - notification_free(n); -} - static void teardown(void) { regex_teardown(); - g_queue_free_full(history, teardown_notification); - g_queue_free_full(displayed, teardown_notification); - g_queue_free_full(queue, teardown_notification); + teardown_queues(); x_free(); } @@ -300,9 +124,7 @@ int dunst_main(int argc, char *argv[]) { - history = g_queue_new(); - displayed = g_queue_new(); - queue = g_queue_new(); + queues_init(); cmdline_load(argc, argv); @@ -328,14 +150,17 @@ 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 = 0; - n->timeout = 10; + n->progress = -1; + n->timeout = 10 * G_USEC_PER_SEC; n->markup = MARKUP_NO; - n->urgency = LOW; - notification_init(n, 0); + 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); @@ -390,8 +215,8 @@ void usage(int exit_status) { - fputs("usage:\n", stderr); - char *us = cmdline_create_usage(); + puts("usage:\n"); + const char *us = cmdline_create_usage(); puts(us); exit(exit_status); } diff -Nru dunst-1.2.0/src/dunst.h dunst-1.3.0/src/dunst.h --- dunst-1.2.0/src/dunst.h 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/dunst.h 2018-01-05 18:56:16.000000000 +0000 @@ -4,42 +4,29 @@ #define DUNST_DUNST_H #include -#include #include +#include #include "notification.h" -#define ERR(msg) printf("%s : %d\n", (msg), __LINE__) #define PERR(msg, errnum) printf("(%d) %s : %s\n", __LINE__, (msg), (strerror(errnum))) -#define LENGTH(X) (sizeof X / sizeof X[0]) #define ColLast 3 #define ColFrame 2 #define ColFG 1 #define ColBG 0 -extern GQueue *queue; -extern GQueue *displayed; -extern GQueue *history; extern GSList *rules; -extern bool pause_display; -extern const char *color_strings[3][3]; +extern const char *colors[3][3]; -/* return id of notification */ -gboolean run(void *data); void wake_up(void); int dunst_main(int argc, char *argv[]); -void check_timeouts(void); -void history_pop(void); -void history_push(notification *n); void usage(int exit_status); -void move_all_to_history(void); void print_version(void); char *extract_urls(const char *str); void context_menu(void); -void wake_up(void); void pause_signal_handler(int sig); #endif diff -Nru dunst-1.2.0/src/markup.c dunst-1.3.0/src/markup.c --- dunst-1.2.0/src/markup.c 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/markup.c 2018-01-05 18:56:16.000000000 +0000 @@ -1,10 +1,11 @@ /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ -#define _GNU_SOURCE #include "markup.h" #include #include +#include +#include #include "settings.h" #include "utils.h" @@ -46,6 +47,178 @@ } /* + * 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 '[] ' + */ +void markup_strip_a(char **str, char **urls) +{ + char *tag1 = NULL; + + if (urls) + *urls = NULL; + + while ((tag1 = strstr(*str, ""); + char *tag2 = strstr(tag1, ""); + + // 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(""); + 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("") : 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 '[] ' + */ +void markup_strip_img(char **str, char **urls) +{ + const char *start = *str; + + if (urls) + *urls = NULL; + + while ((start = strstr(*str, ""); + + // 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 @@ -97,6 +270,8 @@ break; case MARKUP_FULL: str = markup_br2nl(str); + markup_strip_a(&str, NULL); + markup_strip_img(&str, NULL); break; } diff -Nru dunst-1.2.0/src/markup.h dunst-1.3.0/src/markup.h --- dunst-1.2.0/src/markup.h 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/markup.h 2018-01-05 18:56:16.000000000 +0000 @@ -5,6 +5,10 @@ #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 diff -Nru dunst-1.2.0/src/menu.c dunst-1.3.0/src/menu.c --- dunst-1.2.0/src/menu.c 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/menu.c 2018-01-05 18:56:16.000000000 +0000 @@ -1,6 +1,5 @@ /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ -#define _GNU_SOURCE #include "menu.h" #include @@ -15,8 +14,9 @@ #include "dbus.h" #include "dunst.h" -#include "settings.h" #include "notification.h" +#include "queues.h" +#include "settings.h" #include "utils.h" static bool is_initialized = false; @@ -43,8 +43,7 @@ void regex_teardown(void) { - if (is_initialized) - { + if (is_initialized) { regfree(&cregex); is_initialized = false; } @@ -94,11 +93,16 @@ * Open url in browser. * */ -void open_browser(const char *in) { - // remove prefix and test url - char *url = extract_urls(in); - if (!url) - return; +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(); @@ -137,7 +141,7 @@ int appname_len = strlen(appname_begin) - 1; // remove ] int action_len = strlen(action) - appname_len - 3; // remove space, [, ] - for (GList * iter = g_queue_peek_head_link(displayed); iter; + 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) { @@ -157,7 +161,7 @@ } if (invoked && action_identifier) { - action_invoked(invoked, action_identifier); + signal_action_invoked(invoked, action_identifier); } } @@ -189,7 +193,7 @@ } char *dmenu_input = NULL; - for (GList * iter = g_queue_peek_head_link(displayed); iter; + for (const GList *iter = queues_get_displayed(); iter; iter = iter->next) { notification *n = iter->data; diff -Nru dunst-1.2.0/src/notification.c dunst-1.3.0/src/notification.c --- dunst-1.2.0/src/notification.c 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/notification.c 2018-01-05 18:56:16.000000000 +0000 @@ -1,35 +1,37 @@ /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ -#define _GNU_SOURCE #include "notification.h" #include #include #include +#include #include #include #include #include #include -#include #include #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" -int next_notification_id = 1; +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) +void notification_print(notification *n) { printf("{\n"); printf("\tappname: '%s'\n", n->appname); @@ -38,29 +40,32 @@ 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: %d\n", n->timeout); - printf("\turgency: %d\n", n->urgency); + 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->color_strings[ColFG]); - printf("\tbg: %s\n", n->color_strings[ColBG]); - printf("\tframe: %s\n", n->color_strings[ColFrame]); + 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) { - printf("\turls\n"); + char *urls = string_replace_all("\n", "\t\t\n", g_strdup(n->urls)); + printf("\turls:\n"); printf("\t{\n"); - printf("%s\n", n->urls); + 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], + printf("\t\t[%s,%s]\n", n->actions->actions[i], n->actions->actions[i + 1]); } - printf("actions_dmenu: %s\n", n->actions->dmenu_str); - printf("\t]\n"); + printf("\t}\n"); + printf("\tactions_dmenu: %s\n", n->actions->dmenu_str); } printf("\tscript: %s\n", n->script); printf("}\n"); @@ -70,7 +75,7 @@ * Run the script associated with the * given notification. */ -void notification_run_script(notification * n) +void notification_run_script(notification *n) { if (!n->script || strlen(n->script) < 1) return; @@ -80,21 +85,7 @@ char *body = n->body ? n->body : ""; char *icon = n->icon ? n->icon : ""; - char *urgency; - switch (n->urgency) { - case LOW: - urgency = "LOW"; - break; - case NORM: - urgency = "NORMAL"; - break; - case CRIT: - urgency = "CRITICAL"; - break; - default: - urgency = "NORMAL"; - break; - } + const char *urgency = notification_urgency_to_string(n->urgency); int pid1 = fork(); @@ -106,7 +97,8 @@ if (pid2) { exit(0); } else { - int ret = execlp(n->script, n->script, + int ret = execlp(n->script, + n->script, appname, summary, body, @@ -122,6 +114,25 @@ } /* + * 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. */ @@ -164,9 +175,36 @@ } /* + * 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) +void notification_free(notification *n) { assert(n != NULL); g_free(n->appname); @@ -176,289 +214,262 @@ 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]); - if (n->text_to_render) - g_free(n->text_to_render); - - if (n->urls) - g_free(n->urls); - - if (n->actions) { - g_strfreev(n->actions->actions); - g_free(n->actions->dmenu_str); - } - - if (n->raw_icon) { - if (n->raw_icon->data) - g_free(n->raw_icon->data); - g_free(n->raw_icon); - } + actions_free(n->actions); + rawimage_free(n->raw_icon); g_free(n); } /* - * Replace all occurrences of "needle" with a quoted "replacement", - * according to the markup settings. + * 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. + * */ -char *notification_replace_format(const char *needle, const char *replacement, - char *haystack, enum markup_mode markup_mode) { - char* tmp; - char* ret; - - tmp = markup_transform(g_strdup(replacement), markup_mode); - ret = string_replace_all(needle, tmp, haystack); - g_free(tmp); - - return ret; -} - -char *notification_extract_markup_urls(char **str_ptr) { - char *start, *end, *replace_buf, *str, *urls = NULL, *url, *index_buf; - int linkno = 1; - - str = *str_ptr; - while ((start = strstr(str, ""); - if (end != NULL) { - replace_buf = g_strndup(start, end - start + 1); - url = extract_urls(replace_buf); - if (url != NULL) { - str = string_replace(replace_buf, "[", str); - - index_buf = g_strdup_printf("[#%d]", linkno++); - if (urls == NULL) { - urls = g_strconcat(index_buf, " ", url, NULL); - } else { - char *tmp = urls; - urls = g_strconcat(tmp, "\n", index_buf, " ", url, NULL); - g_free(tmp); - } - - index_buf[0] = ' '; - str = string_replace("", index_buf, str); - g_free(index_buf); - g_free(url); - } else { - str = string_replace(replace_buf, "", str); - str = string_replace("", "", str); - } - g_free(replace_buf); - } else { - break; - } - } - *str_ptr = str; - return urls; +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 everything to NULL, - * this function is guaranteed to return a valid pointer. + * 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) { - return g_malloc0(sizeof(notification)); -} + notification *n = g_malloc0(sizeof(notification)); -void notification_init_defaults(notification *n) -{ - assert(n != NULL); - if(n->appname == NULL) n->appname = g_strdup("unknown"); - if(n->summary == NULL) n->summary = g_strdup(""); - if(n->body == NULL) n->body = g_strdup(""); - if(n->category == NULL) n->category = g_strdup(""); + /* 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; } /* - * Initialize the given notification and add it to - * the queue. Replace notification with id if id > 0. + * Sanitize values of notification, apply all matching rules + * and generate derived fields. * - * n should be a pointer to a notification allocated with - * notification_create, it is undefined behaviour to pass a notification - * allocated some other way. + * @n: the notification to sanitize */ -int notification_init(notification * n, int id) +void notification_init(notification *n) { - assert(n != NULL); - - //Prevent undefined behaviour by initialising required fields - notification_init_defaults(n); - - if (strcmp("DUNST_COMMAND_PAUSE", n->summary) == 0) { - pause_display = true; - return 0; - } - - if (strcmp("DUNST_COMMAND_RESUME", n->summary) == 0) { - pause_display = false; - return 0; - } - - n->script = NULL; - n->text_to_render = NULL; + /* 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]); - n->format = settings.format; + /* 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); - n->urls = notification_extract_markup_urls(&(n->body)); + /* 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)); - n->msg = notification_replace_format("%a", n->appname, n->msg, - MARKUP_NO); - n->msg = notification_replace_format("%s", n->summary, n->msg, - n->markup); - n->msg = notification_replace_format("%b", n->body, n->msg, - n->markup); - - if (n->icon) { - n->msg = notification_replace_format("%I", basename(n->icon), - n->msg, MARKUP_NO); - n->msg = notification_replace_format("%i", n->icon, - n->msg, MARKUP_NO); - } - if (n->progress) { - char pg[10]; - sprintf(pg, "[%3d%%]", n->progress - 1); - n->msg = string_replace_all("%p", pg, n->msg); - sprintf(pg, "%d", n->progress - 1); - n->msg = string_replace_all("%n", pg, n->msg); - } else { - n->msg = string_replace_all("%p", "", n->msg); - n->msg = string_replace_all("%n", "", n->msg); + /* 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); + 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; } +} - if (n->icon != NULL && strlen(n->icon) <= 0) { - g_free(n->icon); - n->icon = NULL; - } - - if (n->raw_icon == NULL && n->icon == NULL) { - n->icon = g_strdup(settings.icons[n->urgency]); - } - - if (id == 0) { - n->id = ++next_notification_id; - } else { - notification_close_by_id(id, -1); - n->id = id; - } - - n->dup_count = 0; - - /* check if n is a duplicate */ - if (settings.stack_duplicates) { - for (GList * iter = g_queue_peek_head_link(queue); iter; - iter = iter->next) { - notification *orig = iter->data; - if (notification_is_duplicate(orig, n)) { - /* If the progress differs this was probably intended to replace the notification - * but notify-send was used. So don't increment dup_count in this case - */ - if (orig->progress == n->progress) { - orig->dup_count++; - } else { - orig->progress = n->progress; - } - /* notifications that differ only in progress hints should be expected equal, - * but we want the latest message, with the latest hint value - */ - g_free(orig->msg); - orig->msg = g_strdup(n->msg); - notification_free(n); - wake_up(); - return orig->id; - } - } - - for (GList * iter = g_queue_peek_head_link(displayed); iter; - iter = iter->next) { - notification *orig = iter->data; - if (notification_is_duplicate(orig, n)) { - /* notifications that differ only in progress hints should be expected equal, - * but we want the latest message, with the latest hint value - */ - g_free(orig->msg); - orig->msg = g_strdup(n->msg); - /* If the progress differs this was probably intended to replace the notification - * but notify-send was used. So don't increment dup_count in this case - */ - if (orig->progress == n->progress) { - orig->dup_count++; - } else { - orig->progress = n->progress; - } - orig->start = time(NULL); - notification_free(n); - wake_up(); - return orig->id; - } - } - } - - /* urgency > CRIT -> array out of range */ - n->urgency = n->urgency > CRIT ? CRIT : n->urgency; - - if (!n->color_strings[ColFG]) { - n->color_strings[ColFG] = xctx.color_strings[ColFG][n->urgency]; - } - - if (!n->color_strings[ColBG]) { - n->color_strings[ColBG] = xctx.color_strings[ColBG][n->urgency]; - } - - if (!n->color_strings[ColFrame]) { - n->color_strings[ColFrame] = xctx.color_strings[ColFrame][n->urgency]; - } - - n->timeout = - n->timeout == -1 ? settings.timeouts[n->urgency] : n->timeout; - n->start = 0; +static void notification_extract_urls(notification *n) +{ + g_clear_pointer(&n->urls, g_free); - n->timestamp = time(NULL); + char *urls_in = string_append(g_strdup(n->summary), n->body, " "); - n->redisplayed = false; + 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->first_render = true; - - if (strlen(n->msg) == 0) { - notification_close(n, 2); - if (settings.always_run_script) { - notification_run_script(n); - } - printf("skipping notification: %s %s\n", n->body, n->summary); - } else { - g_queue_insert_sorted(queue, n, notification_cmp_data, NULL); - } + 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"); - char *tmp = g_strconcat(n->summary, " ", n->body, NULL); - - char *tmp_urls = extract_urls(tmp); - if (tmp_urls != NULL) { - if (n->urls != NULL) { - n->urls = string_append(n->urls, tmp_urls, "\n"); - g_free(tmp_urls); - } else { - n->urls = tmp_urls; - } - } + 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) { - n->actions->dmenu_str = NULL; + 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 @@ -471,73 +482,11 @@ } } } - - g_free(tmp); - - if (settings.print_notifications) - notification_print(n); - - return n->id; -} - -/* - * Close the notification that has id. - * - * reasons: - * -1 -> notification is a replacement, no NotificationClosed signal emitted - * 1 -> the notification expired - * 2 -> the notification was dismissed by the user_data - * 3 -> The notification was closed by a call to CloseNotification - */ -int notification_close_by_id(int id, int reason) -{ - notification *target = NULL; - - for (GList * iter = g_queue_peek_head_link(displayed); iter; - iter = iter->next) { - notification *n = iter->data; - if (n->id == id) { - g_queue_remove(displayed, n); - history_push(n); - target = n; - break; - } - } - - for (GList * iter = g_queue_peek_head_link(queue); iter; - iter = iter->next) { - notification *n = iter->data; - if (n->id == id) { - g_queue_remove(queue, n); - history_push(n); - target = n; - break; - } - } - - if (reason > 0 && reason < 4 && target != NULL) { - notification_closed(target, reason); - } - - wake_up(); - return reason; -} - -/* - * Close the given notification. SEE notification_close_by_id. - */ -int notification_close(notification * n, int reason) -{ - assert(n != NULL); - return notification_close_by_id(n->id, reason); } void notification_update_text_to_render(notification *n) { - if (n->text_to_render) { - g_free(n->text_to_render); - n->text_to_render = NULL; - } + g_clear_pointer(&n->text_to_render, g_free); char *buf = NULL; @@ -561,26 +510,26 @@ } /* print age */ - int hours, minutes, seconds; - time_t t_delta = time(NULL) - n->timestamp; + 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 / 3600; - minutes = t_delta / 60 % 60; - seconds = t_delta % 60; + 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 (%dh %dm %ds old)", buf, hours, + g_strdup_printf("%s (%ldh %ldm %lds old)", buf, hours, minutes, seconds); } else if (minutes > 0) { new_buf = - g_strdup_printf("%s (%dm %ds old)", buf, minutes, + g_strdup_printf("%s (%ldm %lds old)", buf, minutes, seconds); } else { - new_buf = g_strdup_printf("%s (%ds old)", buf, seconds); + new_buf = g_strdup_printf("%s (%lds old)", buf, seconds); } g_free(buf); @@ -590,32 +539,21 @@ n->text_to_render = buf; } -int notification_get_ttl(notification *n) { - if (n->timeout == 0) { - return -1; - } else { - return n->timeout - (time(NULL) - n->start); - } -} - -int notification_get_age(notification *n) { - return time(NULL) - n->timestamp; -} - /* * 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) { +void notification_do_action(notification *n) +{ if (n->actions) { if (n->actions->count == 2) { - action_invoked(n, n->actions->actions[0]); + 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) { - action_invoked(n, n->actions->actions[i]); + signal_action_invoked(n, n->actions->actions[i]); return; } } diff -Nru dunst-1.2.0/src/notification.h dunst-1.3.0/src/notification.h --- dunst-1.2.0/src/notification.h 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/notification.h 2018-01-05 18:56:16.000000000 +0000 @@ -4,16 +4,20 @@ #include #include -#include #include "settings.h" -#define LOW 0 -#define NORM 1 -#define CRIT 2 - #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; @@ -31,51 +35,63 @@ } Actions; typedef struct _notification { + int id; + char *dbus_client; + char *appname; char *summary; char *body; - char *icon; - RawImage *raw_icon; - char *msg; /* formatted message */ char *category; - char *text_to_render; - const char *format; - char *dbus_client; - time_t start; - time_t timestamp; - int timeout; - int urgency; + 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? */ - int id; - int dup_count; + bool first_render; /* markup has been rendered before? */ + int dup_count; /* amount of duplicate notifications stacked onto this */ int displayed_height; - const char *color_strings[3]; - bool first_render; - int progress; /* percentage + 1, 0 to hide */ - int line_count; - int history_ignore; - const char *script; - char *urls; - Actions *actions; + /* 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); -int notification_init(notification * n, int id); -void notification_free(notification * n); -int notification_close_by_id(int id, int reason); +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); -int notification_close(notification * n, int reason); -void notification_print(notification * n); -char *notification_replace_format(const char *needle, const char *replacement, char *haystack, enum markup_mode markup); +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); -int notification_get_ttl(notification *n); -int notification_get_age(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: */ diff -Nru dunst-1.2.0/src/option_parser.c dunst-1.3.0/src/option_parser.c --- dunst-1.2.0/src/option_parser.c 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/option_parser.c 2018-01-05 18:56:16.000000000 +0000 @@ -1,6 +1,5 @@ /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ -#define _GNU_SOURCE #include "option_parser.h" #include @@ -25,24 +24,24 @@ static int section_count = 0; static section_t *sections; -static section_t *new_section(char *name); -static section_t *get_section(char *name); -static void add_entry(char *section_name, char *key, char *value); -static char *get_value(char *section, char *key); -static char *clean_value(char *value); +static 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(char *key, char *type, char *description); +static void cmdline_usage_append(const char *key, const char *type, const char *description); -static int cmdline_find_option(char *key); +static int cmdline_find_option(const char *key); -section_t *new_section(char *name) +section_t *new_section(const char *name) { for (int i = 0; i < section_count; i++) { - if(!strcmp(name, sections[i].name)) { + if (!strcmp(name, sections[i].name)) { die("Duplicated section in dunstrc detected.\n", -1); } } @@ -70,7 +69,7 @@ sections = NULL; } -section_t *get_section(char *name) +section_t *get_section(const char *name) { for (int i = 0; i < section_count; i++) { if (strcmp(sections[i].name, name) == 0) @@ -80,7 +79,7 @@ return NULL; } -void add_entry(char *section_name, char *key, char *value) +void add_entry(const char *section_name, const char *key, const char *value) { section_t *s = get_section(section_name); if (s == NULL) { @@ -94,7 +93,7 @@ s->entries[s->entry_count - 1].value = clean_value(value); } -char *get_value(char *section, char *key) +const char *get_value(const char *section, const char *key) { section_t *s = get_section(section); if (!s) { @@ -109,39 +108,56 @@ return NULL; } -char *ini_get_string(char *section, char *key, const char *def) +char *ini_get_path(const char *section, const char *key, const char *def) { - char *value = get_value(section, key); + 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; } -int ini_get_int(char *section, char *key, int def) +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) { - char *value = get_value(section, key); + const char *value = get_value(section, key); if (value == NULL) return def; else return atoi(value); } -double ini_get_double(char *section, char *key, double def) +double ini_get_double(const char *section, const char *key, double def) { - char *value = get_value(section, key); + const char *value = get_value(section, key); if (value == NULL) return def; else return atof(value); } -bool ini_is_set(char *ini_section, char *ini_key) +bool ini_is_set(const char *ini_section, const char *ini_key) { return get_value(ini_section, ini_key) != NULL; } -char *next_section(char *section) +const char *next_section(const char *section) { if (section_count == 0) return NULL; @@ -161,9 +177,9 @@ return NULL; } -int ini_get_bool(char *section, char *key, int def) +int ini_get_bool(const char *section, const char *key, int def) { - char *value = get_value(section, key); + const char *value = get_value(section, key); if (value == NULL) return def; else { @@ -186,7 +202,7 @@ } } -char *clean_value(char *value) +char *clean_value(const char *value) { char *s; @@ -199,10 +215,9 @@ s[strlen(s) - 1] = '\0'; return s; - } -int load_ini_file(FILE * fp) +int load_ini_file(FILE *fp) { if (!fp) return 1; @@ -232,8 +247,7 @@ *end = '\0'; - if (current_section) - g_free(current_section); + g_free(current_section); current_section = (g_strdup(start + 1)); new_section(current_section); continue; @@ -280,8 +294,7 @@ add_entry(current_section, key, value); } free(line); - if (current_section) - g_free(current_section); + g_free(current_section); return 0; } @@ -291,7 +304,7 @@ cmdline_argv = argv; } -int cmdline_find_option(char *key) +int cmdline_find_option(const char *key) { if (!key) { return -1; @@ -326,7 +339,7 @@ return -1; } -static char *cmdline_get_value(char *key) +static const char *cmdline_get_value(const char *key) { int idx = cmdline_find_option(key); if (idx < 0) { @@ -342,10 +355,10 @@ return cmdline_argv[idx + 1]; } -char *cmdline_get_string(char *key, const char *def, char *description) +char *cmdline_get_string(const char *key, const char *def, const char *description) { cmdline_usage_append(key, "string", description); - char *str = cmdline_get_value(key); + const char *str = cmdline_get_value(key); if (str) return g_strdup(str); @@ -355,10 +368,34 @@ return g_strdup(def); } -int cmdline_get_int(char *key, int def, char *description) +char *cmdline_get_path(const char *key, const char *def, const char *description) { - cmdline_usage_append(key, "double", description); - char *str = cmdline_get_value(key); + 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; @@ -366,54 +403,89 @@ return atoi(str); } -double cmdline_get_double(char *key, double def, char *description) +double cmdline_get_double(const char *key, double def, const char *description) { cmdline_usage_append(key, "double", description); - char *str = cmdline_get_value(key); + const char *str = cmdline_get_value(key); + if (str == NULL) return def; else return atof(str); } -int cmdline_get_bool(char *key, int def, char *description) +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(char *key) +bool cmdline_is_set(const char *key) { return cmdline_get_value(key) != NULL; } -char *option_get_string(char *ini_section, char *ini_key, char *cmdline_key, - const char *def, char *description) +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_string(cmdline_key, NULL, description); + 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(char *ini_section, char *ini_key, char *cmdline_key, int def, - char *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. */ - char *str = cmdline_get_value(cmdline_key); + 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 */ @@ -425,10 +497,13 @@ return val; } -double option_get_double(char *ini_section, char *ini_key, char *cmdline_key, - double def, char *description) +double option_get_double(const char *ini_section, + const char *ini_key, + const char *cmdline_key, + double def, + const char *description) { - char *str = cmdline_get_value(cmdline_key); + const char *str = cmdline_get_value(cmdline_key); double val = cmdline_get_double(cmdline_key, def, description); if (!str) @@ -437,8 +512,11 @@ return val; } -int option_get_bool(char *ini_section, char *ini_key, char *cmdline_key, - int def, char *description) +int option_get_bool(const char *ini_section, + const char *ini_key, + const char *cmdline_key, + int def, + const char *description) { int val = false; @@ -454,7 +532,7 @@ return ini_get_bool(ini_section, ini_key, def); } -void cmdline_usage_append(char *key, char *type, char *description) +void cmdline_usage_append(const char *key, const char *type, const char *description) { char *key_type; if (type && strlen(type) > 0) @@ -479,9 +557,9 @@ } -char *cmdline_create_usage(void) +const char *cmdline_create_usage(void) { - return g_strdup(usage_str); + return usage_str; } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.2.0/src/option_parser.h dunst-1.3.0/src/option_parser.h --- dunst-1.2.0/src/option_parser.h 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/option_parser.h 2018-01-05 18:56:16.000000000 +0000 @@ -2,40 +2,66 @@ #ifndef DUNST_OPTION_PARSER_H #define DUNST_OPTION_PARSER_H +#include #include #include int load_ini_file(FILE *); -char *ini_get_string(char *section, char *key, const char *def); -int ini_get_int(char *section, char *key, int def); -double ini_get_double(char *section, char *key, double def); -int ini_get_bool(char *section, char *key, int def); -bool ini_is_set(char *ini_section, char *ini_key); +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(char *key, const char *def, char *description); -int cmdline_get_int(char *key, int def, char *description); -double cmdline_get_double(char *key, double def, char *description); -int cmdline_get_bool(char *key, int def, char *description); -bool cmdline_is_set(char *key); -char *cmdline_create_usage(void); - -char *option_get_string(char *ini_section, char *ini_key, char *cmdline_key, - const char *def, char *description); -int option_get_int(char *ini_section, char *ini_key, char *cmdline_key, int def, - char *description); -double option_get_double(char *ini_section, char *ini_key, char *cmdline_key, - double def, char *description); -int option_get_bool(char *ini_section, char *ini_key, char *cmdline_key, - int def, char *description); +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 */ -char *next_section(char *section); +const char *next_section(const char *section); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.2.0/src/queues.c dunst-1.3.0/src/queues.c --- dunst-1.2.0/src/queues.c 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.3.0/src/queues.c 2018-01-05 18:56:16.000000000 +0000 @@ -0,0 +1,385 @@ +/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ + +#include "queues.h" + +#include +#include +#include +#include + +#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: */ diff -Nru dunst-1.2.0/src/queues.h dunst-1.3.0/src/queues.h --- dunst-1.2.0/src/queues.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.3.0/src/queues.h 2018-01-05 18:56:16.000000000 +0000 @@ -0,0 +1,133 @@ +/* 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: */ diff -Nru dunst-1.2.0/src/rules.c dunst-1.3.0/src/rules.c --- dunst-1.2.0/src/rules.c 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/rules.c 2018-01-05 18:56:16.000000000 +0000 @@ -10,25 +10,32 @@ /* * Apply rule to notification. */ -void rule_apply(rule_t * r, notification * n) +void rule_apply(rule_t *r, notification *n) { if (r->timeout != -1) n->timeout = r->timeout; - if (r->urgency != -1) + 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) { - if(n->icon) - g_free(n->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->fg) - n->color_strings[ColFG] = r->fg; - if (r->bg) - n->color_strings[ColBG] = r->bg; if (r->format) n->format = r->format; if (r->script) @@ -38,9 +45,9 @@ /* * Check all rules if they match n and apply. */ -void rule_apply_all(notification * n) +void rule_apply_all(notification *n) { - for (GSList * iter = rules; iter; iter = iter->next) { + for (GSList *iter = rules; iter; iter = iter->next) { rule_t *r = iter->data; if (rule_matches_notification(r, n)) { rule_apply(r, n); @@ -51,7 +58,7 @@ /* * Initialize rule with default values. */ -void rule_init(rule_t * r) +void rule_init(rule_t *r) { r->name = NULL; r->appname = NULL; @@ -59,12 +66,14 @@ r->body = NULL; r->icon = NULL; r->category = NULL; - r->msg_urgency = -1; + r->msg_urgency = URG_NONE; r->timeout = -1; - r->urgency = -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; @@ -73,13 +82,14 @@ /* * Check whether rule should be applied to n. */ -bool rule_matches_notification(rule_t * r, notification * 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->msg_urgency == -1 || r->msg_urgency == n->urgency)); + && (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: */ diff -Nru dunst-1.2.0/src/rules.h dunst-1.3.0/src/rules.h --- dunst-1.2.0/src/rules.h 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/rules.h 2018-01-05 18:56:16.000000000 +0000 @@ -19,10 +19,12 @@ int msg_urgency; /* actions */ - int timeout; - int urgency; + 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; @@ -32,10 +34,10 @@ 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); +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: */ diff -Nru dunst-1.2.0/src/settings.c dunst-1.3.0/src/settings.c --- dunst-1.2.0/src/settings.c 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/settings.c 2018-01-05 18:56:16.000000000 +0000 @@ -47,25 +47,24 @@ } } -static int ini_get_urgency(char *section, char *key, int def) +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 = LOW; + ret = URG_LOW; else if (strcmp(urg, "normal") == 0) - ret = NORM; + ret = URG_NORM; else if (strcmp(urg, "critical") == 0) - ret = CRIT; + ret = URG_CRIT; else fprintf(stderr, "unknown urgency: %s, ignoring\n", urg); } - if (urg) - g_free(urg); + g_free(urg); return ret; } @@ -79,7 +78,18 @@ xdgInitHandle(&xdg); if (cmdline_config_path != NULL) { - config_file = fopen(cmdline_config_path, "r"); + 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); @@ -95,6 +105,9 @@ } 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( @@ -111,22 +124,13 @@ settings.font = option_get_string( "global", - "font", "-font/-fn", font, + "font", "-font/-fn", defaults.font, "The font dunst should use." ); { - //If markup isn't set, fall back to allow_markup for backwards compatibility - if (ini_is_set("global", "markup") || cmdline_is_set("-markup")) { - char *c = option_get_string( - "global", - "markup", "-markup", markup, - "Specify how markup should be handled" - ); - - settings.markup = parse_markup_mode(c); - g_free(c); - } else if (ini_is_set("global", "allow_markup")) { + // Check if allow_markup set + if (ini_is_set("global", "allow_markup")) { bool allow_markup = option_get_bool( "global", "allow_markup", NULL, false, @@ -135,50 +139,86 @@ settings.markup = (allow_markup ? MARKUP_FULL : MARKUP_STRIP); fprintf(stderr, "Warning: 'allow_markup' is deprecated, please use 'markup' instead.\n"); - } else { - settings.markup = parse_markup_mode(markup); // None are set, parse the default value from config.h } + + 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", format, + "format", "-format", defaults.format, "The format template for the notifications" ); settings.sort = option_get_bool( "global", - "sort", "-sort", sort, + "sort", "-sort", defaults.sort, "Sort notifications by urgency and date?" ); settings.indicate_hidden = option_get_bool( "global", - "indicate_hidden", "-indicate_hidden", indicate_hidden, + "indicate_hidden", "-indicate_hidden", defaults.indicate_hidden, "Show how many notificaitons are hidden?" ); settings.word_wrap = option_get_bool( "global", - "word_wrap", "-word_wrap", word_wrap, + "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", ignore_newline, + "ignore_newline", "-ignore_newline", defaults.ignore_newline, "Ignore newline characters in notifications" ); - settings.idle_threshold = option_get_int( + settings.idle_threshold = option_get_time( "global", - "idle_threshold", "-idle_threshold", idle_threshold, + "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", monitor, + "monitor", "-mon/-monitor", defaults.monitor, "On which monitor should the notifications be displayed" ); @@ -197,37 +237,37 @@ settings.title = option_get_string( "global", - "title", "-t/-title", title, + "title", "-t/-title", defaults.title, "Define the title of windows spawned by dunst." ); settings.class = option_get_string( "global", - "class", "-c/-class", class, + "class", "-c/-class", defaults.class, "Define the class of windows spawned by dunst." ); settings.geom = option_get_string( "global", - "geometry", "-geom/-geometry", geom, + "geometry", "-geom/-geometry", defaults.geom, "Geometry for the window" ); settings.shrink = option_get_bool( "global", - "shrink", "-shrink", shrink, + "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", line_height, + "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", notification_height, + "notification_height", "-nh/-notification_height", defaults.notification_height, "Define height of the window" ); @@ -252,9 +292,9 @@ } } - settings.show_age_threshold = option_get_int( + settings.show_age_threshold = option_get_time( "global", - "show_age_threshold", "-show_age_threshold", show_age_threshold, + "show_age_threshold", "-show_age_threshold", defaults.show_age_threshold, "When should the age of the notification be displayed?" ); @@ -266,43 +306,43 @@ settings.sticky_history = option_get_bool( "global", - "sticky_history", "-sticky_history", sticky_history, + "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", history_length, + "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", show_indicators, + "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", separator_height, + "separator_height", "-sep_height/-separator_height", defaults.separator_height, "height of the separator line" ); settings.padding = option_get_int( "global", - "padding", "-padding", padding, + "padding", "-padding", defaults.padding, "Padding between text and separator" ); settings.h_padding = option_get_int( "global", - "horizontal_padding", "-horizontal_padding", h_padding, + "horizontal_padding", "-horizontal_padding", defaults.h_padding, "horizontal padding" ); settings.transparency = option_get_int( "global", - "transparency", "-transparency", transparency, + "transparency", "-transparency", defaults.transparency, "Transparency. range 0-100" ); @@ -324,8 +364,8 @@ settings.sep_color = CUSTOM; settings.sep_custom_color_str = g_strdup(c); } - g_free(c); } + g_free(c); } settings.stack_duplicates = option_get_bool( @@ -340,9 +380,9 @@ "print notification on startup" ); - settings.dmenu = option_get_string( + settings.dmenu = option_get_path( "global", - "dmenu", "-dmenu", dmenu, + "dmenu", "-dmenu", defaults.dmenu, "path to dmenu" ); @@ -357,9 +397,9 @@ } - settings.browser = option_get_string( + settings.browser = option_get_path( "global", - "browser", "-browser", browser, + "browser", "-browser", defaults.browser, "path to browser" ); @@ -386,63 +426,74 @@ settings.max_icon_size = option_get_int( "global", - "max_icon_size", "-max_icon_size", max_icon_size, + "max_icon_size", "-max_icon_size", defaults.max_icon_size, "Scale larger icons down to this size, set to 0 to disable" ); - settings.icon_folders = option_get_string( + // 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_folders", "-icon_folders", icon_folders, + "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("global", "frame_width")) { - settings.frame_width = option_get_int( - "global", - "frame_width", "-frame_width", frame_width, - "Width of frame around the window" - ); - } else { - if (ini_is_set("frame", "width")) { - fprintf(stderr, "Warning: The frame section is deprecated, width has been renamed to frame_width and moved to the global section.\n"); - } + if (ini_is_set("frame", "width")) { settings.frame_width = option_get_int( "frame", - "width", "-frame_width", frame_width, + "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"); } - if (ini_is_set("global", "frame_color")) { - settings.frame_color = option_get_string( - "global", - "frame_color", "-frame_color", frame_color, - "Color of the frame around the window" - ); - } else { - if (ini_is_set("frame", "color")) { - fprintf(stderr, "Warning: The frame section is deprecated, color has been renamed to frame_color 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", "-frame_color", frame_color, + "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", lowbgcolor, + "background", "-lb", defaults.lowbgcolor, "Background color for notifications with low urgency" ); settings.lowfgcolor = option_get_string( "urgency_low", - "foreground", "-lf", lowfgcolor, + "foreground", "-lf", defaults.lowfgcolor, "Foreground color for notifications with low urgency" ); @@ -452,27 +503,27 @@ "Frame color for notifications with low urgency" ); - settings.timeouts[LOW] = option_get_int( + settings.timeouts[URG_LOW] = option_get_time( "urgency_low", - "timeout", "-lto", timeouts[LOW], + "timeout", "-lto", defaults.timeouts[URG_LOW], "Timeout for notifications with low urgency" ); - settings.icons[LOW] = option_get_string( + settings.icons[URG_LOW] = option_get_string( "urgency_low", - "icon", "-li", icons[LOW], + "icon", "-li", defaults.icons[URG_LOW], "Icon for notifications with low urgency" ); settings.normbgcolor = option_get_string( "urgency_normal", - "background", "-nb", normbgcolor, + "background", "-nb", defaults.normbgcolor, "Background color for notifications with normal urgency" ); settings.normfgcolor = option_get_string( "urgency_normal", - "foreground", "-nf", normfgcolor, + "foreground", "-nf", defaults.normfgcolor, "Foreground color for notifications with normal urgency" ); @@ -482,27 +533,27 @@ "Frame color for notifications with normal urgency" ); - settings.timeouts[NORM] = option_get_int( + settings.timeouts[URG_NORM] = option_get_time( "urgency_normal", - "timeout", "-nto", timeouts[NORM], + "timeout", "-nto", defaults.timeouts[URG_NORM], "Timeout for notifications with normal urgency" ); - settings.icons[NORM] = option_get_string( + settings.icons[URG_NORM] = option_get_string( "urgency_normal", - "icon", "-ni", icons[NORM], + "icon", "-ni", defaults.icons[URG_NORM], "Icon for notifications with normal urgency" ); settings.critbgcolor = option_get_string( "urgency_critical", - "background", "-cb", critbgcolor, + "background", "-cb", defaults.critbgcolor, "Background color for notifications with critical urgency" ); settings.critfgcolor = option_get_string( "urgency_critical", - "foreground", "-cf", critfgcolor, + "foreground", "-cf", defaults.critfgcolor, "Foreground color for notifications with ciritical urgency" ); @@ -512,39 +563,39 @@ "Frame color for notifications with critical urgency" ); - settings.timeouts[CRIT] = option_get_int( + settings.timeouts[URG_CRIT] = option_get_time( "urgency_critical", - "timeout", "-cto", timeouts[CRIT], + "timeout", "-cto", defaults.timeouts[URG_CRIT], "Timeout for notifications with critical urgency" ); - settings.icons[CRIT] = option_get_string( + settings.icons[URG_CRIT] = option_get_string( "urgency_critical", - "icon", "-ci", icons[CRIT], + "icon", "-ci", defaults.icons[URG_CRIT], "Icon for notifications with critical urgency" ); settings.close_ks.str = option_get_string( "shortcuts", - "close", "-key", close_ks.str, + "close", "-key", defaults.close_ks.str, "Shortcut for closing one notification" ); settings.close_all_ks.str = option_get_string( "shortcuts", - "close_all", "-all_key", close_all_ks.str, + "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", history_ks.str, + "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", context_ks.str, + "context", "-context_key", defaults.context_ks.str, "Shortcut for context menu" ); @@ -560,11 +611,11 @@ ); /* push hardcoded default rules into rules list */ - for (int i = 0; i < LENGTH(default_rules); i++) { + for (int i = 0; i < G_N_ELEMENTS(default_rules); i++) { rules = g_slist_insert(rules, &(default_rules[i]), -1); } - char *cur_section = NULL; + const char *cur_section = NULL; for (;;) { cur_section = next_section(cur_section); if (!cur_section) @@ -580,7 +631,7 @@ /* check for existing rule with same name */ rule_t *r = NULL; - for (GSList * iter = rules; iter; iter = iter->next) { + for (GSList *iter = rules; iter; iter = iter->next) { rule_t *match = iter->data; if (match->name && strcmp(match->name, cur_section) == 0) @@ -599,7 +650,7 @@ 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_int(cur_section, "timeout", r->timeout); + r->timeout = ini_get_time(cur_section, "timeout", r->timeout); { char *c = ini_get_string( @@ -620,7 +671,9 @@ 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->script = ini_get_string(cur_section, "script", NULL); + 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 diff -Nru dunst-1.2.0/src/settings.h dunst-1.3.0/src/settings.h --- dunst-1.2.0/src/settings.h 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/settings.h 2018-01-05 18:56:16.000000000 +0000 @@ -7,6 +7,7 @@ #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 }; @@ -29,7 +30,7 @@ char *lowfgcolor; char *lowframecolor; char *format; - int timeouts[3]; + gint64 timeouts[3]; char *icons[3]; unsigned int transparency; char *geom; @@ -38,13 +39,14 @@ int shrink; int sort; int indicate_hidden; - int idle_threshold; - int show_age_threshold; + 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; @@ -53,7 +55,6 @@ int h_padding; enum separator_color sep_color; char *sep_custom_color_str; - char *sep_color_str; int frame_width; char *frame_color; int startup_notification; @@ -63,7 +64,7 @@ char *browser; enum icon_position_t icon_position; int max_icon_size; - char *icon_folders; + char *icon_path; enum follow_mode f_mode; bool always_run_script; keyboard_shortcut close_ks; diff -Nru dunst-1.2.0/src/utils.c dunst-1.3.0/src/utils.c --- dunst-1.2.0/src/utils.c 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/utils.c 2018-01-05 18:56:16.000000000 +0000 @@ -1,15 +1,17 @@ /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ -#define _GNU_SOURCE #include "utils.h" +#include +#include #include #include #include #include -char *string_replace_char(char needle, char replacement, char *haystack) { +char *string_replace_char(char needle, char replacement, char *haystack) +{ char *current = haystack; - while ((current = strchr (current, needle)) != NULL) + while ((current = strchr(current, needle)) != NULL) *current++ = replacement; return haystack; } @@ -27,13 +29,13 @@ tmp = buf; } else { tmp = g_malloc(size); + memcpy(tmp, buf, pos); } - 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) { + if (tmp != buf) { g_free(buf); } @@ -51,8 +53,7 @@ return string_replace_at(haystack, (start - haystack), strlen(needle), replacement); } -char *string_replace_all(const char *needle, const char *replacement, - char *haystack) +char *string_replace_all(const char *needle, const char *replacement, char *haystack) { char *start; int needle_pos; @@ -76,8 +77,12 @@ char *string_append(char *a, const char *b, const char *sep) { - if (!a) + if (!a || *a == '\0') { + g_free(a); return g_strdup(b); + } + if (!b || *b == '\0') + return a; char *new; if (!sep) @@ -105,6 +110,60 @@ 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); diff -Nru dunst-1.2.0/src/utils.h dunst-1.3.0/src/utils.h --- dunst-1.2.0/src/utils.h 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/utils.h 2018-01-05 18:56:16.000000000 +0000 @@ -2,16 +2,19 @@ #ifndef DUNST_UTILS_H #define DUNST_UTILS_H -/* replace all occurences of the character needle with the character replacement in haystack */ +#include + +/* 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); +char *string_replace_all(const char *needle, const char *replacement, char *haystack); + +/* replace characters with at position of the string */ +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_replace(const char *needle, const char *replacement, char *haystack); char *string_append(char *a, const char *b, const char *sep); @@ -21,5 +24,11 @@ /* 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: */ diff -Nru dunst-1.2.0/src/x11/screen.c dunst-1.3.0/src/x11/screen.c --- dunst-1.2.0/src/x11/screen.c 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/x11/screen.c 2018-01-05 18:56:16.000000000 +0000 @@ -4,9 +4,9 @@ #include #include #include -#include -#include #include +#include +#include #include #include #include @@ -23,6 +23,11 @@ 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(); @@ -59,7 +64,8 @@ return dpi; } -void init_screens() { +void init_screens() +{ if (!settings.force_xinerama) { randr_init(); randr_update(); @@ -80,8 +86,6 @@ screens_len = n; } -int randr_event_base = 0; - void randr_init() { int randr_error_base = 0; @@ -89,23 +93,36 @@ 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() { - int n; + 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 (m == NULL || n == -1) { - fprintf(stderr, "(RandR) Could not get screen info, falling back to single monitor mode\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; @@ -141,6 +158,7 @@ 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; @@ -149,7 +167,6 @@ XFree(info); } - void screen_update_fallback() { alloc_screen_ar(1); @@ -162,7 +179,6 @@ screens[0].dim.w = DisplayWidth(xctx.dpy, screen); screens[0].dim.h = DisplayHeight(xctx.dpy, screen); - } /* @@ -195,9 +211,15 @@ unsigned int dummy_ui; Window dummy_win; - XQueryPointer(xctx.dpy, root, &dummy_win, - &dummy_win, &x, &y, &dummy, - &dummy, &dummy_ui); + XQueryPointer(xctx.dpy, + root, + &dummy_win, + &dummy_win, + &x, + &y, + &dummy, + &dummy, + &dummy_ui); } if (settings.f_mode == FOLLOW_KEYBOARD) { @@ -263,11 +285,20 @@ 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); + 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; + focused = *(Window *)prop_return; XFree(prop_return); } @@ -290,7 +321,7 @@ return dunst_follow_errored; } -static int FollowXErrorHandler(Display * display, XErrorEvent * e) +static int FollowXErrorHandler(Display *display, XErrorEvent *e) { dunst_follow_errored = true; char err_buf[BUFSIZ]; diff -Nru dunst-1.2.0/src/x11/x.c dunst-1.3.0/src/x11/x.c --- dunst-1.2.0/src/x11/x.c 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/x11/x.c 2018-01-05 18:56:16.000000000 +0000 @@ -6,27 +6,29 @@ #include #include #include -#include +#include #include -#include +#include #include #include #include #include -#include #include #include #include #include +#include #include #include #include #include #include +#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" @@ -115,24 +117,23 @@ 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; + 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; } } @@ -156,16 +157,16 @@ 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; + 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); @@ -186,7 +187,8 @@ return (xctx.geometry.mask & WidthValue && xctx.geometry.w == 0); } -static bool does_file_exist(const char *filename){ +static bool does_file_exist(const char *filename) +{ return (access(filename, F_OK) != -1); } @@ -195,9 +197,10 @@ return (access(filename, R_OK) != -1); } -const char *get_filename_ext(const char *filename) { +const char *get_filename_ext(const char *filename) +{ const char *dot = strrchr(filename, '.'); - if(!dot || dot == filename) return ""; + if (!dot || dot == filename) return ""; return dot + 1; } @@ -284,21 +287,39 @@ return dim; } -static cairo_surface_t *gdk_pixbuf_to_cairo_surface(const GdkPixbuf *pixbuf) +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; - cairo_t *cr; - cairo_format_t format; - double width, height; - - format = gdk_pixbuf_get_has_alpha(pixbuf) ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24; - width = gdk_pixbuf_get_width(pixbuf); - height = gdk_pixbuf_get_height(pixbuf); - icon_surface = cairo_image_surface_create(format, width, height); - cr = cairo_create(icon_surface); - gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0); - cairo_paint(cr); - cairo_destroy(cr); + 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; } @@ -329,18 +350,19 @@ if (icon_path[0] == '/' || icon_path[0] == '~') { pixbuf = get_pixbuf_from_file(icon_path); } - /* search in icon_folders */ + /* search in icon_path */ if (pixbuf == NULL) { - char *start = settings.icon_folders, + char *start = settings.icon_path, *end, *current_folder, *maybe_icon_path; do { end = strchr(start, ':'); - if (end == NULL) end = strchr(settings.icon_folders, '\0'); /* end = end of string */ + 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); } @@ -403,13 +425,30 @@ cl->l = create_layout(c); if (!settings.word_wrap) { - pango_layout_set_ellipsize(cl->l, PANGO_ELLIPSIZE_MIDDLE); + 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) { + 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); } @@ -446,9 +485,9 @@ cl->icon = NULL; } - cl->fg = x_string_to_color_t(n->color_strings[ColFG]); - cl->bg = x_string_to_color_t(n->color_strings[ColBG]); - cl->frame = x_string_to_color_t(n->color_strings[ColFrame]); + 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; @@ -469,11 +508,11 @@ 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; + 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) @@ -513,11 +552,11 @@ { GSList *layouts = NULL; - int qlen = g_list_length(g_queue_peek_head_link(queue)); + int qlen = queues_length_waiting(); bool xmore_is_needed = qlen > 0 && settings.indicate_hidden; notification *last = NULL; - for (GList *iter = g_queue_peek_head_link(displayed); + for (const GList *iter = queues_get_displayed(); iter; iter = iter->next) { notification *n = iter->data; @@ -591,9 +630,9 @@ bool use_padding = settings.notification_height <= (2 * settings.padding) + h; if (use_padding) - dim.y += settings.padding; + dim.y += settings.padding; else - dim.y += (int) (ceil(bg_half_height) - pango_offset); + 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); @@ -607,9 +646,9 @@ pango_cairo_update_layout(c, cl->l); pango_cairo_show_layout(c, cl->l); if (use_padding) - dim.y += h + settings.padding; + dim.y += h + settings.padding; else - dim.y += (int) (floor(bg_half_height) + pango_offset); + 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); @@ -627,7 +666,7 @@ } cairo_move_to(c, settings.h_padding, dim.y); - if (cl->icon) { + 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, @@ -639,9 +678,9 @@ 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); + 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; @@ -679,10 +718,11 @@ 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) @@ -690,6 +730,7 @@ 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; @@ -711,7 +752,6 @@ XResizeWindow(xctx.dpy, xctx.win, width, height); } - xctx.window_dim.x = x; xctx.window_dim.y = y; xctx.window_dim.h = height; @@ -722,8 +762,14 @@ { 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); + XChangeProperty(xctx.dpy, + win, + _NET_WM_WINDOW_OPACITY, + XA_CARDINAL, + 32, + PropModeReplace, + (unsigned char *)&opacity, + 1L); } /* @@ -733,7 +779,7 @@ { static KeyCode nl = 0; KeySym sym = 0; - XModifierKeymap * map = XGetModifierMapping(xctx.dpy); + XModifierKeymap *map = XGetModifierMapping(xctx.dpy); if (!nl) nl = XKeysymToKeycode(xctx.dpy, XStringToKeysym("Num_Lock")); @@ -745,30 +791,30 @@ * 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; + 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; } } } @@ -783,7 +829,7 @@ * Helper function to use glib's mainloop mechanic * with Xlib */ -gboolean x_mainloop_fd_prepare(GSource * source, gint * timeout) +gboolean x_mainloop_fd_prepare(GSource *source, gint *timeout) { if (timeout) *timeout = -1; @@ -796,7 +842,7 @@ * Helper function to use glib's mainloop mechanic * with Xlib */ -gboolean x_mainloop_fd_check(GSource * source) +gboolean x_mainloop_fd_check(GSource *source) { return XPending(xctx.dpy) > 0; } @@ -804,8 +850,7 @@ /* * Main Dispatcher for XEvents */ -gboolean x_mainloop_fd_dispatch(GSource * source, GSourceFunc callback, - gpointer user_data) +gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { XEvent ev; unsigned int state; @@ -823,6 +868,7 @@ case ButtonRelease: if (ev.xbutton.window == xctx.win) { x_handle_click(ev); + wake_up(); } break; case KeyPress: @@ -833,36 +879,47 @@ && XLookupKeysym(&ev.xkey, 0) == settings.close_ks.sym && settings.close_ks.mask == state) { - if (displayed) { - notification *n = g_queue_peek_head(displayed); - if (n) - notification_close(n, 2); + 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) { - history_pop(); + 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) { - move_all_to_history(); + 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: - case PropertyNotify: 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; @@ -881,7 +938,7 @@ if (settings.idle_threshold == 0) { return false; } - return xctx.screensaver_info->idle / 1000 > settings.idle_threshold; + return xctx.screensaver_info->idle > settings.idle_threshold / 1000; } /* TODO move to x_mainloop_* */ @@ -891,7 +948,7 @@ static void x_handle_click(XEvent ev) { if (ev.xbutton.button == Button3) { - move_all_to_history(); + queues_history_push_all(); return; } @@ -900,7 +957,7 @@ int y = settings.separator_height; notification *n = NULL; int first = true; - for (GList * iter = g_queue_peek_head_link(displayed); iter; + 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) @@ -913,7 +970,7 @@ if (n) { if (ev.xbutton.button == Button1) - notification_close(n, 2); + queues_notification_close(n, REASON_USER); else notification_do_action(n); } @@ -956,26 +1013,26 @@ x_shortcut_grab(&settings.context_ks); x_shortcut_ungrab(&settings.context_ks); - xctx.color_strings[ColFG][LOW] = settings.lowfgcolor; - xctx.color_strings[ColFG][NORM] = settings.normfgcolor; - xctx.color_strings[ColFG][CRIT] = settings.critfgcolor; - - xctx.color_strings[ColBG][LOW] = settings.lowbgcolor; - xctx.color_strings[ColBG][NORM] = settings.normbgcolor; - xctx.color_strings[ColBG][CRIT] = settings.critbgcolor; + 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.color_strings[ColFrame][LOW] = settings.lowframecolor; + xctx.colors[ColFrame][URG_LOW] = settings.lowframecolor; else - xctx.color_strings[ColFrame][LOW] = settings.frame_color; + xctx.colors[ColFrame][URG_LOW] = settings.frame_color; if (settings.normframecolor) - xctx.color_strings[ColFrame][NORM] = settings.normframecolor; + xctx.colors[ColFrame][URG_NORM] = settings.normframecolor; else - xctx.color_strings[ColFrame][NORM] = settings.frame_color; + xctx.colors[ColFrame][URG_NORM] = settings.frame_color; if (settings.critframecolor) - xctx.color_strings[ColFrame][CRIT] = settings.critframecolor; + xctx.colors[ColFrame][URG_CRIT] = settings.critframecolor; else - xctx.color_strings[ColFrame][CRIT] = settings.frame_color; + xctx.colors[ColFrame][URG_CRIT] = settings.frame_color; /* parse and set xctx.geometry and monitor position */ if (settings.geom[0] == '-') { @@ -989,13 +1046,23 @@ &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) @@ -1009,9 +1076,14 @@ 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)); + 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"; @@ -1026,8 +1098,14 @@ 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); + XChangeProperty(xctx.dpy, + win, + net_wm_window_type, + XA_ATOM, + 32, + PropModeReplace, + (unsigned char *)data, + 2L); /* set state above */ Atom net_wm_state = @@ -1063,13 +1141,18 @@ 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); + 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 = @@ -1090,7 +1173,7 @@ void x_win_show(void) { /* window is already mapped or there's nothing to show */ - if (xctx.visible || g_queue_is_empty(displayed)) { + if (xctx.visible || queues_length_displayed() == 0) { return; } @@ -1099,8 +1182,16 @@ x_shortcut_grab(&settings.context_ks); x_shortcut_setup_error_handler(); - XGrabButton(xctx.dpy, AnyButton, AnyModifier, xctx.win, false, - BUTTONMASK, GrabModeAsync, GrabModeSync, None, None); + 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"); } @@ -1145,13 +1236,12 @@ 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) +static int GrabXErrorHandler(Display *display, XErrorEvent *e) { dunst_grab_errored = true; char err_buf[BUFSIZ]; @@ -1191,7 +1281,7 @@ /* * Grab the given keyboard shortcut. */ -int x_shortcut_grab(keyboard_shortcut * ks) +int x_shortcut_grab(keyboard_shortcut *ks) { if (!ks->is_valid) return 1; @@ -1201,10 +1291,20 @@ 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); + 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()) { @@ -1218,7 +1318,7 @@ /* * Ungrab the given keyboard shortcut. */ -void x_shortcut_ungrab(keyboard_shortcut * ks) +void x_shortcut_ungrab(keyboard_shortcut *ks) { Window root; root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)); @@ -1231,7 +1331,7 @@ /* * Initialize the keyboard shortcut. */ -void x_shortcut_init(keyboard_shortcut * ks) +void x_shortcut_init(keyboard_shortcut *ks) { if (ks == NULL || ks->str == NULL) return; diff -Nru dunst-1.2.0/src/x11/x.h dunst-1.3.0/src/x11/x.h --- dunst-1.2.0/src/x11/x.h 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/src/x11/x.h 2018-01-05 18:56:16.000000000 +0000 @@ -10,7 +10,7 @@ #include "screen.h" -#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) +#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) #define FONT_HEIGHT_BORDER 2 #define DEFFONT "Monospace-11" @@ -25,10 +25,11 @@ typedef struct _xctx { Atom utf8; Display *dpy; + int cur_screen; Window win; bool visible; dimension_t geometry; - const char *color_strings[3][3]; + const char *colors[3][3]; XScreenSaverInfo *screensaver_info; dimension_t window_dim; unsigned long sep_custom_col; @@ -48,9 +49,9 @@ 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); +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 */ @@ -58,10 +59,10 @@ void x_setup(void); void x_free(void); -gboolean x_mainloop_fd_dispatch(GSource * source, GSourceFunc callback, +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); +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: */ diff -Nru dunst-1.2.0/test/data/dunstrc.default dunst-1.3.0/test/data/dunstrc.default --- dunst-1.2.0/test/data/dunstrc.default 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/test/data/dunstrc.default 2018-01-05 18:56:16.000000000 +0000 @@ -80,6 +80,7 @@ # 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 ### @@ -122,6 +123,7 @@ # %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 = "%s\n%b" @@ -138,6 +140,10 @@ # 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 @@ -159,7 +165,7 @@ max_icon_size = 32 # Paths to default icons. - icon_folders = /usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/ + icon_path = /usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/ ### History ### diff -Nru dunst-1.2.0/test/data/test-ini dunst-1.3.0/test/data/test-ini --- dunst-1.2.0/test/data/test-ini 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/test/data/test-ini 2018-01-05 18:56:16.000000000 +0000 @@ -23,6 +23,9 @@ quoted = "A quoted string" quoted_with_quotes = "A string "with quotes"" +[path] + expand_tilde = ~/.path/to/tilde + [int] simple = 5 negative = -10 diff -Nru dunst-1.2.0/test/markup.c dunst-1.3.0/test/markup.c --- dunst-1.2.0/test/markup.c 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/test/markup.c 2018-01-05 18:56:16.000000000 +0000 @@ -45,12 +45,108 @@ ASSERT_STR_EQ("foo bar baz", (ptr=markup_transform(g_strdup("foo
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("\"foo
bar\nbaz"), MARKUP_FULL))); + g_free(ptr); + ASSERT_STR_EQ("test ", (ptr=markup_transform(g_strdup("test \"foo image"), MARKUP_FULL))); + g_free(ptr); + ASSERT_STR_EQ("bar baz", (ptr=markup_transform(g_strdup("bar 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, "valid link", "valid link", "[valid] https://url.com"); + RUN_TESTp(helper_markup_strip_a, "valid link", "valid link", "[valid] "); + RUN_TESTp(helper_markup_strip_a, "valid link", "valid link", NULL); + RUN_TESTp(helper_markup_strip_a, "valid link", "valid link", "[valid link] https://url.com"); + + RUN_TESTp(helper_markup_strip_a, " link", " link", NULL); + RUN_TESTp(helper_markup_strip_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", "v [image] img", NULL); + RUN_TESTp(helper_markup_strip_img, "v \"valid\" img", "v valid img", NULL); + RUN_TESTp(helper_markup_strip_img, "v img", "v [image] img", "[image] url.com"); + + RUN_TESTp(helper_markup_strip_img, "v \"valid\" img", "v valid img", "[valid] url.com"); + RUN_TESTp(helper_markup_strip_img, "v \"valid\" img", "v valid img", "[valid] url.com"); + RUN_TESTp(helper_markup_strip_img, "v \"valid\" img", "v valid img", "[valid] url.com"); + + RUN_TESTp(helper_markup_strip_img, "i \"invalid img", "i [image] img", "[image] https://url.com"); + RUN_TESTp(helper_markup_strip_img, "i \"broken\" img", "i broken img", NULL); + RUN_TESTp(helper_markup_strip_img, "i \"invalid img", "i [image] img", NULL); + + RUN_TESTp(helper_markup_strip_img, "i \"broken\" img", "i broken img", NULL); + RUN_TESTp(helper_markup_strip_img, "i \"invalid img", "i [image] img", "[image] url.com"); + RUN_TESTp(helper_markup_strip_img, "i \"invalid img", "i [image] img", NULL); + + RUN_TESTp(helper_markup_strip_img, "i \"invalid\"urgency = LOW; + b->urgency = URG_LOW; ASSERT_FALSE(notification_is_duplicate(a, b)); - b->urgency = NORM; + b->urgency = URG_NORM; ASSERT(notification_is_duplicate(a, b)); - b->urgency = CRIT; + b->urgency = URG_CRIT; ASSERT_FALSE(notification_is_duplicate(a, b)); PASS(); } -TEST test_notification_replace_format(void) +TEST test_notification_replace_single_field(void) { char *str = g_malloc(128 * sizeof(char)); - - strcpy(str, "Testing format replacement"); - ASSERT_STR_EQ("Testing text replacement", (str = notification_replace_format("format", "text", str, MARKUP_FULL))); + char *substr = NULL; strcpy(str, "Markup %a preserved"); - ASSERT_STR_EQ("Markup and & is preserved", (str = notification_replace_format("%a", "and & is", str, MARKUP_FULL))); + substr = strchr(str, '%'); + notification_replace_single_field(&str, &substr, "and & is", MARKUP_FULL); + ASSERT_STR_EQ("Markup and & is preserved", str); + ASSERT_EQ(26, substr - str); strcpy(str, "Markup %a escaped"); - ASSERT_STR_EQ("Markup and & <i>is</i> escaped", (str = notification_replace_format("%a", "and & is", str, MARKUP_NO))); + substr = strchr(str, '%'); + notification_replace_single_field(&str, &substr, "and & is", MARKUP_NO); + ASSERT_STR_EQ("Markup and & <i>is</i> escaped", str); + ASSERT_EQ(38, substr - str); strcpy(str, "Markup %a"); - ASSERT_STR_EQ("Markup is removed and & escaped", (str = notification_replace_format("%a", "is removed and & escaped", str, MARKUP_STRIP))); + substr = strchr(str, '%'); + notification_replace_single_field(&str, &substr, "is removed and & escaped", MARKUP_STRIP); + ASSERT_STR_EQ("Markup is removed and & escaped", str); + ASSERT_EQ(35, substr - str); g_free(str); PASS(); @@ -100,7 +107,7 @@ a->summary = "Summary"; a->body = "Body"; a->icon = "Icon"; - a->urgency = NORM; + a->urgency = URG_NORM; notification *b = notification_create(); memcpy(b, a, sizeof(*b)); @@ -112,7 +119,7 @@ g_free(a); g_free(b); - RUN_TEST(test_notification_replace_format); + RUN_TEST(test_notification_replace_single_field); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.2.0/test/option_parser.c dunst-1.3.0/test/option_parser.c --- dunst-1.2.0/test/option_parser.c 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/test/option_parser.c 2018-01-05 18:56:16.000000000 +0000 @@ -7,9 +7,10 @@ TEST test_next_section(void) { - char *section = NULL; + 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(); @@ -59,6 +60,27 @@ 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"; @@ -86,6 +108,25 @@ 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; @@ -132,7 +173,7 @@ 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"); - char *usage = cmdline_create_usage(); + 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")); @@ -141,7 +182,6 @@ ASSERT(strstr(usage, "A double to test usage creation")); ASSERT(strstr(usage, "-msgbool/-mb")); ASSERT(strstr(usage, "A bool to test usage creation")); - free(usage); PASS(); } @@ -165,6 +205,38 @@ 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"; @@ -215,11 +287,13 @@ 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; @@ -227,12 +301,14 @@ 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); diff -Nru dunst-1.2.0/test/utils.c dunst-1.3.0/test/utils.c --- dunst-1.2.0/test/utils.c 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/test/utils.c 2018-01-05 18:56:16.000000000 +0000 @@ -1,6 +1,8 @@ #include "greatest.h" #include "src/utils.h" +#include + TEST test_string_replace_char(void) { char *text = malloc(128 * sizeof(char)); @@ -70,7 +72,35 @@ TEST test_string_append(void) { - SKIP(); //TODO: Implement this + 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(); } @@ -102,6 +132,46 @@ 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); @@ -109,5 +179,7 @@ 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: */ diff -Nru dunst-1.2.0/.travis.yml dunst-1.3.0/.travis.yml --- dunst-1.2.0/.travis.yml 2017-07-12 08:22:04.000000000 +0000 +++ dunst-1.3.0/.travis.yml 2018-01-05 18:56:16.000000000 +0000 @@ -1,31 +1,32 @@ addons: 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 - - libgtk2.0-dev + - 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 -script: make && make test +before_install: + - pip install --user cpp-coveralls +script: + - CFLAGS="-fprofile-arcs -ftest-coverage -Werror" make all dunstify test-valgrind + - coveralls compiler: - - gcc - - clang -env: - - MULTIMON=xrandr - - MULTIMON=xinerama - - MULTIMON=none + - gcc + - clang notifications: - irc: - channels: - - "chat.freenode.net#dunst" - on_success: change - on_failure: always + irc: + channels: + - "chat.freenode.net#dunst" + on_success: change + on_failure: always diff -Nru dunst-1.2.0/.valgrind.suppressions dunst-1.3.0/.valgrind.suppressions --- dunst-1.2.0/.valgrind.suppressions 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.3.0/.valgrind.suppressions 2018-01-05 18:56:16.000000000 +0000 @@ -0,0 +1,10 @@ +{ + xdgBaseDir_leak + # see https://github.com/devnev/libxdg-basedir/pull/6 + Memcheck:Leak + fun:malloc + ... + fun:xdgInitHandle + ... + fun:main +}