├── .github ├── ISSUE_TEMPLATE.md └── workflows │ └── main.yml ├── .gitignore ├── .valgrind.suppressions ├── AUTHORS ├── CHANGELOG.md ├── HACKING.md ├── LICENSE ├── Makefile ├── README.md ├── RELEASE_NOTES ├── config.mk ├── contrib ├── _dunst.zshcomp ├── _dunstctl.zshcomp ├── dunst_espeak.sh ├── dunst_xr_theme_changer.sh ├── notification-history.sh ├── progress-notify.sh └── screenshots │ ├── default_config.png │ ├── music.png │ ├── screenshot1.png │ ├── screenshot1_cut.png │ ├── screenshot2.png │ ├── screenshot2_cut.png │ ├── screenshot3.png │ ├── screenshot3_cut.png │ └── screenshot_urgency.png ├── docs ├── dunst.1.pod ├── dunst.5.pod ├── dunst_layout.png ├── dunst_layout.xcf ├── dunstctl.pod └── internal │ ├── Doxyfile │ └── release-checklist.md ├── dunst.systemd.service.in ├── dunstctl ├── dunstify.c ├── dunstrc ├── main.c ├── org.knopwob.dunst.service.in ├── src ├── dbus.c ├── dbus.h ├── draw.c ├── draw.h ├── dunst.c ├── dunst.h ├── icon-lookup.c ├── icon-lookup.h ├── icon.c ├── icon.h ├── ini.c ├── ini.h ├── input.c ├── input.h ├── log.c ├── log.h ├── markup.c ├── markup.h ├── menu.c ├── menu.h ├── notification.c ├── notification.h ├── option_parser.c ├── option_parser.h ├── output.c ├── output.h ├── queues.c ├── queues.h ├── rules.c ├── rules.h ├── settings.c ├── settings.h ├── settings_data.h ├── utils.c ├── utils.h ├── wayland │ ├── foreign_toplevel.c │ ├── foreign_toplevel.h │ ├── libgwater-wayland.c │ ├── libgwater-wayland.h │ ├── pool-buffer.c │ ├── pool-buffer.h │ ├── protocols │ │ ├── idle-client-header.h │ │ ├── idle.h │ │ ├── idle.xml │ │ ├── wlr-foreign-toplevel-management-unstable-v1-client-header.h │ │ ├── wlr-foreign-toplevel-management-unstable-v1.h │ │ ├── wlr-foreign-toplevel-management-unstable-v1.xml │ │ ├── wlr-layer-shell-unstable-v1-client-header.h │ │ ├── wlr-layer-shell-unstable-v1.h │ │ ├── wlr-layer-shell-unstable-v1.xml │ │ ├── xdg-output-unstable-v1-client-header.h │ │ ├── xdg-output-unstable-v1.h │ │ ├── xdg-shell-client-header.h │ │ └── xdg-shell.h │ ├── wl.c │ ├── wl.h │ ├── wl_output.c │ └── wl_output.h └── x11 │ ├── screen.c │ ├── screen.h │ ├── x.c │ └── x.h └── test ├── data ├── dunstrc.default ├── dunstrc.markup ├── dunstrc.nomarkup ├── dunstrc.show_age ├── icons │ ├── theme │ │ ├── 16x16 │ │ │ ├── actions │ │ │ │ └── edit.png │ │ │ └── apps │ │ │ │ └── preferences.png │ │ ├── 16x16@2x │ │ │ ├── actions │ │ │ │ └── edit.png │ │ │ └── apps │ │ │ │ └── preferences.png │ │ ├── 32x32 │ │ │ ├── actions │ │ │ │ └── edit.png │ │ │ └── apps │ │ │ │ └── preferences.png │ │ ├── 32x32@2x │ │ │ ├── actions │ │ │ │ └── edit.png │ │ │ └── apps │ │ │ │ └── preferences.png │ │ └── index.theme │ ├── valid.png │ └── valid.svg └── test-ini ├── dbus.c ├── draw.c ├── dunst.c ├── functional-tests ├── dunstrc.default ├── dunstrc.gaps ├── dunstrc.hide_text ├── dunstrc.icon_position ├── dunstrc.markup ├── dunstrc.nomarkup ├── dunstrc.nowrap ├── dunstrc.progress_bar ├── dunstrc.run_script ├── dunstrc.separator_click ├── dunstrc.show_age ├── script_test.sh └── test.sh ├── greatest.h ├── greenest.awk ├── helpers.c ├── helpers.h ├── icon-lookup.c ├── icon.c ├── ini.c ├── input.c ├── log.c ├── markup.c ├── menu.c ├── misc.c ├── notification.c ├── option_parser.c ├── queues.c ├── queues.h ├── rules.c ├── setting.c ├── settings_data.c ├── test-install.sh ├── test.c └── utils.c /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 12 | 13 | ### Issue description 14 | 15 | 16 | ### Installation info 17 | 18 | - Version: `` 19 | - Install type: `` 20 | - Window manager / Desktop environment: `` 21 | 22 |
23 | Minimal dunstrc 24 | 25 | 26 | ```ini 27 | # Dunstrc here 28 | ``` 29 |
30 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | CC: 14 | - clang 15 | - gcc 16 | distro: 17 | - alpine 18 | - archlinux 19 | - debian-stretch 20 | - debian-buster 21 | - fedora 22 | - ubuntu-xenial 23 | - ubuntu-bionic 24 | - ubuntu-focal 25 | 26 | env: 27 | CC: ${{ matrix.CC }} 28 | EXTRA_CFLAGS: "-Werror" 29 | steps: 30 | - uses: actions/checkout@v2 31 | with: 32 | # Clone the whole branch, we have to fetch tags later 33 | fetch-depth: 0 34 | 35 | # Fetch tags to determine proper version number inside git 36 | - name: fetch tags 37 | run: git fetch --tags 38 | # We cannot pull tags with old distros, since there is no `.git`. See below. 39 | if: "! (matrix.distro == 'ubuntu-bionic' || matrix.distro == 'ubuntu-xenial' || matrix.distro == 'debian-stretch')" 40 | 41 | # The Github checkout Action doesn't support distros with git older than 2.18 42 | # With git<2.18 it downloads the code via API and does not clone it via git :facepalm: 43 | # To succeed the tests, we have to manually replace the VERSION macro 44 | - name: fix version number for old distros 45 | run: 'sed -i "s/-non-git/-ci-oldgit-$GITHUB_SHA/" Makefile' 46 | if: " (matrix.distro == 'ubuntu-bionic' || matrix.distro == 'ubuntu-xenial' || matrix.distro == 'debian-stretch')" 47 | 48 | - name: build 49 | run: make -j all dunstify test/test 50 | 51 | - name: test 52 | run: make -j test 53 | 54 | - name: installation 55 | run: ./test/test-install.sh 56 | 57 | - name: valgrind memleaks 58 | run: | 59 | make clean 60 | make -j test-valgrind 61 | 62 | - name: coverage 63 | run: | 64 | make clean 65 | make -j test-coverage 66 | 67 | - name: Generate coverage report 68 | run: lcov -c -d . -o lcov.info 69 | if: "matrix.CC == 'gcc'" 70 | 71 | - name: Upload coverage to Codecov 72 | uses: codecov/codecov-action@v1 73 | with: 74 | token: ${{ secrets.CODECOV_TOKEN }} 75 | flags: unittests 76 | name: ${{ matrix.distro }}-${{ matrix.CC }} 77 | fail_ci_if_error: true 78 | if: "matrix.CC == 'gcc'" 79 | 80 | runs-on: ubuntu-latest 81 | container: 82 | image: dunst/ci:${{ matrix.distro }} 83 | 84 | doxygen: 85 | strategy: 86 | matrix: 87 | distro: 88 | - misc-doxygen 89 | 90 | steps: 91 | - uses: actions/checkout@v2 92 | with: 93 | # Clone the whole branch, we have to fetch tags later 94 | fetch-depth: 0 95 | 96 | - name: doxygen 97 | run: make -j doc-doxygen 98 | 99 | runs-on: ubuntu-latest 100 | container: 101 | image: dunst/ci:${{ matrix.distro }} 102 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.d 3 | *.gcda 4 | *.gcno 5 | *.gcov 6 | /lcov.info 7 | 8 | core 9 | vgcore.* 10 | 11 | /docs/*.1 12 | /docs/*.5 13 | /docs/internal/coverage 14 | /docs/internal/html 15 | /dunst 16 | /dunstify 17 | /dunst.systemd.service 18 | /org.knopwob.dunst.service 19 | /test/test 20 | -------------------------------------------------------------------------------- /.valgrind.suppressions: -------------------------------------------------------------------------------- 1 | # Ignore musls' weird error 2 | { 3 | musl_alpine_libc 4 | Memcheck:Free 5 | fun:free 6 | obj:/lib/ld-musl-x86_64.so.1 7 | } 8 | 9 | # rsvg_error_handle_close got fixed in 10 | # - GNOME/librsvg@7bf1014 11 | # (2018-11-12, first tags: v2.45.0, v2.44.9) 12 | # but the release has to seep into the distros 13 | { 14 | rsvg_error_handle_close 15 | Memcheck:Leak 16 | match-leak-kinds: definite 17 | fun:malloc 18 | fun:g_malloc 19 | fun:g_slice_alloc 20 | fun:g_error_new_valist 21 | fun:g_set_error 22 | obj:*/librsvg-2.so* 23 | fun:rsvg_handle_close 24 | obj:*/loaders/libpixbufloader-svg.so 25 | fun:gdk_pixbuf_loader_close 26 | fun:gdk_pixbuf_get_file_info 27 | fun:get_pixbuf_from_file 28 | ... 29 | } 30 | 31 | # same as above, but as occurs in CI environment 32 | { 33 | rsvg_error_handle_close2 34 | Memcheck:Leak 35 | match-leak-kinds: definite 36 | fun:malloc 37 | fun:g_malloc 38 | fun:g_slice_alloc 39 | fun:g_error_new_valist 40 | fun:g_set_error 41 | obj:*/librsvg-2.so* 42 | obj:*/librsvg-2.so* 43 | obj:*/loaders/libpixbufloader-svg.so 44 | obj:*/libgdk_pixbuf-2.0.so* 45 | fun:gdk_pixbuf_loader_close 46 | fun:gdk_pixbuf_get_file_info 47 | fun:get_pixbuf_from_file 48 | ... 49 | } 50 | 51 | # Some new in ArchLinux 52 | { 53 | rsvg_rust_handle_close 54 | Memcheck:Leak 55 | match-leak-kinds: definite 56 | fun:malloc 57 | ... 58 | fun:rsvg_rust_handle_close 59 | obj:*/loaders/libpixbufloader-svg.so 60 | ... 61 | fun:gdk_pixbuf_new_from_file 62 | ... 63 | } 64 | 65 | # rsvg_error_writehandler got fixed in 66 | # - GNOME/librsvg@7b4cc9b 67 | # (2018-11-12, first tags: v2.45.0, v2.44.9) 68 | # but the release has to seep into the distros 69 | { 70 | rsvg_error_writehandler 71 | Memcheck:Leak 72 | match-leak-kinds: definite 73 | fun:malloc 74 | fun:g_malloc 75 | fun:g_slice_alloc 76 | fun:g_error_new_valist 77 | fun:g_set_error 78 | obj:*/librsvg-2.so* 79 | fun:rsvg_handle_write 80 | obj:*/loaders/libpixbufloader-svg.so 81 | obj:*/libgdk_pixbuf-2.0.so* 82 | fun:gdk_pixbuf_loader_close 83 | fun:gdk_pixbuf_get_file_info 84 | fun:get_pixbuf_from_file 85 | ... 86 | } 87 | 88 | # a librsvg memoryleak that shows up in arch, but not in the CI environment 89 | { 90 | 91 | Memcheck:Leak 92 | match-leak-kinds: definite 93 | fun:malloc 94 | obj:/usr/lib/librsvg-2.so.2.48.0 95 | ... 96 | fun:rsvg_handle_close 97 | obj:/usr/lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-svg.so 98 | obj:/usr/lib/libgdk_pixbuf-2.0.so.0.4200.6 99 | fun:gdk_pixbuf_new_from_file 100 | ... 101 | } 102 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Sascha Kruse (http://github.com/knopwob) 2 | 3 | contributors: 4 | See `git shortlog` for a list of contributors and their contributions 5 | -------------------------------------------------------------------------------- /HACKING.md: -------------------------------------------------------------------------------- 1 | # Important notes on the code 2 | 3 | **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`. You will also need `graphviz` for this.** 4 | 5 | 6 | For people wanting to develop new features or fix bugs for dunst, here are the 7 | steps you should take. 8 | 9 | # Running dunst 10 | 11 | For building dunst, you should take a look at the README. After dunst is build, 12 | you can run it with: 13 | 14 | ./dunst 15 | 16 | This might not work, however, since dunst will abort when another instance of 17 | dunst or another notification daemon is running. You will see a message like: 18 | 19 | CRITICAL: [dbus_cb_name_lost:1044] Cannot acquire 'org.freedesktop.Notifications': Name is acquired by 'dunst' with PID '20937'. 20 | 21 | So it's best to kill any running instance of dunst before trying to run the 22 | version you just built. You can do that by making a shell function as follows 23 | and put it in your bashrc/zshrc/config.fish: 24 | 25 | ```sh 26 | run_dunst() { 27 | if make -j dunst; then 28 | pkill dunst 29 | else 30 | return 1 31 | fi 32 | ./dunst $@ 33 | } 34 | ``` 35 | 36 | If you run this function is the root directory of the repository, it will build 37 | dunst, kill any running instances and run your freshly built version of dunst. 38 | 39 | # Testing dunst 40 | 41 | To test dunst, it's good to know the following commands. This way you can test 42 | dunst on your local system and you don't have to wait for CI to finish. 43 | 44 | ## Run test suite 45 | 46 | This will build dunst if there were any changes and run the test suite. You will 47 | need `awk` for this to work (to color the output of the tests). 48 | 49 | make test 50 | 51 | ## Run memory leak tests 52 | 53 | This will build dunst if there were any changes and run the test suite with 54 | valgrind to make sure there aren't any memory leaks. You will have to build your 55 | tests so that they free all allocated memory after you are done, otherwise this 56 | test will fail. You will need to have `valgrind` installed for this. 57 | 58 | make test-valgrind 59 | 60 | 61 | ## Build the doxygen documentation 62 | 63 | The internal documentation can be built with (`doxygen` and `graphviz` required): 64 | 65 | make doc-doxygen 66 | 67 | To open them in your browser you can run something like: 68 | 69 | firefox docs/internal/html/index.html 70 | 71 | 72 | # Running the tests with docker 73 | 74 | Dunst has a few docker images for running tests on different distributions. The 75 | documentation for this can be found at https://github.com/dunst-project/docker-images 76 | 77 | # Comments 78 | 79 | - Comment system is held similar to JavaDoc 80 | - Use `@param` to describe all input parameters 81 | - Use `@return` to describe the output value 82 | - Use `@retval` to describe special return values (like `NULL`) 83 | - Documentation comments should start with a double star (`/**`) 84 | - Append `()` to function names and prepend variables with `#` to properly reference them in the docs 85 | - Add comments to all functions and methods 86 | - Markdown inside the comments is allowed and also desired 87 | - Add the comments to the prototype. Doxygen will merge the protoype and implementation documentation anyways. 88 | Except for **static** methods, add the documentation header to the implementation and *not to the prototype*. 89 | - Member documentation should happen with `/**<` and should span to the right side of the member 90 | - Test files that have the same name as a file in src/\* can include the 91 | associated .c file. This is because they are being compiled INSTEAD of the src 92 | file. 93 | 94 | 95 | ## Log messages 96 | 97 | ### Messages 98 | 99 | - Keep your message in common format: `: ` 100 | - If you have to write text, single quote values in your sentence. 101 | 102 | ### Levels 103 | 104 | For logging, there are printf-like macros `LOG_(E|C|W|M|I|D)`. 105 | 106 | - `LOG_E` (ERROR): 107 | - 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. 108 | - e.g.: Switching over an enum, `LOG_E` would go into the default case. 109 | - `LOG_C` (CRITICAL): 110 | - The program cannot continue to work. It is used in the wrong manner or some outer conditions are not met. 111 | - e.g.: `-config` parameter value is unreadable file 112 | - `DIE` (CRITICAL): 113 | - A shorthand for `LOG_C` and terminating the program after. This does not dump the core (unlike `LOG_E`). 114 | - `LOG_W` (WARNING): 115 | - Something is not in shape, but it's recoverable. 116 | - e.g.: A value is not parsable in the config file, which will default. 117 | - `LOG_M` (MESSAGE): 118 | - Important info, which informs about the state. 119 | - e.g.: An empty notification does get removed immediately. 120 | - `LOG_I` (INFO): 121 | - Mostly unneccessary info, but important to debug (as the user) some use cases. 122 | - e.g.: print the notification contents after arriving 123 | - `LOG_D` (DEBUG): 124 | - Only important during development or tracing some bugs (as the developer). 125 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2013, Sascha Kruse and contributors 2 | All rights reserved. 3 | 4 | All files (unless otherwise noted) are licensed under the BSD license: 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Sascha Kruse nor the 17 | names of contributors may be used to endorse or promote products 18 | derived from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY Sascha Kruse ''AS IS'' AND ANY 21 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL Sascha Kruse BE LIABLE FOR ANY 24 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | # paths 2 | PREFIX ?= /usr/local 3 | BINDIR ?= ${PREFIX}/bin 4 | SYSCONFDIR ?= ${PREFIX}/etc/xdg 5 | SYSCONFFILE ?= ${SYSCONFDIR}/dunst/dunstrc 6 | DATADIR ?= ${PREFIX}/share 7 | # around for backwards compatibility 8 | MANPREFIX ?= ${DATADIR}/man 9 | MANDIR ?= ${MANPREFIX} 10 | SERVICEDIR_DBUS ?= ${DATADIR}/dbus-1/services 11 | SERVICEDIR_SYSTEMD ?= ${PREFIX}/lib/systemd/user 12 | EXTRA_CFLAGS ?= 13 | 14 | DOXYGEN ?= doxygen 15 | FIND ?= find 16 | GCOVR ?= gcovr 17 | GIT ?= git 18 | PKG_CONFIG ?= pkg-config 19 | POD2MAN ?= pod2man 20 | SED ?= sed 21 | SYSTEMDAEMON ?= systemd 22 | VALGRIND ?= valgrind 23 | 24 | # Disable systemd service file installation, 25 | # if you don't want to use systemd albeit installed 26 | #SYSTEMD ?= 0 27 | 28 | # Disable dependency on wayland. This will force dunst to use 29 | # xwayland on wayland compositors 30 | # You can also use "make WAYLAND=0" to build without wayland 31 | # WAYLAND ?= 0 32 | 33 | ifneq (0, ${WAYLAND}) 34 | ENABLE_WAYLAND= -DENABLE_WAYLAND 35 | endif 36 | 37 | # flags 38 | DEFAULT_CPPFLAGS = -Wno-gnu-zero-variadic-macro-arguments -D_DEFAULT_SOURCE -DVERSION=\"${VERSION}\" -DSYSCONFDIR=\"${SYSCONFDIR}\" 39 | DEFAULT_CFLAGS = -g -std=gnu99 -pedantic -Wall -Wno-overlength-strings -Os ${ENABLE_WAYLAND} ${EXTRA_CFLAGS} 40 | DEFAULT_LDFLAGS = -lm -lrt 41 | 42 | CPPFLAGS_DEBUG := -DDEBUG_BUILD 43 | CFLAGS_DEBUG := -O0 44 | LDFLAGS_DEBUG := 45 | 46 | pkg_config_packs := gio-2.0 \ 47 | gdk-pixbuf-2.0 \ 48 | "glib-2.0 >= 2.44" \ 49 | pangocairo \ 50 | x11 \ 51 | xinerama \ 52 | xext \ 53 | "xrandr >= 1.5" \ 54 | xscrnsaver \ 55 | 56 | 57 | # dunstify also needs libnotify 58 | pkg_config_packs += libnotify 59 | 60 | ifneq (0,${WAYLAND}) 61 | pkg_config_packs += wayland-client 62 | pkg_config_packs += wayland-cursor 63 | endif 64 | -------------------------------------------------------------------------------- /contrib/_dunst.zshcomp: -------------------------------------------------------------------------------- 1 | #compdef _dunst dunst 2 | 3 | # ZSH arguments completion script for the dunst command 4 | 5 | local curcontext="$curcontext" ret=1 6 | local -a state line subs 7 | 8 | _arguments -C \ 9 | '1:opt:->opts' \ 10 | '2:param:->params' \ 11 | && ret=0 12 | 13 | case $state in 14 | (opts) 15 | _arguments \ 16 | {-v,--version,-version}"[Print version]" \ 17 | '(-verbosity)-verbosity[Minimum level for message]' \ 18 | {-conf,--config}"[Path to configuration file]:file:_files" \ 19 | {-h,-help,--help}"[Print help]" 20 | ;; 21 | 22 | (params) 23 | case $line[1] in 24 | -verbosity) 25 | local -a verbosity_params; 26 | verbosity_params=( 27 | "crit" 28 | "warn" 29 | "mesg" 30 | "info" 31 | "debug" 32 | ) 33 | _describe verbosity_params verbosity_params && ret=0 34 | ;; 35 | 36 | -conf|--config) 37 | _arguments '*:file:_files' && ret=0 38 | ;; 39 | 40 | esac 41 | esac 42 | -------------------------------------------------------------------------------- /contrib/_dunstctl.zshcomp: -------------------------------------------------------------------------------- 1 | #compdef _dunstctl dunstctl 2 | 3 | # ZSH arguments completion script for the dunstctl commnd 4 | # Depends on: gAWK (rule), jq (history-pop) 5 | 6 | local curcontext="$curcontext" ret=1 7 | local -a state line subs 8 | 9 | local DUNSTRC="${XDG_CONFIG_HOME:-$HOME/.config}/dunst/dunstrc" 10 | 11 | _arguments -C \ 12 | '1:cmd:->cmds' \ 13 | '2:opt:->opts' \ 14 | '3:third:->thirds' \ 15 | && ret=0 16 | 17 | case $state in 18 | (cmds) 19 | local -a commands 20 | commands=( 21 | 'action:Perform the default action, or open the context menu of the notification at the given position' 22 | 'close:Close the last notification' 23 | 'close-all:Close the all notifications' 24 | 'context:Open context menu' 25 | 'count:Show the number of notifications' 26 | 'history:Display notification history (in JSON)' 27 | 'history-pop:Pop the latest notification from history or optionally the notification with given ID.' 28 | 'is-paused:Check if dunst is running or paused' 29 | 'set-paused:Set the pause status' 30 | 'rule:Enable or disable a rule by its name' 31 | 'debug:Print debugging information' 32 | 'help:Show this help' 33 | ) 34 | _describe commands commands && ret=0 35 | ;; 36 | 37 | (opts) 38 | case $line[1] in 39 | count) 40 | local -a count_opts; 41 | count_opts=( 42 | "displayed" 43 | "history" 44 | "waiting" 45 | ) 46 | 47 | _describe count_opts count_opts && ret=0 48 | ;; 49 | 50 | set-paused) 51 | local -a setpaused_opts; 52 | setpaused_opts=( 53 | "true" 54 | "false" 55 | "toggle" 56 | ) 57 | 58 | _describe setpaused_opts setpaused_opts && ret=0 59 | ;; 60 | 61 | rule) 62 | local -a rules; 63 | rules=( 64 | `awk '/^\[.*\]/{ if ( match($0, /^\[global|urgency|experimental/) == 0 ) { print substr($0, 2, length($0)-2) } }' < "$DUNSTRC"` 65 | ) 66 | _describe rules_opts rules && ret=0 67 | ;; 68 | 69 | history-pop) 70 | local -a history_ids; 71 | history_ids=( 72 | `dunstctl history | jq -M '.data[0][].id.data'` 73 | ) 74 | _describe history_ids history_ids && ret=0 75 | ;; 76 | 77 | esac 78 | ;; 79 | 80 | (thirds) 81 | case $line[1] in 82 | rule) 83 | local -a rulestates_opts; 84 | rulestates_opts=( 85 | "enable" 86 | "disable" 87 | "toggle" 88 | ) 89 | 90 | _describe rulestates_opts rulestates_opts && ret=0 91 | ;; 92 | 93 | esac 94 | esac 95 | 96 | return ret 97 | -------------------------------------------------------------------------------- /contrib/dunst_espeak.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | summary="$2" 4 | body="$3" 5 | 6 | echo "$summary $body" | espeak 7 | -------------------------------------------------------------------------------- /contrib/notification-history.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | history_json=$(dunstctl history) 4 | 5 | history_length=$(echo $history_json|jq -r '.data[] | length') 6 | 7 | options="" 8 | 9 | for iter in $(seq $history_length); do 10 | i=$((iter-1)) 11 | application_name=$(echo $history_json|jq -r .data[][$i].appname.data) 12 | notification_summary=$(echo $history_json|jq -r .data[][$i].summary.data) 13 | notification_timestamp=$(echo $history_json|jq -r .data[][$i].timestamp.data) 14 | system_timestamp=$(cat /proc/uptime|cut -d'.' -f1) 15 | 16 | how_long_ago=$((system_timestamp-notification_timestamp/1000000)) 17 | 18 | notification_time=$(date +%X -d "$(date) - $how_long_ago seconds") 19 | 20 | option=$(printf '%04d - %s: "%s" (at %s)' "$iter" "$application_name" \ 21 | "$notification_summary" "$notification_time") 22 | 23 | options="$options$option\n" 24 | done 25 | options="$options""Cancel" 26 | 27 | result=$(echo -e $options|rofi -dmenu -i) 28 | if [ "$result" = "Cancel" ]; then 29 | # Exit if cancelled 30 | exit 0 31 | fi 32 | if [ "$result" = "" ]; then 33 | # Exit on empty strings 34 | exit 0 35 | fi 36 | if [ "$?" -ne 0 ]; then 37 | # Exit on non-zero return values 38 | exit 0 39 | fi 40 | 41 | # Get the internal notification ID 42 | selection_index=$((${result:0:4}-1)) 43 | notification_id=$(echo $history_json|jq -r .data[][$selection_index].id.data) 44 | 45 | # Tell dunst to revive said notification 46 | dunstctl history-pop $notification_id 47 | 48 | -------------------------------------------------------------------------------- /contrib/progress-notify.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # progress-notify - Send audio and brightness notifications for dunst 4 | 5 | # dependencies: dunstify, ponymix, Papirus (icons) 6 | 7 | ### How to use: ### 8 | # Pass the values via stdin and provide the notification type 9 | # as an argument. Options are audio, brightness and muted 10 | 11 | ### Audio notifications ### 12 | # ponymix increase 5 | notify audio 13 | # ponymix decrease 5 | notify audio 14 | # pulsemixer --toggle-mute --get-mute | notify muted 15 | ### Brightness notifications ### 16 | # xbacklight -inc 5 && xbacklight -get | notify brightness 17 | # xbacklight -dec 5 && xbacklight -get | notify brightness 18 | 19 | notifyMuted() { 20 | volume="$1" 21 | dunstify -h string:x-canonical-private-synchronous:audio "Muted" -h int:value:"$volume" -t 1500 --icon audio-volume-muted 22 | } 23 | 24 | notifyAudio() { 25 | volume="$1" 26 | ponymix is-muted && notifyMuted "$volume" && return 27 | 28 | if [ $volume -eq 0 ]; then 29 | notifyMuted "$volume" 30 | elif [ $volume -le 30 ]; then 31 | dunstify -h string:x-canonical-private-synchronous:audio "Volume: " -h int:value:"$volume" -t 1500 --icon audio-volume-low 32 | elif [ $volume -le 70 ]; then 33 | dunstify -h string:x-canonical-private-synchronous:audio "Volume: " -h int:value:"$volume" -t 1500 --icon audio-volume-medium 34 | else 35 | dunstify -h string:x-canonical-private-synchronous:audio "Volume: " -h int:value:"$volume" -t 1500 --icon audio-volume-high 36 | fi 37 | } 38 | 39 | notifyBrightness() { 40 | brightness="$1" 41 | if [ $brightness -eq 0 ]; then 42 | dunstify -h string:x-canonical-private-synchronous:brightness "Brightness: " -h int:value:"$brightness" -t 1500 --icon display-brightness-off-symbolic 43 | elif [ $brightness -le 30 ]; then 44 | dunstify -h string:x-canonical-private-synchronous:brightness "Brightness: " -h int:value:"$brightness" -t 1500 --icon display-brightness-low-symbolic 45 | elif [ $brightness -le 70 ]; then 46 | dunstify -h string:x-canonical-private-synchronous:brightness "Brightness: " -h int:value:"$brightness" -t 1500 --icon display-brightness-medium-symbolic 47 | else 48 | dunstify -h string:x-canonical-private-synchronous:brightness "Brightness: " -h int:value:"$brightness" -t 1500 --icon display-brightness-high-symbolic 49 | fi 50 | } 51 | 52 | input=`cat /dev/stdin` 53 | 54 | case "$1" in 55 | muted) 56 | volume=`ponymix get-volume` 57 | if [ "$input" -eq 0 ] 58 | then 59 | notifyAudio "$volume" 60 | else 61 | notifyMuted "$volume" 62 | fi 63 | ;; 64 | audio) 65 | notifyAudio "$input" 66 | ;; 67 | brightness) 68 | notifyBrightness "$input" 69 | ;; 70 | 71 | *) 72 | echo "Not the right arguments" 73 | echo "$1" 74 | exit 2 75 | esac 76 | -------------------------------------------------------------------------------- /contrib/screenshots/default_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FT-Labs/phyOS-dunst/aeb275a998778b05cee495efb9b61db66cdc0a7e/contrib/screenshots/default_config.png -------------------------------------------------------------------------------- /contrib/screenshots/music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FT-Labs/phyOS-dunst/aeb275a998778b05cee495efb9b61db66cdc0a7e/contrib/screenshots/music.png -------------------------------------------------------------------------------- /contrib/screenshots/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FT-Labs/phyOS-dunst/aeb275a998778b05cee495efb9b61db66cdc0a7e/contrib/screenshots/screenshot1.png -------------------------------------------------------------------------------- /contrib/screenshots/screenshot1_cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FT-Labs/phyOS-dunst/aeb275a998778b05cee495efb9b61db66cdc0a7e/contrib/screenshots/screenshot1_cut.png -------------------------------------------------------------------------------- /contrib/screenshots/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FT-Labs/phyOS-dunst/aeb275a998778b05cee495efb9b61db66cdc0a7e/contrib/screenshots/screenshot2.png -------------------------------------------------------------------------------- /contrib/screenshots/screenshot2_cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FT-Labs/phyOS-dunst/aeb275a998778b05cee495efb9b61db66cdc0a7e/contrib/screenshots/screenshot2_cut.png -------------------------------------------------------------------------------- /contrib/screenshots/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FT-Labs/phyOS-dunst/aeb275a998778b05cee495efb9b61db66cdc0a7e/contrib/screenshots/screenshot3.png -------------------------------------------------------------------------------- /contrib/screenshots/screenshot3_cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FT-Labs/phyOS-dunst/aeb275a998778b05cee495efb9b61db66cdc0a7e/contrib/screenshots/screenshot3_cut.png -------------------------------------------------------------------------------- /contrib/screenshots/screenshot_urgency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FT-Labs/phyOS-dunst/aeb275a998778b05cee495efb9b61db66cdc0a7e/contrib/screenshots/screenshot_urgency.png -------------------------------------------------------------------------------- /docs/dunst.1.pod: -------------------------------------------------------------------------------- 1 | =head1 NAME 2 | 3 | dunst - A customizable and lightweight notification-daemon 4 | 5 | =head1 SYNOPSIS 6 | 7 | dunst [-conf file] [-verbosity v] [-print] [--startup-notification] 8 | 9 | =head1 DESCRIPTION 10 | 11 | Dunst is a highly configurable and lightweight notification daemon. 12 | 13 | =head2 Autostarting dunst 14 | 15 | On most installations dunst should be able to automatically be started by D-Bus 16 | when a notification is sent. This is not recommended when multiple notification 17 | deamons are installed, because D-Bus will not know which one to start. 18 | Other ways of autostarting dunst include starting dunst with your desktop 19 | environment or window manager's autostart functionality or via the provided 20 | systemd service. 21 | 22 | =head1 COMMAND LINE OPTIONS 23 | 24 | =over 4 25 | 26 | =item B<-h/--help> 27 | 28 | List all command line flags 29 | 30 | =item B<-conf/-config file> 31 | 32 | Use alternative config file. 33 | This disables the search for other config files. 34 | If it cannot be opened Dunst will issue a warning and fall back on its internal 35 | defaults. 36 | (Hint: `dunst -conf - 40 | 41 | Print version information. 42 | 43 | =item B<-verbosity> (values: 'crit', 'warn', 'mesg', 'info', 'debug' default 'mesg') 44 | 45 | Do not display log messages, which have lower precedence than specified 46 | verbosity. This won't affect printing notifications on the terminal. Use 47 | the '-print' option for this. 48 | 49 | =item B<-print> 50 | 51 | Print notifications to stdout. This might be useful for logging, setting up 52 | rules or using the output in other scripts. 53 | 54 | =item B<--startup_notification> (values: [true/false], default: false) 55 | 56 | Display a notification on startup. 57 | 58 | =back 59 | 60 | =head1 CONFIGURATION 61 | 62 | A default configuration file is included (usually ##SYSCONFDIR##/dunst/dunstrc) 63 | and serves as the least important configuration file. Note: this was previously 64 | /usr/share/dunst/dunstrc. You can edit this file to change the system-wide 65 | defaults or copy it to a more important location to override its settings. See 66 | the FILES section for more details on where dunst searches for its 67 | configuration files and how settings get applied. 68 | 69 | See dunst(5) for all possible settings. 70 | 71 | =head2 NOTIFY-SEND 72 | 73 | dunst is able to get different colors for a message via notify-send. 74 | In order to do that you have to add a hint via the -h option. 75 | The progress value can be set with a hint, too. 76 | 77 | =over 4 78 | 79 | =item notify-send -h string:fgcolor:#ff4444 80 | 81 | =item notify-send -h string:bgcolor:#4444ff -h string:fgcolor:#ff4444 -h string:frcolor:#44ff44 82 | 83 | =item notify-send -h int:value:42 "Working ..." 84 | 85 | =back 86 | 87 | =head1 MISCELLANEOUS 88 | 89 | Dunst can be paused via the `dunstctl set-paused true` command. To unpause dunst use 90 | `dunstctl set-paused false`. 91 | Another way is to send SIGUSR1 and SIGUSR2 to pause and unpause 92 | respectively. Pausing using dunstctl is recommended over using signals, because 93 | the meaning of the signals is not be stable and might change in the future. 94 | 95 | When paused dunst will not display any notifications but keep all notifications 96 | in a queue. This can for example be wrapped around a screen locker (i3lock, 97 | slock) to prevent flickering of notifications through the lock and to read all 98 | missed notifications after returning to the computer. 99 | 100 | =head1 FILES 101 | 102 | These are the base directories dunst searches for configuration files in 103 | I: 104 | 105 | =over 8 106 | 107 | =item C<$XDG_CONFIG_HOME> 108 | 109 | This is the most important directory. (C<$HOME/.config> if unset or empty) 110 | 111 | =item C<$XDG_CONFIG_DIRS> 112 | 113 | This, like C<$PATH> for instance, is a :-separated list of base directories 114 | in I. 115 | (F<##SYSCONFDIR##> if unset or empty) 116 | 117 | =back 118 | 119 | Dunst will search these directories for the following relative file paths: 120 | 121 | =over 8 122 | 123 | =item F 124 | 125 | This is the base config and as such the least important in a particular base 126 | directory. 127 | 128 | =item F 129 | 130 | These are "drop-ins" (mind the ".d" suffix of the directory). 131 | They are more important than the base dunstrc in the parent directory, as they 132 | are considered to be small snippets to override settings. 133 | The last in lexical order is the most important one, so you can easily change 134 | the order by renaming them. 135 | A common approach to naming drop-ins is to prefix them with numbers, i.e.: 136 | 137 | 00-least-important.conf 138 | 01-foo.conf 139 | 20-bar.conf 140 | 99-most-important.conf 141 | 142 | Only files with the B<.conf> suffix will be read. 143 | 144 | =back 145 | 146 | Only settings from the last base config the corresponding drop-ins get applied. 147 | So if a dunstrc is first found in F<~/.config/dunst/dunstrc>, drop-ins will be 148 | searched in F<~/.config/dunst/dunstrc.d/*>. Settings in more important files 149 | override those in less important ones. 150 | 151 | =head1 AUTHORS 152 | 153 | Written by Sascha Kruse 154 | 155 | =head1 REPORTING BUGS 156 | 157 | Bugs and suggestions should be reported on GitHub at https://github.com/dunst-project/dunst/issues 158 | 159 | =head1 COPYRIGHT 160 | 161 | Copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) 162 | 163 | If you feel that copyrights are violated, please send me an email. 164 | 165 | =head1 SEE ALSO 166 | 167 | dunst(5), dunstctl(1), dmenu(1), notify-send(1) 168 | -------------------------------------------------------------------------------- /docs/dunst_layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FT-Labs/phyOS-dunst/aeb275a998778b05cee495efb9b61db66cdc0a7e/docs/dunst_layout.png -------------------------------------------------------------------------------- /docs/dunst_layout.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FT-Labs/phyOS-dunst/aeb275a998778b05cee495efb9b61db66cdc0a7e/docs/dunst_layout.xcf -------------------------------------------------------------------------------- /docs/dunstctl.pod: -------------------------------------------------------------------------------- 1 | =head1 NAME 2 | 3 | dunstctl - Command line control utility for dunst, a customizable and lightweight notification-daemon 4 | 5 | =head1 SYNOPSIS 6 | 7 | dunstctl COMMAND [PARAMETER] 8 | 9 | =head1 COMMANDS 10 | 11 | =over 4 12 | 13 | =item B notification_position 14 | 15 | Performs the default action or, if not available, opens the context menu of the 16 | notification at the given position (starting count at the top, first 17 | notification being 0). 18 | 19 | =item B 20 | 21 | Close the topmost notification currently being displayed. 22 | 23 | =item B 24 | 25 | Close all notifications currently being displayed 26 | 27 | =item B 28 | 29 | Open the context menu, presenting all available actions and urls for the 30 | currently open notifications. 31 | 32 | =item B [displayed/history/waiting] 33 | 34 | Returns the number of displayed, shown and waiting notifications. If no argument 35 | is provided, everything will be printed. 36 | 37 | =item B [ID] 38 | 39 | Redisplay the notification that was most recently closed. This can be called 40 | multiple times to show older notifications, up to the history limit configured 41 | in dunst. You can optionally pass an ID to history-pop, in which case the 42 | notification with the given ID will be shown. 43 | 44 | =item B 45 | 46 | Check if dunst is currently running or paused. If dunst is paused notifications 47 | will be kept but not shown until it is unpaused. 48 | 49 | =item B true/false/toggle 50 | 51 | Set the paused status of dunst. If false, dunst is running normally, if true, 52 | dunst is paused. See the is-paused command and the dunst man page for more 53 | information. 54 | 55 | =item B 56 | 57 | Tries to contact dunst and checks for common faults between dunstctl and dunst. 58 | Useful if something isn't working 59 | 60 | =item B 61 | 62 | Show all available commands with a brief description 63 | 64 | =back 65 | 66 | -------------------------------------------------------------------------------- /docs/internal/release-checklist.md: -------------------------------------------------------------------------------- 1 | # Main repo 2 | - [ ] Update the changelog 3 | - [ ] Write release notes (Only if non-patch release) 4 | 5 | - [ ] Verify that the working directory is clean and on the master branch 6 | - [ ] Change the version in the Makefile to "x.x.x (iso-date)" 7 | - [ ] Commit changes (Commit title: `Dunst vX.X.X`) 8 | - [ ] Tag commit, make sure it's an annotated tag (`git tag -a "vX.X.X" -m "Dunst vX.X.X"`) 9 | - [ ] Push commits 10 | - [ ] Push tags 11 | 12 | # Dunst-project.org 13 | - [ ] Run the update script (`REPO=../dunst ./update_new_release.sh OLDVER NEWVER`) 14 | - [ ] Verify that they look fine when rendered (`hugo serve`) 15 | - [ ] Commit changes 16 | - [ ] Run deploy script and push (`./deploy.sh -p`) 17 | 18 | # Main repo 19 | - [ ] Copy release notes to githubs release feature 20 | - [ ] Publish release on github 21 | - [ ] Update maint branch to point to master 22 | 23 | - [ ] Update Makefile version to "x.x.x-non-git" 24 | - [ ] Commit & push 25 | -------------------------------------------------------------------------------- /dunst.systemd.service.in: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Dunst notification daemon 3 | Documentation=man:dunst(1) 4 | PartOf=graphical-session.target 5 | 6 | [Service] 7 | Type=dbus 8 | BusName=org.freedesktop.Notifications 9 | ExecStart=##PREFIX##/bin/dunst 10 | -------------------------------------------------------------------------------- /dunstctl: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | DBUS_NAME="org.freedesktop.Notifications" 6 | DBUS_PATH="/org/freedesktop/Notifications" 7 | DBUS_IFAC_DUNST="org.dunstproject.cmd0" 8 | DBUS_IFAC_PROP="org.freedesktop.DBus.Properties" 9 | DBUS_IFAC_FDN="org.freedesktop.Notifications" 10 | 11 | die(){ printf "%s\n" "${1}" >&2; exit 1; } 12 | 13 | show_help() { 14 | # Below, each line starts with a tab character 15 | cat <<-EOH 16 | Usage: dunstctl [parameters]" 17 | Commands: 18 | action Perform the default action, or open the 19 | context menu of the notification at the 20 | given position 21 | close Close the last notification 22 | close-all Close the all notifications 23 | context Open context menu 24 | count [displayed|history|waiting] Show the number of notifications 25 | history Display notification history (in JSON) 26 | history-pop [ID] Pop the latest notification from 27 | history or optionally the 28 | notification with given ID. 29 | is-paused Check if dunst is running or paused 30 | set-paused [true|false|toggle] Set the pause status 31 | rule name [enable|disable|toggle] Enable or disable a rule by its name 32 | debug Print debugging information 33 | help Show this help 34 | EOH 35 | } 36 | dbus_send_checked() { 37 | dbus-send "$@" \ 38 | || 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." 39 | } 40 | 41 | method_call() { 42 | dbus_send_checked --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "$@" 43 | } 44 | 45 | property_get() { 46 | dbus_send_checked --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_PROP}.Get" "string:${DBUS_IFAC_DUNST}" "string:${1}" 47 | } 48 | 49 | property_set() { 50 | dbus_send_checked --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_PROP}.Set" "string:${DBUS_IFAC_DUNST}" "string:${1}" "${2}" 51 | } 52 | 53 | command -v dbus-send >/dev/null 2>/dev/null || \ 54 | die "Command dbus-send not found" 55 | 56 | 57 | case "${1:-}" in 58 | "action") 59 | method_call "${DBUS_IFAC_DUNST}.NotificationAction" "uint32:${2:-0}" >/dev/null 60 | ;; 61 | "close") 62 | method_call "${DBUS_IFAC_DUNST}.NotificationCloseLast" >/dev/null 63 | ;; 64 | "close-all") 65 | method_call "${DBUS_IFAC_DUNST}.NotificationCloseAll" >/dev/null 66 | ;; 67 | "context") 68 | method_call "${DBUS_IFAC_DUNST}.ContextMenuCall" >/dev/null 69 | ;; 70 | "count") 71 | [ $# -eq 1 ] || [ "${2}" = "displayed" ] || [ "${2}" = "history" ] || [ "${2}" = "waiting" ] \ 72 | || die "Please give either 'displayed', 'history', 'waiting' or none as count parameter." 73 | if [ $# -eq 1 ]; then 74 | property_get waitingLength | ( read -r _ _ waiting; printf " Waiting: %s\n" "${waiting}" ) 75 | property_get displayedLength | ( read -r _ _ displayed; printf " Currently displayed: %s\n" "${displayed}" ) 76 | property_get historyLength | ( read -r _ _ history; printf " History: %s\n" "${history}") 77 | else 78 | property_get ${2}Length | ( read -r _ _ notifications; printf "%s\n" "${notifications}"; ) 79 | fi 80 | ;; 81 | "history-pop") 82 | if [ "$#" -eq 1 ] 83 | then 84 | method_call "${DBUS_IFAC_DUNST}.NotificationShow" >/dev/null 85 | elif [ "$#" -eq 2 ] 86 | then 87 | method_call "${DBUS_IFAC_DUNST}.NotificationPopHistory" "uint32:${2:-0}" >/dev/null 88 | else 89 | die "Please pass the right number of arguments. History-pop takes 0 or 1 arguments" 90 | fi 91 | ;; 92 | "is-paused") 93 | property_get paused | ( read -r _ _ paused; printf "%s\n" "${paused}"; ) 94 | ;; 95 | "set-paused") 96 | [ "${2:-}" ] \ 97 | || die "No status parameter specified. Please give either 'true', 'false' or 'toggle' as paused parameter." 98 | [ "${2}" = "true" ] || [ "${2}" = "false" ] || [ "${2}" = "toggle" ] \ 99 | || die "Please give either 'true', 'false' or 'toggle' as paused parameter." 100 | if [ "${2}" = "toggle" ]; then 101 | paused=$(property_get paused | ( read -r _ _ paused; printf "%s\n" "${paused}"; )) 102 | if [ "${paused}" = "true" ]; then 103 | property_set paused variant:boolean:false 104 | else 105 | property_set paused variant:boolean:true 106 | fi 107 | else 108 | property_set paused variant:boolean:"$2" 109 | fi 110 | ;; 111 | "rule") 112 | [ "${2:-}" ] \ 113 | || die "No rule name parameter specified. Please give the rule name" 114 | state=nope 115 | [ "${3}" = "disable" ] && state=0 116 | [ "${3}" = "enable" ] && state=1 117 | [ "${3}" = "toggle" ] && state=2 118 | [ "${state}" = "nope" ] \ 119 | && die "No valid rule state parameter specified. Please give either 'enable', 'disable' or 'toggle'" 120 | method_call "${DBUS_IFAC_DUNST}.RuleEnable" "string:${2:-1}" "int32:${state}" >/dev/null 121 | ;; 122 | "help"|"--help"|"-h") 123 | show_help 124 | ;; 125 | "debug") 126 | dbus-send --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_FDN}.GetServerInformation" >/dev/null 2>/dev/null \ 127 | || die "Dunst is not running." 128 | 129 | dbus-send --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_FDN}.GetServerInformation" \ 130 | | ( 131 | read -r name _ version _ 132 | [ "${name}" = "dunst" ] 133 | printf "dunst version: %s\n" "${version}" 134 | ) \ 135 | || die "Another notification manager is running. It's not dunst" 136 | 137 | dbus-send --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_DUNST}.Ping" >/dev/null 2>/dev/null \ 138 | || die "Dunst controlling interface not available. Is the version too old?" 139 | ;; 140 | "history") 141 | busctl --user --json=pretty --no-pager call org.freedesktop.Notifications /org/freedesktop/Notifications org.dunstproject.cmd0 NotificationListHistory 2>/dev/null \ 142 | || die "Dunst is not running." 143 | ;; 144 | "") 145 | die "dunstctl: No command specified. Please consult the usage." 146 | ;; 147 | *) 148 | die "dunstctl: unrecognized command '${1:-}'. Please consult the usage." 149 | ;; 150 | esac 151 | # vim: noexpandtab 152 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include "src/dunst.h" 2 | 3 | int main(int argc, char *argv[]) 4 | { 5 | return dunst_main(argc, argv); 6 | } 7 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 8 | -------------------------------------------------------------------------------- /org.knopwob.dunst.service.in: -------------------------------------------------------------------------------- 1 | [D-BUS Service] 2 | Name=org.freedesktop.Notifications 3 | Exec=##PREFIX##/bin/dunst 4 | SystemdService=dunst.service 5 | -------------------------------------------------------------------------------- /src/dbus.h: -------------------------------------------------------------------------------- 1 | /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ 2 | 3 | #ifndef DUNST_DBUS_H 4 | #define DUNST_DBUS_H 5 | 6 | #include "dunst.h" 7 | #include "notification.h" 8 | 9 | /// The reasons according to the notification spec 10 | enum reason { 11 | REASON_MIN = 1, /**< Minimum value, useful for boundary checking */ 12 | REASON_TIME = 1, /**< The notification timed out */ 13 | REASON_USER = 2, /**< The user closed the notification */ 14 | REASON_SIG = 3, /**< The daemon received a `NotificationClose` signal */ 15 | REASON_UNDEF = 4, /**< Undefined reason not matching the previous ones */ 16 | REASON_MAX = 4, /**< Maximum value, useful for boundary checking */ 17 | }; 18 | 19 | int dbus_init(void); 20 | void dbus_teardown(int id); 21 | void signal_notification_closed(struct notification *n, enum reason reason); 22 | void signal_action_invoked(const struct notification *n, const char *identifier); 23 | 24 | #endif 25 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 26 | -------------------------------------------------------------------------------- /src/draw.h: -------------------------------------------------------------------------------- 1 | #ifndef DUNST_DRAW_H 2 | #define DUNST_DRAW_H 3 | 4 | #include 5 | #include 6 | #include "output.h" 7 | 8 | extern window win; // Temporary 9 | extern const struct output *output; 10 | 11 | void draw_setup(void); 12 | 13 | void draw(void); 14 | 15 | void draw_rounded_rect(cairo_t *c, int x, int y, int width, int height, int corner_radius, double scale, bool first, bool last); 16 | 17 | // TODO get rid of this function by passing scale to everything that needs it. 18 | double draw_get_scale(void); 19 | 20 | void draw_deinit(void); 21 | 22 | void calc_window_pos(const struct screen_info *scr, int width, int height, int *ret_x, int *ret_y); 23 | 24 | #endif 25 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 26 | -------------------------------------------------------------------------------- /src/dunst.h: -------------------------------------------------------------------------------- 1 | /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ 2 | 3 | #ifndef DUNST_DUNST_H 4 | #define DUNST_DUNST_H 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "notification.h" 12 | 13 | //!< A structure to describe dunst's global window status 14 | struct dunst_status { 15 | bool fullscreen; //!< a fullscreen window is currently focused 16 | bool running; //!< set true if dunst is currently running 17 | bool idle; //!< set true if the user is idle 18 | }; 19 | 20 | enum dunst_status_field { 21 | S_FULLSCREEN, 22 | S_IDLE, 23 | S_RUNNING, 24 | }; 25 | 26 | /** 27 | * Modify the current status of dunst 28 | * @param field The field to change in the global status structure 29 | * @param value Anything boolean or DO_TOGGLE to toggle the current value 30 | */ 31 | void dunst_status(const enum dunst_status_field field, 32 | bool value); 33 | 34 | struct dunst_status dunst_status_get(void); 35 | 36 | void wake_up(void); 37 | 38 | int dunst_main(int argc, char *argv[]); 39 | 40 | void usage(int exit_status); 41 | void print_version(void); 42 | 43 | gboolean quit_signal(gpointer data); 44 | 45 | #endif 46 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 47 | -------------------------------------------------------------------------------- /src/icon-lookup.h: -------------------------------------------------------------------------------- 1 | #ifndef DUNST_ICON_LOOKUP_H 2 | #define DUNST_ICON_LOOKUP_H 3 | 4 | struct icon_theme { 5 | char *name; 6 | char *location; // full path to the theme 7 | char *subdir_theme; // name of the directory in which the theme is located 8 | 9 | int inherits_count; 10 | int *inherits_index; 11 | 12 | int dirs_count; 13 | struct icon_theme_dir *dirs; 14 | }; 15 | 16 | enum theme_dir_type { THEME_DIR_FIXED, THEME_DIR_SCALABLE, THEME_DIR_THRESHOLD }; 17 | 18 | struct icon_theme_dir { 19 | char *name; 20 | int size; 21 | int scale; 22 | int min_size, max_size; 23 | int threshold; 24 | enum theme_dir_type type; 25 | }; 26 | 27 | 28 | /** 29 | * Load a theme with given name from a standard icon directory. Don't call this 30 | * function if the theme is already loaded. 31 | * 32 | * @param name Name of the directory in which the theme is located. Note that 33 | * it is NOT the name of the theme as specified in index.theme. 34 | * @returns The index of the theme, which can be used to set it as default. 35 | * @retval -1 if the icon theme cannot be loaded. 36 | */ 37 | int load_icon_theme(char *name); 38 | 39 | 40 | /** 41 | * Add theme to the list of default themes. The theme that's added first will 42 | * be used first for lookup. After that the inherited themes will be used and 43 | * only after that the next default theme will be used. 44 | * 45 | * @param theme_index The index of the theme as returned by #load_icon_theme 46 | */ 47 | void add_default_theme(int theme_index); 48 | 49 | /** 50 | * Find icon of specified size in selected theme. This function will not return 51 | * icons that cannot be scaled to \p size according to index.theme. 52 | * 53 | * @param name Name of the icon or full path to it. 54 | * @param theme_index Index of the theme to use. 55 | * @param size Size of the icon. 56 | * @returns The full path to the icon. 57 | * @retval NULL if the icon cannot be found or is not readable. 58 | */ 59 | char *find_icon_in_theme(const char *name, int theme_index, int size); 60 | char *find_icon_path(const char *name, int size); 61 | void set_default_theme(int theme_index); 62 | 63 | /** 64 | * Find icon of specified size in the default theme or an inherited theme. This 65 | * function will not return icons that cannot be scaled to \p size according to 66 | * index.theme. 67 | 68 | * 69 | * @param name Name of the icon or full path to it. 70 | * @param size Size of the icon. 71 | * @returns The full path to the icon. 72 | * @retval NULL if the icon cannot be found or is not readable. 73 | */ 74 | char *find_icon_path(const char *name, int size); 75 | 76 | /** 77 | * Free all icon themes. 78 | */ 79 | void free_all_themes(); 80 | 81 | #endif 82 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 83 | -------------------------------------------------------------------------------- /src/icon.h: -------------------------------------------------------------------------------- 1 | #ifndef DUNST_ICON_H 2 | #define DUNST_ICON_H 3 | 4 | #include 5 | #include 6 | 7 | #include "notification.h" 8 | 9 | cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf); 10 | 11 | /** Retrieve an icon by its full filepath, scaled according to settings. 12 | * 13 | * @param filename A string representing a readable file path 14 | * @param min_size An iteger representing the desired minimum unscaled icon size. 15 | * @param max_size An iteger representing the desired maximum unscaled icon size. 16 | * @param scale An integer representing the output dpi scaling. 17 | * 18 | * @return an instance of `GdkPixbuf` 19 | * @retval NULL: file does not exist, not readable, etc.. 20 | */ 21 | GdkPixbuf *get_pixbuf_from_file(const char *filename, int min_size, int max_size, double scale); 22 | 23 | 24 | /** 25 | * Get the unscaled icon width. 26 | * 27 | * If scale is 2 for example, the icon will render in twice the size, but 28 | * get_icon_width still returns the same size as when scale is 1. 29 | */ 30 | int get_icon_width(cairo_surface_t *icon, double scale); 31 | 32 | /** 33 | * Get the unscaled icon height, see get_icon_width. 34 | */ 35 | int get_icon_height(cairo_surface_t *icon, double scale); 36 | 37 | /** Retrieve a path from an icon name. 38 | * 39 | * @param iconname A string describing a `file://` URL, an arbitary filename 40 | * or an icon name, which then gets searched for in the 41 | * settings.icon_path 42 | * @param size Size of the icon to look for. This is only used when 43 | * recursive icon lookup is enabled. 44 | * 45 | * @return a newly allocated string with the icon path 46 | * @retval NULL: file does not exist, not readable, etc.. 47 | */ 48 | char *get_path_from_icon_name(const char *iconname, int size); 49 | 50 | /** Convert a GVariant like described in GdkPixbuf, scaled according to settings 51 | * 52 | * The returned id will be a unique identifier. To check if two given 53 | * GdkPixbufs are equal, it's sufficient to just compare the id strings. 54 | * 55 | * @param data A GVariant in the format "(iiibii@ay)" filled with values 56 | * like described in the notification spec. 57 | * @param id (necessary) A unique identifier of the returned pixbuf. 58 | * Only filled, if the return value is non-NULL. 59 | * @param dpi_scale An integer representing the output dpi scaling. 60 | * @param min_size An integer representing the desired minimum unscaled icon size. 61 | * @param max_size An integer representing the desired maximum unscaled icon size. 62 | * @return an instance of `GdkPixbuf` derived from the GVariant 63 | * @retval NULL: GVariant parameter nulled, invalid or in wrong format 64 | */ 65 | GdkPixbuf *icon_get_for_data(GVariant *data, char **id, double dpi_scale, int min_size, int max_size); 66 | 67 | #endif 68 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 69 | -------------------------------------------------------------------------------- /src/ini.c: -------------------------------------------------------------------------------- 1 | #include "ini.h" 2 | 3 | #include "utils.h" 4 | #include "log.h" 5 | #include "settings.h" 6 | 7 | struct section *get_section(struct ini *ini, const char *name) 8 | { 9 | for (int i = 0; i < ini->section_count; i++) { 10 | if (STR_EQ(ini->sections[i].name, name)) 11 | return &ini->sections[i]; 12 | } 13 | 14 | return NULL; 15 | } 16 | 17 | struct section *get_or_create_section(struct ini *ini, const char *name) 18 | { 19 | struct section *s = get_section(ini, name); 20 | if (!s) { 21 | ini->section_count++; 22 | ini->sections = g_realloc(ini->sections, sizeof(struct section) * ini->section_count); 23 | 24 | s = &ini->sections[ini->section_count - 1]; 25 | s->name = g_strdup(name); 26 | s->entries = NULL; 27 | s->entry_count = 0; 28 | } 29 | return s; 30 | } 31 | 32 | void add_entry(struct ini *ini, const char *section_name, const char *key, const char *value) 33 | { 34 | struct section *s = get_or_create_section(ini, section_name); 35 | 36 | s->entry_count++; 37 | int len = s->entry_count; 38 | s->entries = g_realloc(s->entries, sizeof(struct entry) * len); 39 | s->entries[s->entry_count - 1].key = g_strdup(key); 40 | s->entries[s->entry_count - 1].value = string_strip_quotes(value); 41 | } 42 | 43 | const char *section_get_value(struct ini *ini, const struct section *s, const char *key) 44 | { 45 | ASSERT_OR_RET(s, NULL); 46 | 47 | for (int i = 0; i < s->entry_count; i++) { 48 | if (STR_EQ(s->entries[i].key, key)) { 49 | return s->entries[i].value; 50 | } 51 | } 52 | return NULL; 53 | } 54 | 55 | const char *get_value(struct ini *ini, const char *section, const char *key) 56 | { 57 | struct section *s = get_section(ini, section); 58 | return section_get_value(ini, s, key); 59 | } 60 | 61 | bool ini_is_set(struct ini *ini, const char *ini_section, const char *ini_key) 62 | { 63 | return get_value(ini, ini_section, ini_key) != NULL; 64 | } 65 | 66 | const char *next_section(const struct ini *ini,const char *section) 67 | { 68 | ASSERT_OR_RET(ini->section_count > 0, NULL); 69 | ASSERT_OR_RET(section, ini->sections[0].name); 70 | 71 | for (int i = 0; i < ini->section_count; i++) { 72 | if (STR_EQ(section, ini->sections[i].name)) { 73 | if (i + 1 >= ini->section_count) 74 | return NULL; 75 | else 76 | return ini->sections[i + 1].name; 77 | } 78 | } 79 | return NULL; 80 | } 81 | 82 | struct ini *load_ini_file(FILE *fp) 83 | { 84 | if (!fp) 85 | return NULL; 86 | 87 | struct ini *ini = calloc(1, sizeof(struct ini)); 88 | char *line = NULL; 89 | size_t line_len = 0; 90 | 91 | int line_num = 0; 92 | char *current_section = NULL; 93 | while (getline(&line, &line_len, fp) != -1) { 94 | line_num++; 95 | 96 | char *start = g_strstrip(line); 97 | 98 | if (*start == ';' || *start == '#' || STR_EMPTY(start)) 99 | continue; 100 | 101 | if (*start == '[') { 102 | char *end = strchr(start + 1, ']'); 103 | if (!end) { 104 | LOG_W("Invalid config file at line %d: Missing ']'.", line_num); 105 | continue; 106 | } 107 | 108 | *end = '\0'; 109 | 110 | g_free(current_section); 111 | current_section = g_strdup(start + 1); 112 | continue; 113 | } 114 | 115 | char *equal = strchr(start + 1, '='); 116 | if (!equal) { 117 | LOG_W("Invalid config file at line %d: Missing '='.", line_num); 118 | continue; 119 | } 120 | 121 | *equal = '\0'; 122 | char *key = g_strstrip(start); 123 | char *value = g_strstrip(equal + 1); 124 | 125 | char *quote = strchr(value, '"'); 126 | char *value_end = NULL; 127 | if (quote) { 128 | value_end = strchr(quote + 1, '"'); 129 | if (!value_end) { 130 | LOG_W("Invalid config file at line %d: Missing '\"'.", line_num); 131 | continue; 132 | } 133 | } else { 134 | value_end = value; 135 | } 136 | 137 | char *comment = strpbrk(value_end, "#;"); 138 | if (comment) 139 | *comment = '\0'; 140 | 141 | value = g_strstrip(value); 142 | 143 | if (!current_section) { 144 | LOG_W("Invalid config file at line %d: Key value pair without a section.", line_num); 145 | continue; 146 | } 147 | 148 | add_entry(ini, current_section, key, value); 149 | } 150 | free(line); 151 | g_free(current_section); 152 | return ini; 153 | } 154 | 155 | 156 | void finish_ini(struct ini *ini) 157 | { 158 | for (int i = 0; i < ini->section_count; i++) { 159 | for (int j = 0; j < ini->sections[i].entry_count; j++) { 160 | g_free(ini->sections[i].entries[j].key); 161 | g_free(ini->sections[i].entries[j].value); 162 | } 163 | g_free(ini->sections[i].entries); 164 | g_free(ini->sections[i].name); 165 | } 166 | g_clear_pointer(&ini->sections, g_free); 167 | ini->section_count = 0; 168 | } 169 | 170 | -------------------------------------------------------------------------------- /src/ini.h: -------------------------------------------------------------------------------- 1 | /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ 2 | #ifndef DUNST_INI_H 3 | #define DUNST_INI_H 4 | 5 | #include 6 | #include 7 | 8 | struct entry { 9 | char *key; 10 | char *value; 11 | }; 12 | 13 | struct section { 14 | char *name; 15 | int entry_count; 16 | struct entry *entries; 17 | }; 18 | 19 | struct ini { 20 | int section_count; 21 | struct section *sections; 22 | }; 23 | 24 | /* returns the next known section. 25 | * if section == NULL returns first section. 26 | * returns NULL if no more sections are available 27 | */ 28 | const char *next_section(const struct ini *ini,const char *section); 29 | const char *section_get_value(struct ini *ini, const struct section *s, const char *key); 30 | const char *get_value(struct ini *ini, const char *section, const char *key); 31 | struct ini *load_ini_file(FILE *fp); 32 | void finish_ini(struct ini *ini); 33 | 34 | #endif 35 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 36 | -------------------------------------------------------------------------------- /src/input.c: -------------------------------------------------------------------------------- 1 | #include "input.h" 2 | #include "log.h" 3 | #include "menu.h" 4 | #include "settings.h" 5 | #include "queues.h" 6 | #include 7 | #include 8 | 9 | int get_notification_clickable_height(struct notification *n, bool first, bool last) 10 | { 11 | int notification_size = n->displayed_height; 12 | if (settings.gap_size) { 13 | notification_size += settings.frame_width * 2; 14 | } else { 15 | double half_separator = settings.separator_height / 2.0; 16 | notification_size += settings.separator_height; 17 | if(first) 18 | notification_size += (settings.frame_width - half_separator); 19 | if(last) 20 | notification_size += (settings.frame_width - half_separator); 21 | } 22 | return notification_size; 23 | } 24 | 25 | struct notification *get_notification_at(const int y) { 26 | int curr_y = 0; 27 | bool first = true; 28 | bool last; 29 | for (const GList *iter = queues_get_displayed(); iter; 30 | iter = iter->next) { 31 | struct notification *current = iter->data; 32 | struct notification *next = iter->next ? iter->next->data : NULL; 33 | 34 | last = !next; 35 | int notification_size = get_notification_clickable_height(current, first, last); 36 | 37 | if (y >= curr_y && y < curr_y + notification_size) { 38 | return current; 39 | } 40 | 41 | curr_y += notification_size; 42 | if (settings.gap_size) 43 | curr_y += settings.gap_size; 44 | 45 | first = false; 46 | } 47 | // no matching notification was found 48 | return NULL; 49 | } 50 | 51 | void input_handle_click(unsigned int button, bool button_down, int mouse_x, int mouse_y){ 52 | LOG_I("Pointer handle button %i: %i", button, button_down); 53 | 54 | if (button_down) { 55 | // make sure it only reacts on button release 56 | return; 57 | } 58 | 59 | enum mouse_action *acts; 60 | 61 | switch (button) { 62 | case BTN_LEFT: 63 | acts = settings.mouse_left_click; 64 | break; 65 | case BTN_MIDDLE: 66 | acts = settings.mouse_middle_click; 67 | break; 68 | case BTN_RIGHT: 69 | acts = settings.mouse_right_click; 70 | break; 71 | case BTN_TOUCH: 72 | // TODO Add separate action for touch 73 | acts = settings.mouse_left_click; 74 | break; 75 | default: 76 | LOG_W("Unsupported mouse button: '%d'", button); 77 | return; 78 | } 79 | 80 | // if other list types are added, make sure they have the same end value 81 | for (int i = 0; acts[i] != MOUSE_ACTION_END; i++) { 82 | enum mouse_action act = acts[i]; 83 | if (act == MOUSE_CLOSE_ALL) { 84 | queues_history_push_all(); 85 | continue; 86 | } 87 | 88 | if (act == MOUSE_CONTEXT_ALL) { 89 | context_menu(); 90 | continue; 91 | } 92 | 93 | if (act == MOUSE_DO_ACTION || act == MOUSE_CLOSE_CURRENT || act == MOUSE_CONTEXT || act == MOUSE_OPEN_URL) { 94 | struct notification *n = get_notification_at(mouse_y); 95 | 96 | if (n) { 97 | if (act == MOUSE_CLOSE_CURRENT) { 98 | n->marked_for_closure = REASON_USER; 99 | } else if (act == MOUSE_DO_ACTION) { 100 | notification_do_action(n); 101 | } else if (act == MOUSE_OPEN_URL) { 102 | notification_open_url(n); 103 | } else { 104 | notification_open_context_menu(n); 105 | } 106 | } 107 | } 108 | } 109 | 110 | wake_up(); 111 | } 112 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 113 | -------------------------------------------------------------------------------- /src/input.h: -------------------------------------------------------------------------------- 1 | #ifndef DUNST_INPUT_H 2 | #define DUNST_INPUT_H 3 | 4 | #include 5 | 6 | /** 7 | * Handle incoming mouse click events 8 | * 9 | * @param button code, A linux input event code 10 | * @param button_down State of the button 11 | * @param mouse_x X-position of the mouse, relative to the window 12 | * @param mouse_y Y-position of the mouse, relative to the window 13 | * 14 | */ 15 | void input_handle_click(unsigned int button, bool button_down, int mouse_x, int mouse_y); 16 | 17 | #endif 18 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 19 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | /* copyright 2012 - 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ 2 | 3 | /** 4 | * @file src/log.c 5 | * @brief logging wrapper to use GLib's logging capabilities 6 | */ 7 | 8 | #include "log.h" 9 | 10 | #include 11 | 12 | #include "utils.h" 13 | 14 | static GLogLevelFlags log_level = G_LOG_LEVEL_WARNING; 15 | 16 | /* see log.h */ 17 | static const char *log_level_to_string(GLogLevelFlags level) 18 | { 19 | switch (level) { 20 | case G_LOG_LEVEL_ERROR: return "ERROR"; 21 | case G_LOG_LEVEL_CRITICAL: return "CRITICAL"; 22 | case G_LOG_LEVEL_WARNING: return "WARNING"; 23 | case G_LOG_LEVEL_MESSAGE: return "MESSAGE"; 24 | case G_LOG_LEVEL_INFO: return "INFO"; 25 | case G_LOG_LEVEL_DEBUG: return "DEBUG"; 26 | default: return "UNKNOWN"; 27 | } 28 | } 29 | 30 | /* see log.h */ 31 | void log_set_level_from_string(const char *level) 32 | { 33 | ASSERT_OR_RET(level,); 34 | 35 | if (STR_CASEQ(level, "critical")) 36 | log_level = G_LOG_LEVEL_CRITICAL; 37 | else if (STR_CASEQ(level, "crit")) 38 | log_level = G_LOG_LEVEL_CRITICAL; 39 | else if (STR_CASEQ(level, "warning")) 40 | log_level = G_LOG_LEVEL_WARNING; 41 | else if (STR_CASEQ(level, "warn")) 42 | log_level = G_LOG_LEVEL_WARNING; 43 | else if (STR_CASEQ(level, "message")) 44 | log_level = G_LOG_LEVEL_MESSAGE; 45 | else if (STR_CASEQ(level, "mesg")) 46 | log_level = G_LOG_LEVEL_MESSAGE; 47 | else if (STR_CASEQ(level, "info")) 48 | log_level = G_LOG_LEVEL_INFO; 49 | else if (STR_CASEQ(level, "debug")) 50 | log_level = G_LOG_LEVEL_DEBUG; 51 | else if (STR_CASEQ(level, "deb")) 52 | log_level = G_LOG_LEVEL_DEBUG; 53 | else 54 | LOG_W("Unknown log level: '%s'", level); 55 | } 56 | 57 | void log_set_level(GLogLevelFlags level) 58 | { 59 | log_level = level; 60 | } 61 | 62 | /** 63 | * Log handling function for GLib's logging wrapper 64 | * 65 | * @param log_domain Used only by GLib 66 | * @param message_level Used only by GLib 67 | * @param message Used only by GLib 68 | * @param testing If not `NULL` (here: `true`), do nothing 69 | */ 70 | static void dunst_log_handler( 71 | const gchar *log_domain, 72 | GLogLevelFlags message_level, 73 | const gchar *message, 74 | gpointer testing) 75 | { 76 | if (testing) 77 | log_level = G_LOG_LEVEL_ERROR; 78 | 79 | GLogLevelFlags message_level_masked = message_level & G_LOG_LEVEL_MASK; 80 | 81 | /* if you want to have a debug build, you want to log anything, 82 | * unconditionally, without specifying debug log level again */ 83 | #ifndef DEBUG_BUILD 84 | if (log_level < message_level_masked) 85 | return; 86 | #endif 87 | const char *log_level_str = 88 | log_level_to_string(message_level_masked); 89 | 90 | /* Use stderr for warnings and higher */ 91 | if (message_level_masked <= G_LOG_LEVEL_WARNING) 92 | g_printerr("%s: %s\n", log_level_str, message); 93 | else 94 | g_print("%s: %s\n", log_level_str, message); 95 | } 96 | 97 | /* see log.h */ 98 | void dunst_log_init(bool testing) 99 | { 100 | g_log_set_default_handler(dunst_log_handler, (void*)testing); 101 | } 102 | 103 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 104 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifndef DUNST_LOG_H 10 | #define DUNST_LOG_H 11 | 12 | /** 13 | * Prefix message with "[::] " 14 | * 15 | * @param format is either a format string like the first argument 16 | * of printf() or a string literal. 17 | * @... are the arguments to above format string. 18 | * 19 | * This requires -Wno-gnu-zero-variadic-macro-arguments with clang 20 | * because of token pasting ',' and %__VA_ARGS__ being a GNU extension. 21 | * However, the result is the same with both gcc and clang and since we are 22 | * compiling with '-std=gnu99', this should be fine. 23 | */ 24 | #if __GNUC__ >= 8 || __clang_major__ >= 6 25 | #define MSG(format, ...) "[%16s:%04d] " format, __func__, __LINE__, ## __VA_ARGS__ 26 | #endif 27 | 28 | #ifdef MSG 29 | // These should benefit from more context 30 | #define LOG_E(...) g_error(MSG(__VA_ARGS__)) 31 | #define LOG_C(...) g_critical(MSG(__VA_ARGS__)) 32 | #define LOG_D(...) g_debug(MSG(__VA_ARGS__)) 33 | #else 34 | #define LOG_E g_error 35 | #define LOG_C g_critical 36 | #define LOG_D g_debug 37 | #endif 38 | 39 | #define LOG_W g_warning 40 | #define LOG_M g_message 41 | #define LOG_I g_info 42 | 43 | #define DIE(...) do { LOG_C(__VA_ARGS__); exit(EXIT_FAILURE); } while (0) 44 | 45 | // unified fopen() result messages 46 | #define MSG_FOPEN_SUCCESS(path, fp) "'%s' open, fd: '%d'", path, fileno(fp) 47 | #define MSG_FOPEN_FAILURE(path) "Cannot open '%s': '%s'", path, strerror(errno) 48 | 49 | /** 50 | * Set the current loglevel to `level` 51 | * 52 | * @param level The desired log level 53 | * 54 | * If `level` is `NULL`, nothing will be done. 55 | * If `level` is an invalid value, nothing will be done. 56 | */ 57 | void log_set_level(GLogLevelFlags level); 58 | 59 | /** 60 | * Set the current loglevel to `level` 61 | * 62 | * @param level The desired log level as a string 63 | * 64 | * If `level` is `NULL`, nothing will be done. 65 | * If `level` is an invalid value, nothing will be done. 66 | */ 67 | void log_set_level_from_string(const char* level); 68 | 69 | /** 70 | * Initialise log handling. Can be called any time. 71 | * 72 | * @param testing If we're in testing mode and should 73 | * suppress all output 74 | */ 75 | void dunst_log_init(bool testing); 76 | 77 | #endif 78 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 79 | -------------------------------------------------------------------------------- /src/markup.h: -------------------------------------------------------------------------------- 1 | /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ 2 | #ifndef DUNST_MARKUP_H 3 | #define DUNST_MARKUP_H 4 | 5 | enum markup_mode { 6 | MARKUP_NULL, 7 | MARKUP_NO, 8 | MARKUP_STRIP, 9 | MARKUP_FULL 10 | }; 11 | 12 | /** 13 | * Strip any markup from text; turn it in to plain text. 14 | * 15 | * For well-formed markup, the following two commands should be 16 | * roughly equivalent: 17 | * 18 | * out = markup_strip(in); 19 | * pango_parse_markup(in, -1, 0, NULL, &out, NULL, NULL); 20 | * 21 | * However, `pango_parse_markup()` balks at invalid markup; 22 | * `markup_strip()` shouldn't care if there is invalid markup. 23 | */ 24 | char *markup_strip(char *str); 25 | 26 | /** 27 | * Remove HTML hyperlinks of a string. 28 | * 29 | * @param str The string to replace a tags 30 | * @param urls (nullable) If any href-attributes found, an `\n` concatenated 31 | * string of the URLs in format `[] ` 32 | */ 33 | void markup_strip_a(char **str, char **urls); 34 | 35 | /** 36 | * Remove img-tags of a string. If alt attribute given, use this as replacement. 37 | * 38 | * @param str The string to replace img tags 39 | * @param urls (nullable) If any src-attributes found, an `\n` concatenated string of 40 | * the URLs in format `[] ` 41 | */ 42 | void markup_strip_img(char **str, char **urls); 43 | 44 | /** 45 | * Transform the string in accordance with `markup_mode` and 46 | * `settings.ignore_newline` 47 | */ 48 | char *markup_transform(char *str, enum markup_mode markup_mode); 49 | 50 | #endif 51 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 52 | -------------------------------------------------------------------------------- /src/menu.h: -------------------------------------------------------------------------------- 1 | /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ 2 | #ifndef DUNST_MENU_H 3 | #define DUNST_MENU_H 4 | 5 | #include 6 | 7 | /** 8 | * Extract all urls from the given string. 9 | * 10 | * @param to_match (nullable) String to extract URLs 11 | * @return a string of urls separated by '\n' 12 | * @retval NULL: No URLs found 13 | */ 14 | char *extract_urls(const char *to_match); 15 | 16 | void open_browser(const char *in); 17 | void invoke_action(const char *action); 18 | void regex_teardown(void); 19 | 20 | /** 21 | * Open the context menu that lets the user select urls/actions/etc for all displayed notifications. 22 | */ 23 | void context_menu(void); 24 | 25 | /** 26 | * Open the context menu that lets the user select urls/actions/etc for the specified notifications. 27 | * @param notifications (nullable) List of notifications for which the context menu should be opened 28 | */ 29 | void context_menu_for(GList *notifications); 30 | 31 | #endif 32 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 33 | -------------------------------------------------------------------------------- /src/option_parser.h: -------------------------------------------------------------------------------- 1 | /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ 2 | #ifndef DUNST_OPTION_PARSER_H 3 | #define DUNST_OPTION_PARSER_H 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "dunst.h" 10 | #include "settings.h" 11 | #include "ini.h" 12 | 13 | int string_parse_enum(const void* data, const char *s, void * ret); 14 | int string_parse_sepcolor(const void *data, const char *s, void *ret); 15 | int string_parse_bool(const void *data, const char *s, void *ret); 16 | 17 | void set_defaults(); 18 | void save_settings(struct ini *ini); 19 | 20 | void cmdline_load(int argc, char *argv[]); 21 | /* for all cmdline_get_* key can be either "-key" or "-key/-longkey" */ 22 | char *cmdline_get_string(const char *key, const char *def, const char *description); 23 | char *cmdline_get_path(const char *key, const char *def, const char *description); 24 | char **cmdline_get_list(const char *key, const char *def, const char *description); 25 | int cmdline_get_int(const char *key, int def, const char *description); 26 | double cmdline_get_double(const char *key, double def, const char *description); 27 | int cmdline_get_bool(const char *key, int def, const char *description); 28 | bool cmdline_is_set(const char *key); 29 | const char *cmdline_create_usage(void); 30 | 31 | #endif 32 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 33 | -------------------------------------------------------------------------------- /src/output.c: -------------------------------------------------------------------------------- 1 | #include "output.h" 2 | 3 | #include "log.h" 4 | #include "x11/x.h" 5 | #include "x11/screen.h" 6 | 7 | #ifdef ENABLE_WAYLAND 8 | #include "wayland/wl.h" 9 | #endif 10 | 11 | bool is_running_wayland(void) { 12 | char* wayland_display = getenv("WAYLAND_DISPLAY"); 13 | return !(wayland_display == NULL); 14 | } 15 | 16 | const struct output output_x11 = { 17 | x_setup, 18 | x_free, 19 | 20 | x_win_create, 21 | x_win_destroy, 22 | 23 | x_win_show, 24 | x_win_hide, 25 | 26 | x_display_surface, 27 | x_win_get_context, 28 | 29 | get_active_screen, 30 | 31 | x_is_idle, 32 | have_fullscreen_window, 33 | 34 | x_get_scale, 35 | }; 36 | 37 | #ifdef ENABLE_WAYLAND 38 | const struct output output_wl = { 39 | wl_init, 40 | wl_deinit, 41 | 42 | wl_win_create, 43 | wl_win_destroy, 44 | 45 | wl_win_show, 46 | wl_win_hide, 47 | 48 | wl_display_surface, 49 | wl_win_get_context, 50 | 51 | wl_get_active_screen, 52 | 53 | wl_is_idle, 54 | wl_have_fullscreen_window, 55 | 56 | wl_get_scale, 57 | }; 58 | #endif 59 | 60 | const struct output* get_x11_output() { 61 | const struct output* output = &output_x11; 62 | if (output->init()) { 63 | return output; 64 | } else { 65 | LOG_E("Couldn't initialize X11 output. Aborting..."); 66 | } 67 | } 68 | 69 | #ifdef ENABLE_WAYLAND 70 | const struct output* get_wl_output() { 71 | const struct output* output = &output_wl; 72 | if (output->init()) { 73 | return output; 74 | } else { 75 | LOG_W("Couldn't initialize wayland output. Falling back to X11 output."); 76 | output->deinit(); 77 | return get_x11_output(); 78 | } 79 | } 80 | #endif 81 | 82 | const struct output* output_create(bool force_xwayland) 83 | { 84 | #ifdef ENABLE_WAYLAND 85 | if (!force_xwayland && is_running_wayland()) { 86 | LOG_I("Using Wayland output"); 87 | return get_wl_output(); 88 | } else { 89 | LOG_I("Using X11 output"); 90 | return get_x11_output(); 91 | } 92 | #else 93 | return get_x11_output(); 94 | #endif 95 | } 96 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 97 | -------------------------------------------------------------------------------- /src/output.h: -------------------------------------------------------------------------------- 1 | #ifndef DUNST_OUTPUT_H 2 | #define DUNST_OUTPUT_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef gpointer window; 9 | 10 | struct dimensions { 11 | int x; 12 | int y; 13 | int w; 14 | int h; 15 | int text_width; 16 | int text_height; 17 | 18 | int corner_radius; 19 | }; 20 | 21 | struct screen_info { 22 | int id; 23 | int x; 24 | int y; 25 | unsigned int h; 26 | unsigned int mmh; 27 | unsigned int w; 28 | int dpi; 29 | }; 30 | 31 | struct output { 32 | bool (*init)(void); 33 | void (*deinit)(void); 34 | 35 | window (*win_create)(void); 36 | void (*win_destroy)(window); 37 | 38 | void (*win_show)(window); 39 | void (*win_hide)(window); 40 | 41 | void (*display_surface)(cairo_surface_t *srf, window win, const struct dimensions*); 42 | 43 | cairo_t* (*win_get_context)(window); 44 | 45 | const struct screen_info* (*get_active_screen)(void); 46 | 47 | bool (*is_idle)(void); 48 | bool (*have_fullscreen_window)(void); 49 | 50 | double (*get_scale)(void); 51 | }; 52 | 53 | /** 54 | * return an initialized output, selecting the correct output type from either 55 | * wayland or X11 according to the settings and environment. 56 | * When the wayland output fails to initilize, it falls back to X11 output. 57 | */ 58 | const struct output* output_create(bool force_xwayland); 59 | 60 | bool is_running_wayland(void); 61 | 62 | #endif 63 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 64 | -------------------------------------------------------------------------------- /src/queues.h: -------------------------------------------------------------------------------- 1 | /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ 2 | 3 | /** 4 | * @file src/queues.h 5 | */ 6 | 7 | #ifndef DUNST_QUEUE_H 8 | #define DUNST_QUEUE_H 9 | 10 | #include "dbus.h" 11 | #include "dunst.h" 12 | #include "notification.h" 13 | 14 | /** 15 | * Initialise necessary queues 16 | * 17 | * @pre Do not call consecutively to avoid memory leaks 18 | * or assure to have queues_teardown() executed before 19 | */ 20 | void queues_init(void); 21 | 22 | /** 23 | * Receive the current list of displayed notifications 24 | * 25 | * @return read only list of notifications 26 | */ 27 | GList *queues_get_displayed(void); 28 | 29 | /** 30 | * Recieve the list of all notifications encountered 31 | * 32 | * @return read only list of notifications 33 | */ 34 | GList *queues_get_history(void); 35 | 36 | /** 37 | * Get the highest notification in line 38 | * 39 | * @returns the first notification in waiting 40 | * @retval NULL: waiting is empty 41 | */ 42 | struct notification *queues_get_head_waiting(void); 43 | 44 | /** 45 | * Returns the current amount of notifications, 46 | * which are waiting to get displayed 47 | */ 48 | unsigned int queues_length_waiting(void); 49 | 50 | /** 51 | * Returns the current amount of notifications, 52 | * which are shown in the UI 53 | */ 54 | unsigned int queues_length_displayed(void); 55 | 56 | /** 57 | * Returns the current amount of notifications, 58 | * which are already in history 59 | */ 60 | unsigned int queues_length_history(void); 61 | 62 | /** 63 | * Insert a fully initialized notification into queues 64 | * 65 | * Respects stack_duplicates, and notification replacement 66 | * 67 | * @param n the notification to insert 68 | * 69 | * - If n->id != 0, n replaces notification n with id n->id 70 | * - If n->id == 0, n gets a new id assigned 71 | * 72 | * @returns The new value of `n->id` 73 | * @retval 0: the notification was dismissed and freed 74 | */ 75 | int queues_notification_insert(struct notification *n); 76 | 77 | /** 78 | * Replace the notification which matches the id field of 79 | * the new notification. The given notification is inserted 80 | * right in the same position as the old notification. 81 | * 82 | * @param new replacement for the old notification 83 | * 84 | * @retval true: a matching notification has been found and is replaced 85 | * @retval false: otherwise 86 | */ 87 | bool queues_notification_replace_id(struct notification *new); 88 | 89 | /** 90 | * Close the notification that has n->id == id 91 | * 92 | * Sends a signal and pushes the selected notification automatically to history. 93 | * 94 | * @param id The id of the notification to close 95 | * @param reason The #reason to close 96 | * 97 | * @post Call wake_up() to synchronize the queues with the UI 98 | * (which closes the notification on screen) 99 | */ 100 | void queues_notification_close_id(int id, enum reason reason); 101 | 102 | /** 103 | * Close the given notification. \see queues_notification_close_id(). 104 | * 105 | * @param n (transfer full) The notification to close 106 | * @param reason The #reason to close 107 | * */ 108 | void queues_notification_close(struct notification *n, enum reason reason); 109 | 110 | /** 111 | * Pushes the latest notification of history to the displayed queue 112 | * and removes it from history 113 | */ 114 | void queues_history_pop(void); 115 | 116 | /** 117 | * Pushes the latest notification found in the history buffer identified by 118 | * it's assigned id 119 | */ 120 | void queues_history_pop_by_id(unsigned int id); 121 | 122 | /** 123 | * Push a single notification to history 124 | * The given notification has to be removed its queue 125 | * 126 | * @param n (transfer full) The notification to push to history 127 | */ 128 | void queues_history_push(struct notification *n); 129 | 130 | /** 131 | * Push all waiting and displayed notifications to history 132 | */ 133 | void queues_history_push_all(void); 134 | 135 | /** 136 | * Move inserted notifications from waiting queue to displayed queue 137 | * and show them. In displayed queue, the amount of elements is limited 138 | * to the amount set via queues_displayed_limit() 139 | * 140 | * @post Call wake_up() to synchronize the queues with the UI 141 | * (which closes old and shows new notifications on screen) 142 | * 143 | * @param status the current status of dunst 144 | * @param time the current time 145 | */ 146 | void queues_update(struct dunst_status status, gint64 time); 147 | 148 | /** 149 | * Calculate the distance to the next event, when an element in the 150 | * queues changes 151 | * 152 | * @param time the current time 153 | * 154 | * @return the distance to the next event in the queue, which forces 155 | * an update visible to the user. This may be: 156 | * - notification hits timeout 157 | * - notification's age second changes 158 | * - notification's age threshold is hit 159 | */ 160 | gint64 queues_get_next_datachange(gint64 time); 161 | 162 | /** 163 | * Get the notification which has the given id in the displayed and waiting queue or 164 | * NULL if not found 165 | * 166 | * @param id the id searched for. 167 | * 168 | * @return the `id` notification or NULL 169 | */ 170 | struct notification* queues_get_by_id(int id); 171 | 172 | 173 | /** 174 | * Remove all notifications from all list and free the notifications 175 | * 176 | * @pre At least one time queues_init() called 177 | */ 178 | void queues_teardown(void); 179 | 180 | #endif 181 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 182 | -------------------------------------------------------------------------------- /src/rules.h: -------------------------------------------------------------------------------- 1 | /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ 2 | #ifndef DUNST_RULES_H 3 | #define DUNST_RULES_H 4 | 5 | #include 6 | #include 7 | 8 | #include "notification.h" 9 | #include "settings.h" 10 | 11 | struct rule { 12 | // Since there's heavy use of offsets from this class, both in rules.c 13 | // and in settings_data.h the layout of the class should not be 14 | // changed, unless it's well considered and tested. See the comments 15 | // below for what should not be changed. 16 | 17 | // This has to be the first member, see struct setting.rule_offset. 18 | char *name; 19 | 20 | /* filters */ 21 | char *appname; // this has to be the first filter, see rules.c 22 | char *summary; 23 | char *body; 24 | char *icon; 25 | char *category; 26 | char *stack_tag; 27 | char *desktop_entry; 28 | int msg_urgency; 29 | gint64 match_dbus_timeout; 30 | 31 | /* modifying */ 32 | gint64 timeout; // this has to be the first modifying rule 33 | gint64 override_dbus_timeout; 34 | enum urgency urgency; 35 | char *action_name; 36 | enum markup_mode markup; 37 | int history_ignore; 38 | int match_transient; 39 | int set_transient; 40 | int skip_display; 41 | int word_wrap; 42 | int ellipsize; 43 | int alignment; 44 | int hide_text; 45 | int icon_position; 46 | int min_icon_size; 47 | int max_icon_size; 48 | char *new_icon; 49 | char *fg; 50 | char *bg; 51 | char *highlight; 52 | char *default_icon; 53 | char *fc; 54 | char *set_category; 55 | const char *format; 56 | const char *script; 57 | enum behavior_fullscreen fullscreen; 58 | bool enabled; 59 | int progress_bar_alignment; 60 | char *set_stack_tag; // this has to be the last modifying rule 61 | }; 62 | 63 | extern GSList *rules; 64 | 65 | /** 66 | * Allocate a new rule with given name. The rule is fully initialised. If the 67 | * name is one of a special section (see settings_data.h), the rule is 68 | * initialized with some filters, and you should not add any filters after 69 | * that. 70 | * 71 | * @param name Name of the rule. 72 | * 73 | * @returns A new initialised rule. 74 | */ 75 | struct rule *rule_new(const char *name); 76 | 77 | void rule_apply(struct rule *r, struct notification *n); 78 | void rule_apply_all(struct notification *n); 79 | bool rule_matches_notification(struct rule *r, struct notification *n); 80 | 81 | /** 82 | * Get rule with this name from rules 83 | * 84 | * @returns the rule that matches. Null if no rule matches 85 | */ 86 | struct rule *get_rule(const char* name); 87 | 88 | /** 89 | * Check if a rule is an action 90 | * 91 | * @returns a boolean if the rule is an action 92 | */ 93 | bool rule_offset_is_modifying(const size_t offset); 94 | 95 | /** 96 | * Check if a rule is an filter 97 | * 98 | * @returns a boolean if the rule is an filter 99 | */ 100 | bool rule_offset_is_filter(const size_t offset); 101 | 102 | #endif 103 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 104 | -------------------------------------------------------------------------------- /src/settings.h: -------------------------------------------------------------------------------- 1 | /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ 2 | #ifndef DUNST_SETTINGS_H 3 | #define DUNST_SETTINGS_H 4 | 5 | #include 6 | 7 | #ifdef ENABLE_WAYLAND 8 | #include "wayland/protocols/wlr-layer-shell-unstable-v1-client-header.h" 9 | #endif 10 | 11 | #include "notification.h" 12 | #include "x11/x.h" 13 | 14 | #define LIST_END (-1) 15 | 16 | enum alignment { ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT }; 17 | enum vertical_alignment { VERTICAL_TOP, VERTICAL_CENTER, VERTICAL_BOTTOM }; 18 | enum separator_color { SEP_FOREGROUND, SEP_AUTO, SEP_FRAME, SEP_CUSTOM }; 19 | enum follow_mode { FOLLOW_NONE, FOLLOW_MOUSE, FOLLOW_KEYBOARD }; 20 | enum mouse_action { MOUSE_NONE, MOUSE_DO_ACTION, MOUSE_CLOSE_CURRENT, 21 | MOUSE_CLOSE_ALL, MOUSE_CONTEXT, MOUSE_CONTEXT_ALL, MOUSE_OPEN_URL, 22 | MOUSE_ACTION_END = LIST_END /* indicates the end of a list of mouse actions */}; 23 | #ifndef ZWLR_LAYER_SHELL_V1_LAYER_ENUM 24 | #define ZWLR_LAYER_SHELL_V1_LAYER_ENUM 25 | // Needed for compiling without wayland dependency 26 | enum zwlr_layer_shell_v1_layer { 27 | ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND = 0, 28 | ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM = 1, 29 | ZWLR_LAYER_SHELL_V1_LAYER_TOP = 2, 30 | ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY = 3, 31 | }; 32 | #endif /* ZWLR_LAYER_SHELL_V1_LAYER_ENUM */ 33 | 34 | #ifndef ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM 35 | #define ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM 36 | enum zwlr_layer_surface_v1_anchor { 37 | /** 38 | * the top edge of the anchor rectangle 39 | */ 40 | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP = 1, 41 | /** 42 | * the bottom edge of the anchor rectangle 43 | */ 44 | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM = 2, 45 | /** 46 | * the left edge of the anchor rectangle 47 | */ 48 | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT = 4, 49 | /** 50 | * the right edge of the anchor rectangle 51 | */ 52 | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT = 8, 53 | }; 54 | #endif /* ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM */ 55 | 56 | enum origin_values { 57 | ORIGIN_TOP_LEFT = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT, 58 | ORIGIN_TOP_CENTER = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, 59 | ORIGIN_TOP_RIGHT = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT, 60 | ORIGIN_BOTTOM_LEFT = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT, 61 | ORIGIN_BOTTOM_CENTER = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, 62 | ORIGIN_BOTTOM_RIGHT = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT, 63 | ORIGIN_LEFT_CENTER = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT, 64 | ORIGIN_RIGHT_CENTER = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT, 65 | ORIGIN_CENTER = 0, 66 | }; 67 | 68 | // TODO make a TYPE_CMD, instead of using TYPE_PATH for settings like dmenu and browser 69 | enum setting_type { TYPE_MIN = 0, TYPE_INT, TYPE_DOUBLE, TYPE_STRING, 70 | TYPE_PATH, TYPE_TIME, TYPE_LIST, TYPE_CUSTOM, TYPE_LENGTH, 71 | TYPE_DEPRECATED, TYPE_MAX = TYPE_DEPRECATED + 1 }; // to be implemented 72 | 73 | struct separator_color_data { 74 | enum separator_color type; 75 | char *sep_color; 76 | }; 77 | 78 | struct length { 79 | int min; 80 | int max; 81 | }; 82 | 83 | struct position { 84 | int x; 85 | int y; 86 | }; 87 | 88 | struct settings { 89 | bool print_notifications; 90 | bool per_monitor_dpi; 91 | bool stack_duplicates; 92 | bool hide_duplicate_count; 93 | char *font; 94 | struct notification_colors colors_low; 95 | struct notification_colors colors_norm; 96 | struct notification_colors colors_crit; 97 | char *format; 98 | gint64 timeouts[3]; 99 | char *icons[3]; 100 | unsigned int transparency; 101 | char *title; 102 | char *class; 103 | int shrink; 104 | int sort; 105 | int indicate_hidden; 106 | gint64 idle_threshold; 107 | gint64 show_age_threshold; 108 | enum alignment align; 109 | int sticky_history; 110 | int history_length; 111 | int show_indicators; 112 | int ignore_dbusclose; 113 | int ignore_newline; 114 | int line_height; 115 | int separator_height; 116 | int padding; 117 | int h_padding; 118 | int text_icon_padding; 119 | struct separator_color_data sep_color; 120 | int frame_width; 121 | char *frame_color; 122 | int startup_notification; 123 | int monitor; 124 | double scale; 125 | char *dmenu; 126 | char **dmenu_cmd; 127 | char *browser; 128 | char **browser_cmd; 129 | enum vertical_alignment vertical_alignment; 130 | char **icon_theme; // experimental 131 | bool enable_recursive_icon_lookup; // experimental 132 | bool enable_regex; // experimental 133 | char *icon_path; 134 | enum follow_mode f_mode; 135 | bool always_run_script; 136 | struct keyboard_shortcut close_ks; 137 | struct keyboard_shortcut close_all_ks; 138 | struct keyboard_shortcut history_ks; 139 | struct keyboard_shortcut context_ks; 140 | bool force_xinerama; 141 | bool force_xwayland; 142 | int corner_radius; 143 | enum mouse_action *mouse_left_click; 144 | enum mouse_action *mouse_middle_click; 145 | enum mouse_action *mouse_right_click; 146 | int progress_bar_height; 147 | int progress_bar_min_width; 148 | int progress_bar_max_width; 149 | int progress_bar_frame_width; 150 | bool progress_bar; 151 | enum zwlr_layer_shell_v1_layer layer; 152 | enum origin_values origin; 153 | struct length width; 154 | int height; 155 | struct position offset; 156 | int notification_limit; 157 | int gap_size; 158 | }; 159 | 160 | extern struct settings settings; 161 | 162 | void load_settings(const char * const path); 163 | 164 | #endif 165 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 166 | -------------------------------------------------------------------------------- /src/wayland/foreign_toplevel.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200112L 2 | #include 3 | #include 4 | #include 5 | #include "protocols/wlr-foreign-toplevel-management-unstable-v1-client-header.h" 6 | /* #include "protocols/wlr-foreign-toplevel-management-unstable-v1.h" */ 7 | 8 | #include "foreign_toplevel.h" 9 | #include "../dunst.h" 10 | #include "wl_output.h" 11 | #include "wl.h" 12 | 13 | struct wl_list toplevel_list; 14 | 15 | static void noop() { 16 | // This space intentionally left blank 17 | } 18 | 19 | static void copy_state(struct toplevel_state *current, 20 | struct toplevel_state *pending) { 21 | if (!(pending->state & TOPLEVEL_STATE_INVALID)) { 22 | current->state = pending->state; 23 | } 24 | 25 | pending->state = TOPLEVEL_STATE_INVALID; 26 | } 27 | 28 | static uint32_t global_id = 0; 29 | 30 | static void toplevel_handle_output_enter(void *data, 31 | struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel, 32 | struct wl_output *wl_output) { 33 | struct toplevel_v1 *toplevel = data; 34 | struct toplevel_output *toplevel_output = calloc(1, sizeof(struct toplevel_output)); 35 | struct dunst_output *dunst_output = wl_output_get_user_data(wl_output); 36 | toplevel_output->dunst_output = dunst_output; 37 | 38 | wl_list_insert(&toplevel->output_list, &toplevel_output->link); 39 | } 40 | 41 | static void toplevel_handle_output_leave(void *data, 42 | struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel, 43 | struct wl_output *wl_output) { 44 | struct toplevel_v1 *toplevel = data; 45 | 46 | struct dunst_output *output = wl_output_get_user_data(wl_output); 47 | struct toplevel_output *pos, *tmp; 48 | wl_list_for_each_safe(pos, tmp, &toplevel->output_list, link){ 49 | if (pos->dunst_output->name == output->name){ 50 | wl_list_remove(&pos->link); 51 | } 52 | } 53 | } 54 | 55 | static uint32_t array_to_state(struct wl_array *array) { 56 | uint32_t state = 0; 57 | uint32_t *entry; 58 | wl_array_for_each(entry, array) { 59 | if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) 60 | state |= TOPLEVEL_STATE_ACTIVATED; 61 | if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN) 62 | state |= TOPLEVEL_STATE_FULLSCREEN; 63 | } 64 | 65 | return state; 66 | } 67 | 68 | static void toplevel_handle_state(void *data, 69 | struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel, 70 | struct wl_array *state) { 71 | struct toplevel_v1 *toplevel = data; 72 | toplevel->pending.state = array_to_state(state); 73 | } 74 | 75 | static void toplevel_handle_done(void *data, 76 | struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel) { 77 | struct toplevel_v1 *toplevel = data; 78 | 79 | bool was_fullscreen = wl_have_fullscreen_window(); 80 | copy_state(&toplevel->current, &toplevel->pending); 81 | bool is_fullscreen = wl_have_fullscreen_window(); 82 | 83 | if (was_fullscreen != is_fullscreen) { 84 | wake_up(); 85 | } 86 | } 87 | 88 | static void toplevel_handle_closed(void *data, 89 | struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel) { 90 | struct toplevel_v1 *toplevel = data; 91 | 92 | wl_list_remove(&toplevel->link); 93 | struct toplevel_output *pos, *tmp; 94 | wl_list_for_each_safe(pos, tmp, &toplevel->output_list, link){ 95 | free(pos); 96 | } 97 | free(toplevel); 98 | zwlr_foreign_toplevel_handle_v1_destroy(zwlr_toplevel); 99 | } 100 | 101 | static const struct zwlr_foreign_toplevel_handle_v1_listener toplevel_impl = { 102 | .title = noop, 103 | .app_id = noop, 104 | .output_enter = toplevel_handle_output_enter, 105 | .output_leave = toplevel_handle_output_leave, 106 | .state = toplevel_handle_state, 107 | .done = toplevel_handle_done, 108 | .closed = toplevel_handle_closed, 109 | }; 110 | 111 | static void toplevel_manager_handle_toplevel(void *data, 112 | struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager, 113 | struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel) { 114 | struct toplevel_v1 *toplevel = calloc(1, sizeof(struct toplevel_v1)); 115 | if (!toplevel) { 116 | fprintf(stderr, "Failed to allocate memory for toplevel\n"); 117 | return; 118 | } 119 | 120 | toplevel->id = global_id++; 121 | toplevel->zwlr_toplevel = zwlr_toplevel; 122 | wl_list_insert(&toplevel_list, &toplevel->link); 123 | wl_list_init(&toplevel->output_list); 124 | 125 | zwlr_foreign_toplevel_handle_v1_add_listener(zwlr_toplevel, &toplevel_impl, 126 | toplevel); 127 | } 128 | 129 | static void toplevel_manager_handle_finished(void *data, 130 | struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager) { 131 | zwlr_foreign_toplevel_manager_v1_destroy(toplevel_manager); 132 | } 133 | 134 | const struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_impl = { 135 | .toplevel = toplevel_manager_handle_toplevel, 136 | .finished = toplevel_manager_handle_finished, 137 | }; 138 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 139 | -------------------------------------------------------------------------------- /src/wayland/foreign_toplevel.h: -------------------------------------------------------------------------------- 1 | #ifndef DUNST_FOREIGN_TOPLEVEL_H 2 | #define DUNST_FOREIGN_TOPLEVEL_H 3 | #include 4 | 5 | enum toplevel_state_field { 6 | TOPLEVEL_STATE_ACTIVATED = (1 << 0), 7 | TOPLEVEL_STATE_FULLSCREEN = (1 << 1), 8 | TOPLEVEL_STATE_INVALID = (1 << 2), 9 | }; 10 | 11 | struct toplevel_state { 12 | uint32_t state; 13 | }; 14 | 15 | struct toplevel_v1 { 16 | struct wl_list link; 17 | struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel; 18 | struct wl_list output_list; 19 | 20 | uint32_t id; 21 | struct toplevel_state current, pending; 22 | }; 23 | 24 | struct toplevel_output { 25 | struct dunst_output *dunst_output; 26 | struct wl_list link; 27 | }; 28 | 29 | extern const struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_impl; 30 | 31 | extern struct wl_list toplevel_list; 32 | #endif 33 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 34 | -------------------------------------------------------------------------------- /src/wayland/libgwater-wayland.c: -------------------------------------------------------------------------------- 1 | /* 2 | * libgwater-wayland - Wayland GSource 3 | * 4 | * Copyright © 2014-2017 Quentin "Sardem FF7" Glidic 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #ifdef HAVE_CONFIG_H 27 | #include "config.h" 28 | #endif /* HAVE_CONFIG_H */ 29 | 30 | #ifdef G_LOG_DOMAIN 31 | #undef G_LOG_DOMAIN 32 | #endif /* G_LOG_DOMAIN */ 33 | #define G_LOG_DOMAIN "GWaterWayland" 34 | 35 | #include 36 | 37 | #include 38 | 39 | #include 40 | 41 | #include "libgwater-wayland.h" 42 | 43 | struct _GWaterWaylandSource { 44 | GSource source; 45 | gboolean display_owned; 46 | struct wl_display *display; 47 | gpointer fd; 48 | int error; 49 | }; 50 | 51 | static gboolean 52 | _g_water_wayland_source_prepare(GSource *source, gint *timeout) 53 | { 54 | GWaterWaylandSource *self = (GWaterWaylandSource *)source; 55 | 56 | *timeout = 0; 57 | if ( wl_display_prepare_read(self->display) != 0 ) 58 | return TRUE; 59 | else if ( wl_display_flush(self->display) < 0 ) 60 | { 61 | self->error = errno; 62 | return TRUE; 63 | } 64 | 65 | *timeout = -1; 66 | return FALSE; 67 | } 68 | 69 | static gboolean 70 | _g_water_wayland_source_check(GSource *source) 71 | { 72 | GWaterWaylandSource *self = (GWaterWaylandSource *)source; 73 | 74 | if ( self->error > 0 ) 75 | return TRUE; 76 | 77 | GIOCondition revents; 78 | revents = g_source_query_unix_fd(source, self->fd); 79 | 80 | if ( revents & G_IO_IN ) 81 | { 82 | if ( wl_display_read_events(self->display) < 0 ) 83 | self->error = errno; 84 | } 85 | else 86 | wl_display_cancel_read(self->display); 87 | 88 | return ( revents > 0 ); 89 | } 90 | 91 | static gboolean 92 | _g_water_wayland_source_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) 93 | { 94 | GWaterWaylandSource *self = (GWaterWaylandSource *)source; 95 | GIOCondition revents; 96 | 97 | revents = g_source_query_unix_fd(source, self->fd); 98 | if ( ( self->error > 0 ) || ( revents & (G_IO_ERR | G_IO_HUP) ) ) 99 | { 100 | errno = self->error; 101 | self->error = 0; 102 | if ( callback != NULL ) 103 | return callback(user_data); 104 | return G_SOURCE_REMOVE; 105 | } 106 | 107 | if ( wl_display_dispatch_pending(self->display) < 0 ) 108 | { 109 | if ( callback != NULL ) 110 | return callback(user_data); 111 | return G_SOURCE_REMOVE; 112 | } 113 | 114 | return G_SOURCE_CONTINUE; 115 | } 116 | 117 | static void 118 | _g_water_wayland_source_finalize(GSource *source) 119 | { 120 | GWaterWaylandSource *self = (GWaterWaylandSource *)source; 121 | 122 | if ( self->display_owned ) 123 | wl_display_disconnect(self->display); 124 | } 125 | 126 | static GSourceFuncs _g_water_wayland_source_funcs = { 127 | .prepare = _g_water_wayland_source_prepare, 128 | .check = _g_water_wayland_source_check, 129 | .dispatch = _g_water_wayland_source_dispatch, 130 | .finalize = _g_water_wayland_source_finalize, 131 | }; 132 | 133 | GWaterWaylandSource * 134 | g_water_wayland_source_new(GMainContext *context, const gchar *name) 135 | { 136 | struct wl_display *display; 137 | GWaterWaylandSource *self; 138 | 139 | display = wl_display_connect(name); 140 | if ( display == NULL ) 141 | return NULL; 142 | 143 | self = g_water_wayland_source_new_for_display(context, display); 144 | self->display_owned = TRUE; 145 | return self; 146 | } 147 | 148 | GWaterWaylandSource * 149 | g_water_wayland_source_new_for_display(GMainContext *context, struct wl_display *display) 150 | { 151 | g_return_val_if_fail(display != NULL, NULL); 152 | 153 | GSource *source; 154 | GWaterWaylandSource *self; 155 | 156 | source = g_source_new(&_g_water_wayland_source_funcs, sizeof(GWaterWaylandSource)); 157 | self = (GWaterWaylandSource *)source; 158 | self->display = display; 159 | 160 | self->fd = g_source_add_unix_fd(source, wl_display_get_fd(self->display), G_IO_IN | G_IO_ERR | G_IO_HUP); 161 | 162 | g_source_attach(source, context); 163 | 164 | return self; 165 | } 166 | 167 | void 168 | g_water_wayland_source_free(GWaterWaylandSource *self) 169 | { 170 | GSource * source = (GSource *)self; 171 | g_return_if_fail(self != NULL); 172 | 173 | g_source_destroy(source); 174 | 175 | g_source_unref(source); 176 | } 177 | 178 | void 179 | g_water_wayland_source_set_error_callback(GWaterWaylandSource *self, GSourceFunc callback, gpointer user_data, GDestroyNotify destroy_notify) 180 | { 181 | g_return_if_fail(self != NULL); 182 | 183 | g_source_set_callback((GSource *)self, callback, user_data, destroy_notify); 184 | } 185 | 186 | struct wl_display * 187 | g_water_wayland_source_get_display(GWaterWaylandSource *self) 188 | { 189 | g_return_val_if_fail(self != NULL, NULL); 190 | 191 | return self->display; 192 | } 193 | -------------------------------------------------------------------------------- /src/wayland/libgwater-wayland.h: -------------------------------------------------------------------------------- 1 | /* 2 | * libgwater-wayland - Wayland GSource 3 | * 4 | * Copyright © 2014-2017 Quentin "Sardem FF7" Glidic 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #ifndef __G_WATER_WAYLAND_H__ 27 | #define __G_WATER_WAYLAND_H__ 28 | 29 | G_BEGIN_DECLS 30 | 31 | typedef struct _GWaterWaylandSource GWaterWaylandSource; 32 | 33 | GWaterWaylandSource *g_water_wayland_source_new(GMainContext *context, const gchar *name); 34 | GWaterWaylandSource *g_water_wayland_source_new_for_display(GMainContext *context, struct wl_display *display); 35 | void g_water_wayland_source_free(GWaterWaylandSource *self); 36 | 37 | void g_water_wayland_source_set_error_callback(GWaterWaylandSource *self, GSourceFunc callback, gpointer user_data, GDestroyNotify destroy_notify); 38 | struct wl_display *g_water_wayland_source_get_display(GWaterWaylandSource *source); 39 | 40 | G_END_DECLS 41 | 42 | #endif /* __G_WATER_WAYLAND_H__ */ 43 | -------------------------------------------------------------------------------- /src/wayland/pool-buffer.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200112L 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "pool-buffer.h" 12 | 13 | static void randname(char *buf) { 14 | struct timespec ts; 15 | clock_gettime(CLOCK_REALTIME, &ts); 16 | long r = ts.tv_nsec; 17 | for (int i = 0; i < 6; ++i) { 18 | buf[i] = 'A'+(r&15)+(r&16)*2; 19 | r >>= 5; 20 | } 21 | } 22 | 23 | static int anonymous_shm_open(void) { 24 | char name[] = "/dunst-XXXXXX"; 25 | int retries = 100; 26 | 27 | do { 28 | randname(name + strlen(name) - 6); 29 | 30 | --retries; 31 | // shm_open guarantees that O_CLOEXEC is set 32 | int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); 33 | if (fd >= 0) { 34 | shm_unlink(name); 35 | return fd; 36 | } 37 | } while (retries > 0 && errno == EEXIST); 38 | 39 | return -1; 40 | } 41 | 42 | static int create_shm_file(off_t size) { 43 | int fd = anonymous_shm_open(); 44 | if (fd < 0) { 45 | return fd; 46 | } 47 | 48 | if (ftruncate(fd, size) < 0) { 49 | close(fd); 50 | return -1; 51 | } 52 | 53 | return fd; 54 | } 55 | 56 | static void buffer_handle_release(void *data, struct wl_buffer *wl_buffer) { 57 | struct pool_buffer *buffer = data; 58 | buffer->busy = false; 59 | } 60 | 61 | static const struct wl_buffer_listener buffer_listener = { 62 | .release = buffer_handle_release, 63 | }; 64 | 65 | static struct pool_buffer *create_buffer(struct wl_shm *shm, 66 | struct pool_buffer *buf, int32_t width, int32_t height) { 67 | const enum wl_shm_format wl_fmt = WL_SHM_FORMAT_ARGB8888; 68 | const cairo_format_t cairo_fmt = CAIRO_FORMAT_ARGB32; 69 | 70 | uint32_t stride = cairo_format_stride_for_width(cairo_fmt, width); 71 | size_t size = stride * height; 72 | 73 | void *data = NULL; 74 | if (size > 0) { 75 | int fd = create_shm_file(size); 76 | if (fd == -1) { 77 | return NULL; 78 | } 79 | 80 | data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 81 | if (data == MAP_FAILED) { 82 | close(fd); 83 | return NULL; 84 | } 85 | 86 | struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); 87 | buf->buffer = 88 | wl_shm_pool_create_buffer(pool, 0, width, height, stride, wl_fmt); 89 | wl_buffer_add_listener(buf->buffer, &buffer_listener, buf); 90 | wl_shm_pool_destroy(pool); 91 | 92 | close(fd); 93 | } 94 | 95 | buf->data = data; 96 | buf->size = size; 97 | buf->width = width; 98 | buf->height = height; 99 | buf->surface = cairo_image_surface_create_for_data(data, cairo_fmt, width, 100 | height, stride); 101 | buf->cairo = cairo_create(buf->surface); 102 | buf->pango = pango_cairo_create_context(buf->cairo); 103 | return buf; 104 | } 105 | 106 | void finish_buffer(struct pool_buffer *buffer) { 107 | if (buffer->buffer) { 108 | wl_buffer_destroy(buffer->buffer); 109 | } 110 | if (buffer->cairo) { 111 | cairo_destroy(buffer->cairo); 112 | } 113 | if (buffer->surface) { 114 | cairo_surface_destroy(buffer->surface); 115 | } 116 | if (buffer->pango) { 117 | g_object_unref(buffer->pango); 118 | } 119 | if (buffer->data) { 120 | munmap(buffer->data, buffer->size); 121 | } 122 | memset(buffer, 0, sizeof(struct pool_buffer)); 123 | } 124 | 125 | struct pool_buffer *get_next_buffer(struct wl_shm *shm, 126 | struct pool_buffer pool[static 2], uint32_t width, uint32_t height) { 127 | struct pool_buffer *buffer = NULL; 128 | for (size_t i = 0; i < 2; ++i) { 129 | if (pool[i].busy) { 130 | continue; 131 | } 132 | buffer = &pool[i]; 133 | } 134 | if (!buffer) { 135 | return NULL; 136 | } 137 | 138 | if (buffer->width != width || buffer->height != height) { 139 | finish_buffer(buffer); 140 | } 141 | 142 | if (!buffer->buffer) { 143 | if (!create_buffer(shm, buffer, width, height)) { 144 | return NULL; 145 | } 146 | } 147 | 148 | return buffer; 149 | } 150 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 151 | -------------------------------------------------------------------------------- /src/wayland/pool-buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef DUNST_POOL_BUFFER_H 2 | #define DUNST_POOL_BUFFER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | struct pool_buffer { 11 | struct wl_buffer *buffer; 12 | cairo_surface_t *surface; 13 | cairo_t *cairo; 14 | PangoContext *pango; 15 | uint32_t width, height; 16 | void *data; 17 | size_t size; 18 | bool busy; 19 | }; 20 | 21 | struct pool_buffer *get_next_buffer(struct wl_shm *shm, 22 | struct pool_buffer pool[static 2], uint32_t width, uint32_t height); 23 | void finish_buffer(struct pool_buffer *buffer); 24 | 25 | #endif 26 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 27 | -------------------------------------------------------------------------------- /src/wayland/protocols/idle.h: -------------------------------------------------------------------------------- 1 | /* Generated by wayland-scanner 1.19.0 */ 2 | 3 | /* 4 | * Copyright (C) 2015 Martin Gräßlin 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 2.1 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include "wayland-util.h" 23 | 24 | #ifndef __has_attribute 25 | # define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ 26 | #endif 27 | 28 | #if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) 29 | #define WL_PRIVATE __attribute__ ((visibility("hidden"))) 30 | #else 31 | #define WL_PRIVATE 32 | #endif 33 | 34 | extern const struct wl_interface org_kde_kwin_idle_timeout_interface; 35 | extern const struct wl_interface wl_seat_interface; 36 | 37 | static const struct wl_interface *idle_types[] = { 38 | &org_kde_kwin_idle_timeout_interface, 39 | &wl_seat_interface, 40 | NULL, 41 | }; 42 | 43 | static const struct wl_message org_kde_kwin_idle_requests[] = { 44 | { "get_idle_timeout", "nou", idle_types + 0 }, 45 | }; 46 | 47 | WL_PRIVATE const struct wl_interface org_kde_kwin_idle_interface = { 48 | "org_kde_kwin_idle", 1, 49 | 1, org_kde_kwin_idle_requests, 50 | 0, NULL, 51 | }; 52 | 53 | static const struct wl_message org_kde_kwin_idle_timeout_requests[] = { 54 | { "release", "", idle_types + 0 }, 55 | { "simulate_user_activity", "", idle_types + 0 }, 56 | }; 57 | 58 | static const struct wl_message org_kde_kwin_idle_timeout_events[] = { 59 | { "idle", "", idle_types + 0 }, 60 | { "resumed", "", idle_types + 0 }, 61 | }; 62 | 63 | WL_PRIVATE const struct wl_interface org_kde_kwin_idle_timeout_interface = { 64 | "org_kde_kwin_idle_timeout", 1, 65 | 2, org_kde_kwin_idle_timeout_requests, 66 | 2, org_kde_kwin_idle_timeout_events, 67 | }; 68 | 69 | -------------------------------------------------------------------------------- /src/wayland/protocols/idle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | . 18 | ]]> 19 | 20 | 21 | This interface allows to monitor user idle time on a given seat. The interface 22 | allows to register timers which trigger after no user activity was registered 23 | on the seat for a given interval. It notifies when user activity resumes. 24 | 25 | This is useful for applications wanting to perform actions when the user is not 26 | interacting with the system, e.g. chat applications setting the user as away, power 27 | management features to dim screen, etc.. 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1.h: -------------------------------------------------------------------------------- 1 | /* Generated by wayland-scanner 1.19.0 */ 2 | 3 | /* 4 | * Copyright © 2018 Ilia Bozhinov 5 | * 6 | * Permission to use, copy, modify, distribute, and sell this 7 | * software and its documentation for any purpose is hereby granted 8 | * without fee, provided that the above copyright notice appear in 9 | * all copies and that both that copyright notice and this permission 10 | * notice appear in supporting documentation, and that the name of 11 | * the copyright holders not be used in advertising or publicity 12 | * pertaining to distribution of the software without specific, 13 | * written prior permission. The copyright holders make no 14 | * representations about the suitability of this software for any 15 | * purpose. It is provided "as is" without express or implied 16 | * warranty. 17 | * 18 | * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS 19 | * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 20 | * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 22 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 23 | * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 24 | * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 25 | * THIS SOFTWARE. 26 | */ 27 | 28 | #include 29 | #include 30 | #include "wayland-util.h" 31 | 32 | #ifndef __has_attribute 33 | # define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ 34 | #endif 35 | 36 | #if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) 37 | #define WL_PRIVATE __attribute__ ((visibility("hidden"))) 38 | #else 39 | #define WL_PRIVATE 40 | #endif 41 | 42 | extern const struct wl_interface wl_output_interface; 43 | extern const struct wl_interface wl_seat_interface; 44 | extern const struct wl_interface wl_surface_interface; 45 | extern const struct wl_interface zwlr_foreign_toplevel_handle_v1_interface; 46 | 47 | static const struct wl_interface *wlr_foreign_toplevel_management_unstable_v1_types[] = { 48 | NULL, 49 | &zwlr_foreign_toplevel_handle_v1_interface, 50 | &wl_seat_interface, 51 | &wl_surface_interface, 52 | NULL, 53 | NULL, 54 | NULL, 55 | NULL, 56 | &wl_output_interface, 57 | &wl_output_interface, 58 | &wl_output_interface, 59 | &zwlr_foreign_toplevel_handle_v1_interface, 60 | }; 61 | 62 | static const struct wl_message zwlr_foreign_toplevel_manager_v1_requests[] = { 63 | { "stop", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, 64 | }; 65 | 66 | static const struct wl_message zwlr_foreign_toplevel_manager_v1_events[] = { 67 | { "toplevel", "n", wlr_foreign_toplevel_management_unstable_v1_types + 1 }, 68 | { "finished", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, 69 | }; 70 | 71 | WL_PRIVATE const struct wl_interface zwlr_foreign_toplevel_manager_v1_interface = { 72 | "zwlr_foreign_toplevel_manager_v1", 3, 73 | 1, zwlr_foreign_toplevel_manager_v1_requests, 74 | 2, zwlr_foreign_toplevel_manager_v1_events, 75 | }; 76 | 77 | static const struct wl_message zwlr_foreign_toplevel_handle_v1_requests[] = { 78 | { "set_maximized", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, 79 | { "unset_maximized", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, 80 | { "set_minimized", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, 81 | { "unset_minimized", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, 82 | { "activate", "o", wlr_foreign_toplevel_management_unstable_v1_types + 2 }, 83 | { "close", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, 84 | { "set_rectangle", "oiiii", wlr_foreign_toplevel_management_unstable_v1_types + 3 }, 85 | { "destroy", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, 86 | { "set_fullscreen", "2?o", wlr_foreign_toplevel_management_unstable_v1_types + 8 }, 87 | { "unset_fullscreen", "2", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, 88 | }; 89 | 90 | static const struct wl_message zwlr_foreign_toplevel_handle_v1_events[] = { 91 | { "title", "s", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, 92 | { "app_id", "s", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, 93 | { "output_enter", "o", wlr_foreign_toplevel_management_unstable_v1_types + 9 }, 94 | { "output_leave", "o", wlr_foreign_toplevel_management_unstable_v1_types + 10 }, 95 | { "state", "a", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, 96 | { "done", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, 97 | { "closed", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, 98 | { "parent", "3?o", wlr_foreign_toplevel_management_unstable_v1_types + 11 }, 99 | }; 100 | 101 | WL_PRIVATE const struct wl_interface zwlr_foreign_toplevel_handle_v1_interface = { 102 | "zwlr_foreign_toplevel_handle_v1", 3, 103 | 10, zwlr_foreign_toplevel_handle_v1_requests, 104 | 8, zwlr_foreign_toplevel_handle_v1_events, 105 | }; 106 | 107 | -------------------------------------------------------------------------------- /src/wayland/protocols/wlr-layer-shell-unstable-v1.h: -------------------------------------------------------------------------------- 1 | /* Generated by wayland-scanner 1.19.0 */ 2 | 3 | /* 4 | * Copyright © 2017 Drew DeVault 5 | * 6 | * Permission to use, copy, modify, distribute, and sell this 7 | * software and its documentation for any purpose is hereby granted 8 | * without fee, provided that the above copyright notice appear in 9 | * all copies and that both that copyright notice and this permission 10 | * notice appear in supporting documentation, and that the name of 11 | * the copyright holders not be used in advertising or publicity 12 | * pertaining to distribution of the software without specific, 13 | * written prior permission. The copyright holders make no 14 | * representations about the suitability of this software for any 15 | * purpose. It is provided "as is" without express or implied 16 | * warranty. 17 | * 18 | * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS 19 | * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 20 | * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 22 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 23 | * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 24 | * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 25 | * THIS SOFTWARE. 26 | */ 27 | 28 | #include 29 | #include 30 | #include "wayland-util.h" 31 | 32 | #ifndef __has_attribute 33 | # define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ 34 | #endif 35 | 36 | #if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) 37 | #define WL_PRIVATE __attribute__ ((visibility("hidden"))) 38 | #else 39 | #define WL_PRIVATE 40 | #endif 41 | 42 | extern const struct wl_interface wl_output_interface; 43 | extern const struct wl_interface wl_surface_interface; 44 | extern const struct wl_interface xdg_popup_interface; 45 | extern const struct wl_interface zwlr_layer_surface_v1_interface; 46 | 47 | static const struct wl_interface *wlr_layer_shell_unstable_v1_types[] = { 48 | NULL, 49 | NULL, 50 | NULL, 51 | NULL, 52 | &zwlr_layer_surface_v1_interface, 53 | &wl_surface_interface, 54 | &wl_output_interface, 55 | NULL, 56 | NULL, 57 | &xdg_popup_interface, 58 | }; 59 | 60 | static const struct wl_message zwlr_layer_shell_v1_requests[] = { 61 | { "get_layer_surface", "no?ous", wlr_layer_shell_unstable_v1_types + 4 }, 62 | }; 63 | 64 | WL_PRIVATE const struct wl_interface zwlr_layer_shell_v1_interface = { 65 | "zwlr_layer_shell_v1", 1, 66 | 1, zwlr_layer_shell_v1_requests, 67 | 0, NULL, 68 | }; 69 | 70 | static const struct wl_message zwlr_layer_surface_v1_requests[] = { 71 | { "set_size", "uu", wlr_layer_shell_unstable_v1_types + 0 }, 72 | { "set_anchor", "u", wlr_layer_shell_unstable_v1_types + 0 }, 73 | { "set_exclusive_zone", "i", wlr_layer_shell_unstable_v1_types + 0 }, 74 | { "set_margin", "iiii", wlr_layer_shell_unstable_v1_types + 0 }, 75 | { "set_keyboard_interactivity", "u", wlr_layer_shell_unstable_v1_types + 0 }, 76 | { "get_popup", "o", wlr_layer_shell_unstable_v1_types + 9 }, 77 | { "ack_configure", "u", wlr_layer_shell_unstable_v1_types + 0 }, 78 | { "destroy", "", wlr_layer_shell_unstable_v1_types + 0 }, 79 | }; 80 | 81 | static const struct wl_message zwlr_layer_surface_v1_events[] = { 82 | { "configure", "uuu", wlr_layer_shell_unstable_v1_types + 0 }, 83 | { "closed", "", wlr_layer_shell_unstable_v1_types + 0 }, 84 | }; 85 | 86 | WL_PRIVATE const struct wl_interface zwlr_layer_surface_v1_interface = { 87 | "zwlr_layer_surface_v1", 1, 88 | 8, zwlr_layer_surface_v1_requests, 89 | 2, zwlr_layer_surface_v1_events, 90 | }; 91 | 92 | -------------------------------------------------------------------------------- /src/wayland/protocols/xdg-output-unstable-v1.h: -------------------------------------------------------------------------------- 1 | /* Generated by wayland-scanner 1.19.0 */ 2 | 3 | /* 4 | * Copyright © 2017 Red Hat Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice (including the next 14 | * paragraph) shall be included in all copies or substantial portions of the 15 | * Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | * DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include "wayland-util.h" 29 | 30 | #ifndef __has_attribute 31 | # define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ 32 | #endif 33 | 34 | #if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) 35 | #define WL_PRIVATE __attribute__ ((visibility("hidden"))) 36 | #else 37 | #define WL_PRIVATE 38 | #endif 39 | 40 | extern const struct wl_interface wl_output_interface; 41 | extern const struct wl_interface zxdg_output_v1_interface; 42 | 43 | static const struct wl_interface *xdg_output_unstable_v1_types[] = { 44 | NULL, 45 | NULL, 46 | &zxdg_output_v1_interface, 47 | &wl_output_interface, 48 | }; 49 | 50 | static const struct wl_message zxdg_output_manager_v1_requests[] = { 51 | { "destroy", "", xdg_output_unstable_v1_types + 0 }, 52 | { "get_xdg_output", "no", xdg_output_unstable_v1_types + 2 }, 53 | }; 54 | 55 | WL_PRIVATE const struct wl_interface zxdg_output_manager_v1_interface = { 56 | "zxdg_output_manager_v1", 3, 57 | 2, zxdg_output_manager_v1_requests, 58 | 0, NULL, 59 | }; 60 | 61 | static const struct wl_message zxdg_output_v1_requests[] = { 62 | { "destroy", "", xdg_output_unstable_v1_types + 0 }, 63 | }; 64 | 65 | static const struct wl_message zxdg_output_v1_events[] = { 66 | { "logical_position", "ii", xdg_output_unstable_v1_types + 0 }, 67 | { "logical_size", "ii", xdg_output_unstable_v1_types + 0 }, 68 | { "done", "", xdg_output_unstable_v1_types + 0 }, 69 | { "name", "2s", xdg_output_unstable_v1_types + 0 }, 70 | { "description", "2s", xdg_output_unstable_v1_types + 0 }, 71 | }; 72 | 73 | WL_PRIVATE const struct wl_interface zxdg_output_v1_interface = { 74 | "zxdg_output_v1", 3, 75 | 1, zxdg_output_v1_requests, 76 | 5, zxdg_output_v1_events, 77 | }; 78 | 79 | -------------------------------------------------------------------------------- /src/wayland/protocols/xdg-shell.h: -------------------------------------------------------------------------------- 1 | /* Generated by wayland-scanner 1.19.0 */ 2 | 3 | /* 4 | * Copyright © 2008-2013 Kristian Høgsberg 5 | * Copyright © 2013 Rafael Antognolli 6 | * Copyright © 2013 Jasper St. Pierre 7 | * Copyright © 2010-2013 Intel Corporation 8 | * Copyright © 2015-2017 Samsung Electronics Co., Ltd 9 | * Copyright © 2015-2017 Red Hat Inc. 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a 12 | * copy of this software and associated documentation files (the "Software"), 13 | * to deal in the Software without restriction, including without limitation 14 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 15 | * and/or sell copies of the Software, and to permit persons to whom the 16 | * Software is furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice (including the next 19 | * paragraph) shall be included in all copies or substantial portions of the 20 | * Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 25 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 28 | * DEALINGS IN THE SOFTWARE. 29 | */ 30 | 31 | #include 32 | #include 33 | #include "wayland-util.h" 34 | 35 | #ifndef __has_attribute 36 | # define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ 37 | #endif 38 | 39 | #if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) 40 | #define WL_PRIVATE __attribute__ ((visibility("hidden"))) 41 | #else 42 | #define WL_PRIVATE 43 | #endif 44 | 45 | extern const struct wl_interface wl_output_interface; 46 | extern const struct wl_interface wl_seat_interface; 47 | extern const struct wl_interface wl_surface_interface; 48 | extern const struct wl_interface xdg_popup_interface; 49 | extern const struct wl_interface xdg_positioner_interface; 50 | extern const struct wl_interface xdg_surface_interface; 51 | extern const struct wl_interface xdg_toplevel_interface; 52 | 53 | static const struct wl_interface *xdg_shell_types[] = { 54 | NULL, 55 | NULL, 56 | NULL, 57 | NULL, 58 | &xdg_positioner_interface, 59 | &xdg_surface_interface, 60 | &wl_surface_interface, 61 | &xdg_toplevel_interface, 62 | &xdg_popup_interface, 63 | &xdg_surface_interface, 64 | &xdg_positioner_interface, 65 | &xdg_toplevel_interface, 66 | &wl_seat_interface, 67 | NULL, 68 | NULL, 69 | NULL, 70 | &wl_seat_interface, 71 | NULL, 72 | &wl_seat_interface, 73 | NULL, 74 | NULL, 75 | &wl_output_interface, 76 | &wl_seat_interface, 77 | NULL, 78 | &xdg_positioner_interface, 79 | NULL, 80 | }; 81 | 82 | static const struct wl_message xdg_wm_base_requests[] = { 83 | { "destroy", "", xdg_shell_types + 0 }, 84 | { "create_positioner", "n", xdg_shell_types + 4 }, 85 | { "get_xdg_surface", "no", xdg_shell_types + 5 }, 86 | { "pong", "u", xdg_shell_types + 0 }, 87 | }; 88 | 89 | static const struct wl_message xdg_wm_base_events[] = { 90 | { "ping", "u", xdg_shell_types + 0 }, 91 | }; 92 | 93 | WL_PRIVATE const struct wl_interface xdg_wm_base_interface = { 94 | "xdg_wm_base", 3, 95 | 4, xdg_wm_base_requests, 96 | 1, xdg_wm_base_events, 97 | }; 98 | 99 | static const struct wl_message xdg_positioner_requests[] = { 100 | { "destroy", "", xdg_shell_types + 0 }, 101 | { "set_size", "ii", xdg_shell_types + 0 }, 102 | { "set_anchor_rect", "iiii", xdg_shell_types + 0 }, 103 | { "set_anchor", "u", xdg_shell_types + 0 }, 104 | { "set_gravity", "u", xdg_shell_types + 0 }, 105 | { "set_constraint_adjustment", "u", xdg_shell_types + 0 }, 106 | { "set_offset", "ii", xdg_shell_types + 0 }, 107 | { "set_reactive", "3", xdg_shell_types + 0 }, 108 | { "set_parent_size", "3ii", xdg_shell_types + 0 }, 109 | { "set_parent_configure", "3u", xdg_shell_types + 0 }, 110 | }; 111 | 112 | WL_PRIVATE const struct wl_interface xdg_positioner_interface = { 113 | "xdg_positioner", 3, 114 | 10, xdg_positioner_requests, 115 | 0, NULL, 116 | }; 117 | 118 | static const struct wl_message xdg_surface_requests[] = { 119 | { "destroy", "", xdg_shell_types + 0 }, 120 | { "get_toplevel", "n", xdg_shell_types + 7 }, 121 | { "get_popup", "n?oo", xdg_shell_types + 8 }, 122 | { "set_window_geometry", "iiii", xdg_shell_types + 0 }, 123 | { "ack_configure", "u", xdg_shell_types + 0 }, 124 | }; 125 | 126 | static const struct wl_message xdg_surface_events[] = { 127 | { "configure", "u", xdg_shell_types + 0 }, 128 | }; 129 | 130 | WL_PRIVATE const struct wl_interface xdg_surface_interface = { 131 | "xdg_surface", 3, 132 | 5, xdg_surface_requests, 133 | 1, xdg_surface_events, 134 | }; 135 | 136 | static const struct wl_message xdg_toplevel_requests[] = { 137 | { "destroy", "", xdg_shell_types + 0 }, 138 | { "set_parent", "?o", xdg_shell_types + 11 }, 139 | { "set_title", "s", xdg_shell_types + 0 }, 140 | { "set_app_id", "s", xdg_shell_types + 0 }, 141 | { "show_window_menu", "ouii", xdg_shell_types + 12 }, 142 | { "move", "ou", xdg_shell_types + 16 }, 143 | { "resize", "ouu", xdg_shell_types + 18 }, 144 | { "set_max_size", "ii", xdg_shell_types + 0 }, 145 | { "set_min_size", "ii", xdg_shell_types + 0 }, 146 | { "set_maximized", "", xdg_shell_types + 0 }, 147 | { "unset_maximized", "", xdg_shell_types + 0 }, 148 | { "set_fullscreen", "?o", xdg_shell_types + 21 }, 149 | { "unset_fullscreen", "", xdg_shell_types + 0 }, 150 | { "set_minimized", "", xdg_shell_types + 0 }, 151 | }; 152 | 153 | static const struct wl_message xdg_toplevel_events[] = { 154 | { "configure", "iia", xdg_shell_types + 0 }, 155 | { "close", "", xdg_shell_types + 0 }, 156 | }; 157 | 158 | WL_PRIVATE const struct wl_interface xdg_toplevel_interface = { 159 | "xdg_toplevel", 3, 160 | 14, xdg_toplevel_requests, 161 | 2, xdg_toplevel_events, 162 | }; 163 | 164 | static const struct wl_message xdg_popup_requests[] = { 165 | { "destroy", "", xdg_shell_types + 0 }, 166 | { "grab", "ou", xdg_shell_types + 22 }, 167 | { "reposition", "3ou", xdg_shell_types + 24 }, 168 | }; 169 | 170 | static const struct wl_message xdg_popup_events[] = { 171 | { "configure", "iiii", xdg_shell_types + 0 }, 172 | { "popup_done", "", xdg_shell_types + 0 }, 173 | { "repositioned", "3u", xdg_shell_types + 0 }, 174 | }; 175 | 176 | WL_PRIVATE const struct wl_interface xdg_popup_interface = { 177 | "xdg_popup", 3, 178 | 3, xdg_popup_requests, 179 | 3, xdg_popup_events, 180 | }; 181 | 182 | -------------------------------------------------------------------------------- /src/wayland/wl.h: -------------------------------------------------------------------------------- 1 | #ifndef DUNST_WL_H 2 | #define DUNST_WL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "../output.h" 9 | 10 | bool wl_init(void); 11 | void wl_deinit(void); 12 | 13 | window wl_win_create(void); 14 | void wl_win_destroy(window); 15 | 16 | void wl_win_show(window); 17 | void wl_win_hide(window); 18 | 19 | void wl_display_surface(cairo_surface_t *srf, window win, const struct dimensions*); 20 | cairo_t* wl_win_get_context(window); 21 | 22 | const struct screen_info* wl_get_active_screen(void); 23 | 24 | bool wl_is_idle(void); 25 | bool wl_have_fullscreen_window(void); 26 | 27 | // Return the dpi scaling of the current output. Everything that's rendered 28 | // should be multiplied by this value, but don't use it to multiply other 29 | // values. All sizes should be in unscaled units. 30 | double wl_get_scale(void); 31 | #endif 32 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 33 | -------------------------------------------------------------------------------- /src/wayland/wl_output.c: -------------------------------------------------------------------------------- 1 | #include "wl_output.h" 2 | 3 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 4 | -------------------------------------------------------------------------------- /src/wayland/wl_output.h: -------------------------------------------------------------------------------- 1 | #ifndef DUNST_WL_OUTPUT_H 2 | #define DUNST_WL_OUTPUT_H 3 | #include 4 | #include 5 | #include 6 | 7 | struct dunst_output { 8 | uint32_t global_name; 9 | char *name; 10 | struct wl_output *wl_output; 11 | struct wl_list link; 12 | 13 | uint32_t scale; 14 | uint32_t subpixel; // TODO do something with it 15 | int32_t width, height; 16 | bool fullscreen; 17 | struct zwlr_foreign_toplevel_handle_v1 *fullscreen_toplevel; // the toplevel that is fullscreened on this output 18 | }; 19 | 20 | #endif 21 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 22 | -------------------------------------------------------------------------------- /src/x11/screen.h: -------------------------------------------------------------------------------- 1 | /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ 2 | #ifndef DUNST_SCREEN_H 3 | #define DUNST_SCREEN_H 4 | 5 | #include 6 | #include 7 | 8 | #define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh)) 9 | 10 | void init_screens(void); 11 | void screen_dpi_xft_cache_purge(void); 12 | bool screen_check_event(XEvent *ev); 13 | 14 | const struct screen_info *get_active_screen(void); 15 | double screen_dpi_get(const struct screen_info *scr); 16 | 17 | /** 18 | * Find the currently focused window and check if it's in 19 | * fullscreen mode 20 | * 21 | * @see window_is_fullscreen() 22 | * @see get_focused_window() 23 | * 24 | * @retval true: the focused window is in fullscreen mode 25 | * @retval false: otherwise 26 | */ 27 | bool have_fullscreen_window(void); 28 | 29 | /** 30 | * Check if window is in fullscreen mode 31 | * 32 | * @param window the x11 window object 33 | * @retval true: \p window is in fullscreen mode 34 | * @retval false: otherwise 35 | */ 36 | bool window_is_fullscreen(Window window); 37 | 38 | #endif 39 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 40 | -------------------------------------------------------------------------------- /src/x11/x.h: -------------------------------------------------------------------------------- 1 | /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ 2 | #ifndef DUNST_X_H 3 | #define DUNST_X_H 4 | 5 | #define XLIB_ILLEGAL_ACCESS 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "../output.h" 15 | 16 | #include "screen.h" 17 | 18 | struct keyboard_shortcut { 19 | const char *str; 20 | KeyCode code; 21 | KeySym sym; 22 | KeySym mask; 23 | bool is_valid; 24 | }; 25 | 26 | // Cyclical dependency 27 | #include "../settings.h" 28 | 29 | struct x_context { 30 | Display *dpy; 31 | XScreenSaverInfo *screensaver_info; 32 | }; 33 | 34 | extern struct x_context xctx; 35 | 36 | /* window */ 37 | window x_win_create(void); 38 | void x_win_destroy(window); 39 | 40 | void x_win_show(window); 41 | void x_win_hide(window); 42 | 43 | void x_display_surface(cairo_surface_t *srf, window, const struct dimensions *dim); 44 | 45 | cairo_t* x_win_get_context(window); 46 | 47 | /* X misc */ 48 | bool x_is_idle(void); 49 | bool x_setup(void); 50 | void x_free(void); 51 | 52 | void loadxrdb(void); 53 | 54 | double x_get_scale(void); 55 | #endif 56 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 57 | -------------------------------------------------------------------------------- /test/data/dunstrc.default: -------------------------------------------------------------------------------- 1 | ../../dunstrc -------------------------------------------------------------------------------- /test/data/dunstrc.markup: -------------------------------------------------------------------------------- 1 | ../functional-tests/dunstrc.markup -------------------------------------------------------------------------------- /test/data/dunstrc.nomarkup: -------------------------------------------------------------------------------- 1 | ../functional-tests/dunstrc.nomarkup -------------------------------------------------------------------------------- /test/data/dunstrc.show_age: -------------------------------------------------------------------------------- 1 | ../functional-tests/dunstrc.show_age -------------------------------------------------------------------------------- /test/data/icons/theme/16x16/actions/edit.png: -------------------------------------------------------------------------------- 1 | ../../../valid.png -------------------------------------------------------------------------------- /test/data/icons/theme/16x16/apps/preferences.png: -------------------------------------------------------------------------------- 1 | ../../../valid.png -------------------------------------------------------------------------------- /test/data/icons/theme/16x16@2x/actions/edit.png: -------------------------------------------------------------------------------- 1 | ../../../valid.png -------------------------------------------------------------------------------- /test/data/icons/theme/16x16@2x/apps/preferences.png: -------------------------------------------------------------------------------- 1 | ../../../valid.png -------------------------------------------------------------------------------- /test/data/icons/theme/32x32/actions/edit.png: -------------------------------------------------------------------------------- 1 | ../../../valid.png -------------------------------------------------------------------------------- /test/data/icons/theme/32x32/apps/preferences.png: -------------------------------------------------------------------------------- 1 | ../../../valid.png -------------------------------------------------------------------------------- /test/data/icons/theme/32x32@2x/actions/edit.png: -------------------------------------------------------------------------------- 1 | ../../../valid.png -------------------------------------------------------------------------------- /test/data/icons/theme/32x32@2x/apps/preferences.png: -------------------------------------------------------------------------------- 1 | ../../../valid.png -------------------------------------------------------------------------------- /test/data/icons/theme/index.theme: -------------------------------------------------------------------------------- 1 | [Icon Theme] 2 | Name=theme 3 | Comment=Test theme for dunst 4 | Example=folder 5 | 6 | X-ignore-this=something 7 | # ignore this 8 | 9 | # Directory list 10 | Directories=16x16/actions,16x16/apps,16x16@2x/actions,16x16@2x/apps,32x32/actions,32x32/apps,32x32@2x/actions,32x32@2x/apps 11 | 12 | [16x16/actions] 13 | Size=16 14 | Type=Threshold 15 | Threshold=3 16 | 17 | [16x16/apps] 18 | Size=16 19 | Type=Threshold 20 | 21 | [16x16@2x/actions] 22 | Size=16 23 | Scale=2 24 | Type=Scalable 25 | MinSize=16 26 | MaxSize=32 27 | 28 | [16x16@2x/apps] 29 | Size=16 30 | Scale=2 31 | Type=Fixed 32 | 33 | [32x32/actions] 34 | Size=32 35 | Type=Scalable 36 | MinSize=32 37 | MaxSize=64 38 | 39 | [32x32/apps] 40 | Size=32 41 | Type=Fixed 42 | 43 | [32x32@2x/actions] 44 | Size=32 45 | Scale=2 46 | Type=Fixed 47 | 48 | [32x32@2x/apps] 49 | Size=32 50 | Scale=2 51 | Type=Fixed 52 | -------------------------------------------------------------------------------- /test/data/icons/valid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FT-Labs/phyOS-dunst/aeb275a998778b05cee495efb9b61db66cdc0a7e/test/data/icons/valid.png -------------------------------------------------------------------------------- /test/data/icons/valid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 38 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 56 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /test/data/test-ini: -------------------------------------------------------------------------------- 1 | #General comment 2 | [bool] 3 | booltrue = true #This is a test inline comment 4 | booltrue_capital = TRUE 5 | 6 | #This is a comment 7 | boolfalse = false 8 | boolfalse_capital = FALSE 9 | 10 | boolyes = yes 11 | boolyes_capital = YES 12 | 13 | boolno = no 14 | boolno_capital = NO 15 | 16 | boolbin0 = 0 17 | boolbin1 = 1 18 | 19 | boolinvalid = invalidbool 20 | 21 | [string] 22 | simple = A simple string 23 | quoted = "A quoted string" 24 | quoted_with_quotes = "A string "with quotes"" 25 | unquoted_with_quotes = A" string with quotes" 26 | quoted_comment = "String with a" # comment 27 | unquoted_comment = String with a # comment 28 | color_comment = "#ffffff" # comment 29 | 30 | [list] 31 | simple = A,simple,list 32 | spaces = A, list, with, spaces 33 | multiword = A list, with, multiword entries 34 | quoted = "A, quoted, list" 35 | quoted_with_quotes = "A, list, "with quotes"" 36 | unquoted_with_quotes = A, list, "with quotes" 37 | quoted_comment = "List, with, a" # comment 38 | unquoted_comment = List, with, a # comment 39 | 40 | [path] 41 | expand_tilde = ~/.path/to/tilde 42 | 43 | [int] 44 | simple = 5 45 | negative = -10 46 | decimal = 2.71828 47 | leading_zeroes = 007 48 | multi_char = 1024 49 | 50 | [double] 51 | simple = 1 52 | decimal = 1.5 53 | negative = -1.2 54 | zeroes = 0.005 55 | long = 3.141592653589793 56 | -------------------------------------------------------------------------------- /test/dunst.c: -------------------------------------------------------------------------------- 1 | #include "../src/dunst.c" 2 | #include "greatest.h" 3 | 4 | TEST test_dunst_status(void) 5 | { 6 | status = (struct dunst_status) {false, false, false}; 7 | 8 | dunst_status(S_FULLSCREEN, true); 9 | ASSERT(status.fullscreen); 10 | dunst_status(S_IDLE, true); 11 | ASSERT(status.idle); 12 | dunst_status(S_RUNNING, true); 13 | ASSERT(status.running); 14 | 15 | PASS(); 16 | } 17 | 18 | SUITE(suite_dunst) 19 | { 20 | RUN_TEST(test_dunst_status); 21 | } 22 | 23 | /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 24 | -------------------------------------------------------------------------------- /test/functional-tests/dunstrc.default: -------------------------------------------------------------------------------- 1 | [urgency_low] 2 | background = "#222222" 3 | foreground = "#888888" 4 | timeout = 10 5 | 6 | [urgency_normal] 7 | background = "#285577" 8 | foreground = "#ffffff" 9 | timeout = 10 10 | 11 | [urgency_critical] 12 | background = "#900000" 13 | foreground = "#ffffff" 14 | timeout = 0 15 | 16 | [global] 17 | # can be appended by script 18 | -------------------------------------------------------------------------------- /test/functional-tests/dunstrc.gaps: -------------------------------------------------------------------------------- 1 | [urgency_low] 2 | background = "#222222" 3 | foreground = "#888888" 4 | timeout = 10 5 | 6 | [urgency_normal] 7 | background = "#285577" 8 | foreground = "#ffffff" 9 | timeout = 10 10 | 11 | [urgency_critical] 12 | background = "#900000" 13 | foreground = "#ffffff" 14 | timeout = 0 15 | 16 | [global] 17 | gap_size = 35 18 | frame_width = 15 19 | 20 | mouse_left_click = do_action 21 | mouse_middle_click = close_current 22 | mouse_right_click = context 23 | -------------------------------------------------------------------------------- /test/functional-tests/dunstrc.hide_text: -------------------------------------------------------------------------------- 1 | [urgency_low] 2 | background = "#222222" 3 | foreground = "#888888" 4 | timeout = 10 5 | icon_position = top 6 | hide_text = yes 7 | 8 | [urgency_normal] 9 | background = "#285577" 10 | foreground = "#ffffff" 11 | timeout = 10 12 | icon_position = top 13 | hide_text = yes 14 | 15 | [urgency_critical] 16 | background = "#900000" 17 | foreground = "#ffffff" 18 | timeout = 0 19 | 20 | [global] 21 | font = Monospace 8 22 | markup = full 23 | format = "%s\n%b" 24 | progress_bar_min_width = 100 25 | progress_bar_max_width = 200 26 | progress_bar_frame_width = 5 27 | progress_bar_height = 30 28 | icon_path = /usr/share/icons/Papirus/24x24/status/:/usr/share/icons/Papirus/24x24/devices/:/usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/ -------------------------------------------------------------------------------- /test/functional-tests/dunstrc.icon_position: -------------------------------------------------------------------------------- 1 | [urgency_low] 2 | background = "#222222" 3 | foreground = "#888888" 4 | timeout = 10 5 | 6 | [urgency_normal] 7 | background = "#285577" 8 | foreground = "#ffffff" 9 | timeout = 0 10 | 11 | [urgency_critical] 12 | background = "#900000" 13 | foreground = "#ffffff" 14 | timeout = 0 15 | 16 | [icon-left-alignment-left] 17 | category = "icon-left-alignment-left" 18 | icon_position = left 19 | alignment = left 20 | 21 | [icon-left-alignment-right] 22 | category = "icon-left-alignment-right" 23 | icon_position = left 24 | alignment = right 25 | 26 | [icon-left-alignment-center] 27 | category = "icon-left-alignment-center" 28 | icon_position = left 29 | alignment = center 30 | 31 | [icon-right-alignment-left] 32 | category = "icon-right-alignment-left" 33 | icon_position = right 34 | alignment = left 35 | 36 | [icon-right-alignment-right] 37 | category = "icon-right-alignment-right" 38 | icon_position = right 39 | alignment = right 40 | 41 | [icon-right-alignment-center] 42 | category = "icon-right-alignment-center" 43 | icon_position = right 44 | alignment = center 45 | 46 | [icon-top-alignment-left] 47 | category = "icon-top-alignment-left" 48 | icon_position = top 49 | alignment = left 50 | 51 | [icon-top-alignment-right] 52 | category = "icon-top-alignment-right" 53 | icon_position = top 54 | alignment = right 55 | 56 | [icon-top-alignment-center] 57 | category = "icon-top-alignment-center" 58 | icon_position = top 59 | alignment = center 60 | 61 | [icon-off-alignment-left] 62 | category = "icon-off-alignment-left" 63 | icon_position = off 64 | alignment = left 65 | 66 | [icon-off-alignment-right] 67 | category = "icon-off-alignment-right" 68 | icon_position = off 69 | alignment = right 70 | 71 | [icon-off-alignment-center] 72 | category = "icon-off-alignment-center" 73 | icon_position = off 74 | alignment = center 75 | 76 | [global] 77 | font = Monospace 8 78 | markup = full 79 | format = "%s\n%b" 80 | progress_bar_min_width = 100 81 | progress_bar_max_width = 200 82 | progress_bar_frame_width = 5 83 | progress_bar_height = 30 84 | width = (500,750) 85 | icon_path = /usr/share/icons/Papirus/24x24/status/:/usr/share/icons/Papirus/24x24/devices/:/usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/ -------------------------------------------------------------------------------- /test/functional-tests/dunstrc.markup: -------------------------------------------------------------------------------- 1 | [urgency_low] 2 | background = "#222222" 3 | foreground = "#888888" 4 | timeout = 10 5 | 6 | [urgency_normal] 7 | background = "#285577" 8 | foreground = "#ffffff" 9 | timeout = 10 10 | 11 | [urgency_critical] 12 | background = "#900000" 13 | foreground = "#ffffff" 14 | timeout = 0 15 | -------------------------------------------------------------------------------- /test/functional-tests/dunstrc.nomarkup: -------------------------------------------------------------------------------- 1 | [global] 2 | markup = no 3 | format = "%s\n%b" 4 | 5 | [urgency_low] 6 | background = "#222222" 7 | foreground = "#888888" 8 | timeout = 10 9 | 10 | [urgency_normal] 11 | background = "#285577" 12 | foreground = "#ffffff" 13 | timeout = 10 14 | 15 | [urgency_critical] 16 | background = "#900000" 17 | foreground = "#ffffff" 18 | timeout = 0 19 | -------------------------------------------------------------------------------- /test/functional-tests/dunstrc.nowrap: -------------------------------------------------------------------------------- 1 | [global] 2 | font = Monospace-10 3 | allow_markup = yes 4 | format = "%s\n%b" 5 | sort = yes 6 | indicate_hidden = yes 7 | alignment = left 8 | show_age_threshold = 60 9 | ignore_newline = no 10 | geometry = "300x5-30+20" 11 | transparency = 0 12 | idle_threshold = 120 13 | monitor = 0 14 | follow = mouse 15 | sticky_history = yes 16 | line_height = 0 17 | separator_height = 2; 18 | padding = 8 19 | horizontal_padding = 8 20 | separator_color = frame 21 | startup_notification = false 22 | dmenu = /usr/bin/dmenu -p dunst: 23 | browser = /usr/bin/firefox -new-tab 24 | 25 | [frame] 26 | width = 3 27 | color = "#aaaaaa" 28 | 29 | [shortcuts] 30 | close = ctrl+space 31 | close_all = ctrl+shift+space 32 | history = ctrl+grave 33 | context = ctrl+shift+period 34 | 35 | [urgency_low] 36 | background = "#222222" 37 | foreground = "#888888" 38 | timeout = 10 39 | 40 | [urgency_normal] 41 | background = "#285577" 42 | foreground = "#ffffff" 43 | timeout = 10 44 | 45 | [urgency_critical] 46 | background = "#900000" 47 | foreground = "#ffffff" 48 | timeout = 0 49 | -------------------------------------------------------------------------------- /test/functional-tests/dunstrc.progress_bar: -------------------------------------------------------------------------------- 1 | [urgency_low] 2 | background = "#222222" 3 | foreground = "#888888" 4 | timeout = 10 5 | 6 | [urgency_normal] 7 | background = "#285577" 8 | foreground = "#ffffff" 9 | timeout = 10 10 | 11 | [urgency_critical] 12 | background = "#900000" 13 | foreground = "#ffffff" 14 | timeout = 0 15 | 16 | [global] 17 | font = Monospace 8 18 | allow_markup = yes 19 | format = "%s\n%b" 20 | geometry = "0x5-30+20" 21 | icon_position = left 22 | progress_bar_min_width = 100 23 | progress_bar_max_width = 200 24 | progress_bar_frame_width = 5 25 | progress_bar_height = 30 26 | -------------------------------------------------------------------------------- /test/functional-tests/dunstrc.run_script: -------------------------------------------------------------------------------- 1 | [urgency_low] 2 | background = "#222222" 3 | foreground = "#888888" 4 | timeout = 10 5 | 6 | [urgency_normal] 7 | background = "#285577" 8 | foreground = "#ffffff" 9 | timeout = 10 10 | 11 | [urgency_critical] 12 | background = "#900000" 13 | foreground = "#ffffff" 14 | timeout = 0 15 | 16 | [script test] 17 | summary = trigger 18 | script = script_test.sh 19 | -------------------------------------------------------------------------------- /test/functional-tests/dunstrc.separator_click: -------------------------------------------------------------------------------- 1 | [urgency_low] 2 | background = "#222222" 3 | foreground = "#888888" 4 | timeout = 10 5 | 6 | [urgency_normal] 7 | background = "#285577" 8 | foreground = "#ffffff" 9 | timeout = 10 10 | 11 | [urgency_critical] 12 | background = "#900000" 13 | foreground = "#ffffff" 14 | timeout = 0 15 | 16 | [global] 17 | frame_width = 5 18 | separator_height = 50 19 | separator_color = "#000000" 20 | 21 | mouse_left_click = do_action 22 | mouse_middle_click = close_current 23 | mouse_right_click = context 24 | -------------------------------------------------------------------------------- /test/functional-tests/dunstrc.show_age: -------------------------------------------------------------------------------- 1 | [global] 2 | font = Monospace 8 3 | allow_markup = yes 4 | format = "%s\n%b" 5 | sort = yes 6 | indicate_hidden = yes 7 | alignment = left 8 | show_age_threshold = 2 9 | ignore_newline = no 10 | geometry = "300x5-30+20" 11 | transparency = 0 12 | idle_threshold = 120 13 | monitor = 0 14 | follow = mouse 15 | sticky_history = yes 16 | line_height = 0 17 | separator_height = 2 18 | padding = 8 19 | horizontal_padding = 8 20 | separator_color = frame 21 | startup_notification = false 22 | dmenu = /usr/bin/dmenu -p dunst 23 | browser = /usr/bin/firefox -new-tab 24 | 25 | [frame] 26 | width = 3 27 | color = "#aaaaaa" 28 | 29 | [shortcuts] 30 | close = ctrl+space 31 | close_all = ctrl+shift+space 32 | history = ctrl+grave 33 | context = ctrl+shift+period 34 | 35 | [urgency_low] 36 | background = "#222222" 37 | foreground = "#888888" 38 | timeout = 10 39 | 40 | [urgency_normal] 41 | background = "#285577" 42 | foreground = "#ffffff" 43 | timeout = 10 44 | 45 | [urgency_critical] 46 | background = "#900000" 47 | foreground = "#ffffff" 48 | timeout = 0 49 | -------------------------------------------------------------------------------- /test/functional-tests/script_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ../../dunstify "Success" "ooooh yeah" 4 | -------------------------------------------------------------------------------- /test/greenest.awk: -------------------------------------------------------------------------------- 1 | #!/usr/bin/awk -f 2 | # Copyright (c) 2016 Scott Vokes 3 | # 4 | # Permission to use, copy, modify, and/or distribute this software for any 5 | # purpose with or without fee is hereby granted, provided that the above 6 | # copyright notice and this permission notice appear in all copies. 7 | # 8 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | 16 | BEGIN { 17 | GREEN = "\033[32m" 18 | RED = "\033[31m" 19 | YELLOW = "\033[33m" 20 | RESET = "\033[m" 21 | } 22 | 23 | /^PASS/ { sub("PASS", GREEN "PASS" RESET) } 24 | /^SKIP/ { sub("SKIP", YELLOW "SKIP" RESET) } 25 | /^FAIL/ { sub("FAIL", RED "FAIL" RESET) } 26 | 27 | # highlight hexdump difference markers 28 | /^[0-9a-f]/ { 29 | sub("X", GREEN "X" RESET, $2) 30 | gsub("<", GREEN "<" RESET, $0) 31 | } 32 | 33 | { print($0) } 34 | -------------------------------------------------------------------------------- /test/helpers.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "helpers.h" 4 | #include "../src/notification.h" 5 | #include "../src/utils.h" 6 | 7 | GVariant *notification_setup_raw_image(const char *path) 8 | { 9 | GdkPixbuf *pb = gdk_pixbuf_new_from_file(path, NULL); 10 | 11 | if (!pb) 12 | return NULL; 13 | 14 | GVariant *hint_data = g_variant_new_from_data( 15 | G_VARIANT_TYPE("ay"), 16 | gdk_pixbuf_read_pixels(pb), 17 | gdk_pixbuf_get_byte_length(pb), 18 | TRUE, 19 | (GDestroyNotify) g_object_unref, 20 | g_object_ref(pb)); 21 | 22 | GVariant *hint = g_variant_new( 23 | "(iiibii@ay)", 24 | gdk_pixbuf_get_width(pb), 25 | gdk_pixbuf_get_height(pb), 26 | gdk_pixbuf_get_rowstride(pb), 27 | gdk_pixbuf_get_has_alpha(pb), 28 | gdk_pixbuf_get_bits_per_sample(pb), 29 | gdk_pixbuf_get_n_channels(pb), 30 | hint_data); 31 | 32 | g_object_unref(pb); 33 | 34 | return hint; 35 | } 36 | 37 | struct notification *test_notification_uninitialized(const char *name) 38 | { 39 | struct notification *n = notification_create(); 40 | 41 | n->dbus_client = g_strconcat(":", name, NULL); 42 | n->appname = g_strconcat("app of ", name, NULL); 43 | n->summary = g_strconcat(name, NULL); 44 | n->body = g_strconcat("See, ", name, ", I've got a body for you!", NULL); 45 | 46 | return n; 47 | } 48 | 49 | struct notification *test_notification(const char *name, gint64 timeout) 50 | { 51 | struct notification *n = test_notification_uninitialized(name); 52 | 53 | notification_init(n); 54 | 55 | n->format = "%s\n%b"; 56 | 57 | if (timeout != -1) 58 | n->timeout = S2US(timeout); 59 | 60 | return n; 61 | } 62 | 63 | struct notification *test_notification_with_icon(const char *name, gint64 timeout) 64 | { 65 | struct notification *n = test_notification(name, timeout); 66 | n->icon = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); 67 | return n; 68 | } 69 | 70 | GSList *get_dummy_notifications(int count) 71 | { 72 | GSList *notifications = NULL; 73 | 74 | int message_size = 24; 75 | for (int i = 0; i < count; i++) { 76 | char msg[message_size]; 77 | snprintf(msg, message_size, "test %d", i); 78 | struct notification *n = test_notification(msg, 10); 79 | n->icon_position = ICON_LEFT; 80 | n->text_to_render = g_strdup("dummy layout"); 81 | notifications = g_slist_append(notifications, n); 82 | } 83 | return notifications; 84 | } 85 | 86 | void free_dummy_notification(void *notification) 87 | { 88 | // wrapper function to work with g_slist_free_full 89 | notification_unref((struct notification *) notification); 90 | } 91 | 92 | /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 93 | -------------------------------------------------------------------------------- /test/helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef DUNST_TEST_HELPERS_H 2 | #define DUNST_TEST_HELPERS_H 3 | 4 | #include 5 | 6 | GVariant *notification_setup_raw_image(const char *path); 7 | struct notification *test_notification_uninitialized(const char *name); 8 | struct notification *test_notification(const char *name, gint64 timeout); 9 | struct notification *test_notification_with_icon(const char *name, gint64 timeout); 10 | GSList *get_dummy_notifications(int count); 11 | void free_dummy_notification(void *notification); 12 | 13 | #endif 14 | /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 15 | -------------------------------------------------------------------------------- /test/icon-lookup.c: -------------------------------------------------------------------------------- 1 | #include "greatest.h" 2 | #include "../src/icon-lookup.c" 3 | #include "helpers.h" 4 | #include "../src/notification.h" 5 | #include "../src/settings_data.h" 6 | 7 | extern const char *base; 8 | #define ICONPREFIX "data", "icons" 9 | 10 | int setup_test_theme(){ 11 | char *theme_path = g_build_filename(base, ICONPREFIX, NULL); 12 | int theme_index = load_icon_theme_from_dir(theme_path, "theme"); 13 | add_default_theme(theme_index); 14 | g_free(theme_path); 15 | return theme_index; 16 | } 17 | 18 | #define find_icon_test(iconname, size, ...) { \ 19 | char *icon = find_icon_path(iconname, size); \ 20 | char *expected = g_build_filename(base, ICONPREFIX, "theme", __VA_ARGS__, NULL); \ 21 | ASSERTm("Could not find icon", icon); \ 22 | ASSERT_STR_EQ(expected, icon); \ 23 | g_free(expected); \ 24 | g_free(icon); \ 25 | } 26 | 27 | #define find_path_test(path, ...) { \ 28 | char *icon = find_icon_path(path, 42); /* size doesn't matter */ \ 29 | char *expected = g_build_filename(base, ICONPREFIX, "theme", __VA_ARGS__, NULL); \ 30 | ASSERTm("Could not find icon", icon); \ 31 | ASSERT_STR_EQ(expected, icon); \ 32 | g_free(expected); \ 33 | g_free(icon); \ 34 | } 35 | 36 | TEST test_load_theme_from_dir(void) 37 | { 38 | setup_test_theme(); 39 | free_all_themes(); 40 | PASS(); 41 | } 42 | 43 | TEST test_find_icon(void) 44 | { 45 | setup_test_theme(); 46 | find_icon_test("edit", 8, "16x16", "actions", "edit.png"); 47 | find_icon_test("edit", 16, "16x16", "actions", "edit.png"); 48 | find_icon_test("edit", 32, "16x16", "actions", "edit.png"); 49 | find_icon_test("edit", 48, "16x16", "actions", "edit.png"); 50 | find_icon_test("edit", 49, "32x32", "actions", "edit.png"); 51 | find_icon_test("edit", 64, "32x32", "actions", "edit.png"); 52 | find_icon_test("preferences", 16, "16x16", "apps", "preferences.png"); 53 | find_icon_test("preferences", 32, "16x16", "apps", "preferences.png"); 54 | free_all_themes(); 55 | PASS(); 56 | } 57 | 58 | TEST test_find_path(void) 59 | { 60 | setup_test_theme(); 61 | char *path = g_build_filename(base, ICONPREFIX, "theme", "16x16", "actions", "edit.png", NULL); 62 | find_path_test(path, "16x16", "actions", "edit.png"); 63 | char *path2 = g_strconcat("file://", path, NULL); 64 | find_path_test(path2, "16x16", "actions", "edit.png"); 65 | g_free(path2); 66 | g_free(path); 67 | free_all_themes(); 68 | PASS(); 69 | } 70 | 71 | 72 | TEST test_new_icon_overrides_raw_icon(void) { 73 | setup_test_theme(); 74 | 75 | struct notification *n = test_notification_with_icon("new_icon", 10); 76 | struct rule *rule = malloc(sizeof(struct rule)); 77 | *rule = empty_rule; 78 | rule->summary = g_strdup("new_icon"); 79 | rule->new_icon = g_strdup("edit"); 80 | 81 | ASSERT(n->icon); 82 | 83 | int old_width = cairo_image_surface_get_width(n->icon); 84 | rule_apply(rule, n); 85 | ASSERT(old_width != cairo_image_surface_get_width(n->icon)); 86 | 87 | cairo_surface_destroy(n->icon); 88 | n->icon = NULL; 89 | 90 | notification_unref(n); 91 | g_free(rule->summary); 92 | g_free(rule->new_icon); 93 | g_free(rule); 94 | free_all_themes(); 95 | PASS(); 96 | } 97 | 98 | // TODO move this out of the test suite, since this isn't a real test 99 | TEST test_bench_search(void) 100 | { 101 | printf("Starting benchmark\n"); 102 | // At the time of committing, I get numbers like 0.25 second for 1000 icon lookups 103 | int index = load_icon_theme("Papirus"); 104 | add_default_theme(index); 105 | printf("Benchmarking icons\n"); 106 | clock_t start_time = clock(); 107 | for (int i = 0; i < 1000; i++){ 108 | // icon is part of papirus, right at the end of index.theme 109 | char *icon = find_icon_path("weather-windy-symbolic", 512); 110 | ASSERT(icon); 111 | g_free(icon); 112 | } 113 | double elapsed_time = (double)(clock() - start_time) / CLOCKS_PER_SEC; 114 | printf("Done in %f seconds\n", elapsed_time); 115 | free_all_themes(); 116 | PASS(); 117 | } 118 | 119 | TEST test_bench_multiple_search(void) 120 | { 121 | printf("Starting benchmark\n"); 122 | // At the time of committing, I get numbers like 2 second for 1000 icon lookups 123 | int index = load_icon_theme("Adwaita"); 124 | add_default_theme(index); 125 | index = load_icon_theme("Papirus"); 126 | add_default_theme(index); 127 | printf("Benchmarking icons\n"); 128 | clock_t start_time = clock(); 129 | for (int i = 0; i < 1000; i++){ 130 | // icon is part of papirus, right at the end of index.theme 131 | char *icon = find_icon_path("view-wrapped-rtl-symbolic", 512); 132 | /* printf("%s\n", icon); */ 133 | ASSERT(icon); 134 | g_free(icon); 135 | } 136 | double elapsed_time = (double)(clock() - start_time) / CLOCKS_PER_SEC; 137 | printf("Done in %f seconds\n", elapsed_time); 138 | free_all_themes(); 139 | PASS(); 140 | } 141 | 142 | TEST test_bench_doesnt_exist(void) 143 | { 144 | printf("Starting benchmark\n"); 145 | // At the time of committing, I get numbers like 9 seconds for 1000 icon lookups 146 | int index = load_icon_theme("Adwaita"); 147 | add_default_theme(index); 148 | index = load_icon_theme("Papirus"); 149 | add_default_theme(index); 150 | printf("Benchmarking icons\n"); 151 | clock_t start_time = clock(); 152 | for (int i = 0; i < 1000; i++){ 153 | // Icon size is chosen as some common icon size. 154 | char *icon = find_icon_path("doesn't exist", 48); 155 | /* printf("%s\n", icon); */ 156 | ASSERT_FALSE(icon); 157 | g_free(icon); 158 | } 159 | double elapsed_time = (double)(clock() - start_time) / CLOCKS_PER_SEC; 160 | printf("Done in %f seconds\n", elapsed_time); 161 | free_all_themes(); 162 | PASS(); 163 | } 164 | 165 | 166 | SUITE (suite_icon_lookup) 167 | { 168 | RUN_TEST(test_load_theme_from_dir); 169 | RUN_TEST(test_find_icon); 170 | RUN_TEST(test_find_path); 171 | RUN_TEST(test_new_icon_overrides_raw_icon); 172 | bool bench = false; 173 | if (bench) { 174 | RUN_TEST(test_bench_search); 175 | RUN_TEST(test_bench_multiple_search); 176 | RUN_TEST(test_bench_doesnt_exist); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /test/icon.c: -------------------------------------------------------------------------------- 1 | #include "../src/icon.c" 2 | #include "greatest.h" 3 | 4 | #define DATAPREFIX "/data" 5 | #define ICONPATH "/data/icons/theme" 6 | 7 | /* As there are no hints to test if the loaded GdkPixbuf is 8 | * read from a PNG or an SVG file, the sample icons in the 9 | * test structure have different sizes 10 | */ 11 | #define IS_ICON_PNG(pb) 4 == gdk_pixbuf_get_width(pb) 12 | #define IS_ICON_SVG(pb) 16 == gdk_pixbuf_get_width(pb) 13 | 14 | extern const char *base; 15 | 16 | int scale = 1; 17 | int size = 16; 18 | 19 | TEST test_get_path_from_icon_null(void) 20 | { 21 | char *result = get_path_from_icon_name(NULL, 16); 22 | ASSERT_EQ(result, NULL); 23 | PASS(); 24 | } 25 | 26 | TEST test_get_path_from_icon_name_full(void) 27 | { 28 | const char *iconpath = ICONPATH; 29 | 30 | gchar *path = g_build_filename(base, iconpath, "16x16", "actions", "edit.png", NULL); 31 | 32 | char *result = get_path_from_icon_name(path, size); 33 | ASSERT(result); 34 | ASSERT_STR_EQ(result, path); 35 | 36 | g_free(path); 37 | g_free(result); 38 | PASS(); 39 | } 40 | 41 | TEST test_icon_size_clamp_too_small(int min_icon_size, int max_icon_size) 42 | { 43 | int w = 12, h = 24; 44 | bool resized = icon_size_clamp(&w, &h, min_icon_size, max_icon_size); 45 | ASSERT(resized); 46 | ASSERT_EQ(w, 16); 47 | ASSERT_EQ(h, 32); 48 | 49 | PASS(); 50 | } 51 | 52 | TEST test_icon_size_clamp_not_necessary(int min_icon_size, int max_icon_size) 53 | { 54 | int w = 20, h = 30; 55 | bool resized = icon_size_clamp(&w, &h, min_icon_size, max_icon_size); 56 | ASSERT(!resized); 57 | ASSERT_EQ(w, 20); 58 | ASSERT_EQ(h, 30); 59 | 60 | PASS(); 61 | } 62 | 63 | TEST test_icon_size_clamp_too_big(int min_icon_size, int max_icon_size) 64 | { 65 | int w = 75, h = 150; 66 | bool resized = icon_size_clamp(&w, &h, min_icon_size, max_icon_size); 67 | ASSERT(resized); 68 | ASSERT_EQ(w, 50); 69 | ASSERT_EQ(h, 100); 70 | 71 | PASS(); 72 | } 73 | 74 | TEST test_icon_size_clamp_too_small_then_too_big(int min_icon_size, int max_icon_size) 75 | { 76 | int w = 8, h = 80; 77 | bool resized = icon_size_clamp(&w, &h, min_icon_size, max_icon_size); 78 | ASSERT(resized); 79 | ASSERT_EQ(w, 10); 80 | ASSERT_EQ(h, 100); 81 | 82 | PASS(); 83 | } 84 | 85 | SUITE(suite_icon) 86 | { 87 | // set only valid icons in the path 88 | char *icon_path = g_build_filename(base, DATAPREFIX, NULL); 89 | setenv("XDG_DATA_HOME", icon_path, 1); 90 | printf("Icon path: %s\n", icon_path); 91 | RUN_TEST(test_get_path_from_icon_null); 92 | RUN_TEST(test_get_path_from_icon_name_full); 93 | RUN_TESTp(test_icon_size_clamp_not_necessary, 0, 100); 94 | 95 | RUN_TESTp(test_icon_size_clamp_too_small, 16, 100); 96 | RUN_TESTp(test_icon_size_clamp_not_necessary, 16, 100); 97 | RUN_TESTp(test_icon_size_clamp_too_big, 16, 100); 98 | RUN_TESTp(test_icon_size_clamp_too_small_then_too_big, 16, 100); 99 | 100 | RUN_TESTp(test_icon_size_clamp_too_small, 16, 0); 101 | RUN_TESTp(test_icon_size_clamp_not_necessary, 16, 0); 102 | 103 | RUN_TESTp(test_icon_size_clamp_not_necessary, 0, 100); 104 | RUN_TESTp(test_icon_size_clamp_too_big, 0, 100); 105 | 106 | g_clear_pointer(&icon_path, g_free); 107 | } 108 | /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 109 | -------------------------------------------------------------------------------- /test/ini.c: -------------------------------------------------------------------------------- 1 | #include "greatest.h" 2 | #include "../src/ini.c" 3 | #include 4 | 5 | extern const char *base; 6 | 7 | TEST test_next_section(void) 8 | { 9 | char *config_path = g_strconcat(base, "/data/test-ini", NULL); 10 | FILE *config_file = fopen(config_path, "r"); 11 | if (!config_file) { 12 | ASSERTm(false, "Test config file 'data/test-ini' couldn't be opened, failing.\n"); 13 | } 14 | struct ini * ini = load_ini_file(config_file); 15 | ASSERT(ini); 16 | fclose(config_file); 17 | free(config_path); 18 | 19 | const char *section = NULL; 20 | ASSERT_STR_EQ("bool", (section = next_section(ini, section))); 21 | ASSERT_STR_EQ("string", (section = next_section(ini, section))); 22 | ASSERT_STR_EQ("list", (section = next_section(ini, section))); 23 | ASSERT_STR_EQ("path", (section = next_section(ini, section))); 24 | ASSERT_STR_EQ("int", (section = next_section(ini, section))); 25 | ASSERT_STR_EQ("double", (section = next_section(ini, section))); 26 | finish_ini(ini); 27 | free(ini); 28 | PASS(); 29 | } 30 | 31 | 32 | SUITE(suite_ini) 33 | { 34 | RUN_TEST(test_next_section); 35 | } 36 | -------------------------------------------------------------------------------- /test/input.c: -------------------------------------------------------------------------------- 1 | #include "../src/input.c" 2 | #include "queues.h" 3 | #include "greatest.h" 4 | #include "helpers.h" 5 | #include "../src/utils.h" 6 | 7 | TEST test_get_notification_clickable_height_first(void) 8 | { 9 | bool orginal_gap_size = settings.gap_size; 10 | settings.gap_size = 0; 11 | 12 | struct notification *n = test_notification("test", 10); 13 | n->displayed_height = 12; 14 | 15 | int expected_size = n->displayed_height + settings.frame_width; 16 | expected_size += (settings.separator_height / 2.0); 17 | int result = get_notification_clickable_height(n, true, false); 18 | 19 | ASSERT(result == expected_size); 20 | 21 | settings.gap_size = orginal_gap_size; 22 | notification_unref(n); 23 | PASS(); 24 | } 25 | 26 | TEST test_get_notification_clickable_height_middle(void) 27 | { 28 | bool orginal_gap_size = settings.gap_size; 29 | settings.gap_size = 0; 30 | 31 | struct notification *n = test_notification("test", 10); 32 | n->displayed_height = 12; 33 | 34 | int expected_size = n->displayed_height + settings.separator_height; 35 | int result = get_notification_clickable_height(n, false, false); 36 | 37 | ASSERT(result == expected_size); 38 | 39 | settings.gap_size = orginal_gap_size; 40 | notification_unref(n); 41 | PASS(); 42 | } 43 | 44 | TEST test_get_notification_clickable_height_last(void) 45 | { 46 | bool orginal_gap_size = settings.gap_size; 47 | settings.gap_size = 0; 48 | 49 | struct notification *n = test_notification("test", 10); 50 | n->displayed_height = 12; 51 | 52 | int expected_size = n->displayed_height + settings.frame_width; 53 | expected_size += (settings.separator_height / 2.0); 54 | int result = get_notification_clickable_height(n, false, true); 55 | 56 | ASSERT(result == expected_size); 57 | 58 | settings.gap_size = orginal_gap_size; 59 | notification_unref(n); 60 | PASS(); 61 | } 62 | 63 | TEST test_get_notification_clickable_height_gaps(void) 64 | { 65 | bool orginal_gap_size = settings.gap_size; 66 | settings.gap_size = 7; 67 | 68 | struct notification *n = test_notification("test", 10); 69 | n->displayed_height = 12; 70 | 71 | int expected_size = n->displayed_height + (settings.frame_width * 2); 72 | 73 | int result_first = get_notification_clickable_height(n, true, false); 74 | ASSERT(result_first == expected_size); 75 | 76 | int result_middle = get_notification_clickable_height(n, false, false); 77 | ASSERT(result_middle == expected_size); 78 | 79 | int result_last = get_notification_clickable_height(n, false, true); 80 | ASSERT(result_last == expected_size); 81 | 82 | settings.gap_size = orginal_gap_size; 83 | notification_unref(n); 84 | PASS(); 85 | } 86 | 87 | TEST test_notification_at(void) 88 | { 89 | int total_notifications = 3; 90 | GSList *notifications = get_dummy_notifications(total_notifications); 91 | 92 | queues_init(); 93 | 94 | int display_height = 12; 95 | struct notification *n; 96 | for (GSList *iter = notifications; iter; iter = iter->next) { 97 | n = iter->data; 98 | n->displayed_height = display_height; 99 | queues_notification_insert(n); 100 | } 101 | 102 | queues_update(STATUS_NORMAL, time_monotonic_now()); 103 | 104 | struct notification *top_notification = g_slist_nth_data(notifications, 0); 105 | int top_notification_height = get_notification_clickable_height(top_notification, true, false); 106 | 107 | struct notification *middle_notification = g_slist_nth_data(notifications, 1); 108 | int middle_notification_height = get_notification_clickable_height(middle_notification, false, false); 109 | 110 | struct notification *bottom_notification = g_slist_nth_data(notifications, 2); 111 | int bottom_notification_height = get_notification_clickable_height(bottom_notification, false, true); 112 | 113 | struct notification *result; 114 | 115 | int top_y_coord; 116 | top_y_coord = 0; 117 | result = get_notification_at(top_y_coord); 118 | ASSERT(result != NULL); 119 | ASSERT(result == top_notification); 120 | 121 | top_y_coord = top_notification_height - 1; 122 | result = get_notification_at(top_y_coord); 123 | ASSERT(result != NULL); 124 | ASSERT(result == top_notification); 125 | 126 | int middle_y_coord; 127 | middle_y_coord = top_notification_height; 128 | result = get_notification_at(middle_y_coord); 129 | ASSERT(result != NULL); 130 | ASSERT(result == middle_notification); 131 | 132 | middle_y_coord = top_notification_height; 133 | result = get_notification_at(middle_y_coord); 134 | ASSERT(result != NULL); 135 | ASSERT(result == middle_notification); 136 | 137 | int bottom_y_coord; 138 | bottom_y_coord = top_notification_height + middle_notification_height; 139 | result = get_notification_at(bottom_y_coord); 140 | ASSERT(result != NULL); 141 | ASSERT(result == bottom_notification); 142 | 143 | bottom_y_coord = top_notification_height + middle_notification_height + bottom_notification_height - 1; 144 | result = get_notification_at(bottom_y_coord); 145 | ASSERT(result != NULL); 146 | ASSERT(result == bottom_notification); 147 | 148 | g_slist_free_full(notifications, free_dummy_notification); 149 | PASS(); 150 | } 151 | 152 | SUITE(suite_input) 153 | { 154 | SHUFFLE_TESTS(time(NULL), { 155 | RUN_TEST(test_get_notification_clickable_height_first); 156 | RUN_TEST(test_get_notification_clickable_height_middle); 157 | RUN_TEST(test_get_notification_clickable_height_last); 158 | RUN_TEST(test_get_notification_clickable_height_gaps); 159 | RUN_TEST(test_notification_at); 160 | }); 161 | } 162 | -------------------------------------------------------------------------------- /test/log.c: -------------------------------------------------------------------------------- 1 | #include "../src/log.c" 2 | #include "greatest.h" 3 | 4 | TEST test_log_level(GLogLevelFlags level, const char *shortstr, const char *longstr) 5 | { 6 | ASSERT_STR_EQ(log_level_to_string(level), longstr); 7 | 8 | log_set_level_from_string(shortstr); 9 | 10 | if (level != G_LOG_LEVEL_ERROR) 11 | ASSERT_ENUM_EQ(level, log_level, log_level_to_string); 12 | 13 | log_set_level_from_string(longstr); 14 | 15 | if (level != G_LOG_LEVEL_ERROR) 16 | ASSERT_ENUM_EQ(level, log_level, log_level_to_string); 17 | 18 | PASS(); 19 | } 20 | 21 | SUITE(suite_log) 22 | { 23 | GLogLevelFlags oldlevel = log_level; 24 | 25 | RUN_TESTp(test_log_level, G_LOG_LEVEL_ERROR, NULL, "ERROR"); 26 | RUN_TESTp(test_log_level, G_LOG_LEVEL_CRITICAL, "crit", "CRITICAL"); 27 | RUN_TESTp(test_log_level, G_LOG_LEVEL_WARNING, "warn", "WARNING"); 28 | RUN_TESTp(test_log_level, G_LOG_LEVEL_MESSAGE, "mesg", "MESSAGE"); 29 | RUN_TESTp(test_log_level, G_LOG_LEVEL_INFO, "info", "INFO"); 30 | RUN_TESTp(test_log_level, G_LOG_LEVEL_DEBUG, "deb", "DEBUG"); 31 | 32 | log_level = oldlevel; 33 | } 34 | 35 | /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 36 | -------------------------------------------------------------------------------- /test/markup.c: -------------------------------------------------------------------------------- 1 | #include "../src/markup.c" 2 | #include "greatest.h" 3 | 4 | TEST test_markup_strip(void) 5 | { 6 | char *ptr; 7 | 8 | ASSERT_STR_EQ(""", (ptr=markup_strip(g_strdup("&quot;")))); 9 | g_free(ptr); 10 | ASSERT_STR_EQ("'", (ptr=markup_strip(g_strdup("&apos;")))); 11 | g_free(ptr); 12 | ASSERT_STR_EQ("<", (ptr=markup_strip(g_strdup("&lt;")))); 13 | g_free(ptr); 14 | ASSERT_STR_EQ(">", (ptr=markup_strip(g_strdup("&gt;")))); 15 | g_free(ptr); 16 | ASSERT_STR_EQ("&", (ptr=markup_strip(g_strdup("&amp;")))); 17 | g_free(ptr); 18 | ASSERT_STR_EQ(">A A foo
bar\nbaz"), MARKUP_NO))); 30 | g_free(ptr); 31 | ASSERT_STR_EQ("foo\nbar\nbaz", (ptr=markup_transform(g_strdup("foo
bar\nbaz"), MARKUP_STRIP))); 32 | g_free(ptr); 33 | ASSERT_STR_EQ("foo\nbar\nbaz", (ptr=markup_transform(g_strdup("foo
bar\nbaz"), MARKUP_FULL))); 34 | g_free(ptr); 35 | 36 | settings.ignore_newline = true; 37 | ASSERT_STR_EQ("<i>foo</i><br>bar baz", (ptr=markup_transform(g_strdup("foo
bar\nbaz"), MARKUP_NO))); 38 | g_free(ptr); 39 | ASSERT_STR_EQ("foo bar baz", (ptr=markup_transform(g_strdup("foo
bar\nbaz"), MARKUP_STRIP))); 40 | g_free(ptr); 41 | ASSERT_STR_EQ("foo bar baz", (ptr=markup_transform(g_strdup("foo
bar\nbaz"), MARKUP_FULL))); 42 | g_free(ptr); 43 | 44 | // Test replacement of img and a tags, not renderable by pango 45 | ASSERT_STR_EQ("foo bar bar baz", (ptr=markup_transform(g_strdup("\"foo
bar\nbaz"), MARKUP_FULL))); 46 | g_free(ptr); 47 | ASSERT_STR_EQ("test ", (ptr=markup_transform(g_strdup("test \"foo image"), MARKUP_FULL))); 50 | g_free(ptr); 51 | ASSERT_STR_EQ("bar baz", (ptr=markup_transform(g_strdup("bar baz"), MARKUP_FULL))); 52 | g_free(ptr); 53 | 54 | ASSERT_STR_EQ("Ψ", (ptr=markup_transform(g_strdup("Ψ"), MARKUP_FULL))); 55 | free(ptr); 56 | ASSERT_STR_EQ("Ψ Ψ", (ptr=markup_transform(g_strdup("Ψ Ψ"), MARKUP_FULL))); 57 | free(ptr); 58 | ASSERT_STR_EQ("> <", (ptr=markup_transform(g_strdup("> <"), MARKUP_FULL))); 59 | free(ptr); 60 | ASSERT_STR_EQ("&invalid; &#abc; &#xG;", (ptr=markup_transform(g_strdup("&invalid; &#abc; &#xG;"), MARKUP_FULL))); 61 | free(ptr); 62 | ASSERT_STR_EQ("&; &#; &#x;", (ptr=markup_transform(g_strdup("&; &#; &#x;"), MARKUP_FULL))); 63 | free(ptr); 64 | 65 | PASS(); 66 | } 67 | 68 | TEST helper_markup_strip_a (const char *in, const char *exp, const char *urls) 69 | { 70 | // out_urls is a return parameter and the content should be ignored 71 | char *out_urls = (char *)0x04; //Chosen by a fair dice roll 72 | char *out = g_strdup(in); 73 | char *msg = g_strconcat("url: ", in, NULL); 74 | 75 | markup_strip_a(&out, &out_urls); 76 | 77 | ASSERT_STR_EQm(msg, exp, out); 78 | 79 | if (urls) { 80 | ASSERT_STR_EQm(msg, urls, out_urls); 81 | } else { 82 | ASSERT_EQm(msg, urls, out_urls); 83 | } 84 | 85 | g_free(out_urls); 86 | g_free(out); 87 | g_free(msg); 88 | 89 | PASS(); 90 | } 91 | 92 | TEST test_markup_strip_a(void) 93 | { 94 | RUN_TESTp(helper_markup_strip_a, "valid link", "valid link", "[valid] https://url.com"); 95 | RUN_TESTp(helper_markup_strip_a, "valid link", "valid link", "[valid] "); 96 | RUN_TESTp(helper_markup_strip_a, "valid link", "valid link", NULL); 97 | RUN_TESTp(helper_markup_strip_a, "valid link", "valid link", "[valid link] https://url.com"); 98 | 99 | RUN_TESTp(helper_markup_strip_a, " link", " link", NULL); 100 | RUN_TESTp(helper_markup_strip_a, " link", " link", NULL); 101 | 102 | PASS(); 103 | } 104 | 105 | TEST helper_markup_strip_img (const char *in, const char *exp, const char *urls) 106 | { 107 | // out_urls is a return parameter and the content should be ignored 108 | char *out_urls = (char *)0x04; //Chosen by a fair dice roll 109 | char *out = g_strdup(in); 110 | char *msg = g_strconcat("url: ", in, NULL); 111 | 112 | markup_strip_img(&out, &out_urls); 113 | 114 | ASSERT_STR_EQm(msg, exp, out); 115 | 116 | if (urls) { 117 | ASSERT_STR_EQm(msg, urls, out_urls); 118 | } else { 119 | ASSERT_EQm(msg, urls, out_urls); 120 | } 121 | 122 | g_free(out_urls); 123 | g_free(out); 124 | g_free(msg); 125 | 126 | PASS(); 127 | } 128 | 129 | TEST test_markup_strip_img(void) 130 | { 131 | RUN_TESTp(helper_markup_strip_img, "v img", "v [image] img", NULL); 132 | RUN_TESTp(helper_markup_strip_img, "v \"valid\" img", "v valid img", NULL); 133 | RUN_TESTp(helper_markup_strip_img, "v img", "v [image] img", "[image] url.com"); 134 | 135 | RUN_TESTp(helper_markup_strip_img, "v \"valid\" img", "v valid img", "[valid] url.com"); 136 | RUN_TESTp(helper_markup_strip_img, "v \"valid\" img", "v valid img", "[valid] url.com"); 137 | RUN_TESTp(helper_markup_strip_img, "v \"valid\" img", "v valid img", "[valid] url.com"); 138 | 139 | RUN_TESTp(helper_markup_strip_img, "i \"invalid img", "i [image] img", "[image] https://url.com"); 140 | RUN_TESTp(helper_markup_strip_img, "i \"broken\" img", "i broken img", NULL); 141 | RUN_TESTp(helper_markup_strip_img, "i \"invalid img", "i [image] img", NULL); 142 | 143 | RUN_TESTp(helper_markup_strip_img, "i \"broken\" img", "i broken img", NULL); 144 | RUN_TESTp(helper_markup_strip_img, "i \"invalid img", "i [image] img", "[image] url.com"); 145 | RUN_TESTp(helper_markup_strip_img, "i \"invalid img", "i [image] img", NULL); 146 | 147 | RUN_TESTp(helper_markup_strip_img, "i \"invalid\" 6 | 7 | TEST test_extract_urls_from_empty_string(void) 8 | { 9 | char *urls = extract_urls(""); 10 | ASSERT_EQ_FMT(NULL, (void*)urls, "%p"); 11 | 12 | urls = extract_urls(NULL); 13 | ASSERT(!urls); 14 | PASS(); 15 | } 16 | 17 | TEST test_extract_urls_from_no_urls_string(void) 18 | { 19 | char *urls = extract_urls("You got a new message from your friend"); 20 | ASSERT(!urls); 21 | PASS(); 22 | } 23 | 24 | TEST test_extract_urls_from_one_url_string(void) 25 | { 26 | char *urls = extract_urls("Hi from https://www.example.com!"); 27 | ASSERT_STR_EQ("https://www.example.com", urls); 28 | g_free(urls); 29 | PASS(); 30 | } 31 | 32 | TEST test_extract_urls_from_two_url_string(void) 33 | { 34 | char *urls = extract_urls("Hi from https://www.example.com and ftp://www.example.com!"); 35 | ASSERT_STR_EQ("https://www.example.com\nftp://www.example.com", urls); 36 | g_free(urls); 37 | PASS(); 38 | } 39 | 40 | TEST test_extract_urls_from_one_url_port(void) 41 | { 42 | char *urls = extract_urls("Hi from https://www.example.com:8100 and have a nice day!"); 43 | ASSERT_STR_EQ("https://www.example.com:8100", urls); 44 | g_free(urls); 45 | PASS(); 46 | } 47 | 48 | TEST test_extract_urls_from_one_url_path(void) 49 | { 50 | char *urls = extract_urls("Hi from https://www.example.com:8100/testpath and have a nice day!"); 51 | ASSERT_STR_EQ("https://www.example.com:8100/testpath", urls); 52 | g_free(urls); 53 | PASS(); 54 | } 55 | 56 | TEST test_extract_urls_from_one_url_anchor(void) 57 | { 58 | char *urls = extract_urls("Hi from https://www.example.com:8100/testpath#anchor and have a nice day!"); 59 | ASSERT_STR_EQ("https://www.example.com:8100/testpath#anchor", urls); 60 | g_free(urls); 61 | PASS(); 62 | } 63 | 64 | SUITE(suite_menu) 65 | { 66 | RUN_TEST(test_extract_urls_from_empty_string); 67 | RUN_TEST(test_extract_urls_from_no_urls_string); 68 | RUN_TEST(test_extract_urls_from_one_url_string); 69 | RUN_TEST(test_extract_urls_from_two_url_string); 70 | RUN_TEST(test_extract_urls_from_one_url_port); 71 | RUN_TEST(test_extract_urls_from_one_url_path); 72 | RUN_TEST(test_extract_urls_from_one_url_anchor); 73 | } 74 | /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 75 | -------------------------------------------------------------------------------- /test/misc.c: -------------------------------------------------------------------------------- 1 | #include "greatest.h" 2 | 3 | // This actually tests the buildsystem to make sure, 4 | // the build system hands over a correct version number 5 | // This is not testable via macros 6 | TEST assert_version_number(void) 7 | { 8 | ASSERTm("Version number is empty", 9 | 0 != strcmp(VERSION, "")); 10 | 11 | ASSERTm("Version number is not seeded by git", 12 | NULL == strstr(VERSION, "non-git")); 13 | PASS(); 14 | } 15 | 16 | SUITE(suite_misc) 17 | { 18 | RUN_TEST(assert_version_number); 19 | } 20 | 21 | /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 22 | -------------------------------------------------------------------------------- /test/queues.h: -------------------------------------------------------------------------------- 1 | #include "greatest.h" 2 | 3 | #ifndef DUNST_TEST_QUEUES_H 4 | #define DUNST_TEST_QUEUES_H 5 | 6 | #include 7 | #include 8 | 9 | #include "../src/notification.h" 10 | #include "../src/queues.h" 11 | 12 | #define STATUS_NORMAL ((struct dunst_status) {.fullscreen=false, .running=true, .idle=false}) 13 | #define STATUS_IDLE ((struct dunst_status) {.fullscreen=false, .running=true, .idle=true}) 14 | #define STATUS_FSIDLE ((struct dunst_status) {.fullscreen=true, .running=true, .idle=true}) 15 | #define STATUS_FS ((struct dunst_status) {.fullscreen=true, .running=true, .idle=false}) 16 | #define STATUS_PAUSE ((struct dunst_status) {.fullscreen=false, .running=false, .idle=false}) 17 | 18 | #define QUEUE_WAIT waiting 19 | #define QUEUE_DISP displayed 20 | #define QUEUE_HIST history 21 | #define QUEUE(q) QUEUE_##q 22 | 23 | #define QUEUE_LEN_ALL(wait, disp, hist) do { \ 24 | if (wait >= 0) ASSERTm("Waiting is not " #wait, wait == g_queue_get_length(QUEUE(WAIT))); \ 25 | if (disp >= 0) ASSERTm("Displayed is not " #disp, disp == g_queue_get_length(QUEUE(DISP))); \ 26 | if (disp >= 0) ASSERTm("History is not " #hist, hist == g_queue_get_length(QUEUE(HIST))); \ 27 | } while (0) 28 | 29 | #define QUEUE_CONTAINS(q, n) QUEUE_CONTAINSm("QUEUE_CONTAINS(" #q "," #n ")", q, n) 30 | #define QUEUE_CONTAINSm(msg, q, n) ASSERTm(msg, g_queue_find(QUEUE(q), n)) 31 | 32 | #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) 33 | 34 | /* Retrieve a notification by its id. Solely for debugging purposes */ 35 | struct notification *queues_debug_find_notification_by_id(int id); 36 | 37 | #endif 38 | /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 39 | -------------------------------------------------------------------------------- /test/rules.c: -------------------------------------------------------------------------------- 1 | #include "../src/rules.c" 2 | 3 | #include "greatest.h" 4 | #include 5 | 6 | extern const char *base; 7 | 8 | // test filtering rules matching 9 | TEST test_pattern_match(void) { 10 | // NULL should match everything 11 | ASSERT(rule_field_matches_string("anything", NULL)); 12 | 13 | // Literal matches 14 | ASSERT(rule_field_matches_string("asdf", "asdf")); 15 | ASSERT(rule_field_matches_string("test123", "test123")); 16 | 17 | ASSERT(rule_field_matches_string("!", "!")); 18 | ASSERT(rule_field_matches_string("!asd", "!asd")); 19 | ASSERT(rule_field_matches_string("/as/d", "/as/d")); 20 | ASSERT(rule_field_matches_string("/as/d", "/as/d")); 21 | 22 | // ranges 23 | ASSERT(rule_field_matches_string("ac", "[a-z][a-z]")); 24 | 25 | // Non-matches 26 | ASSERT_FALSE(rule_field_matches_string("asd", "!asd")); 27 | ASSERT_FALSE(rule_field_matches_string("ffff", "*asd")); 28 | ASSERT_FALSE(rule_field_matches_string("ffff", "?")); 29 | ASSERT_FALSE(rule_field_matches_string("Ac", "[a-z][a-z]")); 30 | 31 | // Things that differ between fnmatch(3) and regex(3) 32 | 33 | if (settings.enable_regex) { 34 | // Single character matching 35 | ASSERT(rule_field_matches_string("a", ".")); 36 | 37 | // Wildcard matching 38 | ASSERT(rule_field_matches_string("anything", ".*")); 39 | ASSERT(rule_field_matches_string("*", ".*")); 40 | ASSERT(rule_field_matches_string("", ".*")); 41 | ASSERT(rule_field_matches_string("ffffasd", ".*asd")); 42 | 43 | // Substring matching 44 | ASSERT(rule_field_matches_string("asd", "")); 45 | ASSERT(rule_field_matches_string("asd", "sd")); 46 | ASSERT(rule_field_matches_string("asd", "a")); 47 | ASSERT(rule_field_matches_string("asd", "d")); 48 | ASSERT(rule_field_matches_string("asd", "asd")); 49 | 50 | // Match multiple strings 51 | ASSERT(rule_field_matches_string("ghj", "asd|dfg|ghj")); 52 | ASSERT(rule_field_matches_string("asd", "asd|dfg|ghj")); 53 | ASSERT(rule_field_matches_string("dfg", "asd|dfg|ghj")); 54 | ASSERT_FALSE(rule_field_matches_string("azd", "asd|dfg|ghj")); 55 | 56 | // Special characters 57 | ASSERT_FALSE(rule_field_matches_string("{", "{")); 58 | ASSERT(rule_field_matches_string("{", "\\{")); 59 | ASSERT(rule_field_matches_string("a", "(a)")); 60 | } else { 61 | // Single character matching 62 | ASSERT(rule_field_matches_string("a", "?")); 63 | 64 | // Wildcard matching 65 | ASSERT(rule_field_matches_string("anything", "*")); 66 | ASSERT(rule_field_matches_string("*", "*")); 67 | ASSERT(rule_field_matches_string("", "*")); 68 | ASSERT(rule_field_matches_string("ffffasd", "*asd")); 69 | 70 | // Substring matching 71 | ASSERT_FALSE(rule_field_matches_string("asd", "")); 72 | ASSERT_FALSE(rule_field_matches_string("asd", "sd")); 73 | ASSERT_FALSE(rule_field_matches_string("asd", "a")); 74 | ASSERT_FALSE(rule_field_matches_string("asd", "d")); 75 | ASSERT(rule_field_matches_string("asd", "asd")); 76 | } 77 | PASS(); 78 | } 79 | 80 | SUITE(suite_rules) { 81 | bool store = settings.enable_regex; 82 | 83 | settings.enable_regex = false; 84 | RUN_TEST(test_pattern_match); 85 | 86 | settings.enable_regex = true; 87 | RUN_TEST(test_pattern_match); 88 | 89 | settings.enable_regex = store; 90 | } 91 | -------------------------------------------------------------------------------- /test/setting.c: -------------------------------------------------------------------------------- 1 | #include "../src/settings.h" 2 | #include "../src/option_parser.h" 3 | #include "../src/settings_data.h" 4 | 5 | #include "greatest.h" 6 | 7 | extern const char *base; 8 | 9 | // In this suite a few dunstrc's are tested to see if the settings code works 10 | // This file is called setting.c, since the name settings.c caused issues. 11 | 12 | char *config_path; 13 | 14 | TEST test_dunstrc_markup(void) { 15 | config_path = g_strconcat(base, "/data/dunstrc.markup", NULL); 16 | load_settings(config_path); 17 | 18 | ASSERT_STR_EQ(settings.font, "Monospace 8"); 19 | 20 | 21 | const char *e_format = "%s\\n%b"; // escape the \n since it would otherwise result in the newline character 22 | const struct rule * r = get_rule("global"); 23 | const char *got_format = r->format; 24 | ASSERT_STR_EQ(e_format, got_format); 25 | ASSERT(settings.indicate_hidden); 26 | 27 | g_free(config_path); 28 | PASS(); 29 | } 30 | 31 | TEST test_dunstrc_nomarkup(void) { 32 | config_path = g_strconcat(base, "/data/dunstrc.nomarkup", NULL); 33 | load_settings(config_path); 34 | 35 | ASSERT_STR_EQ(settings.font, "Monospace 8"); 36 | 37 | 38 | const char *e_format = "%s\\n%b"; // escape the \n since it would otherwise result in the newline character 39 | const struct rule * r = get_rule("global"); 40 | const char *got_format = r->format; 41 | ASSERT_STR_EQ(e_format, got_format); 42 | ASSERT(settings.indicate_hidden); 43 | 44 | g_free(config_path); 45 | PASS(); 46 | } 47 | 48 | // Test if the defaults in code and in dunstrc match 49 | TEST test_dunstrc_defaults(void) { 50 | struct settings s_default; 51 | struct settings s_dunstrc; 52 | 53 | config_path = g_strconcat(base, "/data/dunstrc.default", NULL); 54 | set_defaults(); 55 | s_default = settings; 56 | 57 | load_settings(config_path); 58 | s_dunstrc = settings; 59 | 60 | ASSERT_EQ(s_default.corner_radius, s_dunstrc.corner_radius); 61 | char message[500]; 62 | 63 | for (int i = 0; i < G_N_ELEMENTS(allowed_settings); i++) { 64 | if (!allowed_settings[i].value) { 65 | continue; // it's a rule, that's harder to test 66 | } 67 | if (allowed_settings[i].different_default) { 68 | continue; // Skip testing, since it's an intended difference. 69 | } 70 | size_t offset = (char*)allowed_settings[i].value - (char*)&settings; 71 | enum setting_type type = allowed_settings[i].type; 72 | snprintf(message, 500, "The default of setting %s does not match. Different defaults are set in code and dunstrc" 73 | , allowed_settings[i].name); 74 | switch (type) { 75 | case TYPE_CUSTOM: 76 | if (allowed_settings[i].parser == string_parse_bool) { 77 | { 78 | bool a = *(bool*) ((char*) &s_default + offset); 79 | bool b = *(bool*) ((char*) &s_dunstrc + offset); 80 | ASSERT_EQm(message, a, b); 81 | } 82 | break; 83 | } // else fall through 84 | case TYPE_TIME: 85 | case TYPE_INT:; 86 | { 87 | int a = *(int*) ((char*) &s_default + offset); 88 | int b = *(int*) ((char*) &s_dunstrc + offset); 89 | ASSERT_EQm(message, a, b); 90 | } 91 | break; 92 | case TYPE_DOUBLE: 93 | case TYPE_STRING: 94 | case TYPE_PATH: 95 | case TYPE_LIST: 96 | case TYPE_LENGTH: 97 | break; // TODO implement these checks as well 98 | default: 99 | printf("Type unknown %s:%d\n", __FILE__, __LINE__); 100 | } 101 | /* printf("%zu\n", offset); */ 102 | } 103 | 104 | g_free(config_path); 105 | PASS(); 106 | } 107 | 108 | SUITE(suite_setting) { 109 | RUN_TEST(test_dunstrc_markup); 110 | RUN_TEST(test_dunstrc_nomarkup); 111 | RUN_TEST(test_dunstrc_defaults); 112 | } 113 | -------------------------------------------------------------------------------- /test/settings_data.c: -------------------------------------------------------------------------------- 1 | #include "../src/settings_data.h" 2 | #include "greatest.h" 3 | 4 | extern const char *base; 5 | 6 | // TODO check enums on NULL-termination 7 | 8 | TEST test_names_valid(void) 9 | { 10 | for (size_t i = 0; i < G_N_ELEMENTS(allowed_settings); i++) { 11 | gchar *error1 = g_strdup_printf("Setting name is null (setting description is \"%s\")", allowed_settings[i].description); 12 | gchar *error2 = g_strdup_printf("Setting name is empty (setting description is \"%s\")", allowed_settings[i].description); 13 | ASSERTm(error1, allowed_settings[i].name); 14 | ASSERTm(error2, strlen(allowed_settings[i].name)); 15 | g_free(error1); 16 | g_free(error2); 17 | } 18 | PASS(); 19 | } 20 | 21 | TEST test_description_valid(void) 22 | { 23 | for (size_t i = 0; i < G_N_ELEMENTS(allowed_settings); i++) { 24 | gchar *error1 = g_strdup_printf("Description of setting %s is null", allowed_settings[i].name); 25 | gchar *error2 = g_strdup_printf("Description of setting %s is empty", allowed_settings[i].name); 26 | ASSERTm(error1, allowed_settings[i].description); 27 | ASSERTm(error2, strlen(allowed_settings[i].description)); 28 | g_free(error1); 29 | g_free(error2); 30 | } 31 | PASS(); 32 | } 33 | 34 | #define BETWEEN(arg, low, high) (((arg) > (low) ) && ((arg) < (high))) 35 | 36 | TEST test_type_valid(void) 37 | { 38 | for (size_t i = 0; i < G_N_ELEMENTS(allowed_settings); i++) { 39 | gchar *error1 = g_strdup_printf("Type of setting %s is not valid: %i", allowed_settings[i].name, allowed_settings[i].type); 40 | ASSERTm(error1, BETWEEN(allowed_settings[i].type, TYPE_MIN, TYPE_MAX)); 41 | g_free(error1); 42 | } 43 | PASS(); 44 | } 45 | 46 | TEST test_section_valid(void) 47 | { 48 | for (size_t i = 0; i < G_N_ELEMENTS(allowed_settings); i++) { 49 | gchar *error1 = g_strdup_printf("Section of setting %s is null", allowed_settings[i].name); 50 | gchar *error2 = g_strdup_printf("Section of setting %s is empty", allowed_settings[i].name); 51 | ASSERTm(error1, allowed_settings[i].section); 52 | ASSERTm(error2, strlen(allowed_settings[i].section)); 53 | g_free(error1); 54 | g_free(error2); 55 | } 56 | PASS(); 57 | } 58 | 59 | TEST test_default_value_valid(void) 60 | { 61 | for (size_t i = 0; i < G_N_ELEMENTS(allowed_settings); i++) { 62 | gchar *error1 = g_strdup_printf("Default_value of setting %s is null", allowed_settings[i].name); 63 | gchar *error2 = g_strdup_printf("Default_value of setting %s is empty", allowed_settings[i].name); 64 | ASSERTm(error1, allowed_settings[i].default_value); 65 | if (allowed_settings[i].type != TYPE_STRING) 66 | ASSERTm(error2, strlen(allowed_settings[i].default_value)); 67 | g_free(error1); 68 | g_free(error2); 69 | } 70 | PASS(); 71 | } 72 | 73 | TEST test_value_non_null(void) 74 | { 75 | for (size_t i = 0; i < G_N_ELEMENTS(allowed_settings); i++) { 76 | gchar *error1 = g_strdup_printf("Error in settting %s. A setting must have a 'value' or a 'rule_offset', or both.", 77 | allowed_settings[i].name); 78 | ASSERTm(error1, allowed_settings[i].value || 79 | allowed_settings[i].rule_offset); 80 | g_free(error1); 81 | } 82 | PASS(); 83 | } 84 | 85 | TEST test_valid_parser_and_data_per_type(void) 86 | { 87 | for (size_t i = 0; i < G_N_ELEMENTS(allowed_settings); i++) { 88 | struct setting curr = allowed_settings[i]; 89 | switch (curr.type) { 90 | case TYPE_STRING: 91 | case TYPE_TIME: 92 | case TYPE_DOUBLE: 93 | case TYPE_LENGTH: 94 | case TYPE_INT: ; // no parser and no parser data needed 95 | gchar *error1 = g_strdup_printf("Parser of setting %s should be NULL. It's not needed for this type", curr.name); 96 | gchar *error2 = g_strdup_printf("Parser data of setting %s should be NULL. It's not needed for this type", curr.name); 97 | ASSERTm(error1, !curr.parser); 98 | ASSERTm(error2, !curr.parser_data); 99 | g_free(error1); 100 | g_free(error2); 101 | break; 102 | case TYPE_CUSTOM: ; // both parser data and parser are needed 103 | gchar *error3 = g_strdup_printf("Parser of setting %s should not be NULL. It's needed for this type", curr.name); 104 | gchar *error4 = g_strdup_printf("Parser data of setting %s should not be NULL. It's needed for this type", curr.name); 105 | ASSERTm(error3, curr.parser); 106 | ASSERTm(error4, curr.parser_data); 107 | g_free(error3); 108 | g_free(error4); 109 | break; 110 | case TYPE_LIST: ; // only parser data is needed 111 | gchar *error5 = g_strdup_printf("Parser of setting %s should be NULL. It's needed not for this type", curr.name); 112 | gchar *error6 = g_strdup_printf("Parser data of setting %s should not be NULL. It's needed for this type", curr.name); 113 | ASSERTm(error5, !curr.parser); 114 | ASSERTm(error6, curr.parser_data); 115 | g_free(error5); 116 | g_free(error6); 117 | break; 118 | case TYPE_PATH: ; // only parser data is neede, but when it's a rule none is needed. 119 | gchar *error7 = g_strdup_printf("Parser of setting %s should be NULL. It's needed not for this type", curr.name); 120 | gchar *error8 = g_strdup_printf("Parser data of setting %s should not be NULL. It's needed for this type", curr.name); 121 | bool is_rule = !curr.value; // if it doesn't have a 'value' it's a rule 122 | ASSERTm(error7, !curr.parser); 123 | ASSERTm(error8, is_rule || curr.parser_data); 124 | g_free(error7); 125 | g_free(error8); 126 | break; 127 | default: ; 128 | gchar *error20 = g_strdup_printf("You should make a test for type %i", curr.type); 129 | FAILm(error20); 130 | break; 131 | } 132 | } 133 | PASS(); 134 | } 135 | 136 | SUITE(suite_settings_data) 137 | { 138 | RUN_TEST(test_names_valid); 139 | RUN_TEST(test_description_valid); 140 | RUN_TEST(test_type_valid); 141 | RUN_TEST(test_section_valid); 142 | RUN_TEST(test_default_value_valid); 143 | RUN_TEST(test_value_non_null); 144 | RUN_TEST(test_valid_parser_and_data_per_type); 145 | } 146 | /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 147 | -------------------------------------------------------------------------------- /test/test-install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Throw error any time a command fails 4 | set -euo pipefail 5 | 6 | # Export parameters so they are useable by subshells and make 7 | export BASE="$(dirname "$(dirname "$(readlink -f "$0")")")" 8 | export DESTDIR="${BASE}/install" 9 | export PREFIX="/testprefix" 10 | export SYSCONFDIR="/sysconfdir" 11 | export SYSCONFFILE="${SYSCONFDIR}/dunst/dunstrc" 12 | export SYSTEMD=1 13 | export SERVICEDIR_SYSTEMD="/systemd" 14 | export SERVICEDIR_DBUS="/dbus" 15 | 16 | do_make() { # for convenience/conciseness 17 | make -C "${BASE}" "$@" 18 | } 19 | 20 | check_dest() { 21 | # Check file list given on stdin and see if all are actually present 22 | diff -u <(find "${DESTDIR}" -type f -printf "%P\n" | sort) <(sort -) 23 | } 24 | 25 | do_make install 26 | 27 | check_dest < 4 | #include 5 | #include 6 | #include 7 | 8 | #include "../src/log.h" 9 | #include "../src/settings.h" 10 | #include "helpers.h" 11 | 12 | const char *base; 13 | 14 | SUITE_EXTERN(suite_settings_data); 15 | SUITE_EXTERN(suite_utils); 16 | SUITE_EXTERN(suite_option_parser); 17 | SUITE_EXTERN(suite_notification); 18 | SUITE_EXTERN(suite_markup); 19 | SUITE_EXTERN(suite_misc); 20 | SUITE_EXTERN(suite_icon); 21 | SUITE_EXTERN(suite_queues); 22 | SUITE_EXTERN(suite_dunst); 23 | SUITE_EXTERN(suite_log); 24 | SUITE_EXTERN(suite_menu); 25 | SUITE_EXTERN(suite_dbus); 26 | SUITE_EXTERN(suite_setting); 27 | SUITE_EXTERN(suite_ini); 28 | SUITE_EXTERN(suite_icon_lookup); 29 | SUITE_EXTERN(suite_draw); 30 | SUITE_EXTERN(suite_rules); 31 | SUITE_EXTERN(suite_input); 32 | 33 | GREATEST_MAIN_DEFS(); 34 | 35 | int main(int argc, char *argv[]) { 36 | char *prog = realpath(argv[0], NULL); 37 | if (!prog) { 38 | fprintf(stderr, "Cannot determine actual path of test executable: %s\n", strerror(errno)); 39 | exit(1); 40 | } 41 | base = dirname(prog); 42 | 43 | /* By default do not print out warning messages, when executing tests. 44 | * But allow, if DUNST_TEST_LOG=1 is set in environment. */ 45 | const char *log = getenv("DUNST_TEST_LOG"); 46 | bool printlog = log && atoi(log) ? true : false; 47 | dunst_log_init(!printlog); 48 | 49 | 50 | // initialize settings 51 | char *config_path = g_strconcat(base, "/data/dunstrc.default", NULL); 52 | load_settings(config_path); 53 | 54 | GREATEST_MAIN_BEGIN(); 55 | RUN_SUITE(suite_utils); 56 | RUN_SUITE(suite_option_parser); 57 | RUN_SUITE(suite_notification); 58 | RUN_SUITE(suite_markup); 59 | RUN_SUITE(suite_misc); 60 | RUN_SUITE(suite_icon); 61 | RUN_SUITE(suite_queues); 62 | RUN_SUITE(suite_dunst); 63 | RUN_SUITE(suite_log); 64 | RUN_SUITE(suite_menu); 65 | RUN_SUITE(suite_settings_data); 66 | RUN_SUITE(suite_dbus); 67 | RUN_SUITE(suite_setting); 68 | RUN_SUITE(suite_icon_lookup); 69 | RUN_SUITE(suite_draw); 70 | RUN_SUITE(suite_rules); 71 | RUN_SUITE(suite_input); 72 | 73 | base = NULL; 74 | g_free(config_path); 75 | free(prog); 76 | 77 | // this returns the error code 78 | GREATEST_MAIN_END(); 79 | } 80 | /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 81 | -------------------------------------------------------------------------------- /test/utils.c: -------------------------------------------------------------------------------- 1 | #include "../src/utils.c" 2 | #include "greatest.h" 3 | 4 | TEST test_string_replace_char(void) 5 | { 6 | char *text = g_malloc(128 * sizeof(char)); 7 | 8 | strcpy(text, "a aa aaa"); 9 | ASSERT_STR_EQ("b bb bbb", string_replace_char('a', 'b', text)); 10 | 11 | strcpy(text, "Nothing to replace"); 12 | ASSERT_STR_EQ("Nothing to replace", string_replace_char('s', 'a', text)); 13 | 14 | strcpy(text, ""); 15 | ASSERT_STR_EQ("", string_replace_char('a', 'b', text)); 16 | 17 | g_free(text); 18 | PASS(); 19 | } 20 | 21 | /* 22 | * We trust that string_replace_all and string_replace properly reallocate 23 | * memory if the result is longer than the given string, no real way to test for 24 | * that far as I know. 25 | */ 26 | 27 | TEST test_string_replace_all(void) 28 | { 29 | char *text = g_malloc(128 * sizeof(char)); 30 | 31 | strcpy(text, "aaaaa"); 32 | ASSERT_STR_EQ("bbbbb", (text = string_replace_all("a", "b", text))); 33 | 34 | strcpy(text, ""); 35 | ASSERT_STR_EQ("", (text = string_replace_all("a", "b", text))); 36 | 37 | strcpy(text, "Nothing to replace"); 38 | ASSERT_STR_EQ((text = string_replace_all("z", "a", text)), "Nothing to replace"); 39 | 40 | strcpy(text, "Reverse this"); 41 | ASSERT_STR_EQ("Reverse sith", (text = string_replace_all("this", "sith", text))); 42 | 43 | strcpy(text, "abcdabc"); 44 | ASSERT_STR_EQ("xyzabcdxyzabc", (text = string_replace_all("a", "xyza", text))); 45 | 46 | g_free(text); 47 | PASS(); 48 | } 49 | 50 | TEST test_string_append(void) 51 | { 52 | char *exp; 53 | 54 | ASSERT_STR_EQ("text_sep_bit", (exp = string_append(g_strdup("text"), "bit", "_sep_"))); 55 | g_free(exp); 56 | ASSERT_STR_EQ("textbit", (exp = string_append(g_strdup("text"), "bit", NULL))); 57 | g_free(exp); 58 | ASSERT_STR_EQ("textbit", (exp = string_append(g_strdup("text"), "bit", ""))); 59 | g_free(exp); 60 | 61 | ASSERT_STR_EQ("text", (exp = string_append(g_strdup("text"), "", NULL))); 62 | g_free(exp); 63 | ASSERT_STR_EQ("text", (exp = string_append(g_strdup("text"), "", "_sep_"))); 64 | g_free(exp); 65 | 66 | ASSERT_STR_EQ("b", (exp = string_append(g_strdup(""), "b", NULL))); 67 | g_free(exp); 68 | ASSERT_STR_EQ("b", (exp = string_append(NULL, "b", "_sep_"))); 69 | g_free(exp); 70 | 71 | ASSERT_STR_EQ("a", (exp = string_append(g_strdup("a"), "", NULL))); 72 | g_free(exp); 73 | ASSERT_STR_EQ("a", (exp = string_append(g_strdup("a"), NULL, "_sep_"))); 74 | g_free(exp); 75 | 76 | ASSERT_STR_EQ("", (exp = string_append(g_strdup(""), "", "_sep_"))); 77 | g_free(exp); 78 | ASSERT_EQ(NULL, (exp = string_append(NULL, NULL, "_sep_"))); 79 | g_free(exp); 80 | 81 | PASS(); 82 | } 83 | 84 | TEST test_string_strip_quotes(void) 85 | { 86 | char *exp = string_strip_quotes(NULL); 87 | ASSERT_FALSE(exp); 88 | 89 | ASSERT_STR_EQ("NewString", (exp = string_strip_quotes("NewString"))); 90 | g_free(exp); 91 | 92 | ASSERT_STR_EQ("becomes unquoted", (exp = string_strip_quotes("\"becomes unquoted\""))); 93 | g_free(exp); 94 | 95 | ASSERT_STR_EQ("\"stays quoted", (exp = string_strip_quotes("\"stays quoted"))); 96 | g_free(exp); 97 | 98 | ASSERT_STR_EQ("stays quoted\"", (exp = string_strip_quotes("stays quoted\""))); 99 | g_free(exp); 100 | 101 | ASSERT_STR_EQ("stays \"quoted\"", (exp = string_strip_quotes("stays \"quoted\""))); 102 | g_free(exp); 103 | 104 | ASSERT_STR_EQ(" \"stays quoted\"", (exp = string_strip_quotes(" \"stays quoted\""))); 105 | g_free(exp); 106 | 107 | PASS(); 108 | } 109 | 110 | TEST test_string_strip_delimited(void) 111 | { 112 | char *text = g_malloc(128 * sizeof(char)); 113 | 114 | strcpy(text, "A string_strip_delimited test"); 115 | string_strip_delimited(text, '<', '>'); 116 | ASSERT_STR_EQ("A string_strip_delimited test", text); 117 | 118 | strcpy(text, "Remove html tags"); 119 | string_strip_delimited(text, '<', '>'); 120 | ASSERT_STR_EQ("Remove html tags", text); 121 | 122 | strcpy(text, ""); 123 | string_strip_delimited(text, '<', '>'); 124 | ASSERT_STR_EQ("", text); 125 | 126 | strcpy(text, "Nothing is done if there are no delimiters in the string"); 127 | string_strip_delimited(text, '<', '>'); 128 | ASSERT_STR_EQ("Nothing is done if there are no delimiters in the string", text); 129 | 130 | strcpy(text, "We <3 dunst"); 131 | string_strip_delimited(text, '<', '>'); 132 | ASSERT_STR_EQ("We <3 dunst", text); 133 | 134 | strcpy(text, "We <3 dunst"); 135 | string_strip_delimited(text, '<', '>'); 136 | ASSERT_STR_EQ("We <3 dunst", text); 137 | 138 | strcpy(text, "dunst > the rest"); 139 | string_strip_delimited(text, '<', '>'); 140 | ASSERT_STR_EQ("dunst > the rest", text); 141 | 142 | g_free(text); 143 | PASS(); 144 | } 145 | 146 | TEST test_string_to_path(void) 147 | { 148 | char *ptr, *exp; 149 | char *home = getenv("HOME"); 150 | 151 | exp = "/usr/local/bin/script"; 152 | ASSERT_STR_EQ(exp, (ptr = string_to_path(g_strdup(exp)))); 153 | free(ptr); 154 | 155 | exp = "~path/with/wrong/tilde"; 156 | ASSERT_STR_EQ(exp, (ptr = string_to_path(g_strdup(exp)))); 157 | free(ptr); 158 | 159 | ASSERT_STR_EQ((exp = g_strconcat(home, "/.path/with/tilde", NULL)), 160 | (ptr = string_to_path(g_strdup("~/.path/with/tilde")))); 161 | free(exp); 162 | free(ptr); 163 | 164 | ASSERT_STR_EQ((exp = g_strconcat(home, "/.path/with/tilde and some space", NULL)), 165 | (ptr = string_to_path(g_strdup("~/.path/with/tilde and some space")))); 166 | free(exp); 167 | free(ptr); 168 | 169 | PASS(); 170 | } 171 | 172 | TEST test_string_to_time(void) 173 | { 174 | char *input[] = { "5000 ms", "5000ms", "100", "10s", "2m", "11h", "9d", " 5 ms ", NULL }; 175 | gint64 exp[] = { 5000, 5000, 100000, 10000, 120000, 39600000, 777600000, 5, 0}; 176 | 177 | int i = 0; 178 | while (input[i]){ 179 | ASSERT_EQ_FMT(string_to_time(input[i]), exp[i]*1000, "%ld"); 180 | i++; 181 | } 182 | 183 | PASS(); 184 | } 185 | 186 | SUITE(suite_utils) 187 | { 188 | RUN_TEST(test_string_replace_char); 189 | RUN_TEST(test_string_replace_all); 190 | RUN_TEST(test_string_append); 191 | RUN_TEST(test_string_strip_quotes); 192 | RUN_TEST(test_string_strip_delimited); 193 | RUN_TEST(test_string_to_path); 194 | RUN_TEST(test_string_to_time); 195 | } 196 | /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ 197 | --------------------------------------------------------------------------------