pax_global_header00006660000000000000000000000064137062630460014521gustar00rootroot0000000000000052 comment=52d67616f1dcd9d4201de3f8096cbc2c09dbf1dd dunst-1.5.0/000077500000000000000000000000001370626304600126615ustar00rootroot00000000000000dunst-1.5.0/.github/000077500000000000000000000000001370626304600142215ustar00rootroot00000000000000dunst-1.5.0/.github/ISSUE_TEMPLATE.md000066400000000000000000000011431370626304600167250ustar00rootroot00000000000000 ### Installation info - Version: `` - Install type: `` - Distro and version: ` ` dunst-1.5.0/.github/workflows/000077500000000000000000000000001370626304600162565ustar00rootroot00000000000000dunst-1.5.0/.github/workflows/main.yml000066400000000000000000000041321370626304600177250ustar00rootroot00000000000000name: main on: push: branches: - master pull_request: branches: - master jobs: build: strategy: matrix: CC: - clang - gcc distro: - alpine - archlinux - debian-stretch - fedora - ubuntu-xenial - ubuntu-bionic env: CC: ${{ matrix.CC }} steps: - uses: actions/checkout@v2 with: # Clone the whole branch, we have to fetch tags later fetch-depth: 0 # Fetch tags to determine proper version number inside git - name: fetch tags run: git fetch --tags # We cannot pull tags with old distros, since there is no `.git`. See below. if: "! (matrix.distro == 'ubuntu-bionic' || matrix.distro == 'ubuntu-xenial' || matrix.distro == 'debian-stretch')" # The Github checkout Action doesn't support distros with git older than 2.18 # With git<2.18 it downloads the code via API and does not clone it via git :facepalm: # To succeed the tests, we have to manually replace the VERSION macro - name: fix version number for old distros run: 'sed -i "s/1.4.1-non-git/1.4.1-ci-oldgit-$GITHUB_SHA/" Makefile' if: " (matrix.distro == 'ubuntu-bionic' || matrix.distro == 'ubuntu-xenial' || matrix.distro == 'debian-stretch')" - name: build run: make -j all dunstify test/test - name: test run: make -j test - name: installation run: ./test/test-install.sh - name: valgrind memleaks run: | make clean make -j test-valgrind - name: coverage run: | make clean make -j test-coverage - name: Generate coverage report run: lcov -c -d . -o lcov.info if: "matrix.CC == 'gcc'" - name: Upload coverage to Codecov uses: codecov/codecov-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} flags: unittests name: ${{ matrix.distro }}-${{ matrix.CC }} fail_ci_if_error: true if: "matrix.CC == 'gcc'" runs-on: ubuntu-latest container: image: dunst/ci:${{ matrix.distro }} dunst-1.5.0/.gitignore000066400000000000000000000002741370626304600146540ustar00rootroot00000000000000*.o *.d *.gcda *.gcno *.gcov /lcov.info core vgcore.* /docs/*.1 /docs/internal/coverage /docs/internal/html /dunst /dunstify /dunst.systemd.service /org.knopwob.dunst.service /test/test dunst-1.5.0/.valgrind.suppressions000066400000000000000000000031241370626304600172440ustar00rootroot00000000000000# Ignore musls' weird error { musl_alpine_libc Memcheck:Free fun:free obj:/lib/ld-musl-x86_64.so.1 } # rsvg_error_handle_close got fixed in # - GNOME/librsvg@7bf1014 # (2018-11-12, first tags: v2.45.0, v2.44.9) # but the release has to seep into the distros { rsvg_error_handle_close Memcheck:Leak match-leak-kinds: definite fun:malloc fun:g_malloc fun:g_slice_alloc fun:g_error_new_valist fun:g_set_error obj:*/librsvg-2.so* fun:rsvg_handle_close obj:*/loaders/libpixbufloader-svg.so fun:gdk_pixbuf_loader_close fun:gdk_pixbuf_get_file_info fun:get_pixbuf_from_file ... } # same as above, but as occurs in CI environment { rsvg_error_handle_close2 Memcheck:Leak match-leak-kinds: definite fun:malloc fun:g_malloc fun:g_slice_alloc fun:g_error_new_valist fun:g_set_error obj:*/librsvg-2.so* obj:*/librsvg-2.so* obj:*/loaders/libpixbufloader-svg.so obj:*/libgdk_pixbuf-2.0.so* fun:gdk_pixbuf_loader_close fun:gdk_pixbuf_get_file_info fun:get_pixbuf_from_file ... } # rsvg_error_writehandler got fixed in # - GNOME/librsvg@7b4cc9b # (2018-11-12, first tags: v2.45.0, v2.44.9) # but the release has to seep into the distros { rsvg_error_writehandler Memcheck:Leak match-leak-kinds: definite fun:malloc fun:g_malloc fun:g_slice_alloc fun:g_error_new_valist fun:g_set_error obj:*/librsvg-2.so* fun:rsvg_handle_write obj:*/loaders/libpixbufloader-svg.so obj:*/libgdk_pixbuf-2.0.so* fun:gdk_pixbuf_loader_close fun:gdk_pixbuf_get_file_info fun:get_pixbuf_from_file ... } dunst-1.5.0/AUTHORS000066400000000000000000000001761370626304600137350ustar00rootroot00000000000000Sascha Kruse (http://github.com/knopwob) contributors: See `git shortlog` for a list of contributors and their contributions dunst-1.5.0/CHANGELOG.md000066400000000000000000000216621370626304600145010ustar00rootroot00000000000000# Dunst changelog ## 1.5.0 - 2020-07-23 ### Added - `min_icon_size` option to automatically scale up icons to a desired value (#674) - `vertical_alignment` option to control the text/icon alignment within the notification (#684) - Ability to configure multiple actions for each mouse event (#705) - `dunstctl` command line control client (#651) - RGBA support for all color strings (#717) - Ability to run multiple scripts for each notification - `ignore_dbusclose` setting (#732) ### Changed - `dunstify` notification client is now installed by default (#701) - Keyboard follow mode falls back to the monitor with the mouse if no window has keyboard focus (#708) ### Fixed - Overflow when setting a >=40 minute timeout (#646) - Unset configuration options not falling back to default values (#649) - Crash when `$HOME` environment variable is unset (#693) - Lack of antialiasing with round corners enabled (#713) ## 1.4.1 - 2019-07-03 ### Fixed - `max_icon_size` not working with dynamic width (#614) - Failure to parse color strings with trailing comments in the config (#626) - Negative width in geometry being ignored (#628) - Incorrect handling of the argument terminator `--` in dunstify - Crash when changing DPI while no notifications are displayed (#630) - Fullscreen status change not being detected in some cases (#613) ## 1.4.0 - 2019-03-30 ### Added - Add support to override `frame_color` via rules (#498) - Support for round corners (#420) - Ability to reference `$HOME` in icon paths with `~/` (#520) - Support to customize the mouse bindings (#530) - Command to toggle pause status (#535) - Ability to automatically replace similar notifications (like volume changes) via `stack_tag` (#552) - Comparison of raw icons for duplicate notifications (#571) - Introduce new desktop-entry filter (#470) - `fullscreen` rule to hide notifications when a fullscreen window is active (#472) - Added `skip_display` rule option to skip initial notification display, and include the notification in the history. (#590) ### Fixed - Notification age not counting the time while the computer was suspended (#492) - Dunst losing always-on-top status on a window manager restart (#160) - Xpm icons not being recognized - When new notifications arrive, but display is full, important notifications don't have to wait for a timeout in a displayed notification (#541) - Dunst hanging while the context menu is open (#456) - Having & inside a notification breaking markup (#546) - ` more` notifications don't occupy space anymore, if there is only a single notification waiting to get displayed. The notification gets displayed directly (#467) - Segfault when comparing icon name with a notification with a raw icon (#536) - Icon size can no longer be larger than the notification when a fixed width is specified (#540) ### Changed - Transient notifications no longer skip history by default (#508) - The notification summary no longer accepts markup (#497) ### Removed - Dependency on libxdg-basedir (#550) ## 1.3.2 - 2018-05-06 ### Fixed - Crash when trying to load an invalid or corrupt icon (#512) ## 1.3.1 - 2018-01-30 ### Fixed - Race condition resulting in the service files being empty (#488) ## 1.3.0 - 2018-01-05 ### Added - `ellipsize` option to control how long lines should be ellipsized when `word_wrap` is set to `false` (#374) - A beginning tilde of a path is now expanded to the home of the current user (#351) - The image-path hint is now respected, as GApplications send their icon only via this link (#447) - The (legacy) image\_data hint is now respected (#353) - If dunst can't acquire the DBus name, dunst prints the PID of the process holding the name (#458 #460) - Increased accuracy of timeouts by using microseconds internally (#379 #291) - Support for specifying timeout values in milliseconds, minutes, hours, or days. (#379) - Support for HTML img tags (via context menu) (#428) ### Fixed - `new_icon` rule being ignored on notifications that had a raw icon (#423) - Format strings being replaced recursively in some cases (#322 #365) - DBus related memory leaks (#397) - Crash on X11 servers with RandR support less than 1.5. (#413 #364) - Silently reading the default config file, if `-conf` did not specify a valid file (#452) - Notification window flickering when a notification is replaced (#320 #415) - Inaccurate timeout in some cases (#291 #379) ### Changed - Transient hints are now handled (#343 #310) An additional rule option (`match_transient` and `set_transient`) is added to optionally reset the transient setting - HTML links are now referred to by their text in the context menu rather than numbers (#428) - `icon_folders` setting renamed to `icon_path` (#170) - `config.def.h` and `config.h` got merged (#371) - The dependency on GTK3+ has been removed. Instead of GTK3+, dunst now requires gdk-pixbuf which had been a transient dependency before. (#334 #376) - The `_GNU_SOURCE` macros had been removed to make dunst portable to nonGNU systems (#403) - Internal refactorings of the notification queue handling. (#411) - Dunst does now install the systemd and dbus service files into their proper location given by pkg-config. Use `SERVICEDIR_(DBUS|SYSTEMD)` params to overwrite them. (#463) ## 1.2.0 - 2017-07-12 ### Added - `always_run_script` option to run script even if a notification is suppressed - Support for more icon file types - Support for raw icons - `hide_duplicate_count` option to hide the number of duplicate notifications - Support for per-urgency frame colours - `markup` setting for more fine-grained control over how markup is handled - `history_ignore` rule action to exclude a notification from being added to the history - Support for setting the dpi value dunst will use for font rendering via the `Xft.dpi` X resource - Experimental support for per-monitor dpi calculation - `max_icon_size` option to scale down icons if they exceed a certain size - Middle click on notifications can be used to trigger actions - Systemd service file, installed by default - `%n` format flag for getting progress value without any extra characters ### Changed - Text and icons are now centred vertically - Notifications aren't considered duplicate if urgency or icons differ - The maximum length of a notification is limited to 5000 characters - The frame width and color settings were moved to the global section as `frame_width` and `frame_color` respectively - Dropped Xinerama in favour of RandR, Xinerama can be enabled with the `-force_xinerama` option if needed ### Deprecated - `allow_markup` is deprecated with `markup` as its replacement - The urgency specific command line flags have been deprecated with no replacement, respond to issue #328 on the bug tracker if you depend on them ### Fixed - Infinite loop if there are 2 configuration file sections with the same name - URLs with dashes and underscores in them are now parsed properly - Many memory leaks - Category based rules were applied without actually matching - dmenu command not parsing quoted arguments correctly - Icon alignment with dynamic width - Issue when loading configuration files with very long lines - '\n' is no longer expanded to a newline inside notification text - Notification window wasn't redrawn if obscured on systems without a compositor - `ignore_newline` now works regardless of the markup setting - dmenu process being left as a zombie if no option was selected - Crash when opening urls parsed from `` tags ## 1.1.0 - 2014-07-29 - fix nasty memory leak - icon support (still work in progress) - fix issue where keybindings aren't working when numlock is activated ## 1.0.0 - 2013-04-15 - use pango/cairo as drawing backend - make use of pangos ability to parse markup - support for actions via context menu - indicator for actions/urls found - use blocking I/O. No more waking up the CPU multiple times per second to check for new dbus messages ## 0.5.0 - 2013-01-26 - new default dunstrc - frames for window - trigger scripts on matching notifications - context menu for urls (using dmenu) - pause and resume function - use own code for ini parsing (this removes inih) - progress hints ## 0.4.0 - 2012-09-27 - separator between notifications - word wrap long lines - real transparance - bouncing text (alternative to word_wrap) - new option for line height - better multihead support - don't die when keybindings can't be grabbed - bugfix: forgetting geometry - (optional) static configuration ## 0.3.1 - 2012-08-02 - fix -mon option ## 0.3.0 - 2012-07-30 - full support for Desktop Notification Specification (mandatory parts) - option to select monitor on which notifications are shown - follow focus - oneline mode - text alignment - show age of notifications - sticky history - filter duplicate messages - keybinding to close all notifications - new way to specify keybindings - cleanup / bugfixes etc. - added dunst.service ## 0.2.0 - 2012-06-26 - introduction of dunstrc - removed static configuration via config.h - don't timeout when user is idle - xft-support - history (a.k.a. redisplay old notifications) dunst-1.5.0/HACKING.md000066400000000000000000000043571370626304600142600ustar00rootroot00000000000000# Important notes on the code **You can generate an internal overview with doxygen. For this, use `make doc-doxygen` and you'll find an internal overview of all functions and symbols in `docs/internal/html`.** # Comments - Comment system is held similar to JavaDoc - Use `@param` to describe all input parameters - Use `@return` to describe the output value - Use `@retval` to describe special return values (like `NULL`) - Documentation comments should start with a double star (`/**`) - Append `()` to function names and prepend variables with `#` to properly reference them in the docs - Add comments to all functions and methods - Markdown inside the comments is allowed and also desired - Add the comments to the prototype. Doxygen will merge the protoype and implementation documentation anyways. Except for **static** methods, add the documentation header to the implementation and *not to the prototype*. - Member documentation should happen with `/**<` and should span to the right side of the member ## Log messages ### Messages - Keep your message in common format: `: ` - If you have to write text, single quote values in your sentence. ### Levels For logging, there are printf-like macros `LOG_(E|C|W|M|I|D)`. - `LOG_E` (ERROR): - All messages, which lead to immediate abort and are caused by a programming error. The program needs patching and the error is not user recoverable. - e.g.: Switching over an enum, `LOG_E` would go into the default case. - `LOG_C` (CRITICAL): - The program cannot continue to work. It is used in the wrong manner or some outer conditions are not met. - e.g.: `-config` parameter value is unreadable file - `LOG_W` (WARNING): - Something is not in shape, but it's recoverable. - e.g.: A value is not parsable in the config file, which will default. - `LOG_M` (MESSAGE): - Important info, which informs about the state. - e.g.: An empty notification does get removed immediately. - `LOG_I` (INFO): - Mostly unneccessary info, but important to debug (as the user) some use cases. - e.g.: print the notification contents after arriving - `LOG_D` (DEBUG): - Only important during development or tracing some bugs (as the developer). dunst-1.5.0/LICENSE000066400000000000000000000030161370626304600136660ustar00rootroot00000000000000Copyright © 2013, Sascha Kruse and contributors All rights reserved. All files (unless otherwise noted) are licensed under the BSD license: Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Sascha Kruse nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY Sascha Kruse ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Sascha Kruse BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. dunst-1.5.0/Makefile000066400000000000000000000136701370626304600143300ustar00rootroot00000000000000# dunst - Notification-daemon # See LICENSE file for copyright and license details. include config.mk VERSION := "1.5.0 (2020-07-23)" ifneq ($(wildcard ./.git/),) VERSION := $(shell ${GIT} describe --tags) endif ifeq (,${SYSTEMD}) # Check for systemctl to avoid discrepancies on systems, where # systemd is installed, but systemd.pc is in another package systemctl := $(shell command -v ${SYSTEMCTL} >/dev/null && echo systemctl) ifeq (systemctl,${systemctl}) SYSTEMD := 1 else SYSTEMD := 0 endif endif SERVICEDIR_DBUS ?= $(shell $(PKG_CONFIG) dbus-1 --variable=session_bus_services_dir) SERVICEDIR_DBUS := ${SERVICEDIR_DBUS} ifeq (,${SERVICEDIR_DBUS}) $(error "Failed to query $(PKG_CONFIG) for package 'dbus-1'!") endif ifneq (0,${SYSTEMD}) SERVICEDIR_SYSTEMD ?= $(shell $(PKG_CONFIG) systemd --variable=systemduserunitdir) SERVICEDIR_SYSTEMD := ${SERVICEDIR_SYSTEMD} ifeq (,${SERVICEDIR_SYSTEMD}) $(error "Failed to query $(PKG_CONFIG) for package 'systemd'!") endif endif LIBS := $(shell $(PKG_CONFIG) --libs ${pkg_config_packs}) INCS := $(shell $(PKG_CONFIG) --cflags ${pkg_config_packs}) ifneq (clean, $(MAKECMDGOALS)) ifeq ($(and $(INCS),$(LIBS)),) $(error "$(PKG_CONFIG) failed!") endif endif CFLAGS := ${DEFAULT_CPPFLAGS} ${CPPFLAGS} ${DEFAULT_CFLAGS} ${CFLAGS} ${INCS} -MMD -MP LDFLAGS := ${DEFAULT_LDFLAGS} ${LDFLAGS} ${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) DEPS := ${SRC:.c=.d} ${TEST_SRC:.c=.d} .PHONY: all debug all: doc dunst dunstify service debug: CFLAGS += ${CPPFLAGS_DEBUG} ${CFLAGS_DEBUG} debug: LDFLAGS += ${LDFLAGS_DEBUG} debug: CPPFLAGS += ${CPPFLAGS_DEBUG} debug: all -include $(DEPS) ${OBJ} ${TEST_OBJ}: Makefile config.mk %.o: %.c ${CC} -o $@ -c $< ${CFLAGS} dunst: ${OBJ} main.o ${CC} -o ${@} ${OBJ} main.o ${CFLAGS} ${LDFLAGS} dunstify: dunstify.o ${CC} -o ${@} dunstify.o ${CFLAGS} ${LDFLAGS} .PHONY: test test-valgrind test-coverage test: test/test clean-coverage-run ./test/test -v test-valgrind: test/test ${VALGRIND} \ --suppressions=.valgrind.suppressions \ --leak-check=full \ --show-leak-kinds=definite \ --errors-for-leak-kinds=definite \ --num-callers=40 \ --error-exitcode=123 \ ./test/test -v test-coverage: CFLAGS += -fprofile-arcs -ftest-coverage -O0 test-coverage: test test-coverage-report: test-coverage mkdir -p docs/internal/coverage ${GCOVR} \ -r . \ --exclude=test \ --html \ --html-details \ -o docs/internal/coverage/index.html test/%.o: test/%.c src/%.c ${CC} -o $@ -c $< ${CFLAGS} test/test: ${OBJ} ${TEST_OBJ} ${CC} -o ${@} ${TEST_OBJ} $(filter-out ${TEST_OBJ:test/%=src/%},${OBJ}) ${CFLAGS} ${LDFLAGS} .PHONY: doc doc-doxygen doc: docs/dunst.1 docs/dunstctl.1 # Can't dedup this as we need to explicitly provide the name and title text to # pod2man :( docs/dunst.1: docs/dunst.pod ${POD2MAN} --name=dunst -c "Dunst Reference" --section=1 --release=${VERSION} $< > $@ docs/dunstctl.1: docs/dunstctl.pod ${POD2MAN} --name=dunstctl -c "dunstctl reference" --section=1 --release=${VERSION} $< > $@ doc-doxygen: ${DOXYGEN} docs/internal/Doxyfile .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-coverage clean-coverage-run clean: clean-dunst clean-dunstify clean-doc clean-tests clean-coverage clean-coverage-run clean-dunst: rm -f dunst ${OBJ} main.o main.d ${DEPS} rm -f org.knopwob.dunst.service rm -f dunst.systemd.service clean-dunstify: rm -f dunstify.o rm -f dunstify.d rm -f dunstify clean-doc: rm -f docs/dunst.1 rm -f docs/dunstctl.1 rm -fr docs/internal/html rm -fr docs/internal/coverage clean-tests: rm -f test/test test/*.o test/*.d clean-coverage: clean-coverage-run ${FIND} . -type f -name '*.gcno' -delete ${FIND} . -type f -name '*.gcna' -delete # Cleans the coverage data before every run to not double count any lines clean-coverage-run: ${FIND} . -type f -name '*.gcov' -delete ${FIND} . -type f -name '*.gcda' -delete .PHONY: install install-dunst install-dunstctl install-doc \ install-service install-service-dbus install-service-systemd \ uninstall uninstall-dunstctl \ uninstall-service uninstall-service-dbus uninstall-service-systemd install: install-dunst install-dunstctl install-doc install-service install-dunstify install-dunst: dunst doc install -Dm755 dunst ${DESTDIR}${BINDIR}/dunst install -Dm644 docs/dunst.1 ${DESTDIR}${MANPREFIX}/man1/dunst.1 install -Dm644 docs/dunstctl.1 ${DESTDIR}${MANPREFIX}/man1/dunstctl.1 install-dunstctl: dunstctl install -Dm755 dunstctl ${DESTDIR}${BINDIR}/dunstctl install-doc: install -Dm644 dunstrc ${DESTDIR}${DATADIR}/dunst/dunstrc 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: service-systemd install -Dm644 dunst.systemd.service ${DESTDIR}${SERVICEDIR_SYSTEMD}/dunst.service endif install-dunstify: dunstify install -Dm755 dunstify ${DESTDIR}${BINDIR}/dunstify uninstall: uninstall-service uninstall-dunstctl rm -f ${DESTDIR}${BINDIR}/dunst rm -f ${DESTDIR}${BINDIR}/dunstify rm -f ${DESTDIR}${MANPREFIX}/man1/dunst.1 rm -f ${DESTDIR}${MANPREFIX}/man1/dunstctl.1 rm -rf ${DESTDIR}${DATADIR}/dunst uninstall-dunstctl: rm -f ${DESTDIR}${BINDIR}/dunstctl uninstall-service: uninstall-service-dbus uninstall-service-dbus: rm -f ${DESTDIR}${SERVICEDIR_DBUS}/org.knopwob.dunst.service ifneq (0,${SYSTEMD}) uninstall-service: uninstall-service-systemd uninstall-service-systemd: rm -f ${DESTDIR}${SERVICEDIR_SYSTEMD}/dunst.service endif dunst-1.5.0/README.md000066400000000000000000000047261370626304600141510ustar00rootroot00000000000000[![main](https://github.com/dunst-project/dunst/workflows/main/badge.svg)](https://github.com/dunst-project/dunst/actions?query=workflow%3Amain) [![codecov](https://codecov.io/gh/dunst-project/dunst/branch/master/graph/badge.svg)](https://codecov.io/gh/dunst-project/dunst) ## Dunst * [Wiki][wiki] * [Description](#description) * [Compiling](#compiling) * [Copyright](#copyright) ## Description Dunst is a highly configurable and lightweight notification daemon. ## Installation ### Dependencies Dunst has a number of build dependencies that must be present before attempting configuration. The names are different depending on [distribution](https://github.com/dunst-project/dunst/wiki/Dependencies): - dbus - libxinerama - libxrandr - libxss - glib - pango/cairo - libgtk-3-dev - libnotify (for dunstify only) ### 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`) - `BINDIR=`: Set the `dunst` executable's path (Default: `${PREFIX}/bin`) - `DATADIR=`: Set the path for shared files. (Default: `${PREFIX}/share`) - `MANDIR=`: Set the prefix of the manpage. (Default: `${DATADIR}/man`) - `SYSTEMD=(0|1)`: Enable/Disable the systemd unit. (Default: detected via `pkg-config`) - `SERVICEDIR_SYSTEMD=`: The path to put the systemd user service file. Unused, if `SYSTEMD=0`. (Default: detected via `pkg-config`) - `SERVICEDIR_DBUS=`: The path to put the dbus service file. (Default: detected via `pkg-config`) **Make sure to run all make calls with the same parameter set. So when building with `make PREFIX=/usr`, you have to install it with `make PREFIX=/usr install`, too.** Checkout the [wiki][wiki] for more information. ## Bug reports Please use the [issue tracker][issue-tracker] provided by GitHub to send us bug reports or feature requests. You can also join us on the IRC channel `#dunst` on Freenode. ## Maintainers - [Nikos Tsipinakis](https://github.com/tsipinakis) - [Benedikt Heine](https://github.com/bebehei) ## Author written by Sascha Kruse ## Copyright copyright 2013 Sascha Kruse and contributors (see [`LICENSE`](./LICENSE) for licensing information) If you feel that copyrights are violated, please send me an email. [issue-tracker]: https://github.com/dunst-project/dunst/issues [wiki]: https://github.com/dunst-project/dunst/wiki dunst-1.5.0/RELEASE_NOTES000066400000000000000000000250751370626304600146450ustar00rootroot00000000000000=================================================================================== Release Notes For v1.5.0 =================================================================================== For users: The most important update from the previous version is the addition of the dunstctl command and dunstify utility, a drop-in notify-send replacement (which existed for a while, but wasn't installed by default). The internal keyboard shortcut support in dunst is now considered deprecated and should be replaced by dunstctl calls. You can use the configuration of your WM or DE to bind these to shortcuts of your choice. Additionally, another long requested feature implemented is RGBA/transparency support. Given an active compositor you can now add an optional transparency component to all colors in #RRGGBBAA format. For maintainers: As mentioned above, two new binaries are now installed by default, dunstctl and dunstify. libnotify has been added as a dependency as it's used internally by dunstify. =================================================================================== Release Notes For v1.4.0 =================================================================================== There has been significant internal refactoring since the last release which might have introduced some new bugs. Be sure to report anything you find. However, as usual, there has been a lot of bug-fixing and a lot of new features have been added as well. Look at the full changelog for a breakdown. Some important points to note: For users: * Behavioural changes In the previous release we introduced support for clients to mark notifications as 'transient'. Transient notifications used to 1) bypass idle_threshold and 2) not be saved in history. The latter behaviour has been disabled by default and can be re-created using rules if necessary. Transient notifications will now only bypass idle_threshold. Additionally, to be compliant with the notification spec, the notification summary no longer accepts markup. For maintainers: * Dependency on libxdg-basedir has been removed =================================================================================== Release Notes For v1.3.0 =================================================================================== Version 1.3 is supposed to be fully backwards compatible with 1.2. For users: * Behavioural changes Dunst respects the timeout with millisecond accuracy now. Notifications with a one second timeout are not shown up to three seconds. Additionally you can specify timeout values in milliseconds, seconds, minutes, hours or days using the ms, s, h, or d suffix in the config value respectively. Transient notifications time out ignoring the `idle_threshold` setting and are not saved in history. This can be overridden with a rule containing `set_transient = no`. In the same vein there is the `match_transient` condition to match transient notifications via rules. A prefixed tilde (`~/`) in path settings (browser, dmenu, script) is interpreted as the home folder of the user. * Configuration Options `icon_folders` got deprecated and renamed to `icon_path`. `icon_folders` is still supported, but will get removed in future. The option `ellipsize` got introduced. It controls where to ellipsize the text of an overlong notification if `word_wrap = no`. For maintainers: * Dependencies The GTK3+ dependency got removed. Instead of this gdk-pixbuf is required explicitly. This had been a transient dependency before. In the Makefile, libxrandr is now specified to require version 1.5 or newer. The dependency on libxrandr >= 1.5 is not new, Dunst 1.2.0 required it too but there was no active check for it. * Installation process The internals of dunst's make installation process have slightly changed. The install routine won't install the service files for DBus and systemd in a hardcoded subdirectory of $PREFIX. It'll now query the `dbus-1` and `systemd` pkg-config packages for those paths and will put it there. To overwrite the pkg-config values, you can manually specify another path. Use `SERVICEDIR_(DBUS|SYSTEMD)` vars as parameters to your make calls. For all introduced variables, see [the README.md]. * Portability GNU-specific functions have been disabled to make dunst portable to nongnu libc's. For a full list of changes see [CHANGELOG.md]. =================================================================================== Release Notes For v1.2.0 =================================================================================== After about 3 years of inactivity, dunst is back under active development. Version 1.2 is supposed to be fully backwards compatible with 1.1 but due to the number of changes and the time since the last release there may be some overlooked breakages. If one is found it should be reported to the bug tracker. For users: * Markup The `allow_markup` setting has been deprecated in favour of `markup` which is a multi-value setting that can be used to control much more accurately how markup in notifications should be handled. Currently it only supports `no`, `strip` and `full` as values but it is planned to be expanded soon. To preserve backwards compatibility, `allow_markup` is still supported but users are encouraged to update their configuration files since it will be removed after a few major releases. * DPI handling The DPI value used is now retrieved from the `Xft.dpi` X resource if available. If not, the default value 96 will be used. Additionally, as an experiment a per-monitor dpi setting, which tries to calculate an acceptable dpi values for each monitor, has been added to the experimental section of the configuration file. * RandR and Xinerama Dunst switched from using the Xinerama extension to provide multi-monitor support to using the more modern RandR extension. While this change won't affect the majority of users, some legacy drivers do not support RandR. In that case, the `force_xinerama` option was added as a way to fall back to the old method. The downside of forcing Xinerama to be used is that dunst won't be able to detect when a monitor is added or removed meaning that follow mode might break if the screen layout changes. * Frame settings All the settings in the frame section of the configuration file have been deprecated and have been moved into the global section. The `color` and `size` settings became `frame_color` and `frame_size` respectively. As with `allow_markup`, the old format still works but it'll be removed in one of the next major releases. * Deprecation of urgency-specific command line flags The urgency specific command line flags (`-li, -ni, -ci, -lf, -nf, -cf, -lb, -nb, -cb, -lfr, -nfr, -cfr, -lto, -nto, -cto`) have been deprecated with no plan for a replacement. If you rely on them please respond to issue #328 on the bug tracker with your use case. For maintainers: * The project homepage has been changed to https://dunst-project.org * The main repository has been changed to https://github.com/dunst-project/dunst * Dependency changes: - Dependency on libraries that were unused in the code but were mentioned as dependencies has been dropped. Dunst no longer depends on: libfreetype, libxft and libxext. - Added dependency on libxradnr and libgtk2.0. For a full list of changes see CHANGELOG.md. =================================================================================== Release Notes For v1.0.0 =================================================================================== PACKAGE MAINTAINERS: There are new dependencies introduced with this version: *glib *pango/cairo * The drawing backend was moved from Xlib to Cairo/Pango. This change requires some user intervention since Pango uses different font strings. For example "Monospace-12" becomes "Monospace 12". Font sizes might also get interpreted differently. * Markup Markup within the notification can be interpreted by pango. Examples are italic and bold. If the Markup can't be parsed by pango, the tags are stripped from the notification and the text is displayed as plain text. An error message gets printed to stdout describing why the markup could not be parsed. To make use of markup the option allow_markup must be set in dunstrc. If this option is not set, dunst falls back to the old behaviour (stripping all tags from the text and display plain text). * Actions are now supported. If a client adds an action to a notification this gets indicated by an (A) infront of the notification. In this case the context menu shortcut can be used to invoke this action. * Indicator for URLs. If dunst detects an URL within the notification this gets indicated by an (U) infront of the notification. As with actions the URL can be opened with the context menu shortcut. (This requires the browser option to be set in the dunstrc). * dunstify ( a drop-in replacement for notify-send) Since notify-send lacks some features I need to for testing, I've written dunstify. It supports the same option as notify-send + The abillity to print the id of the notification to stdout and to replace or close existing notifications. example: id=$(dunstify -p "Replace" "this should get replaced after the sleep") sleep 5 dunstify -r $id "replacement" see dunstify --help for additional info. Since dunstify depends on non-public parts of libnotify it may break on every other libnotify version than that version that I use. Because of this it does not get build and installed by default. It can be build with "make dunstify". An installation target does not exist. Please don't open bug reports when dunstify doesn't work with your version of libnotify =================================================================================== Release Notes For v0.4.0 =================================================================================== Since dunst has lost its ability to show notifications independend of dbus/libnotify a long time ago I think it is time that the describtion reflects that. Even though this breaks the acronym. So if you're a packager please update the package description to read something like: short: "Dunst - a dmenu-ish notification-daemon" long: "Dunst is a highly configurable and lightweight notification-daemon" Release Tarballs are now available at: http://www.knopwob.org/public/dunst-release/ For more information have a look at the CHANGELOG and the new options in dunstrc. dunst-1.5.0/config.h000066400000000000000000000114731370626304600143050ustar00rootroot00000000000000/* see example dunstrc for additional explanations about these options */ struct settings defaults = { .font = "-*-terminus-medium-r-*-*-16-*-*-*-*-*-*-*", .markup = MARKUP_NO, .colors_norm.bg = "#1793D1", .colors_norm.fg = "#DDDDDD", .colors_crit.bg = "#ffaaaa", .colors_crit.fg = "#000000", .colors_low.bg = "#aaaaff", .colors_low.fg = "#000000", .format = "%s %b", /* default format */ .timeouts = { S2US(10), S2US(10), S2US(0) }, /* low, normal, critical */ .icons = { "dialog-information", "dialog-information", "dialog-warning" }, /* low, normal, critical */ .transparency = 0, /* transparency */ .geometry = { .x = 0, /* geometry */ .y = 0, .w = 0, .h = 0, .negative_x = 0, .negative_y = 0, .negative_width = 0, .width_set = 0 }, .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 = ALIGN_LEFT, /* text alignment ALIGN_[LEFT|CENTER|RIGHT] */ .vertical_alignment = VERTICAL_CENTER, /* vertical content alignment VERTICAL_[TOP|CENTER|BOTTOM] */ .sticky_history = true, .history_length = 20, /* max amount of notifications kept in history */ .show_indicators = true, .word_wrap = false, .ignore_dbusclose = false, .ellipsize = ELLIPSE_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 */ .corner_radius = 0, .separator_height = 2, /* height of the separator line between two notifications */ .padding = 0, .h_padding = 0, /* horizontal padding */ .sep_color = {SEP_AUTO}, /* SEP_AUTO, SEP_FOREGROUND, SEP_FRAME, SEP_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", .min_icon_size = 0, .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 */ .mouse_left_click = (enum mouse_action []){MOUSE_CLOSE_CURRENT, -1}, .mouse_middle_click = (enum mouse_action []){MOUSE_DO_ACTION, -1}, .mouse_right_click = (enum mouse_action []){MOUSE_CLOSE_ALL, -1}, }; struct rule 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, .skip_display = -1, .new_icon = NULL, .fg = NULL, .bg = NULL, .format = NULL, .script = NULL, } }; /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ dunst-1.5.0/config.mk000066400000000000000000000025041370626304600144600ustar00rootroot00000000000000# paths PREFIX ?= /usr/local BINDIR ?= ${PREFIX}/bin DATADIR ?= ${PREFIX}/share # around for backwards compatibility MANPREFIX ?= ${DATADIR}/man MANDIR ?= ${MANPREFIX} DOXYGEN ?= doxygen FIND ?= find GCOVR ?= gcovr GIT ?= git PKG_CONFIG ?= pkg-config POD2MAN ?= pod2man SED ?= sed SYSTEMCTL ?= systemctl VALGRIND ?= valgrind # Disable systemd service file installation, # if you don't want to use systemd albeit installed #SYSTEMD ?= 0 # uncomment to disable parsing of dunstrc # or use "CFLAGS=-DSTATIC_CONFIG make" to build #STATIC= -DSTATIC_CONFIG # Warning: This is deprecated behavior # flags DEFAULT_CPPFLAGS = -D_DEFAULT_SOURCE -DVERSION=\"${VERSION}\" DEFAULT_CFLAGS = -g --std=gnu99 -pedantic -Wall -Wno-overlength-strings -Os ${STATIC} DEFAULT_LDFLAGS = -lm CPPFLAGS_DEBUG := -DDEBUG_BUILD CFLAGS_DEBUG := -O0 LDFLAGS_DEBUG := pkg_config_packs := gio-2.0 \ gdk-pixbuf-2.0 \ "glib-2.0 >= 2.44" \ pangocairo \ x11 \ xinerama \ xext \ "xrandr >= 1.5" \ xscrnsaver # dunstify also needs libnotify pkg_config_packs += libnotify ifneq (,$(findstring STATIC_CONFIG,$(CFLAGS))) $(warning STATIC_CONFIG is deprecated behavior. It will get removed in future releases) endif dunst-1.5.0/contrib/000077500000000000000000000000001370626304600143215ustar00rootroot00000000000000dunst-1.5.0/contrib/dunst_espeak.sh000077500000000000000000000001041370626304600173400ustar00rootroot00000000000000#!/bin/bash summary="$2" body="$3" echo "$summary $body" | espeak dunst-1.5.0/contrib/dunst_xr_theme_changer.sh000077500000000000000000000204551370626304600214050ustar00rootroot00000000000000#!/usr/bin/env bash ############################################################################### ## ## Usage ## ## ./ [] ## ## If it does not run, give execute permissions to the script with ## chmod +x . Then run ./. ## ## Options ## ## -h|--help Optional. Show help message. ## ## Description ## ## This script creates a dunst themed user config in $HOME/.config/dunst/ ## folder, changing dunst basic theming options (like fonts, colors, etc.) ## according to your current X resources color palette. ## ## To make this possible, it reads your current user config ## ($HOME/.conf/dunst/dunstrc, which is copied from the default config if ## it does not exist) and changes the attributes values with new ones (see ## Theming section for more info). Then it dumps the new configuration to ## $user_xr_color_conf file. ## ## Theming ## ## To change colors and fonts: ## ## * Firstly you have to ensure that those dunst attributes are defined in ## the corresponding sections. For example, to change the frame_color ## value from the global section, in $HOME/.config/dunst/dunstrc should ## be: ## ## ... ## [global] ## ... ## frame_color = ## ... ## ## * Then, you can change attribute values as you wish with the ## following format in theme_attr_dict: ## ## ["-"]="|$(xrdb_get '' '')" ## ## Each means the following: ## ## * sec - current section name e.g.: global. ## * attr - current attribute name e.g.: frame-color. ## * val - a simple value e.g.: "#fffeee", Monospace, 11... ## * X_res - X resource name e.g.: color8. ## * dev_val - default value to set if X_res is not found e.g.: #65737e. ## ## Theming example (you can check theme_attr_dict for other values): ## ## ["global-frame_color"]="\"$(xrdb_get 'color8' '#65737e') ## ## The function xrdb_get, searches the first parameter in the X resources ## database with appres (command installed from xorg-appres in archlinux ## and x11-utils in ubuntu). If the first parameter does not exist, the ## function returns the second parameter. For hex colors, is important to ## scape " characters for proper functioning of dunst config reader (for ## example "#ffeegg"). ## ## You can define X_res variables in $HOME/.Xresources file. For in depth ## syntax go to https://wiki.archlinux.org/index.php/X_resources. ## ############################################################################### set -e # Check if appres is installed if [ ! "$(command -v appres)" ]; then printf 'Install xorg-appres in archlinux and x11-utils in debian/ubuntu.\n' exit 1 fi readonly script_name="$(basename "$0")" readonly base_dir="$(realpath "$(dirname "$0")")" # Show ussage usage() { grep -e '^##[^#]*$' "$base_dir/$script_name" | \ sed -e "s/^##[[:space:]]\{0,1\}//g" \ -e "s//${script_name}/g" exit 2 } 2>/dev/null # Show help if [ "$#" -gt 0 ]; then if ! [[ "$1" == "--help" || "$1" == "-h" ]]; then printf '\nUnknown option.\n' fi usage fi # Function to get resource values xrdb_get () { output="$(appres Dunst | grep "$1:" | head -n1 | cut -f2)" default="$2" printf '%s' "${output:-$default}" } # # Attributes dictionary. Add or remove attributes (see header for more info) # declare -A theme_attr_dict=( ["global-font"]="$(xrdb_get 'font' 'Monospace') $(xrdb_get '*.font_size:' '11')" ["global-frame_width"]="$(xrdb_get 'border_width' '1')" ["global-frame_color"]="\"$(xrdb_get 'color8' '#65737e')\"" ["urgency_low-background"]="\"$(xrdb_get 'color0' '#2b303b')\"" ["urgency_low-foreground"]="\"$(xrdb_get 'color4' '#65737e')\"" ["urgency_low-frame_color"]="\"$(xrdb_get 'color4' '#65737e')\"" ["urgency_normal-background"]="\"$(xrdb_get 'color0' '#2b303b')\"" ["urgency_normal-foreground"]="\"$(xrdb_get 'color2' '#a3be8c')\"" ["urgency_normal-frame_color"]="\"$(xrdb_get 'color2' '#a3be8c')\"" ["urgency_critical-background"]="\"$(xrdb_get 'color0' '#2b303b')\"" ["urgency_critical-foreground"]="\"$(xrdb_get 'color1' '#bf616a')\"" ["urgency_critical-frame_color"]="\"$(xrdb_get 'color1' '#bf616a')\"" ) # Attributes dictionary keys. readonly valid_keys="${!theme_attr_dict[@]}" # # File paths # # User config dir and file readonly user_conf_dir="${XDG_CONFIG_HOME:-$HOME/.config}/dunst" readonly user_conf="$user_conf_dir/dunstrc" # Default config dir and example file example_conf_dir="/usr/share/dunst" example_conf="$example_conf_dir/dunstrc" # User xresources color config file readonly user_xr_color_conf="$user_conf_dir/dunstrc_xr_colors" # Check if the user config directory exists if ! [ -d "$user_conf_dir" ]; then printf 'Creating folder "%s".\n' "$user_conf_dir" mkdir -p "$user_conf_dir" fi # Check if the user config file exists if ! [ -f "$user_conf" ]; then printf '"%s" file does not exist.\nChecking for config file example.' \ "$user_conf" if [ -d "/usr/share/dunst" ]; then # Archlinux default dir and example file example_conf_dir="/usr/share/dunst" if [ -f "$example_conf_dir/dunstrc" ]; then example_conf="$example_conf_dir/dunstrc" else printf 'Could not find the example config file in "%s". \nPlease, change $example_conf variable in the script.' \ "$example_conf_dir" exit 1 fi elif [ -d "/usr/share/doc/dunst" ]; then # Debian/Ubuntu default dir example_conf_dir="/usr/share/doc/dunst" if [ -f "$example_conf_dir/dunstrc.example.gz" ]; then # Ubuntu <= 17.10 and Debian <= 1.2.0-2 example file: example_conf="$example_conf_dir/dunstrc.example.gz" elif [ -f "$example_conf_dir/dunstrc.gz" ]; then # Ubuntu >= 18.04 and Debian >= 1.3.0-2 example file: example_conf="$example_conf_dir/dunstrc.gz" else printf 'Could not find the example config file in "%s". \nPlease, change $example_conf variable in the script.' \ "$example_conf_dir" exit 1 fi else printf 'Could not find the example config directory. \nPlease, change $example_conf_dir variable in the script.' exit 1 fi printf 'Copying example config to "%s".\n' "$user_conf_dir" # Get the extension to check if the file is compressed if [[ "${example_conf##*\.}" == "gz" ]]; then # Extract example file to user config file gunzip -c "$example_conf" > "$user_conf" else cp "$example_conf" "$user_conf_dir" fi fi # Regular expressions readonly re_section_line='^\[(.*)\]$' readonly re_empty_comment_line='(^$)|(^[[:space:]]*(\#)|(;))' readonly re_attribute_line='^([[:space:]]*)([_[:alnum:]]+)' # Create an array with the file lines mapfile -t conf < "$user_conf" # Iterate over the file lines for idx in "${!conf[@]}"; do # Current line curr_line="${conf[$idx]}" # If we are in a new section: if [[ "$curr_line" =~ $re_section_line ]]; then curr_section="${BASH_REMATCH[1]}" continue fi # Skip the line if it is empty or has a comment if [[ "$curr_line" =~ $re_empty_comment_line ]]; then continue fi # Get the attribute in the current line [[ "$curr_line" =~ $re_attribute_line ]] curr_attr_name="${BASH_REMATCH[2]}" curr_sett_name="${curr_section}-${curr_attr_name}" # If the current attribute is not in our dictionary, continue case "$valid_keys" in *"$curr_sett_name"*) printf -v conf[$idx] ' %s = %s' \ "${curr_attr_name}" \ "${theme_attr_dict[$curr_sett_name]}" ;; esac done # Create a header for the xr_color config file user_xr_color_conf_content="\ ################################### # # Config file created with # $script_name wrapper # ################################### " # After everything is completed, write the new config to a file user_xr_color_conf_content+="$(printf '%s\n' "${conf[@]}")" printf '%s\n' "$user_xr_color_conf_content" > "$user_xr_color_conf" printf '"%s" updated successfully.\n' "$user_xr_color_conf" dunst-1.5.0/docs/000077500000000000000000000000001370626304600136115ustar00rootroot00000000000000dunst-1.5.0/docs/dunst.pod000066400000000000000000000666251370626304600154710ustar00rootroot00000000000000=head1 NAME dunst - A customizable and lightweight notification-daemon =head1 SYNOPSIS dunst [-conf file] [-font font] [-geometry geom] [-format fmt] [-follow mode] [-monitor n] [-history_length n] ... =head1 DESCRIPTION Dunst is a highly configurable and lightweight notification daemon. =head1 COMMAND LINE OPTIONS =over 4 =item B<-h/--help> List all command line flags =item B<-conf/-config file> Use alternative config file. =item B<-v/--version> Print version information. =item B<-print> Print notifications to stdout. This might be useful for logging, setting up rules or using the output in other scripts. =back =head1 CONFIGURATION An example configuration file is included (usually /usr/share/dunst/dunstrc). To change the configuration, copy this file to ~/.config/dunst/dunstrc and edit it accordingly. The configuration is divided into sections in an ini-like format. The 'global' section contains most general settings while the 'shortcuts' sections contains all keyboard configuration and the 'experimental' section all the features that have not yet been tested thoroughly. Any section that is not one of the above is assumed to be a rule, see RULES for more details. For backwards compatibility reasons the section name 'frame' is considered bound and can't be used as a rule. =head2 Command line Each configuration option in the global section can be overridden from the command line by adding a single dash in front of it's name. For example the font option can be overridden by running $ dunst -font "LiberationSans Mono 4" Configuration options that take boolean values can only currently be set to "true" through the command line via the same method. e.g. $ dunst -shrink This is a known limitation of the way command line parameters are parsed and will be changed in the future. Available settings per section: =head2 Global section =over 4 =item B (default: 0) Specifies on which monitor the notifications should be displayed in, count starts at 0. See the B setting. =item B (values: [none/mouse/keyboard] default: none) Defines where the notifications should be placed in a multi-monitor setup. All values except I override the B setting. =over 4 =item B The notifications will be placed on the monitor specified by the B setting. =item B The notifications will be placed on the monitor that the mouse is currently in. =item B The notifications will be placed on the monitor that contains the window with keyboard focus. =back =item B (format: [{width}][x{height}][+/-{x}[+/-{y}]], default: "0x0+0-0") The geometry of the window the notifications will be displayed in. =over 4 =item B The width of the notification window in pixels. A negative value sets the width to the screen width B. If the width is omitted then the window expands to cover the whole screen. If it's 0 the window expands to the width of the longest message being displayed. =item B The number of notifications that can appear at one time. When this limit is reached any additional notifications will be queued and displayed when the currently displayed ones either time out or are manually dismissed. If B is true, then the specified limit is reduced by 1 and the last notification is a message informing how many hidden notifications are waiting to be displayed. See the B entry for more information. The physical(pixel) height of the notifications vary depending on the number of lines that need to be displayed. See B for changing the physical height. =item B Respectively the horizontal and vertical offset in pixels from the corner of the screen that the notification should be drawn at. For the horizontal(x) offset, a positive value is measured from the left of the screen while a negative one from the right. For the vertical(y) offset, a positive value is measured from the top while a negative from the bottom. It's important to note that the positive and negative sign B affect the position even if the offset is 0. For example, a horizontal offset of +0 puts the notification on the left border of the screen while a horizontal offset of -0 at the right border. The same goes for the vertical offset. =back =item B (values: [true/false], default: true) If this is set to true, a notification indicating how many notifications are not being displayed due to the notification limit (see B) will be shown B. Meaning that if this is enabled the number of visible notifications will be 1 less than what is specified in geometry, the last slot will be taken by the hidden count. =item B (values: [true/false], default: false) Shrink window if it's smaller than the width. Will be ignored if width is 0. This is used mainly in order to have the shrinking benefit of dynamic width (see geometry) while also having an upper bound on how long a notification can get before wrapping. =item B (default: 0) A 0-100 range on how transparent the notification window should be, with 0 being fully opaque and 100 invisible. This setting will only work if a compositor is running. =item B (default: 0) The minimum height of the notification window in pixels. If the text and padding cannot fit in within the height specified by this value, the height will be increased as needed. =item B (default: 2) The height in pixels of the separator between notifications, if set to 0 there will be no separating line between notifications. =item B (default: 0) The distance in pixels from the content to the separator/border of the window in the vertical axis =item B (default: 0) The distance in pixels from the content to the border of the window in the horizontal axis =item B (default: 0) Defines width in pixels of frame around the notification window. Set to 0 to disable. =item B (default: #888888) Defines color of the frame around the notification window. See COLORS. =item B (values: [auto/foreground/frame/#RRGGBB] default: auto) Sets the color of the separator line between two notifications. =over 4 =item B Dunst tries to find a color that fits the rest of the notification color scheme automatically. =item B The color will be set to the same as the foreground color of the topmost notification that's being separated. =item B The color will be set to the frame color of the notification with the highest urgency between the 2 notifications that are being separated. =item B Any other value is interpreted as a color, see COLORS =back =item B (values: [true/false], default: true) If set to true, display notifications with higher urgency above the others. =item B (default: 0) Don't timeout notifications if user is idle longer than this time. See TIME FORMAT for valid times. Set to 0 to disable. A client can mark a notification as transient to bypass this setting and timeout anyway. Use a rule with 'set_transient = no' to disable this behavior. =item B (default: "Monospace 8") Defines the font or font set used. Optionally set the size as a decimal number after the font name and space. Multiple font options can be separated with commas. This options is parsed as a Pango font description. =item B (default: 0) The amount of extra spacing between text lines in pixels. Set to 0 to disable. =item B (values: [full/strip/no], default: no) Defines how markup in notifications is handled. It's important to note that markup in the format option will be parsed regardless of what this is set to. Possible values: =over 4 =item B Allow a small subset of html markup in notifications bold italic strikethrough underline For a complete reference see =item B This setting is provided for compatibility with some broken clients that send markup even though it's not enabled on the server. Dunst will try to strip the markup but the parsing is simplistic so using this option outside of matching rules for specific applications B. See RULES =item B Disable markup parsing, incoming notifications will be treated as plain text. Dunst will not advertise that it can parse markup if this is set as a global setting. =back =item B (default: "%s %b") Specifies how the various attributes of the notification should be formatted on the notification window. Regardless of the status of the B setting, any markup tags that are present in the format will be parsed. Note that because of that, if a literal ampersand (&) is needed it needs to be escaped as '&' If '\n' is present anywhere in the format, it will be replaced with a literal newline. If any of the following strings are present, they will be replaced with the equivalent notification attribute. =over 4 =item B<%a> appname =item B<%s> summary =item B<%b> body =item B<%i> iconname (including its path) =item B<%I> iconname (without its path) =item B<%p> progress value ([ 0%] to [100%]) =item B<%n> progress value without any extra characters =item B<%%> Literal % =back If any of these exists in the format but hasn't been specified in the notification (e.g. no icon has been set), the placeholders will simply be removed from the format. =item B (values: [left/center/right], default: left) Defines how the text should be aligned within the notification. =item B (values: [top/center/bottom], default: center) Defines how the text and icon should be aligned vertically within the notification. If icons are disabled, this option has no effect. =item B (default: -1) Show age of message if message is older than this time. See TIME FORMAT for valid times. Set to -1 to disable. =item B (values: [true/false], default: false) Specifies how very long lines should be handled If it's set to false, long lines will be truncated and ellipsized. If it's set to true, long lines will be broken into multiple lines expanding the notification window height as necessary for them to fit. =item B (values: [start/middle/end], default: middle) If word_wrap is set to false, specifies where truncated lines should be ellipsized. =item B (values: [true/false], default: false) If set to true, replace newline characters in notifications with whitespace. =item B (values: [true/false], default: true) If set to true, duplicate notifications will be stacked together instead of being displayed separately. Two notifications are considered duplicate if the name of the program that sent it, summary, body, icon and urgency are all identical. =item B (values: [true/false], default: false) Hide the count of stacked duplicate notifications. =item B (values: [true/false], default: true) Show an indicator if a notification contains actions and/or open-able URLs. See ACTIONS below for further details. =item B (values: [left/right/off], default: off) Defines the position of the icon in the notification window. Setting it to off disables icons. =item B (default: 0) Defines the minimum size in pixels for the icons. If the icon is larger than or equal to the specified value it won't be affected. If it's smaller then it will be scaled up so that the smaller axis is equivalent to the specified size. Set to 0 to disable icon upscaling. (default) If B is set to off, this setting is ignored. =item B (default: 0) Defines the maximum size in pixels for the icons. If the icon is smaller than or equal to the specified value it won't be affected. If it's larger then it will be scaled down so that the larger axis is equivalent to the specified size. Set to 0 to disable icon downscaling. (default) If both B and B are enabled, the latter gets the last say. If B is set to off, this setting is ignored. =item B (default: "/usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/") Can be set to a colon-separated list of paths to search for icons to use with notifications. Dunst doesn't currently do any type of icon lookup outside of these directories. =item B (values: [true/false], default: true) If set to true, notifications that have been recalled from history will not time out automatically. =item B (default: 20) Maximum number of notifications that will be kept in history. After that limit is reached, older notifications will be deleted once a new one arrives. See HISTORY. =item B (default: "/usr/bin/dmenu") The command that will be run when opening the context menu. Should be either a dmenu command or a dmenu-compatible menu. =item B (default: "/usr/bin/firefox") The command that will be run when opening a URL. The URL to be opened will be appended to the end of the value of this setting. =item B (values: [true/false] default: true] Always run rule-defined scripts, even if the notification is suppressed with format = "". See SCRIPTING. =item B (default: "Dunst") Defines the title of notification windows spawned by dunst. (_NET_WM_NAME property). There should be no need to modify this setting for regular use. =item B<class> (default: "Dunst") Defines the class of notification windows spawned by dunst. (First part of WM_CLASS). There should be no need to modify this setting for regular use. =item B<startup_notification> (values: [true/false], default: false) Display a notification on startup. This is usually used for debugging and there shouldn't be any need to use this option. =item B<verbosity> (values: 'crit', 'warn', 'mesg', 'info', 'debug' default 'mesg') Do not display log messages, which have lower precedence than specified verbosity. This won't affect printing notifications on the terminal. Use the '-print' option for this. =item B<force_xinerama> (values: [true/false], default: false) Use the Xinerama extension instead of RandR for multi-monitor support. This setting is provided for compatibility with older nVidia drivers that do not support RandR and using it on systems that support RandR is highly discouraged. By enabling this setting dunst will not be able to detect when a monitor is connected or disconnected which might break follow mode if the screen layout changes. =item B<corner_radius> (default: 0) Define the corner radius in pixels. A corner radius of 0 will result in rectangular shaped notifications. By enabling this setting the outer border and the frame will be shaped. If you have multiple notifications, the whole window is shaped, not every single notification. To avoid the corners clipping the icon or text the corner radius will be automatically lowered to half of the notification height if it exceeds it. =item B<mouse_left/middle/right_click> (values: [none/do_action/close_current/close_all]) Defines action of mouse click. =over 4 =item B<none> Don't do anything. =item B<do_action> (default for mouse_middle_click) 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. =item B<close_current> (default for mouse_left_click) Close current notification. =item B<close_all> (default for mouse_right_click) Close all notifications. =back =item B<ignore_dbusclose> (default: false) Ignore the dbus closeNotification message. This is useful to enforce the timeout set by dunst configuration. Without this parameter, an application may close the notification sent before the user defined timeout. =back =head2 Shortcut section B<DEPRECATED SEE DUNSTCTL> Keyboard shortcuts are defined in the following format: "Modifier+key" where the modifier is one of ctrl,mod1,mod2,mod3,mod4 and key is any keyboard key. =over 4 =item B<close> B<command line flag>: -key <key> Specifies the keyboard shortcut for closing a notification. =item B<close_all> B<command line flag>: -all_key <key> Specifies the keyboard shortcut for closing all currently displayed notifications. =item B<history> B<command line flag>: -history_key <key> Specifies the keyboard shortcut for recalling a single notification from history. =item B<context> B<command line flag>: -context_key <key> Specifies the keyboard shortcut that opens the context menu. =back =head2 Urgency sections The urgency sections work in a similar way to rules and can be used to specify attributes for the different urgency levels of notifications (low, normal, critical). Currently only the background, foreground, timeout, frame_color and icon attributes can be modified. The urgency sections are urgency_low, urgency_normal, urgency_critical for low, normal and critical urgency respectively. See the example configuration file for examples. Additionally, you can override these settings via the following command line flags: Please note these flags may be removed in the future. See issue #328 in the bug tracker for discussions (See REPORTING BUGS). =over 4 =item B<-li/ni/ci icon> Defines the icon for low, normal and critical notifications respectively. Where I<icon> is a path to an image file containing the icon. =item B<-lf/nf/cf color> Defines the foreground color for low, normal and critical notifications respectively. See COLORS for the value format. =item B<-lb/nb/cb color> Defines the background color for low, normal and critical notifications respectively. See COLORS for the value format. =item B<-lfr/nfr/cfr color> Defines the frame color for low, normal and critical notifications respectively. See COLORS for more information =item B<-lto/nto/cto secs> Defines the timeout time for low, normal and critical notifications respectively. See TIME FORMAT for valid times. =back =head1 DUNSTCTL Dunst now contains a command line control command that can be used to interact with it. It supports all functions previously done only via keyboard shortcuts but also has a lot of extra functionality. So see more see the dunstctl man page. =head1 HISTORY Dunst saves a number of notifications (specified by B<history_length>) in memory. These notifications can be recalled (i.e. redisplayed) by pressing the B<history_key> (see the shortcuts section), whether these notifications will time out like if they have been just send depends on the value of the B<sticky_history> setting. Past notifications are redisplayed in a first-in-last-out order, meaning that pressing the history key once will bring up the most recent notification that had been closed/timed out. =head1 RULES Rules allow the conditional modification of notifications. They are defined by creating a section in the configuration file that has any name that is not already used internally (i.e. any name other than 'global', 'experimental', 'frame', 'shortcuts', 'urgency_low', 'urgency_normal' and 'urgency_critical'). There are 2 parts in configuring a rule: Defining the filters that control when a rule should apply and then the actions that should be taken when the rule is matched. =over 4 =item B<filtering> Notifications can be matched for any of the following attributes: =over 4 =item C<appname> (discouraged, see desktop_entry) The name of the application as reported by the client. Be aware that the name can often differ depending on the locale used. =item C<body> The body of the notification =item C<category> The category of the notification as defined by the notification spec. See https://developer.gnome.org/notification-spec/#categories =item C<desktop_entry> GLib based applications export their desktop-entry name. In comparison to the appname, the desktop-entry won't get localized. =item C<icon> The icon of the notification in the form of a file path. Can be empty if no icon is available or a raw icon is used instead. =item C<match_transient> Match if the notification has been declared as transient by the client or by some other rule. See C<set_transient> for more details about this attribute. =item C<msg_urgency> Matches the urgency of the notification as set by the client or by some other rule. =item C<stack_tag> Matches the stack tag of the notification as set by the client or by some other rule. See set_stack_tag for more information about stack tags. =item C<summary> Matches the summary, 'title', of the notification. =back C<msg_urgency> is the urgency of the notification, it is named so to not conflict with trying to modify the urgency. Instead of the appname filter, it's recommended to use the desktop_entry filter. To define a matching rule simply assign the specified value to the value that should be matched, for example: appname="notify-send" Matches only messages that were send via notify-send. If multiple filter expressions are present, all of them have to match for the rule to be applied (logical AND). Shell-like globing is supported. =item B<modifying> The following attributes can be overridden: =over 4 =item C<background> The background color of the notification. See COLORS for possible values. =item C<foreground> The background color of the notification. See COLORS for possible values. =item C<format> Equivalent to the C<format> setting. =item C<frame_color> The frame color color of the notification. See COLORS for possible values. =item C<fullscreen> One of show, delay, or pushback. This attribute specifies how notifications are handled if a fullscreen window is focused. By default it's set to show so notifications are being shown. Other possible values are delay: Already shown notifications are continued to be displayed until they are dismissed or time out but new notifications will be held back and displayed when the focus to the fullscreen window is lost. Or pushback which is equivalent to delay with the difference that already existing notifications are paused and hidden until the focus to the fullscreen window is lost. =item C<new_icon> Updates the icon of the notification, it should be a path to a valid image. =item C<set_stack_tag> Sets the stack tag for the notification, notifications with the same (non-empty) stack tag will replace each-other so only the newest one is visible. This can be useful for example in volume or brightness notifications where only want one of the same type visible. The stack tag can be set by the client with the 'synchronous', 'private-synchronous' 'x-canonical-private-synchronous' or the 'x-dunst-stack-tag' hints. =item C<set_transient> Sets whether the notification is considered transient. Transient notifications will bypass the idle_threshold setting. By default notifications are _not_ considered transient but clients can set the value of this by specifying the 'transient' hint when sending notifications. =item C<timeout> Equivalent to the C<timeout> setting in the urgency sections. =item C<urgency> This sets the notification urgency. B<IMPORTANT NOTE>: This currently DOES NOT re-apply the attributes from the urgency_* sections. The changed urgency will only be visible in rules defined later. Use C<msg_urgency> to match it. =item C<skip_display> Setting this to true will prevent the notification from being displayed initially but will be saved in history for later viewing. =back As with the filtering attributes, each one corresponds to the respective notification attribute to be modified. As with filtering, to make a rule modify an attribute simply assign it in the rule definition. If the format is set to an empty string, the notification will not be suppressed. =back =head2 SCRIPTING Within rules you can specify a script to be run every time the rule is matched by assigning the 'script' option to the name of the script to be run. When the script is called details of the notification that triggered it will be passed via command line parameters in the following order: appname, summary, body, icon, urgency. Where icon is the absolute path to the icon file if there is one and urgency is one of "LOW", "NORMAL" or "CRITICAL". If the notification is suppressed, the script will not be run unless B<always_run_scripts> is set to true. If '~/' occurs at the beginning of the script parameter, it will get replaced by the users' home directory. If the value is not an absolute path, the directories in the PATH variable will be searched for an executable of the same name. =head1 COLORS Colors are interpreted as X11 color values. This includes both verbatim color names such as "Yellow", "Blue", "White", etc as well as #RGB and #RRGGBB values. You may also specify a transparency component in #RGBA or #RRGGBBAA format. B<NOTE>: '#' is interpreted as a comment, to use it the entire value needs to be in quotes like so: separator_color="#123456" =head2 NOTIFY-SEND dunst is able to get different colors for a message via notify-send. In order to do that you have to add a hint via the -h option. The progress value can be set with a hint, too. =over 4 =item notify-send -h string:fgcolor:#ff4444 =item notify-send -h string:bgcolor:#4444ff -h string:fgcolor:#ff4444 -h string:frcolor:#44ff44 =item notify-send -h int:value:42 "Working ..." =back =head1 ACTIONS Dunst allows notifiers (i.e.: programs that send the notifications) to specify actions. Dunst has support for both displaying indicators for these, and interacting with these actions. If "show_indicators" is true and a notification has an action, an "(A)" will be prepended to the notification format. Likewise, an "(U)" is prepended to notifications with URLs. It is possible to interact with notifications that have actions regardless of this setting, though it may not be obvious which notifications HAVE actions. The "context" keybinding is used to interact with these actions, by showing a menu of possible actions. This feature requires "dmenu" or a dmenu drop-in replacement present. Alternatively, you can invoke an action with a middle click on the notification. If there is exactly one associated action, or one is marked as default, that one is invoked. If there are multiple, the context menu is shown. The same applies to URLs when there are no actions. =head1 TIME FORMAT A time can be any decimal integer value suffixed with a time unit. If no unit given, seconds ("s") is taken as default. Time units understood by dunst are "ms", "s", "m", "h" and "d". Example time: "1000ms" "10m" =head1 MISCELLANEOUS Dunst can be paused via the `dunstctl set-paused true` command. To unpause dunst use `dunstctl set-paused false`. Alternatively you can send SIGUSR1 and SIGUSR2 to pause and unpause respectively. For Example: =over 4 =item killall -SIGUSR1 dunst # pause =item killall -SIGUSR2 dunst # resume =back When paused dunst will not display any notifications but keep all notifications in a queue. This can for example be wrapped around a screen locker (i3lock, slock) to prevent flickering of notifications through the lock and to read all missed notifications after returning to the computer. =head1 FILES $XDG_CONFIG_HOME/dunst/dunstrc -or- $HOME/.config/dunst/dunstrc =head1 AUTHORS Written by Sascha Kruse <knopwob@googlemail.com> =head1 REPORTING BUGS Bugs and suggestions should be reported on GitHub at https://github.com/dunst-project/dunst/issues =head1 COPYRIGHT Copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) If you feel that copyrights are violated, please send me an email. =head1 SEE ALSO dunstctl(1), dwm(1), dmenu(1), twmn(1), notify-send(1) �����������������������������������������������������������������������������������������������������������dunst-1.5.0/docs/dunst_layout.png�������������������������������������������������������������������0000664�0000000�0000000�00000055700�13706263046�0017060�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR�������ߊ���bKGD������ pHYs�� �� ����tIME S|�� �IDATxy\TUgfD1D }QI+S,{%-5^ Z FkAXګe!.Qfc@3{9s9wfȄDDDԠDDD t"""bDDD t"""bDDD t"""bDDD t"""bDDD t"""bDDD t"""bDDD t"""bNdXwcfcQ xܹsk.!,^wϞ=ѳgg]Nbbխ2L׾}{=ϟW܎B/Q+/;v  ?siý{b&ogϞE1iҤMaF37JHIIU*f̘;;;888`ժUT]QQQRٹs ;;;i>>>ת|&XpppQRRkr\i4rޚYn˖-4i&N-[hU<CT6mڶ}vvvxq)5e;Lc՝E=0sLT*3kKWFpt ڵÔ)SPPP`tQ$jb�K,C /^,Dvv|xꩧ,_.<<\ 6L7n@񢰰PܺuK̜9Sk:)"˗uj-3fcԾZbagg'agg'j]Y>cO_uQ|碸XdeeSJeW^m۶*m5>]~],ZH 8P궱|l%Kר(1bqq /9s&D4Y<BTJ]\\Djjt;99d=DrrQpmѥKݻWY2/^4z_-U]O?-111Fp2f.]͛7 ___a\+ر4X)an+.Rb3gH\"vjT"FI]c+Å BvZ2Ƥ@D=Ϥ[n?Q`N{W>}˟8qBx{{ ;;;*[lرbf͚JJJU5HOOC_~@7e;LcՍac۬6ǫR B( !�!ˍj"bzW=Tk3equQVV&_L&3knʾZ+Wf͚U6kL\r`;OSgƼHh4b޽A!Jm۶W^՚Z˝:uJٳںsQevc666@wj W^Zg/m"jSǏGPPrrrj;yd̚5 .\͛7$uhٲ%oh-۾}{iٳg#;;٘={6j?5s۶m0a'N (--}ȑ#ի:t =vĉ9s&RSSQ\\T̚5K؝0tPi_-؀zj 33׿AAA0ax>}:^uAR!99ƍ3&D XL6Mڊ:jWTb…K.VYF*۷oիNNN"::Zk٨(Ѷm[ĬYf:Ej u>rC#<btgM};vz666] ! aaaZ˩T*&z!lllD=ҥKEiiʼnKXrr<x]v~[rСӧOb]WZ-w67Dt:0K2z]vݽm5T:y%7""7ބ4&QCt~ID@,P'""jP'""j$NDDD$9K'""j$3t:Q#t:Q# t"""j$Y:Q#3ԉ)w:1ЉY:NDD@g5@'""bsNDD@g1DDD t""":gDDD t:NDD@'""b7!NDD@g5nwc~nڷwÈNDbܹصk6-ٳ' 19K'.^OO\m{ѣ9`P'3[F\.=d2֮] GGGjΝ;___١M6A~~V=ᰵEǎg,_vvvprr‘#Gj4[nh׮Lꊳg�rrr ˑ �8s \]]̺U*f̘;;;888`ժUUf21dƍpvvR'RRR'zYYBCCѵkW"**Jz\II �Dz :C !fǡh��~~~1cq%t ,Ъ+//YYY㑛L,^s̑n::t@ii)BCCunߨQp!�={`mm{�~G<sUs琔|w�p!cԨQ6mTv5;w?8�`ʕ駟 . ;;[zeː$$$ )) :ϘV jO&@d�qEܾ}[tEkׯ !(..rZz8stʕ+k׮:ߊ�!F=zBwU*NNN*u� BTJ7o,|}}=z:{Us<P_P' ,>eȦ sW0o<<y7o�( U[Gu[hJ%ZmEEEׯN:'''\p...|2郴4hկT*qM��T*r] Cbј<yuTk(**R1h4<S<ND5;_nܸq4iQVVׯ `c899Ij!KT6lڶmC޽ujΝqvFFF6Eii)<uz:ߩS'sf?7:C,�[F˖-7x캦O_iiiPTHNNƸq}Q|r9�0rH,_F#((999A``Vy푖f9rzB&OYf… y&�̞=ٳnSK;ĈY@si߾}W^J899hK뻭VEttpss֢_~bn߹s� !8u� Ο?b1m4akk+:t "##ʣD۶mz_]!EXXVJ .]tb͚5RYQQ5kb֬YHLy,=&陸)a={b׮]pwwgcP<<(m@t=jt2w0D t" Jdd$agg3gJ_m _L)"P&jXII O.٠_D2̉蜥E}HLLDRRΞ=˗KeT`ƔWaBrr2qAvs*55=� 55cƌwA>}pe�@=giOCO˃�۷Gaa!�탛�ӧOk|1y"SQQQW2t2CW3aT*q-X[[묋 "2OSSY.\кb+:rSt :^K~zQ^AAA0aTfJef3grssKb1G~₰0>#̟?ZSO='|Rk`tC˄[nE}; +++9{<5),)) ;D_pdeeaΜ9fY LPf|>rvvT*ƌp+)wؚB8;rL:lW"br0D t, u,J/u}<2Dpo-8$kTpY:üe26nggg(Jxzz"%%E*h4GnЮ];L2F&VƒL>vvvppp@dddgu*L&1Pg5n8C!??F´iӤuСCGFFJKKjԺ-Uoxx8D<xu?ׅ|W 5cu]0yyyppp��}(,,�wF^��W^ŠApEI탛�ӧ@N >u+#VEPTRrjqaJT*q-X[[�T*Ѫxʽ;|[r;99!==eeePB sK۩S'dffJMA_ u>͚l>}:^uAR!99ƍz0g"77s1i]۷GZZ !i rf?Ou֘0aE}; +++5|<c<V?|ܦl{Ն*)) &z'9K'b׋x]뫠 \rYYY3g90jnCL۔m]gM>jc{J1c <<&}S<p66d{s\2ȉ3t"۔@'x r":C6#90'im8D , gD t>D rbS,&&2 111l(3۰6kö1ʟu|d KÇ,;ިfAN@'"b ^)T<-{ǝ] 7yM}Įqڭ'YnL67@9C' p$CH2~#:1ԉ8'b1ȉ蜥PXsʫk "91Љ~AN@'&ε,Y:Q<L p>o) N1DD@'""":qNDD tbNDD t_W"":qNDD tb):Ǣ$*� 61-fbV1Ӝ/c ͉T6ݏRmKqtqn-mzGolro mYudg%pgJFՙ}8匥au ΆDD ƿkKSH\;QuI� ʳەjoՁ]aUz_ޟSeي% 23kO>=kXΊJq%/[?َ-wZ]d]&o~/ܾ%HoPXCŋ b͆/Sʨ8 ;yS:}3 -QS'N .譫}~=nCSvߜ{g-c4eV⇨iZW^,G<`%GoX0xiN`�DmZ'KOwC O7Z(Q;п ~ēa)bte%`٤f04mcaӲ |_�" ws̠exxacXΊ}-:;:#hMұP#h=!.X%<K_0z[:>Q�F鎃;4y,.;*5zj�qaX8yΨmX]_:&b܆�M__ QOV#<L]m=䟋]GB)nBZߦov��U;]d2gk6 nmuvz'G��0ERM!�o~i�j~~w<�EFo|dm�sɿ:}`]:H]L_P�ӠhngbNý2~+�@֏ z W=.Є<PS ĉB5\H*~ {}�no5WpKA3PV24;ߺJ /Z|[K�H}}c`>XP>{O|c6[Ϡ"%5Rv@&ھK/tuٰ pL8S L P[`FLaċWoegT8o6y3Ղ ,oWfFr>Hltŝ91�|=ˡh1((-kӺn^<n\`دEmߢpTbܵ;�N]ͪ*qYȿSPu�/JP[r�⟽Ҍӣ�� WTv-/`Bm׊"}6̢;%&/lv frlܚŃqNgd; FC]S="v vV$Xxx?L86 A{:-s8e> ɌM=/xc'Rqܱˬ:e?on3?ޟ8G/ĩߎZyo/}Yt:>Ͻ/5Bw1<vLG~u/a J۱#XᔴLȆq,Y }9+ ZOljon _UQ{eںb}1̤esJB?|uKŃ582Qo6qߡ')9V[sv @(kD#q'Ƃ =ÏFcx&Va~8x8CZ%4 s�J -f94jN40':QPc1ЉNDDD t"""?GD ƙؕx~D`r%O1ЉNDDD t"""b1ЉNDDD t2GZ̊:멋:M]җuѮ7CmyE_|U∨_G.C^%ld'ܑoQ=l_>z�`c bp'YJߕg\_e+/9+^\!3zmlֱ+¤:uza|ox:ұd62Gm];ZX\,gFMgz^,*O߶T7&e<;yZw~!ӾR:JaoY7{^{[=3w1Щ !SsDl^*)xp;_b�Cq ϼa&K_8VVy\.B0} iAP{crKE?2օ xm6!xď;1p&yz<4a(N܆GGN!mnLKbuKbfM {'lR:wdeyꁯ`O ܱK@*ܟICǡ2*Ej̤мE!/q_�-=խOEX8O_ q\WXq1+aU4D>!7fS(1f:}%0!׹v GۢoLi "9ejuF?k.-aL붱뫍K@o6=WGcr5Z uGV(L"jSY1%~1QxkCEb�]QujO}4B@r rm&^\EߘW[/ƌ\pݸRR~лմ ø6.Qm +B#_2z-X VfxǤu� �� �IDATQ!TH<-_Ń=,Z'�4= X _ǖUza^\ #}p18˞Es |E%h.W`dԶBvwƮ->^ 9ukl&Cm]"Лcc;b�4j1XpNNѸX�^gX.FE巅P+&)]nt'�oI`,nKc|xGo۲!z-??yQbhLۓbtĤ1jx1ԩ=fEhe%Kg%k2! eߡ' mMs> 0mxm=0~~~lЉi1+ \YmKqi*ۓND/i~lOW#"b1ЉNDDD t"""b1ЉNDDD t"""b1Љ^%NDD@'""":3i1+,>Sjl}ny$9fh2G tjzO6lSoᭌ?nAW֢ΔM^<d‘{e&-lzRXwMUJ-0mu\JǒpJk6ξX ['0p֗��۞^8;hFoxi z۷FsĘ}(TI y9ؖrG*Vmp/7[R,4?۳J3ųn(`{5jC9~˯u-aۦbfϘܻ5B笱̼}4wL(v|Ŭw\x!*` Exxz6�ɻV@ M-R2U!}[Sl۽+ngƛ>�5 !>a$O|6b7EkX Ki7oI?" x vxg^ƺ}񾓀Sc}Cq ϼaFn#rQ$~x e2o;)ɹ{"U:hᱷ]_Q Jٳ_Ω֨] mHCߑ*~him|jսn;CoVm¤Z?�ȐSC`߾u;"d*{iR0�;'~*P)EϷLZ?ض-.4<Qmbo?:zu'슀^3l| %2pRniN69pAZXp/M {YMAv:?"iG8f6_.o@Ohݿv\6å%f='w,C\7pqQ 34o\۞n;Y r+[vD/9UL\:pU{K q{~0r@ڿXgg� 遥x o=:-/vBTԪ{C]u*mhrrʁh{9w'G᭱a`5}۹!|:ST;BjܦBh0( whλe5ZiS}cT V#c<< RWUQ?�>u_h8dl�sO䢃ǫ1dmwJMZߘ6d^1b:}ȏܝ\xQ8{[#̫˛c]9fT[rīOb5|[CO&cW?`h`>2ܻW25Fx 2~!僻*{βl&9Y~t{e {PigL;>1^jg*jmJ km_/䣕 膜VYZfzEUjL 8EzSq*^~!m^wī ,?bT[#b~8dq�zkoI`,`jq%T}gBe6�=:<?yQe_D|^"c ]4iOC~ օ"_7&Y z^2MϤ>JY25F`g.:WNcĻU9[?C魣ǴFcMnj6dxiR0<Q !vJƞ`Xi  ixʝNT?qvND t"""bDDD t"""b}A'"bDDD@'""bDDD@'Ŭ0q>2.k:jl'K؋_޵/k+!c*q U[<76?o~Æ!bF]KNlK#+|a{ضX}�ƶ'N688 ;yS v?_ pBKԉ~?K5zj`_0D&w\p5.;^ug}=fkcHT<PvZ ~g{^nB#⧛]_ ݽo܉c�`1ILN}mzUváH|G?�L\4e?lǁ,?2ގG?x "q zS(|';OW0|y޺,J|Ͳ'1U0iF|v�; 9e;Ouu+'_1gmDԱ?:q9kcowg,''.*spk' EV l8xy2~@a;P5 `rI]A}[�֝Cg l*,?ގ׳?VF �' w_yv'?&9�,:o@A�׻3w}&DžNپ K n%bSe׫19Je-#D(L}VPdP�Tg]�F5qyw d>xG[ɀRO[j ɀ)I} SEuDuro�Z+dwerk~cW7XɛiN>�()ԵTv-e/j�wZumV,-׵]�`d tb3\aOXe\^>z5i|¢zus9͕>E7uK;*|3t2sql yB|F-5"I68 I2q�wpዱAvn8%-Gl2Kֆ \&-'^|k6\=|_Eufÿsö̻ �EO1 Zux?6w20oz5m]/ކ%3SD)ΟMCP^--7b⃷+D'Z+{ RCkk?OX c4P<NDD@'j:8;'":*qDD t""":1ЉNDDD t""":1IF ""zSq&v%DDD tzW#"b1ЉNTҿ^EXӠڬD@!#jl ꖱ`/\cϸ'Ik;-]~*x 3GB^Z_ZԻ6yA,K/ I@'sWplc\@w'GCLⓗ0fKVۇ/g, Ke5"<^);w`[\V m|l] kn^mKqb_]/|9I;`x]sթ3�qmO|j-&Σ0N�Vms|wK(-=1pzb�sv[w,7T4wN2l F@.eͰ6Z?"c0 DwAbZeC^ xLXC!C*W> '#/J�`|޿hc1q)΀ '�Pl=7UVVr4nϿ-�6O_SkOs�J3`D tK?:zu'슀^1̐)]ޟbu~KpA kxaQegKKx{&NX�~͸0dmθ0o'2d;-8a:&B �8]|hH?vF~"M_{VLJNDCng@K29zt<[^FBh`^1dRh HfT:@#�*}`P@0ᲇ^Zh(Z:MqP k9ZE{�W"^>P{VfPZxO."Щn DYiY<(ݽ﯃e~EXl(c/jmZ0nh.o>wQeb5|[V}z!6=aKn1xu<YWfF\aezGzBdl~-aԺgum7v;�شE@vʊ(s܆Q2}- 0?5l ߳Kc'/ ,?bT: xKތ_D|NImʶF@9rN~):8,=N狊8I0V{}>ok޵;fņ6.}�T�8? !vJ?ڰ&CO빎p}޴Lի/^ak6w̧1' C'bV@Xjs MsCB�'b9#cZu$"?=tFoї&":A&!&& ADD 8"""bף9D@!O1Щ;19;osNv'bgDD@ү 3鈈NÐf@;ېNT+!CDD9;&6K'":1Љ^t;5<N@'""":gDDD_[:W|U8ڈI'bS ga~p~]?c)w&(&&lC/|F:g =ƣzQ?7yooOl gDDM"U?bsvNĠb[1ЉD tΉNDuۋ蜝CND (s"b@'"ێ^t;1ԉNDP'bsvN@"":_sΉDlCb1ԉ蜝CND "%19;윈@'"":19;׵a im$*� mJT vF_ܟBz;?Zbv(gMpv+U?y3glS"Л.-NKY3=nxBT"�@ٮSkq)KVlÑ*ew zAq�`쌰}= -y\d:? JM>136;-(XoVmٌn#'"CVYNFNYe)|NFw_l8y/+qN t׳?Vl| %2pReٷni=.-18c¼aCl[#<历jXˑTXj~m'C叢Wh uBAݖͬ;"QPw ٕYd][#̫˛c]9fr ZV6L"ԉt,=ٮi1+U&6/As/ؾˇ+ Q};=un3t6Di]n=aʱz7ޚem5enH#8CZ%4 : tÜ3H"b79_8N@윈Aö&:1ԉ9CDĀ!9]s:Du|gdJ#݉90DD@'"h&蜝1X@';bsvNDP':1P}@M=9;A?b1L@'"""zt;gD|RS!8K6> } ???6"g s"-&&l:$D|.R6S}OЉQB@&!|EJ<B t""bs"""Љb9CDDP':g<hG:1蜝ÂD t",<%:1ԩ :O1$3tA\esvNDP':1 }FM=9;%Љ@7:gDD ub D.5@윈@? 9U>@'"}HM=9;'"bgă@윈t:1Љń9=Oױ3t""<CT6m4 ѭ[7kSLAAA.22̙3RsvvvhӦ |||ڵk\np2LfII �DzF'"W^App0nݺ#G_~֭[C!>>(--Ehh=gbRf̘l\t ;wƂ ?|0?Fcp|BheͲeː$$$ )) zSBcS(ťK3gH\"v5RSS)))ťuݾ}[tEk/>]Tֽ{wHNNcs;Yݻwww}T>}YfP(ppp˗޽ rrr 1bڵkL6m //Oky''',SYnnvCk;tܩ㇣ƥꫯ`ڵ2eV jBj/\wΝƍäI:_^e2_N#==]k;[NDDF@@�N>?‡ƦO_iiiPTHNNƸq㴖 BNNrrr &Hehݺ5Zl,2۷GZZZ}={6ٳg# J&۱cի">>^*S"::Z kkkѯ_?{nrJѡCakk+O.}^z +++$Ədh(Ѷm[ĬYfEEE b?JN7gOnMSk? >??ܴ|guRÃ*P]1Q:_݈tjsDbb"zqF8;;CT)))R9ʯ|m~8['PT1cUViok6>}TWddd<q`){ѣC\\:|5 ӦMjr !i&L<:ۦ.o&?Ns!)) Z]4ܔ~ GVVHEԄٳ�r�Μ9WW*}&O&�o>)]QQQFO+jV.,'.((JFʯ[ǎ?tcԷM41?-j...Uz WPISSSk|"# @5k,qF!|t7߬Nj/^^^";;[\|Y<S/@x{H�G��5IDAT{Lq=d1dիm۶BR !Æ ƍ"00PzlHH/_s<v̘1"''=}666ZW)**666Zh4.#EvvIu&]>|��P(Lt}ehP[ tCR*NM˷~+B5J;bB!w}WMd;ؼynC$''fc&w5\2h/..?gv'O&U)wqq[lׯ_eeezjjvZG'GJJIzmy@'UXX(\\\ݻwyh׮(((dI;Vl޼uTdΜe]rɅM &`Ȑ!}As~8@8㵖 4~3grss9sMDȆIT6lڶmC޽accSeΝ;kYFFVcs='t7Ly}}n΅+?-]_MB�]ɏiӦ [[[ѡCiR)o!&8K#6mڈիW !XjhӦٿ!!!bȑ";;[dgg ///|P>}ĠAʖ-[& &222ŢEt˔6S8d֭1Є;wN�BSN �:$CI_02J%.\(t"lmmŚ5k̚06_a㯭# ׆0o<`ԩxᇱf͚&={Į]$@{ﰡqvvT*ƌp:ܹsM;_8C'Јcϧ1ЉjDD@'"#b34 bNT+q 1ЉDDD@'r%":t":DD t"""bQ35D tNDDD t""":`ND@'" ;":1Љ8;#"b_1ЉN/DDD t"Έ〈NDDD t"fgDD@'FŽ݉NDDD t"""bYOD3ݻwmKLL :tY%gD tl4^om;w.vUk_}v?>/RmmhD@Fvj2{Exzz ܥKпe^"bS#zj@R5d-:v={`尳9ldd$agg3gjW <<ݺuCv0ehkڵptt\{ 00ppp@`` JJJ5 ryWL7JHII1+QjZ*2k׮ETTTv9mڴZ6f<CT6mW㘧݉NM^߾}SOaÆ fב,cEff&/^9sh=Gbb"pY,_\*[n:xdddZ>|ǏFѹ-˖-Cjj*$DDDhXzgqqq8t1j(L6"W.+W⧟~B||<.\l3f@vv6.]Ν;cF6f_y֭[8r~>yɫY"Q>\"v*n޼uu@\~]!DqqqZMMMn鶛8st|*.EԽ{wu$''k BT֨~}Yv=DrrQ}wmѥKkvoGGG-.]T3QS"|s:Z>""{m�eY+ld7&s^8_%=Lb$' 1?^8A0aLIrk(nuVcFa8)9]'|?o99ϭ<Ow~m6🣝=n8AX,�@OOl6 �`„ ()sUՊ@ hնhhEvc8}4��l6?SoHOO;1i"NӘr_MbvggimmUl󡯯e0akRTe ^Yͅ狚VQQe˖)եꛡR|߱sNXi8pdZꫯFƌx뭷ݍ˗//hoo¢EJ\Bz+Ӊkcڵp:I''N^|9ڊ@ �˥uww;w܁6ZJu^ӉƍDL6/јU800 =jGyD,L2E}]k֏l"b٤Rzzz~iӦIjjw}R__i0*̔L`0>o.iii1HBHNNl6ٱc EEE"yyyRWW:WD%|'|"EEEbXFq hp8ND4:f 9NЉ:q@S4xLִ;?twl8#D"D7MP_(:]�4n%+V7Ϗ-\aa!~�@{{;L&:::��.\@aazh+g(m<y2nݪ-Zyz"镩էF?P(5k(el۶-VĶqiF3rL4I@FF~7kH-DiX?9EХukQ)2Hl79455�+�cǎ'4\xڥohkkũSpѸ?#镩էFۼi&v1ՋئcPQQ{OuC= k( 9Q̷iEJ$W""Eb w!q:""p8䥗^s犈ȳ>+-0EM/J[ss}9;1-Zen:7mЉħUҥK'PHDDV^^ 8 R8qB{1l@�lk0*fYfL& &)jׯ_fI t|啧qEgXT qE`0(%fze2Zbɓ'LvʡCTi-kp)W-DMH1&ՊB޽3gDZZf̘]vaJQ#L_˗gFˋ7b^z}j٪|/]XmZ~^|E;0k,퍧-D\Ca%`e$hp8yf̞=�0{ll޼Y;z &ojtttq6FL+SOy…z֭ӬV6H|\??0RRRPSS%K(vko<m!: +zK$W$<-RKK�gϊș3g\x1zE-r_0UVf ٸqELW^,{zzdJ[n/Z۠/kPD^rrr_9_zm!m6x<(++ӝzeƳҞ={pelٲe ;rى6TWWlTyعs'^x^t4g1sLB!<Өe'}Ʉ]vi~ah\r'1y;2r'k'9oCy~2?o<lܸ) uuuYYYIy ??YOC|kQRRχ4^|B':E\́F7T眜<Sػw//$~Fiihh@yy-O0?kE'V__6l؀)SfaJZ"≾IMeO/]z �_͋8SlhE':Ֆ-[pq466~_IK$[<iS8^]|A9s+҉b^D2~x%*UiE'=#^PDp'Zd^^ҍD3Ӌl۫F9J^`"")F x^dffj"(Nzzz`Zyl8ۍ��ٌ0aB500�3JY}zzuԫ �\v Ÿv/$S4ɓ Ot\|iDp'PO/=ut())iw"ۜ9sϏ':QUUV\.%-nF_'ܹsyQNKv"i+ IMMf;v *[׌)Q.~_v矼XN5tٳ8p�;c)//GIIoSN#NDF>F[Q7nvMpP'D&MD4[DDDЉFM:q@'"""DD):q@'"""DDDЉ:q@'"둛{?GFF~`p"^�@KKmGAAG|Ӈ:u*ۇҘE't"Aa6a6?ֺ\rfO T^ iCY.q@'">�˖-ҥKz{{QYY ݎɓ'c֭聁Fzz:VXebݺu<y2&Ou֡Wybd9=n8ٳZ5k~g7<Jzq}}}ذaL͆۷+i---X`v;.?0T^8ۋcXd ۧHjkk׋SNѣ~m455.]7aÆ8wn7n7<6mڤzbͧ#G V^##ٲe ?F+i< ֬YߏW";;/viH)јgO<l?+SNfeܹs~6m\pA)S,oԩr9eJAAw -VuPL߾{׿[rrr kZ[KqD4̙3 .ĢE��{ŧ~�V+/�zzz`Z' &  )O7gZ`Xl62urY"L;qi�lF__Ro;a׮]7|ŋ+K,#Gp5�@VV._ڪ#//>}}}oCvvvڤ`-77>/jZEE-[EWW~ND4}GXhjXDtR|��Ӊjtttժ<*++rJ477# "fNk׮ڵkt:&'N^|9ڊ@ �˥uww;w܁6ZJu^C/cWh(..cǎݲI~ j*l!7n~iӦIjjw}R__`0(UUU)RUU%`05t}zo߾]bCc = IMMf;v(i RTT$)))'uuusM_8dq (aeeeLqʝhs\D[[QVVNao<H-??3gD(O?Zv {r'""8NDck{a-Oyaƍ|B'"IPTTǃ,CXM x^QRRχ4>~! 46੧޽{Gt;8јЀrվGVxUF;+,,/�hoodBGG�… (,,ԬVDe'32'כFN[køqsNd2*=R(š5k~ڶm-̟?_5t"%K+௿w}Qh3Á&�W_}TǎÓO>YWf@#ڑ}駟000`H6mBKK <n7>|1> Μ93/n[ƒK(R͕:zjR:tHN8y饗dܹ""Ç{dDdG{KV?�+W+= ^G"D4L4I:;;UN<)eeebۥP:dE`0(EDD_.?f@ -9q<cb�٬*GUvxݬVf1b2Lt}�d``@WPc#씌}msʝƔb<yR_|~wܹ+V0V0ՊB޽3gDZZf̘]vaQÒӋhp<OH٪c/]t1n%%%\C'")̙9N?7nh2~kԋp8yf̞=�0{ll޼C7ofɎ w`"SP{ rXn-8p�s:H%v]~O>"X,Hcc_a---@Ξ=+""gΜ�rEݺE4Kvd"ŚrWP{===zjnݪJb?6_,CDc믿g] rphs\X~=z{{sϡ;v`njAFDt;b|V[. �NDD8t"""NDD8Q &ͥ����IENDB`����������������������������������������������������������������dunst-1.5.0/docs/dunst_layout.xcf�������������������������������������������������������������������0000664�0000000�0000000�00000144415�13706263046�0017056�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������gimp xcf file���������������������B��B����������������������������gimp-image-grid�������(style solid) (fgcolor (color-rgba 0.000000 0.000000 0.000000 1.000000)) (bgcolor (color-rgba 1.000000 1.000000 1.000000 1.000000)) (xspacing 10.000000) (yspacing 10.000000) (spacing-unit inches) (xoffset 0.000000) (yoffset 0.000000) (offset-unit inches) ������������� ������@�� ��+��<��C4��I1��a����������������������"Indicator for Actions/URLs/dup...���������������������� �������������������� ���������� ���������� ���������� ����������������������������������������������gimp-text-layer������(markup "<markup><span size=\"12288\"><span foreground=\"#000000\"><span font=\"Sans\">Indicator for Actions/URLs/duplicates</span></span></span></markup>") (font "Sans") (font-size 18.000000) (font-size-unit pixels) (antialias yes) (language "en-us") (base-direction ltr) (color (color-rgb 0.000000 0.000000 0.000000)) (justify left) (box-mode dynamic) (box-unit pixels) (hinting yes) ����������� ���������������<�� r�� ~������������X��C��%�� X�������P��(�P��� �������:��� ���������h,��u����s��&����q����� ~������{#��A �������������� ��*��,����,������*��,��������������������4������������������*��+����,��^����*��,����������������z"��P"��)��������������u����q��Jm��r�������������� ����*�:9������q��xy��s������h,��Cd��������{#�������� ~��S��*��,����V��U��,������*��,���� ��m��`������[��\����������������2I��*��,�����,������*��,������K�����r��q�z"��)����������*K�C����@��?�q��r����������XA�}�<�1�~����i�����2�����%��� �2������~���������� �~������B������� ����Cd��B�u������v���������������S������������������� ����m��`��*��,������*��+����`�������#Q����2I��`������������������.��/���� ���K���*��+�� ����*��*����}++z����g���*K�C��悹��~ !������}|����F��XA�}��u��,i��v��� �1���=��=������I����"�����s��&��| ��Cd���{#��A ����r!��S���,������'����m������4������2I���,��^���� ��K���z"��P"��)��o)-��*K���q��Jm��r��mO��XA�n����r����������9����������3���������padding���������������������� �������������������� ���������� ���������� ���������� �������������������N������������������� ��������gimp-text-layer������e(markup "<markup><span foreground=\"#000000\"><span font=\"Sans\"><span size=\"12288\">padding</span></span></span></markup>") (font "Sans") (font-size 18.000000) (font-size-unit pixels) (antialias yes) (language "en-us") (base-direction ltr) (color (color-rgb 0.000000 0.000000 0.000000)) (justify left) (box-mode dynamic) (box-unit pixels) (hinting yes) ����������� �������3�������� �������3����� �������H����(������%�����v��&��u��u����h,��v����A �������� ~����*��+����*��,��*��,������ ��(��*������4��������������������*��*��^��*��+��*��+��������(��*����P"��������������v��Jm��u��u��������}���,�.���)����)�o ����l���������frame width/color���������������������� �������������������� ���������� ���������� ���������� ������������������D������������������� ��������gimp-text-layer������o(markup "<markup><span foreground=\"#000000\"><span font=\"Sans\"><span size=\"12288\">frame width/color</span></span></span></markup>") (font "Sans") (font-size 18.000000) (font-size-unit pixels) (antialias yes) (language "en-us") (base-direction ltr) (color (color-rgb 0.000000 0.000000 0.000000)) (justify left) (box-mode dynamic) (box-unit pixels) (hinting yes) ������������������l�����������������l��������������@�P�4���:�-��������5������q�&��|Ny>��| ��'��(���u������A ��cc��r!��g�if�i�����������������'����I��H���*��,��������4����������  �������������^�������� ��w�w����*��+��������P"��������o)-��ns�tm������)������Jm��������mO��%.�/$����u��r���h�h�h�-�������2������~����h,��B�s��������q�� ~���{#������������ ��`��,��*��,����*��,��������`���������������������,��*��,����*��,�������C��z"�������������}��q��������� �1�w����6����������D��������� line_height���������������������� �������������������� ���������� ���������� ���������� ���������������o���_������������������� ��������gimp-text-layer������i(markup "<markup><span foreground=\"#000000\"><span font=\"Sans\"><span size=\"12288\">line_height</span></span></span></markup>") (font "Sans") (font-size 18.000000) (font-size-unit pixels) (antialias yes) (language "en-us") (base-direction ltr) (color (color-rgb 0.000000 0.000000 0.000000)) (justify left) (box-mode dynamic) (box-unit pixels) (hinting yes) ������������������D����������4�������D���������������@������������ ���������������h,��| ��h,��| ����v��h,������ ~��r!�� ~��r!������ ~�������� ��'������ ��'������(��*���� ������������������������������������ � ����� ����(��*��������������o)-������o)-������������������mO������mO����}�����4�.�9����o ��7�7�7���������������)��r� ����"����������D���������separator ...���������������������� �������������������� ���������� ���������� ���������� �����������������^���������������������������gimp-text-layer������t(markup "<markup><span foreground=\"#000000\"><span font=\"Sans\"><span size=\"12288\">separator\nwidth/color</span></span></span></markup>") (font "Sans") (font-size 18.000000) (font-size-unit pixels) (antialias yes) (language "en-us") (base-direction ltr) (color (color-rgb 0.000000 0.000000 0.000000)) (justify left) (box-mode dynamic) (box-unit pixels) (hinting yes) ������������������D���������� �������D������� �������������=���Cd��| ��v��&��q�&����q��S��r!����A ����A ��������m��'����*��+����������*��,����2I��������4����4����������K�� ��*��*��^����^����*��,����ߧ*K��o)-����P"����P"��)������XA��mO��v��Jm����Jm��r�������=��=������������������2����������~����'��(���u��h,��B�s��������g�if�i������� ~���{#��������I��H���*��,������ ��`��,��*��,����*��,��  �������������`��������������w�w����*��+���������,��*��,����*��,��ns�tm������)�����C��z"��������%.�/$����u��r�����}��q�������!�1���o�o�o�G�q���������� ����"�������������������Age of notification ...���������������������� �������������������� ���������� ���������� ���������� ���������������<�����������������������������gimp-text-layer������(markup "<markup><span foreground=\"#000000\"><span font=\"Sans\"><span size=\"12288\">Age of notification\n(show_age_threshold)</span></span></span></markup>") (font "Sans") (font-size 18.000000) (font-size-unit pixels) (antialias yes) (language "en-us") (base-direction ltr) (color (color-rgb 0.000000 0.000000 0.000000)) (justify left) (box-mode dynamic) (box-width 11.000000) (box-height 6.000000) (box-unit pixels) (hinting yes) �����������#s���������������#��+��+������������#��'��*����������d�P���:����:9������xy��v��| ����h,��������r!������ ~������V��U��(��*��'����*��,������ ��*��,����[��\�������������������������(��*�� ��*��,��������*��,����r��q���o)-������������)�@��?�}��mO������������r� �.�9��9�o �q����1����1�q���1�H��Cd��h,���'��(��&��v��|���S�� ~���g�if�i��A ����r!���m���� ��*��,�I��H� ���(��*��'����2I���������  ��4�������J��K������*��,��w�w��^��(��*�� ��q��*K��������ns�tm��P"����o)���XA��������%.�/$��Jm��}��m���2�.�9��)� �o ��������D�V�8���2�������"����$���s��&������h,��������{#��A �������� ~��������,��������*��,���� ����������4��������������������,��^������*��,������������z"��P"��)����������������q��Jm��r������������� �� ������ �� ������ ��� ���h,��q�| ��Cd��h,���������� ~����r!��S�� ~������������ ����'����m���� ��*��,����*������������2I���������������������� ��K������*��,����*-��)��������o)-��*K����������O��r��������mO��XA�������������6��������~��������l�u��K�����,��������+��K���m�u����~�����E����������"�������������*������Number of notifications ...���������������������� �������������������� ���������� ���������� ���������� ���������������������������������������������gimp-text-layer������(markup "<markup><span foreground=\"#000000\"><span font=\"Sans\"><span size=\"12288\">Number of notifications\nthat are waiting to get displayed\n(indicate_hidden)</span></span></span></markup>") (font "Sans") (font-size 18.000000) (font-size-unit pixels) (antialias yes) (language "en-us") (base-direction ltr) (color (color-rgb 0.000000 0.000000 0.000000)) (justify left) (box-mode dynamic) (box-width 11.000000) (box-height 6.000000) (box-unit pixels) (hinting yes) �����������.X����������*�����.t��<��<����������*��.��4*��9��<0���� � � �\���P�������:�W�������,��������|Ny>��v��| ��q�����q��������cc����r!����ꂻ����������������*��+��'������*��,������z����������������������������� �� ����������*��*�� ����*��,������[��~ !����������o)-������������,i��������v��mO���������8���� ��(���� ��'��h,��&��&��q�| ��'��(��� ~��A ����A ����r!��g�if�i����� ����� �����'����I��H�������4����4������  �������^����^���� ��w�w��)������P"��)��P"����o)-��ns�tm��r������Jm��r��Jm����mO��%.�/$�����%������ ���������q��������H����h,��u����s��&��| ��h����� ~������{#��A ����r!��������� ��*��,����,������'���������������������4��������J��������*��+����,��^���� � ���q������������z"��P"��)��o)-������������u����q��Jm��r��mO�������� � � �?���V�3�����2������������� ���h,������s��&������ ~������������{#��A ���������� ��*��,����������,��������*��������������������4������������*��,����������,��^������*��������)��������z"��P"��)������������r��������q��Jm��r�����H��������!�����&������h,��v�����v��A �������� ~�������������������� ��(��*����*��,��(��*��4��������������������������^������������(��*����*��,��(��*��P"����)����������)����ㄶ��Jm����r��������}��r����}��.��.������o ��o �M�����~��������� �����l��,����u��u��| ��h,��K�� ~��������r!�� ~���� ����*��,��*��,��'������ ����������������������������*��+��*��+�� ������K����������o)-������m������u��u��mO�������;�~��� � � �����h,��Cd�-��� ~��S�-�,���� ��m�/�������2I�-�,������K�-�������*K�-�������XA�E������ �������� �����| ��u����Cd��v����&��|�r!����ǀ����S������A �z��F�r!�'������*��,����m��*��+�����"�X�'������������2I��������4��Y �� ����*��+����K��*��*����^��7:�� ��o)-��)��܂����*K������P"��B��o)�mO��r��u����XA��v����Jm��O��m�"���h�(����(���j�������� �� �� ��u�������*��,�������*��+�-���O��u������f����������3��� �������v���������horizontal_padding����������������������������� �������������������� ���������� ���������� ���������� ������������������k���������������������������gimp-text-layer������(markup "<markup><span foreground=\"#000000\"><span font=\"Sans\"><span size=\"12288\">horizontal_padding</span></span></span></markup>") (font "Sans") (font-size 18.000000) (font-size-unit pixels) (antialias yes) (language "en-us") (base-direction ltr) (color (color-rgb 0.000000 0.000000 0.000000)) (justify left) (box-mode dynamic) (box-width 11.000000) (box-height 6.000000) (box-unit pixels) (hinting yes) �����������?>�������v��������?V��C(�������v�����?j��Aa�������@��7�������� ����*�� ���h,����q�����h,��&���� ~��������c��� ~����A ������ ��*��,������9�*��,���� ��������������������������������4��������*��,�����;��*��,��������^�������������g��������)��P"��������������������r��Jm��������N����+������(�����v��&��u��u����h,��v����A �������� ~����*��+����*��,��*��,������ ��(��*������4��������������������*��*��^��*��+��*��+��������(��*����P"��������������v��Jm��u��u��������}���,�.���)����)�o ����;�����������������text���������������������� �������������������� ���������� ���������� ���������� ��������������������������������������������������C�������������D��I ��I��I%����������E ��E��E-��E=��EM��E]��Em��E}��E��E��E��E��E��E��E��E��F ��F��F-��F=��FM��F]��Fm��F}��F��F��F��F��F��F��F��F��G ��G��G-��G=��GM��G]��Gm��G}��G��G��G��G��G��G��G��G��H ��H��H-��H=��HM��H]��Hm��H}��H��H��H��H��H��H��H��H������������������������������������������������������������ �� �� �� ���������������������������������������������������������� �� �� �� ���������������������������������������������������������� �� �� �� ���������������������������������������������������������� �� �� �� ���������������������������������������������������������� �� �� �� ���������������������������������������������������������� �� �� �� ���������������������������������������������������������� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� � � � ��������������}���}�������>���>��������������arrows���������������������� �������������������� ���������� ���������� ���������� ��������������������������������������������������I�������������J���a{��a��a����������K ��K��K��K��K��K��K��K��L ��LA��Mi��N��O��O��Pk��P{��P��P��P��R:��RJ��R��R��S��S��S!��T��Ub��U��U��U��U��U��V]��WX��W��X ��Y��Z%��Z5��ZE��[��[��\b��\r��]��^��^}��^��^��^��_5��_E��`��`��`��`��`��`��`��a��a��a[��ak���������� ��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��������������������������������������������������� �� �� �� ��������9��>��=��>��>��>��=��>�� ����������>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>��>��>��=��>���������9�<���;���:����:����9����8����9����8����8����7����7����6����5����5����4����4����4����3����3����2����2����1�� ��1�� ��0�� ��/�� ��/�� ��/�� ��/�� ��.�� ��.�� ��'���.�� ��.�� ��.�� ��.�� ��'���=��=��=��������� ��<��=��<�����������<��=��<��<��=��<��<��=��<��<��=��<��<��=��<��<��=��<��<��=��<��=��<��<��=��<��<��=��<��<��=��<��<��=��<��<��=����;�;�;�0�6��������� .�;�;�;�;�;�;�:�:�;�;�;�;�:�:�;�;�;�;�;�:�:�<����z��������� �� �� �� ����������������)��>��=��>��>��>��=��>��>��>��=��>��>�� �������5��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=�� ���������������6��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��=��2� �������@� ��������� �� �� �� ���������������� ��=����3����3����3����3����3����3� 2����3����3����3����3����3����3����2���2���1�����1�����0������0����9����9��<��=��'�������/��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��=��=��=��=��=��=��=��=��<��=��������� ��=��>��=��>��=��=��>��=��=��>��=��>��=��������������������������� �� �� �� �������� ��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��=�����������=��<��=��<��=��<��=��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��=��<��=��~�������(��=��=��=� '� � �>��>��=��=��>��=��=��>��=��=��+�������8��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=�� �������@��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��,���������>��>��>��>��=��>��>��>��>��>��>��>��>��=��>��>��>��>��>��>��>��>��=��>��>��>��>��>��>��>��>��=��>��>��>��>��>��>��>��>��=��>��>��>��>��>��>��>��=�� ��������� �� �� �� ��������6��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��<��=��W�����������������=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=�������������������>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=���������3��>��>��>��>��>��>��>��=��>��>��>��>�� �������@��>��>��>��=��>��>��>��>��>��>��>��>��=��>��>��>��>��o� �� �� �� ������������������������'��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=��=��>��=�� ���������������(��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=��>��=��>��=��=��>��=��=�������������=��>��=��<��������� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� ����>��=��=��>��=��>��=��=��>��=�� y� �� �� �� �� � � � ��������������}���}�������>���>������,����������dunst_doc_base.png���������������������� �������������������� ���������� ���������� ���������� ����������������Y���t�����������������������������bZ������,��������bz������������,�����b��m��s��})��������2��x��'��U����6��P��t��\������4��L�����ê<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<((((+((( (:((((((((Q( ((( (l((,P((((((5( ((( (V((@t((((((-((( (3((v?(((((()( ((7 (3(()K+1((((()( ((-( (V((5(,((((-( ((( (k((Z((P(6+a((5( (U((( (:((}((1(1d((R( (^(((((((,((<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(((((((((((((( ((((((( ( (((Y((0(((Y((((B((1I((/`((((1I(((( (@((5((((;(*((((5(((((( (Ն/*((((((;(*(((((((((( (z+m((((((-_((h(((((((( (O((((((1((g(((((((<(<(<(<(<(<(<(<(<(�ê<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U UVUUVUUU UwUUUUUUUwU UUU UlUU\\UUUUUUlU UUU U^UU؄UUUUUU^UUU UWUUڂUUUUUUWU UUn UWUUV\\VUUUUUWU UUVU U^UUnmUVUUVUU^U UUU UlUUUUU[[UUlU UbUUU UvUUeUUeUghUUwU UUUUU UVUUVUU<U<U<U<U<U<U<U<U<U<U<U<U<U<U<UUUUUUUUUUUUUU UUUUUUU U UUUUUfUUUUUUUVvUUXUUXUUUUXUUUU UUUXUVUUXUXUUUUXUVUUUU UWXUUUUUUXUXUUUUUUUUUU UwYUUUUUUWUUjUUUUUUUU UUUUUUUhUUUUUUUUU<U<U<U<U<U<U<U<U<U�ê<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w wzwwwww wƑwwwwwwwww www wwwzwwwwwww www wzww֦wwwwwwwww wxwwԩwwwwww~w ww wxww}yxwwwww~w wwwx wzwwwwwzwww www wwwwwwywww w|www wőww}www}www wwwww wywwww<w<w<w<w<w<w<w<w<w<w<w<w<w<w<wwwwwwwwwwwwww wwwwwww w w݅wwwwwwwwwwwzwwyww{wwwwywwww wwwxw{wwxwwwwwxw{wwww wxwwwwwwxwwwwwwwwwww wwwwwww{wwwwwwwwww wwwwwwwwwwwwwwww<w<w<w<w<w<w<w<w<w�(((((f((;݆(((;݆(((((((\*A(((\*A( (.((7(((z+(((z+((B((-((((S(((S((z+(((((p05(((p05((fQ(((((F(((F((I((((((>((=(( (H(((H((((((1f((p,( (((p,( (B((((/`((~;( (((~;((@((((;(*(()Z( ((()Z((Ն/*((*((;(*((B( (((B((z+m((~((._(((((((O((K((2i(A(�UU(UUfUU|ڌUUU|ڌUUUUUUUiVsUUUiVsU U`UUnUUU辉VUUU辉VUUUUVUUUUVUUUVUUvXUUUUUXhUUUXhUUhUUUUU}߆UUU}߆UUUUUUUU>UU=UU UUUUUUUUUUggUUvVU UUUvVU UVvUUUUXUUXU UUUXUUUUUUXUXUUVU UUUVUUWXUUUUXUXUUwU UUUwUUwYUUrUUWUUUUUUUUUUUhhUAU�ww(wwfwwؑwwwؑww߅wwwwwzwwwzw wwwwwwÕw wwÕwwwwwxwwwywwwywwwwwww̎xwww̎xww~wwwww݆www݆wwwwwwww>ww=ww wwwwww݅wwww}www wwww wzwwww{׹ww۽xw www۽xwwwwwwxwww}w www}wwxwwwwxwwwyw wwwywwwwww{ֻwwwwwwwwwww}wAw�(( (( (( (($( (((()|(+e((k(+D((((3((;((((j?((v{8((I(wI((((J((((((D((((;*(;*((((;*((((((C((?( (;*(;*((+((;*( ((j=((y3((H(wH(({((I((()~(+((p(+D((A((4((F((((((((((((((((((((((Y((0((qf((1f((0(+;(6(((1I((0_((/_((/`((/`(((v((((5((((<(*((;(*((;(*((;(*((;+@(((((((=(*((;(*((;(*((;(*(()ۖ( ((((((0`((.]((._((-_((]( (h(((((0((pf((2i((1((7)( (g((((F((((((D((�UU UU UU UU$U UUUUWUZiUUjUZZUUUUjUU|UUUU_UUtnUUUUUUUUUUUUUZUUUUXUXXUUUUXXUUUUUUZUUYU UXUXXUUVUUXXUUVUUUU^UUXUUUUUoUUUUUWUZUUlUZZUUUUlUU}UUUUUUUUUUUUUUUUUUUUUUUUeUUgUUggUUfU\XUpUUUXUUXUUXUUXUUXUU܉UԎUUUUXUVUUXUXUUXUXUUXUXUUXUXUUy\YUUUUUUUYUXUUXUXUUXUXUUXUXUUVU UUUUUUXUUWUUWUUWUUkU UjUUUUUeUUgUUhhUUhUUpU UUUUUUUVΦUU}UUZUU�ww ww ww ww$w wwww}w}ww~wywwwwwwwwww{wwwwwwwwwwwwwwwywwwwxwxwwwwxwwwwwwywwxw wxwxwwwwxwwywwwwzwwxwwwwwwwwww}www~wywwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww}ww}wwwxwwwwyww{ww{ָww{׹ww{wwwwwwwxw{wwxwwwxwwwxwwwxwwwywwwwwwwxwwwxwwwxwwwxwww}w wwwwww}wwzշww{ֻww{ww~w wwwwwwww}ww}wwwww wwwwwxww{wwwwyww�@(м݆((((((((((((((\*A((((kp(kp((.((4W(MS4((z+((((((((((B((?((.k<((S((((((((((z+((((((p05(qC(((((((fQ((((Fy((ک(R(((((((I((((+(;(?(:(+(((((!(((((Y((( (0((-((H( (1I((B((( (0_((0F((((p,( (5((((@((( (<(*((((((~;( (((Ն/*((( (=(*((6(((()Z( (((z+m((h( (0`((9((h((B( (((O((g((0((,((g((('(F(:(((�@UڌUUUUUUUUUUUUUUiVsUUUUruUruUU`UUZU]kUU辉VUUUUUUUUUUUUYUU`XUUڂUUVUUUUUUUvXUUUUUUXhUm~UUUUUUUhUUUUeUU߆UUUUUUUUUUUU\U;UyU:UUUUUU!UUUUUUUU UeUU^zUUU UXUUVvUUU UXUU؊X}UUUUvVU UXUVUUUUU UXUXUUUUUUXU UUUWXUUU UYUXUUXUUUUVU UUUwYUUjU UXUU֭\UUjUUwU UUUUUUUeUU\UUUUU'UU:U}UU�@wؑwwwwwww߅wwwwwwwzwwwwwwwww{wzwwÕwwwwwwwwwwwwwywwxwwڣwwzwwwwwwwwwwwwwڎxwwwwwwww~wwww}ww݆wwwwwwwwwwwww:wxw:wwwwww!wwwwwww݅w wwwwww wywwzwww w{wwzwwwww wxw{wwwww wxwwwwwww۽xw wwwxwww wxwwwxwwww}w wwwwww w}wwywwwwyw wwwwwwwwwwwwww&wxw:www�((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((�(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U�(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w<(<(<(<(<(<(<(%(((%(((((%((((( (+9((*K1f((u((0((((H((+ (v((A(/`((5w((/`((((p,(( (7,K(;(*((:((;(*((((~;(( (ĉ*(;(*((((;(*((*(()Z(( (N((._((((-_((~((B(( (,>V((2i((((1((K((((<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(((( ((((( ((((( (( (((Y((0((k(+D(((((B((1I((/`((I(wI(((( (@((5((((;(*((;*(;*(((( (Ն/*((((((;(*((;*(;*((+(( (z+m((((((-_((H(wH(({(( (O((((((1((p(+D((A(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<( (+9((*9((*9((*K((0((1f((1f( (v((Av((Av((A((0_((/`((/`( (7,K7,K7,K((<(*((;(*((;(*( (ĉ*(ĉ*(ĉ*((=(*((;(*((;(*(<U<U<U<U<U<U<U%UUU%UUUUU%UUUUU U\XUUX\ggUUUUfUUUUUU\ UԇUUUXUUZUUXUUUUvVUU Uq\\qUXUXUUXUUXUXUUUUXUU UUUXUXUUUUXUXUUUUVUU UUUWUUUUWUUrUUwUU U^\^UUhhUUUUhUUUUUU<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<UUUU UUUUU UUUUU UU UUUUUfUUjUZZUUUUUVvUUXUUXUUUUUUU UUUXUVUUXUXUUXUXXUUUU UWXUUUUUUXUXUUXUXXUUVUU UwYUUUUUUWUU퉈UUUoUU UUUUUUUhUUlUZZUUU<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U U\XUUXXUUXXUUX\UUeUUggUUggU UԇUUԇUUԇUUUUXUUXUUXU Uq\\qq\\qq\\qUUXUXUUXUXUUXUXU UUUUUUUYUXUUXUXUUXUXU<w<w<w<w<w<w<w%www%wwwww%wwwww wxwwy}wwwwwwwwww wwwөw{׹ww{ww{wwwww w wywxwwwxwwxwwwww۽xww wxwwxwwwwwxwwwww}ww www{ֻwwww{wwwwyww wzww}wwwwwwwwww<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<wwww wwwww wwwww ww w݅wwwwww~wywwwwwzwwyww{wwwwwww wwwxw{wwxwwwxwxwwww wxwwwwwwxwwwxwxwwww wwwwwww{wwwwwww wwwwwwwww~wywww<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w wxwwxwwxwwywwww}ww}w wwwөwwөwwөww{ww{׹ww{׹w wyyywwxwwwxwwwxww wxwxwxwwxwwwxwwwxww((=((=(((9((*K(u((((oe((oe((-((0((((v((A((5w((B((/_((/_((0F((/`( (B((7,K((:((@((;(*((;(*((((;(*((@((ĉ*((((Ն/*((;(*((;(*((6((;(*((Ն/*((N((((z+m((.]((.]((9((-_((z+m((,>V((((O((qf((qf((,((1((O(((((6((((((((((((((3((((((u(((,((((((oe((J((^^((B((5w((/Û((((((/_((;*((++((@((:((((((((;(*((;*((((((Ն/*((((s((-()((;(*((I((((((z+m(((((8<((ʙ*a((.]((4((((((O((((((V((F((qf(9((=((((((((((((:((29((((((l((7-p((0((((-((0((-((V((3(()((0_((((0F((/`((0F((3((5,l((<(*((((((;(*((((3((2((=(*((*((6( (;(*((6( (V((=(UU=UU=UUUXUUX\UUUUUfUUfUU^zUUfUUUUԇUUUUZUUVvUUXUUXUU؊X}UUXU UVvUUq\\qUUXUUUUXUXUUXUXUUUUXUXUUUUUUUUWXUUXUXUUXUXUUXUUXUXUUWXUUUUUUwYUUWUUWUU֭\UUWUUwYUU^\^UUUUޙUUgUUgUU\UUhUUUUUUU6UUUUUUUUUUUUUUjUUUUUUUUU^UUUUUUfUUUUijUUVvUUZUUbUUUUUUXUUXXUUVVUUUUXUUUUUUUUXUXUUXXUUUUUUWXUUUUjUUVUXUUXUXUUUUUUUUwYUUUUUtXUUXUUWUUlUUUUUUUUUUUUUUUUgU9UU=UUUUUUVUUUUUUwUUjXUUUUUUlUU\]UUeUUUU^zUUfUU^zUU^UUWUUXUUXUUUU؊X}UUXUU؊X}UUWUUZ\UUXUXUUUUUUXUXUUUUWUUiUUYUXUUUUXU UXUXUUXU U^UU~Uww=ww=wwwxwwԂywww݅ww|ww|wwwwww݅wwwwөww{wwzww{ֶww{ֶwwzww{w wzwwywwxwwwwxwwwxwwwwwxwwwwwxwwwwxwwxwwwxwwwxwwxwwwxwwwwwwwwzոwwzոwwyww{wwwwzwwwwww}ww}wwwwwwwwwww6wwwwwwwwwwwwwwwwww݅wwwwwwwwwww|wwwwwwzww{wwwwwwww{ֶwwxwwyywwwwxwwwwwwwwxwwwxwwwwwwxwwww~wwwwwwxwwwwwwwwwwwwwwxww|wwzոwwwwwwwwwwwwww٫wwww}w9ww=wwwwwwzwwwwwwƑwwxwwwwwwwwz֖wwwwwwwwwwwwzwwxwwww{wwwwzww{wwzwwxwwywwxwwwwwwwxwwwwwxwwwwxwwwwwxw wxwwwxw wzwww(((( (((((((((((((((Y((0((+9((*K((Y((H((11I((/`((v((A((((((1I((p,((5((((;(*((7,K((((((5((((~;((((((;(*((ĉ*(((((((((()Z((((((-_((N((((h((((( (B((((((1((,>V(((g((((((((2(((=((((((Z((((((((((1v(((Y((0((((Y((((((()(((B((1I((/`( (B((1I(((((((.k(@((5((((;(*((@((5(((((((J(Ն/*((((((;(*((Ն/*((((((,((((()(z+m((((((-_((z+m((((((6+a(((-`O((((((1((O((((((1d(((P(((((+(.(((((Q(.(((((5((H((1f((((0((-((p,( (/`((((/`(()((~;((;(*((((;(*(()(()Z((;(*((*((;(*((-((UUUU UUUUUUUUUUUUUUUUUfUU\XUUX\UUUUUUgXUUXUUԇUUUUUUUUXUUvVUUXUVUUXUXUUq\\qUUUUUUXUVUUXUUUUUUXUXUUUUUUUUUUUUVUUUUUUWUUUUUUjUUUUU UwUUUUUUhUU^\^UUUUUUUUUUUhUUU=UUUUUUcUUUUUUUUUUbUUUUUfUUUUUUUUUUUXUUUVvUUXUUXU UVvUUXUUUUUUU^UUUXUVUUXUXUUUUXUVUUUUU\UWXUUUUUUXUXUUWXUUUUUUVUUVUUUVͤUwYUUUUUUWUUwYUUUUUU[[UUU^`UUUUUUhUUUUUUUUghUUUUUUUUVU.UUUUUwU.UUUUUlUUUUggUUUUfUU^UUvVU UXUUUUXUUWUUXUUXUXUUUUXUXUUWUUVUUXUXUUUUXUXUU^UUwwww wwwwwwwwwwwwwwwwwwwxwwywwwwwwyww{wwwwөwwwwwwywwwwxw{wwxwwwywwwwwwxw{ww۽xwwwwwwxwwwxwwwwwwwwww}wwwwww{wwwwwwwwwww wywwwwwwwwzwwwwwwwwwwwwww=wwwwww|wwwwwwwwwxw݅wwwwww݅wwwwwwwwwwwwzwwyww{w wzwwywwwwwwwwwwxw{wwxwwwwwxw{wwwwwzwxwwwwwwxwwwxwwwwwwwwzwww|wwwwwww{wwwwwwwwywww{wwwwwwwwwwwwww}wwwܵwwwwww/wwwwww.wwwwwwwww}wwwwwwwww w{׹wwww{ww~ww۽xwwxwwwwwxwww~ww}wwxwwwwwxwwwww(((=P((('(j( (+(.(*,( (4( (f((((-((((9(((D(((([/`((^^((0F( (9((?((4((\((9((@;(*((++((( (T((`((4((((T((;(*((((((6( (((((.D((R((((2._((((((9( (T((b((b;((V((T((ϒi((((((,((V((4(jڠ((((V((C(((UUU|\UUU'UbU U\U.UXVU UjU UgUUUU^zUUUUXUUUUUUU왞XUUijUU؊X}U UtUUUUhUUĐUUtUUXUXUUVVUUU UUUɐUUlUUkUUUUpXUXUUUUUUXU UqUUfUUWUU]UUqUUWWUUUUUU֭\U U]UUfUUhoUUdUU]UUhUUUUUU\UUUUWUlUUUUUUUUUwwwzwww'w{w ww.ww ww w}wwwwwwwwxwwwܚwwww{׹wwwwzw wͬwwסwwwwwwͬwwxwwwyywww wwwwwwwwwwwxwwwwwwwxw www}wwx٤wwzwwwwx{ֻwwwwwwyw wzww}ww~ww|wwzww}wwwwwwwwwwxwwwwwwwwww((((((((((((((((((((($(_*(%(((((+(&(V($((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((U(U(U(U(U(U(U(U(U(UU$UpU&U(U(UVU&U^U$U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(w(w(w(w(w(w(w(w(w(ww$ww&w(w(w(wzw$w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w (N((N((N((((0`((._((._( (,>V((,>V((,>V((((0((2i((2i(+(F( ()(( (<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(�<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(<(((+( (:((%( (l((($( (V((( (((1f((u((-( (3((( (^^((/`((5w((0F( (3((( (++((;(*((:((( (V((( (((((;(*((((6(( (k((( (((((._((((9(( (:(((((((2i((((,(((+(<(<(<(<(<(<( UUUUUUUUUXUUWUUWU U^\^UU^\^UU^\^UUUUeUUhhUUhhU+UU U)U}U U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U�<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U<U UVU+U UwUU%U UlUUU$U U^UUU UUUggUUUU^zU UWUUU UijUUXUUZUU؊X}U UWUUU UVVUUXUXUUXUUU U^UUU UUUUUXUXUUUUXUU UlUUU UUUUUWUUUU֭\UU UvUUUUUUUhhUUUU\U UVU+U<U<U<U<U<U<U wwwwwwwww}ww{ֻww{ֻw wzwwzwwzwwwwww}ww}w*wxw w)ww w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w�<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w<w wzw+w wƑww%w wwww$w wzwww www}wwwww wxwww www{׹ww{wwzw wxwww wyywwxwwwxwww wzwww wwwwwxwwwwwxww wwww wwwww{ֻwwwwyww wőwwwwwww}wwwww wyw+w<w<w<w<w<w<w(0`((~((9((((-_((9( (k((+T֤((0((K((,((((1((,((:((ߍ((F(,(( ((y(�B(+(<(Q(<(5(<(-(<()(<()(<(-(<(5(<(R(<(,((UXUUrUU֭\UUUUWUU֭\U UlUUZUUeUUUU\UUUUhUU\UUvUUݐUUU+UVU U}UyU�BUVU<UwU<UlU<U^U<UWU<UWU<U^U<UlU<UwU<UVUUw}wwwwywwww{wwyw www~wwwwwwwwwwwwwwőwwےwwxw+wyw wwyw�Bww=ww<ww<ww<w~w<w~w<ww<ww<ww<www(B((._((~((-_((5((((2i((K((1((R(<(,((� @(UwUUWUUrUUWUUlUUUUhhUUUUhUUwU<UVUU� @Uwyww{ֻwwww{wwwwww}wwwwwww<www� @w@(� @(@U� @U@w� @w((((((((((((((((((((((((((((((((((((((((((((((((((Z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(UZ(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(U(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(wZ(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w(w<(<(<(<(<(<(<(<(<(<(<(<(<(�<U<U<U<U<U<U<U<U<U<U<U<U<U�<w<w<w<w<w<w<w<w<w<w<w<w<w�@(�@U�@w�@(�@U�@w�@(�@U�@w�((((((((((((((((((((((((((�(U(U(U(U(U(U(U(U(U(U(U(U(U�(w(w(w(w(w(w(w(w(w(w(w(w(w�������h�������K���4�������%�����������������bg���������������������� �������������������� ���������� ���������� ���������� ��������������������������������������������������ý��������������������������������� ����)��9��I��Y��i��y��ʼn��ř��ũ��Ź���������� ����)��9��I��Y��i��y��Ɖ��ƙ��Ʃ��ƹ���������� ����)��9��I��Y��i��y��lj��Ǚ��ǩ��ǹ���������� ����)��9��I��Y��i��y��ȉ��ș��ȩ��ȹ������������������������������������ � � � ����������������������������� � � � ����������������������������� � � � ����������������������������� � � � ����������������������������� � � � ����������������������������� � � � ����������������������������� � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �    �������������}���}�������>���>�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/docs/dunstctl.pod�����������������������������������������������������������������������0000664�0000000�0000000�00000002616�13706263046�0016162�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������=head1 NAME dunstctl - Command line control utility for dunst, a customizable and lightweight notification-daemon =head1 SYNOPSIS dunstctl COMMAND [PARAMETER] =head1 COMMANDS =over 4 =item B<action> notification_position Performs the default action or, if not available, opens the context menu of the notification at the given position (starting count at the top, first notification being 0). =item B<close> Close the topmost notification currently being displayed. =item B<close-all> Close all notifications currently being displayed =item B<context> Open the context menu, presenting all available actions and urls for the currently open notifications. =item B<history-pop> Redisplay the notification that was most recently closed. This can be called multiple times to show older notifications, up to the history limit configured in dunst. =item B<is-paused> Check if dunst is currently running or paused. If dunst is paused notifications will be kept but not shown until it is unpaused. =item B<set-paused> true/false/toggle Set the paused status of dunst. If false, dunst is running normally, if true, dunst is paused. See the is-paused command and the dunst man page for more information. =item B<debug> Tries to contact dunst and checks for common faults between dunstctl and dunst. Useful if something isn't working =item B<help> Show all available commands with a brief description =back ������������������������������������������������������������������������������������������������������������������dunst-1.5.0/docs/internal/��������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13706263046�0015425�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/docs/internal/Doxyfile������������������������������������������������������������������0000664�0000000�0000000�00000017647�13706263046�0017152�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Doxyfile 1.8.14 #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = Dunst PROJECT_NUMBER = PROJECT_BRIEF = "Lightweight notification daemon" PROJECT_LOGO = OUTPUT_DIRECTORY = docs/internal CREATE_SUBDIRS = NO ALLOW_UNICODE_NAMES = NO OUTPUT_LANGUAGE = English BRIEF_MEMBER_DESC = YES REPEAT_BRIEF = YES ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO FULL_PATH_NAMES = YES STRIP_FROM_PATH = STRIP_FROM_INC_PATH = SHORT_NAMES = NO JAVADOC_AUTOBRIEF = NO QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO INHERIT_DOCS = YES SEPARATE_MEMBER_PAGES = NO TAB_SIZE = 8 ALIASES = TCL_SUBST = OPTIMIZE_OUTPUT_FOR_C = YES OPTIMIZE_OUTPUT_JAVA = NO OPTIMIZE_FOR_FORTRAN = NO OPTIMIZE_OUTPUT_VHDL = NO EXTENSION_MAPPING = MARKDOWN_SUPPORT = YES TOC_INCLUDE_HEADINGS = 0 AUTOLINK_SUPPORT = YES BUILTIN_STL_SUPPORT = NO CPP_CLI_SUPPORT = NO SIP_SUPPORT = NO IDL_PROPERTY_SUPPORT = YES DISTRIBUTE_GROUP_DOC = NO GROUP_NESTED_COMPOUNDS = NO SUBGROUPING = YES INLINE_GROUPED_CLASSES = NO INLINE_SIMPLE_STRUCTS = NO TYPEDEF_HIDES_STRUCT = YES LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- EXTRACT_ALL = YES EXTRACT_PRIVATE = YES EXTRACT_PACKAGE = YES EXTRACT_STATIC = YES EXTRACT_LOCAL_CLASSES = YES EXTRACT_LOCAL_METHODS = YES EXTRACT_ANON_NSPACES = NO HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO HIDE_FRIEND_COMPOUNDS = NO HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO CASE_SENSE_NAMES = NO HIDE_SCOPE_NAMES = YES HIDE_COMPOUND_REFERENCE= NO SHOW_INCLUDE_FILES = YES SHOW_GROUPED_MEMB_INC = NO FORCE_LOCAL_INCLUDES = NO INLINE_INFO = YES SORT_MEMBER_DOCS = YES SORT_BRIEF_DOCS = NO SORT_MEMBERS_CTORS_1ST = NO SORT_GROUP_NAMES = NO SORT_BY_SCOPE_NAME = NO STRICT_PROTO_MATCHING = NO GENERATE_TODOLIST = YES GENERATE_TESTLIST = YES GENERATE_BUGLIST = YES GENERATE_DEPRECATEDLIST= YES ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 SHOW_USED_FILES = YES SHOW_FILES = YES SHOW_NAMESPACES = YES FILE_VERSION_FILTER = LAYOUT_FILE = CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- QUIET = NO WARNINGS = YES WARN_IF_UNDOCUMENTED = YES WARN_IF_DOC_ERROR = YES WARN_NO_PARAMDOC = YES WARN_AS_ERROR = YES WARN_FORMAT = "$file:$line: $text" WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- INPUT = . \ HACKING.md INPUT_ENCODING = UTF-8 FILE_PATTERNS = *.c \ *.h RECURSIVE = YES EXCLUDE = EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = */test/greatest.h EXCLUDE_SYMBOLS = EXAMPLE_PATH = EXAMPLE_PATTERNS = * EXAMPLE_RECURSIVE = NO IMAGE_PATH = INPUT_FILTER = FILTER_PATTERNS = FILTER_SOURCE_FILES = NO FILTER_SOURCE_PATTERNS = USE_MDFILE_AS_MAINPAGE = HACKING.md #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- SOURCE_BROWSER = YES INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES REFERENCED_BY_RELATION = NO REFERENCES_RELATION = NO REFERENCES_LINK_SOURCE = YES SOURCE_TOOLTIPS = YES USE_HTAGS = NO VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- ALPHABETICAL_INDEX = YES COLS_IN_ALPHA_INDEX = 5 IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- GENERATE_HTML = YES HTML_OUTPUT = html HTML_FILE_EXTENSION = .html HTML_HEADER = HTML_FOOTER = HTML_STYLESHEET = HTML_EXTRA_STYLESHEET = HTML_EXTRA_FILES = HTML_COLORSTYLE_HUE = 220 HTML_COLORSTYLE_SAT = 100 HTML_COLORSTYLE_GAMMA = 80 HTML_TIMESTAMP = YES HTML_DYNAMIC_MENUS = YES HTML_DYNAMIC_SECTIONS = YES HTML_INDEX_NUM_ENTRIES = 0 GENERATE_DOCSET = NO GENERATE_HTMLHELP = NO GENERATE_CHI = NO GENERATE_QHP = NO GENERATE_ECLIPSEHELP = NO ECLIPSE_DOC_ID = org.dunst-project.dunst DISABLE_INDEX = NO GENERATE_TREEVIEW = YES ENUM_VALUES_PER_LINE = 4 TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES USE_MATHJAX = NO SEARCHENGINE = YES SERVER_BASED_SEARCH = NO EXTERNAL_SEARCH = NO SEARCHENGINE_URL = SEARCHDATA_FILE = searchdata.xml EXTERNAL_SEARCH_ID = EXTRA_SEARCH_MAPPINGS = #--------------------------------------------------------------------------- # Multiple disabled output formats #--------------------------------------------------------------------------- GENERATE_LATEX = NO GENERATE_RTF = NO GENERATE_MAN = NO GENERATE_XML = NO GENERATE_DOCBOOK = NO GENERATE_AUTOGEN_DEF = NO GENERATE_PERLMOD = NO ENABLE_PREPROCESSING = YES MACRO_EXPANSION = NO EXPAND_ONLY_PREDEF = NO SEARCH_INCLUDES = YES INCLUDE_PATH = INCLUDE_FILE_PATTERNS = PREDEFINED = EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration options related to external references #--------------------------------------------------------------------------- TAGFILES = GENERATE_TAGFILE = ALLEXTERNALS = NO EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- CLASS_DIAGRAMS = YES MSCGEN_PATH = DIA_PATH = HIDE_UNDOC_RELATIONS = YES HAVE_DOT = YES DOT_NUM_THREADS = 0 DOT_FONTNAME = Helvetica DOT_FONTSIZE = 10 DOT_FONTPATH = CLASS_GRAPH = YES COLLABORATION_GRAPH = YES GROUP_GRAPHS = YES UML_LOOK = NO UML_LIMIT_NUM_FIELDS = 10 TEMPLATE_RELATIONS = NO INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES CALL_GRAPH = NO CALLER_GRAPH = NO GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES DOT_IMAGE_FORMAT = svg INTERACTIVE_SVG = YES DOT_PATH = DOTFILE_DIRS = MSCFILE_DIRS = DIAFILE_DIRS = PLANTUML_JAR_PATH = PLANTUML_CFG_FILE = PLANTUML_INCLUDE_PATH = DOT_GRAPH_MAX_NODES = 50 MAX_DOT_GRAPH_DEPTH = 0 DOT_TRANSPARENT = NO DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES DOT_CLEANUP = YES �����������������������������������������������������������������������������������������dunst-1.5.0/dunst.systemd.service.in����������������������������������������������������������������0000664�0000000�0000000�00000000302�13706263046�0017467�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[Unit] Description=Dunst notification daemon Documentation=man:dunst(1) PartOf=graphical-session.target [Service] Type=dbus BusName=org.freedesktop.Notifications ExecStart=##PREFIX##/bin/dunst ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/dunstctl��������������������������������������������������������������������������������0000775�0000000�0000000�00000007347�13706263046�0014462�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh set -eu DBUS_NAME="org.freedesktop.Notifications" DBUS_PATH="/org/freedesktop/Notifications" DBUS_IFAC_DUNST="org.dunstproject.cmd0" DBUS_IFAC_PROP="org.freedesktop.DBus.Properties" DBUS_IFAC_FDN="org.freedesktop.Notifications" die(){ printf "%s\n" "${1}" >&2; exit 1; } show_help() { cat <<-EOH Usage: dunstctl <command> [parameters]" Commands: action Perform the default action, or open the context menu of the notification at the given position close Close the last notification close-all Close the all notifications context Open context menu history-pop Pop one notification from history is-paused Check if dunst is running or paused set-paused [true|false|toggle] Set the pause status debug Print debugging information help Show this help EOH } dbus_send_checked() { dbus-send "$@" \ || die "Failed to communicate with dunst, is it running? Or maybe the version is outdated. You can try 'dunstctl debug' as a next debugging step." } method_call() { dbus_send_checked --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "$@" } property_get() { dbus_send_checked --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_PROP}.Get" "string:${DBUS_IFAC_DUNST}" "string:${1}" } property_set() { dbus_send_checked --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_PROP}.Set" "string:${DBUS_IFAC_DUNST}" "string:${1}" "${2}" } command -v dbus-send >/dev/null 2>/dev/null || \ die "Command dbus-send not found" case "${1:-}" in "action") method_call "${DBUS_IFAC_DUNST}.NotificationAction" "int32:${2:-0}" >/dev/null ;; "close") method_call "${DBUS_IFAC_DUNST}.NotificationCloseLast" >/dev/null ;; "close-all") method_call "${DBUS_IFAC_DUNST}.NotificationCloseAll" >/dev/null ;; "context") method_call "${DBUS_IFAC_DUNST}.ContextMenuCall" >/dev/null ;; "history-pop") method_call "${DBUS_IFAC_DUNST}.NotificationShow" >/dev/null ;; "is-paused") property_get paused | ( read -r _ _ paused; printf "%s\n" "${paused}"; ) ;; "set-paused") [ "${2:-}" ] \ || die "No status parameter specified. Please give either 'true', 'false' or 'toggle' as paused parameter." [ "${2}" = "true" ] || [ "${2}" = "false" ] || [ "${2}" = "toggle" ] \ || die "Please give either 'true', 'false' or 'toggle' as paused parameter." if [ "${2}" = "toggle" ]; then paused=$(property_get paused | ( read -r _ _ paused; printf "%s\n" "${paused}"; )) if [ "${paused}" = "true" ]; then property_set paused variant:boolean:false else property_set paused variant:boolean:true fi else property_set paused variant:boolean:"$2" fi ;; "help"|"--help"|"-h") show_help ;; "debug") dbus-send --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_FDN}.GetServerInformation" >/dev/null 2>/dev/null \ || die "Dunst is not running." dbus-send --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_FDN}.GetServerInformation" \ | ( read -r name _ version _ [ "${name}" = "dunst" ] printf "dunst version: %s\n" "${version}" ) \ || die "Another notification manager is running. It's not dunst" dbus-send --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_DUNST}.Ping" >/dev/null 2>/dev/null \ || die "Dunst controlling interface not available. Is the version too old?" ;; "") die "dunstctl: No command specified. Please consult the usage." ;; *) die "dunstctl: unrecognized command '${1:-}'. Please consult the usage." ;; esac �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/dunstify.c������������������������������������������������������������������������������0000664�0000000�0000000�00000024411�13706263046�0014674�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <glib.h> #include <libnotify/notify.h> #include <locale.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> #include <gdk-pixbuf/gdk-pixbuf.h> static gchar *appname = "dunstify"; static gchar *summary = NULL; static gchar *body = NULL; static NotifyUrgency urgency = NOTIFY_URGENCY_NORMAL; static gchar *urgency_str = NULL; static gchar **hint_strs = NULL; static gchar **action_strs = NULL; static gint timeout = NOTIFY_EXPIRES_DEFAULT; static gchar *icon = NULL; static gchar *raw_icon_path = NULL; static gboolean capabilities = false; static gboolean serverinfo = false; static gboolean printid = false; static guint32 replace_id = 0; static guint32 close_id = 0; static gboolean block = false; static GOptionEntry entries[] = { { "appname", 'a', 0, G_OPTION_ARG_STRING, &appname, "Name of your application", "NAME" }, { "urgency", 'u', 0, G_OPTION_ARG_STRING, &urgency_str, "The urgency of this notification", "URG" }, { "hints", 'h', 0, G_OPTION_ARG_STRING_ARRAY, &hint_strs, "User specified hints", "HINT" }, { "action", 'A', 0, G_OPTION_ARG_STRING_ARRAY, &action_strs, "Actions the user can invoke", "ACTION" }, { "timeout", 't', 0, G_OPTION_ARG_INT, &timeout, "The time until the notification expires", "TIMEOUT" }, { "icon", 'i', 0, G_OPTION_ARG_STRING, &icon, "An Icon that should be displayed with the notification", "ICON" }, { "raw_icon", 'I', 0, G_OPTION_ARG_STRING, &raw_icon_path, "Path to the icon to be sent as raw image data", "RAW_ICON"}, { "capabilities", 'c', 0, G_OPTION_ARG_NONE, &capabilities, "Print the server capabilities and exit", NULL}, { "serverinfo", 's', 0, G_OPTION_ARG_NONE, &serverinfo, "Print server information and exit", NULL}, { "printid", 'p', 0, G_OPTION_ARG_NONE, &printid, "Print id, which can be used to update/replace this notification", NULL}, { "replace", 'r', 0, G_OPTION_ARG_INT, &replace_id, "Set id of this notification.", "ID"}, { "close", 'C', 0, G_OPTION_ARG_INT, &close_id, "Close the notification with the specified ID", "ID"}, { "block", 'b', 0, G_OPTION_ARG_NONE, &block, "Block until notification is closed and print close reason", NULL}, { NULL } }; void die(int exit_value) { if (notify_is_initted()) notify_uninit(); exit(exit_value); } void print_capabilities(void) { GList *caps = notify_get_server_caps(); for (GList *iter = caps; iter; iter = iter->next) { if (strlen(iter->data) > 0) { g_print("%s\n", (char *)iter->data); } } } void print_serverinfo(void) { char *name; char *vendor; char *version; char *spec_version; if (!notify_get_server_info(&name, &vendor, &version, &spec_version)) { g_printerr("Unable to get server information"); exit(1); } g_print("name:%s\nvendor:%s\nversion:%s\nspec_version:%s\n", name, vendor, version, spec_version); } /* * Glib leaves the option terminator "--" in the argv after parsing in some * cases. This function gets the specified argv element ignoring the first * terminator. * * See https://developer.gnome.org/glib/stable/glib-Commandline-option-parser.html#g-option-context-parse for details */ char *get_argv(char *argv[], int index) { for (int i = 0; i <= index; i++) { if (strcmp(argv[i], "--") == 0) { return argv[index + 1]; } } return argv[index]; } /* Count the number of arguments in argv excluding the terminator "--" */ int count_args(char *argv[], int argc) { for (int i = 0; i < argc; i++) { if (strcmp(argv[i], "--") == 0) return argc - 1; } return argc; } void parse_commandline(int argc, char *argv[]) { GError *error = NULL; GOptionContext *context; context = g_option_context_new("- Dunstify"); g_option_context_add_main_entries(context, entries, NULL); if (!g_option_context_parse(context, &argc, &argv, &error)){ g_printerr("Invalid commandline: %s\n", error->message); exit(1); } g_option_context_free(context); if (capabilities) { print_capabilities(); die(0); } if (serverinfo) { print_serverinfo(); die(0); } int n_args = count_args(argv, argc); if (n_args < 2 && close_id < 1) { g_printerr("I need at least a summary\n"); die(1); } else if (n_args < 2) { summary = g_strdup("These are not the summaries you are looking for"); } else { summary = g_strdup(get_argv(argv, 1)); } if (n_args > 2) { body = g_strcompress(get_argv(argv, 2)); } if (urgency_str) { switch (urgency_str[0]) { case 'l': case 'L': case '0': urgency = NOTIFY_URGENCY_LOW; break; case 'n': case 'N': case '1': urgency = NOTIFY_URGENCY_NORMAL; break; case 'c': case 'C': case '2': urgency = NOTIFY_URGENCY_CRITICAL; break; default: g_printerr("Unknown urgency: %s\n", urgency_str); g_printerr("Assuming normal urgency\n"); break; } } } typedef struct _NotifyNotificationPrivate { guint32 id; char *app_name; char *summary; char *body; /* NULL to use icon data. Anything else to have server lookup icon */ char *icon_name; /* * -1 = use server default * 0 = never timeout * > 0 = Number of milliseconds before we timeout */ gint timeout; GSList *actions; GHashTable *action_map; GHashTable *hints; gboolean has_nondefault_actions; gboolean updates_pending; gulong proxy_signal_handler; gint closed_reason; } knickers; int get_id(NotifyNotification *n) { knickers *kn = n->priv; /* I'm sorry for taking a peek */ return kn->id; } void put_id(NotifyNotification *n, guint32 id) { knickers *kn = n->priv; /* And know I'm putting stuff into * your knickers. I'm sorry. * I'm so sorry. * */ kn->id = id; } void actioned(NotifyNotification *n, char *a, gpointer foo) { notify_notification_close(n, NULL); g_print("%s\n", a); die(0); } void closed(NotifyNotification *n, gpointer foo) { g_print("%d\n", notify_notification_get_closed_reason(n)); die(0); } void add_action(NotifyNotification *n, char *str) { char *action = str; char *label = strchr(str, ','); if (!label || *(label+1) == '\0') { g_printerr("Malformed action. Excpected \"action,label\", got \"%s\"", str); return; } *label = '\0'; label++; notify_notification_add_action(n, action, label, actioned, NULL, NULL); } void add_hint(NotifyNotification *n, char *str) { char *type = str; char *name = strchr(str, ':'); if (!name || *(name+1) == '\0') { g_printerr("Malformed hint. Expected \"type:name:value\", got \"%s\"", str); return; } *name = '\0'; name++; char *value = strchr(name, ':'); if (!value || *(value+1) == '\0') { g_printerr("Malformed hint. Expected \"type:name:value\", got \"%s\"", str); return; } *value = '\0'; value++; if (strcmp(type, "int") == 0) notify_notification_set_hint_int32(n, name, atoi(value)); else if (strcmp(type, "double") == 0) notify_notification_set_hint_double(n, name, atof(value)); else if (strcmp(type, "string") == 0) notify_notification_set_hint_string(n, name, value); else if (strcmp(type, "byte") == 0) { gint h_byte = g_ascii_strtoull(value, NULL, 10); if (h_byte < 0 || h_byte > 0xFF) g_printerr("Not a byte: \"%s\"", value); else notify_notification_set_hint_byte(n, name, (guchar) h_byte); } else g_printerr("Malformed hint. Expected a type of int, double, string or byte, got %s\n", type); } int main(int argc, char *argv[]) { setlocale(LC_ALL, ""); #if !GLIB_CHECK_VERSION(2,35,0) g_type_init(); #endif parse_commandline(argc, argv); if (!notify_init(appname)) { g_printerr("Unable to initialize libnotify\n"); die(1); } NotifyNotification *n; n = notify_notification_new(summary, body, icon); notify_notification_set_timeout(n, timeout); notify_notification_set_urgency(n, urgency); GError *err = NULL; if (raw_icon_path) { GdkPixbuf *raw_icon = gdk_pixbuf_new_from_file(raw_icon_path, &err); if(err) { g_printerr("Unable to get raw icon: %s\n", err->message); die(1); } notify_notification_set_image_from_pixbuf(n, raw_icon); } if (close_id > 0) { put_id(n, close_id); notify_notification_close(n, &err); if (err) { g_printerr("Unable to close notification: %s\n", err->message); die(1); } die(0); } if (replace_id > 0) { put_id(n, replace_id); } GMainLoop *l = NULL; if (block || action_strs) { l = g_main_loop_new(NULL, false); g_signal_connect(n, "closed", G_CALLBACK(closed), NULL); } if (action_strs) for (int i = 0; action_strs[i]; i++) { add_action(n, action_strs[i]); } if (hint_strs) for (int i = 0; hint_strs[i]; i++) { add_hint(n, hint_strs[i]); } notify_notification_show(n, &err); if (err) { g_printerr("Unable to send notification: %s\n", err->message); die(1); } if (printid) g_print("%d\n", get_id(n)); if (block || action_strs) g_main_loop_run(l); g_object_unref(G_OBJECT (n)); die(0); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/dunstrc���������������������������������������������������������������������������������0000664�0000000�0000000�00000033237�13706263046�0014276�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[global] ### Display ### # Which monitor should the notifications be displayed on. monitor = 0 # Display notification on focused monitor. Possible modes are: # mouse: follow mouse pointer # keyboard: follow window with keyboard focus # none: don't follow anything # # "keyboard" needs a window manager that exports the # _NET_ACTIVE_WINDOW property. # This should be the case for almost all modern window managers. # # If this option is set to mouse or keyboard, the monitor option # will be ignored. follow = mouse # The geometry of the window: # [{width}]x{height}[+/-{x}+/-{y}] # The geometry of the message window. # The height is measured in number of notifications everything else # in pixels. If the width is omitted but the height is given # ("-geometry x2"), the message window expands over the whole screen # (dmenu-like). If width is 0, the window expands to the longest # message displayed. A positive x is measured from the left, a # negative from the right side of the screen. Y is measured from # the top and down respectively. # The width can be negative. In this case the actual width is the # screen width minus the width defined in within the geometry option. geometry = "300x5-30+20" # Show how many messages are currently hidden (because of geometry). indicate_hidden = yes # Shrink window if it's smaller than the width. Will be ignored if # width is 0. shrink = no # The transparency of the window. Range: [0; 100]. # This option will only work if a compositing window manager is # present (e.g. xcompmgr, compiz, etc.). transparency = 0 # The height of the entire notification. If the height is smaller # than the font height and padding combined, it will be raised # to the font height and padding. notification_height = 0 # Draw a line of "separator_height" pixel height between two # notifications. # Set to 0 to disable. separator_height = 2 # Padding between text and separator. padding = 8 # Horizontal padding. horizontal_padding = 8 # Defines width in pixels of frame around the notification window. # Set to 0 to disable. frame_width = 3 # Defines color of the frame around the notification window. frame_color = "#aaaaaa" # Define a color for the separator. # possible values are: # * auto: dunst tries to find a color fitting to the background; # * foreground: use the same color as the foreground; # * frame: use the same color as the frame; # * anything else will be interpreted as a X color. separator_color = frame # Sort messages by urgency. sort = yes # Don't remove messages, if the user is idle (no mouse or keyboard input) # for longer than idle_threshold seconds. # Set to 0 to disable. # A client can set the 'transient' hint to bypass this. See the rules # section for how to disable this if necessary idle_threshold = 120 ### Text ### font = Monospace 8 # The spacing between lines. If the height is smaller than the # font height, it will get raised to the font height. line_height = 0 # Possible values are: # full: Allow a small subset of html markup in notifications: # <b>bold</b> # <i>italic</i> # <s>strikethrough</s> # <u>underline</u> # # For a complete reference see # <https://developer.gnome.org/pango/stable/pango-Markup.html>. # # strip: This setting is provided for compatibility with some broken # clients that send markup even though it's not enabled on the # server. Dunst will try to strip the markup but the parsing is # simplistic so using this option outside of matching rules for # specific applications *IS GREATLY DISCOURAGED*. # # no: Disable markup parsing, incoming notifications will be treated as # plain text. Dunst will not advertise that it has the body-markup # capability if this is set as a global setting. # # It's important to note that markup inside the format option will be parsed # regardless of what this is set to. markup = full # The format of the message. Possible variables are: # %a appname # %s summary # %b body # %i iconname (including its path) # %I iconname (without its path) # %p progress value if set ([ 0%] to [100%]) or nothing # %n progress value if set without any extra characters # %% Literal % # Markup is allowed format = "<b>%s</b>\n%b" # Alignment of message text. # Possible values are "left", "center" and "right". alignment = left # Vertical alignment of message text and icon. # Possible values are "top", "center" and "bottom". vertical_alignment = center # Show age of message if message is older than show_age_threshold # seconds. # Set to -1 to disable. show_age_threshold = 60 # Split notifications into multiple lines if they don't fit into # geometry. word_wrap = yes # When word_wrap is set to no, specify where to make an ellipsis in long lines. # Possible values are "start", "middle" and "end". ellipsize = middle # Ignore newlines '\n' in notifications. ignore_newline = no # Stack together notifications with the same content stack_duplicates = true # Hide the count of stacked notifications with the same content hide_duplicate_count = false # Display indicators for URLs (U) and actions (A). show_indicators = yes ### Icons ### # Align icons left/right/off icon_position = left # Scale small icons up to this size, set to 0 to disable. Helpful # for e.g. small files or high-dpi screens. In case of conflict, # max_icon_size takes precedence over this. min_icon_size = 0 # Scale larger icons down to this size, set to 0 to disable max_icon_size = 32 # Paths to default icons. icon_path = /usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/ ### History ### # Should a notification popped up from history be sticky or timeout # as if it would normally do. sticky_history = yes # Maximum amount of notifications kept in history history_length = 20 ### Misc/Advanced ### # dmenu path. dmenu = /usr/bin/dmenu -p dunst: # Browser for opening urls in context menu. browser = /usr/bin/firefox -new-tab # Always run rule-defined scripts, even if the notification is suppressed always_run_script = true # Define the title of the windows spawned by dunst title = Dunst # Define the class of the windows spawned by dunst class = Dunst # Print a notification on startup. # This is mainly for error detection, since dbus (re-)starts dunst # automatically after a crash. startup_notification = false # Manage dunst's desire for talking # Can be one of the following values: # crit: Critical features. Dunst aborts # warn: Only non-fatal warnings # mesg: Important Messages # info: all unimportant stuff # debug: all less than unimportant stuff verbosity = mesg # Define the corner radius of the notification window # in pixel size. If the radius is 0, you have no rounded # corners. # The radius will be automatically lowered if it exceeds half of the # notification height to avoid clipping text and/or icons. corner_radius = 0 # Ignore the dbus closeNotification message. # Useful to enforce the timeout set by dunst configuration. Without this # parameter, an application may close the notification sent before the # user defined timeout. ignore_dbusclose = false ### Legacy # Use the Xinerama extension instead of RandR for multi-monitor support. # This setting is provided for compatibility with older nVidia drivers that # do not support RandR and using it on systems that support RandR is highly # discouraged. # # By enabling this setting dunst will not be able to detect when a monitor # is connected or disconnected which might break follow mode if the screen # layout changes. force_xinerama = false ### mouse # Defines list of actions for each mouse event # Possible values are: # * none: Don't do anything. # * do_action: 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. # * close_current: Close current notification. # * close_all: Close all notifications. # These values can be strung together for each mouse event, and # will be executed in sequence. mouse_left_click = close_current mouse_middle_click = do_action, close_current mouse_right_click = close_all # Experimental features that may or may not work correctly. Do not expect them # to have a consistent behaviour across releases. [experimental] # Calculate the dpi to use on a per-monitor basis. # If this setting is enabled the Xft.dpi value will be ignored and instead # dunst will attempt to calculate an appropriate dpi value for each monitor # using the resolution and physical size. This might be useful in setups # where there are multiple screens with very different dpi values. per_monitor_dpi = false [shortcuts] # Shortcuts are specified as [modifier+][modifier+]...key # Available modifiers are "ctrl", "mod1" (the alt-key), "mod2", # "mod3" and "mod4" (windows-key). # Xev might be helpful to find names for keys. # Close notification. close = ctrl+space # Close all notifications. close_all = ctrl+shift+space # Redisplay last message(s). # On the US keyboard layout "grave" is normally above TAB and left # of "1". Make sure this key actually exists on your keyboard layout, # e.g. check output of 'xmodmap -pke' history = ctrl+grave # Context menu. context = ctrl+shift+period [urgency_low] # IMPORTANT: colors have to be defined in quotation marks. # Otherwise the "#" and following would be interpreted as a comment. background = "#222222" foreground = "#888888" timeout = 10 # Icon for notifications with low urgency, uncomment to enable #icon = /path/to/icon [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 # Icon for notifications with normal urgency, uncomment to enable #icon = /path/to/icon [urgency_critical] background = "#900000" foreground = "#ffffff" frame_color = "#ff0000" timeout = 0 # Icon for notifications with critical urgency, uncomment to enable #icon = /path/to/icon # Every section that isn't one of the above is interpreted as a rules to # override settings for certain messages. # # Messages can be matched by # appname (discouraged, see desktop_entry) # body # category # desktop_entry # icon # match_transient # msg_urgency # stack_tag # summary # # and you can override the # background # foreground # format # frame_color # fullscreen # new_icon # set_stack_tag # set_transient # timeout # urgency # # Shell-like globbing will get expanded. # # Instead of the appname filter, it's recommended to use the desktop_entry filter. # GLib based applications export their desktop-entry name. In comparison to the appname, # the desktop-entry won't get localized. # # SCRIPTING # You can specify a script that gets run when the rule matches by # setting the "script" option. # The script will be called as follows: # script appname summary body icon urgency # where urgency can be "LOW", "NORMAL" or "CRITICAL". # # NOTE: if you don't want a notification to be displayed, set the format # to "". # NOTE: It might be helpful to run dunst -print in a terminal in order # to find fitting options for rules. # Disable the transient hint so that idle_threshold cannot be bypassed from the # client #[transient_disable] # match_transient = yes # set_transient = no # # Make the handling of transient notifications more strict by making them not # be placed in history. #[transient_history_ignore] # match_transient = yes # history_ignore = yes # fullscreen values # show: show the notifications, regardless if there is a fullscreen window opened # delay: displays the new notification, if there is no fullscreen window active # If the notification is already drawn, it won't get undrawn. # pushback: same as delay, but when switching into fullscreen, the notification will get # withdrawn from screen again and will get delayed like a new notification #[fullscreen_delay_everything] # fullscreen = delay #[fullscreen_show_critical] # msg_urgency = critical # fullscreen = show #[espeak] # summary = "*" # script = dunst_espeak.sh #[script-test] # summary = "*script*" # script = dunst_test.sh #[ignore] # # This notification will not be displayed # summary = "foobar" # format = "" #[history-ignore] # # This notification will not be saved in history # summary = "foobar" # history_ignore = yes #[skip-display] # # This notification will not be displayed, but will be included in the history # summary = "foobar" # skip_display = yes #[signed_on] # appname = Pidgin # summary = "*signed on*" # urgency = low # #[signed_off] # appname = Pidgin # summary = *signed off* # urgency = low # #[says] # appname = Pidgin # summary = *says* # urgency = critical # #[twitter] # appname = Pidgin # summary = *twitter.com* # urgency = normal # #[stack-volumes] # appname = "some_volume_notifiers" # set_stack_tag = "volume" # # vim: ft=cfg �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/main.c����������������������������������������������������������������������������������0000664�0000000�0000000�00000000241�13706263046�0013746�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "src/dunst.h" int main(int argc, char *argv[]) { return dunst_main(argc, argv); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/org.knopwob.dunst.service.in������������������������������������������������������������0000664�0000000�0000000�00000000152�13706263046�0020247�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[D-BUS Service] Name=org.freedesktop.Notifications Exec=##PREFIX##/bin/dunst SystemdService=dunst.service ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/������������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13706263046�0013450�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/dbus.c������������������������������������������������������������������������������0000664�0000000�0000000�00000076566�13706263046�0014575�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "dbus.h" #include <gio/gio.h> #include <glib.h> #include <stdio.h> #include <stdlib.h> #include "dunst.h" #include "log.h" #include "menu.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" #define DUNST_PATH "/org/freedesktop/Notifications" #define DUNST_IFAC "org.dunstproject.cmd0" #define DUNST_NAME "org.freedesktop.Notifications" #define PROPERTIES_IFAC "org.freedesktop.DBus.Properties" GDBusConnection *dbus_conn; static GDBusNodeInfo *introspection_data = NULL; static const char *introspection_xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" "<node name=\""FDN_PATH"\">" " <interface name=\""FDN_IFAC"\">" " <method name=\"GetCapabilities\">" " <arg direction=\"out\" name=\"capabilities\" type=\"as\"/>" " </method>" " <method name=\"Notify\">" " <arg direction=\"in\" name=\"app_name\" type=\"s\"/>" " <arg direction=\"in\" name=\"replaces_id\" type=\"u\"/>" " <arg direction=\"in\" name=\"app_icon\" type=\"s\"/>" " <arg direction=\"in\" name=\"summary\" type=\"s\"/>" " <arg direction=\"in\" name=\"body\" type=\"s\"/>" " <arg direction=\"in\" name=\"actions\" type=\"as\"/>" " <arg direction=\"in\" name=\"hints\" type=\"a{sv}\"/>" " <arg direction=\"in\" name=\"expire_timeout\" type=\"i\"/>" " <arg direction=\"out\" name=\"id\" type=\"u\"/>" " </method>" " <method name=\"CloseNotification\">" " <arg direction=\"in\" name=\"id\" type=\"u\"/>" " </method>" " <method name=\"GetServerInformation\">" " <arg direction=\"out\" name=\"name\" type=\"s\"/>" " <arg direction=\"out\" name=\"vendor\" type=\"s\"/>" " <arg direction=\"out\" name=\"version\" type=\"s\"/>" " <arg direction=\"out\" name=\"spec_version\" type=\"s\"/>" " </method>" " <signal name=\"NotificationClosed\">" " <arg name=\"id\" type=\"u\"/>" " <arg name=\"reason\" type=\"u\"/>" " </signal>" " <signal name=\"ActionInvoked\">" " <arg name=\"id\" type=\"u\"/>" " <arg name=\"action_key\" type=\"s\"/>" " </signal>" " </interface>" " <interface name=\""DUNST_IFAC"\">" " <method name=\"ContextMenuCall\" />" " <method name=\"NotificationAction\">" " <arg name=\"number\" type=\"i\"/>" " </method>" " <method name=\"NotificationCloseLast\" />" " <method name=\"NotificationCloseAll\" />" " <method name=\"NotificationShow\" />" " <method name=\"Ping\" />" " <property name=\"paused\" type=\"b\" access=\"readwrite\">" " <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>" " </property>" " </interface>" "</node>"; static const char *stack_tag_hints[] = { "synchronous", "private-synchronous", "x-canonical-private-synchronous", "x-dunst-stack-tag" }; struct dbus_method { const char *method_name; void (*method) (GDBusConnection *connection, const gchar *sender, GVariant *parameters, GDBusMethodInvocation *invocation); }; #define DBUS_METHOD(name) static void dbus_cb_##name( \ GDBusConnection *connection, \ const gchar *sender, \ GVariant *parameters, \ GDBusMethodInvocation *invocation) int cmp_methods(const void *vkey, const void *velem) { const char *key = (const char*)vkey; const struct dbus_method *m = (const struct dbus_method*)velem; return strcmp(key, m->method_name); } DBUS_METHOD(Notify); DBUS_METHOD(CloseNotification); DBUS_METHOD(GetCapabilities); DBUS_METHOD(GetServerInformation); static struct dbus_method methods_fdn[] = { {"CloseNotification", dbus_cb_CloseNotification}, {"GetCapabilities", dbus_cb_GetCapabilities}, {"GetServerInformation", dbus_cb_GetServerInformation}, {"Notify", dbus_cb_Notify}, }; void dbus_cb_fdn_methods(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { struct dbus_method *m = bsearch(method_name, methods_fdn, G_N_ELEMENTS(methods_fdn), sizeof(struct dbus_method), cmp_methods); if (m) { m->method(connection, sender, parameters, invocation); } else { LOG_M("Unknown method name: '%s' (sender: '%s').", method_name, sender); } } DBUS_METHOD(dunst_ContextMenuCall); DBUS_METHOD(dunst_NotificationAction); DBUS_METHOD(dunst_NotificationCloseAll); DBUS_METHOD(dunst_NotificationCloseLast); DBUS_METHOD(dunst_NotificationShow); DBUS_METHOD(dunst_Ping); static struct dbus_method methods_dunst[] = { {"ContextMenuCall", dbus_cb_dunst_ContextMenuCall}, {"NotificationAction", dbus_cb_dunst_NotificationAction}, {"NotificationCloseAll", dbus_cb_dunst_NotificationCloseAll}, {"NotificationCloseLast", dbus_cb_dunst_NotificationCloseLast}, {"NotificationShow", dbus_cb_dunst_NotificationShow}, {"Ping", dbus_cb_dunst_Ping}, }; void dbus_cb_dunst_methods(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { struct dbus_method *m = bsearch(method_name, methods_dunst, G_N_ELEMENTS(methods_dunst), sizeof(struct dbus_method), cmp_methods); if (m) { m->method(connection, sender, parameters, invocation); } else { LOG_M("Unknown method name: '%s' (sender: '%s').", method_name, sender); } } static void dbus_cb_dunst_ContextMenuCall(GDBusConnection *connection, const gchar *sender, GVariant *parameters, GDBusMethodInvocation *invocation) { LOG_D("CMD: Calling context menu"); context_menu(); g_dbus_method_invocation_return_value(invocation, NULL); g_dbus_connection_flush(connection, NULL, NULL, NULL); } static void dbus_cb_dunst_NotificationAction(GDBusConnection *connection, const gchar *sender, GVariant *parameters, GDBusMethodInvocation *invocation) { int notification_nr = 0; g_variant_get(parameters, "(i)", ¬ification_nr); LOG_D("CMD: Calling action for notification %d", notification_nr); if (notification_nr < 0 || queues_length_waiting() < notification_nr) { g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Couldn't activate action for notification in position %d, %d notifications currently open", notification_nr, queues_length_waiting()); return; } struct notification *n = g_list_nth_data(queues_get_displayed(), notification_nr); if (n) { LOG_D("CMD: Calling action for notification %s", n->summary); notification_do_action(n); } g_dbus_method_invocation_return_value(invocation, NULL); g_dbus_connection_flush(connection, NULL, NULL, NULL); } static void dbus_cb_dunst_NotificationCloseAll(GDBusConnection *connection, const gchar *sender, GVariant *parameters, GDBusMethodInvocation *invocation) { LOG_D("CMD: Pushing all to history"); queues_history_push_all(); wake_up(); g_dbus_method_invocation_return_value(invocation, NULL); g_dbus_connection_flush(connection, NULL, NULL, NULL); } static void dbus_cb_dunst_NotificationCloseLast(GDBusConnection *connection, const gchar *sender, GVariant *parameters, GDBusMethodInvocation *invocation) { LOG_D("CMD: Closing last notification"); const GList *list = queues_get_displayed(); if (list && list->data) { struct notification *n = list->data; queues_notification_close_id(n->id, REASON_USER); wake_up(); } g_dbus_method_invocation_return_value(invocation, NULL); g_dbus_connection_flush(connection, NULL, NULL, NULL); } static void dbus_cb_dunst_NotificationShow(GDBusConnection *connection, const gchar *sender, GVariant *parameters, GDBusMethodInvocation *invocation) { LOG_D("CMD: Showing last notification from history"); queues_history_pop(); wake_up(); g_dbus_method_invocation_return_value(invocation, NULL); g_dbus_connection_flush(connection, NULL, NULL, NULL); } /* Just a simple Ping command to give the ability to dunstctl to test for the existence of this interface * Any other way requires parsing the XML of the Introspection or other foo. Just calling the Ping on an old dunst version will fail. */ static void dbus_cb_dunst_Ping(GDBusConnection *connection, const gchar *sender, GVariant *parameters, GDBusMethodInvocation *invocation) { g_dbus_method_invocation_return_value(invocation, NULL); g_dbus_connection_flush(connection, NULL, NULL, NULL); } static void dbus_cb_GetCapabilities( GDBusConnection *connection, const gchar *sender, GVariant *parameters, GDBusMethodInvocation *invocation) { GVariantBuilder *builder; GVariant *value; builder = g_variant_builder_new(G_VARIANT_TYPE("as")); g_variant_builder_add(builder, "s", "actions"); g_variant_builder_add(builder, "s", "body"); g_variant_builder_add(builder, "s", "body-hyperlinks"); for (int i = 0; i < sizeof(stack_tag_hints)/sizeof(*stack_tag_hints); ++i) g_variant_builder_add(builder, "s", stack_tag_hints[i]); if (settings.markup != MARKUP_NO) g_variant_builder_add(builder, "s", "body-markup"); value = g_variant_new("(as)", builder); g_clear_pointer(&builder, g_variant_builder_unref); g_dbus_method_invocation_return_value(invocation, value); g_dbus_connection_flush(connection, NULL, NULL, NULL); } static struct notification *dbus_message_to_notification(const gchar *sender, GVariant *parameters) { /* Assert that the parameters' type is actually correct. Albeit usually DBus * already rejects ill typed parameters, it may not be always the case. */ GVariantType *required_type = g_variant_type_new("(susssasa{sv}i)"); if (!g_variant_is_of_type(parameters, required_type)) { g_variant_type_free(required_type); return NULL; } struct notification *n = notification_create(); n->dbus_client = g_strdup(sender); n->dbus_valid = true; GVariant *hints; gchar **actions; int timeout; GVariantIter i; g_variant_iter_init(&i, parameters); g_variant_iter_next(&i, "s", &n->appname); g_variant_iter_next(&i, "u", &n->id); g_variant_iter_next(&i, "s", &n->iconname); g_variant_iter_next(&i, "s", &n->summary); g_variant_iter_next(&i, "s", &n->body); g_variant_iter_next(&i, "^a&s", &actions); g_variant_iter_next(&i, "@a{?*}", &hints); g_variant_iter_next(&i, "i", &timeout); gsize num = 0; while (actions[num]) { if (actions[num+1]) { g_hash_table_insert(n->actions, g_strdup(actions[num]), g_strdup(actions[num+1])); num+=2; } else { LOG_W("Odd length in actions array. Ignoring element: %s", actions[num]); break; } } GVariant *dict_value; if ((dict_value = g_variant_lookup_value(hints, "urgency", G_VARIANT_TYPE_BYTE))) { n->urgency = g_variant_get_byte(dict_value); g_variant_unref(dict_value); } if ((dict_value = g_variant_lookup_value(hints, "fgcolor", G_VARIANT_TYPE_STRING))) { n->colors.fg = g_variant_dup_string(dict_value, NULL); g_variant_unref(dict_value); } if ((dict_value = g_variant_lookup_value(hints, "bgcolor", G_VARIANT_TYPE_STRING))) { n->colors.bg = g_variant_dup_string(dict_value, NULL); g_variant_unref(dict_value); } if ((dict_value = g_variant_lookup_value(hints, "frcolor", G_VARIANT_TYPE_STRING))) { n->colors.frame = g_variant_dup_string(dict_value, NULL); g_variant_unref(dict_value); } if ((dict_value = g_variant_lookup_value(hints, "category", G_VARIANT_TYPE_STRING))) { n->category = g_variant_dup_string(dict_value, NULL); g_variant_unref(dict_value); } if ((dict_value = g_variant_lookup_value(hints, "desktop-entry", G_VARIANT_TYPE_STRING))) { n->desktop_entry = g_variant_dup_string(dict_value, NULL); g_variant_unref(dict_value); } if ((dict_value = g_variant_lookup_value(hints, "image-path", G_VARIANT_TYPE_STRING))) { g_free(n->iconname); n->iconname = g_variant_dup_string(dict_value, NULL); g_variant_unref(dict_value); } dict_value = g_variant_lookup_value(hints, "image-data", G_VARIANT_TYPE("(iiibiiay)")); if (!dict_value) dict_value = g_variant_lookup_value(hints, "image_data", G_VARIANT_TYPE("(iiibiiay)")); if (!dict_value) dict_value = g_variant_lookup_value(hints, "icon_data", G_VARIANT_TYPE("(iiibiiay)")); if (dict_value) { notification_icon_replace_data(n, 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(hints, "transient", G_VARIANT_TYPE_BOOLEAN))) { n->transient = g_variant_get_boolean(dict_value); g_variant_unref(dict_value); } else if ((dict_value = g_variant_lookup_value(hints, "transient", G_VARIANT_TYPE_UINT32))) { n->transient = g_variant_get_uint32(dict_value) > 0; g_variant_unref(dict_value); } else if ((dict_value = g_variant_lookup_value(hints, "transient", G_VARIANT_TYPE_INT32))) { n->transient = g_variant_get_int32(dict_value) > 0; g_variant_unref(dict_value); } if ((dict_value = g_variant_lookup_value(hints, "value", G_VARIANT_TYPE_INT32))) { n->progress = g_variant_get_int32(dict_value); g_variant_unref(dict_value); } else if ((dict_value = g_variant_lookup_value(hints, "value", G_VARIANT_TYPE_UINT32))) { n->progress = g_variant_get_uint32(dict_value); g_variant_unref(dict_value); } /* Check for hints that define the stack_tag * * Only accept to first one we find. */ for (int i = 0; i < sizeof(stack_tag_hints)/sizeof(*stack_tag_hints); ++i) { if ((dict_value = g_variant_lookup_value(hints, stack_tag_hints[i], G_VARIANT_TYPE_STRING))) { n->stack_tag = g_variant_dup_string(dict_value, NULL); g_variant_unref(dict_value); break; } } if (timeout >= 0) n->timeout = ((gint64)timeout) * 1000; g_variant_unref(hints); g_variant_type_free(required_type); g_free(actions); // the strv is only a shallow copy notification_init(n); return n; } static void dbus_cb_Notify( GDBusConnection *connection, const gchar *sender, GVariant *parameters, GDBusMethodInvocation *invocation) { struct notification *n = dbus_message_to_notification(sender, parameters); if (!n) { LOG_W("A notification failed to decode."); g_dbus_method_invocation_return_dbus_error( invocation, FDN_IFAC".Error", "Cannot decode notification!"); return; } int id = queues_notification_insert(n); GVariant *reply = g_variant_new("(u)", id); g_dbus_method_invocation_return_value(invocation, reply); g_dbus_connection_flush(connection, NULL, NULL, NULL); // The message got discarded if (id == 0) { signal_notification_closed(n, REASON_USER); notification_unref(n); } wake_up(); } static void dbus_cb_CloseNotification( GDBusConnection *connection, const gchar *sender, GVariant *parameters, GDBusMethodInvocation *invocation) { guint32 id; g_variant_get(parameters, "(u)", &id); if (settings.ignore_dbusclose) { LOG_D("Ignoring CloseNotification message"); // Stay commpliant by lying to the sender, telling him we closed the notification if (id > 0) { struct notification *n = queues_get_by_id(id); if (n) signal_notification_closed(n, REASON_SIG); } } else { 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 dbus_cb_GetServerInformation( GDBusConnection *connection, const gchar *sender, GVariant *parameters, GDBusMethodInvocation *invocation) { GVariant *answer = g_variant_new("(ssss)", "dunst", "knopwob", VERSION, "1.2"); g_dbus_method_invocation_return_value(invocation, answer); g_dbus_connection_flush(connection, NULL, NULL, NULL); } void signal_notification_closed(struct notification *n, enum reason reason) { if (!n->dbus_valid) { return; } if (reason < REASON_MIN || REASON_MAX < reason) { LOG_W("Closing notification with reason '%d' not supported. " "Closing it with reason '%d'.", reason, REASON_UNDEF); reason = REASON_UNDEF; } if (!dbus_conn) { LOG_E("Unable to close notification: No DBus connection."); } GVariant *body = g_variant_new("(uu)", n->id, reason); GError *err = NULL; g_dbus_connection_emit_signal(dbus_conn, n->dbus_client, FDN_PATH, FDN_IFAC, "NotificationClosed", body, &err); notification_invalidate_actions(n); n->dbus_valid = false; if (err) { LOG_W("Unable to close notification: %s", err->message); g_error_free(err); } } void signal_action_invoked(const struct notification *n, const char *identifier) { if (!n->dbus_valid) { LOG_W("Invoking action '%s' not supported. " "Notification already closed.", identifier); return; } GVariant *body = g_variant_new("(us)", n->id, identifier); GError *err = NULL; g_dbus_connection_emit_signal(dbus_conn, n->dbus_client, FDN_PATH, FDN_IFAC, "ActionInvoked", body, &err); if (err) { LOG_W("Unable to invoke action: %s", err->message); g_error_free(err); } } GVariant *dbus_cb_dunst_Properties_Get(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GError **error, gpointer user_data) { struct dunst_status status = dunst_status_get(); if (STR_EQ(property_name, "paused")) { return g_variant_new_boolean(!status.running); } else { LOG_W("Unknown property!\n"); *error = g_error_new(G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property"); return NULL; } } gboolean dbus_cb_dunst_Properties_Set(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant *value, GError **error, gpointer user_data) { if (STR_EQ(property_name, "paused")) { dunst_status(S_RUNNING, !g_variant_get_boolean(value)); wake_up(); return true; } *error = g_error_new(G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property"); return false; } static const GDBusInterfaceVTable interface_vtable_fdn = { dbus_cb_fdn_methods }; static const GDBusInterfaceVTable interface_vtable_dunst = { dbus_cb_dunst_methods, dbus_cb_dunst_Properties_Get, dbus_cb_dunst_Properties_Set, }; static void dbus_cb_bus_acquired(GDBusConnection *connection, const gchar *name, gpointer user_data) { GError *err = NULL; if(!g_dbus_connection_register_object( connection, FDN_PATH, introspection_data->interfaces[0], &interface_vtable_fdn, NULL, NULL, &err)) { DIE("Unable to register dbus connection interface '%s': %s", introspection_data->interfaces[0]->name, err->message); } if(!g_dbus_connection_register_object( connection, FDN_PATH, introspection_data->interfaces[1], &interface_vtable_dunst, NULL, NULL, &err)) { DIE("Unable to register dbus connection interface '%s': %s", introspection_data->interfaces[1]->name, err->message); } } static void dbus_cb_name_acquired(GDBusConnection *connection, const gchar *name, gpointer user_data) { // If we're not able to get org.fd.N bus, we've still got a problem if (STR_EQ(name, FDN_NAME)) dbus_conn = connection; } /** * Get the PID of the current process, which acquired FDN DBus Name. * * If name or vendor specified, the name and vendor * will get additionally get via the FDN GetServerInformation method * * @param connection The DBus connection * @param pid The place to report the PID to * @param name The place to report the name to, if not required set to NULL * @param vendor The place to report the vendor to, if not required set to NULL * * @retval true: on success * @retval false: Any error happened */ static bool dbus_get_fdn_daemon_info(GDBusConnection *connection, guint *pid, char **name, char **vendor) { ASSERT_OR_RET(pid, false); ASSERT_OR_RET(connection, false); char *owner = NULL; GError *error = NULL; GDBusProxy *proxy_fdn; GDBusProxy *proxy_dbus; 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 false; } GVariant *daemoninfo = NULL; if (name || vendor) { daemoninfo = g_dbus_proxy_call_sync( proxy_fdn, FDN_IFAC ".GetServerInformation", NULL, G_DBUS_CALL_FLAGS_NONE, /* It's not worth to wait for the info * longer than half a second when dying */ 500, NULL, /* cancelable */ &error); } if (error) { /* Ignore the error, we may still be able to retrieve the PID */ g_clear_pointer(&error, g_error_free); } else { g_variant_get(daemoninfo, "(ssss)", name, vendor, NULL, NULL); } 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 false; } 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 false; } g_object_unref(proxy_fdn); g_object_unref(proxy_dbus); g_free(owner); if (daemoninfo) g_variant_unref(daemoninfo); if (pidinfo) { g_variant_get(pidinfo, "(u)", pid); g_variant_unref(pidinfo); return true; } else { return false; } } static void dbus_cb_name_lost(GDBusConnection *connection, const gchar *name, gpointer user_data) { if (connection) { char *name; unsigned int pid; if (dbus_get_fdn_daemon_info(connection, &pid, &name, NULL)) { DIE("Cannot acquire '"FDN_NAME"': " "Name is acquired by '%s' with PID '%d'.", name, pid); } else { DIE("Cannot acquire '"FDN_NAME"'."); } } else { DIE("Cannot connect to DBus."); } exit(1); } int dbus_init(void) { guint owner_id; introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, NULL); owner_id = g_bus_own_name(G_BUS_TYPE_SESSION, FDN_NAME, G_BUS_NAME_OWNER_FLAGS_NONE, dbus_cb_bus_acquired, dbus_cb_name_acquired, dbus_cb_name_lost, NULL, NULL); return owner_id; } void dbus_teardown(int owner_id) { g_clear_pointer(&introspection_data, g_dbus_node_info_unref); g_bus_unown_name(owner_id); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/dbus.h������������������������������������������������������������������������������0000664�0000000�0000000�00000001720�13706263046�0014556�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_DBUS_H #define DUNST_DBUS_H #include "dunst.h" #include "notification.h" /// The reasons according to the notification spec enum reason { REASON_MIN = 1, /**< Minimum value, useful for boundary checking */ REASON_TIME = 1, /**< The notification timed out */ REASON_USER = 2, /**< The user closed the notification */ REASON_SIG = 3, /**< The daemon received a `NotificationClose` signal */ REASON_UNDEF = 4, /**< Undefined reason not matching the previous ones */ REASON_MAX = 4, /**< Maximum value, useful for boundary checking */ }; int dbus_init(void); void dbus_teardown(int id); void signal_notification_closed(struct notification *n, enum reason reason); void signal_action_invoked(const struct notification *n, const char *identifier); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ������������������������������������������������dunst-1.5.0/src/draw.c������������������������������������������������������������������������������0000664�0000000�0000000�00000057364�13706263046�0014570�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "draw.h" #include <assert.h> #include <cairo.h> #include <math.h> #include <pango/pango-attributes.h> #include <pango/pangocairo.h> #include <pango/pango-font.h> #include <pango/pango-layout.h> #include <pango/pango-types.h> #include <stdlib.h> #include <inttypes.h> #include "dunst.h" #include "icon.h" #include "log.h" #include "markup.h" #include "notification.h" #include "queues.h" #include "x11/x.h" struct colored_layout { PangoLayout *l; struct color fg; struct color bg; struct color frame; char *text; PangoAttrList *attr; cairo_surface_t *icon; const struct notification *n; }; struct window_x11 *win; PangoFontDescription *pango_fdesc; #define UINT_MAX_N(bits) ((1 << bits) - 1) void draw_setup(void) { x_setup(); win = x_win_create(); pango_fdesc = pango_font_description_from_string(settings.font); } static struct color hex_to_color(uint32_t hexValue, int dpc) { const int bpc = 4 * dpc; const unsigned single_max = UINT_MAX_N(bpc); struct color ret; ret.r = ((hexValue >> 3 * bpc) & single_max) / (double)single_max; ret.g = ((hexValue >> 2 * bpc) & single_max) / (double)single_max; ret.b = ((hexValue >> 1 * bpc) & single_max) / (double)single_max; ret.a = ((hexValue) & single_max) / (double)single_max; return ret; } static struct color string_to_color(const char *str) { char *end; uint_fast32_t val = strtoul(str+1, &end, 16); if (end[0] != '\0' && end[1] != '\0') { LOG_W("Invalid color string: '%s'", str); } switch (end - (str+1)) { case 3: return hex_to_color((val << 4) | 0xF, 1); case 6: return hex_to_color((val << 8) | 0xFF, 2); case 4: return hex_to_color(val, 1); case 8: return hex_to_color(val, 2); } /* return black on error */ LOG_W("Invalid color string: '%s'", str); return hex_to_color(0xF, 1); } static inline double color_apply_delta(double base, double delta) { base += delta; if (base > 1) base = 1; if (base < 0) base = 0; return base; } static struct color calculate_foreground_color(struct color bg) { double c_delta = 0.1; struct color fg = bg; /* do we need to darken or brighten the colors? */ bool darken = (bg.r + bg.g + bg.b) / 3 > 0.5; int signedness = darken ? -1 : 1; fg.r = color_apply_delta(fg.r, c_delta * signedness); fg.g = color_apply_delta(fg.g, c_delta * signedness); fg.b = color_apply_delta(fg.b, c_delta * signedness); return fg; } static struct color layout_get_sepcolor(struct colored_layout *cl, struct colored_layout *cl_next) { switch (settings.sep_color.type) { case SEP_FRAME: if (cl_next->n->urgency > cl->n->urgency) return cl_next->frame; else return cl->frame; case SEP_CUSTOM: return string_to_color(settings.sep_color.sep_color); case SEP_FOREGROUND: return cl->fg; case SEP_AUTO: return calculate_foreground_color(cl->bg); default: LOG_E("Invalid %s enum value in %s:%d", "sep_color", __FILE__, __LINE__); break; } } static void layout_setup_pango(PangoLayout *layout, int width) { pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR); pango_layout_set_width(layout, width * PANGO_SCALE); pango_layout_set_font_description(layout, pango_fdesc); pango_layout_set_spacing(layout, settings.line_height * PANGO_SCALE); PangoAlignment align; switch (settings.align) { case ALIGN_LEFT: default: align = PANGO_ALIGN_LEFT; break; case ALIGN_CENTER: align = PANGO_ALIGN_CENTER; break; case ALIGN_RIGHT: align = PANGO_ALIGN_RIGHT; break; } pango_layout_set_alignment(layout, align); } static void free_colored_layout(void *data) { struct colored_layout *cl = data; g_object_unref(cl->l); pango_attr_list_unref(cl->attr); g_free(cl->text); if (cl->icon) cairo_surface_destroy(cl->icon); g_free(cl); } static bool have_dynamic_width(void) { return (settings.geometry.width_set && settings.geometry.w == 0); } static struct dimensions calculate_dimensions(GSList *layouts) { struct dimensions dim = { 0 }; struct screen_info *scr = get_active_screen(); if (have_dynamic_width()) { /* dynamic width */ dim.w = 0; } else if (settings.geometry.width_set) { /* fixed width */ if (settings.geometry.negative_width) { dim.w = scr->w - settings.geometry.w; } else { dim.w = settings.geometry.w; } } else { /* across the screen */ dim.w = scr->w; } dim.h += 2 * settings.frame_width; dim.h += (g_slist_length(layouts) - 1) * settings.separator_height; dim.corner_radius = settings.corner_radius; int text_width = 0, total_width = 0; for (GSList *iter = layouts; iter; iter = iter->next) { struct colored_layout *cl = iter->data; int w=0,h=0; pango_layout_get_pixel_size(cl->l, &w, &h); if (cl->icon) { h = MAX(cairo_image_surface_get_height(cl->icon), h); w += cairo_image_surface_get_width(cl->icon) + settings.h_padding; } h = MAX(settings.notification_height, h + settings.padding * 2); dim.h += h; text_width = MAX(w, text_width); if (have_dynamic_width() || settings.shrink) { /* dynamic width */ total_width = MAX(text_width + 2 * settings.h_padding, total_width); /* subtract height from the unwrapped text */ dim.h -= h; if (total_width > scr->w) { /* set width to screen width */ dim.w = scr->w - settings.geometry.x * 2; } else if (have_dynamic_width() || (total_width < settings.geometry.w && settings.shrink)) { /* set width to text width */ dim.w = total_width + 2 * settings.frame_width; } /* re-setup the layout */ w = dim.w; w -= 2 * settings.h_padding; w -= 2 * settings.frame_width; if (cl->icon) w -= cairo_image_surface_get_width(cl->icon) + settings.h_padding; layout_setup_pango(cl->l, w); /* re-read information */ pango_layout_get_pixel_size(cl->l, &w, &h); if (cl->icon) { h = MAX(cairo_image_surface_get_height(cl->icon), h); w += cairo_image_surface_get_width(cl->icon) + settings.h_padding; } h = MAX(settings.notification_height, h + settings.padding * 2); dim.h += h; text_width = MAX(w, text_width); } dim.corner_radius = MIN(dim.corner_radius, h/2); } if (dim.w <= 0) { dim.w = text_width + 2 * settings.h_padding; dim.w += 2 * settings.frame_width; } return dim; } static PangoLayout *layout_create(cairo_t *c) { struct screen_info *screen = get_active_screen(); PangoContext *context = pango_cairo_create_context(c); pango_cairo_context_set_resolution(context, screen_dpi_get(screen)); PangoLayout *layout = pango_layout_new(context); g_object_unref(context); return layout; } static struct colored_layout *layout_init_shared(cairo_t *c, const struct notification *n) { struct colored_layout *cl = g_malloc(sizeof(struct colored_layout)); cl->l = layout_create(c); if (!settings.word_wrap) { PangoEllipsizeMode ellipsize; switch (settings.ellipsize) { case ELLIPSE_START: ellipsize = PANGO_ELLIPSIZE_START; break; case ELLIPSE_MIDDLE: ellipsize = PANGO_ELLIPSIZE_MIDDLE; break; case ELLIPSE_END: ellipsize = PANGO_ELLIPSIZE_END; break; default: LOG_E("Invalid %s enum value in %s:%d", "ellipsize", __FILE__, __LINE__); break; } pango_layout_set_ellipsize(cl->l, ellipsize); } if (settings.icon_position != ICON_OFF && n->icon) { cl->icon = gdk_pixbuf_to_cairo_surface(n->icon); } else { cl->icon = NULL; } if (cl->icon && cairo_surface_status(cl->icon) != CAIRO_STATUS_SUCCESS) { cairo_surface_destroy(cl->icon); cl->icon = NULL; } cl->fg = string_to_color(n->colors.fg); cl->bg = string_to_color(n->colors.bg); cl->frame = string_to_color(n->colors.frame); cl->n = n; struct dimensions dim = calculate_dimensions(NULL); int width = dim.w; if (have_dynamic_width()) { layout_setup_pango(cl->l, -1); } else { width -= 2 * settings.h_padding; width -= 2 * settings.frame_width; if (cl->icon) width -= cairo_image_surface_get_width(cl->icon) + settings.h_padding; layout_setup_pango(cl->l, width); } return cl; } static struct colored_layout *layout_derive_xmore(cairo_t *c, const struct notification *n, int qlen) { struct colored_layout *cl = layout_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 struct colored_layout *layout_from_notification(cairo_t *c, struct notification *n) { struct colored_layout *cl = layout_init_shared(c, n); /* markup */ GError *err = NULL; pango_parse_markup(n->text_to_render, -1, 0, &(cl->attr), &(cl->text), NULL, &err); if (!err) { pango_layout_set_text(cl->l, cl->text, -1); pango_layout_set_attributes(cl->l, cl->attr); } else { /* remove markup and display plain message instead */ n->text_to_render = markup_strip(n->text_to_render); cl->text = NULL; cl->attr = NULL; pango_layout_set_text(cl->l, n->text_to_render, -1); if (n->first_render) { LOG_W("Unable to parse markup: %s", err->message); } g_error_free(err); } pango_layout_get_pixel_size(cl->l, NULL, &(n->displayed_height)); if (cl->icon) n->displayed_height = MAX(cairo_image_surface_get_height(cl->icon), n->displayed_height); n->displayed_height = MAX(settings.notification_height, n->displayed_height + settings.padding * 2); n->first_render = false; return cl; } static GSList *create_layouts(cairo_t *c) { GSList *layouts = NULL; int qlen = queues_length_waiting(); bool xmore_is_needed = qlen > 0 && settings.indicate_hidden; for (const GList *iter = queues_get_displayed(); iter; iter = iter->next) { struct notification *n = iter->data; notification_update_text_to_render(n); if (!iter->next && xmore_is_needed && settings.geometry.h == 1) { char *new_ttr = g_strdup_printf("%s (%d more)", n->text_to_render, qlen); g_free(n->text_to_render); n->text_to_render = new_ttr; } layouts = g_slist_append(layouts, layout_from_notification(c, n)); } if (xmore_is_needed && settings.geometry.h != 1) { /* append xmore message as new message */ layouts = g_slist_append(layouts, layout_derive_xmore(c, queues_get_head_waiting(), qlen)); } return layouts; } static int layout_get_height(struct colored_layout *cl) { int h; int h_icon = 0; pango_layout_get_pixel_size(cl->l, NULL, &h); if (cl->icon) h_icon = cairo_image_surface_get_height(cl->icon); return MAX(h, h_icon); } /* Attempt to make internal radius more organic. * Simple r-w is not enough for too small r/w ratio. * simplifications: r/2 == r - w + w*w / (r * 2) with (w == r) * r, w - corner radius & frame width, * h - box height */ static int frame_internal_radius (int r, int w, int h) { if (r == 0 || w == 0 || h == 0) return 0; // Integer precision scaler, using 1/4 of int size const int s = 2 << (8 * sizeof(int) / 4); int r1, r2, ret; h *= s; r *= s; w *= s; r1 = r - w + w * w / (r * 2); // w < r r2 = r * h / (h + (w - r) * 2); // w >= r ret = (r > w) ? r1 : (r / 2 < r2) ? r / 2 : r2; return ret / s; } /** * Create a path on the given cairo context to draw the background of a notification. * The top corners will get rounded by `corner_radius`, if `first` is set. * Respectably the same for `last` with the bottom corners. */ void draw_rounded_rect(cairo_t *c, int x, int y, int width, int height, int corner_radius, bool first, bool last) { const float degrees = M_PI / 180.0; cairo_new_sub_path(c); if (last) { // bottom right cairo_arc(c, x + width - corner_radius, y + height - corner_radius, corner_radius, degrees * 0, degrees * 90); // bottom left cairo_arc(c, x + corner_radius, y + height - corner_radius, corner_radius, degrees * 90, degrees * 180); } else { cairo_line_to(c, x + width, y + height); cairo_line_to(c, x, y + height); } if (first) { // top left cairo_arc(c, x + corner_radius, y + corner_radius, corner_radius, degrees * 180, degrees * 270); // top right cairo_arc(c, x + width - corner_radius, y + corner_radius, corner_radius, degrees * 270, degrees * 360); } else { cairo_line_to(c, x, y); cairo_line_to(c, x + width, y); } cairo_close_path(c); } static cairo_surface_t *render_background(cairo_surface_t *srf, struct colored_layout *cl, struct colored_layout *cl_next, int y, int width, int height, int corner_radius, bool first, bool last, int *ret_width) { int x = 0; int radius_int = corner_radius; cairo_t *c = cairo_create(srf); /* stroke area doesn't intersect with main area */ cairo_set_fill_rule(c, CAIRO_FILL_RULE_EVEN_ODD); /* for correct combination of adjacent areas */ cairo_set_operator(c, CAIRO_OPERATOR_ADD); if (first) height += settings.frame_width; if (last) height += settings.frame_width; else height += settings.separator_height; draw_rounded_rect(c, x, y, width, height, corner_radius, first, last); /* adding frame */ x += settings.frame_width; if (first) { y += settings.frame_width; height -= settings.frame_width; } width -= 2 * settings.frame_width; if (last) height -= settings.frame_width; else height -= settings.separator_height; radius_int = frame_internal_radius(corner_radius, settings.frame_width, height); draw_rounded_rect(c, x, y, width, height, radius_int, first, last); cairo_set_source_rgba(c, cl->frame.r, cl->frame.g, cl->frame.b, cl->frame.a); cairo_fill(c); draw_rounded_rect(c, x, y, width, height, radius_int, first, last); cairo_set_source_rgba(c, cl->bg.r, cl->bg.g, cl->bg.b, cl->bg.a); cairo_fill(c); cairo_set_operator(c, CAIRO_OPERATOR_SOURCE); if ( settings.sep_color.type != SEP_FRAME && settings.separator_height > 0 && !last) { struct color sep_color = layout_get_sepcolor(cl, cl_next); cairo_set_source_rgba(c, sep_color.r, sep_color.g, sep_color.b, sep_color.a); cairo_rectangle(c, settings.frame_width, y + height, width, settings.separator_height); cairo_fill(c); } cairo_destroy(c); if (ret_width) *ret_width = width; return cairo_surface_create_for_rectangle(srf, x, y, width, height); } static void render_content(cairo_t *c, struct colored_layout *cl, int width) { const int h = layout_get_height(cl); int h_text; pango_layout_get_pixel_size(cl->l, NULL, &h_text); int text_x = settings.h_padding, text_y = settings.padding + h / 2 - h_text / 2; // text positioning if (cl->icon) { // vertical alignment if (settings.vertical_alignment == VERTICAL_TOP) { text_y = settings.padding; } else if (settings.vertical_alignment == VERTICAL_BOTTOM) { text_y = h + settings.padding - h_text; if (text_y < 0) text_y = settings.padding; } // else VERTICAL_CENTER // icon position if (settings.icon_position == ICON_LEFT) { text_x = cairo_image_surface_get_width(cl->icon) + 2 * settings.h_padding; } // else ICON_RIGHT } cairo_move_to(c, text_x, text_y); cairo_set_source_rgba(c, cl->fg.r, cl->fg.g, cl->fg.b, cl->fg.a); pango_cairo_update_layout(c, cl->l); pango_cairo_show_layout(c, cl->l); // icon positioning 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 = width - settings.h_padding - image_width, image_y = settings.padding + h/2 - image_height/2; // vertical alignment if (settings.vertical_alignment == VERTICAL_TOP) { image_y = settings.padding; } else if (settings.vertical_alignment == VERTICAL_BOTTOM) { image_y = h + settings.padding - image_height; if (image_y < settings.padding || image_y > h) image_y = settings.padding; } // else VERTICAL_CENTER // icon position if (settings.icon_position == ICON_LEFT) { image_x = settings.h_padding; } // else ICON_RIGHT 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); } } static struct dimensions layout_render(cairo_surface_t *srf, struct colored_layout *cl, struct colored_layout *cl_next, struct dimensions dim, bool first, bool last) { const int cl_h = layout_get_height(cl); int h_text = 0; pango_layout_get_pixel_size(cl->l, NULL, &h_text); int bg_width = 0; int bg_height = MAX(settings.notification_height, (2 * settings.padding) + cl_h); cairo_surface_t *content = render_background(srf, cl, cl_next, dim.y, dim.w, bg_height, dim.corner_radius, first, last, &bg_width); cairo_t *c = cairo_create(content); render_content(c, cl, bg_width); /* adding frame */ if (first) dim.y += settings.frame_width; if (!last) dim.y += settings.separator_height; if (settings.notification_height <= (2 * settings.padding) + cl_h) dim.y += cl_h + 2 * settings.padding; else dim.y += settings.notification_height; cairo_destroy(c); cairo_surface_destroy(content); return dim; } /** * Calculates the position the window should be placed at given its width and * height and stores them in \p ret_x and \p ret_y. */ static void calc_window_pos(int width, int height, int *ret_x, int *ret_y) { struct screen_info *scr = get_active_screen(); if (ret_x) { if (settings.geometry.negative_x) { *ret_x = (scr->x + (scr->w - width)) + settings.geometry.x; } else { *ret_x = scr->x + settings.geometry.x; } } if (ret_y) { if (settings.geometry.negative_y) { *ret_y = scr->y + (scr->h + settings.geometry.y) - height; } else { *ret_y = scr->y + settings.geometry.y; } } } void draw(void) { assert(queues_length_displayed() > 0); GSList *layouts = create_layouts(x_win_get_context(win)); struct dimensions dim = calculate_dimensions(layouts); cairo_surface_t *image_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, dim.w, dim.h); bool first = true; for (GSList *iter = layouts; iter; iter = iter->next) { struct colored_layout *cl_this = iter->data; struct colored_layout *cl_next = iter->next ? iter->next->data : NULL; dim = layout_render(image_surface, cl_this, cl_next, dim, first, !cl_next); first = false; } calc_window_pos(dim.w, dim.h, &dim.x, &dim.y); x_display_surface(image_surface, win, &dim); cairo_surface_destroy(image_surface); g_slist_free_full(layouts, free_colored_layout); } void draw_deinit(void) { x_win_destroy(win); x_free(); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/draw.h������������������������������������������������������������������������������0000664�0000000�0000000�00000000546�13706263046�0014563�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef DUNST_DRAW_H #define DUNST_DRAW_H #include "x11/x.h" extern struct window_x11 *win; // Temporary void draw_setup(void); void draw(void); void draw_rounded_rect(cairo_t *c, int x, int y, int width, int height, int corner_radius, bool first, bool last); void draw_deinit(void); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ����������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/dunst.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000013373�13706263046�0014760�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2012 - 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "dunst.h" #include <assert.h> #include <glib.h> #include <glib-unix.h> #include <signal.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <X11/Xlib.h> #include "dbus.h" #include "draw.h" #include "log.h" #include "menu.h" #include "notification.h" #include "option_parser.h" #include "queues.h" #include "settings.h" #include "utils.h" #include "x11/screen.h" #include "x11/x.h" GMainLoop *mainloop = NULL; static struct dunst_status status; /* see dunst.h */ void dunst_status(const enum dunst_status_field field, bool value) { switch (field) { case S_FULLSCREEN: status.fullscreen = value; break; case S_IDLE: status.idle = value; break; case S_RUNNING: status.running = value; break; default: LOG_E("Invalid %s enum value in %s:%d", "dunst_status", __FILE__, __LINE__); break; } } /* see dunst.h */ struct dunst_status dunst_status_get(void) { return status; } /* misc functions */ static gboolean run(void *data); void wake_up(void) { run(NULL); } static gboolean run(void *data) { static gint64 next_timeout = 0; LOG_D("RUN"); dunst_status(S_FULLSCREEN, have_fullscreen_window()); dunst_status(S_IDLE, x_is_idle()); queues_update(status); bool active = queues_length_displayed() > 0; if (active) { // Call draw before showing the window to avoid flickering draw(); x_win_show(win); } else { x_win_hide(win); } if (active) { gint64 now = time_monotonic_now(); gint64 sleep = queues_get_next_datachange(now); gint64 timeout_at = now + sleep; if (sleep >= 0) { if (next_timeout < now || timeout_at < next_timeout) { g_timeout_add(sleep/1000, run, NULL); next_timeout = timeout_at; } } } /* If the execution got triggered by g_timeout_add, * we have to remove the timeout (which is actually a * recurring interval), as we have set a new one * by ourselves. */ return G_SOURCE_REMOVE; } gboolean pause_signal(gpointer data) { dunst_status(S_RUNNING, false); wake_up(); return G_SOURCE_CONTINUE; } gboolean unpause_signal(gpointer data) { dunst_status(S_RUNNING, true); wake_up(); return G_SOURCE_CONTINUE; } gboolean quit_signal(gpointer data) { g_main_loop_quit(mainloop); return G_SOURCE_CONTINUE; } static void teardown(void) { regex_teardown(); queues_teardown(); draw_deinit(); } int dunst_main(int argc, char *argv[]) { dunst_status(S_RUNNING, true); dunst_status(S_IDLE, false); queues_init(); cmdline_load(argc, argv); dunst_log_init(false); if (cmdline_get_bool("-v/-version", false, "Print version") || cmdline_get_bool("--version", false, "Print version")) { print_version(); } char *verbosity = cmdline_get_string("-verbosity", NULL, "Minimum level for message"); log_set_level_from_string(verbosity); g_free(verbosity); char *cmdline_config_path; cmdline_config_path = cmdline_get_string("-conf/-config", NULL, "Path to configuration file"); load_settings(cmdline_config_path); if (cmdline_get_bool("-h/-help", false, "Print help") || cmdline_get_bool("--help", false, "Print help")) { usage(EXIT_SUCCESS); } int dbus_owner_id = dbus_init(); mainloop = g_main_loop_new(NULL, FALSE); draw_setup(); guint pause_src = g_unix_signal_add(SIGUSR1, pause_signal, NULL); guint unpause_src = g_unix_signal_add(SIGUSR2, unpause_signal, NULL); /* register SIGINT/SIGTERM handler for * graceful termination */ guint term_src = g_unix_signal_add(SIGTERM, quit_signal, NULL); guint int_src = g_unix_signal_add(SIGINT, quit_signal, NULL); if (settings.startup_notification) { struct notification *n = notification_create(); n->id = 0; n->appname = g_strdup("dunst"); n->summary = g_strdup("startup"); n->body = g_strdup("dunst is up and running"); n->progress = -1; n->timeout = S2US(10); n->markup = MARKUP_NO; n->urgency = URG_LOW; notification_init(n); queues_notification_insert(n); // we do not call wakeup now, wake_up does not work here yet } run(NULL); g_main_loop_run(mainloop); g_clear_pointer(&mainloop, g_main_loop_unref); /* remove signal handler watches */ g_source_remove(pause_src); g_source_remove(unpause_src); g_source_remove(term_src); g_source_remove(int_src); dbus_teardown(dbus_owner_id); teardown(); return 0; } void usage(int exit_status) { puts("usage:\n"); const char *us = cmdline_create_usage(); puts(us); exit(exit_status); } void print_version(void) { printf ("Dunst - A customizable and lightweight notification-daemon %s\n", VERSION); exit(EXIT_SUCCESS); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/dunst.h�����������������������������������������������������������������������������0000664�0000000�0000000�00000002136�13706263046�0014760�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_DUNST_H #define DUNST_DUNST_H #include <glib.h> #include <stdbool.h> #include <stdio.h> #include <stddef.h> #include "notification.h" //!< A structure to describe dunst's global window status struct dunst_status { bool fullscreen; //!< a fullscreen window is currently focused bool running; //!< set true if dunst is currently running bool idle; //!< set true if the user is idle }; enum dunst_status_field { S_FULLSCREEN, S_IDLE, S_RUNNING, }; /** * Modify the current status of dunst * @param field The field to change in the global status structure * @param value Anything boolean or DO_TOGGLE to toggle the current value */ void dunst_status(const enum dunst_status_field field, bool value); struct dunst_status dunst_status_get(void); void wake_up(void); int dunst_main(int argc, char *argv[]); void usage(int exit_status); void print_version(void); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/icon.c������������������������������������������������������������������������������0000664�0000000�0000000�00000032246�13706263046�0014553�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "icon.h" #include <assert.h> #include <cairo.h> #include <gdk-pixbuf/gdk-pixbuf.h> #include <stdbool.h> #include <string.h> #include "log.h" #include "notification.h" #include "settings.h" #include "utils.h" static bool is_readable_file(const char *filename) { return (access(filename, R_OK) != -1); } /** * Reassemble the data parts of a GdkPixbuf into a cairo_surface_t's data field. * * Requires to call on the surface flush before and mark_dirty after the execution. */ static void pixbuf_data_to_cairo_data( const unsigned char *pixels_p, unsigned char *pixels_c, size_t rowstride_p, size_t rowstride_c, int width, int height, int n_channels) { #if G_BYTE_ORDER == G_LITTLE_ENDIAN static const size_t CAIRO_B = 0; static const size_t CAIRO_G = 1; static const size_t CAIRO_R = 2; static const size_t CAIRO_A = 3; #elif G_BYTE_ORDER == G_BIG_ENDIAN static const size_t CAIRO_A = 0; static const size_t CAIRO_R = 1; static const size_t CAIRO_G = 2; static const size_t CAIRO_B = 3; #elif G_BYTE_ORDER == G_PDP_ENDIAN static const size_t CAIRO_R = 0; static const size_t CAIRO_A = 1; static const size_t CAIRO_B = 2; static const size_t CAIRO_G = 3; #else // GLib doesn't support any other endiannesses #error Unsupported Endianness #endif assert(pixels_p); assert(pixels_c); assert(width > 0); assert(height > 0); if (n_channels == 3) { for (int h = 0; h < height; h++) { unsigned char *iter_c = pixels_c + h * rowstride_c; const unsigned char *iter_p = pixels_p + h * rowstride_p; for (int w = 0; w < width; w++) { iter_c[CAIRO_R] = iter_p[0]; iter_c[CAIRO_G] = iter_p[1]; iter_c[CAIRO_B] = iter_p[2]; iter_c[CAIRO_A] = 0xff; iter_c += 4; iter_p += n_channels; } } } else { for (int h = 0; h < height; h++) { unsigned char *iter_c = pixels_c + h * rowstride_c; const unsigned char *iter_p = pixels_p + h * rowstride_p; for (int w = 0; w < width; w++) { double alpha_factor = iter_p[3] / (double)0xff; iter_c[CAIRO_R] = (unsigned char)(iter_p[0] * alpha_factor + .5); iter_c[CAIRO_G] = (unsigned char)(iter_p[1] * alpha_factor + .5); iter_c[CAIRO_B] = (unsigned char)(iter_p[2] * alpha_factor + .5); iter_c[CAIRO_A] = iter_p[3]; iter_c += 4; iter_p += n_channels; } } } } cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf) { assert(pixbuf); int width = gdk_pixbuf_get_width(pixbuf); int height = gdk_pixbuf_get_height(pixbuf); cairo_format_t fmt = gdk_pixbuf_get_has_alpha(pixbuf) ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24; cairo_surface_t *icon_surface = cairo_image_surface_create(fmt, width, height); /* Copy pixel data from pixbuf to surface */ cairo_surface_flush(icon_surface); pixbuf_data_to_cairo_data(gdk_pixbuf_read_pixels(pixbuf), cairo_image_surface_get_data(icon_surface), gdk_pixbuf_get_rowstride(pixbuf), cairo_format_stride_for_width(fmt, width), gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf), gdk_pixbuf_get_n_channels(pixbuf)); cairo_surface_mark_dirty(icon_surface); return icon_surface; } /** * Scales the given image dimensions if necessary according to the settings. * * @param w a pointer to the image width, to be modified in-place * @param h a pointer to the image height, to be modified in-place * @return TRUE if the dimensions were updated, FALSE if they were left unchanged */ static bool icon_size_clamp(int *w, int *h) { int _w = *w, _h = *h; int landscape = _w > _h; int orig_larger = landscape ? _w : _h; double larger = orig_larger; double smaller = landscape ? _h : _w; if (settings.min_icon_size && smaller < settings.min_icon_size) { larger = larger / smaller * settings.min_icon_size; smaller = settings.min_icon_size; } if (settings.max_icon_size && larger > settings.max_icon_size) { smaller = smaller / larger * settings.max_icon_size; larger = settings.max_icon_size; } if ((int) larger != orig_larger) { *w = (int) (landscape ? larger : smaller); *h = (int) (landscape ? smaller : larger); return TRUE; } return FALSE; } /** * Scales the given GdkPixbuf if necessary according to the settings. * * @param pixbuf (nullable) The pixbuf, which may be too big. * Takes ownership of the reference. * @return the scaled version of the pixbuf. If scaling wasn't * necessary, it returns the same pixbuf. Transfers full * ownership of the reference. */ static GdkPixbuf *icon_pixbuf_scale(GdkPixbuf *pixbuf) { ASSERT_OR_RET(pixbuf, NULL); int w = gdk_pixbuf_get_width(pixbuf); int h = gdk_pixbuf_get_height(pixbuf); if (icon_size_clamp(&w, &h)) { GdkPixbuf *scaled = gdk_pixbuf_scale_simple( pixbuf, w, h, GDK_INTERP_BILINEAR); g_object_unref(pixbuf); pixbuf = scaled; } return pixbuf; } GdkPixbuf *get_pixbuf_from_file(const char *filename) { char *path = string_to_path(g_strdup(filename)); GError *error = NULL; gint w, h; if (!gdk_pixbuf_get_file_info (path, &w, &h)) { LOG_W("Failed to load image info for %s", filename); g_free(path); return NULL; } icon_size_clamp(&w, &h); GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_scale(path, w, h, TRUE, &error); if (error) { LOG_W("%s", error->message); g_error_free(error); } g_free(path); return pixbuf; } GdkPixbuf *get_pixbuf_from_icon(const char *iconname) { if (STR_EMPTY(iconname)) return NULL; const char *suffixes[] = { ".svg", ".svgz", ".png", ".xpm", NULL }; GdkPixbuf *pixbuf = NULL; gchar *uri_path = NULL; if (g_str_has_prefix(iconname, "file://")) { uri_path = g_filename_from_uri(iconname, NULL, NULL); if (uri_path) iconname = uri_path; } /* absolute path? */ if (iconname[0] == '/' || iconname[0] == '~') { pixbuf = get_pixbuf_from_file(iconname); } else { /* search in icon_path */ char *start = settings.icon_path, *end, *current_folder, *maybe_icon_path; do { end = strchr(start, ':'); if (!end) end = strchr(settings.icon_path, '\0'); /* end = end of string */ current_folder = g_strndup(start, end - start); for (const char **suf = suffixes; *suf; suf++) { maybe_icon_path = g_strconcat(current_folder, "/", iconname, *suf, NULL); if (is_readable_file(maybe_icon_path)) pixbuf = get_pixbuf_from_file(maybe_icon_path); g_free(maybe_icon_path); if (pixbuf) break; } g_free(current_folder); if (pixbuf) break; start = end + 1; } while (STR_FULL(end)); if (!pixbuf) LOG_W("No icon found in path: '%s'", iconname); } g_free(uri_path); return pixbuf; } GdkPixbuf *icon_get_for_name(const char *name, char **id) { ASSERT_OR_RET(name, NULL); ASSERT_OR_RET(id, NULL); GdkPixbuf *pb = get_pixbuf_from_icon(name); if (pb) *id = g_strdup(name); return pb; } GdkPixbuf *icon_get_for_data(GVariant *data, char **id) { ASSERT_OR_RET(data, NULL); ASSERT_OR_RET(id, NULL); if (!STR_EQ("(iiibiiay)", g_variant_get_type_string(data))) { LOG_W("Invalid data for pixbuf given."); return NULL; } /* The raw image is a big array of char data. * * The image is serialised rowwise pixel by pixel. The rows are aligned * by a spacer full of garbage. The overall data length of data + garbage * is called the rowstride. * * Mind the missing spacer at the last row. * * len: |<--------------rowstride---------------->| * len: |<-width*pixelstride->| * row 1: | data for row 1 | spacer of garbage | * row 2: | data for row 2 | spacer of garbage | * | . | spacer of garbage | * | . | spacer of garbage | * | . | spacer of garbage | * row n-1: | data for row n-1 | spacer of garbage | * row n: | data for row n | */ GdkPixbuf *pixbuf = NULL; GVariant *data_variant = NULL; unsigned char *data_pb; gsize len_expected; gsize len_actual; gsize pixelstride; int width; int height; int rowstride; int has_alpha; int bits_per_sample; int n_channels; g_variant_get(data, "(iiibii@ay)", &width, &height, &rowstride, &has_alpha, &bits_per_sample, &n_channels, &data_variant); // note: (A+7)/8 rounds up A to the next byte boundary pixelstride = (n_channels * bits_per_sample + 7)/8; len_expected = (height - 1) * rowstride + width * pixelstride; len_actual = g_variant_get_size(data_variant); if (len_actual != len_expected) { LOG_W("Expected image data to be of length %" G_GSIZE_FORMAT " but got a length of %" G_GSIZE_FORMAT, len_expected, len_actual); g_variant_unref(data_variant); return NULL; } data_pb = (guchar *) g_memdup(g_variant_get_data(data_variant), len_actual); pixbuf = gdk_pixbuf_new_from_data(data_pb, GDK_COLORSPACE_RGB, has_alpha, bits_per_sample, width, height, rowstride, (GdkPixbufDestroyNotify) g_free, data_pb); if (!pixbuf) { /* Dear user, I'm sorry, I'd like to give you a more specific * error message. But sadly, I can't */ LOG_W("Cannot serialise raw icon data into pixbuf."); return NULL; } /* To calculate a checksum of the current image, we have to remove * all excess spacers, so that our checksummed memory only contains * real data. */ size_t data_chk_len = pixelstride * width * height; unsigned char *data_chk = g_malloc(data_chk_len); size_t rowstride_short = pixelstride * width; for (int i = 0; i < height; i++) { memcpy(data_chk + (i*rowstride_short), data_pb + (i*rowstride), rowstride_short); } *id = g_compute_checksum_for_data(G_CHECKSUM_MD5, data_chk, data_chk_len); g_free(data_chk); g_variant_unref(data_variant); pixbuf = icon_pixbuf_scale(pixbuf); return pixbuf; } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/icon.h������������������������������������������������������������������������������0000664�0000000�0000000�00000004625�13706263046�0014560�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef DUNST_ICON_H #define DUNST_ICON_H #include <cairo.h> #include <gdk-pixbuf/gdk-pixbuf.h> #include "notification.h" cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf); /** Retrieve an icon by its full filepath, scaled according to settings. * * @param filename A string representing a readable file path * * @return an instance of `GdkPixbuf` * @retval NULL: file does not exist, not readable, etc.. */ GdkPixbuf *get_pixbuf_from_file(const char *filename); /** Retrieve an icon by its name sent via the notification bus, scaled according to settings * * @param iconname A string describing a `file://` URL, an arbitary filename * or an icon name, which then gets searched for in the * settings.icon_path * * @return an instance of `GdkPixbuf` * @retval NULL: file does not exist, not readable, etc.. */ GdkPixbuf *get_pixbuf_from_icon(const char *iconname); /** Read an icon from disk and convert it to a GdkPixbuf, scaled according to settings * * The returned id will be a unique identifier. To check if two given * GdkPixbufs are equal, it's sufficient to just compare the id strings. * * @param name A string describing and icon. May be a full path, a file path or * just a simple name. If it's a name without a slash, the icon will * get searched in the folders of the icon_path setting. * @param id (necessary) A unique identifier of the returned pixbuf. Only filled, * if the return value is non-NULL. * @return an instance of `GdkPixbuf`, representing the name's image * @retval NULL: Invalid path given */ GdkPixbuf *icon_get_for_name(const char *name, char **id); /** Convert a GVariant like described in GdkPixbuf, scaled according to settings * * The returned id will be a unique identifier. To check if two given * GdkPixbufs are equal, it's sufficient to just compare the id strings. * * @param data A GVariant in the format "(iiibii@ay)" filled with values * like described in the notification spec. * @param id (necessary) A unique identifier of the returned pixbuf. * Only filled, if the return value is non-NULL. * @return an instance of `GdkPixbuf` derived from the GVariant * @retval NULL: GVariant parameter nulled, invalid or in wrong format */ GdkPixbuf *icon_get_for_data(GVariant *data, char **id); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �����������������������������������������������������������������������������������������������������������dunst-1.5.0/src/log.c�������������������������������������������������������������������������������0000664�0000000�0000000�00000006000�13706263046�0014371�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2012 - 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ /** * @file src/log.c * @brief logging wrapper to use GLib's logging capabilities */ #include "log.h" #include <glib.h> #include "utils.h" static GLogLevelFlags log_level = G_LOG_LEVEL_WARNING; /* see log.h */ static const char *log_level_to_string(GLogLevelFlags level) { switch (level) { case G_LOG_LEVEL_ERROR: return "ERROR"; case G_LOG_LEVEL_CRITICAL: return "CRITICAL"; case G_LOG_LEVEL_WARNING: return "WARNING"; case G_LOG_LEVEL_MESSAGE: return "MESSAGE"; case G_LOG_LEVEL_INFO: return "INFO"; case G_LOG_LEVEL_DEBUG: return "DEBUG"; default: return "UNKNOWN"; } } /* see log.h */ void log_set_level_from_string(const char *level) { ASSERT_OR_RET(level,); if (STR_CASEQ(level, "critical")) log_level = G_LOG_LEVEL_CRITICAL; else if (STR_CASEQ(level, "crit")) log_level = G_LOG_LEVEL_CRITICAL; else if (STR_CASEQ(level, "warning")) log_level = G_LOG_LEVEL_WARNING; else if (STR_CASEQ(level, "warn")) log_level = G_LOG_LEVEL_WARNING; else if (STR_CASEQ(level, "message")) log_level = G_LOG_LEVEL_MESSAGE; else if (STR_CASEQ(level, "mesg")) log_level = G_LOG_LEVEL_MESSAGE; else if (STR_CASEQ(level, "info")) log_level = G_LOG_LEVEL_INFO; else if (STR_CASEQ(level, "debug")) log_level = G_LOG_LEVEL_DEBUG; else if (STR_CASEQ(level, "deb")) log_level = G_LOG_LEVEL_DEBUG; else LOG_W("Unknown log level: '%s'", level); } void log_set_level(GLogLevelFlags level) { log_level = level; } /** * Log handling function for GLib's logging wrapper * * @param log_domain Used only by GLib * @param message_level Used only by GLib * @param message Used only by GLib * @param testing If not `NULL` (here: `true`), do nothing */ static void dunst_log_handler( const gchar *log_domain, GLogLevelFlags message_level, const gchar *message, gpointer testing) { if (testing) return; /* if you want to have a debug build, you want to log anything, * unconditionally, without specifying debug log level again */ #ifndef DEBUG_BUILD if (log_level < message_level) return; #endif const char *log_level_str = log_level_to_string(message_level & G_LOG_LEVEL_MASK); /* Use stderr for warnings and higher */ if (message_level <= G_LOG_LEVEL_WARNING) g_printerr("%s: %s\n", log_level_str, message); else g_print("%s: %s\n", log_level_str, message); } /* see log.h */ void dunst_log_init(bool testing) { g_log_set_default_handler(dunst_log_handler, (void*)testing); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ dunst-1.5.0/src/log.h�������������������������������������������������������������������������������0000664�0000000�0000000�00000002213�13706263046�0014400�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include <glib.h> #include <stdbool.h> #include <stdlib.h> #ifndef DUNST_LOG_H #define DUNST_LOG_H #define LOG_E g_error #define LOG_C g_critical #define LOG_W g_warning #define LOG_M g_message #define LOG_I g_info #define LOG_D g_debug #define DIE(...) do { LOG_C(__VA_ARGS__); exit(EXIT_FAILURE); } while (0) /** * Set the current loglevel to `level` * * @param level The desired log level * * If `level` is `NULL`, nothing will be done. * If `level` is an invalid value, nothing will be done. */ void log_set_level(GLogLevelFlags level); /** * Set the current loglevel to `level` * * @param level The desired log level as a string * * If `level` is `NULL`, nothing will be done. * If `level` is an invalid value, nothing will be done. */ void log_set_level_from_string(const char* level); /** * Initialise log handling. Can be called any time. * * @param testing If we're in testing mode and should * suppress all output */ void dunst_log_init(bool testing); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/markup.c����������������������������������������������������������������������������0000664�0000000�0000000�00000025207�13706263046�0015121�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "markup.h" #include <assert.h> #include <ctype.h> #include <stdbool.h> #include <stdio.h> #include <string.h> #include "log.h" #include "settings.h" #include "utils.h" /** * Convert all HTML special symbols to HTML entities. * @param str (nullable) */ static char *markup_quote(char *str) { ASSERT_OR_RET(str, NULL); str = string_replace_all("&", "&", str); str = string_replace_all("\"", """, str); str = string_replace_all("'", "'", str); str = string_replace_all("<", "<", str); str = string_replace_all(">", ">", str); return str; } /** * Convert all HTML special entities to their actual char. * @param str (nullable) */ static char *markup_unquote(char *str) { ASSERT_OR_RET(str, NULL); str = string_replace_all(""", "\"", str); str = string_replace_all("'", "'", str); str = string_replace_all("<", "<", str); str = string_replace_all(">", ">", str); str = string_replace_all("&", "&", str); return str; } /** * Convert all HTML linebreak tags to a newline character * @param str (nullable) */ static char *markup_br2nl(char *str) { ASSERT_OR_RET(str, NULL); str = string_replace_all("<br>", "\n", str); str = string_replace_all("<br/>", "\n", str); str = string_replace_all("<br />", "\n", str); return str; } /* see markup.h */ void markup_strip_a(char **str, char **urls) { assert(*str); char *tag1 = NULL; if (urls) *urls = NULL; while ((tag1 = strstr(*str, "<a"))) { // use href=" as stated in the notification spec char *href = strstr(tag1, "href=\""); char *tag1_end = strstr(tag1, ">"); char *tag2 = strstr(tag1, "</a>"); // the tag is broken, ignore it if (!tag1_end) { LOG_W("Given link is broken: '%s'", tag1); string_replace_at(*str, tag1-*str, strlen(tag1), ""); break; } if (tag2 && tag2 < tag1_end) { int repl_len = (tag2 - tag1) + strlen("</a>"); LOG_W("Given link is broken: '%.*s.'", repl_len, tag1); string_replace_at(*str, tag1-*str, repl_len, ""); break; } // search contents of href attribute char *plain_url = NULL; if (href && href < tag1_end) { // shift href to the actual begin of the value href = href+6; const char *quote = strstr(href, "\""); if (quote && quote < tag1_end) { plain_url = g_strndup(href, quote-href); } } // text between a tags int text_len; if (tag2) text_len = tag2 - (tag1_end+1); else text_len = strlen(tag1_end+1); char *text = g_strndup(tag1_end+1, text_len); int repl_len = text_len + (tag1_end-tag1) + 1; repl_len += tag2 ? strlen("</a>") : 0; *str = string_replace_at(*str, tag1-*str, repl_len, text); // if there had been a href attribute, // add it to the URLs if (plain_url && urls) { text = string_replace_all("]", "", text); text = string_replace_all("[", "", text); char *url = g_strdup_printf("[%s] %s", text, plain_url); *urls = string_append(*urls, url, "\n"); g_free(url); } g_free(plain_url); g_free(text); } } /* see markup.h */ void markup_strip_img(char **str, char **urls) { const char *start; if (urls) *urls = NULL; while ((start = strstr(*str, "<img"))) { const char *end = strstr(start, ">"); // the tag is broken, ignore it if (!end) { LOG_W("Given image is broken: '%s'", 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 { LOG_W("Given image argument is broken: '%.*s'", (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); } } /* see markup.h */ char *markup_strip(char *str) { ASSERT_OR_RET(str, NULL); /* strip all tags */ string_strip_delimited(str, '<', '>'); /* unquote the remainder */ str = markup_unquote(str); return str; } /** * Determine if an & character pointed to by \p str is a markup & entity or * part of the text * * @retval true: \p str is an entity * @retval false: It's no valid entity */ static bool markup_is_entity(const char *str) { assert(str); assert(*str == '&'); char *end = strchr(str, ';'); ASSERT_OR_RET(end, false); // Parse (hexa)decimal entities with the format Ӓ or ઼ if (str[1] == '#') { const char *cur = str + 2; if (*cur == 'x') { cur++; // Reject &#x; if (*cur == ';') return false; while (isxdigit(*cur) && cur < end) cur++; } else { // Reject &#; if (*cur == ';') return false; while (isdigit(*cur) && cur < end) cur++; } return (cur == end); } else { const char *supported_tags[] = {"&", "<", ">", """, "'"}; for (int i = 0; i < sizeof(supported_tags)/sizeof(*supported_tags); i++) { if (g_str_has_prefix(str, supported_tags[i])) return true; } return false; } } /** * Escape all unsupported and invalid &-entities in a string. If the resulting * string does not fit it will be reallocated. * * @param str The string to be transformed */ static char *markup_escape_unsupported(char *str) { ASSERT_OR_RET(str, NULL); char *match = str; while ((match = strchr(match, '&'))) { if (!markup_is_entity(match)) { int pos = match - str; str = string_replace_at(str, pos, 1, "&"); match = str + pos + strlen("&"); } else { match++; } } return str; } /* see markup.h */ char *markup_transform(char *str, enum markup_mode markup_mode) { ASSERT_OR_RET(str, NULL); switch (markup_mode) { case MARKUP_NULL: /* `assert(false)`, but with a meaningful error message */ assert(markup_mode != MARKUP_NULL); break; case MARKUP_NO: str = markup_quote(str); break; case MARKUP_STRIP: str = markup_br2nl(str); str = markup_strip(str); str = markup_quote(str); break; case MARKUP_FULL: str = markup_escape_unsupported(str); str = markup_br2nl(str); markup_strip_a(&str, NULL); markup_strip_img(&str, NULL); break; } if (settings.ignore_newline) { str = string_replace_all("\n", " ", str); } return str; } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/markup.h����������������������������������������������������������������������������0000664�0000000�0000000�00000002720�13706263046�0015121�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_MARKUP_H #define DUNST_MARKUP_H enum markup_mode { MARKUP_NULL, MARKUP_NO, MARKUP_STRIP, MARKUP_FULL }; /** * Strip any markup from text; turn it in to plain text. * * For well-formed markup, the following two commands should be * roughly equivalent: * * out = markup_strip(in); * pango_parse_markup(in, -1, 0, NULL, &out, NULL, NULL); * * However, `pango_parse_markup()` balks at invalid markup; * `markup_strip()` shouldn't care if there is invalid markup. */ char *markup_strip(char *str); /** * Remove HTML hyperlinks of a string. * * @param str The string to replace a tags * @param urls (nullable) If any href-attributes found, an `\n` concatenated * string of the URLs in format `[<text between tags>] <href>` */ void markup_strip_a(char **str, char **urls); /** * Remove img-tags of a string. If alt attribute given, use this as replacement. * * @param str The string to replace img tags * @param urls (nullable) If any src-attributes found, an `\n` concatenated string of * the URLs in format `[<alt>] <src>` */ void markup_strip_img(char **str, char **urls); /** * Transform the string in accordance with `markup_mode` and * `settings.ignore_newline` */ char *markup_transform(char *str, enum markup_mode markup_mode); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ������������������������������������������������dunst-1.5.0/src/menu.c������������������������������������������������������������������������������0000664�0000000�0000000�00000023746�13706263046�0014574�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "menu.h" #include <errno.h> #include <glib.h> #include <regex.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/wait.h> #include <unistd.h> #include "dbus.h" #include "dunst.h" #include "log.h" #include "notification.h" #include "queues.h" #include "settings.h" #include "utils.h" static bool is_initialized = false; static regex_t url_regex; struct notification_lock { struct notification *n; gint64 timeout; }; static gpointer context_menu_thread(gpointer data); /** * Initializes regexes needed for matching. * * @return true if initialization succeeded */ static bool regex_init(void) { if (is_initialized) return true; char *regex = "\\<(https?://|ftps?://|news://|mailto:|file://|www\\.)" "[-[:alnum:]_\\@;/?:&=%$.+!*\x27,~#]*" "(\\([-[:alnum:]_\\@;/?:&=%$.+!*\x27,~#]*\\)|[-[:alnum:]_\\@;/?:&=%$+*~])+"; int code = regcomp(&url_regex, regex, REG_EXTENDED | REG_ICASE); if (code != 0) { char error_buf[120]; regerror(code, &url_regex, error_buf, sizeof(error_buf)); LOG_W("Failed to compile URL-matching regex: %s", error_buf); return false; } else { is_initialized = true; return true; } } void regex_teardown(void) { if (is_initialized) { regfree(&url_regex); is_initialized = false; } } /* see menu.h */ char *extract_urls(const char *to_match) { if (!to_match) return NULL; if (!regex_init()) return NULL; char *urls = NULL; const char *p = to_match; regmatch_t m; while (1) { int nomatch = regexec(&url_regex, p, 1, &m, 0); if (nomatch || m.rm_so == -1) break; int start = m.rm_so + (p - to_match); int finish = m.rm_eo + (p - to_match); char *match = g_strndup(to_match + start, finish - start); urls = string_append(urls, match, "\n"); g_free(match); p += m.rm_eo; } return urls; } /* * Open url in browser. * */ void open_browser(const char *in) { if (!settings.browser_cmd) { LOG_C("Unable to open browser: No browser command set."); return; } char *url, *end; // If any, remove leading [ linktext ] from URL if (*in == '[' && (end = strstr(in, "] "))) url = g_strdup(end + 2); else url = g_strdup(in); int argc = 2+g_strv_length(settings.browser_cmd); char **argv = g_malloc_n(argc, sizeof(char*)); memcpy(argv, settings.browser_cmd, argc * sizeof(char*)); argv[argc-2] = url; argv[argc-1] = NULL; GError *err = NULL; g_spawn_async(NULL, argv, NULL, G_SPAWN_DEFAULT | G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, NULL, &err); if (err) { LOG_C("Cannot spawn browser: %s", err->message); g_error_free(err); } g_free(argv); g_free(url); } char *notification_dmenu_string(struct notification *n) { char *dmenu_str = NULL; gpointer p_key; gpointer p_value; GHashTableIter iter; g_hash_table_iter_init(&iter, n->actions); while (g_hash_table_iter_next(&iter, &p_key, &p_value)) { char *key = (char*) p_key; char *value = (char*) p_value; char *act_str = g_strdup_printf("#%s (%s) [%d,%s]", value, n->summary, n->id, key); dmenu_str = string_append(dmenu_str, act_str, "\n"); g_free(act_str); } return dmenu_str; } /* * Notify the corresponding client * that an action has been invoked */ void invoke_action(const char *action) { struct notification *invoked = NULL; uint id; char *data_start, *data_comma, *data_end; /* format: #<human readable> (<summary>)[<id>,<action>] */ data_start = strrchr(action, '['); if (!data_start) { LOG_W("Invalid action: '%s'", action); return; } id = strtol(++data_start, &data_comma, 10); if (*data_comma != ',') { LOG_W("Invalid action: '%s'", action); return; } data_end = strchr(data_comma+1, ']'); if (!data_end) { LOG_W("Invalid action: '%s'", action); return; } char *action_key = g_strndup(data_comma+1, data_end-data_comma-1); for (const GList *iter = queues_get_displayed(); iter; iter = iter->next) { struct notification *n = iter->data; if (n->id != id) continue; if (g_hash_table_contains(n->actions, action_key)) { invoked = n; break; } } if (invoked && action_key) { signal_action_invoked(invoked, action_key); } g_free(action_key); } /** * Dispatch whatever has been returned by dmenu. * If the given result of dmenu is empty or NULL, nothing will be done. * * @param input The result from dmenu. */ void dispatch_menu_result(const char *input) { ASSERT_OR_RET(input,); char *in = g_strdup(input); g_strstrip(in); if (in[0] == '#') invoke_action(in + 1); else if (in[0] != '\0') open_browser(in); g_free(in); } /** Call dmenu with the specified input. Blocks until dmenu is finished. * * @param dmenu_input The input string to feed into dmenu * @returns the selected string from dmenu */ char *invoke_dmenu(const char *dmenu_input) { if (!settings.dmenu_cmd) { LOG_C("Unable to open dmenu: No dmenu command set."); return NULL; } ASSERT_OR_RET(STR_FULL(dmenu_input), NULL); gint dunst_to_dmenu; gint dmenu_to_dunst; GError *err = NULL; char buf[1024]; char *ret = NULL; g_spawn_async_with_pipes(NULL, settings.dmenu_cmd, NULL, G_SPAWN_DEFAULT | G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &dunst_to_dmenu, &dmenu_to_dunst, NULL, &err); if (err) { LOG_C("Cannot spawn dmenu: %s", err->message); g_error_free(err); } else { size_t wlen = strlen(dmenu_input); if (write(dunst_to_dmenu, dmenu_input, wlen) != wlen) { LOG_W("Cannot feed dmenu with input: %s", strerror(errno)); } close(dunst_to_dmenu); ssize_t rlen = read(dmenu_to_dunst, buf, sizeof(buf)); close(dmenu_to_dunst); if (rlen > 0) ret = g_strndup(buf, rlen); else LOG_W("Didn't receive input from dmenu."); } return ret; } /* see menu.h */ void context_menu(void) { GError *err = NULL; g_thread_unref(g_thread_try_new("dmenu", context_menu_thread, NULL, &err)); if (err) { LOG_C("Cannot start thread to call dmenu: %s", err->message); g_error_free(err); } } static gpointer context_menu_thread(gpointer data) { char *dmenu_input = NULL; char *dmenu_output; GList *locked_notifications = NULL; for (const GList *iter = queues_get_displayed(); iter; iter = iter->next) { struct notification *n = iter->data; // Reference and lock the notification if we need it if (n->urls || g_hash_table_size(n->actions)) { notification_ref(n); struct notification_lock *nl = g_malloc(sizeof(struct notification_lock)); nl->n = n; nl->timeout = n->timeout; n->timeout = 0; locked_notifications = g_list_prepend(locked_notifications, nl); } char *dmenu_str = notification_dmenu_string(n); dmenu_input = string_append(dmenu_input, dmenu_str, "\n"); g_free(dmenu_str); if (n->urls) dmenu_input = string_append(dmenu_input, n->urls, "\n"); } dmenu_output = invoke_dmenu(dmenu_input); dispatch_menu_result(dmenu_output); g_free(dmenu_input); g_free(dmenu_output); // unref all notifications for (GList *iter = locked_notifications; iter; iter = iter->next) { struct notification_lock *nl = iter->data; struct notification *n = nl->n; n->timeout = nl->timeout; g_free(nl); notification_unref(n); } g_list_free(locked_notifications); return NULL; } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ��������������������������dunst-1.5.0/src/menu.h������������������������������������������������������������������������������0000664�0000000�0000000�00000001173�13706263046�0014567�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_MENU_H #define DUNST_MENU_H /** * Extract all urls from the given string. * * @param to_match (nullable) String to extract URLs * @return a string of urls separated by '\n' * @retval NULL: No URLs found */ char *extract_urls(const char *to_match); void open_browser(const char *in); void invoke_action(const char *action); void regex_teardown(void); /** * Open the context menu that lets the user select urls/actions/etc. */ void context_menu(void); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/notification.c����������������������������������������������������������������������0000664�0000000�0000000�00000050157�13706263046�0016312�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "notification.h" #include <assert.h> #include <errno.h> #include <glib.h> #include <libgen.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/wait.h> #include <unistd.h> #include "dbus.h" #include "dunst.h" #include "icon.h" #include "log.h" #include "markup.h" #include "menu.h" #include "queues.h" #include "rules.h" #include "settings.h" #include "utils.h" static void notification_extract_urls(struct notification *n); static void notification_format_message(struct notification *n); /* see notification.h */ const char *enum_to_string_fullscreen(enum behavior_fullscreen in) { switch (in) { case FS_SHOW: return "show"; case FS_DELAY: return "delay"; case FS_PUSHBACK: return "pushback"; case FS_NULL: return "(null)"; default: LOG_E("Invalid %s enum value in %s:%d", "fullscreen", __FILE__, __LINE__); break; } } struct _notification_private { gint refcount; }; /* see notification.h */ void notification_print(const struct notification *n) { //TODO: use logging info for this printf("{\n"); printf("\tappname: '%s'\n", n->appname); printf("\tsummary: '%s'\n", n->summary); printf("\tbody: '%s'\n", n->body); printf("\ticon: '%s'\n", n->iconname); printf("\traw_icon set: %s\n", (n->icon_id && !STR_EQ(n->iconname, n->icon_id)) ? "true" : "false"); printf("\ticon_id: '%s'\n", n->icon_id); printf("\tdesktop_entry: '%s'\n", n->desktop_entry ? n->desktop_entry : ""); printf("\tcategory: %s\n", n->category); printf("\ttimeout: %ld\n", n->timeout/1000); printf("\turgency: %s\n", notification_urgency_to_string(n->urgency)); printf("\ttransient: %d\n", n->transient); printf("\tformatted: '%s'\n", n->msg); printf("\tfg: %s\n", n->colors.fg); printf("\tbg: %s\n", n->colors.bg); printf("\tframe: %s\n", n->colors.frame); printf("\tfullscreen: %s\n", enum_to_string_fullscreen(n->fullscreen)); printf("\tprogress: %d\n", n->progress); printf("\tstack_tag: %s\n", (n->stack_tag ? n->stack_tag : "")); printf("\tid: %d\n", n->id); if (n->urls) { char *urls = string_replace_all("\n", "\t\t\n", g_strdup(n->urls)); printf("\turls:\n"); printf("\t{\n"); printf("\t\t%s\n", urls); printf("\t}\n"); g_free(urls); } if (g_hash_table_size(n->actions) == 0) { printf("\tactions: {}\n"); } else { gpointer p_key, p_value; GHashTableIter iter; g_hash_table_iter_init(&iter, n->actions); printf("\tactions: {\n"); while (g_hash_table_iter_next(&iter, &p_key, &p_value)) printf("\t\t\"%s\": \"%s\"\n", (char*)p_key, (char*)p_value); printf("\t}\n"); } printf("\tscript_count: %d\n", n->script_count); if (n->script_count > 0) { printf("\tscripts: "); for (int i = 0; i < n->script_count; i++) { printf("'%s' ",n->scripts[i]); } printf("\n"); } printf("}\n"); } /* see notification.h */ void notification_run_script(struct notification *n) { if (n->script_run && !settings.always_run_script) return; n->script_run = true; const char *appname = n->appname ? n->appname : ""; const char *summary = n->summary ? n->summary : ""; const char *body = n->body ? n->body : ""; const char *icon = n->iconname ? n->iconname : ""; const char *urgency = notification_urgency_to_string(n->urgency); for(int i = 0; i < n->script_count; i++) { const char *script = n->scripts[i]; if (STR_EMPTY(script)) continue; int pid1 = fork(); if (pid1) { int status; waitpid(pid1, &status, 0); } else { int pid2 = fork(); if (pid2) { exit(0); } else { int ret = execlp(script, script, appname, summary, body, icon, urgency, (char *)NULL); if (ret != 0) { LOG_W("Unable to run script: %s", strerror(errno)); exit(EXIT_FAILURE); } } } } } /* * Helper function to convert an urgency to a string */ const char *notification_urgency_to_string(const 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"; } } /* see notification.h */ int notification_cmp(const struct notification *a, const struct notification *b) { if (a->urgency != b->urgency) { return b->urgency - a->urgency; } else { return a->id - b->id; } } /* see notification.h */ int notification_cmp_data(const void *va, const void *vb, void *data) { struct notification *a = (struct notification *) va; struct notification *b = (struct notification *) vb; ASSERT_OR_RET(settings.sort, 1); return notification_cmp(a, b); } bool notification_is_duplicate(const struct notification *a, const struct notification *b) { return STR_EQ(a->appname, b->appname) && STR_EQ(a->summary, b->summary) && STR_EQ(a->body, b->body) && (settings.icon_position != ICON_OFF ? STR_EQ(a->icon_id, b->icon_id) : 1) && a->urgency == b->urgency; } static void notification_private_free(NotificationPrivate *p) { g_free(p); } /* see notification.h */ gint notification_refcount_get(struct notification *n) { assert(n->priv->refcount > 0); return g_atomic_int_get(&n->priv->refcount); } /* see notification.h */ void notification_ref(struct notification *n) { assert(n->priv->refcount > 0); g_atomic_int_inc(&n->priv->refcount); } /* see notification.h */ void notification_unref(struct notification *n) { ASSERT_OR_RET(n,); assert(n->priv->refcount > 0); if (!g_atomic_int_dec_and_test(&n->priv->refcount)) return; g_free(n->appname); g_free(n->summary); g_free(n->body); g_free(n->iconname); 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.fg); g_free(n->colors.bg); g_free(n->colors.frame); g_free(n->stack_tag); g_free(n->desktop_entry); g_hash_table_unref(n->actions); if (n->icon) g_object_unref(n->icon); g_free(n->icon_id); notification_private_free(n->priv); if (n->script_count > 0){ g_free(n->scripts); } g_free(n); } void notification_icon_replace_path(struct notification *n, const char *new_icon) { ASSERT_OR_RET(n,); ASSERT_OR_RET(new_icon,); ASSERT_OR_RET(n->iconname != new_icon,); g_free(n->iconname); n->iconname = g_strdup(new_icon); g_clear_object(&n->icon); g_clear_pointer(&n->icon_id, g_free); n->icon = icon_get_for_name(new_icon, &n->icon_id); } void notification_icon_replace_data(struct notification *n, GVariant *new_icon) { ASSERT_OR_RET(n,); ASSERT_OR_RET(new_icon,); g_clear_object(&n->icon); g_clear_pointer(&n->icon_id, g_free); n->icon = icon_get_for_data(new_icon, &n->icon_id); } /* see notification.h */ 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); } static NotificationPrivate *notification_private_create(void) { NotificationPrivate *priv = g_malloc0(sizeof(NotificationPrivate)); g_atomic_int_set(&priv->refcount, 1); return priv; } /* see notification.h */ struct notification *notification_create(void) { struct notification *n = g_malloc0(sizeof(struct notification)); n->priv = notification_private_create(); /* Unparameterized default values */ n->first_render = true; n->markup = settings.markup; n->format = settings.format; n->timestamp = time_monotonic_now(); n->urgency = URG_NORM; n->timeout = -1; n->transient = false; n->progress = -1; n->script_run = false; n->dbus_valid = false; n->fullscreen = FS_SHOW; n->actions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); n->script_count = 0; return n; } /* see notification.h */ void notification_init(struct notification *n) { /* default to empty string to avoid further NULL faults */ n->appname = n->appname ? n->appname : g_strdup("unknown"); n->summary = n->summary ? n->summary : g_strdup(""); n->body = n->body ? n->body : g_strdup(""); n->category = n->category ? n->category : g_strdup(""); /* sanitize urgency */ if (n->urgency < URG_MIN) n->urgency = URG_LOW; if (n->urgency > URG_MAX) n->urgency = URG_CRIT; /* Timeout processing */ if (n->timeout < 0) n->timeout = settings.timeouts[n->urgency]; /* Icon handling */ if (STR_EMPTY(n->iconname)) g_clear_pointer(&n->iconname, g_free); if (!n->icon && n->iconname) { char *icon = g_strdup(n->iconname); notification_icon_replace_path(n, icon); g_free(icon); } if (!n->icon && !n->iconname) notification_icon_replace_path(n, settings.icons[n->urgency]); /* Color hints */ struct notification_colors defcolors; switch (n->urgency) { case URG_LOW: defcolors = settings.colors_low; break; case URG_NORM: defcolors = settings.colors_norm; break; case URG_CRIT: defcolors = settings.colors_crit; break; default: g_error("Unhandled urgency type: %d", n->urgency); } if (!n->colors.fg) n->colors.fg = g_strdup(defcolors.fg); if (!n->colors.bg) n->colors.bg = g_strdup(defcolors.bg); if (!n->colors.frame) n->colors.frame = g_strdup(defcolors.frame); /* Sanitize misc hints */ if (n->progress < 0) n->progress = -1; /* Process rules */ rule_apply_all(n); /* UPDATE derived fields */ notification_extract_urls(n); notification_format_message(n); } static void notification_format_message(struct notification *n) { g_clear_pointer(&n->msg, g_free); n->msg = string_replace_all("\\n", "\n", g_strdup(n->format)); /* replace all formatter */ for(char *substr = strchr(n->msg, '%'); substr && *substr; 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, MARKUP_NO); break; case 'b': notification_replace_single_field( &n->msg, &substr, n->body, n->markup); break; case 'I': icon_tmp = g_strdup(n->iconname); 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->iconname ? n->iconname : "", 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': LOG_W("format_string has trailing %% character. " "To escape it use %%%%."); substr++; break; default: LOG_W("format_string %%%c is unknown.", 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 (strnlen(n->msg, DUNST_NOTIF_MAX_CHARS + 1) > DUNST_NOTIF_MAX_CHARS) { char * buffer = g_strndup(n->msg, DUNST_NOTIF_MAX_CHARS); g_free(n->msg); n->msg = buffer; } } static void notification_extract_urls(struct notification *n) { g_clear_pointer(&n->urls, g_free); char *urls_in = string_append(g_strdup(n->summary), n->body, " "); char *urls_a = NULL; char *urls_img = NULL; markup_strip_a(&urls_in, &urls_a); markup_strip_img(&urls_in, &urls_img); // remove links and images first to not confuse // plain urls extraction char *urls_text = extract_urls(urls_in); n->urls = string_append(n->urls, urls_a, "\n"); n->urls = string_append(n->urls, urls_img, "\n"); n->urls = string_append(n->urls, urls_text, "\n"); g_free(urls_in); g_free(urls_a); g_free(urls_img); g_free(urls_text); } void notification_update_text_to_render(struct notification *n) { g_clear_pointer(&n->text_to_render, g_free); char *buf = NULL; char *msg = g_strchomp(n->msg); /* print dup_count and msg */ if ((n->dup_count > 0 && !settings.hide_duplicate_count) && (g_hash_table_size(n->actions) || n->urls) && settings.show_indicators) { buf = g_strdup_printf("(%d%s%s) %s", n->dup_count, g_hash_table_size(n->actions) ? "A" : "", n->urls ? "U" : "", msg); } else if ((g_hash_table_size(n->actions) || n->urls) && settings.show_indicators) { buf = g_strdup_printf("(%s%s) %s", g_hash_table_size(n->actions) ? "A" : "", n->urls ? "U" : "", msg); } else if (n->dup_count > 0 && !settings.hide_duplicate_count) { buf = g_strdup_printf("(%d) %s", n->dup_count, msg); } else { buf = g_strdup(msg); } /* print age */ gint64 hours, minutes, seconds; gint64 t_delta = time_monotonic_now() - n->timestamp; if (settings.show_age_threshold >= 0 && t_delta >= settings.show_age_threshold) { hours = t_delta / G_USEC_PER_SEC / 3600; minutes = t_delta / G_USEC_PER_SEC / 60 % 60; seconds = t_delta / G_USEC_PER_SEC % 60; char *new_buf; if (hours > 0) { new_buf = g_strdup_printf("%s (%ldh %ldm %lds old)", buf, hours, minutes, seconds); } else if (minutes > 0) { new_buf = g_strdup_printf("%s (%ldm %lds old)", buf, minutes, seconds); } else { new_buf = g_strdup_printf("%s (%lds old)", buf, seconds); } g_free(buf); buf = new_buf; } n->text_to_render = buf; } /* see notification.h */ void notification_do_action(const struct notification *n) { if (g_hash_table_size(n->actions)) { if (g_hash_table_contains(n->actions, "default")) { signal_action_invoked(n, "default"); return; } if (g_hash_table_size(n->actions) == 1) { GList *keys = g_hash_table_get_keys(n->actions); signal_action_invoked(n, keys->data); g_list_free(keys); return; } context_menu(); } else if (n->urls) { if (strstr(n->urls, "\n")) context_menu(); else open_browser(n->urls); } } void notification_invalidate_actions(struct notification *n) { g_hash_table_remove_all(n->actions); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/notification.h����������������������������������������������������������������������0000664�0000000�0000000�00000016632�13706263046�0016317�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_NOTIFICATION_H #define DUNST_NOTIFICATION_H #include <gdk-pixbuf/gdk-pixbuf.h> #include <glib.h> #include <stdbool.h> #include "markup.h" #define DUNST_NOTIF_MAX_CHARS 50000 enum behavior_fullscreen { FS_NULL, //!< Invalid value FS_DELAY, //!< Delay the notification until leaving fullscreen mode FS_PUSHBACK, //!< When entering fullscreen mode, push the notification back to waiting FS_SHOW, //!< Show the message when in fullscreen mode }; /// Representing the urgencies according to the notification spec enum urgency { URG_NONE = -1, /**< Urgency not set (invalid) */ URG_MIN = 0, /**< Minimum value, useful for boundary checking */ URG_LOW = 0, /**< Low urgency */ URG_NORM = 1, /**< Normal urgency */ URG_CRIT = 2, /**< Critical urgency */ URG_MAX = 2, /**< Maximum value, useful for boundary checking */ }; typedef struct _notification_private NotificationPrivate; struct notification_colors { char *frame; char *bg; char *fg; }; struct notification { NotificationPrivate *priv; int id; char *dbus_client; bool dbus_valid; char *appname; char *summary; char *body; char *category; char *desktop_entry; /**< The desktop entry hint sent via every GApplication */ enum urgency urgency; GdkPixbuf *icon; /**< The raw cached icon data used to draw */ char *icon_id; /**< plain icon information, which acts as the pixbuf's id, which is saved in .icon May be a hash for a raw icon or a name/path for a regular app icon. */ char *iconname; /**< plain icon information (may be a path or just a name) Use this to compare the icon name with rules.*/ gint64 start; /**< begin of current display */ gint64 timestamp; /**< arrival time */ gint64 timeout; /**< time to display */ GHashTable *actions; enum markup_mode markup; const char *format; const char **scripts; int script_count; struct notification_colors colors; char *stack_tag; /**< stack notifications by tag */ /* Hints */ bool transient; /**< timeout albeit user is idle */ int progress; /**< percentage (-1: undefined) */ int history_ignore; /**< push to history or free directly */ int skip_display; /**< insert notification into history, skipping initial waiting and display */ /* internal */ bool redisplayed; /**< has been displayed before? */ bool first_render; /**< markup has been rendered before? */ int dup_count; /**< amount of duplicate notifications stacked onto this */ int displayed_height; enum behavior_fullscreen fullscreen; //!< The instruction what to do with it, when desktop enters fullscreen bool script_run; /**< Has the script been executed already? */ /* derived fields */ char *msg; /**< formatted message */ char *text_to_render; /**< formatted message (with age and action indicators) */ char *urls; /**< urllist delimited by '\\n' */ }; /** * 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) * * The reference counter is set to 1. * * This function is guaranteed to return a valid pointer. * @returns The generated notification */ struct notification *notification_create(void); /** * Retrieve the current reference count of the notification */ gint notification_refcount_get(struct notification *n); /** * Increase the reference counter of the notification. */ void notification_ref(struct notification *n); /** * Sanitize values of notification, apply all matching rules * and generate derived fields. * * @param n: the notification to sanitize */ void notification_init(struct notification *n); /** * Decrease the reference counter of the notification. * * If the reference count drops to 0, the object gets freed. */ void notification_unref(struct notification *n); /** * Helper function to compare two given notifications. */ int notification_cmp(const struct notification *a, const struct notification *b); /** * Wrapper for notification_cmp to match glib's * compare functions signature. */ int notification_cmp_data(const void *va, const void *vb, void *data); bool notification_is_duplicate(const struct notification *a, const struct notification *b); /**Replace the current notification's icon with the icon specified by path. * * Removes the reference for the previous icon automatically and will also free the * iconname field. So passing n->iconname as new_icon is invalid. * * @param n the notification to replace the icon * @param new_icon The path of the new icon. May be an absolute path or an icon name. */ void notification_icon_replace_path(struct notification *n, const char *new_icon); /**Replace the current notification's icon with the raw icon given in the GVariant. * * Removes the reference for the previous icon automatically. * * @param n the notification to replace the icon * @param new_icon The icon's data. Has to be in the format of the notification spec. */ void notification_icon_replace_data(struct notification *n, GVariant *new_icon); /** * Run the script associated with the * given notification. * * If the script of the notification has been executed already and * settings.always_run_script is not set, do nothing. */ void notification_run_script(struct notification *n); /** * print a human readable representation * of the given notification to stdout. */ void notification_print(const struct notification *n); /** * Replace the two chars where **needle points * with a quoted "replacement", according to the markup settings. * * The needle is a double pointer and gets updated upon return * to point to the first char, which occurs after replacement. */ void notification_replace_single_field(char **haystack, char **needle, const char *replacement, enum markup_mode markup_mode); void notification_update_text_to_render(struct notification *n); /** * 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(const struct notification *n); /** * Remove all client action data from the notification. * * This should be called after a notification is closed to avoid showing * actions that will not work anymore since the client has stopped listening * for them. */ void notification_invalidate_actions(struct notification *n); const char *notification_urgency_to_string(const enum urgency urgency); /** * Return the string representation for fullscreen behavior * * @param in the #behavior_fullscreen enum value to represent * @return the string representation for `in` */ const char *enum_to_string_fullscreen(enum behavior_fullscreen in); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ������������������������������������������������������������������������������������������������������dunst-1.5.0/src/option_parser.c���������������������������������������������������������������������0000664�0000000�0000000�00000047511�13706263046�0016510�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "option_parser.h" #include <glib.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "dunst.h" #include "log.h" #include "utils.h" #include "settings.h" struct entry { char *key; char *value; }; struct section { char *name; int entry_count; struct entry *entries; }; static int section_count = 0; static struct section *sections; static struct section *new_section(const char *name); static struct section *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 int cmdline_argc; static char **cmdline_argv; static char *usage_str = NULL; static void cmdline_usage_append(const char *key, const char *type, const char *description); static int cmdline_find_option(const char *key); #define STRING_PARSE_RET(string, value) if (STR_EQ(s, string)) { *ret = value; return true; } bool string_parse_alignment(const char *s, enum alignment *ret) { ASSERT_OR_RET(STR_FULL(s), false); ASSERT_OR_RET(ret, false); STRING_PARSE_RET("left", ALIGN_LEFT); STRING_PARSE_RET("center", ALIGN_CENTER); STRING_PARSE_RET("right", ALIGN_RIGHT); return false; } bool string_parse_ellipsize(const char *s, enum ellipsize *ret) { ASSERT_OR_RET(STR_FULL(s), false); ASSERT_OR_RET(ret, false); STRING_PARSE_RET("start", ELLIPSE_START); STRING_PARSE_RET("middle", ELLIPSE_MIDDLE); STRING_PARSE_RET("end", ELLIPSE_END); return false; } bool string_parse_follow_mode(const char *s, enum follow_mode *ret) { ASSERT_OR_RET(STR_FULL(s), false); ASSERT_OR_RET(ret, false); STRING_PARSE_RET("mouse", FOLLOW_MOUSE); STRING_PARSE_RET("keyboard", FOLLOW_KEYBOARD); STRING_PARSE_RET("none", FOLLOW_NONE); return false; } bool string_parse_fullscreen(const char *s, enum behavior_fullscreen *ret) { ASSERT_OR_RET(STR_FULL(s), false); ASSERT_OR_RET(ret, false); STRING_PARSE_RET("show", FS_SHOW); STRING_PARSE_RET("delay", FS_DELAY); STRING_PARSE_RET("pushback", FS_PUSHBACK); return false; } bool string_parse_icon_position(const char *s, enum icon_position *ret) { ASSERT_OR_RET(STR_FULL(s), false); ASSERT_OR_RET(ret, false); STRING_PARSE_RET("left", ICON_LEFT); STRING_PARSE_RET("right", ICON_RIGHT); STRING_PARSE_RET("off", ICON_OFF); return false; } bool string_parse_vertical_alignment(const char *s, enum vertical_alignment *ret) { ASSERT_OR_RET(STR_FULL(s), false); ASSERT_OR_RET(ret, false); STRING_PARSE_RET("top", VERTICAL_TOP); STRING_PARSE_RET("center", VERTICAL_CENTER); STRING_PARSE_RET("bottom", VERTICAL_BOTTOM); return false; } bool string_parse_markup_mode(const char *s, enum markup_mode *ret) { ASSERT_OR_RET(STR_FULL(s), false); ASSERT_OR_RET(ret, false); STRING_PARSE_RET("strip", MARKUP_STRIP); STRING_PARSE_RET("no", MARKUP_NO); STRING_PARSE_RET("full", MARKUP_FULL); STRING_PARSE_RET("yes", MARKUP_FULL); return false; } bool string_parse_mouse_action(const char *s, enum mouse_action *ret) { ASSERT_OR_RET(STR_FULL(s), false); ASSERT_OR_RET(ret, false); STRING_PARSE_RET("none", MOUSE_NONE); STRING_PARSE_RET("do_action", MOUSE_DO_ACTION); STRING_PARSE_RET("close_current", MOUSE_CLOSE_CURRENT); STRING_PARSE_RET("close_all", MOUSE_CLOSE_ALL); return false; } bool string_parse_mouse_action_list(char **s, enum mouse_action **ret) { ASSERT_OR_RET(s, false); ASSERT_OR_RET(ret, false); int len = 0; while (s[len]) len++; *ret = g_malloc_n((len + 1), sizeof(enum mouse_action)); for (int i = 0; i < len; i++) { if (!string_parse_mouse_action(s[i], *ret + i)) { LOG_W("Unknown mouse action value: '%s'", s[i]); g_free(*ret); return false; } } (*ret)[len] = -1; // sentinel end value return true; } bool string_parse_sepcolor(const char *s, struct separator_color_data *ret) { ASSERT_OR_RET(STR_FULL(s), false); ASSERT_OR_RET(ret, false); STRING_PARSE_RET("auto", (struct separator_color_data){.type = SEP_AUTO}); STRING_PARSE_RET("foreground", (struct separator_color_data){.type = SEP_FOREGROUND}); STRING_PARSE_RET("frame", (struct separator_color_data){.type = SEP_FRAME}); ret->type = SEP_CUSTOM; ret->sep_color = g_strdup(s); return true; } bool string_parse_urgency(const char *s, enum urgency *ret) { ASSERT_OR_RET(STR_FULL(s), false); ASSERT_OR_RET(ret, false); STRING_PARSE_RET("low", URG_LOW); STRING_PARSE_RET("normal", URG_NORM); STRING_PARSE_RET("critical", URG_CRIT); return false; } struct section *new_section(const char *name) { for (int i = 0; i < section_count; i++) { if (STR_EQ(name, sections[i].name)) { DIE("Duplicated section in dunstrc detected."); } } section_count++; sections = g_realloc(sections, sizeof(struct section) * section_count); sections[section_count - 1].name = g_strdup(name); sections[section_count - 1].entries = NULL; sections[section_count - 1].entry_count = 0; return §ions[section_count - 1]; } void free_ini(void) { for (int i = 0; i < section_count; i++) { for (int j = 0; j < sections[i].entry_count; j++) { g_free(sections[i].entries[j].key); g_free(sections[i].entries[j].value); } g_free(sections[i].entries); g_free(sections[i].name); } g_clear_pointer(§ions, g_free); section_count = 0; } struct section *get_section(const char *name) { for (int i = 0; i < section_count; i++) { if (STR_EQ(sections[i].name, name)) return §ions[i]; } return NULL; } void add_entry(const char *section_name, const char *key, const char *value) { struct section *s = get_section(section_name); if (!s) s = new_section(section_name); s->entry_count++; int len = s->entry_count; s->entries = g_realloc(s->entries, sizeof(struct entry) * len); s->entries[s->entry_count - 1].key = g_strdup(key); s->entries[s->entry_count - 1].value = string_strip_quotes(value); } const char *get_value(const char *section, const char *key) { struct section *s = get_section(section); ASSERT_OR_RET(s, NULL); for (int i = 0; i < s->entry_count; i++) { if (STR_EQ(s->entries[i].key, key)) { return s->entries[i].value; } } return NULL; } char *ini_get_path(const char *section, const char *key, const char *def) { return string_to_path(ini_get_string(section, key, def)); } char *ini_get_string(const char *section, const char *key, const char *def) { const char *value = get_value(section, key); if (value) return g_strdup(value); return def ? g_strdup(def) : NULL; } gint64 ini_get_time(const char *section, const char *key, gint64 def) { const char *timestring = get_value(section, key); gint64 val = def; if (timestring) { val = string_to_time(timestring); } return val; } char **ini_get_list(const char *section, const char *key, const char *def) { const char *value = get_value(section, key); if (value) return string_to_array(value); else return string_to_array(def); } int ini_get_int(const char *section, const char *key, int def) { const char *value = get_value(section, key); if (value) return atoi(value); else return def; } double ini_get_double(const char *section, const char *key, double def) { const char *value = get_value(section, key); if (value) return atof(value); else return def; } bool ini_is_set(const char *ini_section, const char *ini_key) { return get_value(ini_section, ini_key) != NULL; } const char *next_section(const char *section) { ASSERT_OR_RET(section_count > 0, NULL); ASSERT_OR_RET(section, sections[0].name); for (int i = 0; i < section_count; i++) { if (STR_EQ(section, sections[i].name)) { if (i + 1 >= section_count) return NULL; else return sections[i + 1].name; } } return NULL; } int ini_get_bool(const char *section, const char *key, int def) { const char *value = get_value(section, key); if (value) { switch (value[0]) { case 'y': case 'Y': case 't': case 'T': case '1': return true; case 'n': case 'N': case 'f': case 'F': case '0': return false; default: return def; } } else { return def; } } int load_ini_file(FILE *fp) { ASSERT_OR_RET(fp, 1); char *line = NULL; size_t line_len = 0; int line_num = 0; char *current_section = NULL; while (getline(&line, &line_len, fp) != -1) { line_num++; char *start = g_strstrip(line); if (*start == ';' || *start == '#' || STR_EMPTY(start)) continue; if (*start == '[') { char *end = strchr(start + 1, ']'); if (!end) { LOG_W("Invalid config file at line %d: Missing ']'.", line_num); continue; } *end = '\0'; g_free(current_section); current_section = (g_strdup(start + 1)); new_section(current_section); continue; } char *equal = strchr(start + 1, '='); if (!equal) { LOG_W("Invalid config file at line %d: Missing '='.", line_num); continue; } *equal = '\0'; char *key = g_strstrip(start); char *value = g_strstrip(equal + 1); char *quote = strchr(value, '"'); char *value_end = NULL; if (quote) { value_end = strchr(quote + 1, '"'); if (!value_end) { LOG_W("Invalid config file at line %d: Missing '\"'.", line_num); continue; } } else { value_end = value; } char *comment = strpbrk(value_end, "#;"); if (comment) *comment = '\0'; value = g_strstrip(value); if (!current_section) { LOG_W("Invalid config file at line %d: Key value pair without a section.", line_num); continue; } add_entry(current_section, key, value); } free(line); g_free(current_section); return 0; } void cmdline_load(int argc, char *argv[]) { cmdline_argc = argc; cmdline_argv = argv; } int cmdline_find_option(const char *key) { ASSERT_OR_RET(key, -1); char *key1 = g_strdup(key); char *key2 = strchr(key1, '/'); if (key2) { *key2 = '\0'; key2++; } /* look for first key */ for (int i = 0; i < cmdline_argc; i++) { if (STR_EQ(key1, cmdline_argv[i])) { g_free(key1); return i; } } /* look for second key if one was specified */ if (key2) { for (int i = 0; i < cmdline_argc; i++) { if (STR_EQ(key2, cmdline_argv[i])) { g_free(key1); return i; } } } g_free(key1); return -1; } static const char *cmdline_get_value(const char *key) { int idx = cmdline_find_option(key); if (idx < 0) { return NULL; } if (idx + 1 >= cmdline_argc) { /* the argument is missing */ LOG_W("%s: Missing argument. Ignoring.", key); return NULL; } return cmdline_argv[idx + 1]; } char *cmdline_get_string(const char *key, const char *def, const char *description) { cmdline_usage_append(key, "string", description); const char *str = cmdline_get_value(key); if (str) return g_strdup(str); if (def) return g_strdup(def); else return NULL; } char *cmdline_get_path(const char *key, const char *def, const char *description) { cmdline_usage_append(key, "string", description); const char *str = cmdline_get_value(key); if (str) return string_to_path(g_strdup(str)); else return string_to_path(g_strdup(def)); } char **cmdline_get_list(const char *key, const char *def, const char *description) { cmdline_usage_append(key, "list", description); const char *str = cmdline_get_value(key); if (str) return string_to_array(str); else return string_to_array(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) return atoi(str); else return def; } double cmdline_get_double(const char *key, double def, const char *description) { cmdline_usage_append(key, "double", description); const char *str = cmdline_get_value(key); if (str) return atof(str); else return def; } int cmdline_get_bool(const char *key, int def, const char *description) { cmdline_usage_append(key, "", description); int idx = cmdline_find_option(key); if (idx > 0) return true; else return def; } bool cmdline_is_set(const char *key) { return cmdline_get_value(key) != NULL; } char *option_get_path(const char *ini_section, const char *ini_key, const char *cmdline_key, const char *def, const char *description) { char *val = NULL; if (cmdline_key) { val = cmdline_get_path(cmdline_key, NULL, description); } if (val) { return val; } else { return ini_get_path(ini_section, ini_key, def); } } char *option_get_string(const char *ini_section, const char *ini_key, const char *cmdline_key, const char *def, const char *description) { char *val = NULL; if (cmdline_key) { val = cmdline_get_string(cmdline_key, NULL, description); } if (val) { return val; } else { return ini_get_string(ini_section, ini_key, def); } } gint64 option_get_time(const char *ini_section, const char *ini_key, const char *cmdline_key, gint64 def, const char *description) { gint64 ini_val = ini_get_time(ini_section, ini_key, def); return cmdline_get_time(cmdline_key, ini_val, description); } char **option_get_list(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_list(cmdline_key, NULL, description); if (val) return val; else return ini_get_list(ini_section, ini_key, def); } int option_get_int(const char *ini_section, const char *ini_key, const char *cmdline_key, int def, const char *description) { /* *str is only used to check wether the cmdline option is actually set. */ const char *str = cmdline_get_value(cmdline_key); /* we call cmdline_get_int even when the option isn't set in order to * add the usage info */ int val = cmdline_get_int(cmdline_key, def, description); if (!str) return ini_get_int(ini_section, ini_key, def); else return val; } double option_get_double(const char *ini_section, const char *ini_key, const char *cmdline_key, double def, const char *description) { const char *str = cmdline_get_value(cmdline_key); double val = cmdline_get_double(cmdline_key, def, description); if (!str) return ini_get_double(ini_section, ini_key, def); else return val; } int option_get_bool(const char *ini_section, const char *ini_key, const char *cmdline_key, int def, const char *description) { int val = false; if (cmdline_key) val = cmdline_get_bool(cmdline_key, false, description); if (cmdline_key && val) { /* this can only be true if the value has been set, * so we can return */ return true; } return ini_get_bool(ini_section, ini_key, def); } void cmdline_usage_append(const char *key, const char *type, const char *description) { char *key_type; if (STR_FULL(type)) key_type = g_strdup_printf("%s (%s)", key, type); else key_type = g_strdup(key); if (!usage_str) { usage_str = g_strdup_printf("%-40s - %s\n", key_type, description); g_free(key_type); return; } char *tmp; tmp = g_strdup_printf("%s%-40s - %s\n", usage_str, key_type, description); g_free(key_type); g_free(usage_str); usage_str = tmp; } const char *cmdline_create_usage(void) { return usage_str; } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/option_parser.h���������������������������������������������������������������������0000664�0000000�0000000�00000010033�13706263046�0016502�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_OPTION_PARSER_H #define DUNST_OPTION_PARSER_H #include <glib.h> #include <stdbool.h> #include <stdio.h> #include "dunst.h" #include "settings.h" bool string_parse_alignment(const char *s, enum alignment *ret); bool string_parse_ellipsize(const char *s, enum ellipsize *ret); bool string_parse_follow_mode(const char *s, enum follow_mode *ret); bool string_parse_fullscreen(const char *s, enum behavior_fullscreen *ret); bool string_parse_icon_position(const char *s, enum icon_position *ret); bool string_parse_vertical_alignment(const char *s, enum vertical_alignment *ret); bool string_parse_markup_mode(const char *s, enum markup_mode *ret); bool string_parse_mouse_action(const char *s, enum mouse_action *ret); bool string_parse_mouse_action_list(char **s, enum mouse_action **ret); bool string_parse_sepcolor(const char *s, struct separator_color_data *ret); bool string_parse_urgency(const char *s, enum urgency *ret); int load_ini_file(FILE *); char *ini_get_path(const char *section, const char *key, const char *def); char *ini_get_string(const char *section, const char *key, const char *def); gint64 ini_get_time(const char *section, const char *key, gint64 def); char **ini_get_list(const char *section, const char *key, const char *def); int ini_get_int(const char *section, const char *key, int def); double ini_get_double(const char *section, const char *key, double def); int ini_get_bool(const char *section, const char *key, int def); bool ini_is_set(const char *ini_section, const char *ini_key); void free_ini(void); void cmdline_load(int argc, char *argv[]); /* for all cmdline_get_* key can be either "-key" or "-key/-longkey" */ char *cmdline_get_string(const char *key, const char *def, const char *description); char *cmdline_get_path(const char *key, const char *def, const char *description); char **cmdline_get_list(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); char **option_get_list(const char *ini_section, const char *ini_key, const char *cmdline_key, const char *def, const char *description); int option_get_int(const char *ini_section, const char *ini_key, const char *cmdline_key, int def, const char *description); double option_get_double(const char *ini_section, const char *ini_key, const char *cmdline_key, double def, const char *description); int option_get_bool(const char *ini_section, const char *ini_key, const char *cmdline_key, int def, const char *description); /* returns the next known section. * if section == NULL returns first section. * returns NULL if no more sections are available */ const char *next_section(const char *section); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/queues.c����������������������������������������������������������������������������0000664�0000000�0000000�00000044503�13706263046�0015131�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ /** * @file src/queues.c * @brief All important functions to handle the notification queues for * history, entrance and currently displayed ones. * * Every method requires to have executed queues_init() at the start. * * A read only representation of the queue with the current notifications * can get acquired by calling queues_get_displayed(). * * When ending the program or resetting the queues, tear down the stack with * queues_teardown(). (And reinit with queues_init() if needed.) */ #include "queues.h" #include <assert.h> #include <glib.h> #include <stdio.h> #include <string.h> #include "dunst.h" #include "log.h" #include "notification.h" #include "settings.h" #include "utils.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 */ int next_notification_id = 1; static bool queues_stack_duplicate(struct notification *n); static bool queues_stack_by_tag(struct notification *n); /* see queues.h */ void queues_init(void) { history = g_queue_new(); displayed = g_queue_new(); waiting = g_queue_new(); } /* see queues.h */ GList *queues_get_displayed(void) { return g_queue_peek_head_link(displayed); } /* see queues.h */ const struct notification *queues_get_head_waiting(void) { if (waiting->length == 0) return NULL; return g_queue_peek_head(waiting); } /* see queues.h */ unsigned int queues_length_waiting(void) { return waiting->length; } /* see queues.h */ unsigned int queues_length_displayed(void) { return displayed->length; } /* see queues.h */ unsigned int queues_length_history(void) { return history->length; } /** * Swap two given queue elements. The element's data has to be a notification. * * @pre { elemA has to be part of queueA. } * @pre { elemB has to be part of queueB. } * * @param queueA The queue, which elemB's data will get inserted * @param elemA The element, which will get removed from queueA * @param queueB The queue, which elemA's data will get inserted * @param elemB The element, which will get removed from queueB */ static void queues_swap_notifications(GQueue *queueA, GList *elemA, GQueue *queueB, GList *elemB) { struct notification *toB = elemA->data; struct notification *toA = elemB->data; g_queue_delete_link(queueA, elemA); g_queue_delete_link(queueB, elemB); if (toA) g_queue_insert_sorted(queueA, toA, notification_cmp_data, NULL); if (toB) g_queue_insert_sorted(queueB, toB, notification_cmp_data, NULL); } /** * Check if a notification is eligible to get shown. * * @param n The notification to check * @param status The current status of dunst * @param shown True if the notification is currently displayed */ static bool queues_notification_is_ready(const struct notification *n, struct dunst_status status, bool shown) { ASSERT_OR_RET(status.running, false); if (status.fullscreen && shown) return n && n->fullscreen != FS_PUSHBACK; else if (status.fullscreen && !shown) return n && n->fullscreen == FS_SHOW; else return true; } /** * Check if a notification has timed out * * @param n the notification to check * @param status the current status of dunst * @retval true: the notification is timed out * @retval false: otherwise */ static bool queues_notification_is_finished(struct notification *n, struct dunst_status status) { assert(n); if (n->skip_display && !n->redisplayed) return true; if (n->timeout == 0) // sticky return false; bool is_idle = status.fullscreen ? false : status.idle; /* don't timeout when user is idle */ if (is_idle && !n->transient) { n->start = time_monotonic_now(); return false; } /* remove old message */ if (time_monotonic_now() - n->start > n->timeout) { return true; } return false; } /* see queues.h */ int queues_notification_insert(struct notification *n) { /* do not display the message, if the message is empty */ if (STR_EMPTY(n->msg)) { if (settings.always_run_script) { notification_run_script(n); } LOG_M("Skipping notification: '%s' '%s'", n->body, n->summary); return 0; } /* Do not insert the message if it's a command */ if (STR_EQ("DUNST_COMMAND_PAUSE", n->summary)) { dunst_status(S_RUNNING, false); return 0; } if (STR_EQ("DUNST_COMMAND_RESUME", n->summary)) { dunst_status(S_RUNNING, true); return 0; } if (STR_EQ("DUNST_COMMAND_TOGGLE", n->summary)) { dunst_status(S_RUNNING, !dunst_status_get().running); return 0; } bool inserted = false; if (n->id != 0) { if (!queues_notification_replace_id(n)) { // Requested id was not valid, but play nice and assign it anyway g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL); } inserted = true; } else { n->id = ++next_notification_id; } if (!inserted && STR_FULL(n->stack_tag) && queues_stack_by_tag(n)) inserted = true; if (!inserted && settings.stack_duplicates && queues_stack_duplicate(n)) inserted = true; if (!inserted) 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 * * @retval true: notification got stacked * @retval false: notification did not get stacked */ static bool queues_stack_duplicate(struct notification *n) { GQueue *allqueues[] = { displayed, waiting }; for (int i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) { for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter; iter = iter->next) { struct 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); if (allqueues[i] == displayed) n->start = time_monotonic_now(); notification_unref(orig); return true; } } } return false; } /** * Replaces the first notification of the same stack_tag * * @retval true: notification got stacked * @retval false: notification did not get stacked */ static bool queues_stack_by_tag(struct notification *new) { GQueue *allqueues[] = { displayed, waiting }; for (int i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) { for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter; iter = iter->next) { struct notification *old = iter->data; if (STR_FULL(old->stack_tag) && STR_EQ(old->stack_tag, new->stack_tag)) { iter->data = new; new->dup_count = old->dup_count; signal_notification_closed(old, 1); if (allqueues[i] == displayed) { new->start = time_monotonic_now(); notification_run_script(new); } notification_unref(old); return true; } } } return false; } /* see queues.h */ bool queues_notification_replace_id(struct notification *new) { GQueue *allqueues[] = { displayed, waiting }; for (int i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) { for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter; iter = iter->next) { struct notification *old = iter->data; if (old->id == new->id) { iter->data = new; new->dup_count = old->dup_count; if (allqueues[i] == displayed) { new->start = time_monotonic_now(); notification_run_script(new); } notification_unref(old); return true; } } } return false; } /* see queues.h */ void queues_notification_close_id(int id, enum reason reason) { struct notification *target = NULL; GQueue *allqueues[] = { displayed, waiting }; for (int i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) { for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter; iter = iter->next) { struct notification *n = iter->data; if (n->id == id) { g_queue_remove(allqueues[i], 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); } } /* see queues.h */ void queues_notification_close(struct notification *n, enum reason reason) { assert(n != NULL); queues_notification_close_id(n->id, reason); } /* see queues.h */ void queues_history_pop(void) { if (g_queue_is_empty(history)) return; struct notification *n = g_queue_pop_tail(history); n->redisplayed = true; n->timeout = settings.sticky_history ? 0 : n->timeout; g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL); } /* see queues.h */ void queues_history_push(struct notification *n) { if (!n->history_ignore) { if (settings.history_length > 0 && history->length >= settings.history_length) { struct notification *to_free = g_queue_pop_head(history); notification_unref(to_free); } g_queue_push_tail(history, n); } else { notification_unref(n); } } /* see queues.h */ 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); } } /* see queues.h */ void queues_update(struct dunst_status status) { GList *iter, *nextiter; /* Move back all notifications, which aren't eligible to get shown anymore * Will move the notifications back to waiting, if dunst isn't running or fullscreen * and notifications is not eligible to get shown anymore */ iter = g_queue_peek_head_link(displayed); while (iter) { struct notification *n = iter->data; nextiter = iter->next; if (queues_notification_is_finished(n, status)){ queues_notification_close(n, REASON_TIME); iter = nextiter; continue; } if (!queues_notification_is_ready(n, status, true)) { g_queue_delete_link(displayed, iter); g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL); iter = nextiter; continue; } iter = nextiter; } int cur_displayed_limit; if (settings.geometry.h == 0) cur_displayed_limit = INT_MAX; else if ( settings.indicate_hidden && settings.geometry.h > 1 && displayed->length + waiting->length > settings.geometry.h) cur_displayed_limit = settings.geometry.h-1; else cur_displayed_limit = settings.geometry.h; /* move notifications from queue to displayed */ iter = g_queue_peek_head_link(waiting); while (displayed->length < cur_displayed_limit && iter) { struct notification *n = iter->data; nextiter = iter->next; ASSERT_OR_RET(n,); if (!queues_notification_is_ready(n, status, false)) { iter = nextiter; continue; } n->start = time_monotonic_now(); notification_run_script(n); if (n->skip_display && !n->redisplayed) { queues_notification_close(n, REASON_USER); } else { g_queue_delete_link(waiting, iter); g_queue_insert_sorted(displayed, n, notification_cmp_data, NULL); } iter = nextiter; } /* if necessary, push the overhanging notifications from displayed to waiting again */ while (displayed->length > cur_displayed_limit) { struct notification *n = g_queue_pop_tail(displayed); g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL); //TODO: actually it should be on the head if unsorted } /* If displayed is actually full, let the more important notifications * from waiting seep into displayed. */ if (settings.sort && displayed->length == cur_displayed_limit) { GList *i_waiting, *i_displayed; while ( (i_waiting = g_queue_peek_head_link(waiting)) && (i_displayed = g_queue_peek_tail_link(displayed))) { while (i_waiting && ! queues_notification_is_ready(i_waiting->data, status, false)) { i_waiting = i_waiting->prev; } if (i_waiting && notification_cmp(i_displayed->data, i_waiting->data) > 0) { struct notification *todisp = i_waiting->data; todisp->start = time_monotonic_now(); notification_run_script(todisp); queues_swap_notifications(displayed, i_displayed, waiting, i_waiting); } else { break; } } } } /* see queues.h */ gint64 queues_get_next_datachange(gint64 time) { gint64 sleep = G_MAXINT64; for (GList *iter = g_queue_peek_head_link(displayed); iter; iter = iter->next) { struct 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; // sleep exactly until the next shift of the second happens if (age > settings.show_age_threshold - S2US(1)) sleep = MIN(sleep, (S2US(1) - (age % S2US(1)))); else sleep = MIN(sleep, settings.show_age_threshold - age); } } return sleep != G_MAXINT64 ? sleep : -1; } /* see queues.h */ struct notification* queues_get_by_id(int id) { assert(id > 0); GQueue *recqueues[] = { displayed, waiting, history }; for (int i = 0; i < sizeof(recqueues)/sizeof(GQueue*); i++) { for (GList *iter = g_queue_peek_head_link(recqueues[i]); iter; iter = iter->next) { struct notification *cur = iter->data; if (cur->id == id) return cur; } } return NULL; } /** * Helper function for queues_teardown() to free a single notification * * @param data The notification to free */ static void teardown_notification(gpointer data) { struct notification *n = data; notification_unref(n); } /* see queues.h */ void queues_teardown(void) { g_queue_free_full(history, teardown_notification); history = NULL; g_queue_free_full(displayed, teardown_notification); displayed = NULL; g_queue_free_full(waiting, teardown_notification); waiting = NULL; } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/queues.h����������������������������������������������������������������������������0000664�0000000�0000000�00000010537�13706263046�0015136�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ /** * @file src/queues.h */ #ifndef DUNST_QUEUE_H #define DUNST_QUEUE_H #include "dbus.h" #include "dunst.h" #include "notification.h" /** * Initialise necessary queues * * @pre Do not call consecutively to avoid memory leaks * or assure to have queues_teardown() executed before */ void queues_init(void); /** * Receive the current list of displayed notifications * * @return read only list of notifications */ GList *queues_get_displayed(void); /** * Get the highest notification in line * * @returns the first notification in waiting * @retval NULL: waiting is empty */ const struct notification *queues_get_head_waiting(void); /** * Returns the current amount of notifications, * which are waiting to get displayed */ unsigned int queues_length_waiting(void); /** * Returns the current amount of notifications, * which are shown in the UI */ unsigned int queues_length_displayed(void); /** * Returns the current amount of notifications, * which are already in history */ unsigned int queues_length_history(void); /** * Insert a fully initialized notification into queues * * Respects stack_duplicates, and notification replacement * * @param n the notification to insert * * - If n->id != 0, n replaces notification n with id n->id * - If n->id == 0, n gets a new id assigned * * @returns The new value of `n->id` * @retval 0: the notification was dismissed and freed */ int queues_notification_insert(struct 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. * * @param new replacement for the old notification * * @retval true: a matching notification has been found and is replaced * @retval false: otherwise */ bool queues_notification_replace_id(struct notification *new); /** * Close the notification that has n->id == id * * Sends a signal and pushes the selected notification automatically to history. * * @param id The id of the notification to close * @param reason The #reason to close * * @post Call wake_up() to synchronize the queues with the UI * (which closes the notification on screen) */ void queues_notification_close_id(int id, enum reason reason); /** * Close the given notification. \see queues_notification_close_id(). * * @param n (transfer full) The notification to close * @param reason The #reason to close * */ void queues_notification_close(struct notification *n, enum reason reason); /** * Pushes 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 * * @param n (transfer full) The notification to push to history */ void queues_history_push(struct notification *n); /** * Push all waiting and displayed notifications to history */ void queues_history_push_all(void); /** * 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() * * @post Call wake_up() to synchronize the queues with the UI * (which closes old and shows new notifications on screen) * * @param status the current status of dunst */ void queues_update(struct dunst_status status); /** * Calculate the distance to the next event, when an element in the * queues changes * * @param time the current time * * @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); /** * Get the notification which has the given id in the displayed and waiting queue or * NULL if not found * * @param the id searched for. * * @return the `id` notification or NULL */ struct notification* queues_get_by_id(int id); /** * Remove all notifications from all list and free the notifications * * @pre At least one time queues_init() called */ void queues_teardown(void); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/rules.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000006655�13706263046�0014762�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "rules.h" #include <fnmatch.h> #include <glib.h> #include "dunst.h" GSList *rules = NULL; /* * Apply rule to notification. */ void rule_apply(struct rule *r, struct notification *n) { if (r->timeout != -1) n->timeout = r->timeout; if (r->urgency != URG_NONE) n->urgency = r->urgency; if (r->fullscreen != FS_NULL) n->fullscreen = r->fullscreen; if (r->history_ignore != -1) n->history_ignore = r->history_ignore; if (r->set_transient != -1) n->transient = r->set_transient; if (r->skip_display != -1) n->skip_display = r->skip_display; if (r->markup != MARKUP_NULL) n->markup = r->markup; if (r->new_icon) notification_icon_replace_path(n, r->new_icon); if (r->fg) { g_free(n->colors.fg); n->colors.fg = g_strdup(r->fg); } if (r->bg) { g_free(n->colors.bg); n->colors.bg = g_strdup(r->bg); } if (r->fc) { g_free(n->colors.frame); n->colors.frame = g_strdup(r->fc); } if (r->format) n->format = r->format; if (r->script){ n->scripts = g_renew(const char*,n->scripts,n->script_count + 1); n->scripts[n->script_count] = r->script; n->script_count++; } if (r->set_stack_tag) { g_free(n->stack_tag); n->stack_tag = g_strdup(r->set_stack_tag); } } /* * Check all rules if they match n and apply. */ void rule_apply_all(struct notification *n) { for (GSList *iter = rules; iter; iter = iter->next) { struct rule *r = iter->data; if (rule_matches_notification(r, n)) { rule_apply(r, n); } } } struct rule *rule_new(void) { struct rule *r = g_malloc0(sizeof(struct rule)); r->msg_urgency = URG_NONE; r->timeout = -1; r->urgency = URG_NONE; r->fullscreen = FS_NULL; r->markup = MARKUP_NULL; r->history_ignore = false; r->match_transient = -1; r->set_transient = -1; r->skip_display = -1; return r; } static inline bool rule_field_matches_string(const char *value, const char *pattern) { return !pattern || (value && !fnmatch(pattern, value, 0)); } /* * Check whether rule should be applied to n. */ bool rule_matches_notification(struct rule *r, struct notification *n) { return (r->msg_urgency == URG_NONE || r->msg_urgency == n->urgency) && (r->match_transient == -1 || (r->match_transient == n->transient)) && rule_field_matches_string(n->appname, r->appname) && rule_field_matches_string(n->desktop_entry, r->desktop_entry) && rule_field_matches_string(n->summary, r->summary) && rule_field_matches_string(n->body, r->body) && rule_field_matches_string(n->iconname, r->icon) && rule_field_matches_string(n->category, r->category) && rule_field_matches_string(n->stack_tag, r->stack_tag); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �����������������������������������������������������������������������������������dunst-1.5.0/src/rules.h�����������������������������������������������������������������������������0000664�0000000�0000000�00000002424�13706263046�0014755�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_RULES_H #define DUNST_RULES_H #include <glib.h> #include <stdbool.h> #include "notification.h" #include "settings.h" struct rule { char *name; /* filters */ char *appname; char *summary; char *body; char *icon; char *category; char *stack_tag; char *desktop_entry; int msg_urgency; /* actions */ gint64 timeout; enum urgency urgency; enum markup_mode markup; int history_ignore; int match_transient; int set_transient; int skip_display; char *new_icon; char *fg; char *bg; char *fc; const char *format; const char *script; enum behavior_fullscreen fullscreen; char *set_stack_tag; }; extern GSList *rules; /** * Allocate a new rule. The rule is fully initialised. * * @returns A new initialised rule. */ struct rule *rule_new(void); void rule_apply(struct rule *r, struct notification *n); void rule_apply_all(struct notification *n); bool rule_matches_notification(struct rule *r, struct notification *n); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/settings.c��������������������������������������������������������������������������0000664�0000000�0000000�00000072402�13706263046�0015461�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "settings.h" #include <glib.h> #include <stdio.h> #include <string.h> #include "dunst.h" #include "log.h" #include "notification.h" #include "option_parser.h" #include "rules.h" #include "utils.h" #include "x11/x.h" #include "../config.h" struct settings settings; static enum urgency ini_get_urgency(const char *section, const char *key, const enum urgency def) { enum urgency ret; char *c = ini_get_string(section, key, NULL); if (!string_parse_urgency(c, &ret)) { if (c) LOG_W("Unknown urgency: '%s'", c); ret = def; } g_free(c); return ret; } static FILE *xdg_config(const char *filename) { const gchar * const * systemdirs = g_get_system_config_dirs(); const gchar * userdir = g_get_user_config_dir(); FILE *f; char *path; path = g_strconcat(userdir, filename, NULL); f = fopen(path, "r"); g_free(path); for (const gchar * const *d = systemdirs; !f && *d; d++) { path = g_strconcat(*d, filename, NULL); f = fopen(path, "r"); g_free(path); } return f; } void load_settings(char *cmdline_config_path) { #ifndef STATIC_CONFIG FILE *config_file = NULL; if (cmdline_config_path) { if (STR_EQ(cmdline_config_path, "-")) { config_file = stdin; } else { config_file = fopen(cmdline_config_path, "r"); } if (!config_file) { DIE("Cannot find config file: '%s'", cmdline_config_path); } } if (!config_file) { config_file = xdg_config("/dunst/dunstrc"); } if (!config_file) { /* Fall back to just "dunstrc", which was used before 2013-06-23 * (before v0.2). */ config_file = xdg_config("/dunstrc"); } if (!config_file) { LOG_W("No dunstrc found."); } load_ini_file(config_file); #else LOG_M("dunstrc parsing disabled. " "Using STATIC_CONFIG is deprecated behavior."); #endif { char *loglevel = option_get_string( "global", "verbosity", "-verbosity", NULL, "The verbosity to log (one of 'crit', 'warn', 'mesg', 'info', 'debug')" ); log_set_level_from_string(loglevel); g_free(loglevel); } settings.per_monitor_dpi = option_get_bool( "experimental", "per_monitor_dpi", NULL, false, "" ); settings.force_xinerama = option_get_bool( "global", "force_xinerama", "-force_xinerama", false, "Force the use of the Xinerama extension" ); settings.font = option_get_string( "global", "font", "-font/-fn", defaults.font, "The font dunst should use." ); { // Check if allow_markup set if (ini_is_set("global", "allow_markup")) { bool allow_markup = option_get_bool( "global", "allow_markup", NULL, false, "Allow markup in notifications" ); settings.markup = (allow_markup ? MARKUP_FULL : MARKUP_STRIP); LOG_M("'allow_markup' is deprecated, please " "use 'markup' instead."); } char *c = option_get_string( "global", "markup", "-markup", NULL, "Specify how markup should be handled" ); if (!string_parse_markup_mode(c, &settings.markup)) { if (c) LOG_W("Cannot parse markup mode value: '%s'", c); if (!settings.markup) settings.markup = defaults.markup; } g_free(c); } settings.format = option_get_string( "global", "format", "-format", defaults.format, "The format template for the notifications" ); settings.sort = option_get_bool( "global", "sort", "-sort", defaults.sort, "Sort notifications by urgency and date?" ); settings.indicate_hidden = option_get_bool( "global", "indicate_hidden", "-indicate_hidden", defaults.indicate_hidden, "Show how many notifications are hidden" ); settings.word_wrap = option_get_bool( "global", "word_wrap", "-word_wrap", defaults.word_wrap, "Truncating long lines or do word wrap" ); settings.ignore_dbusclose = option_get_bool( "global", "ignore_dbusclose", "-ignore_dbusclose", defaults.ignore_dbusclose, "Ignore dbus CloseNotification events" ); { char *c = option_get_string( "global", "ellipsize", "-ellipsize", NULL, "Ellipsize truncated lines on the start/middle/end" ); if (!string_parse_ellipsize(c, &settings.ellipsize)) { if (c) LOG_W("Unknown ellipsize value: '%s'", c); settings.ellipsize = defaults.ellipsize; } g_free(c); } settings.ignore_newline = option_get_bool( "global", "ignore_newline", "-ignore_newline", defaults.ignore_newline, "Ignore newline characters in notifications" ); settings.idle_threshold = option_get_time( "global", "idle_threshold", "-idle_threshold", defaults.idle_threshold, "Don't timeout notifications if user is longer idle than threshold" ); settings.monitor = option_get_int( "global", "monitor", "-mon/-monitor", defaults.monitor, "On which monitor should the notifications be displayed" ); { char *c = option_get_string( "global", "follow", "-follow", NULL, "Follow mouse, keyboard or none?" ); if (!string_parse_follow_mode(c, &settings.f_mode)) { if (c) LOG_W("Cannot parse follow mode: %s", c); settings.f_mode = defaults.f_mode; } g_free(c); } settings.title = option_get_string( "global", "title", "-t/-title", defaults.title, "Define the title of windows spawned by dunst." ); settings.class = option_get_string( "global", "class", "-c/-class", defaults.class, "Define the class of windows spawned by dunst." ); { char *c = option_get_string( "global", "geometry", "-geom/-geometry", NULL, "Geometry for the window" ); if (c) { // TODO: Implement own geometry parsing to get rid of // the include dependency on X11 settings.geometry = x_parse_geometry(c); g_free(c); } else { settings.geometry = defaults.geometry; } } settings.shrink = option_get_bool( "global", "shrink", "-shrink", defaults.shrink, "Shrink window if it's smaller than the width" ); settings.line_height = option_get_int( "global", "line_height", "-lh/-line_height", defaults.line_height, "Add spacing between lines of text" ); settings.notification_height = option_get_int( "global", "notification_height", "-nh/-notification_height", defaults.notification_height, "Define height of the window" ); { char *c = option_get_string( "global", "alignment", "-align/-alignment", NULL, "Text alignment left/center/right" ); if (!string_parse_alignment(c, &settings.align)) { if (c) LOG_W("Unknown alignment value: '%s'", c); settings.align = defaults.align; } g_free(c); } settings.show_age_threshold = option_get_time( "global", "show_age_threshold", "-show_age_threshold", defaults.show_age_threshold, "When should the age of the notification be displayed?" ); settings.hide_duplicate_count = option_get_bool( "global", "hide_duplicate_count", "-hide_duplicate_count", false, "Hide the count of stacked notifications with the same content" ); settings.sticky_history = option_get_bool( "global", "sticky_history", "-sticky_history", defaults.sticky_history, "Don't timeout notifications popped up from history" ); settings.history_length = option_get_int( "global", "history_length", "-history_length", defaults.history_length, "Max amount of notifications kept in history" ); settings.show_indicators = option_get_bool( "global", "show_indicators", "-show_indicators", defaults.show_indicators, "Show indicators for actions \"(A)\" and URLs \"(U)\"" ); settings.separator_height = option_get_int( "global", "separator_height", "-sep_height/-separator_height", defaults.separator_height, "height of the separator line" ); settings.padding = option_get_int( "global", "padding", "-padding", defaults.padding, "Padding between text and separator" ); settings.h_padding = option_get_int( "global", "horizontal_padding", "-horizontal_padding", defaults.h_padding, "horizontal padding" ); settings.transparency = option_get_int( "global", "transparency", "-transparency", defaults.transparency, "Transparency. Range 0-100" ); settings.corner_radius = option_get_int( "global", "corner_radius", "-corner_radius", defaults.corner_radius, "Window corner radius" ); { char *c = option_get_string( "global", "separator_color", "-sep_color/-separator_color", "", "Color of the separator line (or 'auto')" ); if (!string_parse_sepcolor(c, &settings.sep_color)) { settings.sep_color = defaults.sep_color; } g_free(c); } settings.stack_duplicates = option_get_bool( "global", "stack_duplicates", "-stack_duplicates", true, "Stack together notifications with the same content" ); settings.startup_notification = option_get_bool( "global", "startup_notification", "-startup_notification", false, "print notification on startup" ); settings.dmenu = option_get_path( "global", "dmenu", "-dmenu", defaults.dmenu, "path to dmenu" ); { GError *error = NULL; if (!g_shell_parse_argv(settings.dmenu, NULL, &settings.dmenu_cmd, &error)) { LOG_W("Unable to parse dmenu command: '%s'." "dmenu functionality will be disabled.", error->message); g_error_free(error); settings.dmenu_cmd = NULL; } } settings.browser = option_get_path( "global", "browser", "-browser", defaults.browser, "path to browser" ); { GError *error = NULL; if (!g_shell_parse_argv(settings.browser, NULL, &settings.browser_cmd, &error)) { LOG_W("Unable to parse browser command: '%s'." " URL functionality will be disabled.", error->message); g_error_free(error); settings.browser_cmd = NULL; } } { char *c = option_get_string( "global", "icon_position", "-icon_position", "off", "Align icons left/right/off" ); if (!string_parse_icon_position(c, &settings.icon_position)) { if (c) LOG_W("Unknown icon position: '%s'", c); settings.icon_position = defaults.icon_position; } g_free(c); } { char *c = option_get_string( "global", "vertical_alignment", "-vertical_alignment", "center", "Align icon and text top/center/bottom" ); if (!string_parse_vertical_alignment(c, &settings.vertical_alignment)) { if (c) LOG_W("Unknown vertical alignment: '%s'", c); settings.vertical_alignment = defaults.vertical_alignment; } g_free(c); } settings.min_icon_size = option_get_int( "global", "min_icon_size", "-min_icon_size", defaults.min_icon_size, "Scale smaller icons up to this size, set to 0 to disable. If max_icon_size also specified, that has the final say." ); settings.max_icon_size = option_get_int( "global", "max_icon_size", "-max_icon_size", defaults.max_icon_size, "Scale larger icons down to this size, set to 0 to disable" ); // restrict the icon size to a reasonable limit if we have a fixed width. // Otherwise the layout will be broken by too large icons. // See https://github.com/dunst-project/dunst/issues/540 if (settings.geometry.width_set && settings.geometry.w != 0) { const int icon_size_limit = settings.geometry.w / 2; if ( settings.max_icon_size == 0 || settings.max_icon_size > icon_size_limit) { if (settings.max_icon_size != 0) { LOG_W("Max width was set to %d but got a max_icon_size of %d, too large to use. Setting max_icon_size=%d", settings.geometry.w, settings.max_icon_size, icon_size_limit); } else { LOG_I("Max width was set but max_icon_size is unlimited. Limiting icons to %d pixels", icon_size_limit); } settings.max_icon_size = icon_size_limit; } } // 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)" ); LOG_M("The option 'icon_folders' is deprecated, please use 'icon_path' instead."); } // Read value and generate usage string for icon_path. // If icon_path is set, override icon_folder. // if not, but icon_folder is set, use that instead of the compile time default. settings.icon_path = option_get_string( "global", "icon_path", "-icon_path", settings.icon_path ? settings.icon_path : defaults.icon_path, "paths to default icons" ); { // Backwards compatibility with the legacy 'frame' section. if (ini_is_set("frame", "width")) { settings.frame_width = option_get_int( "frame", "width", NULL, defaults.frame_width, "Width of frame around the window" ); LOG_M("The frame section is deprecated, width has " "been renamed to frame_width and moved to " "the global section."); } settings.frame_width = option_get_int( "global", "frame_width", "-frame_width", settings.frame_width ? settings.frame_width : defaults.frame_width, "Width of frame around the window" ); if (ini_is_set("frame", "color")) { settings.frame_color = option_get_string( "frame", "color", NULL, defaults.frame_color, "Color of the frame around the window" ); LOG_M("The frame section is deprecated, color " "has been renamed to frame_color and moved " "to the global section."); } 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" ); } { char **c = option_get_list( "global", "mouse_left_click", "-mouse_left_click", NULL, "Action of Left click event" ); if (!string_parse_mouse_action_list(c, &settings.mouse_left_click)) { settings.mouse_left_click = defaults.mouse_left_click; } free_string_array(c); } { char **c = option_get_list( "global", "mouse_middle_click", "-mouse_middle_click", NULL, "Action of middle click event" ); if (!string_parse_mouse_action_list(c, &settings.mouse_middle_click)) { settings.mouse_middle_click = defaults.mouse_middle_click; } free_string_array(c); } { char **c = option_get_list( "global", "mouse_right_click", "-mouse_right_click", NULL, "Action of right click event" ); if (!string_parse_mouse_action_list(c, &settings.mouse_right_click)) { settings.mouse_right_click = defaults.mouse_right_click; } free_string_array(c); } settings.colors_low.bg = option_get_string( "urgency_low", "background", "-lb", defaults.colors_low.bg, "Background color for notifications with low urgency" ); settings.colors_low.fg = option_get_string( "urgency_low", "foreground", "-lf", defaults.colors_low.fg, "Foreground color for notifications with low urgency" ); settings.colors_low.frame = option_get_string( "urgency_low", "frame_color", "-lfr", settings.frame_color ? settings.frame_color : defaults.colors_low.frame, "Frame color for notifications with low urgency" ); settings.timeouts[URG_LOW] = option_get_time( "urgency_low", "timeout", "-lto", defaults.timeouts[URG_LOW], "Timeout for notifications with low urgency" ); settings.icons[URG_LOW] = option_get_string( "urgency_low", "icon", "-li", defaults.icons[URG_LOW], "Icon for notifications with low urgency" ); settings.colors_norm.bg = option_get_string( "urgency_normal", "background", "-nb", defaults.colors_norm.bg, "Background color for notifications with normal urgency" ); settings.colors_norm.fg = option_get_string( "urgency_normal", "foreground", "-nf", defaults.colors_norm.fg, "Foreground color for notifications with normal urgency" ); settings.colors_norm.frame = option_get_string( "urgency_normal", "frame_color", "-nfr", settings.frame_color ? settings.frame_color : defaults.colors_norm.frame, "Frame color for notifications with normal urgency" ); settings.timeouts[URG_NORM] = option_get_time( "urgency_normal", "timeout", "-nto", defaults.timeouts[URG_NORM], "Timeout for notifications with normal urgency" ); settings.icons[URG_NORM] = option_get_string( "urgency_normal", "icon", "-ni", defaults.icons[URG_NORM], "Icon for notifications with normal urgency" ); settings.colors_crit.bg = option_get_string( "urgency_critical", "background", "-cb", defaults.colors_crit.bg, "Background color for notifications with critical urgency" ); settings.colors_crit.fg = option_get_string( "urgency_critical", "foreground", "-cf", defaults.colors_crit.fg, "Foreground color for notifications with ciritical urgency" ); settings.colors_crit.frame = option_get_string( "urgency_critical", "frame_color", "-cfr", settings.frame_color ? settings.frame_color : defaults.colors_crit.frame, "Frame color for notifications with critical urgency" ); settings.timeouts[URG_CRIT] = option_get_time( "urgency_critical", "timeout", "-cto", defaults.timeouts[URG_CRIT], "Timeout for notifications with critical urgency" ); settings.icons[URG_CRIT] = option_get_string( "urgency_critical", "icon", "-ci", defaults.icons[URG_CRIT], "Icon for notifications with critical urgency" ); settings.close_ks.str = option_get_string( "shortcuts", "close", "-key", defaults.close_ks.str, "Shortcut for closing one notification" ); settings.close_all_ks.str = option_get_string( "shortcuts", "close_all", "-all_key", defaults.close_all_ks.str, "Shortcut for closing all notifications" ); settings.history_ks.str = option_get_string( "shortcuts", "history", "-history_key", defaults.history_ks.str, "Shortcut to pop the last notification from history" ); settings.context_ks.str = option_get_string( "shortcuts", "context", "-context_key", defaults.context_ks.str, "Shortcut for context menu" ); settings.print_notifications = cmdline_get_bool( "-print", false, "Print notifications to cmdline (DEBUG)" ); settings.always_run_script = option_get_bool( "global", "always_run_script", "-always_run_script", true, "Always run rule-defined scripts, even if the notification is suppressed with format = \"\"." ); /* push hardcoded default rules into rules list */ for (int i = 0; i < G_N_ELEMENTS(default_rules); i++) { rules = g_slist_insert(rules, &(default_rules[i]), -1); } const char *cur_section = NULL; for (;;) { cur_section = next_section(cur_section); if (!cur_section) break; if (STR_EQ(cur_section, "global") || STR_EQ(cur_section, "frame") || STR_EQ(cur_section, "experimental") || STR_EQ(cur_section, "shortcuts") || STR_EQ(cur_section, "urgency_low") || STR_EQ(cur_section, "urgency_normal") || STR_EQ(cur_section, "urgency_critical")) continue; /* check for existing rule with same name */ struct rule *r = NULL; for (GSList *iter = rules; iter; iter = iter->next) { struct rule *match = iter->data; if (match->name && STR_EQ(match->name, cur_section)) r = match; } if (!r) { r = rule_new(); rules = g_slist_insert(rules, r, -1); } r->name = g_strdup(cur_section); r->appname = ini_get_string(cur_section, "appname", r->appname); r->summary = ini_get_string(cur_section, "summary", r->summary); r->body = ini_get_string(cur_section, "body", r->body); r->icon = ini_get_string(cur_section, "icon", r->icon); r->category = ini_get_string(cur_section, "category", r->category); r->stack_tag = ini_get_string(cur_section, "stack_tag", r->stack_tag); r->timeout = ini_get_time(cur_section, "timeout", r->timeout); { char *c = ini_get_string( cur_section, "markup", NULL ); if (!string_parse_markup_mode(c, &r->markup)) { if (c) LOG_W("Invalid markup mode value: %s", c); } g_free(c); } r->urgency = ini_get_urgency(cur_section, "urgency", r->urgency); r->msg_urgency = ini_get_urgency(cur_section, "msg_urgency", r->msg_urgency); r->fg = ini_get_string(cur_section, "foreground", r->fg); r->bg = ini_get_string(cur_section, "background", r->bg); r->fc = ini_get_string(cur_section, "frame_color", r->fc); r->format = ini_get_string(cur_section, "format", r->format); r->new_icon = ini_get_string(cur_section, "new_icon", r->new_icon); r->history_ignore = ini_get_bool(cur_section, "history_ignore", r->history_ignore); r->match_transient = ini_get_bool(cur_section, "match_transient", r->match_transient); r->set_transient = ini_get_bool(cur_section, "set_transient", r->set_transient); r->desktop_entry = ini_get_string(cur_section, "desktop_entry", r->desktop_entry); r->skip_display = ini_get_bool(cur_section, "skip_display", r->skip_display); { char *c = ini_get_string( cur_section, "fullscreen", NULL ); if (!string_parse_fullscreen(c, &r->fullscreen)) { if (c) LOG_W("Invalid fullscreen value: %s", c); } g_free(c); } r->script = ini_get_path(cur_section, "script", NULL); r->set_stack_tag = ini_get_string(cur_section, "set_stack_tag", r->set_stack_tag); } #ifndef STATIC_CONFIG if (config_file) { fclose(config_file); free_ini(); } #endif } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/settings.h��������������������������������������������������������������������������0000664�0000000�0000000�00000005753�13706263046�0015473�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_SETTINGS_H #define DUNST_SETTINGS_H #include <stdbool.h> #include "markup.h" #include "notification.h" #include "x11/x.h" enum alignment { ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT }; enum ellipsize { ELLIPSE_START, ELLIPSE_MIDDLE, ELLIPSE_END }; enum icon_position { ICON_LEFT, ICON_RIGHT, ICON_OFF }; enum vertical_alignment { VERTICAL_TOP, VERTICAL_CENTER, VERTICAL_BOTTOM }; enum separator_color { SEP_FOREGROUND, SEP_AUTO, SEP_FRAME, SEP_CUSTOM }; enum follow_mode { FOLLOW_NONE, FOLLOW_MOUSE, FOLLOW_KEYBOARD }; enum mouse_action { MOUSE_NONE, MOUSE_DO_ACTION, MOUSE_CLOSE_CURRENT, MOUSE_CLOSE_ALL }; struct separator_color_data { enum separator_color type; char *sep_color; }; struct geometry { int x; int y; unsigned int w; unsigned int h; bool negative_x; bool negative_y; bool negative_width; bool width_set; }; struct settings { bool print_notifications; bool per_monitor_dpi; enum markup_mode markup; bool stack_duplicates; bool hide_duplicate_count; char *font; struct notification_colors colors_low; struct notification_colors colors_norm; struct notification_colors colors_crit; char *format; gint64 timeouts[3]; char *icons[3]; unsigned int transparency; struct geometry geometry; char *title; char *class; int shrink; int sort; int indicate_hidden; gint64 idle_threshold; gint64 show_age_threshold; enum alignment align; int sticky_history; int history_length; int show_indicators; int word_wrap; int ignore_dbusclose; enum ellipsize ellipsize; int ignore_newline; int line_height; int notification_height; int separator_height; int padding; int h_padding; struct separator_color_data sep_color; int frame_width; char *frame_color; int startup_notification; int monitor; char *dmenu; char **dmenu_cmd; char *browser; char **browser_cmd; enum icon_position icon_position; enum vertical_alignment vertical_alignment; int min_icon_size; int max_icon_size; char *icon_path; enum follow_mode f_mode; bool always_run_script; struct keyboard_shortcut close_ks; struct keyboard_shortcut close_all_ks; struct keyboard_shortcut history_ks; struct keyboard_shortcut context_ks; bool force_xinerama; int corner_radius; enum mouse_action *mouse_left_click; enum mouse_action *mouse_middle_click; enum mouse_action *mouse_right_click; }; extern struct settings settings; void load_settings(char *cmdline_config_path); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ���������������������dunst-1.5.0/src/utils.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000014262�13706263046�0014761�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "utils.h" #include <assert.h> #include <ctype.h> #include <errno.h> #include <glib.h> #include <pwd.h> #include <stdio.h> #include <stdlib.h> #include <time.h> #include <unistd.h> #include "log.h" /* see utils.h */ void free_string_array(char **arr) { if (arr){ for (int i = 0; arr[i]; i++){ g_free(arr[i]); } } g_free(arr); } /* see utils.h */ char *string_replace_char(char needle, char replacement, char *haystack) { ASSERT_OR_RET(haystack, NULL); char *current = haystack; while ((current = strchr(current, needle))) *current++ = replacement; return haystack; } /* see utils.h */ char *string_replace_at(char *buf, int pos, int len, const char *repl) { assert(buf); assert(repl); char *tmp; int size, buf_len, repl_len; buf_len = strlen(buf); repl_len = strlen(repl); size = (buf_len - len) + repl_len + 1; if (repl_len <= len) { tmp = buf; } else { tmp = g_malloc(size); memcpy(tmp, buf, pos); } memcpy(tmp + pos, repl, repl_len); memmove(tmp + pos + repl_len, buf + pos + len, buf_len - (pos + len) + 1); if (tmp != buf) { g_free(buf); } return tmp; } /* see utils.h */ char *string_replace_all(const char *needle, const char *replacement, char *haystack) { ASSERT_OR_RET(haystack, NULL); assert(needle); assert(replacement); char *start; int needle_pos; int needle_len, repl_len; needle_len = strlen(needle); if (needle_len == 0) { return haystack; } start = strstr(haystack, needle); repl_len = strlen(replacement); while (start) { needle_pos = start - haystack; haystack = string_replace_at(haystack, needle_pos, needle_len, replacement); start = strstr(haystack + needle_pos + repl_len, needle); } return haystack; } /* see utils.h */ char *string_append(char *a, const char *b, const char *sep) { if (STR_EMPTY(a)) { g_free(a); return g_strdup(b); } if (STR_EMPTY(b)) return a; char *new; if (!sep) new = g_strconcat(a, b, NULL); else new = g_strconcat(a, sep, b, NULL); g_free(a); return new; } /* see utils.h */ char *string_strip_quotes(const char *value) { ASSERT_OR_RET(value, NULL); size_t len = strlen(value); char *s; if (value[0] == '"' && value[len-1] == '"') s = g_strndup(value + 1, len-2); else s = g_strdup(value); return s; } /* see utils.h */ void string_strip_delimited(char *str, char a, char b) { assert(str); int iread=-1, iwrite=0, copen=0; while (str[++iread] != 0) { if (str[iread] == a) { ++copen; } else if (str[iread] == b && copen > 0) { --copen; } else if (copen == 0) { str[iwrite++] = str[iread]; } } str[iwrite] = 0; } /* see utils.h */ char **string_to_array(const char *string) { char **arr = NULL; if (string) { arr = g_strsplit(string, ",", 0); for (int i = 0; arr[i]; i++){ g_strstrip(arr[i]); } } return arr; } /* see utils.h */ char *string_to_path(char *string) { if (string && STRN_EQ(string, "~/", 2)) { char *home = g_strconcat(user_get_home(), "/", NULL); string = string_replace_at(string, 0, 2, home); g_free(home); } return string; } /* see utils.h */ gint64 string_to_time(const char *string) { assert(string); errno = 0; char *endptr; gint64 val = strtoll(string, &endptr, 10); if (errno != 0) { LOG_W("Time: '%s': %s.", string, strerror(errno)); return 0; } else if (string == endptr) { LOG_W("Time: '%s': No digits found.", string); return 0; } else if (errno != 0 && val == 0) { LOG_W("Time: '%s': Unknown error.", string); return 0; } else if (errno == 0 && !*endptr) { return S2US(val); } // endptr may point to a separating space while (isspace(*endptr)) endptr++; if (STRN_EQ(endptr, "ms", 2)) return val * 1000; else if (STRN_EQ(endptr, "s", 1)) return S2US(val); else if (STRN_EQ(endptr, "m", 1)) return S2US(val) * 60; else if (STRN_EQ(endptr, "h", 1)) return S2US(val) * 60 * 60; else if (STRN_EQ(endptr, "d", 1)) return S2US(val) * 60 * 60 * 24; else return 0; } /* see utils.h */ gint64 time_monotonic_now(void) { struct timespec tv_now; /* On Linux, BOOTTIME is the correct monotonic time, * as BOOTTIME counts onwards during sleep. For all other * POSIX compliant OSes, MONOTONIC should also count onwards * during system sleep. */ #ifdef __linux__ clock_gettime(CLOCK_BOOTTIME, &tv_now); #else clock_gettime(CLOCK_MONOTONIC, &tv_now); #endif return S2US(tv_now.tv_sec) + tv_now.tv_nsec / 1000; } /* see utils.h */ const char *user_get_home(void) { static const char *home_directory = NULL; ASSERT_OR_RET(!home_directory, home_directory); // Check the HOME variable for the user's home home_directory = getenv("HOME"); ASSERT_OR_RET(!home_directory, home_directory); // Check the /etc/passwd entry for the user's home home_directory = getpwuid(getuid())->pw_dir; return home_directory; } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/utils.h�����������������������������������������������������������������������������0000664�0000000�0000000�00000010627�13706263046�0014767�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_UTILS_H #define DUNST_UTILS_H #include <glib.h> #include <string.h> //! Test if a string is NULL or empty #define STR_EMPTY(s) (!s || (*s == '\0')) //! Test if a string is non-NULL and not empty #define STR_FULL(s) !(STR_EMPTY(s)) //! Test if string a and b contain the same chars #define STR_EQ(a, b) (g_strcmp0(a, b) == 0) //! Test if string a and b are same up to n chars #define STRN_EQ(a, b, n) (strncmp(a, b, n) == 0) //! Test if string a and b are the same case-insensitively #define STR_CASEQ(a, b) (strcasecmp(a, b) == 0) //! Assert that expr evaluates to true, if not return with val #define ASSERT_OR_RET(expr, val) if (!(expr)) return val; //! Convert a second into the internal time representation #define S2US(s) (((gint64)(s)) * 1000 * 1000) /** * Frees an array of strings whose last element is a NULL pointer. * * Assumes the last element is a NULL pointer, otherwise will result in a buffer overflow. * @param arr The array of strings to free */ void free_string_array(char **arr); /** * Replaces all occurrences of the char \p needle with the char \p replacement in \p haystack. * * Does not allocate a new string. * * @param needle The char to replace with replacement * @param replacement The char which is the new one * @param haystack (nullable) The string to replace * * @returns The exact value of the haystack paramater (to allow nesting) */ char *string_replace_char(char needle, char replacement, char *haystack); /** * Replace a substring inside a string with another string. * * May reallocate memory. Free the result with `g_free`. * * @param buf The string to replace * @param pos The position of the substring to replace * @param len The length of the substring to replace * @param repl The new contents of the substring. */ char *string_replace_at(char *buf, int pos, int len, const char *repl); /** * Replace all occurences of a substring. * * @param needle The substring to search * @param replacement The substring to replace * @param haystack (nullable) The string to search the substring for */ char *string_replace_all(const char *needle, const char *replacement, char *haystack); /** * Append \p b to string \p a. And concatenate both strings with \p concat, if both are non-empty. * * @param a (nullable) The left side of the string * @param b (nullable) The right side of the string * @param sep (nullable) The concatenator to concatenate if a and b given */ char *string_append(char *a, const char *b, const char *sep); /** * Strip quotes from a string. Won't touch inner quotes. * * @param value The string to strip the quotes from * @returns A copy of the string value with the outer quotes removed (if any) */ char *string_strip_quotes(const char *value); /** * Strip content between two delimiter characters * * @param str The string to operate in place * @param a Starting delimiter * @param b Ending delimiter */ void string_strip_delimited(char *str, char a, char b); /** * Parse a comma-delimited string into a dynamic array of tokens * * The string is split on commas and strips spaces from tokens. The last element * of the array is NULL in order to avoid passing around a length variable. * * @param string The string to convert to an array * @returns The array of tokens. */ char **string_to_array(const char *string); /** * Replace tilde and path-specific values with its equivalents * * The string gets invalidated. The new valid representation is the return value. * * @param string (nullable) The string to convert to a path. * @returns The tilde-replaced string. */ char *string_to_path(char *string); /** * Convert time units (ms, s, m) to the internal `gint64` microseconds format * * If no unit is given, 's' (seconds) is assumed by default. * * @param string The string to parse the time format from. */ gint64 string_to_time(const char *string); /** * Get the current monotonic time. In contrast to `g_get_monotonic_time`, * this function respects the real monotonic time of the system and * counts onwards during system sleep. * * @returns: A `gint64` monotonic time representation */ gint64 time_monotonic_now(void); /** * Retrieve the HOME directory of the user running dunst * * @returns: A string of the current home directory */ const char *user_get_home(void); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ���������������������������������������������������������������������������������������������������������dunst-1.5.0/src/x11/��������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13706263046�0014061�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/x11/screen.c������������������������������������������������������������������������0000664�0000000�0000000�00000031651�13706263046�0015512�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "screen.h" #include <assert.h> #include <glib.h> #include <locale.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <X11/extensions/randr.h> #include <X11/extensions/Xinerama.h> #include <X11/extensions/Xrandr.h> #include <X11/Xatom.h> #include <X11/X.h> #include <X11/Xlib.h> #include <X11/Xresource.h> #include "../log.h" #include "../settings.h" #include "../utils.h" #include "x.h" struct screen_info *screens; int screens_len; bool dunst_follow_errored = false; static int randr_major_version = 0; static int randr_minor_version = 0; void randr_init(void); void randr_update(void); void xinerama_update(void); void screen_update_fallback(void); static void x_follow_setup_error_handler(void); static int x_follow_tear_down_error_handler(void); static int FollowXErrorHandler(Display *display, XErrorEvent *e); static Window get_focused_window(void); /** * A cache variable to cache the Xft.dpi xrdb values. * We do not expect to change xrdb often, but there's much * overhead to query it once. * * @retval -DBL_MAX: uncached * @retval <=0: Invalid and unusable value * @retval >0: valid */ double screen_dpi_xft_cache = -DBL_MAX; void screen_dpi_xft_cache_purge(void) { screen_dpi_xft_cache = -DBL_MAX; } static double screen_dpi_get_from_xft(void) { if (screen_dpi_xft_cache == -DBL_MAX) { screen_dpi_xft_cache = 0; char *xrmType; XrmValue xrmValue; XrmDatabase db = XrmGetDatabase(xctx.dpy); ASSERT_OR_RET(db, screen_dpi_xft_cache); if (XrmGetResource(db, "Xft.dpi", "Xft.dpi", &xrmType, &xrmValue)) screen_dpi_xft_cache = strtod(xrmValue.addr, NULL); } return screen_dpi_xft_cache; } static double screen_dpi_get_from_monitor(struct screen_info *scr) { return (double)scr->h * 25.4 / (double)scr->mmh; } double screen_dpi_get(struct screen_info *scr) { if ( ! settings.force_xinerama && settings.per_monitor_dpi) return screen_dpi_get_from_monitor(scr); if (screen_dpi_get_from_xft() > 0) return screen_dpi_get_from_xft(); // Calculate the DPI on the overall screen size. // xrandr --dpi <DPI> does only change the overall screen's millimeters, // but not the physical screen's sizes. // // The screen parameter is XDefaultScreen(), as our scr->id references // the xrandr monitor and not the xrandr screen return ((((double)DisplayWidth (xctx.dpy, XDefaultScreen(xctx.dpy))) * 25.4) / ((double)DisplayWidthMM(xctx.dpy, XDefaultScreen(xctx.dpy)))); } void init_screens(void) { if (settings.force_xinerama) { xinerama_update(); } else { randr_init(); randr_update(); } } void alloc_screen_ar(int n) { assert(n > 0); g_free(screens); screens = g_malloc0(n * sizeof(struct screen_info)); screens_len = n; } void randr_init(void) { int ignored; if (!XRRQueryExtension(xctx.dpy, &ignored, &ignored)) { LOG_W("Could not initialize the RandR extension. " "Falling back to single monitor mode."); return; } XRRQueryVersion(xctx.dpy, &randr_major_version, &randr_minor_version); XRRSelectInput(xctx.dpy, RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)), RRScreenChangeNotifyMask); } void randr_update(void) { if (randr_major_version < 1 || (randr_major_version == 1 && randr_minor_version < 5)) { LOG_W("Server RandR version too low (%i.%i). " "Falling back to single monitor mode.", randr_major_version, randr_minor_version); screen_update_fallback(); return; } int n = 0; XRRMonitorInfo *m = XRRGetMonitors(xctx.dpy, RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)), true, &n); if (n < 1) { LOG_C("Get monitors reported %i monitors. " "Falling back to single monitor mode.", n); screen_update_fallback(); return; } assert(m); alloc_screen_ar(n); for (int i = 0; i < n; i++) { screens[i].id = i; screens[i].x = m[i].x; screens[i].y = m[i].y; screens[i].w = m[i].width; screens[i].h = m[i].height; screens[i].mmh = m[i].mheight; } XRRFreeMonitors(m); } bool screen_check_event(XEvent *ev) { if (XRRUpdateConfiguration(ev)) { LOG_D("XEvent: processing 'RRScreenChangeNotify'"); randr_update(); return true; } return false; } void xinerama_update(void) { int n; XineramaScreenInfo *info = XineramaQueryScreens(xctx.dpy, &n); if (!info) { LOG_W("Could not get xinerama screen info. " "Falling back to single monitor mode."); screen_update_fallback(); return; } alloc_screen_ar(n); for (int i = 0; i < n; i++) { screens[i].id = i; screens[i].x = info[i].x_org; screens[i].y = info[i].y_org; screens[i].h = info[i].height; screens[i].w = info[i].width; } XFree(info); } void screen_update_fallback(void) { alloc_screen_ar(1); int screen; if (settings.monitor >= 0) screen = settings.monitor; else screen = DefaultScreen(xctx.dpy); screens[0].w = DisplayWidth(xctx.dpy, screen); screens[0].h = DisplayHeight(xctx.dpy, screen); } /* see screen.h */ bool have_fullscreen_window(void) { return window_is_fullscreen(get_focused_window()); } /** * X11 ErrorHandler to mainly discard BadWindow parameter error */ static int XErrorHandlerFullscreen(Display *display, XErrorEvent *e) { /* Ignore BadWindow errors. Window may have been gone */ if (e->error_code == BadWindow) { return 0; } char err_buf[BUFSIZ]; XGetErrorText(display, e->error_code, err_buf, BUFSIZ); fputs(err_buf, stderr); fputs("\n", stderr); return 0; } /* see screen.h */ bool window_is_fullscreen(Window window) { bool fs = false; ASSERT_OR_RET(window, false); Atom has_wm_state = XInternAtom(xctx.dpy, "_NET_WM_STATE", True); if (has_wm_state == None){ return false; } XFlush(xctx.dpy); XSetErrorHandler(XErrorHandlerFullscreen); Atom actual_type_return; int actual_format_return; unsigned long bytes_after_return; unsigned char *prop_to_return; unsigned long n_items; int result = XGetWindowProperty( xctx.dpy, window, has_wm_state, 0, /* long_offset */ sizeof(window), /* long_length */ false, /* delete */ AnyPropertyType, /* req_type */ &actual_type_return, &actual_format_return, &n_items, &bytes_after_return, &prop_to_return); XFlush(xctx.dpy); XSync(xctx.dpy, false); XSetErrorHandler(NULL); if (result == Success) { for(int i = 0; i < n_items; i++) { Atom atom = ((Atom*) prop_to_return)[i]; if (!atom) continue; char *s = XGetAtomName(xctx.dpy, atom); if (!s) continue; if (STR_EQ(s, "_NET_WM_STATE_FULLSCREEN")) fs = true; XFree(s); if (fs) break; } } if (prop_to_return) XFree(prop_to_return); return fs; } /* * Select the screen on which the Window * should be displayed. */ struct screen_info *get_active_screen(void) { int ret = 0; bool force_follow_mouse = false; if (settings.monitor > 0 && settings.monitor < screens_len) { ret = settings.monitor; goto sc_cleanup; } x_follow_setup_error_handler(); if (settings.f_mode == FOLLOW_NONE) { ret = XDefaultScreen(xctx.dpy); goto sc_cleanup; } else { int x, y; assert(settings.f_mode == FOLLOW_MOUSE || settings.f_mode == FOLLOW_KEYBOARD); Window root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)); if (settings.f_mode == FOLLOW_KEYBOARD) { Window focused = get_focused_window(); if (focused == 0) { /* * This can happen in the case that the user * just has the root window open, eg. in empty * tags in dwm or similar window managers. In * that case, fall back to FOLLOW_MOUSE, since * it probably still has the right screen. */ force_follow_mouse = true; } else { Window child_return; XTranslateCoordinates(xctx.dpy, focused, root, 0, 0, &x, &y, &child_return); } } if (settings.f_mode == FOLLOW_MOUSE || force_follow_mouse) { int dummy; unsigned int dummy_ui; Window dummy_win; XQueryPointer(xctx.dpy, root, &dummy_win, &dummy_win, &x, &y, &dummy, &dummy, &dummy_ui); } for (int i = 0; i < screens_len; i++) { if (INRECT(x, y, screens[i].x, screens[i].y, screens[i].w, screens[i].h)) { ret = i; } } if (ret > 0) goto sc_cleanup; /* something seems to be wrong. Fall back to default */ ret = XDefaultScreen(xctx.dpy); goto sc_cleanup; } sc_cleanup: x_follow_tear_down_error_handler(); assert(screens); assert(ret >= 0 && ret < screens_len); return &screens[ret]; } /* * Return the window that currently has * the keyboard focus. */ static Window get_focused_window(void) { Window focused = 0; Atom type; int format; unsigned long nitems, bytes_after; unsigned char *prop_return = NULL; Window root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)); Atom netactivewindow = XInternAtom(xctx.dpy, "_NET_ACTIVE_WINDOW", false); XGetWindowProperty(xctx.dpy, root, netactivewindow, 0L, sizeof(Window), false, XA_WINDOW, &type, &format, &nitems, &bytes_after, &prop_return); if (prop_return) { focused = *(Window *)prop_return; XFree(prop_return); } return focused; } static void x_follow_setup_error_handler(void) { dunst_follow_errored = false; XFlush(xctx.dpy); XSetErrorHandler(FollowXErrorHandler); } static int x_follow_tear_down_error_handler(void) { XFlush(xctx.dpy); XSync(xctx.dpy, false); XSetErrorHandler(NULL); return dunst_follow_errored; } static int FollowXErrorHandler(Display *display, XErrorEvent *e) { dunst_follow_errored = true; char err_buf[BUFSIZ]; XGetErrorText(display, e->error_code, err_buf, BUFSIZ); LOG_W("%s", err_buf); return 0; } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ���������������������������������������������������������������������������������������dunst-1.5.0/src/x11/screen.h������������������������������������������������������������������������0000664�0000000�0000000�00000002227�13706263046�0015514�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_SCREEN_H #define DUNST_SCREEN_H #include <stdbool.h> #include <X11/Xlib.h> #define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh)) struct screen_info { int id; int x; int y; unsigned int h; unsigned int mmh; unsigned int w; }; void init_screens(void); void screen_dpi_xft_cache_purge(void); bool screen_check_event(XEvent *ev); struct screen_info *get_active_screen(void); double screen_dpi_get(struct screen_info *scr); /** * Find the currently focused window and check if it's in * fullscreen mode * * @see window_is_fullscreen() * @see get_focused_window() * * @retval true: the focused window is in fullscreen mode * @retval false: otherwise */ bool have_fullscreen_window(void); /** * Check if window is in fullscreen mode * * @param window the x11 window object * @retval true: \p window is in fullscreen mode * @retval false: otherwise */ bool window_is_fullscreen(Window window); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/x11/x.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000073173�13706263046�0014507�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #include "x.h" #include <assert.h> #include <cairo.h> #include <cairo-xlib.h> #include <glib-object.h> #include <locale.h> #include <math.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <X11/extensions/shape.h> #include <X11/Xatom.h> #include <X11/X.h> #include <X11/XKBlib.h> #include <X11/Xlib.h> #include <X11/Xresource.h> #include <X11/Xutil.h> #include "../dbus.h" #include "../draw.h" #include "../dunst.h" #include "../log.h" #include "../markup.h" #include "../menu.h" #include "../notification.h" #include "../queues.h" #include "../settings.h" #include "../utils.h" #include "screen.h" #define WIDTH 400 #define HEIGHT 400 struct window_x11 { Window xwin; cairo_surface_t *root_surface; cairo_t *c_ctx; GSource *esrc; int cur_screen; bool visible; struct dimensions dim; }; struct x11_source { GSource source; struct window_x11 *win; }; struct x_context xctx; bool dunst_grab_errored = false; static bool fullscreen_last = false; static void XRM_update_db(void); static void x_shortcut_init(struct keyboard_shortcut *ks); static int x_shortcut_grab(struct keyboard_shortcut *ks); static void x_shortcut_ungrab(struct keyboard_shortcut *ks); /* FIXME refactor setup teardown handlers into one setup and one teardown */ static void x_shortcut_setup_error_handler(void); static int x_shortcut_tear_down_error_handler(void); static void setopacity(Window win, unsigned long opacity); static void x_handle_click(XEvent ev); static void x_win_move(struct window_x11 *win, int x, int y, int width, int height) { /* move and resize */ if (x != win->dim.x || y != win->dim.y) { XMoveWindow(xctx.dpy, win->xwin, x, y); win->dim.x = x; win->dim.y = y; } if (width != win->dim.w || height != win->dim.h) { XResizeWindow(xctx.dpy, win->xwin, width, height); win->dim.h = height; win->dim.w = width; } } static void x_win_corners_shape(struct window_x11 *win, const int rad) { const int width = win->dim.w; const int height = win->dim.h; Pixmap mask; cairo_surface_t * cxbm; cairo_t * cr; Screen * scr; mask = XCreatePixmap(xctx.dpy, win->xwin, width, height, 1); scr = ScreenOfDisplay(xctx.dpy, win->cur_screen); cxbm = cairo_xlib_surface_create_for_bitmap(xctx.dpy, mask, scr, width, height); cr = cairo_create(cxbm); cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 0, 0, 0, 0); cairo_paint(cr); cairo_set_source_rgba(cr, 1, 1, 1, 1); draw_rounded_rect(cr, 0, 0, width, height, rad, true, true); cairo_fill(cr); cairo_show_page(cr); cairo_destroy(cr); cairo_surface_flush(cxbm); cairo_surface_destroy(cxbm); XShapeCombineMask(xctx.dpy, win->xwin, ShapeBounding, 0, 0, mask, ShapeSet); XFreePixmap(xctx.dpy, mask); XShapeSelectInput(xctx.dpy, win->xwin, ShapeNotifyMask); } static void x_win_corners_unshape(struct window_x11 *win) { XRectangle rect = { .x = 0, .y = 0, .width = win->dim.w, .height = win->dim.h }; XShapeCombineRectangles(xctx.dpy, win->xwin, ShapeBounding, 0, 0, &rect, 1, ShapeSet, 1); XShapeSelectInput(xctx.dpy, win->xwin, ShapeNotifyMask); } static bool x_win_composited(struct window_x11 *win) { char astr[sizeof("_NET_WM_CM_S") / sizeof(char) + 8]; Atom cm_sel; sprintf(astr, "_NET_WM_CM_S%i", win->cur_screen); cm_sel = XInternAtom(xctx.dpy, astr, true); if (cm_sel == None) { return false; } else { return XGetSelectionOwner(xctx.dpy, cm_sel) != None; } } void x_display_surface(cairo_surface_t *srf, struct window_x11 *win, const struct dimensions *dim) { x_win_move(win, dim->x, dim->y, dim->w, dim->h); cairo_xlib_surface_set_size(win->root_surface, dim->w, dim->h); XClearWindow(xctx.dpy, win->xwin); cairo_set_source_surface(win->c_ctx, srf, 0, 0); cairo_paint(win->c_ctx); cairo_show_page(win->c_ctx); if (settings.corner_radius != 0 && ! x_win_composited(win)) x_win_corners_shape(win, dim->corner_radius); else x_win_corners_unshape(win); XFlush(xctx.dpy); } bool x_win_visible(struct window_x11 *win) { return win->visible; } cairo_t* x_win_get_context(struct window_x11 *win) { return win->c_ctx; } static void setopacity(Window win, unsigned long opacity) { Atom _NET_WM_WINDOW_OPACITY = XInternAtom(xctx.dpy, "_NET_WM_WINDOW_OPACITY", false); XChangeProperty(xctx.dpy, win, _NET_WM_WINDOW_OPACITY, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&opacity, 1L); } /* * Returns the modifier which is NumLock. */ static KeySym x_numlock_mod(void) { static KeyCode nl = 0; KeySym sym = 0; XModifierKeymap *map = XGetModifierMapping(xctx.dpy); if (!nl) nl = XKeysymToKeycode(xctx.dpy, XStringToKeysym("Num_Lock")); for (int mod = 0; mod < 8; mod++) { for (int j = 0; j < map->max_keypermod; j++) { if (map->modifiermap[mod*map->max_keypermod+j] == nl) { /* In theory, one could use `1 << mod`, but this * could count as 'using implementation details', * so use this large switch. */ switch (mod) { case ShiftMapIndex: sym = ShiftMask; goto end; case LockMapIndex: sym = LockMask; goto end; case ControlMapIndex: sym = ControlMask; goto end; case Mod1MapIndex: sym = Mod1Mask; goto end; case Mod2MapIndex: sym = Mod2Mask; goto end; case Mod3MapIndex: sym = Mod3Mask; goto end; case Mod4MapIndex: sym = Mod4Mask; goto end; case Mod5MapIndex: sym = Mod5Mask; goto end; } } } } end: XFreeModifiermap(map); return sym; } /* * Helper function to use glib's mainloop mechanic * with Xlib */ gboolean x_mainloop_fd_prepare(GSource *source, gint *timeout) { if (timeout) *timeout = -1; return false; } /* * Helper function to use glib's mainloop mechanic * with Xlib */ gboolean x_mainloop_fd_check(GSource *source) { return XPending(xctx.dpy) > 0; } /* * Main Dispatcher for XEvents */ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { struct window_x11 *win = ((struct x11_source*) source)->win; bool fullscreen_now; struct screen_info *scr; XEvent ev; unsigned int state; while (XPending(xctx.dpy) > 0) { XNextEvent(xctx.dpy, &ev); switch (ev.type) { case Expose: LOG_D("XEvent: processing 'Expose'"); if (ev.xexpose.count == 0 && win->visible) { draw(); } break; case ButtonRelease: LOG_D("XEvent: processing 'ButtonRelease'"); if (ev.xbutton.window == win->xwin) { x_handle_click(ev); wake_up(); } break; case KeyPress: LOG_D("XEvent: processing 'KeyPress'"); state = ev.xkey.state; /* NumLock is also encoded in the state. Remove it. */ state &= ~x_numlock_mod(); if (settings.close_ks.str && XLookupKeysym(&ev.xkey, 0) == settings.close_ks.sym && settings.close_ks.mask == state) { const GList *displayed = queues_get_displayed(); if (displayed && displayed->data) { queues_notification_close(displayed->data, REASON_USER); wake_up(); } } if (settings.history_ks.str && XLookupKeysym(&ev.xkey, 0) == settings.history_ks.sym && settings.history_ks.mask == state) { queues_history_pop(); wake_up(); } if (settings.close_all_ks.str && XLookupKeysym(&ev.xkey, 0) == settings.close_all_ks.sym && settings.close_all_ks.mask == state) { queues_history_push_all(); wake_up(); } if (settings.context_ks.str && XLookupKeysym(&ev.xkey, 0) == settings.context_ks.sym && settings.context_ks.mask == state) { context_menu(); wake_up(); } break; case CreateNotify: LOG_D("XEvent: processing 'CreateNotify'"); if (win->visible && ev.xcreatewindow.override_redirect == 0) XRaiseWindow(xctx.dpy, win->xwin); break; case PropertyNotify: if (ev.xproperty.atom == XA_RESOURCE_MANAGER) { LOG_D("XEvent: processing PropertyNotify for Resource manager"); XRM_update_db(); screen_dpi_xft_cache_purge(); if (win->visible) { draw(); } break; } /* Explicitly fallthrough. Other PropertyNotify events, e.g. catching * _NET_WM get handled in the Focus(In|Out) section */ case ConfigureNotify: case FocusIn: case FocusOut: LOG_D("XEvent: Checking for active screen changes"); fullscreen_now = have_fullscreen_window(); scr = get_active_screen(); if (fullscreen_now != fullscreen_last) { fullscreen_last = fullscreen_now; wake_up(); } else if ( settings.f_mode != FOLLOW_NONE /* Ignore PropertyNotify, when we're still on the * same screen. PropertyNotify is only necessary * to detect a focus change to another screen */ && win->visible && scr->id != win->cur_screen) { draw(); win->cur_screen = scr->id; } break; default: if (!screen_check_event(&ev)) { LOG_D("XEvent: Ignoring '%d'", ev.type); } break; } } return G_SOURCE_CONTINUE; } /* * Check whether the user is currently idle. */ bool x_is_idle(void) { XScreenSaverQueryInfo(xctx.dpy, DefaultRootWindow(xctx.dpy), xctx.screensaver_info); if (settings.idle_threshold == 0) { return false; } return xctx.screensaver_info->idle > settings.idle_threshold / 1000; } /* TODO move to x_mainloop_* */ /* * Handle incoming mouse click events */ static void x_handle_click(XEvent ev) { enum mouse_action *acts; switch (ev.xbutton.button) { case Button1: acts = settings.mouse_left_click; break; case Button2: acts = settings.mouse_middle_click; break; case Button3: acts = settings.mouse_right_click; break; default: LOG_W("Unsupported mouse button: '%d'", ev.xbutton.button); return; } for (int i = 0; acts[i]; i++) { enum mouse_action act = acts[i]; if (act == MOUSE_CLOSE_ALL) { queues_history_push_all(); return; } if (act == MOUSE_DO_ACTION || act == MOUSE_CLOSE_CURRENT) { int y = settings.separator_height; struct notification *n = NULL; int first = true; for (const GList *iter = queues_get_displayed(); iter; iter = iter->next) { n = iter->data; if (ev.xbutton.y > y && ev.xbutton.y < y + n->displayed_height) break; y += n->displayed_height + settings.separator_height; if (first) y += settings.frame_width; } if (n) { if (act == MOUSE_CLOSE_CURRENT) queues_notification_close(n, REASON_USER); else notification_do_action(n); } } } } void x_free(void) { if (xctx.screensaver_info) XFree(xctx.screensaver_info); if (xctx.dpy) XCloseDisplay(xctx.dpy); } static int XErrorHandlerDB(Display *display, XErrorEvent *e) { char err_buf[BUFSIZ]; XGetErrorText(display, e->error_code, err_buf, BUFSIZ); LOG_W("%s", err_buf); return 0; } static void XRM_update_db(void) { XrmDatabase db; XTextProperty prop; Window root; // We shouldn't destroy the first DB coming // from the display object itself static bool runonce = false; XFlush(xctx.dpy); XSetErrorHandler(XErrorHandlerDB); root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)); XLockDisplay(xctx.dpy); if (XGetTextProperty(xctx.dpy, root, &prop, XA_RESOURCE_MANAGER)) { if (runonce) { db = XrmGetDatabase(xctx.dpy); XrmDestroyDatabase(db); } db = XrmGetStringDatabase((const char*)prop.value); XrmSetDatabase(xctx.dpy, db); } XUnlockDisplay(xctx.dpy); runonce = true; XFlush(xctx.dpy); XSync(xctx.dpy, false); XSetErrorHandler(NULL); } /* * Setup X11 stuff */ void x_setup(void) { /* initialize xctx.dc, font, keyboard, colors */ if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) LOG_W("No locale support"); if (!(xctx.dpy = XOpenDisplay(NULL))) { DIE("Cannot open X11 display."); } x_shortcut_init(&settings.close_ks); x_shortcut_init(&settings.close_all_ks); x_shortcut_init(&settings.history_ks); x_shortcut_init(&settings.context_ks); x_shortcut_grab(&settings.close_ks); x_shortcut_ungrab(&settings.close_ks); x_shortcut_grab(&settings.close_all_ks); x_shortcut_ungrab(&settings.close_all_ks); x_shortcut_grab(&settings.history_ks); x_shortcut_ungrab(&settings.history_ks); x_shortcut_grab(&settings.context_ks); x_shortcut_ungrab(&settings.context_ks); xctx.screensaver_info = XScreenSaverAllocInfo(); init_screens(); x_shortcut_grab(&settings.history_ks); XrmInitialize(); } struct geometry x_parse_geometry(const char *geom_str) { assert(geom_str); struct geometry geometry = { 0 }; if (geom_str[0] == '-') { geometry.negative_width = true; geom_str++; } else { geometry.negative_width = false; } int mask = XParseGeometry(geom_str, &geometry.x, &geometry.y, &geometry.w, &geometry.h); geometry.width_set = mask & WidthValue; geometry.negative_x = mask & XNegative; geometry.negative_y = mask & YNegative; return geometry; } static void x_set_wm(Window win) { Atom data[2]; /* set window title */ char *title = settings.title != NULL ? settings.title : "Dunst"; Atom _net_wm_title = XInternAtom(xctx.dpy, "_NET_WM_NAME", false); XStoreName(xctx.dpy, win, title); XChangeProperty(xctx.dpy, win, _net_wm_title, XInternAtom(xctx.dpy, "UTF8_STRING", false), 8, PropModeReplace, (unsigned char *)title, strlen(title)); /* set window class */ char *class = settings.class != NULL ? settings.class : "Dunst"; XClassHint classhint = { class, "Dunst" }; XSetClassHint(xctx.dpy, win, &classhint); /* set window type */ Atom net_wm_window_type = XInternAtom(xctx.dpy, "_NET_WM_WINDOW_TYPE", false); data[0] = XInternAtom(xctx.dpy, "_NET_WM_WINDOW_TYPE_NOTIFICATION", false); data[1] = XInternAtom(xctx.dpy, "_NET_WM_WINDOW_TYPE_UTILITY", false); XChangeProperty(xctx.dpy, win, net_wm_window_type, XA_ATOM, 32, PropModeReplace, (unsigned char *)data, 2L); /* set state above */ Atom net_wm_state = XInternAtom(xctx.dpy, "_NET_WM_STATE", false); data[0] = XInternAtom(xctx.dpy, "_NET_WM_STATE_ABOVE", false); XChangeProperty(xctx.dpy, win, net_wm_state, XA_ATOM, 32, PropModeReplace, (unsigned char *) data, 1L); } GSource* x_win_reg_source(struct window_x11 *win) { // Static is necessary here because glib keeps the pointer and we need // to keep the reference alive. static GSourceFuncs xsrc_fn = { x_mainloop_fd_prepare, x_mainloop_fd_check, x_mainloop_fd_dispatch, NULL, NULL, NULL }; struct x11_source *xsrc = (struct x11_source*) g_source_new(&xsrc_fn, sizeof(struct x11_source)); xsrc->win = win; g_source_add_unix_fd((GSource*) xsrc, xctx.dpy->fd, G_IO_IN | G_IO_HUP | G_IO_ERR); g_source_attach((GSource*) xsrc, NULL); return (GSource*)xsrc; } /* * Setup the window */ struct window_x11 *x_win_create(void) { struct window_x11 *win = g_malloc0(sizeof(struct window_x11)); Window root; int scr_n; int depth; Visual * vis; XVisualInfo vi; XSetWindowAttributes wa; scr_n = DefaultScreen(xctx.dpy); root = RootWindow(xctx.dpy, scr_n); if (XMatchVisualInfo(xctx.dpy, scr_n, 32, TrueColor, &vi)) { vis = vi.visual; depth = vi.depth; } else { vis = DefaultVisual(xctx.dpy, scr_n); depth = DefaultDepth(xctx.dpy, scr_n); } wa.override_redirect = true; wa.background_pixmap = None; wa.background_pixel = 0; wa.border_pixel = 0; wa.colormap = XCreateColormap(xctx.dpy, root, vis, AllocNone); wa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask | ButtonReleaseMask | FocusChangeMask| StructureNotifyMask; struct screen_info *scr = get_active_screen(); win->xwin = XCreateWindow(xctx.dpy, root, scr->x, scr->y, scr->w, 1, 0, depth, CopyFromParent, vis, CWOverrideRedirect | CWBackPixmap | CWBackPixel | CWBorderPixel | CWColormap | CWEventMask, &wa); x_set_wm(win->xwin); settings.transparency = settings.transparency > 100 ? 100 : settings.transparency; setopacity(win->xwin, (unsigned long)((100 - settings.transparency) * (0xffffffff / 100))); win->root_surface = cairo_xlib_surface_create(xctx.dpy, win->xwin, vis, WIDTH, HEIGHT); win->c_ctx = cairo_create(win->root_surface); win->esrc = x_win_reg_source(win); /* SubstructureNotifyMask is required for receiving CreateNotify events * in order to raise the window when something covers us. See #160 * * PropertyChangeMask is requred for getting screen change events when follow_mode != none * and it's also needed to receive * XA_RESOURCE_MANAGER events to update the dpi when * the xresource value is updated */ long root_event_mask = SubstructureNotifyMask | PropertyChangeMask; if (settings.f_mode != FOLLOW_NONE) { root_event_mask |= FocusChangeMask; } XSelectInput(xctx.dpy, root, root_event_mask); return win; } void x_win_destroy(struct window_x11 *win) { g_source_destroy(win->esrc); g_source_unref(win->esrc); cairo_destroy(win->c_ctx); cairo_surface_destroy(win->root_surface); XDestroyWindow(xctx.dpy, win->xwin); g_free(win); } /* * Show the window and grab shortcuts. */ void x_win_show(struct window_x11 *win) { /* window is already mapped or there's nothing to show */ if (win->visible) return; x_shortcut_grab(&settings.close_ks); x_shortcut_grab(&settings.close_all_ks); x_shortcut_grab(&settings.context_ks); x_shortcut_setup_error_handler(); XGrabButton(xctx.dpy, AnyButton, AnyModifier, win->xwin, false, (ButtonPressMask|ButtonReleaseMask), GrabModeAsync, GrabModeSync, None, None); if (x_shortcut_tear_down_error_handler()) { LOG_W("Unable to grab mouse button(s)."); } XMapRaised(xctx.dpy, win->xwin); win->visible = true; } /* * Hide the window and ungrab unused keyboard_shortcuts */ void x_win_hide(struct window_x11 *win) { ASSERT_OR_RET(win->visible,); x_shortcut_ungrab(&settings.close_ks); x_shortcut_ungrab(&settings.close_all_ks); x_shortcut_ungrab(&settings.context_ks); XUngrabButton(xctx.dpy, AnyButton, AnyModifier, win->xwin); XUnmapWindow(xctx.dpy, win->xwin); XFlush(xctx.dpy); win->visible = false; } /* * Parse a string into a modifier mask. */ KeySym x_shortcut_string_to_mask(const char *str) { if (STR_EQ(str, "ctrl")) { return ControlMask; } else if (STR_EQ(str, "mod4")) { return Mod4Mask; } else if (STR_EQ(str, "mod3")) { return Mod3Mask; } else if (STR_EQ(str, "mod2")) { return Mod2Mask; } else if (STR_EQ(str, "mod1")) { return Mod1Mask; } else if (STR_EQ(str, "shift")) { return ShiftMask; } else { LOG_W("Unknown Modifier: '%s'", str); return 0; } } /* * Error handler for grabbing mouse and keyboard errors. */ static int GrabXErrorHandler(Display *display, XErrorEvent *e) { dunst_grab_errored = true; char err_buf[BUFSIZ]; XGetErrorText(display, e->error_code, err_buf, BUFSIZ); if (e->error_code != BadAccess) { DIE("%s", err_buf); } else { LOG_W("%s", err_buf); } return 0; } /* * Setup the Error handler. */ static void x_shortcut_setup_error_handler(void) { dunst_grab_errored = false; XFlush(xctx.dpy); XSetErrorHandler(GrabXErrorHandler); } /* * Tear down the Error handler. */ static int x_shortcut_tear_down_error_handler(void) { XFlush(xctx.dpy); XSync(xctx.dpy, false); XSetErrorHandler(NULL); return dunst_grab_errored; } /* * Grab the given keyboard shortcut. */ static int x_shortcut_grab(struct keyboard_shortcut *ks) { ASSERT_OR_RET(ks->is_valid, 1); Window root; root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)); x_shortcut_setup_error_handler(); if (ks->is_valid) { XGrabKey(xctx.dpy, ks->code, ks->mask, root, true, GrabModeAsync, GrabModeAsync); XGrabKey(xctx.dpy, ks->code, ks->mask | x_numlock_mod(), root, true, GrabModeAsync, GrabModeAsync); } if (x_shortcut_tear_down_error_handler()) { LOG_W("Unable to grab key '%s'.", ks->str); ks->is_valid = false; return 1; } return 0; } /* * Ungrab the given keyboard shortcut. */ static void x_shortcut_ungrab(struct keyboard_shortcut *ks) { Window root; root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)); if (ks->is_valid) { XUngrabKey(xctx.dpy, ks->code, ks->mask, root); XUngrabKey(xctx.dpy, ks->code, ks->mask | x_numlock_mod(), root); } } /* * Initialize the keyboard shortcut. */ static void x_shortcut_init(struct keyboard_shortcut *ks) { ASSERT_OR_RET(ks && ks->str,); if (STR_EQ(ks->str, "none") || (STR_EQ(ks->str, ""))) { ks->is_valid = false; return; } char *str = g_strdup(ks->str); char *str_begin = str; while (strchr(str, '+')) { char *mod = str; while (*str != '+') str++; *str = '\0'; str++; g_strchomp(mod); ks->mask = ks->mask | x_shortcut_string_to_mask(mod); } g_strstrip(str); ks->sym = XStringToKeysym(str); /* find matching keycode for ks->sym */ int min_keysym, max_keysym; XDisplayKeycodes(xctx.dpy, &min_keysym, &max_keysym); ks->code = NoSymbol; for (int i = min_keysym; i <= max_keysym; i++) { if (XkbKeycodeToKeysym(xctx.dpy, i, 0, 0) == ks->sym || XkbKeycodeToKeysym(xctx.dpy, i, 0, 1) == ks->sym) { ks->code = i; break; } } if (ks->sym == NoSymbol || ks->code == NoSymbol) { LOG_W("Unknown keyboard shortcut: '%s'", ks->str); ks->is_valid = false; } else { ks->is_valid = true; } g_free(str_begin); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/src/x11/x.h�����������������������������������������������������������������������������0000664�0000000�0000000�00000002624�13706263046�0014505�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ #ifndef DUNST_X_H #define DUNST_X_H #define XLIB_ILLEGAL_ACCESS #include <cairo.h> #include <glib.h> #include <stdbool.h> #include <X11/extensions/scrnsaver.h> #include <X11/X.h> #include <X11/Xlib.h> #include "screen.h" struct keyboard_shortcut { const char *str; KeyCode code; KeySym sym; KeySym mask; bool is_valid; }; // Cyclical dependency #include "../settings.h" struct window_x11; struct dimensions { int x; int y; int w; int h; int corner_radius; }; struct x_context { Display *dpy; XScreenSaverInfo *screensaver_info; }; struct color { double r; double g; double b; double a; }; extern struct x_context xctx; /* window */ struct window_x11 *x_win_create(void); void x_win_destroy(struct window_x11 *win); void x_win_show(struct window_x11 *win); void x_win_hide(struct window_x11 *win); void x_display_surface(cairo_surface_t *srf, struct window_x11 *win, const struct dimensions *dim); bool x_win_visible(struct window_x11 *win); cairo_t* x_win_get_context(struct window_x11 *win); /* X misc */ bool x_is_idle(void); void x_setup(void); void x_free(void); struct geometry x_parse_geometry(const char *geom_str); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/�����������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13706263046�0013640�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/data/������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13706263046�0014551�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/data/dunstrc.default���������������������������������������������������������������0000777�0000000�0000000�00000000000�13706263046�0021627�2../../dunstrc���������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/data/icons/������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13706263046�0015664�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/data/icons/invalid.png�������������������������������������������������������������0000664�0000000�0000000�00000000041�13706263046�0020013�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Got'cha! This has to be invalid! �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/data/icons/invalid.svg�������������������������������������������������������������0000664�0000000�0000000�00000000041�13706263046�0020026�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Got'cha! This has to be invalid! �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/data/icons/path/�������������������������������������������������������������������0000775�0000000�0000000�00000000000�13706263046�0016620�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/data/icons/path/invalid/�����������������������������������������������������������0000775�0000000�0000000�00000000000�13706263046�0020246�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/data/icons/path/invalid/icon1.png��������������������������������������������������0000777�0000000�0000000�00000000000�13706263046�0024542�2../../invalid.png�����������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/data/icons/path/invalid/icon1.svg��������������������������������������������������0000777�0000000�0000000�00000000000�13706263046�0024570�2../../invalid.svg�����������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/data/icons/path/valid/�������������������������������������������������������������0000775�0000000�0000000�00000000000�13706263046�0017717�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/data/icons/path/valid/icon1.png����������������������������������������������������0000777�0000000�0000000�00000000000�13706263046�0023664�2../../valid.png�������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/data/icons/path/valid/icon1.svg����������������������������������������������������0000777�0000000�0000000�00000000000�13706263046�0023712�2../../valid.svg�������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/data/icons/path/valid/onlypng.png��������������������������������������������������0000777�0000000�0000000�00000000000�13706263046�0024341�2../../valid.png�������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/data/icons/path/valid/onlysvg.svg��������������������������������������������������0000777�0000000�0000000�00000000000�13706263046�0024402�2../../valid.svg�������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/data/icons/valid.png���������������������������������������������������������������0000664�0000000�0000000�00000000301�13706263046�0017463�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������~���bKGD������ pHYs�� �� ����tIME  Z]���iTXtComment�����Created with GIMPd.e���%IDAT5ʱ � �0.e(UEO,bo6]kP����IENDB`�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/data/icons/valid.svg���������������������������������������������������������������0000664�0000000�0000000�00000003557�13706263046�0017516�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!-- Created with Inkscape (http://www.inkscape.org/) --> <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" id="SVGRoot" inkscape:version="0.92.2 5c3e80d, 2017-08-06" sodipodi:docname="valid.svg"> <sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="16" inkscape:cx="4.6127988" inkscape:cy="8" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="false" inkscape:window-width="958" inkscape:window-height="1034" inkscape:window-x="960" inkscape:window-y="46" inkscape:window-maximized="0" inkscape:grid-bbox="true" /> <defs id="defs20" /> <metadata id="metadata23"> <rdf:RDF> <cc:Work rdf:about=""> <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> <dc:title></dc:title> </cc:Work> </rdf:RDF> </metadata> <g id="layer1" inkscape:groupmode="layer" inkscape:label="Layer 1"> <rect style="opacity:0.98999999;fill:#cccccc;fill-opacity:1;stroke:#cccccc;stroke-width:1;stroke-miterlimit:4.19999981;stroke-dasharray:none;stroke-opacity:0.15686275" id="rect36" width="8" height="15.4375" x="3.75" y="0.25" ry="4.875" /> </g> </svg> �������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/data/test-ini����������������������������������������������������������������������0000664�0000000�0000000�00000002154�13706263046�0016232�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#General comment [bool] booltrue = true #This is a test inline comment booltrue_capital = TRUE #This is a comment boolfalse = false boolfalse_capital = FALSE boolyes = yes boolyes_capital = YES boolno = no boolno_capital = NO boolbin0 = 0 boolbin1 = 1 boolinvalid = invalidbool [string] simple = A simple string quoted = "A quoted string" quoted_with_quotes = "A string "with quotes"" unquoted_with_quotes = A" string with quotes" quoted_comment = "String with a" # comment unquoted_comment = String with a # comment color_comment = "#ffffff" # comment [list] simple = A,simple,list spaces = A, list, with, spaces multiword = A list, with, multiword entries quoted = "A, quoted, list" quoted_with_quotes = "A, list, "with quotes"" unquoted_with_quotes = A, list, "with quotes" quoted_comment = "List, with, a" # comment unquoted_comment = List, with, a # comment [path] expand_tilde = ~/.path/to/tilde [int] simple = 5 negative = -10 decimal = 2.71828 leading_zeroes = 007 multi_char = 1024 [double] simple = 1 decimal = 1.5 negative = -1.2 zeroes = 0.005 long = 3.141592653589793 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/dbus.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000062266�13706263046�0014755�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#define wake_up wake_up_void #include "../src/dbus.c" #include "greatest.h" #include <assert.h> #include <gdk-pixbuf/gdk-pixbuf.h> #include <gio/gio.h> #include "helpers.h" #include "queues.h" extern const char *base; void wake_up_void(void) { } struct signal_actioninvoked { guint id; gchar *key; guint subscription_id; GDBusConnection *conn; }; struct signal_closed { guint32 id; guint32 reason; guint subscription_id; GDBusConnection *conn; }; void dbus_signal_cb_actioninvoked(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { g_return_if_fail(user_data); guint32 id; gchar *key; struct signal_actioninvoked *sig = (struct signal_actioninvoked*) user_data; g_variant_get(parameters, "(us)", &id, &key); if (id == sig->id) { sig->id = id; sig->key = key; } } void dbus_signal_subscribe_actioninvoked(struct signal_actioninvoked *actioninvoked) { assert(actioninvoked); actioninvoked->conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); actioninvoked->subscription_id = g_dbus_connection_signal_subscribe( actioninvoked->conn, FDN_NAME, FDN_IFAC, "ActionInvoked", FDN_PATH, NULL, G_DBUS_SIGNAL_FLAGS_NONE, dbus_signal_cb_actioninvoked, actioninvoked, NULL); } void dbus_signal_unsubscribe_actioninvoked(struct signal_actioninvoked *actioninvoked) { assert(actioninvoked); g_dbus_connection_signal_unsubscribe(actioninvoked->conn, actioninvoked->subscription_id); g_object_unref(actioninvoked->conn); actioninvoked->conn = NULL; actioninvoked->subscription_id = -1; } void dbus_signal_cb_closed(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { g_return_if_fail(user_data); guint32 id; guint32 reason; struct signal_closed *sig = (struct signal_closed*) user_data; g_variant_get(parameters, "(uu)", &id, &reason); if (id == sig->id) { sig->id = id; sig->reason = reason; } } void dbus_signal_subscribe_closed(struct signal_closed *closed) { assert(closed); closed->conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); closed->subscription_id = g_dbus_connection_signal_subscribe( closed->conn, FDN_NAME, FDN_IFAC, "NotificationClosed", FDN_PATH, NULL, G_DBUS_SIGNAL_FLAGS_NONE, dbus_signal_cb_closed, closed, NULL); } void dbus_signal_unsubscribe_closed(struct signal_closed *closed) { assert(closed); g_dbus_connection_signal_unsubscribe(closed->conn, closed->subscription_id); g_object_unref(closed->conn); closed->conn = NULL; closed->subscription_id = -1; } GVariant *dbus_invoke(const char *method, GVariant *params) { GDBusConnection *connection_client; GVariant *retdata; GError *error = NULL; connection_client = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); retdata = g_dbus_connection_call_sync( connection_client, FDN_NAME, FDN_PATH, FDN_IFAC, method, params, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (error) { printf("Error while calling GTestDBus instance: %s\n", error->message); g_error_free(error); } g_object_unref(connection_client); return retdata; } struct dbus_notification { const char* app_name; guint replaces_id; const char* app_icon; const char* summary; const char* body; GHashTable *actions; GHashTable *hints; int expire_timeout; }; void g_free_variant_value(gpointer tofree) { g_variant_unref((GVariant*) tofree); } struct dbus_notification *dbus_notification_new(void) { struct dbus_notification *n = g_malloc0(sizeof(struct dbus_notification)); n->expire_timeout = -1; n->replaces_id = 0; n->actions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); n->hints = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free_variant_value); return n; } void dbus_notification_free(struct dbus_notification *n) { g_hash_table_unref(n->hints); g_hash_table_unref(n->actions); g_free(n); } bool dbus_notification_fire(struct dbus_notification *n, uint *id) { assert(n); assert(id); GVariantBuilder b; GVariantType *t; gpointer p_key; gpointer p_value; GHashTableIter iter; t = g_variant_type_new("(susssasa{sv}i)"); g_variant_builder_init(&b, t); g_variant_type_free(t); g_variant_builder_add(&b, "s", n->app_name); g_variant_builder_add(&b, "u", n->replaces_id); g_variant_builder_add(&b, "s", n->app_icon); g_variant_builder_add(&b, "s", n->summary); g_variant_builder_add(&b, "s", n->body); // Add the actions t = g_variant_type_new("as"); g_variant_builder_open(&b, t); g_hash_table_iter_init(&iter, n->actions); while (g_hash_table_iter_next(&iter, &p_key, &p_value)) { g_variant_builder_add(&b, "s", (char*)p_key); g_variant_builder_add(&b, "s", (char*)p_value); } // Add an invalid appendix to cover odd numbered action arrays // Shouldn't interfere with normal testing g_variant_builder_add(&b, "s", "invalid appendix"); g_variant_builder_close(&b); g_variant_type_free(t); // Add the hints t = g_variant_type_new("a{sv}"); g_variant_builder_open(&b, t); g_hash_table_iter_init(&iter, n->hints); while (g_hash_table_iter_next(&iter, &p_key, &p_value)) { g_variant_builder_add(&b, "{sv}", (char*)p_key, (GVariant*)p_value); } g_variant_builder_close(&b); g_variant_type_free(t); g_variant_builder_add(&b, "i", n->expire_timeout); GVariant *reply = dbus_invoke("Notify", g_variant_builder_end(&b)); if (reply) { g_variant_get(reply, "(u)", id); g_variant_unref(reply); return true; } else { return false; } } void dbus_notification_set_raw_image(struct dbus_notification *n_dbus, const char *path) { GVariant *hint = notification_setup_raw_image(path); if (!hint) return; g_hash_table_insert(n_dbus->hints, g_strdup("image-data"), g_variant_ref_sink(hint)); } /////// TESTS gint owner_id; TEST test_dbus_init(void) { owner_id = dbus_init(); uint waiting = 0; while (!dbus_conn && waiting < 2000) { usleep(500); waiting++; } ASSERTm("After 1s, there is still no dbus connection available.", dbus_conn); PASS(); } TEST test_dbus_teardown(void) { dbus_teardown(owner_id); PASS(); } TEST test_invalid_notification(void) { GVariant *faulty = g_variant_new_boolean(true); ASSERT(NULL == dbus_message_to_notification(":123", faulty)); ASSERT(NULL == dbus_invoke("Notify", faulty)); g_variant_unref(faulty); PASS(); } TEST test_empty_notification(void) { struct dbus_notification *n = dbus_notification_new(); gsize len = queues_length_waiting(); guint id; ASSERT(dbus_notification_fire(n, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); dbus_notification_free(n); PASS(); } TEST test_basic_notification(void) { struct dbus_notification *n = dbus_notification_new(); gsize len = queues_length_waiting(); n->app_name = "dunstteststack"; n->app_icon = "NONE"; n->summary = "Headline"; n->body = "Text"; g_hash_table_insert(n->actions, g_strdup("actionid"), g_strdup("Print this text")); g_hash_table_insert(n->hints, g_strdup("x-dunst-stack-tag"), g_variant_ref_sink(g_variant_new_string("volume"))); n->replaces_id = 10; guint id; ASSERT(dbus_notification_fire(n, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); dbus_notification_free(n); PASS(); } TEST test_dbus_notify_colors(void) { const char *color_frame = "I allow all string values for frame!"; const char *color_bg = "I allow all string values for background!"; const char *color_fg = "I allow all string values for foreground!"; struct notification *n; struct dbus_notification *n_dbus; gsize len = queues_length_waiting(); n_dbus = dbus_notification_new(); n_dbus->app_name = "dunstteststack"; n_dbus->app_icon = "NONE"; n_dbus->summary = "test_dbus_notify_colors"; n_dbus->body = "Summary of it"; g_hash_table_insert(n_dbus->hints, g_strdup("frcolor"), g_variant_ref_sink(g_variant_new_string(color_frame))); g_hash_table_insert(n_dbus->hints, g_strdup("bgcolor"), g_variant_ref_sink(g_variant_new_string(color_bg))); g_hash_table_insert(n_dbus->hints, g_strdup("fgcolor"), g_variant_ref_sink(g_variant_new_string(color_fg))); guint id; ASSERT(dbus_notification_fire(n_dbus, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); n = queues_debug_find_notification_by_id(id); ASSERT_STR_EQ(n->colors.frame, color_frame); ASSERT_STR_EQ(n->colors.fg, color_fg); ASSERT_STR_EQ(n->colors.bg, color_bg); dbus_notification_free(n_dbus); PASS(); } TEST test_hint_transient(void) { static char msg[50]; struct notification *n; struct dbus_notification *n_dbus; gsize len = queues_length_waiting(); n_dbus = dbus_notification_new(); n_dbus->app_name = "dunstteststack"; n_dbus->app_icon = "NONE"; n_dbus->summary = "test_hint_transient"; n_dbus->body = "Summary of it"; bool values[] = { true, true, true, false, false, false, false }; GVariant *variants[] = { g_variant_new_boolean(true), g_variant_new_int32(1), g_variant_new_uint32(1), g_variant_new_boolean(false), g_variant_new_int32(0), g_variant_new_uint32(0), g_variant_new_int32(-1), }; for (size_t i = 0; i < G_N_ELEMENTS(variants); i++) { g_hash_table_insert(n_dbus->hints, g_strdup("transient"), g_variant_ref_sink(variants[i])); guint id; ASSERT(dbus_notification_fire(n_dbus, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); n = queues_debug_find_notification_by_id(id); snprintf(msg, sizeof(msg), "In round %ld", i); ASSERT_EQm(msg, values[i], n->transient); } dbus_notification_free(n_dbus); PASS(); } TEST test_hint_progress(void) { static char msg[50]; struct notification *n; struct dbus_notification *n_dbus; gsize len = queues_length_waiting(); n_dbus = dbus_notification_new(); n_dbus->app_name = "dunstteststack"; n_dbus->app_icon = "NONE"; n_dbus->summary = "test_hint_progress"; n_dbus->body = "Summary of it"; int values[] = { 99, 12, 123, 123, -1, -1 }; GVariant *variants[] = { g_variant_new_int32(99), g_variant_new_uint32(12), g_variant_new_int32(123), // allow higher than 100 g_variant_new_uint32(123), g_variant_new_int32(-192), g_variant_new_uint32(-192), }; for (size_t i = 0; i < G_N_ELEMENTS(variants); i++) { g_hash_table_insert(n_dbus->hints, g_strdup("value"), g_variant_ref_sink(variants[i])); guint id; ASSERT(dbus_notification_fire(n_dbus, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); n = queues_debug_find_notification_by_id(id); snprintf(msg, sizeof(msg), "In round %ld", i); ASSERT_EQm(msg, values[i], n->progress); } dbus_notification_free(n_dbus); PASS(); } TEST test_hint_icons(void) { struct notification *n; struct dbus_notification *n_dbus; const char *iconname = "NEWICON"; gsize len = queues_length_waiting(); n_dbus = dbus_notification_new(); n_dbus->app_name = "dunstteststack"; n_dbus->app_icon = "NONE"; n_dbus->summary = "test_hint_icons"; n_dbus->body = "Summary of it"; g_hash_table_insert(n_dbus->hints, g_strdup("image-path"), g_variant_ref_sink(g_variant_new_string(iconname))); guint id; ASSERT(dbus_notification_fire(n_dbus, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); n = queues_debug_find_notification_by_id(id); ASSERT_STR_EQ(iconname, n->iconname); dbus_notification_free(n_dbus); PASS(); } TEST test_hint_category(void) { struct notification *n; struct dbus_notification *n_dbus; const char *category = "VOLUME"; gsize len = queues_length_waiting(); n_dbus = dbus_notification_new(); n_dbus->app_name = "dunstteststack"; n_dbus->app_icon = "NONE"; n_dbus->summary = "test_hint_category"; n_dbus->body = "Summary of it"; g_hash_table_insert(n_dbus->hints, g_strdup("category"), g_variant_ref_sink(g_variant_new_string(category))); guint id; ASSERT(dbus_notification_fire(n_dbus, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); n = queues_debug_find_notification_by_id(id); ASSERT_STR_EQ(category, n->category); dbus_notification_free(n_dbus); PASS(); } TEST test_hint_desktop_entry(void) { struct notification *n; struct dbus_notification *n_dbus; const char *desktop_entry = "org.dunst-project.dunst"; gsize len = queues_length_waiting(); n_dbus = dbus_notification_new(); n_dbus->app_name = "dunstteststack"; n_dbus->app_icon = "NONE"; n_dbus->summary = "test_hint_desktopentry"; n_dbus->body = "Summary of my desktop_entry"; g_hash_table_insert(n_dbus->hints, g_strdup("desktop-entry"), g_variant_ref_sink(g_variant_new_string(desktop_entry))); guint id; ASSERT(dbus_notification_fire(n_dbus, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); n = queues_debug_find_notification_by_id(id); ASSERT_STR_EQ(desktop_entry, n->desktop_entry); dbus_notification_free(n_dbus); PASS(); } TEST test_hint_urgency(void) { static char msg[50]; struct notification *n; struct dbus_notification *n_dbus; gsize len = queues_length_waiting(); n_dbus = dbus_notification_new(); n_dbus->app_name = "dunstteststack"; n_dbus->app_icon = "NONE"; n_dbus->summary = "test_hint_urgency"; n_dbus->body = "Summary of it"; enum urgency values[] = { URG_MAX, URG_LOW, URG_NORM, URG_CRIT }; GVariant *variants[] = { g_variant_new_byte(10), g_variant_new_byte(0), g_variant_new_byte(1), g_variant_new_byte(2), }; for (size_t i = 0; i < G_N_ELEMENTS(variants); i++) { g_hash_table_insert(n_dbus->hints, g_strdup("urgency"), g_variant_ref_sink(variants[i])); guint id; ASSERT(dbus_notification_fire(n_dbus, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); n = queues_debug_find_notification_by_id(id); snprintf(msg, sizeof(msg), "In round %ld", i); ASSERT_EQm(msg, values[i], n->urgency); queues_notification_close_id(id, REASON_UNDEF); } dbus_notification_free(n_dbus); PASS(); } TEST test_hint_raw_image(void) { guint id; struct notification *n; struct dbus_notification *n_dbus; char *path = g_strconcat(base, "/data/icons/valid.png", NULL); gsize len = queues_length_waiting(); n_dbus = dbus_notification_new(); dbus_notification_set_raw_image(n_dbus, path); n_dbus->app_name = "dunstteststack"; n_dbus->app_icon = "NONE"; n_dbus->summary = "test_hint_raw_image"; n_dbus->body = "Summary of it"; ASSERT(dbus_notification_fire(n_dbus, &id)); ASSERT(id != 0); ASSERT_EQ(queues_length_waiting(), len+1); n = queues_debug_find_notification_by_id(id); ASSERT(n->icon); ASSERT(!STR_EQ(n->icon_id, n_dbus->app_icon)); dbus_notification_free(n_dbus); g_free(path); PASS(); } /* We didn't process the timeout parameter via DBus correctly * and it got limited to an int instead of a long int * See: Issue #646 (The timeout value in dunst wraps around) */ TEST test_timeout_overflow(void) { struct notification *n; struct dbus_notification *n_dbus; n_dbus = dbus_notification_new(); n_dbus->app_name = "dunstteststack"; n_dbus->app_icon = "NONE"; n_dbus->summary = "test_hint_urgency"; n_dbus->body = "Summary of it"; n_dbus->expire_timeout = 2147484; gint64 expected_timeout = G_GINT64_CONSTANT(2147484000); guint id; ASSERT(dbus_notification_fire(n_dbus, &id)); ASSERT(id != 0); n = queues_debug_find_notification_by_id(id); ASSERT_EQ_FMT(expected_timeout, n->timeout, "%" G_GINT64_FORMAT); dbus_notification_free(n_dbus); PASS(); } TEST test_server_caps(enum markup_mode markup) { GVariant *reply; GVariant *caps = NULL; const char **capsarray; settings.markup = markup; reply = dbus_invoke("GetCapabilities", NULL); caps = g_variant_get_child_value(reply, 0); capsarray = g_variant_get_strv(caps, NULL); ASSERT(capsarray); ASSERT(g_strv_contains(capsarray, "actions")); ASSERT(g_strv_contains(capsarray, "body")); ASSERT(g_strv_contains(capsarray, "body-hyperlinks")); ASSERT(g_strv_contains(capsarray, "x-dunst-stack-tag")); if (settings.markup != MARKUP_NO) ASSERT(g_strv_contains(capsarray, "body-markup")); else ASSERT_FALSE(g_strv_contains(capsarray, "body-markup")); g_free(capsarray); g_variant_unref(caps); g_variant_unref(reply); PASS(); } TEST test_signal_actioninvoked(void) { const struct notification *n; struct dbus_notification *n_dbus; struct signal_actioninvoked sig = {0, NULL, -1}; dbus_signal_subscribe_actioninvoked(&sig); n_dbus = dbus_notification_new(); n_dbus->app_name = "dunstteststack"; n_dbus->app_icon = "NONE2"; n_dbus->summary = "Headline for New"; n_dbus->body = "Text"; g_hash_table_insert(n_dbus->actions, g_strdup("actionkey"), g_strdup("Print this text")); dbus_notification_fire(n_dbus, &sig.id); n = queues_debug_find_notification_by_id(sig.id); signal_action_invoked(n, "actionkey"); uint waiting = 0; while (!sig.key && waiting < 2000) { usleep(500); waiting++; } ASSERT_STR_EQ("actionkey", sig.key); g_free(sig.key); dbus_notification_free(n_dbus); dbus_signal_unsubscribe_actioninvoked(&sig); PASS(); } TEST test_close_and_signal(void) { GVariant *data, *ret; struct dbus_notification *n; struct signal_closed sig = {0, REASON_MIN-1, -1}; dbus_signal_subscribe_closed(&sig); n = dbus_notification_new(); n->app_name = "dunstteststack"; n->app_icon = "NONE2"; n->summary = "Headline for New"; n->body = "Text"; dbus_notification_fire(n, &sig.id); data = g_variant_new("(u)", sig.id); ret = dbus_invoke("CloseNotification", data); ASSERT(ret); uint waiting = 0; while (sig.reason == REASON_MIN-1 && waiting < 2000) { usleep(500); waiting++; } ASSERT(sig.reason != REASON_MIN-1); dbus_notification_free(n); dbus_signal_unsubscribe_closed(&sig); g_variant_unref(ret); PASS(); } TEST test_get_fdn_daemon_info(void) { unsigned int pid_is; pid_t pid_should; char *name, *vendor; GDBusConnection *conn; pid_should = getpid(); conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); ASSERT(dbus_get_fdn_daemon_info(conn, &pid_is, &name, &vendor)); ASSERT_EQ_FMT(pid_should, pid_is, "%d"); ASSERT_STR_EQ("dunst", name); ASSERT_STR_EQ("knopwob", vendor); g_free(name); g_free(vendor); g_object_unref(conn); PASS(); } TEST assert_methodlists_sorted(void) { for (size_t i = 0; i+1 < G_N_ELEMENTS(methods_fdn); i++) { ASSERT(0 > strcmp( methods_fdn[i].method_name, methods_fdn[i+1].method_name)); } for (size_t i = 0; i+1 < G_N_ELEMENTS(methods_dunst); i++) { ASSERT(0 > strcmp( methods_dunst[i].method_name, methods_dunst[i+1].method_name)); } PASS(); } // TESTS END GMainLoop *loop; GThread *thread_tests; gpointer run_threaded_tests(gpointer data) { RUN_TEST(test_dbus_init); RUN_TEST(test_get_fdn_daemon_info); RUN_TEST(test_empty_notification); RUN_TEST(test_basic_notification); RUN_TEST(test_invalid_notification); RUN_TEST(test_hint_transient); RUN_TEST(test_hint_progress); RUN_TEST(test_hint_icons); RUN_TEST(test_hint_category); RUN_TEST(test_hint_desktop_entry); RUN_TEST(test_hint_urgency); RUN_TEST(test_hint_raw_image); RUN_TEST(test_dbus_notify_colors); RUN_TESTp(test_server_caps, MARKUP_FULL); RUN_TESTp(test_server_caps, MARKUP_STRIP); RUN_TESTp(test_server_caps, MARKUP_NO); RUN_TEST(test_close_and_signal); RUN_TEST(test_signal_actioninvoked); RUN_TEST(test_timeout_overflow); RUN_TEST(assert_methodlists_sorted); RUN_TEST(test_dbus_teardown); g_main_loop_quit(loop); return NULL; } SUITE(suite_dbus) { settings.icon_path = ""; GTestDBus *dbus_bus; g_test_dbus_unset(); queues_init(); loop = g_main_loop_new(NULL, false); dbus_bus = g_test_dbus_new(G_TEST_DBUS_NONE); g_test_dbus_up(dbus_bus); thread_tests = g_thread_new("testexecutor", run_threaded_tests, loop); g_main_loop_run(loop); queues_teardown(); g_test_dbus_down(dbus_bus); g_object_unref(dbus_bus); g_thread_unref(thread_tests); g_main_loop_unref(loop); settings.icon_path = NULL; } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/dunst.c����������������������������������������������������������������������������0000664�0000000�0000000�00000001273�13706263046�0015144�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#define dbus_signal_status_changed(status) signal_sent_stub(status) #include "../src/dunst.c" #include "greatest.h" static bool signal_sent = false; void signal_sent_stub(struct dunst_status status) { signal_sent = true; return; } TEST test_dunst_status(void) { status = (struct dunst_status) {false, false, false}; dunst_status(S_FULLSCREEN, true); ASSERT(status.fullscreen); dunst_status(S_IDLE, true); ASSERT(status.idle); dunst_status(S_RUNNING, true); ASSERT(status.running); PASS(); } SUITE(suite_dunst) { RUN_TEST(test_dunst_status); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/functional-tests/������������������������������������������������������������������0000775�0000000�0000000�00000000000�13706263046�0017142�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/functional-tests/dunstrc.default���������������������������������������������������0000664�0000000�0000000�00000001766�13706263046�0022204�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[global] font = Monospace 8 allow_markup = yes format = "<b>%s</b>\n%b" sort = yes indicate_hidden = yes alignment = left show_age_threshold = 60 word_wrap = yes ignore_newline = no geometry = "300x5-30+20" transparency = 0 idle_threshold = 120 monitor = 0 follow = mouse sticky_history = yes line_height = 0 separator_height = 2 padding = 8 horizontal_padding = 8 separator_color = frame startup_notification = false dmenu = /usr/bin/dmenu -p dunst browser = /usr/bin/firefox -new-tab [frame] width = 3 color = "#aaaaaa" [shortcuts] close = ctrl+space close_all = ctrl+shift+space history = ctrl+grave context = ctrl+shift+period [urgency_low] background = "#222222" foreground = "#888888" timeout = 10 [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 [urgency_critical] background = "#900000" foreground = "#ffffff" timeout = 0 ����������dunst-1.5.0/test/functional-tests/dunstrc.ignore_newline��������������������������������������������0000664�0000000�0000000�00000001767�13706263046�0023565�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[global] font = Monospace 8 allow_markup = yes format = "<b>%s</b>\n%b" sort = yes indicate_hidden = yes alignment = left show_age_threshold = 60 word_wrap = yes ignore_newline = yes geometry = "200x0-30+20" transparency = 0 idle_threshold = 120 monitor = 0 follow = mouse sticky_history = yes line_height = 0 separator_height = 2 padding = 8 horizontal_padding = 8 separator_color = frame startup_notification = false dmenu = /usr/bin/dmenu -p dunst browser = /usr/bin/firefox -new-tab [frame] width = 3 color = "#aaaaaa" [shortcuts] close = ctrl+space close_all = ctrl+shift+space history = ctrl+grave context = ctrl+shift+period [urgency_low] background = "#222222" foreground = "#888888" timeout = 10 [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 [urgency_critical] background = "#900000" foreground = "#ffffff" timeout = 0 ���������dunst-1.5.0/test/functional-tests/dunstrc.ignore_newline_no_wrap������������������������������������0000664�0000000�0000000�00000001766�13706263046�0025311�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[global] font = Monospace 8 allow_markup = yes format = "<b>%s</b>\n%b" sort = yes indicate_hidden = yes alignment = left show_age_threshold = 60 word_wrap = no ignore_newline = yes geometry = "250x0-30+20" transparency = 0 idle_threshold = 120 monitor = 0 follow = mouse sticky_history = yes line_height = 0 separator_height = 2 padding = 8 horizontal_padding = 8 separator_color = frame startup_notification = false dmenu = /usr/bin/dmenu -p dunst browser = /usr/bin/firefox -new-tab [frame] width = 3 color = "#aaaaaa" [shortcuts] close = ctrl+space close_all = ctrl+shift+space history = ctrl+grave context = ctrl+shift+period [urgency_low] background = "#222222" foreground = "#888888" timeout = 10 [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 [urgency_critical] background = "#900000" foreground = "#ffffff" timeout = 0 ����������dunst-1.5.0/test/functional-tests/dunstrc.markup����������������������������������������������������0000664�0000000�0000000�00000001757�13706263046�0022057�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[global] font = Monospace 8 allow_markup = yes format = "%s\n%b" sort = yes indicate_hidden = yes alignment = left show_age_threshold = 60 word_wrap = yes ignore_newline = no geometry = "300x5-30+20" transparency = 0 idle_threshold = 120 monitor = 0 follow = mouse sticky_history = yes line_height = 0 separator_height = 2 padding = 8 horizontal_padding = 8 separator_color = frame startup_notification = false dmenu = /usr/bin/dmenu -p dunst browser = /usr/bin/firefox -new-tab [frame] width = 3 color = "#aaaaaa" [shortcuts] close = ctrl+space close_all = ctrl+shift+space history = ctrl+grave context = ctrl+shift+period [urgency_low] background = "#222222" foreground = "#888888" timeout = 10 [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 [urgency_critical] background = "#900000" foreground = "#ffffff" timeout = 0 �����������������dunst-1.5.0/test/functional-tests/dunstrc.nomarkup��������������������������������������������������0000664�0000000�0000000�00000001774�13706263046�0022413�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[global] font = Monospace 8 allow_markup = no format = "<b>%s</b>\n<i>%b</i>" sort = yes indicate_hidden = yes alignment = left show_age_threshold = 60 word_wrap = yes ignore_newline = no geometry = "300x5-30+20" transparency = 0 idle_threshold = 120 monitor = 0 follow = mouse sticky_history = yes line_height = 0 separator_height = 2 padding = 8 horizontal_padding = 8 separator_color = frame startup_notification = false dmenu = /usr/bin/dmenu -p dunst browser = /usr/bin/firefox -new-tab [frame] width = 3 color = "#aaaaaa" [shortcuts] close = ctrl+space close_all = ctrl+shift+space history = ctrl+grave context = ctrl+shift+period [urgency_low] background = "#222222" foreground = "#888888" timeout = 10 [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 [urgency_critical] background = "#900000" foreground = "#ffffff" timeout = 0 ����dunst-1.5.0/test/functional-tests/dunstrc.nowrap����������������������������������������������������0000664�0000000�0000000�00000001761�13706263046�0022061�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[global] font = Monospace-10 allow_markup = yes format = "%s\n%b" sort = yes indicate_hidden = yes alignment = left show_age_threshold = 60 word_wrap = no ignore_newline = no geometry = "300x5-30+20" transparency = 0 idle_threshold = 120 monitor = 0 follow = mouse sticky_history = yes line_height = 0 separator_height = 2; padding = 8 horizontal_padding = 8 separator_color = frame startup_notification = false dmenu = /usr/bin/dmenu -p dunst: browser = /usr/bin/firefox -new-tab [frame] width = 3 color = "#aaaaaa" [shortcuts] close = ctrl+space close_all = ctrl+shift+space history = ctrl+grave context = ctrl+shift+period [urgency_low] background = "#222222" foreground = "#888888" timeout = 10 [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 [urgency_critical] background = "#900000" foreground = "#ffffff" timeout = 0 ���������������dunst-1.5.0/test/functional-tests/dunstrc.run_script������������������������������������������������0000664�0000000�0000000�00000002067�13706263046�0022743�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[global] font = Monospace 8 allow_markup = yes format = "<b>%s</b>\n%b" sort = yes indicate_hidden = yes alignment = left show_age_threshold = 60 word_wrap = yes ignore_newline = no geometry = "300x5-30+20" transparency = 0 idle_threshold = 120 monitor = 0 follow = mouse sticky_history = yes line_height = 0 separator_height = 2 padding = 8 horizontal_padding = 8 separator_color = frame startup_notification = false dmenu = /usr/bin/dmenu -p dunst browser = /usr/bin/firefox -new-tab [frame] width = 3 color = "#aaaaaa" [shortcuts] close = ctrl+space close_all = ctrl+shift+space history = ctrl+grave context = ctrl+shift+period [urgency_low] background = "#222222" foreground = "#888888" timeout = 10 [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 [urgency_critical] background = "#900000" foreground = "#ffffff" timeout = 0 [script test] summary = trigger script = script_test.sh �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/functional-tests/dunstrc.show_age��������������������������������������������������0000664�0000000�0000000�00000001765�13706263046�0022353�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[global] font = Monospace 8 allow_markup = yes format = "<b>%s</b>\n%b" sort = yes indicate_hidden = yes alignment = left show_age_threshold = 2 word_wrap = yes ignore_newline = no geometry = "300x5-30+20" transparency = 0 idle_threshold = 120 monitor = 0 follow = mouse sticky_history = yes line_height = 0 separator_height = 2 padding = 8 horizontal_padding = 8 separator_color = frame startup_notification = false dmenu = /usr/bin/dmenu -p dunst browser = /usr/bin/firefox -new-tab [frame] width = 3 color = "#aaaaaa" [shortcuts] close = ctrl+space close_all = ctrl+shift+space history = ctrl+grave context = ctrl+shift+period [urgency_low] background = "#222222" foreground = "#888888" timeout = 10 [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 [urgency_critical] background = "#900000" foreground = "#ffffff" timeout = 0 �����������dunst-1.5.0/test/functional-tests/script_test.sh����������������������������������������������������0000775�0000000�0000000�00000000060�13706263046�0022040�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/bash notify-send "Success" "ooooh yeah" ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/functional-tests/test.sh�����������������������������������������������������������0000775�0000000�0000000�00000013316�13706263046�0020464�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/bash function keypress { echo "press enter to continue..." read key } function basic_notifications { ../../dunstify -a "dunst tester" "normal" "<i>italic body</i>" ../../dunstify -a "dunst tester" -u c "critical" "<b>bold body</b>" ../../dunstify -a "dunst tester" "long body" "This is a notification with a very long body" ../../dunstify -a "dunst tester" "duplucate" ../../dunstify -a "dunst tester" "duplucate" ../../dunstify -a "dunst tester" "duplucate" ../../dunstify -a "dunst tester" "url" "www.google.de" } function show_age { echo "###################################" echo "show age" echo "###################################" killall dunst ../../dunst -config dunstrc.show_age & ../../dunstify -a "dunst tester" -u c "Show Age" "These should print their age after 2 seconds" basic_notifications keypress } function run_script { echo "###################################" echo "run script" echo "###################################" killall dunst PATH=".:$PATH" ../../dunst -config dunstrc.run_script & ../../dunstify -a "dunst tester" -u c \ "Run Script" "After Keypress, 2 other notification should pop up. THis needs notify-send installed" keypress ../../dunstify -a "dunst tester" -u c "trigger" "this should trigger a notification" keypress } function ignore_newline { echo "###################################" echo "ignore newline" echo "###################################" killall dunst ../../dunst -config dunstrc.ignore_newline_no_wrap & ../../dunstify -a "dunst tester" -u c "Ignore Newline No Wrap" "There should be no newline anywhere" ../../dunstify -a "dunst tester" -u c "Th\nis\n\n\n is\n fu\nll of \n" "\nnew\nlines" basic_notifications keypress killall dunst ../../dunst -config dunstrc.ignore_newline & ../../dunstify -a "dunst tester" -u c "Ignore Newline" \ "The only newlines you should encounter here are wordwraps. That's why I'm so long." ../../dunstify -a "dunst tester" -u c "Th\nis\n\n\n is\n fu\nll of \n" "\nnew\nlines" basic_notifications keypress } function replace { echo "###################################" echo "replace" echo "###################################" killall dunst ../../dunst -config dunstrc.default & id=$(../../dunstify -a "dunst tester" -p "Replace" "this should get replaces after keypress") keypress ../../dunstify -a "dunst tester" -r $id "Success?" "I hope this is not a new notification" keypress } function markup { echo "###################################" echo "markup" echo "###################################" killall dunst ../../dunst -config dunstrc.markup "200x0+10+10" & ../../dunstify -a "dunst tester" "Markup Tests" -u "c" ../../dunstify -a "dunst tester" "<b>bold</b> <i>italic</i>" ../../dunstify -a "dunst tester" "<b>broken markup</i>" keypress killall dunst ../../dunst -config dunstrc.nomarkup "200x0+10+10" & ../../dunstify -a "dunst tester" -u c "NO Markup Tests" ../../dunstify -a "dunst tester" "<b>bold</b><i>italic</i>" ../../dunstify -a "dunst tester" "<b>broken markup</i>" keypress } function corners { echo "###################################" echo "corners" echo "###################################" killall dunst ../../dunst -config dunstrc.default -geom "200x0+10+10" & ../../dunstify -a "dunst tester" -u c "upper left" basic_notifications keypress killall dunst ../../dunst -config dunstrc.default -geom "200x0-10+10" & ../../dunstify -a "dunst tester" -u c "upper right" basic_notifications keypress killall dunst ../../dunst -config dunstrc.default -geom "200x0-10-10" & ../../dunstify -a "dunst tester" -u c "lower right" basic_notifications keypress killall dunst ../../dunst -config dunstrc.default -geom "200x0+10-10" & ../../dunstify -a "dunst tester" -u c "lower left" basic_notifications keypress } function geometry { echo "###################################" echo "geometry" echo "###################################" killall dunst ../../dunst -config dunstrc.default -geom "0x0" & ../../dunstify -a "dunst tester" -u c "0x0" basic_notifications keypress killall dunst ../../dunst -config dunstrc.default -geom "200x0" & ../../dunstify -a "dunst tester" -u c "200x0" basic_notifications keypress killall dunst ../../dunst -config dunstrc.default -geom "200x2" & ../../dunstify -a "dunst tester" -u c "200x2" basic_notifications keypress killall dunst ../../dunst -config dunstrc.default -geom "200x1" & ../../dunstify -a "dunst tester" -u c "200x1" basic_notifications keypress killall dunst ../../dunst -config dunstrc.default -geom "0x1" & ../../dunstify -a "dunst tester" -u c "0x1" basic_notifications keypress killall dunst ../../dunst -config dunstrc.default -geom "-300x1" & ../../dunstify -a "dunst tester" -u c "-300x1" basic_notifications keypress killall dunst ../../dunst -config dunstrc.default -geom "-300x1-20-20" & ../../dunstify -a "dunst tester" -u c "-300x1-20-20" basic_notifications keypress killall dunst ../../dunst -config dunstrc.default -geom "x1" & ../../dunstify -a "dunst tester" -u c "x1-20-20" "across the screen" basic_notifications keypress } if [ -n "$1" ]; then while [ -n "$1" ]; do $1 shift done else geometry corners show_age run_script ignore_newline replace markup fi killall dunst ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/greatest.h�������������������������������������������������������������������������0000664�0000000�0000000�00000202005�13706263046�0015626�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2011-2018 Scott Vokes <vokes.s@gmail.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef GREATEST_H #define GREATEST_H #if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS) extern "C" { #endif /* 1.4.0 */ #define GREATEST_VERSION_MAJOR 1 #define GREATEST_VERSION_MINOR 4 #define GREATEST_VERSION_PATCH 0 /* A unit testing system for C, contained in 1 file. * It doesn't use dynamic allocation or depend on anything * beyond ANSI C89. * * An up-to-date version can be found at: * https://github.com/silentbicycle/greatest/ */ /********************************************************************* * Minimal test runner template *********************************************************************/ #if 0 #include "greatest.h" TEST foo_should_foo(void) { PASS(); } static void setup_cb(void *data) { printf("setup callback for each test case\n"); } static void teardown_cb(void *data) { printf("teardown callback for each test case\n"); } SUITE(suite) { /* Optional setup/teardown callbacks which will be run before/after * every test case. If using a test suite, they will be cleared when * the suite finishes. */ SET_SETUP(setup_cb, voidp_to_callback_data); SET_TEARDOWN(teardown_cb, voidp_to_callback_data); RUN_TEST(foo_should_foo); } /* Add definitions that need to be in the test runner's main file. */ GREATEST_MAIN_DEFS(); /* Set up, run suite(s) of tests, report pass/fail/skip stats. */ int run_tests(void) { GREATEST_INIT(); /* init. greatest internals */ /* List of suites to run (if any). */ RUN_SUITE(suite); /* Tests can also be run directly, without using test suites. */ RUN_TEST(foo_should_foo); GREATEST_PRINT_REPORT(); /* display results */ return greatest_all_passed(); } /* main(), for a standalone command-line test runner. * This replaces run_tests above, and adds command line option * handling and exiting with a pass/fail status. */ int main(int argc, char **argv) { GREATEST_MAIN_BEGIN(); /* init & parse command-line args */ RUN_SUITE(suite); GREATEST_MAIN_END(); /* display results */ } #endif /*********************************************************************/ #include <stdlib.h> #include <stdio.h> #include <string.h> #include <ctype.h> /*********** * Options * ***********/ /* Default column width for non-verbose output. */ #ifndef GREATEST_DEFAULT_WIDTH #define GREATEST_DEFAULT_WIDTH 72 #endif /* FILE *, for test logging. */ #ifndef GREATEST_STDOUT #define GREATEST_STDOUT stdout #endif /* Remove GREATEST_ prefix from most commonly used symbols? */ #ifndef GREATEST_USE_ABBREVS #define GREATEST_USE_ABBREVS 1 #endif /* Set to 0 to disable all use of setjmp/longjmp. */ #ifndef GREATEST_USE_LONGJMP #define GREATEST_USE_LONGJMP 1 #endif /* Make it possible to replace fprintf with another * function with the same interface. */ #ifndef GREATEST_FPRINTF #define GREATEST_FPRINTF fprintf #endif #if GREATEST_USE_LONGJMP #include <setjmp.h> #endif /* Set to 0 to disable all use of time.h / clock(). */ #ifndef GREATEST_USE_TIME #define GREATEST_USE_TIME 1 #endif #if GREATEST_USE_TIME #include <time.h> #endif /* Floating point type, for ASSERT_IN_RANGE. */ #ifndef GREATEST_FLOAT #define GREATEST_FLOAT double #define GREATEST_FLOAT_FMT "%g" #endif /* Size of buffer for test name + optional '_' separator and suffix */ #ifndef GREATEST_TESTNAME_BUF_SIZE #define GREATEST_TESTNAME_BUF_SIZE 128 #endif /********* * Types * *********/ /* Info for the current running suite. */ typedef struct greatest_suite_info { unsigned int tests_run; unsigned int passed; unsigned int failed; unsigned int skipped; #if GREATEST_USE_TIME /* timers, pre/post running suite and individual tests */ clock_t pre_suite; clock_t post_suite; clock_t pre_test; clock_t post_test; #endif } greatest_suite_info; /* Type for a suite function. */ typedef void greatest_suite_cb(void); /* Types for setup/teardown callbacks. If non-NULL, these will be run * and passed the pointer to their additional data. */ typedef void greatest_setup_cb(void *udata); typedef void greatest_teardown_cb(void *udata); /* Type for an equality comparison between two pointers of the same type. * Should return non-0 if equal, otherwise 0. * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */ typedef int greatest_equal_cb(const void *exp, const void *got, void *udata); /* Type for a callback that prints a value pointed to by T. * Return value has the same meaning as printf's. * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */ typedef int greatest_printf_cb(const void *t, void *udata); /* Callbacks for an arbitrary type; needed for type-specific * comparisons via GREATEST_ASSERT_EQUAL_T[m].*/ typedef struct greatest_type_info { greatest_equal_cb *equal; greatest_printf_cb *print; } greatest_type_info; typedef struct greatest_memory_cmp_env { const unsigned char *exp; const unsigned char *got; size_t size; } greatest_memory_cmp_env; /* Callbacks for string and raw memory types. */ extern greatest_type_info greatest_type_info_string; extern greatest_type_info greatest_type_info_memory; typedef enum { GREATEST_FLAG_FIRST_FAIL = 0x01, GREATEST_FLAG_LIST_ONLY = 0x02, GREATEST_FLAG_ABORT_ON_FAIL = 0x04 } greatest_flag_t; /* Internal state for a PRNG, used to shuffle test order. */ struct greatest_prng { unsigned char random_order; /* use random ordering? */ unsigned char initialized; /* is random ordering initialized? */ unsigned char pad_0[6]; unsigned long state; /* PRNG state */ unsigned long count; /* how many tests, this pass */ unsigned long count_ceil; /* total number of tests */ unsigned long count_run; /* total tests run */ unsigned long mod; /* power-of-2 ceiling of count_ceil */ unsigned long a; /* LCG multiplier */ unsigned long c; /* LCG increment */ }; /* Struct containing all test runner state. */ typedef struct greatest_run_info { unsigned char flags; unsigned char verbosity; unsigned char pad_0[2]; unsigned int tests_run; /* total test count */ /* currently running test suite */ greatest_suite_info suite; /* overall pass/fail/skip counts */ unsigned int passed; unsigned int failed; unsigned int skipped; unsigned int assertions; /* info to print about the most recent failure */ unsigned int fail_line; unsigned int pad_1; const char *fail_file; const char *msg; /* current setup/teardown hooks and userdata */ greatest_setup_cb *setup; void *setup_udata; greatest_teardown_cb *teardown; void *teardown_udata; /* formatting info for ".....s...F"-style output */ unsigned int col; unsigned int width; /* only run a specific suite or test */ const char *suite_filter; const char *test_filter; const char *test_exclude; const char *name_suffix; /* print suffix with test name */ char name_buf[GREATEST_TESTNAME_BUF_SIZE]; struct greatest_prng prng[2]; /* 0: suites, 1: tests */ #if GREATEST_USE_TIME /* overall timers */ clock_t begin; clock_t end; #endif #if GREATEST_USE_LONGJMP int pad_jmp_buf; unsigned char pad_2[4]; jmp_buf jump_dest; #endif } greatest_run_info; struct greatest_report_t { /* overall pass/fail/skip counts */ unsigned int passed; unsigned int failed; unsigned int skipped; unsigned int assertions; }; /* Global var for the current testing context. * Initialized by GREATEST_MAIN_DEFS(). */ extern greatest_run_info greatest_info; /* Type for ASSERT_ENUM_EQ's ENUM_STR argument. */ typedef const char *greatest_enum_str_fun(int value); /********************** * Exported functions * **********************/ /* These are used internally by greatest macros. */ int greatest_test_pre(const char *name); void greatest_test_post(int res); int greatest_do_assert_equal_t(const void *exp, const void *got, greatest_type_info *type_info, void *udata); void greatest_prng_init_first_pass(int id); int greatest_prng_init_second_pass(int id, unsigned long seed); void greatest_prng_step(int id); /* These are part of the public greatest API. */ void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata); void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata); void GREATEST_INIT(void); void GREATEST_PRINT_REPORT(void); int greatest_all_passed(void); void greatest_set_suite_filter(const char *filter); void greatest_set_test_filter(const char *filter); void greatest_set_test_exclude(const char *filter); void greatest_stop_at_first_fail(void); void greatest_abort_on_fail(void); void greatest_list_only(void); void greatest_get_report(struct greatest_report_t *report); unsigned int greatest_get_verbosity(void); void greatest_set_verbosity(unsigned int verbosity); void greatest_set_flag(greatest_flag_t flag); void greatest_set_test_suffix(const char *suffix); /******************** * Language Support * ********************/ /* If __VA_ARGS__ (C99) is supported, allow parametric testing * without needing to manually manage the argument struct. */ #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 19901L) || \ (defined(_MSC_VER) && _MSC_VER >= 1800) #define GREATEST_VA_ARGS #endif /********** * Macros * **********/ /* Define a suite. */ #define GREATEST_SUITE(NAME) void NAME(void); void NAME(void) /* Declare a suite, provided by another compilation unit. */ #define GREATEST_SUITE_EXTERN(NAME) void NAME(void) /* Start defining a test function. * The arguments are not included, to allow parametric testing. */ #define GREATEST_TEST static enum greatest_test_res /* PASS/FAIL/SKIP result from a test. Used internally. */ typedef enum greatest_test_res { GREATEST_TEST_RES_PASS = 0, GREATEST_TEST_RES_FAIL = -1, GREATEST_TEST_RES_SKIP = 1 } greatest_test_res; /* Run a suite. */ #define GREATEST_RUN_SUITE(S_NAME) greatest_run_suite(S_NAME, #S_NAME) /* Run a test in the current suite. */ #define GREATEST_RUN_TEST(TEST) \ do { \ if (greatest_test_pre(#TEST) == 1) { \ enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ if (res == GREATEST_TEST_RES_PASS) { \ res = TEST(); \ } \ greatest_test_post(res); \ } \ } while (0) /* Ignore a test, don't warn about it being unused. */ #define GREATEST_IGNORE_TEST(TEST) (void)TEST /* Run a test in the current suite with one void * argument, * which can be a pointer to a struct with multiple arguments. */ #define GREATEST_RUN_TEST1(TEST, ENV) \ do { \ if (greatest_test_pre(#TEST) == 1) { \ enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ if (res == GREATEST_TEST_RES_PASS) { \ res = TEST(ENV); \ } \ greatest_test_post(res); \ } \ } while (0) #ifdef GREATEST_VA_ARGS #define GREATEST_RUN_TESTp(TEST, ...) \ do { \ if (greatest_test_pre(#TEST) == 1) { \ enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ if (res == GREATEST_TEST_RES_PASS) { \ res = TEST(__VA_ARGS__); \ } \ greatest_test_post(res); \ } \ } while (0) #endif /* Check if the test runner is in verbose mode. */ #define GREATEST_IS_VERBOSE() ((greatest_info.verbosity) > 0) #define GREATEST_LIST_ONLY() \ (greatest_info.flags & GREATEST_FLAG_LIST_ONLY) #define GREATEST_FIRST_FAIL() \ (greatest_info.flags & GREATEST_FLAG_FIRST_FAIL) #define GREATEST_ABORT_ON_FAIL() \ (greatest_info.flags & GREATEST_FLAG_ABORT_ON_FAIL) #define GREATEST_FAILURE_ABORT() \ (GREATEST_FIRST_FAIL() && \ (greatest_info.suite.failed > 0 || greatest_info.failed > 0)) /* Message-less forms of tests defined below. */ #define GREATEST_PASS() GREATEST_PASSm(NULL) #define GREATEST_FAIL() GREATEST_FAILm(NULL) #define GREATEST_SKIP() GREATEST_SKIPm(NULL) #define GREATEST_ASSERT(COND) \ GREATEST_ASSERTm(#COND, COND) #define GREATEST_ASSERT_OR_LONGJMP(COND) \ GREATEST_ASSERT_OR_LONGJMPm(#COND, COND) #define GREATEST_ASSERT_FALSE(COND) \ GREATEST_ASSERT_FALSEm(#COND, COND) #define GREATEST_ASSERT_EQ(EXP, GOT) \ GREATEST_ASSERT_EQm(#EXP " != " #GOT, EXP, GOT) #define GREATEST_ASSERT_EQ_FMT(EXP, GOT, FMT) \ GREATEST_ASSERT_EQ_FMTm(#EXP " != " #GOT, EXP, GOT, FMT) #define GREATEST_ASSERT_IN_RANGE(EXP, GOT, TOL) \ GREATEST_ASSERT_IN_RANGEm(#EXP " != " #GOT " +/- " #TOL, EXP, GOT, TOL) #define GREATEST_ASSERT_EQUAL_T(EXP, GOT, TYPE_INFO, UDATA) \ GREATEST_ASSERT_EQUAL_Tm(#EXP " != " #GOT, EXP, GOT, TYPE_INFO, UDATA) #define GREATEST_ASSERT_STR_EQ(EXP, GOT) \ GREATEST_ASSERT_STR_EQm(#EXP " != " #GOT, EXP, GOT) #define GREATEST_ASSERT_STRN_EQ(EXP, GOT, SIZE) \ GREATEST_ASSERT_STRN_EQm(#EXP " != " #GOT, EXP, GOT, SIZE) #define GREATEST_ASSERT_MEM_EQ(EXP, GOT, SIZE) \ GREATEST_ASSERT_MEM_EQm(#EXP " != " #GOT, EXP, GOT, SIZE) #define GREATEST_ASSERT_ENUM_EQ(EXP, GOT, ENUM_STR) \ GREATEST_ASSERT_ENUM_EQm(#EXP " != " #GOT, EXP, GOT, ENUM_STR) /* The following forms take an additional message argument first, * to be displayed by the test runner. */ /* Fail if a condition is not true, with message. */ #define GREATEST_ASSERTm(MSG, COND) \ do { \ greatest_info.assertions++; \ if (!(COND)) { GREATEST_FAILm(MSG); } \ } while (0) /* Fail if a condition is not true, longjmping out of test. */ #define GREATEST_ASSERT_OR_LONGJMPm(MSG, COND) \ do { \ greatest_info.assertions++; \ if (!(COND)) { GREATEST_FAIL_WITH_LONGJMPm(MSG); } \ } while (0) /* Fail if a condition is not false, with message. */ #define GREATEST_ASSERT_FALSEm(MSG, COND) \ do { \ greatest_info.assertions++; \ if ((COND)) { GREATEST_FAILm(MSG); } \ } while (0) /* Fail if EXP != GOT (equality comparison by ==). */ #define GREATEST_ASSERT_EQm(MSG, EXP, GOT) \ do { \ greatest_info.assertions++; \ if ((EXP) != (GOT)) { GREATEST_FAILm(MSG); } \ } while (0) /* Fail if EXP != GOT (equality comparison by ==). * Warning: FMT, EXP, and GOT will be evaluated more * than once on failure. */ #define GREATEST_ASSERT_EQ_FMTm(MSG, EXP, GOT, FMT) \ do { \ greatest_info.assertions++; \ if ((EXP) != (GOT)) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \ GREATEST_FPRINTF(GREATEST_STDOUT, FMT, EXP); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \ GREATEST_FPRINTF(GREATEST_STDOUT, FMT, GOT); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ GREATEST_FAILm(MSG); \ } \ } while (0) /* Fail if EXP is not equal to GOT, printing enum IDs. */ #define GREATEST_ASSERT_ENUM_EQm(MSG, EXP, GOT, ENUM_STR) \ do { \ int greatest_EXP = (int)(EXP); \ int greatest_GOT = (int)(GOT); \ greatest_enum_str_fun *greatest_ENUM_STR = ENUM_STR; \ if (greatest_EXP != greatest_GOT) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: %s", \ greatest_ENUM_STR(greatest_EXP)); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: %s\n", \ greatest_ENUM_STR(greatest_GOT)); \ GREATEST_FAILm(MSG); \ } \ } while (0) \ /* Fail if GOT not in range of EXP +|- TOL. */ #define GREATEST_ASSERT_IN_RANGEm(MSG, EXP, GOT, TOL) \ do { \ GREATEST_FLOAT greatest_EXP = (EXP); \ GREATEST_FLOAT greatest_GOT = (GOT); \ GREATEST_FLOAT greatest_TOL = (TOL); \ greatest_info.assertions++; \ if ((greatest_EXP > greatest_GOT && \ greatest_EXP - greatest_GOT > greatest_TOL) || \ (greatest_EXP < greatest_GOT && \ greatest_GOT - greatest_EXP > greatest_TOL)) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "\nExpected: " GREATEST_FLOAT_FMT \ " +/- " GREATEST_FLOAT_FMT \ "\n Got: " GREATEST_FLOAT_FMT \ "\n", \ greatest_EXP, greatest_TOL, greatest_GOT); \ GREATEST_FAILm(MSG); \ } \ } while (0) /* Fail if EXP is not equal to GOT, according to strcmp. */ #define GREATEST_ASSERT_STR_EQm(MSG, EXP, GOT) \ do { \ GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \ &greatest_type_info_string, NULL); \ } while (0) \ /* Fail if EXP is not equal to GOT, according to strcmp. */ #define GREATEST_ASSERT_STRN_EQm(MSG, EXP, GOT, SIZE) \ do { \ size_t size = SIZE; \ GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \ &greatest_type_info_string, &size); \ } while (0) \ /* Fail if EXP is not equal to GOT, according to memcmp. */ #define GREATEST_ASSERT_MEM_EQm(MSG, EXP, GOT, SIZE) \ do { \ greatest_memory_cmp_env env; \ env.exp = (const unsigned char *)EXP; \ env.got = (const unsigned char *)GOT; \ env.size = SIZE; \ GREATEST_ASSERT_EQUAL_Tm(MSG, env.exp, env.got, \ &greatest_type_info_memory, &env); \ } while (0) \ /* Fail if EXP is not equal to GOT, according to a comparison * callback in TYPE_INFO. If they are not equal, optionally use a * print callback in TYPE_INFO to print them. */ #define GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, TYPE_INFO, UDATA) \ do { \ greatest_type_info *type_info = (TYPE_INFO); \ greatest_info.assertions++; \ if (!greatest_do_assert_equal_t(EXP, GOT, \ type_info, UDATA)) { \ if (type_info == NULL || type_info->equal == NULL) { \ GREATEST_FAILm("type_info->equal callback missing!"); \ } else { \ GREATEST_FAILm(MSG); \ } \ } \ } while (0) \ /* Pass. */ #define GREATEST_PASSm(MSG) \ do { \ greatest_info.msg = MSG; \ return GREATEST_TEST_RES_PASS; \ } while (0) /* Fail. */ #define GREATEST_FAILm(MSG) \ do { \ greatest_info.fail_file = __FILE__; \ greatest_info.fail_line = __LINE__; \ greatest_info.msg = MSG; \ if (GREATEST_ABORT_ON_FAIL()) { abort(); } \ return GREATEST_TEST_RES_FAIL; \ } while (0) /* Optional GREATEST_FAILm variant that longjmps. */ #if GREATEST_USE_LONGJMP #define GREATEST_FAIL_WITH_LONGJMP() GREATEST_FAIL_WITH_LONGJMPm(NULL) #define GREATEST_FAIL_WITH_LONGJMPm(MSG) \ do { \ greatest_info.fail_file = __FILE__; \ greatest_info.fail_line = __LINE__; \ greatest_info.msg = MSG; \ longjmp(greatest_info.jump_dest, GREATEST_TEST_RES_FAIL); \ } while (0) #endif /* Skip the current test. */ #define GREATEST_SKIPm(MSG) \ do { \ greatest_info.msg = MSG; \ return GREATEST_TEST_RES_SKIP; \ } while (0) /* Check the result of a subfunction using ASSERT, etc. */ #define GREATEST_CHECK_CALL(RES) \ do { \ enum greatest_test_res greatest_RES = RES; \ if (greatest_RES != GREATEST_TEST_RES_PASS) { \ return greatest_RES; \ } \ } while (0) \ #if GREATEST_USE_TIME #define GREATEST_SET_TIME(NAME) \ NAME = clock(); \ if (NAME == (clock_t) -1) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "clock error: %s\n", #NAME); \ exit(EXIT_FAILURE); \ } #define GREATEST_CLOCK_DIFF(C1, C2) \ GREATEST_FPRINTF(GREATEST_STDOUT, " (%lu ticks, %.3f sec)", \ (long unsigned int) (C2) - (long unsigned int)(C1), \ (double)((C2) - (C1)) / (1.0 * (double)CLOCKS_PER_SEC)) #else #define GREATEST_SET_TIME(UNUSED) #define GREATEST_CLOCK_DIFF(UNUSED1, UNUSED2) #endif #if GREATEST_USE_LONGJMP #define GREATEST_SAVE_CONTEXT() \ /* setjmp returns 0 (GREATEST_TEST_RES_PASS) on first call * \ * so the test runs, then RES_FAIL from FAIL_WITH_LONGJMP. */ \ ((enum greatest_test_res)(setjmp(greatest_info.jump_dest))) #else #define GREATEST_SAVE_CONTEXT() \ /*a no-op, since setjmp/longjmp aren't being used */ \ GREATEST_TEST_RES_PASS #endif /* Run every suite / test function run within BODY in pseudo-random * order, seeded by SEED. (The top 3 bits of the seed are ignored.) * * This should be called like: * GREATEST_SHUFFLE_TESTS(seed, { * GREATEST_RUN_TEST(some_test); * GREATEST_RUN_TEST(some_other_test); * GREATEST_RUN_TEST(yet_another_test); * }); * * Note that the body of the second argument will be evaluated * multiple times. */ #define GREATEST_SHUFFLE_SUITES(SD, BODY) GREATEST_SHUFFLE(0, SD, BODY) #define GREATEST_SHUFFLE_TESTS(SD, BODY) GREATEST_SHUFFLE(1, SD, BODY) #define GREATEST_SHUFFLE(ID, SD, BODY) \ do { \ struct greatest_prng *prng = &greatest_info.prng[ID]; \ greatest_prng_init_first_pass(ID); \ do { \ prng->count = 0; \ if (prng->initialized) { greatest_prng_step(ID); } \ BODY; \ if (!prng->initialized) { \ if (!greatest_prng_init_second_pass(ID, SD)) { break; } \ } else if (prng->count_run == prng->count_ceil) { \ break; \ } \ } while (!GREATEST_FAILURE_ABORT()); \ prng->count_run = prng->random_order = prng->initialized = 0; \ } while(0) /* Include several function definitions in the main test file. */ #define GREATEST_MAIN_DEFS() \ \ /* Is FILTER a subset of NAME? */ \ static int greatest_name_match(const char *name, const char *filter, \ int res_if_none) { \ size_t offset = 0; \ size_t filter_len = filter ? strlen(filter) : 0; \ if (filter_len == 0) { return res_if_none; } /* no filter */ \ while (name[offset] != '\0') { \ if (name[offset] == filter[0]) { \ if (0 == strncmp(&name[offset], filter, filter_len)) { \ return 1; \ } \ } \ offset++; \ } \ \ return 0; \ } \ \ static void greatest_buffer_test_name(const char *name) { \ struct greatest_run_info *g = &greatest_info; \ size_t len = strlen(name), size = sizeof(g->name_buf); \ memset(g->name_buf, 0x00, size); \ (void)strncat(g->name_buf, name, size - 1); \ if (g->name_suffix && (len + 1 < size)) { \ g->name_buf[len] = '_'; \ strncat(&g->name_buf[len+1], g->name_suffix, size-(len+2)); \ } \ } \ \ /* Before running a test, check the name filtering and \ * test shuffling state, if applicable, and then call setup hooks. */ \ int greatest_test_pre(const char *name) { \ struct greatest_run_info *g = &greatest_info; \ int match; \ greatest_buffer_test_name(name); \ match = greatest_name_match(g->name_buf, g->test_filter, 1) && \ !greatest_name_match(g->name_buf, g->test_exclude, 0); \ if (GREATEST_LIST_ONLY()) { /* just listing test names */ \ if (match) { \ fprintf(GREATEST_STDOUT, " %s\n", g->name_buf); \ } \ goto clear; \ } \ if (match && (!GREATEST_FIRST_FAIL() || g->suite.failed == 0)) { \ struct greatest_prng *p = &g->prng[1]; \ if (p->random_order) { \ p->count++; \ if (!p->initialized || ((p->count - 1) != p->state)) { \ goto clear; /* don't run this test yet */ \ } \ } \ GREATEST_SET_TIME(g->suite.pre_test); \ if (g->setup) { g->setup(g->setup_udata); } \ p->count_run++; \ return 1; /* test should be run */ \ } else { \ goto clear; /* skipped */ \ } \ clear: \ g->name_suffix = NULL; \ return 0; \ } \ \ static void greatest_do_pass(void) { \ struct greatest_run_info *g = &greatest_info; \ if (GREATEST_IS_VERBOSE()) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "PASS %s: %s", \ g->name_buf, g->msg ? g->msg : ""); \ } else { \ GREATEST_FPRINTF(GREATEST_STDOUT, "."); \ } \ g->suite.passed++; \ } \ \ static void greatest_do_fail(void) { \ struct greatest_run_info *g = &greatest_info; \ if (GREATEST_IS_VERBOSE()) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "FAIL %s: %s (%s:%u)", g->name_buf, \ g->msg ? g->msg : "", g->fail_file, g->fail_line); \ } else { \ GREATEST_FPRINTF(GREATEST_STDOUT, "F"); \ g->col++; /* add linebreak if in line of '.'s */ \ if (g->col != 0) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ g->col = 0; \ } \ GREATEST_FPRINTF(GREATEST_STDOUT, "FAIL %s: %s (%s:%u)\n", \ g->name_buf, g->msg ? g->msg : "", \ g->fail_file, g->fail_line); \ } \ g->suite.failed++; \ } \ \ static void greatest_do_skip(void) { \ struct greatest_run_info *g = &greatest_info; \ if (GREATEST_IS_VERBOSE()) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "SKIP %s: %s", \ g->name_buf, g->msg ? g->msg : ""); \ } else { \ GREATEST_FPRINTF(GREATEST_STDOUT, "s"); \ } \ g->suite.skipped++; \ } \ \ void greatest_test_post(int res) { \ GREATEST_SET_TIME(greatest_info.suite.post_test); \ if (greatest_info.teardown) { \ void *udata = greatest_info.teardown_udata; \ greatest_info.teardown(udata); \ } \ \ if (res <= GREATEST_TEST_RES_FAIL) { \ greatest_do_fail(); \ } else if (res >= GREATEST_TEST_RES_SKIP) { \ greatest_do_skip(); \ } else if (res == GREATEST_TEST_RES_PASS) { \ greatest_do_pass(); \ } \ greatest_info.name_suffix = NULL; \ greatest_info.suite.tests_run++; \ greatest_info.col++; \ if (GREATEST_IS_VERBOSE()) { \ GREATEST_CLOCK_DIFF(greatest_info.suite.pre_test, \ greatest_info.suite.post_test); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ } else if (greatest_info.col % greatest_info.width == 0) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ greatest_info.col = 0; \ } \ fflush(GREATEST_STDOUT); \ } \ \ static void report_suite(void) { \ if (greatest_info.suite.tests_run > 0) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "\n%u test%s - %u passed, %u failed, %u skipped", \ greatest_info.suite.tests_run, \ greatest_info.suite.tests_run == 1 ? "" : "s", \ greatest_info.suite.passed, \ greatest_info.suite.failed, \ greatest_info.suite.skipped); \ GREATEST_CLOCK_DIFF(greatest_info.suite.pre_suite, \ greatest_info.suite.post_suite); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ } \ } \ \ static void update_counts_and_reset_suite(void) { \ greatest_info.setup = NULL; \ greatest_info.setup_udata = NULL; \ greatest_info.teardown = NULL; \ greatest_info.teardown_udata = NULL; \ greatest_info.passed += greatest_info.suite.passed; \ greatest_info.failed += greatest_info.suite.failed; \ greatest_info.skipped += greatest_info.suite.skipped; \ greatest_info.tests_run += greatest_info.suite.tests_run; \ memset(&greatest_info.suite, 0, sizeof(greatest_info.suite)); \ greatest_info.col = 0; \ } \ \ static int greatest_suite_pre(const char *suite_name) { \ struct greatest_prng *p = &greatest_info.prng[0]; \ if (!greatest_name_match(suite_name, greatest_info.suite_filter, 1) \ || (GREATEST_FIRST_FAIL() && greatest_info.failed > 0)) { \ return 0; \ } \ if (p->random_order) { \ p->count++; \ if (!p->initialized || ((p->count - 1) != p->state)) { \ return 0; /* don't run this suite yet */ \ } \ } \ p->count_run++; \ update_counts_and_reset_suite(); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n* Suite %s:\n", suite_name); \ GREATEST_SET_TIME(greatest_info.suite.pre_suite); \ return 1; \ } \ \ static void greatest_suite_post(void) { \ GREATEST_SET_TIME(greatest_info.suite.post_suite); \ report_suite(); \ } \ \ static void greatest_run_suite(greatest_suite_cb *suite_cb, \ const char *suite_name) { \ if (greatest_suite_pre(suite_name)) { \ suite_cb(); \ greatest_suite_post(); \ } \ } \ \ int greatest_do_assert_equal_t(const void *exp, const void *got, \ greatest_type_info *type_info, void *udata) { \ int eq = 0; \ if (type_info == NULL || type_info->equal == NULL) { \ return 0; \ } \ eq = type_info->equal(exp, got, udata); \ if (!eq) { \ if (type_info->print != NULL) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \ (void)type_info->print(exp, udata); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \ (void)type_info->print(got, udata); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ } \ } \ return eq; \ } \ \ static void greatest_usage(const char *name) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "Usage: %s [--help] [-hlfav] [-s SUITE] [-t TEST]\n" \ " -h, --help print this Help\n" \ " -l List suites and tests, then exit (dry run)\n" \ " -f Stop runner after first failure\n" \ " -a Abort on first failure (implies -f)\n" \ " -v Verbose output\n" \ " -s SUITE only run suites containing string SUITE\n" \ " -t TEST only run tests containing string TEST\n" \ " -x EXCLUDE exclude tests containing string EXCLUDE\n", \ name); \ } \ \ static void greatest_parse_options(int argc, char **argv) { \ int i = 0; \ for (i = 1; i < argc; i++) { \ if (argv[i][0] == '-') { \ char f = argv[i][1]; \ if ((f == 's' || f == 't' || f == 'x') && argc <= i + 1) { \ greatest_usage(argv[0]); exit(EXIT_FAILURE); \ } \ switch (f) { \ case 's': /* suite name filter */ \ greatest_set_suite_filter(argv[i + 1]); i++; break; \ case 't': /* test name filter */ \ greatest_set_test_filter(argv[i + 1]); i++; break; \ case 'x': /* test name exclusion */ \ greatest_set_test_exclude(argv[i + 1]); i++; break; \ case 'f': /* first fail flag */ \ greatest_stop_at_first_fail(); break; \ case 'a': /* abort() on fail flag */ \ greatest_abort_on_fail(); break; \ case 'l': /* list only (dry run) */ \ greatest_list_only(); break; \ case 'v': /* first fail flag */ \ greatest_info.verbosity++; break; \ case 'h': /* help */ \ greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ case '-': \ if (0 == strncmp("--help", argv[i], 6)) { \ greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ } else if (0 == strncmp("--", argv[i], 2)) { \ return; /* ignore following arguments */ \ } /* fall through */ \ default: \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "Unknown argument '%s'\n", argv[i]); \ greatest_usage(argv[0]); \ exit(EXIT_FAILURE); \ } \ } \ } \ } \ \ int greatest_all_passed(void) { return (greatest_info.failed == 0); } \ \ void greatest_set_test_filter(const char *filter) { \ greatest_info.test_filter = filter; \ } \ \ void greatest_set_test_exclude(const char *filter) { \ greatest_info.test_exclude = filter; \ } \ \ void greatest_set_suite_filter(const char *filter) { \ greatest_info.suite_filter = filter; \ } \ \ void greatest_stop_at_first_fail(void) { \ greatest_set_flag(GREATEST_FLAG_FIRST_FAIL); \ } \ \ void greatest_abort_on_fail(void) { \ greatest_set_flag(GREATEST_FLAG_ABORT_ON_FAIL); \ } \ \ void greatest_list_only(void) { \ greatest_set_flag(GREATEST_FLAG_LIST_ONLY); \ } \ \ void greatest_get_report(struct greatest_report_t *report) { \ if (report) { \ report->passed = greatest_info.passed; \ report->failed = greatest_info.failed; \ report->skipped = greatest_info.skipped; \ report->assertions = greatest_info.assertions; \ } \ } \ \ unsigned int greatest_get_verbosity(void) { \ return greatest_info.verbosity; \ } \ \ void greatest_set_verbosity(unsigned int verbosity) { \ greatest_info.verbosity = (unsigned char)verbosity; \ } \ \ void greatest_set_flag(greatest_flag_t flag) { \ greatest_info.flags |= flag; \ } \ \ void greatest_set_test_suffix(const char *suffix) { \ greatest_info.name_suffix = suffix; \ } \ \ void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata) { \ greatest_info.setup = cb; \ greatest_info.setup_udata = udata; \ } \ \ void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, \ void *udata) { \ greatest_info.teardown = cb; \ greatest_info.teardown_udata = udata; \ } \ \ static int greatest_string_equal_cb(const void *exp, const void *got, \ void *udata) { \ size_t *size = (size_t *)udata; \ return (size != NULL \ ? (0 == strncmp((const char *)exp, (const char *)got, *size)) \ : (0 == strcmp((const char *)exp, (const char *)got))); \ } \ \ static int greatest_string_printf_cb(const void *t, void *udata) { \ (void)udata; /* note: does not check \0 termination. */ \ return GREATEST_FPRINTF(GREATEST_STDOUT, "%s", (const char *)t); \ } \ \ greatest_type_info greatest_type_info_string = { \ greatest_string_equal_cb, \ greatest_string_printf_cb, \ }; \ \ static int greatest_memory_equal_cb(const void *exp, const void *got, \ void *udata) { \ greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \ return (0 == memcmp(exp, got, env->size)); \ } \ \ /* Hexdump raw memory, with differences highlighted */ \ static int greatest_memory_printf_cb(const void *t, void *udata) { \ greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \ const unsigned char *buf = (const unsigned char *)t; \ unsigned char diff_mark = ' '; \ FILE *out = GREATEST_STDOUT; \ size_t i, line_i, line_len = 0; \ int len = 0; /* format hexdump with differences highlighted */ \ for (i = 0; i < env->size; i+= line_len) { \ diff_mark = ' '; \ line_len = env->size - i; \ if (line_len > 16) { line_len = 16; } \ for (line_i = i; line_i < i + line_len; line_i++) { \ if (env->exp[line_i] != env->got[line_i]) diff_mark = 'X'; \ } \ len += GREATEST_FPRINTF(out, "\n%04x %c ", \ (unsigned int)i, diff_mark); \ for (line_i = i; line_i < i + line_len; line_i++) { \ int m = env->exp[line_i] == env->got[line_i]; /* match? */ \ len += GREATEST_FPRINTF(out, "%02x%c", \ buf[line_i], m ? ' ' : '<'); \ } \ for (line_i = 0; line_i < 16 - line_len; line_i++) { \ len += GREATEST_FPRINTF(out, " "); \ } \ GREATEST_FPRINTF(out, " "); \ for (line_i = i; line_i < i + line_len; line_i++) { \ unsigned char c = buf[line_i]; \ len += GREATEST_FPRINTF(out, "%c", isprint(c) ? c : '.'); \ } \ } \ len += GREATEST_FPRINTF(out, "\n"); \ return len; \ } \ \ void greatest_prng_init_first_pass(int id) { \ greatest_info.prng[id].random_order = 1; \ greatest_info.prng[id].count_run = 0; \ } \ \ int greatest_prng_init_second_pass(int id, unsigned long seed) { \ static unsigned long primes[] = { 11, 101, 1009, 10007, \ 100003, 1000003, 10000019, 100000007, 1000000007, \ 1538461, 1865471, 17471, 2147483647 /* 2**32 - 1 */, }; \ struct greatest_prng *prng = &greatest_info.prng[id]; \ if (prng->count == 0) { return 0; } \ prng->mod = 1; \ prng->count_ceil = prng->count; \ while (prng->mod < prng->count) { prng->mod <<= 1; } \ prng->state = seed & 0x1fffffff; /* only use lower 29 bits... */ \ prng->a = (4LU * prng->state) + 1; /* to avoid overflow */ \ prng->c = primes[(seed * 16451) % sizeof(primes)/sizeof(primes[0])];\ prng->initialized = 1; \ return 1; \ } \ \ /* Step the pseudorandom number generator until its state reaches \ * another test ID between 0 and the test count. \ * This use a linear congruential pseudorandom number generator, \ * with the power-of-two ceiling of the test count as the modulus, the \ * masked seed as the multiplier, and a prime as the increment. For \ * each generated value < the test count, run the corresponding test. \ * This will visit all IDs 0 <= X < mod once before repeating, \ * with a starting position chosen based on the initial seed. \ * For details, see: Knuth, The Art of Computer Programming \ * Volume. 2, section 3.2.1. */ \ void greatest_prng_step(int id) { \ struct greatest_prng *p = &greatest_info.prng[id]; \ do { \ p->state = ((p->a * p->state) + p->c) & (p->mod - 1); \ } while (p->state >= p->count_ceil); \ } \ \ void GREATEST_INIT(void) { \ /* Suppress unused function warning if features aren't used */ \ (void)greatest_run_suite; \ (void)greatest_parse_options; \ (void)greatest_prng_step; \ (void)greatest_prng_init_first_pass; \ (void)greatest_prng_init_second_pass; \ (void)greatest_set_test_suffix; \ \ memset(&greatest_info, 0, sizeof(greatest_info)); \ greatest_info.width = GREATEST_DEFAULT_WIDTH; \ GREATEST_SET_TIME(greatest_info.begin); \ } \ \ /* Report passes, failures, skipped tests, the number of \ * assertions, and the overall run time. */ \ void GREATEST_PRINT_REPORT(void) { \ if (!GREATEST_LIST_ONLY()) { \ update_counts_and_reset_suite(); \ GREATEST_SET_TIME(greatest_info.end); \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "\nTotal: %u test%s", \ greatest_info.tests_run, \ greatest_info.tests_run == 1 ? "" : "s"); \ GREATEST_CLOCK_DIFF(greatest_info.begin, \ greatest_info.end); \ GREATEST_FPRINTF(GREATEST_STDOUT, ", %u assertion%s\n", \ greatest_info.assertions, \ greatest_info.assertions == 1 ? "" : "s"); \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "Pass: %u, fail: %u, skip: %u.\n", \ greatest_info.passed, \ greatest_info.failed, greatest_info.skipped); \ } \ } \ \ greatest_type_info greatest_type_info_memory = { \ greatest_memory_equal_cb, \ greatest_memory_printf_cb, \ }; \ \ greatest_run_info greatest_info /* Handle command-line arguments, etc. */ #define GREATEST_MAIN_BEGIN() \ do { \ GREATEST_INIT(); \ greatest_parse_options(argc, argv); \ } while (0) /* Report results, exit with exit status based on results. */ #define GREATEST_MAIN_END() \ do { \ GREATEST_PRINT_REPORT(); \ return (greatest_all_passed() ? EXIT_SUCCESS : EXIT_FAILURE); \ } while (0) /* Make abbreviations without the GREATEST_ prefix for the * most commonly used symbols. */ #if GREATEST_USE_ABBREVS #define TEST GREATEST_TEST #define SUITE GREATEST_SUITE #define SUITE_EXTERN GREATEST_SUITE_EXTERN #define RUN_TEST GREATEST_RUN_TEST #define RUN_TEST1 GREATEST_RUN_TEST1 #define RUN_SUITE GREATEST_RUN_SUITE #define IGNORE_TEST GREATEST_IGNORE_TEST #define ASSERT GREATEST_ASSERT #define ASSERTm GREATEST_ASSERTm #define ASSERT_FALSE GREATEST_ASSERT_FALSE #define ASSERT_EQ GREATEST_ASSERT_EQ #define ASSERT_EQ_FMT GREATEST_ASSERT_EQ_FMT #define ASSERT_IN_RANGE GREATEST_ASSERT_IN_RANGE #define ASSERT_EQUAL_T GREATEST_ASSERT_EQUAL_T #define ASSERT_STR_EQ GREATEST_ASSERT_STR_EQ #define ASSERT_STRN_EQ GREATEST_ASSERT_STRN_EQ #define ASSERT_MEM_EQ GREATEST_ASSERT_MEM_EQ #define ASSERT_ENUM_EQ GREATEST_ASSERT_ENUM_EQ #define ASSERT_FALSEm GREATEST_ASSERT_FALSEm #define ASSERT_EQm GREATEST_ASSERT_EQm #define ASSERT_EQ_FMTm GREATEST_ASSERT_EQ_FMTm #define ASSERT_IN_RANGEm GREATEST_ASSERT_IN_RANGEm #define ASSERT_EQUAL_Tm GREATEST_ASSERT_EQUAL_Tm #define ASSERT_STR_EQm GREATEST_ASSERT_STR_EQm #define ASSERT_STRN_EQm GREATEST_ASSERT_STRN_EQm #define ASSERT_MEM_EQm GREATEST_ASSERT_MEM_EQm #define ASSERT_ENUM_EQm GREATEST_ASSERT_ENUM_EQm #define PASS GREATEST_PASS #define FAIL GREATEST_FAIL #define SKIP GREATEST_SKIP #define PASSm GREATEST_PASSm #define FAILm GREATEST_FAILm #define SKIPm GREATEST_SKIPm #define SET_SETUP GREATEST_SET_SETUP_CB #define SET_TEARDOWN GREATEST_SET_TEARDOWN_CB #define CHECK_CALL GREATEST_CHECK_CALL #define SHUFFLE_TESTS GREATEST_SHUFFLE_TESTS #define SHUFFLE_SUITES GREATEST_SHUFFLE_SUITES #ifdef GREATEST_VA_ARGS #define RUN_TESTp GREATEST_RUN_TESTp #endif #if GREATEST_USE_LONGJMP #define ASSERT_OR_LONGJMP GREATEST_ASSERT_OR_LONGJMP #define ASSERT_OR_LONGJMPm GREATEST_ASSERT_OR_LONGJMPm #define FAIL_WITH_LONGJMP GREATEST_FAIL_WITH_LONGJMP #define FAIL_WITH_LONGJMPm GREATEST_FAIL_WITH_LONGJMPm #endif #endif /* USE_ABBREVS */ #if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS) } #endif #endif ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/helpers.c��������������������������������������������������������������������������0000664�0000000�0000000�00000002321�13706263046�0015444�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <gdk-pixbuf/gdk-pixbuf.h> #include "helpers.h" GVariant *notification_setup_raw_image(const char *path) { GdkPixbuf *pb = gdk_pixbuf_new_from_file(path, NULL); if (!pb) return NULL; GVariant *hint_data = g_variant_new_from_data( G_VARIANT_TYPE("ay"), gdk_pixbuf_read_pixels(pb), gdk_pixbuf_get_byte_length(pb), TRUE, (GDestroyNotify) g_object_unref, g_object_ref(pb)); GVariant *hint = g_variant_new( "(iiibii@ay)", gdk_pixbuf_get_width(pb), gdk_pixbuf_get_height(pb), gdk_pixbuf_get_rowstride(pb), gdk_pixbuf_get_has_alpha(pb), gdk_pixbuf_get_bits_per_sample(pb), gdk_pixbuf_get_n_channels(pb), hint_data); g_object_unref(pb); return hint; } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/helpers.h��������������������������������������������������������������������������0000664�0000000�0000000�00000000315�13706263046�0015452�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef DUNST_TEST_HELPERS_H #define DUNST_TEST_HELPERS_H #include <glib.h> GVariant *notification_setup_raw_image(const char *path); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/icon.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000014006�13706263046�0014735�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "../src/icon.c" #include "greatest.h" #define ICONPREFIX "/data/icons/path" /* As there are no hints to test if the loaded GdkPixbuf is * read from a PNG or an SVG file, the sample icons in the * test structure have different sizes */ #define IS_ICON_PNG(pb) 4 == gdk_pixbuf_get_width(pb) #define IS_ICON_SVG(pb) 16 == gdk_pixbuf_get_width(pb) extern const char *base; TEST test_get_pixbuf_from_file_tilde(void) { const char *home = g_get_home_dir(); const char *iconpath = ICONPREFIX; if (0 != strncmp(home, base, strlen(home))) { SKIPm("Current directory is not a subdirectory from user's home." " Cannot test iconpath tilde expansion.\n"); } gchar *path = g_build_filename(base, iconpath, "valid", "icon1.svg", NULL); path = string_replace_at(path, 0, strlen(home), "~"); GdkPixbuf *pixbuf = get_pixbuf_from_file(path); g_clear_pointer(&path, g_free); ASSERT(pixbuf); ASSERTm("The wrong pixbuf is loaded in the icon file.", IS_ICON_SVG(pixbuf)); g_clear_pointer(&pixbuf, g_object_unref); PASS(); } TEST test_get_pixbuf_from_file_absolute(void) { const char *iconpath = ICONPREFIX; gchar *path = g_build_filename(base, iconpath, "valid", "icon1.svg", NULL); GdkPixbuf *pixbuf = get_pixbuf_from_file(path); g_clear_pointer(&path, g_free); ASSERT(pixbuf); ASSERTm("The wrong pixbuf is loaded in the icon file.", IS_ICON_SVG(pixbuf)); g_clear_pointer(&pixbuf, g_object_unref); PASS(); } TEST test_get_pixbuf_from_icon_invalid(void) { GdkPixbuf *pixbuf = get_pixbuf_from_icon("invalid"); ASSERT(pixbuf == NULL); g_clear_pointer(&pixbuf, g_object_unref); PASS(); } TEST test_get_pixbuf_from_icon_both(void) { GdkPixbuf *pixbuf = get_pixbuf_from_icon("icon1"); ASSERT(pixbuf); ASSERTm("SVG pixbuf hasn't precedence", IS_ICON_SVG(pixbuf)); g_clear_pointer(&pixbuf, g_object_unref); PASS(); } TEST test_get_pixbuf_from_icon_onlysvg(void) { GdkPixbuf *pixbuf = get_pixbuf_from_icon("onlysvg"); ASSERT(pixbuf); ASSERTm("SVG pixbuf isn't loaded", IS_ICON_SVG(pixbuf)); g_clear_pointer(&pixbuf, g_object_unref); PASS(); } TEST test_get_pixbuf_from_icon_onlypng(void) { GdkPixbuf *pixbuf = get_pixbuf_from_icon("onlypng"); ASSERT(pixbuf); ASSERTm("PNG pixbuf isn't loaded", IS_ICON_PNG(pixbuf)); g_clear_pointer(&pixbuf, g_object_unref); PASS(); } TEST test_get_pixbuf_from_icon_filename(void) { char *icon = g_strconcat(base, "/data/icons/valid.png", NULL); GdkPixbuf *pixbuf = get_pixbuf_from_icon(icon); ASSERT(pixbuf); ASSERTm("PNG pixbuf isn't loaded", IS_ICON_PNG(pixbuf)); g_clear_pointer(&pixbuf, g_object_unref); g_free(icon); PASS(); } TEST test_get_pixbuf_from_icon_fileuri(void) { char *icon = g_strconcat("file://", base, "/data/icons/valid.svg", NULL); GdkPixbuf *pixbuf = get_pixbuf_from_icon(icon); ASSERT(pixbuf); ASSERTm("SVG pixbuf isn't loaded", IS_ICON_SVG(pixbuf)); g_clear_pointer(&pixbuf, g_object_unref); g_free(icon); PASS(); } TEST test_icon_size_clamp_too_small(void) { int w = 12, h = 24; bool resized = icon_size_clamp(&w, &h); ASSERT(resized); ASSERT_EQ(w, 16); ASSERT_EQ(h, 32); PASS(); } TEST test_icon_size_clamp_not_necessary(void) { int w = 20, h = 30; bool resized = icon_size_clamp(&w, &h); ASSERT(!resized); ASSERT_EQ(w, 20); ASSERT_EQ(h, 30); PASS(); } TEST test_icon_size_clamp_too_big(void) { int w = 75, h = 150; bool resized = icon_size_clamp(&w, &h); ASSERT(resized); ASSERT_EQ(w, 50); ASSERT_EQ(h, 100); PASS(); } TEST test_icon_size_clamp_too_small_then_too_big(void) { int w = 8, h = 80; bool resized = icon_size_clamp(&w, &h); ASSERT(resized); ASSERT_EQ(w, 10); ASSERT_EQ(h, 100); PASS(); } TEST test_get_pixbuf_from_icon_both_is_scaled(void) { GdkPixbuf *pixbuf = get_pixbuf_from_icon("onlypng"); ASSERT(pixbuf); ASSERT_EQ(gdk_pixbuf_get_width(pixbuf), 16); ASSERT_EQ(gdk_pixbuf_get_height(pixbuf), 16); g_clear_pointer(&pixbuf, g_object_unref); PASS(); } SUITE(suite_icon) { settings.icon_path = g_strconcat( base, ICONPREFIX "/invalid" ":", base, ICONPREFIX "/valid" ":", base, ICONPREFIX "/both", NULL); RUN_TEST(test_get_pixbuf_from_file_tilde); RUN_TEST(test_get_pixbuf_from_file_absolute); RUN_TEST(test_get_pixbuf_from_icon_invalid); RUN_TEST(test_get_pixbuf_from_icon_both); RUN_TEST(test_get_pixbuf_from_icon_onlysvg); RUN_TEST(test_get_pixbuf_from_icon_onlypng); RUN_TEST(test_get_pixbuf_from_icon_filename); RUN_TEST(test_get_pixbuf_from_icon_fileuri); RUN_TEST(test_icon_size_clamp_not_necessary); settings.min_icon_size = 16; settings.max_icon_size = 100; RUN_TEST(test_get_pixbuf_from_icon_both_is_scaled); RUN_TEST(test_icon_size_clamp_too_small); RUN_TEST(test_icon_size_clamp_not_necessary); RUN_TEST(test_icon_size_clamp_too_big); RUN_TEST(test_icon_size_clamp_too_small_then_too_big); settings.min_icon_size = 16; settings.max_icon_size = 0; RUN_TEST(test_icon_size_clamp_too_small); RUN_TEST(test_icon_size_clamp_not_necessary); settings.min_icon_size = 0; settings.max_icon_size = 100; RUN_TEST(test_icon_size_clamp_not_necessary); RUN_TEST(test_icon_size_clamp_too_big); settings.min_icon_size = 0; settings.max_icon_size = 0; g_clear_pointer(&settings.icon_path, g_free); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/log.c������������������������������������������������������������������������������0000664�0000000�0000000�00000002163�13706263046�0014567�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "../src/log.c" #include "greatest.h" TEST test_log_level(GLogLevelFlags level, const char *shortstr, const char *longstr) { ASSERT_STR_EQ(log_level_to_string(level), longstr); log_set_level_from_string(shortstr); if (level != G_LOG_LEVEL_ERROR) ASSERT_ENUM_EQ(level, log_level, log_level_to_string); log_set_level_from_string(longstr); if (level != G_LOG_LEVEL_ERROR) ASSERT_ENUM_EQ(level, log_level, log_level_to_string); PASS(); } SUITE(suite_log) { GLogLevelFlags oldlevel = log_level; RUN_TESTp(test_log_level, G_LOG_LEVEL_ERROR, NULL, "ERROR"); RUN_TESTp(test_log_level, G_LOG_LEVEL_CRITICAL, "crit", "CRITICAL"); RUN_TESTp(test_log_level, G_LOG_LEVEL_WARNING, "warn", "WARNING"); RUN_TESTp(test_log_level, G_LOG_LEVEL_MESSAGE, "mesg", "MESSAGE"); RUN_TESTp(test_log_level, G_LOG_LEVEL_INFO, "info", "INFO"); RUN_TESTp(test_log_level, G_LOG_LEVEL_DEBUG, "deb", "DEBUG"); log_level = oldlevel; } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/markup.c���������������������������������������������������������������������������0000664�0000000�0000000�00000015571�13706263046�0015314�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "../src/markup.c" #include "greatest.h" TEST test_markup_strip(void) { char *ptr; ASSERT_STR_EQ(""", (ptr=markup_strip(g_strdup("&quot;")))); g_free(ptr); ASSERT_STR_EQ("'", (ptr=markup_strip(g_strdup("&apos;")))); g_free(ptr); ASSERT_STR_EQ("<", (ptr=markup_strip(g_strdup("&lt;")))); g_free(ptr); ASSERT_STR_EQ(">", (ptr=markup_strip(g_strdup("&gt;")))); g_free(ptr); ASSERT_STR_EQ("&", (ptr=markup_strip(g_strdup("&amp;")))); g_free(ptr); ASSERT_STR_EQ(">A ", (ptr=markup_strip(g_strdup(">A <img> <string")))); g_free(ptr); PASS(); } TEST test_markup_transform(void) { char *ptr; settings.ignore_newline = false; ASSERT_STR_EQ("<i>foo</i><br>bar\nbaz", (ptr=markup_transform(g_strdup("<i>foo</i><br>bar\nbaz"), MARKUP_NO))); g_free(ptr); ASSERT_STR_EQ("foo\nbar\nbaz", (ptr=markup_transform(g_strdup("<i>foo</i><br>bar\nbaz"), MARKUP_STRIP))); g_free(ptr); ASSERT_STR_EQ("<i>foo</i>\nbar\nbaz", (ptr=markup_transform(g_strdup("<i>foo</i><br>bar\nbaz"), MARKUP_FULL))); g_free(ptr); settings.ignore_newline = true; ASSERT_STR_EQ("<i>foo</i><br>bar baz", (ptr=markup_transform(g_strdup("<i>foo</i><br>bar\nbaz"), MARKUP_NO))); g_free(ptr); ASSERT_STR_EQ("foo bar baz", (ptr=markup_transform(g_strdup("<i>foo</i><br>bar\nbaz"), MARKUP_STRIP))); g_free(ptr); ASSERT_STR_EQ("<i>foo</i> bar baz", (ptr=markup_transform(g_strdup("<i>foo</i><br>bar\nbaz"), MARKUP_FULL))); g_free(ptr); // Test replacement of img and a tags, not renderable by pango ASSERT_STR_EQ("foo bar bar baz", (ptr=markup_transform(g_strdup("<img alt=\"foo bar\"><br>bar\nbaz"), MARKUP_FULL))); g_free(ptr); ASSERT_STR_EQ("test ", (ptr=markup_transform(g_strdup("test <img alt=\"foo bar\""), MARKUP_FULL))); g_free(ptr); ASSERT_STR_EQ("test [image] image", (ptr=markup_transform(g_strdup("test <img src=\"nothing.jpg\"> image"), MARKUP_FULL))); g_free(ptr); ASSERT_STR_EQ("bar baz", (ptr=markup_transform(g_strdup("<a href=\"asdf\">bar</a> baz"), MARKUP_FULL))); g_free(ptr); ASSERT_STR_EQ("Ψ", (ptr=markup_transform(g_strdup("Ψ"), MARKUP_FULL))); free(ptr); ASSERT_STR_EQ("Ψ Ψ", (ptr=markup_transform(g_strdup("Ψ Ψ"), MARKUP_FULL))); free(ptr); ASSERT_STR_EQ("> <", (ptr=markup_transform(g_strdup("> <"), MARKUP_FULL))); free(ptr); ASSERT_STR_EQ("&invalid; &#abc; &#xG;", (ptr=markup_transform(g_strdup("&invalid; &#abc; &#xG;"), MARKUP_FULL))); free(ptr); ASSERT_STR_EQ("&; &#; &#x;", (ptr=markup_transform(g_strdup("&; &#; &#x;"), MARKUP_FULL))); free(ptr); PASS(); } TEST helper_markup_strip_a (const char *in, const char *exp, const char *urls) { // out_urls is a return parameter and the content should be ignored char *out_urls = (char *)0x04; //Chosen by a fair dice roll char *out = g_strdup(in); char *msg = g_strconcat("url: ", in, NULL); markup_strip_a(&out, &out_urls); ASSERT_STR_EQm(msg, exp, out); if (urls) { ASSERT_STR_EQm(msg, urls, out_urls); } else { ASSERT_EQm(msg, urls, out_urls); } g_free(out_urls); g_free(out); g_free(msg); PASS(); } TEST test_markup_strip_a(void) { RUN_TESTp(helper_markup_strip_a, "<a href=\"https://url.com\">valid</a> link", "valid link", "[valid] https://url.com"); RUN_TESTp(helper_markup_strip_a, "<a href=\"\">valid</a> link", "valid link", "[valid] "); RUN_TESTp(helper_markup_strip_a, "<a>valid</a> link", "valid link", NULL); RUN_TESTp(helper_markup_strip_a, "<a href=\"https://url.com\">valid link", "valid link", "[valid link] https://url.com"); RUN_TESTp(helper_markup_strip_a, "<a href=\"https://url.com\" invalid</a> link", " link", NULL); RUN_TESTp(helper_markup_strip_a, "<a invalid</a> link", " link", NULL); PASS(); } TEST helper_markup_strip_img (const char *in, const char *exp, const char *urls) { // out_urls is a return parameter and the content should be ignored char *out_urls = (char *)0x04; //Chosen by a fair dice roll char *out = g_strdup(in); char *msg = g_strconcat("url: ", in, NULL); markup_strip_img(&out, &out_urls); ASSERT_STR_EQm(msg, exp, out); if (urls) { ASSERT_STR_EQm(msg, urls, out_urls); } else { ASSERT_EQm(msg, urls, out_urls); } g_free(out_urls); g_free(out); g_free(msg); PASS(); } TEST test_markup_strip_img(void) { RUN_TESTp(helper_markup_strip_img, "v <img> img", "v [image] img", NULL); RUN_TESTp(helper_markup_strip_img, "v <img alt=\"valid\" alt=\"invalid\"> img", "v valid img", NULL); RUN_TESTp(helper_markup_strip_img, "v <img src=\"url.com\"> img", "v [image] img", "[image] url.com"); RUN_TESTp(helper_markup_strip_img, "v <img alt=\"valid\" src=\"url.com\"> img", "v valid img", "[valid] url.com"); RUN_TESTp(helper_markup_strip_img, "v <img src=\"url.com\" alt=\"valid\"> img", "v valid img", "[valid] url.com"); RUN_TESTp(helper_markup_strip_img, "v <img src=\"url.com\" alt=\"valid\" alt=\"i\"> img", "v valid img", "[valid] url.com"); RUN_TESTp(helper_markup_strip_img, "i <img alt=\"invalid src=\"https://url.com\"> img", "i [image] img", "[image] https://url.com"); RUN_TESTp(helper_markup_strip_img, "i <img alt=\"broken\" src=\"https://url.com > img", "i broken img", NULL); RUN_TESTp(helper_markup_strip_img, "i <img alt=\"invalid src=\"https://url.com > img", "i [image] img", NULL); RUN_TESTp(helper_markup_strip_img, "i <img src=\"url.com alt=\"broken\"> img", "i broken img", NULL); RUN_TESTp(helper_markup_strip_img, "i <img src=\"url.com\" alt=\"invalid > img", "i [image] img", "[image] url.com"); RUN_TESTp(helper_markup_strip_img, "i <img src=\"url.com alt=\"invalid > img", "i [image] img", NULL); RUN_TESTp(helper_markup_strip_img, "i <img src=\"url.com\" alt=\"invalid\" img", "i ", NULL); PASS(); } SUITE(suite_markup) { RUN_TEST(test_markup_strip); RUN_TEST(test_markup_strip_a); RUN_TEST(test_markup_strip_img); RUN_TEST(test_markup_transform); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ���������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/menu.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000004240�13706263046�0014750�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "../src/menu.c" #include "greatest.h" #include <glib.h> TEST test_extract_urls_from_empty_string(void) { char *urls = extract_urls(""); ASSERT_EQ_FMT(NULL, (void*)urls, "%p"); urls = extract_urls(NULL); ASSERT(!urls); PASS(); } TEST test_extract_urls_from_no_urls_string(void) { char *urls = extract_urls("You got a new message from your friend"); ASSERT(!urls); PASS(); } TEST test_extract_urls_from_one_url_string(void) { char *urls = extract_urls("Hi from https://www.example.com!"); ASSERT_STR_EQ("https://www.example.com", urls); g_free(urls); PASS(); } TEST test_extract_urls_from_two_url_string(void) { char *urls = extract_urls("Hi from https://www.example.com and ftp://www.example.com!"); ASSERT_STR_EQ("https://www.example.com\nftp://www.example.com", urls); g_free(urls); PASS(); } TEST test_extract_urls_from_one_url_port(void) { char *urls = extract_urls("Hi from https://www.example.com:8100 and have a nice day!"); ASSERT_STR_EQ("https://www.example.com:8100", urls); g_free(urls); PASS(); } TEST test_extract_urls_from_one_url_path(void) { char *urls = extract_urls("Hi from https://www.example.com:8100/testpath and have a nice day!"); ASSERT_STR_EQ("https://www.example.com:8100/testpath", urls); g_free(urls); PASS(); } TEST test_extract_urls_from_one_url_anchor(void) { char *urls = extract_urls("Hi from https://www.example.com:8100/testpath#anchor and have a nice day!"); ASSERT_STR_EQ("https://www.example.com:8100/testpath#anchor", urls); g_free(urls); PASS(); } SUITE(suite_menu) { RUN_TEST(test_extract_urls_from_empty_string); RUN_TEST(test_extract_urls_from_no_urls_string); RUN_TEST(test_extract_urls_from_one_url_string); RUN_TEST(test_extract_urls_from_two_url_string); RUN_TEST(test_extract_urls_from_one_url_port); RUN_TEST(test_extract_urls_from_one_url_path); RUN_TEST(test_extract_urls_from_one_url_anchor); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/misc.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000001035�13706263046�0014736�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "greatest.h" // This actually tests the buildsystem to make sure, // the build system hands over a correct version number // This is not testable via macros TEST assert_version_number(void) { ASSERTm("Version number is empty", 0 != strcmp(VERSION, "")); ASSERTm("Version number is not seeded by git", NULL == strstr(VERSION, "non-git")); PASS(); } SUITE(suite_misc) { RUN_TEST(assert_version_number); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/notification.c���������������������������������������������������������������������0000664�0000000�0000000�00000021063�13706263046�0016474�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "../src/notification.c" #include "greatest.h" #include "helpers.h" #include "../src/option_parser.h" #include "../src/settings.h" extern const char *base; TEST test_notification_is_duplicate_field(char **field, struct notification *a, struct notification *b) { ASSERT(notification_is_duplicate(a, b)); char *tmp = *field; (*field) = "Something different"; ASSERT_FALSE(notification_is_duplicate(a, b)); (*field) = tmp; PASS(); } TEST test_notification_is_duplicate(void) { struct notification *a = notification_create(); a->appname = g_strdup("Test"); a->summary = g_strdup("Summary"); a->body = g_strdup("Body"); a->iconname = g_strdup("Icon"); a->icon_id = g_strdup("Icon"); a->urgency = URG_NORM; struct notification *b = notification_create(); b->appname = g_strdup("Test"); b->summary = g_strdup("Summary"); b->body = g_strdup("Body"); b->iconname = g_strdup("Icon"); b->icon_id = g_strdup("Icon"); b->urgency = URG_NORM; ASSERT(notification_is_duplicate(a, b)); CHECK_CALL(test_notification_is_duplicate_field(&(b->appname), a, b)); CHECK_CALL(test_notification_is_duplicate_field(&(b->summary), a, b)); CHECK_CALL(test_notification_is_duplicate_field(&(b->body), a, b)); ASSERTm("One of the notifications got corrupted during test", notification_is_duplicate(a, b)); enum icon_position icon_setting_tmp = settings.icon_position; settings.icon_position = ICON_OFF; ASSERT(notification_is_duplicate(a, b)); //Setting pointer to a random value since we are checking for null char *icon_id = b->icon_id; b->icon_id = "false"; ASSERTm("Icons have to get ignored for duplicate check when icons are off", notification_is_duplicate(a, b)); b->icon_id = icon_id; settings.icon_position = ICON_LEFT; CHECK_CALL(test_notification_is_duplicate_field(&(b->icon_id), a, b)); settings.icon_position = ICON_RIGHT; CHECK_CALL(test_notification_is_duplicate_field(&(b->icon_id), a, b)); settings.icon_position = icon_setting_tmp; ASSERT(notification_is_duplicate(a, b)); b->urgency = URG_LOW; ASSERT_FALSE(notification_is_duplicate(a, b)); b->urgency = URG_NORM; ASSERT(notification_is_duplicate(a, b)); b->urgency = URG_CRIT; ASSERT_FALSE(notification_is_duplicate(a, b)); notification_unref(a); notification_unref(b); PASS(); } TEST test_notification_replace_single_field(void) { char *str = g_malloc(128 * sizeof(char)); char *substr = NULL; strcpy(str, "Markup %a preserved"); substr = strchr(str, '%'); notification_replace_single_field(&str, &substr, "and & <i>is</i>", MARKUP_FULL); ASSERT_STR_EQ("Markup and & <i>is</i> preserved", str); ASSERT_EQ(26, substr - str); strcpy(str, "Markup %a escaped"); substr = strchr(str, '%'); notification_replace_single_field(&str, &substr, "and & <i>is</i>", MARKUP_NO); ASSERT_STR_EQ("Markup and & <i>is</i> escaped", str); ASSERT_EQ(38, substr - str); strcpy(str, "Markup %a"); substr = strchr(str, '%'); notification_replace_single_field(&str, &substr, "<i>is removed</i> and & escaped", MARKUP_STRIP); ASSERT_STR_EQ("Markup is removed and & escaped", str); ASSERT_EQ(35, substr - str); g_free(str); PASS(); } TEST test_notification_referencing(void) { struct notification *n = notification_create(); ASSERT(notification_refcount_get(n) == 1); notification_ref(n); ASSERT(notification_refcount_get(n) == 2); notification_unref(n); ASSERT(notification_refcount_get(n) == 1); // Now we have to rely on valgrind to test, that // it gets actually freed notification_unref(n); PASS(); } static struct notification *notification_load_icon_with_scaling(int min_icon_size, int max_icon_size) { struct notification *n = notification_create(); char *path = g_strconcat(base, "/data/icons/valid.svg", NULL); // 16x16 GVariant *rawIcon = notification_setup_raw_image(path); settings.min_icon_size = min_icon_size; settings.max_icon_size = max_icon_size; notification_icon_replace_data(n, rawIcon); settings.min_icon_size = 0; settings.max_icon_size = 0; g_variant_unref(rawIcon); g_free(path); return n; } TEST test_notification_icon_scaling_toosmall(void) { struct notification *n = notification_load_icon_with_scaling(20, 100); ASSERT_EQ(gdk_pixbuf_get_width(n->icon), 20); ASSERT_EQ(gdk_pixbuf_get_height(n->icon), 20); notification_unref(n); PASS(); } TEST test_notification_icon_scaling_toolarge(void) { struct notification *n = notification_load_icon_with_scaling(5, 10); ASSERT_EQ(gdk_pixbuf_get_width(n->icon), 10); ASSERT_EQ(gdk_pixbuf_get_height(n->icon), 10); notification_unref(n); PASS(); } TEST test_notification_icon_scaling_notconfigured(void) { struct notification *n = notification_load_icon_with_scaling(0, 0); ASSERT_EQ(gdk_pixbuf_get_width(n->icon), 16); ASSERT_EQ(gdk_pixbuf_get_height(n->icon), 16); notification_unref(n); PASS(); } TEST test_notification_icon_scaling_notneeded(void) { struct notification *n = notification_load_icon_with_scaling(10, 20); ASSERT_EQ(gdk_pixbuf_get_width(n->icon), 16); ASSERT_EQ(gdk_pixbuf_get_height(n->icon), 16); notification_unref(n); PASS(); } TEST test_notification_format_message(struct notification *n, const char *format, const char *exp) { n->format = format; notification_format_message(n); ASSERT_STR_EQ(exp, n->msg); PASS(); } TEST test_notification_maxlength(void) { unsigned int len = 5005; struct notification *n = notification_create(); n->format = "%a"; n->appname = g_malloc(len + 1); n->appname[len] = '\0'; static const char sigma[] = " 0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"; for (int i = 0; i < len; ++i) n->appname[i] = sigma[rand() % (sizeof(sigma) - 1)]; notification_format_message(n); ASSERT(STRN_EQ(n->appname, n->msg, 5000)); notification_unref(n); PASS(); } SUITE(suite_notification) { cmdline_load(0, NULL); char *config_path = g_strconcat(base, "/data/dunstrc.default", NULL); load_settings(config_path); RUN_TEST(test_notification_is_duplicate); RUN_TEST(test_notification_replace_single_field); RUN_TEST(test_notification_referencing); RUN_TEST(test_notification_icon_scaling_toosmall); RUN_TEST(test_notification_icon_scaling_toolarge); RUN_TEST(test_notification_icon_scaling_notconfigured); RUN_TEST(test_notification_icon_scaling_notneeded); // TEST notification_format_message struct notification *a = notification_create(); a->appname = g_strdup("MyApp"); a->summary = g_strdup("I've got a summary!"); a->body = g_strdup("Look at my shiny <notification>"); a->iconname = g_strdup("/this/is/my/icoknpath.png"); a->progress = 95; const char *strings[] = { "%a", "MyApp", "%s", "I've got a summary!", "%b", "Look at my shiny <notification>", "%I", "icoknpath.png", "%i", "/this/is/my/icoknpath.png", "%p", "[ 95%]", "%n", "95", "%%", "%", "%", "%", "%UNKNOWN", "%UNKNOWN", NULL }; const char **in = strings; const char **out = strings+1; while (*in && *out) { RUN_TESTp(test_notification_format_message, a, *in, *out); in +=2; out+=2; } g_clear_pointer(&a, notification_unref); RUN_TEST(test_notification_maxlength); g_clear_pointer(&settings.icon_path, g_free); g_free(config_path); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/option_parser.c��������������������������������������������������������������������0000664�0000000�0000000�00000041271�13706263046�0016675�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "../src/option_parser.c" #include "greatest.h" extern const char *base; TEST test_next_section(void) { const char *section = NULL; ASSERT_STR_EQ("bool", (section = next_section(section))); ASSERT_STR_EQ("string", (section = next_section(section))); ASSERT_STR_EQ("list", (section = next_section(section))); ASSERT_STR_EQ("path", (section = next_section(section))); ASSERT_STR_EQ("int", (section = next_section(section))); ASSERT_STR_EQ("double", (section = next_section(section))); PASS(); } TEST test_ini_get_bool(void) { char *bool_section = "bool"; ASSERT(ini_get_bool(bool_section, "booltrue", false)); ASSERT(ini_get_bool(bool_section, "booltrue_capital", false)); ASSERT_FALSE(ini_get_bool(bool_section, "boolfalse", true)); ASSERT_FALSE(ini_get_bool(bool_section, "boolfalse_capital", true)); ASSERT(ini_get_bool(bool_section, "boolyes", false)); ASSERT(ini_get_bool(bool_section, "boolyes_capital", false)); ASSERT_FALSE(ini_get_bool(bool_section, "boolno", true)); ASSERT_FALSE(ini_get_bool(bool_section, "boolno_capital", true)); ASSERT(ini_get_bool(bool_section, "boolbin1", false)); ASSERT_FALSE(ini_get_bool(bool_section, "boolbin0", true)); ASSERT(ini_get_bool(bool_section, "boolinvalid", true)); ASSERT_FALSE(ini_get_bool(bool_section, "boolinvalid", false)); ASSERT(ini_get_bool(bool_section, "nonexistent", true)); ASSERT_FALSE(ini_get_bool(bool_section, "nonexistent", false)); PASS(); } TEST test_ini_get_string(void) { char *string_section = "string"; char *ptr; ASSERT_STR_EQ("A simple string", (ptr = ini_get_string(string_section, "simple", ""))); free(ptr); ASSERT_STR_EQ("A quoted string", (ptr = ini_get_string(string_section, "quoted", ""))); free(ptr); ASSERT_STR_EQ("A string \"with quotes\"", (ptr = ini_get_string(string_section, "quoted_with_quotes", ""))); free(ptr); ASSERT_STR_EQ("A\" string with quotes\"", (ptr = ini_get_string(string_section, "unquoted_with_quotes", ""))); free(ptr); ASSERT_STR_EQ("String with a", (ptr = ini_get_string(string_section, "quoted_comment", ""))); free(ptr); ASSERT_STR_EQ("String with a", (ptr = ini_get_string(string_section, "unquoted_comment", ""))); free(ptr); ASSERT_STR_EQ("#ffffff", (ptr = ini_get_string(string_section, "color_comment", ""))); free(ptr); ASSERT_STR_EQ("default value", (ptr = ini_get_string(string_section, "nonexistent", "default value"))); free(ptr); PASS(); } enum greatest_test_res ARRAY_EQ(char **a, char **b){ ASSERT(a); ASSERT(b); int i = 0; while (a[i] && b[i]){ ASSERT_STR_EQ(a[i], b[i]); i++; } ASSERT_FALSE(a[i]); ASSERT_FALSE(b[i]); PASS(); } TEST test_ini_get_list(void) { char *list_section = "list"; char *cmp1[] = {"A", "simple", "list", NULL}; char *cmp2[] = {"A", "list", "with", "spaces", NULL}; char *cmp3[] = {"A list", "with", "multiword entries", NULL}; char *cmp4[] = {"A", "quoted", "list", NULL}; char *cmp5[] = {"A", "list", "\"with quotes\"", NULL}; char *cmp6[] = {"List", "with", "a", NULL}; char **ptr; CHECK_CALL(ARRAY_EQ(cmp1, (ptr = ini_get_list(list_section, "simple", "")))); free_string_array(ptr); CHECK_CALL(ARRAY_EQ(cmp2, (ptr = ini_get_list(list_section, "spaces", "")))); free_string_array(ptr); CHECK_CALL(ARRAY_EQ(cmp3, (ptr = ini_get_list(list_section, "multiword", "")))); free_string_array(ptr); CHECK_CALL(ARRAY_EQ(cmp4, (ptr = ini_get_list(list_section, "quoted", "")))); free_string_array(ptr); CHECK_CALL(ARRAY_EQ(cmp5, (ptr = ini_get_list(list_section, "quoted_with_quotes", "")))); free_string_array(ptr); CHECK_CALL(ARRAY_EQ(cmp5, (ptr = ini_get_list(list_section, "unquoted_with_quotes", "")))); free_string_array(ptr); CHECK_CALL(ARRAY_EQ(cmp6, (ptr = ini_get_list(list_section, "quoted_comment", "")))); free_string_array(ptr); CHECK_CALL(ARRAY_EQ(cmp6, (ptr = ini_get_list(list_section, "unquoted_comment", "")))); free_string_array(ptr); PASS(); } TEST test_ini_get_path(void) { char *section = "path"; char *ptr, *exp; char *home = getenv("HOME"); // return default, if nonexistent key ASSERT_EQ(NULL, (ptr = ini_get_path(section, "nonexistent", NULL))); ASSERT_STR_EQ("default", (ptr = ini_get_path(section, "nonexistent", "default"))); g_free(ptr); // return path with replaced home ASSERT_STR_EQ((exp = g_strconcat(home, "/.path/to/tilde", NULL)), (ptr = ini_get_path(section, "expand_tilde", NULL))); g_free(ptr); g_free(exp); PASS(); } TEST test_ini_get_int(void) { char *int_section = "int"; ASSERT_EQ(5, ini_get_int(int_section, "simple", 0)); ASSERT_EQ(-10, ini_get_int(int_section, "negative", 0)); ASSERT_EQ(2, ini_get_int(int_section, "decimal", 0)); ASSERT_EQ(7, ini_get_int(int_section, "leading_zeroes", 0)); ASSERT_EQ(1024, ini_get_int(int_section, "multi_char", 0)); ASSERT_EQ(10, ini_get_int(int_section, "nonexistent", 10)); PASS(); } TEST test_ini_get_double(void) { if (2.3 != atof("2.3")) { SKIPm("Skipping test_ini_get_double, as it seems we're running under musl+valgrind!"); } char *double_section = "double"; ASSERT_EQ(1, ini_get_double(double_section, "simple", 0)); ASSERT_EQ(1.5, ini_get_double(double_section, "decimal", 0)); ASSERT_EQ(-1.2, ini_get_double(double_section, "negative", 0)); ASSERT_EQ(0.005, ini_get_double(double_section, "zeroes", 0)); ASSERT_EQ(3.141592653589793, ini_get_double(double_section, "long", 0)); ASSERT_EQ(10.5, ini_get_double(double_section, "nonexistent", 10.5)); PASS(); } TEST test_cmdline_get_path(void) { char *ptr, *exp; char *home = getenv("HOME"); // return default, if nonexistent key ASSERT_EQ(NULL, (ptr = cmdline_get_path("-nonexistent", NULL, "desc"))); ASSERT_STR_EQ("default", (ptr = cmdline_get_path("-nonexistent", "default", "desc"))); g_free(ptr); // return path with replaced home ASSERT_STR_EQ((exp = g_strconcat(home, "/path/from/cmdline", NULL)), (ptr = cmdline_get_path("-path", NULL, "desc"))); g_free(ptr); g_free(exp); PASS(); } TEST test_cmdline_get_string(void) { char *ptr; ASSERT_STR_EQ("A simple string from the cmdline", (ptr =cmdline_get_string("-string", "", ""))); free(ptr); ASSERT_STR_EQ("Single_word_string", (ptr = cmdline_get_string("-str/-s", "", ""))); free(ptr); ASSERT_STR_EQ("Default", (ptr = cmdline_get_string("-nonexistent", "Default", ""))); free(ptr); PASS(); } TEST test_cmdline_get_list(void) { char **ptr; char *cmp1[] = {"A", "simple", "list", "from", "the", "cmdline", NULL}; char *cmp2[] = {"A", "list", "with", "spaces", NULL}; char *cmp3[] = {"A", "default", "list", NULL}; CHECK_CALL(ARRAY_EQ(cmp1, (ptr = cmdline_get_list("-list", "", "")))); free_string_array(ptr); CHECK_CALL(ARRAY_EQ(cmp2, (ptr = cmdline_get_list("-list2", "", "")))); free_string_array(ptr); CHECK_CALL(ARRAY_EQ(cmp3, (ptr = cmdline_get_list("-nonexistent", "A, default, list", "")))); free_string_array(ptr); PASS(); } TEST test_cmdline_get_int(void) { ASSERT_EQ(3, cmdline_get_int("-int", 0, "")); ASSERT_EQ(2, cmdline_get_int("-int2/-i", 0, "")); ASSERT_EQ(-7, cmdline_get_int("-negative", 0, "")); ASSERT_EQ(4, cmdline_get_int("-zeroes", 0, "")); ASSERT_EQ(2, cmdline_get_int("-intdecim", 0, "")); ASSERT_EQ(10, cmdline_get_int("-nonexistent", 10, "")); PASS(); } TEST test_cmdline_get_double(void) { if (2.3 != atof("2.3")) { SKIPm("Skipping test_cmdline_get_double, as it seems we're running under musl+valgrind!"); } ASSERT_EQ(2, cmdline_get_double("-simple_double", 0, "")); ASSERT_EQ(5.2, cmdline_get_double("-double", 0, "")); ASSERT_EQ(3.14, cmdline_get_double("-nonexistent", 3.14, "")); PASS(); } TEST test_cmdline_get_bool(void) { ASSERT(cmdline_get_bool("-bool", false, "")); ASSERT(cmdline_get_bool("-shortbool/-b", false, "")); ASSERT(cmdline_get_bool("-boolnd/-n", true, "")); ASSERT_FALSE(cmdline_get_bool("-boolnd/-n", false, "")); PASS(); } TEST test_cmdline_create_usage(void) { g_free(cmdline_get_string("-msgstring/-ms", "", "A string to test usage creation")); cmdline_get_int("-msgint/-mi", 0, "An int to test usage creation"); cmdline_get_double("-msgdouble/-md", 0, "A double to test usage creation"); cmdline_get_bool("-msgbool/-mb", false, "A bool to test usage creation"); const char *usage = cmdline_create_usage(); ASSERT(strstr(usage, "-msgstring/-ms")); ASSERT(strstr(usage, "A string to test usage creation")); ASSERT(strstr(usage, "-msgint/-mi")); ASSERT(strstr(usage, "An int to test usage creation")); ASSERT(strstr(usage, "-msgdouble/-md")); ASSERT(strstr(usage, "A double to test usage creation")); ASSERT(strstr(usage, "-msgbool/-mb")); ASSERT(strstr(usage, "A bool to test usage creation")); PASS(); } TEST test_option_get_string(void) { char *string_section = "string"; char *ptr; ASSERT_STR_EQ("A simple string", (ptr =option_get_string(string_section, "simple", "-nonexistent", "", ""))); free(ptr); ASSERT_STR_EQ("Single_word_string", (ptr = option_get_string(string_section, "simple", "-str/-s", "", ""))); free(ptr); ASSERT_STR_EQ("A simple string from the cmdline", (ptr = option_get_string(string_section, "simple", "-string", "", ""))); free(ptr); ASSERT_STR_EQ("A simple string from the cmdline", (ptr = option_get_string(string_section, "simple", "-string/-s", "", ""))); free(ptr); ASSERT_STR_EQ("Single_word_string", (ptr = option_get_string(string_section, "simple", "-s", "", ""))); free(ptr); ASSERT_STR_EQ("Default", (ptr = option_get_string(string_section, "nonexistent", "-nonexistent", "Default", ""))); free(ptr); PASS(); } TEST test_option_get_list(void) { char *list_section = "list"; char **ptr; char *cmp1[] = {"A", "simple", "list", NULL}; char *cmp2[] = {"A", "list", "with", "spaces", NULL}; char *cmp3[] = {"A", "simple", "list", "from", "the", "cmdline", NULL}; char *cmp4[] = {"A", "default", "list", NULL}; CHECK_CALL(ARRAY_EQ(cmp1, (ptr = option_get_list(list_section, "simple", "-nonexistent", "", "")))); free_string_array(ptr); CHECK_CALL(ARRAY_EQ(cmp2, (ptr = option_get_list(list_section, "quoted", "-list2", "", "")))); free_string_array(ptr); CHECK_CALL(ARRAY_EQ(cmp3, (ptr = option_get_list(list_section, "simple", "-list", "", "")))); free_string_array(ptr); CHECK_CALL(ARRAY_EQ(cmp3, (ptr = option_get_list(list_section, "simple", "-list/-l", "", "")))); free_string_array(ptr); CHECK_CALL(ARRAY_EQ(cmp4, (ptr = option_get_list(list_section, "nonexistent", "-nonexistent", "A, default, list", "")))); free_string_array(ptr); PASS(); } TEST test_option_get_path(void) { char *section = "path"; char *ptr, *exp; char *home = getenv("HOME"); // invalid ini, invalid cmdline ASSERT_EQ(NULL, (ptr = option_get_path(section, "nonexistent", "-nonexistent", NULL, "desc"))); ASSERT_STR_EQ("default", (ptr = option_get_path(section, "nonexistent", "-nonexistent", "default", "desc"))); free(ptr); // valid ini, invalid cmdline ASSERT_STR_EQ((exp = g_strconcat(home, "/.path/to/tilde", NULL)), (ptr = option_get_path(section, "expand_tilde", "-nonexistent", NULL, "desc"))); g_free(exp); g_free(ptr); // valid ini, valid cmdline ASSERT_STR_EQ((exp = g_strconcat(home, "/path/from/cmdline", NULL)), (ptr = option_get_path(section, "expand_tilde", "-path", NULL, "desc"))); g_free(exp); g_free(ptr); // invalid ini, valid cmdline ASSERT_STR_EQ((exp = g_strconcat(home, "/path/from/cmdline", NULL)), (ptr = option_get_path(section, "nonexistent", "-path", NULL, "desc"))); g_free(exp); g_free(ptr); PASS(); } TEST test_option_get_int(void) { char *int_section = "int"; ASSERT_EQ(3, option_get_int(int_section, "negative", "-int", 0, "")); ASSERT_EQ(2, option_get_int(int_section, "simple", "-int2/-i", 0, "")); ASSERT_EQ(-7, option_get_int(int_section, "decimal", "-negative", 0, "")); ASSERT_EQ(4, option_get_int(int_section, "simple", "-zeroes", 0, "")); ASSERT_EQ(2, option_get_int(int_section, "simple", "-intdecim", 0, "")); ASSERT_EQ(5, option_get_int(int_section, "simple", "-nonexistent", 0, "")); ASSERT_EQ(-10, option_get_int(int_section, "negative", "-nonexistent", 0, "")); ASSERT_EQ(2, option_get_int(int_section, "decimal", "-nonexistent", 0, "")); ASSERT_EQ(7, option_get_int(int_section, "leading_zeroes", "-nonexistent", 0, "")); ASSERT_EQ(1024, option_get_int(int_section, "multi_char", "-nonexistent", 0, "")); ASSERT_EQ(3, option_get_int(int_section, "nonexistent", "-nonexistent", 3, "")); PASS(); } TEST test_option_get_double(void) { if (2.3 != atof("2.3")) { SKIPm("Skipping test_option_get_double, as it seems we're running under musl+valgrind!"); } char *double_section = "double"; ASSERT_EQ(2, option_get_double(double_section, "simple", "-simple_double", 0, "")); ASSERT_EQ(5.2, option_get_double(double_section, "simple", "-double", 0, "")); ASSERT_EQ(0.005, option_get_double(double_section, "zeroes", "-nonexistent", 0, "")); ASSERT_EQ(10.5, option_get_double(double_section, "nonexistent", "-nonexistent", 10.5, "")); PASS(); } TEST test_option_get_bool(void) { char *bool_section = "bool"; ASSERT(option_get_bool(bool_section, "boolfalse", "-bool/-b", false, "")); ASSERT(option_get_bool(bool_section, "boolbin1", "-nonexistent", false, "")); ASSERT_FALSE(option_get_bool(bool_section, "boolbin0", "-nonexistent", false, "")); ASSERT_FALSE(option_get_bool(bool_section, "nonexistent", "-nonexistent", false, "")); PASS(); } SUITE(suite_option_parser) { char *config_path = g_strconcat(base, "/data/test-ini", NULL); FILE *config_file = fopen(config_path, "r"); if (!config_file) { fputs("\nTest config file 'data/test-ini' couldn't be opened, failing.\n", stderr); exit(1); } load_ini_file(config_file); RUN_TEST(test_next_section); RUN_TEST(test_ini_get_bool); RUN_TEST(test_ini_get_string); RUN_TEST(test_ini_get_list); 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 " "-list A,simple,list,from,the,cmdline -list2 \"A, list, with, spaces\" " "-int 3 -i 2 -negative -7 -zeroes 04 -intdecim 2.5 " "-path ~/path/from/cmdline " "-simple_double 2 -double 5.2" ; int argc; char **argv; g_shell_parse_argv(&cmdline[0], &argc, &argv, NULL); cmdline_load(argc, argv); RUN_TEST(test_cmdline_get_string); RUN_TEST(test_cmdline_get_list); 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_list); RUN_TEST(test_option_get_path); RUN_TEST(test_option_get_int); RUN_TEST(test_option_get_double); RUN_TEST(test_option_get_bool); g_free(config_path); free_ini(); g_strfreev(argv); fclose(config_file); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/queues.c���������������������������������������������������������������������������0000664�0000000�0000000�00000052277�13706263046�0015330�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "../src/queues.c" #define GREATEST_FLOAT gint64 #define GREATEST_FLOAT_FMT "%ld" #include "greatest.h" #include "queues.h" struct notification *queues_debug_find_notification_by_id(int id) { assert(id > 0); GQueue *allqueues[] = { displayed, waiting, history }; for (int i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) { for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter; iter = iter->next) { struct notification *cur = iter->data; if (cur->id == id) return cur; } } return NULL; } TEST test_queue_length(void) { queues_init(); struct notification *n; n = test_notification("n1", 0); queues_notification_insert(n); queues_notification_close(n, REASON_UNDEF); n = test_notification("n2", 0); queues_notification_insert(n); queues_update(STATUS_NORMAL); n = test_notification("n3", 0); queues_notification_insert(n); QUEUE_LEN_ALL(1,1,1); ASSERT_EQm("Queue waiting has to contain an element", 1, queues_length_waiting()); ASSERT_EQm("Queue displayed has to contain an element", 1, queues_length_displayed()); ASSERT_EQm("Queue history has to contain an element", 1, queues_length_history()); queues_teardown(); PASS(); } TEST test_queue_insert_id_valid_newid(void) { struct notification *n; queues_init(); n = test_notification("n", -1); n->id = 0; queues_notification_insert(n); QUEUE_LEN_ALL(1, 0, 0); ASSERT(n->id > 0); queues_teardown(); PASS(); } TEST test_queue_insert_id_invalid(void) { struct notification *n; queues_init(); n = test_notification("n", -1); n->id = 1000; queues_notification_insert(n); QUEUE_LEN_ALL(1, 0, 0); ASSERTm("The given ID shouldn't be 0 anymore.", n->id > 0); ASSERT_EQm("This is a relict from times before stack_tag: " "Even if next_notification_id is lower than the requested id, " "it should use the requested id.", 1000, n->id); queues_teardown(); PASS(); } TEST test_queue_insert_id_replacement(void) { struct notification *a, *b, *c; queues_init(); a = test_notification("a", -1); notification_ref(a); queues_notification_insert(a); QUEUE_LEN_ALL(1, 0, 0); b = test_notification("b", -1); notification_ref(b); b->id = a->id; queues_notification_insert(b); QUEUE_LEN_ALL(1, 0, 0); ASSERT_EQ(a->id, b->id); NOT_LAST(a); queues_update(STATUS_NORMAL); c = test_notification("c", -1); c->id = b->id; queues_notification_insert(c); QUEUE_LEN_ALL(0, 1, 0); ASSERT_EQ(b->id, c->id); NOT_LAST(b); queues_teardown(); PASS(); } TEST test_queue_notification_close(void) { struct notification *n; // Test closing from waiting queue n = test_notification("n", -1); queues_init(); queues_notification_insert(n); QUEUE_LEN_ALL(1, 0, 0); queues_notification_close(n, REASON_UNDEF); queues_update(STATUS_NORMAL); QUEUE_LEN_ALL(0, 0, 1); queues_teardown(); // Test closing from displayed queue n = test_notification("n", -1); queues_init(); queues_notification_insert(n); QUEUE_LEN_ALL(1, 0, 0); queues_update(STATUS_NORMAL); QUEUE_LEN_ALL(0, 1, 0); queues_notification_close(n, REASON_UNDEF); queues_update(STATUS_NORMAL); QUEUE_LEN_ALL(0, 0, 1); queues_teardown(); PASS(); } TEST test_queue_notification_close_histignore(void) { struct notification *n; // Test closing from waiting queue n = test_notification("n", -1); n->history_ignore = true; queues_init(); queues_notification_insert(n); QUEUE_LEN_ALL(1, 0, 0); queues_notification_close(n, REASON_UNDEF); queues_update(STATUS_NORMAL); QUEUE_LEN_ALL(0, 0, 0); queues_teardown(); // Test closing from displayed queue n = test_notification("n", -1); n->history_ignore = true; queues_init(); queues_notification_insert(n); QUEUE_LEN_ALL(1, 0, 0); queues_update(STATUS_NORMAL); QUEUE_LEN_ALL(0, 1, 0); queues_notification_close(n, REASON_UNDEF); queues_update(STATUS_NORMAL); QUEUE_LEN_ALL(0, 0, 0); queues_teardown(); PASS(); } TEST test_queue_notification_skip_display(void) { struct notification *n; // Test skipping display n = test_notification("n", -1); n->skip_display = true; queues_init(); queues_notification_insert(n); QUEUE_LEN_ALL(1, 0, 0); queues_update(STATUS_NORMAL); QUEUE_LEN_ALL(0, 0, 1); queues_teardown(); PASS(); } TEST test_queue_notification_skip_display_redisplayed(void) { struct notification *n; // Test skipping display n = test_notification("n", -1); n->skip_display = true; queues_init(); queues_notification_insert(n); QUEUE_LEN_ALL(1, 0, 0); queues_update(STATUS_NORMAL); QUEUE_LEN_ALL(0, 0, 1); queues_history_pop(); QUEUE_LEN_ALL(1, 0, 0); queues_update(STATUS_NORMAL); QUEUE_CONTAINSm("A skip display notification should stay in displayed " "queue when it got pulled out of history queue", DISP, n); queues_teardown(); PASS(); } TEST test_queue_history_overfull(void) { settings.history_length = 10; queues_init(); struct notification *n; for (int i = 0; i < 10; i++) { char name[] = { 'n', '0'+i, '\0' }; // n<i> n = test_notification(name, -1); queues_notification_insert(n); queues_update(STATUS_NORMAL); queues_notification_close(n, REASON_UNDEF); } QUEUE_LEN_ALL(0, 0, 10); n = test_notification("n", -1); queues_notification_insert(n); queues_notification_close(n, REASON_UNDEF); QUEUE_CONTAINS(HIST, n); QUEUE_LEN_ALL(0, 0, 10); queues_teardown(); PASS(); } TEST test_queue_history_pushall(void) { settings.history_length = 5; settings.indicate_hidden = false; settings.geometry.h = 0; queues_init(); struct notification *n; for (int i = 0; i < 10; i++) { char name[] = { 'n', '0'+i, '\0' }; // n<i> n = test_notification(name, -1); queues_notification_insert(n); } queues_update(STATUS_NORMAL); for (int i = 0; i < 10; i++) { char name[] = { '2', 'n', '0'+i, '\0' }; // 2n<i> n = test_notification(name, -1); queues_notification_insert(n); } QUEUE_LEN_ALL(10, 10, 0); queues_history_push_all(); QUEUE_CONTAINS(HIST, n); QUEUE_LEN_ALL(0, 0, 5); queues_teardown(); PASS(); } TEST test_queue_init(void) { queues_init(); QUEUE_LEN_ALL(0, 0, 0); queues_teardown(); PASS(); } TEST test_queue_teardown(void) { queues_init(); QUEUE_LEN_ALL(0, 0, 0); struct notification *n = test_notification("n", -1); queues_notification_insert(n); queues_teardown(); ASSERT(waiting == NULL); ASSERT(displayed == NULL); ASSERT(history == NULL); PASS(); } TEST test_datachange_beginning_empty(void) { queues_init(); ASSERTm("There are no notifications at all, the timeout has to be less than 0.", queues_get_next_datachange(time_monotonic_now()) < 0); queues_teardown(); PASS(); } TEST test_datachange_endless(void) { queues_init(); settings.show_age_threshold = -1; struct notification *n = test_notification("n", 0); queues_notification_insert(n); queues_update(STATUS_NORMAL); ASSERTm("Age threshold is deactivated and the notification is infinite, there is no wakeup necessary.", queues_get_next_datachange(time_monotonic_now()) < 0); queues_teardown(); PASS(); } TEST test_datachange_endless_agethreshold(void) { settings.show_age_threshold = S2US(5); queues_init(); struct notification *n = test_notification("n", 0); queues_notification_insert(n); queues_update(STATUS_NORMAL); ASSERT_IN_RANGEm("Age threshold is activated and the next wakeup should be less than a second away", S2US(1)/2, queues_get_next_datachange(time_monotonic_now() + S2US(4)), S2US(1)/2); ASSERT_IN_RANGEm("Age threshold is activated and the next wakeup should be less than the age threshold", settings.show_age_threshold/2, queues_get_next_datachange(time_monotonic_now()), settings.show_age_threshold/2); settings.show_age_threshold = S2US(0); ASSERT_IN_RANGEm("Age threshold is activated and the next wakeup should be less than a second away", S2US(1)/2, queues_get_next_datachange(time_monotonic_now()), S2US(1)/2); queues_teardown(); PASS(); } TEST test_datachange_queues(void) { queues_init(); struct notification *n = test_notification("n", 10); queues_notification_insert(n); ASSERTm("The inserted notification is inside the waiting queue, so it should get ignored.", queues_get_next_datachange(time_monotonic_now()) < S2US(0)); queues_update(STATUS_NORMAL); ASSERT_IN_RANGEm("The notification has to get closed in less than its timeout", S2US(10)/2, queues_get_next_datachange(time_monotonic_now()), S2US(10)/2); queues_notification_close(n, REASON_UNDEF); ASSERTm("The inserted notification is inside the history queue, so it should get ignored", queues_get_next_datachange(time_monotonic_now()) < S2US(0)); queues_teardown(); PASS(); } TEST test_datachange_ttl(void) { struct notification *n; queues_init(); n = test_notification("n1", 15); queues_notification_insert(n); queues_update(STATUS_NORMAL); ASSERT_IN_RANGEm("The notification has to get closed in less than its timeout.", n->timeout/2, queues_get_next_datachange(time_monotonic_now()), n->timeout/2); n = test_notification("n2", 10); queues_notification_insert(n); queues_update(STATUS_NORMAL); ASSERT_IN_RANGEm("The timeout of the second notification has to get used as sleep time now.", n->timeout/2, queues_get_next_datachange(time_monotonic_now()), n->timeout/2); ASSERT_EQm("The notification already timed out. You have to answer with 0.", S2US(0), queues_get_next_datachange(time_monotonic_now() + S2US(10))); queues_teardown(); PASS(); } TEST test_queue_stacking(void) { settings.stack_duplicates = true; struct notification *n1, *n2, *n3; queues_init(); n1 = test_notification("n1", -1); n2 = test_notification("n1", -1); n3 = test_notification("n1", -1); queues_notification_insert(n1); QUEUE_LEN_ALL(1, 0, 0); notification_ref(n1); queues_notification_insert(n2); NOT_LAST(n1); notification_ref(n2); queues_update(STATUS_NORMAL); queues_notification_insert(n3); QUEUE_LEN_ALL(0, 1, 0); NOT_LAST(n2); queues_teardown(); PASS(); } TEST test_queue_stacktag(void) { const char *stacktag = "THIS IS A SUPER WIERD STACK TAG"; struct notification *n1, *n2, *n3; queues_init(); n1 = test_notification("n1", 1); n2 = test_notification("n2", 1); n3 = test_notification("n3", 1); n1->stack_tag = g_strdup(stacktag); n2->stack_tag = g_strdup(stacktag); n3->stack_tag = g_strdup(stacktag); queues_notification_insert(n1); QUEUE_LEN_ALL(1, 0, 0); notification_ref(n1); queues_notification_insert(n2); NOT_LAST(n1); notification_ref(n2); queues_update(STATUS_NORMAL); queues_notification_insert(n3); QUEUE_LEN_ALL(0, 1, 0); NOT_LAST(n2); queues_teardown(); PASS(); } TEST test_queue_timeout(void) { settings.geometry.h = 5; struct notification *n1, *n2, *n3; queues_init(); n1 = test_notification("n1", 0); n2 = test_notification("n2", 10); n3 = test_notification("n3", 10); n3->transient = true; queues_notification_insert(n1); queues_notification_insert(n2); queues_notification_insert(n3); queues_update(STATUS_NORMAL); // hacky way to shift time n1->start -= S2US(11); n2->start -= S2US(11); n3->start -= S2US(11); queues_update(STATUS_IDLE); QUEUE_LEN_ALL(0,2,1); QUEUE_CONTAINS(HIST, n3); // hacky way to shift time n1->start -= S2US(11); n2->start -= S2US(11); queues_update(STATUS_NORMAL); QUEUE_LEN_ALL(0,1,2); QUEUE_CONTAINS(DISP, n1); QUEUE_CONTAINS(HIST, n2); QUEUE_CONTAINS(HIST, n3); queues_teardown(); PASS(); } TEST test_queues_update_fullscreen(void) { settings.geometry.h = 5; struct notification *n_show, *n_dela, *n_push; queues_init(); n_show = test_notification("show", 10); n_dela = test_notification("dela", 10); n_push = test_notification("push", 10); n_show->fullscreen = FS_SHOW; n_dela->fullscreen = FS_DELAY; n_push->fullscreen = FS_PUSHBACK; queues_notification_insert(n_show); queues_notification_insert(n_dela); queues_notification_insert(n_push); queues_update(STATUS_FS); QUEUE_CONTAINS(DISP, n_show); QUEUE_CONTAINS(WAIT, n_dela); QUEUE_CONTAINS(WAIT, n_push); queues_update(STATUS_NORMAL); QUEUE_CONTAINS(DISP, n_show); QUEUE_CONTAINS(DISP, n_dela); QUEUE_CONTAINS(DISP, n_push); queues_update(STATUS_FS); QUEUE_CONTAINS(DISP, n_show); QUEUE_CONTAINS(DISP, n_dela); QUEUE_CONTAINS(WAIT, n_push); queues_teardown(); PASS(); } TEST test_queues_update_paused(void) { settings.geometry.h = 5; struct notification *n1, *n2, *n3; queues_init(); n1 = test_notification("n1", 0); n2 = test_notification("n2", 0); n3 = test_notification("n3", 0); queues_notification_insert(n1); queues_notification_insert(n2); queues_notification_insert(n3); QUEUE_LEN_ALL(3,0,0); queues_update(STATUS_PAUSE); QUEUE_LEN_ALL(3,0,0); queues_update(STATUS_NORMAL); QUEUE_LEN_ALL(0,3,0); queues_update(STATUS_PAUSE); QUEUE_LEN_ALL(3,0,0); queues_teardown(); PASS(); } TEST test_queues_update_seeping(void) { settings.geometry.h = 5; settings.sort = true; settings.indicate_hidden = false; struct notification *nl1, *nl2, *nl3, *nl4, *nl5; struct notification *nc1, *nc2, *nc3, *nc4, *nc5; queues_init(); nl1 = test_notification("nl1", 0); nl2 = test_notification("nl2", 0); nl3 = test_notification("nl3", 0); nl4 = test_notification("nl4", 0); nl5 = test_notification("nl5", 0); nc1 = test_notification("nc1", 0); nc2 = test_notification("nc2", 0); nc3 = test_notification("nc3", 0); nc4 = test_notification("nc4", 0); nc5 = test_notification("nc5", 0); nc1->urgency = URG_CRIT; nc2->urgency = URG_CRIT; nc3->urgency = URG_CRIT; nc4->urgency = URG_CRIT; nc5->urgency = URG_CRIT; queues_notification_insert(nl1); queues_notification_insert(nl2); queues_notification_insert(nl3); queues_notification_insert(nl4); queues_notification_insert(nl5); QUEUE_LEN_ALL(5,0,0); queues_update(STATUS_NORMAL); QUEUE_LEN_ALL(0,5,0); queues_notification_insert(nc1); queues_notification_insert(nc2); queues_notification_insert(nc3); queues_notification_insert(nc4); queues_notification_insert(nc5); QUEUE_LEN_ALL(5,5,0); queues_update(STATUS_NORMAL); QUEUE_LEN_ALL(5,5,0); QUEUE_CONTAINS(DISP, nc1); QUEUE_CONTAINS(DISP, nc2); QUEUE_CONTAINS(DISP, nc3); QUEUE_CONTAINS(DISP, nc4); QUEUE_CONTAINS(DISP, nc5); QUEUE_CONTAINS(WAIT, nl1); QUEUE_CONTAINS(WAIT, nl2); QUEUE_CONTAINS(WAIT, nl3); QUEUE_CONTAINS(WAIT, nl4); QUEUE_CONTAINS(WAIT, nl5); queues_teardown(); PASS(); } TEST test_queues_update_xmore(void) { settings.indicate_hidden = true; settings.geometry.h = 4; struct notification *n1, *n2, *n3, *n4, *n5; queues_init(); n1 = test_notification("n1", 0); n2 = test_notification("n2", 0); n3 = test_notification("n3", 0); n4 = test_notification("n4", 0); n5 = test_notification("n5", 0); queues_notification_insert(n1); queues_notification_insert(n2); queues_notification_insert(n3); queues_update(STATUS_NORMAL); QUEUE_LEN_ALL(0,3,0); queues_notification_insert(n4); queues_update(STATUS_NORMAL); QUEUE_LEN_ALL(0,4,0); queues_notification_insert(n5); queues_update(STATUS_NORMAL); QUEUE_LEN_ALL(2,3,0); queues_teardown(); PASS(); } TEST test_queues_update_seep_showlowurg(void) { // Test 3 notifications during fullscreen and only the one // with the lowest priority is eligible to get shown settings.geometry.h = 4; struct notification *n1, *n2, *n3; queues_init(); n1 = test_notification("n1", 0); n2 = test_notification("n2", 0); n3 = test_notification("n3", 0); n1->fullscreen = FS_DELAY; n2->fullscreen = FS_DELAY; n3->fullscreen = FS_SHOW; n3->urgency = URG_LOW; queues_notification_insert(n1); queues_notification_insert(n2); queues_update(STATUS_FS); QUEUE_LEN_ALL(2,0,0); queues_notification_insert(n3); queues_update(STATUS_FS); QUEUE_LEN_ALL(2,1,0); QUEUE_CONTAINS(WAIT, n1); QUEUE_CONTAINS(WAIT, n2); QUEUE_CONTAINS(DISP, n3); queues_teardown(); PASS(); } TEST test_queues_timeout_before_paused(void) { struct notification *n; queues_init(); n = test_notification("n", 10); queues_notification_insert(n); queues_update(STATUS_NORMAL); n->start -= S2US(11); queues_update(STATUS_PAUSE); QUEUE_LEN_ALL(0,0,1); queues_teardown(); PASS(); } TEST test_queue_find_by_id(void) { struct notification *n; int id; queues_init(); n = test_notification("n", 0); queues_notification_insert(n); n = test_notification("n1", 0); queues_notification_insert(n); id = n->id; n = test_notification("n2", 0); queues_notification_insert(n); n = queues_get_by_id(id); ASSERT(n->id == id); ASSERT(!strncmp(n->summary, "n1", 2)); queues_teardown(); PASS(); } SUITE(suite_queues) { settings.icon_path = ""; RUN_TEST(test_datachange_beginning_empty); RUN_TEST(test_datachange_endless); RUN_TEST(test_datachange_endless_agethreshold); RUN_TEST(test_datachange_queues); RUN_TEST(test_datachange_ttl); RUN_TEST(test_queue_history_overfull); RUN_TEST(test_queue_history_pushall); RUN_TEST(test_queue_init); RUN_TEST(test_queue_insert_id_invalid); RUN_TEST(test_queue_insert_id_replacement); RUN_TEST(test_queue_insert_id_valid_newid); RUN_TEST(test_queue_length); RUN_TEST(test_queue_notification_close); RUN_TEST(test_queue_notification_close_histignore); RUN_TEST(test_queue_notification_skip_display); RUN_TEST(test_queue_notification_skip_display_redisplayed); RUN_TEST(test_queue_stacking); RUN_TEST(test_queue_stacktag); RUN_TEST(test_queue_teardown); RUN_TEST(test_queue_timeout); RUN_TEST(test_queues_update_fullscreen); RUN_TEST(test_queues_update_paused); RUN_TEST(test_queues_update_seep_showlowurg); RUN_TEST(test_queues_update_seeping); RUN_TEST(test_queues_update_xmore); RUN_TEST(test_queues_timeout_before_paused); RUN_TEST(test_queue_find_by_id); settings.icon_path = NULL; } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/queues.h���������������������������������������������������������������������������0000664�0000000�0000000�00000004273�13706263046�0015326�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "greatest.h" #ifndef DUNST_TEST_QUEUES_H #define DUNST_TEST_QUEUES_H #include <stdbool.h> #include <glib.h> #include "../src/notification.h" #include "../src/queues.h" #define STATUS_NORMAL ((struct dunst_status) {.fullscreen=false, .running=true, .idle=false}) #define STATUS_IDLE ((struct dunst_status) {.fullscreen=false, .running=true, .idle=true}) #define STATUS_FSIDLE ((struct dunst_status) {.fullscreen=true, .running=true, .idle=true}) #define STATUS_FS ((struct dunst_status) {.fullscreen=true, .running=true, .idle=false}) #define STATUS_PAUSE ((struct dunst_status) {.fullscreen=false, .running=false, .idle=false}) #define QUEUE_WAIT waiting #define QUEUE_DISP displayed #define QUEUE_HIST history #define QUEUE(q) QUEUE_##q #define QUEUE_LEN_ALL(wait, disp, hist) do { \ if (wait >= 0) ASSERTm("Waiting is not " #wait, wait == g_queue_get_length(QUEUE(WAIT))); \ if (disp >= 0) ASSERTm("Displayed is not " #disp, disp == g_queue_get_length(QUEUE(DISP))); \ if (disp >= 0) ASSERTm("History is not " #hist, hist == g_queue_get_length(QUEUE(HIST))); \ } while (0) #define QUEUE_CONTAINS(q, n) QUEUE_CONTAINSm("QUEUE_CONTAINS(" #q "," #n ")", q, n) #define QUEUE_CONTAINSm(msg, q, n) ASSERTm(msg, g_queue_find(QUEUE(q), n)) #define NOT_LAST(n) do {ASSERT_EQm("Notification " #n " should have been deleted.", 1, notification_refcount_get(n)); g_clear_pointer(&n, notification_unref); } while(0) static inline struct notification *test_notification(const char *name, gint64 timeout) { struct notification *n = notification_create(); if (timeout != -1) n->timeout = S2US(timeout); n->dbus_client = g_strconcat(":", name, NULL); n->appname = g_strconcat("app of ", name, NULL); n->summary = g_strconcat(name, NULL); n->body = g_strconcat("See, ", name, ", I've got a body for you!", NULL); n->format = "%s\n%b"; notification_init(n); return n; } /* Retrieve a notification by its id. Solely for debugging purposes */ struct notification *queues_debug_find_notification_by_id(int id); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/test-install.sh��������������������������������������������������������������������0000775�0000000�0000000�00000001402�13706263046�0016617�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env bash set -euo pipefail BASE="$(dirname "$(dirname "$(readlink -f "$0")")")" PREFIX="${BASE}/install" make -C "${BASE}" SYSTEMD=1 SERVICEDIR_SYSTEMD="${PREFIX}/systemd" SERVICEDIR_DBUS="${PREFIX}/dbus" PREFIX="${PREFIX}" install diff -u <(find "${PREFIX}" -type f -printf "%P\n" | sort) - <<EOF bin/dunst bin/dunstctl bin/dunstify dbus/org.knopwob.dunst.service share/dunst/dunstrc share/man/man1/dunst.1 share/man/man1/dunstctl.1 systemd/dunst.service EOF make -C "${BASE}" SYSTEMD=1 SERVICEDIR_SYSTEMD="${PREFIX}/systemd" SERVICEDIR_DBUS="${PREFIX}/dbus" PREFIX="${PREFIX}" uninstall if ! [ -z "$(find "${PREFIX}" -type f)" ]; then echo "Uninstall failed, following files weren't removed" find "${PREFIX}" -type f exit 1 fi ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/test.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000003047�13706263046�0014767�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "greatest.h" #include <errno.h> #include <libgen.h> #include <stdbool.h> #include <stdlib.h> #include "../src/log.h" const char *base; SUITE_EXTERN(suite_utils); SUITE_EXTERN(suite_option_parser); SUITE_EXTERN(suite_notification); SUITE_EXTERN(suite_markup); SUITE_EXTERN(suite_misc); SUITE_EXTERN(suite_icon); SUITE_EXTERN(suite_queues); SUITE_EXTERN(suite_dunst); SUITE_EXTERN(suite_log); SUITE_EXTERN(suite_menu); SUITE_EXTERN(suite_dbus); GREATEST_MAIN_DEFS(); int main(int argc, char *argv[]) { char *prog = realpath(argv[0], NULL); if (!prog) { fprintf(stderr, "Cannot determine actual path of test executable: %s\n", strerror(errno)); exit(1); } base = dirname(prog); /* By default do not print out warning messages, when executing tests. * But allow, if DUNST_TEST_LOG=1 is set in environment. */ const char *log = getenv("DUNST_TEST_LOG"); bool printlog = log && atoi(log) ? true : false; dunst_log_init(!printlog); GREATEST_MAIN_BEGIN(); RUN_SUITE(suite_utils); RUN_SUITE(suite_option_parser); RUN_SUITE(suite_notification); RUN_SUITE(suite_markup); RUN_SUITE(suite_misc); RUN_SUITE(suite_icon); RUN_SUITE(suite_queues); RUN_SUITE(suite_dunst); RUN_SUITE(suite_log); RUN_SUITE(suite_menu); RUN_SUITE(suite_dbus); GREATEST_MAIN_END(); base = NULL; free(prog); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������dunst-1.5.0/test/utils.c����������������������������������������������������������������������������0000664�0000000�0000000�00000013606�13706263046�0015152�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "../src/utils.c" #include "greatest.h" TEST test_string_replace_char(void) { char *text = g_malloc(128 * sizeof(char)); strcpy(text, "a aa aaa"); ASSERT_STR_EQ("b bb bbb", string_replace_char('a', 'b', text)); strcpy(text, "Nothing to replace"); ASSERT_STR_EQ("Nothing to replace", string_replace_char('s', 'a', text)); strcpy(text, ""); ASSERT_STR_EQ("", string_replace_char('a', 'b', text)); g_free(text); PASS(); } /* * We trust that string_replace_all and string_replace properly reallocate * memory if the result is longer than the given string, no real way to test for * that far as I know. */ TEST test_string_replace_all(void) { char *text = g_malloc(128 * sizeof(char)); strcpy(text, "aaaaa"); ASSERT_STR_EQ("bbbbb", (text = string_replace_all("a", "b", text))); strcpy(text, ""); ASSERT_STR_EQ("", (text = string_replace_all("a", "b", text))); strcpy(text, "Nothing to replace"); ASSERT_STR_EQ((text = string_replace_all("z", "a", text)), "Nothing to replace"); strcpy(text, "Reverse this"); ASSERT_STR_EQ("Reverse sith", (text = string_replace_all("this", "sith", text))); strcpy(text, "abcdabc"); ASSERT_STR_EQ("xyzabcdxyzabc", (text = string_replace_all("a", "xyza", text))); g_free(text); PASS(); } TEST test_string_append(void) { char *exp; ASSERT_STR_EQ("text_sep_bit", (exp = string_append(g_strdup("text"), "bit", "_sep_"))); g_free(exp); ASSERT_STR_EQ("textbit", (exp = string_append(g_strdup("text"), "bit", NULL))); g_free(exp); ASSERT_STR_EQ("textbit", (exp = string_append(g_strdup("text"), "bit", ""))); g_free(exp); ASSERT_STR_EQ("text", (exp = string_append(g_strdup("text"), "", NULL))); g_free(exp); ASSERT_STR_EQ("text", (exp = string_append(g_strdup("text"), "", "_sep_"))); g_free(exp); ASSERT_STR_EQ("b", (exp = string_append(g_strdup(""), "b", NULL))); g_free(exp); ASSERT_STR_EQ("b", (exp = string_append(NULL, "b", "_sep_"))); g_free(exp); ASSERT_STR_EQ("a", (exp = string_append(g_strdup("a"), "", NULL))); g_free(exp); ASSERT_STR_EQ("a", (exp = string_append(g_strdup("a"), NULL, "_sep_"))); g_free(exp); ASSERT_STR_EQ("", (exp = string_append(g_strdup(""), "", "_sep_"))); g_free(exp); ASSERT_EQ(NULL, (exp = string_append(NULL, NULL, "_sep_"))); g_free(exp); PASS(); } TEST test_string_strip_quotes(void) { char *exp = string_strip_quotes(NULL); ASSERT_FALSE(exp); ASSERT_STR_EQ("NewString", (exp = string_strip_quotes("NewString"))); g_free(exp); ASSERT_STR_EQ("becomes unquoted", (exp = string_strip_quotes("\"becomes unquoted\""))); g_free(exp); ASSERT_STR_EQ("\"stays quoted", (exp = string_strip_quotes("\"stays quoted"))); g_free(exp); ASSERT_STR_EQ("stays quoted\"", (exp = string_strip_quotes("stays quoted\""))); g_free(exp); ASSERT_STR_EQ("stays \"quoted\"", (exp = string_strip_quotes("stays \"quoted\""))); g_free(exp); ASSERT_STR_EQ(" \"stays quoted\"", (exp = string_strip_quotes(" \"stays quoted\""))); g_free(exp); PASS(); } TEST test_string_strip_delimited(void) { char *text = g_malloc(128 * sizeof(char)); strcpy(text, "A <simple> string_strip_delimited test"); string_strip_delimited(text, '<', '>'); ASSERT_STR_EQ("A string_strip_delimited test", text); strcpy(text, "Remove <blink>html <b><i>tags</i></b></blink>"); string_strip_delimited(text, '<', '>'); ASSERT_STR_EQ("Remove html tags", text); strcpy(text, "Calls|with|identical|delimiters|are|handled|properly"); string_strip_delimited(text, '|', '|'); ASSERT_STR_EQ("Calls", text); strcpy(text, "<Return empty string if there is nothing left>"); string_strip_delimited(text, '<', '>'); ASSERT_STR_EQ("", text); strcpy(text, "Nothing is done if there are no delimiters in the string"); string_strip_delimited(text, '<', '>'); ASSERT_STR_EQ("Nothing is done if there are no delimiters in the string", text); g_free(text); PASS(); } TEST test_string_to_path(void) { char *ptr, *exp; char *home = getenv("HOME"); exp = "/usr/local/bin/script"; ASSERT_STR_EQ(exp, (ptr = string_to_path(g_strdup(exp)))); free(ptr); exp = "~path/with/wrong/tilde"; ASSERT_STR_EQ(exp, (ptr = string_to_path(g_strdup(exp)))); free(ptr); ASSERT_STR_EQ((exp = g_strconcat(home, "/.path/with/tilde", NULL)), (ptr = string_to_path(g_strdup("~/.path/with/tilde")))); free(exp); free(ptr); ASSERT_STR_EQ((exp = g_strconcat(home, "/.path/with/tilde and some space", NULL)), (ptr = string_to_path(g_strdup("~/.path/with/tilde and some space")))); free(exp); free(ptr); PASS(); } TEST test_string_to_time(void) { char *input[] = { "5000 ms", "5000ms", "100", "10s", "2m", "11h", "9d", " 5 ms ", NULL }; gint64 exp[] = { 5000, 5000, 100000, 10000, 120000, 39600000, 777600000, 5, 0}; int i = 0; while (input[i]){ ASSERT_EQ_FMT(string_to_time(input[i]), exp[i]*1000, "%ld"); i++; } PASS(); } SUITE(suite_utils) { RUN_TEST(test_string_replace_char); RUN_TEST(test_string_replace_all); RUN_TEST(test_string_append); RUN_TEST(test_string_strip_quotes); 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: */ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������