diff -Nru dunst-1.5.0/CHANGELOG.md dunst-1.8.1/CHANGELOG.md --- dunst-1.5.0/CHANGELOG.md 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/CHANGELOG.md 2022-03-02 10:55:25.000000000 +0000 @@ -1,5 +1,208 @@ # Dunst changelog +## 1.8.1 -- 2022-03-02 + +### Fixed +- Dunst sometimes not using the right config file, sometimes falling back to the + internal defaults. + +## 1.8.0 -- 2022-02-24 + +### Added +- Implemented `progress_bar_min_width`. Before it was an unused setting. (#1006) +- `progress_bar_horizontal_alignment` for changing the alignment of the progress + bar. (#1021) +- Support for config drop-ins. You can add as many configuration files as you + want in `dunstrc.d`. See the man page dunst(1) for more information. This was + done with help from @WhitePeter. (#997) +- Thanks to @m-barlett you can place your icons at the center of your + notifications with `icon_position = top`. +- `icon_position` is now a rule (also by @m-barlett). +- `hide_text` for hiding all text of a notification. This also removes all + padding that would be present for a notification without text. (also by + @m-barlett) (#985) +- The previously removed keyboard shortcuts have been added again, but now they + are in the `[global]` section of the config. Not everything that was possible + with the keyboard shortcuts was possible with dunstctl on X11. Mainly + activating a keyboard shortcut only when notifications are on screen. Thanks + to @wgmayer0 for testing. (#1033). + +### Changed +- Improved the man page regarding transitioning from the old geometry. +- The default alignment of the progress bar is now center instead of left. +- Better regex matching for rules. When you set `enable_posix_regex`. Take a + look at + https://en.m.wikibooks.org/wiki/Regular_Expressions/POSIX-Extended_Regular_Expressions + for how the new regex syntax works. Note that you cannot do inverse matching + yet, I'm working on that in #1040. (#1017) +- Thanks to @kurogetsusai you can once again use negative offsets to put a + notification window slightly off-screen if you so like. (#1027) +- As mentioned above, the keyboard shortcuts have been moved to the `[global]` + section. Please move your settings there. + + +### Fixed +- Crash when `open_url` was used without URL's. (#1000) +- Icons sometimes being incorrectly sized with the new icon lookup. (#1003) +- Incorrect defaults mentioned in the documentation. (#1004, #1029 and more) +- Crash when icon could not be read by glib. (#1023) +- Not being able to override anymore raw icons with `new_icon` (#1009) +- High cpu usage when selecting an action in dmenu or similar. This was caused + by dunst not going to sleep when waiting for a response. (#898) +- Updated default values documentation (with help from @profpatch) (#1004 and + more) + + +## 1.7.3 -- 2021-12-08 + +### Added +### Changed +- `follow` is now `none` again by default. This was the case before v1.7.0 as well. (#990). + +### Fixed +- `dunstctl action` is now working again. +- Segfault in experimental icon lookup when an inherited theme doesn't exist. +- `icon_position = off` not being respected (#996). + +## 1.7.2 -- 2021-11-30 + +### Added +- Experimental recursive icon lookup. This is not enabled by default and can be + enabled by setting `enable_recursive_icon_lookup=true`. Setting icon sizes + still doesn't work entirely as it's supposed to and will be improved in future + releases. (#965) +- You can now enable or disable rules on the fly with `dunstctl rule $name$ + enable/disable`. (#981) +- `dunstctl history` lists your notification history in JSON format for + processing by scripts. (#970) +- You can now pop specific notifications from history by passing a notification + ID to `dunstctl history-pop`. (#970) +- `default_icon` setting for setting the icon when no icons are given (#984) +- Implemented display size detection in Wayland. (#973) +### Changed +### Fixed +- Text being cut off on X11 when using fractional scaling. (#975) +- Incorrect hitbox for notification on X11 with scaling. (#980) +- Improved warning messages for deprecated sections. (#974) +- `icon` being interpreted as a filter and not being allowed in the special + urgency sections. This is a compatibility fix, but it's recommended to replace + all usages of `icon` in these sections with `default_icon` to prevent + confusion with the `icon` rule in other sections. (#984) +- `new_icon` being used in the default dunstrc where `default_icon` is the + intended settings. This was commented by default, so it doesn't affect any + default behaviour. (#984) +- Notifications bleeding to other screens when the width was big enough. Now the + notification's width is lowered when it would otherwise leave the display. + + +## 1.7.1 -- 2021-11-01 + +### Added +- Script environment variable `DUNST_DESKTOP_ENTRY`. (#874) +- Rule `set_category` for change a notifications category with rules. (1b72b2a) + +### Fixed +- Dunst not building with WAYLAND=0. (#938) +- Wrong icon being shown in chromium-based browsers. (#939) +- `set_stack_tag` not working anymore. (#942) +- Outdated documentation. (#943, #944 and more) +- Empty strings not being allowed in settings. (#946) +- Dunst crashing when compositor doesn't support `zwlr_foreign_toplevel_v1`. (#948) +- Xmore notifications showing a progress bar and icon. (#915) +- Markup is now a rule again. Before this was undocumented behaviour. (#955) +- Double free when setting `XDG_CONFIG_DIR`. (#957) +- Dunst crashing on some compositors. (#948) +- Dunst not exiting when wayland compositor quits. (#961) +- Now the separators are not responsive to mouse clicks anymore. (#960) +- Mouse action stopping the rest of the actions. (bf58928) + +## 1.7.0 -- 2021-10-19: + +### Added +- `context` and `context_all` mouse actions for opening the context menu (#848) +- `open_url` mouse action for opening url's in a notification (#848) +- `action_name` rule for setting a default action to perform when using + `do_action` (#848) +- HiDPI support for both Wayland and X11. On wayland the scale can be set from + your compositor's settings and is automatically picked up by dunst. On X11 + dunst will guess the scale based on the DPI of the screen. If that isn't good, + you can set the `scale` variable in the settings. (#854 and #890) +- `highlight` can now also be set through dbus hints with the key `hlcolor` + (#862) +- Your dunstrc is now being checked by dunst. Dunst will print a warning when + coming across an non-existing/invalid setting. (#803) +- Wayland fullscreen detection (#814) +- Wayland touch support (#814) +- Cursor is now being changed to `left_ptr` when hovering over dunst (Wayland) + (#903) + +### Changed +- `startup_notification` and `verbosity` are now only available as a command + line arguments. (#803) +- Rule settings can now also be used in the `[global]` section. They will then + apply to all the notifications. (#803) +- `fullscreen`, `ellpsize` and `word_wrap` are now rules. They can still be used + in the `[global]` section as well (see above). (#937 and #803) +- The appid's now also need to match when stacking notifications. (#886) +- `xdg-open` is now being used by default for opening URL's. (#889) +- `geometry` and `notification_height` have been replaced by `origin`, `width`, + `height`, `offset` and `notification_limit`. This allows for more flexible + geometry settings. (#855) +- There were a bunch of changes in the installation and default locations. See + the release notes for more information. +- Upon seeing invalid markup, dunst is a bit smarter in stripping the markup. + +### Fixed +- Lots of debug messages when `idle_timeout=0` (#814) +- `follow=none` not working on Wayland (#814) +- Incorrect sorting when `sort` is false +- NULL pointer dereference on Wayland +- Dunst not redrawing after `close_all` action. +- Dunst not announcing icon-static capability over dbus (#867) +- Dunst not falling back to X11 output when it can't initialize the Wayland + output. (#834) +- Improve stability on Wayland. (#930 and more) + +### Removed +- The `[shortcuts]` section with all it's settings. Use your WM/DE's shortcut + manager and `dunstctl` to replace it. (#803) +- Setting settings via command line arguments. (#803) +- Setting settings via `config.h`. (#803) + +## 1.6.1 - 2021-02-21: + +### Fixed +- Incorrect version in Makefile + +## 1.6.0 - 2021-02-21: + +### Added +- Wayland support. Dunst now runs natively on wayland. This fixes several bugs + with dunst on wayland and allows idle detection. (#781) +- A progress bar, useful for showing volume or brightness in notifications (#775) +- A script in contrib for using the progress bar (#791) +- `dunstctl count` for showing the number of notifications (#793) +- Expose environment variables info about the notification to scripts (#802) +- `text_icon_padding` for adding padding between the notification icon and text + (#810) + +### Changed +- Dunst now installs a system-wide config in `/etc/dunst/dunstrc` (#798) +- Move part of the man page to dunst(5) (#799) + +### Fixed +- `history_ignore` flag broken when using multiple rules (#747) +- Divide by zero in radius calculation (#750) +- Monitor setting overriding `follow_mode` (#755) +- Incorrect monitor usage when using multiple X11 screens (#762) +- Emit signal when `paused` property changes (#766) +- `dunstify` can pass empty appname to libnotify (#768) +- Incorrect handling of 'do_action, close' mouse action (#778) + +# Removed + +- `DUNST_COMMAND_{PAUSE,RESUME,TOGGLE}` (#830) + ## 1.5.0 - 2020-07-23 ### Added diff -Nru dunst-1.5.0/config.h dunst-1.8.1/config.h --- dunst-1.5.0/config.h 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/config.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,144 +0,0 @@ -/* see example dunstrc for additional explanations about these options */ - -struct settings defaults = { - -.font = "-*-terminus-medium-r-*-*-16-*-*-*-*-*-*-*", -.markup = MARKUP_NO, -.colors_norm.bg = "#1793D1", -.colors_norm.fg = "#DDDDDD", -.colors_crit.bg = "#ffaaaa", -.colors_crit.fg = "#000000", -.colors_low.bg = "#aaaaff", -.colors_low.fg = "#000000", -.format = "%s %b", /* default format */ - -.timeouts = { S2US(10), S2US(10), S2US(0) }, /* low, normal, critical */ -.icons = { "dialog-information", "dialog-information", "dialog-warning" }, /* low, normal, critical */ - -.transparency = 0, /* transparency */ -.geometry = { .x = 0, /* geometry */ - .y = 0, - .w = 0, - .h = 0, - .negative_x = 0, - .negative_y = 0, - .negative_width = 0, - .width_set = 0 - }, -.title = "Dunst", /* the title of dunst notification windows */ -.class = "Dunst", /* the class of dunst notification windows */ -.shrink = false, /* shrinking */ -.sort = true, /* sort messages by urgency */ -.indicate_hidden = true, /* show count of hidden messages */ -.idle_threshold = 0, /* don't timeout notifications when idle for x seconds */ -.show_age_threshold = -1, /* show age of notification, when notification is older than x seconds */ -.align = ALIGN_LEFT, /* text alignment ALIGN_[LEFT|CENTER|RIGHT] */ -.vertical_alignment = VERTICAL_CENTER, /* vertical content alignment VERTICAL_[TOP|CENTER|BOTTOM] */ -.sticky_history = true, -.history_length = 20, /* max amount of notifications kept in history */ -.show_indicators = true, -.word_wrap = false, -.ignore_dbusclose = false, -.ellipsize = ELLIPSE_MIDDLE, -.ignore_newline = false, -.line_height = 0, /* if line height < font height, it will be raised to font height */ -.notification_height = 0, /* if notification height < font height and padding, it will be raised */ -.corner_radius = 0, - -.separator_height = 2, /* height of the separator line between two notifications */ -.padding = 0, -.h_padding = 0, /* horizontal padding */ -.sep_color = {SEP_AUTO}, /* SEP_AUTO, SEP_FOREGROUND, SEP_FRAME, SEP_CUSTOM */ - -.frame_width = 0, -.frame_color = "#888888", - -/* show a notification on startup - * This is mainly for crash detection since dbus restarts dunst - * automatically after a crash, so crashes might get unnotices otherwise - * */ -.startup_notification = false, - -/* monitor to display notifications on */ -.monitor = 0, - -/* path to dmenu */ -.dmenu = "/usr/bin/dmenu", - -.browser = "/usr/bin/firefox", - -.min_icon_size = 0, -.max_icon_size = 0, - -/* paths to default icons */ -.icon_path = "/usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/", - - -/* follow focus to different monitor and display notifications there? - * possible values: - * FOLLOW_NONE - * FOLLOW_MOUSE - * FOLLOW_KEYBOARD - * - * everything else than FOLLOW_NONE overrides 'monitor' - */ -.f_mode = FOLLOW_NONE, - -/* keyboard shortcuts - * use for example "ctrl+shift+space" - * use "none" to disable - */ -.close_ks = {.str = "none", - .code = 0,.sym = NoSymbol,.is_valid = false -}, /* ignore this */ - -.close_all_ks = {.str = "none", - .code = 0,.sym = NoSymbol,.is_valid = false -}, /* ignore this */ - -.history_ks = {.str = "none", - .code = 0,.sym = NoSymbol,.is_valid = false -}, /* ignore this */ - -.context_ks = {.str = "none", - .code = 0,.sym = NoSymbol,.is_valid = false -}, /* ignore this */ - -.mouse_left_click = (enum mouse_action []){MOUSE_CLOSE_CURRENT, -1}, - -.mouse_middle_click = (enum mouse_action []){MOUSE_DO_ACTION, -1}, - -.mouse_right_click = (enum mouse_action []){MOUSE_CLOSE_ALL, -1}, - -}; - -struct rule default_rules[] = { - /* name can be any unique string. It is used to identify - * the rule in dunstrc to override it there - */ - - /* an empty rule with no effect */ - { - .name = "empty", - .appname = NULL, - .summary = NULL, - .body = NULL, - .icon = NULL, - .category = NULL, - .msg_urgency = -1, - .timeout = -1, - .urgency = -1, - .markup = MARKUP_NULL, - .history_ignore = -1, - .match_transient = -1, - .set_transient = -1, - .skip_display = -1, - .new_icon = NULL, - .fg = NULL, - .bg = NULL, - .format = NULL, - .script = NULL, - } -}; - -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/config.mk dunst-1.8.1/config.mk --- dunst-1.5.0/config.mk 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/config.mk 2022-03-02 10:55:25.000000000 +0000 @@ -1,10 +1,15 @@ # paths PREFIX ?= /usr/local BINDIR ?= ${PREFIX}/bin +SYSCONFDIR ?= ${PREFIX}/etc/xdg +SYSCONFFILE ?= ${SYSCONFDIR}/dunst/dunstrc DATADIR ?= ${PREFIX}/share # around for backwards compatibility MANPREFIX ?= ${DATADIR}/man MANDIR ?= ${MANPREFIX} +SERVICEDIR_DBUS ?= ${DATADIR}/dbus-1/services +SERVICEDIR_SYSTEMD ?= ${PREFIX}/lib/systemd/user +EXTRA_CFLAGS ?= DOXYGEN ?= doxygen FIND ?= find @@ -13,21 +18,26 @@ PKG_CONFIG ?= pkg-config POD2MAN ?= pod2man SED ?= sed -SYSTEMCTL ?= systemctl +SYSTEMDAEMON ?= systemd VALGRIND ?= valgrind # Disable systemd service file installation, # if you don't want to use systemd albeit installed #SYSTEMD ?= 0 -# uncomment to disable parsing of dunstrc -# or use "CFLAGS=-DSTATIC_CONFIG make" to build -#STATIC= -DSTATIC_CONFIG # Warning: This is deprecated behavior +# Disable dependency on wayland. This will force dunst to use +# xwayland on wayland compositors +# You can also use "make WAYLAND=0" to build without wayland +# WAYLAND ?= 0 + +ifneq (0, ${WAYLAND}) +ENABLE_WAYLAND= -DENABLE_WAYLAND +endif # flags -DEFAULT_CPPFLAGS = -D_DEFAULT_SOURCE -DVERSION=\"${VERSION}\" -DEFAULT_CFLAGS = -g --std=gnu99 -pedantic -Wall -Wno-overlength-strings -Os ${STATIC} -DEFAULT_LDFLAGS = -lm +DEFAULT_CPPFLAGS = -Wno-gnu-zero-variadic-macro-arguments -D_DEFAULT_SOURCE -DVERSION=\"${VERSION}\" -DSYSCONFDIR=\"${SYSCONFDIR}\" +DEFAULT_CFLAGS = -g -std=gnu99 -pedantic -Wall -Wno-overlength-strings -Os ${ENABLE_WAYLAND} ${EXTRA_CFLAGS} +DEFAULT_LDFLAGS = -lm -lrt CPPFLAGS_DEBUG := -DDEBUG_BUILD CFLAGS_DEBUG := -O0 @@ -41,11 +51,13 @@ xinerama \ xext \ "xrandr >= 1.5" \ - xscrnsaver + xscrnsaver \ + # dunstify also needs libnotify pkg_config_packs += libnotify -ifneq (,$(findstring STATIC_CONFIG,$(CFLAGS))) -$(warning STATIC_CONFIG is deprecated behavior. It will get removed in future releases) +ifneq (0,${WAYLAND}) +pkg_config_packs += wayland-client +pkg_config_packs += wayland-cursor endif diff -Nru dunst-1.5.0/contrib/notification-history.sh dunst-1.8.1/contrib/notification-history.sh --- dunst-1.5.0/contrib/notification-history.sh 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/contrib/notification-history.sh 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,47 @@ +#!/bin/bash + +history_json=$(dunstctl history) + +history_length=$(echo $history_json|jq -r '.data[] | length') + +options="" + +for iter in $(seq $history_length); do + i=$((iter-1)) + application_name=$(echo $history_json|jq -r .data[][$i].appname.data) + notification_summary=$(echo $history_json|jq -r .data[][$i].summary.data) + notification_timestamp=$(echo $history_json|jq -r .data[][$i].timestamp.data) + system_timestamp=$(cat /proc/uptime|cut -d'.' -f1) + + how_long_ago=$((system_timestamp-notification_timestamp/1000000)) + + notification_time=$(date +%X -d "$(date) - $how_long_ago seconds") + + option=$(printf '%04d - %s: "%s" (at %s)' "$iter" "$application_name" \ + "$notification_summary" "$notification_time") + + options="$options$option\n" +done +options="$options""Cancel" + +result=$(echo -e $options|rofi -dmenu -i) +if [ "$result" = "Cancel" ]; then + # Exit if cancelled + exit 0 +fi +if [ "$result" = "" ]; then + # Exit on empty strings + exit 0 +fi +if [ "$?" -ne 0 ]; then + # Exit on non-zero return values + exit 0 +fi + +# Get the internal notification ID +selection_index=$((${result:0:4}-1)) +notification_id=$(echo $history_json|jq -r .data[][$selection_index].id.data) + +# Tell dunst to revive said notification +dunstctl history-pop $notification_id + diff -Nru dunst-1.5.0/contrib/progress-notify.sh dunst-1.8.1/contrib/progress-notify.sh --- dunst-1.5.0/contrib/progress-notify.sh 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/contrib/progress-notify.sh 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,75 @@ +#!/usr/bin/env sh + +# progress-notify - Send audio and brightness notifications for dunst + +# dependencies: dunstify, ponymix, Papirus (icons) + +### How to use: ### +# Pass the values via stdin and provide the notification type +# as an argument. Options are audio, brightness and muted + +### Audio notifications ### +# ponymix increase 5 | notify audio +# ponymix decrease 5 | notify audio +# pulsemixer --toggle-mute --get-mute | notify muted +### Brightness notifications ### +# xbacklight -inc 5 && xbacklight -get | notify brightness +# xbacklight -dec 5 && xbacklight -get | notify brightness + +notifyMuted() { + volume="$1" + dunstify -h string:x-canonical-private-synchronous:audio "Muted" -h int:value:"$volume" -t 1500 --icon audio-volume-muted +} + +notifyAudio() { + volume="$1" + ponymix is-muted && notifyMuted "$volume" && return + + if [ $volume -eq 0 ]; then + notifyMuted "$volume" + elif [ $volume -le 30 ]; then + dunstify -h string:x-canonical-private-synchronous:audio "Volume: " -h int:value:"$volume" -t 1500 --icon audio-volume-low + elif [ $volume -le 70 ]; then + dunstify -h string:x-canonical-private-synchronous:audio "Volume: " -h int:value:"$volume" -t 1500 --icon audio-volume-medium + else + dunstify -h string:x-canonical-private-synchronous:audio "Volume: " -h int:value:"$volume" -t 1500 --icon audio-volume-high + fi +} + +notifyBrightness() { + brightness="$1" + if [ $brightness -eq 0 ]; then + dunstify -h string:x-canonical-private-synchronous:brightness "Brightness: " -h int:value:"$brightness" -t 1500 --icon display-brightness-off-symbolic + elif [ $brightness -le 30 ]; then + dunstify -h string:x-canonical-private-synchronous:brightness "Brightness: " -h int:value:"$brightness" -t 1500 --icon display-brightness-low-symbolic + elif [ $brightness -le 70 ]; then + dunstify -h string:x-canonical-private-synchronous:brightness "Brightness: " -h int:value:"$brightness" -t 1500 --icon display-brightness-medium-symbolic + else + dunstify -h string:x-canonical-private-synchronous:brightness "Brightness: " -h int:value:"$brightness" -t 1500 --icon display-brightness-high-symbolic + fi +} + +input=`cat /dev/stdin` + +case "$1" in + muted) + volume=`ponymix get-volume` + if [ "$input" -eq 0 ] + then + notifyAudio "$volume" + else + notifyMuted "$volume" + fi + ;; + audio) + notifyAudio "$input" + ;; + brightness) + notifyBrightness "$input" + ;; + + *) + echo "Not the right arguments" + echo "$1" + exit 2 +esac Binary files /tmp/tmp_0le5vzy/5ow2W6Tzyb/dunst-1.5.0/contrib/screenshots/default_config.png and /tmp/tmp_0le5vzy/1UuqYpLmbr/dunst-1.8.1/contrib/screenshots/default_config.png differ Binary files /tmp/tmp_0le5vzy/5ow2W6Tzyb/dunst-1.5.0/contrib/screenshots/music.png and /tmp/tmp_0le5vzy/1UuqYpLmbr/dunst-1.8.1/contrib/screenshots/music.png differ Binary files /tmp/tmp_0le5vzy/5ow2W6Tzyb/dunst-1.5.0/contrib/screenshots/screenshot1_cut.png and /tmp/tmp_0le5vzy/1UuqYpLmbr/dunst-1.8.1/contrib/screenshots/screenshot1_cut.png differ Binary files /tmp/tmp_0le5vzy/5ow2W6Tzyb/dunst-1.5.0/contrib/screenshots/screenshot1.png and /tmp/tmp_0le5vzy/1UuqYpLmbr/dunst-1.8.1/contrib/screenshots/screenshot1.png differ Binary files /tmp/tmp_0le5vzy/5ow2W6Tzyb/dunst-1.5.0/contrib/screenshots/screenshot2_cut.png and /tmp/tmp_0le5vzy/1UuqYpLmbr/dunst-1.8.1/contrib/screenshots/screenshot2_cut.png differ Binary files /tmp/tmp_0le5vzy/5ow2W6Tzyb/dunst-1.5.0/contrib/screenshots/screenshot2.png and /tmp/tmp_0le5vzy/1UuqYpLmbr/dunst-1.8.1/contrib/screenshots/screenshot2.png differ Binary files /tmp/tmp_0le5vzy/5ow2W6Tzyb/dunst-1.5.0/contrib/screenshots/screenshot3_cut.png and /tmp/tmp_0le5vzy/1UuqYpLmbr/dunst-1.8.1/contrib/screenshots/screenshot3_cut.png differ Binary files /tmp/tmp_0le5vzy/5ow2W6Tzyb/dunst-1.5.0/contrib/screenshots/screenshot3.png and /tmp/tmp_0le5vzy/1UuqYpLmbr/dunst-1.8.1/contrib/screenshots/screenshot3.png differ Binary files /tmp/tmp_0le5vzy/5ow2W6Tzyb/dunst-1.5.0/contrib/screenshots/screenshot_urgency.png and /tmp/tmp_0le5vzy/1UuqYpLmbr/dunst-1.8.1/contrib/screenshots/screenshot_urgency.png differ diff -Nru dunst-1.5.0/debian/changelog dunst-1.8.1/debian/changelog --- dunst-1.5.0/debian/changelog 2022-03-10 11:58:37.000000000 +0000 +++ dunst-1.8.1/debian/changelog 2022-06-11 08:42:31.000000000 +0000 @@ -1,8 +1,24 @@ -dunst (1.5.0-1build1) jammy; urgency=medium +dunst (1.8.1-1) unstable; urgency=medium - * No-change rebuild against latest gdk-pixbuf + [ Debian Janitor ] + * Set upstream metadata fields: Bug-Database, Bug-Submit, Repository, + Repository-Browse. + * Update standards version to 4.4.1, no changes needed. - -- Jeremy Bicha Thu, 10 Mar 2022 06:58:37 -0500 + [ Nikos Tsipinakis ] + * New upstream version 1.8.1 + * d/patches: Drop all patches, not needed + * d/control: Use correct sysconfdir + * Bump standards to 4.6.1, no changes + * d/control: Add wayland dependency + + [ Boyuan Yang ] + * d/control: Mark buid-dep systemd as [linux-any]. + * d/control: Apply "wrap-and-sort -abst". + * d/control: Drop recommendation on sensible-utils, no longer used. + * d/control: Add dependency on xdg-utils, used by upstream. + + -- Nikos Tsipinakis Sat, 11 Jun 2022 10:42:31 +0200 dunst (1.5.0-1) unstable; urgency=medium diff -Nru dunst-1.5.0/debian/control dunst-1.8.1/debian/control --- dunst-1.5.0/debian/control 2020-09-24 10:16:50.000000000 +0000 +++ dunst-1.8.1/debian/control 2022-06-11 08:42:31.000000000 +0000 @@ -2,22 +2,23 @@ Section: x11 Priority: optional Maintainer: Nikos Tsipinakis -Build-Depends: debhelper-compat (= 13), - libdbus-1-dev, - libx11-dev, - libxinerama-dev, - libxss-dev, - libgdk-pixbuf2.0-dev, - libglib2.0-dev, - libpango1.0-dev, - libcairo2-dev, - libnotify-dev, - libxrandr-dev, - dpkg-dev (>= 1.16.1.1), - systemd, - default-dbus-session-bus | dbus-session-bus, - librsvg2-common -Standards-Version: 4.5.0 +Build-Depends: + debhelper-compat (= 13), + default-dbus-session-bus | dbus-session-bus, + libcairo2-dev, + libdbus-1-dev, + libgdk-pixbuf2.0-dev, + libglib2.0-dev, + libnotify-dev, + libpango1.0-dev, + librsvg2-common, + libwayland-dev, + libx11-dev, + libxinerama-dev, + libxrandr-dev, + libxss-dev, + systemd [linux-any], +Standards-Version: 4.6.1 Homepage: https://dunst-project.org/ Vcs-Git: https://salsa.debian.org/debian/dunst.git Vcs-Browser: https://salsa.debian.org/debian/dunst @@ -25,9 +26,13 @@ Package: dunst Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, default-dbus-session-bus | dbus-session-bus -Recommends: sensible-utils -Provides: notification-daemon +Depends: + default-dbus-session-bus | dbus-session-bus, + xdg-utils, + ${misc:Depends}, + ${shlibs:Depends}, +Provides: + notification-daemon, Description: dmenu-ish notification-daemon Dunst is a highly configurable and lightweight notification-daemon: The only thing it displays is a colored box with unformatted text. The whole diff -Nru dunst-1.5.0/debian/patches/default_browser.patch dunst-1.8.1/debian/patches/default_browser.patch --- dunst-1.5.0/debian/patches/default_browser.patch 2020-09-24 09:39:20.000000000 +0000 +++ dunst-1.8.1/debian/patches/default_browser.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -Author: Francois Marier -Last-Update: 2019-05-23 -Bug-Debian: https://bugs.debian.org/929456 -Description: Honour the default browser set by the user. - ---- a/dunstrc -+++ b/dunstrc -@@ -192,7 +192,7 @@ - dmenu = /usr/bin/dmenu -p dunst: - - # Browser for opening urls in context menu. -- browser = /usr/bin/firefox -new-tab -+ browser = /usr/bin/sensible-browser - - # Always run rule-defined scripts, even if the notification is suppressed - always_run_script = true diff -Nru dunst-1.5.0/debian/patches/example_path.patch dunst-1.8.1/debian/patches/example_path.patch --- dunst-1.5.0/debian/patches/example_path.patch 2020-09-24 09:39:20.000000000 +0000 +++ dunst-1.8.1/debian/patches/example_path.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,28 +0,0 @@ -Description: Place example in /usr/share/doc/dunst/ -Author: Michael Stapelberg -Forwarded: not-needed - ---- - ---- a/Makefile -+++ b/Makefile -@@ -173,7 +173,7 @@ - install -Dm755 dunstctl ${DESTDIR}${BINDIR}/dunstctl - - install-doc: -- install -Dm644 dunstrc ${DESTDIR}${DATADIR}/dunst/dunstrc -+ install -Dm644 dunstrc ${DESTDIR}${DATADIR}/doc/dunst/dunstrc - - install-service: install-service-dbus - install-service-dbus: service-dbus ---- a/docs/dunst.pod -+++ b/docs/dunst.pod -@@ -35,7 +35,7 @@ - - =head1 CONFIGURATION - --An example configuration file is included (usually /usr/share/dunst/dunstrc). -+An example configuration file is included (usually /usr/share/doc/dunst/dunstrc.gz). - To change the configuration, copy this file to ~/.config/dunst/dunstrc and edit - it accordingly. - diff -Nru dunst-1.5.0/debian/patches/fix-typos.patch dunst-1.8.1/debian/patches/fix-typos.patch --- dunst-1.5.0/debian/patches/fix-typos.patch 2020-09-24 09:39:20.000000000 +0000 +++ dunst-1.8.1/debian/patches/fix-typos.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ ---- a/dunstify.c -+++ b/dunstify.c -@@ -234,7 +234,7 @@ - char *label = strchr(str, ','); - - if (!label || *(label+1) == '\0') { -- g_printerr("Malformed action. Excpected \"action,label\", got \"%s\"", str); -+ g_printerr("Malformed action. Expected \"action,label\", got \"%s\"", str); - return; - } - diff -Nru dunst-1.5.0/debian/patches/series dunst-1.8.1/debian/patches/series --- dunst-1.5.0/debian/patches/series 2020-09-24 09:39:20.000000000 +0000 +++ dunst-1.8.1/debian/patches/series 2022-06-11 08:42:31.000000000 +0000 @@ -1,3 +0,0 @@ -example_path.patch -default_browser.patch -fix-typos.patch diff -Nru dunst-1.5.0/debian/rules dunst-1.8.1/debian/rules --- dunst-1.5.0/debian/rules 2020-10-26 10:52:04.000000000 +0000 +++ dunst-1.8.1/debian/rules 2022-06-11 08:42:31.000000000 +0000 @@ -7,11 +7,8 @@ export PKG_CONFIG ?= pkg-config %: - dh $@ - -override_dh_auto_install: - dh_auto_install -- PREFIX=/usr + SYSCONFDIR="/etc/xdg" PREFIX=/usr dh $@ override_dh_installdocs: - install -m644 RELEASE_NOTES debian/dunst/usr/share/doc/dunst/NEWS + install -D -m644 RELEASE_NOTES debian/dunst/usr/share/doc/dunst/NEWS dh_installdocs diff -Nru dunst-1.5.0/debian/upstream/metadata dunst-1.8.1/debian/upstream/metadata --- dunst-1.5.0/debian/upstream/metadata 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/debian/upstream/metadata 2022-06-11 08:42:31.000000000 +0000 @@ -0,0 +1,4 @@ +Bug-Database: https://github.com/dunst-project/dunst/issues +Bug-Submit: https://github.com/dunst-project/dunst/issues/new +Repository: https://github.com/dunst-project/dunst.git +Repository-Browse: https://github.com/dunst-project/dunst diff -Nru dunst-1.5.0/docs/dunst.1.pod dunst-1.8.1/docs/dunst.1.pod --- dunst-1.5.0/docs/dunst.1.pod 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/docs/dunst.1.pod 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,167 @@ +=head1 NAME + +dunst - A customizable and lightweight notification-daemon + +=head1 SYNOPSIS + +dunst [-conf file] [-verbosity v] [-print] [--startup-notification] + +=head1 DESCRIPTION + +Dunst is a highly configurable and lightweight notification daemon. + +=head2 Autostarting dunst + +On most installations dunst should be able to automatically be started by D-Bus +when a notification is sent. This is not recommended when multiple notification +deamons are installed, because D-Bus will not know which one to start. +Other ways of autostarting dunst include starting dunst with your desktop +environment or window manager's autostart functionality or via the provided +systemd service. + +=head1 COMMAND LINE OPTIONS + +=over 4 + +=item B<-h/--help> + +List all command line flags + +=item B<-conf/-config file> + +Use alternative config file. +This disables the search for other config files. +If it cannot be opened Dunst will issue a warning and fall back on its internal +defaults. +(Hint: `dunst -conf - + +Print version information. + +=item B<-verbosity> (values: 'crit', 'warn', 'mesg', 'info', 'debug' default 'mesg') + +Do not display log messages, which have lower precedence than specified +verbosity. This won't affect printing notifications on the terminal. Use +the '-print' option for this. + +=item B<-print> + +Print notifications to stdout. This might be useful for logging, setting up +rules or using the output in other scripts. + +=item B<--startup_notification> (values: [true/false], default: false) + +Display a notification on startup. + +=back + +=head1 CONFIGURATION + +A default configuration file is included (usually ##SYSCONFDIR##/dunst/dunstrc) +and serves as the least important configuration file. Note: this was previously +/usr/share/dunst/dunstrc. You can edit this file to change the system-wide +defaults or copy it to a more important location to override its settings. See +the FILES section for more details on where dunst searches for its +configuration files and how settings get applied. + +See dunst(5) for all possible settings. + +=head2 NOTIFY-SEND + +dunst is able to get different colors for a message via notify-send. +In order to do that you have to add a hint via the -h option. +The progress value can be set with a hint, too. + +=over 4 + +=item notify-send -h string:fgcolor:#ff4444 + +=item notify-send -h string:bgcolor:#4444ff -h string:fgcolor:#ff4444 -h string:frcolor:#44ff44 + +=item notify-send -h int:value:42 "Working ..." + +=back + +=head1 MISCELLANEOUS + +Dunst can be paused via the `dunstctl set-paused true` command. To unpause dunst use +`dunstctl set-paused false`. +Another way is to send SIGUSR1 and SIGUSR2 to pause and unpause +respectively. Pausing using dunstctl is recommended over using signals, because +the meaning of the signals is not be stable and might change in the future. + +When paused dunst will not display any notifications but keep all notifications +in a queue. This can for example be wrapped around a screen locker (i3lock, +slock) to prevent flickering of notifications through the lock and to read all +missed notifications after returning to the computer. + +=head1 FILES + +These are the base directories dunst searches for configuration files in +I: + +=over 8 + +=item C<$XDG_CONFIG_HOME> + +This is the most important directory. (C<$HOME/.config> if unset or empty) + +=item C<$XDG_CONFIG_DIRS> + +This, like C<$PATH> for instance, is a :-separated list of base directories +in I. +(F<##SYSCONFDIR##> if unset or empty) + +=back + +Dunst will search these directories for the following relative file paths: + +=over 8 + +=item F + +This is the base config and as such the least important in a particular base +directory. + +=item F + +These are "drop-ins" (mind the ".d" suffix of the directory). +They are more important than the base dunstrc in the parent directory, as they +are considered to be small snippets to override settings. +The last in lexical order is the most important one, so you can easily change +the order by renaming them. +A common approach to naming drop-ins is to prefix them with numbers, i.e.: + + 00-least-important.conf + 01-foo.conf + 20-bar.conf + 99-most-important.conf + +Only files with the B<.conf> suffix will be read. + +=back + +Only settings from the last base config the corresponding drop-ins get applied. +So if a dunstrc is first found in F<~/.config/dunst/dunstrc>, drop-ins will be +searched in F<~/.config/dunst/dunstrc.d/*>. Settings in more important files +override those in less important ones. + +=head1 AUTHORS + +Written by Sascha Kruse + +=head1 REPORTING BUGS + +Bugs and suggestions should be reported on GitHub at https://github.com/dunst-project/dunst/issues + +=head1 COPYRIGHT + +Copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) + +If you feel that copyrights are violated, please send me an email. + +=head1 SEE ALSO + +dunst(5), dunstctl(1), dmenu(1), notify-send(1) diff -Nru dunst-1.5.0/docs/dunst.5.pod dunst-1.8.1/docs/dunst.5.pod --- dunst-1.5.0/docs/dunst.5.pod 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/docs/dunst.5.pod 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,1161 @@ +=head1 NAME + +dunst - configuration file + +=head1 DESCRIPTION + +The configuration is divided into sections in an ini-like format. Every section +start with the section's name in square brackets. After that is a list of +key-value pairs that specify the settings. Whitespace is purely cosmetic and +doesn't matter for the result. + +The 'global' section contains the general settings applying to all of dunst. The +rest of the settings can be specified via rules and can be located in any +section. These rules can change a notification based on it's properties. There +are filtering rules and modifying rules. The filtering rules specify on what +notifications the rule is applied and the modifying rules specify what is +changed about the matching notifications. Some special sections have implied +filters that cannot be changed. The "global" section, for example has no +filters, thus applies to all notifications. + +See RULES for more details. + +All experimental settings are marked with I + +=head2 Global section + +=over 4 + +=item B (default: 0) + +Specifies on which monitor the notifications should be displayed in, count +starts at 0. See the B setting. + +=item B (values: [none/mouse/keyboard] default: none) + +Defines where the notifications should be placed in a multi-monitor setup. All +values except I override the B setting. + +On Wayland there is no difference between mouse and keyboard focus. When either +of the is used, the compositor will choose an output. This will generally be +the output last interacted with. + +=over 4 + +=item B + +The notifications will be placed on the monitor specified by the B +setting. + +=item B + +The notifications will be placed on the monitor that the mouse is currently in. + +=item B + +The notifications will be placed on the monitor that contains the window with +keyboard focus. + +=back + +=item B (default: false) + +When set to true (recommended), you can use POSIX regular expressions for +filtering rules. It uses the POSIX Extended Regular Expression syntax: +https://en.m.wikibooks.org/wiki/Regular_Expressions/POSIX-Extended_Regular_Expressions. + +If this is set to false (not recommended), dunst will us fnmatch(3) for matching +strings. Dunst doesn't pass any flags to fnmatch, so you cannot make use of +extended patterns. + +The POSIX syntax is more powerful and will eventually become the default. The main +differences between POSIX and fnmatch(3) is that POSIX uses ".*" for wildcards +instead of "*" and POSIX allows for partial matches without needing wildcards. +This means that the pattern "abc" will match all strings that contain "abc", +like "abcdef". + +=item B DEPRECATED + +This setting is deprecated and removed. It's split up into B, B, B, +B and B. +For quickly transitioning to the new syntax, you can take the numbers from your +old geometry config as follows: + geometry = x+ + +In the new config you can then set the following variables (make sure to remove +any negative signs) + width = + height = + offset = + origin = top-right # or top-left, or any other direction you prefer + +=item B + +The width of the notification window in pixels. This can be a single number to +specify a constant width or two numbers for the minimum and maximum width. The +notification will expand from the minimum width as neccesary. + +Examples: + width = 300 # constant width of 300 + width = (0, 300) # width between 0 and 300 + +When setting a width bigger than the screen, dunst will clamp the width to the +screen width. So if you want the notifcation to stretch the entire screen +dynamically, you may set the width to a high enough number, which none of your +screens exceed (e.g. 10000). + +=item B + +The maximum height of a single notification. + +=item B (default: 0) + +The number of notifications that can appear at one time. When this +limit is reached any additional notifications will be queued and displayed when +the currently displayed ones either time out or are manually dismissed. The +value 0 means no limit. If B is true, then the specified limit +is reduced by 1 and the last notification is a message informing how many hidden +notifications are waiting to be displayed. See the B entry for +more information. + +=item B (default: top-right) + +The origin of the notification window on the screen. It can then be moved with +offset. +Origin can be one of: + top-left + top-center + top-right + bottom-left + bottom-center + bottom-right + left-center + center + right-center + +=item B format: (horizontal, vertical) + +Respectively the horizontal and vertical offset in pixels from the corner +of the screen specified by B. A negative offset will lead to the +notification being off screen. + +Examples: + origin = top-right + offset = 10x300 # a margin of 10 pixels from the right and 300 pixels from the top + +=item B (default: 0, X11 only) + +Specifies a scale factor for dimensions to adapt notifications to +HiDPI screens. This scales the notification geometry and it's +contents. It is not recommended to use a fractional scaling factor, as +this may result in things being one pixel off. Try to use a whole +number scaling factor and adjust the font size and other sizes as +needed. If 0 is specified, the scale factor is auto-detected. + +=item B (values: [true/false], default: true) + +When an integer value is passed to dunst as a hint (see B), a +progress bar will be drawn at the bottom of the notification. This +behavior can be turned off by setting this setting to false. + +=item B (values: [left/center/right], default: center) + +Horizontal alignment of the progress bar. The progress bar will always keep a +distance of B from the edge of the notification. + +=item B (default: 10) + +The height of the progress bar in pixel. This includes the frame. Make sure +this value is bigger than twice the frame width. + +=item B (default: 150) + +The minimum width of the progress bar in pixels. The notification is rescaled +to fit the bar. + +=item B (default: 300) + +The maximum width of the progress bar in pixels. The notification is resized +to fit the progress bar. + +=item B (default: 1) + +The frame width of the progress bar in pixels. This value should be smaller +than half of the progress bar height. + +=item B (values: [true/false], default: true) + +If this is set to true, a notification indicating how many notifications are +not being displayed due to the notification limit (see B) +will be shown B. + +Meaning that if this is enabled the number of visible notifications will be 1 +less than what is specified by B, the last slot will be +taken by the hidden count. + +=item B (default: 0) (X11 only) + +A 0-100 range on how transparent the notification window should be, with 0 +being fully opaque and 100 invisible. + +To make windows transparent on wayland, set the transparency part of a color, +see COLORS. + +This setting will only work if a compositor is running. + +=item B (default: 2) + +The height in pixels of the separator between notifications, if set to 0 there +will be no separating line between notifications. + +=item B (default: 8) + +The distance in pixels from the content to the separator/border of the window +in the vertical axis + +=item B (default: 8) + +The distance in pixels from the content to the border of the window +in the horizontal axis + +=item B (default: 0) + +The distance in pixels from the text to the icon (or vice versa) +in the horizontal axis. + +Setting this to a non-zero value overwrites any padding that horizontal_padding was adding between the notification text and icon. + +So for example setting + + text_icon_padding=10 + horizontal_padding=10 + +is equivalent to + + text_icon_padding=0 + horizontal_padding=10 + +=item B (default: 3) + +Defines width in pixels of frame around the notification window. Set to 0 to +disable. + +=item B (values: [auto/foreground/frame/#RRGGBB] default: frame) + +Sets the color of the separator line between two notifications. + +=over 4 + +=item B + +Dunst tries to find a color that fits the rest of the notification color +scheme automatically. + +=item B + +The color will be set to the same as the foreground color of the topmost +notification that's being separated. + +=item B + +The color will be set to the frame color of the notification with the highest +urgency between the 2 notifications that are being separated. + +=item B + +Any other value is interpreted as a color, see COLORS + +=back + +=item B (values: [true/false], default: true) + +If set to true, display notifications with higher urgency above the others. + +=item B (default: 0) + +Don't timeout notifications if user is idle longer than this time. +See TIME FORMAT for valid times. + +Set to 0 to disable. + +A client can mark a notification as transient to bypass this setting and timeout +anyway. Use a rule with 'set_transient = no' to disable this behavior. + +Note: this doesn't work on xwayland. + +=item B (Wayland only) + +One of bottom, top or overlay. + +Place dunst notifications on the selected layer. Using overlay +will cause notifications to be displayed above fullscreen windows, though +this may also occur at top depending on your compositor. + +The bottom layer is below all windows and above the background. + +Default: overlay + +=item B (values: [true/false], default: false) (Wayland only) + +Force the use of X11 output, even on a wayland compositor. This setting +has no effect when not using a Wayland compositor. + +=item B (default: "Monospace 8") + +Defines the font or font set used. Optionally set the size as a decimal number +after the font name and space. +Multiple font options can be separated with commas. + +This options is parsed as a Pango font description. + +=item B (default: 0) + +The amount of extra spacing between text lines in pixels. Set to 0 to +disable. + +=item B (default: "%s\n%b") + +Specifies how the various attributes of the notification should be formatted on +the notification window. + +Regardless of the status of the B setting, any markup tags that are +present in the format will be parsed. Note that because of that, if a literal +ampersand (&) is needed it needs to be escaped as '&' + +If '\n' is present anywhere in the format, it will be replaced with +a literal newline. + +If any of the following strings are present, they will be replaced with the +equivalent notification attribute. + +=over 4 + +=item B<%a> appname + +=item B<%s> summary + +=item B<%b> body + +=item B<%i> iconname (including its path) + +=item B<%I> iconname (without its path) + +=item B<%p> progress value ([ 0%] to [100%]) + +=item B<%n> progress value without any extra characters + +=item B<%%> Literal % + +=back + +If any of these exists in the format but hasn't been specified in the +notification (e.g. no icon has been set), the placeholders will simply be +removed from the format. + +=item B (values: [top/center/bottom], default: center) + +Defines how the text and icon should be aligned vertically within the +notification. If icons are disabled, this option has no effect. + +=item B (default: 60) + +Show age of message if message is older than this time. +See TIME FORMAT for valid times. + +Set to -1 to disable. + +=item B (values: [true/false], default: false) + +If set to true, replace newline characters in notifications with whitespace. + +=item B (values: [true/false], default: true) + +If set to true, duplicate notifications will be stacked together instead of +being displayed separately. + +Two notifications are considered duplicate if the name of the program that sent +it, summary, body, icon and urgency are all identical. + +=item B (values: [true/false], default: false) + +Hide the count of stacked duplicate notifications. + +=item B (values: [true/false], default: true) + +Show an indicator if a notification contains actions and/or open-able URLs. See +ACTIONS below for further details. + +=item B (default: 0) + +Defines the minimum size in pixels for the icons. +If the icon is larger than or equal to the specified value it won't be affected. +If it's smaller then it will be scaled up so that the smaller axis is equivalent +to the specified size. + +Set to 0 to disable icon upscaling. (default) + +If B is set to off, this setting is ignored. + +=item B (default: 32) + +Defines the maximum size in pixels for the icons. +If the icon is smaller than or equal to the specified value it won't be affected. +If it's larger then it will be scaled down so that the larger axis is equivalent +to the specified size. + +Set to 0 to disable icon downscaling. + +If both B and B are enabled, the latter +gets the last say. + +If B is set to off, this setting is ignored. + +=item B (default: "/usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/") + +Can be set to a colon-separated list of paths to search for icons to use with +notifications. + +Dunst doens't search outside of these direcories. For a recursive icon lookup +system, see B. This new system will eventually +replace this and will need new settings. + +=item B (default: "Adwaita", example: "Adwaita, breeze") I + +Comma-separated of names of the the themes to use for looking up icons. This has +to be the name of the directory in which the theme is located, not the +human-friendly name of the theme. So for example, the theme B is +located in F. In this case you have to set the +theme to B. + +The first theme in the list is the most important. Only if the icon cannot be +found in that theme, the next theme will be tried. + +Dunst will look for the themes in B and +B<$XDG_DATA_DIRS/icons> as specified in the icon theme specification: +https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html. + +If the theme inherits from other themes, they will be used as a backup. + +This setting is experimental and not enabled by default. See +B for how to enable it. + +=item B (default: false) I + +This setting enables the new icon lookup method. This new system will eventually +be the old icon lookup. + +Currently icons are looked up in the B and scaled according to +B and B. Since the B wasn't recursive, +one had to add a ton of paths to this list. +This has been drastically simplified by the new lookup method. Now you only have +to set B to the name of the theme and B to the icon size +you want. To enable this new behaviour, set B to +true in the I<[experimental]> section. See the respective settings for more +details. + +=item B (values: [true/false], default: true) + +If set to true, notifications that have been recalled from history will not +time out automatically. + +=item B (default: 20) + +Maximum number of notifications that will be kept in history. After that limit +is reached, older notifications will be deleted once a new one arrives. See +HISTORY. + +=item B (default: "/usr/bin/dmenu -p dunst") + +The command that will be run when opening the context menu. Should be either +a dmenu command or a dmenu-compatible menu. + +=item B (default: "/usr/bin/xdg-open") + +The command that will be run when opening a URL. The URL to be opened will be +appended to the end of the value of this setting. + +=item B (values: [true/false] default: true] + +Always run rule-defined scripts, even if the notification is suppressed with +format = "". See SCRIPTING. + +=item B (default: "Dunst") + +Defines the title of notification windows spawned by dunst. (_NET_WM_NAME +property). There should be no need to modify this setting for regular use. + +=item B<class> (default: "Dunst") + +Defines the class of notification windows spawned by dunst. (First part of +WM_CLASS). There should be no need to modify this setting for regular use. + +=item B<force_xinerama> (values: [true/false], default: false) (X11 only) + +Use the Xinerama extension instead of RandR for multi-monitor support. This +setting is provided for compatibility with older nVidia drivers that do not +support RandR and using it on systems that support RandR is highly discouraged. + +By enabling this setting dunst will not be able to detect when a monitor is +connected or disconnected which might break follow mode if the screen layout +changes. + +=item B<corner_radius> (default: 0) + +Define the corner radius in pixels. A corner radius of 0 will result in +rectangular shaped notifications. + +By enabling this setting the outer border and the frame will be shaped. +If you have multiple notifications, the whole window is shaped, not every +single notification. + +To avoid the corners clipping the icon or text the corner radius will be +automatically lowered to half of the notification height if it exceeds it. + +=item B<mouse_left/middle/right_click> (values: [none/do_action/close_current/close_all/context/context_all]) + +Defines action of mouse click. A touch input in Wayland acts as a mouse left +click. + +=over 4 + +=item B<none> + +Don't do anything. + +=item B<do_action> (default for mouse_middle_click) + +Invoke the action determined by the action_name rule. If there is no such +action, open the context menu. + +=item B<open_url> + +If the notification has exactly one url, open it. If there are multiple +ones, open the context menu. + +=item B<close_current> (default for mouse_left_click) + +Close current notification. + +=item B<close_all> (default for mouse_right_click) + +Close all notifications. + +=item B<context> + +Open context menu for the notification. + +=item B<context_all> + +Open context menu for all notifications. + +=back + +=item B<ignore_dbusclose> (default: false) + +Ignore the dbus closeNotification message. This is useful to enforce the timeout +set by dunst configuration. Without this parameter, an application may close +the notification sent before the user defined timeout. + +=back + +=head2 Keyboard shortcuts (X11 only) + +Keyboard shortcuts are defined in the following format: "Modifier+key" where the +modifier is one of ctrl,mod1,mod2,mod3,mod4 and key is any keyboard key. + +Note that the keyboard shortcuts have been moved to the B<global> section of the +config for consistency with other settings. + +Alternatively you can also define shortcuts inside your window manager and bind +them to dunstctl(1) commands. + +=over 4 + +=item B<close> + +Specifies the keyboard shortcut for closing a notification. + +=item B<close_all> + +Specifies the keyboard shortcut for closing all currently displayed notifications. + +=item B<history> + +Specifies the keyboard shortcut for recalling a single notification from history. + +=item B<context> + +Specifies the keyboard shortcut that opens the context menu. + +=back + +=head2 Urgency sections + +The urgency sections work in a similar way to rules and can be used to specify +attributes for the different urgency levels of notifications (low, normal, +critical). Currently only the background, foreground, hightlight, timeout, +frame_color and icon attributes can be modified. + +The urgency sections are urgency_low, urgency_normal, urgency_critical for low, +normal and critical urgency respectively. + +See the example configuration file for examples. + +The flags for setting the colors notifications of different urgencies have been +removed. See issue #328 in the bug tracker for discussions (See REPORTING BUGS). + +=over 4 + +=item B<-li/ni/ci icon> DEPRECATED + +Defines the icon for low, normal and critical notifications respectively. This +setting will be replaced by the B<default_icon> setting, so it's +recommended to replace it as soon as possible. + +Where I<icon> is a path to an image file containing the icon. + +=item B<-lf/nf/cf color> REMOVED + +Defines the foreground color for low, normal and critical notifications respectively. + +See COLORS for the value format. + +=item B<-lb/nb/cb color> REMOVED + +Defines the background color for low, normal and critical notifications respectively. + +See COLORS for the value format. + +=item B<-lh/nh/ch color> REMOVED + +Defines the highlight color for low, normal and critical notifications respectively. + +See COLORS for the value format. + +=item B<-lfr/nfr/cfr color> REMOVED + +Defines the frame color for low, normal and critical notifications respectively. + +See COLORS for more information + +=item B<-lto/nto/cto secs> REMOVED + +Defines the timeout time for low, normal and critical notifications +respectively. +See TIME FORMAT for valid times. + +=back + +=head1 DUNSTCTL + +Dunst now contains a command line control command that can be used to interact +with it. It supports all functions previously done only via keyboard shortcuts +but also has a lot of extra functionality. For more information, see +dunstctl(1). + +=head1 HISTORY + +Dunst saves a number of notifications (specified by B<history_length>) in memory. +These notifications can be recalled (i.e. redisplayed) by calling +B<dunstctl history> (see dunstctl(1)). Whether these notifications will time out +like if they have been just send depends on the value of the B<sticky_history> +setting. Actions are invalidated once the notification is closed, so you cannot +execute that action when you bring back a notification from history. + +Past notifications are redisplayed in a first-in-last-out order, meaning that +pressing the history key once will bring up the most recent notification that +had been closed/timed out. + +=head1 WAYLAND + +Dunst has Wayland support since version 1.6.0. Because the Wayland protocol +is more focused on security, some things that are possible in X11 are not +possible in Wayland. Those differences are reflected in the configuration. +The main things that change are that dunst on Wayland cannot use global +hotkeys (they are deprecated anyways, use dunstctl). + +Some dunst features on wayland might need your compositor to support a certain +protocol. Dunst will warn you if an optional feature isn't supported and will +disable the corresponding functionality. + +Fullscreen detection works on wayland with some limitations (see B<fullscreen>). +If you want notifications to appear over fullscreen windows, set +B<layer = overlay> in the global options. + +Note that the same limitations exist when using xwayland. +If something doesn't quite work in Wayland, please file a bug report. In the +mean time, you can try if the X11 output does work on wayland. Use +B<force_xwayland = true> for that. + +If you have your dunst notifications on the same side of your display as your +status bar, you might notice that your notifications appear a bit higher or +lower than on X11. This is because the notification cannot be placed on top of +your status bar. The notifications are placed relative to your status bar, +making them appear higher or lower by the height of your status bar. We cannot +do anything about that behavior. + +=head1 RULES + +Rules allow the conditional modification of notifications. They can be located +in a section with any name, even the special sections. The special sections do +not allow filters to be added, since they have implied filters by default. + +=over 4 + +=item 'global' + +No filters, matches all notifications. + +=item 'urgency_low', 'urgency_normal' and 'urgency_critical' + +Matches low, normal or critical urgency respectively. + +=back + +There are 2 parts in configuring a rule: Defining the filters that controls when +a rule should apply and then the actions that should be taken when the rule is +matched. It's also possible to not specify any filters, in which case the rule +will match all notifications. + +Rules are applied in order of appearance. Beware: if a notification is changed by a +rule, it may affect if it's being matched by a later rule. + +=over 4 + +=item B<filtering> + +With filtering rules you can match notifications to apply rules to only a subset +of notifications. + +For filtering rules that filter based on strings you can use regular +expressions. It's recommended to set B<enable_posix_regex> to true. You can then +use the POSIX Extended Regular Expression syntax: +https://en.m.wikibooks.org/wiki/Regular_Expressions/POSIX-Extended_Regular_Expressions. + +Notifications can be matched for any of the following attributes. + +=over 4 + +=item C<appname> (discouraged, see desktop_entry) + +The name of the application as reported by the client. Be aware that the name +can often differ depending on the locale used. + +=item C<body> + +The body of the notification + +=item C<category> + +The category of the notification as defined by the notification spec. See +https://specifications.freedesktop.org/notification-spec/latest/ar01s06.html. + +=item C<desktop_entry> + +GLib based applications export their desktop-entry name. In comparison to the appname, +the desktop-entry won't get localized. + +=item C<icon> + +The icon of the notification in the form of a file path. Can be empty if no icon +is available or a raw icon is used instead. This setting is not to be confused +with the icon setting in the urgency section. + +=item C<match_transient> + +Match if the notification has been declared as transient by the client or by +some other rule. + +See C<set_transient> for more details about this attribute. + +=item C<msg_urgency> + +Matches the urgency of the notification as set by the client or by some other +rule. + +=item C<stack_tag> + +Matches the stack tag of the notification as set by the client or by some other +rule. + +See set_stack_tag for more information about stack tags. + +=item C<summary> + +Matches the summary, 'title', of the notification. + +=back + +C<msg_urgency> is the urgency of the notification, it is named so to not conflict +with trying to modify the urgency. + +Instead of the appname filter, it's recommended to use the desktop_entry filter. + + +To define a matching rule simply assign the specified value to the value that +should be matched, for example: + + appname="notify-send" + +Matches only messages that were send via notify-send. If multiple filter +expressions are present, all of them have to match for the rule to be applied +(logical AND). + +Shell-like globing is supported. + +=item B<modifying> + +The following attributes can be overridden: + +=over 4 + +=item C<background> + +The background color of the notification. See COLORS for possible values. + +=item C<foreground> + +The foreground color of the notification. See COLORS for possible values. + +=item C<highlight> + +The highlight color of the notification. This color is used for coloring the +progress bar. See COLORS for possible values. + +=item C<format> + +Equivalent to the C<format> setting. + +=item C<frame_color> + +The frame color color of the notification. See COLORS for possible values. + +=item C<fullscreen> + +One of show, delay, or pushback. + +This attribute specifies how notifications are handled if a fullscreen window +is focused. By default it's set to show so notifications are being shown. + +Other possible values are delay: Already shown notifications are continued to be +displayed until they are dismissed or time out but new notifications will be +held back and displayed when the focus to the fullscreen window is lost. + +Or pushback which is equivalent to delay with the difference that already +existing notifications are paused and hidden until the focus to the fullscreen +window is lost. + +On wayland, if B<follow> is set to mouse or keyboard, the output where the +notification is located cannot be determined. So dunst will delay or pushback if +any of the outputs is fullscreen. Since the fullscreen protocol is fairly new, +you will need a recent version of a compositor that supports it. At the time of +writing, you will need the git version of sway. +See also B<layer> to change if notifications appear above fullscreen windows in +Wayland. + +Default: show + +=item C<new_icon> + +Updates the icon of the notification, it should be a path or a name for a valid +image. This overrides the icon that was sent with dunstify or another notification +tool. + +=item C<icon_position> (values: [left/right/top/off], default: left) + +Defines the position of the icon in the notification window. Setting it to off +disables icons. + +=item C<default_icon> + +Sets the default icon of the notification, it should be a path or a name for a +valid image. This does B<not> override the icon that was sent with dunstify or +another notification tool. + +=item C<set_stack_tag> + +Sets the stack tag for the notification, notifications with the same (non-empty) +stack tag and the same appid will replace each-other so only the newest one is +visible. This can be useful for example in volume or brightness notifications +where you only want one of the same type visible. + +The stack tag can be set by the client with the 'synchronous', +'private-synchronous' 'x-canonical-private-synchronous' or the +'x-dunst-stack-tag' hints. + +=item C<set_transient> + +Sets whether the notification is considered transient. +Transient notifications will bypass the idle_threshold setting. + +By default notifications are _not_ considered transient but clients can set the +value of this by specifying the 'transient' hint when sending notifications. + +=item C<set_category> + +Sets the category of the notification. See +https://specifications.freedesktop.org/notification-spec/latest/ar01s06.html +for a list of standard categories. + +=item C<timeout> + +Equivalent to the C<timeout> setting in the urgency sections. + +=item C<urgency> + +This sets the notification urgency. + +B<IMPORTANT NOTE>: This currently DOES NOT re-apply the attributes from the +urgency_* sections. The changed urgency will only be visible in rules defined +later. Use C<msg_urgency> to match it. + +=item C<skip_display> + +Setting this to true will prevent the notification from being displayed +initially but will be saved in history for later viewing. + +=item C<history_ignore> + +Setting this to true will display the notification initially, but stop it +from being recalled via the history. + +=item C<action_name> + +Sets the name of the action to be invoked on do_action. If not specified, the +action set as default action or the only available action will be invoked. + +Default: "default" + +=item B<word_wrap> (values: [true/false], default: true) + +Specifies whether to wrap the text if the lines get longer than the maximum +notification width. If it's set to true, long lines will be broken into multiple +lines expanding the notification window height as necessary for them to fit. If +the text doesn't fit in the window, it will be ellipsize according to ellipsize. + +=item B<ellipsize> (values: [start/middle/end], default: middle) + +Specifies where truncated lines should be ellipsized. + +=item B<alignment> (values: [left/center/right], default: left) + +Defines how the text should be aligned within the notification. + +=item B<hide_text> (values: [true/false], default: false) + +Setting this to true will skip displaying any text related to the notification. +The notification icon and progress bar will still be displayed. This option may +be useful for notifications where an icon or progress bar may be sufficient +information for the notification, such as audio volume or brightness level. + +=item B<markup> (values: [full/strip/no], default: no) + +Defines how markup in notifications is handled. + +It's important to note that markup in the format option will be parsed +regardless of what this is set to. + +Possible values: + +=over 4 + +=item B<full> + +Allow a small subset of html markup in notifications + + <b>bold</b> + <i>italic</i> + <s>strikethrough</s> + <u>underline</u> + +For a complete reference see +<https://docs.gtk.org/Pango/pango_markup.html> + +=item B<strip> + +This setting is provided for compatibility with some broken +clients that send markup even though it's not enabled on the +server. + +Dunst will try to strip the markup but the parsing is simplistic so using this +option outside of matching rules for specific applications B<IS GREATLY +DISCOURAGED>. + +See RULES + +=item B<no> + +Disable markup parsing, incoming notifications will be treated as +plain text. Dunst will not advertise that it can parse markup if this is set as +a global setting. + +=back + +=item B<icon_size> (default: 32) I<Experimental> + +The size of the icon in pixels. This is commonly a multiple of 2, for example: +16, 32 or 64. This size is used for searching the right icon in B<icon_theme>. +If no icon of the right size can be found, no icon is displayed. When passing a +full icon path to dunst the icon will be used even when it's not the right +size. The icon is then scaled to be of size B<icon_size>. + +This setting is experimental and not enabled by default. See +B<enable_recursive_icon_lookup> for how to enable it. + +=back + +As with the filtering attributes, each one corresponds to +the respective notification attribute to be modified. + +As with filtering, to make a rule modify an attribute simply assign it in the +rule definition. + +If the format is set to an empty string, the notification will not be +suppressed. + +=back + +=head2 SCRIPTING + +Within rules you can specify a script to be run every time the rule is matched +by assigning the 'script' option to the name of the script to be run. + +When the script is called details of the notification that triggered it will be +passed via environment variables. The following variables are available: +B<DUNST_APP_NAME>, B<DUNST_SUMMARY>, B<DUNST_BODY>, B<DUNST_ICON_PATH>, +B<DUNST_URGENCY>, B<DUNST_ID>, B<DUNST_PROGRESS>, B<DUNST_CATEGORY>, +B<DUNST_STACK_TAG>, B<DUNST_URLS>, B<DUNST_TIMEOUT>, B<DUNST_TIMESTAMP>, +B<DUNST_DESKTOP_ENTRY>, and B<DUNST_STACK_TAG>. + +Another, less recommended way to get notifcations details from a script is via +command line parameters. These are passed to the script in the following order: +B<appname>, B<summary>, B<body>, B<icon_path>, B<urgency>. + +Where B<DUNST_ICON_PATH> or B<icon_path> is the absolute path to the icon file +if there is one. B<DUNST_URGENCY> or B<urgency> is one of "LOW", "NORMAL" or +"CRITICAL". B<DUNST_URLS> is a newline-separated list of urls associated with +the notification. + +Note that some variables may be empty. + +If the notification is suppressed, the script will not be run unless +B<always_run_scripts> is set to true. + +If '~/' occurs at the beginning of the script parameter, it will get replaced by the +users' home directory. If the value is not an absolute path, the directories in the +PATH variable will be searched for an executable of the same name. + +=head1 COLORS + +Colors are interpreted as X11 color values. This includes both verbatim +color names such as "Yellow", "Blue", "White", etc as well as #RGB and #RRGGBB +values. + +You may also specify a transparency component in #RGBA or #RRGGBBAA format. + +B<NOTE>: '#' is interpreted as a comment, to use it the entire value needs to +be in quotes like so: separator_color="#123456" + +=head2 NOTIFY-SEND + +dunst is able to get different colors for a message via notify-send. +In order to do that you have to add a hint via the -h option. +The progress value can be set with a hint, too. + +B<All hints> + +See RULES for more detailed explanations for some options. + +=over 4 + +=item B<fgcolor>: +Foreground cololor + +=item B<bgcolor>: +Background color + +=item B<frcolor>: +Frame color + +=item B<hlcolor>: +Highlight color + +=item B<value>: +Progress value. + +=item B<image-path>: +Icon name. This may be a path or just the icon name. + +=item B<image-data>: +A stream of raw image data. + +=item B<category>: +The category. + +=item B<desktop-entry>: +The desktop entry. + +=item B<transient>: +The transient value. + +=back + +B<Examples> + +=over 4 + +=item notify-send -h string:fgcolor:#ff4444 + +=item notify-send -h string:bgcolor:#4444ff -h string:fgcolor:#ff4444 -h string:frcolor:#44ff44 + +=item notify-send -h int:value:42 "Working ..." + +=back + +=head1 ACTIONS + +Dunst allows notifiers (i.e.: programs that send the notifications) to specify +actions. Dunst has support for both displaying indicators for these, and +interacting with these actions. + +If "show_indicators" is true and a notification has an action, an "(A)" will be +prepended to the notification format. Likewise, an "(U)" is prepended to +notifications with URLs. It is possible to interact with notifications that +have actions regardless of this setting, though it may not be obvious which +notifications HAVE actions. + +The "context" keybinding is used to interact with these actions, by showing a +menu of possible actions. This feature requires "dmenu" or a dmenu drop-in +replacement present. + +Alternatively, you can invoke an action with a middle click on the notification. +If there is exactly one associated action, or one is marked as default, that one +is invoked. If there are multiple, the context menu is shown. The same applies +to URLs when there are no actions. + +=head1 TIME FORMAT + +A time can be any decimal integer value suffixed with a time unit. If no unit +given, seconds ("s") is taken as default. + +Time units understood by dunst are "ms", "s", "m", "h" and "d". + +Example time: "1000ms" "10m" + +=head1 AUTHORS + +Written by Sascha Kruse <knopwob@googlemail.com> + +=head1 REPORTING BUGS + +Bugs and suggestions should be reported on GitHub at https://github.com/dunst-project/dunst/issues + +=head1 COPYRIGHT + +Copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) + +If you feel that copyrights are violated, please send me an email. + +=head1 SEE ALSO + +dunst(1), dunstctl(1), dmenu(1), notify-send(1) diff -Nru dunst-1.5.0/docs/dunstctl.pod dunst-1.8.1/docs/dunstctl.pod --- dunst-1.5.0/docs/dunstctl.pod 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/docs/dunstctl.pod 2022-03-02 10:55:25.000000000 +0000 @@ -29,11 +29,17 @@ Open the context menu, presenting all available actions and urls for the currently open notifications. -=item B<history-pop> +=item B<count> [displayed/history/waiting] + +Returns the number of displayed, shown and waiting notifications. If no argument +is provided, everything will be printed. + +=item B<history-pop> [ID] Redisplay the notification that was most recently closed. This can be called multiple times to show older notifications, up to the history limit configured -in dunst. +in dunst. You can optionally pass an ID to history-pop, in which case the +notification with the given ID will be shown. =item B<is-paused> diff -Nru dunst-1.5.0/docs/dunst.pod dunst-1.8.1/docs/dunst.pod --- dunst-1.5.0/docs/dunst.pod 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/docs/dunst.pod 1970-01-01 00:00:00.000000000 +0000 @@ -1,926 +0,0 @@ -=head1 NAME - -dunst - A customizable and lightweight notification-daemon - -=head1 SYNOPSIS - -dunst [-conf file] [-font font] [-geometry geom] [-format fmt] [-follow mode] [-monitor n] [-history_length n] ... - -=head1 DESCRIPTION - -Dunst is a highly configurable and lightweight notification daemon. - -=head1 COMMAND LINE OPTIONS - -=over 4 - -=item B<-h/--help> - -List all command line flags - -=item B<-conf/-config file> - -Use alternative config file. - -=item B<-v/--version> - -Print version information. - -=item B<-print> - -Print notifications to stdout. This might be useful for logging, setting up -rules or using the output in other scripts. - -=back - -=head1 CONFIGURATION - -An example configuration file is included (usually /usr/share/dunst/dunstrc). -To change the configuration, copy this file to ~/.config/dunst/dunstrc and edit -it accordingly. - -The configuration is divided into sections in an ini-like format. The 'global' -section contains most general settings while the 'shortcuts' sections contains -all keyboard configuration and the 'experimental' section all the features that -have not yet been tested thoroughly. - -Any section that is not one of the above is assumed to be a rule, see RULES for -more details. - -For backwards compatibility reasons the section name 'frame' is considered bound -and can't be used as a rule. - -=head2 Command line - -Each configuration option in the global section can be overridden from the -command line by adding a single dash in front of it's name. -For example the font option can be overridden by running - - $ dunst -font "LiberationSans Mono 4" - -Configuration options that take boolean values can only currently be set to -"true" through the command line via the same method. e.g. - - $ dunst -shrink - -This is a known limitation of the way command line parameters are parsed and -will be changed in the future. - -Available settings per section: - -=head2 Global section - -=over 4 - -=item B<monitor> (default: 0) - -Specifies on which monitor the notifications should be displayed in, count -starts at 0. See the B<follow> setting. - -=item B<follow> (values: [none/mouse/keyboard] default: none) - -Defines where the notifications should be placed in a multi-monitor setup. All -values except I<none> override the B<monitor> setting. - -=over 4 - -=item B<none> - -The notifications will be placed on the monitor specified by the B<monitor> -setting. - -=item B<mouse> - -The notifications will be placed on the monitor that the mouse is currently in. - -=item B<keyboard> - -The notifications will be placed on the monitor that contains the window with -keyboard focus. - -=back - -=item B<geometry> (format: [{width}][x{height}][+/-{x}[+/-{y}]], default: "0x0+0-0") - -The geometry of the window the notifications will be displayed in. - -=over 4 - -=item B<width> - -The width of the notification window in pixels. A negative value sets the width -to the screen width B<minus the absolute value of the width>. If the width is -omitted then the window expands to cover the whole screen. If it's 0 the window -expands to the width of the longest message being displayed. - -=item B<height> - -The number of notifications that can appear at one time. When this -limit is reached any additional notifications will be queued and displayed when -the currently displayed ones either time out or are manually dismissed. If -B<indicate_hidden> is true, then the specified limit is reduced by 1 and the -last notification is a message informing how many hidden notifications are -waiting to be displayed. See the B<indicate_hidden> entry for more information. - -The physical(pixel) height of the notifications vary depending on the number of -lines that need to be displayed. - -See B<notification_height> for changing the physical height. - -=item B<x/y> - -Respectively the horizontal and vertical offset in pixels from the corner -of the screen that the notification should be drawn at. For the horizontal(x) -offset, a positive value is measured from the left of the screen while a -negative one from the right. For the vertical(y) offset, a positive value is -measured from the top while a negative from the bottom. - -It's important to note that the positive and negative sign B<DOES> affect the -position even if the offset is 0. For example, a horizontal offset of +0 puts -the notification on the left border of the screen while a horizontal offset of --0 at the right border. The same goes for the vertical offset. - -=back - -=item B<indicate_hidden> (values: [true/false], default: true) - -If this is set to true, a notification indicating how many notifications are -not being displayed due to the notification limit (see B<geometry>) will be -shown B<in place of the last notification slot>. - -Meaning that if this is enabled the number of visible notifications will be 1 -less than what is specified in geometry, the last slot will be taken by the -hidden count. - -=item B<shrink> (values: [true/false], default: false) - -Shrink window if it's smaller than the width. Will be ignored if width is 0. - -This is used mainly in order to have the shrinking benefit of dynamic width (see -geometry) while also having an upper bound on how long a notification can get -before wrapping. - -=item B<transparency> (default: 0) - -A 0-100 range on how transparent the notification window should be, with 0 -being fully opaque and 100 invisible. - -This setting will only work if a compositor is running. - -=item B<notification_height> (default: 0) - -The minimum height of the notification window in pixels. If the text and -padding cannot fit in within the height specified by this value, the height -will be increased as needed. - -=item B<separator_height> (default: 2) - -The height in pixels of the separator between notifications, if set to 0 there -will be no separating line between notifications. - -=item B<padding> (default: 0) - -The distance in pixels from the content to the separator/border of the window -in the vertical axis - -=item B<horizontal_padding> (default: 0) - -The distance in pixels from the content to the border of the window -in the horizontal axis - -=item B<frame_width> (default: 0) - -Defines width in pixels of frame around the notification window. Set to 0 to -disable. - -=item B<frame_color color> (default: #888888) - -Defines color of the frame around the notification window. See COLORS. - -=item B<separator_color> (values: [auto/foreground/frame/#RRGGBB] default: auto) - -Sets the color of the separator line between two notifications. - -=over 4 - -=item B<auto> - -Dunst tries to find a color that fits the rest of the notification color -scheme automatically. - -=item B<foreground> - -The color will be set to the same as the foreground color of the topmost -notification that's being separated. - -=item B<frame> - -The color will be set to the frame color of the notification with the highest -urgency between the 2 notifications that are being separated. - -=item B<anything else> - -Any other value is interpreted as a color, see COLORS - -=back - -=item B<sort> (values: [true/false], default: true) - -If set to true, display notifications with higher urgency above the others. - -=item B<idle_threshold> (default: 0) - -Don't timeout notifications if user is idle longer than this time. -See TIME FORMAT for valid times. - -Set to 0 to disable. - -A client can mark a notification as transient to bypass this setting and timeout -anyway. Use a rule with 'set_transient = no' to disable this behavior. - -=item B<font> (default: "Monospace 8") - -Defines the font or font set used. Optionally set the size as a decimal number -after the font name and space. -Multiple font options can be separated with commas. - -This options is parsed as a Pango font description. - -=item B<line_height> (default: 0) - -The amount of extra spacing between text lines in pixels. Set to 0 to -disable. - -=item B<markup> (values: [full/strip/no], default: no) - -Defines how markup in notifications is handled. - -It's important to note that markup in the format option will be parsed -regardless of what this is set to. - -Possible values: - -=over 4 - -=item B<full> - -Allow a small subset of html markup in notifications - - <b>bold</b> - <i>italic</i> - <s>strikethrough</s> - <u>underline</u> - -For a complete reference see -<https://developer.gnome.org/pango/stable/pango-Markup.html> - -=item B<strip> - -This setting is provided for compatibility with some broken -clients that send markup even though it's not enabled on the -server. - -Dunst will try to strip the markup but the parsing is simplistic so using this -option outside of matching rules for specific applications B<IS GREATLY -DISCOURAGED>. - -See RULES - -=item B<no> - -Disable markup parsing, incoming notifications will be treated as -plain text. Dunst will not advertise that it can parse markup if this is set as -a global setting. - -=back - -=item B<format> (default: "%s %b") - -Specifies how the various attributes of the notification should be formatted on -the notification window. - -Regardless of the status of the B<markup> setting, any markup tags that are -present in the format will be parsed. Note that because of that, if a literal -ampersand (&) is needed it needs to be escaped as '&' - -If '\n' is present anywhere in the format, it will be replaced with -a literal newline. - -If any of the following strings are present, they will be replaced with the -equivalent notification attribute. - -=over 4 - -=item B<%a> appname - -=item B<%s> summary - -=item B<%b> body - -=item B<%i> iconname (including its path) - -=item B<%I> iconname (without its path) - -=item B<%p> progress value ([ 0%] to [100%]) - -=item B<%n> progress value without any extra characters - -=item B<%%> Literal % - -=back - -If any of these exists in the format but hasn't been specified in the -notification (e.g. no icon has been set), the placeholders will simply be -removed from the format. - -=item B<alignment> (values: [left/center/right], default: left) - -Defines how the text should be aligned within the notification. - -=item B<vertical_alignment> (values: [top/center/bottom], default: center) - -Defines how the text and icon should be aligned vertically within the -notification. If icons are disabled, this option has no effect. - -=item B<show_age_threshold> (default: -1) - -Show age of message if message is older than this time. -See TIME FORMAT for valid times. - -Set to -1 to disable. - -=item B<word_wrap> (values: [true/false], default: false) - -Specifies how very long lines should be handled - -If it's set to false, long lines will be truncated and ellipsized. - -If it's set to true, long lines will be broken into multiple lines expanding -the notification window height as necessary for them to fit. - -=item B<ellipsize> (values: [start/middle/end], default: middle) - -If word_wrap is set to false, specifies where truncated lines should be -ellipsized. - -=item B<ignore_newline> (values: [true/false], default: false) - -If set to true, replace newline characters in notifications with whitespace. - -=item B<stack_duplicates> (values: [true/false], default: true) - -If set to true, duplicate notifications will be stacked together instead of -being displayed separately. - -Two notifications are considered duplicate if the name of the program that sent -it, summary, body, icon and urgency are all identical. - -=item B<hide_duplicates_count> (values: [true/false], default: false) - -Hide the count of stacked duplicate notifications. - -=item B<show_indicators> (values: [true/false], default: true) - -Show an indicator if a notification contains actions and/or open-able URLs. See -ACTIONS below for further details. - -=item B<icon_position> (values: [left/right/off], default: off) - -Defines the position of the icon in the notification window. Setting it to off -disables icons. - -=item B<min_icon_size> (default: 0) - -Defines the minimum size in pixels for the icons. -If the icon is larger than or equal to the specified value it won't be affected. -If it's smaller then it will be scaled up so that the smaller axis is equivalent -to the specified size. - -Set to 0 to disable icon upscaling. (default) - -If B<icon_position> is set to off, this setting is ignored. - -=item B<max_icon_size> (default: 0) - -Defines the maximum size in pixels for the icons. -If the icon is smaller than or equal to the specified value it won't be affected. -If it's larger then it will be scaled down so that the larger axis is equivalent -to the specified size. - -Set to 0 to disable icon downscaling. (default) - -If both B<min_icon_size> and B<max_icon_size> are enabled, the latter -gets the last say. - -If B<icon_position> is set to off, this setting is ignored. - -=item B<icon_path> (default: "/usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/") - -Can be set to a colon-separated list of paths to search for icons to use with -notifications. - -Dunst doesn't currently do any type of icon lookup outside of these -directories. - -=item B<sticky_history> (values: [true/false], default: true) - -If set to true, notifications that have been recalled from history will not -time out automatically. - -=item B<history_length> (default: 20) - -Maximum number of notifications that will be kept in history. After that limit -is reached, older notifications will be deleted once a new one arrives. See -HISTORY. - -=item B<dmenu> (default: "/usr/bin/dmenu") - -The command that will be run when opening the context menu. Should be either -a dmenu command or a dmenu-compatible menu. - -=item B<browser> (default: "/usr/bin/firefox") - -The command that will be run when opening a URL. The URL to be opened will be -appended to the end of the value of this setting. - -=item B<always_run_script> (values: [true/false] default: true] - -Always run rule-defined scripts, even if the notification is suppressed with -format = "". See SCRIPTING. - -=item B<title> (default: "Dunst") - -Defines the title of notification windows spawned by dunst. (_NET_WM_NAME -property). There should be no need to modify this setting for regular use. - -=item B<class> (default: "Dunst") - -Defines the class of notification windows spawned by dunst. (First part of -WM_CLASS). There should be no need to modify this setting for regular use. - -=item B<startup_notification> (values: [true/false], default: false) - -Display a notification on startup. This is usually used for debugging and there -shouldn't be any need to use this option. - -=item B<verbosity> (values: 'crit', 'warn', 'mesg', 'info', 'debug' default 'mesg') - -Do not display log messages, which have lower precedence than specified -verbosity. This won't affect printing notifications on the terminal. Use -the '-print' option for this. - -=item B<force_xinerama> (values: [true/false], default: false) - -Use the Xinerama extension instead of RandR for multi-monitor support. This -setting is provided for compatibility with older nVidia drivers that do not -support RandR and using it on systems that support RandR is highly discouraged. - -By enabling this setting dunst will not be able to detect when a monitor is -connected or disconnected which might break follow mode if the screen layout -changes. - -=item B<corner_radius> (default: 0) - -Define the corner radius in pixels. A corner radius of 0 will result in -rectangular shaped notifications. - -By enabling this setting the outer border and the frame will be shaped. -If you have multiple notifications, the whole window is shaped, not every -single notification. - -To avoid the corners clipping the icon or text the corner radius will be -automatically lowered to half of the notification height if it exceeds it. - -=item B<mouse_left/middle/right_click> (values: [none/do_action/close_current/close_all]) - -Defines action of mouse click. - -=over 4 - -=item B<none> - -Don't do anything. - -=item B<do_action> (default for mouse_middle_click) - -If the notification has exactly one action, or one is marked as default, invoke it. If there are multiple and no default, open the context menu. - -=item B<close_current> (default for mouse_left_click) - -Close current notification. - -=item B<close_all> (default for mouse_right_click) - -Close all notifications. - -=back - - -=item B<ignore_dbusclose> (default: false) - -Ignore the dbus closeNotification message. This is useful to enforce the timeout -set by dunst configuration. Without this parameter, an application may close -the notification sent before the user defined timeout. - -=back - -=head2 Shortcut section B<DEPRECATED SEE DUNSTCTL> - -Keyboard shortcuts are defined in the following format: "Modifier+key" where the -modifier is one of ctrl,mod1,mod2,mod3,mod4 and key is any keyboard key. - -=over 4 - -=item B<close> - -B<command line flag>: -key <key> - -Specifies the keyboard shortcut for closing a notification. - -=item B<close_all> - -B<command line flag>: -all_key <key> - -Specifies the keyboard shortcut for closing all currently displayed notifications. - -=item B<history> - -B<command line flag>: -history_key <key> - -Specifies the keyboard shortcut for recalling a single notification from history. - -=item B<context> - -B<command line flag>: -context_key <key> - -Specifies the keyboard shortcut that opens the context menu. - -=back - -=head2 Urgency sections - -The urgency sections work in a similar way to rules and can be used to specify -attributes for the different urgency levels of notifications (low, normal, -critical). Currently only the background, foreground, timeout, frame_color and -icon attributes can be modified. - -The urgency sections are urgency_low, urgency_normal, urgency_critical for low, -normal and critical urgency respectively. - -See the example configuration file for examples. - -Additionally, you can override these settings via the following command line -flags: - -Please note these flags may be removed in the future. See issue #328 in the bug -tracker for discussions (See REPORTING BUGS). - -=over 4 - -=item B<-li/ni/ci icon> - -Defines the icon for low, normal and critical notifications respectively. - -Where I<icon> is a path to an image file containing the icon. - -=item B<-lf/nf/cf color> - -Defines the foreground color for low, normal and critical notifications respectively. - -See COLORS for the value format. - -=item B<-lb/nb/cb color> - -Defines the background color for low, normal and critical notifications respectively. - -See COLORS for the value format. - -=item B<-lfr/nfr/cfr color> - -Defines the frame color for low, normal and critical notifications respectively. - -See COLORS for more information - -=item B<-lto/nto/cto secs> - -Defines the timeout time for low, normal and critical notifications -respectively. -See TIME FORMAT for valid times. - -=back - -=head1 DUNSTCTL - -Dunst now contains a command line control command that can be used to interact -with it. It supports all functions previously done only via keyboard shortcuts -but also has a lot of extra functionality. So see more see the dunstctl man -page. - -=head1 HISTORY - -Dunst saves a number of notifications (specified by B<history_length>) in memory. -These notifications can be recalled (i.e. redisplayed) by pressing the -B<history_key> (see the shortcuts section), whether these notifications will -time out like if they have been just send depends on the value of the -B<sticky_history> setting. - -Past notifications are redisplayed in a first-in-last-out order, meaning that -pressing the history key once will bring up the most recent notification that -had been closed/timed out. - -=head1 RULES - -Rules allow the conditional modification of notifications. They are defined by -creating a section in the configuration file that has any name that is not -already used internally (i.e. any name other than 'global', 'experimental', -'frame', 'shortcuts', 'urgency_low', 'urgency_normal' and 'urgency_critical'). - -There are 2 parts in configuring a rule: Defining the filters that control when -a rule should apply and then the actions that should be taken when the rule is -matched. - -=over 4 - -=item B<filtering> - -Notifications can be matched for any of the following attributes: - -=over 4 - -=item C<appname> (discouraged, see desktop_entry) - -The name of the application as reported by the client. Be aware that the name -can often differ depending on the locale used. - -=item C<body> - -The body of the notification - -=item C<category> - -The category of the notification as defined by the notification spec. See -https://developer.gnome.org/notification-spec/#categories - -=item C<desktop_entry> - -GLib based applications export their desktop-entry name. In comparison to the appname, -the desktop-entry won't get localized. - -=item C<icon> - -The icon of the notification in the form of a file path. Can be empty if no icon -is available or a raw icon is used instead. - -=item C<match_transient> - -Match if the notification has been declared as transient by the client or by -some other rule. - -See C<set_transient> for more details about this attribute. - -=item C<msg_urgency> - -Matches the urgency of the notification as set by the client or by some other -rule. - -=item C<stack_tag> - -Matches the stack tag of the notification as set by the client or by some other -rule. - -See set_stack_tag for more information about stack tags. - -=item C<summary> - -Matches the summary, 'title', of the notification. - -=back - -C<msg_urgency> is the urgency of the notification, it is named so to not conflict -with trying to modify the urgency. - -Instead of the appname filter, it's recommended to use the desktop_entry filter. - - -To define a matching rule simply assign the specified value to the value that -should be matched, for example: - - appname="notify-send" - -Matches only messages that were send via notify-send. If multiple filter -expressions are present, all of them have to match for the rule to be applied -(logical AND). - -Shell-like globing is supported. - -=item B<modifying> - -The following attributes can be overridden: - -=over 4 - -=item C<background> - -The background color of the notification. See COLORS for possible values. - -=item C<foreground> - -The background color of the notification. See COLORS for possible values. - -=item C<format> - -Equivalent to the C<format> setting. - -=item C<frame_color> - -The frame color color of the notification. See COLORS for possible values. - -=item C<fullscreen> - -One of show, delay, or pushback. - -This attribute specifies how notifications are handled if a fullscreen window -is focused. By default it's set to show so notifications are being shown. - -Other possible values are delay: Already shown notifications are continued to be -displayed until they are dismissed or time out but new notifications will be -held back and displayed when the focus to the fullscreen window is lost. - -Or pushback which is equivalent to delay with the difference that already -existing notifications are paused and hidden until the focus to the fullscreen -window is lost. - -=item C<new_icon> - -Updates the icon of the notification, it should be a path to a valid image. - -=item C<set_stack_tag> - -Sets the stack tag for the notification, notifications with the same (non-empty) -stack tag will replace each-other so only the newest one is visible. This can be -useful for example in volume or brightness notifications where only want one of -the same type visible. - -The stack tag can be set by the client with the 'synchronous', -'private-synchronous' 'x-canonical-private-synchronous' or the -'x-dunst-stack-tag' hints. - -=item C<set_transient> - -Sets whether the notification is considered transient. -Transient notifications will bypass the idle_threshold setting. - -By default notifications are _not_ considered transient but clients can set the -value of this by specifying the 'transient' hint when sending notifications. - -=item C<timeout> - -Equivalent to the C<timeout> setting in the urgency sections. - -=item C<urgency> - -This sets the notification urgency. - -B<IMPORTANT NOTE>: This currently DOES NOT re-apply the attributes from the -urgency_* sections. The changed urgency will only be visible in rules defined -later. Use C<msg_urgency> to match it. - -=item C<skip_display> - -Setting this to true will prevent the notification from being displayed -initially but will be saved in history for later viewing. - -=back - -As with the filtering attributes, each one corresponds to -the respective notification attribute to be modified. - -As with filtering, to make a rule modify an attribute simply assign it in the -rule definition. - -If the format is set to an empty string, the notification will not be -suppressed. - -=back - -=head2 SCRIPTING - -Within rules you can specify a script to be run every time the rule is matched -by assigning the 'script' option to the name of the script to be run. - -When the script is called details of the notification that triggered it will be -passed via command line parameters in the following order: appname, summary, -body, icon, urgency. - -Where icon is the absolute path to the icon file if there is one and urgency is -one of "LOW", "NORMAL" or "CRITICAL". - -If the notification is suppressed, the script will not be run unless -B<always_run_scripts> is set to true. - -If '~/' occurs at the beginning of the script parameter, it will get replaced by the -users' home directory. If the value is not an absolute path, the directories in the -PATH variable will be searched for an executable of the same name. - -=head1 COLORS - -Colors are interpreted as X11 color values. This includes both verbatim -color names such as "Yellow", "Blue", "White", etc as well as #RGB and #RRGGBB -values. - -You may also specify a transparency component in #RGBA or #RRGGBBAA format. - -B<NOTE>: '#' is interpreted as a comment, to use it the entire value needs to -be in quotes like so: separator_color="#123456" - -=head2 NOTIFY-SEND - -dunst is able to get different colors for a message via notify-send. -In order to do that you have to add a hint via the -h option. -The progress value can be set with a hint, too. - -=over 4 - -=item notify-send -h string:fgcolor:#ff4444 - -=item notify-send -h string:bgcolor:#4444ff -h string:fgcolor:#ff4444 -h string:frcolor:#44ff44 - -=item notify-send -h int:value:42 "Working ..." - -=back - -=head1 ACTIONS - -Dunst allows notifiers (i.e.: programs that send the notifications) to specify -actions. Dunst has support for both displaying indicators for these, and -interacting with these actions. - -If "show_indicators" is true and a notification has an action, an "(A)" will be -prepended to the notification format. Likewise, an "(U)" is prepended to -notifications with URLs. It is possible to interact with notifications that -have actions regardless of this setting, though it may not be obvious which -notifications HAVE actions. - -The "context" keybinding is used to interact with these actions, by showing a -menu of possible actions. This feature requires "dmenu" or a dmenu drop-in -replacement present. - -Alternatively, you can invoke an action with a middle click on the notification. -If there is exactly one associated action, or one is marked as default, that one -is invoked. If there are multiple, the context menu is shown. The same applies -to URLs when there are no actions. - -=head1 TIME FORMAT - -A time can be any decimal integer value suffixed with a time unit. If no unit -given, seconds ("s") is taken as default. - -Time units understood by dunst are "ms", "s", "m", "h" and "d". - -Example time: "1000ms" "10m" - -=head1 MISCELLANEOUS - -Dunst can be paused via the `dunstctl set-paused true` command. To unpause dunst use -`dunstctl set-paused false`. -Alternatively you can send SIGUSR1 and SIGUSR2 to pause and unpause -respectively. For Example: - -=over 4 - -=item killall -SIGUSR1 dunst # pause - -=item killall -SIGUSR2 dunst # resume - -=back - -When paused dunst will not display any notifications but keep all notifications -in a queue. This can for example be wrapped around a screen locker (i3lock, -slock) to prevent flickering of notifications through the lock and to read all -missed notifications after returning to the computer. - -=head1 FILES - -$XDG_CONFIG_HOME/dunst/dunstrc - --or- - -$HOME/.config/dunst/dunstrc - -=head1 AUTHORS - -Written by Sascha Kruse <knopwob@googlemail.com> - -=head1 REPORTING BUGS - -Bugs and suggestions should be reported on GitHub at https://github.com/dunst-project/dunst/issues - -=head1 COPYRIGHT - -Copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) - -If you feel that copyrights are violated, please send me an email. - -=head1 SEE ALSO - -dunstctl(1), dwm(1), dmenu(1), twmn(1), notify-send(1) diff -Nru dunst-1.5.0/docs/internal/release-checklist.md dunst-1.8.1/docs/internal/release-checklist.md --- dunst-1.5.0/docs/internal/release-checklist.md 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/docs/internal/release-checklist.md 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,24 @@ +# Main repo + - [ ] Update the changelog + - [ ] Write release notes (Only if non-patch release) + + - [ ] Verify that the working directory is clean and on the master branch + - [ ] Change the version in the Makefile to "x.x.x (iso-date)" + - [ ] Commit changes (Commit title: `Dunst vX.X.X`) + - [ ] Tag commit, make sure it's an annotated tag (`git tag -a "vX.X.X" -m "Dunst vX.X.X"`) + - [ ] Push commits + - [ ] Push tags + +# Dunst-project.org + - [ ] Run the update script (`REPO=../dunst ./update_new_release.sh OLDVER NEWVER`) + - [ ] Verify that they look fine when rendered (`hugo serve`) + - [ ] Commit changes + - [ ] Run deploy script and push (`./deploy.sh -p`) + +# Main repo + - [ ] Copy release notes to githubs release feature + - [ ] Publish release on github + - [ ] Update maint branch to point to master + + - [ ] Update Makefile version to "x.x.x-non-git" + - [ ] Commit & push diff -Nru dunst-1.5.0/dunstctl dunst-1.8.1/dunstctl --- dunst-1.5.0/dunstctl 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/dunstctl 2022-03-02 10:55:25.000000000 +0000 @@ -11,20 +11,26 @@ die(){ printf "%s\n" "${1}" >&2; exit 1; } show_help() { + # Below, each line starts with a tab character cat <<-EOH Usage: dunstctl <command> [parameters]" Commands: - action Perform the default action, or open the - context menu of the notification at the - given position - close Close the last notification - close-all Close the all notifications - context Open context menu - history-pop Pop one notification from history - is-paused Check if dunst is running or paused - set-paused [true|false|toggle] Set the pause status - debug Print debugging information - help Show this help + action Perform the default action, or open the + context menu of the notification at the + given position + close Close the last notification + close-all Close the all notifications + context Open context menu + count [displayed|history|waiting] Show the number of notifications + history Display notification history (in JSON) + history-pop [ID] Pop the latest notification from + history or optionally the + notification with given ID. + is-paused Check if dunst is running or paused + set-paused [true|false|toggle] Set the pause status + rule name [enable|disable|toggle] Enable or disable a rule by its name + debug Print debugging information + help Show this help EOH } dbus_send_checked() { @@ -50,7 +56,7 @@ case "${1:-}" in "action") - method_call "${DBUS_IFAC_DUNST}.NotificationAction" "int32:${2:-0}" >/dev/null + method_call "${DBUS_IFAC_DUNST}.NotificationAction" "uint32:${2:-0}" >/dev/null ;; "close") method_call "${DBUS_IFAC_DUNST}.NotificationCloseLast" >/dev/null @@ -61,8 +67,27 @@ "context") method_call "${DBUS_IFAC_DUNST}.ContextMenuCall" >/dev/null ;; + "count") + [ $# -eq 1 ] || [ "${2}" = "displayed" ] || [ "${2}" = "history" ] || [ "${2}" = "waiting" ] \ + || die "Please give either 'displayed', 'history', 'waiting' or none as count parameter." + if [ $# -eq 1 ]; then + property_get waitingLength | ( read -r _ _ waiting; printf " Waiting: %s\n" "${waiting}" ) + property_get displayedLength | ( read -r _ _ displayed; printf " Currently displayed: %s\n" "${displayed}" ) + property_get historyLength | ( read -r _ _ history; printf " History: %s\n" "${history}") + else + property_get ${2}Length | ( read -r _ _ notifications; printf "%s\n" "${notifications}"; ) + fi + ;; "history-pop") - method_call "${DBUS_IFAC_DUNST}.NotificationShow" >/dev/null + if [ "$#" -eq 1 ] + then + method_call "${DBUS_IFAC_DUNST}.NotificationShow" >/dev/null + elif [ "$#" -eq 2 ] + then + method_call "${DBUS_IFAC_DUNST}.NotificationPopHistory" "uint32:${2:-0}" >/dev/null + else + die "Please pass the right number of arguments. History-pop takes 0 or 1 arguments" + fi ;; "is-paused") property_get paused | ( read -r _ _ paused; printf "%s\n" "${paused}"; ) @@ -83,6 +108,17 @@ property_set paused variant:boolean:"$2" fi ;; + "rule") + [ "${2:-}" ] \ + || die "No rule name parameter specified. Please give the rule name" + state=nope + [ "${3}" = "disable" ] && state=0 + [ "${3}" = "enable" ] && state=1 + [ "${3}" = "toggle" ] && state=2 + [ "${state}" = "nope" ] \ + && die "No valid rule state parameter specified. Please give either 'enable', 'disable' or 'toggle'" + method_call "${DBUS_IFAC_DUNST}.RuleEnable" "string:${2:-1}" "int32:${state}" >/dev/null + ;; "help"|"--help"|"-h") show_help ;; @@ -101,6 +137,10 @@ dbus-send --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_DUNST}.Ping" >/dev/null 2>/dev/null \ || die "Dunst controlling interface not available. Is the version too old?" ;; + "history") + busctl --user --json=pretty -l --no-pager call org.freedesktop.Notifications /org/freedesktop/Notifications org.dunstproject.cmd0 NotificationListHistory 2>/dev/null \ + || die "Dunst is not running." + ;; "") die "dunstctl: No command specified. Please consult the usage." ;; @@ -108,3 +148,4 @@ die "dunstctl: unrecognized command '${1:-}'. Please consult the usage." ;; esac +# vim: noexpandtab diff -Nru dunst-1.5.0/dunstify.c dunst-1.8.1/dunstify.c --- dunst-1.5.0/dunstify.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/dunstify.c 2022-03-02 10:55:25.000000000 +0000 @@ -29,7 +29,7 @@ { "urgency", 'u', 0, G_OPTION_ARG_STRING, &urgency_str, "The urgency of this notification", "URG" }, { "hints", 'h', 0, G_OPTION_ARG_STRING_ARRAY, &hint_strs, "User specified hints", "HINT" }, { "action", 'A', 0, G_OPTION_ARG_STRING_ARRAY, &action_strs, "Actions the user can invoke", "ACTION" }, - { "timeout", 't', 0, G_OPTION_ARG_INT, &timeout, "The time until the notification expires", "TIMEOUT" }, + { "timeout", 't', 0, G_OPTION_ARG_INT, &timeout, "The time in milliseconds until the notification expires", "TIMEOUT" }, { "icon", 'i', 0, G_OPTION_ARG_STRING, &icon, "An Icon that should be displayed with the notification", "ICON" }, { "raw_icon", 'I', 0, G_OPTION_ARG_STRING, &raw_icon_path, "Path to the icon to be sent as raw image data", "RAW_ICON"}, { "capabilities", 'c', 0, G_OPTION_ARG_NONE, &capabilities, "Print the server capabilities and exit", NULL}, @@ -81,7 +81,7 @@ * cases. This function gets the specified argv element ignoring the first * terminator. * - * See https://developer.gnome.org/glib/stable/glib-Commandline-option-parser.html#g-option-context-parse for details + * See https://docs.gtk.org/glib/method.OptionContext.parse.html for details */ char *get_argv(char *argv[], int index) { @@ -127,6 +127,11 @@ die(0); } + if (*appname == '\0') { + g_printerr("Provided appname was empty\n"); + die(1); + } + int n_args = count_args(argv, argc); if (n_args < 2 && close_id < 1) { g_printerr("I need at least a summary\n"); @@ -234,7 +239,7 @@ char *label = strchr(str, ','); if (!label || *(label+1) == '\0') { - g_printerr("Malformed action. Excpected \"action,label\", got \"%s\"", str); + g_printerr("Malformed action. Expected \"action,label\", got \"%s\"", str); return; } @@ -359,4 +364,4 @@ die(0); } -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/dunstrc dunst-1.8.1/dunstrc --- dunst-1.5.0/dunstrc 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/dunstrc 2022-03-02 10:55:25.000000000 +0000 @@ -1,3 +1,5 @@ +# See dunst(5) for all configuration options + [global] ### Display ### @@ -15,39 +17,59 @@ # # If this option is set to mouse or keyboard, the monitor option # will be ignored. - follow = mouse + follow = none - # The geometry of the window: - # [{width}]x{height}[+/-{x}+/-{y}] - # The geometry of the message window. - # The height is measured in number of notifications everything else - # in pixels. If the width is omitted but the height is given - # ("-geometry x2"), the message window expands over the whole screen - # (dmenu-like). If width is 0, the window expands to the longest - # message displayed. A positive x is measured from the left, a - # negative from the right side of the screen. Y is measured from - # the top and down respectively. - # The width can be negative. In this case the actual width is the - # screen width minus the width defined in within the geometry option. - geometry = "300x5-30+20" + ### Geometry ### - # Show how many messages are currently hidden (because of geometry). - indicate_hidden = yes + # dynamic width from 0 to 300 + # width = (0, 300) + # constant width of 300 + width = 300 + + # The maximum height of a single notification, excluding the frame. + height = 300 + + # Position the notification in the top right corner + origin = top-right + + # Offset from the origin + offset = 10x50 + + # Scale factor. It is auto-detected if value is 0. + scale = 0 + + # Maximum number of notification (0 means no limit) + notification_limit = 0 + + ### Progress bar ### + + # Turn on the progess bar. It appears when a progress hint is passed with + # for example dunstify -h int:value:12 + progress_bar = true + + # Set the progress bar height. This includes the frame, so make sure + # it's at least twice as big as the frame width. + progress_bar_height = 10 + + # Set the frame width of the progress bar + progress_bar_frame_width = 1 + + # Set the minimum width for the progress bar + progress_bar_min_width = 150 - # Shrink window if it's smaller than the width. Will be ignored if - # width is 0. - shrink = no + # Set the maximum width for the progress bar + progress_bar_max_width = 300 + + + # Show how many messages are currently hidden (because of + # notification_limit). + indicate_hidden = yes # The transparency of the window. Range: [0; 100]. # This option will only work if a compositing window manager is - # present (e.g. xcompmgr, compiz, etc.). + # present (e.g. xcompmgr, compiz, etc.). (X11 only) transparency = 0 - # The height of the entire notification. If the height is smaller - # than the font height and padding combined, it will be raised - # to the font height and padding. - notification_height = 0 - # Draw a line of "separator_height" pixel height between two # notifications. # Set to 0 to disable. @@ -59,6 +81,9 @@ # Horizontal padding. horizontal_padding = 8 + # Padding between text and icon. + text_icon_padding = 0 + # Defines width in pixels of frame around the notification window. # Set to 0 to disable. frame_width = 3 @@ -82,7 +107,7 @@ # Set to 0 to disable. # A client can set the 'transient' hint to bypass this. See the rules # section for how to disable this if necessary - idle_threshold = 120 + # idle_threshold = 120 ### Text ### @@ -100,7 +125,7 @@ # <u>underline</u> # # For a complete reference see - # <https://developer.gnome.org/pango/stable/pango-Markup.html>. + # <https://docs.gtk.org/Pango/pango_markup.html>. # # strip: This setting is provided for compatibility with some broken # clients that send markup even though it's not enabled on the @@ -141,11 +166,7 @@ # Set to -1 to disable. show_age_threshold = 60 - # Split notifications into multiple lines if they don't fit into - # geometry. - word_wrap = yes - - # When word_wrap is set to no, specify where to make an ellipsis in long lines. + # Specify where to make an ellipsis in long lines. # Possible values are "start", "middle" and "end". ellipsize = middle @@ -163,7 +184,7 @@ ### Icons ### - # Align icons left/right/off + # Align icons left/right/top/off icon_position = left # Scale small icons up to this size, set to 0 to disable. Helpful @@ -192,7 +213,7 @@ dmenu = /usr/bin/dmenu -p dunst: # Browser for opening urls in context menu. - browser = /usr/bin/firefox -new-tab + browser = /usr/bin/xdg-open # Always run rule-defined scripts, even if the notification is suppressed always_run_script = true @@ -203,20 +224,6 @@ # Define the class of the windows spawned by dunst class = Dunst - # Print a notification on startup. - # This is mainly for error detection, since dbus (re-)starts dunst - # automatically after a crash. - startup_notification = false - - # Manage dunst's desire for talking - # Can be one of the following values: - # crit: Critical features. Dunst aborts - # warn: Only non-fatal warnings - # mesg: Important Messages - # info: all unimportant stuff - # debug: all less than unimportant stuff - verbosity = mesg - # Define the corner radius of the notification window # in pixel size. If the radius is 0, you have no rounded # corners. @@ -226,10 +233,20 @@ # Ignore the dbus closeNotification message. # Useful to enforce the timeout set by dunst configuration. Without this - # parameter, an application may close the notification sent before the + # parameter, an application may close the notification sent before the # user defined timeout. ignore_dbusclose = false + ### Wayland ### + # These settings are Wayland-specific. They have no effect when using X11 + + # Uncomment this if you want to let notications appear under fullscreen + # applications (default: overlay) + # layer = top + + # Set this to true to use X11 output on Wayland. + force_xwayland = false + ### Legacy # Use the Xinerama extension instead of RandR for multi-monitor support. @@ -247,10 +264,14 @@ # Defines list of actions for each mouse event # Possible values are: # * none: Don't do anything. - # * do_action: If the notification has exactly one action, or one is marked as default, - # invoke it. If there are multiple and no default, open the context menu. + # * do_action: Invoke the action determined by the action_name rule. If there is no + # such action, open the context menu. + # * open_url: If the notification has exactly one url, open it. If there are multiple + # ones, open the context menu. # * close_current: Close current notification. # * close_all: Close all notifications. + # * context: Open context menu for the notification. + # * context_all: Open context menu for all notifications. # These values can be strung together for each mouse event, and # will be executed in sequence. mouse_left_click = close_current @@ -267,27 +288,6 @@ # where there are multiple screens with very different dpi values. per_monitor_dpi = false -[shortcuts] - - # Shortcuts are specified as [modifier+][modifier+]...key - # Available modifiers are "ctrl", "mod1" (the alt-key), "mod2", - # "mod3" and "mod4" (windows-key). - # Xev might be helpful to find names for keys. - - # Close notification. - close = ctrl+space - - # Close all notifications. - close_all = ctrl+shift+space - - # Redisplay last message(s). - # On the US keyboard layout "grave" is normally above TAB and left - # of "1". Make sure this key actually exists on your keyboard layout, - # e.g. check output of 'xmodmap -pke' - history = ctrl+grave - - # Context menu. - context = ctrl+shift+period [urgency_low] # IMPORTANT: colors have to be defined in quotation marks. @@ -296,14 +296,14 @@ foreground = "#888888" timeout = 10 # Icon for notifications with low urgency, uncomment to enable - #icon = /path/to/icon + #default_icon = /path/to/icon [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 # Icon for notifications with normal urgency, uncomment to enable - #icon = /path/to/icon + #default_icon = /path/to/icon [urgency_critical] background = "#900000" @@ -311,7 +311,7 @@ frame_color = "#ff0000" timeout = 0 # Icon for notifications with critical urgency, uncomment to enable - #icon = /path/to/icon + #default_icon = /path/to/icon # Every section that isn't one of the above is interpreted as a rules to # override settings for certain messages. @@ -336,8 +336,17 @@ # new_icon # set_stack_tag # set_transient +# set_category # timeout # urgency +# icon_position +# skip_display +# history_ignore +# action_name +# word_wrap +# ellipsize +# alignment +# hide_text # # Shell-like globbing will get expanded. # @@ -352,8 +361,6 @@ # script appname summary body icon urgency # where urgency can be "LOW", "NORMAL" or "CRITICAL". # -# NOTE: if you don't want a notification to be displayed, set the format -# to "". # NOTE: It might be helpful to run dunst -print in a terminal in order # to find fitting options for rules. @@ -392,7 +399,7 @@ #[ignore] # # This notification will not be displayed # summary = "foobar" -# format = "" +# skip_display = true #[history-ignore] # # This notification will not be saved in history diff -Nru dunst-1.5.0/.github/ISSUE_TEMPLATE.md dunst-1.8.1/.github/ISSUE_TEMPLATE.md --- dunst-1.5.0/.github/ISSUE_TEMPLATE.md 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/.github/ISSUE_TEMPLATE.md 2022-03-02 10:55:25.000000000 +0000 @@ -2,23 +2,28 @@ If you want to report bugs, it's the best to get something reproducible! -These program calls might help: - -While the notification gets sent: -`dbus-monitor path=/org/freedesktop/Notifications` +Try looking at the output of dunst by running it from the command line: +`pkill dunst; dunst` If dunst segfaults (please install the debug symbols or install dunst manually again): `gdb -ex run dunst -ex bt` * ISSUE DESCRIPTION GOES BELOW THIS LINE * --> - +### Issue description ### Installation info -<!-- If your version dates before 1.2, please rule out, that the behavior is fixed in master already --> - - Version: `<!-- output of dunst -v -->` - Install type: `<!-- [package|manually|...] -->` -- Distro and version: ` ` +- Window manager / Desktop environment: `<!-- -->` + +<details> +<summary>Minimal dunstrc</summary> +<!-- Try creating a minimal dunstrc that still reproduces the issue and paste it below --> + +```ini +# Dunstrc here +``` +</details> diff -Nru dunst-1.5.0/.github/workflows/main.yml dunst-1.8.1/.github/workflows/main.yml --- dunst-1.5.0/.github/workflows/main.yml 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/.github/workflows/main.yml 2022-03-02 10:55:25.000000000 +0000 @@ -2,8 +2,6 @@ on: push: - branches: - - master pull_request: branches: - master @@ -19,12 +17,15 @@ - alpine - archlinux - debian-stretch + - debian-buster - fedora - ubuntu-xenial - ubuntu-bionic + - ubuntu-focal env: CC: ${{ matrix.CC }} + EXTRA_CFLAGS: "-Werror" steps: - uses: actions/checkout@v2 with: @@ -41,7 +42,7 @@ # With git<2.18 it downloads the code via API and does not clone it via git :facepalm: # To succeed the tests, we have to manually replace the VERSION macro - name: fix version number for old distros - run: 'sed -i "s/1.4.1-non-git/1.4.1-ci-oldgit-$GITHUB_SHA/" Makefile' + run: 'sed -i "s/-non-git/-ci-oldgit-$GITHUB_SHA/" Makefile' if: " (matrix.distro == 'ubuntu-bionic' || matrix.distro == 'ubuntu-xenial' || matrix.distro == 'debian-stretch')" - name: build @@ -78,4 +79,23 @@ runs-on: ubuntu-latest container: + image: dunst/ci:${{ matrix.distro }} + + doxygen: + strategy: + matrix: + distro: + - misc-doxygen + + steps: + - uses: actions/checkout@v2 + with: + # Clone the whole branch, we have to fetch tags later + fetch-depth: 0 + + - name: doxygen + run: make -j doc-doxygen + + runs-on: ubuntu-latest + container: image: dunst/ci:${{ matrix.distro }} diff -Nru dunst-1.5.0/.gitignore dunst-1.8.1/.gitignore --- dunst-1.5.0/.gitignore 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/.gitignore 2022-03-02 10:55:25.000000000 +0000 @@ -9,6 +9,7 @@ vgcore.* /docs/*.1 +/docs/*.5 /docs/internal/coverage /docs/internal/html /dunst diff -Nru dunst-1.5.0/HACKING.md dunst-1.8.1/HACKING.md --- dunst-1.5.0/HACKING.md 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/HACKING.md 2022-03-02 10:55:25.000000000 +0000 @@ -1,6 +1,6 @@ # Important notes on the code -**You can generate an internal overview with doxygen. For this, use `make doc-doxygen` and you'll find an internal overview of all functions and symbols in `docs/internal/html`.** +**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.** # Comments @@ -15,6 +15,10 @@ - Add the comments to the prototype. Doxygen will merge the protoype and implementation documentation anyways. Except for **static** methods, add the documentation header to the implementation and *not to the prototype*. - Member documentation should happen with `/**<` and should span to the right side of the member +- Test files that have the same name as a file in src/\* can include the + associated .c file. This is because they are being compiled INSTEAD of the src + file. + ## Log messages @@ -33,6 +37,8 @@ - `LOG_C` (CRITICAL): - The program cannot continue to work. It is used in the wrong manner or some outer conditions are not met. - e.g.: `-config` parameter value is unreadable file +- `DIE` (CRITICAL): + - A shorthand for `LOG_C` and terminating the program after. This does not dump the core (unlike `LOG_E`). - `LOG_W` (WARNING): - Something is not in shape, but it's recoverable. - e.g.: A value is not parsable in the config file, which will default. diff -Nru dunst-1.5.0/main.c dunst-1.8.1/main.c --- dunst-1.5.0/main.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/main.c 2022-03-02 10:55:25.000000000 +0000 @@ -4,4 +4,4 @@ { return dunst_main(argc, argv); } -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/Makefile dunst-1.8.1/Makefile --- dunst-1.5.0/Makefile 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/Makefile 2022-03-02 10:55:25.000000000 +0000 @@ -3,33 +3,18 @@ include config.mk -VERSION := "1.5.0 (2020-07-23)" +VERSION := "1.8.1 (2022-03-02)" ifneq ($(wildcard ./.git/),) VERSION := $(shell ${GIT} describe --tags) endif -ifeq (,${SYSTEMD}) -# Check for systemctl to avoid discrepancies on systems, where -# systemd is installed, but systemd.pc is in another package -systemctl := $(shell command -v ${SYSTEMCTL} >/dev/null && echo systemctl) -ifeq (systemctl,${systemctl}) -SYSTEMD := 1 -else -SYSTEMD := 0 -endif -endif - -SERVICEDIR_DBUS ?= $(shell $(PKG_CONFIG) dbus-1 --variable=session_bus_services_dir) -SERVICEDIR_DBUS := ${SERVICEDIR_DBUS} -ifeq (,${SERVICEDIR_DBUS}) -$(error "Failed to query $(PKG_CONFIG) for package 'dbus-1'!") -endif +SYSTEMD ?= $(shell $(PKG_CONFIG) --silence-errors ${SYSTEMDAEMON} || echo 0) -ifneq (0,${SYSTEMD}) -SERVICEDIR_SYSTEMD ?= $(shell $(PKG_CONFIG) systemd --variable=systemduserunitdir) -SERVICEDIR_SYSTEMD := ${SERVICEDIR_SYSTEMD} -ifeq (,${SERVICEDIR_SYSTEMD}) -$(error "Failed to query $(PKG_CONFIG) for package 'systemd'!") +ifneq (0,${WAYLAND}) +DATA_DIR_WAYLAND_PROTOCOLS ?= $(shell $(PKG_CONFIG) wayland-protocols --variable=pkgdatadir) +DATA_DIR_WAYLAND_PROTOCOLS := ${DATA_DIR_WAYLAND_PROTOCOLS} +ifeq (,${DATA_DIR_WAYLAND_PROTOCOLS}) +$(warning "Failed to query $(PKG_CONFIG) for package 'wayland-protocols'!") endif endif @@ -42,10 +27,19 @@ endif endif +SYSCONF_FORCE_NEW ?= $(shell [ -f ${DESTDIR}${SYSCONFFILE} ] || echo 1) + CFLAGS := ${DEFAULT_CPPFLAGS} ${CPPFLAGS} ${DEFAULT_CFLAGS} ${CFLAGS} ${INCS} -MMD -MP LDFLAGS := ${DEFAULT_LDFLAGS} ${LDFLAGS} ${LIBS} + +ifeq (0,${WAYLAND}) +# without wayland support +SRC := $(sort $(shell ${FIND} src/ -not \( -path src/wayland -prune \) -name '*.c')) +else +# with Wayland support SRC := $(sort $(shell ${FIND} src/ -name '*.c')) +endif OBJ := ${SRC:.c=.o} TEST_SRC := $(sort $(shell ${FIND} test/ -name '*.c')) TEST_OBJ := $(TEST_SRC:.c=.o) @@ -75,7 +69,9 @@ .PHONY: test test-valgrind test-coverage test: test/test clean-coverage-run - ./test/test -v + # Make sure an error code is returned when the test fails + /usr/bin/env bash -c 'set -euo pipefail;\ + ./test/test -v | ./test/greenest.awk ' test-valgrind: test/test ${VALGRIND} \ @@ -106,19 +102,21 @@ ${CC} -o ${@} ${TEST_OBJ} $(filter-out ${TEST_OBJ:test/%=src/%},${OBJ}) ${CFLAGS} ${LDFLAGS} .PHONY: doc doc-doxygen -doc: docs/dunst.1 docs/dunstctl.1 +doc: docs/dunst.1 docs/dunst.5 docs/dunstctl.1 # Can't dedup this as we need to explicitly provide the name and title text to # pod2man :( -docs/dunst.1: docs/dunst.pod - ${POD2MAN} --name=dunst -c "Dunst Reference" --section=1 --release=${VERSION} $< > $@ +docs/dunst.1: docs/dunst.1.pod + ${SED} "s|##SYSCONFDIR##|${SYSCONFDIR}|" $< | ${POD2MAN} --name=dunst -c "Dunst Reference" --section=1 --release=${VERSION} > $@ +docs/dunst.5: docs/dunst.5.pod + ${POD2MAN} --name=dunst -c "Dunst Reference" --section=5 --release=${VERSION} $< > $@ docs/dunstctl.1: docs/dunstctl.pod ${POD2MAN} --name=dunstctl -c "dunstctl reference" --section=1 --release=${VERSION} $< > $@ doc-doxygen: ${DOXYGEN} docs/internal/Doxyfile -.PHONY: service service-dbus service-systemd +.PHONY: service service-dbus service-systemd wayland-protocols service: service-dbus service-dbus: @${SED} "s|##PREFIX##|$(PREFIX)|" org.knopwob.dunst.service.in > org.knopwob.dunst.service @@ -128,7 +126,23 @@ @${SED} "s|##PREFIX##|$(PREFIX)|" dunst.systemd.service.in > dunst.systemd.service endif -.PHONY: clean clean-dunst clean-dunstify clean-doc clean-tests clean-coverage clean-coverage-run +ifneq (0,${WAYLAND}) +wayland-protocols: src/wayland/protocols/wlr-layer-shell-unstable-v1.xml src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1.xml + # TODO: write this shorter + mkdir -p src/wayland/protocols + wayland-scanner private-code ${DATA_DIR_WAYLAND_PROTOCOLS}/stable/xdg-shell/xdg-shell.xml src/wayland/protocols/xdg-shell.h + wayland-scanner client-header ${DATA_DIR_WAYLAND_PROTOCOLS}/stable/xdg-shell/xdg-shell.xml src/wayland/protocols/xdg-shell-client-header.h + wayland-scanner client-header ${DATA_DIR_WAYLAND_PROTOCOLS}/unstable/xdg-output/xdg-output-unstable-v1.xml src/wayland/protocols/xdg-output-unstable-v1-client-header.h + wayland-scanner private-code ${DATA_DIR_WAYLAND_PROTOCOLS}/unstable/xdg-output/xdg-output-unstable-v1.xml src/wayland/protocols/xdg-output-unstable-v1.h + wayland-scanner client-header src/wayland/protocols/wlr-layer-shell-unstable-v1.xml src/wayland/protocols/wlr-layer-shell-unstable-v1-client-header.h + wayland-scanner private-code src/wayland/protocols/wlr-layer-shell-unstable-v1.xml src/wayland/protocols/wlr-layer-shell-unstable-v1.h + wayland-scanner client-header src/wayland/protocols/idle.xml src/wayland/protocols/idle-client-header.h + wayland-scanner private-code src/wayland/protocols/idle.xml src/wayland/protocols/idle.h + wayland-scanner client-header src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1.xml src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1-client-header.h + wayland-scanner private-code src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1.xml src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1.h +endif + +.PHONY: clean clean-dunst clean-dunstify clean-doc clean-tests clean-coverage clean-coverage-run clean-wayland-protocols clean: clean-dunst clean-dunstify clean-doc clean-tests clean-coverage clean-coverage-run clean-dunst: @@ -143,6 +157,7 @@ clean-doc: rm -f docs/dunst.1 + rm -f docs/dunst.5 rm -f docs/dunstctl.1 rm -fr docs/internal/html rm -fr docs/internal/coverage @@ -158,22 +173,29 @@ ${FIND} . -type f -name '*.gcov' -delete ${FIND} . -type f -name '*.gcda' -delete -.PHONY: install install-dunst install-dunstctl install-doc \ +clean-wayland-protocols: + rm -f src/wayland/protocols/*.h + +.PHONY: install install-dunst install-dunstctl install-dunstrc \ install-service install-service-dbus install-service-systemd \ - uninstall uninstall-dunstctl \ - uninstall-service uninstall-service-dbus uninstall-service-systemd -install: install-dunst install-dunstctl install-doc install-service install-dunstify + uninstall uninstall-dunstctl uninstall-dunstrc \ + uninstall-service uninstall-service-dbus uninstall-service-systemd \ + uninstall-keepconf uninstall-purge +install: install-dunst install-dunstctl install-dunstrc install-service install-dunstify install-dunst: dunst doc install -Dm755 dunst ${DESTDIR}${BINDIR}/dunst install -Dm644 docs/dunst.1 ${DESTDIR}${MANPREFIX}/man1/dunst.1 + install -Dm644 docs/dunst.5 ${DESTDIR}${MANPREFIX}/man5/dunst.5 install -Dm644 docs/dunstctl.1 ${DESTDIR}${MANPREFIX}/man1/dunstctl.1 install-dunstctl: dunstctl install -Dm755 dunstctl ${DESTDIR}${BINDIR}/dunstctl -install-doc: - install -Dm644 dunstrc ${DESTDIR}${DATADIR}/dunst/dunstrc +ifeq (1,${SYSCONF_FORCE_NEW}) +install-dunstrc: + install -Dm644 dunstrc ${DESTDIR}${SYSCONFFILE} +endif install-service: install-service-dbus install-service-dbus: service-dbus @@ -187,12 +209,18 @@ install-dunstify: dunstify install -Dm755 dunstify ${DESTDIR}${BINDIR}/dunstify -uninstall: uninstall-service uninstall-dunstctl +uninstall: uninstall-keepconf +uninstall-purge: uninstall-keepconf uninstall-dunstrc +uninstall-keepconf: uninstall-service uninstall-dunstctl rm -f ${DESTDIR}${BINDIR}/dunst rm -f ${DESTDIR}${BINDIR}/dunstify rm -f ${DESTDIR}${MANPREFIX}/man1/dunst.1 + rm -f ${DESTDIR}${MANPREFIX}/man5/dunst.5 rm -f ${DESTDIR}${MANPREFIX}/man1/dunstctl.1 - rm -rf ${DESTDIR}${DATADIR}/dunst + +uninstall-dunstrc: + rm -f ${DESTDIR}${SYSCONFFILE} + rmdir --ignore-fail-on-non-empty ${DESTDIR}${SYSCONFDIR}/dunst uninstall-dunstctl: rm -f ${DESTDIR}${BINDIR}/dunstctl diff -Nru dunst-1.5.0/README.md dunst-1.8.1/README.md --- dunst-1.5.0/README.md 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/README.md 2022-03-02 10:55:25.000000000 +0000 @@ -1,31 +1,96 @@ [![main](https://github.com/dunst-project/dunst/workflows/main/badge.svg)](https://github.com/dunst-project/dunst/actions?query=workflow%3Amain) [![codecov](https://codecov.io/gh/dunst-project/dunst/branch/master/graph/badge.svg)](https://codecov.io/gh/dunst-project/dunst) -## Dunst +# Dunst -* [Wiki][wiki] -* [Description](#description) -* [Compiling](#compiling) +<i>A highly configurable and lightweight notification daemon.</i> + +![music](contrib/screenshots/music.png) + +## Table of Contents + +* [Features](#features) +* [Building](#building) +* [Documentation](#documentation) +* [Troubleshooting](#troubleshooting) * [Copyright](#copyright) -## Description +# Features + +## ⚙️ Highly customizable + +Customize fonts, icons, timeouts, and more. Are you unhappy with the default +shortcuts and colors? No worries, you can change these all with a simple +configuration file tweak. + +_click the images to see the dunstrc_ + +<a href="https://gist.github.com/NNBnh/5f6e601a6a82a6ed43b1959698758141"> +<img alt="screenshot1" src="contrib/screenshots/screenshot1_cut.png"> +</a> + +<a href="https://gist.github.com/fwSmit/9127d988b07bcec9d869f2c927d0f616"> +<img alt="screenshot2" src="contrib/screenshots/screenshot2_cut.png"> +</a> + +## 📜 Scripting + +<a href="https://gitlab.manjaro.org/profiles-and-settings/manjaro-theme-settings/-/blob/master/skel/.config/dunst/dunstrc"> +<img alt="screenshot_urgency" src="contrib/screenshots/screenshot_urgency.png"> +</a> + +Run custom scripts on notifications matching a specified pattern. Have espeak +read out your notifications, or play a song when your significant other signs on +in pidgin! + +## 📋 Rules + +Change the look or behavior of notifications matching a specified pattern. You +could use this to change the color of message notifications from your favorite +jabber buddies, or to prevent important work email notifications from +disappearing until you manually dismiss them. -Dunst is a highly configurable and lightweight notification daemon. +## ⏸️ Pause +If you want to take a break and not receive any notifications for a while, just +pause dunst. All notifications will be saved for you to catch up +later. + +## 🕘 History + +Catch an unread notification disappearing from the corner of your eye? Just tap +a keyboard shortcut to replay the last notification, or continue tapping to see +your notification history. + +# Documentation + +Most documentation can be found in dunst's man pages. In +[**dunst(1)**](docs/dunst.1.pod) contains some general instructions on how +to run dunst and in +[**dunst(5)**](docs/dunst.5.pod) all of dunst's configuration options are +explained. + +On the dunst [wiki][wiki] you can find guides and installation instructions and +on the dunst [website][website] there is a [FAQ][FAQ] with common issues. ## Installation -### Dependencies +Dunst is available in many package repositories. If it's not available in your +distro's repositories, don't worry, it's not hard to build it yourself. -Dunst has a number of build dependencies that must be present before attempting configuration. The names are different depending on [distribution](https://github.com/dunst-project/dunst/wiki/Dependencies): +### Dependencies -- dbus +- dbus (runtime) - libxinerama - libxrandr - libxss - glib - pango/cairo -- libgtk-3-dev -- libnotify (for dunstify only) +- libnotify (optional, for dunstify) +- wayland-client (can build without, see [make parameters](#make-parameters)) +- wayland-protocols (optional, for recompiling protocols) +- xdg-utils (optional, xdg-open is the default 'browser' for opening URLs) + +The names will be different depending on your [distribution](https://github.com/dunst-project/dunst/wiki/Dependencies). ### Building @@ -38,36 +103,84 @@ ### Make parameters +- `DESTDIR=<PATH>`: Set the destination directory of the installation. (Default: `/`) - `PREFIX=<PATH>`: Set the prefix of the installation. (Default: `/usr/local`) - `BINDIR=<PATH>`: Set the `dunst` executable's path (Default: `${PREFIX}/bin`) - `DATADIR=<PATH>`: Set the path for shared files. (Default: `${PREFIX}/share`) +- `SYSCONFDIR=<PATH>`: Set the base directory for system config files. (Default: `${PREFIX}/etc/xdg`) +- `SYSCONFFILE=<PATH>`: Set the absolute path to which the default dunstrc shall be installed. (Default: `${SYSCONFDIR}/dunst/dunstrc`) +- `SYSCONF_FORCE_NEW=(0|1)`: Overwrite existing `${SYSCONFFILE}`. (Default: 0 (don't overwrite)) - `MANDIR=<PATH>`: Set the prefix of the manpage. (Default: `${DATADIR}/man`) -- `SYSTEMD=(0|1)`: Enable/Disable the systemd unit. (Default: detected via `pkg-config`) -- `SERVICEDIR_SYSTEMD=<PATH>`: The path to put the systemd user service file. Unused, if `SYSTEMD=0`. (Default: detected via `pkg-config`) -- `SERVICEDIR_DBUS=<PATH>`: The path to put the dbus service file. (Default: detected via `pkg-config`) +- `SYSTEMD=(0|1)`: Disable/Enable the systemd unit. (Default: autodetect systemd) +- `WAYLAND=(0|1)`: Disable/Enable wayland support. (Default: 1 (enabled)) +- `SERVICEDIR_SYSTEMD=<PATH>`: The path to put the systemd user service file. Unused, if `SYSTEMD=0`. (Default: `${PREFIX}/lib/systemd/user`) +- `SERVICEDIR_DBUS=<PATH>`: The path to put the dbus service file. (Default: `${DATADIR}/dbus-1/services`) +- `EXTRA_CFLAGS=<FLAGS>`: Additional flags for the compiler. **Make sure to run all make calls with the same parameter set. So when building with `make PREFIX=/usr`, you have to install it with `make PREFIX=/usr install`, too.** -Checkout the [wiki][wiki] for more information. +**Notes on default of XDG_CONFIG_DIRS** + +Dunst uses a different default (${SYSCONFDIR}) for XDG_CONFIG_DIRS at runtime. +This is a slight digression from the recommended value in the XDG Base Directory +Specification (/etc/xdg), because the default config file gets installed to +${SYSCONFDIR/dunst/dunstrc} to avoid conflicts with /etc/xdg/dunst/dunstrc which +might have been installed from a distribution repository. If you do want dunst +to use the spec's recommended default, set XDG_CONFIG_DIR=/etc/xdg at runtime or +SYSCONFDIR=/etc/xdg at compile time. + +**Notes on SYSCONFFILE** + +Changing SYSCONFFILE does not affect the search for config files, meaning it +will not take effect if you choose to install dunstrc to a location that cannot +be found by the algorithm outlined in the FILES section of dunst(1). + +`make install` will not overwrite an already existing ${SYSCONFFILE} (i.e. +/usr/local/etc/xdg/dunst/dunstrc), see SYSCONF_FORCE_NEW above. This is so you +do not lose local changes to said file on upgrade. However, it is recommended +to leave that file untouched and use a more important config location to +override settings, see the FILES section in dunst(1) for more details. + +`make uninstall` will not remove ${SYSCONFFILE}, use `make uninstall-purge` if +you do want it removed as well. + +## Troubleshooting + +### Cannot set settings via command line + +This functionality was removed during the refactor. It might be re-added later +in some form. See [#940](https://github.com/dunst-project/dunst/issues/940) for +details. ## Bug reports -Please use the [issue tracker][issue-tracker] provided by GitHub to send us bug reports or feature requests. You can also join us on the IRC channel `#dunst` on Freenode. +Please use the [issue tracker][issue-tracker] provided by GitHub to send us bug reports or feature requests. + +## Screenshots + +<a href="https://gist.github.com/MCotocel/2b34486ae59ccda4319fcb93454d212c"> +<img alt="screenshot3" src="contrib/screenshots/screenshot3_cut.png"> +</a> + +<a href="https://gitlab.manjaro.org/profiles-and-settings/manjaro-theme-settings/-/blob/master/skel/.config/dunst/dunstrc"> +<img alt="progress" src="https://user-images.githubusercontent.com/23078054/102542111-98b01e00-40b1-11eb-967e-bc952430bd06.png"> +</a> ## Maintainers +- [Friso Smit](https://github.com/fwsmit) <fw.smit01@gmail.com> - [Nikos Tsipinakis](https://github.com/tsipinakis) <nikos@tsipinakis.com> - [Benedikt Heine](https://github.com/bebehei) <bebe@bebehei.de> ## Author -written by Sascha Kruse <dunst@knopwob.de> +Written by Sascha Kruse <dunst@knopwob.de> ## Copyright -copyright 2013 Sascha Kruse and contributors (see [`LICENSE`](./LICENSE) for licensing information) - -If you feel that copyrights are violated, please send me an email. +Copyright 2013 Sascha Kruse and contributors (see [`LICENSE`](./LICENSE) for licensing information) [issue-tracker]: https://github.com/dunst-project/dunst/issues [wiki]: https://github.com/dunst-project/dunst/wiki +[website]: https://dunst-project.org +[FAQ]: https://dunst-project.org/faq diff -Nru dunst-1.5.0/RELEASE_NOTES dunst-1.8.1/RELEASE_NOTES --- dunst-1.5.0/RELEASE_NOTES 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/RELEASE_NOTES 2022-03-02 10:55:25.000000000 +0000 @@ -1,4 +1,82 @@ =================================================================================== +Release Notes For v1.8.0 +=================================================================================== + +Lots of exciting and useful features are being added to dunst, all while making +dunst even more configurable and reliable. + +For users: + +This release re-adds the keyboard shortcuts for those who have been missing +them. Also, support for drop-in files has been implemented. Files placed in +dunstrc.d/*.conf will be read after the main dunstrc. This may be useful for +theming (with pywal or otherwise). In the future we will add example drop-in +files for different default themes and special configuration. + +For maintainers: + +Not much has been changed for maintainers. Maybe you'll find the drop-in files +useful for distro-specific fixes. Note that only the files in the directory of +the used dunstrc will be read (see dunst(1) for documentation). + + +=================================================================================== +Release Notes For v1.7.0 +=================================================================================== + +This release was long overdue. There have been a lot of changes in the mean +time. For a full list of changes, see the changelog. + +For users: + +The wayland support of v1.6.0 was already pretty good, but this release added +fullscreen detection and improved the stability. + +This release added a few improvements to the wayland support. Dunst now +automatically falls back to X11 when the wayland compositor doesn't support the +neccesary protocols. + +For maintainers: + +Previously the readme said dunst depended on GTK3, which hasn't been the case +for a while. Make sure that GTK3 is not included as a dependency. + +The default program for opening URL's in notifications has been changed from +firefox to xdg-open. + +The Makefile and dunstrc searching has been significantly changed to be more +compliant with the XDG spec. The default config directory, `SYSCONFDIR`, has +been changed from "/etc" to "${PREFIX}/etc/xdg/". To change back to the old +behaviour, run make with: `make SYSCONFDIR="/etc"` (make sure to pass the same +variables to make in subsequent calls). Take a look at the "Make parameters" +section of the readme and the FILES section of the man page for more details. + +=================================================================================== +Release Notes For v1.6.0 +=================================================================================== + +For users: + +At long last, dunst has native wayland support. On startup dunst will now +autodetect the display environment it's run on and use the appropriate backend +(X11 or wayland). +Additionally, support for progress bars has been added when the 'value' hint is +used. Try it out with `notify-send -h int:value:70 'Progress bars!'` + +Last but most importantly, support for the +`DUNST_COMMAND_{PAUSE,RESUME,TOGGLE}` has been removed as they could +potentially be used to DoS dunst. `dunstctl` has been available as a direct +replacement for the use-case they served since last release. See +https://github.com/dunst-project/dunst/pull/830 for details + +For maintainers: + +Dunst now depends on the wayland libraries and (optionally) on the +wayland-protocols package. A global configuration file is now installed by +default in `/etc/dunst/dunstrc` + + +=================================================================================== Release Notes For v1.5.0 =================================================================================== diff -Nru dunst-1.5.0/src/dbus.c dunst-1.8.1/src/dbus.c --- dunst-1.5.0/src/dbus.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/dbus.c 2022-03-02 10:55:25.000000000 +0000 @@ -13,6 +13,7 @@ #include "queues.h" #include "settings.h" #include "utils.h" +#include "rules.h" #define FDN_PATH "/org/freedesktop/Notifications" #define FDN_IFAC "org.freedesktop.Notifications" @@ -74,17 +75,31 @@ " <method name=\"ContextMenuCall\" />" " <method name=\"NotificationAction\">" - " <arg name=\"number\" type=\"i\"/>" + " <arg name=\"number\" type=\"u\"/>" " </method>" " <method name=\"NotificationCloseLast\" />" " <method name=\"NotificationCloseAll\" />" + " <method name=\"NotificationListHistory\">" + " <arg direction=\"out\" name=\"notifications\" type=\"aa{sv}\"/>" + " </method>" + " <method name=\"NotificationPopHistory\">" + " <arg direction=\"in\" name=\"id\" type=\"u\"/>" + " </method>" " <method name=\"NotificationShow\" />" + " <method name=\"RuleEnable\">" + " <arg name=\"name\" type=\"s\"/>" + " <arg name=\"state\" type=\"i\"/>" + " </method>" " <method name=\"Ping\" />" " <property name=\"paused\" type=\"b\" access=\"readwrite\">" " <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>" " </property>" + " <property name=\"displayedLength\" type=\"u\" access=\"read\" />" + " <property name=\"historyLength\" type=\"u\" access=\"read\" />" + " <property name=\"waitingLength\" type=\"u\" access=\"read\" />" + " </interface>" "</node>"; @@ -157,15 +172,21 @@ DBUS_METHOD(dunst_NotificationAction); DBUS_METHOD(dunst_NotificationCloseAll); DBUS_METHOD(dunst_NotificationCloseLast); +DBUS_METHOD(dunst_NotificationListHistory); +DBUS_METHOD(dunst_NotificationPopHistory); DBUS_METHOD(dunst_NotificationShow); +DBUS_METHOD(dunst_RuleEnable); DBUS_METHOD(dunst_Ping); static struct dbus_method methods_dunst[] = { - {"ContextMenuCall", dbus_cb_dunst_ContextMenuCall}, - {"NotificationAction", dbus_cb_dunst_NotificationAction}, - {"NotificationCloseAll", dbus_cb_dunst_NotificationCloseAll}, - {"NotificationCloseLast", dbus_cb_dunst_NotificationCloseLast}, - {"NotificationShow", dbus_cb_dunst_NotificationShow}, - {"Ping", dbus_cb_dunst_Ping}, + {"ContextMenuCall", dbus_cb_dunst_ContextMenuCall}, + {"NotificationAction", dbus_cb_dunst_NotificationAction}, + {"NotificationCloseAll", dbus_cb_dunst_NotificationCloseAll}, + {"NotificationCloseLast", dbus_cb_dunst_NotificationCloseLast}, + {"NotificationListHistory", dbus_cb_dunst_NotificationListHistory}, + {"NotificationPopHistory", dbus_cb_dunst_NotificationPopHistory}, + {"NotificationShow", dbus_cb_dunst_NotificationShow}, + {"Ping", dbus_cb_dunst_Ping}, + {"RuleEnable", dbus_cb_dunst_RuleEnable}, }; void dbus_cb_dunst_methods(GDBusConnection *connection, @@ -210,12 +231,12 @@ GVariant *parameters, GDBusMethodInvocation *invocation) { - int notification_nr = 0; - g_variant_get(parameters, "(i)", ¬ification_nr); + guint32 notification_nr = 0; + g_variant_get(parameters, "(u)", ¬ification_nr); LOG_D("CMD: Calling action for notification %d", notification_nr); - if (notification_nr < 0 || queues_length_waiting() < notification_nr) { + if (queues_length_waiting() < notification_nr) { g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, @@ -278,6 +299,148 @@ g_dbus_connection_flush(connection, NULL, NULL, NULL); } +static void dbus_cb_dunst_NotificationListHistory(GDBusConnection *connection, + const gchar *sender, + GVariant *parameters, + GDBusMethodInvocation *invocation) +{ + LOG_D("CMD: Listing all notifications from history"); + + GVariant *answer = NULL; + GVariantBuilder *builder; + + builder = g_variant_builder_new(G_VARIANT_TYPE("aa{sv}")); + + GList *notification_list = queues_get_history(); + + // reverse chronological list + for(int i = queues_length_history(); i > 0; i--) { + struct notification *n; + n = g_list_nth_data(notification_list, i-1); + + GVariantBuilder n_builder; + + g_variant_builder_init(&n_builder, g_variant_type_new("a{sv}")); + + char *body, *msg, *summary, *appname, *category; + char *default_action_name, *icon_path; + + body = (n->body == NULL) ? "" : n->body; + msg = (n->msg == NULL) ? "" : n->msg; + summary = (n->summary == NULL) ? "" : n->summary; + appname = (n->appname == NULL) ? "" : n->appname; + category = (n->category == NULL) ? "" : n->category; + default_action_name= (n->default_action_name == NULL) ? + "" : n->default_action_name; + icon_path = (n->icon_path == NULL) ? "" : n->icon_path; + + g_variant_builder_add(&n_builder, "{sv}", "body", + g_variant_new_from_bytes(G_VARIANT_TYPE("s"), + g_bytes_new(body, strlen(body)+1), TRUE)); + g_variant_builder_add(&n_builder, "{sv}", "message", + g_variant_new_from_bytes(G_VARIANT_TYPE("s"), + g_bytes_new(msg, strlen(msg)+1), TRUE)); + g_variant_builder_add(&n_builder, "{sv}", "summary", + g_variant_new_from_bytes(G_VARIANT_TYPE("s"), + g_bytes_new(summary, strlen(summary)+1), TRUE)); + g_variant_builder_add(&n_builder, "{sv}", "appname", + g_variant_new_from_bytes(G_VARIANT_TYPE("s"), + g_bytes_new(appname, strlen(appname)+1), TRUE)); + g_variant_builder_add(&n_builder, "{sv}", "category", + g_variant_new_from_bytes(G_VARIANT_TYPE("s"), + g_bytes_new(category, strlen(category)+1), TRUE)); + g_variant_builder_add(&n_builder, "{sv}", "default_action_name", + g_variant_new_from_bytes(G_VARIANT_TYPE("s"), + g_bytes_new(default_action_name, + strlen(default_action_name)+1), TRUE)); + g_variant_builder_add(&n_builder, "{sv}", "icon_path", + g_variant_new_from_bytes(G_VARIANT_TYPE("s"), + g_bytes_new(icon_path, strlen(icon_path)+1), TRUE)); + g_variant_builder_add(&n_builder, "{sv}", "id", + g_variant_new_from_bytes(G_VARIANT_TYPE("i"), + g_bytes_new(&n->id, sizeof(int)), TRUE)); + g_variant_builder_add(&n_builder, "{sv}", "timestamp", + g_variant_new_from_bytes(G_VARIANT_TYPE("x"), + g_bytes_new(&n->timestamp, sizeof(gint64)), TRUE)); + g_variant_builder_add(&n_builder, "{sv}", "timeout", + g_variant_new_from_bytes(G_VARIANT_TYPE("x"), + g_bytes_new(&n->timeout, sizeof(gint64)), TRUE)); + g_variant_builder_add(&n_builder, "{sv}", "progress", + g_variant_new_from_bytes(G_VARIANT_TYPE("i"), + g_bytes_new(&n->progress, sizeof(int)), TRUE)); + + g_variant_builder_add(builder, "a{sv}", &n_builder); + + } + + answer = g_variant_new("(aa{sv})", builder); + + g_clear_pointer(&builder, g_variant_builder_unref); + g_dbus_method_invocation_return_value(invocation, answer); + g_dbus_connection_flush(connection, NULL, NULL, NULL); +} + +static void dbus_cb_dunst_NotificationPopHistory(GDBusConnection *connection, + const gchar *sender, + GVariant *parameters, + GDBusMethodInvocation *invocation) +{ + LOG_D("CMD: Popping notification from history"); + + guint32 id; + g_variant_get(parameters, "(u)", &id); + + queues_history_pop_by_id(id); + wake_up(); + + g_dbus_method_invocation_return_value(invocation, NULL); + g_dbus_connection_flush(connection, NULL, NULL, NULL); +} + +static void dbus_cb_dunst_RuleEnable(GDBusConnection *connection, + const gchar *sender, + GVariant *parameters, + GDBusMethodInvocation *invocation) +{ + // dbus param state: 0 → disable, 1 → enable, 2 → toggle. + + int state = 0; + char *name = NULL; + g_variant_get(parameters, "(si)", &name, &state); + + LOG_D("CMD: Changing rule \"%s\" enable state to %d", name, state); + + if (state < 0 || state > 2) { + g_dbus_method_invocation_return_error(invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Couldn't understand state %d. It must be 0, 1 or 2", + state); + return; + } + + struct rule *target_rule = get_rule(name); + + if (target_rule == NULL) { + g_dbus_method_invocation_return_error(invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "There is no rule named \"%s\"", + name); + return; + } + + if (state == 0) + target_rule->enabled = false; + else if (state == 1) + target_rule->enabled = true; + else if (state == 2) + target_rule->enabled = !target_rule->enabled; + + g_dbus_method_invocation_return_value(invocation, NULL); + g_dbus_connection_flush(connection, NULL, NULL, NULL); +} + /* Just a simple Ping command to give the ability to dunstctl to test for the existence of this interface * Any other way requires parsing the XML of the Introspection or other foo. Just calling the Ping on an old dunst version will fail. */ static void dbus_cb_dunst_Ping(GDBusConnection *connection, @@ -303,11 +466,15 @@ g_variant_builder_add(builder, "s", "actions"); g_variant_builder_add(builder, "s", "body"); g_variant_builder_add(builder, "s", "body-hyperlinks"); + g_variant_builder_add(builder, "s", "icon-static"); for (int i = 0; i < sizeof(stack_tag_hints)/sizeof(*stack_tag_hints); ++i) g_variant_builder_add(builder, "s", stack_tag_hints[i]); - if (settings.markup != MARKUP_NO) + // Since markup isn't a global variable anymore, look it up in the + // global rule + struct rule *global_rule = get_rule("global"); + if (global_rule && global_rule->markup != MARKUP_NO) g_variant_builder_add(builder, "s", "body-markup"); value = g_variant_new("(as)", builder); @@ -361,33 +528,59 @@ } GVariant *dict_value; + + // First process the items that can be filtered on if ((dict_value = g_variant_lookup_value(hints, "urgency", G_VARIANT_TYPE_BYTE))) { n->urgency = g_variant_get_byte(dict_value); g_variant_unref(dict_value); } - if ((dict_value = g_variant_lookup_value(hints, "fgcolor", G_VARIANT_TYPE_STRING))) { - n->colors.fg = g_variant_dup_string(dict_value, NULL); + if ((dict_value = g_variant_lookup_value(hints, "category", G_VARIANT_TYPE_STRING))) { + n->category = g_variant_dup_string(dict_value, NULL); g_variant_unref(dict_value); } - if ((dict_value = g_variant_lookup_value(hints, "bgcolor", G_VARIANT_TYPE_STRING))) { - n->colors.bg = g_variant_dup_string(dict_value, NULL); + if ((dict_value = g_variant_lookup_value(hints, "desktop-entry", G_VARIANT_TYPE_STRING))) { + n->desktop_entry = g_variant_dup_string(dict_value, NULL); g_variant_unref(dict_value); } - if ((dict_value = g_variant_lookup_value(hints, "frcolor", G_VARIANT_TYPE_STRING))) { - n->colors.frame = g_variant_dup_string(dict_value, NULL); + if ((dict_value = g_variant_lookup_value(hints, "value", G_VARIANT_TYPE_INT32))) { + n->progress = g_variant_get_int32(dict_value); + g_variant_unref(dict_value); + } else if ((dict_value = g_variant_lookup_value(hints, "value", G_VARIANT_TYPE_UINT32))) { + n->progress = g_variant_get_uint32(dict_value); g_variant_unref(dict_value); } + if (n->progress < 0) + n->progress = -1; - if ((dict_value = g_variant_lookup_value(hints, "category", G_VARIANT_TYPE_STRING))) { - n->category = g_variant_dup_string(dict_value, NULL); - g_variant_unref(dict_value); + /* Check for hints that define the stack_tag + * + * Only accept to first one we find. + */ + for (int i = 0; i < sizeof(stack_tag_hints)/sizeof(*stack_tag_hints); ++i) { + if ((dict_value = g_variant_lookup_value(hints, stack_tag_hints[i], G_VARIANT_TYPE_STRING))) { + n->stack_tag = g_variant_dup_string(dict_value, NULL); + g_variant_unref(dict_value); + break; + } } - if ((dict_value = g_variant_lookup_value(hints, "desktop-entry", G_VARIANT_TYPE_STRING))) { - n->desktop_entry = g_variant_dup_string(dict_value, NULL); + /* Check for transient hints + * + * According to the spec, the transient hint should be boolean. + * But notify-send does not support hints of type 'boolean'. + * So let's check for int and boolean until notify-send is fixed. + */ + if ((dict_value = g_variant_lookup_value(hints, "transient", G_VARIANT_TYPE_BOOLEAN))) { + n->transient = g_variant_get_boolean(dict_value); + g_variant_unref(dict_value); + } else if ((dict_value = g_variant_lookup_value(hints, "transient", G_VARIANT_TYPE_UINT32))) { + n->transient = g_variant_get_uint32(dict_value) > 0; + g_variant_unref(dict_value); + } else if ((dict_value = g_variant_lookup_value(hints, "transient", G_VARIANT_TYPE_INT32))) { + n->transient = g_variant_get_int32(dict_value) > 0; g_variant_unref(dict_value); } @@ -397,6 +590,10 @@ g_variant_unref(dict_value); } + // Set raw icon data only after initializing the notification, so the + // desired icon size is known. This way the buffer can be immediately + // rescaled. If at some point you might want to match by if a + // notificaton has an image, this has to be reworked. dict_value = g_variant_lookup_value(hints, "image-data", G_VARIANT_TYPE("(iiibiiay)")); if (!dict_value) dict_value = g_variant_lookup_value(hints, "image_data", G_VARIANT_TYPE("(iiibiiay)")); @@ -407,41 +604,33 @@ g_variant_unref(dict_value); } - /* Check for transient hints - * - * According to the spec, the transient hint should be boolean. - * But notify-send does not support hints of type 'boolean'. - * So let's check for int and boolean until notify-send is fixed. - */ - if ((dict_value = g_variant_lookup_value(hints, "transient", G_VARIANT_TYPE_BOOLEAN))) { - n->transient = g_variant_get_boolean(dict_value); - g_variant_unref(dict_value); - } else if ((dict_value = g_variant_lookup_value(hints, "transient", G_VARIANT_TYPE_UINT32))) { - n->transient = g_variant_get_uint32(dict_value) > 0; - g_variant_unref(dict_value); - } else if ((dict_value = g_variant_lookup_value(hints, "transient", G_VARIANT_TYPE_INT32))) { - n->transient = g_variant_get_int32(dict_value) > 0; + // All attributes that have to be set before initializations are set, + // so we can initialize the notification. This applies all rules that + // are defined and applies the formatting to the message. + notification_init(n); + + // Modify these values after the notification is initialized and all rules are applied. + if ((dict_value = g_variant_lookup_value(hints, "fgcolor", G_VARIANT_TYPE_STRING))) { + g_free(n->colors.fg); + n->colors.fg = g_variant_dup_string(dict_value, NULL); g_variant_unref(dict_value); } - if ((dict_value = g_variant_lookup_value(hints, "value", G_VARIANT_TYPE_INT32))) { - n->progress = g_variant_get_int32(dict_value); + if ((dict_value = g_variant_lookup_value(hints, "bgcolor", G_VARIANT_TYPE_STRING))) { + g_free(n->colors.bg); + n->colors.bg = g_variant_dup_string(dict_value, NULL); g_variant_unref(dict_value); - } else if ((dict_value = g_variant_lookup_value(hints, "value", G_VARIANT_TYPE_UINT32))) { - n->progress = g_variant_get_uint32(dict_value); + } + + if ((dict_value = g_variant_lookup_value(hints, "frcolor", G_VARIANT_TYPE_STRING))) { + g_free(n->colors.frame); + n->colors.frame = g_variant_dup_string(dict_value, NULL); g_variant_unref(dict_value); } - /* Check for hints that define the stack_tag - * - * Only accept to first one we find. - */ - for (int i = 0; i < sizeof(stack_tag_hints)/sizeof(*stack_tag_hints); ++i) { - if ((dict_value = g_variant_lookup_value(hints, stack_tag_hints[i], G_VARIANT_TYPE_STRING))) { - n->stack_tag = g_variant_dup_string(dict_value, NULL); - g_variant_unref(dict_value); - break; - } + if ((dict_value = g_variant_lookup_value(hints, "hlcolor", G_VARIANT_TYPE_STRING))) { + n->colors.highlight = g_variant_dup_string(dict_value, NULL); + g_variant_unref(dict_value); } if (timeout >= 0) @@ -451,7 +640,6 @@ g_variant_type_free(required_type); g_free(actions); // the strv is only a shallow copy - notification_init(n); return n; } @@ -496,10 +684,10 @@ g_variant_get(parameters, "(u)", &id); if (settings.ignore_dbusclose) { LOG_D("Ignoring CloseNotification message"); - // Stay commpliant by lying to the sender, telling him we closed the notification + // Stay commpliant by lying to the sender, telling him we closed the notification if (id > 0) { struct notification *n = queues_get_by_id(id); - if (n) + if (n) signal_notification_closed(n, REASON_SIG); } } else { @@ -556,6 +744,27 @@ if (err) { LOG_W("Unable to close notification: %s", err->message); g_error_free(err); + } else { + char* reason_string; + switch (reason) { + case REASON_TIME: + reason_string="time"; + break; + case REASON_USER: + reason_string="user"; + break; + case REASON_SIG: + reason_string="signal"; + break; + case REASON_UNDEF: + reason_string="undfined"; + break; + default: + reason_string="unknown"; + } + + LOG_D("Queues: Closing notification for reason: %s", reason_string); + } } @@ -597,6 +806,15 @@ if (STR_EQ(property_name, "paused")) { return g_variant_new_boolean(!status.running); + } else if (STR_EQ(property_name, "displayedLength")) { + unsigned int displayed = queues_length_displayed(); + return g_variant_new_uint32(displayed); + } else if (STR_EQ(property_name, "historyLength")) { + unsigned int history = queues_length_history(); + return g_variant_new_uint32(history); + } else if (STR_EQ(property_name, "waitingLength")) { + unsigned int waiting = queues_length_waiting(); + return g_variant_new_uint32(waiting); } else { LOG_W("Unknown property!\n"); *error = g_error_new(G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property"); @@ -616,6 +834,22 @@ if (STR_EQ(property_name, "paused")) { dunst_status(S_RUNNING, !g_variant_get_boolean(value)); wake_up(); + + GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY); + GVariantBuilder *invalidated_builder = g_variant_builder_new(G_VARIANT_TYPE("as")); + g_variant_builder_add(builder, + "{sv}", + "paused", g_variant_new_boolean(g_variant_get_boolean(value))); + g_dbus_connection_emit_signal(connection, + NULL, + object_path, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + g_variant_new("(sa{sv}as)", + interface_name, + builder, + invalidated_builder), + NULL); return true; } @@ -831,4 +1065,4 @@ g_bus_unown_name(owner_id); } -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/dbus.h dunst-1.8.1/src/dbus.h --- dunst-1.5.0/src/dbus.h 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/dbus.h 2022-03-02 10:55:25.000000000 +0000 @@ -22,4 +22,4 @@ void signal_action_invoked(const struct notification *n, const char *identifier); #endif -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/draw.c dunst-1.8.1/src/draw.c --- dunst-1.5.0/src/draw.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/draw.c 2022-03-02 10:55:25.000000000 +0000 @@ -1,7 +1,6 @@ #include "draw.h" #include <assert.h> -#include <cairo.h> #include <math.h> #include <pango/pango-attributes.h> #include <pango/pangocairo.h> @@ -10,6 +9,7 @@ #include <pango/pango-types.h> #include <stdlib.h> #include <inttypes.h> +#include <glib.h> #include "dunst.h" #include "icon.h" @@ -17,31 +17,69 @@ #include "markup.h" #include "notification.h" #include "queues.h" -#include "x11/x.h" +#include "output.h" +#include "settings.h" +#include "utils.h" +#include "icon-lookup.h" + +struct color { + double r; + double g; + double b; + double a; +}; struct colored_layout { PangoLayout *l; struct color fg; struct color bg; + struct color highlight; struct color frame; char *text; PangoAttrList *attr; cairo_surface_t *icon; - const struct notification *n; + struct notification *n; + bool is_xmore; }; -struct window_x11 *win; +const struct output *output; +window win; PangoFontDescription *pango_fdesc; #define UINT_MAX_N(bits) ((1 << bits) - 1) +void load_icon_themes() +{ + bool loaded_theme = false; + + for (int i = 0; settings.icon_theme[i] != NULL; i++) { + char *theme = settings.icon_theme[i]; + int theme_index = load_icon_theme(theme); + if (theme_index >= 0) { + LOG_I("Adding theme %s", theme); + add_default_theme(theme_index); + loaded_theme = true; + } + } + if (!loaded_theme) { + int theme_index = load_icon_theme("hicolor"); + add_default_theme(theme_index); + } + +} + void draw_setup(void) { - x_setup(); + const struct output *out = output_create(settings.force_xwayland); + output = out; + + win = out->win_create(); - win = x_win_create(); pango_fdesc = pango_font_description_from_string(settings.font); + + if (settings.enable_recursive_icon_lookup) + load_icon_themes(); } static struct color hex_to_color(uint32_t hexValue, int dpc) @@ -60,17 +98,19 @@ static struct color string_to_color(const char *str) { - char *end; - uint_fast32_t val = strtoul(str+1, &end, 16); - if (end[0] != '\0' && end[1] != '\0') { - LOG_W("Invalid color string: '%s'", str); - } + if (STR_FULL(str)) { + char *end; + uint_fast32_t val = strtoul(str+1, &end, 16); + if (end[0] != '\0' && end[1] != '\0') { + LOG_W("Invalid color string: '%s'", str); + } - switch (end - (str+1)) { - case 3: return hex_to_color((val << 4) | 0xF, 1); - case 6: return hex_to_color((val << 8) | 0xFF, 2); - case 4: return hex_to_color(val, 1); - case 8: return hex_to_color(val, 2); + switch (end - (str+1)) { + case 3: return hex_to_color((val << 4) | 0xF, 1); + case 6: return hex_to_color((val << 8) | 0xFF, 2); + case 4: return hex_to_color(val, 1); + case 8: return hex_to_color(val, 2); + } } /* return black on error */ @@ -127,28 +167,78 @@ } } -static void layout_setup_pango(PangoLayout *layout, int width) +static int get_horizontal_text_icon_padding(struct notification *n) { + bool horizontal_icon = ( + n->icon && (n->icon_position == ICON_LEFT || n->icon_position == ICON_RIGHT) + ); + if (settings.text_icon_padding && horizontal_icon) { + return settings.text_icon_padding; + } else { + return settings.h_padding; + } +} + +static int get_vertical_text_icon_padding(struct notification *n) +{ + bool vertical_icon = n->icon && (n->icon_position == ICON_TOP); + if (settings.text_icon_padding && vertical_icon) { + return settings.text_icon_padding; + } else { + return settings.padding; + } +} + +static bool have_progress_bar(const struct colored_layout *cl) +{ + return (cl->n->progress >= 0 && settings.progress_bar == true && + !cl->is_xmore); +} + +static void get_text_size(PangoLayout *l, int *w, int *h, double scale) { + pango_layout_get_pixel_size(l, w, h); + // scale the size down, because it may be rendered at higher DPI + + if (w) + *w = ceil(*w / scale); + if (h) + *h = ceil(*h / scale); +} + +// Set up pango for a given layout. +// @param width The avaiable text width in pixels, used for caluclating alignment and wrapping +// @param height The maximum text height in pixels. +static void layout_setup_pango(PangoLayout *layout, int width, int height, + bool word_wrap, PangoEllipsizeMode ellipsize_mode, + PangoAlignment alignment) +{ + double scale = output->get_scale(); pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR); - pango_layout_set_width(layout, width * PANGO_SCALE); + pango_layout_set_width(layout, round(width * scale * PANGO_SCALE)); + + // When no height is set, word wrap is turned off + if (word_wrap) + pango_layout_set_height(layout, round(height * scale * PANGO_SCALE)); + pango_layout_set_font_description(layout, pango_fdesc); - pango_layout_set_spacing(layout, settings.line_height * PANGO_SCALE); + pango_layout_set_spacing(layout, round(settings.line_height * scale * PANGO_SCALE)); - PangoAlignment align; - switch (settings.align) { - case ALIGN_LEFT: - default: - align = PANGO_ALIGN_LEFT; - break; - case ALIGN_CENTER: - align = PANGO_ALIGN_CENTER; - break; - case ALIGN_RIGHT: - align = PANGO_ALIGN_RIGHT; - break; - } - pango_layout_set_alignment(layout, align); + pango_layout_set_ellipsize(layout, ellipsize_mode); + pango_layout_set_alignment(layout, alignment); +} + +// Set up the layout of a single notification +// @param width Width of the layout +// @param height Height of the layout +static void layout_setup(struct colored_layout *cl, int width, int height, double scale) +{ + int horizontal_padding = get_horizontal_text_icon_padding(cl->n); + int icon_width = cl->icon ? get_icon_width(cl->icon, scale) + horizontal_padding : 0; + int text_width = width - 2 * settings.h_padding - (cl->n->icon_position == ICON_TOP ? 0 : icon_width); + int progress_bar_height = have_progress_bar(cl) ? settings.progress_bar_height + settings.padding : 0; + int max_text_height = MAX(0, settings.height - progress_bar_height - 2 * settings.padding); + layout_setup_pango(cl->l, text_width, max_text_height, cl->n->word_wrap, cl->n->ellipsize, cl->n->alignment); } static void free_colored_layout(void *data) @@ -157,92 +247,76 @@ g_object_unref(cl->l); pango_attr_list_unref(cl->attr); g_free(cl->text); - if (cl->icon) cairo_surface_destroy(cl->icon); g_free(cl); } -static bool have_dynamic_width(void) +// calculates the minimum dimensions of the notification excluding the frame +static struct dimensions calculate_notification_dimensions(struct colored_layout *cl, double scale) { - return (settings.geometry.width_set && settings.geometry.w == 0); + struct dimensions dim = { 0 }; + layout_setup(cl, settings.width.max, settings.height, scale); + + int horizontal_padding = get_horizontal_text_icon_padding(cl->n); + int icon_width = cl->icon? get_icon_width(cl->icon, scale) + horizontal_padding : 0; + int icon_height = cl->icon? get_icon_height(cl->icon, scale) : 0; + int progress_bar_height = have_progress_bar(cl) ? settings.progress_bar_height + settings.padding : 0; + + int vertical_padding; + if (cl->n->hide_text) { + vertical_padding = 0; + dim.text_width = 0; + dim.text_height = 0; + } else { + get_text_size(cl->l, &dim.text_width, &dim.text_height, scale); + vertical_padding = get_vertical_text_icon_padding(cl->n); + } + + if (cl->n->icon_position == ICON_TOP && cl->n->icon) { + dim.h = icon_height + dim.text_height + vertical_padding; + } else { + dim.h = MAX(icon_height, dim.text_height); + } + + dim.h += progress_bar_height; + dim.w = dim.text_width + icon_width + 2 * settings.h_padding; + + dim.h = MIN(settings.height, dim.h + settings.padding * 2); + dim.w = MAX(settings.width.min, dim.w); + if (have_progress_bar(cl)) + dim.w = MAX(settings.progress_bar_min_width, dim.w); + + dim.w = MIN(settings.width.max, dim.w); + + cl->n->displayed_height = dim.h; + return dim; } static struct dimensions calculate_dimensions(GSList *layouts) { struct dimensions dim = { 0 }; - - struct screen_info *scr = get_active_screen(); - if (have_dynamic_width()) { - /* dynamic width */ - dim.w = 0; - } else if (settings.geometry.width_set) { - /* fixed width */ - if (settings.geometry.negative_width) { - dim.w = scr->w - settings.geometry.w; - } else { - dim.w = settings.geometry.w; - } - } else { - /* across the screen */ - dim.w = scr->w; - } + double scale = output->get_scale(); dim.h += 2 * settings.frame_width; dim.h += (g_slist_length(layouts) - 1) * settings.separator_height; dim.corner_radius = settings.corner_radius; - int text_width = 0, total_width = 0; for (GSList *iter = layouts; iter; iter = iter->next) { struct colored_layout *cl = iter->data; - int w=0,h=0; - pango_layout_get_pixel_size(cl->l, &w, &h); - if (cl->icon) { - h = MAX(cairo_image_surface_get_height(cl->icon), h); - w += cairo_image_surface_get_width(cl->icon) + settings.h_padding; - } - h = MAX(settings.notification_height, h + settings.padding * 2); - dim.h += h; - text_width = MAX(w, text_width); - - if (have_dynamic_width() || settings.shrink) { - /* dynamic width */ - total_width = MAX(text_width + 2 * settings.h_padding, total_width); - - /* subtract height from the unwrapped text */ - dim.h -= h; - - if (total_width > scr->w) { - /* set width to screen width */ - dim.w = scr->w - settings.geometry.x * 2; - } else if (have_dynamic_width() || (total_width < settings.geometry.w && settings.shrink)) { - /* set width to text width */ - dim.w = total_width + 2 * settings.frame_width; - } - - /* re-setup the layout */ - w = dim.w; - w -= 2 * settings.h_padding; - w -= 2 * settings.frame_width; - if (cl->icon) w -= cairo_image_surface_get_width(cl->icon) + settings.h_padding; - layout_setup_pango(cl->l, w); - - /* re-read information */ - pango_layout_get_pixel_size(cl->l, &w, &h); - if (cl->icon) { - h = MAX(cairo_image_surface_get_height(cl->icon), h); - w += cairo_image_surface_get_width(cl->icon) + settings.h_padding; - } - h = MAX(settings.notification_height, h + settings.padding * 2); - dim.h += h; - text_width = MAX(w, text_width); - } - - dim.corner_radius = MIN(dim.corner_radius, h/2); + struct dimensions n_dim = calculate_notification_dimensions(cl, scale); + dim.h += n_dim.h; + LOG_D("Notification dimensions %ix%i", n_dim.w, n_dim.h); + dim.w = MAX(dim.w, n_dim.w + settings.frame_width); } - if (dim.w <= 0) { - dim.w = text_width + 2 * settings.h_padding; - dim.w += 2 * settings.frame_width; + dim.w += 2 * settings.frame_width; + dim.corner_radius = MIN(dim.corner_radius, dim.h/2); + + /* clamp max width to screen width */ + const struct screen_info *scr = output->get_active_screen(); + int max_width = scr->w - settings.offset.x; + if (dim.w > max_width) { + dim.w = max_width; } return dim; @@ -250,10 +324,10 @@ static PangoLayout *layout_create(cairo_t *c) { - struct screen_info *screen = get_active_screen(); + const struct screen_info *screen = output->get_active_screen(); PangoContext *context = pango_cairo_create_context(c); - pango_cairo_context_set_resolution(context, screen_dpi_get(screen)); + pango_cairo_context_set_resolution(context, screen->dpi); PangoLayout *layout = pango_layout_new(context); @@ -262,67 +336,28 @@ return layout; } -static struct colored_layout *layout_init_shared(cairo_t *c, const struct notification *n) +static struct colored_layout *layout_init_shared(cairo_t *c, struct notification *n) { struct colored_layout *cl = g_malloc(sizeof(struct colored_layout)); cl->l = layout_create(c); - if (!settings.word_wrap) { - PangoEllipsizeMode ellipsize; - switch (settings.ellipsize) { - case ELLIPSE_START: - ellipsize = PANGO_ELLIPSIZE_START; - break; - case ELLIPSE_MIDDLE: - ellipsize = PANGO_ELLIPSIZE_MIDDLE; - break; - case ELLIPSE_END: - ellipsize = PANGO_ELLIPSIZE_END; - break; - default: - LOG_E("Invalid %s enum value in %s:%d", "ellipsize", __FILE__, __LINE__); - break; - } - pango_layout_set_ellipsize(cl->l, ellipsize); - } - - if (settings.icon_position != ICON_OFF && n->icon) { - cl->icon = gdk_pixbuf_to_cairo_surface(n->icon); - } else { - cl->icon = NULL; - } - - if (cl->icon && cairo_surface_status(cl->icon) != CAIRO_STATUS_SUCCESS) { - cairo_surface_destroy(cl->icon); - cl->icon = NULL; - } - cl->fg = string_to_color(n->colors.fg); cl->bg = string_to_color(n->colors.bg); + cl->highlight = string_to_color(n->colors.highlight); cl->frame = string_to_color(n->colors.frame); + cl->is_xmore = false; cl->n = n; - - struct dimensions dim = calculate_dimensions(NULL); - int width = dim.w; - - if (have_dynamic_width()) { - layout_setup_pango(cl->l, -1); - } else { - width -= 2 * settings.h_padding; - width -= 2 * settings.frame_width; - if (cl->icon) width -= cairo_image_surface_get_width(cl->icon) + settings.h_padding; - layout_setup_pango(cl->l, width); - } - return cl; } -static struct colored_layout *layout_derive_xmore(cairo_t *c, const struct notification *n, int qlen) +static struct colored_layout *layout_derive_xmore(cairo_t *c, struct notification *n, int qlen) { struct colored_layout *cl = layout_init_shared(c, n); cl->text = g_strdup_printf("(%d more)", qlen); cl->attr = NULL; + cl->is_xmore = true; + cl->icon = NULL; pango_layout_set_text(cl->l, cl->text, -1); return cl; } @@ -332,6 +367,12 @@ struct colored_layout *cl = layout_init_shared(c, n); + if (n->icon_position != ICON_OFF && n->icon) { + cl->icon = n->icon; + } else { + cl->icon = NULL; + } + /* markup */ GError *err = NULL; pango_parse_markup(n->text_to_render, -1, 0, &(cl->attr), &(cl->text), NULL, &err); @@ -351,11 +392,6 @@ g_error_free(err); } - - pango_layout_get_pixel_size(cl->l, NULL, &(n->displayed_height)); - if (cl->icon) n->displayed_height = MAX(cairo_image_surface_get_height(cl->icon), n->displayed_height); - n->displayed_height = MAX(settings.notification_height, n->displayed_height + settings.padding * 2); - n->first_render = false; return cl; } @@ -374,7 +410,7 @@ notification_update_text_to_render(n); - if (!iter->next && xmore_is_needed && settings.geometry.h == 1) { + if (!iter->next && xmore_is_needed && settings.notification_limit == 1) { char *new_ttr = g_strdup_printf("%s (%d more)", n->text_to_render, qlen); g_free(n->text_to_render); n->text_to_render = new_ttr; @@ -383,7 +419,7 @@ layout_from_notification(c, n)); } - if (xmore_is_needed && settings.geometry.h != 1) { + if (xmore_is_needed && settings.notification_limit != 1) { /* append xmore message as new message */ layouts = g_slist_append(layouts, layout_derive_xmore(c, queues_get_head_waiting(), qlen)); @@ -393,15 +429,33 @@ } -static int layout_get_height(struct colored_layout *cl) +static int layout_get_height(struct colored_layout *cl, double scale) { - int h; + int h_text = 0; int h_icon = 0; - pango_layout_get_pixel_size(cl->l, NULL, &h); + int h_progress_bar = 0; + + int vertical_padding; + if (cl->n->hide_text) { + vertical_padding = 0; + } else { + get_text_size(cl->l, NULL, &h_text, scale); + vertical_padding = get_vertical_text_icon_padding(cl->n); + } + if (cl->icon) - h_icon = cairo_image_surface_get_height(cl->icon); + h_icon = get_icon_height(cl->icon, scale); - return MAX(h, h_icon); + if (have_progress_bar(cl)) { + h_progress_bar = settings.progress_bar_height + settings.padding; + } + + + if (cl->n->icon_position == ICON_TOP && cl->n->icon) { + return h_icon + h_text + h_progress_bar + vertical_padding; + } else { + return MAX(h_text, h_icon) + h_progress_bar; + } } /* Attempt to make internal radius more organic. @@ -412,7 +466,7 @@ */ static int frame_internal_radius (int r, int w, int h) { - if (r == 0 || w == 0 || h == 0) + if (r == 0 || h + (w - r) * 2 == 0) return 0; // Integer precision scaler, using 1/4 of int size @@ -435,9 +489,15 @@ * The top corners will get rounded by `corner_radius`, if `first` is set. * Respectably the same for `last` with the bottom corners. */ -void draw_rounded_rect(cairo_t *c, int x, int y, int width, int height, int corner_radius, bool first, bool last) +void draw_rounded_rect(cairo_t *c, int x, int y, int width, int height, int corner_radius, double scale, bool first, bool last) { - const float degrees = M_PI / 180.0; + width = round(width * scale); + height = round(height * scale); + x = round(x * scale); + y = round(y * scale); + corner_radius = round(corner_radius * scale); + + const double degrees = M_PI / 180.0; cairo_new_sub_path(c); @@ -484,6 +544,13 @@ cairo_close_path(c); } +/** + * A small wrapper around cairo_rectange for drawing a scaled rectangle. + */ +static void draw_rect(cairo_t *c, double x, double y, double width, double height, double scale) { + cairo_rectangle(c, round(x * scale), round(y * scale), round(width * scale), round(height * scale)); +} + static cairo_surface_t *render_background(cairo_surface_t *srf, struct colored_layout *cl, struct colored_layout *cl_next, @@ -493,7 +560,8 @@ int corner_radius, bool first, bool last, - int *ret_width) + int *ret_width, + double scale) { int x = 0; int radius_int = corner_radius; @@ -513,7 +581,7 @@ else height += settings.separator_height; - draw_rounded_rect(c, x, y, width, height, corner_radius, first, last); + draw_rounded_rect(c, x, y, width, height, corner_radius, scale, first, last); /* adding frame */ x += settings.frame_width; @@ -531,11 +599,11 @@ radius_int = frame_internal_radius(corner_radius, settings.frame_width, height); - draw_rounded_rect(c, x, y, width, height, radius_int, first, last); + draw_rounded_rect(c, x, y, width, height, radius_int, scale, first, last); cairo_set_source_rgba(c, cl->frame.r, cl->frame.g, cl->frame.b, cl->frame.a); cairo_fill(c); - draw_rounded_rect(c, x, y, width, height, radius_int, first, last); + draw_rounded_rect(c, x, y, width, height, radius_int, scale, first, last); cairo_set_source_rgba(c, cl->bg.r, cl->bg.g, cl->bg.b, cl->bg.a); cairo_fill(c); @@ -547,7 +615,7 @@ struct color sep_color = layout_get_sepcolor(cl, cl_next); cairo_set_source_rgba(c, sep_color.r, sep_color.g, sep_color.b, sep_color.a); - cairo_rectangle(c, settings.frame_width, y + height, width, settings.separator_height); + draw_rect(c, settings.frame_width, y + height, width, settings.separator_height, scale); cairo_fill(c); } @@ -557,67 +625,136 @@ if (ret_width) *ret_width = width; - return cairo_surface_create_for_rectangle(srf, x, y, width, height); + return cairo_surface_create_for_rectangle(srf, + round(x * scale), round(y * scale), + round(width * scale), round(height * scale)); } -static void render_content(cairo_t *c, struct colored_layout *cl, int width) +static void render_content(cairo_t *c, struct colored_layout *cl, int width, double scale) { - const int h = layout_get_height(cl); - int h_text; - pango_layout_get_pixel_size(cl->l, NULL, &h_text); + // Redo layout setup, while knowing the width. This is to make + // alignment work correctly + layout_setup(cl, width, settings.height, scale); - int text_x = settings.h_padding, - text_y = settings.padding + h / 2 - h_text / 2; + const int h = layout_get_height(cl, scale); + LOG_D("Layout height %i", h); + int h_without_progress_bar = h; + if (have_progress_bar(cl)) { + h_without_progress_bar -= settings.progress_bar_height + settings.padding; + } - // text positioning - if (cl->icon) { - // vertical alignment - if (settings.vertical_alignment == VERTICAL_TOP) { - text_y = settings.padding; - } else if (settings.vertical_alignment == VERTICAL_BOTTOM) { - text_y = h + settings.padding - h_text; - if (text_y < 0) - text_y = settings.padding; - } // else VERTICAL_CENTER + if (!cl->n->hide_text) { + int h_text = 0; + get_text_size(cl->l, NULL, &h_text, scale); - // icon position - if (settings.icon_position == ICON_LEFT) { - text_x = cairo_image_surface_get_width(cl->icon) + 2 * settings.h_padding; - } // else ICON_RIGHT - } - cairo_move_to(c, text_x, text_y); + int text_x = settings.h_padding, + text_y = settings.padding + h_without_progress_bar / 2 - h_text / 2; - cairo_set_source_rgba(c, cl->fg.r, cl->fg.g, cl->fg.b, cl->fg.a); - pango_cairo_update_layout(c, cl->l); - pango_cairo_show_layout(c, cl->l); + // text positioning + if (cl->icon) { + // vertical alignment + if (settings.vertical_alignment == VERTICAL_TOP) { + text_y = settings.padding; + } else if (settings.vertical_alignment == VERTICAL_BOTTOM) { + text_y = h_without_progress_bar + settings.padding - h_text; + if (text_y < 0) + text_y = settings.padding; + } // else VERTICAL_CENTER + + // icon position + if (cl->n->icon_position == ICON_LEFT) { + text_x = get_icon_width(cl->icon, scale) + settings.h_padding + get_horizontal_text_icon_padding(cl->n); + } else if (cl->n->icon_position == ICON_TOP) { + text_y = get_icon_height(cl->icon, scale) + settings.padding + get_vertical_text_icon_padding(cl->n); + } // else ICON_RIGHT + } + cairo_move_to(c, round(text_x * scale), round(text_y * scale)); + cairo_set_source_rgba(c, cl->fg.r, cl->fg.g, cl->fg.b, cl->fg.a); + pango_cairo_update_layout(c, cl->l); + pango_cairo_show_layout(c, cl->l); + } // icon positioning if (cl->icon) { - unsigned int image_width = cairo_image_surface_get_width(cl->icon), - image_height = cairo_image_surface_get_height(cl->icon), + unsigned int image_width = get_icon_width(cl->icon, scale), + image_height = get_icon_height(cl->icon, scale), image_x = width - settings.h_padding - image_width, - image_y = settings.padding + h/2 - image_height/2; + image_y = settings.padding + h_without_progress_bar/2 - image_height/2; // vertical alignment if (settings.vertical_alignment == VERTICAL_TOP) { image_y = settings.padding; } else if (settings.vertical_alignment == VERTICAL_BOTTOM) { - image_y = h + settings.padding - image_height; - if (image_y < settings.padding || image_y > h) + image_y = h_without_progress_bar + settings.padding - image_height; + if (image_y < settings.padding || image_y > h_without_progress_bar) image_y = settings.padding; } // else VERTICAL_CENTER // icon position - if (settings.icon_position == ICON_LEFT) { + if (cl->n->icon_position == ICON_LEFT) { image_x = settings.h_padding; + } else if (cl->n->icon_position == ICON_TOP) { + image_y = settings.padding; + image_x = width/2 - image_width/2; } // else ICON_RIGHT - cairo_set_source_surface(c, cl->icon, image_x, image_y); - cairo_rectangle(c, image_x, image_y, image_width, image_height); + cairo_set_source_surface(c, cl->icon, round(image_x * scale), round(image_y * scale)); + draw_rect(c, image_x, image_y, image_width, image_height, scale); cairo_fill(c); } + // progress bar positioning + if (have_progress_bar(cl)){ + int progress = MIN(cl->n->progress, 100); + unsigned int frame_x = 0; + unsigned int frame_width = settings.progress_bar_frame_width, + progress_width = MIN(width - 2 * settings.h_padding, settings.progress_bar_max_width), + progress_height = settings.progress_bar_height - frame_width, + frame_y = settings.padding + h - settings.progress_bar_height, + progress_width_without_frame = progress_width - 2 * frame_width, + progress_width_1 = progress_width_without_frame * progress / 100, + progress_width_2 = progress_width_without_frame - progress_width_1; + + switch (cl->n->progress_bar_alignment) { + case PANGO_ALIGN_LEFT: + frame_x = settings.h_padding; + break; + case PANGO_ALIGN_CENTER: + frame_x = width/2 - progress_width/2; + break; + case PANGO_ALIGN_RIGHT: + frame_x = width - progress_width - settings.h_padding; + break; + } + unsigned int x_bar_1 = frame_x + frame_width, + x_bar_2 = x_bar_1 + progress_width_1; + + double half_frame_width = frame_width / 2.0; + + // draw progress bar + // Note: the bar could be drawn a bit smaller, because the frame is drawn on top + // left side + cairo_set_source_rgba(c, cl->highlight.r, cl->highlight.g, cl->highlight.b, cl->highlight.a); + draw_rect(c, x_bar_1, frame_y, progress_width_1, progress_height, scale); + cairo_fill(c); + // right side + cairo_set_source_rgba(c, cl->bg.r, cl->bg.g, cl->bg.b, cl->bg.a); + draw_rect(c, x_bar_2, frame_y, progress_width_2, progress_height, scale); + cairo_fill(c); + // border + cairo_set_source_rgba(c, cl->frame.r, cl->frame.g, cl->frame.b, cl->frame.a); + // TODO draw_rect instead of cairo_rectangle resulted + // in blurry lines due to rounding (half_frame_width + // can be non-integer) + cairo_rectangle(c, + (frame_x + half_frame_width) * scale, + (frame_y + half_frame_width) * scale, + (progress_width - frame_width) * scale, + progress_height * scale); + cairo_set_line_width(c, frame_width * scale); + cairo_stroke(c); + } } static struct dimensions layout_render(cairo_surface_t *srf, @@ -627,18 +764,19 @@ bool first, bool last) { - const int cl_h = layout_get_height(cl); + double scale = output->get_scale(); + const int cl_h = layout_get_height(cl, scale); int h_text = 0; - pango_layout_get_pixel_size(cl->l, NULL, &h_text); + get_text_size(cl->l, NULL, &h_text, scale); int bg_width = 0; - int bg_height = MAX(settings.notification_height, (2 * settings.padding) + cl_h); + int bg_height = MIN(settings.height, (2 * settings.padding) + cl_h); - cairo_surface_t *content = render_background(srf, cl, cl_next, dim.y, dim.w, bg_height, dim.corner_radius, first, last, &bg_width); + cairo_surface_t *content = render_background(srf, cl, cl_next, dim.y, dim.w, bg_height, dim.corner_radius, first, last, &bg_width, scale); cairo_t *c = cairo_create(content); - render_content(c, cl, bg_width); + render_content(c, cl, bg_width, scale); /* adding frame */ if (first) @@ -648,10 +786,10 @@ dim.y += settings.separator_height; - if (settings.notification_height <= (2 * settings.padding) + cl_h) + if ((2 * settings.padding + cl_h) < settings.height) dim.y += cl_h + 2 * settings.padding; else - dim.y += settings.notification_height; + dim.y += settings.height; cairo_destroy(c); cairo_surface_destroy(content); @@ -662,24 +800,49 @@ * Calculates the position the window should be placed at given its width and * height and stores them in \p ret_x and \p ret_y. */ -static void calc_window_pos(int width, int height, int *ret_x, int *ret_y) +void calc_window_pos(const struct screen_info *scr, int width, int height, int *ret_x, int *ret_y) { - struct screen_info *scr = get_active_screen(); + if(!ret_x || !ret_y) + return; - if (ret_x) { - if (settings.geometry.negative_x) { - *ret_x = (scr->x + (scr->w - width)) + settings.geometry.x; - } else { - *ret_x = scr->x + settings.geometry.x; - } + // horizontal + switch (settings.origin) { + case ORIGIN_TOP_LEFT: + case ORIGIN_LEFT_CENTER: + case ORIGIN_BOTTOM_LEFT: + *ret_x = scr->x + settings.offset.x; + break; + case ORIGIN_TOP_RIGHT: + case ORIGIN_RIGHT_CENTER: + case ORIGIN_BOTTOM_RIGHT: + *ret_x = scr->x + (scr->w - width) - settings.offset.x; + break; + case ORIGIN_TOP_CENTER: + case ORIGIN_CENTER: + case ORIGIN_BOTTOM_CENTER: + default: + *ret_x = scr->x + (scr->w - width) / 2; + break; } - if (ret_y) { - if (settings.geometry.negative_y) { - *ret_y = scr->y + (scr->h + settings.geometry.y) - height; - } else { - *ret_y = scr->y + settings.geometry.y; - } + // vertical + switch (settings.origin) { + case ORIGIN_TOP_LEFT: + case ORIGIN_TOP_CENTER: + case ORIGIN_TOP_RIGHT: + *ret_y = scr->y + settings.offset.y; + break; + case ORIGIN_BOTTOM_LEFT: + case ORIGIN_BOTTOM_CENTER: + case ORIGIN_BOTTOM_RIGHT: + *ret_y = scr->y + (scr->h - height) - settings.offset.y; + break; + case ORIGIN_LEFT_CENTER: + case ORIGIN_CENTER: + case ORIGIN_RIGHT_CENTER: + default: + *ret_y = scr->y + (scr->h - height) / 2; + break; } } @@ -687,11 +850,15 @@ { assert(queues_length_displayed() > 0); - GSList *layouts = create_layouts(x_win_get_context(win)); + GSList *layouts = create_layouts(output->win_get_context(win)); struct dimensions dim = calculate_dimensions(layouts); + LOG_D("Window dimensions %ix%i", dim.w, dim.h); + double scale = output->get_scale(); - cairo_surface_t *image_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, dim.w, dim.h); + cairo_surface_t *image_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + round(dim.w * scale), + round(dim.h * scale)); bool first = true; for (GSList *iter = layouts; iter; iter = iter->next) { @@ -704,8 +871,7 @@ first = false; } - calc_window_pos(dim.w, dim.h, &dim.x, &dim.y); - x_display_surface(image_surface, win, &dim); + output->display_surface(image_surface, win, &dim); cairo_surface_destroy(image_surface); g_slist_free_full(layouts, free_colored_layout); @@ -713,7 +879,19 @@ void draw_deinit(void) { - x_win_destroy(win); - x_free(); + output->win_destroy(win); + output->deinit(); + if (settings.enable_recursive_icon_lookup) + free_all_themes(); +} + +double draw_get_scale(void) +{ + if (output) { + return output->get_scale(); + } else { + LOG_W("Called draw_get_scale before output init"); + return 1; + } } -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/draw.h dunst-1.8.1/src/draw.h --- dunst-1.5.0/src/draw.h 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/draw.h 2022-03-02 10:55:25.000000000 +0000 @@ -1,16 +1,25 @@ #ifndef DUNST_DRAW_H #define DUNST_DRAW_H -#include "x11/x.h" -extern struct window_x11 *win; // Temporary +#include <stdbool.h> +#include <cairo.h> +#include "output.h" + +extern window win; // Temporary +extern const struct output *output; void draw_setup(void); void draw(void); -void draw_rounded_rect(cairo_t *c, int x, int y, int width, int height, int corner_radius, bool first, bool last); +void draw_rounded_rect(cairo_t *c, int x, int y, int width, int height, int corner_radius, double scale, bool first, bool last); + +// TODO get rid of this function by passing scale to everything that needs it. +double draw_get_scale(void); void draw_deinit(void); +void calc_window_pos(const struct screen_info *scr, int width, int height, int *ret_x, int *ret_y); + #endif -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/dunst.c dunst-1.8.1/src/dunst.c --- dunst-1.5.0/src/dunst.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/dunst.c 2022-03-02 10:55:25.000000000 +0000 @@ -9,7 +9,6 @@ #include <stdbool.h> #include <stdio.h> #include <stdlib.h> -#include <X11/Xlib.h> #include "dbus.h" #include "draw.h" @@ -20,12 +19,12 @@ #include "queues.h" #include "settings.h" #include "utils.h" -#include "x11/screen.h" -#include "x11/x.h" +#include "output.h" GMainLoop *mainloop = NULL; static struct dunst_status status; +static bool setup_done = false; /* see dunst.h */ void dunst_status(const enum dunst_status_field field, @@ -58,6 +57,15 @@ void wake_up(void) { + // If wake_up is being called before the output has been setup we should + // return. + if (!setup_done) + { + LOG_D("Ignoring wake up"); + return; + } + + LOG_D("Waking up"); run(NULL); } @@ -67,8 +75,8 @@ LOG_D("RUN"); - dunst_status(S_FULLSCREEN, have_fullscreen_window()); - dunst_status(S_IDLE, x_is_idle()); + dunst_status(S_FULLSCREEN, output->have_fullscreen_window()); + dunst_status(S_IDLE, output->is_idle()); queues_update(status); @@ -77,9 +85,9 @@ if (active) { // Call draw before showing the window to avoid flickering draw(); - x_win_show(win); + output->win_show(win); } else { - x_win_hide(win); + output->win_hide(win); } if (active) { @@ -87,6 +95,8 @@ gint64 sleep = queues_get_next_datachange(now); gint64 timeout_at = now + sleep; + LOG_D("Sleeping for %li ms", sleep/1000); + if (sleep >= 0) { if (next_timeout < now || timeout_at < next_timeout) { g_timeout_add(sleep/1000, run, NULL); @@ -167,6 +177,14 @@ usage(EXIT_SUCCESS); } + if (cmdline_get_bool("-print", false, "Print notifications to stdout") + || cmdline_get_bool("--print", false, "Print notifications to stdout")) { + settings.print_notifications = true; + } + + settings.startup_notification = cmdline_get_bool("--startup_notification", + 0, "Display a notification on startup."); + int dbus_owner_id = dbus_init(); mainloop = g_main_loop_new(NULL, FALSE); @@ -196,6 +214,7 @@ // we do not call wakeup now, wake_up does not work here yet } + setup_done = true; run(NULL); g_main_loop_run(mainloop); g_clear_pointer(&mainloop, g_main_loop_unref); @@ -229,4 +248,4 @@ exit(EXIT_SUCCESS); } -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/dunst.h dunst-1.8.1/src/dunst.h --- dunst-1.5.0/src/dunst.h 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/dunst.h 2022-03-02 10:55:25.000000000 +0000 @@ -40,5 +40,7 @@ void usage(int exit_status); void print_version(void); +gboolean quit_signal(gpointer data); + #endif -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/icon.c dunst-1.8.1/src/icon.c --- dunst-1.5.0/src/icon.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/icon.c 2022-03-02 10:55:25.000000000 +0000 @@ -5,16 +5,13 @@ #include <gdk-pixbuf/gdk-pixbuf.h> #include <stdbool.h> #include <string.h> +#include <math.h> #include "log.h" #include "notification.h" #include "settings.h" #include "utils.h" - -static bool is_readable_file(const char *filename) -{ - return (access(filename, R_OK) != -1); -} +#include "icon-lookup.h" /** * Reassemble the data parts of a GdkPixbuf into a cairo_surface_t's data field. @@ -86,9 +83,19 @@ } } +int get_icon_width(cairo_surface_t *icon, double scale) { + return round(cairo_image_surface_get_width(icon) / scale); +} + +int get_icon_height(cairo_surface_t *icon, double scale) { + return round(cairo_image_surface_get_height(icon) / scale); +} + cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf) { - assert(pixbuf); + if (!pixbuf) { + return NULL; + } int width = gdk_pixbuf_get_width(pixbuf); int height = gdk_pixbuf_get_height(pixbuf); @@ -139,23 +146,55 @@ return FALSE; } +static bool icon_size_clamp2(int *w, int *h, int desired_size, double scale) { + int largest_size = MAX(*w, *h); + int new_size = desired_size * scale; + if (largest_size != new_size) { + double scale = (double) new_size / (double) largest_size; + *w = round(*w * scale); + *h = round(*h * scale); + return true; + } + return false; +} /** - * Scales the given GdkPixbuf if necessary according to the settings. + * Scales the given GdkPixbuf to a given size.. If the image is not square, the + * largest size will be scaled up to the given size. + * + * The icon is scaled to a size of icon_size * dpi_scale. * * @param pixbuf (nullable) The pixbuf, which may be too big. * Takes ownership of the reference. + * @param icon_size An integer the unscaled icon size. + * @param dpi_scale A double for the dpi scaling. * @return the scaled version of the pixbuf. If scaling wasn't * necessary, it returns the same pixbuf. Transfers full * ownership of the reference. */ -static GdkPixbuf *icon_pixbuf_scale(GdkPixbuf *pixbuf) +static GdkPixbuf *icon_pixbuf_scale_to_size(GdkPixbuf *pixbuf, int icon_size, double dpi_scale) { ASSERT_OR_RET(pixbuf, NULL); int w = gdk_pixbuf_get_width(pixbuf); int h = gdk_pixbuf_get_height(pixbuf); + bool needs_change = false; - if (icon_size_clamp(&w, &h)) { + if (settings.enable_recursive_icon_lookup) + { + needs_change = icon_size_clamp2(&w, &h, icon_size, dpi_scale); + if (needs_change) { + LOG_D("Scaling to a size of %ix%i", w, h); + LOG_D("While the icon size and scale are %ix%f", icon_size, dpi_scale); + } + } else { + // TODO immediately rescale icon upon scale changes + if(icon_size_clamp(&w, &h)) { + w = round(w * dpi_scale); + h = round(h * dpi_scale); + needs_change = true; + } + } + if (needs_change) { GdkPixbuf *scaled = gdk_pixbuf_scale_simple( pixbuf, w, @@ -164,11 +203,10 @@ g_object_unref(pixbuf); pixbuf = scaled; } - return pixbuf; } -GdkPixbuf *get_pixbuf_from_file(const char *filename) +GdkPixbuf *get_pixbuf_from_file(const char *filename, int icon_size, double scale) { char *path = string_to_path(g_strdup(filename)); GError *error = NULL; @@ -179,12 +217,24 @@ g_free(path); return NULL; } - icon_size_clamp(&w, &h); - GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_scale(path, - w, - h, - TRUE, - &error); + GdkPixbuf *pixbuf = NULL; + if (settings.enable_recursive_icon_lookup) + { + icon_size_clamp2(&w, &h, icon_size, scale); + pixbuf = gdk_pixbuf_new_from_file_at_scale(path, + w, + h, + TRUE, + &error); + } else { + // TODO immediately rescale icon upon scale changes + icon_size_clamp(&w, &h); + pixbuf = gdk_pixbuf_new_from_file_at_scale(path, + round(w * scale), + round(h * scale), + TRUE, + &error); + } if (error) { LOG_W("%s", error->message); @@ -195,14 +245,19 @@ return pixbuf; } -GdkPixbuf *get_pixbuf_from_icon(const char *iconname) +char *get_path_from_icon_name(const char *iconname, int size) { + if (settings.enable_recursive_icon_lookup) { + char *path = find_icon_path(iconname, size); + LOG_I("Found icon at %s", path); + return path; + } if (STR_EMPTY(iconname)) return NULL; const char *suffixes[] = { ".svg", ".svgz", ".png", ".xpm", NULL }; - GdkPixbuf *pixbuf = NULL; gchar *uri_path = NULL; + char *new_name = NULL; if (g_str_has_prefix(iconname, "file://")) { uri_path = g_filename_from_uri(iconname, NULL, NULL); @@ -212,7 +267,7 @@ /* absolute path? */ if (iconname[0] == '/' || iconname[0] == '~') { - pixbuf = get_pixbuf_from_file(iconname); + new_name = g_strdup(iconname); } else { /* search in icon_path */ char *start = settings.icon_path, @@ -224,41 +279,33 @@ current_folder = g_strndup(start, end - start); for (const char **suf = suffixes; *suf; suf++) { - maybe_icon_path = g_strconcat(current_folder, "/", iconname, *suf, NULL); - if (is_readable_file(maybe_icon_path)) - pixbuf = get_pixbuf_from_file(maybe_icon_path); + gchar *name_with_extension = g_strconcat(iconname, *suf, NULL); + maybe_icon_path = g_build_filename(current_folder, name_with_extension, NULL); + if (is_readable_file(maybe_icon_path)) { + new_name = g_strdup(maybe_icon_path); + } + g_free(name_with_extension); g_free(maybe_icon_path); - if (pixbuf) + if (new_name) break; } g_free(current_folder); - if (pixbuf) + if (new_name) break; start = end + 1; } while (STR_FULL(end)); - if (!pixbuf) + if (!new_name) LOG_W("No icon found in path: '%s'", iconname); } g_free(uri_path); - return pixbuf; -} - -GdkPixbuf *icon_get_for_name(const char *name, char **id) -{ - ASSERT_OR_RET(name, NULL); - ASSERT_OR_RET(id, NULL); - - GdkPixbuf *pb = get_pixbuf_from_icon(name); - if (pb) - *id = g_strdup(name); - return pb; + return new_name; } -GdkPixbuf *icon_get_for_data(GVariant *data, char **id) +GdkPixbuf *icon_get_for_data(GVariant *data, char **id, double dpi_scale, int icon_size) { ASSERT_OR_RET(data, NULL); ASSERT_OR_RET(id, NULL); @@ -326,7 +373,13 @@ return NULL; } + // g_memdup is deprecated in glib 2.67.4 and higher. + // g_memdup2 is a safer alternative +#if GLIB_CHECK_VERSION(2,67,3) + data_pb = (guchar *) g_memdup2(g_variant_get_data(data_variant), len_actual); +#else data_pb = (guchar *) g_memdup(g_variant_get_data(data_variant), len_actual); +#endif pixbuf = gdk_pixbuf_new_from_data(data_pb, GDK_COLORSPACE_RGB, @@ -362,9 +415,9 @@ g_free(data_chk); g_variant_unref(data_variant); - pixbuf = icon_pixbuf_scale(pixbuf); + pixbuf = icon_pixbuf_scale_to_size(pixbuf, icon_size, dpi_scale); return pixbuf; } -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/icon.h dunst-1.8.1/src/icon.h --- dunst-1.5.0/src/icon.h 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/icon.h 2022-03-02 10:55:25.000000000 +0000 @@ -11,37 +11,40 @@ /** Retrieve an icon by its full filepath, scaled according to settings. * * @param filename A string representing a readable file path + * @param icon_size An iteger representing the desired unscaled icon size. + * @param scale An integer representing the output dpi scaling. * * @return an instance of `GdkPixbuf` * @retval NULL: file does not exist, not readable, etc.. */ -GdkPixbuf *get_pixbuf_from_file(const char *filename); +GdkPixbuf *get_pixbuf_from_file(const char *filename, int icon_size, double scale); -/** Retrieve an icon by its name sent via the notification bus, scaled according to settings + +/** + * Get the unscaled icon width. + * + * If scale is 2 for example, the icon will render in twice the size, but + * get_icon_width still returns the same size as when scale is 1. + */ +int get_icon_width(cairo_surface_t *icon, double scale); + +/** + * Get the unscaled icon height, see get_icon_width. + */ +int get_icon_height(cairo_surface_t *icon, double scale); + +/** Retrieve a path from an icon name. * * @param iconname A string describing a `file://` URL, an arbitary filename * or an icon name, which then gets searched for in the * settings.icon_path + * @param size Size of the icon to look for. This is only used when + * recursive icon lookup is enabled. * - * @return an instance of `GdkPixbuf` + * @return a newly allocated string with the icon path * @retval NULL: file does not exist, not readable, etc.. */ -GdkPixbuf *get_pixbuf_from_icon(const char *iconname); - -/** Read an icon from disk and convert it to a GdkPixbuf, scaled according to settings - * - * The returned id will be a unique identifier. To check if two given - * GdkPixbufs are equal, it's sufficient to just compare the id strings. - * - * @param name A string describing and icon. May be a full path, a file path or - * just a simple name. If it's a name without a slash, the icon will - * get searched in the folders of the icon_path setting. - * @param id (necessary) A unique identifier of the returned pixbuf. Only filled, - * if the return value is non-NULL. - * @return an instance of `GdkPixbuf`, representing the name's image - * @retval NULL: Invalid path given - */ -GdkPixbuf *icon_get_for_name(const char *name, char **id); +char *get_path_from_icon_name(const char *iconname, int size); /** Convert a GVariant like described in GdkPixbuf, scaled according to settings * @@ -52,10 +55,12 @@ * like described in the notification spec. * @param id (necessary) A unique identifier of the returned pixbuf. * Only filled, if the return value is non-NULL. + * @param scale An integer representing the output dpi scaling. + * @param icon_size An integer representing the desired unscaled icon size. * @return an instance of `GdkPixbuf` derived from the GVariant * @retval NULL: GVariant parameter nulled, invalid or in wrong format */ -GdkPixbuf *icon_get_for_data(GVariant *data, char **id); +GdkPixbuf *icon_get_for_data(GVariant *data, char **id, double scale, int icon_size); #endif -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/icon-lookup.c dunst-1.8.1/src/icon-lookup.c --- dunst-1.5.0/src/icon-lookup.c 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/icon-lookup.c 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,337 @@ +#define _GNU_SOURCE +#include "icon-lookup.h" + +#include <glib.h> +#include <stdio.h> +#include <malloc.h> +#include <unistd.h> +#include <assert.h> + +#include "ini.h" +#include "utils.h" +#include "log.h" + +struct icon_theme *icon_themes = NULL; +int icon_themes_count = 0; +int *default_themes_index = NULL; +int default_themes_count = 0; + +int get_icon_theme(char *name) { + for (int i = 0; i < icon_themes_count; i++) { + if (STR_EQ(icon_themes[i].subdir_theme, name)){ + return i; + } + } + return -1; +} + +/** + * Load a theme from a directory. Don't call this function if the theme is + * already loaded. It also loads the inherited themes. If there are no + * inherited themes, the theme "hicolor" is inherited. + * + * If it succeeds loading the theme, it adds theme to the list "icon_themes". + * + * @param icon_dir A directory where icon themes are stored + * @param subdir_theme The subdirectory in which the theme is located + * + * @returns the index to the theme that was loaded + * @retval -1 means no index was found + */ +int load_icon_theme_from_dir(const char *icon_dir, const char *subdir_theme) { + LOG_D("Loading theme %s/%s\n", icon_dir, subdir_theme); + char *theme_index_dir = g_build_filename(icon_dir, subdir_theme, "index.theme", NULL); + FILE *theme_index = fopen(theme_index_dir, "r"); + g_free(theme_index_dir); + if (!theme_index) + return -1; + + struct ini *ini = load_ini_file(theme_index); + fclose(theme_index); + if (ini->section_count == 0) { + finish_ini(ini); + free(ini); + return -1; + } + + icon_themes_count++; + icon_themes = realloc(icon_themes, icon_themes_count * sizeof(struct icon_theme)); + int index = icon_themes_count - 1; + icon_themes[index].name = g_strdup(section_get_value(ini, &ini->sections[0], "Name")); + icon_themes[index].location = g_strdup(icon_dir); + icon_themes[index].subdir_theme = g_strdup(subdir_theme); + icon_themes[index].inherits_index = NULL; + icon_themes[index].inherits_count = 0; + + // load theme directories + icon_themes[index].dirs_count = ini->section_count - 1; + icon_themes[index].dirs = calloc(icon_themes[index].dirs_count, sizeof(struct icon_theme_dir)); + + for (int i = 0; i < icon_themes[index].dirs_count; i++) { + struct section section = ini->sections[i+1]; + icon_themes[index].dirs[i].name = g_strdup(section.name); + + // read size + const char *size_str = section_get_value(ini, §ion, "Size"); + safe_string_to_int(&icon_themes[index].dirs[i].size, size_str); + + // read optional scale, defaulting to 1 + const char *scale_str = section_get_value(ini, §ion, "Scale"); + icon_themes[index].dirs[i].scale = 1; + if (scale_str){ + safe_string_to_int(&icon_themes[index].dirs[i].scale, scale_str); + } + + // read type + const char *type = section_get_value(ini, §ion, "Type"); + if (STR_EQ(type, "Fixed")) { + icon_themes[index].dirs[i].type = THEME_DIR_FIXED; + } else if (STR_EQ(type, "Scalable")) { + icon_themes[index].dirs[i].type = THEME_DIR_SCALABLE; + } else if (STR_EQ(type, "Threshold")) { + icon_themes[index].dirs[i].type = THEME_DIR_THRESHOLD; + } else { + // default to type threshold + icon_themes[index].dirs[i].type = THEME_DIR_THRESHOLD; + } + + // read type-specific data + if (icon_themes[index].dirs[i].type == THEME_DIR_SCALABLE) { + const char *min_size = section_get_value(ini, §ion, "MinSize"); + if (min_size) + safe_string_to_int(&icon_themes[index].dirs[i].min_size, min_size); + else + icon_themes[index].dirs[i].min_size = icon_themes[index].dirs[i].size; + + const char *max_size = section_get_value(ini, §ion, "MaxSize"); + if (max_size) + safe_string_to_int(&icon_themes[index].dirs[i].max_size, max_size); + else + icon_themes[index].dirs[i].max_size = icon_themes[index].dirs[i].size; + + } else if (icon_themes[index].dirs[i].type == THEME_DIR_THRESHOLD) { + icon_themes[index].dirs[i].threshold = 2; + const char *threshold = section_get_value(ini, §ion, "Threshold"); + if (threshold){ + safe_string_to_int(&icon_themes[index].dirs[i].threshold, threshold); + } + } + } + + + // load inherited themes + if (!STR_EQ(icon_themes[index].name, "Hicolor")) + { + char **inherits = string_to_array(get_value(ini, "Icon Theme", "Inherits"), ","); + icon_themes[index].inherits_count = string_array_length(inherits); + LOG_D("Theme has %i inherited themes\n", icon_themes[index].inherits_count); + if (icon_themes[index].inherits_count <= 0) { + // set fallback theme to hicolor if there are no inherits + g_strfreev(inherits); + inherits = calloc(2, sizeof(char*)); + inherits[0] = g_strdup("hicolor"); + inherits[1] = NULL; + icon_themes[index].inherits_count = 1; + } + + icon_themes[index].inherits_index = calloc(icon_themes[index].inherits_count, sizeof(int)); + + for (int i = 0; inherits[i] != NULL; i++) { + LOG_D("inherits: %s\n", inherits[i]); + icon_themes[index].inherits_index[i] = get_icon_theme(inherits[i]); + if (icon_themes[index].inherits_index[i] == -1) { + LOG_D("Loading inherited theme\n"); + // FIXME don't use a pointer to the theme, + // since it may be invalidated after realloc. Use an index instead + icon_themes[index].inherits_index[i] = load_icon_theme(inherits[i]); + } + } + g_strfreev(inherits); + } + + + + finish_ini(ini); + free(ini); + return index; +} + +// a list of directories where icon themes might be located +GPtrArray *theme_path = NULL; + +void get_theme_path() { + theme_path = g_ptr_array_new_full(5, g_free); + const char *home = g_get_home_dir(); + g_ptr_array_add(theme_path, g_build_filename(home, ".icons", NULL)); + + char *data_home_default = g_build_filename(home, ".local", "share", NULL); + add_paths_from_env(theme_path, "XDG_DATA_HOME", "icons", data_home_default); + g_free(data_home_default); + + add_paths_from_env(theme_path, "XDG_DATA_DIRS", "icons", "/usr/local/share/:/usr/share/"); + g_ptr_array_add(theme_path, g_strdup("/usr/share/pixmaps")); + for (int i = 0; i < theme_path->len; i++) { + LOG_D("Theme locations: %s\n", (char*)theme_path->pdata[i]); + } +} + +// see icon-lookup.h +int load_icon_theme(char *name) { + if(!theme_path) { + get_theme_path(); + } + + for (int i = 0; i < theme_path->len; i++) { + int theme_index = load_icon_theme_from_dir(theme_path->pdata[i], name); + if (theme_index != -1) + return theme_index; + } + + LOG_W("Could not find theme %s", name); + return -1; +} + +void finish_icon_theme_dir(struct icon_theme_dir *dir) { + if (!dir) + return; + free(dir->name); +} + +void finish_icon_theme(struct icon_theme *theme) { + if (!theme) + return; + for (int i = 0; i < theme->dirs_count; i++) { + finish_icon_theme_dir(&theme->dirs[i]); + } + free(theme->name); + free(theme->location); + free(theme->subdir_theme); + free(theme->inherits_index); + free(theme->dirs); +} + +void free_all_themes() { + free(default_themes_index); + default_themes_index = NULL; + default_themes_count = 0; + LOG_D("Finishing %i themes\n", icon_themes_count); + for (int i = 0; i < icon_themes_count; i++) { + finish_icon_theme(&icon_themes[i]); + } + free(icon_themes); + icon_themes_count = 0; + icon_themes = NULL; + g_ptr_array_unref(theme_path); + theme_path = NULL; +} + +// see icon-lookup.h +void add_default_theme(int theme_index) { + if (theme_index < 0) { + LOG_W("Invalid theme index: %i", theme_index); + return; + } + if (theme_index >= icon_themes_count) { + LOG_W("Invalid theme index: %i. Theme does not exists.", + theme_index); + return; + } + default_themes_count++; + default_themes_index = realloc(default_themes_index, + default_themes_count * sizeof(int)); + default_themes_index[default_themes_count - 1] = theme_index; +} + +// see icon-lookup.h +char *find_icon_in_theme(const char *name, int theme_index, int size) { + struct icon_theme *theme = &icon_themes[theme_index]; + LOG_D("Finding icon %s in theme %s\n", name, theme->name); + for (int i = 0; i < theme->dirs_count; i++) { + bool match_size = false; + struct icon_theme_dir dir = theme->dirs[i]; + switch (dir.type) { + case THEME_DIR_FIXED: + match_size = dir.size == size; + break; + + case THEME_DIR_SCALABLE: + match_size = dir.min_size <= size && dir.max_size >= size; + break; + + case THEME_DIR_THRESHOLD: + match_size = (float)dir.size / dir.threshold <= size + && dir.size * dir.threshold >= size; + break; + } + if (match_size) { + const char *suffixes[] = { ".svg", ".svgz", ".png", ".xpm", NULL }; + for (const char **suf = suffixes; *suf; suf++) { + char *name_with_extension = g_strconcat(name, *suf, NULL); + char *icon = g_build_filename(theme->location, theme->subdir_theme, + dir.name, name_with_extension, + NULL); + if (is_readable_file(icon)) { + g_free(name_with_extension); + return icon; + } + g_free(name_with_extension); + g_free(icon); + } + } + } + return NULL; +} + +char *find_icon_in_theme_with_inherit(const char *name, int theme_index, int size) { + char *icon = find_icon_in_theme(name, theme_index, size); + if (icon) + return icon; + + for (int i = 0; i < icon_themes[theme_index].inherits_count; i++) { + if (icon_themes[theme_index].inherits_index[i] <= 0) + continue; // inherited theme could not be found + icon = find_icon_in_theme(name, + icon_themes[theme_index].inherits_index[i], + size); + if (icon) + return icon; + } + return NULL; +} + +/* see icon-lookup.h */ +char *find_icon_path(const char *name, int size) { + if (STR_EMPTY(name)) + return NULL; + + gchar *uri_path = NULL; + + if (g_str_has_prefix(name, "file://")) { + uri_path = g_filename_from_uri(name, NULL, NULL); + if (is_readable_file(uri_path)) + return uri_path; + else + return NULL; + } + + /* absolute path? */ + if (name[0] == '/' || name[0] == '~') { + if (is_readable_file(name)) + return g_strdup(name); + else + return NULL; + } + + if (!default_themes_index) { + LOG_W("No icon theme has been set.\n"); + return NULL; + } + for (int i = 0; i < default_themes_count; i++) { + char *icon = find_icon_in_theme_with_inherit(name, + default_themes_index[i], size); + if (icon) + return icon; + + } + return NULL; +} diff -Nru dunst-1.5.0/src/icon-lookup.h dunst-1.8.1/src/icon-lookup.h --- dunst-1.5.0/src/icon-lookup.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/icon-lookup.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,82 @@ +#ifndef DUNST_ICON_LOOKUP_H +#define DUNST_ICON_LOOKUP_H + +struct icon_theme { + char *name; + char *location; // full path to the theme + char *subdir_theme; // name of the directory in which the theme is located + + int inherits_count; + int *inherits_index; + + int dirs_count; + struct icon_theme_dir *dirs; +}; + +enum theme_dir_type { THEME_DIR_FIXED, THEME_DIR_SCALABLE, THEME_DIR_THRESHOLD }; + +struct icon_theme_dir { + char *name; + int size; + int scale; + int min_size, max_size; + int threshold; + enum theme_dir_type type; +}; + + +/** + * Load a theme with given name from a standard icon directory. Don't call this + * function if the theme is already loaded. + * + * @param name Name of the directory in which the theme is located. Note that + * it is NOT the name of the theme as specified in index.theme. + * @returns The index of the theme, which can be used to set it as default. + * @retval -1 if the icon theme cannot be loaded. + */ +int load_icon_theme(char *name); + + +/** + * Add theme to the list of default themes. The theme that's added first will + * be used first for lookup. After that the inherited themes will be used and + * only after that the next default theme will be used. + * + * @param theme_index The index of the theme as returned by #load_icon_theme + */ +void add_default_theme(int theme_index); + +/** + * Find icon of specified size in selected theme. This function will not return + * icons that cannot be scaled to \p size according to index.theme. + * + * @param name Name of the icon or full path to it. + * @param theme_index Index of the theme to use. + * @param size Size of the icon. + * @returns The full path to the icon. + * @retval NULL if the icon cannot be found or is not readable. + */ +char *find_icon_in_theme(const char *name, int theme_index, int size); +char *find_icon_path(const char *name, int size); +void set_default_theme(int theme_index); + +/** + * Find icon of specified size in the default theme or an inherited theme. This + * function will not return icons that cannot be scaled to \p size according to + * index.theme. + + * + * @param name Name of the icon or full path to it. + * @param size Size of the icon. + * @returns The full path to the icon. + * @retval NULL if the icon cannot be found or is not readable. + */ +char *find_icon_path(const char *name, int size); + +/** + * Free all icon themes. + */ +void free_all_themes(); + +#endif +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/ini.c dunst-1.8.1/src/ini.c --- dunst-1.5.0/src/ini.c 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/ini.c 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,169 @@ +#include "ini.h" + +#include "utils.h" +#include "log.h" +#include "settings.h" + +struct section *get_section(struct ini *ini, const char *name) +{ + for (int i = 0; i < ini->section_count; i++) { + if (STR_EQ(ini->sections[i].name, name)) + return &ini->sections[i]; + } + + return NULL; +} + +struct section *get_or_create_section(struct ini *ini, const char *name) +{ + struct section *s = get_section(ini, name); + if (!s) { + ini->section_count++; + ini->sections = g_realloc(ini->sections, sizeof(struct section) * ini->section_count); + + s = &ini->sections[ini->section_count - 1]; + s->name = g_strdup(name); + s->entries = NULL; + s->entry_count = 0; + } + return s; +} + +void add_entry(struct ini *ini, const char *section_name, const char *key, const char *value) +{ + struct section *s = get_or_create_section(ini, section_name); + + s->entry_count++; + int len = s->entry_count; + s->entries = g_realloc(s->entries, sizeof(struct entry) * len); + s->entries[s->entry_count - 1].key = g_strdup(key); + s->entries[s->entry_count - 1].value = string_strip_quotes(value); +} + +const char *section_get_value(struct ini *ini, const struct section *s, const char *key) +{ + ASSERT_OR_RET(s, NULL); + + for (int i = 0; i < s->entry_count; i++) { + if (STR_EQ(s->entries[i].key, key)) { + return s->entries[i].value; + } + } + return NULL; +} + +const char *get_value(struct ini *ini, const char *section, const char *key) +{ + struct section *s = get_section(ini, section); + return section_get_value(ini, s, key); +} + +bool ini_is_set(struct ini *ini, const char *ini_section, const char *ini_key) +{ + return get_value(ini, ini_section, ini_key) != NULL; +} + +const char *next_section(const struct ini *ini,const char *section) +{ + ASSERT_OR_RET(ini->section_count > 0, NULL); + ASSERT_OR_RET(section, ini->sections[0].name); + + for (int i = 0; i < ini->section_count; i++) { + if (STR_EQ(section, ini->sections[i].name)) { + if (i + 1 >= ini->section_count) + return NULL; + else + return ini->sections[i + 1].name; + } + } + return NULL; +} + +struct ini *load_ini_file(FILE *fp) +{ + if (!fp) + return NULL; + + struct ini *ini = calloc(1, sizeof(struct ini)); + char *line = NULL; + size_t line_len = 0; + + int line_num = 0; + char *current_section = NULL; + while (getline(&line, &line_len, fp) != -1) { + line_num++; + + char *start = g_strstrip(line); + + if (*start == ';' || *start == '#' || STR_EMPTY(start)) + continue; + + if (*start == '[') { + char *end = strchr(start + 1, ']'); + if (!end) { + LOG_W("Invalid config file at line %d: Missing ']'.", line_num); + continue; + } + + *end = '\0'; + + g_free(current_section); + current_section = g_strdup(start + 1); + continue; + } + + char *equal = strchr(start + 1, '='); + if (!equal) { + LOG_W("Invalid config file at line %d: Missing '='.", line_num); + continue; + } + + *equal = '\0'; + char *key = g_strstrip(start); + char *value = g_strstrip(equal + 1); + + char *quote = strchr(value, '"'); + char *value_end = NULL; + if (quote) { + value_end = strchr(quote + 1, '"'); + if (!value_end) { + LOG_W("Invalid config file at line %d: Missing '\"'.", line_num); + continue; + } + } else { + value_end = value; + } + + char *comment = strpbrk(value_end, "#;"); + if (comment) + *comment = '\0'; + + value = g_strstrip(value); + + if (!current_section) { + LOG_W("Invalid config file at line %d: Key value pair without a section.", line_num); + continue; + } + + add_entry(ini, current_section, key, value); + } + free(line); + g_free(current_section); + return ini; +} + + +void finish_ini(struct ini *ini) +{ + for (int i = 0; i < ini->section_count; i++) { + for (int j = 0; j < ini->sections[i].entry_count; j++) { + g_free(ini->sections[i].entries[j].key); + g_free(ini->sections[i].entries[j].value); + } + g_free(ini->sections[i].entries); + g_free(ini->sections[i].name); + } + g_clear_pointer(&ini->sections, g_free); + ini->section_count = 0; +} + diff -Nru dunst-1.5.0/src/ini.h dunst-1.8.1/src/ini.h --- dunst-1.5.0/src/ini.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/ini.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,35 @@ +/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ +#ifndef DUNST_INI_H +#define DUNST_INI_H + +#include <stdbool.h> +#include <stdio.h> + +struct entry { + char *key; + char *value; +}; + +struct section { + char *name; + int entry_count; + struct entry *entries; +}; + +struct ini { + int section_count; + struct section *sections; +}; + +/* returns the next known section. + * if section == NULL returns first section. + * returns NULL if no more sections are available + */ +const char *next_section(const struct ini *ini,const char *section); +const char *section_get_value(struct ini *ini, const struct section *s, const char *key); +const char *get_value(struct ini *ini, const char *section, const char *key); +struct ini *load_ini_file(FILE *fp); +void finish_ini(struct ini *ini); + +#endif +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/input.c dunst-1.8.1/src/input.c --- dunst-1.5.0/src/input.c 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/input.c 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,85 @@ +#include "input.h" +#include "log.h" +#include "menu.h" +#include "settings.h" +#include "queues.h" +#include <stddef.h> +#include <linux/input-event-codes.h> + +struct notification *get_notification_at(const int y) { + int curr_y = settings.frame_width; + for (const GList *iter = queues_get_displayed(); iter; + iter = iter->next) { + struct notification *current = iter->data; + if (y > curr_y && y < curr_y + current->displayed_height) { + return current; + } + + curr_y += current->displayed_height + settings.separator_height; + } + // no matching notification was found + return NULL; +} + +void input_handle_click(unsigned int button, bool button_down, int mouse_x, int mouse_y){ + LOG_I("Pointer handle button %i: %i", button, button_down); + + if (button_down) { + // make sure it only reacts on button release + return; + } + + enum mouse_action *acts; + + switch (button) { + case BTN_LEFT: + acts = settings.mouse_left_click; + break; + case BTN_MIDDLE: + acts = settings.mouse_middle_click; + break; + case BTN_RIGHT: + acts = settings.mouse_right_click; + break; + case BTN_TOUCH: + // TODO Add separate action for touch + acts = settings.mouse_left_click; + break; + default: + LOG_W("Unsupported mouse button: '%d'", button); + return; + } + + // if other list types are added, make sure they have the same end value + for (int i = 0; acts[i] != MOUSE_ACTION_END; i++) { + enum mouse_action act = acts[i]; + if (act == MOUSE_CLOSE_ALL) { + queues_history_push_all(); + continue; + } + + if (act == MOUSE_CONTEXT_ALL) { + context_menu(); + continue; + } + + if (act == MOUSE_DO_ACTION || act == MOUSE_CLOSE_CURRENT || act == MOUSE_CONTEXT || act == MOUSE_OPEN_URL) { + struct notification *n = get_notification_at(mouse_y); + + if (n) { + if (act == MOUSE_CLOSE_CURRENT) { + n->marked_for_closure = REASON_USER; + } else if (act == MOUSE_DO_ACTION) { + notification_do_action(n); + } else if (act == MOUSE_OPEN_URL) { + notification_open_url(n); + } else { + notification_open_context_menu(n); + } + } + } + } + + wake_up(); +} +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/input.h dunst-1.8.1/src/input.h --- dunst-1.5.0/src/input.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/input.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,18 @@ +#ifndef DUNST_INPUT_H +#define DUNST_INPUT_H + +#include <stdbool.h> + +/** + * Handle incoming mouse click events + * + * @param button code, A linux input event code + * @param button_down State of the button + * @param mouse_x X-position of the mouse, relative to the window + * @param mouse_y Y-position of the mouse, relative to the window + * + */ +void input_handle_click(unsigned int button, bool button_down, int mouse_x, int mouse_y); + +#endif +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/log.c dunst-1.8.1/src/log.c --- dunst-1.5.0/src/log.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/log.c 2022-03-02 10:55:25.000000000 +0000 @@ -74,19 +74,21 @@ gpointer testing) { if (testing) - return; + log_level = G_LOG_LEVEL_ERROR; + + GLogLevelFlags message_level_masked = message_level & G_LOG_LEVEL_MASK; /* if you want to have a debug build, you want to log anything, * unconditionally, without specifying debug log level again */ #ifndef DEBUG_BUILD - if (log_level < message_level) + if (log_level < message_level_masked) return; #endif const char *log_level_str = - log_level_to_string(message_level & G_LOG_LEVEL_MASK); + log_level_to_string(message_level_masked); /* Use stderr for warnings and higher */ - if (message_level <= G_LOG_LEVEL_WARNING) + if (message_level_masked <= G_LOG_LEVEL_WARNING) g_printerr("%s: %s\n", log_level_str, message); else g_print("%s: %s\n", log_level_str, message); @@ -98,4 +100,4 @@ g_log_set_default_handler(dunst_log_handler, (void*)testing); } -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/log.h dunst-1.8.1/src/log.h --- dunst-1.5.0/src/log.h 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/log.h 2022-03-02 10:55:25.000000000 +0000 @@ -1,21 +1,51 @@ /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ +#include <errno.h> #include <glib.h> #include <stdbool.h> +#include <stdio.h> #include <stdlib.h> #ifndef DUNST_LOG_H #define DUNST_LOG_H +/** + * Prefix message with "[<source path>:<function name>:<line number>] " + * + * @param format is either a format string like the first argument + * of printf() or a string literal. + * @... are the arguments to above format string. + * + * This requires -Wno-gnu-zero-variadic-macro-arguments with clang + * because of token pasting ',' and %__VA_ARGS__ being a GNU extension. + * However, the result is the same with both gcc and clang and since we are + * compiling with '-std=gnu99', this should be fine. + */ +#if __GNUC__ >= 8 || __clang_major__ >= 6 +#define MSG(format, ...) "[%16s:%04d] " format, __func__, __LINE__, ## __VA_ARGS__ +#endif + +#ifdef MSG +// These should benefit from more context +#define LOG_E(...) g_error(MSG(__VA_ARGS__)) +#define LOG_C(...) g_critical(MSG(__VA_ARGS__)) +#define LOG_D(...) g_debug(MSG(__VA_ARGS__)) +#else #define LOG_E g_error #define LOG_C g_critical +#define LOG_D g_debug +#endif + #define LOG_W g_warning #define LOG_M g_message #define LOG_I g_info -#define LOG_D g_debug #define DIE(...) do { LOG_C(__VA_ARGS__); exit(EXIT_FAILURE); } while (0) +// unified fopen() result messages +#define MSG_FOPEN_SUCCESS(path, fp) "'%s' open, fd: '%d'", path, fileno(fp) +#define MSG_FOPEN_FAILURE(path) "Cannot open '%s': '%s'", path, strerror(errno) + /** * Set the current loglevel to `level` * @@ -45,4 +75,4 @@ void dunst_log_init(bool testing); #endif -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/markup.c dunst-1.8.1/src/markup.c --- dunst-1.5.0/src/markup.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/markup.c 2022-03-02 10:55:25.000000000 +0000 @@ -338,4 +338,4 @@ return str; } -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/markup.h dunst-1.8.1/src/markup.h --- dunst-1.5.0/src/markup.h 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/markup.h 2022-03-02 10:55:25.000000000 +0000 @@ -48,4 +48,4 @@ char *markup_transform(char *str, enum markup_mode markup_mode); #endif -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/menu.c dunst-1.8.1/src/menu.c --- dunst-1.5.0/src/menu.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/menu.c 2022-03-02 10:55:25.000000000 +0000 @@ -23,12 +23,12 @@ static bool is_initialized = false; static regex_t url_regex; -struct notification_lock { - struct notification *n; - gint64 timeout; -}; static gpointer context_menu_thread(gpointer data); +struct { + GList *locked_notifications; +} menu_ctx; + /** * Initializes regexes needed for matching. * @@ -290,9 +290,43 @@ return ret; } +/** + * Lock and get all notifications with an action or URL. + **/ +static GList *get_actionable_notifications(void) +{ + GList *locked_notifications = NULL; + + for (const GList *iter = queues_get_displayed(); iter; + iter = iter->next) { + struct notification *n = iter->data; + + if (n->urls || g_hash_table_size(n->actions)) { + notification_lock(n); + locked_notifications = g_list_prepend(locked_notifications, n); + } + } + + return locked_notifications; +} + /* see menu.h */ void context_menu(void) { + GList *notifications = get_actionable_notifications(); + context_menu_for(notifications); +} + +/* see menu.h */ +void context_menu_for(GList *notifications) +{ + if (menu_ctx.locked_notifications) { + LOG_W("Context menu already running, refusing to rerun"); + return; + } + + menu_ctx.locked_notifications = notifications; + GError *err = NULL; g_thread_unref(g_thread_try_new("dmenu", context_menu_thread, @@ -305,31 +339,40 @@ } } -static gpointer context_menu_thread(gpointer data) +static gboolean context_menu_result_dispatch(gpointer user_data) { - char *dmenu_input = NULL; - char *dmenu_output; + char *dmenu_output = (char*)user_data; - GList *locked_notifications = NULL; + dispatch_menu_result(dmenu_output); - for (const GList *iter = queues_get_displayed(); iter; - iter = iter->next) { + for (GList *iter = menu_ctx.locked_notifications; iter; iter = iter->next) { struct notification *n = iter->data; + notification_unlock(n); + if (n->marked_for_closure) { + // Don't close notification if context was aborted + if (dmenu_output != NULL) + queues_notification_close(n, n->marked_for_closure); + n->marked_for_closure = 0; + } + } + menu_ctx.locked_notifications = NULL; - // Reference and lock the notification if we need it - if (n->urls || g_hash_table_size(n->actions)) { - notification_ref(n); + g_list_free(menu_ctx.locked_notifications); + g_free(dmenu_output); - struct notification_lock *nl = - g_malloc(sizeof(struct notification_lock)); + wake_up(); - nl->n = n; - nl->timeout = n->timeout; - n->timeout = 0; + return G_SOURCE_REMOVE; +} - locked_notifications = g_list_prepend(locked_notifications, nl); - } +static gpointer context_menu_thread(gpointer data) +{ + char *dmenu_input = NULL; + char *dmenu_output; + + for (GList *iter = menu_ctx.locked_notifications; iter; iter = iter->next) { + struct notification *n = iter->data; char *dmenu_str = notification_dmenu_string(n); dmenu_input = string_append(dmenu_input, dmenu_str, "\n"); @@ -340,26 +383,10 @@ } dmenu_output = invoke_dmenu(dmenu_input); - dispatch_menu_result(dmenu_output); + g_timeout_add(50, context_menu_result_dispatch, dmenu_output); g_free(dmenu_input); - g_free(dmenu_output); - - // unref all notifications - for (GList *iter = locked_notifications; - iter; - iter = iter->next) { - - struct notification_lock *nl = iter->data; - struct notification *n = nl->n; - - n->timeout = nl->timeout; - - g_free(nl); - notification_unref(n); - } - g_list_free(locked_notifications); return NULL; } -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/menu.h dunst-1.8.1/src/menu.h --- dunst-1.5.0/src/menu.h 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/menu.h 2022-03-02 10:55:25.000000000 +0000 @@ -2,6 +2,8 @@ #ifndef DUNST_MENU_H #define DUNST_MENU_H +#include <glib.h> + /** * Extract all urls from the given string. * @@ -16,9 +18,15 @@ void regex_teardown(void); /** - * Open the context menu that lets the user select urls/actions/etc. + * Open the context menu that lets the user select urls/actions/etc for all displayed notifications. */ void context_menu(void); +/** + * Open the context menu that lets the user select urls/actions/etc for the specified notifications. + * @param notifications (nullable) List of notifications for which the context menu should be opened + */ +void context_menu_for(GList *notifications); + #endif -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/notification.c dunst-1.8.1/src/notification.c --- dunst-1.5.0/src/notification.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/notification.c 2022-03-02 10:55:25.000000000 +0000 @@ -23,6 +23,8 @@ #include "rules.h" #include "settings.h" #include "utils.h" +#include "draw.h" +#include "icon-lookup.h" static void notification_extract_urls(struct notification *n); static void notification_format_message(struct notification *n); @@ -64,8 +66,10 @@ printf("\tformatted: '%s'\n", n->msg); printf("\tfg: %s\n", n->colors.fg); printf("\tbg: %s\n", n->colors.bg); + printf("\thighlight: %s\n", n->colors.highlight); printf("\tframe: %s\n", n->colors.frame); printf("\tfullscreen: %s\n", enum_to_string_fullscreen(n->fullscreen)); + printf("\tformat: %s\n", n->format); printf("\tprogress: %d\n", n->progress); printf("\tstack_tag: %s\n", (n->stack_tag ? n->stack_tag : "")); printf("\tid: %d\n", n->id); @@ -97,6 +101,7 @@ printf("\n"); } printf("}\n"); + fflush(stdout); } /* see notification.h */ @@ -127,11 +132,32 @@ int status; waitpid(pid1, &status, 0); } else { + // second fork to prevent zombie processes int pid2 = fork(); if (pid2) { exit(0); } else { - int ret = execlp(script, + // Set environment variables + gchar *n_id_str = g_strdup_printf("%i", n->id); + gchar *n_progress_str = g_strdup_printf("%i", n->progress); + gchar *n_timeout_str = g_strdup_printf("%li", n->timeout/1000); + gchar *n_timestamp_str = g_strdup_printf("%li", n->timestamp / 1000); + safe_setenv("DUNST_APP_NAME", appname); + safe_setenv("DUNST_SUMMARY", summary); + safe_setenv("DUNST_BODY", body); + safe_setenv("DUNST_ICON_PATH", n->icon_path); + safe_setenv("DUNST_URGENCY", urgency); + safe_setenv("DUNST_ID", n_id_str); + safe_setenv("DUNST_PROGRESS", n_progress_str); + safe_setenv("DUNST_CATEGORY", n->category); + safe_setenv("DUNST_STACK_TAG", n->stack_tag); + safe_setenv("DUNST_URLS", n->urls); + safe_setenv("DUNST_TIMEOUT", n_timeout_str); + safe_setenv("DUNST_TIMESTAMP", n_timestamp_str); + safe_setenv("DUNST_STACK_TAG", n->stack_tag); + safe_setenv("DUNST_DESKTOP_ENTRY", n->desktop_entry); + + execlp(script, script, appname, summary, @@ -139,10 +165,9 @@ icon, urgency, (char *)NULL); - if (ret != 0) { - LOG_W("Unable to run script: %s", strerror(errno)); - exit(EXIT_FAILURE); - } + + LOG_W("Unable to run script %s: %s", n->scripts[i], strerror(errno)); + exit(EXIT_FAILURE); } } } @@ -170,7 +195,7 @@ /* see notification.h */ int notification_cmp(const struct notification *a, const struct notification *b) { - if (a->urgency != b->urgency) { + if (settings.sort && a->urgency != b->urgency) { return b->urgency - a->urgency; } else { return a->id - b->id; @@ -183,8 +208,6 @@ struct notification *a = (struct notification *) va; struct notification *b = (struct notification *) vb; - ASSERT_OR_RET(settings.sort, 1); - return notification_cmp(a, b); } @@ -193,10 +216,34 @@ return STR_EQ(a->appname, b->appname) && STR_EQ(a->summary, b->summary) && STR_EQ(a->body, b->body) - && (settings.icon_position != ICON_OFF ? STR_EQ(a->icon_id, b->icon_id) : 1) + && (a->icon_position != ICON_OFF ? STR_EQ(a->icon_id, b->icon_id) : 1) && a->urgency == b->urgency; } +bool notification_is_locked(struct notification *n) { + assert(n); + + return g_atomic_int_get(&n->locked) != 0; +} + +struct notification* notification_lock(struct notification *n) { + assert(n); + + g_atomic_int_set(&n->locked, 1); + notification_ref(n); + + return n; +} + +struct notification* notification_unlock(struct notification *n) { + assert(n); + + g_atomic_int_set(&n->locked, 0); + notification_unref(n); + + return n; +} + static void notification_private_free(NotificationPrivate *p) { g_free(p); @@ -229,6 +276,8 @@ g_free(n->summary); g_free(n->body); g_free(n->iconname); + g_free(n->default_icon_name); + g_free(n->icon_path); g_free(n->msg); g_free(n->dbus_client); g_free(n->category); @@ -236,14 +285,16 @@ g_free(n->urls); g_free(n->colors.fg); g_free(n->colors.bg); + g_free(n->colors.highlight); g_free(n->colors.frame); g_free(n->stack_tag); g_free(n->desktop_entry); g_hash_table_unref(n->actions); + g_free(n->default_action_name); if (n->icon) - g_object_unref(n->icon); + cairo_surface_destroy(n->icon); g_free(n->icon_id); notification_private_free(n->priv); @@ -255,19 +306,48 @@ g_free(n); } +void notification_transfer_icon(struct notification *from, struct notification *to) +{ + if (from->iconname && to->iconname + && strcmp(from->iconname, to->iconname) == 0){ + // Icons are the same. Transfer icon surface + to->icon = from->icon; + + // prevent the surface being freed by the old notification + from->icon = NULL; + } +} + void notification_icon_replace_path(struct notification *n, const char *new_icon) { ASSERT_OR_RET(n,); ASSERT_OR_RET(new_icon,); - ASSERT_OR_RET(n->iconname != new_icon,); + if(n->iconname && n->icon && strcmp(n->iconname, new_icon) == 0) { + return; + } - g_free(n->iconname); - n->iconname = g_strdup(new_icon); + // make sure it works, even if n->iconname is passed as new_icon + if (n->iconname != new_icon) { + g_free(n->iconname); + n->iconname = g_strdup(new_icon); + } - g_clear_object(&n->icon); + cairo_surface_destroy(n->icon); + n->icon = NULL; g_clear_pointer(&n->icon_id, g_free); - n->icon = icon_get_for_name(new_icon, &n->icon_id); + g_free(n->icon_path); + n->icon_path = get_path_from_icon_name(new_icon, n->icon_size); + if (n->icon_path) { + GdkPixbuf *pixbuf = get_pixbuf_from_file(n->icon_path, + n->icon_size, draw_get_scale()); + if (pixbuf) { + n->icon = gdk_pixbuf_to_cairo_surface(pixbuf); + g_object_unref(pixbuf); + } else { + LOG_W("No icon found in path: '%s'", n->icon_path); + } + } } void notification_icon_replace_data(struct notification *n, GVariant *new_icon) @@ -275,10 +355,15 @@ ASSERT_OR_RET(n,); ASSERT_OR_RET(new_icon,); - g_clear_object(&n->icon); + cairo_surface_destroy(n->icon); + n->icon = NULL; g_clear_pointer(&n->icon_id, g_free); - n->icon = icon_get_for_data(new_icon, &n->icon_id); + GdkPixbuf *icon = icon_get_for_data(new_icon, &n->icon_id, + draw_get_scale(), n->icon_size); + n->icon = gdk_pixbuf_to_cairo_surface(icon); + if (icon) + g_object_unref(icon); } /* see notification.h */ @@ -322,7 +407,7 @@ /* Unparameterized default values */ n->first_render = true; - n->markup = settings.markup; + n->markup = MARKUP_FULL; n->format = settings.format; n->timestamp = time_monotonic_now(); @@ -332,6 +417,13 @@ n->transient = false; n->progress = -1; + n->word_wrap = true; + n->ellipsize = PANGO_ELLIPSIZE_MIDDLE; + n->alignment = PANGO_ALIGN_LEFT; + n->progress_bar_alignment = PANGO_ALIGN_CENTER; + n->hide_text = false; + n->icon_position = ICON_LEFT; + n->icon_size = 32; n->script_run = false; n->dbus_valid = false; @@ -339,6 +431,7 @@ n->fullscreen = FS_SHOW; n->actions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + n->default_action_name = g_strdup("default"); n->script_count = 0; return n; @@ -363,17 +456,6 @@ if (n->timeout < 0) n->timeout = settings.timeouts[n->urgency]; - /* Icon handling */ - if (STR_EMPTY(n->iconname)) - g_clear_pointer(&n->iconname, g_free); - if (!n->icon && n->iconname) { - char *icon = g_strdup(n->iconname); - notification_icon_replace_path(n, icon); - g_free(icon); - } - if (!n->icon && !n->iconname) - notification_icon_replace_path(n, settings.icons[n->urgency]); - /* Color hints */ struct notification_colors defcolors; switch (n->urgency) { @@ -393,6 +475,8 @@ n->colors.fg = g_strdup(defcolors.fg); if (!n->colors.bg) n->colors.bg = g_strdup(defcolors.bg); + if (!n->colors.highlight) + n->colors.highlight = g_strdup(defcolors.highlight); if (!n->colors.frame) n->colors.frame = g_strdup(defcolors.frame); @@ -403,6 +487,22 @@ /* Process rules */ rule_apply_all(n); + if (g_str_has_prefix(n->summary, "DUNST_COMMAND_")) { + char *msg = "DUNST_COMMAND_* has been removed, please switch to dunstctl. See #830 for more details. https://github.com/dunst-project/dunst/pull/830"; + LOG_W("%s", msg); + n->body = string_append(n->body, msg, "\n"); + } + + /* Icon handling */ + if (STR_EMPTY(n->iconname)) + g_clear_pointer(&n->iconname, g_free); + if (!n->icon && !n->iconname && n->default_icon_name) { + n->iconname = g_strdup(n->default_icon_name); + } + if (!n->icon && !n->iconname) + n->iconname = g_strdup(settings.icons[n->urgency]); + + /* UPDATE derived fields */ notification_extract_urls(n); notification_format_message(n); @@ -592,31 +692,51 @@ } /* see notification.h */ -void notification_do_action(const struct notification *n) +void notification_do_action(struct notification *n) { + assert(n->default_action_name); + if (g_hash_table_size(n->actions)) { - if (g_hash_table_contains(n->actions, "default")) { - signal_action_invoked(n, "default"); + if (g_hash_table_contains(n->actions, n->default_action_name)) { + signal_action_invoked(n, n->default_action_name); return; } - if (g_hash_table_size(n->actions) == 1) { + if (strcmp(n->default_action_name, "default") == 0 && g_hash_table_size(n->actions) == 1) { GList *keys = g_hash_table_get_keys(n->actions); signal_action_invoked(n, keys->data); g_list_free(keys); return; } - context_menu(); + notification_open_context_menu(n); - } else if (n->urls) { - if (strstr(n->urls, "\n")) - context_menu(); - else - open_browser(n->urls); } } +/* see notification.h */ +void notification_open_url(struct notification *n) +{ + if (!n->urls) { + LOG_W("There are no URL's in this notification"); + return; + } + if (strstr(n->urls, "\n")) + notification_open_context_menu(n); + else + open_browser(n->urls); +} + +/* see notification.h */ +void notification_open_context_menu(struct notification *n) +{ + GList *notifications = NULL; + notifications = g_list_append(notifications, n); + notification_lock(n); + + context_menu_for(notifications); +} + void notification_invalidate_actions(struct notification *n) { g_hash_table_remove_all(n->actions); } -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/notification.h dunst-1.8.1/src/notification.h --- dunst-1.5.0/src/notification.h 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/notification.h 2022-03-02 10:55:25.000000000 +0000 @@ -2,14 +2,22 @@ #ifndef DUNST_NOTIFICATION_H #define DUNST_NOTIFICATION_H -#include <gdk-pixbuf/gdk-pixbuf.h> #include <glib.h> #include <stdbool.h> +#include <pango/pango-layout.h> +#include <cairo.h> #include "markup.h" #define DUNST_NOTIF_MAX_CHARS 50000 +enum icon_position { + ICON_LEFT, + ICON_RIGHT, + ICON_TOP, + ICON_OFF +}; + enum behavior_fullscreen { FS_NULL, //!< Invalid value FS_DELAY, //!< Delay the notification until leaving fullscreen mode @@ -33,6 +41,7 @@ char *frame; char *bg; char *fg; + char *highlight; }; struct notification { @@ -48,17 +57,24 @@ char *desktop_entry; /**< The desktop entry hint sent via every GApplication */ enum urgency urgency; - GdkPixbuf *icon; /**< The raw cached icon data used to draw */ - char *icon_id; /**< plain icon information, which acts as the pixbuf's id, which is saved in .icon + cairo_surface_t *icon; /**< The raw cached icon data used to draw */ + char *icon_id; /**< Plain icon information, which acts as the icon's id. May be a hash for a raw icon or a name/path for a regular app icon. */ - char *iconname; /**< plain icon information (may be a path or just a name) - Use this to compare the icon name with rules.*/ - - gint64 start; /**< begin of current display */ - gint64 timestamp; /**< arrival time */ - gint64 timeout; /**< time to display */ + char *iconname; /**< plain icon information (may be a path or just a name) as recieved from dbus. + Use this to compare the icon name with rules. May also be modified by rules.*/ + char *icon_path; /**< Full path to the notification's icon. */ + char *default_icon_name; /**< The icon that is used when no other icon is available. */ + int icon_size; /**< Size of the icon used for searching the right icon. */ + enum icon_position icon_position; /**< Icon position (enum left,right,top,off). */ + + gint64 start; /**< begin of current display (in milliseconds) */ + gint64 timestamp; /**< arrival time (in milliseconds) */ + gint64 timeout; /**< time to display (in milliseconds) */ + int locked; /**< If non-zero the notification is locked **/ + PangoAlignment progress_bar_alignment; /**< Horizontal alignment of the progress bar **/ GHashTable *actions; + char *default_action_name; /**< The name of the action to be invoked on do_action */ enum markup_mode markup; const char *format; @@ -81,6 +97,11 @@ int displayed_height; enum behavior_fullscreen fullscreen; //!< The instruction what to do with it, when desktop enters fullscreen bool script_run; /**< Has the script been executed already? */ + guint8 marked_for_closure; + bool word_wrap; + PangoEllipsizeMode ellipsize; + PangoAlignment alignment; + bool hide_text; /* derived fields */ char *msg; /**< formatted message */ @@ -138,6 +159,22 @@ bool notification_is_duplicate(const struct notification *a, const struct notification *b); +bool notification_is_locked(struct notification *n); + +struct notification *notification_lock(struct notification *n); + +struct notification *notification_unlock(struct notification *n); + +/** + * Transfer the image surface of \p from to \p to. The image surface is + * transfered only if the icon names match. When the icon is transferred, it is + * removed from the old notification to make sure it's not freed twice. + * + * @param from The notification of which the icon surface is removed. + * @param to The notification that receives the icon surface. + */ +void notification_transfer_icon(struct notification *from, struct notification *to); + /**Replace the current notification's icon with the icon specified by path. * * Removes the reference for the previous icon automatically and will also free the @@ -186,11 +223,24 @@ void notification_update_text_to_render(struct notification *n); /** - * If the notification has exactly one action, or one is marked as default, - * invoke it. If there are multiple and no default, open the context menu. If - * there are no actions, proceed similarly with urls. + * If the notification has an action named n->default_action_name or there is only one + * action and n->default_action_name is set to "default", invoke it. If there is no + * such action, open the context menu if threre are other actions. Otherwise, do nothing. + */ +void notification_do_action(struct notification *n); + +/** + * If the notification has exactly one url, invoke it. If there are multiple, + * open the context menu. If there are no urls, do nothing. + */ +void notification_open_url(struct notification *n); + +/** + * Open the context menu for the notification. + * + * Convenience function that creates the GList and passes it to context_menu_for(). */ -void notification_do_action(const struct notification *n); +void notification_open_context_menu(struct notification *n); /** * Remove all client action data from the notification. @@ -212,4 +262,4 @@ const char *enum_to_string_fullscreen(enum behavior_fullscreen in); #endif -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/option_parser.c dunst-1.8.1/src/option_parser.c --- dunst-1.5.0/src/option_parser.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/option_parser.c 2022-03-02 10:55:25.000000000 +0000 @@ -7,30 +7,15 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <errno.h> +#include "x11/x.h" #include "dunst.h" #include "log.h" #include "utils.h" #include "settings.h" - -struct entry { - char *key; - char *value; -}; - -struct section { - char *name; - int entry_count; - struct entry *entries; -}; - -static int section_count = 0; -static struct section *sections; - -static struct section *new_section(const char *name); -static struct section *get_section(const char *name); -static void add_entry(const char *section_name, const char *key, const char *value); -static const char *get_value(const char *section, const char *key); +#include "rules.h" +#include "settings_data.h" static int cmdline_argc; static char **cmdline_argv; @@ -42,388 +27,450 @@ #define STRING_PARSE_RET(string, value) if (STR_EQ(s, string)) { *ret = value; return true; } -bool string_parse_alignment(const char *s, enum alignment *ret) -{ - ASSERT_OR_RET(STR_FULL(s), false); - ASSERT_OR_RET(ret, false); - - STRING_PARSE_RET("left", ALIGN_LEFT); - STRING_PARSE_RET("center", ALIGN_CENTER); - STRING_PARSE_RET("right", ALIGN_RIGHT); - - return false; -} - -bool string_parse_ellipsize(const char *s, enum ellipsize *ret) -{ - ASSERT_OR_RET(STR_FULL(s), false); - ASSERT_OR_RET(ret, false); - - STRING_PARSE_RET("start", ELLIPSE_START); - STRING_PARSE_RET("middle", ELLIPSE_MIDDLE); - STRING_PARSE_RET("end", ELLIPSE_END); - - return false; -} - -bool string_parse_follow_mode(const char *s, enum follow_mode *ret) -{ - ASSERT_OR_RET(STR_FULL(s), false); - ASSERT_OR_RET(ret, false); - - STRING_PARSE_RET("mouse", FOLLOW_MOUSE); - STRING_PARSE_RET("keyboard", FOLLOW_KEYBOARD); - STRING_PARSE_RET("none", FOLLOW_NONE); - - return false; -} - - -bool string_parse_fullscreen(const char *s, enum behavior_fullscreen *ret) -{ - ASSERT_OR_RET(STR_FULL(s), false); - ASSERT_OR_RET(ret, false); - - STRING_PARSE_RET("show", FS_SHOW); - STRING_PARSE_RET("delay", FS_DELAY); - STRING_PARSE_RET("pushback", FS_PUSHBACK); - - return false; -} - -bool string_parse_icon_position(const char *s, enum icon_position *ret) -{ - ASSERT_OR_RET(STR_FULL(s), false); - ASSERT_OR_RET(ret, false); - - STRING_PARSE_RET("left", ICON_LEFT); - STRING_PARSE_RET("right", ICON_RIGHT); - STRING_PARSE_RET("off", ICON_OFF); - - return false; -} - -bool string_parse_vertical_alignment(const char *s, enum vertical_alignment *ret) -{ - ASSERT_OR_RET(STR_FULL(s), false); - ASSERT_OR_RET(ret, false); - - STRING_PARSE_RET("top", VERTICAL_TOP); - STRING_PARSE_RET("center", VERTICAL_CENTER); - STRING_PARSE_RET("bottom", VERTICAL_BOTTOM); - - return false; -} - -bool string_parse_markup_mode(const char *s, enum markup_mode *ret) -{ - ASSERT_OR_RET(STR_FULL(s), false); - ASSERT_OR_RET(ret, false); - - STRING_PARSE_RET("strip", MARKUP_STRIP); - STRING_PARSE_RET("no", MARKUP_NO); - STRING_PARSE_RET("full", MARKUP_FULL); - STRING_PARSE_RET("yes", MARKUP_FULL); - +int string_parse_enum(const void *data, const char *s, void * ret) { + struct string_to_enum_def *string_to_enum = (struct string_to_enum_def*)data; + for (int i = 0; string_to_enum[i].string != NULL; i++) { + if (strcmp(s, string_to_enum[i].string) == 0){ + *(int*) ret = string_to_enum[i].enum_value; + LOG_D("Setting enum to %i (%s)", *(int*) ret, string_to_enum[i].string); + return true; + } + } return false; } -bool string_parse_mouse_action(const char *s, enum mouse_action *ret) +// Only changes the return when succesful +// +// @returns True for success, false otherwise. +int string_parse_enum_list(const void *data, char **s, void *ret_void) { - ASSERT_OR_RET(STR_FULL(s), false); + int **ret = (int **) ret_void; + int *tmp; + ASSERT_OR_RET(s, false); ASSERT_OR_RET(ret, false); - STRING_PARSE_RET("none", MOUSE_NONE); - STRING_PARSE_RET("do_action", MOUSE_DO_ACTION); - STRING_PARSE_RET("close_current", MOUSE_CLOSE_CURRENT); - STRING_PARSE_RET("close_all", MOUSE_CLOSE_ALL); + int len = string_array_length(s); - return false; + tmp = g_malloc_n((len + 1), sizeof(int)); + for (int i = 0; i < len; i++) { + if (!string_parse_enum(data, s[i], tmp + i)) { + LOG_W("Unknown mouse action value: '%s'", s[i]); + g_free(tmp); + return false; + } + } + tmp[len] = MOUSE_ACTION_END; // sentinel end value + g_free(*ret); + *ret = tmp; + return true; } -bool string_parse_mouse_action_list(char **s, enum mouse_action **ret) +// Parse a string list of enum values and return a single integer with the +// values bit-flipped into it. This is only useful when the enum values all +// occupy different bits (for exampel 1<<0, 1<<1 and 1<<2) and the order +// doesn't matter. +// Only changes the return when succesful +// +// @returns True for success, false otherwise. +int string_parse_enum_list_to_single(const void *data, char **s, int *ret) { + int tmp = 0, tmp_ret = 0; ASSERT_OR_RET(s, false); ASSERT_OR_RET(ret, false); - int len = 0; - while (s[len]) - len++; - - *ret = g_malloc_n((len + 1), sizeof(enum mouse_action)); + int len = string_array_length(s); for (int i = 0; i < len; i++) { - if (!string_parse_mouse_action(s[i], *ret + i)) { + if (!string_parse_enum(data, s[i], &tmp)) { LOG_W("Unknown mouse action value: '%s'", s[i]); - g_free(*ret); return false; } + tmp_ret |= tmp; } - (*ret)[len] = -1; // sentinel end value + *ret = tmp_ret; return true; } -bool string_parse_sepcolor(const char *s, struct separator_color_data *ret) -{ - ASSERT_OR_RET(STR_FULL(s), false); - ASSERT_OR_RET(ret, false); +// When allow empty is true, empty strings are interpreted as -1 +bool string_parse_int_list(char **s, int **ret, bool allow_empty) { + int len = string_array_length(s); + ASSERT_OR_RET(s, false); - STRING_PARSE_RET("auto", (struct separator_color_data){.type = SEP_AUTO}); - STRING_PARSE_RET("foreground", (struct separator_color_data){.type = SEP_FOREGROUND}); - STRING_PARSE_RET("frame", (struct separator_color_data){.type = SEP_FRAME}); + int *tmp = g_malloc_n((len + 1), sizeof(int)); + for (int i = 0; i < len; i++) { + if (allow_empty && STR_EMPTY(s[i])) { + tmp[i] = -1; + continue; + } + bool success = safe_string_to_int(&tmp[i], s[i]); + if (!success) { + LOG_W("Invalid int value: '%s'", s[i]); + free(tmp); + return false; + } - ret->type = SEP_CUSTOM; - ret->sep_color = g_strdup(s); + } + tmp[len] = LIST_END; + g_free(*ret); + *ret = tmp; return true; } -bool string_parse_urgency(const char *s, enum urgency *ret) -{ - ASSERT_OR_RET(STR_FULL(s), false); - ASSERT_OR_RET(ret, false); - - STRING_PARSE_RET("low", URG_LOW); - STRING_PARSE_RET("normal", URG_NORM); - STRING_PARSE_RET("critical", URG_CRIT); - - return false; -} +// Only changes the return when succesful +int string_parse_list(const void *data, const char *s, void *ret) { + const enum list_type type = GPOINTER_TO_INT(data); + char **arr = NULL; + int success = false; + switch (type) { + case MOUSE_LIST: + arr = string_to_array(s, ","); + success = string_parse_enum_list(&mouse_action_enum_data, + arr, ret); + break; + case OFFSET_LIST: + arr = string_to_array(s, "x"); + int len = string_array_length(arr); + if (len != 2) { + success = false; + LOG_W("Offset has two values, separated by an 'x'"); + break; + } + int *int_arr = NULL; + success = string_parse_int_list(arr, &int_arr, false); + if (!success) + break; + + struct position* offset = (struct position*) ret; + offset->x = int_arr[0]; + offset->y = int_arr[1]; + g_free(int_arr); + break; + case STRING_LIST: ; + g_strfreev(*(char ***) ret); + *(char ***) ret = string_to_array(s, ","); + success = true; + break; -struct section *new_section(const char *name) -{ - for (int i = 0; i < section_count; i++) { - if (STR_EQ(name, sections[i].name)) { - DIE("Duplicated section in dunstrc detected."); - } + default: + LOG_W("Don't know this list type: %i", type); + break; } - - section_count++; - sections = g_realloc(sections, sizeof(struct section) * section_count); - sections[section_count - 1].name = g_strdup(name); - sections[section_count - 1].entries = NULL; - sections[section_count - 1].entry_count = 0; - return §ions[section_count - 1]; + g_strfreev(arr); + return success; } -void free_ini(void) +int string_parse_sepcolor(const void *data, const char *s, void *ret) { - for (int i = 0; i < section_count; i++) { - for (int j = 0; j < sections[i].entry_count; j++) { - g_free(sections[i].entries[j].key); - g_free(sections[i].entries[j].value); + LOG_D("parsing sep_color"); + struct separator_color_data *sep_color = (struct separator_color_data*) ret; + + enum separator_color type; + bool is_enum = string_parse_enum(data, s, &type); + if (is_enum) { + sep_color->type = type; + g_free(sep_color->sep_color); + sep_color->sep_color = NULL; + return true; + } else { + if (STR_EMPTY(s)) { + LOG_W("Sep color is empty, make sure to quote the value if it's a color."); + return false; } - g_free(sections[i].entries); - g_free(sections[i].name); - } - g_clear_pointer(§ions, g_free); - section_count = 0; -} + if (s[0] != '#') { + LOG_W("Sep color should start with a '#'"); + return false; + } + if (strlen(s) < 4) { + LOG_W("Make sure the sep color is formatted correctly"); + return false; + } + // TODO add more checks for if the color is valid -struct section *get_section(const char *name) -{ - for (int i = 0; i < section_count; i++) { - if (STR_EQ(sections[i].name, name)) - return §ions[i]; + sep_color->type = SEP_CUSTOM; + g_free(sep_color->sep_color); + sep_color->sep_color = g_strdup(s); + return true; } - - return NULL; } -void add_entry(const char *section_name, const char *key, const char *value) -{ - struct section *s = get_section(section_name); - if (!s) - s = new_section(section_name); - - s->entry_count++; - int len = s->entry_count; - s->entries = g_realloc(s->entries, sizeof(struct entry) * len); - s->entries[s->entry_count - 1].key = g_strdup(key); - s->entries[s->entry_count - 1].value = string_strip_quotes(value); -} -const char *get_value(const char *section, const char *key) +int string_parse_bool(const void *data, const char *s, void *ret) { - struct section *s = get_section(section); - ASSERT_OR_RET(s, NULL); + // this is needed, since string_parse_enum assumses a + // variable of size int is passed + int tmp_int = -1; + bool success = string_parse_enum(data, s, &tmp_int); + + *(bool*) ret = (bool) tmp_int; + return success; +} + +int get_setting_id(const char *key, const char *section) { + int error_code = 0; + int partial_match_id = -1; + bool match_section = section && is_special_section(section); + if (!match_section) { + LOG_D("not matching section %s", section); + } + for (int i = 0; i < G_N_ELEMENTS(allowed_settings); i++) { + if (strcmp(allowed_settings[i].name, key) == 0) { + bool is_rule = allowed_settings[i].rule_offset > 0; - for (int i = 0; i < s->entry_count; i++) { - if (STR_EQ(s->entries[i].key, key)) { - return s->entries[i].value; + // a rule matches every section + if (is_rule || strcmp(section, allowed_settings[i].section) == 0) { + return i; + } else { + // name matches, but in wrong section. Continueing to see + // if we find the same setting name with another section + error_code = -2; + partial_match_id = i; + continue; + } } } - return NULL; -} -char *ini_get_path(const char *section, const char *key, const char *def) -{ - return string_to_path(ini_get_string(section, key, def)); -} - -char *ini_get_string(const char *section, const char *key, const char *def) -{ - const char *value = get_value(section, key); - if (value) - return g_strdup(value); + if (error_code == -2) { + LOG_W("Setting %s is in the wrong section (%s, should be %s)", + key, section, + allowed_settings[partial_match_id].section); + // found, but in wrong section + return -2; + } - return def ? g_strdup(def) : NULL; + // not found + return -1; } -gint64 ini_get_time(const char *section, const char *key, gint64 def) -{ - const char *timestring = get_value(section, key); - gint64 val = def; - - if (timestring) { - val = string_to_time(timestring); +// TODO simplify this function +int string_parse_length(void *ret_in, const char *s) { + struct length *ret = (struct length*) ret_in; + int val = 0; + char *s_stripped = string_strip_brackets(s); + if (!s_stripped) + { + // single int without brackets + bool success = safe_string_to_int(&val, s); + if (success && val > 0) { + // single int + ret->min = val; + ret->max = val; + return true; + } + if (val <= 0) { + LOG_W("A length should be a positive value"); + } + return false; } - return val; -} -char **ini_get_list(const char *section, const char *key, const char *def) -{ - const char *value = get_value(section, key); - if (value) - return string_to_array(value); - else - return string_to_array(def); -} + char **s_arr = string_to_array(s_stripped, ","); + int len = string_array_length(s_arr); -int ini_get_int(const char *section, const char *key, int def) -{ - const char *value = get_value(section, key); - if (value) - return atoi(value); - else - return def; -} + if (len <= 1) { + LOG_W("Please specify a minimum and maximum value or a single value without brackets"); + g_strfreev(s_arr); + g_free(s_stripped); + return false; + } + if (len > 2) { + g_strfreev(s_arr); + g_free(s_stripped); + LOG_W("Too many values in array. A length should be only one or two values"); + return false; + } -double ini_get_double(const char *section, const char *key, double def) -{ - const char *value = get_value(section, key); - if (value) - return atof(value); - else - return def; -} + int *int_arr = NULL; + bool success = string_parse_int_list(s_arr, &int_arr, true); + if (!success) { + g_strfreev(s_arr); + g_free(s_stripped); + return false; + } -bool ini_is_set(const char *ini_section, const char *ini_key) -{ - return get_value(ini_section, ini_key) != NULL; -} + if (int_arr[0] == -1) + int_arr[0] = 0; -const char *next_section(const char *section) -{ - ASSERT_OR_RET(section_count > 0, NULL); - ASSERT_OR_RET(section, sections[0].name); + if (int_arr[1] == -1) + int_arr[1] = INT_MAX; - for (int i = 0; i < section_count; i++) { - if (STR_EQ(section, sections[i].name)) { - if (i + 1 >= section_count) - return NULL; - else - return sections[i + 1].name; - } + if (int_arr[0] < 0 || int_arr[1] < 0) { + LOG_W("A lengths should be positive"); + success = false; + } else if (int_arr[0] > int_arr[1]) { + LOG_W("The minimum value should be less than the maximum value. (%i > %i)", + int_arr[0], int_arr[1]); + success = false; + } else { + ret->min = int_arr[0]; + ret->max = int_arr[1]; } - return NULL; -} -int ini_get_bool(const char *section, const char *key, int def) -{ - const char *value = get_value(section, key); - if (value) { - switch (value[0]) { - case 'y': - case 'Y': - case 't': - case 'T': - case '1': + g_free(int_arr); + g_strfreev(s_arr); + g_free(s_stripped); + return success; +} + +bool set_from_string(void *target, struct setting setting, const char *value) { + GError *error = NULL; + + if (!strlen(value) && setting.type != TYPE_STRING) { + LOG_W("Cannot set empty value for setting %s", setting.name); + return false; + } + + bool success = false; + // Do not use setting.value, since we might want to set a rule. Use + // target instead + switch (setting.type) { + case TYPE_INT: + return safe_string_to_int(target, value); + case TYPE_DOUBLE: + return safe_string_to_double(target, value); + case TYPE_STRING: + g_free(*(char**) target); + *(char**) target = g_strdup(value); return true; - case 'n': - case 'N': - case 'f': - case 'F': - case '0': - return false; + case TYPE_CUSTOM: + if (setting.parser == NULL) { + LOG_W("Setting %s doesn't have parser", setting.name); + return false; + } + success = setting.parser(setting.parser_data, value, target); + + if (!success) LOG_W("Invalid %s value: '%s'", setting.name, value); + return success; + case TYPE_PATH: ; + g_free(*(char**) target); + *(char**) target = string_to_path(g_strdup(value)); + + // TODO make scripts take arguments in the config and + // deprecate the arguments that are now passed to the + // scripts + if (!setting.parser_data) + return true; + g_strfreev(*(char***)setting.parser_data); + if (!g_shell_parse_argv(*(char**) target, NULL, (char***)setting.parser_data, &error)) { + LOG_W("Unable to parse %s command: '%s'. " + "It's functionality will be disabled.", + setting.name, error->message); + g_error_free(error); + return false; + } + return true; + case TYPE_TIME: ; + gint64 tmp_time = string_to_time(value); + if (errno != 0) { + return false; + } + *(gint64*) target = tmp_time; + return true; + case TYPE_LIST: ; + LOG_D("list type %i", GPOINTER_TO_INT(setting.parser_data)); + return string_parse_list(setting.parser_data, value, target); + case TYPE_LENGTH: + return string_parse_length(target, value); default: - return def; - } - } else { - return def; + LOG_W("Setting type of '%s' is not known (type %i)", setting.name, setting.type); + return false; } } -int load_ini_file(FILE *fp) -{ - ASSERT_OR_RET(fp, 1); +bool set_setting(struct setting setting, char* value) { + LOG_D("[%s] Trying to set %s to %s", setting.section, setting.name, value); + if (setting.value == NULL) { + // setting.value is NULL, so it must be only a rule + return true; + } - char *line = NULL; - size_t line_len = 0; + return set_from_string(setting.value, setting, value); +} - int line_num = 0; - char *current_section = NULL; - while (getline(&line, &line_len, fp) != -1) { - line_num++; +int set_rule_value(struct rule* r, struct setting setting, char* value) { + // Apply rule member offset. Converting to char* because it's + // guaranteed to be 1 byte + void *target = (char*)r + setting.rule_offset; - char *start = g_strstrip(line); + return set_from_string(target, setting, value); +} - if (*start == ';' || *start == '#' || STR_EMPTY(start)) - continue; +bool set_rule(struct setting setting, char* value, char* section) { + struct rule *r = get_rule(section); + if (!r) { + r = rule_new(section); + LOG_D("Creating new rule '%s'", section); + } - if (*start == '[') { - char *end = strchr(start + 1, ']'); - if (!end) { - LOG_W("Invalid config file at line %d: Missing ']'.", line_num); - continue; - } + return set_rule_value(r, setting, value); +} - *end = '\0'; +void set_defaults() { + for (int i = 0; i < G_N_ELEMENTS(allowed_settings); i++) { + // FIXME Rule settings can only have a default if they have an + // working entry in the settings struct as well. Make an + // alternative way of setting defaults for rules. - g_free(current_section); - current_section = (g_strdup(start + 1)); - new_section(current_section); + if (!allowed_settings[i].value) // don't set default if it's only a rule continue; + + if(!set_setting(allowed_settings[i], allowed_settings[i].default_value)) { + LOG_E("Could not set default of setting %s", allowed_settings[i].name); } + } +} - char *equal = strchr(start + 1, '='); - if (!equal) { - LOG_W("Invalid config file at line %d: Missing '='.", line_num); +void save_settings(struct ini *ini) { + for (int i = 0; i < ini->section_count; i++) { + const struct section curr_section = ini->sections[i]; + + if (is_deprecated_section(curr_section.name)) { + LOG_W("Section %s is deprecated.\n%s\nIgnoring this section.", + curr_section.name, + get_section_deprecation_message(curr_section.name)); continue; } - *equal = '\0'; - char *key = g_strstrip(start); - char *value = g_strstrip(equal + 1); - - char *quote = strchr(value, '"'); - char *value_end = NULL; - if (quote) { - value_end = strchr(quote + 1, '"'); - if (!value_end) { - LOG_W("Invalid config file at line %d: Missing '\"'.", line_num); + LOG_D("Entering section [%s]", curr_section.name); + for (int j = 0; j < curr_section.entry_count; j++) { + const struct entry curr_entry = curr_section.entries[j]; + int setting_id = get_setting_id(curr_entry.key, curr_section.name); + struct setting curr_setting = allowed_settings[setting_id]; + if (setting_id < 0){ + if (setting_id == -1) { + LOG_W("Setting %s in section %s doesn't exist", curr_entry.key, curr_section.name); + } continue; } - } else { - value_end = value; - } - char *comment = strpbrk(value_end, "#;"); - if (comment) - *comment = '\0'; - - value = g_strstrip(value); - - if (!current_section) { - LOG_W("Invalid config file at line %d: Key value pair without a section.", line_num); - continue; + bool is_rule = curr_setting.rule_offset > 0; + if (is_special_section(curr_section.name)) { + if (is_rule) { + // set as a rule, but only if it's not a filter + if (rule_offset_is_modifying(curr_setting.rule_offset)) { + LOG_D("Adding rule '%s = %s' to special section %s", + curr_entry.key, + curr_entry.value, + curr_section.name); + set_rule(curr_setting, curr_entry.value, curr_section.name); + } else { + LOG_W("Cannot use filtering rules in special section. Ignoring %s in section %s.", + curr_entry.key, + curr_section.name); + } + } else { + // set as a regular setting + set_setting(curr_setting, curr_entry.value); + } + } else { + // interpret this section as a rule + LOG_D("Adding rule '%s = %s' to section %s", + curr_entry.key, + curr_entry.value, + curr_section.name); + set_rule(curr_setting, curr_entry.value, curr_section.name); + } } - - add_entry(current_section, key, value); } - free(line); - g_free(current_section); - return 0; } void cmdline_load(int argc, char *argv[]) @@ -511,9 +558,9 @@ const char *str = cmdline_get_value(key); if (str) - return string_to_array(str); + return string_to_array(str, ","); else - return string_to_array(def); + return string_to_array(def, ","); } gint64 cmdline_get_time(const char *key, gint64 def, const char *description) @@ -567,125 +614,6 @@ return cmdline_get_value(key) != NULL; } -char *option_get_path(const char *ini_section, - const char *ini_key, - const char *cmdline_key, - const char *def, - const char *description) -{ - char *val = NULL; - - if (cmdline_key) { - val = cmdline_get_path(cmdline_key, NULL, description); - } - - if (val) { - return val; - } else { - return ini_get_path(ini_section, ini_key, def); - } -} - -char *option_get_string(const char *ini_section, - const char *ini_key, - const char *cmdline_key, - const char *def, - const char *description) -{ - char *val = NULL; - - if (cmdline_key) { - val = cmdline_get_string(cmdline_key, NULL, description); - } - - if (val) { - return val; - } else { - return ini_get_string(ini_section, ini_key, def); - } -} - -gint64 option_get_time(const char *ini_section, - const char *ini_key, - const char *cmdline_key, - gint64 def, - const char *description) -{ - gint64 ini_val = ini_get_time(ini_section, ini_key, def); - return cmdline_get_time(cmdline_key, ini_val, description); -} - - -char **option_get_list(const char *ini_section, - const char *ini_key, - const char *cmdline_key, - const char *def, - const char *description) -{ - char **val = NULL; - if (cmdline_key) - val = cmdline_get_list(cmdline_key, NULL, description); - - if (val) - return val; - else - return ini_get_list(ini_section, ini_key, def); -} - -int option_get_int(const char *ini_section, - const char *ini_key, - const char *cmdline_key, - int def, - const char *description) -{ - /* *str is only used to check wether the cmdline option is actually set. */ - const char *str = cmdline_get_value(cmdline_key); - - /* we call cmdline_get_int even when the option isn't set in order to - * add the usage info */ - int val = cmdline_get_int(cmdline_key, def, description); - - if (!str) - return ini_get_int(ini_section, ini_key, def); - else - return val; -} - -double option_get_double(const char *ini_section, - const char *ini_key, - const char *cmdline_key, - double def, - const char *description) -{ - const char *str = cmdline_get_value(cmdline_key); - double val = cmdline_get_double(cmdline_key, def, description); - - if (!str) - return ini_get_double(ini_section, ini_key, def); - else - return val; -} - -int option_get_bool(const char *ini_section, - const char *ini_key, - const char *cmdline_key, - int def, - const char *description) -{ - int val = false; - - if (cmdline_key) - val = cmdline_get_bool(cmdline_key, false, description); - - if (cmdline_key && val) { - /* this can only be true if the value has been set, - * so we can return */ - return true; - } - - return ini_get_bool(ini_section, ini_key, def); -} - void cmdline_usage_append(const char *key, const char *type, const char *description) { char *key_type; @@ -716,4 +644,4 @@ return usage_str; } -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/option_parser.h dunst-1.8.1/src/option_parser.h --- dunst-1.5.0/src/option_parser.h 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/option_parser.h 2022-03-02 10:55:25.000000000 +0000 @@ -8,29 +8,14 @@ #include "dunst.h" #include "settings.h" +#include "ini.h" -bool string_parse_alignment(const char *s, enum alignment *ret); -bool string_parse_ellipsize(const char *s, enum ellipsize *ret); -bool string_parse_follow_mode(const char *s, enum follow_mode *ret); -bool string_parse_fullscreen(const char *s, enum behavior_fullscreen *ret); -bool string_parse_icon_position(const char *s, enum icon_position *ret); -bool string_parse_vertical_alignment(const char *s, enum vertical_alignment *ret); -bool string_parse_markup_mode(const char *s, enum markup_mode *ret); -bool string_parse_mouse_action(const char *s, enum mouse_action *ret); -bool string_parse_mouse_action_list(char **s, enum mouse_action **ret); -bool string_parse_sepcolor(const char *s, struct separator_color_data *ret); -bool string_parse_urgency(const char *s, enum urgency *ret); +int string_parse_enum(const void* data, const char *s, void * ret); +int string_parse_sepcolor(const void *data, const char *s, void *ret); +int string_parse_bool(const void *data, const char *s, void *ret); -int load_ini_file(FILE *); -char *ini_get_path(const char *section, const char *key, const char *def); -char *ini_get_string(const char *section, const char *key, const char *def); -gint64 ini_get_time(const char *section, const char *key, gint64 def); -char **ini_get_list(const char *section, const char *key, const char *def); -int ini_get_int(const char *section, const char *key, int def); -double ini_get_double(const char *section, const char *key, double def); -int ini_get_bool(const char *section, const char *key, int def); -bool ini_is_set(const char *ini_section, const char *ini_key); -void free_ini(void); +void set_defaults(); +void save_settings(struct ini *ini); void cmdline_load(int argc, char *argv[]); /* for all cmdline_get_* key can be either "-key" or "-key/-longkey" */ @@ -43,47 +28,5 @@ bool cmdline_is_set(const char *key); const char *cmdline_create_usage(void); -char *option_get_string(const char *ini_section, - const char *ini_key, - const char *cmdline_key, - const char *def, - const char *description); -char *option_get_path(const char *ini_section, - const char *ini_key, - const char *cmdline_key, - const char *def, - const char *description); -gint64 option_get_time(const char *ini_section, - const char *ini_key, - const char *cmdline_key, - gint64 def, - const char *description); -char **option_get_list(const char *ini_section, - const char *ini_key, - const char *cmdline_key, - const char *def, - const char *description); -int option_get_int(const char *ini_section, - const char *ini_key, - const char *cmdline_key, - int def, - const char *description); -double option_get_double(const char *ini_section, - const char *ini_key, - const char *cmdline_key, - double def, - const char *description); -int option_get_bool(const char *ini_section, - const char *ini_key, - const char *cmdline_key, - int def, - const char *description); - -/* returns the next known section. - * if section == NULL returns first section. - * returns NULL if no more sections are available - */ -const char *next_section(const char *section); - #endif -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/output.c dunst-1.8.1/src/output.c --- dunst-1.5.0/src/output.c 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/output.c 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,96 @@ +#include "output.h" + +#include "log.h" +#include "x11/x.h" +#include "x11/screen.h" + +#ifdef ENABLE_WAYLAND +#include "wayland/wl.h" +#endif + +bool is_running_wayland(void) { + char* wayland_display = getenv("WAYLAND_DISPLAY"); + return !(wayland_display == NULL); +} + +const struct output output_x11 = { + x_setup, + x_free, + + x_win_create, + x_win_destroy, + + x_win_show, + x_win_hide, + + x_display_surface, + x_win_get_context, + + get_active_screen, + + x_is_idle, + have_fullscreen_window, + + x_get_scale, +}; + +#ifdef ENABLE_WAYLAND +const struct output output_wl = { + wl_init, + wl_deinit, + + wl_win_create, + wl_win_destroy, + + wl_win_show, + wl_win_hide, + + wl_display_surface, + wl_win_get_context, + + wl_get_active_screen, + + wl_is_idle, + wl_have_fullscreen_window, + + wl_get_scale, +}; +#endif + +const struct output* get_x11_output() { + const struct output* output = &output_x11; + if (output->init()) { + return output; + } else { + LOG_E("Couldn't initialize X11 output. Aborting..."); + } +} + +#ifdef ENABLE_WAYLAND +const struct output* get_wl_output() { + const struct output* output = &output_wl; + if (output->init()) { + return output; + } else { + LOG_W("Couldn't initialize wayland output. Falling back to X11 output."); + output->deinit(); + return get_x11_output(); + } +} +#endif + +const struct output* output_create(bool force_xwayland) +{ +#ifdef ENABLE_WAYLAND + if (!force_xwayland && is_running_wayland()) { + LOG_I("Using Wayland output"); + return get_wl_output(); + } else { + LOG_I("Using X11 output"); + return get_x11_output(); + } +#else + return get_x11_output(); +#endif +} +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/output.h dunst-1.8.1/src/output.h --- dunst-1.5.0/src/output.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/output.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,63 @@ +#ifndef DUNST_OUTPUT_H +#define DUNST_OUTPUT_H + +#include <stdbool.h> +#include <glib.h> +#include <cairo.h> + +typedef gpointer window; + +struct dimensions { + int x; + int y; + int w; + int h; + int text_width; + int text_height; + + int corner_radius; +}; + +struct screen_info { + int id; + int x; + int y; + unsigned int h; + unsigned int mmh; + unsigned int w; + int dpi; +}; + +struct output { + bool (*init)(void); + void (*deinit)(void); + + window (*win_create)(void); + void (*win_destroy)(window); + + void (*win_show)(window); + void (*win_hide)(window); + + void (*display_surface)(cairo_surface_t *srf, window win, const struct dimensions*); + + cairo_t* (*win_get_context)(window); + + const struct screen_info* (*get_active_screen)(void); + + bool (*is_idle)(void); + bool (*have_fullscreen_window)(void); + + double (*get_scale)(void); +}; + +/** + * return an initialized output, selecting the correct output type from either + * wayland or X11 according to the settings and environment. + * When the wayland output fails to initilize, it falls back to X11 output. + */ +const struct output* output_create(bool force_xwayland); + +bool is_running_wayland(void); + +#endif +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/queues.c dunst-1.8.1/src/queues.c --- dunst-1.5.0/src/queues.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/queues.c 2022-03-02 10:55:25.000000000 +0000 @@ -25,6 +25,7 @@ #include "notification.h" #include "settings.h" #include "utils.h" +#include "output.h" // For checking if wayland is active. /* notification lists */ static GQueue *waiting = NULL; /**< all new notifications get into here */ @@ -51,7 +52,7 @@ } /* see queues.h */ -const struct notification *queues_get_head_waiting(void) +struct notification *queues_get_head_waiting(void) { if (waiting->length == 0) return NULL; @@ -76,6 +77,12 @@ return history->length; } +/* see queues.h */ +GList *queues_get_history(void) +{ + return g_queue_peek_head_link(history); +} + /** * Swap two given queue elements. The element's data has to be a notification. * @@ -167,19 +174,6 @@ LOG_M("Skipping notification: '%s' '%s'", n->body, n->summary); return 0; } - /* Do not insert the message if it's a command */ - if (STR_EQ("DUNST_COMMAND_PAUSE", n->summary)) { - dunst_status(S_RUNNING, false); - return 0; - } - if (STR_EQ("DUNST_COMMAND_RESUME", n->summary)) { - dunst_status(S_RUNNING, true); - return 0; - } - if (STR_EQ("DUNST_COMMAND_TOGGLE", n->summary)) { - dunst_status(S_RUNNING, !dunst_status_get().running); - return 0; - } bool inserted = false; if (n->id != 0) { @@ -201,6 +195,10 @@ if (!inserted) g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL); + if (!n->icon) { + notification_icon_replace_path(n, n->iconname); + } + if (settings.print_notifications) notification_print(n); @@ -213,31 +211,33 @@ * @retval true: notification got stacked * @retval false: notification did not get stacked */ -static bool queues_stack_duplicate(struct notification *n) +static bool queues_stack_duplicate(struct notification *new) { GQueue *allqueues[] = { displayed, waiting }; for (int i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) { for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter; iter = iter->next) { - struct notification *orig = iter->data; - if (notification_is_duplicate(orig, n)) { + struct notification *old = iter->data; + if (notification_is_duplicate(old, new)) { /* If the progress differs, probably notify-send was used to update the notification * So only count it as a duplicate, if the progress was not the same. * */ - if (orig->progress == n->progress) { - orig->dup_count++; + if (old->progress == new->progress) { + old->dup_count++; } else { - orig->progress = n->progress; + old->progress = new->progress; } - iter->data = n; + iter->data = new; - n->dup_count = orig->dup_count; - signal_notification_closed(orig, 1); + new->dup_count = old->dup_count; + signal_notification_closed(old, 1); if (allqueues[i] == displayed) - n->start = time_monotonic_now(); + new->start = time_monotonic_now(); + + notification_transfer_icon(old, new); - notification_unref(orig); + notification_unref(old); return true; } } @@ -259,7 +259,8 @@ for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter; iter = iter->next) { struct notification *old = iter->data; - if (STR_FULL(old->stack_tag) && STR_EQ(old->stack_tag, new->stack_tag)) { + if (STR_FULL(old->stack_tag) && STR_EQ(old->stack_tag, new->stack_tag) + && STR_EQ(old->appname, new->appname)) { iter->data = new; new->dup_count = old->dup_count; @@ -270,6 +271,8 @@ notification_run_script(new); } + notification_transfer_icon(old, new); + notification_unref(old); return true; } @@ -350,6 +353,34 @@ } /* see queues.h */ +void queues_history_pop_by_id(unsigned int id) +{ + struct notification *n = NULL; + + if (g_queue_is_empty(history)) + return; + + // search through the history buffer + for (GList *iter = g_queue_peek_head_link(history); iter; + iter = iter->next) { + struct notification *cur = iter->data; + if (cur->id == id) { + n = cur; + break; + } + } + + // must be a valid notification + if (n == NULL) + return; + + g_queue_remove(history, n); + n->redisplayed = true; + n->timeout = settings.sticky_history ? 0 : n->timeout; + g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL); +} + +/* see queues.h */ void queues_history_push(struct notification *n) { if (!n->history_ignore) { @@ -389,6 +420,19 @@ struct notification *n = iter->data; nextiter = iter->next; + if (notification_is_locked(n)) { + iter = nextiter; + continue; + } + + if (n->marked_for_closure) { + queues_notification_close(n, n->marked_for_closure); + n->marked_for_closure = 0; + iter = nextiter; + continue; + } + + if (queues_notification_is_finished(n, status)){ queues_notification_close(n, REASON_TIME); iter = nextiter; @@ -406,14 +450,14 @@ } int cur_displayed_limit; - if (settings.geometry.h == 0) + if (settings.notification_limit == 0) cur_displayed_limit = INT_MAX; else if ( settings.indicate_hidden - && settings.geometry.h > 1 - && displayed->length + waiting->length > settings.geometry.h) - cur_displayed_limit = settings.geometry.h-1; + && settings.notification_limit > 1 + && displayed->length + waiting->length > settings.notification_limit) + cur_displayed_limit = settings.notification_limit-1; else - cur_displayed_limit = settings.geometry.h; + cur_displayed_limit = settings.notification_limit; /* move notifications from queue to displayed */ iter = g_queue_peek_head_link(waiting); @@ -484,7 +528,7 @@ struct notification *n = iter->data; gint64 ttl = n->timeout - (time - n->start); - if (n->timeout > 0) { + if (n->timeout > 0 && n->locked == 0) { if (ttl > 0) sleep = MIN(sleep, ttl); else @@ -550,4 +594,4 @@ } -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/queues.h dunst-1.8.1/src/queues.h --- dunst-1.5.0/src/queues.h 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/queues.h 2022-03-02 10:55:25.000000000 +0000 @@ -27,12 +27,19 @@ GList *queues_get_displayed(void); /** + * Recieve the list of all notifications encountered + * + * @return read only list of notifications + */ +GList *queues_get_history(void); + +/** * Get the highest notification in line * * @returns the first notification in waiting * @retval NULL: waiting is empty */ -const struct notification *queues_get_head_waiting(void); +struct notification *queues_get_head_waiting(void); /** * Returns the current amount of notifications, @@ -107,6 +114,12 @@ void queues_history_pop(void); /** + * Pushes the latest notification found in the history buffer identified by + * it's assigned id + */ +void queues_history_pop_by_id(unsigned int id); + +/** * Push a single notification to history * The given notification has to be removed its queue * @@ -149,7 +162,7 @@ * Get the notification which has the given id in the displayed and waiting queue or * NULL if not found * - * @param the id searched for. + * @param id the id searched for. * * @return the `id` notification or NULL */ @@ -164,4 +177,4 @@ void queues_teardown(void); #endif -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/rules.c dunst-1.8.1/src/rules.c --- dunst-1.5.0/src/rules.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/rules.c 2022-03-02 10:55:25.000000000 +0000 @@ -4,8 +4,13 @@ #include <fnmatch.h> #include <glib.h> +#include <stddef.h> +#include <regex.h> #include "dunst.h" +#include "utils.h" +#include "settings_data.h" +#include "log.h" GSList *rules = NULL; @@ -26,10 +31,30 @@ n->transient = r->set_transient; if (r->skip_display != -1) n->skip_display = r->skip_display; + if (r->word_wrap != -1) + n->word_wrap = r->word_wrap; + if (r->ellipsize != -1) + n->ellipsize = r->ellipsize; + if (r->alignment != -1) + n->alignment = r->alignment; + if (r->hide_text != -1) + n->hide_text = r->hide_text; + if (r->progress_bar_alignment != -1) + n->progress_bar_alignment = r->progress_bar_alignment; + if (r->action_name) { + g_free(n->default_action_name); + n->default_action_name = g_strdup(r->action_name); + } + if (r->set_category) { + g_free(n->category); + n->category = g_strdup(r->set_category); + } if (r->markup != MARKUP_NULL) n->markup = r->markup; - if (r->new_icon) - notification_icon_replace_path(n, r->new_icon); + if (r->icon_position != -1) + n->icon_position = r->icon_position; + if (r->set_icon_size > 0) + n->icon_size = r->set_icon_size; if (r->fg) { g_free(n->colors.fg); n->colors.fg = g_strdup(r->fg); @@ -38,12 +63,27 @@ g_free(n->colors.bg); n->colors.bg = g_strdup(r->bg); } + if (r->highlight) { + g_free(n->colors.highlight); + n->colors.highlight = g_strdup(r->highlight); + } if (r->fc) { g_free(n->colors.frame); n->colors.frame = g_strdup(r->fc); } if (r->format) n->format = r->format; + if (r->default_icon) { + g_free(n->default_icon_name); + n->default_icon_name = g_strdup(r->default_icon); + } + if (r->new_icon) { + // FIXME This is not efficient when the icon is replaced + // multiple times for the same notification. To fix this, a + // separate variable is needed to track if the icon is + // replaced, like in 86cbc1d34bb0f551461dbd466cd9e4860ae01817. + notification_icon_replace_path(n, r->new_icon); + } if (r->script){ n->scripts = g_renew(const char*,n->scripts,n->script_count + 1); n->scripts[n->script_count] = r->script; @@ -69,26 +109,78 @@ } } -struct rule *rule_new(void) -{ - struct rule *r = g_malloc0(sizeof(struct rule)); +bool rule_apply_special_filters(struct rule *r, const char *name) { + if (is_deprecated_section(name)) // shouldn't happen, but just in case + return false; - r->msg_urgency = URG_NONE; - r->timeout = -1; - r->urgency = URG_NONE; - r->fullscreen = FS_NULL; - r->markup = MARKUP_NULL; - r->history_ignore = false; - r->match_transient = -1; - r->set_transient = -1; - r->skip_display = -1; + if (strcmp(name, "global") == 0) { + // no filters for global section + return true; + } + if (strcmp(name, "urgency_low") == 0) { + r->msg_urgency = URG_LOW; + return true; + } + if (strcmp(name, "urgency_normal") == 0) { + r->msg_urgency = URG_NORM; + return true; + } + if (strcmp(name, "urgency_critical") == 0) { + r->msg_urgency = URG_CRIT; + return true; + } + + return false; +} +struct rule *rule_new(const char *name) +{ + struct rule *r = g_malloc0(sizeof(struct rule)); + *r = empty_rule; + rules = g_slist_insert(rules, r, -1); + r->name = g_strdup(name); + if (is_special_section(name)) { + bool success = rule_apply_special_filters(r, name); + if (!success) { + LOG_M("Could not apply special filters for section %s", name); + } + } return r; } static inline bool rule_field_matches_string(const char *value, const char *pattern) { - return !pattern || (value && !fnmatch(pattern, value, 0)); + if (settings.enable_regex) { + if (!pattern) { + return true; + } + if (!value) { + return false; + } + regex_t regex; + + // TODO compile each regex only once + int err = regcomp(®ex, pattern, REG_NEWLINE | REG_EXTENDED | REG_NOSUB); + if (err) { + size_t err_size = regerror(err, ®ex, NULL, 0); + char *err_buf = malloc(err_size); + regerror(err, ®ex, err_buf, err_size); + LOG_W("%s: \"%s\"", err_buf, pattern); + free(err_buf); + return false; + } + + for (int i = 0; ; i++) { + if (regexec(®ex, value, 0, NULL, 0)) + break; + regfree(®ex); + return true; + } + regfree(®ex); + return false; + } else { + return !pattern || (value && !fnmatch(pattern, value, 0)); + } } /* @@ -96,7 +188,8 @@ */ bool rule_matches_notification(struct rule *r, struct notification *n) { - return (r->msg_urgency == URG_NONE || r->msg_urgency == n->urgency) + return r->enabled + && (r->msg_urgency == URG_NONE || r->msg_urgency == n->urgency) && (r->match_transient == -1 || (r->match_transient == n->transient)) && rule_field_matches_string(n->appname, r->appname) && rule_field_matches_string(n->desktop_entry, r->desktop_entry) @@ -106,4 +199,34 @@ && rule_field_matches_string(n->category, r->category) && rule_field_matches_string(n->stack_tag, r->stack_tag); } -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ + +/** + * Check if a rule exists with that name + */ +struct rule *get_rule(const char* name) { + for (GSList *iter = rules; iter; iter = iter->next) { + struct rule *r = iter->data; + if (r->name && STR_EQ(r->name, name)) + return r; + } + return NULL; +} + +/** + * see rules.h + */ +bool rule_offset_is_modifying(const size_t offset) { + const size_t first_action = offsetof(struct rule, timeout); + const size_t last_action = offsetof(struct rule, set_stack_tag); + return (offset >= first_action) && (offset <= last_action); +} + +/** + * see rules.h + */ +bool rule_offset_is_filter(const size_t offset) { + const size_t first_filter = offsetof(struct rule, appname); + return (offset >= first_filter) && !rule_offset_is_modifying(offset); +} + +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/rules.h dunst-1.8.1/src/rules.h --- dunst-1.5.0/src/rules.h 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/rules.h 2022-03-02 10:55:25.000000000 +0000 @@ -9,9 +9,16 @@ #include "settings.h" struct rule { + // Since there's heavy use of offsets from this class, both in rules.c + // and in settings_data.h the layout of the class should not be + // changed, unless it's well considered and tested. See the comments + // below for what should not be changed. + + // This has to be the first member, see struct setting.rule_offset. char *name; + /* filters */ - char *appname; + char *appname; // this has to be the first filter, see rules.c char *summary; char *body; char *icon; @@ -20,36 +27,74 @@ char *desktop_entry; int msg_urgency; - /* actions */ - gint64 timeout; + /* modifying */ + gint64 timeout; // this has to be the first modifying rule enum urgency urgency; + char *action_name; enum markup_mode markup; int history_ignore; int match_transient; int set_transient; int skip_display; + int word_wrap; + int ellipsize; + int alignment; + int hide_text; + int icon_position; + int set_icon_size; char *new_icon; char *fg; char *bg; + char *highlight; + char *default_icon; char *fc; + char *set_category; const char *format; const char *script; enum behavior_fullscreen fullscreen; - char *set_stack_tag; + bool enabled; + int progress_bar_alignment; + char *set_stack_tag; // this has to be the last modifying rule }; extern GSList *rules; /** - * Allocate a new rule. The rule is fully initialised. + * Allocate a new rule with given name. The rule is fully initialised. If the + * name is one of a special section (see settings_data.h), the rule is + * initialized with some filters, and you should not add any filters after + * that. + * + * @param name Name of the rule. * * @returns A new initialised rule. */ -struct rule *rule_new(void); +struct rule *rule_new(const char *name); void rule_apply(struct rule *r, struct notification *n); void rule_apply_all(struct notification *n); bool rule_matches_notification(struct rule *r, struct notification *n); +/** + * Get rule with this name from rules + * + * @returns the rule that matches. Null if no rule matches + */ +struct rule *get_rule(const char* name); + +/** + * Check if a rule is an action + * + * @returns a boolean if the rule is an action + */ +bool rule_offset_is_modifying(const size_t offset); + +/** + * Check if a rule is an filter + * + * @returns a boolean if the rule is an filter + */ +bool rule_offset_is_filter(const size_t offset); + #endif -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/settings.c dunst-1.8.1/src/settings.c --- dunst-1.5.0/src/settings.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/settings.c 2022-03-02 10:55:25.000000000 +0000 @@ -1,7 +1,14 @@ /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ +/** @file src/settings.c + * @brief Take care of the settings. + */ + #include "settings.h" +#include <dirent.h> +#include <errno.h> +#include <fnmatch.h> #include <glib.h> #include <stdio.h> #include <string.h> @@ -10,781 +17,260 @@ #include "log.h" #include "notification.h" #include "option_parser.h" +#include "ini.h" #include "rules.h" #include "utils.h" #include "x11/x.h" +#include "output.h" -#include "../config.h" +#ifndef SYSCONFDIR +/** @brief Fallback for doxygen, mostly. + * + * Since this gets defined by $DEFAULT_CPPFLAGS at compile time doxygen seems to + * miss the correct value. + */ +#define SYSCONFDIR "/usr/local/etc/xdg" +#endif struct settings settings; -static enum urgency ini_get_urgency(const char *section, const char *key, const enum urgency def) -{ - enum urgency ret; - char *c = ini_get_string(section, key, NULL); - - if (!string_parse_urgency(c, &ret)) { - if (c) - LOG_W("Unknown urgency: '%s'", c); - ret = def; - } - - g_free(c); - return ret; +/** @brief Filter for scandir(). + * + * @returns @brief An integer indicating success + * + * @retval @brief 1 if file name matches *.conf + * @retval @brief 0 otherwise + * + * @param dent [in] @brief directory entry + */ +static int is_drop_in(const struct dirent *dent) { + return 0 == fnmatch("*.conf", dent->d_name, FNM_PATHNAME | FNM_PERIOD) + ? 1 // success + : 0; } -static FILE *xdg_config(const char *filename) -{ - const gchar * const * systemdirs = g_get_system_config_dirs(); - const gchar * userdir = g_get_user_config_dir(); - - FILE *f; - char *path; +/** @brief Get all relevant config base directories + * + * Returns an array of all XDG config base directories, @e most @e important @e + * first. + * + * @returns A %NULL-terminated array of gchar* strings representing the paths + * of all XDG base directories in @e descending order of importance. + * + * The result @e must @e not be freed! The array is cached in a static variable, + * so it is OK to call this again instead of caching its return value. + */ +static GPtrArray *get_xdg_conf_basedirs() { + GPtrArray *arr = g_ptr_array_new_full(4, g_free); + g_ptr_array_add(arr, g_build_filename(g_get_user_config_dir(), "dunst", NULL)); + + /* + * A default of SYSCONFDIR is set to separate installs to a + * local PREFIX. With this default /usr/local/etc/xdg is set a + * system-wide config location and not /etc/xdg. Users/admins + * can override this by explicitly setting XDG_CONFIG_DIRS to + * their liking at runtime or by setting SYSCONFDIR=/etc/xdg at + * compile time. + */ + add_paths_from_env(arr, "XDG_CONFIG_DIRS", "dunst", SYSCONFDIR); + return arr; +} - path = g_strconcat(userdir, filename, NULL); - f = fopen(path, "r"); - g_free(path); - - for (const gchar * const *d = systemdirs; - !f && *d; - d++) { - path = g_strconcat(*d, filename, NULL); - f = fopen(path, "r"); - g_free(path); +static void config_files_add_drop_ins(GPtrArray *config_files, const char *path) { + int insert_index = config_files->len; + if (insert_index == 0) { + // there is no base config file + return; + } + char *drop_in_dir = g_strconcat(path, ".d", NULL); + struct dirent **drop_ins = NULL; + int n = scandir(drop_in_dir, &drop_ins, is_drop_in, alphasort); + + if (n == -1) { + // Scandir error. Most likely the directory doesn't exist. + return; + } + + while (n--) { + char *drop_in = g_strconcat(drop_in_dir, "/", + drop_ins[n]->d_name, NULL); + LOG_D("Found drop-in: %s\n", drop_in); + g_ptr_array_insert(config_files, insert_index, drop_in); + free(drop_ins[n]); } - - return f; + free(drop_ins); } -void load_settings(char *cmdline_config_path) -{ - -#ifndef STATIC_CONFIG - FILE *config_file = NULL; - - if (cmdline_config_path) { - if (STR_EQ(cmdline_config_path, "-")) { - config_file = stdin; - } else { - config_file = fopen(cmdline_config_path, "r"); - } - - if (!config_file) { - DIE("Cannot find config file: '%s'", cmdline_config_path); +/** @brief Find all config files. + * + * Searches the default config locations most important config file and it's + * drop-ins and puts their locations in a GPtrArray, @e most important last. + * + * The returned GPtrArray and it's elements are owned by the caller. + * + * @param path The config path that overrides the default config path. No + * drop-in files or other configs are searched. + */ +static GPtrArray* get_conf_files(const char *path) { + if (path) { + GPtrArray *result = g_ptr_array_new_full(1, g_free); + g_ptr_array_add(result, g_strdup(path)); + return result; + } + + GPtrArray *config_locations = get_xdg_conf_basedirs(); + GPtrArray *config_files = g_ptr_array_new_full(3, g_free); + char *dunstrc_location = NULL; + for (int i = 0; i < config_locations->len; i++) { + dunstrc_location = g_build_filename(config_locations->pdata[i], + "dunstrc", NULL); + LOG_D("Trying config location: %s", dunstrc_location); + if (is_readable_file(dunstrc_location)) { + g_ptr_array_add(config_files, dunstrc_location); + break; } } - if (!config_file) { - config_file = xdg_config("/dunst/dunstrc"); - } - - if (!config_file) { - /* Fall back to just "dunstrc", which was used before 2013-06-23 - * (before v0.2). */ - config_file = xdg_config("/dunstrc"); - } - - if (!config_file) { - LOG_W("No dunstrc found."); - } + config_files_add_drop_ins(config_files, dunstrc_location); - load_ini_file(config_file); -#else - LOG_M("dunstrc parsing disabled. " - "Using STATIC_CONFIG is deprecated behavior."); -#endif - - { - char *loglevel = option_get_string( - "global", - "verbosity", "-verbosity", NULL, - "The verbosity to log (one of 'crit', 'warn', 'mesg', 'info', 'debug')" - ); - - log_set_level_from_string(loglevel); - - g_free(loglevel); - } - - settings.per_monitor_dpi = option_get_bool( - "experimental", - "per_monitor_dpi", NULL, false, - "" - ); - - settings.force_xinerama = option_get_bool( - "global", - "force_xinerama", "-force_xinerama", false, - "Force the use of the Xinerama extension" - ); - - settings.font = option_get_string( - "global", - "font", "-font/-fn", defaults.font, - "The font dunst should use." - ); - - { - // Check if allow_markup set - if (ini_is_set("global", "allow_markup")) { - bool allow_markup = option_get_bool( - "global", - "allow_markup", NULL, false, - "Allow markup in notifications" - ); - - settings.markup = (allow_markup ? MARKUP_FULL : MARKUP_STRIP); - LOG_M("'allow_markup' is deprecated, please " - "use 'markup' instead."); - } + g_ptr_array_unref(config_locations); + return config_files; +} - char *c = option_get_string( - "global", - "markup", "-markup", NULL, - "Specify how markup should be handled" - ); - - if (!string_parse_markup_mode(c, &settings.markup)) { - if (c) - LOG_W("Cannot parse markup mode value: '%s'", c); - if (!settings.markup) - settings.markup = defaults.markup; - } - g_free(c); - } +FILE *fopen_conf(char * const path) +{ + FILE *f = NULL; + char *real_path = string_to_path(strdup(path)); - settings.format = option_get_string( - "global", - "format", "-format", defaults.format, - "The format template for the notifications" - ); - - settings.sort = option_get_bool( - "global", - "sort", "-sort", defaults.sort, - "Sort notifications by urgency and date?" - ); - - settings.indicate_hidden = option_get_bool( - "global", - "indicate_hidden", "-indicate_hidden", defaults.indicate_hidden, - "Show how many notifications are hidden" - ); - - settings.word_wrap = option_get_bool( - "global", - "word_wrap", "-word_wrap", defaults.word_wrap, - "Truncating long lines or do word wrap" - ); - settings.ignore_dbusclose = option_get_bool( - "global", - "ignore_dbusclose", "-ignore_dbusclose", defaults.ignore_dbusclose, - "Ignore dbus CloseNotification events" - ); + if (is_readable_file(real_path) && (f = fopen(real_path, "r"))) + LOG_I(MSG_FOPEN_SUCCESS(path, f)); + else + LOG_W(MSG_FOPEN_FAILURE(path)); - { - char *c = option_get_string( - "global", - "ellipsize", "-ellipsize", NULL, - "Ellipsize truncated lines on the start/middle/end" - ); - - if (!string_parse_ellipsize(c, &settings.ellipsize)) { - if (c) - LOG_W("Unknown ellipsize value: '%s'", c); - settings.ellipsize = defaults.ellipsize; - } - g_free(c); - } + free(real_path); + return f; +} - settings.ignore_newline = option_get_bool( - "global", - "ignore_newline", "-ignore_newline", defaults.ignore_newline, - "Ignore newline characters in notifications" - ); - - settings.idle_threshold = option_get_time( - "global", - "idle_threshold", "-idle_threshold", defaults.idle_threshold, - "Don't timeout notifications if user is longer idle than threshold" - ); - - settings.monitor = option_get_int( - "global", - "monitor", "-mon/-monitor", defaults.monitor, - "On which monitor should the notifications be displayed" - ); +void settings_init() { + static bool init_done = false; + if (!init_done) { + LOG_D("Initializing settings"); + settings = (struct settings) {0}; - { - char *c = option_get_string( - "global", - "follow", "-follow", NULL, - "Follow mouse, keyboard or none?" - ); - - if (!string_parse_follow_mode(c, &settings.f_mode)) { - if (c) - LOG_W("Cannot parse follow mode: %s", c); - settings.f_mode = defaults.f_mode; - } - g_free(c); + init_done = true; } +} - settings.title = option_get_string( - "global", - "title", "-t/-title", defaults.title, - "Define the title of windows spawned by dunst." - ); - - settings.class = option_get_string( - "global", - "class", "-c/-class", defaults.class, - "Define the class of windows spawned by dunst." - ); - - { +void print_rule(struct rule* r) { + LOG_D("Rule %s", r->name); + LOG_D("summary %s", r->summary); + LOG_D("appname %s", r->appname); + LOG_D("script %s", r->script); + LOG_D("frame %s", r->fc); +} - char *c = option_get_string( - "global", - "geometry", "-geom/-geometry", NULL, - "Geometry for the window" - ); - - if (c) { - // TODO: Implement own geometry parsing to get rid of - // the include dependency on X11 - settings.geometry = x_parse_geometry(c); - g_free(c); - } else { - settings.geometry = defaults.geometry; - } +void check_and_correct_settings(struct settings *s) { +#ifndef ENABLE_WAYLAND + if (is_running_wayland()){ + /* We are using xwayland now. Setting force_xwayland to make sure + * the idle workaround below is activated */ + settings.force_xwayland = true; } +#endif - settings.shrink = option_get_bool( - "global", - "shrink", "-shrink", defaults.shrink, - "Shrink window if it's smaller than the width" - ); - - settings.line_height = option_get_int( - "global", - "line_height", "-lh/-line_height", defaults.line_height, - "Add spacing between lines of text" - ); - - settings.notification_height = option_get_int( - "global", - "notification_height", "-nh/-notification_height", defaults.notification_height, - "Define height of the window" - ); - - { - char *c = option_get_string( - "global", - "alignment", "-align/-alignment", NULL, - "Text alignment left/center/right" - ); - - if (!string_parse_alignment(c, &settings.align)) { - if (c) - LOG_W("Unknown alignment value: '%s'", c); - settings.align = defaults.align; - } - - g_free(c); - } - - settings.show_age_threshold = option_get_time( - "global", - "show_age_threshold", "-show_age_threshold", defaults.show_age_threshold, - "When should the age of the notification be displayed?" - ); - - settings.hide_duplicate_count = option_get_bool( - "global", - "hide_duplicate_count", "-hide_duplicate_count", false, - "Hide the count of stacked notifications with the same content" - ); - - settings.sticky_history = option_get_bool( - "global", - "sticky_history", "-sticky_history", defaults.sticky_history, - "Don't timeout notifications popped up from history" - ); - - settings.history_length = option_get_int( - "global", - "history_length", "-history_length", defaults.history_length, - "Max amount of notifications kept in history" - ); - - settings.show_indicators = option_get_bool( - "global", - "show_indicators", "-show_indicators", defaults.show_indicators, - "Show indicators for actions \"(A)\" and URLs \"(U)\"" - ); - - settings.separator_height = option_get_int( - "global", - "separator_height", "-sep_height/-separator_height", defaults.separator_height, - "height of the separator line" - ); - - settings.padding = option_get_int( - "global", - "padding", "-padding", defaults.padding, - "Padding between text and separator" - ); - - settings.h_padding = option_get_int( - "global", - "horizontal_padding", "-horizontal_padding", defaults.h_padding, - "horizontal padding" - ); - - settings.transparency = option_get_int( - "global", - "transparency", "-transparency", defaults.transparency, - "Transparency. Range 0-100" - ); - - settings.corner_radius = option_get_int( - "global", - "corner_radius", "-corner_radius", defaults.corner_radius, - "Window corner radius" - ); - - { - char *c = option_get_string( - "global", - "separator_color", "-sep_color/-separator_color", "", - "Color of the separator line (or 'auto')" - ); - - if (!string_parse_sepcolor(c, &settings.sep_color)) { - settings.sep_color = defaults.sep_color; - } - g_free(c); + if (settings.force_xwayland && is_running_wayland()) { + if (settings.idle_threshold > 0) + LOG_W("Using xwayland. Disabling idle."); + /* There is no way to detect if the user is idle + * on xwayland, so turn this feature off */ + settings.idle_threshold = 0; } - settings.stack_duplicates = option_get_bool( - "global", - "stack_duplicates", "-stack_duplicates", true, - "Stack together notifications with the same content" - ); - - settings.startup_notification = option_get_bool( - "global", - "startup_notification", "-startup_notification", false, - "print notification on startup" - ); - - settings.dmenu = option_get_path( - "global", - "dmenu", "-dmenu", defaults.dmenu, - "path to dmenu" - ); - + // check sanity of the progress bar options { - GError *error = NULL; - if (!g_shell_parse_argv(settings.dmenu, NULL, &settings.dmenu_cmd, &error)) { - LOG_W("Unable to parse dmenu command: '%s'." - "dmenu functionality will be disabled.", error->message); - g_error_free(error); - settings.dmenu_cmd = NULL; + if (s->progress_bar_height < (2 * s->progress_bar_frame_width)){ + DIE("setting progress_bar_frame_width is bigger than half of progress_bar_height"); } - } - - - settings.browser = option_get_path( - "global", - "browser", "-browser", defaults.browser, - "path to browser" - ); - - { - GError *error = NULL; - if (!g_shell_parse_argv(settings.browser, NULL, &settings.browser_cmd, &error)) { - LOG_W("Unable to parse browser command: '%s'." - " URL functionality will be disabled.", error->message); - g_error_free(error); - settings.browser_cmd = NULL; + if (s->progress_bar_max_width < (2 * s->progress_bar_frame_width)){ + DIE("setting progress_bar_frame_width is bigger than half of progress_bar_max_width"); } - } - - { - char *c = option_get_string( - "global", - "icon_position", "-icon_position", "off", - "Align icons left/right/off" - ); - - if (!string_parse_icon_position(c, &settings.icon_position)) { - if (c) - LOG_W("Unknown icon position: '%s'", c); - settings.icon_position = defaults.icon_position; + if (s->progress_bar_max_width < s->progress_bar_min_width){ + DIE("setting progress_bar_max_width is smaller than progress_bar_min_width"); } - g_free(c); - } - - { - char *c = option_get_string( - "global", - "vertical_alignment", "-vertical_alignment", "center", - "Align icon and text top/center/bottom" - ); - if (!string_parse_vertical_alignment(c, &settings.vertical_alignment)) { - if (c) - LOG_W("Unknown vertical alignment: '%s'", c); - settings.vertical_alignment = defaults.vertical_alignment; + if (s->progress_bar_min_width > s->width.max) { + LOG_W("Progress bar min width is greater than the max width of the notification."); } - g_free(c); - } - settings.min_icon_size = option_get_int( - "global", - "min_icon_size", "-min_icon_size", defaults.min_icon_size, - "Scale smaller icons up to this size, set to 0 to disable. If max_icon_size also specified, that has the final say." - ); - - settings.max_icon_size = option_get_int( - "global", - "max_icon_size", "-max_icon_size", defaults.max_icon_size, - "Scale larger icons down to this size, set to 0 to disable" - ); - // restrict the icon size to a reasonable limit if we have a fixed width. // Otherwise the layout will be broken by too large icons. // See https://github.com/dunst-project/dunst/issues/540 - if (settings.geometry.width_set && settings.geometry.w != 0) { - const int icon_size_limit = settings.geometry.w / 2; - if ( settings.max_icon_size == 0 - || settings.max_icon_size > icon_size_limit) { - if (settings.max_icon_size != 0) { + if (s->width.max > 0) { + const int icon_size_limit = s->width.max / 2; + if ( s->max_icon_size == 0 + || s->max_icon_size > icon_size_limit) { + if (s->max_icon_size != 0) { LOG_W("Max width was set to %d but got a max_icon_size of %d, too large to use. Setting max_icon_size=%d", - settings.geometry.w, settings.max_icon_size, icon_size_limit); + s->width.max, s->max_icon_size, icon_size_limit); } else { LOG_I("Max width was set but max_icon_size is unlimited. Limiting icons to %d pixels", icon_size_limit); } - settings.max_icon_size = icon_size_limit; + s->max_icon_size = icon_size_limit; } } - // If the deprecated icon_folders option is used, - // read it and generate its usage string. - if (ini_is_set("global", "icon_folders") || cmdline_is_set("-icon_folders")) { - settings.icon_path = option_get_string( - "global", - "icon_folders", "-icon_folders", defaults.icon_path, - "folders to default icons (deprecated, please use 'icon_path' instead)" - ); - LOG_M("The option 'icon_folders' is deprecated, please use 'icon_path' instead."); - } - // Read value and generate usage string for icon_path. - // If icon_path is set, override icon_folder. - // if not, but icon_folder is set, use that instead of the compile time default. - settings.icon_path = option_get_string( - "global", - "icon_path", "-icon_path", - settings.icon_path ? settings.icon_path : defaults.icon_path, - "paths to default icons" - ); - - { - // Backwards compatibility with the legacy 'frame' section. - if (ini_is_set("frame", "width")) { - settings.frame_width = option_get_int( - "frame", - "width", NULL, defaults.frame_width, - "Width of frame around the window" - ); - LOG_M("The frame section is deprecated, width has " - "been renamed to frame_width and moved to " - "the global section."); - } - - settings.frame_width = option_get_int( - "global", - "frame_width", "-frame_width", - settings.frame_width ? settings.frame_width : defaults.frame_width, - "Width of frame around the window" - ); - - if (ini_is_set("frame", "color")) { - settings.frame_color = option_get_string( - "frame", - "color", NULL, defaults.frame_color, - "Color of the frame around the window" - ); - LOG_M("The frame section is deprecated, color " - "has been renamed to frame_color and moved " - "to the global section."); - } + int text_icon_padding = settings.text_icon_padding != 0 ? settings.text_icon_padding : settings.h_padding; + int max_text_width = settings.width.max - settings.max_icon_size - text_icon_padding - 2 * settings.h_padding; + if (max_text_width < 10) { + DIE("max_icon_size and horizontal padding are too large for the given width"); + } - settings.frame_color = option_get_string( - "global", - "frame_color", "-frame_color", - settings.frame_color ? settings.frame_color : defaults.frame_color, - "Color of the frame around the window" - ); +} - } +static void process_conf_file(const gpointer conf_fname, gpointer n_success) { + const gchar * const p = conf_fname; - { - char **c = option_get_list( - "global", - "mouse_left_click", "-mouse_left_click", NULL, - "Action of Left click event" - ); + LOG_D("Reading config file '%s'", p); + /* Check for "-" here, so the file handling stays in one place */ + FILE *f = STR_EQ(p, "-") ? stdin : fopen_verbose(p); + if (!f) + return; - if (!string_parse_mouse_action_list(c, &settings.mouse_left_click)) { - settings.mouse_left_click = defaults.mouse_left_click; - } - free_string_array(c); - } + struct ini *ini = load_ini_file(f); + fclose(f); - { - char **c = option_get_list( - "global", - "mouse_middle_click", "-mouse_middle_click", NULL, - "Action of middle click event" - ); + LOG_D("Loading settings"); + save_settings(ini); - if (!string_parse_mouse_action_list(c, &settings.mouse_middle_click)) { - settings.mouse_middle_click = defaults.mouse_middle_click; - } - free_string_array(c); - } + LOG_D("Checking/correcting settings"); + check_and_correct_settings(&settings); - { - char **c = option_get_list( - "global", - "mouse_right_click", "-mouse_right_click", NULL, - "Action of right click event" - ); + finish_ini(ini); + free(ini); - if (!string_parse_mouse_action_list(c, &settings.mouse_right_click)) { - settings.mouse_right_click = defaults.mouse_right_click; - } - free_string_array(c); - } + ++(*(int *) n_success); +} - settings.colors_low.bg = option_get_string( - "urgency_low", - "background", "-lb", defaults.colors_low.bg, - "Background color for notifications with low urgency" - ); - - settings.colors_low.fg = option_get_string( - "urgency_low", - "foreground", "-lf", defaults.colors_low.fg, - "Foreground color for notifications with low urgency" - ); - - settings.colors_low.frame = option_get_string( - "urgency_low", - "frame_color", "-lfr", settings.frame_color ? settings.frame_color : defaults.colors_low.frame, - "Frame color for notifications with low urgency" - ); - - settings.timeouts[URG_LOW] = option_get_time( - "urgency_low", - "timeout", "-lto", defaults.timeouts[URG_LOW], - "Timeout for notifications with low urgency" - ); - - settings.icons[URG_LOW] = option_get_string( - "urgency_low", - "icon", "-li", defaults.icons[URG_LOW], - "Icon for notifications with low urgency" - ); - - settings.colors_norm.bg = option_get_string( - "urgency_normal", - "background", "-nb", defaults.colors_norm.bg, - "Background color for notifications with normal urgency" - ); - - settings.colors_norm.fg = option_get_string( - "urgency_normal", - "foreground", "-nf", defaults.colors_norm.fg, - "Foreground color for notifications with normal urgency" - ); - - settings.colors_norm.frame = option_get_string( - "urgency_normal", - "frame_color", "-nfr", settings.frame_color ? settings.frame_color : defaults.colors_norm.frame, - "Frame color for notifications with normal urgency" - ); - - settings.timeouts[URG_NORM] = option_get_time( - "urgency_normal", - "timeout", "-nto", defaults.timeouts[URG_NORM], - "Timeout for notifications with normal urgency" - ); - - settings.icons[URG_NORM] = option_get_string( - "urgency_normal", - "icon", "-ni", defaults.icons[URG_NORM], - "Icon for notifications with normal urgency" - ); - - settings.colors_crit.bg = option_get_string( - "urgency_critical", - "background", "-cb", defaults.colors_crit.bg, - "Background color for notifications with critical urgency" - ); - - settings.colors_crit.fg = option_get_string( - "urgency_critical", - "foreground", "-cf", defaults.colors_crit.fg, - "Foreground color for notifications with ciritical urgency" - ); - - settings.colors_crit.frame = option_get_string( - "urgency_critical", - "frame_color", "-cfr", settings.frame_color ? settings.frame_color : defaults.colors_crit.frame, - "Frame color for notifications with critical urgency" - ); - - settings.timeouts[URG_CRIT] = option_get_time( - "urgency_critical", - "timeout", "-cto", defaults.timeouts[URG_CRIT], - "Timeout for notifications with critical urgency" - ); - - settings.icons[URG_CRIT] = option_get_string( - "urgency_critical", - "icon", "-ci", defaults.icons[URG_CRIT], - "Icon for notifications with critical urgency" - ); - - settings.close_ks.str = option_get_string( - "shortcuts", - "close", "-key", defaults.close_ks.str, - "Shortcut for closing one notification" - ); - - settings.close_all_ks.str = option_get_string( - "shortcuts", - "close_all", "-all_key", defaults.close_all_ks.str, - "Shortcut for closing all notifications" - ); - - settings.history_ks.str = option_get_string( - "shortcuts", - "history", "-history_key", defaults.history_ks.str, - "Shortcut to pop the last notification from history" - ); - - settings.context_ks.str = option_get_string( - "shortcuts", - "context", "-context_key", defaults.context_ks.str, - "Shortcut for context menu" - ); - - settings.print_notifications = cmdline_get_bool( - "-print", false, - "Print notifications to cmdline (DEBUG)" - ); - - settings.always_run_script = option_get_bool( - "global", - "always_run_script", "-always_run_script", true, - "Always run rule-defined scripts, even if the notification is suppressed with format = \"\"." - ); - - /* push hardcoded default rules into rules list */ - for (int i = 0; i < G_N_ELEMENTS(default_rules); i++) { - rules = g_slist_insert(rules, &(default_rules[i]), -1); - } - - const char *cur_section = NULL; - for (;;) { - cur_section = next_section(cur_section); - if (!cur_section) - break; - if (STR_EQ(cur_section, "global") - || STR_EQ(cur_section, "frame") - || STR_EQ(cur_section, "experimental") - || STR_EQ(cur_section, "shortcuts") - || STR_EQ(cur_section, "urgency_low") - || STR_EQ(cur_section, "urgency_normal") - || STR_EQ(cur_section, "urgency_critical")) - continue; - - /* check for existing rule with same name */ - struct rule *r = NULL; - for (GSList *iter = rules; iter; iter = iter->next) { - struct rule *match = iter->data; - if (match->name && - STR_EQ(match->name, cur_section)) - r = match; - } +void load_settings(const char * const path) { + settings_init(); + LOG_D("Setting defaults"); + set_defaults(); - if (!r) { - r = rule_new(); - rules = g_slist_insert(rules, r, -1); - } + GPtrArray *conf_files = get_conf_files(path); - r->name = g_strdup(cur_section); - r->appname = ini_get_string(cur_section, "appname", r->appname); - r->summary = ini_get_string(cur_section, "summary", r->summary); - r->body = ini_get_string(cur_section, "body", r->body); - r->icon = ini_get_string(cur_section, "icon", r->icon); - r->category = ini_get_string(cur_section, "category", r->category); - r->stack_tag = ini_get_string(cur_section, "stack_tag", r->stack_tag); - r->timeout = ini_get_time(cur_section, "timeout", r->timeout); - - { - char *c = ini_get_string( - cur_section, - "markup", NULL - ); - - if (!string_parse_markup_mode(c, &r->markup)) { - if (c) - LOG_W("Invalid markup mode value: %s", c); - } - g_free(c); - } + /* Load all conf files and drop-ins, least important first. */ + int n_loaded_confs = 0; + g_ptr_array_foreach(conf_files, process_conf_file, &n_loaded_confs); - r->urgency = ini_get_urgency(cur_section, "urgency", r->urgency); - r->msg_urgency = ini_get_urgency(cur_section, "msg_urgency", r->msg_urgency); - r->fg = ini_get_string(cur_section, "foreground", r->fg); - r->bg = ini_get_string(cur_section, "background", r->bg); - r->fc = ini_get_string(cur_section, "frame_color", r->fc); - r->format = ini_get_string(cur_section, "format", r->format); - r->new_icon = ini_get_string(cur_section, "new_icon", r->new_icon); - r->history_ignore = ini_get_bool(cur_section, "history_ignore", r->history_ignore); - r->match_transient = ini_get_bool(cur_section, "match_transient", r->match_transient); - r->set_transient = ini_get_bool(cur_section, "set_transient", r->set_transient); - r->desktop_entry = ini_get_string(cur_section, "desktop_entry", r->desktop_entry); - r->skip_display = ini_get_bool(cur_section, "skip_display", r->skip_display); - { - char *c = ini_get_string( - cur_section, - "fullscreen", NULL - ); - - if (!string_parse_fullscreen(c, &r->fullscreen)) { - if (c) - LOG_W("Invalid fullscreen value: %s", c); - } - g_free(c); - } - r->script = ini_get_path(cur_section, "script", NULL); - r->set_stack_tag = ini_get_string(cur_section, "set_stack_tag", r->set_stack_tag); - } + if (0 == n_loaded_confs) + LOG_I("No configuration file found, using defaults"); -#ifndef STATIC_CONFIG - if (config_file) { - fclose(config_file); - free_ini(); + for (GSList *iter = rules; iter; iter = iter->next) { + struct rule *r = iter->data; + print_rule(r); } -#endif + g_ptr_array_unref(conf_files); } -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/settings_data.h dunst-1.8.1/src/settings_data.h --- dunst-1.5.0/src/settings_data.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/settings_data.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,1458 @@ +/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ +#ifndef DUNST_SETTING_DATA_H +#define DUNST_SETTING_DATA_H +#include <stddef.h> +#include <pango/pango-layout.h> + +#include "option_parser.h" +#include "settings.h" +#include "rules.h" + +struct string_to_enum_def { + const char* string; + const int enum_value; +}; + +struct setting { + /** + * A string with the setting key as found in the config file. + */ + char *name; + + /** + * A string with the ini section where the variable is allowed. This + * section should be part of the special_sections array. + * + * Example: + * .section = "global", + */ + char *section; + + /** + * A string with a short description of the config variable. This is + * currently not used, but it may be used to generate help messages. + */ + char *description; + + // IDEA: Add long description to generate man page from this. This could + // also be useful for an extended help text. + + /** + * Enum of the setting type. Every setting type is parsed differently in + * option_parser.c. + */ + enum setting_type type; + + /** + * A string with the default value of the setting. This should be the + * same as what it would be in the config file, as this is parsed by the + * same parser. + * default_value is unused when the setting is only a rule (value == NULL). + * + * Example: + * .default_value = "10s", // 10 seconds of time + */ + char *default_value; + + /** + * (nullable) + * A pointer to the corresponding setting in the setting struct. Make + * sure to always take the address, even if it's already a pointer in the + * settings struct. + * If value is NULL, the setting is interpreted as a rule. + * + * Example: + * .value = &settings.font, + */ + void *value; + + + /** + * (nullable) + * Function pointer for the parser - to be used in case of enums or other + * special settings. If the parse requires extra data, it should be given + * with parser_data. This allows for one generic parser for, for example, + * enums, instead of a parser for every enum. + * + * @param data The required data for parsing the value. See parser_data. + * @param cfg_value The string representing the value of the config + * variable + * @param ret A pointer to the return value. This casted by the parser to + * the right type. + */ + int (*parser)(const void* data, const char *cfg_value, void* ret); + + /** + * (nullable) + * A pointer to the data required for the parser to parse this setting. + */ + const void* parser_data; // This is passed to the parser function + + /** + * The offset of this setting in the rule struct, if it exists. Zero is + * being interpreted as if no rule exists for this setting. + * + * Example: + * .rule_offset = offsetof(struct rule, *member*); + */ + size_t rule_offset; +}; + + +/* + * How to add/change a rule + * ------------------------ + * + * - Add variable to `struct rules` in `rules.h` (make sure to read the comment + * at the top of the struct) + * - Add variable to to `struct notification` in `notification.h` + * - Apply the rule in `rule_apply` in `rules.c` + * - Change the listing in `settings_data.h` (make sure to move it to the other + * rule listings for clarity) + * - Add the default rule value in `settings_data.h` (usually -1 or NULL) + * - Set default value in notification.c (`notification_create`). This is where + * the real default is set. + * - Free the variable in `notification.c` if dynamically allocated. + * - Free the variable in `rules.c` if dynamically allocated. + * - Remove the setting from the global settings struct in `settings.h`. + * - Actually use the new setting. + * - Update the documentation + * - Test that it works + * + * An example of making a setting a rule can be found in commit edc6f5a8c7a51a56b591cfa72618a43adc7b8d11 + */ + +static const struct rule empty_rule = { + .name = "empty", + .appname = NULL, + .summary = NULL, + .body = NULL, + .icon = NULL, + .category = NULL, + .msg_urgency = URG_NONE, + .timeout = -1, + .urgency = URG_NONE, + .markup = MARKUP_NULL, + .history_ignore = -1, + .match_transient = -1, + .set_transient = -1, + .icon_position = -1, + .set_icon_size = -1, + .skip_display = -1, + .word_wrap = -1, + .ellipsize = -1, + .alignment = -1, + .hide_text = -1, + .new_icon = NULL, + .fg = NULL, + .bg = NULL, + .format = NULL, + .default_icon = NULL, + .script = NULL, + .enabled = true, + .progress_bar_alignment = -1, +}; + + +#ifndef ZWLR_LAYER_SHELL_V1_LAYER_ENUM +#define ZWLR_LAYER_SHELL_V1_LAYER_ENUM +// Needed for compiling without wayland dependency +const enum zwlr_layer_shell_v1_layer { + ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND = 0, + ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM = 1, + ZWLR_LAYER_SHELL_V1_LAYER_TOP = 2, + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY = 3, +}; +#endif /* ZWLR_LAYER_SHELL_V1_LAYER_ENUM */ + +enum list_type { + INVALID_LIST = 0, + MOUSE_LIST = 1, + OFFSET_LIST = 2, + STRING_LIST = 3, +}; + +#define ENUM_END {NULL, 0} +static const struct string_to_enum_def verbosity_enum_data[] = { + {"critical", G_LOG_LEVEL_CRITICAL }, + {"crit", G_LOG_LEVEL_CRITICAL }, + {"warning", G_LOG_LEVEL_WARNING }, + {"warn", G_LOG_LEVEL_WARNING }, + {"message", G_LOG_LEVEL_MESSAGE }, + {"mesg", G_LOG_LEVEL_MESSAGE }, + {"info", G_LOG_LEVEL_INFO }, + {"debug", G_LOG_LEVEL_DEBUG }, + {"deb", G_LOG_LEVEL_DEBUG }, + ENUM_END, +}; + +static const struct string_to_enum_def boolean_enum_data[] = { + {"True", true }, + {"true", true }, + {"On", true }, + {"on", true }, + {"Yes", true }, + {"yes", true }, + {"1", true }, + {"False", false }, + {"false", false }, + {"Off", false }, + {"off", false }, + {"No", false }, + {"no", false }, + {"0", false }, + {"n", false }, + {"y", false }, + {"N", false }, + {"Y", true }, + ENUM_END, +}; + +static const struct string_to_enum_def horizontal_alignment_enum_data[] = { + {"left", PANGO_ALIGN_LEFT }, + {"center", PANGO_ALIGN_CENTER }, + {"right", PANGO_ALIGN_RIGHT }, + ENUM_END, +}; + +static const struct string_to_enum_def ellipsize_enum_data[] = { + {"start", PANGO_ELLIPSIZE_START }, + {"middle", PANGO_ELLIPSIZE_MIDDLE }, + {"end", PANGO_ELLIPSIZE_END }, + ENUM_END, +}; + +static struct string_to_enum_def follow_mode_enum_data[] = { + {"mouse", FOLLOW_MOUSE }, + {"keyboard", FOLLOW_KEYBOARD }, + {"none", FOLLOW_NONE }, + ENUM_END, +}; + +static const struct string_to_enum_def fullscreen_enum_data[] = { + {"show", FS_SHOW }, + {"delay", FS_DELAY }, + {"pushback", FS_PUSHBACK }, + ENUM_END, +}; + +static const struct string_to_enum_def icon_position_enum_data[] = { + {"left", ICON_LEFT }, + {"right", ICON_RIGHT }, + {"top", ICON_TOP }, + {"off", ICON_OFF }, + ENUM_END, +}; + +static const struct string_to_enum_def vertical_alignment_enum_data[] = { + {"top", VERTICAL_TOP }, + {"center", VERTICAL_CENTER }, + {"bottom", VERTICAL_BOTTOM }, + ENUM_END, +}; + +static const struct string_to_enum_def markup_mode_enum_data[] = { + {"strip", MARKUP_STRIP }, + {"no", MARKUP_NO }, + {"full", MARKUP_FULL }, + {"yes", MARKUP_FULL }, + ENUM_END, +}; + +static const struct string_to_enum_def mouse_action_enum_data[] = { + {"none", MOUSE_NONE }, + {"do_action", MOUSE_DO_ACTION }, + {"close_current", MOUSE_CLOSE_CURRENT }, + {"close_all", MOUSE_CLOSE_ALL }, + {"context", MOUSE_CONTEXT }, + {"context_all", MOUSE_CONTEXT_ALL }, + {"open_url", MOUSE_OPEN_URL }, + ENUM_END, +}; + +static const struct string_to_enum_def sep_color_enum_data[] = { + {"auto", SEP_AUTO }, + {"foreground", SEP_FOREGROUND }, + {"frame", SEP_FRAME }, + ENUM_END, +}; + +static const struct string_to_enum_def urgency_enum_data[] = { + {"low", URG_LOW }, + {"normal", URG_NORM }, + {"critical", URG_CRIT }, + ENUM_END, +}; + +static const struct string_to_enum_def layer_enum_data[] = { + {"bottom", ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM }, + {"top", ZWLR_LAYER_SHELL_V1_LAYER_TOP }, + {"overlay", ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY }, + ENUM_END, +}; + +static const struct string_to_enum_def origin_enum_data[] = { + { "top-left", ORIGIN_TOP_LEFT }, + { "top-center", ORIGIN_TOP_CENTER }, + { "top-right", ORIGIN_TOP_RIGHT }, + { "bottom-left", ORIGIN_BOTTOM_LEFT }, + { "bottom-center", ORIGIN_BOTTOM_CENTER }, + { "bottom-right", ORIGIN_BOTTOM_RIGHT }, + { "left-center", ORIGIN_LEFT_CENTER }, + { "right-center", ORIGIN_RIGHT_CENTER }, + { "center", ORIGIN_CENTER }, + ENUM_END, +}; + +static const struct setting allowed_settings[] = { + // These icon settings have to be above the icon rule + { + .name = "icon", + .section = "urgency_low", + .description = "Icon for notifications with low urgency", + .type = TYPE_STRING, + .default_value = "dialog-information", + .value = &settings.icons[URG_LOW], + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "icon", + .section = "urgency_normal", + .description = "Icon for notifications with normal urgency", + .type = TYPE_STRING, + .default_value = "dialog-information", + .value = &settings.icons[URG_NORM], + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "icon", + .section = "urgency_critical", + .description = "Icon for notifications with critical urgency", + .type = TYPE_STRING, + .default_value = "dialog-warning", + .value = &settings.icons[URG_CRIT], + .parser = NULL, + .parser_data = NULL, + }, + // filtering rules below + { + .name = "appname", + .section = "*", + .description = "The name of the application as reported by the client. Be aware that the name can often differ depending on the locale used.", + .type = TYPE_STRING, + .default_value = "*", // default_value is not used for rules + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, appname), + }, + { + .name = "body", + .section = "*", + .description = "The body of the notification", + .type = TYPE_STRING, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, body), + }, + { + .name = "category", + .section = "*", + .description = "The category of the notification as defined by the notification spec. See https://specifications.freedesktop.org/notification-spec/latest/ar01s06.html", + .type = TYPE_STRING, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, category), + }, + { + .name = "desktop_entry", + .section = "*", + .description = "GLib based applications export their desktop-entry name. In comparison to the appname, the desktop-entry won't get localized.", + .type = TYPE_STRING, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, desktop_entry), + }, + { + .name = "icon", + .section = "*", + .description = "The icon of the notification in the form of a file path. Can be empty if no icon is available or a raw icon is used instead.", + .type = TYPE_STRING, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, icon), + }, + { + .name = "match_transient", + .section = "*", + .description = "Match if the notification has been declared as transient by the client or by some other rule.", + .type = TYPE_CUSTOM, + .default_value = "*", + .value = NULL, + .parser = string_parse_enum, + .parser_data = boolean_enum_data, + .rule_offset = offsetof(struct rule, match_transient), + }, + { + .name = "msg_urgency", + .section = "*", + .description = "Matches the urgency of the notification as set by the client or by some other rule.", + .type = TYPE_CUSTOM, + .default_value = "*", + .value = NULL, + .parser = string_parse_enum, + .parser_data = urgency_enum_data, + .rule_offset = offsetof(struct rule, msg_urgency), + }, + { + .name = "stack_tag", + .section = "*", + .description = "Matches the stack tag of the notification as set by the client or by some other rule.", + .type = TYPE_STRING, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, stack_tag), + }, + { + .name = "summary", + .section = "*", + .description = "summary text of the notification", + .type = TYPE_STRING, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, summary), + }, + + // modifying rules below + { + .name = "script", + .section = "*", + .description = "script", + .type = TYPE_PATH, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, script), + }, + { + .name = "background", + .section = "*", + .description = "The background color of the notification.", + .type = TYPE_STRING, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, bg), + }, + { + .name = "foreground", + .section = "*", + .description = "The foreground color of the notification.", + .type = TYPE_STRING, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, fg), + }, + { + .name = "highlight", + .section = "*", + .description = "The highlight color of the notification.", + .type = TYPE_STRING, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, highlight), + }, + { + .name = "default_icon", + .section = "*", + .description = "The default icon that is used when no icon is passed", + .type = TYPE_STRING, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, default_icon), + }, + { + .name = "format", + .section = "global", + .description = "The format template for the notifications", + .type = TYPE_STRING, + .default_value = "<b>%s</b>\n%b", + .value = &settings.format, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, format), + }, + { + .name = "fullscreen", + .section = "*", + .description = "This attribute specifies how notifications are handled if a fullscreen window is focused. One of show, delay, or pushback.", + .type = TYPE_CUSTOM, + .default_value = "show", + .value = NULL, + .parser = string_parse_enum, + .parser_data = fullscreen_enum_data, + .rule_offset = offsetof(struct rule, fullscreen), + }, + { + .name = "new_icon", + .section = "*", + .description = "Updates the icon of the notification, it should be a path to a valid image.", + .type = TYPE_PATH, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, new_icon), + }, + { + .name = "set_stack_tag", + .section = "*", + .description = "Sets the stack tag for the notification, notifications with the same (non-empty) stack tag will replace each-other so only the newest one is visible.", + .type = TYPE_STRING, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, set_stack_tag), + }, + { + .name = "set_transient", + .section = "*", + .description = "Sets whether the notification is considered transient. Transient notifications will bypass the idle_threshold setting.", + .type = TYPE_CUSTOM, + .default_value = "*", + .value = NULL, + .parser = string_parse_enum, + .parser_data = boolean_enum_data, + .rule_offset = offsetof(struct rule, set_transient), + }, + { + .name = "set_category", + .section = "*", + .description = "The category of the notification as defined by the notification spec. See https://specifications.freedesktop.org/notification-spec/latest/ar01s06.html", + .type = TYPE_STRING, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, set_category), + }, + { + .name = "timeout", + .section = "*", + .description = "Don't timeout notifications if user is longer idle than threshold", + .type = TYPE_TIME, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, timeout), + }, + { + .name = "urgency", + .section = "*", + .description = "This sets the notification urgency.", + .type = TYPE_CUSTOM, + .default_value = "*", + .value = NULL, + .parser = string_parse_enum, + .parser_data = urgency_enum_data, + .rule_offset = offsetof(struct rule, urgency), + }, + { + .name = "skip_display", + .section = "*", + .description = "Setting this to true will prevent the notification from being displayed initially but will be saved in history for later viewing.", + .type = TYPE_CUSTOM, + .default_value = "*", + .value = NULL, + .parser = string_parse_enum, + .parser_data = boolean_enum_data, + .rule_offset = offsetof(struct rule, skip_display), + }, + { + .name = "history_ignore", + .section = "*", + .description = "Setting this to true will display the notification initially, but stop it from being recalled via the history.", + .type = TYPE_CUSTOM, + .default_value = "*", + .value = NULL, + .parser = string_parse_enum, + .parser_data = boolean_enum_data, + .rule_offset = offsetof(struct rule, history_ignore), + }, + { + .name = "word_wrap", + .section = "*", + .description = "Wrap long lines of text", + .type = TYPE_CUSTOM, + .default_value = "*", + .value = NULL, + .parser = string_parse_enum, + .parser_data = boolean_enum_data, + .rule_offset = offsetof(struct rule, word_wrap), + }, + { + .name = "ellipsize", + .section = "*", + .description = "Ellipsize truncated lines on the start/middle/end", + .type = TYPE_CUSTOM, + .default_value = "*", + .value = NULL, + .parser = string_parse_enum, + .parser_data = ellipsize_enum_data, + .rule_offset = offsetof(struct rule, ellipsize), + }, + { + .name = "alignment", + .section = "*", + .description = "Text alignment left/center/right", + .type = TYPE_CUSTOM, + .default_value = "*", + .value = NULL, + .parser = string_parse_enum, + .parser_data = horizontal_alignment_enum_data, + .rule_offset = offsetof(struct rule, alignment), + }, + { + .name = "hide_text", + .section = "*", + .description = "Skip rendering summary and body text in notification window (keeps icon and progress bar)", + .type = TYPE_CUSTOM, + .default_value = "*", + .value = NULL, + .parser = string_parse_enum, + .parser_data = boolean_enum_data, + .rule_offset = offsetof(struct rule, hide_text), + }, + { + .name = "markup", + .section = "*", + .description = "Specify how markup should be handled", + .type = TYPE_CUSTOM, + .default_value = "*", + .value = NULL, + .parser = string_parse_enum, + .parser_data = markup_mode_enum_data, + .rule_offset = offsetof(struct rule, markup), + }, + { + .name = "icon_position", + .section = "*", + .description = "Align icons left/right/top/off", + .type = TYPE_CUSTOM, + .default_value = "*", + .value = NULL, + .parser = string_parse_enum, + .parser_data = icon_position_enum_data, + .rule_offset = offsetof(struct rule, icon_position), + }, + { + .name = "icon_size", + .section = "*", + .description = "Set the size of the icon", + .type = TYPE_INT, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, set_icon_size), + }, + { + .name = "enabled", + .section = "*", + .description = "Enable or disable a rule", + .type = TYPE_CUSTOM, + .default_value = "true", + .value = NULL, + .parser = string_parse_bool, + .parser_data = boolean_enum_data, + .rule_offset = offsetof(struct rule, enabled), + }, + { + .name = "progress_bar_horizontal_alignment", + .section = "*", + .description = "Set the horizontal alignment of the progress bar", + .type = TYPE_CUSTOM, + .default_value = "center", + .value = NULL, + .parser = string_parse_enum, + .parser_data = horizontal_alignment_enum_data, + .rule_offset = offsetof(struct rule, progress_bar_alignment), + }, + // end of modifying rules + + // other settings below + { + .name = "frame_color", + .section = "*", + .description = "Color of the frame around the window", + .type = TYPE_STRING, + .default_value = "#888888", + .value = &settings.frame_color, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, fc), + }, + { + .name = "per_monitor_dpi", + .section = "experimental", + .description = "Use a different DPI per monitor", + .type = TYPE_CUSTOM, + .default_value = "false", + .value = &settings.per_monitor_dpi, + .parser = string_parse_bool, + .parser_data = boolean_enum_data, + }, + { + .name = "force_xinerama", + .section = "global", + .description = "Force the use of the Xinerama extension", + .type = TYPE_CUSTOM, + .default_value = "false", + .value = &settings.force_xinerama, + .parser = string_parse_bool, + .parser_data = boolean_enum_data, + }, + { + .name = "force_xwayland", + .section = "global", + .description = "Force the use of the xwayland output", + .type = TYPE_CUSTOM, + .default_value = "false", + .value = &settings.force_xwayland, + .parser = string_parse_bool, + .parser_data = boolean_enum_data, + }, + { + .name = "font", + .section = "global", + .description = "The font dunst should use.", + .type = TYPE_STRING, + .default_value = "Monospace 8", + .value = &settings.font, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "sort", + .section = "global", + .description = "Sort notifications by urgency and date?", + .type = TYPE_CUSTOM, + .default_value = "true", + .value = &settings.sort, + .parser = string_parse_enum, + .parser_data = boolean_enum_data, + }, + { + .name = "indicate_hidden", + .section = "global", + .description = "Show how many notifications are hidden", + .type = TYPE_CUSTOM, + .default_value = "true", + .value = &settings.indicate_hidden, + .parser = string_parse_enum, + .parser_data = boolean_enum_data, + }, + { + .name = "ignore_dbusclose", + .section = "global", + .description = "Ignore dbus CloseNotification events", + .type = TYPE_CUSTOM, + .default_value = "false", + .value = &settings.ignore_dbusclose, + .parser = string_parse_enum, + .parser_data = boolean_enum_data, + }, + { + .name = "ignore_newline", + .section = "global", + .description = "Ignore newline characters in notifications", + .type = TYPE_CUSTOM, + .default_value = "false", + .value = &settings.ignore_newline, + .parser = string_parse_enum, + .parser_data = boolean_enum_data, + }, + { + .name = "idle_threshold", + .section = "global", + .description = "Don't timeout notifications if user is longer idle than threshold", + .type = TYPE_TIME, + .default_value = "0", + .value = &settings.idle_threshold, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "monitor", + .section = "global", + .description = "On which monitor should the notifications be displayed", + .type = TYPE_INT, + .default_value = "0", + .value = &settings.monitor, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "title", + .section = "global", + .description = "Define the title of windows spawned by dunst.", + .type = TYPE_STRING, + .default_value = "Dunst", + .value = &settings.title, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "class", + .section = "global", + .description = "Define the class of windows spawned by dunst.", + .type = TYPE_STRING, + .default_value = "Dunst", + .value = &settings.class, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "shrink", + .section = "global", + .description = "Shrink window if it's smaller than the width", + .type = TYPE_CUSTOM, + .default_value = "false", + .value = &settings.shrink, + .parser = string_parse_enum, + .parser_data = boolean_enum_data, + }, + { + .name = "line_height", + .section = "global", + .description = "Add spacing between lines of text", + .type = TYPE_INT, + .default_value = "0", + .value = &settings.line_height, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "show_age_threshold", + .section = "global", + .description = "When should the age of the notification be displayed?", + .type = TYPE_TIME, + .default_value = "60", + .value = &settings.show_age_threshold, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "hide_duplicate_count", + .section = "global", + .description = "Hide the count of stacked notifications with the same content", + .type = TYPE_CUSTOM, + .default_value = "false", + .value = &settings.hide_duplicate_count, + .parser = string_parse_bool, + .parser_data = boolean_enum_data, + }, + { + .name = "sticky_history", + .section = "global", + .description = "Don't timeout notifications popped up from history", + .type = TYPE_CUSTOM, + .default_value = "true", + .value = &settings.sticky_history, + .parser = string_parse_enum, + .parser_data = boolean_enum_data, + }, + { + .name = "history_length", + .section = "global", + .description = "Max amount of notifications kept in history", + .type = TYPE_INT, + .default_value = "20", + .value = &settings.history_length, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "show_indicators", + .section = "global", + .description = "Show indicators for actions \"(A)\" and URLs \"(U)\"", + .type = TYPE_CUSTOM, + .default_value = "true", + .value = &settings.show_indicators, + .parser = string_parse_enum, + .parser_data = boolean_enum_data, + }, + { + .name = "separator_height", + .section = "global", + .description = "height of the separator line", + .type = TYPE_INT, + .default_value = "2", + .value = &settings.separator_height, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "padding", + .section = "global", + .description = "Padding between text and separator", + .type = TYPE_INT, + .default_value = "8", + .value = &settings.padding, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "horizontal_padding", + .section = "global", + .description = "horizontal padding", + .type = TYPE_INT, + .default_value = "8", + .value = &settings.h_padding, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "text_icon_padding", + .section = "global", + .description = "Padding between text and icon", + .type = TYPE_INT, + .default_value = "0", + .value = &settings.text_icon_padding, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "transparency", + .section = "global", + .description = "Transparency. Range 0-100", + .type = TYPE_INT, + .default_value = "0", + .value = &settings.transparency, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "corner_radius", + .section = "global", + .description = "Window corner radius", + .type = TYPE_INT, + .default_value = "0", + .value = &settings.corner_radius, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "progress_bar_height", + .section = "global", + .description = "Height of the progress bar", + .type = TYPE_INT, + .default_value = "10", + .value = &settings.progress_bar_height, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "progress_bar_min_width", + .section = "global", + .description = "Minimum width of the progress bar", + .type = TYPE_INT, + .default_value = "150", + .value = &settings.progress_bar_min_width, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "progress_bar_max_width", + .section = "global", + .description = "Maximum width of the progress bar", + .type = TYPE_INT, + .default_value = "300", + .value = &settings.progress_bar_max_width, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "progress_bar_frame_width", + .section = "global", + .description = "Frame width of the progress bar", + .type = TYPE_INT, + .default_value = "1", + .value = &settings.progress_bar_frame_width, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "progress_bar", + .section = "global", + .description = "Show the progress bar", + .type = TYPE_CUSTOM, + .default_value = "true", + .value = &settings.progress_bar, + .parser = string_parse_bool, + .parser_data = boolean_enum_data, + }, + { + .name = "stack_duplicates", + .section = "global", + .description = "Stack together notifications with the same content", + .type = TYPE_CUSTOM, + .default_value = "true", + .value = &settings.stack_duplicates, + .parser = string_parse_bool, + .parser_data = boolean_enum_data, + }, + { + .name = "dmenu", + .section = "global", + .description = "path to dmenu", + .type = TYPE_PATH, + .default_value = "/usr/bin/dmenu -p dunst", + .value = &settings.dmenu, + .parser = NULL, + .parser_data = &settings.dmenu_cmd, + }, + { + .name = "browser", + .section = "global", + .description = "path to browser", + .type = TYPE_PATH, + .default_value = "/usr/bin/xdg-open", + .value = &settings.browser, + .parser = NULL, + .parser_data = &settings.browser_cmd, + }, + { + .name = "min_icon_size", + .section = "global", + .description = "Scale smaller icons up to this size, set to 0 to disable. If max_icon_size also specified, that has the final say.", + .type = TYPE_INT, + .default_value = "0", + .value = &settings.min_icon_size, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "max_icon_size", + .section = "global", + .description = "Scale larger icons down to this size, set to 0 to disable", + .type = TYPE_INT, + .default_value = "32", + .value = &settings.max_icon_size, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "always_run_script", + .section = "global", + .description = "Always run rule-defined scripts, even if the notification is suppressed with format = \"\".", + .type = TYPE_CUSTOM, + .default_value = "true", + .value = &settings.always_run_script, + .parser = string_parse_bool, + .parser_data = boolean_enum_data, + }, + // manual extractions below + { + .name = "follow", + .section = "global", + .description = "Follow mouse, keyboard or none?", + .type = TYPE_CUSTOM, + .default_value = "none", + .value = &settings.f_mode, + .parser = string_parse_enum, + .parser_data = follow_mode_enum_data, + }, + { + .name = "scale", + .section = "global", + .description = "Scale factor, set to 0 to auto-detect, X11 only", + .type = TYPE_DOUBLE, + .default_value = "0", + .value = &settings.scale, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "separator_color", + .section = "global", + .description = "Color of the separator line (or 'auto')", + .type = TYPE_CUSTOM, + .default_value = "frame", + .value = &settings.sep_color, + .parser = string_parse_sepcolor, + .parser_data = sep_color_enum_data, + }, + { + .name = "vertical_alignment", + .section = "global", + .description = "Align icon and text top/center/bottom", + .type = TYPE_CUSTOM, + .default_value = "center", + .value = &settings.vertical_alignment, + .parser = string_parse_enum, + .parser_data = vertical_alignment_enum_data, + }, + { + .name = "layer", + .section = "global", + .description = "Select the layer where notifications should be placed", + .type = TYPE_CUSTOM, + .default_value = "overlay", + .value = &settings.layer, + .parser = string_parse_enum, + .parser_data = layer_enum_data, + }, + { + .name = "mouse_left_click", + .section = "global", + .description = "Action of left click event", + .type = TYPE_LIST, + .default_value = "close_current", + .value = &settings.mouse_left_click, + .parser = NULL, + .parser_data = GINT_TO_POINTER(MOUSE_LIST), + }, + { + .name = "mouse_middle_click", + .section = "global", + .description = "Action of middle click event", + .type = TYPE_LIST, + .default_value = "do_action, close_current", + .value = &settings.mouse_middle_click, + .parser = NULL, + .parser_data = GINT_TO_POINTER(MOUSE_LIST), + }, + { + .name = "mouse_right_click", + .section = "global", + .description = "Action of right click event", + .type = TYPE_LIST, + .default_value = "close_all", + .value = &settings.mouse_right_click, + .parser = NULL, + .parser_data = GINT_TO_POINTER(MOUSE_LIST), + }, + { + .name = "icon_theme", + .section = "global", + .description = "Name of the icon theme", + .type = TYPE_LIST, + .default_value = "Adwaita", + .value = &settings.icon_theme, + .parser = NULL, + .parser_data = GINT_TO_POINTER(STRING_LIST), + }, + { + .name = "icon_path", + .section = "global", + .description = "paths to default icons", + .type = TYPE_STRING, + .default_value = "/usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/", + .value = &settings.icon_path, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "enable_recursive_icon_lookup", + .section = "global", + .description = "Name of the icon theme", + .type = TYPE_CUSTOM, + .default_value = "false", + .value = &settings.enable_recursive_icon_lookup, + .parser = string_parse_bool, + .parser_data = boolean_enum_data, + }, + { + .name = "enable_posix_regex", + .section = "global", + .description = "Enable POSIX regex for filtering rules", + .type = TYPE_CUSTOM, + .default_value = "false", + .value = &settings.enable_regex, + .parser = string_parse_bool, + .parser_data = boolean_enum_data, + }, + { + .name = "frame_width", + .section = "global", + .description = "Width of frame around the window", + .type = TYPE_INT, + .default_value = "3", + .value = &settings.frame_width, + .parser = NULL, + .parser_data = NULL, + }, + + // These are only used for setting defaults, since there is a rule + // above doing the same. + // TODO it's probably better to create an array with default rules. + { + .name = "background", + .section = "urgency_low", + .description = "Background color for notifications with low urgency", + .type = TYPE_STRING, + .default_value = "#222222", + .value = &settings.colors_low.bg, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "foreground", + .section = "urgency_low", + .description = "Foreground color for notifications with low urgency", + .type = TYPE_STRING, + .default_value = "#888888", + .value = &settings.colors_low.fg, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "highlight", + .section = "urgency_low", + .description = "Highlight color for notifications with low urgency", + .type = TYPE_STRING, + .default_value = "#7f7fff", + .value = &settings.colors_low.highlight, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "frame_color", + .section = "urgency_low", + .description = "Frame color for notifications with low urgency", + .type = TYPE_STRING, + .default_value = "#888888", + .value = &settings.colors_low.frame, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "timeout", + .section = "urgency_low", + .description = "Timeout for notifications with low urgency", + .type = TYPE_TIME, + .default_value = "10", // in seconds + .value = &settings.timeouts[URG_LOW], + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "background", + .section = "urgency_normal", + .description = "Background color for notifications with normal urgency", + .type = TYPE_STRING, + .default_value = "#285577", + .value = &settings.colors_norm.bg, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "foreground", + .section = "urgency_normal", + .description = "Foreground color for notifications with normal urgency", + .type = TYPE_STRING, + .default_value = "#ffffff", + .value = &settings.colors_norm.fg, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "highlight", + .section = "urgency_normal", + .description = "Highlight color for notifications with normal urgency", + .type = TYPE_STRING, + .default_value = "#1745d1", + .value = &settings.colors_norm.highlight, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "frame_color", + .section = "urgency_normal", + .description = "Frame color for notifications with normal urgency", + .type = TYPE_STRING, + .default_value = "#888888", + .value = &settings.colors_norm.frame, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "timeout", + .section = "urgency_normal", + .description = "Timeout for notifications with normal urgency", + .type = TYPE_TIME, + .default_value = "10", + .value = &settings.timeouts[URG_NORM], + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "background", + .section = "urgency_critical", + .description = "Background color for notifications with critical urgency", + .type = TYPE_STRING, + .default_value = "#900000", + .value = &settings.colors_crit.bg, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "foreground", + .section = "urgency_critical", + .description = "Foreground color for notifications with ciritical urgency", + .type = TYPE_STRING, + .default_value = "#ffffff", + .value = &settings.colors_crit.fg, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "highlight", + .section = "urgency_critical", + .description = "Highlight color for notifications with ciritical urgency", + .type = TYPE_STRING, + .default_value = "#ff6666", + .value = &settings.colors_crit.highlight, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "frame_color", + .section = "urgency_critical", + .description = "Frame color for notifications with critical urgency", + .type = TYPE_STRING, + .default_value = "#ff0000", + .value = &settings.colors_crit.frame, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "timeout", + .section = "urgency_critical", + .description = "Timeout for notifications with critical urgency", + .type = TYPE_TIME, + .default_value = "0", + .value = &settings.timeouts[URG_CRIT], + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "origin", + .section = "global", + .description = "Specifies the where the notification is positioned before offsetting.", + .type = TYPE_CUSTOM, + .default_value = "top-right", + .value = &settings.origin, + .parser = string_parse_enum, + .parser_data = origin_enum_data, + }, + { + .name = "width", + .section = "global", + .description = "The width of the notification, excluding the frame.", + .type = TYPE_LENGTH, + .default_value = "300", + .value = &settings.width, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "height", + .section = "global", + .description = "The maximum height of a single notification, excluding the frame.", + .type = TYPE_INT, + .default_value = "300", + .value = &settings.height, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "offset", + .section = "global", + .description = "The offset of the notification from the origin.", + .type = TYPE_LIST, + .default_value = "10x50", + .value = &settings.offset, + .parser = NULL, + .parser_data = GINT_TO_POINTER(OFFSET_LIST), + }, + { + .name = "notification_limit", + .section = "global", + .description = "Maximum number of notifications allowed", + .type = TYPE_INT, + .default_value = "0", + .value = &settings.notification_limit, + .parser = NULL, + .parser_data = NULL, + }, + + // Keyboard shortcuts (still in global section) + { + .name = "close", + .section = "global", + .description = "Shortcut for closing one notification", + .type = TYPE_STRING, + .default_value = "none", + .value = &settings.close_ks.str, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "close_all", + .section = "global", + .description = "Shortcut for closing all notifications", + .type = TYPE_STRING, + .default_value = "none", + .value = &settings.close_all_ks.str, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "history", + .section = "global", + .description = "Shortcut to pop the last notification from history", + .type = TYPE_STRING, + .default_value = "none", + .value = &settings.history_ks.str, + .parser = NULL, + .parser_data = NULL, + }, + { + .name = "context", + .section = "global", + .description = "Shortcut for context menu", + .type = TYPE_STRING, + .default_value = "none", + .value = &settings.context_ks.str, + .parser = NULL, + .parser_data = NULL, + }, +}; +#endif +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/settings.h dunst-1.8.1/src/settings.h --- dunst-1.5.0/src/settings.h 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/settings.h 2022-03-02 10:55:25.000000000 +0000 @@ -4,38 +4,90 @@ #include <stdbool.h> -#include "markup.h" +#ifdef ENABLE_WAYLAND +#include "wayland/protocols/wlr-layer-shell-unstable-v1-client-header.h" +#endif + #include "notification.h" #include "x11/x.h" +#define LIST_END (-1) + enum alignment { ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT }; -enum ellipsize { ELLIPSE_START, ELLIPSE_MIDDLE, ELLIPSE_END }; -enum icon_position { ICON_LEFT, ICON_RIGHT, ICON_OFF }; enum vertical_alignment { VERTICAL_TOP, VERTICAL_CENTER, VERTICAL_BOTTOM }; enum separator_color { SEP_FOREGROUND, SEP_AUTO, SEP_FRAME, SEP_CUSTOM }; enum follow_mode { FOLLOW_NONE, FOLLOW_MOUSE, FOLLOW_KEYBOARD }; -enum mouse_action { MOUSE_NONE, MOUSE_DO_ACTION, MOUSE_CLOSE_CURRENT, MOUSE_CLOSE_ALL }; +enum mouse_action { MOUSE_NONE, MOUSE_DO_ACTION, MOUSE_CLOSE_CURRENT, + MOUSE_CLOSE_ALL, MOUSE_CONTEXT, MOUSE_CONTEXT_ALL, MOUSE_OPEN_URL, + MOUSE_ACTION_END = LIST_END /* indicates the end of a list of mouse actions */}; +#ifndef ZWLR_LAYER_SHELL_V1_LAYER_ENUM +#define ZWLR_LAYER_SHELL_V1_LAYER_ENUM +// Needed for compiling without wayland dependency +enum zwlr_layer_shell_v1_layer { + ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND = 0, + ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM = 1, + ZWLR_LAYER_SHELL_V1_LAYER_TOP = 2, + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY = 3, +}; +#endif /* ZWLR_LAYER_SHELL_V1_LAYER_ENUM */ + +#ifndef ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM +#define ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM +enum zwlr_layer_surface_v1_anchor { + /** + * the top edge of the anchor rectangle + */ + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP = 1, + /** + * the bottom edge of the anchor rectangle + */ + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM = 2, + /** + * the left edge of the anchor rectangle + */ + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT = 4, + /** + * the right edge of the anchor rectangle + */ + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT = 8, +}; +#endif /* ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM */ + +enum origin_values { + ORIGIN_TOP_LEFT = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT, + ORIGIN_TOP_CENTER = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, + ORIGIN_TOP_RIGHT = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT, + ORIGIN_BOTTOM_LEFT = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT, + ORIGIN_BOTTOM_CENTER = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, + ORIGIN_BOTTOM_RIGHT = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT, + ORIGIN_LEFT_CENTER = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT, + ORIGIN_RIGHT_CENTER = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT, + ORIGIN_CENTER = 0, +}; + +// TODO make a TYPE_CMD, instead of using TYPE_PATH for settings like dmenu and browser +enum setting_type { TYPE_MIN = 0, TYPE_INT, TYPE_DOUBLE, TYPE_STRING, + TYPE_PATH, TYPE_TIME, TYPE_LIST, TYPE_CUSTOM, TYPE_LENGTH, + TYPE_DEPRECATED, TYPE_MAX = TYPE_DEPRECATED + 1 }; // to be implemented struct separator_color_data { enum separator_color type; char *sep_color; }; -struct geometry { +struct length { + int min; + int max; +}; + +struct position { int x; int y; - unsigned int w; - unsigned int h; - bool negative_x; - bool negative_y; - bool negative_width; - bool width_set; }; struct settings { bool print_notifications; bool per_monitor_dpi; - enum markup_mode markup; bool stack_duplicates; bool hide_duplicate_count; char *font; @@ -46,7 +98,6 @@ gint64 timeouts[3]; char *icons[3]; unsigned int transparency; - struct geometry geometry; char *title; char *class; int shrink; @@ -58,28 +109,29 @@ int sticky_history; int history_length; int show_indicators; - int word_wrap; int ignore_dbusclose; - enum ellipsize ellipsize; int ignore_newline; int line_height; - int notification_height; int separator_height; int padding; int h_padding; + int text_icon_padding; struct separator_color_data sep_color; int frame_width; char *frame_color; int startup_notification; int monitor; + double scale; char *dmenu; char **dmenu_cmd; char *browser; char **browser_cmd; - enum icon_position icon_position; enum vertical_alignment vertical_alignment; int min_icon_size; int max_icon_size; + char **icon_theme; // experimental + bool enable_recursive_icon_lookup; // experimental + bool enable_regex; // experimental char *icon_path; enum follow_mode f_mode; bool always_run_script; @@ -88,15 +140,27 @@ struct keyboard_shortcut history_ks; struct keyboard_shortcut context_ks; bool force_xinerama; + bool force_xwayland; int corner_radius; enum mouse_action *mouse_left_click; enum mouse_action *mouse_middle_click; enum mouse_action *mouse_right_click; + int progress_bar_height; + int progress_bar_min_width; + int progress_bar_max_width; + int progress_bar_frame_width; + bool progress_bar; + enum zwlr_layer_shell_v1_layer layer; + enum origin_values origin; + struct length width; + int height; + struct position offset; + int notification_limit; }; extern struct settings settings; -void load_settings(char *cmdline_config_path); +void load_settings(const char * const path); #endif -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/utils.c dunst-1.8.1/src/utils.c --- dunst-1.5.0/src/utils.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/utils.c 2022-03-02 10:55:25.000000000 +0000 @@ -6,23 +6,16 @@ #include <errno.h> #include <glib.h> #include <pwd.h> +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> #include <time.h> #include <unistd.h> #include "log.h" - -/* see utils.h */ -void free_string_array(char **arr) -{ - if (arr){ - for (int i = 0; arr[i]; i++){ - g_free(arr[i]); - } - } - g_free(arr); -} +#include "settings_data.h" /* see utils.h */ char *string_replace_char(char needle, char replacement, char *haystack) @@ -134,24 +127,35 @@ assert(str); int iread=-1, iwrite=0, copen=0; + int cskip = 0; while (str[++iread] != 0) { if (str[iread] == a) { ++copen; } else if (str[iread] == b && copen > 0) { --copen; } else if (copen == 0) { + cskip = 0; str[iwrite++] = str[iread]; } + if (copen > 0){ + cskip++; + } + } + if (copen > 0) { + iread -= cskip; + for (int i = 0; i < cskip; i++) { + str[iwrite++] = str[iread++]; + } } str[iwrite] = 0; } /* see utils.h */ -char **string_to_array(const char *string) +char **string_to_array(const char *string, const char *delimiter) { char **arr = NULL; if (string) { - arr = g_strsplit(string, ",", 0); + arr = g_strsplit(string, delimiter, 0); for (int i = 0; arr[i]; i++){ g_strstrip(arr[i]); } @@ -160,6 +164,19 @@ } /* see utils.h */ +int string_array_length(char **s) +{ + if (!s) + return -1; + + int len = 0; + while (s[len]) + len++; + + return len; +} + +/* see utils.h */ char *string_to_path(char *string) { @@ -175,31 +192,97 @@ } /* see utils.h */ +bool safe_string_to_long_long(long long *in, const char *str) { + errno = 0; + char *endptr; + long long val = g_ascii_strtoll(str, &endptr, 10); + + if (errno != 0) { + LOG_W("'%s': %s.", str, strerror(errno)); + return false; + } else if (str == endptr) { + LOG_W("'%s': No digits found.", str); + return false; + } else if (*endptr != '\0') { + LOG_W("'%s': String contains non-digits.", str); + return false; + } + *in = val; + return true; +} + +/* see utils.h */ +bool safe_string_to_int(int *in, const char *str) { + long long l; + if (!safe_string_to_long_long(&l, str)) + return false; + + // Check if it's in int range + if (l < INT_MIN || l > INT_MAX) { + errno = ERANGE; + LOG_W("'%s': %s.", str, strerror(errno)); + return false; + } + + *in = (int) l; + return true; +} + +/* see utils.h */ +bool safe_string_to_double(double *in, const char *str) { + errno = 0; + char *endptr; + double val = g_ascii_strtod(str, &endptr); + if (errno != 0) { + LOG_W("'%s': %s.", str, strerror(errno)); + return false; + } else if (str == endptr) { + LOG_W("'%s': No digits found.", str); + return false; + } else if (*endptr != '\0') { + LOG_W("'%s': String contains non-digits.", str); + return false; + } + *in = val; + return true; +} + +/* see utils.h */ gint64 string_to_time(const char *string) { assert(string); errno = 0; char *endptr; - gint64 val = strtoll(string, &endptr, 10); + gint64 val = strtol(string, &endptr, 10); if (errno != 0) { LOG_W("Time: '%s': %s.", string, strerror(errno)); return 0; } else if (string == endptr) { + errno = EINVAL; LOG_W("Time: '%s': No digits found.", string); return 0; - } else if (errno != 0 && val == 0) { - LOG_W("Time: '%s': Unknown error.", string); - return 0; - } else if (errno == 0 && !*endptr) { + } else if (val < -1) { + // most times should not be negative, but show_age_threshhold + // can be -1 + LOG_W("Time: '%s': Time should be positive (-1 is allowed too sometimes)", + string); + errno = EINVAL; + } + else if (errno == 0 && !*endptr) { return S2US(val); } - // endptr may point to a separating space while (isspace(*endptr)) endptr++; + if (val < 0) { + LOG_W("Time: '%s' signal value -1 should not have a suffix", string); + errno = EINVAL; + return 0; + } + if (STRN_EQ(endptr, "ms", 2)) return val * 1000; else if (STRN_EQ(endptr, "s", 1)) @@ -211,7 +294,10 @@ else if (STRN_EQ(endptr, "d", 1)) return S2US(val) * 60 * 60 * 24; else + { + errno = EINVAL; return 0; + } } /* see utils.h */ @@ -247,4 +333,131 @@ return home_directory; } -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* see utils.h */ +bool safe_setenv(const char* key, const char* value){ + if (!key) + return false; + + if (!value) + setenv(key, "", 1); + else + setenv(key, value, 1); + + return true; +} + +// These sections are not interpreted as rules +static const char* special_sections[] = { + "global", + "frame", + "experimental", + "shortcuts", + "urgency_low", + "urgency_normal", + "urgency_critical", +}; + +static const char* deprecated_sections[] = { + "frame", + "shortcuts", +}; + +static const char* deprecated_sections_message[] = { + "The settings from the frame section have been moved to the global section.", // frame + "Settings in the shortcuts sections have been moved to the global section.\nAlternatively you can bind shortcuts in you window manager to dunstctl commands. For that, see the manual for dunstctl.", // shortcuts +}; + +/* see utils.h */ +bool is_special_section(const char* s) { + for (size_t i = 0; i < G_N_ELEMENTS(special_sections); i++) { + if (STR_EQ(special_sections[i], s)) { + return true; + } + } + return false; +} + +/* see utils.h */ +bool is_deprecated_section(const char* s) { + for (size_t i = 0; i < G_N_ELEMENTS(deprecated_sections); i++) { + if (STR_EQ(deprecated_sections[i], s)) { + return true; + } + } + return false; +} + +const char *get_section_deprecation_message(const char *s) { + for (size_t i = 0; i < G_N_ELEMENTS(deprecated_sections); i++) { + if (STR_EQ(deprecated_sections[i], s)) { + return deprecated_sections_message[i]; + } + } + return ""; +} + +/* see utils.h */ +char *string_strip_brackets(const char* s) { + if (!s) + return NULL; + + size_t len = strlen(s); + if (s[0] == '(' && s[len-1] == ')') + return g_strndup(s + 1, len-2); + else + return NULL; + +} + +/* see utils.h */ +bool is_readable_file(const char * const path) +{ + struct stat statbuf; + bool result = false; + + if (0 == stat(path, &statbuf)) { + /** See what intersting stuff can be done with FIFOs */ + if (!(statbuf.st_mode & (S_IFIFO | S_IFREG))) { + /** Sets errno if stat() was successful but @p path [in] + * does not point to a regular file or FIFO. This + * just in case someone queries errno which would + * otherwise indicate success. */ + errno = EINVAL; + } else if (0 == access(path, R_OK)) { /* must also be readable */ + result = true; + } + } + + return result; +} + +/* see utils.h */ +FILE *fopen_verbose(const char * const path) +{ + FILE *f = NULL; + char *real_path = string_to_path(strdup(path)); + + if (is_readable_file(real_path) && (f = fopen(real_path, "r"))) + LOG_I(MSG_FOPEN_SUCCESS(path, f)); + else + LOG_W(MSG_FOPEN_FAILURE(path)); + + free(real_path); + return f; +} + +/* see utils.h */ +void add_paths_from_env(GPtrArray *arr, char *env_name, char *subdir, char *alternative) { + const char *xdg_data_dirs = g_getenv(env_name); + if (!xdg_data_dirs) + xdg_data_dirs = alternative; + + char **xdg_data_dirs_arr = string_to_array(xdg_data_dirs, ":"); + for (int i = 0; xdg_data_dirs_arr[i] != NULL; i++) { + char *loc = g_build_filename(xdg_data_dirs_arr[i], subdir, NULL); + g_ptr_array_add(arr, loc); + } + g_strfreev(xdg_data_dirs_arr); +} + +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/utils.h dunst-1.8.1/src/utils.h --- dunst-1.5.0/src/utils.h 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/utils.h 2022-03-02 10:55:25.000000000 +0000 @@ -3,6 +3,8 @@ #define DUNST_UTILS_H #include <glib.h> +#include <stdbool.h> +#include <stdio.h> #include <string.h> //! Test if a string is NULL or empty @@ -23,15 +25,6 @@ #define S2US(s) (((gint64)(s)) * 1000 * 1000) /** - * Frees an array of strings whose last element is a NULL pointer. - * - * Assumes the last element is a NULL pointer, otherwise will result in a buffer overflow. - - * @param arr The array of strings to free - */ -void free_string_array(char **arr); - -/** * Replaces all occurrences of the char \p needle with the char \p replacement in \p haystack. * * Does not allocate a new string. @@ -92,15 +85,17 @@ void string_strip_delimited(char *str, char a, char b); /** - * Parse a comma-delimited string into a dynamic array of tokens + * Parse a string into a dynamic array of tokens, using the delimiter string. * - * The string is split on commas and strips spaces from tokens. The last element - * of the array is NULL in order to avoid passing around a length variable. + * The string is split on the separator character and strips spaces from + * tokens. The last element of the array is NULL in order to avoid passing + * around a length variable. * * @param string The string to convert to an array - * @returns The array of tokens. + * @param delimiter The character that separates list entries + * @returns The array of tokens owned by the caller. Free with g_strfreev. */ -char **string_to_array(const char *string); +char **string_to_array(const char *string, const char *delimiter); /** * Replace tilde and path-specific values with its equivalents @@ -113,6 +108,28 @@ char *string_to_path(char *string); /** + * Convert string to int in a safe way. When there is an error the int is not + * changed and false is returned. + * + * @param[out] in The int to save the result in. This is not changed when the + * parsing does not succeed + * + * @param[in] str The string to parse + * @return a bool if the conversion succeeded + */ +bool safe_string_to_int(int *in, const char *str); + +/** + * Same as safe_string_to_int, but then for a long + */ +bool safe_string_to_long_long(long long *in, const char *str); + +/** + * Same as safe_string_to_int, but then for a double + */ +bool safe_string_to_double(double *in, const char *str); + +/** * Convert time units (ms, s, m) to the internal `gint64` microseconds format * * If no unit is given, 's' (seconds) is assumed by default. @@ -137,5 +154,96 @@ */ const char *user_get_home(void); +/** + * Try to set an environment variable safely. If an environment variable with + * name `key` exists, it will be overwritten. + * If `value` is null, `key` will be set to an empty string. + * + * @param key (nullable) The environment variable to change + * @param value (nullable) The value to change it to. + * @returns: A bool that is true when it succeeds + */ +bool safe_setenv(const char* key, const char* value); + +/** + * Some sections are handled differently in dunst. This function tells wether a + * sections is such a special section. + * + * @param s The name of the section + * @returns A bool wether this section is one of the special sections + */ +bool is_special_section(const char* s); + +/** + * This function tells if a section is deprecated. + * + * @param s The name of the section + * @returns A bool wether this section is deprecated + */ +bool is_deprecated_section(const char* s); + +const char *get_section_deprecation_message(const char *s); + + +/** + * Strips a string of it's brackets if the first and last character are a + * bracket. Returns NULL on error. + * + * @param s String to strip + * @returns Newly allocated string without the brackets, or NULL. + */ +char *string_strip_brackets(const char* s); + + +/** + * Returns the length of a string array, -1 if the input is NULL. + */ +int string_array_length(char **s); + +/** @brief Check if file is readable + * + * This is a convenience function to check if @p path can be resolved and makes + * sense to try and open, like regular files and FIFOs (named pipes). Finally, + * a preliminary check is done to see if read permission is granted. + * + * Do not rely too hard on the result, though, since this is racy. A case can + * be made that these conditions might not be true anymore by the time the file + * is acutally opened for reading. + * + * Also, no tilde expansion is done. Use the result of `string_to_path()` for + * @p path. + * + * @param path [in] A string representing a path. + * @retval true in case of success. + * @retval false in case of failure, errno will be set appropriately. + */ +bool is_readable_file(const char * const path); + +/** @brief Open files verbosely. + * + * This is a wrapper around fopen() and is_readable_file() that does some + * preliminary checks and sends log messages. + * + * @p path [in] A char* string representing a filesystem path + * @returns The result of the fopen() call. + * @retval NULL if the fopen() call failed or @p path does not satisfy the + * conditions of is_readable_file(). + */ +FILE * fopen_verbose(const char * const path); + +/** + * Adds the contents of env_name with subdir to the array, interpreting the + * environment variable as a colon-separated list of paths. If the environment + * variable doesn't exist, alternative is used. + * + * @param arr The array to add to. This array has to be created first with + * g_ptr_array_new() or a similar function. + * @param env_name The name of the environment variable that contains a + * colon-separated list of paths. + * @param subdir The subdirectory to add a the end of each path in env_name + * @param alternative A colon-separated list of paths to use as alternative + * when the environment variable doesn't exits. + */ +void add_paths_from_env(GPtrArray *arr, char *env_name, char *subdir, char *alternative); #endif -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/wayland/foreign_toplevel.c dunst-1.8.1/src/wayland/foreign_toplevel.c --- dunst-1.5.0/src/wayland/foreign_toplevel.c 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/foreign_toplevel.c 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,138 @@ +#define _POSIX_C_SOURCE 200112L +#include <stdio.h> +#include <stdbool.h> +#include <stdlib.h> +#include "protocols/wlr-foreign-toplevel-management-unstable-v1-client-header.h" +/* #include "protocols/wlr-foreign-toplevel-management-unstable-v1.h" */ + +#include "foreign_toplevel.h" +#include "../dunst.h" +#include "wl_output.h" +#include "wl.h" + +struct wl_list toplevel_list; + +static void noop() { + // This space intentionally left blank +} + +static void copy_state(struct toplevel_state *current, + struct toplevel_state *pending) { + if (!(pending->state & TOPLEVEL_STATE_INVALID)) { + current->state = pending->state; + } + + pending->state = TOPLEVEL_STATE_INVALID; +} + +static uint32_t global_id = 0; + +static void toplevel_handle_output_enter(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel, + struct wl_output *wl_output) { + struct toplevel_v1 *toplevel = data; + struct toplevel_output *toplevel_output = calloc(1, sizeof(struct toplevel_output)); + struct dunst_output *dunst_output = wl_output_get_user_data(wl_output); + toplevel_output->dunst_output = dunst_output; + + wl_list_insert(&toplevel->output_list, &toplevel_output->link); +} + +static void toplevel_handle_output_leave(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel, + struct wl_output *wl_output) { + struct toplevel_v1 *toplevel = data; + + struct dunst_output *output = wl_output_get_user_data(wl_output); + struct toplevel_output *pos, *tmp; + wl_list_for_each_safe(pos, tmp, &toplevel->output_list, link){ + if (pos->dunst_output->name == output->name){ + wl_list_remove(&pos->link); + } + } +} + +static uint32_t array_to_state(struct wl_array *array) { + uint32_t state = 0; + uint32_t *entry; + wl_array_for_each(entry, array) { + if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) + state |= TOPLEVEL_STATE_ACTIVATED; + if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN) + state |= TOPLEVEL_STATE_FULLSCREEN; + } + + return state; +} + +static void toplevel_handle_state(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel, + struct wl_array *state) { + struct toplevel_v1 *toplevel = data; + toplevel->pending.state = array_to_state(state); +} + +static void toplevel_handle_done(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel) { + struct toplevel_v1 *toplevel = data; + + bool was_fullscreen = wl_have_fullscreen_window(); + copy_state(&toplevel->current, &toplevel->pending); + bool is_fullscreen = wl_have_fullscreen_window(); + + if (was_fullscreen != is_fullscreen) { + wake_up(); + } +} + +static void toplevel_handle_closed(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel) { + struct toplevel_v1 *toplevel = data; + + wl_list_remove(&toplevel->link); + struct toplevel_output *pos, *tmp; + wl_list_for_each_safe(pos, tmp, &toplevel->output_list, link){ + free(pos); + } + free(toplevel); + zwlr_foreign_toplevel_handle_v1_destroy(zwlr_toplevel); +} + +static const struct zwlr_foreign_toplevel_handle_v1_listener toplevel_impl = { + .title = noop, + .app_id = noop, + .output_enter = toplevel_handle_output_enter, + .output_leave = toplevel_handle_output_leave, + .state = toplevel_handle_state, + .done = toplevel_handle_done, + .closed = toplevel_handle_closed, +}; + +static void toplevel_manager_handle_toplevel(void *data, + struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel) { + struct toplevel_v1 *toplevel = calloc(1, sizeof(struct toplevel_v1)); + if (!toplevel) { + fprintf(stderr, "Failed to allocate memory for toplevel\n"); + return; + } + + toplevel->id = global_id++; + toplevel->zwlr_toplevel = zwlr_toplevel; + wl_list_insert(&toplevel_list, &toplevel->link); + wl_list_init(&toplevel->output_list); + + zwlr_foreign_toplevel_handle_v1_add_listener(zwlr_toplevel, &toplevel_impl, + toplevel); +} + +static void toplevel_manager_handle_finished(void *data, + struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager) { + zwlr_foreign_toplevel_manager_v1_destroy(toplevel_manager); +} + +const struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_impl = { + .toplevel = toplevel_manager_handle_toplevel, + .finished = toplevel_manager_handle_finished, +}; +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/wayland/foreign_toplevel.h dunst-1.8.1/src/wayland/foreign_toplevel.h --- dunst-1.5.0/src/wayland/foreign_toplevel.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/foreign_toplevel.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,33 @@ +#ifndef DUNST_FOREIGN_TOPLEVEL_H +#define DUNST_FOREIGN_TOPLEVEL_H +#include <wayland-client.h> + +enum toplevel_state_field { + TOPLEVEL_STATE_ACTIVATED = (1 << 0), + TOPLEVEL_STATE_FULLSCREEN = (1 << 1), + TOPLEVEL_STATE_INVALID = (1 << 2), +}; + +struct toplevel_state { + uint32_t state; +}; + +struct toplevel_v1 { + struct wl_list link; + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel; + struct wl_list output_list; + + uint32_t id; + struct toplevel_state current, pending; +}; + +struct toplevel_output { + struct dunst_output *dunst_output; + struct wl_list link; +}; + +extern const struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_impl; + +extern struct wl_list toplevel_list; +#endif +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/wayland/libgwater-wayland.c dunst-1.8.1/src/wayland/libgwater-wayland.c --- dunst-1.5.0/src/wayland/libgwater-wayland.c 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/libgwater-wayland.c 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,192 @@ +/* + * libgwater-wayland - Wayland GSource + * + * Copyright © 2014-2017 Quentin "Sardem FF7" Glidic + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#ifdef G_LOG_DOMAIN +#undef G_LOG_DOMAIN +#endif /* G_LOG_DOMAIN */ +#define G_LOG_DOMAIN "GWaterWayland" + +#include <errno.h> + +#include <glib.h> + +#include <wayland-client.h> + +#include "libgwater-wayland.h" + +struct _GWaterWaylandSource { + GSource source; + gboolean display_owned; + struct wl_display *display; + gpointer fd; + int error; +}; + +static gboolean +_g_water_wayland_source_prepare(GSource *source, gint *timeout) +{ + GWaterWaylandSource *self = (GWaterWaylandSource *)source; + + *timeout = 0; + if ( wl_display_prepare_read(self->display) != 0 ) + return TRUE; + else if ( wl_display_flush(self->display) < 0 ) + { + self->error = errno; + return TRUE; + } + + *timeout = -1; + return FALSE; +} + +static gboolean +_g_water_wayland_source_check(GSource *source) +{ + GWaterWaylandSource *self = (GWaterWaylandSource *)source; + + if ( self->error > 0 ) + return TRUE; + + GIOCondition revents; + revents = g_source_query_unix_fd(source, self->fd); + + if ( revents & G_IO_IN ) + { + if ( wl_display_read_events(self->display) < 0 ) + self->error = errno; + } + else + wl_display_cancel_read(self->display); + + return ( revents > 0 ); +} + +static gboolean +_g_water_wayland_source_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) +{ + GWaterWaylandSource *self = (GWaterWaylandSource *)source; + GIOCondition revents; + + revents = g_source_query_unix_fd(source, self->fd); + if ( ( self->error > 0 ) || ( revents & (G_IO_ERR | G_IO_HUP) ) ) + { + errno = self->error; + self->error = 0; + if ( callback != NULL ) + return callback(user_data); + return G_SOURCE_REMOVE; + } + + if ( wl_display_dispatch_pending(self->display) < 0 ) + { + if ( callback != NULL ) + return callback(user_data); + return G_SOURCE_REMOVE; + } + + return G_SOURCE_CONTINUE; +} + +static void +_g_water_wayland_source_finalize(GSource *source) +{ + GWaterWaylandSource *self = (GWaterWaylandSource *)source; + + if ( self->display_owned ) + wl_display_disconnect(self->display); +} + +static GSourceFuncs _g_water_wayland_source_funcs = { + .prepare = _g_water_wayland_source_prepare, + .check = _g_water_wayland_source_check, + .dispatch = _g_water_wayland_source_dispatch, + .finalize = _g_water_wayland_source_finalize, +}; + +GWaterWaylandSource * +g_water_wayland_source_new(GMainContext *context, const gchar *name) +{ + struct wl_display *display; + GWaterWaylandSource *self; + + display = wl_display_connect(name); + if ( display == NULL ) + return NULL; + + self = g_water_wayland_source_new_for_display(context, display); + self->display_owned = TRUE; + return self; +} + +GWaterWaylandSource * +g_water_wayland_source_new_for_display(GMainContext *context, struct wl_display *display) +{ + g_return_val_if_fail(display != NULL, NULL); + + GSource *source; + GWaterWaylandSource *self; + + source = g_source_new(&_g_water_wayland_source_funcs, sizeof(GWaterWaylandSource)); + self = (GWaterWaylandSource *)source; + self->display = display; + + self->fd = g_source_add_unix_fd(source, wl_display_get_fd(self->display), G_IO_IN | G_IO_ERR | G_IO_HUP); + + g_source_attach(source, context); + + return self; +} + +void +g_water_wayland_source_free(GWaterWaylandSource *self) +{ + GSource * source = (GSource *)self; + g_return_if_fail(self != NULL); + + g_source_destroy(source); + + g_source_unref(source); +} + +void +g_water_wayland_source_set_error_callback(GWaterWaylandSource *self, GSourceFunc callback, gpointer user_data, GDestroyNotify destroy_notify) +{ + g_return_if_fail(self != NULL); + + g_source_set_callback((GSource *)self, callback, user_data, destroy_notify); +} + +struct wl_display * +g_water_wayland_source_get_display(GWaterWaylandSource *self) +{ + g_return_val_if_fail(self != NULL, NULL); + + return self->display; +} diff -Nru dunst-1.5.0/src/wayland/libgwater-wayland.h dunst-1.8.1/src/wayland/libgwater-wayland.h --- dunst-1.5.0/src/wayland/libgwater-wayland.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/libgwater-wayland.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,42 @@ +/* + * libgwater-wayland - Wayland GSource + * + * Copyright © 2014-2017 Quentin "Sardem FF7" Glidic + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef __G_WATER_WAYLAND_H__ +#define __G_WATER_WAYLAND_H__ + +G_BEGIN_DECLS + +typedef struct _GWaterWaylandSource GWaterWaylandSource; + +GWaterWaylandSource *g_water_wayland_source_new(GMainContext *context, const gchar *name); +GWaterWaylandSource *g_water_wayland_source_new_for_display(GMainContext *context, struct wl_display *display); +void g_water_wayland_source_free(GWaterWaylandSource *self); + +void g_water_wayland_source_set_error_callback(GWaterWaylandSource *self, GSourceFunc callback, gpointer user_data, GDestroyNotify destroy_notify); +struct wl_display *g_water_wayland_source_get_display(GWaterWaylandSource *source); + +G_END_DECLS + +#endif /* __G_WATER_WAYLAND_H__ */ diff -Nru dunst-1.5.0/src/wayland/pool-buffer.c dunst-1.8.1/src/wayland/pool-buffer.c --- dunst-1.5.0/src/wayland/pool-buffer.c 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/pool-buffer.c 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,150 @@ +#define _POSIX_C_SOURCE 200112L +#include <cairo/cairo.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <time.h> +#include <unistd.h> +#include <wayland-client.h> +#include <string.h> + +#include "pool-buffer.h" + +static void randname(char *buf) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + long r = ts.tv_nsec; + for (int i = 0; i < 6; ++i) { + buf[i] = 'A'+(r&15)+(r&16)*2; + r >>= 5; + } +} + +static int anonymous_shm_open(void) { + char name[] = "/dunst-XXXXXX"; + int retries = 100; + + do { + randname(name + strlen(name) - 6); + + --retries; + // shm_open guarantees that O_CLOEXEC is set + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + shm_unlink(name); + return fd; + } + } while (retries > 0 && errno == EEXIST); + + return -1; +} + +static int create_shm_file(off_t size) { + int fd = anonymous_shm_open(); + if (fd < 0) { + return fd; + } + + if (ftruncate(fd, size) < 0) { + close(fd); + return -1; + } + + return fd; +} + +static void buffer_handle_release(void *data, struct wl_buffer *wl_buffer) { + struct pool_buffer *buffer = data; + buffer->busy = false; +} + +static const struct wl_buffer_listener buffer_listener = { + .release = buffer_handle_release, +}; + +static struct pool_buffer *create_buffer(struct wl_shm *shm, + struct pool_buffer *buf, int32_t width, int32_t height) { + const enum wl_shm_format wl_fmt = WL_SHM_FORMAT_ARGB8888; + const cairo_format_t cairo_fmt = CAIRO_FORMAT_ARGB32; + + uint32_t stride = cairo_format_stride_for_width(cairo_fmt, width); + size_t size = stride * height; + + void *data = NULL; + if (size > 0) { + int fd = create_shm_file(size); + if (fd == -1) { + return NULL; + } + + data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + close(fd); + return NULL; + } + + struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); + buf->buffer = + wl_shm_pool_create_buffer(pool, 0, width, height, stride, wl_fmt); + wl_buffer_add_listener(buf->buffer, &buffer_listener, buf); + wl_shm_pool_destroy(pool); + + close(fd); + } + + buf->data = data; + buf->size = size; + buf->width = width; + buf->height = height; + buf->surface = cairo_image_surface_create_for_data(data, cairo_fmt, width, + height, stride); + buf->cairo = cairo_create(buf->surface); + buf->pango = pango_cairo_create_context(buf->cairo); + return buf; +} + +void finish_buffer(struct pool_buffer *buffer) { + if (buffer->buffer) { + wl_buffer_destroy(buffer->buffer); + } + if (buffer->cairo) { + cairo_destroy(buffer->cairo); + } + if (buffer->surface) { + cairo_surface_destroy(buffer->surface); + } + if (buffer->pango) { + g_object_unref(buffer->pango); + } + if (buffer->data) { + munmap(buffer->data, buffer->size); + } + memset(buffer, 0, sizeof(struct pool_buffer)); +} + +struct pool_buffer *get_next_buffer(struct wl_shm *shm, + struct pool_buffer pool[static 2], uint32_t width, uint32_t height) { + struct pool_buffer *buffer = NULL; + for (size_t i = 0; i < 2; ++i) { + if (pool[i].busy) { + continue; + } + buffer = &pool[i]; + } + if (!buffer) { + return NULL; + } + + if (buffer->width != width || buffer->height != height) { + finish_buffer(buffer); + } + + if (!buffer->buffer) { + if (!create_buffer(shm, buffer, width, height)) { + return NULL; + } + } + + return buffer; +} +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/wayland/pool-buffer.h dunst-1.8.1/src/wayland/pool-buffer.h --- dunst-1.5.0/src/wayland/pool-buffer.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/pool-buffer.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,26 @@ +#ifndef DUNST_POOL_BUFFER_H +#define DUNST_POOL_BUFFER_H + +#include <cairo/cairo.h> +#include <pango/pangocairo.h> +#include <stdbool.h> +#include <stdint.h> +#include <wayland-client.h> + +struct pool_buffer { + struct wl_buffer *buffer; + cairo_surface_t *surface; + cairo_t *cairo; + PangoContext *pango; + uint32_t width, height; + void *data; + size_t size; + bool busy; +}; + +struct pool_buffer *get_next_buffer(struct wl_shm *shm, + struct pool_buffer pool[static 2], uint32_t width, uint32_t height); +void finish_buffer(struct pool_buffer *buffer); + +#endif +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/wayland/protocols/idle-client-header.h dunst-1.8.1/src/wayland/protocols/idle-client-header.h --- dunst-1.5.0/src/wayland/protocols/idle-client-header.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/protocols/idle-client-header.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,239 @@ +/* Generated by wayland-scanner 1.19.0 */ + +#ifndef IDLE_CLIENT_PROTOCOL_H +#define IDLE_CLIENT_PROTOCOL_H + +#include <stdint.h> +#include <stddef.h> +#include "wayland-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @page page_idle The idle protocol + * @section page_ifaces_idle Interfaces + * - @subpage page_iface_org_kde_kwin_idle - User idle time manager + * - @subpage page_iface_org_kde_kwin_idle_timeout - + * @section page_copyright_idle Copyright + * <pre> + * + * Copyright (C) 2015 Martin Gräßlin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * </pre> + */ +struct org_kde_kwin_idle; +struct org_kde_kwin_idle_timeout; +struct wl_seat; + +#ifndef ORG_KDE_KWIN_IDLE_INTERFACE +#define ORG_KDE_KWIN_IDLE_INTERFACE +/** + * @page page_iface_org_kde_kwin_idle org_kde_kwin_idle + * @section page_iface_org_kde_kwin_idle_desc Description + * + * This interface allows to monitor user idle time on a given seat. The interface + * allows to register timers which trigger after no user activity was registered + * on the seat for a given interval. It notifies when user activity resumes. + * + * This is useful for applications wanting to perform actions when the user is not + * interacting with the system, e.g. chat applications setting the user as away, power + * management features to dim screen, etc.. + * @section page_iface_org_kde_kwin_idle_api API + * See @ref iface_org_kde_kwin_idle. + */ +/** + * @defgroup iface_org_kde_kwin_idle The org_kde_kwin_idle interface + * + * This interface allows to monitor user idle time on a given seat. The interface + * allows to register timers which trigger after no user activity was registered + * on the seat for a given interval. It notifies when user activity resumes. + * + * This is useful for applications wanting to perform actions when the user is not + * interacting with the system, e.g. chat applications setting the user as away, power + * management features to dim screen, etc.. + */ +extern const struct wl_interface org_kde_kwin_idle_interface; +#endif +#ifndef ORG_KDE_KWIN_IDLE_TIMEOUT_INTERFACE +#define ORG_KDE_KWIN_IDLE_TIMEOUT_INTERFACE +/** + * @page page_iface_org_kde_kwin_idle_timeout org_kde_kwin_idle_timeout + * @section page_iface_org_kde_kwin_idle_timeout_api API + * See @ref iface_org_kde_kwin_idle_timeout. + */ +/** + * @defgroup iface_org_kde_kwin_idle_timeout The org_kde_kwin_idle_timeout interface + */ +extern const struct wl_interface org_kde_kwin_idle_timeout_interface; +#endif + +#define ORG_KDE_KWIN_IDLE_GET_IDLE_TIMEOUT 0 + + +/** + * @ingroup iface_org_kde_kwin_idle + */ +#define ORG_KDE_KWIN_IDLE_GET_IDLE_TIMEOUT_SINCE_VERSION 1 + +/** @ingroup iface_org_kde_kwin_idle */ +static inline void +org_kde_kwin_idle_set_user_data(struct org_kde_kwin_idle *org_kde_kwin_idle, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) org_kde_kwin_idle, user_data); +} + +/** @ingroup iface_org_kde_kwin_idle */ +static inline void * +org_kde_kwin_idle_get_user_data(struct org_kde_kwin_idle *org_kde_kwin_idle) +{ + return wl_proxy_get_user_data((struct wl_proxy *) org_kde_kwin_idle); +} + +static inline uint32_t +org_kde_kwin_idle_get_version(struct org_kde_kwin_idle *org_kde_kwin_idle) +{ + return wl_proxy_get_version((struct wl_proxy *) org_kde_kwin_idle); +} + +/** @ingroup iface_org_kde_kwin_idle */ +static inline void +org_kde_kwin_idle_destroy(struct org_kde_kwin_idle *org_kde_kwin_idle) +{ + wl_proxy_destroy((struct wl_proxy *) org_kde_kwin_idle); +} + +/** + * @ingroup iface_org_kde_kwin_idle + */ +static inline struct org_kde_kwin_idle_timeout * +org_kde_kwin_idle_get_idle_timeout(struct org_kde_kwin_idle *org_kde_kwin_idle, struct wl_seat *seat, uint32_t timeout) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_constructor((struct wl_proxy *) org_kde_kwin_idle, + ORG_KDE_KWIN_IDLE_GET_IDLE_TIMEOUT, &org_kde_kwin_idle_timeout_interface, NULL, seat, timeout); + + return (struct org_kde_kwin_idle_timeout *) id; +} + +/** + * @ingroup iface_org_kde_kwin_idle_timeout + * @struct org_kde_kwin_idle_timeout_listener + */ +struct org_kde_kwin_idle_timeout_listener { + /** + * Triggered when there has not been any user activity in the requested idle time interval + * + * + */ + void (*idle)(void *data, + struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout); + /** + * Triggered on the first user activity after an idle event + * + * + */ + void (*resumed)(void *data, + struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout); +}; + +/** + * @ingroup iface_org_kde_kwin_idle_timeout + */ +static inline int +org_kde_kwin_idle_timeout_add_listener(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout, + const struct org_kde_kwin_idle_timeout_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) org_kde_kwin_idle_timeout, + (void (**)(void)) listener, data); +} + +#define ORG_KDE_KWIN_IDLE_TIMEOUT_RELEASE 0 +#define ORG_KDE_KWIN_IDLE_TIMEOUT_SIMULATE_USER_ACTIVITY 1 + +/** + * @ingroup iface_org_kde_kwin_idle_timeout + */ +#define ORG_KDE_KWIN_IDLE_TIMEOUT_IDLE_SINCE_VERSION 1 +/** + * @ingroup iface_org_kde_kwin_idle_timeout + */ +#define ORG_KDE_KWIN_IDLE_TIMEOUT_RESUMED_SINCE_VERSION 1 + +/** + * @ingroup iface_org_kde_kwin_idle_timeout + */ +#define ORG_KDE_KWIN_IDLE_TIMEOUT_RELEASE_SINCE_VERSION 1 +/** + * @ingroup iface_org_kde_kwin_idle_timeout + */ +#define ORG_KDE_KWIN_IDLE_TIMEOUT_SIMULATE_USER_ACTIVITY_SINCE_VERSION 1 + +/** @ingroup iface_org_kde_kwin_idle_timeout */ +static inline void +org_kde_kwin_idle_timeout_set_user_data(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) org_kde_kwin_idle_timeout, user_data); +} + +/** @ingroup iface_org_kde_kwin_idle_timeout */ +static inline void * +org_kde_kwin_idle_timeout_get_user_data(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout) +{ + return wl_proxy_get_user_data((struct wl_proxy *) org_kde_kwin_idle_timeout); +} + +static inline uint32_t +org_kde_kwin_idle_timeout_get_version(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout) +{ + return wl_proxy_get_version((struct wl_proxy *) org_kde_kwin_idle_timeout); +} + +/** @ingroup iface_org_kde_kwin_idle_timeout */ +static inline void +org_kde_kwin_idle_timeout_destroy(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout) +{ + wl_proxy_destroy((struct wl_proxy *) org_kde_kwin_idle_timeout); +} + +/** + * @ingroup iface_org_kde_kwin_idle_timeout + */ +static inline void +org_kde_kwin_idle_timeout_release(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout) +{ + wl_proxy_marshal((struct wl_proxy *) org_kde_kwin_idle_timeout, + ORG_KDE_KWIN_IDLE_TIMEOUT_RELEASE); + + wl_proxy_destroy((struct wl_proxy *) org_kde_kwin_idle_timeout); +} + +/** + * @ingroup iface_org_kde_kwin_idle_timeout + */ +static inline void +org_kde_kwin_idle_timeout_simulate_user_activity(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout) +{ + wl_proxy_marshal((struct wl_proxy *) org_kde_kwin_idle_timeout, + ORG_KDE_KWIN_IDLE_TIMEOUT_SIMULATE_USER_ACTIVITY); +} + +#ifdef __cplusplus +} +#endif + +#endif diff -Nru dunst-1.5.0/src/wayland/protocols/idle.h dunst-1.8.1/src/wayland/protocols/idle.h --- dunst-1.5.0/src/wayland/protocols/idle.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/protocols/idle.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,68 @@ +/* Generated by wayland-scanner 1.19.0 */ + +/* + * Copyright (C) 2015 Martin Gräßlin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdint.h> +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface org_kde_kwin_idle_timeout_interface; +extern const struct wl_interface wl_seat_interface; + +static const struct wl_interface *idle_types[] = { + &org_kde_kwin_idle_timeout_interface, + &wl_seat_interface, + NULL, +}; + +static const struct wl_message org_kde_kwin_idle_requests[] = { + { "get_idle_timeout", "nou", idle_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface org_kde_kwin_idle_interface = { + "org_kde_kwin_idle", 1, + 1, org_kde_kwin_idle_requests, + 0, NULL, +}; + +static const struct wl_message org_kde_kwin_idle_timeout_requests[] = { + { "release", "", idle_types + 0 }, + { "simulate_user_activity", "", idle_types + 0 }, +}; + +static const struct wl_message org_kde_kwin_idle_timeout_events[] = { + { "idle", "", idle_types + 0 }, + { "resumed", "", idle_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface org_kde_kwin_idle_timeout_interface = { + "org_kde_kwin_idle_timeout", 1, + 2, org_kde_kwin_idle_timeout_requests, + 2, org_kde_kwin_idle_timeout_events, +}; + diff -Nru dunst-1.5.0/src/wayland/protocols/idle.xml dunst-1.8.1/src/wayland/protocols/idle.xml --- dunst-1.5.0/src/wayland/protocols/idle.xml 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/protocols/idle.xml 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="idle"> + <copyright><![CDATA[ + Copyright (C) 2015 Martin Gräßlin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ]]></copyright> + <interface name="org_kde_kwin_idle" version="1"> + <description summary="User idle time manager"> + This interface allows to monitor user idle time on a given seat. The interface + allows to register timers which trigger after no user activity was registered + on the seat for a given interval. It notifies when user activity resumes. + + This is useful for applications wanting to perform actions when the user is not + interacting with the system, e.g. chat applications setting the user as away, power + management features to dim screen, etc.. + </description> + <request name="get_idle_timeout"> + <arg name="id" type="new_id" interface="org_kde_kwin_idle_timeout"/> + <arg name="seat" type="object" interface="wl_seat"/> + <arg name="timeout" type="uint" summary="The idle timeout in msec"/> + </request> + </interface> + <interface name="org_kde_kwin_idle_timeout" version="1"> + <request name="release" type="destructor"> + <description summary="release the timeout object"/> + </request> + <request name="simulate_user_activity"> + <description summary="Simulates user activity for this timeout, behaves just like real user activity on the seat"/> + </request> + <event name="idle"> + <description summary="Triggered when there has not been any user activity in the requested idle time interval"/> + </event> + <event name="resumed"> + <description summary="Triggered on the first user activity after an idle event"/> + </event> + </interface> +</protocol> diff -Nru dunst-1.5.0/src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1-client-header.h dunst-1.8.1/src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1-client-header.h --- dunst-1.5.0/src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1-client-header.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1-client-header.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,617 @@ +/* Generated by wayland-scanner 1.19.0 */ + +#ifndef WLR_FOREIGN_TOPLEVEL_MANAGEMENT_UNSTABLE_V1_CLIENT_PROTOCOL_H +#define WLR_FOREIGN_TOPLEVEL_MANAGEMENT_UNSTABLE_V1_CLIENT_PROTOCOL_H + +#include <stdint.h> +#include <stddef.h> +#include "wayland-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @page page_wlr_foreign_toplevel_management_unstable_v1 The wlr_foreign_toplevel_management_unstable_v1 protocol + * @section page_ifaces_wlr_foreign_toplevel_management_unstable_v1 Interfaces + * - @subpage page_iface_zwlr_foreign_toplevel_manager_v1 - list and control opened apps + * - @subpage page_iface_zwlr_foreign_toplevel_handle_v1 - an opened toplevel + * @section page_copyright_wlr_foreign_toplevel_management_unstable_v1 Copyright + * <pre> + * + * Copyright © 2018 Ilia Bozhinov + * + * Permission to use, copy, modify, distribute, and sell this + * software and its documentation for any purpose is hereby granted + * without fee, provided that the above copyright notice appear in + * all copies and that both that copyright notice and this permission + * notice appear in supporting documentation, and that the name of + * the copyright holders not be used in advertising or publicity + * pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied + * warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + * THIS SOFTWARE. + * </pre> + */ +struct wl_output; +struct wl_seat; +struct wl_surface; +struct zwlr_foreign_toplevel_handle_v1; +struct zwlr_foreign_toplevel_manager_v1; + +#ifndef ZWLR_FOREIGN_TOPLEVEL_MANAGER_V1_INTERFACE +#define ZWLR_FOREIGN_TOPLEVEL_MANAGER_V1_INTERFACE +/** + * @page page_iface_zwlr_foreign_toplevel_manager_v1 zwlr_foreign_toplevel_manager_v1 + * @section page_iface_zwlr_foreign_toplevel_manager_v1_desc Description + * + * The purpose of this protocol is to enable the creation of taskbars + * and docks by providing them with a list of opened applications and + * letting them request certain actions on them, like maximizing, etc. + * + * After a client binds the zwlr_foreign_toplevel_manager_v1, each opened + * toplevel window will be sent via the toplevel event + * @section page_iface_zwlr_foreign_toplevel_manager_v1_api API + * See @ref iface_zwlr_foreign_toplevel_manager_v1. + */ +/** + * @defgroup iface_zwlr_foreign_toplevel_manager_v1 The zwlr_foreign_toplevel_manager_v1 interface + * + * The purpose of this protocol is to enable the creation of taskbars + * and docks by providing them with a list of opened applications and + * letting them request certain actions on them, like maximizing, etc. + * + * After a client binds the zwlr_foreign_toplevel_manager_v1, each opened + * toplevel window will be sent via the toplevel event + */ +extern const struct wl_interface zwlr_foreign_toplevel_manager_v1_interface; +#endif +#ifndef ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_INTERFACE +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_INTERFACE +/** + * @page page_iface_zwlr_foreign_toplevel_handle_v1 zwlr_foreign_toplevel_handle_v1 + * @section page_iface_zwlr_foreign_toplevel_handle_v1_desc Description + * + * A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel + * window. Each app may have multiple opened toplevels. + * + * Each toplevel has a list of outputs it is visible on, conveyed to the + * client with the output_enter and output_leave events. + * @section page_iface_zwlr_foreign_toplevel_handle_v1_api API + * See @ref iface_zwlr_foreign_toplevel_handle_v1. + */ +/** + * @defgroup iface_zwlr_foreign_toplevel_handle_v1 The zwlr_foreign_toplevel_handle_v1 interface + * + * A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel + * window. Each app may have multiple opened toplevels. + * + * Each toplevel has a list of outputs it is visible on, conveyed to the + * client with the output_enter and output_leave events. + */ +extern const struct wl_interface zwlr_foreign_toplevel_handle_v1_interface; +#endif + +/** + * @ingroup iface_zwlr_foreign_toplevel_manager_v1 + * @struct zwlr_foreign_toplevel_manager_v1_listener + */ +struct zwlr_foreign_toplevel_manager_v1_listener { + /** + * a toplevel has been created + * + * This event is emitted whenever a new toplevel window is + * created. It is emitted for all toplevels, regardless of the app + * that has created them. + * + * All initial details of the toplevel(title, app_id, states, etc.) + * will be sent immediately after this event via the corresponding + * events in zwlr_foreign_toplevel_handle_v1. + */ + void (*toplevel)(void *data, + struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1, + struct zwlr_foreign_toplevel_handle_v1 *toplevel); + /** + * the compositor has finished with the toplevel manager + * + * This event indicates that the compositor is done sending + * events to the zwlr_foreign_toplevel_manager_v1. The server will + * destroy the object immediately after sending this request, so it + * will become invalid and the client should free any resources + * associated with it. + */ + void (*finished)(void *data, + struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1); +}; + +/** + * @ingroup iface_zwlr_foreign_toplevel_manager_v1 + */ +static inline int +zwlr_foreign_toplevel_manager_v1_add_listener(struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1, + const struct zwlr_foreign_toplevel_manager_v1_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) zwlr_foreign_toplevel_manager_v1, + (void (**)(void)) listener, data); +} + +#define ZWLR_FOREIGN_TOPLEVEL_MANAGER_V1_STOP 0 + +/** + * @ingroup iface_zwlr_foreign_toplevel_manager_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_MANAGER_V1_TOPLEVEL_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_foreign_toplevel_manager_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_MANAGER_V1_FINISHED_SINCE_VERSION 1 + +/** + * @ingroup iface_zwlr_foreign_toplevel_manager_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_MANAGER_V1_STOP_SINCE_VERSION 1 + +/** @ingroup iface_zwlr_foreign_toplevel_manager_v1 */ +static inline void +zwlr_foreign_toplevel_manager_v1_set_user_data(struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) zwlr_foreign_toplevel_manager_v1, user_data); +} + +/** @ingroup iface_zwlr_foreign_toplevel_manager_v1 */ +static inline void * +zwlr_foreign_toplevel_manager_v1_get_user_data(struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1) +{ + return wl_proxy_get_user_data((struct wl_proxy *) zwlr_foreign_toplevel_manager_v1); +} + +static inline uint32_t +zwlr_foreign_toplevel_manager_v1_get_version(struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1) +{ + return wl_proxy_get_version((struct wl_proxy *) zwlr_foreign_toplevel_manager_v1); +} + +/** @ingroup iface_zwlr_foreign_toplevel_manager_v1 */ +static inline void +zwlr_foreign_toplevel_manager_v1_destroy(struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1) +{ + wl_proxy_destroy((struct wl_proxy *) zwlr_foreign_toplevel_manager_v1); +} + +/** + * @ingroup iface_zwlr_foreign_toplevel_manager_v1 + * + * Indicates the client no longer wishes to receive events for new toplevels. + * However the compositor may emit further toplevel_created events, until + * the finished event is emitted. + * + * The client must not send any more requests after this one. + */ +static inline void +zwlr_foreign_toplevel_manager_v1_stop(struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1) +{ + wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_manager_v1, + ZWLR_FOREIGN_TOPLEVEL_MANAGER_V1_STOP); +} + +#ifndef ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ENUM +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ENUM +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + * types of states on the toplevel + * + * The different states that a toplevel can have. These have the same meaning + * as the states with the same names defined in xdg-toplevel + */ +enum zwlr_foreign_toplevel_handle_v1_state { + /** + * the toplevel is maximized + */ + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED = 0, + /** + * the toplevel is minimized + */ + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED = 1, + /** + * the toplevel is active + */ + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED = 2, + /** + * the toplevel is fullscreen + * @since 2 + */ + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN = 3, +}; +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN_SINCE_VERSION 2 +#endif /* ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ENUM */ + +#ifndef ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_ERROR_ENUM +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_ERROR_ENUM +enum zwlr_foreign_toplevel_handle_v1_error { + /** + * the provided rectangle is invalid + */ + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_ERROR_INVALID_RECTANGLE = 0, +}; +#endif /* ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_ERROR_ENUM */ + +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + * @struct zwlr_foreign_toplevel_handle_v1_listener + */ +struct zwlr_foreign_toplevel_handle_v1_listener { + /** + * title change + * + * This event is emitted whenever the title of the toplevel + * changes. + */ + void (*title)(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, + const char *title); + /** + * app-id change + * + * This event is emitted whenever the app-id of the toplevel + * changes. + */ + void (*app_id)(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, + const char *app_id); + /** + * toplevel entered an output + * + * This event is emitted whenever the toplevel becomes visible on + * the given output. A toplevel may be visible on multiple outputs. + */ + void (*output_enter)(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, + struct wl_output *output); + /** + * toplevel left an output + * + * This event is emitted whenever the toplevel stops being + * visible on the given output. It is guaranteed that an + * entered-output event with the same output has been emitted + * before this event. + */ + void (*output_leave)(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, + struct wl_output *output); + /** + * the toplevel state changed + * + * This event is emitted immediately after the + * zlw_foreign_toplevel_handle_v1 is created and each time the + * toplevel state changes, either because of a compositor action or + * because of a request in this protocol. + */ + void (*state)(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, + struct wl_array *state); + /** + * all information about the toplevel has been sent + * + * This event is sent after all changes in the toplevel state + * have been sent. + * + * This allows changes to the zwlr_foreign_toplevel_handle_v1 + * properties to be seen as atomic, even if they happen via + * multiple events. + */ + void (*done)(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1); + /** + * this toplevel has been destroyed + * + * This event means the toplevel has been destroyed. It is + * guaranteed there won't be any more events for this + * zwlr_foreign_toplevel_handle_v1. The toplevel itself becomes + * inert so any requests will be ignored except the destroy + * request. + */ + void (*closed)(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1); + /** + * parent change + * + * This event is emitted whenever the parent of the toplevel + * changes. + * + * No event is emitted when the parent handle is destroyed by the + * client. + * @since 3 + */ + void (*parent)(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, + struct zwlr_foreign_toplevel_handle_v1 *parent); +}; + +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +static inline int +zwlr_foreign_toplevel_handle_v1_add_listener(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, + const struct zwlr_foreign_toplevel_handle_v1_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1, + (void (**)(void)) listener, data); +} + +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_MAXIMIZED 0 +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_UNSET_MAXIMIZED 1 +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_MINIMIZED 2 +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_UNSET_MINIMIZED 3 +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_ACTIVATE 4 +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_CLOSE 5 +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_RECTANGLE 6 +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_DESTROY 7 +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_FULLSCREEN 8 +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_UNSET_FULLSCREEN 9 + +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_TITLE_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_APP_ID_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_OUTPUT_ENTER_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_OUTPUT_LEAVE_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_DONE_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_CLOSED_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_PARENT_SINCE_VERSION 3 + +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_MAXIMIZED_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_UNSET_MAXIMIZED_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_MINIMIZED_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_UNSET_MINIMIZED_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_ACTIVATE_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_CLOSE_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_RECTANGLE_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_FULLSCREEN_SINCE_VERSION 2 +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + */ +#define ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_UNSET_FULLSCREEN_SINCE_VERSION 2 + +/** @ingroup iface_zwlr_foreign_toplevel_handle_v1 */ +static inline void +zwlr_foreign_toplevel_handle_v1_set_user_data(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1, user_data); +} + +/** @ingroup iface_zwlr_foreign_toplevel_handle_v1 */ +static inline void * +zwlr_foreign_toplevel_handle_v1_get_user_data(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1) +{ + return wl_proxy_get_user_data((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1); +} + +static inline uint32_t +zwlr_foreign_toplevel_handle_v1_get_version(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1) +{ + return wl_proxy_get_version((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1); +} + +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + * + * Requests that the toplevel be maximized. If the maximized state actually + * changes, this will be indicated by the state event. + */ +static inline void +zwlr_foreign_toplevel_handle_v1_set_maximized(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1) +{ + wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1, + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_MAXIMIZED); +} + +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + * + * Requests that the toplevel be unmaximized. If the maximized state actually + * changes, this will be indicated by the state event. + */ +static inline void +zwlr_foreign_toplevel_handle_v1_unset_maximized(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1) +{ + wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1, + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_UNSET_MAXIMIZED); +} + +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + * + * Requests that the toplevel be minimized. If the minimized state actually + * changes, this will be indicated by the state event. + */ +static inline void +zwlr_foreign_toplevel_handle_v1_set_minimized(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1) +{ + wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1, + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_MINIMIZED); +} + +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + * + * Requests that the toplevel be unminimized. If the minimized state actually + * changes, this will be indicated by the state event. + */ +static inline void +zwlr_foreign_toplevel_handle_v1_unset_minimized(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1) +{ + wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1, + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_UNSET_MINIMIZED); +} + +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + * + * Request that this toplevel be activated on the given seat. + * There is no guarantee the toplevel will be actually activated. + */ +static inline void +zwlr_foreign_toplevel_handle_v1_activate(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, struct wl_seat *seat) +{ + wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1, + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_ACTIVATE, seat); +} + +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + * + * Send a request to the toplevel to close itself. The compositor would + * typically use a shell-specific method to carry out this request, for + * example by sending the xdg_toplevel.close event. However, this gives + * no guarantees the toplevel will actually be destroyed. If and when + * this happens, the zwlr_foreign_toplevel_handle_v1.closed event will + * be emitted. + */ +static inline void +zwlr_foreign_toplevel_handle_v1_close(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1) +{ + wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1, + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_CLOSE); +} + +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + * + * The rectangle of the surface specified in this request corresponds to + * the place where the app using this protocol represents the given toplevel. + * It can be used by the compositor as a hint for some operations, e.g + * minimizing. The client is however not required to set this, in which + * case the compositor is free to decide some default value. + * + * If the client specifies more than one rectangle, only the last one is + * considered. + * + * The dimensions are given in surface-local coordinates. + * Setting width=height=0 removes the already-set rectangle. + */ +static inline void +zwlr_foreign_toplevel_handle_v1_set_rectangle(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, struct wl_surface *surface, int32_t x, int32_t y, int32_t width, int32_t height) +{ + wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1, + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_RECTANGLE, surface, x, y, width, height); +} + +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + * + * Destroys the zwlr_foreign_toplevel_handle_v1 object. + * + * This request should be called either when the client does not want to + * use the toplevel anymore or after the closed event to finalize the + * destruction of the object. + */ +static inline void +zwlr_foreign_toplevel_handle_v1_destroy(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1) +{ + wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1, + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_DESTROY); + + wl_proxy_destroy((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1); +} + +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + * + * Requests that the toplevel be fullscreened on the given output. If the + * fullscreen state and/or the outputs the toplevel is visible on actually + * change, this will be indicated by the state and output_enter/leave + * events. + * + * The output parameter is only a hint to the compositor. Also, if output + * is NULL, the compositor should decide which output the toplevel will be + * fullscreened on, if at all. + */ +static inline void +zwlr_foreign_toplevel_handle_v1_set_fullscreen(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, struct wl_output *output) +{ + wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1, + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_FULLSCREEN, output); +} + +/** + * @ingroup iface_zwlr_foreign_toplevel_handle_v1 + * + * Requests that the toplevel be unfullscreened. If the fullscreen state + * actually changes, this will be indicated by the state event. + */ +static inline void +zwlr_foreign_toplevel_handle_v1_unset_fullscreen(struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1) +{ + wl_proxy_marshal((struct wl_proxy *) zwlr_foreign_toplevel_handle_v1, + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_UNSET_FULLSCREEN); +} + +#ifdef __cplusplus +} +#endif + +#endif diff -Nru dunst-1.5.0/src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1.h dunst-1.8.1/src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1.h --- dunst-1.5.0/src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,106 @@ +/* Generated by wayland-scanner 1.19.0 */ + +/* + * Copyright © 2018 Ilia Bozhinov + * + * Permission to use, copy, modify, distribute, and sell this + * software and its documentation for any purpose is hereby granted + * without fee, provided that the above copyright notice appear in + * all copies and that both that copyright notice and this permission + * notice appear in supporting documentation, and that the name of + * the copyright holders not be used in advertising or publicity + * pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied + * warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + * THIS SOFTWARE. + */ + +#include <stdlib.h> +#include <stdint.h> +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_output_interface; +extern const struct wl_interface wl_seat_interface; +extern const struct wl_interface wl_surface_interface; +extern const struct wl_interface zwlr_foreign_toplevel_handle_v1_interface; + +static const struct wl_interface *wlr_foreign_toplevel_management_unstable_v1_types[] = { + NULL, + &zwlr_foreign_toplevel_handle_v1_interface, + &wl_seat_interface, + &wl_surface_interface, + NULL, + NULL, + NULL, + NULL, + &wl_output_interface, + &wl_output_interface, + &wl_output_interface, + &zwlr_foreign_toplevel_handle_v1_interface, +}; + +static const struct wl_message zwlr_foreign_toplevel_manager_v1_requests[] = { + { "stop", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, +}; + +static const struct wl_message zwlr_foreign_toplevel_manager_v1_events[] = { + { "toplevel", "n", wlr_foreign_toplevel_management_unstable_v1_types + 1 }, + { "finished", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface zwlr_foreign_toplevel_manager_v1_interface = { + "zwlr_foreign_toplevel_manager_v1", 3, + 1, zwlr_foreign_toplevel_manager_v1_requests, + 2, zwlr_foreign_toplevel_manager_v1_events, +}; + +static const struct wl_message zwlr_foreign_toplevel_handle_v1_requests[] = { + { "set_maximized", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, + { "unset_maximized", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, + { "set_minimized", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, + { "unset_minimized", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, + { "activate", "o", wlr_foreign_toplevel_management_unstable_v1_types + 2 }, + { "close", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, + { "set_rectangle", "oiiii", wlr_foreign_toplevel_management_unstable_v1_types + 3 }, + { "destroy", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, + { "set_fullscreen", "2?o", wlr_foreign_toplevel_management_unstable_v1_types + 8 }, + { "unset_fullscreen", "2", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, +}; + +static const struct wl_message zwlr_foreign_toplevel_handle_v1_events[] = { + { "title", "s", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, + { "app_id", "s", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, + { "output_enter", "o", wlr_foreign_toplevel_management_unstable_v1_types + 9 }, + { "output_leave", "o", wlr_foreign_toplevel_management_unstable_v1_types + 10 }, + { "state", "a", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, + { "done", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, + { "closed", "", wlr_foreign_toplevel_management_unstable_v1_types + 0 }, + { "parent", "3?o", wlr_foreign_toplevel_management_unstable_v1_types + 11 }, +}; + +WL_PRIVATE const struct wl_interface zwlr_foreign_toplevel_handle_v1_interface = { + "zwlr_foreign_toplevel_handle_v1", 3, + 10, zwlr_foreign_toplevel_handle_v1_requests, + 8, zwlr_foreign_toplevel_handle_v1_events, +}; + diff -Nru dunst-1.5.0/src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1.xml dunst-1.8.1/src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1.xml --- dunst-1.5.0/src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1.xml 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/protocols/wlr-foreign-toplevel-management-unstable-v1.xml 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,270 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="wlr_foreign_toplevel_management_unstable_v1"> + <copyright> + Copyright © 2018 Ilia Bozhinov + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + </copyright> + + <interface name="zwlr_foreign_toplevel_manager_v1" version="3"> + <description summary="list and control opened apps"> + The purpose of this protocol is to enable the creation of taskbars + and docks by providing them with a list of opened applications and + letting them request certain actions on them, like maximizing, etc. + + After a client binds the zwlr_foreign_toplevel_manager_v1, each opened + toplevel window will be sent via the toplevel event + </description> + + <event name="toplevel"> + <description summary="a toplevel has been created"> + This event is emitted whenever a new toplevel window is created. It + is emitted for all toplevels, regardless of the app that has created + them. + + All initial details of the toplevel(title, app_id, states, etc.) will + be sent immediately after this event via the corresponding events in + zwlr_foreign_toplevel_handle_v1. + </description> + <arg name="toplevel" type="new_id" interface="zwlr_foreign_toplevel_handle_v1"/> + </event> + + <request name="stop"> + <description summary="stop sending events"> + Indicates the client no longer wishes to receive events for new toplevels. + However the compositor may emit further toplevel_created events, until + the finished event is emitted. + + The client must not send any more requests after this one. + </description> + </request> + + <event name="finished"> + <description summary="the compositor has finished with the toplevel manager"> + This event indicates that the compositor is done sending events to the + zwlr_foreign_toplevel_manager_v1. The server will destroy the object + immediately after sending this request, so it will become invalid and + the client should free any resources associated with it. + </description> + </event> + </interface> + + <interface name="zwlr_foreign_toplevel_handle_v1" version="3"> + <description summary="an opened toplevel"> + A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel + window. Each app may have multiple opened toplevels. + + Each toplevel has a list of outputs it is visible on, conveyed to the + client with the output_enter and output_leave events. + </description> + + <event name="title"> + <description summary="title change"> + This event is emitted whenever the title of the toplevel changes. + </description> + <arg name="title" type="string"/> + </event> + + <event name="app_id"> + <description summary="app-id change"> + This event is emitted whenever the app-id of the toplevel changes. + </description> + <arg name="app_id" type="string"/> + </event> + + <event name="output_enter"> + <description summary="toplevel entered an output"> + This event is emitted whenever the toplevel becomes visible on + the given output. A toplevel may be visible on multiple outputs. + </description> + <arg name="output" type="object" interface="wl_output"/> + </event> + + <event name="output_leave"> + <description summary="toplevel left an output"> + This event is emitted whenever the toplevel stops being visible on + the given output. It is guaranteed that an entered-output event + with the same output has been emitted before this event. + </description> + <arg name="output" type="object" interface="wl_output"/> + </event> + + <request name="set_maximized"> + <description summary="requests that the toplevel be maximized"> + Requests that the toplevel be maximized. If the maximized state actually + changes, this will be indicated by the state event. + </description> + </request> + + <request name="unset_maximized"> + <description summary="requests that the toplevel be unmaximized"> + Requests that the toplevel be unmaximized. If the maximized state actually + changes, this will be indicated by the state event. + </description> + </request> + + <request name="set_minimized"> + <description summary="requests that the toplevel be minimized"> + Requests that the toplevel be minimized. If the minimized state actually + changes, this will be indicated by the state event. + </description> + </request> + + <request name="unset_minimized"> + <description summary="requests that the toplevel be unminimized"> + Requests that the toplevel be unminimized. If the minimized state actually + changes, this will be indicated by the state event. + </description> + </request> + + <request name="activate"> + <description summary="activate the toplevel"> + Request that this toplevel be activated on the given seat. + There is no guarantee the toplevel will be actually activated. + </description> + <arg name="seat" type="object" interface="wl_seat"/> + </request> + + <enum name="state"> + <description summary="types of states on the toplevel"> + The different states that a toplevel can have. These have the same meaning + as the states with the same names defined in xdg-toplevel + </description> + + <entry name="maximized" value="0" summary="the toplevel is maximized"/> + <entry name="minimized" value="1" summary="the toplevel is minimized"/> + <entry name="activated" value="2" summary="the toplevel is active"/> + <entry name="fullscreen" value="3" summary="the toplevel is fullscreen" since="2"/> + </enum> + + <event name="state"> + <description summary="the toplevel state changed"> + This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 + is created and each time the toplevel state changes, either because of a + compositor action or because of a request in this protocol. + </description> + + <arg name="state" type="array"/> + </event> + + <event name="done"> + <description summary="all information about the toplevel has been sent"> + This event is sent after all changes in the toplevel state have been + sent. + + This allows changes to the zwlr_foreign_toplevel_handle_v1 properties + to be seen as atomic, even if they happen via multiple events. + </description> + </event> + + <request name="close"> + <description summary="request that the toplevel be closed"> + Send a request to the toplevel to close itself. The compositor would + typically use a shell-specific method to carry out this request, for + example by sending the xdg_toplevel.close event. However, this gives + no guarantees the toplevel will actually be destroyed. If and when + this happens, the zwlr_foreign_toplevel_handle_v1.closed event will + be emitted. + </description> + </request> + + <request name="set_rectangle"> + <description summary="the rectangle which represents the toplevel"> + The rectangle of the surface specified in this request corresponds to + the place where the app using this protocol represents the given toplevel. + It can be used by the compositor as a hint for some operations, e.g + minimizing. The client is however not required to set this, in which + case the compositor is free to decide some default value. + + If the client specifies more than one rectangle, only the last one is + considered. + + The dimensions are given in surface-local coordinates. + Setting width=height=0 removes the already-set rectangle. + </description> + + <arg name="surface" type="object" interface="wl_surface"/> + <arg name="x" type="int"/> + <arg name="y" type="int"/> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + </request> + + <enum name="error"> + <entry name="invalid_rectangle" value="0" + summary="the provided rectangle is invalid"/> + </enum> + + <event name="closed"> + <description summary="this toplevel has been destroyed"> + This event means the toplevel has been destroyed. It is guaranteed there + won't be any more events for this zwlr_foreign_toplevel_handle_v1. The + toplevel itself becomes inert so any requests will be ignored except the + destroy request. + </description> + </event> + + <request name="destroy" type="destructor"> + <description summary="destroy the zwlr_foreign_toplevel_handle_v1 object"> + Destroys the zwlr_foreign_toplevel_handle_v1 object. + + This request should be called either when the client does not want to + use the toplevel anymore or after the closed event to finalize the + destruction of the object. + </description> + </request> + + <!-- Version 2 additions --> + + <request name="set_fullscreen" since="2"> + <description summary="request that the toplevel be fullscreened"> + Requests that the toplevel be fullscreened on the given output. If the + fullscreen state and/or the outputs the toplevel is visible on actually + change, this will be indicated by the state and output_enter/leave + events. + + The output parameter is only a hint to the compositor. Also, if output + is NULL, the compositor should decide which output the toplevel will be + fullscreened on, if at all. + </description> + <arg name="output" type="object" interface="wl_output" allow-null="true"/> + </request> + + <request name="unset_fullscreen" since="2"> + <description summary="request that the toplevel be unfullscreened"> + Requests that the toplevel be unfullscreened. If the fullscreen state + actually changes, this will be indicated by the state event. + </description> + </request> + + <!-- Version 3 additions --> + + <event name="parent" since="3"> + <description summary="parent change"> + This event is emitted whenever the parent of the toplevel changes. + + No event is emitted when the parent handle is destroyed by the client. + </description> + <arg name="parent" type="object" interface="zwlr_foreign_toplevel_handle_v1" allow-null="true"/> + </event> + </interface> +</protocol> diff -Nru dunst-1.5.0/src/wayland/protocols/wlr-layer-shell-unstable-v1-client-header.h dunst-1.8.1/src/wayland/protocols/wlr-layer-shell-unstable-v1-client-header.h --- dunst-1.5.0/src/wayland/protocols/wlr-layer-shell-unstable-v1-client-header.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/protocols/wlr-layer-shell-unstable-v1-client-header.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,565 @@ +/* Generated by wayland-scanner 1.19.0 */ + +#ifndef WLR_LAYER_SHELL_UNSTABLE_V1_CLIENT_PROTOCOL_H +#define WLR_LAYER_SHELL_UNSTABLE_V1_CLIENT_PROTOCOL_H + +#include <stdint.h> +#include <stddef.h> +#include "wayland-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @page page_wlr_layer_shell_unstable_v1 The wlr_layer_shell_unstable_v1 protocol + * @section page_ifaces_wlr_layer_shell_unstable_v1 Interfaces + * - @subpage page_iface_zwlr_layer_shell_v1 - create surfaces that are layers of the desktop + * - @subpage page_iface_zwlr_layer_surface_v1 - layer metadata interface + * @section page_copyright_wlr_layer_shell_unstable_v1 Copyright + * <pre> + * + * Copyright © 2017 Drew DeVault + * + * Permission to use, copy, modify, distribute, and sell this + * software and its documentation for any purpose is hereby granted + * without fee, provided that the above copyright notice appear in + * all copies and that both that copyright notice and this permission + * notice appear in supporting documentation, and that the name of + * the copyright holders not be used in advertising or publicity + * pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied + * warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + * THIS SOFTWARE. + * </pre> + */ +struct wl_output; +struct wl_surface; +struct xdg_popup; +struct zwlr_layer_shell_v1; +struct zwlr_layer_surface_v1; + +#ifndef ZWLR_LAYER_SHELL_V1_INTERFACE +#define ZWLR_LAYER_SHELL_V1_INTERFACE +/** + * @page page_iface_zwlr_layer_shell_v1 zwlr_layer_shell_v1 + * @section page_iface_zwlr_layer_shell_v1_desc Description + * + * Clients can use this interface to assign the surface_layer role to + * wl_surfaces. Such surfaces are assigned to a "layer" of the output and + * rendered with a defined z-depth respective to each other. They may also be + * anchored to the edges and corners of a screen and specify input handling + * semantics. This interface should be suitable for the implementation of + * many desktop shell components, and a broad number of other applications + * that interact with the desktop. + * @section page_iface_zwlr_layer_shell_v1_api API + * See @ref iface_zwlr_layer_shell_v1. + */ +/** + * @defgroup iface_zwlr_layer_shell_v1 The zwlr_layer_shell_v1 interface + * + * Clients can use this interface to assign the surface_layer role to + * wl_surfaces. Such surfaces are assigned to a "layer" of the output and + * rendered with a defined z-depth respective to each other. They may also be + * anchored to the edges and corners of a screen and specify input handling + * semantics. This interface should be suitable for the implementation of + * many desktop shell components, and a broad number of other applications + * that interact with the desktop. + */ +extern const struct wl_interface zwlr_layer_shell_v1_interface; +#endif +#ifndef ZWLR_LAYER_SURFACE_V1_INTERFACE +#define ZWLR_LAYER_SURFACE_V1_INTERFACE +/** + * @page page_iface_zwlr_layer_surface_v1 zwlr_layer_surface_v1 + * @section page_iface_zwlr_layer_surface_v1_desc Description + * + * An interface that may be implemented by a wl_surface, for surfaces that + * are designed to be rendered as a layer of a stacked desktop-like + * environment. + * + * Layer surface state (size, anchor, exclusive zone, margin, interactivity) + * is double-buffered, and will be applied at the time wl_surface.commit of + * the corresponding wl_surface is called. + * @section page_iface_zwlr_layer_surface_v1_api API + * See @ref iface_zwlr_layer_surface_v1. + */ +/** + * @defgroup iface_zwlr_layer_surface_v1 The zwlr_layer_surface_v1 interface + * + * An interface that may be implemented by a wl_surface, for surfaces that + * are designed to be rendered as a layer of a stacked desktop-like + * environment. + * + * Layer surface state (size, anchor, exclusive zone, margin, interactivity) + * is double-buffered, and will be applied at the time wl_surface.commit of + * the corresponding wl_surface is called. + */ +extern const struct wl_interface zwlr_layer_surface_v1_interface; +#endif + +#ifndef ZWLR_LAYER_SHELL_V1_ERROR_ENUM +#define ZWLR_LAYER_SHELL_V1_ERROR_ENUM +enum zwlr_layer_shell_v1_error { + /** + * wl_surface has another role + */ + ZWLR_LAYER_SHELL_V1_ERROR_ROLE = 0, + /** + * layer value is invalid + */ + ZWLR_LAYER_SHELL_V1_ERROR_INVALID_LAYER = 1, + /** + * wl_surface has a buffer attached or committed + */ + ZWLR_LAYER_SHELL_V1_ERROR_ALREADY_CONSTRUCTED = 2, +}; +#endif /* ZWLR_LAYER_SHELL_V1_ERROR_ENUM */ + +#ifndef ZWLR_LAYER_SHELL_V1_LAYER_ENUM +#define ZWLR_LAYER_SHELL_V1_LAYER_ENUM +/** + * @ingroup iface_zwlr_layer_shell_v1 + * available layers for surfaces + * + * These values indicate which layers a surface can be rendered in. They + * are ordered by z depth, bottom-most first. Traditional shell surfaces + * will typically be rendered between the bottom and top layers. + * Fullscreen shell surfaces are typically rendered at the top layer. + * Multiple surfaces can share a single layer, and ordering within a + * single layer is undefined. + */ +enum zwlr_layer_shell_v1_layer { + ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND = 0, + ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM = 1, + ZWLR_LAYER_SHELL_V1_LAYER_TOP = 2, + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY = 3, +}; +#endif /* ZWLR_LAYER_SHELL_V1_LAYER_ENUM */ + +#define ZWLR_LAYER_SHELL_V1_GET_LAYER_SURFACE 0 + + +/** + * @ingroup iface_zwlr_layer_shell_v1 + */ +#define ZWLR_LAYER_SHELL_V1_GET_LAYER_SURFACE_SINCE_VERSION 1 + +/** @ingroup iface_zwlr_layer_shell_v1 */ +static inline void +zwlr_layer_shell_v1_set_user_data(struct zwlr_layer_shell_v1 *zwlr_layer_shell_v1, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) zwlr_layer_shell_v1, user_data); +} + +/** @ingroup iface_zwlr_layer_shell_v1 */ +static inline void * +zwlr_layer_shell_v1_get_user_data(struct zwlr_layer_shell_v1 *zwlr_layer_shell_v1) +{ + return wl_proxy_get_user_data((struct wl_proxy *) zwlr_layer_shell_v1); +} + +static inline uint32_t +zwlr_layer_shell_v1_get_version(struct zwlr_layer_shell_v1 *zwlr_layer_shell_v1) +{ + return wl_proxy_get_version((struct wl_proxy *) zwlr_layer_shell_v1); +} + +/** @ingroup iface_zwlr_layer_shell_v1 */ +static inline void +zwlr_layer_shell_v1_destroy(struct zwlr_layer_shell_v1 *zwlr_layer_shell_v1) +{ + wl_proxy_destroy((struct wl_proxy *) zwlr_layer_shell_v1); +} + +/** + * @ingroup iface_zwlr_layer_shell_v1 + * + * Create a layer surface for an existing surface. This assigns the role of + * layer_surface, or raises a protocol error if another role is already + * assigned. + * + * Creating a layer surface from a wl_surface which has a buffer attached + * or committed is a client error, and any attempts by a client to attach + * or manipulate a buffer prior to the first layer_surface.configure call + * must also be treated as errors. + * + * You may pass NULL for output to allow the compositor to decide which + * output to use. Generally this will be the one that the user most + * recently interacted with. + * + * Clients can specify a namespace that defines the purpose of the layer + * surface. + */ +static inline struct zwlr_layer_surface_v1 * +zwlr_layer_shell_v1_get_layer_surface(struct zwlr_layer_shell_v1 *zwlr_layer_shell_v1, struct wl_surface *surface, struct wl_output *output, uint32_t layer, const char *namespace) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_constructor((struct wl_proxy *) zwlr_layer_shell_v1, + ZWLR_LAYER_SHELL_V1_GET_LAYER_SURFACE, &zwlr_layer_surface_v1_interface, NULL, surface, output, layer, namespace); + + return (struct zwlr_layer_surface_v1 *) id; +} + +#ifndef ZWLR_LAYER_SURFACE_V1_ERROR_ENUM +#define ZWLR_LAYER_SURFACE_V1_ERROR_ENUM +enum zwlr_layer_surface_v1_error { + /** + * provided surface state is invalid + */ + ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SURFACE_STATE = 0, + /** + * size is invalid + */ + ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE = 1, + /** + * anchor bitfield is invalid + */ + ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_ANCHOR = 2, +}; +#endif /* ZWLR_LAYER_SURFACE_V1_ERROR_ENUM */ + +#ifndef ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM +#define ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM +enum zwlr_layer_surface_v1_anchor { + /** + * the top edge of the anchor rectangle + */ + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP = 1, + /** + * the bottom edge of the anchor rectangle + */ + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM = 2, + /** + * the left edge of the anchor rectangle + */ + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT = 4, + /** + * the right edge of the anchor rectangle + */ + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT = 8, +}; +#endif /* ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM */ + +/** + * @ingroup iface_zwlr_layer_surface_v1 + * @struct zwlr_layer_surface_v1_listener + */ +struct zwlr_layer_surface_v1_listener { + /** + * suggest a surface change + * + * The configure event asks the client to resize its surface. + * + * Clients should arrange their surface for the new states, and + * then send an ack_configure request with the serial sent in this + * configure event at some point before committing the new surface. + * + * The client is free to dismiss all but the last configure event + * it received. + * + * The width and height arguments specify the size of the window in + * surface-local coordinates. + * + * The size is a hint, in the sense that the client is free to + * ignore it if it doesn't resize, pick a smaller size (to satisfy + * aspect ratio or resize in steps of NxM pixels). If the client + * picks a smaller size and is anchored to two opposite anchors + * (e.g. 'top' and 'bottom'), the surface will be centered on this + * axis. + * + * If the width or height arguments are zero, it means the client + * should decide its own window dimension. + */ + void (*configure)(void *data, + struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, + uint32_t serial, + uint32_t width, + uint32_t height); + /** + * surface should be closed + * + * The closed event is sent by the compositor when the surface + * will no longer be shown. The output may have been destroyed or + * the user may have asked for it to be removed. Further changes to + * the surface will be ignored. The client should destroy the + * resource after receiving this event, and create a new surface if + * they so choose. + */ + void (*closed)(void *data, + struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1); +}; + +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +static inline int +zwlr_layer_surface_v1_add_listener(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, + const struct zwlr_layer_surface_v1_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) zwlr_layer_surface_v1, + (void (**)(void)) listener, data); +} + +#define ZWLR_LAYER_SURFACE_V1_SET_SIZE 0 +#define ZWLR_LAYER_SURFACE_V1_SET_ANCHOR 1 +#define ZWLR_LAYER_SURFACE_V1_SET_EXCLUSIVE_ZONE 2 +#define ZWLR_LAYER_SURFACE_V1_SET_MARGIN 3 +#define ZWLR_LAYER_SURFACE_V1_SET_KEYBOARD_INTERACTIVITY 4 +#define ZWLR_LAYER_SURFACE_V1_GET_POPUP 5 +#define ZWLR_LAYER_SURFACE_V1_ACK_CONFIGURE 6 +#define ZWLR_LAYER_SURFACE_V1_DESTROY 7 + +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_CONFIGURE_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_CLOSED_SINCE_VERSION 1 + +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_SET_SIZE_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_SET_ANCHOR_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_SET_EXCLUSIVE_ZONE_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_SET_MARGIN_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_SET_KEYBOARD_INTERACTIVITY_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_GET_POPUP_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_ACK_CONFIGURE_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_DESTROY_SINCE_VERSION 1 + +/** @ingroup iface_zwlr_layer_surface_v1 */ +static inline void +zwlr_layer_surface_v1_set_user_data(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) zwlr_layer_surface_v1, user_data); +} + +/** @ingroup iface_zwlr_layer_surface_v1 */ +static inline void * +zwlr_layer_surface_v1_get_user_data(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1) +{ + return wl_proxy_get_user_data((struct wl_proxy *) zwlr_layer_surface_v1); +} + +static inline uint32_t +zwlr_layer_surface_v1_get_version(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1) +{ + return wl_proxy_get_version((struct wl_proxy *) zwlr_layer_surface_v1); +} + +/** + * @ingroup iface_zwlr_layer_surface_v1 + * + * Sets the size of the surface in surface-local coordinates. The + * compositor will display the surface centered with respect to its + * anchors. + * + * If you pass 0 for either value, the compositor will assign it and + * inform you of the assignment in the configure event. You must set your + * anchor to opposite edges in the dimensions you omit; not doing so is a + * protocol error. Both values are 0 by default. + * + * Size is double-buffered, see wl_surface.commit. + */ +static inline void +zwlr_layer_surface_v1_set_size(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, uint32_t width, uint32_t height) +{ + wl_proxy_marshal((struct wl_proxy *) zwlr_layer_surface_v1, + ZWLR_LAYER_SURFACE_V1_SET_SIZE, width, height); +} + +/** + * @ingroup iface_zwlr_layer_surface_v1 + * + * Requests that the compositor anchor the surface to the specified edges + * and corners. If two orthoginal edges are specified (e.g. 'top' and + * 'left'), then the anchor point will be the intersection of the edges + * (e.g. the top left corner of the output); otherwise the anchor point + * will be centered on that edge, or in the center if none is specified. + * + * Anchor is double-buffered, see wl_surface.commit. + */ +static inline void +zwlr_layer_surface_v1_set_anchor(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, uint32_t anchor) +{ + wl_proxy_marshal((struct wl_proxy *) zwlr_layer_surface_v1, + ZWLR_LAYER_SURFACE_V1_SET_ANCHOR, anchor); +} + +/** + * @ingroup iface_zwlr_layer_surface_v1 + * + * Requests that the compositor avoids occluding an area of the surface + * with other surfaces. The compositor's use of this information is + * implementation-dependent - do not assume that this region will not + * actually be occluded. + * + * A positive value is only meaningful if the surface is anchored to an + * edge, rather than a corner. The zone is the number of surface-local + * coordinates from the edge that are considered exclusive. + * + * Surfaces that do not wish to have an exclusive zone may instead specify + * how they should interact with surfaces that do. If set to zero, the + * surface indicates that it would like to be moved to avoid occluding + * surfaces with a positive excluzive zone. If set to -1, the surface + * indicates that it would not like to be moved to accomodate for other + * surfaces, and the compositor should extend it all the way to the edges + * it is anchored to. + * + * For example, a panel might set its exclusive zone to 10, so that + * maximized shell surfaces are not shown on top of it. A notification + * might set its exclusive zone to 0, so that it is moved to avoid + * occluding the panel, but shell surfaces are shown underneath it. A + * wallpaper or lock screen might set their exclusive zone to -1, so that + * they stretch below or over the panel. + * + * The default value is 0. + * + * Exclusive zone is double-buffered, see wl_surface.commit. + */ +static inline void +zwlr_layer_surface_v1_set_exclusive_zone(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, int32_t zone) +{ + wl_proxy_marshal((struct wl_proxy *) zwlr_layer_surface_v1, + ZWLR_LAYER_SURFACE_V1_SET_EXCLUSIVE_ZONE, zone); +} + +/** + * @ingroup iface_zwlr_layer_surface_v1 + * + * Requests that the surface be placed some distance away from the anchor + * point on the output, in surface-local coordinates. Setting this value + * for edges you are not anchored to has no effect. + * + * The exclusive zone includes the margin. + * + * Margin is double-buffered, see wl_surface.commit. + */ +static inline void +zwlr_layer_surface_v1_set_margin(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, int32_t top, int32_t right, int32_t bottom, int32_t left) +{ + wl_proxy_marshal((struct wl_proxy *) zwlr_layer_surface_v1, + ZWLR_LAYER_SURFACE_V1_SET_MARGIN, top, right, bottom, left); +} + +/** + * @ingroup iface_zwlr_layer_surface_v1 + * + * Set to 1 to request that the seat send keyboard events to this layer + * surface. For layers below the shell surface layer, the seat will use + * normal focus semantics. For layers above the shell surface layers, the + * seat will always give exclusive keyboard focus to the top-most layer + * which has keyboard interactivity set to true. + * + * Layer surfaces receive pointer, touch, and tablet events normally. If + * you do not want to receive them, set the input region on your surface + * to an empty region. + * + * Events is double-buffered, see wl_surface.commit. + */ +static inline void +zwlr_layer_surface_v1_set_keyboard_interactivity(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, uint32_t keyboard_interactivity) +{ + wl_proxy_marshal((struct wl_proxy *) zwlr_layer_surface_v1, + ZWLR_LAYER_SURFACE_V1_SET_KEYBOARD_INTERACTIVITY, keyboard_interactivity); +} + +/** + * @ingroup iface_zwlr_layer_surface_v1 + * + * This assigns an xdg_popup's parent to this layer_surface. This popup + * should have been created via xdg_surface::get_popup with the parent set + * to NULL, and this request must be invoked before committing the popup's + * initial state. + * + * See the documentation of xdg_popup for more details about what an + * xdg_popup is and how it is used. + */ +static inline void +zwlr_layer_surface_v1_get_popup(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, struct xdg_popup *popup) +{ + wl_proxy_marshal((struct wl_proxy *) zwlr_layer_surface_v1, + ZWLR_LAYER_SURFACE_V1_GET_POPUP, popup); +} + +/** + * @ingroup iface_zwlr_layer_surface_v1 + * + * When a configure event is received, if a client commits the + * surface in response to the configure event, then the client + * must make an ack_configure request sometime before the commit + * request, passing along the serial of the configure event. + * + * If the client receives multiple configure events before it + * can respond to one, it only has to ack the last configure event. + * + * A client is not required to commit immediately after sending + * an ack_configure request - it may even ack_configure several times + * before its next surface commit. + * + * A client may send multiple ack_configure requests before committing, but + * only the last request sent before a commit indicates which configure + * event the client really is responding to. + */ +static inline void +zwlr_layer_surface_v1_ack_configure(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, uint32_t serial) +{ + wl_proxy_marshal((struct wl_proxy *) zwlr_layer_surface_v1, + ZWLR_LAYER_SURFACE_V1_ACK_CONFIGURE, serial); +} + +/** + * @ingroup iface_zwlr_layer_surface_v1 + * + * This request destroys the layer surface. + */ +static inline void +zwlr_layer_surface_v1_destroy(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1) +{ + wl_proxy_marshal((struct wl_proxy *) zwlr_layer_surface_v1, + ZWLR_LAYER_SURFACE_V1_DESTROY); + + wl_proxy_destroy((struct wl_proxy *) zwlr_layer_surface_v1); +} + +#ifdef __cplusplus +} +#endif + +#endif diff -Nru dunst-1.5.0/src/wayland/protocols/wlr-layer-shell-unstable-v1.h dunst-1.8.1/src/wayland/protocols/wlr-layer-shell-unstable-v1.h --- dunst-1.5.0/src/wayland/protocols/wlr-layer-shell-unstable-v1.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/protocols/wlr-layer-shell-unstable-v1.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,91 @@ +/* Generated by wayland-scanner 1.19.0 */ + +/* + * Copyright © 2017 Drew DeVault + * + * Permission to use, copy, modify, distribute, and sell this + * software and its documentation for any purpose is hereby granted + * without fee, provided that the above copyright notice appear in + * all copies and that both that copyright notice and this permission + * notice appear in supporting documentation, and that the name of + * the copyright holders not be used in advertising or publicity + * pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied + * warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + * THIS SOFTWARE. + */ + +#include <stdlib.h> +#include <stdint.h> +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_output_interface; +extern const struct wl_interface wl_surface_interface; +extern const struct wl_interface xdg_popup_interface; +extern const struct wl_interface zwlr_layer_surface_v1_interface; + +static const struct wl_interface *wlr_layer_shell_unstable_v1_types[] = { + NULL, + NULL, + NULL, + NULL, + &zwlr_layer_surface_v1_interface, + &wl_surface_interface, + &wl_output_interface, + NULL, + NULL, + &xdg_popup_interface, +}; + +static const struct wl_message zwlr_layer_shell_v1_requests[] = { + { "get_layer_surface", "no?ous", wlr_layer_shell_unstable_v1_types + 4 }, +}; + +WL_PRIVATE const struct wl_interface zwlr_layer_shell_v1_interface = { + "zwlr_layer_shell_v1", 1, + 1, zwlr_layer_shell_v1_requests, + 0, NULL, +}; + +static const struct wl_message zwlr_layer_surface_v1_requests[] = { + { "set_size", "uu", wlr_layer_shell_unstable_v1_types + 0 }, + { "set_anchor", "u", wlr_layer_shell_unstable_v1_types + 0 }, + { "set_exclusive_zone", "i", wlr_layer_shell_unstable_v1_types + 0 }, + { "set_margin", "iiii", wlr_layer_shell_unstable_v1_types + 0 }, + { "set_keyboard_interactivity", "u", wlr_layer_shell_unstable_v1_types + 0 }, + { "get_popup", "o", wlr_layer_shell_unstable_v1_types + 9 }, + { "ack_configure", "u", wlr_layer_shell_unstable_v1_types + 0 }, + { "destroy", "", wlr_layer_shell_unstable_v1_types + 0 }, +}; + +static const struct wl_message zwlr_layer_surface_v1_events[] = { + { "configure", "uuu", wlr_layer_shell_unstable_v1_types + 0 }, + { "closed", "", wlr_layer_shell_unstable_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface zwlr_layer_surface_v1_interface = { + "zwlr_layer_surface_v1", 1, + 8, zwlr_layer_surface_v1_requests, + 2, zwlr_layer_surface_v1_events, +}; + diff -Nru dunst-1.5.0/src/wayland/protocols/wlr-layer-shell-unstable-v1.xml dunst-1.8.1/src/wayland/protocols/wlr-layer-shell-unstable-v1.xml --- dunst-1.5.0/src/wayland/protocols/wlr-layer-shell-unstable-v1.xml 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/protocols/wlr-layer-shell-unstable-v1.xml 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,285 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="wlr_layer_shell_unstable_v1"> + <copyright> + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + </copyright> + + <interface name="zwlr_layer_shell_v1" version="1"> + <description summary="create surfaces that are layers of the desktop"> + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + </description> + + <request name="get_layer_surface"> + <description summary="create a layer_surface from a surface"> + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + </description> + <arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/> + <arg name="surface" type="object" interface="wl_surface"/> + <arg name="output" type="object" interface="wl_output" allow-null="true"/> + <arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/> + <arg name="namespace" type="string" summary="namespace for the layer surface"/> + </request> + + <enum name="error"> + <entry name="role" value="0" summary="wl_surface has another role"/> + <entry name="invalid_layer" value="1" summary="layer value is invalid"/> + <entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/> + </enum> + + <enum name="layer"> + <description summary="available layers for surfaces"> + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + </description> + + <entry name="background" value="0"/> + <entry name="bottom" value="1"/> + <entry name="top" value="2"/> + <entry name="overlay" value="3"/> + </enum> + </interface> + + <interface name="zwlr_layer_surface_v1" version="1"> + <description summary="layer metadata interface"> + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (size, anchor, exclusive zone, margin, interactivity) + is double-buffered, and will be applied at the time wl_surface.commit of + the corresponding wl_surface is called. + </description> + + <request name="set_size"> + <description summary="sets the size of the surface"> + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + </description> + <arg name="width" type="uint"/> + <arg name="height" type="uint"/> + </request> + + <request name="set_anchor"> + <description summary="configures the anchor point of the surface"> + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthoginal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + </description> + <arg name="anchor" type="uint" enum="anchor"/> + </request> + + <request name="set_exclusive_zone"> + <description summary="configures the exclusive geometry of this surface"> + Requests that the compositor avoids occluding an area of the surface + with other surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to an + edge, rather than a corner. The zone is the number of surface-local + coordinates from the edge that are considered exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive excluzive zone. If set to -1, the surface + indicates that it would not like to be moved to accomodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + </description> + <arg name="zone" type="int"/> + </request> + + <request name="set_margin"> + <description summary="sets a margin from the anchor point"> + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + </description> + <arg name="top" type="int"/> + <arg name="right" type="int"/> + <arg name="bottom" type="int"/> + <arg name="left" type="int"/> + </request> + + <request name="set_keyboard_interactivity"> + <description summary="requests keyboard events"> + Set to 1 to request that the seat send keyboard events to this layer + surface. For layers below the shell surface layer, the seat will use + normal focus semantics. For layers above the shell surface layers, the + seat will always give exclusive keyboard focus to the top-most layer + which has keyboard interactivity set to true. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Events is double-buffered, see wl_surface.commit. + </description> + <arg name="keyboard_interactivity" type="uint"/> + </request> + + <request name="get_popup"> + <description summary="assign this layer_surface as an xdg_popup parent"> + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + </description> + <arg name="popup" type="object" interface="xdg_popup"/> + </request> + + <request name="ack_configure"> + <description summary="ack a configure event"> + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + </description> + <arg name="serial" type="uint" summary="the serial from the configure event"/> + </request> + + <request name="destroy" type="destructor"> + <description summary="destroy the layer_surface"> + This request destroys the layer surface. + </description> + </request> + + <event name="configure"> + <description summary="suggest a surface change"> + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + </description> + <arg name="serial" type="uint"/> + <arg name="width" type="uint"/> + <arg name="height" type="uint"/> + </event> + + <event name="closed"> + <description summary="surface should be closed"> + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + </description> + </event> + + <enum name="error"> + <entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/> + <entry name="invalid_size" value="1" summary="size is invalid"/> + <entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/> + </enum> + + <enum name="anchor" bitfield="true"> + <entry name="top" value="1" summary="the top edge of the anchor rectangle"/> + <entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/> + <entry name="left" value="4" summary="the left edge of the anchor rectangle"/> + <entry name="right" value="8" summary="the right edge of the anchor rectangle"/> + </enum> + </interface> +</protocol> diff -Nru dunst-1.5.0/src/wayland/protocols/xdg-output-unstable-v1-client-header.h dunst-1.8.1/src/wayland/protocols/xdg-output-unstable-v1-client-header.h --- dunst-1.5.0/src/wayland/protocols/xdg-output-unstable-v1-client-header.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/protocols/xdg-output-unstable-v1-client-header.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,415 @@ +/* Generated by wayland-scanner 1.19.0 */ + +#ifndef XDG_OUTPUT_UNSTABLE_V1_CLIENT_PROTOCOL_H +#define XDG_OUTPUT_UNSTABLE_V1_CLIENT_PROTOCOL_H + +#include <stdint.h> +#include <stddef.h> +#include "wayland-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @page page_xdg_output_unstable_v1 The xdg_output_unstable_v1 protocol + * Protocol to describe output regions + * + * @section page_desc_xdg_output_unstable_v1 Description + * + * This protocol aims at describing outputs in a way which is more in line + * with the concept of an output on desktop oriented systems. + * + * Some information are more specific to the concept of an output for + * a desktop oriented system and may not make sense in other applications, + * such as IVI systems for example. + * + * Typically, the global compositor space on a desktop system is made of + * a contiguous or overlapping set of rectangular regions. + * + * Some of the information provided in this protocol might be identical + * to their counterparts already available from wl_output, in which case + * the information provided by this protocol should be preferred to their + * equivalent in wl_output. The goal is to move the desktop specific + * concepts (such as output location within the global compositor space, + * the connector name and types, etc.) out of the core wl_output protocol. + * + * Warning! The protocol described in this file is experimental and + * backward incompatible changes may be made. Backward compatible + * changes may be added together with the corresponding interface + * version bump. + * Backward incompatible changes are done by bumping the version + * number in the protocol and interface names and resetting the + * interface version. Once the protocol is to be declared stable, + * the 'z' prefix and the version number in the protocol and + * interface names are removed and the interface version number is + * reset. + * + * @section page_ifaces_xdg_output_unstable_v1 Interfaces + * - @subpage page_iface_zxdg_output_manager_v1 - manage xdg_output objects + * - @subpage page_iface_zxdg_output_v1 - compositor logical output region + * @section page_copyright_xdg_output_unstable_v1 Copyright + * <pre> + * + * Copyright © 2017 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * </pre> + */ +struct wl_output; +struct zxdg_output_manager_v1; +struct zxdg_output_v1; + +#ifndef ZXDG_OUTPUT_MANAGER_V1_INTERFACE +#define ZXDG_OUTPUT_MANAGER_V1_INTERFACE +/** + * @page page_iface_zxdg_output_manager_v1 zxdg_output_manager_v1 + * @section page_iface_zxdg_output_manager_v1_desc Description + * + * A global factory interface for xdg_output objects. + * @section page_iface_zxdg_output_manager_v1_api API + * See @ref iface_zxdg_output_manager_v1. + */ +/** + * @defgroup iface_zxdg_output_manager_v1 The zxdg_output_manager_v1 interface + * + * A global factory interface for xdg_output objects. + */ +extern const struct wl_interface zxdg_output_manager_v1_interface; +#endif +#ifndef ZXDG_OUTPUT_V1_INTERFACE +#define ZXDG_OUTPUT_V1_INTERFACE +/** + * @page page_iface_zxdg_output_v1 zxdg_output_v1 + * @section page_iface_zxdg_output_v1_desc Description + * + * An xdg_output describes part of the compositor geometry. + * + * This typically corresponds to a monitor that displays part of the + * compositor space. + * + * For objects version 3 onwards, after all xdg_output properties have been + * sent (when the object is created and when properties are updated), a + * wl_output.done event is sent. This allows changes to the output + * properties to be seen as atomic, even if they happen via multiple events. + * @section page_iface_zxdg_output_v1_api API + * See @ref iface_zxdg_output_v1. + */ +/** + * @defgroup iface_zxdg_output_v1 The zxdg_output_v1 interface + * + * An xdg_output describes part of the compositor geometry. + * + * This typically corresponds to a monitor that displays part of the + * compositor space. + * + * For objects version 3 onwards, after all xdg_output properties have been + * sent (when the object is created and when properties are updated), a + * wl_output.done event is sent. This allows changes to the output + * properties to be seen as atomic, even if they happen via multiple events. + */ +extern const struct wl_interface zxdg_output_v1_interface; +#endif + +#define ZXDG_OUTPUT_MANAGER_V1_DESTROY 0 +#define ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT 1 + + +/** + * @ingroup iface_zxdg_output_manager_v1 + */ +#define ZXDG_OUTPUT_MANAGER_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_zxdg_output_manager_v1 + */ +#define ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT_SINCE_VERSION 1 + +/** @ingroup iface_zxdg_output_manager_v1 */ +static inline void +zxdg_output_manager_v1_set_user_data(struct zxdg_output_manager_v1 *zxdg_output_manager_v1, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) zxdg_output_manager_v1, user_data); +} + +/** @ingroup iface_zxdg_output_manager_v1 */ +static inline void * +zxdg_output_manager_v1_get_user_data(struct zxdg_output_manager_v1 *zxdg_output_manager_v1) +{ + return wl_proxy_get_user_data((struct wl_proxy *) zxdg_output_manager_v1); +} + +static inline uint32_t +zxdg_output_manager_v1_get_version(struct zxdg_output_manager_v1 *zxdg_output_manager_v1) +{ + return wl_proxy_get_version((struct wl_proxy *) zxdg_output_manager_v1); +} + +/** + * @ingroup iface_zxdg_output_manager_v1 + * + * Using this request a client can tell the server that it is not + * going to use the xdg_output_manager object anymore. + * + * Any objects already created through this instance are not affected. + */ +static inline void +zxdg_output_manager_v1_destroy(struct zxdg_output_manager_v1 *zxdg_output_manager_v1) +{ + wl_proxy_marshal((struct wl_proxy *) zxdg_output_manager_v1, + ZXDG_OUTPUT_MANAGER_V1_DESTROY); + + wl_proxy_destroy((struct wl_proxy *) zxdg_output_manager_v1); +} + +/** + * @ingroup iface_zxdg_output_manager_v1 + * + * This creates a new xdg_output object for the given wl_output. + */ +static inline struct zxdg_output_v1 * +zxdg_output_manager_v1_get_xdg_output(struct zxdg_output_manager_v1 *zxdg_output_manager_v1, struct wl_output *output) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_constructor((struct wl_proxy *) zxdg_output_manager_v1, + ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT, &zxdg_output_v1_interface, NULL, output); + + return (struct zxdg_output_v1 *) id; +} + +/** + * @ingroup iface_zxdg_output_v1 + * @struct zxdg_output_v1_listener + */ +struct zxdg_output_v1_listener { + /** + * position of the output within the global compositor space + * + * The position event describes the location of the wl_output + * within the global compositor space. + * + * The logical_position event is sent after creating an xdg_output + * (see xdg_output_manager.get_xdg_output) and whenever the + * location of the output changes within the global compositor + * space. + * @param x x position within the global compositor space + * @param y y position within the global compositor space + */ + void (*logical_position)(void *data, + struct zxdg_output_v1 *zxdg_output_v1, + int32_t x, + int32_t y); + /** + * size of the output in the global compositor space + * + * The logical_size event describes the size of the output in the + * global compositor space. + * + * For example, a surface without any buffer scale, transformation + * nor rotation set, with the size matching the logical_size will + * have the same size as the corresponding output when displayed. + * + * Most regular Wayland clients should not pay attention to the + * logical size and would rather rely on xdg_shell interfaces. + * + * Some clients such as Xwayland, however, need this to configure + * their surfaces in the global compositor space as the compositor + * may apply a different scale from what is advertised by the + * output scaling property (to achieve fractional scaling, for + * example). + * + * For example, for a wl_output mode 3840×2160 and a scale factor + * 2: + * + * - A compositor not scaling the surface buffers will advertise a + * logical size of 3840×2160, + * + * - A compositor automatically scaling the surface buffers will + * advertise a logical size of 1920×1080, + * + * - A compositor using a fractional scale of 1.5 will advertise a + * logical size of 2560×1440. + * + * For example, for a wl_output mode 1920×1080 and a 90 degree + * rotation, the compositor will advertise a logical size of + * 1080x1920. + * + * The logical_size event is sent after creating an xdg_output (see + * xdg_output_manager.get_xdg_output) and whenever the logical size + * of the output changes, either as a result of a change in the + * applied scale or because of a change in the corresponding output + * mode(see wl_output.mode) or transform (see wl_output.transform). + * @param width width in global compositor space + * @param height height in global compositor space + */ + void (*logical_size)(void *data, + struct zxdg_output_v1 *zxdg_output_v1, + int32_t width, + int32_t height); + /** + * all information about the output have been sent + * + * This event is sent after all other properties of an xdg_output + * have been sent. + * + * This allows changes to the xdg_output properties to be seen as + * atomic, even if they happen via multiple events. + * + * For objects version 3 onwards, this event is deprecated. + * Compositors are not required to send it anymore and must send + * wl_output.done instead. + */ + void (*done)(void *data, + struct zxdg_output_v1 *zxdg_output_v1); + /** + * name of this output + * + * Many compositors will assign names to their outputs, show them + * to the user, allow them to be configured by name, etc. The + * client may wish to know this name as well to offer the user + * similar behaviors. + * + * The naming convention is compositor defined, but limited to + * alphanumeric characters and dashes (-). Each name is unique + * among all wl_output globals, but if a wl_output global is + * destroyed the same name may be reused later. The names will also + * remain consistent across sessions with the same hardware and + * software configuration. + * + * Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. + * However, do not assume that the name is a reflection of an + * underlying DRM connector, X11 connection, etc. + * + * The name event is sent after creating an xdg_output (see + * xdg_output_manager.get_xdg_output). This event is only sent once + * per xdg_output, and the name does not change over the lifetime + * of the wl_output global. + * @param name output name + * @since 2 + */ + void (*name)(void *data, + struct zxdg_output_v1 *zxdg_output_v1, + const char *name); + /** + * human-readable description of this output + * + * Many compositors can produce human-readable descriptions of + * their outputs. The client may wish to know this description as + * well, to communicate the user for various purposes. + * + * The description is a UTF-8 string with no convention defined for + * its contents. Examples might include 'Foocorp 11" Display' or + * 'Virtual X11 output via :1'. + * + * The description event is sent after creating an xdg_output (see + * xdg_output_manager.get_xdg_output) and whenever the description + * changes. The description is optional, and may not be sent at + * all. + * + * For objects of version 2 and lower, this event is only sent once + * per xdg_output, and the description does not change over the + * lifetime of the wl_output global. + * @param description output description + * @since 2 + */ + void (*description)(void *data, + struct zxdg_output_v1 *zxdg_output_v1, + const char *description); +}; + +/** + * @ingroup iface_zxdg_output_v1 + */ +static inline int +zxdg_output_v1_add_listener(struct zxdg_output_v1 *zxdg_output_v1, + const struct zxdg_output_v1_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) zxdg_output_v1, + (void (**)(void)) listener, data); +} + +#define ZXDG_OUTPUT_V1_DESTROY 0 + +/** + * @ingroup iface_zxdg_output_v1 + */ +#define ZXDG_OUTPUT_V1_LOGICAL_POSITION_SINCE_VERSION 1 +/** + * @ingroup iface_zxdg_output_v1 + */ +#define ZXDG_OUTPUT_V1_LOGICAL_SIZE_SINCE_VERSION 1 +/** + * @ingroup iface_zxdg_output_v1 + */ +#define ZXDG_OUTPUT_V1_DONE_SINCE_VERSION 1 +/** + * @ingroup iface_zxdg_output_v1 + */ +#define ZXDG_OUTPUT_V1_NAME_SINCE_VERSION 2 +/** + * @ingroup iface_zxdg_output_v1 + */ +#define ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION 2 + +/** + * @ingroup iface_zxdg_output_v1 + */ +#define ZXDG_OUTPUT_V1_DESTROY_SINCE_VERSION 1 + +/** @ingroup iface_zxdg_output_v1 */ +static inline void +zxdg_output_v1_set_user_data(struct zxdg_output_v1 *zxdg_output_v1, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) zxdg_output_v1, user_data); +} + +/** @ingroup iface_zxdg_output_v1 */ +static inline void * +zxdg_output_v1_get_user_data(struct zxdg_output_v1 *zxdg_output_v1) +{ + return wl_proxy_get_user_data((struct wl_proxy *) zxdg_output_v1); +} + +static inline uint32_t +zxdg_output_v1_get_version(struct zxdg_output_v1 *zxdg_output_v1) +{ + return wl_proxy_get_version((struct wl_proxy *) zxdg_output_v1); +} + +/** + * @ingroup iface_zxdg_output_v1 + * + * Using this request a client can tell the server that it is not + * going to use the xdg_output object anymore. + */ +static inline void +zxdg_output_v1_destroy(struct zxdg_output_v1 *zxdg_output_v1) +{ + wl_proxy_marshal((struct wl_proxy *) zxdg_output_v1, + ZXDG_OUTPUT_V1_DESTROY); + + wl_proxy_destroy((struct wl_proxy *) zxdg_output_v1); +} + +#ifdef __cplusplus +} +#endif + +#endif diff -Nru dunst-1.5.0/src/wayland/protocols/xdg-output-unstable-v1.h dunst-1.8.1/src/wayland/protocols/xdg-output-unstable-v1.h --- dunst-1.5.0/src/wayland/protocols/xdg-output-unstable-v1.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/protocols/xdg-output-unstable-v1.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,78 @@ +/* Generated by wayland-scanner 1.19.0 */ + +/* + * Copyright © 2017 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <stdlib.h> +#include <stdint.h> +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_output_interface; +extern const struct wl_interface zxdg_output_v1_interface; + +static const struct wl_interface *xdg_output_unstable_v1_types[] = { + NULL, + NULL, + &zxdg_output_v1_interface, + &wl_output_interface, +}; + +static const struct wl_message zxdg_output_manager_v1_requests[] = { + { "destroy", "", xdg_output_unstable_v1_types + 0 }, + { "get_xdg_output", "no", xdg_output_unstable_v1_types + 2 }, +}; + +WL_PRIVATE const struct wl_interface zxdg_output_manager_v1_interface = { + "zxdg_output_manager_v1", 3, + 2, zxdg_output_manager_v1_requests, + 0, NULL, +}; + +static const struct wl_message zxdg_output_v1_requests[] = { + { "destroy", "", xdg_output_unstable_v1_types + 0 }, +}; + +static const struct wl_message zxdg_output_v1_events[] = { + { "logical_position", "ii", xdg_output_unstable_v1_types + 0 }, + { "logical_size", "ii", xdg_output_unstable_v1_types + 0 }, + { "done", "", xdg_output_unstable_v1_types + 0 }, + { "name", "2s", xdg_output_unstable_v1_types + 0 }, + { "description", "2s", xdg_output_unstable_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface zxdg_output_v1_interface = { + "zxdg_output_v1", 3, + 1, zxdg_output_v1_requests, + 5, zxdg_output_v1_events, +}; + diff -Nru dunst-1.5.0/src/wayland/protocols/xdg-shell-client-header.h dunst-1.8.1/src/wayland/protocols/xdg-shell-client-header.h --- dunst-1.5.0/src/wayland/protocols/xdg-shell-client-header.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/protocols/xdg-shell-client-header.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,2005 @@ +/* Generated by wayland-scanner 1.19.0 */ + +#ifndef XDG_SHELL_CLIENT_PROTOCOL_H +#define XDG_SHELL_CLIENT_PROTOCOL_H + +#include <stdint.h> +#include <stddef.h> +#include "wayland-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @page page_xdg_shell The xdg_shell protocol + * @section page_ifaces_xdg_shell Interfaces + * - @subpage page_iface_xdg_wm_base - create desktop-style surfaces + * - @subpage page_iface_xdg_positioner - child surface positioner + * - @subpage page_iface_xdg_surface - desktop user interface surface base interface + * - @subpage page_iface_xdg_toplevel - toplevel surface + * - @subpage page_iface_xdg_popup - short-lived, popup surfaces for menus + * @section page_copyright_xdg_shell Copyright + * <pre> + * + * Copyright © 2008-2013 Kristian Høgsberg + * Copyright © 2013 Rafael Antognolli + * Copyright © 2013 Jasper St. Pierre + * Copyright © 2010-2013 Intel Corporation + * Copyright © 2015-2017 Samsung Electronics Co., Ltd + * Copyright © 2015-2017 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * </pre> + */ +struct wl_output; +struct wl_seat; +struct wl_surface; +struct xdg_popup; +struct xdg_positioner; +struct xdg_surface; +struct xdg_toplevel; +struct xdg_wm_base; + +#ifndef XDG_WM_BASE_INTERFACE +#define XDG_WM_BASE_INTERFACE +/** + * @page page_iface_xdg_wm_base xdg_wm_base + * @section page_iface_xdg_wm_base_desc Description + * + * The xdg_wm_base interface is exposed as a global object enabling clients + * to turn their wl_surfaces into windows in a desktop environment. It + * defines the basic functionality needed for clients and the compositor to + * create windows that can be dragged, resized, maximized, etc, as well as + * creating transient windows such as popup menus. + * @section page_iface_xdg_wm_base_api API + * See @ref iface_xdg_wm_base. + */ +/** + * @defgroup iface_xdg_wm_base The xdg_wm_base interface + * + * The xdg_wm_base interface is exposed as a global object enabling clients + * to turn their wl_surfaces into windows in a desktop environment. It + * defines the basic functionality needed for clients and the compositor to + * create windows that can be dragged, resized, maximized, etc, as well as + * creating transient windows such as popup menus. + */ +extern const struct wl_interface xdg_wm_base_interface; +#endif +#ifndef XDG_POSITIONER_INTERFACE +#define XDG_POSITIONER_INTERFACE +/** + * @page page_iface_xdg_positioner xdg_positioner + * @section page_iface_xdg_positioner_desc Description + * + * The xdg_positioner provides a collection of rules for the placement of a + * child surface relative to a parent surface. Rules can be defined to ensure + * the child surface remains within the visible area's borders, and to + * specify how the child surface changes its position, such as sliding along + * an axis, or flipping around a rectangle. These positioner-created rules are + * constrained by the requirement that a child surface must intersect with or + * be at least partially adjacent to its parent surface. + * + * See the various requests for details about possible rules. + * + * At the time of the request, the compositor makes a copy of the rules + * specified by the xdg_positioner. Thus, after the request is complete the + * xdg_positioner object can be destroyed or reused; further changes to the + * object will have no effect on previous usages. + * + * For an xdg_positioner object to be considered complete, it must have a + * non-zero size set by set_size, and a non-zero anchor rectangle set by + * set_anchor_rect. Passing an incomplete xdg_positioner object when + * positioning a surface raises an error. + * @section page_iface_xdg_positioner_api API + * See @ref iface_xdg_positioner. + */ +/** + * @defgroup iface_xdg_positioner The xdg_positioner interface + * + * The xdg_positioner provides a collection of rules for the placement of a + * child surface relative to a parent surface. Rules can be defined to ensure + * the child surface remains within the visible area's borders, and to + * specify how the child surface changes its position, such as sliding along + * an axis, or flipping around a rectangle. These positioner-created rules are + * constrained by the requirement that a child surface must intersect with or + * be at least partially adjacent to its parent surface. + * + * See the various requests for details about possible rules. + * + * At the time of the request, the compositor makes a copy of the rules + * specified by the xdg_positioner. Thus, after the request is complete the + * xdg_positioner object can be destroyed or reused; further changes to the + * object will have no effect on previous usages. + * + * For an xdg_positioner object to be considered complete, it must have a + * non-zero size set by set_size, and a non-zero anchor rectangle set by + * set_anchor_rect. Passing an incomplete xdg_positioner object when + * positioning a surface raises an error. + */ +extern const struct wl_interface xdg_positioner_interface; +#endif +#ifndef XDG_SURFACE_INTERFACE +#define XDG_SURFACE_INTERFACE +/** + * @page page_iface_xdg_surface xdg_surface + * @section page_iface_xdg_surface_desc Description + * + * An interface that may be implemented by a wl_surface, for + * implementations that provide a desktop-style user interface. + * + * It provides a base set of functionality required to construct user + * interface elements requiring management by the compositor, such as + * toplevel windows, menus, etc. The types of functionality are split into + * xdg_surface roles. + * + * Creating an xdg_surface does not set the role for a wl_surface. In order + * to map an xdg_surface, the client must create a role-specific object + * using, e.g., get_toplevel, get_popup. The wl_surface for any given + * xdg_surface can have at most one role, and may not be assigned any role + * not based on xdg_surface. + * + * A role must be assigned before any other requests are made to the + * xdg_surface object. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_surface state to take effect. + * + * Creating an xdg_surface from a wl_surface which has a buffer attached or + * committed is a client error, and any attempts by a client to attach or + * manipulate a buffer prior to the first xdg_surface.configure call must + * also be treated as errors. + * + * After creating a role-specific object and setting it up, the client must + * perform an initial commit without any buffer attached. The compositor + * will reply with an xdg_surface.configure event. The client must + * acknowledge it and is then allowed to attach a buffer to map the surface. + * + * Mapping an xdg_surface-based role surface is defined as making it + * possible for the surface to be shown by the compositor. Note that + * a mapped surface is not guaranteed to be visible once it is mapped. + * + * For an xdg_surface to be mapped by the compositor, the following + * conditions must be met: + * (1) the client has assigned an xdg_surface-based role to the surface + * (2) the client has set and committed the xdg_surface state and the + * role-dependent state to the surface + * (3) the client has committed a buffer to the surface + * + * A newly-unmapped surface is considered to have met condition (1) out + * of the 3 required conditions for mapping a surface if its role surface + * has not been destroyed. + * @section page_iface_xdg_surface_api API + * See @ref iface_xdg_surface. + */ +/** + * @defgroup iface_xdg_surface The xdg_surface interface + * + * An interface that may be implemented by a wl_surface, for + * implementations that provide a desktop-style user interface. + * + * It provides a base set of functionality required to construct user + * interface elements requiring management by the compositor, such as + * toplevel windows, menus, etc. The types of functionality are split into + * xdg_surface roles. + * + * Creating an xdg_surface does not set the role for a wl_surface. In order + * to map an xdg_surface, the client must create a role-specific object + * using, e.g., get_toplevel, get_popup. The wl_surface for any given + * xdg_surface can have at most one role, and may not be assigned any role + * not based on xdg_surface. + * + * A role must be assigned before any other requests are made to the + * xdg_surface object. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_surface state to take effect. + * + * Creating an xdg_surface from a wl_surface which has a buffer attached or + * committed is a client error, and any attempts by a client to attach or + * manipulate a buffer prior to the first xdg_surface.configure call must + * also be treated as errors. + * + * After creating a role-specific object and setting it up, the client must + * perform an initial commit without any buffer attached. The compositor + * will reply with an xdg_surface.configure event. The client must + * acknowledge it and is then allowed to attach a buffer to map the surface. + * + * Mapping an xdg_surface-based role surface is defined as making it + * possible for the surface to be shown by the compositor. Note that + * a mapped surface is not guaranteed to be visible once it is mapped. + * + * For an xdg_surface to be mapped by the compositor, the following + * conditions must be met: + * (1) the client has assigned an xdg_surface-based role to the surface + * (2) the client has set and committed the xdg_surface state and the + * role-dependent state to the surface + * (3) the client has committed a buffer to the surface + * + * A newly-unmapped surface is considered to have met condition (1) out + * of the 3 required conditions for mapping a surface if its role surface + * has not been destroyed. + */ +extern const struct wl_interface xdg_surface_interface; +#endif +#ifndef XDG_TOPLEVEL_INTERFACE +#define XDG_TOPLEVEL_INTERFACE +/** + * @page page_iface_xdg_toplevel xdg_toplevel + * @section page_iface_xdg_toplevel_desc Description + * + * This interface defines an xdg_surface role which allows a surface to, + * among other things, set window-like properties such as maximize, + * fullscreen, and minimize, set application-specific metadata like title and + * id, and well as trigger user interactive operations such as interactive + * resize and move. + * + * Unmapping an xdg_toplevel means that the surface cannot be shown + * by the compositor until it is explicitly mapped again. + * All active operations (e.g., move, resize) are canceled and all + * attributes (e.g. title, state, stacking, ...) are discarded for + * an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to + * the state it had right after xdg_surface.get_toplevel. The client + * can re-map the toplevel by perfoming a commit without any buffer + * attached, waiting for a configure event and handling it as usual (see + * xdg_surface description). + * + * Attaching a null buffer to a toplevel unmaps the surface. + * @section page_iface_xdg_toplevel_api API + * See @ref iface_xdg_toplevel. + */ +/** + * @defgroup iface_xdg_toplevel The xdg_toplevel interface + * + * This interface defines an xdg_surface role which allows a surface to, + * among other things, set window-like properties such as maximize, + * fullscreen, and minimize, set application-specific metadata like title and + * id, and well as trigger user interactive operations such as interactive + * resize and move. + * + * Unmapping an xdg_toplevel means that the surface cannot be shown + * by the compositor until it is explicitly mapped again. + * All active operations (e.g., move, resize) are canceled and all + * attributes (e.g. title, state, stacking, ...) are discarded for + * an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to + * the state it had right after xdg_surface.get_toplevel. The client + * can re-map the toplevel by perfoming a commit without any buffer + * attached, waiting for a configure event and handling it as usual (see + * xdg_surface description). + * + * Attaching a null buffer to a toplevel unmaps the surface. + */ +extern const struct wl_interface xdg_toplevel_interface; +#endif +#ifndef XDG_POPUP_INTERFACE +#define XDG_POPUP_INTERFACE +/** + * @page page_iface_xdg_popup xdg_popup + * @section page_iface_xdg_popup_desc Description + * + * A popup surface is a short-lived, temporary surface. It can be used to + * implement for example menus, popovers, tooltips and other similar user + * interface concepts. + * + * A popup can be made to take an explicit grab. See xdg_popup.grab for + * details. + * + * When the popup is dismissed, a popup_done event will be sent out, and at + * the same time the surface will be unmapped. See the xdg_popup.popup_done + * event for details. + * + * Explicitly destroying the xdg_popup object will also dismiss the popup and + * unmap the surface. Clients that want to dismiss the popup when another + * surface of their own is clicked should dismiss the popup using the destroy + * request. + * + * A newly created xdg_popup will be stacked on top of all previously created + * xdg_popup surfaces associated with the same xdg_toplevel. + * + * The parent of an xdg_popup must be mapped (see the xdg_surface + * description) before the xdg_popup itself. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_popup state to take effect. + * @section page_iface_xdg_popup_api API + * See @ref iface_xdg_popup. + */ +/** + * @defgroup iface_xdg_popup The xdg_popup interface + * + * A popup surface is a short-lived, temporary surface. It can be used to + * implement for example menus, popovers, tooltips and other similar user + * interface concepts. + * + * A popup can be made to take an explicit grab. See xdg_popup.grab for + * details. + * + * When the popup is dismissed, a popup_done event will be sent out, and at + * the same time the surface will be unmapped. See the xdg_popup.popup_done + * event for details. + * + * Explicitly destroying the xdg_popup object will also dismiss the popup and + * unmap the surface. Clients that want to dismiss the popup when another + * surface of their own is clicked should dismiss the popup using the destroy + * request. + * + * A newly created xdg_popup will be stacked on top of all previously created + * xdg_popup surfaces associated with the same xdg_toplevel. + * + * The parent of an xdg_popup must be mapped (see the xdg_surface + * description) before the xdg_popup itself. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_popup state to take effect. + */ +extern const struct wl_interface xdg_popup_interface; +#endif + +#ifndef XDG_WM_BASE_ERROR_ENUM +#define XDG_WM_BASE_ERROR_ENUM +enum xdg_wm_base_error { + /** + * given wl_surface has another role + */ + XDG_WM_BASE_ERROR_ROLE = 0, + /** + * xdg_wm_base was destroyed before children + */ + XDG_WM_BASE_ERROR_DEFUNCT_SURFACES = 1, + /** + * the client tried to map or destroy a non-topmost popup + */ + XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP = 2, + /** + * the client specified an invalid popup parent surface + */ + XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT = 3, + /** + * the client provided an invalid surface state + */ + XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE = 4, + /** + * the client provided an invalid positioner + */ + XDG_WM_BASE_ERROR_INVALID_POSITIONER = 5, +}; +#endif /* XDG_WM_BASE_ERROR_ENUM */ + +/** + * @ingroup iface_xdg_wm_base + * @struct xdg_wm_base_listener + */ +struct xdg_wm_base_listener { + /** + * check if the client is alive + * + * The ping event asks the client if it's still alive. Pass the + * serial specified in the event back to the compositor by sending + * a "pong" request back with the specified serial. See + * xdg_wm_base.pong. + * + * Compositors can use this to determine if the client is still + * alive. It's unspecified what will happen if the client doesn't + * respond to the ping request, or in what timeframe. Clients + * should try to respond in a reasonable amount of time. + * + * A compositor is free to ping in any way it wants, but a client + * must always respond to any xdg_wm_base object it created. + * @param serial pass this to the pong request + */ + void (*ping)(void *data, + struct xdg_wm_base *xdg_wm_base, + uint32_t serial); +}; + +/** + * @ingroup iface_xdg_wm_base + */ +static inline int +xdg_wm_base_add_listener(struct xdg_wm_base *xdg_wm_base, + const struct xdg_wm_base_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) xdg_wm_base, + (void (**)(void)) listener, data); +} + +#define XDG_WM_BASE_DESTROY 0 +#define XDG_WM_BASE_CREATE_POSITIONER 1 +#define XDG_WM_BASE_GET_XDG_SURFACE 2 +#define XDG_WM_BASE_PONG 3 + +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_PING_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_CREATE_POSITIONER_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_GET_XDG_SURFACE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_PONG_SINCE_VERSION 1 + +/** @ingroup iface_xdg_wm_base */ +static inline void +xdg_wm_base_set_user_data(struct xdg_wm_base *xdg_wm_base, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) xdg_wm_base, user_data); +} + +/** @ingroup iface_xdg_wm_base */ +static inline void * +xdg_wm_base_get_user_data(struct xdg_wm_base *xdg_wm_base) +{ + return wl_proxy_get_user_data((struct wl_proxy *) xdg_wm_base); +} + +static inline uint32_t +xdg_wm_base_get_version(struct xdg_wm_base *xdg_wm_base) +{ + return wl_proxy_get_version((struct wl_proxy *) xdg_wm_base); +} + +/** + * @ingroup iface_xdg_wm_base + * + * Destroy this xdg_wm_base object. + * + * Destroying a bound xdg_wm_base object while there are surfaces + * still alive created by this xdg_wm_base object instance is illegal + * and will result in a protocol error. + */ +static inline void +xdg_wm_base_destroy(struct xdg_wm_base *xdg_wm_base) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_wm_base, + XDG_WM_BASE_DESTROY); + + wl_proxy_destroy((struct wl_proxy *) xdg_wm_base); +} + +/** + * @ingroup iface_xdg_wm_base + * + * Create a positioner object. A positioner object is used to position + * surfaces relative to some parent surface. See the interface description + * and xdg_surface.get_popup for details. + */ +static inline struct xdg_positioner * +xdg_wm_base_create_positioner(struct xdg_wm_base *xdg_wm_base) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_constructor((struct wl_proxy *) xdg_wm_base, + XDG_WM_BASE_CREATE_POSITIONER, &xdg_positioner_interface, NULL); + + return (struct xdg_positioner *) id; +} + +/** + * @ingroup iface_xdg_wm_base + * + * This creates an xdg_surface for the given surface. While xdg_surface + * itself is not a role, the corresponding surface may only be assigned + * a role extending xdg_surface, such as xdg_toplevel or xdg_popup. It is + * illegal to create an xdg_surface for a wl_surface which already has an + * assigned role and this will result in a protocol error. + * + * This creates an xdg_surface for the given surface. An xdg_surface is + * used as basis to define a role to a given surface, such as xdg_toplevel + * or xdg_popup. It also manages functionality shared between xdg_surface + * based surface roles. + * + * See the documentation of xdg_surface for more details about what an + * xdg_surface is and how it is used. + */ +static inline struct xdg_surface * +xdg_wm_base_get_xdg_surface(struct xdg_wm_base *xdg_wm_base, struct wl_surface *surface) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_constructor((struct wl_proxy *) xdg_wm_base, + XDG_WM_BASE_GET_XDG_SURFACE, &xdg_surface_interface, NULL, surface); + + return (struct xdg_surface *) id; +} + +/** + * @ingroup iface_xdg_wm_base + * + * A client must respond to a ping event with a pong request or + * the client may be deemed unresponsive. See xdg_wm_base.ping. + */ +static inline void +xdg_wm_base_pong(struct xdg_wm_base *xdg_wm_base, uint32_t serial) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_wm_base, + XDG_WM_BASE_PONG, serial); +} + +#ifndef XDG_POSITIONER_ERROR_ENUM +#define XDG_POSITIONER_ERROR_ENUM +enum xdg_positioner_error { + /** + * invalid input provided + */ + XDG_POSITIONER_ERROR_INVALID_INPUT = 0, +}; +#endif /* XDG_POSITIONER_ERROR_ENUM */ + +#ifndef XDG_POSITIONER_ANCHOR_ENUM +#define XDG_POSITIONER_ANCHOR_ENUM +enum xdg_positioner_anchor { + XDG_POSITIONER_ANCHOR_NONE = 0, + XDG_POSITIONER_ANCHOR_TOP = 1, + XDG_POSITIONER_ANCHOR_BOTTOM = 2, + XDG_POSITIONER_ANCHOR_LEFT = 3, + XDG_POSITIONER_ANCHOR_RIGHT = 4, + XDG_POSITIONER_ANCHOR_TOP_LEFT = 5, + XDG_POSITIONER_ANCHOR_BOTTOM_LEFT = 6, + XDG_POSITIONER_ANCHOR_TOP_RIGHT = 7, + XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT = 8, +}; +#endif /* XDG_POSITIONER_ANCHOR_ENUM */ + +#ifndef XDG_POSITIONER_GRAVITY_ENUM +#define XDG_POSITIONER_GRAVITY_ENUM +enum xdg_positioner_gravity { + XDG_POSITIONER_GRAVITY_NONE = 0, + XDG_POSITIONER_GRAVITY_TOP = 1, + XDG_POSITIONER_GRAVITY_BOTTOM = 2, + XDG_POSITIONER_GRAVITY_LEFT = 3, + XDG_POSITIONER_GRAVITY_RIGHT = 4, + XDG_POSITIONER_GRAVITY_TOP_LEFT = 5, + XDG_POSITIONER_GRAVITY_BOTTOM_LEFT = 6, + XDG_POSITIONER_GRAVITY_TOP_RIGHT = 7, + XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT = 8, +}; +#endif /* XDG_POSITIONER_GRAVITY_ENUM */ + +#ifndef XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM +#define XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM +/** + * @ingroup iface_xdg_positioner + * vertically resize the surface + * + * Resize the surface vertically so that it is completely unconstrained. + */ +enum xdg_positioner_constraint_adjustment { + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE = 0, + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X = 1, + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y = 2, + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X = 4, + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y = 8, + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X = 16, + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y = 32, +}; +#endif /* XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM */ + +#define XDG_POSITIONER_DESTROY 0 +#define XDG_POSITIONER_SET_SIZE 1 +#define XDG_POSITIONER_SET_ANCHOR_RECT 2 +#define XDG_POSITIONER_SET_ANCHOR 3 +#define XDG_POSITIONER_SET_GRAVITY 4 +#define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT 5 +#define XDG_POSITIONER_SET_OFFSET 6 +#define XDG_POSITIONER_SET_REACTIVE 7 +#define XDG_POSITIONER_SET_PARENT_SIZE 8 +#define XDG_POSITIONER_SET_PARENT_CONFIGURE 9 + + +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_SIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_ANCHOR_RECT_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_ANCHOR_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_GRAVITY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_OFFSET_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION 3 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_PARENT_SIZE_SINCE_VERSION 3 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_PARENT_CONFIGURE_SINCE_VERSION 3 + +/** @ingroup iface_xdg_positioner */ +static inline void +xdg_positioner_set_user_data(struct xdg_positioner *xdg_positioner, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) xdg_positioner, user_data); +} + +/** @ingroup iface_xdg_positioner */ +static inline void * +xdg_positioner_get_user_data(struct xdg_positioner *xdg_positioner) +{ + return wl_proxy_get_user_data((struct wl_proxy *) xdg_positioner); +} + +static inline uint32_t +xdg_positioner_get_version(struct xdg_positioner *xdg_positioner) +{ + return wl_proxy_get_version((struct wl_proxy *) xdg_positioner); +} + +/** + * @ingroup iface_xdg_positioner + * + * Notify the compositor that the xdg_positioner will no longer be used. + */ +static inline void +xdg_positioner_destroy(struct xdg_positioner *xdg_positioner) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_DESTROY); + + wl_proxy_destroy((struct wl_proxy *) xdg_positioner); +} + +/** + * @ingroup iface_xdg_positioner + * + * Set the size of the surface that is to be positioned with the positioner + * object. The size is in surface-local coordinates and corresponds to the + * window geometry. See xdg_surface.set_window_geometry. + * + * If a zero or negative size is set the invalid_input error is raised. + */ +static inline void +xdg_positioner_set_size(struct xdg_positioner *xdg_positioner, int32_t width, int32_t height) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_SIZE, width, height); +} + +/** + * @ingroup iface_xdg_positioner + * + * Specify the anchor rectangle within the parent surface that the child + * surface will be placed relative to. The rectangle is relative to the + * window geometry as defined by xdg_surface.set_window_geometry of the + * parent surface. + * + * When the xdg_positioner object is used to position a child surface, the + * anchor rectangle may not extend outside the window geometry of the + * positioned child's parent surface. + * + * If a negative size is set the invalid_input error is raised. + */ +static inline void +xdg_positioner_set_anchor_rect(struct xdg_positioner *xdg_positioner, int32_t x, int32_t y, int32_t width, int32_t height) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_ANCHOR_RECT, x, y, width, height); +} + +/** + * @ingroup iface_xdg_positioner + * + * Defines the anchor point for the anchor rectangle. The specified anchor + * is used derive an anchor point that the child surface will be + * positioned relative to. If a corner anchor is set (e.g. 'top_left' or + * 'bottom_right'), the anchor point will be at the specified corner; + * otherwise, the derived anchor point will be centered on the specified + * edge, or in the center of the anchor rectangle if no edge is specified. + */ +static inline void +xdg_positioner_set_anchor(struct xdg_positioner *xdg_positioner, uint32_t anchor) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_ANCHOR, anchor); +} + +/** + * @ingroup iface_xdg_positioner + * + * Defines in what direction a surface should be positioned, relative to + * the anchor point of the parent surface. If a corner gravity is + * specified (e.g. 'bottom_right' or 'top_left'), then the child surface + * will be placed towards the specified gravity; otherwise, the child + * surface will be centered over the anchor point on any axis that had no + * gravity specified. + */ +static inline void +xdg_positioner_set_gravity(struct xdg_positioner *xdg_positioner, uint32_t gravity) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_GRAVITY, gravity); +} + +/** + * @ingroup iface_xdg_positioner + * + * Specify how the window should be positioned if the originally intended + * position caused the surface to be constrained, meaning at least + * partially outside positioning boundaries set by the compositor. The + * adjustment is set by constructing a bitmask describing the adjustment to + * be made when the surface is constrained on that axis. + * + * If no bit for one axis is set, the compositor will assume that the child + * surface should not change its position on that axis when constrained. + * + * If more than one bit for one axis is set, the order of how adjustments + * are applied is specified in the corresponding adjustment descriptions. + * + * The default adjustment is none. + */ +static inline void +xdg_positioner_set_constraint_adjustment(struct xdg_positioner *xdg_positioner, uint32_t constraint_adjustment) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT, constraint_adjustment); +} + +/** + * @ingroup iface_xdg_positioner + * + * Specify the surface position offset relative to the position of the + * anchor on the anchor rectangle and the anchor on the surface. For + * example if the anchor of the anchor rectangle is at (x, y), the surface + * has the gravity bottom|right, and the offset is (ox, oy), the calculated + * surface position will be (x + ox, y + oy). The offset position of the + * surface is the one used for constraint testing. See + * set_constraint_adjustment. + * + * An example use case is placing a popup menu on top of a user interface + * element, while aligning the user interface element of the parent surface + * with some user interface element placed somewhere in the popup surface. + */ +static inline void +xdg_positioner_set_offset(struct xdg_positioner *xdg_positioner, int32_t x, int32_t y) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_OFFSET, x, y); +} + +/** + * @ingroup iface_xdg_positioner + * + * When set reactive, the surface is reconstrained if the conditions used + * for constraining changed, e.g. the parent window moved. + * + * If the conditions changed and the popup was reconstrained, an + * xdg_popup.configure event is sent with updated geometry, followed by an + * xdg_surface.configure event. + */ +static inline void +xdg_positioner_set_reactive(struct xdg_positioner *xdg_positioner) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_REACTIVE); +} + +/** + * @ingroup iface_xdg_positioner + * + * Set the parent window geometry the compositor should use when + * positioning the popup. The compositor may use this information to + * determine the future state the popup should be constrained using. If + * this doesn't match the dimension of the parent the popup is eventually + * positioned against, the behavior is undefined. + * + * The arguments are given in the surface-local coordinate space. + */ +static inline void +xdg_positioner_set_parent_size(struct xdg_positioner *xdg_positioner, int32_t parent_width, int32_t parent_height) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_PARENT_SIZE, parent_width, parent_height); +} + +/** + * @ingroup iface_xdg_positioner + * + * Set the serial of an xdg_surface.configure event this positioner will be + * used in response to. The compositor may use this information together + * with set_parent_size to determine what future state the popup should be + * constrained using. + */ +static inline void +xdg_positioner_set_parent_configure(struct xdg_positioner *xdg_positioner, uint32_t serial) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_PARENT_CONFIGURE, serial); +} + +#ifndef XDG_SURFACE_ERROR_ENUM +#define XDG_SURFACE_ERROR_ENUM +enum xdg_surface_error { + XDG_SURFACE_ERROR_NOT_CONSTRUCTED = 1, + XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED = 2, + XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER = 3, +}; +#endif /* XDG_SURFACE_ERROR_ENUM */ + +/** + * @ingroup iface_xdg_surface + * @struct xdg_surface_listener + */ +struct xdg_surface_listener { + /** + * suggest a surface change + * + * The configure event marks the end of a configure sequence. A + * configure sequence is a set of one or more events configuring + * the state of the xdg_surface, including the final + * xdg_surface.configure event. + * + * Where applicable, xdg_surface surface roles will during a + * configure sequence extend this event as a latched state sent as + * events before the xdg_surface.configure event. Such events + * should be considered to make up a set of atomically applied + * configuration states, where the xdg_surface.configure commits + * the accumulated state. + * + * Clients should arrange their surface for the new states, and + * then send an ack_configure request with the serial sent in this + * configure event at some point before committing the new surface. + * + * If the client receives multiple configure events before it can + * respond to one, it is free to discard all but the last event it + * received. + * @param serial serial of the configure event + */ + void (*configure)(void *data, + struct xdg_surface *xdg_surface, + uint32_t serial); +}; + +/** + * @ingroup iface_xdg_surface + */ +static inline int +xdg_surface_add_listener(struct xdg_surface *xdg_surface, + const struct xdg_surface_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) xdg_surface, + (void (**)(void)) listener, data); +} + +#define XDG_SURFACE_DESTROY 0 +#define XDG_SURFACE_GET_TOPLEVEL 1 +#define XDG_SURFACE_GET_POPUP 2 +#define XDG_SURFACE_SET_WINDOW_GEOMETRY 3 +#define XDG_SURFACE_ACK_CONFIGURE 4 + +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_CONFIGURE_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_GET_TOPLEVEL_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_GET_POPUP_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_SET_WINDOW_GEOMETRY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_ACK_CONFIGURE_SINCE_VERSION 1 + +/** @ingroup iface_xdg_surface */ +static inline void +xdg_surface_set_user_data(struct xdg_surface *xdg_surface, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) xdg_surface, user_data); +} + +/** @ingroup iface_xdg_surface */ +static inline void * +xdg_surface_get_user_data(struct xdg_surface *xdg_surface) +{ + return wl_proxy_get_user_data((struct wl_proxy *) xdg_surface); +} + +static inline uint32_t +xdg_surface_get_version(struct xdg_surface *xdg_surface) +{ + return wl_proxy_get_version((struct wl_proxy *) xdg_surface); +} + +/** + * @ingroup iface_xdg_surface + * + * Destroy the xdg_surface object. An xdg_surface must only be destroyed + * after its role object has been destroyed. + */ +static inline void +xdg_surface_destroy(struct xdg_surface *xdg_surface) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_surface, + XDG_SURFACE_DESTROY); + + wl_proxy_destroy((struct wl_proxy *) xdg_surface); +} + +/** + * @ingroup iface_xdg_surface + * + * This creates an xdg_toplevel object for the given xdg_surface and gives + * the associated wl_surface the xdg_toplevel role. + * + * See the documentation of xdg_toplevel for more details about what an + * xdg_toplevel is and how it is used. + */ +static inline struct xdg_toplevel * +xdg_surface_get_toplevel(struct xdg_surface *xdg_surface) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_constructor((struct wl_proxy *) xdg_surface, + XDG_SURFACE_GET_TOPLEVEL, &xdg_toplevel_interface, NULL); + + return (struct xdg_toplevel *) id; +} + +/** + * @ingroup iface_xdg_surface + * + * This creates an xdg_popup object for the given xdg_surface and gives + * the associated wl_surface the xdg_popup role. + * + * If null is passed as a parent, a parent surface must be specified using + * some other protocol, before committing the initial state. + * + * See the documentation of xdg_popup for more details about what an + * xdg_popup is and how it is used. + */ +static inline struct xdg_popup * +xdg_surface_get_popup(struct xdg_surface *xdg_surface, struct xdg_surface *parent, struct xdg_positioner *positioner) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_constructor((struct wl_proxy *) xdg_surface, + XDG_SURFACE_GET_POPUP, &xdg_popup_interface, NULL, parent, positioner); + + return (struct xdg_popup *) id; +} + +/** + * @ingroup iface_xdg_surface + * + * The window geometry of a surface is its "visible bounds" from the + * user's perspective. Client-side decorations often have invisible + * portions like drop-shadows which should be ignored for the + * purposes of aligning, placing and constraining windows. + * + * The window geometry is double buffered, and will be applied at the + * time wl_surface.commit of the corresponding wl_surface is called. + * + * When maintaining a position, the compositor should treat the (x, y) + * coordinate of the window geometry as the top left corner of the window. + * A client changing the (x, y) window geometry coordinate should in + * general not alter the position of the window. + * + * Once the window geometry of the surface is set, it is not possible to + * unset it, and it will remain the same until set_window_geometry is + * called again, even if a new subsurface or buffer is attached. + * + * If never set, the value is the full bounds of the surface, + * including any subsurfaces. This updates dynamically on every + * commit. This unset is meant for extremely simple clients. + * + * The arguments are given in the surface-local coordinate space of + * the wl_surface associated with this xdg_surface. + * + * The width and height must be greater than zero. Setting an invalid size + * will raise an error. When applied, the effective window geometry will be + * the set window geometry clamped to the bounding rectangle of the + * combined geometry of the surface of the xdg_surface and the associated + * subsurfaces. + */ +static inline void +xdg_surface_set_window_geometry(struct xdg_surface *xdg_surface, int32_t x, int32_t y, int32_t width, int32_t height) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_surface, + XDG_SURFACE_SET_WINDOW_GEOMETRY, x, y, width, height); +} + +/** + * @ingroup iface_xdg_surface + * + * When a configure event is received, if a client commits the + * surface in response to the configure event, then the client + * must make an ack_configure request sometime before the commit + * request, passing along the serial of the configure event. + * + * For instance, for toplevel surfaces the compositor might use this + * information to move a surface to the top left only when the client has + * drawn itself for the maximized or fullscreen state. + * + * If the client receives multiple configure events before it + * can respond to one, it only has to ack the last configure event. + * + * A client is not required to commit immediately after sending + * an ack_configure request - it may even ack_configure several times + * before its next surface commit. + * + * A client may send multiple ack_configure requests before committing, but + * only the last request sent before a commit indicates which configure + * event the client really is responding to. + */ +static inline void +xdg_surface_ack_configure(struct xdg_surface *xdg_surface, uint32_t serial) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_surface, + XDG_SURFACE_ACK_CONFIGURE, serial); +} + +#ifndef XDG_TOPLEVEL_RESIZE_EDGE_ENUM +#define XDG_TOPLEVEL_RESIZE_EDGE_ENUM +/** + * @ingroup iface_xdg_toplevel + * edge values for resizing + * + * These values are used to indicate which edge of a surface + * is being dragged in a resize operation. + */ +enum xdg_toplevel_resize_edge { + XDG_TOPLEVEL_RESIZE_EDGE_NONE = 0, + XDG_TOPLEVEL_RESIZE_EDGE_TOP = 1, + XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM = 2, + XDG_TOPLEVEL_RESIZE_EDGE_LEFT = 4, + XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT = 5, + XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT = 6, + XDG_TOPLEVEL_RESIZE_EDGE_RIGHT = 8, + XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT = 9, + XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT = 10, +}; +#endif /* XDG_TOPLEVEL_RESIZE_EDGE_ENUM */ + +#ifndef XDG_TOPLEVEL_STATE_ENUM +#define XDG_TOPLEVEL_STATE_ENUM +/** + * @ingroup iface_xdg_toplevel + * the surface is tiled + * + * The window is currently in a tiled layout and the bottom edge is + * considered to be adjacent to another part of the tiling grid. + */ +enum xdg_toplevel_state { + /** + * the surface is maximized + */ + XDG_TOPLEVEL_STATE_MAXIMIZED = 1, + /** + * the surface is fullscreen + */ + XDG_TOPLEVEL_STATE_FULLSCREEN = 2, + /** + * the surface is being resized + */ + XDG_TOPLEVEL_STATE_RESIZING = 3, + /** + * the surface is now activated + */ + XDG_TOPLEVEL_STATE_ACTIVATED = 4, + /** + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_LEFT = 5, + /** + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_RIGHT = 6, + /** + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_TOP = 7, + /** + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_BOTTOM = 8, +}; +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION 2 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION 2 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_TOP_SINCE_VERSION 2 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_BOTTOM_SINCE_VERSION 2 +#endif /* XDG_TOPLEVEL_STATE_ENUM */ + +/** + * @ingroup iface_xdg_toplevel + * @struct xdg_toplevel_listener + */ +struct xdg_toplevel_listener { + /** + * suggest a surface change + * + * This configure event asks the client to resize its toplevel + * surface or to change its state. The configured state should not + * be applied immediately. See xdg_surface.configure for details. + * + * The width and height arguments specify a hint to the window + * about how its surface should be resized in window geometry + * coordinates. See set_window_geometry. + * + * If the width or height arguments are zero, it means the client + * should decide its own window dimension. This may happen when the + * compositor needs to configure the state of the surface but + * doesn't have any information about any previous or expected + * dimension. + * + * The states listed in the event specify how the width/height + * arguments should be interpreted, and possibly how it should be + * drawn. + * + * Clients must send an ack_configure in response to this event. + * See xdg_surface.configure and xdg_surface.ack_configure for + * details. + */ + void (*configure)(void *data, + struct xdg_toplevel *xdg_toplevel, + int32_t width, + int32_t height, + struct wl_array *states); + /** + * surface wants to be closed + * + * The close event is sent by the compositor when the user wants + * the surface to be closed. This should be equivalent to the user + * clicking the close button in client-side decorations, if your + * application has any. + * + * This is only a request that the user intends to close the + * window. The client may choose to ignore this request, or show a + * dialog to ask the user to save their data, etc. + */ + void (*close)(void *data, + struct xdg_toplevel *xdg_toplevel); +}; + +/** + * @ingroup iface_xdg_toplevel + */ +static inline int +xdg_toplevel_add_listener(struct xdg_toplevel *xdg_toplevel, + const struct xdg_toplevel_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) xdg_toplevel, + (void (**)(void)) listener, data); +} + +#define XDG_TOPLEVEL_DESTROY 0 +#define XDG_TOPLEVEL_SET_PARENT 1 +#define XDG_TOPLEVEL_SET_TITLE 2 +#define XDG_TOPLEVEL_SET_APP_ID 3 +#define XDG_TOPLEVEL_SHOW_WINDOW_MENU 4 +#define XDG_TOPLEVEL_MOVE 5 +#define XDG_TOPLEVEL_RESIZE 6 +#define XDG_TOPLEVEL_SET_MAX_SIZE 7 +#define XDG_TOPLEVEL_SET_MIN_SIZE 8 +#define XDG_TOPLEVEL_SET_MAXIMIZED 9 +#define XDG_TOPLEVEL_UNSET_MAXIMIZED 10 +#define XDG_TOPLEVEL_SET_FULLSCREEN 11 +#define XDG_TOPLEVEL_UNSET_FULLSCREEN 12 +#define XDG_TOPLEVEL_SET_MINIMIZED 13 + +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_CONFIGURE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_CLOSE_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_PARENT_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_TITLE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_APP_ID_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SHOW_WINDOW_MENU_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_MOVE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_RESIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MAX_SIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MIN_SIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MAXIMIZED_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_UNSET_MAXIMIZED_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_FULLSCREEN_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_UNSET_FULLSCREEN_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MINIMIZED_SINCE_VERSION 1 + +/** @ingroup iface_xdg_toplevel */ +static inline void +xdg_toplevel_set_user_data(struct xdg_toplevel *xdg_toplevel, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) xdg_toplevel, user_data); +} + +/** @ingroup iface_xdg_toplevel */ +static inline void * +xdg_toplevel_get_user_data(struct xdg_toplevel *xdg_toplevel) +{ + return wl_proxy_get_user_data((struct wl_proxy *) xdg_toplevel); +} + +static inline uint32_t +xdg_toplevel_get_version(struct xdg_toplevel *xdg_toplevel) +{ + return wl_proxy_get_version((struct wl_proxy *) xdg_toplevel); +} + +/** + * @ingroup iface_xdg_toplevel + * + * This request destroys the role surface and unmaps the surface; + * see "Unmapping" behavior in interface section for details. + */ +static inline void +xdg_toplevel_destroy(struct xdg_toplevel *xdg_toplevel) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_DESTROY); + + wl_proxy_destroy((struct wl_proxy *) xdg_toplevel); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Set the "parent" of this surface. This surface should be stacked + * above the parent surface and all other ancestor surfaces. + * + * Parent windows should be set on dialogs, toolboxes, or other + * "auxiliary" surfaces, so that the parent is raised when the dialog + * is raised. + * + * Setting a null parent for a child window removes any parent-child + * relationship for the child. Setting a null parent for a window which + * currently has no parent is a no-op. + * + * If the parent is unmapped then its children are managed as + * though the parent of the now-unmapped parent has become the + * parent of this surface. If no parent exists for the now-unmapped + * parent then the children are managed as though they have no + * parent surface. + */ +static inline void +xdg_toplevel_set_parent(struct xdg_toplevel *xdg_toplevel, struct xdg_toplevel *parent) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_PARENT, parent); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Set a short title for the surface. + * + * This string may be used to identify the surface in a task bar, + * window list, or other user interface elements provided by the + * compositor. + * + * The string must be encoded in UTF-8. + */ +static inline void +xdg_toplevel_set_title(struct xdg_toplevel *xdg_toplevel, const char *title) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_TITLE, title); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Set an application identifier for the surface. + * + * The app ID identifies the general class of applications to which + * the surface belongs. The compositor can use this to group multiple + * surfaces together, or to determine how to launch a new application. + * + * For D-Bus activatable applications, the app ID is used as the D-Bus + * service name. + * + * The compositor shell will try to group application surfaces together + * by their app ID. As a best practice, it is suggested to select app + * ID's that match the basename of the application's .desktop file. + * For example, "org.freedesktop.FooViewer" where the .desktop file is + * "org.freedesktop.FooViewer.desktop". + * + * Like other properties, a set_app_id request can be sent after the + * xdg_toplevel has been mapped to update the property. + * + * See the desktop-entry specification [0] for more details on + * application identifiers and how they relate to well-known D-Bus + * names and .desktop files. + * + * [0] http://standards.freedesktop.org/desktop-entry-spec/ + */ +static inline void +xdg_toplevel_set_app_id(struct xdg_toplevel *xdg_toplevel, const char *app_id) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_APP_ID, app_id); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Clients implementing client-side decorations might want to show + * a context menu when right-clicking on the decorations, giving the + * user a menu that they can use to maximize or minimize the window. + * + * This request asks the compositor to pop up such a window menu at + * the given position, relative to the local surface coordinates of + * the parent surface. There are no guarantees as to what menu items + * the window menu contains. + * + * This request must be used in response to some sort of user action + * like a button press, key press, or touch down event. + */ +static inline void +xdg_toplevel_show_window_menu(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial, int32_t x, int32_t y) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SHOW_WINDOW_MENU, seat, serial, x, y); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Start an interactive, user-driven move of the surface. + * + * This request must be used in response to some sort of user action + * like a button press, key press, or touch down event. The passed + * serial is used to determine the type of interactive move (touch, + * pointer, etc). + * + * The server may ignore move requests depending on the state of + * the surface (e.g. fullscreen or maximized), or if the passed serial + * is no longer valid. + * + * If triggered, the surface will lose the focus of the device + * (wl_pointer, wl_touch, etc) used for the move. It is up to the + * compositor to visually indicate that the move is taking place, such as + * updating a pointer cursor, during the move. There is no guarantee + * that the device focus will return when the move is completed. + */ +static inline void +xdg_toplevel_move(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_MOVE, seat, serial); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Start a user-driven, interactive resize of the surface. + * + * This request must be used in response to some sort of user action + * like a button press, key press, or touch down event. The passed + * serial is used to determine the type of interactive resize (touch, + * pointer, etc). + * + * The server may ignore resize requests depending on the state of + * the surface (e.g. fullscreen or maximized). + * + * If triggered, the client will receive configure events with the + * "resize" state enum value and the expected sizes. See the "resize" + * enum value for more details about what is required. The client + * must also acknowledge configure events using "ack_configure". After + * the resize is completed, the client will receive another "configure" + * event without the resize state. + * + * If triggered, the surface also will lose the focus of the device + * (wl_pointer, wl_touch, etc) used for the resize. It is up to the + * compositor to visually indicate that the resize is taking place, + * such as updating a pointer cursor, during the resize. There is no + * guarantee that the device focus will return when the resize is + * completed. + * + * The edges parameter specifies how the surface should be resized, + * and is one of the values of the resize_edge enum. The compositor + * may use this information to update the surface position for + * example when dragging the top left corner. The compositor may also + * use this information to adapt its behavior, e.g. choose an + * appropriate cursor image. + */ +static inline void +xdg_toplevel_resize(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial, uint32_t edges) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_RESIZE, seat, serial, edges); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Set a maximum size for the window. + * + * The client can specify a maximum size so that the compositor does + * not try to configure the window beyond this size. + * + * The width and height arguments are in window geometry coordinates. + * See xdg_surface.set_window_geometry. + * + * Values set in this way are double-buffered. They will get applied + * on the next commit. + * + * The compositor can use this information to allow or disallow + * different states like maximize or fullscreen and draw accurate + * animations. + * + * Similarly, a tiling window manager may use this information to + * place and resize client windows in a more effective way. + * + * The client should not rely on the compositor to obey the maximum + * size. The compositor may decide to ignore the values set by the + * client and request a larger size. + * + * If never set, or a value of zero in the request, means that the + * client has no expected maximum size in the given dimension. + * As a result, a client wishing to reset the maximum size + * to an unspecified state can use zero for width and height in the + * request. + * + * Requesting a maximum size to be smaller than the minimum size of + * a surface is illegal and will result in a protocol error. + * + * The width and height must be greater than or equal to zero. Using + * strictly negative values for width and height will result in a + * protocol error. + */ +static inline void +xdg_toplevel_set_max_size(struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_MAX_SIZE, width, height); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Set a minimum size for the window. + * + * The client can specify a minimum size so that the compositor does + * not try to configure the window below this size. + * + * The width and height arguments are in window geometry coordinates. + * See xdg_surface.set_window_geometry. + * + * Values set in this way are double-buffered. They will get applied + * on the next commit. + * + * The compositor can use this information to allow or disallow + * different states like maximize or fullscreen and draw accurate + * animations. + * + * Similarly, a tiling window manager may use this information to + * place and resize client windows in a more effective way. + * + * The client should not rely on the compositor to obey the minimum + * size. The compositor may decide to ignore the values set by the + * client and request a smaller size. + * + * If never set, or a value of zero in the request, means that the + * client has no expected minimum size in the given dimension. + * As a result, a client wishing to reset the minimum size + * to an unspecified state can use zero for width and height in the + * request. + * + * Requesting a minimum size to be larger than the maximum size of + * a surface is illegal and will result in a protocol error. + * + * The width and height must be greater than or equal to zero. Using + * strictly negative values for width and height will result in a + * protocol error. + */ +static inline void +xdg_toplevel_set_min_size(struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_MIN_SIZE, width, height); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Maximize the surface. + * + * After requesting that the surface should be maximized, the compositor + * will respond by emitting a configure event. Whether this configure + * actually sets the window maximized is subject to compositor policies. + * The client must then update its content, drawing in the configured + * state. The client must also acknowledge the configure when committing + * the new content (see ack_configure). + * + * It is up to the compositor to decide how and where to maximize the + * surface, for example which output and what region of the screen should + * be used. + * + * If the surface was already maximized, the compositor will still emit + * a configure event with the "maximized" state. + * + * If the surface is in a fullscreen state, this request has no direct + * effect. It may alter the state the surface is returned to when + * unmaximized unless overridden by the compositor. + */ +static inline void +xdg_toplevel_set_maximized(struct xdg_toplevel *xdg_toplevel) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_MAXIMIZED); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Unmaximize the surface. + * + * After requesting that the surface should be unmaximized, the compositor + * will respond by emitting a configure event. Whether this actually + * un-maximizes the window is subject to compositor policies. + * If available and applicable, the compositor will include the window + * geometry dimensions the window had prior to being maximized in the + * configure event. The client must then update its content, drawing it in + * the configured state. The client must also acknowledge the configure + * when committing the new content (see ack_configure). + * + * It is up to the compositor to position the surface after it was + * unmaximized; usually the position the surface had before maximizing, if + * applicable. + * + * If the surface was already not maximized, the compositor will still + * emit a configure event without the "maximized" state. + * + * If the surface is in a fullscreen state, this request has no direct + * effect. It may alter the state the surface is returned to when + * unmaximized unless overridden by the compositor. + */ +static inline void +xdg_toplevel_unset_maximized(struct xdg_toplevel *xdg_toplevel) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_UNSET_MAXIMIZED); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Make the surface fullscreen. + * + * After requesting that the surface should be fullscreened, the + * compositor will respond by emitting a configure event. Whether the + * client is actually put into a fullscreen state is subject to compositor + * policies. The client must also acknowledge the configure when + * committing the new content (see ack_configure). + * + * The output passed by the request indicates the client's preference as + * to which display it should be set fullscreen on. If this value is NULL, + * it's up to the compositor to choose which display will be used to map + * this surface. + * + * If the surface doesn't cover the whole output, the compositor will + * position the surface in the center of the output and compensate with + * with border fill covering the rest of the output. The content of the + * border fill is undefined, but should be assumed to be in some way that + * attempts to blend into the surrounding area (e.g. solid black). + * + * If the fullscreened surface is not opaque, the compositor must make + * sure that other screen content not part of the same surface tree (made + * up of subsurfaces, popups or similarly coupled surfaces) are not + * visible below the fullscreened surface. + */ +static inline void +xdg_toplevel_set_fullscreen(struct xdg_toplevel *xdg_toplevel, struct wl_output *output) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_FULLSCREEN, output); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Make the surface no longer fullscreen. + * + * After requesting that the surface should be unfullscreened, the + * compositor will respond by emitting a configure event. + * Whether this actually removes the fullscreen state of the client is + * subject to compositor policies. + * + * Making a surface unfullscreen sets states for the surface based on the following: + * * the state(s) it may have had before becoming fullscreen + * * any state(s) decided by the compositor + * * any state(s) requested by the client while the surface was fullscreen + * + * The compositor may include the previous window geometry dimensions in + * the configure event, if applicable. + * + * The client must also acknowledge the configure when committing the new + * content (see ack_configure). + */ +static inline void +xdg_toplevel_unset_fullscreen(struct xdg_toplevel *xdg_toplevel) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_UNSET_FULLSCREEN); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Request that the compositor minimize your surface. There is no + * way to know if the surface is currently minimized, nor is there + * any way to unset minimization on this surface. + * + * If you are looking to throttle redrawing when minimized, please + * instead use the wl_surface.frame event for this, as this will + * also work with live previews on windows in Alt-Tab, Expose or + * similar compositor features. + */ +static inline void +xdg_toplevel_set_minimized(struct xdg_toplevel *xdg_toplevel) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_MINIMIZED); +} + +#ifndef XDG_POPUP_ERROR_ENUM +#define XDG_POPUP_ERROR_ENUM +enum xdg_popup_error { + /** + * tried to grab after being mapped + */ + XDG_POPUP_ERROR_INVALID_GRAB = 0, +}; +#endif /* XDG_POPUP_ERROR_ENUM */ + +/** + * @ingroup iface_xdg_popup + * @struct xdg_popup_listener + */ +struct xdg_popup_listener { + /** + * configure the popup surface + * + * This event asks the popup surface to configure itself given + * the configuration. The configured state should not be applied + * immediately. See xdg_surface.configure for details. + * + * The x and y arguments represent the position the popup was + * placed at given the xdg_positioner rule, relative to the upper + * left corner of the window geometry of the parent surface. + * + * For version 2 or older, the configure event for an xdg_popup is + * only ever sent once for the initial configuration. Starting with + * version 3, it may be sent again if the popup is setup with an + * xdg_positioner with set_reactive requested, or in response to + * xdg_popup.reposition requests. + * @param x x position relative to parent surface window geometry + * @param y y position relative to parent surface window geometry + * @param width window geometry width + * @param height window geometry height + */ + void (*configure)(void *data, + struct xdg_popup *xdg_popup, + int32_t x, + int32_t y, + int32_t width, + int32_t height); + /** + * popup interaction is done + * + * The popup_done event is sent out when a popup is dismissed by + * the compositor. The client should destroy the xdg_popup object + * at this point. + */ + void (*popup_done)(void *data, + struct xdg_popup *xdg_popup); + /** + * signal the completion of a repositioned request + * + * The repositioned event is sent as part of a popup + * configuration sequence, together with xdg_popup.configure and + * lastly xdg_surface.configure to notify the completion of a + * reposition request. + * + * The repositioned event is to notify about the completion of a + * xdg_popup.reposition request. The token argument is the token + * passed in the xdg_popup.reposition request. + * + * Immediately after this event is emitted, xdg_popup.configure and + * xdg_surface.configure will be sent with the updated size and + * position, as well as a new configure serial. + * + * The client should optionally update the content of the popup, + * but must acknowledge the new popup configuration for the new + * position to take effect. See xdg_surface.ack_configure for + * details. + * @param token reposition request token + * @since 3 + */ + void (*repositioned)(void *data, + struct xdg_popup *xdg_popup, + uint32_t token); +}; + +/** + * @ingroup iface_xdg_popup + */ +static inline int +xdg_popup_add_listener(struct xdg_popup *xdg_popup, + const struct xdg_popup_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) xdg_popup, + (void (**)(void)) listener, data); +} + +#define XDG_POPUP_DESTROY 0 +#define XDG_POPUP_GRAB 1 +#define XDG_POPUP_REPOSITION 2 + +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_CONFIGURE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_POPUP_DONE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_REPOSITIONED_SINCE_VERSION 3 + +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_GRAB_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_REPOSITION_SINCE_VERSION 3 + +/** @ingroup iface_xdg_popup */ +static inline void +xdg_popup_set_user_data(struct xdg_popup *xdg_popup, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) xdg_popup, user_data); +} + +/** @ingroup iface_xdg_popup */ +static inline void * +xdg_popup_get_user_data(struct xdg_popup *xdg_popup) +{ + return wl_proxy_get_user_data((struct wl_proxy *) xdg_popup); +} + +static inline uint32_t +xdg_popup_get_version(struct xdg_popup *xdg_popup) +{ + return wl_proxy_get_version((struct wl_proxy *) xdg_popup); +} + +/** + * @ingroup iface_xdg_popup + * + * This destroys the popup. Explicitly destroying the xdg_popup + * object will also dismiss the popup, and unmap the surface. + * + * If this xdg_popup is not the "topmost" popup, a protocol error + * will be sent. + */ +static inline void +xdg_popup_destroy(struct xdg_popup *xdg_popup) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_popup, + XDG_POPUP_DESTROY); + + wl_proxy_destroy((struct wl_proxy *) xdg_popup); +} + +/** + * @ingroup iface_xdg_popup + * + * This request makes the created popup take an explicit grab. An explicit + * grab will be dismissed when the user dismisses the popup, or when the + * client destroys the xdg_popup. This can be done by the user clicking + * outside the surface, using the keyboard, or even locking the screen + * through closing the lid or a timeout. + * + * If the compositor denies the grab, the popup will be immediately + * dismissed. + * + * This request must be used in response to some sort of user action like a + * button press, key press, or touch down event. The serial number of the + * event should be passed as 'serial'. + * + * The parent of a grabbing popup must either be an xdg_toplevel surface or + * another xdg_popup with an explicit grab. If the parent is another + * xdg_popup it means that the popups are nested, with this popup now being + * the topmost popup. + * + * Nested popups must be destroyed in the reverse order they were created + * in, e.g. the only popup you are allowed to destroy at all times is the + * topmost one. + * + * When compositors choose to dismiss a popup, they may dismiss every + * nested grabbing popup as well. When a compositor dismisses popups, it + * will follow the same dismissing order as required from the client. + * + * The parent of a grabbing popup must either be another xdg_popup with an + * active explicit grab, or an xdg_popup or xdg_toplevel, if there are no + * explicit grabs already taken. + * + * If the topmost grabbing popup is destroyed, the grab will be returned to + * the parent of the popup, if that parent previously had an explicit grab. + * + * If the parent is a grabbing popup which has already been dismissed, this + * popup will be immediately dismissed. If the parent is a popup that did + * not take an explicit grab, an error will be raised. + * + * During a popup grab, the client owning the grab will receive pointer + * and touch events for all their surfaces as normal (similar to an + * "owner-events" grab in X11 parlance), while the top most grabbing popup + * will always have keyboard focus. + */ +static inline void +xdg_popup_grab(struct xdg_popup *xdg_popup, struct wl_seat *seat, uint32_t serial) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_popup, + XDG_POPUP_GRAB, seat, serial); +} + +/** + * @ingroup iface_xdg_popup + * + * Reposition an already-mapped popup. The popup will be placed given the + * details in the passed xdg_positioner object, and a + * xdg_popup.repositioned followed by xdg_popup.configure and + * xdg_surface.configure will be emitted in response. Any parameters set + * by the previous positioner will be discarded. + * + * The passed token will be sent in the corresponding + * xdg_popup.repositioned event. The new popup position will not take + * effect until the corresponding configure event is acknowledged by the + * client. See xdg_popup.repositioned for details. The token itself is + * opaque, and has no other special meaning. + * + * If multiple reposition requests are sent, the compositor may skip all + * but the last one. + * + * If the popup is repositioned in response to a configure event for its + * parent, the client should send an xdg_positioner.set_parent_configure + * and possibly an xdg_positioner.set_parent_size request to allow the + * compositor to properly constrain the popup. + * + * If the popup is repositioned together with a parent that is being + * resized, but not in response to a configure event, the client should + * send an xdg_positioner.set_parent_size request. + */ +static inline void +xdg_popup_reposition(struct xdg_popup *xdg_popup, struct xdg_positioner *positioner, uint32_t token) +{ + wl_proxy_marshal((struct wl_proxy *) xdg_popup, + XDG_POPUP_REPOSITION, positioner, token); +} + +#ifdef __cplusplus +} +#endif + +#endif diff -Nru dunst-1.5.0/src/wayland/protocols/xdg-shell.h dunst-1.8.1/src/wayland/protocols/xdg-shell.h --- dunst-1.5.0/src/wayland/protocols/xdg-shell.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/protocols/xdg-shell.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,181 @@ +/* Generated by wayland-scanner 1.19.0 */ + +/* + * Copyright © 2008-2013 Kristian Høgsberg + * Copyright © 2013 Rafael Antognolli + * Copyright © 2013 Jasper St. Pierre + * Copyright © 2010-2013 Intel Corporation + * Copyright © 2015-2017 Samsung Electronics Co., Ltd + * Copyright © 2015-2017 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <stdlib.h> +#include <stdint.h> +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_output_interface; +extern const struct wl_interface wl_seat_interface; +extern const struct wl_interface wl_surface_interface; +extern const struct wl_interface xdg_popup_interface; +extern const struct wl_interface xdg_positioner_interface; +extern const struct wl_interface xdg_surface_interface; +extern const struct wl_interface xdg_toplevel_interface; + +static const struct wl_interface *xdg_shell_types[] = { + NULL, + NULL, + NULL, + NULL, + &xdg_positioner_interface, + &xdg_surface_interface, + &wl_surface_interface, + &xdg_toplevel_interface, + &xdg_popup_interface, + &xdg_surface_interface, + &xdg_positioner_interface, + &xdg_toplevel_interface, + &wl_seat_interface, + NULL, + NULL, + NULL, + &wl_seat_interface, + NULL, + &wl_seat_interface, + NULL, + NULL, + &wl_output_interface, + &wl_seat_interface, + NULL, + &xdg_positioner_interface, + NULL, +}; + +static const struct wl_message xdg_wm_base_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "create_positioner", "n", xdg_shell_types + 4 }, + { "get_xdg_surface", "no", xdg_shell_types + 5 }, + { "pong", "u", xdg_shell_types + 0 }, +}; + +static const struct wl_message xdg_wm_base_events[] = { + { "ping", "u", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_wm_base_interface = { + "xdg_wm_base", 3, + 4, xdg_wm_base_requests, + 1, xdg_wm_base_events, +}; + +static const struct wl_message xdg_positioner_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "set_size", "ii", xdg_shell_types + 0 }, + { "set_anchor_rect", "iiii", xdg_shell_types + 0 }, + { "set_anchor", "u", xdg_shell_types + 0 }, + { "set_gravity", "u", xdg_shell_types + 0 }, + { "set_constraint_adjustment", "u", xdg_shell_types + 0 }, + { "set_offset", "ii", xdg_shell_types + 0 }, + { "set_reactive", "3", xdg_shell_types + 0 }, + { "set_parent_size", "3ii", xdg_shell_types + 0 }, + { "set_parent_configure", "3u", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_positioner_interface = { + "xdg_positioner", 3, + 10, xdg_positioner_requests, + 0, NULL, +}; + +static const struct wl_message xdg_surface_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "get_toplevel", "n", xdg_shell_types + 7 }, + { "get_popup", "n?oo", xdg_shell_types + 8 }, + { "set_window_geometry", "iiii", xdg_shell_types + 0 }, + { "ack_configure", "u", xdg_shell_types + 0 }, +}; + +static const struct wl_message xdg_surface_events[] = { + { "configure", "u", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_surface_interface = { + "xdg_surface", 3, + 5, xdg_surface_requests, + 1, xdg_surface_events, +}; + +static const struct wl_message xdg_toplevel_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "set_parent", "?o", xdg_shell_types + 11 }, + { "set_title", "s", xdg_shell_types + 0 }, + { "set_app_id", "s", xdg_shell_types + 0 }, + { "show_window_menu", "ouii", xdg_shell_types + 12 }, + { "move", "ou", xdg_shell_types + 16 }, + { "resize", "ouu", xdg_shell_types + 18 }, + { "set_max_size", "ii", xdg_shell_types + 0 }, + { "set_min_size", "ii", xdg_shell_types + 0 }, + { "set_maximized", "", xdg_shell_types + 0 }, + { "unset_maximized", "", xdg_shell_types + 0 }, + { "set_fullscreen", "?o", xdg_shell_types + 21 }, + { "unset_fullscreen", "", xdg_shell_types + 0 }, + { "set_minimized", "", xdg_shell_types + 0 }, +}; + +static const struct wl_message xdg_toplevel_events[] = { + { "configure", "iia", xdg_shell_types + 0 }, + { "close", "", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_toplevel_interface = { + "xdg_toplevel", 3, + 14, xdg_toplevel_requests, + 2, xdg_toplevel_events, +}; + +static const struct wl_message xdg_popup_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "grab", "ou", xdg_shell_types + 22 }, + { "reposition", "3ou", xdg_shell_types + 24 }, +}; + +static const struct wl_message xdg_popup_events[] = { + { "configure", "iiii", xdg_shell_types + 0 }, + { "popup_done", "", xdg_shell_types + 0 }, + { "repositioned", "3u", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_popup_interface = { + "xdg_popup", 3, + 3, xdg_popup_requests, + 3, xdg_popup_events, +}; + diff -Nru dunst-1.5.0/src/wayland/wl.c dunst-1.8.1/src/wayland/wl.c --- dunst-1.5.0/src/wayland/wl.c 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/wl.c 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,880 @@ +#define _POSIX_C_SOURCE 200112L +#include "wl.h" + +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdbool.h> +#include <wayland-client.h> +#include <wayland-client-protocol.h> +#include <wayland-util.h> +#include <wayland-cursor.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <time.h> +#include <unistd.h> +#include <linux/input-event-codes.h> +#include <string.h> +#include <glib.h> + +#include "protocols/xdg-shell-client-header.h" +#include "protocols/xdg-shell.h" +#include "protocols/wlr-layer-shell-unstable-v1-client-header.h" +#include "protocols/wlr-layer-shell-unstable-v1.h" +#include "protocols/wlr-foreign-toplevel-management-unstable-v1-client-header.h" +#include "protocols/wlr-foreign-toplevel-management-unstable-v1.h" +#include "protocols/idle-client-header.h" +#include "protocols/idle.h" +#include "pool-buffer.h" + + +#include "../log.h" +#include "../settings.h" +#include "../queues.h" +#include "../input.h" +#include "libgwater-wayland.h" +#include "foreign_toplevel.h" +#include "wl_output.h" + +#define MAX_TOUCHPOINTS 10 + +struct window_wl { + cairo_surface_t *c_surface; + cairo_t * c_ctx; +}; + +struct wl_ctx { + GWaterWaylandSource *esrc; + struct wl_display *display; // owned by esrc + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_shm *shm; + struct zwlr_layer_shell_v1 *layer_shell; + struct wl_seat *seat; + + struct wl_list outputs; + + struct wl_surface *surface; + struct dunst_output *surface_output; + struct zwlr_layer_surface_v1 *layer_surface; + struct dunst_output *layer_surface_output; + struct wl_callback *frame_callback; + struct org_kde_kwin_idle *idle_handler; + struct org_kde_kwin_idle_timeout *idle_timeout; + uint32_t toplevel_manager_name; + struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager; + bool configured; + bool dirty; + bool is_idle; + bool has_idle_monitor; + + struct { + struct wl_pointer *wl_pointer; + int32_t x, y; + } pointer; + + struct { + struct wl_touch *wl_touch; + struct { + int32_t x, y; + } pts[MAX_TOUCHPOINTS]; + } touch; + + struct dimensions cur_dim; + + int32_t width, height; + struct pool_buffer buffers[2]; + struct pool_buffer *current_buffer; + struct wl_cursor_theme *cursor_theme; + const struct wl_cursor_image *cursor_image; + struct wl_surface *cursor_surface; +}; + +struct wl_ctx ctx; + +static void noop() { + // This space intentionally left blank +} + +void set_dirty(); + +static void output_handle_geometry(void *data, struct wl_output *wl_output, + int32_t x, int32_t y, int32_t phy_width, int32_t phy_height, + int32_t subpixel, const char *make, const char *model, + int32_t transform) { + //TODO do something with the subpixel data + struct dunst_output *output = data; + output->subpixel = subpixel; +} +static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, + int32_t width, int32_t height, int32_t refresh) { + struct dunst_output *output = data; + output->width = width; + output->height = height; +} + +static void output_handle_scale(void *data, struct wl_output *wl_output, + int32_t factor) { + struct dunst_output *output = data; + output->scale = factor; + + wake_up(); +} + +static const struct wl_output_listener output_listener = { + .geometry = output_handle_geometry, + .mode = output_handle_mode, + .done = noop, + .scale = output_handle_scale, +}; + +static void create_output( struct wl_output *wl_output, uint32_t global_name) { + struct dunst_output *output = g_malloc0(sizeof(struct dunst_output)); + if (output == NULL) { + LOG_E("allocation failed"); + return; + } + static int number = 0; + LOG_I("New output found - id %i", number); + output->global_name = global_name; + output->wl_output = wl_output; + output->scale = 1; + output->fullscreen = false; + wl_list_insert(&ctx.outputs, &output->link); + + wl_output_set_user_data(wl_output, output); + wl_output_add_listener(wl_output, &output_listener, output); + number++; +} + +static void destroy_output(struct dunst_output *output) { + if (ctx.surface_output == output) { + ctx.surface_output = NULL; + } + if (ctx.layer_surface_output == output) { + ctx.layer_surface_output = NULL; + } + wl_list_remove(&output->link); + wl_output_destroy(output->wl_output); + free(output->name); + free(output); +} + +static void touch_handle_motion(void *data, struct wl_touch *wl_touch, + uint32_t time, int32_t id, + wl_fixed_t surface_x, wl_fixed_t surface_y) { + if (id >= MAX_TOUCHPOINTS) { + return; + } + ctx.touch.pts[id].x = wl_fixed_to_int(surface_x); + ctx.touch.pts[id].y = wl_fixed_to_int(surface_y); +} + +static void touch_handle_down(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, struct wl_surface *sfc, int32_t id, + wl_fixed_t surface_x, wl_fixed_t surface_y) { + if (id >= MAX_TOUCHPOINTS) { + return; + } + ctx.touch.pts[id].x = wl_fixed_to_int(surface_x); + ctx.touch.pts[id].y = wl_fixed_to_int(surface_y); +} + +static void touch_handle_up(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, int32_t id) { + if (id >= MAX_TOUCHPOINTS) { + return; + } + input_handle_click(BTN_TOUCH, false, + ctx.touch.pts[id].x, ctx.touch.pts[id].y); + +} + +static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y) { + // Change the mouse cursor to "left_ptr" + if (ctx.cursor_theme != NULL) { + wl_pointer_set_cursor(wl_pointer, serial, ctx.cursor_surface, ctx.cursor_image->hotspot_x, ctx.cursor_image->hotspot_y); + } +} + +static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, + uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { + ctx.pointer.x = wl_fixed_to_int(surface_x); + ctx.pointer.y = wl_fixed_to_int(surface_y); +} + +static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, uint32_t time, uint32_t button, + uint32_t button_state) { + input_handle_click(button, button_state, ctx.pointer.x, ctx.pointer.y); +} + +static const struct wl_pointer_listener pointer_listener = { + .enter = pointer_handle_enter, + .leave = noop, + .motion = pointer_handle_motion, + .button = pointer_handle_button, + .axis = noop, +}; + +static const struct wl_touch_listener touch_listener = { + .down = touch_handle_down, + .up = touch_handle_up, + .motion = touch_handle_motion, + .frame = noop, + .cancel = noop, +}; + +static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, + uint32_t capabilities) { + + if (ctx.pointer.wl_pointer != NULL) { + wl_pointer_release(ctx.pointer.wl_pointer); + ctx.pointer.wl_pointer = NULL; + } + if (capabilities & WL_SEAT_CAPABILITY_POINTER) { + ctx.pointer.wl_pointer = wl_seat_get_pointer(wl_seat); + wl_pointer_add_listener(ctx.pointer.wl_pointer, + &pointer_listener, ctx.seat); + } + if (ctx.touch.wl_touch != NULL) { + wl_touch_release(ctx.touch.wl_touch); + ctx.touch.wl_touch = NULL; + } + if (capabilities & WL_SEAT_CAPABILITY_TOUCH) { + ctx.touch.wl_touch = wl_seat_get_touch(wl_seat); + wl_touch_add_listener(ctx.touch.wl_touch, + &touch_listener, ctx.seat); + } +} + +static const struct wl_seat_listener seat_listener = { + .capabilities = seat_handle_capabilities, + .name = noop, +}; + + +static void surface_handle_enter(void *data, struct wl_surface *surface, + struct wl_output *wl_output) { + // Don't bother keeping a list of outputs, a layer surface can only be on + // one output a a time + ctx.surface_output = wl_output_get_user_data(wl_output); + set_dirty(); +} + +static void surface_handle_leave(void *data, struct wl_surface *surface, + struct wl_output *wl_output) { + ctx.surface_output = NULL; +} + +static const struct wl_surface_listener surface_listener = { + .enter = surface_handle_enter, + .leave = surface_handle_leave, +}; + + +static void schedule_frame_and_commit(); +static void send_frame(); + +static void layer_surface_handle_configure(void *data, + struct zwlr_layer_surface_v1 *surface, + uint32_t serial, uint32_t width, uint32_t height) { + ctx.configured = true; + ctx.width = width; + ctx.height = height; + + // not needed as it is set somewhere else + /* zwlr_layer_surface_v1_set_size(surface, width, height); */ + zwlr_layer_surface_v1_ack_configure(surface, serial); + send_frame(); +} + +static void layer_surface_handle_closed(void *data, + struct zwlr_layer_surface_v1 *surface) { + LOG_I("Destroying layer"); + if (ctx.layer_surface) + zwlr_layer_surface_v1_destroy(ctx.layer_surface); + ctx.layer_surface = NULL; + + if (ctx.surface) + wl_surface_destroy(ctx.surface); + ctx.surface = NULL; + + if (ctx.frame_callback) { + wl_callback_destroy(ctx.frame_callback); + ctx.frame_callback = NULL; + ctx.dirty = true; + } + + if (ctx.configured) { + ctx.configured = false; + ctx.width = ctx.height = 0; + ctx.dirty = true; + } + + if (ctx.dirty) { + schedule_frame_and_commit(); + } +} + +static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { + .configure = layer_surface_handle_configure, + .closed = layer_surface_handle_closed, +}; + + +static void idle_start (void *data, struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout) { + ctx.is_idle = true; + LOG_D("User went idle"); +} +static void idle_stop (void *data, struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout) { + ctx.is_idle = false; + LOG_D("User isn't idle anymore"); +} + +static const struct org_kde_kwin_idle_timeout_listener idle_timeout_listener = { + .idle = idle_start, + .resumed = idle_stop, +}; + +static void add_seat_to_idle_handler(struct wl_seat *seat) { + if (!ctx.idle_handler){ + return; + } + if (settings.idle_threshold > 0) { + uint32_t timeout_ms = settings.idle_threshold/1000; + ctx.idle_timeout = org_kde_kwin_idle_get_idle_timeout(ctx.idle_handler, seat, timeout_ms); + org_kde_kwin_idle_timeout_add_listener(ctx.idle_timeout, &idle_timeout_listener, 0); + ctx.has_idle_monitor = true; + } +} + +// Warning, can return NULL +static struct dunst_output *get_configured_output() { + int n = 0; + int target_monitor = settings.monitor; + + struct dunst_output *first_output = NULL, *configured_output = NULL, + *tmp_output = NULL; + wl_list_for_each(tmp_output, &ctx.outputs, link) { + if (n == 0) + first_output = tmp_output; + if (n == target_monitor) + configured_output = tmp_output; + n++; + } + + // There's only 1 output, so return that + if (n == 1) + return first_output; + + switch (settings.f_mode){ + case FOLLOW_NONE: ; // this semicolon is neccesary + if (!configured_output) { + LOG_W("Monitor %i doesn't exist, using focused monitor", settings.monitor); + } + return configured_output; + case FOLLOW_MOUSE: + // fallthrough + case FOLLOW_KEYBOARD: + // fallthrough + default: + return NULL; + } +} + +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + if (strcmp(interface, wl_compositor_interface.name) == 0) { + ctx.compositor = wl_registry_bind(registry, name, + &wl_compositor_interface, 4); + } else if (strcmp(interface, wl_shm_interface.name) == 0) { + ctx.shm = wl_registry_bind(registry, name, + &wl_shm_interface, 1); + } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { + ctx.layer_shell = wl_registry_bind(registry, name, + &zwlr_layer_shell_v1_interface, 1); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + ctx.seat = wl_registry_bind(registry, name, &wl_seat_interface, 3); + wl_seat_add_listener(ctx.seat, &seat_listener, ctx.seat); + add_seat_to_idle_handler(ctx.seat); + } else if (strcmp(interface, wl_output_interface.name) == 0) { + struct wl_output *output = + wl_registry_bind(registry, name, &wl_output_interface, 3); + create_output(output, name); + LOG_D("Binding to output %i", name); + } else if (strcmp(interface, org_kde_kwin_idle_interface.name) == 0 && + version >= ORG_KDE_KWIN_IDLE_TIMEOUT_IDLE_SINCE_VERSION) { + ctx.idle_handler = wl_registry_bind(registry, name, &org_kde_kwin_idle_interface, 1); + } else if (strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0 && + version >= ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN_SINCE_VERSION) { + LOG_D("Found toplevel manager %i", name); + ctx.toplevel_manager_name = name; + } +} + +static void handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) { + struct dunst_output *output, *tmp; + wl_list_for_each_safe(output, tmp, &ctx.outputs, link) { + if (output->global_name == name) { + destroy_output(output); + break; + } + } +} + +static const struct wl_registry_listener registry_listener = { + .global = handle_global, + .global_remove = handle_global_remove, +}; + +bool wl_init() { + wl_list_init(&ctx.outputs); + wl_list_init(&toplevel_list); + //wl_list_init(&ctx.seats); // TODO multi-seat support + + ctx.esrc = g_water_wayland_source_new(NULL, NULL); + ctx.display = g_water_wayland_source_get_display(ctx.esrc); +#if GLIB_CHECK_VERSION(2, 58, 0) + g_water_wayland_source_set_error_callback(ctx.esrc, G_SOURCE_FUNC(quit_signal), NULL, NULL); +#else + g_water_wayland_source_set_error_callback(ctx.esrc, (GSourceFunc)quit_signal, NULL, NULL); +#endif + + if (ctx.display == NULL) { + LOG_W("failed to create display"); + return false; + } + + ctx.toplevel_manager_name = UINT32_MAX; + + ctx.registry = wl_display_get_registry(ctx.display); + wl_registry_add_listener(ctx.registry, ®istry_listener, NULL); + wl_display_roundtrip(ctx.display); + if (ctx.toplevel_manager_name != UINT32_MAX) { + ctx.toplevel_manager = wl_registry_bind(ctx.registry, ctx.toplevel_manager_name, + &zwlr_foreign_toplevel_manager_v1_interface, + 2); + zwlr_foreign_toplevel_manager_v1_add_listener(ctx.toplevel_manager, + &toplevel_manager_impl, NULL); + } + wl_display_roundtrip(ctx.display); // load list of toplevels + wl_display_roundtrip(ctx.display); // load toplevel details + wl_display_flush(ctx.display); + + if (ctx.compositor == NULL) { + LOG_W("compositor doesn't support wl_compositor"); + return false; + } + if (ctx.shm == NULL) { + LOG_W("compositor doesn't support wl_shm"); + return false; + } + if (ctx.layer_shell == NULL) { + LOG_W("compositor doesn't support zwlr_layer_shell_v1"); + return false; + } + if (ctx.seat == NULL) { + LOG_W("no seat was found, so dunst cannot see input"); + } else { + if (ctx.idle_handler == NULL) { + LOG_I("compositor doesn't support org_kde_kwin_idle_interface"); + } + else if (ctx.idle_timeout == NULL && settings.idle_threshold > 0) { + // something went wrong in setting the timeout + LOG_W("couldn't set idle timeout"); + } + } + + if (ctx.toplevel_manager == NULL) { + LOG_W("compositor doesn't support zwlr_foreign_toplevel_v1. Fullscreen detection won't work"); + } + + // Set up the cursor. It needs a wl_surface with the cursor loaded into it. + // If one of these fail, dunst will work fine without the cursor being able to change. + const char *cursor_size_env = getenv("XCURSOR_SIZE"); + int cursor_size = 24; + if (cursor_size_env != NULL) { + errno = 0; + char *end; + int temp_size = (int)strtol(cursor_size_env, &end, 10); + if (errno == 0 && cursor_size_env[0] != 0 && end[0] == 0 && temp_size > 0) { + cursor_size = temp_size; + } else { + LOG_W("Error: XCURSOR_SIZE is invalid"); + } + } + const char *xcursor_theme = getenv("XCURSOR_THEME"); + ctx.cursor_theme = wl_cursor_theme_load(xcursor_theme, cursor_size, ctx.shm); + if (ctx.cursor_theme == NULL) { + LOG_W("couldn't find a cursor theme"); + return true; + } + struct wl_cursor *cursor = wl_cursor_theme_get_cursor(ctx.cursor_theme, "left_ptr"); + if (cursor == NULL) { + LOG_W("couldn't find cursor icon \"left_ptr\""); + wl_cursor_theme_destroy(ctx.cursor_theme); + // Set to NULL so it doesn't get free'd again + ctx.cursor_theme = NULL; + return true; + } + ctx.cursor_image = cursor->images[0]; + struct wl_buffer *cursor_buffer = wl_cursor_image_get_buffer(cursor->images[0]); + ctx.cursor_surface = wl_compositor_create_surface(ctx.compositor); + wl_surface_attach(ctx.cursor_surface, cursor_buffer, 0, 0); + wl_surface_commit(ctx.cursor_surface); + + return true; +} + +void wl_deinit() { + // We need to check if any of these are NULL, since the initialization + // could have been aborted half way through, or the compositor doesn't + // support some of these features. + if (ctx.layer_surface != NULL) { + zwlr_layer_surface_v1_destroy(ctx.layer_surface); + } + if (ctx.surface != NULL) { + wl_surface_destroy(ctx.surface); + } + finish_buffer(&ctx.buffers[0]); + finish_buffer(&ctx.buffers[1]); + + // The output list is initialized at the start of init, so no need to + // check for NULL + struct dunst_output *output, *output_tmp; + wl_list_for_each_safe(output, output_tmp, &ctx.outputs, link) { + destroy_output(output); + } + + if (ctx.seat) { + if (ctx.pointer.wl_pointer) + wl_pointer_release(ctx.pointer.wl_pointer); + wl_seat_release(ctx.seat); + ctx.seat = NULL; + } + + if (ctx.idle_handler) + org_kde_kwin_idle_destroy(ctx.idle_handler); + + if (ctx.idle_timeout) + org_kde_kwin_idle_timeout_release(ctx.idle_timeout); + + if (ctx.layer_shell) + zwlr_layer_shell_v1_destroy(ctx.layer_shell); + + if (ctx.compositor) + wl_compositor_destroy(ctx.compositor); + + if (ctx.shm) + wl_shm_destroy(ctx.shm); + + if (ctx.registry) + wl_registry_destroy(ctx.registry); + + if (ctx.cursor_theme != NULL) { + wl_cursor_theme_destroy(ctx.cursor_theme); + wl_surface_destroy(ctx.cursor_surface); + } + + // this also disconnects the wl_display + g_water_wayland_source_free(ctx.esrc); +} + +static void schedule_frame_and_commit(); + +// Draw and commit a new frame. +static void send_frame() { + int scale = wl_get_scale(); + + struct dunst_output *output = get_configured_output(); + int height = ctx.cur_dim.h; + int width = ctx.cur_dim.w; + + // There are two cases where we want to tear down the surface: zero + // notifications (height = 0) or moving between outputs. + if (height == 0 || ctx.layer_surface_output != output) { + if (ctx.layer_surface != NULL) { + zwlr_layer_surface_v1_destroy(ctx.layer_surface); + ctx.layer_surface = NULL; + } + if (ctx.surface != NULL) { + wl_surface_destroy(ctx.surface); + ctx.surface = NULL; + } + ctx.width = ctx.height = 0; + ctx.surface_output = NULL; + ctx.configured = false; + } + + { + struct dunst_output *o; + int i = 0; + wl_list_for_each(o, &ctx.outputs, link) { + i++; + } + if (i == 0) { + // There are no outputs, so don't create a surface + ctx.dirty = false; + return; + } + } + + // If there are no notifications, there's no point in recreating the + // surface right now. + if (height == 0) { + ctx.dirty = false; + return; + } + + // If we've made it here, there is something to draw. If the surface + // doesn't exist (this is the first notification, or we moved to a + // different output), we need to create it. + if (ctx.layer_surface == NULL) { + struct wl_output *wl_output = NULL; + if (output != NULL) { + wl_output = output->wl_output; + } + ctx.layer_surface_output = output; + ctx.surface = wl_compositor_create_surface(ctx.compositor); + wl_surface_add_listener(ctx.surface, &surface_listener, NULL); + + ctx.layer_surface = zwlr_layer_shell_v1_get_layer_surface( + ctx.layer_shell, ctx.surface, wl_output, + settings.layer, "notifications"); + zwlr_layer_surface_v1_add_listener(ctx.layer_surface, + &layer_surface_listener, NULL); + + // Because we're creating a new surface, we aren't going to draw + // anything into it during this call. We don't know what size the + // surface will be until we've asked the compositor for what we want + // and it has responded with what it actually gave us. We also know + // that the height we would _like_ to draw (greater than zero, or we + // would have bailed already) is different from our ctx.height + // (which has to be zero here), so we can fall through to the next + // block to let it set the size for us. + } + + assert(ctx.layer_surface); + + // We now want to resize the surface if it isn't the right size. If the + // surface is brand new, it doesn't even have a size yet. If it already + // exists, we might need to resize if the list of notifications has changed + // since the last time we drew. + if (ctx.height != height || ctx.width != width) { + struct dimensions dim = ctx.cur_dim; + // Set window size + zwlr_layer_surface_v1_set_size(ctx.layer_surface, + dim.w, dim.h); + + // Put the window at the right position + zwlr_layer_surface_v1_set_anchor(ctx.layer_surface, + settings.origin); + zwlr_layer_surface_v1_set_margin(ctx.layer_surface, + // Offsets where no anchors are specified are + // ignored. We can safely assume the offset is + // positive. + settings.offset.y, // top + settings.offset.x, // right + settings.offset.y, // bottom + settings.offset.x);// left + + wl_surface_commit(ctx.surface); + + // Now we're going to bail without drawing anything. This gives the + // compositor a chance to create the surface and tell us what size we + // were actually granted, which may be smaller than what we asked for + // depending on the screen size and layout of other layer surfaces. + // This information is provided in layer_surface_handle_configure, + // which will then call send_frame again. When that call happens, the + // layer surface will exist and the height will hopefully match what + // we asked for. That means we won't return here, and will actually + // draw into the surface down below. + // TODO: If the compositor doesn't send a configure with the size we + // requested, we'll enter an infinite loop. We need to keep track of + // the fact that a request was sent separately from what height we are. + wl_display_roundtrip(ctx.display); + return; + } + + assert(ctx.configured); + + // Yay we can finally draw something! + wl_surface_set_buffer_scale(ctx.surface, scale); + wl_surface_damage_buffer(ctx.surface, 0, 0, INT32_MAX, INT32_MAX); + wl_surface_attach(ctx.surface, ctx.current_buffer->buffer, 0, 0); + ctx.current_buffer->busy = true; + + // Schedule a frame in case the state becomes dirty again + schedule_frame_and_commit(); + + ctx.dirty = false; +} + +static void frame_handle_done(void *data, struct wl_callback *callback, + uint32_t time) { + wl_callback_destroy(ctx.frame_callback); + ctx.frame_callback = NULL; + + // Only draw again if we need to + if (ctx.dirty) { + send_frame(); + } +} + +static const struct wl_callback_listener frame_listener = { + .done = frame_handle_done, +}; + +static void schedule_frame_and_commit() { + if (ctx.frame_callback) { + return; + } + if (ctx.surface == NULL) { + // We don't yet have a surface, create it immediately + send_frame(); + return; + } + ctx.frame_callback = wl_surface_frame(ctx.surface); + wl_callback_add_listener(ctx.frame_callback, &frame_listener, NULL); + wl_surface_commit(ctx.surface); +} + +void set_dirty() { + if (ctx.dirty) { + return; + } + ctx.dirty = true; + schedule_frame_and_commit(); +} + +window wl_win_create(void) { + struct window_wl *win = g_malloc0(sizeof(struct window_wl)); + return win; +} + +void wl_win_destroy(window winptr) { + struct window_wl *win = (struct window_wl*)winptr; + // FIXME: Dealloc everything + g_free(win); +} + +void wl_win_show(window win) { + // This is here for compatibilty with the X11 output. The window is + // already shown in wl_display_surface. +} + +void wl_win_hide(window win) { + LOG_I("Wayland: Hiding window"); + ctx.cur_dim.h = 0; + set_dirty(); + wl_display_roundtrip(ctx.display); +} + +void wl_display_surface(cairo_surface_t *srf, window winptr, const struct dimensions* dim) { + /* struct window_wl *win = (struct window_wl*)winptr; */ + int scale = wl_get_scale(); + LOG_D("Buffer size (scaled) %ix%i", dim->w * scale, dim->h * scale); + ctx.current_buffer = get_next_buffer(ctx.shm, ctx.buffers, + dim->w * scale, dim->h * scale); + + cairo_t *c = ctx.current_buffer->cairo; + cairo_save(c); + cairo_set_source_surface(c, srf, 0, 0); + cairo_rectangle(c, 0, 0, dim->w * scale, dim->h * scale); + cairo_fill(c); + cairo_restore(c); + + ctx.cur_dim = *dim; + + set_dirty(); + wl_display_roundtrip(ctx.display); +} + +cairo_t* wl_win_get_context(window winptr) { + struct window_wl *win = (struct window_wl*)winptr; + ctx.current_buffer = get_next_buffer(ctx.shm, ctx.buffers, 500, 500); + win->c_surface = ctx.current_buffer->surface; + win->c_ctx = ctx.current_buffer->cairo; + return win->c_ctx; +} + +const struct screen_info* wl_get_active_screen(void) { + static struct screen_info scr = { + .w = 3840, + .h = 2160, + .x = 0, + .y = 0, + .id = 0, + .mmh = 500 + }; + scr.dpi = wl_get_scale() * 96; + + struct dunst_output *current_output = get_configured_output(); + if (current_output != NULL) { + scr.w = current_output->width; + scr.h = current_output->height; + return &scr; + } else { + // Using auto output. We don't know on which output we are + // (although we might find it out by looking at the list of + // toplevels). + return &scr; + } +} + +bool wl_is_idle(void) { + LOG_I("Idle status queried: %i", ctx.is_idle); + // When the user doesn't have a seat, or their compositor doesn't support the idle + // protocol, we'll assume that they are not idle. + if (settings.idle_threshold == 0 || ctx.has_idle_monitor == false) { + return false; + } else { + return ctx.is_idle; + } +} + +bool wl_have_fullscreen_window(void) { + struct dunst_output *current_output = get_configured_output(); + uint32_t output_name = UINT32_MAX; + if (current_output) + output_name = current_output->global_name; + + struct toplevel_v1 *toplevel; + wl_list_for_each(toplevel, &toplevel_list, link) { + if (!(toplevel->current.state & TOPLEVEL_STATE_FULLSCREEN && + toplevel->current.state & + TOPLEVEL_STATE_ACTIVATED)) + continue; + + struct toplevel_output *pos; + wl_list_for_each(pos, &toplevel->output_list, link) { + if (output_name == UINT32_MAX || + pos->dunst_output->global_name == + output_name) { + return true; + } + } + } + return false; +} + +double wl_get_scale(void) { + int scale = 0; + struct dunst_output *output = get_configured_output(); + if (output) { + scale = output->scale; + } else { + // return the largest scale + struct dunst_output *output; + wl_list_for_each(output, &ctx.outputs, link) { + scale = MAX(output->scale, scale); + } + } + if (scale <= 0) + scale = 1; + return scale; +} +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/wayland/wl.h dunst-1.8.1/src/wayland/wl.h --- dunst-1.5.0/src/wayland/wl.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/wl.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,32 @@ +#ifndef DUNST_WL_H +#define DUNST_WL_H + +#include <stdbool.h> +#include <cairo.h> +#include <glib.h> + +#include "../output.h" + +bool wl_init(void); +void wl_deinit(void); + +window wl_win_create(void); +void wl_win_destroy(window); + +void wl_win_show(window); +void wl_win_hide(window); + +void wl_display_surface(cairo_surface_t *srf, window win, const struct dimensions*); +cairo_t* wl_win_get_context(window); + +const struct screen_info* wl_get_active_screen(void); + +bool wl_is_idle(void); +bool wl_have_fullscreen_window(void); + +// Return the dpi scaling of the current output. Everything that's rendered +// should be multiplied by this value, but don't use it to multiply other +// values. All sizes should be in unscaled units. +double wl_get_scale(void); +#endif +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/wayland/wl_output.c dunst-1.8.1/src/wayland/wl_output.c --- dunst-1.5.0/src/wayland/wl_output.c 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/wl_output.c 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,3 @@ +#include "wl_output.h" + +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/wayland/wl_output.h dunst-1.8.1/src/wayland/wl_output.h --- dunst-1.5.0/src/wayland/wl_output.h 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/src/wayland/wl_output.h 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,21 @@ +#ifndef DUNST_WL_OUTPUT_H +#define DUNST_WL_OUTPUT_H +#include <stdint.h> +#include <stdbool.h> +#include <wayland-util.h> + +struct dunst_output { + uint32_t global_name; + char *name; + struct wl_output *wl_output; + struct wl_list link; + + uint32_t scale; + uint32_t subpixel; // TODO do something with it + int32_t width, height; + bool fullscreen; + struct zwlr_foreign_toplevel_handle_v1 *fullscreen_toplevel; // the toplevel that is fullscreened on this output +}; + +#endif +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/x11/screen.c dunst-1.8.1/src/x11/screen.c --- dunst-1.5.0/src/x11/screen.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/x11/screen.c 2022-03-02 10:55:25.000000000 +0000 @@ -69,12 +69,12 @@ return screen_dpi_xft_cache; } -static double screen_dpi_get_from_monitor(struct screen_info *scr) +static double screen_dpi_get_from_monitor(const struct screen_info *scr) { return (double)scr->h * 25.4 / (double)scr->mmh; } -double screen_dpi_get(struct screen_info *scr) +double screen_dpi_get(const struct screen_info *scr) { if ( ! settings.force_xinerama && settings.per_monitor_dpi) @@ -156,6 +156,7 @@ screens[i].w = m[i].width; screens[i].h = m[i].height; screens[i].mmh = m[i].mheight; + screens[i].dpi = screen_dpi_get(&screens[i]); } XRRFreeMonitors(m); @@ -300,44 +301,48 @@ * Select the screen on which the Window * should be displayed. */ -struct screen_info *get_active_screen(void) +const struct screen_info *get_active_screen(void) { int ret = 0; bool force_follow_mouse = false; - if (settings.monitor > 0 && settings.monitor < screens_len) { - ret = settings.monitor; - goto sc_cleanup; - } - - x_follow_setup_error_handler(); if (settings.f_mode == FOLLOW_NONE) { - ret = XDefaultScreen(xctx.dpy); + if (settings.monitor >= 0 && settings.monitor < screens_len) { + ret = settings.monitor; + } goto sc_cleanup; - } else { int x, y; assert(settings.f_mode == FOLLOW_MOUSE || settings.f_mode == FOLLOW_KEYBOARD); + + x_follow_setup_error_handler(); + Window root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)); if (settings.f_mode == FOLLOW_KEYBOARD) { Window focused = get_focused_window(); - if (focused == 0) { + if (!focused) { /* - * This can happen in the case that the user - * just has the root window open, eg. in empty - * tags in dwm or similar window managers. In - * that case, fall back to FOLLOW_MOUSE, since - * it probably still has the right screen. + * If no window is focused, or the focus is set + * to dynamically change to the root window of + * the screen the pointer is on, force following + * the mouse. */ force_follow_mouse = true; } else { Window child_return; - XTranslateCoordinates(xctx.dpy, focused, root, - 0, 0, &x, &y, &child_return); + /* + * The window with input focus might be on a + * different X screen. Use the mouse location + * in that case. + */ + force_follow_mouse = !XTranslateCoordinates( + xctx.dpy, focused,root, + 0, 0, &x, &y, + &child_return); } } @@ -368,7 +373,7 @@ goto sc_cleanup; /* something seems to be wrong. Fall back to default */ - ret = XDefaultScreen(xctx.dpy); + ret = 0; goto sc_cleanup; } sc_cleanup: @@ -384,32 +389,13 @@ */ static Window get_focused_window(void) { - Window focused = 0; - Atom type; - int format; - unsigned long nitems, bytes_after; - unsigned char *prop_return = NULL; - Window root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)); - Atom netactivewindow = - XInternAtom(xctx.dpy, "_NET_ACTIVE_WINDOW", false); - - XGetWindowProperty(xctx.dpy, - root, - netactivewindow, - 0L, - sizeof(Window), - false, - XA_WINDOW, - &type, - &format, - &nitems, - &bytes_after, - &prop_return); - if (prop_return) { - focused = *(Window *)prop_return; - XFree(prop_return); - } + Window focused; + int ignored; + + XGetInputFocus(xctx.dpy, &focused, &ignored); + if (focused == None || focused == PointerRoot) + focused = 0; return focused; } @@ -438,4 +424,4 @@ return 0; } -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/x11/screen.h dunst-1.8.1/src/x11/screen.h --- dunst-1.5.0/src/x11/screen.h 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/x11/screen.h 2022-03-02 10:55:25.000000000 +0000 @@ -7,21 +7,12 @@ #define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh)) -struct screen_info { - int id; - int x; - int y; - unsigned int h; - unsigned int mmh; - unsigned int w; -}; - void init_screens(void); void screen_dpi_xft_cache_purge(void); bool screen_check_event(XEvent *ev); -struct screen_info *get_active_screen(void); -double screen_dpi_get(struct screen_info *scr); +const struct screen_info *get_active_screen(void); +double screen_dpi_get(const struct screen_info *scr); /** * Find the currently focused window and check if it's in @@ -45,4 +36,4 @@ bool window_is_fullscreen(Window window); #endif -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/x11/x.c dunst-1.8.1/src/x11/x.c --- dunst-1.5.0/src/x11/x.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/x11/x.c 2022-03-02 10:55:25.000000000 +0000 @@ -19,6 +19,7 @@ #include <X11/Xlib.h> #include <X11/Xresource.h> #include <X11/Xutil.h> +#include <linux/input-event-codes.h> #include "../dbus.h" #include "../draw.h" @@ -30,6 +31,7 @@ #include "../queues.h" #include "../settings.h" #include "../utils.h" +#include "../input.h" #include "screen.h" @@ -67,8 +69,10 @@ static void setopacity(Window win, unsigned long opacity); static void x_handle_click(XEvent ev); -static void x_win_move(struct window_x11 *win, int x, int y, int width, int height) +static void x_win_move(window winptr, int x, int y, int width, int height) { + struct window_x11 *win = (struct window_x11*)winptr; + /* move and resize */ if (x != win->dim.x || y != win->dim.y) { XMoveWindow(xctx.dpy, win->xwin, x, y); @@ -109,7 +113,7 @@ draw_rounded_rect(cr, 0, 0, width, height, - rad, + rad, 1, true, true); cairo_fill(cr); @@ -126,8 +130,9 @@ win->xwin, ShapeNotifyMask); } -static void x_win_corners_unshape(struct window_x11 *win) +static void x_win_corners_unshape(window winptr) { + struct window_x11 *win = (struct window_x11*)winptr; XRectangle rect = { .x = 0, .y = 0, @@ -153,10 +158,17 @@ } } -void x_display_surface(cairo_surface_t *srf, struct window_x11 *win, const struct dimensions *dim) +void x_display_surface(cairo_surface_t *srf, window winptr, const struct dimensions *dim) { - x_win_move(win, dim->x, dim->y, dim->w, dim->h); - cairo_xlib_surface_set_size(win->root_surface, dim->w, dim->h); + struct window_x11 *win = (struct window_x11*)winptr; + const struct screen_info *scr = get_active_screen(); + double scale = x_get_scale(); + int x, y; + + calc_window_pos(scr, round(dim->w * scale), round(dim->h * scale), &x, &y); + + x_win_move(win, x, y, round(dim->w * scale), round(dim->h * scale)); + cairo_xlib_surface_set_size(win->root_surface, round(dim->w * scale), round(dim->h * scale)); XClearWindow(xctx.dpy, win->xwin); @@ -165,7 +177,7 @@ cairo_show_page(win->c_ctx); if (settings.corner_radius != 0 && ! x_win_composited(win)) - x_win_corners_shape(win, dim->corner_radius); + x_win_corners_shape(win, round(dim->corner_radius * scale)); else x_win_corners_unshape(win); @@ -173,14 +185,9 @@ } -bool x_win_visible(struct window_x11 *win) -{ - return win->visible; -} - -cairo_t* x_win_get_context(struct window_x11 *win) +cairo_t* x_win_get_context(window winptr) { - return win->c_ctx; + return ((struct window_x11*)win)->c_ctx; } static void setopacity(Window win, unsigned long opacity) @@ -278,7 +285,7 @@ struct window_x11 *win = ((struct x11_source*) source)->win; bool fullscreen_now; - struct screen_info *scr; + const struct screen_info *scr; XEvent ev; unsigned int state; while (XPending(xctx.dpy) > 0) { @@ -399,59 +406,49 @@ return xctx.screensaver_info->idle > settings.idle_threshold / 1000; } -/* TODO move to x_mainloop_* */ /* - * Handle incoming mouse click events + * Convert x button code to linux event code + * Returns 0 if button is not recognized. */ -static void x_handle_click(XEvent ev) +static unsigned int x_mouse_button_to_linux_event_code(unsigned int x_button) { - enum mouse_action *acts; - - switch (ev.xbutton.button) { + switch (x_button) { case Button1: - acts = settings.mouse_left_click; - break; + return BTN_LEFT; case Button2: - acts = settings.mouse_middle_click; - break; + return BTN_MIDDLE; case Button3: - acts = settings.mouse_right_click; - break; + return BTN_RIGHT; default: - LOG_W("Unsupported mouse button: '%d'", ev.xbutton.button); - return; + LOG_W("Unsupported mouse button: '%d'", x_button); + return 0; } +} - for (int i = 0; acts[i]; i++) { - enum mouse_action act = acts[i]; - if (act == MOUSE_CLOSE_ALL) { - queues_history_push_all(); - return; - } +/* TODO move to x_mainloop_* */ +/* + * Handle incoming mouse click events + */ +static void x_handle_click(XEvent ev) +{ + unsigned int linux_code = x_mouse_button_to_linux_event_code(ev.xbutton.button); - if (act == MOUSE_DO_ACTION || act == MOUSE_CLOSE_CURRENT) { - int y = settings.separator_height; - struct notification *n = NULL; - int first = true; - for (const GList *iter = queues_get_displayed(); iter; - iter = iter->next) { - n = iter->data; - if (ev.xbutton.y > y && ev.xbutton.y < y + n->displayed_height) - break; - - y += n->displayed_height + settings.separator_height; - if (first) - y += settings.frame_width; - } + if (linux_code == 0) { + return; + } - if (n) { - if (act == MOUSE_CLOSE_CURRENT) - queues_notification_close(n, REASON_USER); - else - notification_do_action(n); - } - } + bool button_state; + if(ev.type == ButtonRelease) { + button_state = false; // button is up + } else { + // this shouldn't happen, because this function + // is only called when it'a a ButtonRelease event + button_state = true; // button is down } + + float scale = x_get_scale(); + input_handle_click(linux_code, button_state, ev.xbutton.x/scale, + ev.xbutton.y/scale); } void x_free(void) @@ -507,14 +504,16 @@ /* * Setup X11 stuff */ -void x_setup(void) +bool x_setup(void) { /* initialize xctx.dc, font, keyboard, colors */ if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) LOG_W("No locale support"); + if (!(xctx.dpy = XOpenDisplay(NULL))) { - DIE("Cannot open X11 display."); + LOG_W("Cannot open X11 display."); + return false; } x_shortcut_init(&settings.close_ks); @@ -533,32 +532,12 @@ xctx.screensaver_info = XScreenSaverAllocInfo(); - init_screens(); - x_shortcut_grab(&settings.history_ks); - XrmInitialize(); -} - -struct geometry x_parse_geometry(const char *geom_str) -{ - assert(geom_str); - struct geometry geometry = { 0 }; + XRM_update_db(); - if (geom_str[0] == '-') { - geometry.negative_width = true; - geom_str++; - } else { - geometry.negative_width = false; - } - - int mask = XParseGeometry(geom_str, - &geometry.x, &geometry.y, - &geometry.w, &geometry.h); - geometry.width_set = mask & WidthValue; - geometry.negative_x = mask & XNegative; - geometry.negative_y = mask & YNegative; - - return geometry; + init_screens(); + x_shortcut_grab(&settings.history_ks); + return true; } static void x_set_wm(Window win) @@ -641,7 +620,7 @@ /* * Setup the window */ -struct window_x11 *x_win_create(void) +window x_win_create(void) { struct window_x11 *win = g_malloc0(sizeof(struct window_x11)); @@ -671,7 +650,7 @@ ExposureMask | KeyPressMask | VisibilityChangeMask | ButtonReleaseMask | FocusChangeMask| StructureNotifyMask; - struct screen_info *scr = get_active_screen(); + const struct screen_info *scr = get_active_screen(); win->xwin = XCreateWindow(xctx.dpy, root, scr->x, @@ -713,11 +692,13 @@ } XSelectInput(xctx.dpy, root, root_event_mask); - return win; + return (window)win; } -void x_win_destroy(struct window_x11 *win) +void x_win_destroy(window winptr) { + struct window_x11 *win = (struct window_x11*)winptr; + g_source_destroy(win->esrc); g_source_unref(win->esrc); @@ -731,12 +712,15 @@ /* * Show the window and grab shortcuts. */ -void x_win_show(struct window_x11 *win) +void x_win_show(window winptr) { + struct window_x11 *win = (struct window_x11*)winptr; + /* window is already mapped or there's nothing to show */ if (win->visible) return; + x_shortcut_grab(&settings.close_ks); x_shortcut_grab(&settings.close_all_ks); x_shortcut_grab(&settings.context_ks); @@ -758,13 +742,17 @@ XMapRaised(xctx.dpy, win->xwin); win->visible = true; + + x_display_surface(win->root_surface, win, &win->dim); } /* * Hide the window and ungrab unused keyboard_shortcuts */ -void x_win_hide(struct window_x11 *win) +void x_win_hide(window winptr) { + LOG_I("X11: Hiding window"); + struct window_x11 *win = (struct window_x11*)winptr; ASSERT_OR_RET(win->visible,); x_shortcut_ungrab(&settings.close_ks); @@ -940,4 +928,15 @@ g_free(str_begin); } -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +double x_get_scale(void) { + if (settings.scale > 0) + return settings.scale; + + const struct screen_info *scr_info = get_active_screen(); + double scale = MAX(1, scr_info->dpi/96.); + LOG_D("X11 dpi: %i", scr_info->dpi); + LOG_D("X11 scale: %f", scale); + return scale; +} + +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/src/x11/x.h dunst-1.8.1/src/x11/x.h --- dunst-1.5.0/src/x11/x.h 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/src/x11/x.h 2022-03-02 10:55:25.000000000 +0000 @@ -11,6 +11,8 @@ #include <X11/X.h> #include <X11/Xlib.h> +#include "../output.h" + #include "screen.h" struct keyboard_shortcut { @@ -24,49 +26,29 @@ // Cyclical dependency #include "../settings.h" -struct window_x11; - -struct dimensions { - int x; - int y; - int w; - int h; - - int corner_radius; -}; - struct x_context { Display *dpy; XScreenSaverInfo *screensaver_info; }; -struct color { - double r; - double g; - double b; - double a; -}; - extern struct x_context xctx; /* window */ -struct window_x11 *x_win_create(void); -void x_win_destroy(struct window_x11 *win); +window x_win_create(void); +void x_win_destroy(window); -void x_win_show(struct window_x11 *win); -void x_win_hide(struct window_x11 *win); +void x_win_show(window); +void x_win_hide(window); -void x_display_surface(cairo_surface_t *srf, struct window_x11 *win, const struct dimensions *dim); +void x_display_surface(cairo_surface_t *srf, window, const struct dimensions *dim); -bool x_win_visible(struct window_x11 *win); -cairo_t* x_win_get_context(struct window_x11 *win); +cairo_t* x_win_get_context(window); /* X misc */ bool x_is_idle(void); -void x_setup(void); +bool x_setup(void); void x_free(void); -struct geometry x_parse_geometry(const char *geom_str); - +double x_get_scale(void); #endif -/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/test/data/dunstrc.default dunst-1.8.1/test/data/dunstrc.default --- dunst-1.5.0/test/data/dunstrc.default 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/data/dunstrc.default 2022-03-02 10:55:25.000000000 +0000 @@ -1,3 +1,5 @@ +# See dunst(5) for all configuration options + [global] ### Display ### @@ -15,39 +17,59 @@ # # If this option is set to mouse or keyboard, the monitor option # will be ignored. - follow = mouse + follow = none - # The geometry of the window: - # [{width}]x{height}[+/-{x}+/-{y}] - # The geometry of the message window. - # The height is measured in number of notifications everything else - # in pixels. If the width is omitted but the height is given - # ("-geometry x2"), the message window expands over the whole screen - # (dmenu-like). If width is 0, the window expands to the longest - # message displayed. A positive x is measured from the left, a - # negative from the right side of the screen. Y is measured from - # the top and down respectively. - # The width can be negative. In this case the actual width is the - # screen width minus the width defined in within the geometry option. - geometry = "300x5-30+20" + ### Geometry ### - # Show how many messages are currently hidden (because of geometry). - indicate_hidden = yes + # dynamic width from 0 to 300 + # width = (0, 300) + # constant width of 300 + width = 300 + + # The maximum height of a single notification, excluding the frame. + height = 300 + + # Position the notification in the top right corner + origin = top-right + + # Offset from the origin + offset = 10x50 + + # Scale factor. It is auto-detected if value is 0. + scale = 0 + + # Maximum number of notification (0 means no limit) + notification_limit = 0 + + ### Progress bar ### + + # Turn on the progess bar. It appears when a progress hint is passed with + # for example dunstify -h int:value:12 + progress_bar = true + + # Set the progress bar height. This includes the frame, so make sure + # it's at least twice as big as the frame width. + progress_bar_height = 10 + + # Set the frame width of the progress bar + progress_bar_frame_width = 1 + + # Set the minimum width for the progress bar + progress_bar_min_width = 150 - # Shrink window if it's smaller than the width. Will be ignored if - # width is 0. - shrink = no + # Set the maximum width for the progress bar + progress_bar_max_width = 300 + + + # Show how many messages are currently hidden (because of + # notification_limit). + indicate_hidden = yes # The transparency of the window. Range: [0; 100]. # This option will only work if a compositing window manager is - # present (e.g. xcompmgr, compiz, etc.). + # present (e.g. xcompmgr, compiz, etc.). (X11 only) transparency = 0 - # The height of the entire notification. If the height is smaller - # than the font height and padding combined, it will be raised - # to the font height and padding. - notification_height = 0 - # Draw a line of "separator_height" pixel height between two # notifications. # Set to 0 to disable. @@ -59,6 +81,9 @@ # Horizontal padding. horizontal_padding = 8 + # Padding between text and icon. + text_icon_padding = 0 + # Defines width in pixels of frame around the notification window. # Set to 0 to disable. frame_width = 3 @@ -82,7 +107,7 @@ # Set to 0 to disable. # A client can set the 'transient' hint to bypass this. See the rules # section for how to disable this if necessary - idle_threshold = 120 + # idle_threshold = 120 ### Text ### @@ -100,7 +125,7 @@ # <u>underline</u> # # For a complete reference see - # <https://developer.gnome.org/pango/stable/pango-Markup.html>. + # <https://docs.gtk.org/Pango/pango_markup.html>. # # strip: This setting is provided for compatibility with some broken # clients that send markup even though it's not enabled on the @@ -141,11 +166,7 @@ # Set to -1 to disable. show_age_threshold = 60 - # Split notifications into multiple lines if they don't fit into - # geometry. - word_wrap = yes - - # When word_wrap is set to no, specify where to make an ellipsis in long lines. + # Specify where to make an ellipsis in long lines. # Possible values are "start", "middle" and "end". ellipsize = middle @@ -163,7 +184,7 @@ ### Icons ### - # Align icons left/right/off + # Align icons left/right/top/off icon_position = left # Scale small icons up to this size, set to 0 to disable. Helpful @@ -192,7 +213,7 @@ dmenu = /usr/bin/dmenu -p dunst: # Browser for opening urls in context menu. - browser = /usr/bin/firefox -new-tab + browser = /usr/bin/xdg-open # Always run rule-defined scripts, even if the notification is suppressed always_run_script = true @@ -203,20 +224,6 @@ # Define the class of the windows spawned by dunst class = Dunst - # Print a notification on startup. - # This is mainly for error detection, since dbus (re-)starts dunst - # automatically after a crash. - startup_notification = false - - # Manage dunst's desire for talking - # Can be one of the following values: - # crit: Critical features. Dunst aborts - # warn: Only non-fatal warnings - # mesg: Important Messages - # info: all unimportant stuff - # debug: all less than unimportant stuff - verbosity = mesg - # Define the corner radius of the notification window # in pixel size. If the radius is 0, you have no rounded # corners. @@ -226,10 +233,20 @@ # Ignore the dbus closeNotification message. # Useful to enforce the timeout set by dunst configuration. Without this - # parameter, an application may close the notification sent before the + # parameter, an application may close the notification sent before the # user defined timeout. ignore_dbusclose = false + ### Wayland ### + # These settings are Wayland-specific. They have no effect when using X11 + + # Uncomment this if you want to let notications appear under fullscreen + # applications (default: overlay) + # layer = top + + # Set this to true to use X11 output on Wayland. + force_xwayland = false + ### Legacy # Use the Xinerama extension instead of RandR for multi-monitor support. @@ -247,10 +264,14 @@ # Defines list of actions for each mouse event # Possible values are: # * none: Don't do anything. - # * do_action: If the notification has exactly one action, or one is marked as default, - # invoke it. If there are multiple and no default, open the context menu. + # * do_action: Invoke the action determined by the action_name rule. If there is no + # such action, open the context menu. + # * open_url: If the notification has exactly one url, open it. If there are multiple + # ones, open the context menu. # * close_current: Close current notification. # * close_all: Close all notifications. + # * context: Open context menu for the notification. + # * context_all: Open context menu for all notifications. # These values can be strung together for each mouse event, and # will be executed in sequence. mouse_left_click = close_current @@ -267,27 +288,6 @@ # where there are multiple screens with very different dpi values. per_monitor_dpi = false -[shortcuts] - - # Shortcuts are specified as [modifier+][modifier+]...key - # Available modifiers are "ctrl", "mod1" (the alt-key), "mod2", - # "mod3" and "mod4" (windows-key). - # Xev might be helpful to find names for keys. - - # Close notification. - close = ctrl+space - - # Close all notifications. - close_all = ctrl+shift+space - - # Redisplay last message(s). - # On the US keyboard layout "grave" is normally above TAB and left - # of "1". Make sure this key actually exists on your keyboard layout, - # e.g. check output of 'xmodmap -pke' - history = ctrl+grave - - # Context menu. - context = ctrl+shift+period [urgency_low] # IMPORTANT: colors have to be defined in quotation marks. @@ -296,14 +296,14 @@ foreground = "#888888" timeout = 10 # Icon for notifications with low urgency, uncomment to enable - #icon = /path/to/icon + #default_icon = /path/to/icon [urgency_normal] background = "#285577" foreground = "#ffffff" timeout = 10 # Icon for notifications with normal urgency, uncomment to enable - #icon = /path/to/icon + #default_icon = /path/to/icon [urgency_critical] background = "#900000" @@ -311,7 +311,7 @@ frame_color = "#ff0000" timeout = 0 # Icon for notifications with critical urgency, uncomment to enable - #icon = /path/to/icon + #default_icon = /path/to/icon # Every section that isn't one of the above is interpreted as a rules to # override settings for certain messages. @@ -336,8 +336,17 @@ # new_icon # set_stack_tag # set_transient +# set_category # timeout # urgency +# icon_position +# skip_display +# history_ignore +# action_name +# word_wrap +# ellipsize +# alignment +# hide_text # # Shell-like globbing will get expanded. # @@ -352,8 +361,6 @@ # script appname summary body icon urgency # where urgency can be "LOW", "NORMAL" or "CRITICAL". # -# NOTE: if you don't want a notification to be displayed, set the format -# to "". # NOTE: It might be helpful to run dunst -print in a terminal in order # to find fitting options for rules. @@ -392,7 +399,7 @@ #[ignore] # # This notification will not be displayed # summary = "foobar" -# format = "" +# skip_display = true #[history-ignore] # # This notification will not be saved in history diff -Nru dunst-1.5.0/test/data/dunstrc.markup dunst-1.8.1/test/data/dunstrc.markup --- dunst-1.5.0/test/data/dunstrc.markup 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/test/data/dunstrc.markup 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,14 @@ +[urgency_low] + background = "#222222" + foreground = "#888888" + timeout = 10 + +[urgency_normal] + background = "#285577" + foreground = "#ffffff" + timeout = 10 + +[urgency_critical] + background = "#900000" + foreground = "#ffffff" + timeout = 0 diff -Nru dunst-1.5.0/test/data/dunstrc.nomarkup dunst-1.8.1/test/data/dunstrc.nomarkup --- dunst-1.5.0/test/data/dunstrc.nomarkup 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/test/data/dunstrc.nomarkup 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,18 @@ +[global] + markup = no + format = "<b>%s</b>\n<i>%b</i>" + +[urgency_low] + background = "#222222" + foreground = "#888888" + timeout = 10 + +[urgency_normal] + background = "#285577" + foreground = "#ffffff" + timeout = 10 + +[urgency_critical] + background = "#900000" + foreground = "#ffffff" + timeout = 0 diff -Nru dunst-1.5.0/test/data/dunstrc.show_age dunst-1.8.1/test/data/dunstrc.show_age --- dunst-1.5.0/test/data/dunstrc.show_age 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/test/data/dunstrc.show_age 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,48 @@ +[global] + font = Monospace 8 + allow_markup = yes + format = "<b>%s</b>\n%b" + sort = yes + indicate_hidden = yes + alignment = left + show_age_threshold = 2 + ignore_newline = no + geometry = "300x5-30+20" + transparency = 0 + idle_threshold = 120 + monitor = 0 + follow = mouse + sticky_history = yes + line_height = 0 + separator_height = 2 + padding = 8 + horizontal_padding = 8 + separator_color = frame + startup_notification = false + dmenu = /usr/bin/dmenu -p dunst + browser = /usr/bin/firefox -new-tab + +[frame] + width = 3 + color = "#aaaaaa" + +[shortcuts] + close = ctrl+space + close_all = ctrl+shift+space + history = ctrl+grave + context = ctrl+shift+period + +[urgency_low] + background = "#222222" + foreground = "#888888" + timeout = 10 + +[urgency_normal] + background = "#285577" + foreground = "#ffffff" + timeout = 10 + +[urgency_critical] + background = "#900000" + foreground = "#ffffff" + timeout = 0 diff -Nru dunst-1.5.0/test/data/icons/invalid.png dunst-1.8.1/test/data/icons/invalid.png --- dunst-1.5.0/test/data/icons/invalid.png 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/data/icons/invalid.png 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -Got'cha! This has to be invalid! diff -Nru dunst-1.5.0/test/data/icons/invalid.svg dunst-1.8.1/test/data/icons/invalid.svg --- dunst-1.5.0/test/data/icons/invalid.svg 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/data/icons/invalid.svg 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -Got'cha! This has to be invalid! diff -Nru dunst-1.5.0/test/data/icons/path/invalid/icon1.png dunst-1.8.1/test/data/icons/path/invalid/icon1.png --- dunst-1.5.0/test/data/icons/path/invalid/icon1.png 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/data/icons/path/invalid/icon1.png 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -Got'cha! This has to be invalid! diff -Nru dunst-1.5.0/test/data/icons/path/invalid/icon1.svg dunst-1.8.1/test/data/icons/path/invalid/icon1.svg --- dunst-1.5.0/test/data/icons/path/invalid/icon1.svg 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/data/icons/path/invalid/icon1.svg 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -Got'cha! This has to be invalid! Binary files /tmp/tmp_0le5vzy/5ow2W6Tzyb/dunst-1.5.0/test/data/icons/path/valid/icon1.png and /tmp/tmp_0le5vzy/1UuqYpLmbr/dunst-1.8.1/test/data/icons/path/valid/icon1.png differ diff -Nru dunst-1.5.0/test/data/icons/path/valid/icon1.svg dunst-1.8.1/test/data/icons/path/valid/icon1.svg --- dunst-1.5.0/test/data/icons/path/valid/icon1.svg 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/data/icons/path/valid/icon1.svg 1970-01-01 00:00:00.000000000 +0000 @@ -1,65 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="16px" - height="16px" - viewBox="0 0 16 16" - version="1.1" - id="SVGRoot" - inkscape:version="0.92.2 5c3e80d, 2017-08-06" - sodipodi:docname="valid.svg"> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="16" - inkscape:cx="4.6127988" - inkscape:cy="8" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - inkscape:window-width="958" - inkscape:window-height="1034" - inkscape:window-x="960" - inkscape:window-y="46" - inkscape:window-maximized="0" - inkscape:grid-bbox="true" /> - <defs - id="defs20" /> - <metadata - id="metadata23"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <g - id="layer1" - inkscape:groupmode="layer" - inkscape:label="Layer 1"> - <rect - style="opacity:0.98999999;fill:#cccccc;fill-opacity:1;stroke:#cccccc;stroke-width:1;stroke-miterlimit:4.19999981;stroke-dasharray:none;stroke-opacity:0.15686275" - id="rect36" - width="8" - height="15.4375" - x="3.75" - y="0.25" - ry="4.875" /> - </g> -</svg> Binary files /tmp/tmp_0le5vzy/5ow2W6Tzyb/dunst-1.5.0/test/data/icons/path/valid/onlypng.png and /tmp/tmp_0le5vzy/1UuqYpLmbr/dunst-1.8.1/test/data/icons/path/valid/onlypng.png differ diff -Nru dunst-1.5.0/test/data/icons/path/valid/onlysvg.svg dunst-1.8.1/test/data/icons/path/valid/onlysvg.svg --- dunst-1.5.0/test/data/icons/path/valid/onlysvg.svg 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/data/icons/path/valid/onlysvg.svg 1970-01-01 00:00:00.000000000 +0000 @@ -1,65 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="16px" - height="16px" - viewBox="0 0 16 16" - version="1.1" - id="SVGRoot" - inkscape:version="0.92.2 5c3e80d, 2017-08-06" - sodipodi:docname="valid.svg"> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="16" - inkscape:cx="4.6127988" - inkscape:cy="8" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - inkscape:window-width="958" - inkscape:window-height="1034" - inkscape:window-x="960" - inkscape:window-y="46" - inkscape:window-maximized="0" - inkscape:grid-bbox="true" /> - <defs - id="defs20" /> - <metadata - id="metadata23"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <g - id="layer1" - inkscape:groupmode="layer" - inkscape:label="Layer 1"> - <rect - style="opacity:0.98999999;fill:#cccccc;fill-opacity:1;stroke:#cccccc;stroke-width:1;stroke-miterlimit:4.19999981;stroke-dasharray:none;stroke-opacity:0.15686275" - id="rect36" - width="8" - height="15.4375" - x="3.75" - y="0.25" - ry="4.875" /> - </g> -</svg> Binary files /tmp/tmp_0le5vzy/5ow2W6Tzyb/dunst-1.5.0/test/data/icons/theme/16x16/actions/edit.png and /tmp/tmp_0le5vzy/1UuqYpLmbr/dunst-1.8.1/test/data/icons/theme/16x16/actions/edit.png differ Binary files /tmp/tmp_0le5vzy/5ow2W6Tzyb/dunst-1.5.0/test/data/icons/theme/16x16/apps/preferences.png and /tmp/tmp_0le5vzy/1UuqYpLmbr/dunst-1.8.1/test/data/icons/theme/16x16/apps/preferences.png differ Binary files /tmp/tmp_0le5vzy/5ow2W6Tzyb/dunst-1.5.0/test/data/icons/theme/16x16@2x/actions/edit.png and /tmp/tmp_0le5vzy/1UuqYpLmbr/dunst-1.8.1/test/data/icons/theme/16x16@2x/actions/edit.png differ Binary files /tmp/tmp_0le5vzy/5ow2W6Tzyb/dunst-1.5.0/test/data/icons/theme/16x16@2x/apps/preferences.png and /tmp/tmp_0le5vzy/1UuqYpLmbr/dunst-1.8.1/test/data/icons/theme/16x16@2x/apps/preferences.png differ Binary files /tmp/tmp_0le5vzy/5ow2W6Tzyb/dunst-1.5.0/test/data/icons/theme/32x32/actions/edit.png and /tmp/tmp_0le5vzy/1UuqYpLmbr/dunst-1.8.1/test/data/icons/theme/32x32/actions/edit.png differ Binary files /tmp/tmp_0le5vzy/5ow2W6Tzyb/dunst-1.5.0/test/data/icons/theme/32x32/apps/preferences.png and /tmp/tmp_0le5vzy/1UuqYpLmbr/dunst-1.8.1/test/data/icons/theme/32x32/apps/preferences.png differ Binary files /tmp/tmp_0le5vzy/5ow2W6Tzyb/dunst-1.5.0/test/data/icons/theme/32x32@2x/actions/edit.png and /tmp/tmp_0le5vzy/1UuqYpLmbr/dunst-1.8.1/test/data/icons/theme/32x32@2x/actions/edit.png differ Binary files /tmp/tmp_0le5vzy/5ow2W6Tzyb/dunst-1.5.0/test/data/icons/theme/32x32@2x/apps/preferences.png and /tmp/tmp_0le5vzy/1UuqYpLmbr/dunst-1.8.1/test/data/icons/theme/32x32@2x/apps/preferences.png differ diff -Nru dunst-1.5.0/test/data/icons/theme/index.theme dunst-1.8.1/test/data/icons/theme/index.theme --- dunst-1.5.0/test/data/icons/theme/index.theme 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/test/data/icons/theme/index.theme 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,51 @@ +[Icon Theme] +Name=theme +Comment=Test theme for dunst +Example=folder + +X-ignore-this=something +# ignore this + +# Directory list +Directories=16x16/actions,16x16/apps,16x16@2x/actions,16x16@2x/apps,32x32/actions,32x32/apps,32x32@2x/actions,32x32@2x/apps + +[16x16/actions] +Size=16 +Type=Threshold +Threshold=3 + +[16x16/apps] +Size=16 +Type=Threshold + +[16x16@2x/actions] +Size=16 +Scale=2 +Type=Scalable +MinSize=16 +MaxSize=32 + +[16x16@2x/apps] +Size=16 +Scale=2 +Type=Fixed + +[32x32/actions] +Size=32 +Type=Scalable +MinSize=32 +MaxSize=64 + +[32x32/apps] +Size=32 +Type=Fixed + +[32x32@2x/actions] +Size=32 +Scale=2 +Type=Fixed + +[32x32@2x/apps] +Size=32 +Scale=2 +Type=Fixed diff -Nru dunst-1.5.0/test/dbus.c dunst-1.8.1/test/dbus.c --- dunst-1.5.0/test/dbus.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/dbus.c 2022-03-02 10:55:25.000000000 +0000 @@ -1,5 +1,6 @@ #define wake_up wake_up_void #include "../src/dbus.c" +#include "../src/rules.h" #include "greatest.h" #include <assert.h> @@ -408,11 +409,11 @@ ASSERT(dbus_notification_fire(n_dbus, &id)); ASSERT(id != 0); - ASSERT_EQ(queues_length_waiting(), len+1); + snprintf(msg, sizeof(msg), "In round %ld", i); + ASSERT_EQm(msg, queues_length_waiting(), len+1); n = queues_debug_find_notification_by_id(id); - snprintf(msg, sizeof(msg), "In round %ld", i); ASSERT_EQm(msg, values[i], n->transient); } @@ -453,11 +454,12 @@ ASSERT(dbus_notification_fire(n_dbus, &id)); ASSERT(id != 0); - ASSERT_EQ(queues_length_waiting(), len+1); + snprintf(msg, sizeof(msg), "In round %ld", i); + ASSERT_EQm(msg, queues_length_waiting(), len+1); n = queues_debug_find_notification_by_id(id); - snprintf(msg, sizeof(msg), "In round %ld", i); + snprintf(msg, sizeof(msg), "In round %ld progress should be %i, but is %i", i, n->progress, values[i]); ASSERT_EQm(msg, values[i], n->progress); } @@ -675,7 +677,11 @@ GVariant *caps = NULL; const char **capsarray; - settings.markup = markup; + struct rule *global_rule = get_rule("global"); + if (!global_rule) { + global_rule = rule_new("global"); + } + global_rule->markup = markup; reply = dbus_invoke("GetCapabilities", NULL); @@ -686,9 +692,10 @@ ASSERT(g_strv_contains(capsarray, "actions")); ASSERT(g_strv_contains(capsarray, "body")); ASSERT(g_strv_contains(capsarray, "body-hyperlinks")); + ASSERT(g_strv_contains(capsarray, "icon-static")); ASSERT(g_strv_contains(capsarray, "x-dunst-stack-tag")); - if (settings.markup != MARKUP_NO) + if (markup != MARKUP_NO) ASSERT(g_strv_contains(capsarray, "body-markup")); else ASSERT_FALSE(g_strv_contains(capsarray, "body-markup")); @@ -847,8 +854,6 @@ SUITE(suite_dbus) { - settings.icon_path = ""; - GTestDBus *dbus_bus; g_test_dbus_unset(); queues_init(); @@ -856,6 +861,10 @@ loop = g_main_loop_new(NULL, false); dbus_bus = g_test_dbus_new(G_TEST_DBUS_NONE); + + // workaround bug in glib where stdout output is duplicated + // See https://gitlab.gnome.org/GNOME/glib/-/issues/2322 + fflush(stdout); g_test_dbus_up(dbus_bus); thread_tests = g_thread_new("testexecutor", run_threaded_tests, loop); @@ -866,8 +875,6 @@ g_object_unref(dbus_bus); g_thread_unref(thread_tests); g_main_loop_unref(loop); - - settings.icon_path = NULL; } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/test/draw.c dunst-1.8.1/test/draw.c --- dunst-1.5.0/test/draw.c 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/test/draw.c 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,85 @@ +#include "../src/draw.c" +#include "greatest.h" +#include "helpers.h" +#include <cairo.h> + +cairo_t *c; + +const struct screen_info* noop_screen(void) { + static struct screen_info i; + return &i; +} + +const struct output dummy_output = { + x_setup, + x_free, + + x_win_create, + x_win_destroy, + + x_win_show, + x_win_hide, + + x_display_surface, + x_win_get_context, + + noop_screen, + + x_is_idle, + have_fullscreen_window, + + x_get_scale, +}; + +TEST test_layout_from_notification(void) +{ + struct notification *n = test_notification_with_icon("test", 10); + n->icon_position = ICON_LEFT; + ASSERT(n->icon); + n->text_to_render = g_strdup(""); + struct colored_layout *cl = layout_from_notification(c, n); + ASSERT(cl->icon); + free_colored_layout(cl); + notification_unref(n); + PASS(); +} + +TEST test_layout_from_notification_icon_off(void) +{ + struct notification *n = test_notification_with_icon("test", 10); + n->icon_position = ICON_OFF; + ASSERT(n->icon); + n->text_to_render = g_strdup(""); + struct colored_layout *cl = layout_from_notification(c, n); + ASSERT_FALSE(cl->icon); + free_colored_layout(cl); + notification_unref(n); + PASS(); +} + +TEST test_layout_from_notification_no_icon(void) +{ + struct notification *n = test_notification("test", 10); + n->icon_position = ICON_LEFT; + ASSERT_FALSE(n->icon); + n->text_to_render = g_strdup(""); + struct colored_layout *cl = layout_from_notification(c, n); + ASSERT_FALSE(cl->icon); + + free_colored_layout(cl); + notification_unref(n); + PASS(); +} + +SUITE(suite_draw) +{ + output = &dummy_output; + cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + c = cairo_create(s); + + SHUFFLE_TESTS(time(NULL), { + RUN_TEST(test_layout_from_notification); + RUN_TEST(test_layout_from_notification_icon_off); + RUN_TEST(test_layout_from_notification_no_icon); + }); +} diff -Nru dunst-1.5.0/test/dunst.c dunst-1.8.1/test/dunst.c --- dunst-1.5.0/test/dunst.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/dunst.c 2022-03-02 10:55:25.000000000 +0000 @@ -1,15 +1,6 @@ -#define dbus_signal_status_changed(status) signal_sent_stub(status) #include "../src/dunst.c" #include "greatest.h" -static bool signal_sent = false; - -void signal_sent_stub(struct dunst_status status) -{ - signal_sent = true; - return; -} - TEST test_dunst_status(void) { status = (struct dunst_status) {false, false, false}; diff -Nru dunst-1.5.0/test/functional-tests/dunstrc.default dunst-1.8.1/test/functional-tests/dunstrc.default --- dunst-1.5.0/test/functional-tests/dunstrc.default 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/functional-tests/dunstrc.default 2022-03-02 10:55:25.000000000 +0000 @@ -1,38 +1,3 @@ -[global] - font = Monospace 8 - allow_markup = yes - format = "<b>%s</b>\n%b" - sort = yes - indicate_hidden = yes - alignment = left - show_age_threshold = 60 - word_wrap = yes - ignore_newline = no - geometry = "300x5-30+20" - transparency = 0 - idle_threshold = 120 - monitor = 0 - follow = mouse - sticky_history = yes - line_height = 0 - separator_height = 2 - padding = 8 - horizontal_padding = 8 - separator_color = frame - startup_notification = false - dmenu = /usr/bin/dmenu -p dunst - browser = /usr/bin/firefox -new-tab - -[frame] - width = 3 - color = "#aaaaaa" - -[shortcuts] - close = ctrl+space - close_all = ctrl+shift+space - history = ctrl+grave - context = ctrl+shift+period - [urgency_low] background = "#222222" foreground = "#888888" @@ -47,3 +12,6 @@ background = "#900000" foreground = "#ffffff" timeout = 0 + +[global] +# can be appended by script diff -Nru dunst-1.5.0/test/functional-tests/dunstrc.hide_text dunst-1.8.1/test/functional-tests/dunstrc.hide_text --- dunst-1.5.0/test/functional-tests/dunstrc.hide_text 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/test/functional-tests/dunstrc.hide_text 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,28 @@ +[urgency_low] + background = "#222222" + foreground = "#888888" + timeout = 10 + icon_position = top + hide_text = yes + +[urgency_normal] + background = "#285577" + foreground = "#ffffff" + timeout = 10 + icon_position = top + hide_text = yes + +[urgency_critical] + background = "#900000" + foreground = "#ffffff" + timeout = 0 + +[global] + font = Monospace 8 + markup = full + format = "<b>%s</b>\n%b" + progress_bar_min_width = 100 + progress_bar_max_width = 200 + progress_bar_frame_width = 5 + progress_bar_height = 30 + 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/ \ No newline at end of file diff -Nru dunst-1.5.0/test/functional-tests/dunstrc.icon_position dunst-1.8.1/test/functional-tests/dunstrc.icon_position --- dunst-1.5.0/test/functional-tests/dunstrc.icon_position 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/test/functional-tests/dunstrc.icon_position 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,85 @@ +[urgency_low] + background = "#222222" + foreground = "#888888" + timeout = 10 + +[urgency_normal] + background = "#285577" + foreground = "#ffffff" + timeout = 0 + +[urgency_critical] + background = "#900000" + foreground = "#ffffff" + timeout = 0 + +[icon-left-alignment-left] + category = "icon-left-alignment-left" + icon_position = left + alignment = left + +[icon-left-alignment-right] + category = "icon-left-alignment-right" + icon_position = left + alignment = right + +[icon-left-alignment-center] + category = "icon-left-alignment-center" + icon_position = left + alignment = center + +[icon-right-alignment-left] + category = "icon-right-alignment-left" + icon_position = right + alignment = left + +[icon-right-alignment-right] + category = "icon-right-alignment-right" + icon_position = right + alignment = right + +[icon-right-alignment-center] + category = "icon-right-alignment-center" + icon_position = right + alignment = center + +[icon-top-alignment-left] + category = "icon-top-alignment-left" + icon_position = top + alignment = left + +[icon-top-alignment-right] + category = "icon-top-alignment-right" + icon_position = top + alignment = right + +[icon-top-alignment-center] + category = "icon-top-alignment-center" + icon_position = top + alignment = center + +[icon-off-alignment-left] + category = "icon-off-alignment-left" + icon_position = off + alignment = left + +[icon-off-alignment-right] + category = "icon-off-alignment-right" + icon_position = off + alignment = right + +[icon-off-alignment-center] + category = "icon-off-alignment-center" + icon_position = off + alignment = center + +[global] + font = Monospace 8 + markup = full + format = "<b>%s</b>\n%b" + progress_bar_min_width = 100 + progress_bar_max_width = 200 + progress_bar_frame_width = 5 + progress_bar_height = 30 + width = (500,750) + 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/ \ No newline at end of file diff -Nru dunst-1.5.0/test/functional-tests/dunstrc.ignore_newline dunst-1.8.1/test/functional-tests/dunstrc.ignore_newline --- dunst-1.5.0/test/functional-tests/dunstrc.ignore_newline 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/functional-tests/dunstrc.ignore_newline 1970-01-01 00:00:00.000000000 +0000 @@ -1,49 +0,0 @@ -[global] - font = Monospace 8 - allow_markup = yes - format = "<b>%s</b>\n%b" - sort = yes - indicate_hidden = yes - alignment = left - show_age_threshold = 60 - word_wrap = yes - ignore_newline = yes - geometry = "200x0-30+20" - transparency = 0 - idle_threshold = 120 - monitor = 0 - follow = mouse - sticky_history = yes - line_height = 0 - separator_height = 2 - padding = 8 - horizontal_padding = 8 - separator_color = frame - startup_notification = false - dmenu = /usr/bin/dmenu -p dunst - browser = /usr/bin/firefox -new-tab - -[frame] - width = 3 - color = "#aaaaaa" - -[shortcuts] - close = ctrl+space - close_all = ctrl+shift+space - history = ctrl+grave - context = ctrl+shift+period - -[urgency_low] - background = "#222222" - foreground = "#888888" - timeout = 10 - -[urgency_normal] - background = "#285577" - foreground = "#ffffff" - timeout = 10 - -[urgency_critical] - background = "#900000" - foreground = "#ffffff" - timeout = 0 diff -Nru dunst-1.5.0/test/functional-tests/dunstrc.ignore_newline_no_wrap dunst-1.8.1/test/functional-tests/dunstrc.ignore_newline_no_wrap --- dunst-1.5.0/test/functional-tests/dunstrc.ignore_newline_no_wrap 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/functional-tests/dunstrc.ignore_newline_no_wrap 1970-01-01 00:00:00.000000000 +0000 @@ -1,49 +0,0 @@ -[global] - font = Monospace 8 - allow_markup = yes - format = "<b>%s</b>\n%b" - sort = yes - indicate_hidden = yes - alignment = left - show_age_threshold = 60 - word_wrap = no - ignore_newline = yes - geometry = "250x0-30+20" - transparency = 0 - idle_threshold = 120 - monitor = 0 - follow = mouse - sticky_history = yes - line_height = 0 - separator_height = 2 - padding = 8 - horizontal_padding = 8 - separator_color = frame - startup_notification = false - dmenu = /usr/bin/dmenu -p dunst - browser = /usr/bin/firefox -new-tab - -[frame] - width = 3 - color = "#aaaaaa" - -[shortcuts] - close = ctrl+space - close_all = ctrl+shift+space - history = ctrl+grave - context = ctrl+shift+period - -[urgency_low] - background = "#222222" - foreground = "#888888" - timeout = 10 - -[urgency_normal] - background = "#285577" - foreground = "#ffffff" - timeout = 10 - -[urgency_critical] - background = "#900000" - foreground = "#ffffff" - timeout = 0 diff -Nru dunst-1.5.0/test/functional-tests/dunstrc.markup dunst-1.8.1/test/functional-tests/dunstrc.markup --- dunst-1.5.0/test/functional-tests/dunstrc.markup 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/functional-tests/dunstrc.markup 2022-03-02 10:55:25.000000000 +0000 @@ -1,38 +1,3 @@ -[global] - font = Monospace 8 - allow_markup = yes - format = "%s\n%b" - sort = yes - indicate_hidden = yes - alignment = left - show_age_threshold = 60 - word_wrap = yes - ignore_newline = no - geometry = "300x5-30+20" - transparency = 0 - idle_threshold = 120 - monitor = 0 - follow = mouse - sticky_history = yes - line_height = 0 - separator_height = 2 - padding = 8 - horizontal_padding = 8 - separator_color = frame - startup_notification = false - dmenu = /usr/bin/dmenu -p dunst - browser = /usr/bin/firefox -new-tab - -[frame] - width = 3 - color = "#aaaaaa" - -[shortcuts] - close = ctrl+space - close_all = ctrl+shift+space - history = ctrl+grave - context = ctrl+shift+period - [urgency_low] background = "#222222" foreground = "#888888" diff -Nru dunst-1.5.0/test/functional-tests/dunstrc.nomarkup dunst-1.8.1/test/functional-tests/dunstrc.nomarkup --- dunst-1.5.0/test/functional-tests/dunstrc.nomarkup 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/functional-tests/dunstrc.nomarkup 2022-03-02 10:55:25.000000000 +0000 @@ -1,37 +1,6 @@ [global] - font = Monospace 8 - allow_markup = no + markup = no format = "<b>%s</b>\n<i>%b</i>" - sort = yes - indicate_hidden = yes - alignment = left - show_age_threshold = 60 - word_wrap = yes - ignore_newline = no - geometry = "300x5-30+20" - transparency = 0 - idle_threshold = 120 - monitor = 0 - follow = mouse - sticky_history = yes - line_height = 0 - separator_height = 2 - padding = 8 - horizontal_padding = 8 - separator_color = frame - startup_notification = false - dmenu = /usr/bin/dmenu -p dunst - browser = /usr/bin/firefox -new-tab - -[frame] - width = 3 - color = "#aaaaaa" - -[shortcuts] - close = ctrl+space - close_all = ctrl+shift+space - history = ctrl+grave - context = ctrl+shift+period [urgency_low] background = "#222222" diff -Nru dunst-1.5.0/test/functional-tests/dunstrc.nowrap dunst-1.8.1/test/functional-tests/dunstrc.nowrap --- dunst-1.5.0/test/functional-tests/dunstrc.nowrap 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/functional-tests/dunstrc.nowrap 2022-03-02 10:55:25.000000000 +0000 @@ -6,7 +6,6 @@ indicate_hidden = yes alignment = left show_age_threshold = 60 - word_wrap = no ignore_newline = no geometry = "300x5-30+20" transparency = 0 diff -Nru dunst-1.5.0/test/functional-tests/dunstrc.progress_bar dunst-1.8.1/test/functional-tests/dunstrc.progress_bar --- dunst-1.5.0/test/functional-tests/dunstrc.progress_bar 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/test/functional-tests/dunstrc.progress_bar 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,25 @@ +[urgency_low] + background = "#222222" + foreground = "#888888" + timeout = 10 + +[urgency_normal] + background = "#285577" + foreground = "#ffffff" + timeout = 10 + +[urgency_critical] + background = "#900000" + foreground = "#ffffff" + timeout = 0 + +[global] + font = Monospace 8 + allow_markup = yes + format = "<b>%s</b>\n%b" + geometry = "0x5-30+20" + icon_position = left + progress_bar_min_width = 100 + progress_bar_max_width = 200 + progress_bar_frame_width = 5 + progress_bar_height = 30 diff -Nru dunst-1.5.0/test/functional-tests/dunstrc.run_script dunst-1.8.1/test/functional-tests/dunstrc.run_script --- dunst-1.5.0/test/functional-tests/dunstrc.run_script 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/functional-tests/dunstrc.run_script 2022-03-02 10:55:25.000000000 +0000 @@ -1,38 +1,3 @@ -[global] - font = Monospace 8 - allow_markup = yes - format = "<b>%s</b>\n%b" - sort = yes - indicate_hidden = yes - alignment = left - show_age_threshold = 60 - word_wrap = yes - ignore_newline = no - geometry = "300x5-30+20" - transparency = 0 - idle_threshold = 120 - monitor = 0 - follow = mouse - sticky_history = yes - line_height = 0 - separator_height = 2 - padding = 8 - horizontal_padding = 8 - separator_color = frame - startup_notification = false - dmenu = /usr/bin/dmenu -p dunst - browser = /usr/bin/firefox -new-tab - -[frame] - width = 3 - color = "#aaaaaa" - -[shortcuts] - close = ctrl+space - close_all = ctrl+shift+space - history = ctrl+grave - context = ctrl+shift+period - [urgency_low] background = "#222222" foreground = "#888888" diff -Nru dunst-1.5.0/test/functional-tests/dunstrc.show_age dunst-1.8.1/test/functional-tests/dunstrc.show_age --- dunst-1.5.0/test/functional-tests/dunstrc.show_age 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/functional-tests/dunstrc.show_age 2022-03-02 10:55:25.000000000 +0000 @@ -6,7 +6,6 @@ indicate_hidden = yes alignment = left show_age_threshold = 2 - word_wrap = yes ignore_newline = no geometry = "300x5-30+20" transparency = 0 diff -Nru dunst-1.5.0/test/functional-tests/script_test.sh dunst-1.8.1/test/functional-tests/script_test.sh --- dunst-1.5.0/test/functional-tests/script_test.sh 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/functional-tests/script_test.sh 2022-03-02 10:55:25.000000000 +0000 @@ -1,3 +1,3 @@ #!/bin/bash -notify-send "Success" "ooooh yeah" +../../dunstify "Success" "ooooh yeah" diff -Nru dunst-1.5.0/test/functional-tests/test.sh dunst-1.8.1/test/functional-tests/test.sh --- dunst-1.5.0/test/functional-tests/test.sh 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/functional-tests/test.sh 2022-03-02 10:55:25.000000000 +0000 @@ -5,6 +5,17 @@ read key } +function tmp_dunstrc { + cp "$1" dunstrc.tmp + echo -e "\n$2" >> dunstrc.tmp +} + +function start_dunst { + killall dunst + ../../dunst -config $1 & + sleep 0.1 +} + function basic_notifications { ../../dunstify -a "dunst tester" "normal" "<i>italic body</i>" ../../dunstify -a "dunst tester" -u c "critical" "<b>bold body</b>" @@ -34,32 +45,12 @@ killall dunst PATH=".:$PATH" ../../dunst -config dunstrc.run_script & ../../dunstify -a "dunst tester" -u c \ - "Run Script" "After Keypress, 2 other notification should pop up. THis needs notify-send installed" + "Run Script" "After Keypress, 2 other notification should pop up." keypress ../../dunstify -a "dunst tester" -u c "trigger" "this should trigger a notification" keypress } -function ignore_newline { - echo "###################################" - echo "ignore newline" - echo "###################################" - killall dunst - ../../dunst -config dunstrc.ignore_newline_no_wrap & - ../../dunstify -a "dunst tester" -u c "Ignore Newline No Wrap" "There should be no newline anywhere" - ../../dunstify -a "dunst tester" -u c "Th\nis\n\n\n is\n fu\nll of \n" "\nnew\nlines" - basic_notifications - keypress - - killall dunst - ../../dunst -config dunstrc.ignore_newline & - ../../dunstify -a "dunst tester" -u c "Ignore Newline" \ - "The only newlines you should encounter here are wordwraps. That's why I'm so long." - ../../dunstify -a "dunst tester" -u c "Th\nis\n\n\n is\n fu\nll of \n" "\nnew\nlines" - basic_notifications - keypress -} - function replace { echo "###################################" echo "replace" @@ -73,107 +64,182 @@ } -function markup { +function limit { echo "###################################" - echo "markup" + echo "limit" echo "###################################" - killall dunst - ../../dunst -config dunstrc.markup "200x0+10+10" & - ../../dunstify -a "dunst tester" "Markup Tests" -u "c" - ../../dunstify -a "dunst tester" "<b>bold</b> <i>italic</i>" - ../../dunstify -a "dunst tester" "<b>broken markup</i>" + tmp_dunstrc dunstrc.default "notification_limit=4" + start_dunst dunstrc.tmp + ../../dunstify -a "dunst tester" -u c "notification limit = 4" + basic_notifications + rm dunstrc.tmp keypress - killall dunst - ../../dunst -config dunstrc.nomarkup "200x0+10+10" & - ../../dunstify -a "dunst tester" -u c "NO Markup Tests" - ../../dunstify -a "dunst tester" "<b>bold</b><i>italic</i>" - ../../dunstify -a "dunst tester" "<b>broken markup</i>" + tmp_dunstrc dunstrc.default "notification_limit=0" + start_dunst dunstrc.tmp + ../../dunstify -a "dunst tester" -u c "notification limit = 0 (unlimited notifications)" + basic_notifications + rm dunstrc.tmp keypress - } -function corners { +function markup { echo "###################################" - echo "corners" + echo "markup" echo "###################################" killall dunst - ../../dunst -config dunstrc.default -geom "200x0+10+10" & - ../../dunstify -a "dunst tester" -u c "upper left" - basic_notifications + ../../dunst -config dunstrc.default & + ../../dunstify -a "dunst tester" "Markup Tests" -u "c" + ../../dunstify -a "dunst tester" "Title" "<b>bold</b> <i>italic</i>" + ../../dunstify -a "dunst tester" "Title" '<a href="github.com"> Github link </a>' + ../../dunstify -a "dunst tester" "Title" "<b>broken markup</i>" keypress killall dunst - ../../dunst -config dunstrc.default -geom "200x0-10+10" & - ../../dunstify -a "dunst tester" -u c "upper right" - basic_notifications + ../../dunst -config dunstrc.nomarkup & + ../../dunstify -a "dunst tester" -u c "No markup Tests" "Titles shoud still be in bold and body in italics" + ../../dunstify -a "dunst tester" "Title" "<b>bold</b><i>italic</i>" + ../../dunstify -a "dunst tester" "Title" "<b>broken markup</i>" keypress - killall dunst - ../../dunst -config dunstrc.default -geom "200x0-10-10" & - ../../dunstify -a "dunst tester" -u c "lower right" - basic_notifications - keypress +} - killall dunst - ../../dunst -config dunstrc.default -geom "200x0+10-10" & - ../../dunstify -a "dunst tester" -u c "lower left" +function test_origin { + tmp_dunstrc dunstrc.default "origin = $1\n offset = 10x10" + start_dunst dunstrc.tmp + ../../dunstify -a "dunst tester" -u c "$1" basic_notifications + rm dunstrc.tmp keypress - } -function geometry { +function origin { echo "###################################" - echo "geometry" + echo "origin" echo "###################################" - killall dunst - ../../dunst -config dunstrc.default -geom "0x0" & - ../../dunstify -a "dunst tester" -u c "0x0" + test_origin "top-left" + test_origin "top-center" + test_origin "top-right" + test_origin "left-center" + test_origin "center" + test_origin "right-center" + test_origin "bottom-left" + test_origin "bottom-center" + test_origin "bottom-right" +} + +function test_width { + tmp_dunstrc dunstrc.default "width = $1" + start_dunst dunstrc.tmp + ../../dunstify -a "dunst tester" -u c "width = $1" basic_notifications + rm dunstrc.tmp keypress +} +function width { + echo "###################################" + echo "width" + echo "###################################" + test_width "100" + test_width "200" + test_width "400" + test_width "(0,400)" +} - killall dunst - ../../dunst -config dunstrc.default -geom "200x0" & - ../../dunstify -a "dunst tester" -u c "200x0" +function test_height { + tmp_dunstrc dunstrc.default "height = $1" + start_dunst dunstrc.tmp + ../../dunstify -a "dunst tester" -u c "height = $1" + ../../dunstify -a "dunst tester" -u c "Temporibus accusantium libero sequi at nostrum dolor sequi sed. Cum minus reprehenderit voluptatibus laboriosam et et ut. Laudantium blanditiis omnis ipsa rerum quas velit ut. Quae voluptate soluta enim consequatur libero eum similique ad. Veritatis neque consequatur et aperiam quisquam id nostrum. Consequatur voluptas aut ut omnis atque cum perferendis. Possimus laudantium tempore iste qui nemo voluptate quod. Labore totam debitis consectetur amet. Maxime quibusdam ipsum voluptates quod ex nam sunt. Officiis repellat quod maxime cumque tenetur. Veritatis labore aperiam repellendus. Provident dignissimos ducimus voluptates." basic_notifications + rm dunstrc.tmp keypress +} - killall dunst - ../../dunst -config dunstrc.default -geom "200x2" & - ../../dunstify -a "dunst tester" -u c "200x2" - basic_notifications +function test_progress_bar_alignment { + tmp_dunstrc dunstrc.default "progress_bar_horizontal_alignment = $1\n progress_bar_max_width = 200" + start_dunst dunstrc.tmp + ../../dunstify -a "dunst tester" -u c "alignment = $1" + ../../dunstify -h int:value:33 -a "dunst tester" -u n "The progress bar should not be the entire width" + ../../dunstify -h int:value:33 -a "dunst tester" -u c "Short" + rm dunstrc.tmp keypress +} - killall dunst - ../../dunst -config dunstrc.default -geom "200x1" & - ../../dunstify -a "dunst tester" -u c "200x1" - basic_notifications - keypress +function height { + echo "###################################" + echo "height" + echo "###################################" + test_height "50" + test_height "100" + test_height "200" +} +function progress_bar { killall dunst - ../../dunst -config dunstrc.default -geom "0x1" & - ../../dunstify -a "dunst tester" -u c "0x1" - basic_notifications + ../../dunst -config dunstrc.default & + ../../dunstify -h int:value:0 -a "dunst tester" -u c "Progress bar 0%: " + ../../dunstify -h int:value:33 -a "dunst tester" -u c "Progress bar 33%: " + ../../dunstify -h int:value:66 -a "dunst tester" -u c "Progress bar 66%: " + ../../dunstify -h int:value:100 -a "dunst tester" -u c "Progress bar 100%: " keypress - killall dunst - ../../dunst -config dunstrc.default -geom "-300x1" & - ../../dunstify -a "dunst tester" -u c "-300x1" - basic_notifications + ../../dunst -config dunstrc.default & + ../../dunstify -h int:value:33 -a "dunst tester" -u l "Low priority: " + ../../dunstify -h int:value:33 -a "dunst tester" -u n "Normal priority: " + ../../dunstify -h int:value:33 -a "dunst tester" -u c "Critical priority: " keypress - killall dunst - ../../dunst -config dunstrc.default -geom "-300x1-20-20" & - ../../dunstify -a "dunst tester" -u c "-300x1-20-20" - basic_notifications - keypress + ../../dunst -config dunstrc.progress_bar & + ../../dunstify -h int:value:33 -a "dunst tester" -u n "The progress bar should not be the entire width" + ../../dunstify -h int:value:33 -a "dunst tester" -u n "You might also notice height and frame size are changed" + ../../dunstify -h int:value:33 -a "dunst tester" -u c "Short" + keypress + test_progress_bar_alignment "left" + test_progress_bar_alignment "center" + test_progress_bar_alignment "right" +} - killall dunst - ../../dunst -config dunstrc.default -geom "x1" & - ../../dunstify -a "dunst tester" -u c "x1-20-20" "across the screen" - basic_notifications +function icon_position { + padding_cases=( + '0 0 0 no padding' + '15 1 1 vertical' + '1 50 1 horizontal' + '1 1 25 icon ' + ) + + for padding_case in "${padding_cases[@]}"; do + read vertical horizontal icon label <<<"$padding_case" + + padding_settings=" + padding = $vertical + horizontal_padding = $horizontal + text_icon_padding = $icon + " + + tmp_dunstrc dunstrc.icon_position "$padding_settings" + start_dunst dunstrc.tmp + + for position in left top right off; do + for alignment in left center right; do + category="icon-$position-alignment-$alignment" + ../../dunstify -a "dunst tester" --hints string:category:$category -u n "$category"$'\n'"padding emphasis: $label" + done + done + rm dunstrc.tmp + keypress + done +} + +function hide_text { + start_dunst dunstrc.hide_text + ../../dunstify -a "dunst tester" -u c "text not hidden" "You should be able to read me!\nThe next notifications should not have any text." + local hidden_body="If you can read me then hide_text is not working." + ../../dunstify -a "dunst tester" -u l "text hidden" "$hidden_body" + ../../dunstify -a "dunst tester" -h int:value:$((RANDOM%100)) -u l "text hidden + progress bar" "$hidden_body" + ../../dunstify -a "dunst tester" -u n "text hidden + icon" "$hidden_body" + ../../dunstify -a "dunst tester" -h int:value:$((RANDOM%100)) -u n "text hidden + icon + progress bar" "$hidden_body" keypress } @@ -183,13 +249,18 @@ shift done else - geometry - corners + width + height + origin + limit show_age run_script ignore_newline replace markup + progress_bar + icon_position + hide_text fi killall dunst diff -Nru dunst-1.5.0/test/greenest.awk dunst-1.8.1/test/greenest.awk --- dunst-1.5.0/test/greenest.awk 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/test/greenest.awk 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,33 @@ +#!/usr/bin/awk -f +# Copyright (c) 2016 Scott Vokes <vokes.s@gmail.com> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +BEGIN { + GREEN = "\033[32m" + RED = "\033[31m" + YELLOW = "\033[33m" + RESET = "\033[m" +} + +/^PASS/ { sub("PASS", GREEN "PASS" RESET) } +/^SKIP/ { sub("SKIP", YELLOW "SKIP" RESET) } +/^FAIL/ { sub("FAIL", RED "FAIL" RESET) } + +# highlight hexdump difference markers +/^[0-9a-f]/ { + sub("X", GREEN "X" RESET, $2) + gsub("<", GREEN "<" RESET, $0) +} + +{ print($0) } diff -Nru dunst-1.5.0/test/helpers.c dunst-1.8.1/test/helpers.c --- dunst-1.5.0/test/helpers.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/helpers.c 2022-03-02 10:55:25.000000000 +0000 @@ -1,6 +1,8 @@ #include <gdk-pixbuf/gdk-pixbuf.h> #include "helpers.h" +#include "../src/notification.h" +#include "../src/utils.h" GVariant *notification_setup_raw_image(const char *path) { @@ -32,4 +34,37 @@ return hint; } +struct notification *test_notification_uninitialized(const char *name) +{ + struct notification *n = notification_create(); + + n->dbus_client = g_strconcat(":", name, NULL); + n->appname = g_strconcat("app of ", name, NULL); + n->summary = g_strconcat(name, NULL); + n->body = g_strconcat("See, ", name, ", I've got a body for you!", NULL); + + return n; +} + +struct notification *test_notification(const char *name, gint64 timeout) +{ + struct notification *n = test_notification_uninitialized(name); + + notification_init(n); + + n->format = "%s\n%b"; + + if (timeout != -1) + n->timeout = S2US(timeout); + + return n; +} + +struct notification *test_notification_with_icon(const char *name, gint64 timeout) +{ + struct notification *n = test_notification(name, timeout); + n->icon = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + return n; +} + /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/test/helpers.h dunst-1.8.1/test/helpers.h --- dunst-1.5.0/test/helpers.h 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/helpers.h 2022-03-02 10:55:25.000000000 +0000 @@ -4,6 +4,9 @@ #include <glib.h> GVariant *notification_setup_raw_image(const char *path); +struct notification *test_notification_uninitialized(const char *name); +struct notification *test_notification(const char *name, gint64 timeout); +struct notification *test_notification_with_icon(const char *name, gint64 timeout); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/test/icon.c dunst-1.8.1/test/icon.c --- dunst-1.5.0/test/icon.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/icon.c 2022-03-02 10:55:25.000000000 +0000 @@ -1,7 +1,8 @@ #include "../src/icon.c" #include "greatest.h" -#define ICONPREFIX "/data/icons/path" +#define DATAPREFIX "/data" +#define ICONPATH "/data/icons/theme" /* As there are no hints to test if the loaded GdkPixbuf is * read from a PNG or an SVG file, the sample icons in the @@ -12,104 +13,28 @@ extern const char *base; -TEST test_get_pixbuf_from_file_tilde(void) -{ - const char *home = g_get_home_dir(); - const char *iconpath = ICONPREFIX; - - if (0 != strncmp(home, base, strlen(home))) { - SKIPm("Current directory is not a subdirectory from user's home." - " Cannot test iconpath tilde expansion.\n"); - } - - gchar *path = g_build_filename(base, iconpath, "valid", "icon1.svg", NULL); - path = string_replace_at(path, 0, strlen(home), "~"); - - GdkPixbuf *pixbuf = get_pixbuf_from_file(path); - g_clear_pointer(&path, g_free); - - ASSERT(pixbuf); - ASSERTm("The wrong pixbuf is loaded in the icon file.", IS_ICON_SVG(pixbuf)); - g_clear_pointer(&pixbuf, g_object_unref); - PASS(); -} - -TEST test_get_pixbuf_from_file_absolute(void) -{ - const char *iconpath = ICONPREFIX; - - gchar *path = g_build_filename(base, iconpath, "valid", "icon1.svg", NULL); - - GdkPixbuf *pixbuf = get_pixbuf_from_file(path); - g_clear_pointer(&path, g_free); - - ASSERT(pixbuf); - ASSERTm("The wrong pixbuf is loaded in the icon file.", IS_ICON_SVG(pixbuf)); - g_clear_pointer(&pixbuf, g_object_unref); - - PASS(); -} +int scale = 1; +int size = 16; -TEST test_get_pixbuf_from_icon_invalid(void) +TEST test_get_path_from_icon_null(void) { - GdkPixbuf *pixbuf = get_pixbuf_from_icon("invalid"); - ASSERT(pixbuf == NULL); - g_clear_pointer(&pixbuf, g_object_unref); - + char *result = get_path_from_icon_name(NULL, 16); + ASSERT_EQ(result, NULL); PASS(); } -TEST test_get_pixbuf_from_icon_both(void) +TEST test_get_path_from_icon_name_full(void) { - GdkPixbuf *pixbuf = get_pixbuf_from_icon("icon1"); - ASSERT(pixbuf); - ASSERTm("SVG pixbuf hasn't precedence", IS_ICON_SVG(pixbuf)); - g_clear_pointer(&pixbuf, g_object_unref); + const char *iconpath = ICONPATH; - PASS(); -} - -TEST test_get_pixbuf_from_icon_onlysvg(void) -{ - GdkPixbuf *pixbuf = get_pixbuf_from_icon("onlysvg"); - ASSERT(pixbuf); - ASSERTm("SVG pixbuf isn't loaded", IS_ICON_SVG(pixbuf)); - g_clear_pointer(&pixbuf, g_object_unref); - - PASS(); -} - -TEST test_get_pixbuf_from_icon_onlypng(void) -{ - GdkPixbuf *pixbuf = get_pixbuf_from_icon("onlypng"); - ASSERT(pixbuf); - ASSERTm("PNG pixbuf isn't loaded", IS_ICON_PNG(pixbuf)); - g_clear_pointer(&pixbuf, g_object_unref); - - PASS(); -} - -TEST test_get_pixbuf_from_icon_filename(void) -{ - char *icon = g_strconcat(base, "/data/icons/valid.png", NULL); - GdkPixbuf *pixbuf = get_pixbuf_from_icon(icon); - ASSERT(pixbuf); - ASSERTm("PNG pixbuf isn't loaded", IS_ICON_PNG(pixbuf)); - g_clear_pointer(&pixbuf, g_object_unref); - - g_free(icon); - PASS(); -} + gchar *path = g_build_filename(base, iconpath, "valid", "icon1.svg", NULL); -TEST test_get_pixbuf_from_icon_fileuri(void) -{ - char *icon = g_strconcat("file://", base, "/data/icons/valid.svg", NULL); - GdkPixbuf *pixbuf = get_pixbuf_from_icon(icon); - ASSERT(pixbuf); - ASSERTm("SVG pixbuf isn't loaded", IS_ICON_SVG(pixbuf)); - g_clear_pointer(&pixbuf, g_object_unref); + char *result = get_path_from_icon_name(path, size); + ASSERT(result); + ASSERT_STR_EQ(result, path); - g_free(icon); + g_free(path); + g_free(result); PASS(); } @@ -157,39 +82,19 @@ PASS(); } -TEST test_get_pixbuf_from_icon_both_is_scaled(void) -{ - GdkPixbuf *pixbuf = get_pixbuf_from_icon("onlypng"); - ASSERT(pixbuf); - ASSERT_EQ(gdk_pixbuf_get_width(pixbuf), 16); - ASSERT_EQ(gdk_pixbuf_get_height(pixbuf), 16); - g_clear_pointer(&pixbuf, g_object_unref); - - PASS(); -} - SUITE(suite_icon) { - settings.icon_path = g_strconcat( - base, ICONPREFIX "/invalid" - ":", base, ICONPREFIX "/valid" - ":", base, ICONPREFIX "/both", - NULL); - - RUN_TEST(test_get_pixbuf_from_file_tilde); - RUN_TEST(test_get_pixbuf_from_file_absolute); - RUN_TEST(test_get_pixbuf_from_icon_invalid); - RUN_TEST(test_get_pixbuf_from_icon_both); - RUN_TEST(test_get_pixbuf_from_icon_onlysvg); - RUN_TEST(test_get_pixbuf_from_icon_onlypng); - RUN_TEST(test_get_pixbuf_from_icon_filename); - RUN_TEST(test_get_pixbuf_from_icon_fileuri); + // set only valid icons in the path + char *icon_path = g_build_filename(base, DATAPREFIX, NULL); + setenv("XDG_DATA_HOME", icon_path, 1); + printf("Icon path: %s\n", icon_path); + RUN_TEST(test_get_path_from_icon_null); + RUN_TEST(test_get_path_from_icon_name_full); RUN_TEST(test_icon_size_clamp_not_necessary); settings.min_icon_size = 16; settings.max_icon_size = 100; - RUN_TEST(test_get_pixbuf_from_icon_both_is_scaled); RUN_TEST(test_icon_size_clamp_too_small); RUN_TEST(test_icon_size_clamp_not_necessary); RUN_TEST(test_icon_size_clamp_too_big); @@ -209,7 +114,6 @@ settings.min_icon_size = 0; settings.max_icon_size = 0; - - g_clear_pointer(&settings.icon_path, g_free); + g_clear_pointer(&icon_path, g_free); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/test/icon-lookup.c dunst-1.8.1/test/icon-lookup.c --- dunst-1.5.0/test/icon-lookup.c 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/test/icon-lookup.c 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,185 @@ +#include "greatest.h" +#include "../src/icon-lookup.c" +#include "helpers.h" +#include "../src/notification.h" +#include "../src/settings_data.h" + +extern const char *base; +#define ICONPREFIX "data", "icons" + +int setup_test_theme(){ + char *theme_path = g_build_filename(base, ICONPREFIX, NULL); + int theme_index = load_icon_theme_from_dir(theme_path, "theme"); + add_default_theme(theme_index); + g_free(theme_path); + return theme_index; +} + +#define find_icon_test(iconname, size, ...) { \ + char *icon = find_icon_path(iconname, size); \ + char *expected = g_build_filename(base, ICONPREFIX, "theme", __VA_ARGS__, NULL); \ + ASSERTm("Could not find icon", icon); \ + ASSERT_STR_EQ(expected, icon); \ + g_free(expected); \ + g_free(icon); \ +} + +#define find_path_test(path, ...) { \ + char *icon = find_icon_path(path, 42); /* size doesn't matter */ \ + char *expected = g_build_filename(base, ICONPREFIX, "theme", __VA_ARGS__, NULL); \ + ASSERTm("Could not find icon", icon); \ + ASSERT_STR_EQ(expected, icon); \ + g_free(expected); \ + g_free(icon); \ +} + +TEST test_load_theme_from_dir(void) +{ + setup_test_theme(); + free_all_themes(); + PASS(); +} + +TEST test_find_icon(void) +{ + setup_test_theme(); + find_icon_test("edit", 8, "16x16", "actions", "edit.png"); + find_icon_test("edit", 16, "16x16", "actions", "edit.png"); + find_icon_test("edit", 32, "16x16", "actions", "edit.png"); + find_icon_test("edit", 48, "16x16", "actions", "edit.png"); + find_icon_test("edit", 49, "32x32", "actions", "edit.png"); + find_icon_test("edit", 64, "32x32", "actions", "edit.png"); + find_icon_test("preferences", 16, "16x16", "apps", "preferences.png"); + find_icon_test("preferences", 32, "16x16", "apps", "preferences.png"); + free_all_themes(); + PASS(); +} + +TEST test_find_path(void) +{ + setup_test_theme(); + char *path = g_build_filename(base, ICONPREFIX, "theme", "16x16", "actions", "edit.png", NULL); + find_path_test(path, "16x16", "actions", "edit.png"); + char *path2 = g_strconcat("file://", path, NULL); + find_path_test(path2, "16x16", "actions", "edit.png"); + g_free(path2); + g_free(path); + free_all_themes(); + PASS(); +} + + +TEST test_new_icon_overrides_raw_icon(void) { + setup_test_theme(); + + struct notification *n = test_notification_with_icon("new_icon", 10); + struct rule *rule = malloc(sizeof(struct rule)); + *rule = empty_rule; + rule->summary = g_strdup("new_icon"); + rule->new_icon = g_strdup("edit"); + + ASSERT(n->icon); + + void *old_icon = (void*) n->icon; + ASSERT(old_icon == n->icon); + rule_apply(rule, n); + ASSERT(old_icon != n->icon); + /* n->icon = malloc(1); // allocate some data to emulate a raw icon */ + + /* printf("%lu\n", sizeof(n->icon)); */ + + + /* printf("%lu\n", sizeof(n->icon)); */ + + free(n->icon); + n->icon = NULL; + + notification_unref(n); + g_free(rule->summary); + g_free(rule->new_icon); + g_free(rule); + free_all_themes(); + PASS(); +} + +// TODO move this out of the test suite, since this isn't a real test +TEST test_bench_search(void) +{ + printf("Starting benchmark\n"); + // At the time of committing, I get numbers like 0.25 second for 1000 icon lookups + int index = load_icon_theme("Papirus"); + add_default_theme(index); + printf("Benchmarking icons\n"); + clock_t start_time = clock(); + for (int i = 0; i < 1000; i++){ + // icon is part of papirus, right at the end of index.theme + char *icon = find_icon_path("weather-windy-symbolic", 512); + ASSERT(icon); + g_free(icon); + } + double elapsed_time = (double)(clock() - start_time) / CLOCKS_PER_SEC; + printf("Done in %f seconds\n", elapsed_time); + free_all_themes(); + PASS(); +} + +TEST test_bench_multiple_search(void) +{ + printf("Starting benchmark\n"); + // At the time of committing, I get numbers like 2 second for 1000 icon lookups + int index = load_icon_theme("Adwaita"); + add_default_theme(index); + index = load_icon_theme("Papirus"); + add_default_theme(index); + printf("Benchmarking icons\n"); + clock_t start_time = clock(); + for (int i = 0; i < 1000; i++){ + // icon is part of papirus, right at the end of index.theme + char *icon = find_icon_path("view-wrapped-rtl-symbolic", 512); + /* printf("%s\n", icon); */ + ASSERT(icon); + g_free(icon); + } + double elapsed_time = (double)(clock() - start_time) / CLOCKS_PER_SEC; + printf("Done in %f seconds\n", elapsed_time); + free_all_themes(); + PASS(); +} + +TEST test_bench_doesnt_exist(void) +{ + printf("Starting benchmark\n"); + // At the time of committing, I get numbers like 9 seconds for 1000 icon lookups + int index = load_icon_theme("Adwaita"); + add_default_theme(index); + index = load_icon_theme("Papirus"); + add_default_theme(index); + printf("Benchmarking icons\n"); + clock_t start_time = clock(); + for (int i = 0; i < 1000; i++){ + // Icon size is chosen as some common icon size. + char *icon = find_icon_path("doesn't exist", 48); + /* printf("%s\n", icon); */ + ASSERT_FALSE(icon); + g_free(icon); + } + double elapsed_time = (double)(clock() - start_time) / CLOCKS_PER_SEC; + printf("Done in %f seconds\n", elapsed_time); + free_all_themes(); + PASS(); +} + + +SUITE (suite_icon_lookup) +{ + RUN_TEST(test_load_theme_from_dir); + RUN_TEST(test_find_icon); + RUN_TEST(test_find_path); + RUN_TEST(test_new_icon_overrides_raw_icon); + bool bench = false; + if (bench) { + RUN_TEST(test_bench_search); + RUN_TEST(test_bench_multiple_search); + RUN_TEST(test_bench_doesnt_exist); + } +} diff -Nru dunst-1.5.0/test/ini.c dunst-1.8.1/test/ini.c --- dunst-1.5.0/test/ini.c 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/test/ini.c 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,35 @@ +#include "greatest.h" +#include "../src/ini.c" +#include <glib.h> + +extern const char *base; + +TEST test_next_section(void) +{ + char *config_path = g_strconcat(base, "/data/test-ini", NULL); + FILE *config_file = fopen(config_path, "r"); + if (!config_file) { + ASSERTm(false, "Test config file 'data/test-ini' couldn't be opened, failing.\n"); + } + struct ini * ini = load_ini_file(config_file); + ASSERT(ini); + fclose(config_file); + free(config_path); + + const char *section = NULL; + ASSERT_STR_EQ("bool", (section = next_section(ini, section))); + ASSERT_STR_EQ("string", (section = next_section(ini, section))); + ASSERT_STR_EQ("list", (section = next_section(ini, section))); + ASSERT_STR_EQ("path", (section = next_section(ini, section))); + ASSERT_STR_EQ("int", (section = next_section(ini, section))); + ASSERT_STR_EQ("double", (section = next_section(ini, section))); + finish_ini(ini); + free(ini); + PASS(); +} + + +SUITE(suite_ini) +{ + RUN_TEST(test_next_section); +} diff -Nru dunst-1.5.0/test/markup.c dunst-1.8.1/test/markup.c --- dunst-1.5.0/test/markup.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/markup.c 2022-03-02 10:55:25.000000000 +0000 @@ -15,7 +15,7 @@ g_free(ptr); ASSERT_STR_EQ("&", (ptr=markup_strip(g_strdup("&amp;")))); g_free(ptr); - ASSERT_STR_EQ(">A ", (ptr=markup_strip(g_strdup(">A <img> <string")))); + ASSERT_STR_EQ(">A <string", (ptr=markup_strip(g_strdup(">A <img> <string")))); g_free(ptr); PASS(); diff -Nru dunst-1.5.0/test/notification.c dunst-1.8.1/test/notification.c --- dunst-1.5.0/test/notification.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/notification.c 2022-03-02 10:55:25.000000000 +0000 @@ -4,6 +4,7 @@ #include "../src/option_parser.h" #include "../src/settings.h" +#include "../src/icon.h" extern const char *base; @@ -47,9 +48,9 @@ ASSERTm("One of the notifications got corrupted during test", notification_is_duplicate(a, b)); - enum icon_position icon_setting_tmp = settings.icon_position; + enum icon_position icon_setting_tmp = a->icon_position; - settings.icon_position = ICON_OFF; + a->icon_position = ICON_OFF; ASSERT(notification_is_duplicate(a, b)); //Setting pointer to a random value since we are checking for null char *icon_id = b->icon_id; @@ -58,13 +59,15 @@ notification_is_duplicate(a, b)); b->icon_id = icon_id; - settings.icon_position = ICON_LEFT; + a->icon_position = ICON_LEFT; + b->icon_position = ICON_LEFT; CHECK_CALL(test_notification_is_duplicate_field(&(b->icon_id), a, b)); - settings.icon_position = ICON_RIGHT; + a->icon_position = ICON_RIGHT; + b->icon_position = ICON_RIGHT; CHECK_CALL(test_notification_is_duplicate_field(&(b->icon_id), a, b)); - settings.icon_position = icon_setting_tmp; + a->icon_position = icon_setting_tmp; ASSERT(notification_is_duplicate(a, b)); @@ -150,8 +153,8 @@ { struct notification *n = notification_load_icon_with_scaling(20, 100); - ASSERT_EQ(gdk_pixbuf_get_width(n->icon), 20); - ASSERT_EQ(gdk_pixbuf_get_height(n->icon), 20); + ASSERT_EQ(get_icon_width(n->icon, 1), 20); + ASSERT_EQ(get_icon_height(n->icon, 1), 20); notification_unref(n); @@ -163,8 +166,8 @@ { struct notification *n = notification_load_icon_with_scaling(5, 10); - ASSERT_EQ(gdk_pixbuf_get_width(n->icon), 10); - ASSERT_EQ(gdk_pixbuf_get_height(n->icon), 10); + ASSERT_EQ(get_icon_width(n->icon, 1), 10); + ASSERT_EQ(get_icon_height(n->icon, 1), 10); notification_unref(n); @@ -175,8 +178,8 @@ { struct notification *n = notification_load_icon_with_scaling(0, 0); - ASSERT_EQ(gdk_pixbuf_get_width(n->icon), 16); - ASSERT_EQ(gdk_pixbuf_get_height(n->icon), 16); + ASSERT_EQ(get_icon_width(n->icon, 1), 16); + ASSERT_EQ(get_icon_height(n->icon, 1), 16); notification_unref(n); @@ -187,8 +190,8 @@ { struct notification *n = notification_load_icon_with_scaling(10, 20); - ASSERT_EQ(gdk_pixbuf_get_width(n->icon), 16); - ASSERT_EQ(gdk_pixbuf_get_height(n->icon), 16); + ASSERT_EQ(get_icon_width(n->icon, 1), 16); + ASSERT_EQ(get_icon_height(n->icon, 1), 16); notification_unref(n); @@ -232,8 +235,6 @@ SUITE(suite_notification) { cmdline_load(0, NULL); - char *config_path = g_strconcat(base, "/data/dunstrc.default", NULL); - load_settings(config_path); RUN_TEST(test_notification_is_duplicate); RUN_TEST(test_notification_replace_single_field); @@ -275,9 +276,6 @@ g_clear_pointer(&a, notification_unref); RUN_TEST(test_notification_maxlength); - - g_clear_pointer(&settings.icon_path, g_free); - g_free(config_path); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/test/option_parser.c dunst-1.8.1/test/option_parser.c --- dunst-1.5.0/test/option_parser.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/option_parser.c 2022-03-02 10:55:25.000000000 +0000 @@ -1,72 +1,9 @@ #include "../src/option_parser.c" #include "greatest.h" -extern const char *base; - -TEST test_next_section(void) -{ - const char *section = NULL; - ASSERT_STR_EQ("bool", (section = next_section(section))); - ASSERT_STR_EQ("string", (section = next_section(section))); - ASSERT_STR_EQ("list", (section = next_section(section))); - ASSERT_STR_EQ("path", (section = next_section(section))); - ASSERT_STR_EQ("int", (section = next_section(section))); - ASSERT_STR_EQ("double", (section = next_section(section))); - PASS(); -} - -TEST test_ini_get_bool(void) -{ - char *bool_section = "bool"; - ASSERT(ini_get_bool(bool_section, "booltrue", false)); - ASSERT(ini_get_bool(bool_section, "booltrue_capital", false)); - - ASSERT_FALSE(ini_get_bool(bool_section, "boolfalse", true)); - ASSERT_FALSE(ini_get_bool(bool_section, "boolfalse_capital", true)); - - ASSERT(ini_get_bool(bool_section, "boolyes", false)); - ASSERT(ini_get_bool(bool_section, "boolyes_capital", false)); - - ASSERT_FALSE(ini_get_bool(bool_section, "boolno", true)); - ASSERT_FALSE(ini_get_bool(bool_section, "boolno_capital", true)); - - ASSERT(ini_get_bool(bool_section, "boolbin1", false)); - ASSERT_FALSE(ini_get_bool(bool_section, "boolbin0", true)); - - ASSERT(ini_get_bool(bool_section, "boolinvalid", true)); - ASSERT_FALSE(ini_get_bool(bool_section, "boolinvalid", false)); - - ASSERT(ini_get_bool(bool_section, "nonexistent", true)); - ASSERT_FALSE(ini_get_bool(bool_section, "nonexistent", false)); - PASS(); -} - -TEST test_ini_get_string(void) -{ - char *string_section = "string"; - char *ptr; - ASSERT_STR_EQ("A simple string", (ptr = ini_get_string(string_section, "simple", ""))); - free(ptr); - - ASSERT_STR_EQ("A quoted string", (ptr = ini_get_string(string_section, "quoted", ""))); - free(ptr); - ASSERT_STR_EQ("A string \"with quotes\"", (ptr = ini_get_string(string_section, "quoted_with_quotes", ""))); - free(ptr); - ASSERT_STR_EQ("A\" string with quotes\"", (ptr = ini_get_string(string_section, "unquoted_with_quotes", ""))); - free(ptr); - - ASSERT_STR_EQ("String with a", (ptr = ini_get_string(string_section, "quoted_comment", ""))); - free(ptr); - ASSERT_STR_EQ("String with a", (ptr = ini_get_string(string_section, "unquoted_comment", ""))); - free(ptr); - ASSERT_STR_EQ("#ffffff", (ptr = ini_get_string(string_section, "color_comment", ""))); - free(ptr); - - - ASSERT_STR_EQ("default value", (ptr = ini_get_string(string_section, "nonexistent", "default value"))); - free(ptr); - - PASS(); +#define ARRAY_SAME_LENGTH(a, b) { \ + ASSERT_EQm("Test is invalid. Input data has to be the same length",\ + G_N_ELEMENTS(a), G_N_ELEMENTS(b));\ } enum greatest_test_res ARRAY_EQ(char **a, char **b){ @@ -82,94 +19,6 @@ PASS(); } -TEST test_ini_get_list(void) -{ - char *list_section = "list"; - - char *cmp1[] = {"A", "simple", "list", NULL}; - char *cmp2[] = {"A", "list", "with", "spaces", NULL}; - char *cmp3[] = {"A list", "with", "multiword entries", NULL}; - char *cmp4[] = {"A", "quoted", "list", NULL}; - char *cmp5[] = {"A", "list", "\"with quotes\"", NULL}; - char *cmp6[] = {"List", "with", "a", NULL}; - - char **ptr; - CHECK_CALL(ARRAY_EQ(cmp1, (ptr = ini_get_list(list_section, "simple", "")))); - free_string_array(ptr); - - CHECK_CALL(ARRAY_EQ(cmp2, (ptr = ini_get_list(list_section, "spaces", "")))); - free_string_array(ptr); - - CHECK_CALL(ARRAY_EQ(cmp3, (ptr = ini_get_list(list_section, "multiword", "")))); - free_string_array(ptr); - CHECK_CALL(ARRAY_EQ(cmp4, (ptr = ini_get_list(list_section, "quoted", "")))); - free_string_array(ptr); - - CHECK_CALL(ARRAY_EQ(cmp5, (ptr = ini_get_list(list_section, "quoted_with_quotes", "")))); - free_string_array(ptr); - CHECK_CALL(ARRAY_EQ(cmp5, (ptr = ini_get_list(list_section, "unquoted_with_quotes", "")))); - free_string_array(ptr); - - CHECK_CALL(ARRAY_EQ(cmp6, (ptr = ini_get_list(list_section, "quoted_comment", "")))); - free_string_array(ptr); - CHECK_CALL(ARRAY_EQ(cmp6, (ptr = ini_get_list(list_section, "unquoted_comment", "")))); - free_string_array(ptr); - - PASS(); -} - -TEST test_ini_get_path(void) -{ - char *section = "path"; - char *ptr, *exp; - char *home = getenv("HOME"); - - // return default, if nonexistent key - ASSERT_EQ(NULL, (ptr = ini_get_path(section, "nonexistent", NULL))); - ASSERT_STR_EQ("default", (ptr = ini_get_path(section, "nonexistent", "default"))); - g_free(ptr); - - // return path with replaced home - ASSERT_STR_EQ((exp = g_strconcat(home, "/.path/to/tilde", NULL)), - (ptr = ini_get_path(section, "expand_tilde", NULL))); - g_free(ptr); - g_free(exp); - - PASS(); -} - - -TEST test_ini_get_int(void) -{ - char *int_section = "int"; - - ASSERT_EQ(5, ini_get_int(int_section, "simple", 0)); - ASSERT_EQ(-10, ini_get_int(int_section, "negative", 0)); - ASSERT_EQ(2, ini_get_int(int_section, "decimal", 0)); - ASSERT_EQ(7, ini_get_int(int_section, "leading_zeroes", 0)); - ASSERT_EQ(1024, ini_get_int(int_section, "multi_char", 0)); - - ASSERT_EQ(10, ini_get_int(int_section, "nonexistent", 10)); - PASS(); -} - -TEST test_ini_get_double(void) -{ - if (2.3 != atof("2.3")) { - SKIPm("Skipping test_ini_get_double, as it seems we're running under musl+valgrind!"); - } - - char *double_section = "double"; - ASSERT_EQ(1, ini_get_double(double_section, "simple", 0)); - ASSERT_EQ(1.5, ini_get_double(double_section, "decimal", 0)); - ASSERT_EQ(-1.2, ini_get_double(double_section, "negative", 0)); - ASSERT_EQ(0.005, ini_get_double(double_section, "zeroes", 0)); - ASSERT_EQ(3.141592653589793, ini_get_double(double_section, "long", 0)); - - ASSERT_EQ(10.5, ini_get_double(double_section, "nonexistent", 10.5)); - PASS(); -} - TEST test_cmdline_get_path(void) { char *ptr, *exp; @@ -209,11 +58,11 @@ char *cmp3[] = {"A", "default", "list", NULL}; CHECK_CALL(ARRAY_EQ(cmp1, (ptr = cmdline_get_list("-list", "", "")))); - free_string_array(ptr); + g_strfreev(ptr); CHECK_CALL(ARRAY_EQ(cmp2, (ptr = cmdline_get_list("-list2", "", "")))); - free_string_array(ptr); + g_strfreev(ptr); CHECK_CALL(ARRAY_EQ(cmp3, (ptr = cmdline_get_list("-nonexistent", "A, default, list", "")))); - free_string_array(ptr); + g_strfreev(ptr); PASS(); } @@ -267,140 +116,638 @@ PASS(); } -TEST test_option_get_string(void) +TEST test_string_to_int(void) { - char *string_section = "string"; - char *ptr; + int val = -123; + const char* inputs[] = { + "0", + "1", + "-1", + "12345678", + "-12345678" + }; + const int results[] = { + 0, + 1, + -1, + 12345678, + -12345678 + }; + + ARRAY_SAME_LENGTH(inputs, results); + + struct setting s; + s.type = TYPE_INT; + + char buf[50]; + for (int i = 0; i < G_N_ELEMENTS(inputs); i++) { + sprintf(buf, "Failed in round %i", i); + ASSERTm(buf, set_from_string(&val, s, inputs[i])); + ASSERT_EQm(buf, val, results[i]); + } + PASS(); +} - ASSERT_STR_EQ("A simple string", (ptr =option_get_string(string_section, "simple", "-nonexistent", "", ""))); - free(ptr); - ASSERT_STR_EQ("Single_word_string", (ptr = option_get_string(string_section, "simple", "-str/-s", "", ""))); - free(ptr); - ASSERT_STR_EQ("A simple string from the cmdline", (ptr = option_get_string(string_section, "simple", "-string", "", ""))); - free(ptr); - ASSERT_STR_EQ("A simple string from the cmdline", (ptr = option_get_string(string_section, "simple", "-string/-s", "", ""))); - free(ptr); - ASSERT_STR_EQ("Single_word_string", (ptr = option_get_string(string_section, "simple", "-s", "", ""))); - free(ptr); - ASSERT_STR_EQ("Default", (ptr = option_get_string(string_section, "nonexistent", "-nonexistent", "Default", ""))); - free(ptr); +TEST test_string_to_int_invalid(void) +{ + int val = -123; + const char* inputs[] = { + "a0", + "something", + "x674asdf", + "-dsf4544asdf", + "0x145", + "64a567", + "(5678)", + "5678)", + }; + + struct setting s; + s.type = TYPE_INT; + s.name = "test_int"; + + char buf[50]; + for (int i = 0; i < G_N_ELEMENTS(inputs); i++) { + sprintf(buf, "Failed in round %i", i); + ASSERT_FALSEm(buf, set_from_string(&val, s, inputs[i])); + } + ASSERT_EQm("Value should not be changed for invalid ints", val, -123); PASS(); } -TEST test_option_get_list(void) +TEST test_string_to_double(void) { - char *list_section = "list"; - char **ptr; + if (2.3 != atof("2.3")) { + SKIPm("Skipping test_string_to_double, as it seems we're running under musl+valgrind!"); + } - char *cmp1[] = {"A", "simple", "list", NULL}; - char *cmp2[] = {"A", "list", "with", "spaces", NULL}; - char *cmp3[] = {"A", "simple", "list", "from", "the", "cmdline", NULL}; - char *cmp4[] = {"A", "default", "list", NULL}; + double val = -100.0; + const char* inputs[] = { + "0", + "1", + "-1", + "45.8", + "-45.8" + }; + const double results[] = { + 0, + 1, + -1, + 45.8, + -45.8 + }; + struct setting s; + s.type = TYPE_DOUBLE; + + char buf[50]; + for (int i = 0; i < G_N_ELEMENTS(inputs); i++) { + sprintf(buf, "Failed in round %i", i); + ASSERTm(buf, set_from_string(&val, s, inputs[i])); + ASSERT_EQm(buf, val, results[i]); + } + PASS(); +} - CHECK_CALL(ARRAY_EQ(cmp1, (ptr = option_get_list(list_section, "simple", "-nonexistent", "", "")))); - free_string_array(ptr); - CHECK_CALL(ARRAY_EQ(cmp2, (ptr = option_get_list(list_section, "quoted", "-list2", "", "")))); - free_string_array(ptr); - CHECK_CALL(ARRAY_EQ(cmp3, (ptr = option_get_list(list_section, "simple", "-list", "", "")))); - free_string_array(ptr); - CHECK_CALL(ARRAY_EQ(cmp3, (ptr = option_get_list(list_section, "simple", "-list/-l", "", "")))); - free_string_array(ptr); - CHECK_CALL(ARRAY_EQ(cmp4, (ptr = option_get_list(list_section, "nonexistent", "-nonexistent", "A, default, list", "")))); - free_string_array(ptr); +TEST test_string_to_double_invalid(void) +{ + double val = -100.0; + const char* inputs[] = { + "a0", + "something", + "x1234asdf", + "-dsf1234asdf", + "1234a567", + "12.34a567", + "56.7.1", + }; + + struct setting s; + s.type = TYPE_DOUBLE; + s.name = "test_double"; + + char buf[50]; + for (int i = 0; i < G_N_ELEMENTS(inputs); i++) { + sprintf(buf, "Failed in round %i", i); + ASSERT_FALSEm(buf, set_from_string(&val, s, inputs[i])); + } + ASSERT_EQm("Value should not be changed for invalid doubles", val, -100.0); PASS(); } -TEST test_option_get_path(void) +TEST test_string_to_boolean(void) { - char *section = "path"; - char *ptr, *exp; - char *home = getenv("HOME"); + bool val; - // invalid ini, invalid cmdline - ASSERT_EQ(NULL, (ptr = option_get_path(section, "nonexistent", "-nonexistent", NULL, "desc"))); - ASSERT_STR_EQ("default", (ptr = option_get_path(section, "nonexistent", "-nonexistent", "default", "desc"))); - free(ptr); + struct setting s; + s.type = TYPE_CUSTOM; + s.parser = string_parse_bool; + s.parser_data = boolean_enum_data; + s.value = &val; + + const char* inputs[] = { + "0", + "1", + "true", + "True", + "false", + "off" + }; + const int results[] = { + 0, + 1, + 1, + 1, + 0, + 0 + }; + + ARRAY_SAME_LENGTH(inputs, results); + + char buf[50]; + for (int i = 0; i < G_N_ELEMENTS(inputs); i++) { + sprintf(buf, "Failed in round %i", i); + ASSERTm(buf, set_from_string(&val, s, inputs[i])); + sprintf(buf, "Failed in round %i. %i should be %i", i, val, results[i]); + ASSERT_EQm(buf, val, results[i]); + } + PASS(); +} - // valid ini, invalid cmdline - ASSERT_STR_EQ((exp = g_strconcat(home, "/.path/to/tilde", NULL)), - (ptr = option_get_path(section, "expand_tilde", "-nonexistent", NULL, "desc"))); - g_free(exp); - g_free(ptr); +TEST test_string_to_boolean_invalid(void) +{ + bool val = true; - // valid ini, valid cmdline - ASSERT_STR_EQ((exp = g_strconcat(home, "/path/from/cmdline", NULL)), - (ptr = option_get_path(section, "expand_tilde", "-path", NULL, "desc"))); - g_free(exp); - g_free(ptr); + struct setting s = {0}; + s.type = TYPE_CUSTOM; + s.parser = string_parse_bool; + s.parser_data = boolean_enum_data; + s.value = &val; + s.name = "test_boolean"; + + const char* invalid_inputs[] = { + "-1", + "123", + "something", + "else", + }; + + char buf[50]; + + for (int i = 0; i < G_N_ELEMENTS(invalid_inputs); i++) { + sprintf(buf, "Failed in round %i", i); + bool success = set_from_string(&val, s, invalid_inputs[i]); + ASSERT_FALSEm(buf, success); + } + ASSERT_EQm("Boolean should not change from invalid values", val, true); + PASS(); +} - // invalid ini, valid cmdline - ASSERT_STR_EQ((exp = g_strconcat(home, "/path/from/cmdline", NULL)), - (ptr = option_get_path(section, "nonexistent", "-path", NULL, "desc"))); - g_free(exp); - g_free(ptr); +TEST test_string_to_enum(void) +{ + int val = -123; + struct setting s; + s.type = TYPE_CUSTOM; + s.value = &val; + s.parser = string_parse_enum; + s.parser_data = ellipsize_enum_data; + + char buf[50]; + + // do not go until last element, since it's ENUM_END (all 0) + for (int i = 0; i < G_N_ELEMENTS(ellipsize_enum_data)-1; i++) { + sprintf(buf, "Failed in round %i", i); + ASSERTm(buf, set_from_string(&val, s, ellipsize_enum_data[i].string)); + ASSERT_EQm(buf, val, ellipsize_enum_data[i].enum_value); + } PASS(); } -TEST test_option_get_int(void) +TEST test_string_to_enum_invalid(void) { - char *int_section = "int"; - ASSERT_EQ(3, option_get_int(int_section, "negative", "-int", 0, "")); - ASSERT_EQ(2, option_get_int(int_section, "simple", "-int2/-i", 0, "")); - ASSERT_EQ(-7, option_get_int(int_section, "decimal", "-negative", 0, "")); - ASSERT_EQ(4, option_get_int(int_section, "simple", "-zeroes", 0, "")); - ASSERT_EQ(2, option_get_int(int_section, "simple", "-intdecim", 0, "")); + int val = -123; - ASSERT_EQ(5, option_get_int(int_section, "simple", "-nonexistent", 0, "")); - ASSERT_EQ(-10, option_get_int(int_section, "negative", "-nonexistent", 0, "")); - ASSERT_EQ(2, option_get_int(int_section, "decimal", "-nonexistent", 0, "")); - ASSERT_EQ(7, option_get_int(int_section, "leading_zeroes", "-nonexistent", 0, "")); - ASSERT_EQ(1024, option_get_int(int_section, "multi_char", "-nonexistent", 0, "")); + struct setting s; + s.name = "test_enum"; + s.type = TYPE_CUSTOM; + s.value = &val; + s.parser = string_parse_enum; + s.parser_data = ellipsize_enum_data; + + const char* invalid_inputs[] = { + "0", + "1", + "-1", + "something", + "else" + }; + + char buf[50]; + + for (int i = 0; i < G_N_ELEMENTS(invalid_inputs); i++) { + sprintf(buf, "Failed in round %i", i); + ASSERT_FALSEm(buf, set_from_string(&val, s, invalid_inputs[i])); + } + ASSERT_EQm("Enum should not change from invalid values", val, -123); + PASS(); +} - ASSERT_EQ(3, option_get_int(int_section, "nonexistent", "-nonexistent", 3, "")); +int get_list_len(const enum mouse_action *in) { + int len = 0; + while (in[len] != MOUSE_ACTION_END) + len++; + return len; +} + +TEST test_string_to_list(void) +{ + enum mouse_action *val = NULL; + + struct setting s; + s.type = TYPE_LIST; + s.value = &val; + s.parser_data = GINT_TO_POINTER(MOUSE_LIST); + + const char* inputs[] = { + "close_current", + "none", + "none, close_current", + "close_all,close_current", + "close_all,close_current,close_all", + }; + const enum mouse_action results[][4] = { + {MOUSE_CLOSE_CURRENT, MOUSE_ACTION_END}, + {MOUSE_NONE, MOUSE_ACTION_END}, + {MOUSE_NONE, MOUSE_CLOSE_CURRENT, MOUSE_ACTION_END}, + {MOUSE_CLOSE_ALL, MOUSE_CLOSE_CURRENT, MOUSE_ACTION_END}, + {MOUSE_CLOSE_ALL, MOUSE_CLOSE_CURRENT, MOUSE_CLOSE_ALL, MOUSE_ACTION_END}, + }; + + char buf[50]; + for (int i = 0; i < G_N_ELEMENTS(inputs); i++) { + sprintf(buf, "Failed in round %i", i); + ASSERTm(buf, set_from_string(s.value, s, inputs[i])); + ASSERT_EQm(buf, get_list_len(val), get_list_len(results[i])); + for (int j = 0; val[j] != MOUSE_ACTION_END; j++){ + sprintf(buf, "Failed in round %i, element %i. Is %i, should be %i", i, j, val[j], results[i][j]); + ASSERT_EQm(buf, val[j], results[i][j]); + } + } + free(val); PASS(); } -TEST test_option_get_double(void) +TEST test_string_to_list_invalid(void) { - if (2.3 != atof("2.3")) { - SKIPm("Skipping test_option_get_double, as it seems we're running under musl+valgrind!"); + enum mouse_action val_initial[] = {123, MOUSE_ACTION_END}; + enum mouse_action *val; + + // set the list to some initial value + int len = get_list_len(val_initial); + val = g_malloc_n(len+1, sizeof(enum mouse_action)); + for (int i = 0; i < len + 1; i++) { + val[i] = val_initial[i]; + } + + struct setting s; + s.name = "test_list"; + s.type = TYPE_LIST; + s.value = &val; + s.parser_data = GINT_TO_POINTER(MOUSE_LIST); + + const char* invalid_inputs[] = { + "0", + "1", + "-1", + "something", + "else" + "close_all,close_current,close_all,invalid", + "close_all,invalid,close_current,close_all", + }; + + char buf[256]; + + for (int i = 0; i < G_N_ELEMENTS(invalid_inputs); i++) { + sprintf(buf, "Failed in round %i", i); + ASSERT_FALSEm(buf, set_from_string(&val, s, invalid_inputs[i])); } + sprintf(buf,"List should not change from invalid values. Expected length %i, got %i", len, get_list_len(val)); + ASSERT_EQm(buf, get_list_len(val), len); + ASSERT_EQm("List should not change from invalid values", (int) val[0], 123); + g_free(val); + PASS(); +} - char *double_section = "double"; - ASSERT_EQ(2, option_get_double(double_section, "simple", "-simple_double", 0, "")); - ASSERT_EQ(5.2, option_get_double(double_section, "simple", "-double", 0, "")); - ASSERT_EQ(0.005, option_get_double(double_section, "zeroes", "-nonexistent", 0, "")); - ASSERT_EQ(10.5, option_get_double(double_section, "nonexistent", "-nonexistent", 10.5, "")); +TEST test_string_to_time(void) +{ + gint64 val; + struct setting s; + s.type = TYPE_TIME; + s.value = &val; + s.name = "test_time"; + + const char* inputs[] = { + "-1", + "0", + "1", + "3s", + "100ms", + "2m", + }; + const int results[] = { + S2US(-1), + S2US(0), + S2US(1), + S2US(3), + 100 * 1000, + S2US(120), + }; + + ARRAY_SAME_LENGTH(inputs, results); + + char buf[50]; + for (int i = 0; i < G_N_ELEMENTS(inputs); i++) { + sprintf(buf, "Failed in round %i", i); + ASSERTm(buf, set_from_string(&val, s, inputs[i])); + sprintf(buf, "Failed in round %i. %li should be %i", i, val, results[i]); + ASSERT_EQm(buf, val, results[i]); + } PASS(); } -TEST test_option_get_bool(void) +TEST test_string_to_time_invalid(void) { - char *bool_section = "bool"; - ASSERT(option_get_bool(bool_section, "boolfalse", "-bool/-b", false, "")); - ASSERT(option_get_bool(bool_section, "boolbin1", "-nonexistent", false, "")); - ASSERT_FALSE(option_get_bool(bool_section, "boolbin0", "-nonexistent", false, "")); - ASSERT_FALSE(option_get_bool(bool_section, "nonexistent", "-nonexistent", false, "")); + gint64 val = 1234; + struct setting s; + s.type = TYPE_TIME; + s.value = &val; + + const char* invalid_inputs[] = { + // -1 is allowed, but only without suffix + "-1s", + "-1ms", + "-2", + "-4s", + "-2ms", + "3basdf", + "100asdf", + "anything", + "s", + }; + + char buf[50]; + for (int i = 0; i < G_N_ELEMENTS(invalid_inputs); i++) { + sprintf(buf, "Failed in round %i", i); + ASSERT_FALSEm(buf, set_from_string(&val, s, invalid_inputs[i])); + } + ASSERT_EQm("Time should not change from invalid values", val, 1234); + PASS(); +} + +TEST test_string_to_path(void) +{ + char *val = NULL; + char **val2 = NULL; + struct setting s; + s.type = TYPE_PATH; + s.value = &val; + s.name = "test_path"; + s.parser_data = &val2; + + const char* inputs[] = { + "/bin/something", + "something", + "/path/path/path/", + "/path/p argument", + "p with multiple arguments", + "~/p/p", + }; + + char *expanded_home = g_strconcat(user_get_home(), "/", "p/p", NULL); + const char* results[] = { + "/bin/something", + "something", + "/path/path/path/", + "/path/p argument", + "p with multiple arguments", + expanded_home, + }; + + const char* results2[][5] = { + {"/bin/something", NULL}, + {"something", NULL}, + {"/path/path/path/", NULL}, + {"/path/p", "argument", NULL}, + {"p", "with", "multiple", "arguments", NULL}, + {expanded_home}, + }; + + ARRAY_SAME_LENGTH(inputs, results); + ARRAY_SAME_LENGTH(inputs, results2); + + char buf[256]; + for (int i = 0; i < G_N_ELEMENTS(inputs); i++) { + sprintf(buf, "Failed in round %i", i); + ASSERTm(buf, set_from_string(&val, s, inputs[i])); + sprintf(buf, "Failed in round %i. %s should be %s", i, val, results[i]); + ASSERTm(buf, STR_EQ(val, results[i])); + for (int j = 0; results2[i][j] != NULL; j++) { + ASSERT_STR_EQ(results2[i][j], val2[j]); + } + } + + g_free(val); + g_free(expanded_home); + g_strfreev(val2); + PASS(); +} + +TEST test_string_to_sepcolor(void) +{ + struct separator_color_data val = {0}; + struct setting s; + s.type = TYPE_CUSTOM; + s.value = &val; + s.name = "test_sepcolor"; + s.parser = string_parse_sepcolor; + s.parser_data = sep_color_enum_data; + + const char* inputs[] = { + "auto", + "foreground", + "frame", + "#123456", + "#ab123c", + "#AB123C", + }; + + const struct separator_color_data results[] = { + {SEP_AUTO, NULL}, + {SEP_FOREGROUND, NULL}, + {SEP_FRAME, NULL}, + {SEP_CUSTOM, "#123456"}, + {SEP_CUSTOM, "#ab123c"}, + {SEP_CUSTOM, "#AB123C"}, + }; + + ARRAY_SAME_LENGTH(inputs, results); + + char buf[50]; + for (int i = 0; i < G_N_ELEMENTS(inputs); i++) { + sprintf(buf, "Failed in round %i. Expected %i, got %i", i, results[i].type, val.type); + ASSERTm(buf, set_from_string(&val, s, inputs[i])); + ASSERT_EQm(buf, results[i].type, val.type); + sprintf(buf, "Failed in round %i. Expected %s, got %s", i, results[i].sep_color, val.sep_color); + ASSERTm(buf, STR_EQ(results[i].sep_color, val.sep_color)); + } + + g_free(val.sep_color); + PASS(); +} + +TEST test_string_to_sepcolor_invalid(void) +{ + struct separator_color_data val = {123, "test123"}; + struct setting s; + s.type = TYPE_CUSTOM; + s.value = &val; + s.name = "test_sepcolor"; + s.parser = string_parse_sepcolor; + s.parser_data = sep_color_enum_data; + + const char* inputs[] = { + "", + "f00reground", + "fraame", + "123456", + "#ab", + + // TODO detect these mistakes as well + /* "#12456", */ + /* "#gg123c", */ + /* "#AB123C123212", */ + }; + + char buf[50]; + for (int i = 0; i < G_N_ELEMENTS(inputs); i++) { + sprintf(buf, "Failed in round %i.", i); + ASSERT_FALSEm(buf, set_from_string(&val, s, inputs[i])); + } + + ASSERT_EQm("Sep color shouldn't changed from invalid inputs", 123, (int) val.type); + ASSERT_STR_EQm("Sep color shouldn't changed from invalid inputs", "test123", val.sep_color); + PASS(); +} + +TEST test_string_to_length(void) +{ + struct length val = {0}; + struct setting s; + s.type = TYPE_LENGTH; + s.value = &val.min; + s.name = "test_length"; + s.parser = NULL; + s.parser_data = NULL; + + const char* inputs[] = { + "123", + "(123, 1234)", + "( , )", + "(234, )", + "(, 123)", + }; + + const struct length results[] = { + { 123, 123 }, + /* { 123, 123 }, */ + { 123, 1234 }, + /* { -123, 1234 }, */ + /* { -1234, 123 }, */ + /* { -1234, -123 }, */ + /* { -123, -1 }, */ + { 0, INT_MAX }, + { 234, INT_MAX }, + { 0, 123 }, + }; + + ARRAY_SAME_LENGTH(inputs, results); + + char buf[500]; + for (int i = 0; i < G_N_ELEMENTS(inputs); i++) { + sprintf(buf, "Failed in round %i.", i); + ASSERTm(buf, set_from_string(&val, s, inputs[i])); + sprintf(buf, "Failed in round %i. Expected min to be %i, got %i", i, results[i].min, val.min); + ASSERT_EQm(buf, results[i].min, val.min); + sprintf(buf, "Failed in round %i. Expected max to be %i, got %i", i, results[i].max, val.max); + ASSERT_EQm(buf, results[i].max, val.max); + } + + PASS(); +} + +TEST test_string_to_length_invalid(void) +{ + struct length val = {-123, -1234}; + struct setting s; + s.type = TYPE_LENGTH; + s.value = &val.min; + s.name = "test_length"; + s.parser = NULL; + s.parser_data = NULL; + + const char* inputs[] = { + "", + "f00reground", + "fraame", + "asb", + "#ab", + "(456", + "456)", + "(456, 567", + "456, 567", + "456, 567)", + "-456", + "-1", + "(-123, 1234)", // Negative values + "(123, -1234)", + "(-123, -1234)", + "(-123, )", + "(123)", + "(123, 122)", // invalid order + }; + + char buf[50]; + for (int i = 0; i < G_N_ELEMENTS(inputs); i++) { + sprintf(buf, "Failed in round %i.", i); + ASSERT_FALSEm(buf, set_from_string(&val, s, inputs[i])); + } + + ASSERT_EQm("Length shouldn't change from invalid inputs", -123, val.min); + ASSERT_EQm("Length shouldn't change from invalid inputs", -1234, val.max); + PASS(); +} + +#define TEST_ENUM(t) { \ +ASSERT_EQ(sizeof(t), sizeof(int)); \ +} + +// The settings code relies on the enums being the same size as an int. If +// they're bigger it's not a big problem, but if they're smaller, other parts +// of the memory may be overwritten. +TEST test_enum_size(void) +{ + TEST_ENUM(enum markup_mode); + TEST_ENUM(enum alignment); + TEST_ENUM(enum icon_position); + TEST_ENUM(enum vertical_alignment); + TEST_ENUM(enum follow_mode); + TEST_ENUM(enum mouse_action ); + TEST_ENUM(enum zwlr_layer_shell_v1_layer); PASS(); } SUITE(suite_option_parser) { - char *config_path = g_strconcat(base, "/data/test-ini", NULL); - FILE *config_file = fopen(config_path, "r"); - if (!config_file) { - fputs("\nTest config file 'data/test-ini' couldn't be opened, failing.\n", stderr); - exit(1); - } - load_ini_file(config_file); - RUN_TEST(test_next_section); - RUN_TEST(test_ini_get_bool); - RUN_TEST(test_ini_get_string); - RUN_TEST(test_ini_get_list); - RUN_TEST(test_ini_get_path); - RUN_TEST(test_ini_get_int); - RUN_TEST(test_ini_get_double); char cmdline[] = "dunst -bool -b " "-string \"A simple string from the cmdline\" -s Single_word_string " "-list A,simple,list,from,the,cmdline -list2 \"A, list, with, spaces\" " @@ -420,16 +767,31 @@ RUN_TEST(test_cmdline_get_bool); RUN_TEST(test_cmdline_create_usage); - RUN_TEST(test_option_get_string); - RUN_TEST(test_option_get_list); - RUN_TEST(test_option_get_path); - RUN_TEST(test_option_get_int); - RUN_TEST(test_option_get_double); - RUN_TEST(test_option_get_bool); - g_free(config_path); - free_ini(); + // These test try out the parsing of set_from_string for different + // config types. + // Non-stripped strings should not be passed to set_from_string. These + // are normally stripped out by the ini parser. + RUN_TEST(test_string_to_int); + RUN_TEST(test_string_to_int_invalid); + RUN_TEST(test_string_to_double); + RUN_TEST(test_string_to_double_invalid); + RUN_TEST(test_string_to_enum); + RUN_TEST(test_string_to_enum_invalid); + RUN_TEST(test_string_to_boolean); + RUN_TEST(test_string_to_boolean_invalid); + RUN_TEST(test_string_to_list); + RUN_TEST(test_string_to_list_invalid); + RUN_TEST(test_string_to_time); + RUN_TEST(test_string_to_time_invalid); + RUN_TEST(test_string_to_path); + // Paths are now almost always considered valid + RUN_TEST(test_string_to_sepcolor); + RUN_TEST(test_string_to_sepcolor_invalid); + RUN_TEST(test_enum_size); + RUN_TEST(test_string_to_length); + RUN_TEST(test_string_to_length_invalid); + g_strfreev(argv); - fclose(config_file); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/test/queues.c dunst-1.8.1/test/queues.c --- dunst-1.5.0/test/queues.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/queues.c 2022-03-02 10:55:25.000000000 +0000 @@ -5,6 +5,7 @@ #include "greatest.h" #include "queues.h" +#include "helpers.h" struct notification *queues_debug_find_notification_by_id(int id) { @@ -234,6 +235,72 @@ PASS(); } +TEST test_queue_notification_skip_display_redisplayed_by_random_id(void) { + // breaks if the notification_buffer_size is above 5, + // possible bug?????? + size_t notification_buffer_size = 5; + + struct notification *n[notification_buffer_size]; + bool is_n_popped[notification_buffer_size]; + + // insert notifications + char string[128]; + for(size_t i=0; i<notification_buffer_size; i++) { + snprintf(string, 128, "n%08lu", i+1); + + n[i] = test_notification(string, -1); + n[i]->skip_display = true; + n[i]->id = i+1; + + // generate a noisy pop table + is_n_popped[i] = rand() > RAND_MAX/4; + } + + queues_init(); + for(size_t i=0; i<notification_buffer_size; i++) { + queues_notification_insert(n[i]); + } + + QUEUE_LEN_ALL(notification_buffer_size, 0, 0); + + // update notification event + queues_update(STATUS_NORMAL); + + QUEUE_LEN_ALL(0, 0, notification_buffer_size); + + // pop according to the pop table + size_t popped_notification_count = 0; + for(size_t i=0; i<notification_buffer_size; i++) { + if(is_n_popped[i]) { + queues_history_pop_by_id(i+1); + popped_notification_count++; + queues_update(STATUS_NORMAL); + } + } + + // test whether the notifications were unpopped properly + QUEUE_LEN_ALL(0, popped_notification_count, + notification_buffer_size-popped_notification_count); + + queues_update(STATUS_NORMAL); + // check if any of the notifications got moved + for(size_t i=0; i<notification_buffer_size; i++) { + if(is_n_popped[i]) { + QUEUE_CONTAINSm("A skip display notification should stay in displayed " + "queue when it got pulled out of history queue", + DISP, n[i]); + } else { + QUEUE_CONTAINSm("A skip display notification that didn't get popped" + "out of history should've stayed there", + HIST, n[i]); + } + } + + queues_teardown(); + + PASS(); +} + TEST test_queue_history_overfull(void) { settings.history_length = 10; @@ -266,7 +333,7 @@ { settings.history_length = 5; settings.indicate_hidden = false; - settings.geometry.h = 0; + settings.notification_limit = 0; queues_init(); @@ -461,8 +528,8 @@ queues_init(); n1 = test_notification("n1", 1); - n2 = test_notification("n2", 1); - n3 = test_notification("n3", 1); + n2 = test_notification("n1", 1); + n3 = test_notification("n1", 1); n1->stack_tag = g_strdup(stacktag); n2->stack_tag = g_strdup(stacktag); n3->stack_tag = g_strdup(stacktag); @@ -484,9 +551,70 @@ PASS(); } +TEST test_queue_different_stacktag(void) +{ + const char *stacktag = "asdf"; + const char *stacktag2 = "something else"; + struct notification *n1, *n2, *n3; + settings.stack_duplicates = false; + + queues_init(); + + n1 = test_notification("n1", 1); + n2 = test_notification("n1", 1); + n3 = test_notification("n1", 1); + n1->stack_tag = g_strdup(stacktag); + n2->stack_tag = g_strdup(stacktag); + n3->stack_tag = g_strdup(stacktag2); + + queues_notification_insert(n1); + QUEUE_LEN_ALL(1, 0, 0); + + notification_ref(n1); + queues_notification_insert(n2); + NOT_LAST(n1); + + queues_notification_insert(n3); + queues_update(STATUS_NORMAL); + printf("queue %i\n",g_queue_get_length(QUEUE(HIST))); + QUEUE_LEN_ALL(0, 2, 0); + + queues_teardown(); + PASS(); +} + +TEST test_queue_stacktag_different_appid(void) +{ + const char *stacktag = "THIS IS A SUPER WIERD STACK TAG"; + struct notification *n1, *n2, *n3; + + queues_init(); + + n1 = test_notification("n1", 1); + n2 = test_notification("n2", 1); + n3 = test_notification("n2", 1); + n1->stack_tag = g_strdup(stacktag); + n2->stack_tag = g_strdup(stacktag); + n3->stack_tag = g_strdup(stacktag); + + queues_notification_insert(n1); + QUEUE_LEN_ALL(1, 0, 0); + + queues_notification_insert(n2); + + notification_ref(n2); + queues_update(STATUS_NORMAL); + queues_notification_insert(n3); + QUEUE_LEN_ALL(0, 2, 0); + NOT_LAST(n2); + + queues_teardown(); + PASS(); +} + TEST test_queue_timeout(void) { - settings.geometry.h = 5; + settings.notification_limit = 5; struct notification *n1, *n2, *n3; queues_init(); @@ -527,7 +655,7 @@ TEST test_queues_update_fullscreen(void) { - settings.geometry.h = 5; + settings.notification_limit = 5; struct notification *n_show, *n_dela, *n_push; queues_init(); @@ -564,7 +692,7 @@ TEST test_queues_update_paused(void) { - settings.geometry.h = 5; + settings.notification_limit = 5; struct notification *n1, *n2, *n3; queues_init(); @@ -593,7 +721,7 @@ TEST test_queues_update_seeping(void) { - settings.geometry.h = 5; + settings.notification_limit = 5; settings.sort = true; settings.indicate_hidden = false; struct notification *nl1, *nl2, *nl3, *nl4, *nl5; @@ -656,7 +784,7 @@ TEST test_queues_update_xmore(void) { settings.indicate_hidden = true; - settings.geometry.h = 4; + settings.notification_limit = 4; struct notification *n1, *n2, *n3, *n4, *n5; queues_init(); @@ -689,7 +817,7 @@ { // Test 3 notifications during fullscreen and only the one // with the lowest priority is eligible to get shown - settings.geometry.h = 4; + settings.notification_limit = 4; struct notification *n1, *n2, *n3; queues_init(); @@ -763,9 +891,98 @@ PASS(); } +TEST test_queue_get_history(void) +{ + struct notification *n; + queues_init(); + + n = test_notification("n", -1); + n->skip_display = true; + queues_notification_insert(n); + n = test_notification("n1", -1); + n->skip_display = true; + queues_notification_insert(n); + n = test_notification("n3", -1); + n->skip_display = true; + queues_notification_insert(n); + + queues_update(STATUS_NORMAL); + + QUEUE_LEN_ALL(0, 0, 3); + + GList *h = queues_get_history(); + ASSERT(g_list_length(h) == 3); + + queues_teardown(); + PASS(); +} + + +void print_queues() { + printf("\nQueues:\n"); + for (GList *iter = g_queue_peek_head_link(QUEUE_WAIT); iter; + iter = iter->next) { + struct notification *notif = iter->data; + printf("waiting %s\n", notif->summary); + } +} + +// Test if notifications are correctly sorted, even if dunst is paused in +// between. See #838 for the issue. +TEST test_queue_no_sort_and_pause(void) +{ + // Setting sort to false, this means that notifications will only be + // sorted based on time + settings.sort = false; + settings.notification_limit = 0; + struct notification *n; + queues_init(); + + n = test_notification("n0", 0); + queues_notification_insert(n); + queues_update(STATUS_NORMAL); + + n = test_notification("n1", 0); + queues_notification_insert(n); + queues_update(STATUS_NORMAL); + + n = test_notification("n2", 0); + queues_notification_insert(n); + queues_update(STATUS_PAUSE); + + n = test_notification("n3", 0); + queues_notification_insert(n); + queues_update(STATUS_PAUSE); + /* queues_update(STATUS_NORMAL); */ + + n = test_notification("n4", 0); + queues_notification_insert(n); + queues_update(STATUS_NORMAL); + + QUEUE_LEN_ALL(0, 5, 0); + + const char* order[] = { + "n0", + "n1", + "n2", + "n3", + "n4", + }; + + for (int i = 0; i < g_queue_get_length(QUEUE_DISP); i++) { + struct notification *notif = g_queue_peek_nth(QUEUE_DISP, i); + ASSERTm("Notifications are not in the right order", + STR_EQ(notif->summary, order[i])); + } + + queues_teardown(); + PASS(); +} + SUITE(suite_queues) { - settings.icon_path = ""; + bool store = settings.stack_duplicates; + settings.stack_duplicates = false; RUN_TEST(test_datachange_beginning_empty); RUN_TEST(test_datachange_endless); @@ -783,8 +1000,11 @@ RUN_TEST(test_queue_notification_close_histignore); RUN_TEST(test_queue_notification_skip_display); RUN_TEST(test_queue_notification_skip_display_redisplayed); + RUN_TEST(test_queue_notification_skip_display_redisplayed_by_random_id); RUN_TEST(test_queue_stacking); RUN_TEST(test_queue_stacktag); + RUN_TEST(test_queue_different_stacktag); + RUN_TEST(test_queue_stacktag_different_appid); RUN_TEST(test_queue_teardown); RUN_TEST(test_queue_timeout); RUN_TEST(test_queues_update_fullscreen); @@ -794,8 +1014,10 @@ RUN_TEST(test_queues_update_xmore); RUN_TEST(test_queues_timeout_before_paused); RUN_TEST(test_queue_find_by_id); + RUN_TEST(test_queue_no_sort_and_pause); + RUN_TEST(test_queue_get_history); - settings.icon_path = NULL; + settings.stack_duplicates = store; } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/test/queues.h dunst-1.8.1/test/queues.h --- dunst-1.5.0/test/queues.h 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/queues.h 2022-03-02 10:55:25.000000000 +0000 @@ -31,25 +31,6 @@ #define NOT_LAST(n) do {ASSERT_EQm("Notification " #n " should have been deleted.", 1, notification_refcount_get(n)); g_clear_pointer(&n, notification_unref); } while(0) -static inline struct notification *test_notification(const char *name, gint64 timeout) -{ - struct notification *n = notification_create(); - - if (timeout != -1) - n->timeout = S2US(timeout); - - n->dbus_client = g_strconcat(":", name, NULL); - n->appname = g_strconcat("app of ", name, NULL); - n->summary = g_strconcat(name, NULL); - n->body = g_strconcat("See, ", name, ", I've got a body for you!", NULL); - - n->format = "%s\n%b"; - - notification_init(n); - - return n; -} - /* Retrieve a notification by its id. Solely for debugging purposes */ struct notification *queues_debug_find_notification_by_id(int id); diff -Nru dunst-1.5.0/test/rules.c dunst-1.8.1/test/rules.c --- dunst-1.5.0/test/rules.c 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/test/rules.c 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,90 @@ +#include "../src/rules.c" + +#include "greatest.h" +#include <regex.h> + +extern const char *base; + +// test filtering rules matching +TEST test_pattern_match(void) { + // NULL should match everything + ASSERT(rule_field_matches_string("anything", NULL)); + + // Literal matches + ASSERT(rule_field_matches_string("asdf", "asdf")); + ASSERT(rule_field_matches_string("test123", "test123")); + + ASSERT(rule_field_matches_string("!", "!")); + ASSERT(rule_field_matches_string("!asd", "!asd")); + ASSERT(rule_field_matches_string("/as/d", "/as/d")); + ASSERT(rule_field_matches_string("/as/d", "/as/d")); + + // ranges + ASSERT(rule_field_matches_string("ac", "[a-z][a-z]")); + + // Non-matches + ASSERT_FALSE(rule_field_matches_string("asd", "!asd")); + ASSERT_FALSE(rule_field_matches_string("ffff", "*asd")); + ASSERT_FALSE(rule_field_matches_string("ffff", "?")); + ASSERT_FALSE(rule_field_matches_string("Ac", "[a-z][a-z]")); + + // Things that differ between fnmatch(3) and regex(3) + + if (settings.enable_regex) { + // Single character matching + ASSERT(rule_field_matches_string("a", ".")); + + // Wildcard matching + ASSERT(rule_field_matches_string("anything", ".*")); + ASSERT(rule_field_matches_string("*", ".*")); + ASSERT(rule_field_matches_string("", ".*")); + ASSERT(rule_field_matches_string("ffffasd", ".*asd")); + + // Substring matching + ASSERT(rule_field_matches_string("asd", "")); + ASSERT(rule_field_matches_string("asd", "sd")); + ASSERT(rule_field_matches_string("asd", "a")); + ASSERT(rule_field_matches_string("asd", "d")); + ASSERT(rule_field_matches_string("asd", "asd")); + + // Match multiple strings + ASSERT(rule_field_matches_string("ghj", "asd|dfg|ghj")); + ASSERT(rule_field_matches_string("asd", "asd|dfg|ghj")); + ASSERT(rule_field_matches_string("dfg", "asd|dfg|ghj")); + ASSERT_FALSE(rule_field_matches_string("azd", "asd|dfg|ghj")); + + // Special characters + ASSERT_FALSE(rule_field_matches_string("{", "{")); + ASSERT(rule_field_matches_string("{", "\\{")); + ASSERT(rule_field_matches_string("a", "(a)")); + } else { + // Single character matching + ASSERT(rule_field_matches_string("a", "?")); + + // Wildcard matching + ASSERT(rule_field_matches_string("anything", "*")); + ASSERT(rule_field_matches_string("*", "*")); + ASSERT(rule_field_matches_string("", "*")); + ASSERT(rule_field_matches_string("ffffasd", "*asd")); + + // Substring matching + ASSERT_FALSE(rule_field_matches_string("asd", "")); + ASSERT_FALSE(rule_field_matches_string("asd", "sd")); + ASSERT_FALSE(rule_field_matches_string("asd", "a")); + ASSERT_FALSE(rule_field_matches_string("asd", "d")); + ASSERT(rule_field_matches_string("asd", "asd")); + } + PASS(); +} + +SUITE(suite_rules) { + bool store = settings.enable_regex; + + settings.enable_regex = false; + RUN_TEST(test_pattern_match); + + settings.enable_regex = true; + RUN_TEST(test_pattern_match); + + settings.enable_regex = store; +} diff -Nru dunst-1.5.0/test/setting.c dunst-1.8.1/test/setting.c --- dunst-1.5.0/test/setting.c 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/test/setting.c 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,109 @@ +#include "../src/settings.h" +#include "../src/option_parser.h" +#include "../src/settings_data.h" + +#include "greatest.h" + +extern const char *base; + +// In this suite a few dunstrc's are tested to see if the settings code works +// This file is called setting.c, since the name settings.c caused issues. + +char *config_path; + +TEST test_dunstrc_markup(void) { + config_path = g_strconcat(base, "/data/dunstrc.markup", NULL); + load_settings(config_path); + + ASSERT_STR_EQ(settings.font, "Monospace 8"); + + + const char *e_format = "<b>%s</b>\\n%b"; // escape the \n since it would otherwise result in the newline character + const struct rule * r = get_rule("global"); + const char *got_format = r->format; + ASSERT_STR_EQ(e_format, got_format); + ASSERT(settings.indicate_hidden); + + g_free(config_path); + PASS(); +} + +TEST test_dunstrc_nomarkup(void) { + config_path = g_strconcat(base, "/data/dunstrc.nomarkup", NULL); + load_settings(config_path); + + ASSERT_STR_EQ(settings.font, "Monospace 8"); + + + const char *e_format = "<b>%s</b>\\n<i>%b</i>"; // escape the \n since it would otherwise result in the newline character + const struct rule * r = get_rule("global"); + const char *got_format = r->format; + ASSERT_STR_EQ(e_format, got_format); + ASSERT(settings.indicate_hidden); + + g_free(config_path); + PASS(); +} + +// Test if the defaults in code and in dunstrc match +TEST test_dunstrc_defaults(void) { + struct settings s_default; + struct settings s_dunstrc; + + config_path = g_strconcat(base, "/data/dunstrc.default", NULL); + set_defaults(); + s_default = settings; + + load_settings(config_path); + s_dunstrc = settings; + + ASSERT_EQ(s_default.corner_radius, s_dunstrc.corner_radius); + char message[500]; + + for (int i = 0; i < G_N_ELEMENTS(allowed_settings); i++) { + if (!allowed_settings[i].value) { + continue; // it's a rule, that's harder to test + } + size_t offset = (char*)allowed_settings[i].value - (char*)&settings; + enum setting_type type = allowed_settings[i].type; + snprintf(message, 500, "The default of setting %s does not match. Different defaults are set in code and dunstrc" + , allowed_settings[i].name); + switch (type) { + case TYPE_CUSTOM: + if (allowed_settings[i].parser == string_parse_bool) { + { + bool a = *(bool*) ((char*) &s_default + offset); + bool b = *(bool*) ((char*) &s_dunstrc + offset); + ASSERT_EQm(message, a, b); + } + break; + } // else fall through + case TYPE_TIME: + case TYPE_INT:; + { + int a = *(int*) ((char*) &s_default + offset); + int b = *(int*) ((char*) &s_dunstrc + offset); + ASSERT_EQm(message, a, b); + } + break; + case TYPE_DOUBLE: + case TYPE_STRING: + case TYPE_PATH: + case TYPE_LIST: + case TYPE_LENGTH: + break; // TODO implement these checks as well + default: + printf("Type unknown %s:%d\n", __FILE__, __LINE__); + } + /* printf("%zu\n", offset); */ + } + + g_free(config_path); + PASS(); +} + +SUITE(suite_setting) { + RUN_TEST(test_dunstrc_markup); + RUN_TEST(test_dunstrc_nomarkup); + RUN_TEST(test_dunstrc_defaults); +} diff -Nru dunst-1.5.0/test/settings_data.c dunst-1.8.1/test/settings_data.c --- dunst-1.5.0/test/settings_data.c 1970-01-01 00:00:00.000000000 +0000 +++ dunst-1.8.1/test/settings_data.c 2022-03-02 10:55:25.000000000 +0000 @@ -0,0 +1,146 @@ +#include "../src/settings_data.h" +#include "greatest.h" + +extern const char *base; + +// TODO check enums on NULL-termination + +TEST test_names_valid(void) +{ + for (size_t i = 0; i < G_N_ELEMENTS(allowed_settings); i++) { + gchar *error1 = g_strdup_printf("Setting name is null (setting description is \"%s\")", allowed_settings[i].description); + gchar *error2 = g_strdup_printf("Setting name is empty (setting description is \"%s\")", allowed_settings[i].description); + ASSERTm(error1, allowed_settings[i].name); + ASSERTm(error2, strlen(allowed_settings[i].name)); + g_free(error1); + g_free(error2); + } + PASS(); +} + +TEST test_description_valid(void) +{ + for (size_t i = 0; i < G_N_ELEMENTS(allowed_settings); i++) { + gchar *error1 = g_strdup_printf("Description of setting %s is null", allowed_settings[i].name); + gchar *error2 = g_strdup_printf("Description of setting %s is empty", allowed_settings[i].name); + ASSERTm(error1, allowed_settings[i].description); + ASSERTm(error2, strlen(allowed_settings[i].description)); + g_free(error1); + g_free(error2); + } + PASS(); +} + +#define BETWEEN(arg, low, high) (((arg) > (low) ) && ((arg) < (high))) + +TEST test_type_valid(void) +{ + for (size_t i = 0; i < G_N_ELEMENTS(allowed_settings); i++) { + gchar *error1 = g_strdup_printf("Type of setting %s is not valid: %i", allowed_settings[i].name, allowed_settings[i].type); + ASSERTm(error1, BETWEEN(allowed_settings[i].type, TYPE_MIN, TYPE_MAX)); + g_free(error1); + } + PASS(); +} + +TEST test_section_valid(void) +{ + for (size_t i = 0; i < G_N_ELEMENTS(allowed_settings); i++) { + gchar *error1 = g_strdup_printf("Section of setting %s is null", allowed_settings[i].name); + gchar *error2 = g_strdup_printf("Section of setting %s is empty", allowed_settings[i].name); + ASSERTm(error1, allowed_settings[i].section); + ASSERTm(error2, strlen(allowed_settings[i].section)); + g_free(error1); + g_free(error2); + } + PASS(); +} + +TEST test_default_value_valid(void) +{ + for (size_t i = 0; i < G_N_ELEMENTS(allowed_settings); i++) { + gchar *error1 = g_strdup_printf("Default_value of setting %s is null", allowed_settings[i].name); + gchar *error2 = g_strdup_printf("Default_value of setting %s is empty", allowed_settings[i].name); + ASSERTm(error1, allowed_settings[i].default_value); + if (allowed_settings[i].type != TYPE_STRING) + ASSERTm(error2, strlen(allowed_settings[i].default_value)); + g_free(error1); + g_free(error2); + } + PASS(); +} + +TEST test_value_non_null(void) +{ + for (size_t i = 0; i < G_N_ELEMENTS(allowed_settings); i++) { + gchar *error1 = g_strdup_printf("Error in settting %s. A setting must have a 'value' or a 'rule_offset', or both.", + allowed_settings[i].name); + ASSERTm(error1, allowed_settings[i].value || + allowed_settings[i].rule_offset); + g_free(error1); + } + PASS(); +} + +TEST test_valid_parser_and_data_per_type(void) +{ + for (size_t i = 0; i < G_N_ELEMENTS(allowed_settings); i++) { + struct setting curr = allowed_settings[i]; + switch (curr.type) { + case TYPE_STRING: + case TYPE_TIME: + case TYPE_DOUBLE: + case TYPE_LENGTH: + case TYPE_INT: ; // no parser and no parser data needed + gchar *error1 = g_strdup_printf("Parser of setting %s should be NULL. It's not needed for this type", curr.name); + gchar *error2 = g_strdup_printf("Parser data of setting %s should be NULL. It's not needed for this type", curr.name); + ASSERTm(error1, !curr.parser); + ASSERTm(error2, !curr.parser_data); + g_free(error1); + g_free(error2); + break; + case TYPE_CUSTOM: ; // both parser data and parser are needed + gchar *error3 = g_strdup_printf("Parser of setting %s should not be NULL. It's needed for this type", curr.name); + gchar *error4 = g_strdup_printf("Parser data of setting %s should not be NULL. It's needed for this type", curr.name); + ASSERTm(error3, curr.parser); + ASSERTm(error4, curr.parser_data); + g_free(error3); + g_free(error4); + break; + case TYPE_LIST: ; // only parser data is needed + gchar *error5 = g_strdup_printf("Parser of setting %s should be NULL. It's needed not for this type", curr.name); + gchar *error6 = g_strdup_printf("Parser data of setting %s should not be NULL. It's needed for this type", curr.name); + ASSERTm(error5, !curr.parser); + ASSERTm(error6, curr.parser_data); + g_free(error5); + g_free(error6); + break; + case TYPE_PATH: ; // only parser data is neede, but when it's a rule none is needed. + gchar *error7 = g_strdup_printf("Parser of setting %s should be NULL. It's needed not for this type", curr.name); + gchar *error8 = g_strdup_printf("Parser data of setting %s should not be NULL. It's needed for this type", curr.name); + bool is_rule = !curr.value; // if it doesn't have a 'value' it's a rule + ASSERTm(error7, !curr.parser); + ASSERTm(error8, is_rule || curr.parser_data); + g_free(error7); + g_free(error8); + break; + default: ; + gchar *error20 = g_strdup_printf("You should make a test for type %i", curr.type); + FAILm(error20); + break; + } + } + PASS(); +} + +SUITE(suite_settings_data) +{ + RUN_TEST(test_names_valid); + RUN_TEST(test_description_valid); + RUN_TEST(test_type_valid); + RUN_TEST(test_section_valid); + RUN_TEST(test_default_value_valid); + RUN_TEST(test_value_non_null); + RUN_TEST(test_valid_parser_and_data_per_type); +} +/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/test/test.c dunst-1.8.1/test/test.c --- dunst-1.5.0/test/test.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/test.c 2022-03-02 10:55:25.000000000 +0000 @@ -6,9 +6,11 @@ #include <stdlib.h> #include "../src/log.h" +#include "../src/settings.h" const char *base; +SUITE_EXTERN(suite_settings_data); SUITE_EXTERN(suite_utils); SUITE_EXTERN(suite_option_parser); SUITE_EXTERN(suite_notification); @@ -20,6 +22,11 @@ SUITE_EXTERN(suite_log); SUITE_EXTERN(suite_menu); SUITE_EXTERN(suite_dbus); +SUITE_EXTERN(suite_setting); +SUITE_EXTERN(suite_ini); +SUITE_EXTERN(suite_icon_lookup); +SUITE_EXTERN(suite_draw); +SUITE_EXTERN(suite_rules); GREATEST_MAIN_DEFS(); @@ -37,6 +44,11 @@ bool printlog = log && atoi(log) ? true : false; dunst_log_init(!printlog); + + // initialize settings + char *config_path = g_strconcat(base, "/data/dunstrc.default", NULL); + load_settings(config_path); + GREATEST_MAIN_BEGIN(); RUN_SUITE(suite_utils); RUN_SUITE(suite_option_parser); @@ -48,10 +60,18 @@ RUN_SUITE(suite_dunst); RUN_SUITE(suite_log); RUN_SUITE(suite_menu); + RUN_SUITE(suite_settings_data); RUN_SUITE(suite_dbus); - GREATEST_MAIN_END(); + RUN_SUITE(suite_setting); + RUN_SUITE(suite_icon_lookup); + RUN_SUITE(suite_draw); + RUN_SUITE(suite_rules); base = NULL; + g_free(config_path); free(prog); + + // this returns the error code + GREATEST_MAIN_END(); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.5.0/test/test-install.sh dunst-1.8.1/test/test-install.sh --- dunst-1.5.0/test/test-install.sh 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/test-install.sh 2022-03-02 10:55:25.000000000 +0000 @@ -1,27 +1,53 @@ #!/usr/bin/env bash +# Throw error any time a command fails set -euo pipefail -BASE="$(dirname "$(dirname "$(readlink -f "$0")")")" -PREFIX="${BASE}/install" +# Export parameters so they are useable by subshells and make +export BASE="$(dirname "$(dirname "$(readlink -f "$0")")")" +export DESTDIR="${BASE}/install" +export PREFIX="/testprefix" +export SYSCONFDIR="/sysconfdir" +export SYSCONFFILE="${SYSCONFDIR}/dunst/dunstrc" +export SYSTEMD=1 +export SERVICEDIR_SYSTEMD="/systemd" +export SERVICEDIR_DBUS="/dbus" + +do_make() { # for convenience/conciseness + make -C "${BASE}" "$@" +} + +check_dest() { + # Check file list given on stdin and see if all are actually present + diff -u <(find "${DESTDIR}" -type f -printf "%P\n" | sort) <(sort -) +} -make -C "${BASE}" SYSTEMD=1 SERVICEDIR_SYSTEMD="${PREFIX}/systemd" SERVICEDIR_DBUS="${PREFIX}/dbus" PREFIX="${PREFIX}" install +do_make install -diff -u <(find "${PREFIX}" -type f -printf "%P\n" | sort) - <<EOF -bin/dunst -bin/dunstctl -bin/dunstify +check_dest <<EOF dbus/org.knopwob.dunst.service -share/dunst/dunstrc -share/man/man1/dunst.1 -share/man/man1/dunstctl.1 +sysconfdir/dunst/dunstrc systemd/dunst.service +testprefix/bin/dunst +testprefix/bin/dunstctl +testprefix/bin/dunstify +testprefix/share/man/man1/dunst.1 +testprefix/share/man/man1/dunstctl.1 +testprefix/share/man/man5/dunst.5 EOF -make -C "${BASE}" SYSTEMD=1 SERVICEDIR_SYSTEMD="${PREFIX}/systemd" SERVICEDIR_DBUS="${PREFIX}/dbus" PREFIX="${PREFIX}" uninstall +do_make uninstall -if ! [ -z "$(find "${PREFIX}" -type f)" ]; then +# dunstrc should still be there +check_dest <<EOF +sysconfdir/dunst/dunstrc +EOF + +do_make uninstall-purge + +# Expect empty +if ! [ -z "$(find "${DESTDIR}" -type f)" ]; then echo "Uninstall failed, following files weren't removed" - find "${PREFIX}" -type f + find "${DESTDIR}" -type f exit 1 fi diff -Nru dunst-1.5.0/test/utils.c dunst-1.8.1/test/utils.c --- dunst-1.5.0/test/utils.c 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/test/utils.c 2022-03-02 10:55:25.000000000 +0000 @@ -119,10 +119,6 @@ string_strip_delimited(text, '<', '>'); ASSERT_STR_EQ("Remove html tags", text); - strcpy(text, "Calls|with|identical|delimiters|are|handled|properly"); - string_strip_delimited(text, '|', '|'); - ASSERT_STR_EQ("Calls", text); - strcpy(text, "<Return empty string if there is nothing left>"); string_strip_delimited(text, '<', '>'); ASSERT_STR_EQ("", text); @@ -131,6 +127,18 @@ string_strip_delimited(text, '<', '>'); ASSERT_STR_EQ("Nothing is done if there are no delimiters in the string", text); + strcpy(text, "We <3 dunst"); + string_strip_delimited(text, '<', '>'); + ASSERT_STR_EQ("We <3 dunst", text); + + strcpy(text, "<b>We</b> <3 dunst"); + string_strip_delimited(text, '<', '>'); + ASSERT_STR_EQ("We <3 dunst", text); + + strcpy(text, "dunst > the rest"); + string_strip_delimited(text, '<', '>'); + ASSERT_STR_EQ("dunst > the rest", text); + g_free(text); PASS(); } diff -Nru dunst-1.5.0/.valgrind.suppressions dunst-1.8.1/.valgrind.suppressions --- dunst-1.5.0/.valgrind.suppressions 2020-07-23 10:27:50.000000000 +0000 +++ dunst-1.8.1/.valgrind.suppressions 2022-03-02 10:55:25.000000000 +0000 @@ -48,6 +48,20 @@ ... } +# Some new in ArchLinux +{ + rsvg_rust_handle_close + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + ... + fun:rsvg_rust_handle_close + obj:*/loaders/libpixbufloader-svg.so + ... + fun:gdk_pixbuf_new_from_file + ... +} + # rsvg_error_writehandler got fixed in # - GNOME/librsvg@7b4cc9b # (2018-11-12, first tags: v2.45.0, v2.44.9) @@ -70,3 +84,18 @@ fun:get_pixbuf_from_file ... } + +# a librsvg memoryleak that shows up in arch, but not in the CI environment +{ + <librsvg-arch> + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + obj:/usr/lib/librsvg-2.so.2.48.0 + ... + fun:rsvg_handle_close + obj:/usr/lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-svg.so + obj:/usr/lib/libgdk_pixbuf-2.0.so.0.4200.6 + fun:gdk_pixbuf_new_from_file + ... +}