Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package fnott for openSUSE:Factory checked 
in at 2024-08-03 20:04:38
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/fnott (Old)
 and      /work/SRC/openSUSE:Factory/.fnott.new.7232 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "fnott"

Sat Aug  3 20:04:38 2024 rev:10 rq:1191228 version:1.7.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/fnott/fnott.changes      2024-07-17 
15:15:16.226329966 +0200
+++ /work/SRC/openSUSE:Factory/.fnott.new.7232/fnott.changes    2024-08-03 
20:04:42.555475424 +0200
@@ -1,0 +2,45 @@
+Fri Aug  2 14:59:54 UTC 2024 - Soc Virnyl Estela <o...@uncomfyhalomacro.pl>
+
+- Specfile cleanup
+  * %config macro for config file
+
+-------------------------------------------------------------------
+Fri Aug  2 14:54:43 UTC 2024 - Soc Virnyl Estela <o...@uncomfyhalomacro.pl>
+
+- Set c11 to leap only. confusing workaround
+
+-------------------------------------------------------------------
+Fri Aug  2 14:48:58 UTC 2024 - Soc Virnyl Estela <o...@uncomfyhalomacro.pl>
+
+- Update to version 1.7.0:
+  * Log output now respects the [`NO_COLOR`](http://no-color.org/)
+    environment variable.
+  * `border-radius` configuration option (yes, this means fnott now
+    supports rounded corners).
+  * Support for linking against a system provided nanosvg library. See
+    the new `-Dsystem-nanosvg` meson option. Defaults to `disabled`
+    (i.e. use the bundled version).
+  * Support for the `x-canonical-private-synchronous` hint.
+  * XDG activation support; when triggering an action, fnott attempts to
+    retrieve an XDG activation token. This will only succeed if the
+    cursor is inside the notification window. The token is then
+    signaled over the D-Bus _Notifications_ interface.
+  * `fnottctl dismiss-with-default-action`.
+  * Implemented the `org.freedesktop.DBus.Introspectable`
+    interface. This fixes an issue where e.g. `gdbus` was not able to
+    create, or close, notifications.
+  * Left clicking a notification now triggers the default action, if
+    any, in addition to dismissing the notification. Right click to
+    dismiss the notification without trigger the default action.
+  * `STRING:image-path` hint that points to either a non-existing file,
+    or an invalid image, will now be ignored (instead of removing the
+    notification's icon).
+  * All notifications are now dismissed
+    (i.e. `org.freedesktop.Notifications.NotificationClosed` is
+    signaled) when fnott exits.
+  * `reason` in the `NotificationClosed` signal being off-by-one.
+  * Icons loaded via `image-data` hints being too dark.
+  * Not all data being read from the action selection helper, under
+    certain circumstances.
+
+-------------------------------------------------------------------

Old:
----
  fnott-1.6.0.tar.gz
  fnott-1.6.0.tar.gz.sig

New:
----
  fnott-1.7.0.tar.gz
  fnott-1.7.0.tar.gz.sig

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ fnott.spec ++++++
--- /var/tmp/diff_new_pack.eBABfj/_old  2024-08-03 20:04:44.491554964 +0200
+++ /var/tmp/diff_new_pack.eBABfj/_new  2024-08-03 20:04:44.491554964 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           fnott
-Version:        1.6.0
+Version:        1.7.0
 Release:        0
 Summary:        Lightweight notification daemon for Wayland
 License:        MIT
@@ -65,7 +65,7 @@
 
 %build
 %meson \
-%if 0%{?sle_version} == 150400 && 0%{?is_opensuse}
+%if 0%{?suse_version} <= 1600 && 0%{?is_opensuse}
   -Dc_std=c11 \
 %endif
   -Db_lto=true
@@ -88,7 +88,7 @@
 %doc CHANGELOG.md
 
 %dir %{_sysconfdir}/xdg/%{name}/
-%{_sysconfdir}/xdg/%{name}/fnott.ini
+%config(noreplace) %{_sysconfdir}/xdg/%{name}/fnott.ini
 %{_datadir}/applications/fnott.desktop
 
 %dir %{_datadir}/dbus-1

++++++ fnott-1.6.0.tar.gz -> fnott-1.7.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/3rd-party/nanosvg/src/nanosvg.h 
new/fnott-1.7.0/3rd-party/nanosvg/src/nanosvg.h
--- old/fnott-1.6.0/3rd-party/nanosvg/src/nanosvg.h     2024-04-30 
08:30:46.000000000 +0200
+++ new/fnott-1.7.0/3rd-party/nanosvg/src/nanosvg.h     2024-08-02 
11:48:03.000000000 +0200
@@ -1653,7 +1653,7 @@
 
 static void nsvg__parseTransform(float* xform, const char* str)
 {
-       float t[6];
+       float t[6] = {0};
        int len;
        nsvg__xformIdentity(xform);
        while (*str)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/CHANGELOG.md new/fnott-1.7.0/CHANGELOG.md
--- old/fnott-1.6.0/CHANGELOG.md        2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/CHANGELOG.md        2024-08-02 11:48:03.000000000 +0200
@@ -1,5 +1,6 @@
 # Changelog
 
+* [1.7.0](#1-7-0)
 * [1.6.0](#1-6-0)
 * [1.5.0](#1-5-0)
 * [1.4.1](#1-4-1)
@@ -14,6 +15,55 @@
 * [1.0.0](#1-0-0)
 
 
+## 1.7.0
+
+### Added
+
+* Log output now respects the [`NO_COLOR`](http://no-color.org/)
+  environment variable.
+* `border-radius` configuration option (yes, this means fnott now
+  supports rounded corners).
+* Support for linking against a system provided nanosvg library. See
+  the new `-Dsystem-nanosvg` meson option. Defaults to `disabled`
+  (i.e. use the bundled version).
+* Support for the `x-canonical-private-synchronous` hint.
+* XDG activation support; when triggering an action, fnott attempts to
+  retrieve an XDG activation token. This will only succeed if the
+  cursor is inside the notification window. The token is then
+  signaled over the D-Bus _Notifications_ interface.
+* `fnottctl dismiss-with-default-action`.
+* Implemented the `org.freedesktop.DBus.Introspectable`
+  interface. This fixes an issue where e.g. `gdbus` was not able to
+  create, or close, notifications.
+
+
+### Changed
+
+* Left clicking a notification now triggers the default action, if
+  any, in addition to dismissing the notification. Right click to
+  dismiss the notification without trigger the default action.
+* `STRING:image-path` hint that points to either a non-existing file,
+  or an invalid image, will now be ignored (instead of removing the
+  notification's icon).
+* All notifications are now dismissed
+  (i.e. `org.freedesktop.Notifications.NotificationClosed` is
+  signaled) when fnott exits.
+
+
+### Fixed
+
+* `reason` in the `NotificationClosed` signal being off-by-one.
+* Icons loaded via `image-data` hints being too dark.
+* Not all data being read from the action selection helper, under
+  certain circumstances.
+
+
+### Contributors
+
+* Evyatar Stalinsky
+* ldev
+
+
 ## 1.6.0
 
 ### Added
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/PKGBUILD new/fnott-1.7.0/PKGBUILD
--- old/fnott-1.6.0/PKGBUILD    2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/PKGBUILD    2024-08-02 11:48:03.000000000 +0200
@@ -1,5 +1,5 @@
 pkgname=fnott
-pkgver=1.6.0
+pkgver=1.7.0
 pkgrel=1
 pkgdesc="Lightweight notification daemon for Wayland"
 arch=('x86_64' 'aarch64')
@@ -7,7 +7,7 @@
 license=(mit)
 makedepends=('meson' 'ninja' 'scdoc' 'tllist>=1.0.1')
 depends=(
-  'wayland' 'pixman' 'libpng' 'dbus'
+  'wayland' 'pixman' 'libpng' 'dbus' 'nanosvg'
   'fcft>=3.0.0' 'fcft<4.0.0')
 source=()
 changelog=CHANGELOG.md
@@ -18,7 +18,7 @@
 }
 
 build() {
-  meson --prefix=/usr --buildtype=release --wrap-mode=nofallback -Db_lto=true 
..
+  meson --prefix=/usr --buildtype=release --wrap-mode=nofallback -Db_lto=true 
-Dsystem-nanosvg=enabled ..
   ninja
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/config.c new/fnott-1.7.0/config.c
--- old/fnott-1.6.0/config.c    2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/config.c    2024-08-02 11:48:03.000000000 +0200
@@ -301,6 +301,16 @@
         conf->border.color = color;
     }
 
+    else if (strcmp(key, "border-radius") == 0) {
+        unsigned long rad;
+        if (!str_to_ulong(value, 10, &rad)) {
+            LOG_ERR("%s:%u: invalid border-radius (expected an integer): %s",
+                    path, lineno, value);
+            return false;
+        }
+        conf->border.radius = rad;
+    }
+
     else if (strcmp(key, "border-size") == 0) {
         unsigned long sz;
         if (!str_to_ulong(value, 10, &sz)) {
@@ -709,6 +719,18 @@
             conf->by_urgency[i].border.color = color;
     }
 
+    else if (strcmp(key, "border-radius") == 0) {
+        unsigned long rad;
+        if (!str_to_ulong(value, 10, &rad)) {
+            LOG_ERR("%s:%u: invalid border-radius (expected an integer): %s",
+                    path, lineno, value);
+            return false;
+        }
+
+        for (int i = 0; i < 3; i++)
+            conf->by_urgency[i].border.radius = rad;
+    }
+
     else if (strcmp(key, "border-size") == 0) {
         unsigned long sz;
         if (!str_to_ulong(value, 10, &sz)) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/config.h new/fnott-1.7.0/config.h
--- old/fnott-1.6.0/config.h    2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/config.h    2024-08-02 11:48:03.000000000 +0200
@@ -28,6 +28,7 @@
     struct {
         pixman_color_t color;
         int size;
+        int radius;
     } border;
 
     struct {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/ctrl-protocol.h 
new/fnott-1.7.0/ctrl-protocol.h
--- old/fnott-1.6.0/ctrl-protocol.h     2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/ctrl-protocol.h     2024-08-02 11:48:03.000000000 +0200
@@ -8,6 +8,7 @@
     CTRL_UNPAUSE,
     CTRL_DISMISS_BY_ID, CTRL_DISMISS_ALL,
     CTRL_ACTIONS_BY_ID,
+    CTRL_DISMISS_WITH_DEFAULT_ACTION_BY_ID,
 };
 
 struct ctrl_request {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/ctrl.c new/fnott-1.7.0/ctrl.c
--- old/fnott-1.6.0/ctrl.c      2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/ctrl.c      2024-08-02 11:48:03.000000000 +0200
@@ -80,7 +80,7 @@
 };
 
 static void
-actions_complete(uint32_t notif_id, const char *action_id, void *data)
+actions_complete(struct notif *notif, const char *action_id, void *data)
 {
     struct actions_cb_data *info = data;
     struct ctrl *ctrl = info->ctrl;
@@ -93,8 +93,7 @@
     if (action_id == NULL)
         goto out;
 
-    reply.result = dbus_signal_action(info->ctrl->bus, notif_id, action_id)
-        ? CTRL_OK : CTRL_ERROR;
+    reply.result = notif_signal_action(notif, action_id) ? CTRL_OK : 
CTRL_ERROR;
 
 out:
     send_reply(fd, &reply);
@@ -198,6 +197,23 @@
              * response before sending reply to fnottctl */
             goto no_reply;
         }
+        break;
+
+    case CTRL_DISMISS_WITH_DEFAULT_ACTION_BY_ID: {
+        LOG_DBG("client: FD=%d, dismiss-with-default-action: %u", fd, 
client->recv.cmd.id);
+
+        struct notif *notif =
+            notif_mgr_get_notif(ctrl->notif_mgr, client->recv.cmd.id);
+
+        if (notif != NULL) {
+            notif_signal_action(notif, "default");
+            reply.result =
+                notif_mgr_dismiss_id(ctrl->notif_mgr, client->recv.cmd.id)
+                    ? CTRL_OK : CTRL_INVALID_ID;
+        } else
+            reply.result = CTRL_INVALID_ID;
+        break;
+    }
     }
 
     if (!send_reply(fd, &reply))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/dbus.c new/fnott-1.7.0/dbus.c
--- old/fnott-1.6.0/dbus.c      2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/dbus.c      2024-08-02 11:48:03.000000000 +0200
@@ -56,8 +56,8 @@
     if (!dbus_connection_send(bus->conn, reply, NULL))
         goto err;
 
-    if (dbus_connection_has_messages_to_send(bus->conn))
-        fdm_event_add(bus->fdm, bus->bus_fd, EPOLLOUT);
+    dbus_connection_flush(bus->conn);
+    assert(!dbus_connection_has_messages_to_send(bus->conn));
     ret = true;
 
 err:
@@ -81,13 +81,14 @@
     dbus_message_iter_append_basic(&arr, DBUS_TYPE_STRING, &(const char 
*){"body-markup"});
     dbus_message_iter_append_basic(&arr, DBUS_TYPE_STRING, &(const char 
*){"actions"});
     dbus_message_iter_append_basic(&arr, DBUS_TYPE_STRING, &(const char 
*){"icon-static"});
+    dbus_message_iter_append_basic(&arr, DBUS_TYPE_STRING, &(const char 
*){"x-canonical-private-synchronous"});
 
     dbus_message_iter_close_container(&iter, &arr);
     if (!dbus_connection_send(bus->conn, reply, NULL))
         goto err;
 
-    if (dbus_connection_has_messages_to_send(bus->conn))
-        fdm_event_add(bus->fdm, bus->bus_fd, EPOLLOUT);
+    dbus_connection_flush(bus->conn);
+    assert(!dbus_connection_has_messages_to_send(bus->conn));
     ret = true;
 
 err:
@@ -121,7 +122,7 @@
     if (notif_mgr_is_paused(bus->notif_mgr)) {
         LOG_DBG("paused, refusing to notify");
     } else {
-        char *app_name, *app_icon, *summary, *body;
+        char *app_name, *app_icon, *summary, *body, *sync_tag = NULL;
         enum urgency urgency = URGENCY_NORMAL;
         int8_t progress_percent = -1;
 
@@ -253,8 +254,6 @@
             dbus_message_iter_recurse(&entry_iter, &value_iter);
             dbus_message_iter_next(&entry_iter);
 
-            LOG_DBG("hint: %s", name);
-
             if (strcmp(name, "urgency") == 0) {
                 if (dbus_message_iter_get_arg_type(&value_iter) != 
DBUS_TYPE_BYTE)
                     goto err;
@@ -267,6 +266,14 @@
                 urgency = level;
             }
 
+            else if (strcmp(name, "x-canonical-private-synchronous") == 0) {
+                if (dbus_message_iter_get_arg_type(&value_iter) != 
DBUS_TYPE_STRING)
+                    goto err;
+
+                dbus_message_iter_get_basic(&value_iter, &sync_tag);
+                LOG_DBG("x-canonical-private-synchronous: %s", sync_tag);
+            }
+
             else if (strcmp(name, "value") == 0) {
                 if (dbus_message_iter_get_arg_type(&value_iter) != 
DBUS_TYPE_INT32)
                     goto err;
@@ -298,13 +305,17 @@
                     image_path = path;
                 }
 
-                if (pix != NULL) {
-                    free(pixman_image_get_data(pix));
-                    pixman_image_unref(pix);
-                    pix = NULL;
-                }
+                pixman_image_t *image = icon_load(
+                    image_path, bus->conf->max_icon_size, bus->icon_theme);
 
-                pix = icon_load(image_path, bus->conf->max_icon_size, 
bus->icon_theme);
+                if (image != NULL) {
+                    if (pix != NULL) {
+                        free(pixman_image_get_data(pix));
+                        pixman_image_unref(pix);
+                    }
+
+                    pix = image;
+                }
 
                 free(scheme);
                 free(host);
@@ -397,9 +408,38 @@
                         pix = NULL;
                     }
 
+                    /* pixman expects pre-multiplied alpha */
+                    if (format == PIXMAN_a8b8g8r8) {
+                        for (int i = 0; i < height; i++) {
+                            uint32_t *p = (uint32_t *)&image_data[i * stride];
+                            for (int j = 0; j < width; j++, p++) {
+                                uint8_t a = (*p >> 24) & 0xff;
+                                uint8_t b = (*p >> 16) & 0xff;
+                                uint8_t g = (*p >> 8) & 0xff;
+                                uint8_t r = (*p >> 0) & 0xff;
+
+                                if (a == 0xff)
+                                    continue;
+
+                                if (a == 0) {
+                                    r = g = b = 0;
+                                } else {
+                                    r = r * a / 0xff;
+                                    g = g * a / 0xff;
+                                    b = b * a / 0xff;
+                                }
+
+                                *p = (uint32_t)a << 24 | b << 16 | g << 8 | r;
+                            }
+                        }
+                    }
+
                     pix = pixman_image_create_bits_no_clear(
                         format, width, height, (uint32_t *)image_data, stride);
                 }
+            } else {
+                LOG_DBG("hint: %s unrecognized, ignoring", name);
+
             }
         }
 
@@ -412,7 +452,7 @@
         dbus_message_iter_get_basic(&args_iter, &timeout_ms);
         LOG_DBG("timeout = %dms", timeout_ms);
 
-        notif = notif_mgr_create_notif(bus->notif_mgr, replaces_id);
+        notif = notif_mgr_create_notif(bus->notif_mgr, replaces_id, sync_tag);
         if (notif == NULL)
             goto err;
 
@@ -448,8 +488,8 @@
     if (!dbus_connection_send(bus->conn, reply, NULL))
         goto err;
 
-    if (dbus_connection_has_messages_to_send(bus->conn))
-        fdm_event_add(bus->fdm, bus->bus_fd, EPOLLOUT);
+    dbus_connection_flush(bus->conn);
+    assert(!dbus_connection_has_messages_to_send(bus->conn));
     ret = true;
     goto out;
 
@@ -506,8 +546,84 @@
     if (!dbus_connection_send(bus->conn, reply, NULL))
         goto err;
 
-    if (dbus_connection_has_messages_to_send(bus->conn))
-        fdm_event_add(bus->fdm, bus->bus_fd, EPOLLOUT);
+    dbus_connection_flush(bus->conn);
+    assert(!dbus_connection_has_messages_to_send(bus->conn));
+    ret = true;
+
+err:
+    dbus_message_unref(reply);
+    return ret;
+}
+
+static bool
+introspect(struct dbus *bus, DBusMessage *msg)
+{
+    const char *data =
+        "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object 
Introspection 1.0//EN\"\n"
+        " \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\";>\n"
+        "<node name=\"/org/freedesktop/Notifications\">\n"
+        "  <interface name=\"org.freedesktop.Notifications\">\n"
+
+        "    <method name=\"Notify\">\n"
+        "      <arg name=\"id\" type=\"u\" direction=\"out\"/>\n"
+        "      <arg name=\"app_name\" type=\"s\" direction=\"in\"/>\n"
+        "      <arg name=\"replaces_id\" type=\"u\" direction=\"in\"/>\n"
+        "      <arg name=\"app_icon\" type=\"s\" direction=\"in\"/>\n"
+        "      <arg name=\"summary\" type=\"s\" direction=\"in\"/>\n"
+        "      <arg name=\"body\" type=\"s\" direction=\"in\"/>\n"
+        "      <arg name=\"actions\" type=\"as\" direction=\"in\"/>\n"
+        "      <arg name=\"hints\" type=\"a{sv}\" direction=\"in\"/>\n"
+        "      <arg name=\"expire_timeout\" type=\"i\" direction=\"in\"/>\n"
+        "    </method>\n"
+
+        "    <method name=\"CloseNotification\">\n"
+        "      <arg name=\"id\" type=\"u\" direction=\"in\"/>\n"
+        "    </method>\n"
+
+        "    <method name=\"GetServerInformation\">\n"
+        "      <arg name=\"name\" type=\"s\" direction=\"out\"/>\n"
+        "      <arg name=\"vendor\" type=\"s\" direction=\"out\"/>\n"
+        "      <arg name=\"version\" type=\"s\" direction=\"out\"/>\n"
+        "      <arg name=\"spec_version\" type=\"s\" direction=\"out\"/>\n"
+        "    </method>\n"
+
+        "    <method name=\"GetCapabilities\">\n"
+        "      <arg name=\"capabilities\" type=\"as\" direction=\"out\"/>\n"
+        "    </method>\n"
+
+        "    <signal name=\"NotificationClosed\">\n"
+        "      <arg name=\"id\" type=\"u\"/>\n"
+        "      <arg name=\"reason\" type=\"u\"/>\n"
+        "    </signal>\n"
+
+        "    <signal name=\"ActionInvoked\">\n"
+        "      <arg name=\"id\" type=\"u\"/>\n"
+        "      <arg name=\"action_key\" type=\"s\"/>\n"
+        "    </signal>\n"
+
+        "    <signal name=\"ActivationToken\">\n"
+        "      <arg name=\"id\" type=\"u\"/>\n"
+        "      <arg name=\"activation_token\" type=\"s\"/>\n"
+        "    </signal>\n"
+
+        "  </interface>\n"
+        "</node>\n";
+
+    bool ret = false;
+    DBusMessage *reply = dbus_message_new_method_return(msg);
+
+    if (reply == NULL)
+        goto err;
+
+    DBusMessageIter args;
+    dbus_message_iter_init_append(reply, &args);
+    dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &data);
+
+    if (!dbus_connection_send(bus->conn, reply, NULL))
+        goto err;
+
+    dbus_connection_flush(bus->conn);
+    assert(!dbus_connection_has_messages_to_send(bus->conn));
     ret = true;
 
 err:
@@ -520,21 +636,27 @@
 {
     struct dbus *bus = data;
 
-    const char *iface __attribute__((unused)) = 
dbus_message_get_interface(msg);
+    const char *iface = dbus_message_get_interface(msg);
     const char *member = dbus_message_get_member(msg);
     LOG_DBG("%s:%s", iface, member);
 
     static const struct {
+        const char *iface;
         const char *name;
         bool (*handler)(struct dbus *bus, DBusMessage *msg);
     } handlers[] = {
-        {"GetServerInformation", &get_server_information},
-        {"GetCapabilities", &get_capabilities},
-        {"Notify", &notify},
-        {"CloseNotification", &close_notification},
+        /* Don't forget to update introspect() when adding methods or signals 
*/
+        {"org.freedesktop.DBus.Introspectable", "Introspect", &introspect},
+
+        {"org.freedesktop.Notifications", "GetServerInformation", 
&get_server_information},
+        {"org.freedesktop.Notifications", "GetCapabilities", 
&get_capabilities},
+        {"org.freedesktop.Notifications", "Notify", &notify},
+        {"org.freedesktop.Notifications", "CloseNotification", 
&close_notification},
     };
 
     for (size_t i = 0; i < sizeof(handlers) / sizeof(handlers[0]); i++) {
+        if (strcmp(handlers[i].iface, iface) != 0)
+            continue;
         if (strcmp(handlers[i].name, member) != 0)
             continue;
 
@@ -562,28 +684,50 @@
     dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, 
&(dbus_uint32_t){id});
     dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, 
&(dbus_uint32_t){reason});
     dbus_connection_send(bus->conn, signal, NULL);
+    dbus_connection_flush(bus->conn);
+    assert(!dbus_connection_has_messages_to_send(bus->conn));
     dbus_message_unref(signal);
-    if (dbus_connection_has_messages_to_send(bus->conn))
-        fdm_event_add(bus->fdm, bus->bus_fd, EPOLLOUT);
     return true;
 }
 
 bool
 dbus_signal_expired(struct dbus *bus, uint32_t id)
 {
-    return signal_notification_closed(bus, id, 0);
+    return signal_notification_closed(bus, id, 1);
 }
 
 bool
 dbus_signal_dismissed(struct dbus *bus, uint32_t id)
 {
-    return signal_notification_closed(bus, id, 1);
+    return signal_notification_closed(bus, id, 2);
 }
 
 bool
 dbus_signal_closed(struct dbus *bus, uint32_t id)
 {
-    return signal_notification_closed(bus, id, 2);
+    return signal_notification_closed(bus, id, 3);
+}
+
+bool
+dbus_signal_token(struct dbus *bus, uint32_t id, const char *token)
+{
+    DBusMessage *signal = dbus_message_new_signal(
+        "/org/freedesktop/Notifications",
+        "org.freedesktop.Notifications",
+        "ActivationToken");
+
+    if (signal == NULL)
+        return false;
+
+    DBusMessageIter iter;
+    dbus_message_iter_init_append(signal, &iter);
+    dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, 
&(dbus_uint32_t){id});
+    dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &token);
+    dbus_connection_send(bus->conn, signal, NULL);
+    dbus_connection_flush(bus->conn);
+    assert(!dbus_connection_has_messages_to_send(bus->conn));
+    dbus_message_unref(signal);
+    return true;
 }
 
 bool
@@ -602,9 +746,9 @@
     dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, 
&(dbus_uint32_t){id});
     dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &action_id);
     dbus_connection_send(bus->conn, signal, NULL);
+    dbus_connection_flush(bus->conn);
+    assert(!dbus_connection_has_messages_to_send(bus->conn));
     dbus_message_unref(signal);
-    if (dbus_connection_has_messages_to_send(bus->conn))
-        fdm_event_add(bus->fdm, bus->bus_fd, EPOLLOUT);
     return true;
 }
 
@@ -614,23 +758,11 @@
     bool ret = false;
     struct dbus *bus = data;
 
-    bool had_outgoing = dbus_connection_has_messages_to_send(bus->conn);
-
-    /* A sending function added EPOLLOUT when it wasn't necessary */
-    if ((events & EPOLLOUT) && !had_outgoing) {
-        LOG_WARN("EPOLLOUT set, but no outgoing messages");
-        fdm_event_del(bus->fdm, bus->bus_fd, EPOLLOUT);
-    }
-
     if (!dbus_connection_read_write(bus->conn, 0)) {
         LOG_ERRNO("failed to read/write dbus connection");
         goto err;
     }
 
-    /* Remove EPOLLOUT when no longer needed */
-    if (had_outgoing && !dbus_connection_has_messages_to_send(bus->conn))
-        fdm_event_del(bus->fdm, bus->bus_fd, EPOLLOUT);
-
     while (dbus_connection_dispatch(bus->conn) != DBUS_DISPATCH_COMPLETE)
         ;
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/dbus.h new/fnott-1.7.0/dbus.h
--- old/fnott-1.6.0/dbus.h      2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/dbus.h      2024-08-02 11:48:03.000000000 +0200
@@ -17,6 +17,7 @@
 bool dbus_signal_expired(struct dbus *bus, uint32_t id);
 bool dbus_signal_dismissed(struct dbus *bus, uint32_t id);
 bool dbus_signal_closed(struct dbus *bus, uint32_t id);
+bool dbus_signal_token(struct dbus *bus, uint32_t id, const char *token);
 bool dbus_signal_action(struct dbus *bus, uint32_t id, const char *action_id);
 
 int dbus_poll_fd(const struct dbus *bus);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/doc/fnott.ini.5.scd 
new/fnott-1.7.0/doc/fnott.ini.5.scd
--- old/fnott-1.6.0/doc/fnott.ini.5.scd 2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/doc/fnott.ini.5.scd 2024-08-02 11:48:03.000000000 +0200
@@ -154,6 +154,9 @@
        Border color of the notification, in RGBA format. Default:
        _909090ff_.
 
+*border-radius*
+       Corner radius on the border in pixels. Default: _0_.
+
 *border-size*
        Border size, in pixels. Default: _1_.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/doc/fnottctl.1.scd 
new/fnott-1.7.0/doc/fnottctl.1.scd
--- old/fnott-1.6.0/doc/fnottctl.1.scd  2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/doc/fnottctl.1.scd  2024-08-02 11:48:03.000000000 +0200
@@ -5,6 +5,7 @@
 
 # SYNOPSIS
 *fnottctl* *dismiss* [_id_]++
+*fnottctl* *dismiss-with-default-action* [_id_]++
 *fnottctl* *actions* [_id_]++
 *fnottctl* *list*++
 *fnottctl* *pause*++
@@ -28,12 +29,18 @@
 
 The most common operation is *fnottctl dismiss*. This will dismiss the
 highest priority notification. You might want to bind this to a
-keyboard shortcut in your Wayland compositor configuration.
+keyboard shortcut in your Wayland compositor configuration. This is
+the same as right clicking the notification.
 
 To see, and select between, actions associated with the notification,
 use *fnottctl actions*. This requires a dmenu-like utility to have
 been configured in *fnott.ini*(5).
 
+Finally, you can trigger the notification's default action (an action
+named *default*) and dismiss it simultaneously with
+*dismiss-with-default-action*. This is the same as left clicking the
+notification.
+
 You can optionally specify a notification ID, to dismiss (or show
 actions for) a specific notification instead of the highest priority
 one.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/fnott.ini new/fnott-1.7.0/fnott.ini
--- old/fnott-1.6.0/fnott.ini   2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/fnott.ini   2024-08-02 11:48:03.000000000 +0200
@@ -23,6 +23,7 @@
 # background=3f5f3fff
 
 # border-color=909090ff
+# border-radius=0
 # border-size=1
 
 # padding-vertical=20
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/fnottctl.c new/fnott-1.7.0/fnottctl.c
--- old/fnott-1.6.0/fnottctl.c  2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/fnottctl.c  2024-08-02 11:48:03.000000000 +0200
@@ -18,7 +18,7 @@
 static void
 print_usage(const char *prog)
 {
-    printf("Usage: %s dismiss | actions [<id>]\n"
+    printf("Usage: %s dismiss | actions | dismiss-with-default-action [<id>]\n"
            "       %s list | pause | unpause | quit\n"
            "       %s --version\n"
            "\n"
@@ -86,6 +86,8 @@
             ? CTRL_DISMISS_ALL : CTRL_DISMISS_BY_ID;
     } else if (strcmp(cmd_word, "actions") == 0)
         cmd_type = CTRL_ACTIONS_BY_ID;
+    else if (strcmp(cmd_word, "dismiss-with-default-action") == 0)
+        cmd_type = CTRL_DISMISS_WITH_DEFAULT_ACTION_BY_ID;
     else if (strcmp(cmd_word, "list") == 0)
         cmd_type = CTRL_LIST;
     else if (strcmp(cmd_word, "pause") == 0)
@@ -102,6 +104,7 @@
     switch (cmd_type) {
     case CTRL_DISMISS_BY_ID:
     case CTRL_ACTIONS_BY_ID:
+    case CTRL_DISMISS_WITH_DEFAULT_ACTION_BY_ID:
         if (have_id) {
             char *end = NULL;
             errno = 0;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/icon.c new/fnott-1.7.0/icon.c
--- old/fnott-1.6.0/icon.c      2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/icon.c      2024-08-02 11:48:03.000000000 +0200
@@ -13,8 +13,8 @@
 
 #include <tllist.h>
 
-#include <nanosvg.h>
-#include <nanosvgrast.h>
+#include <nanosvg/nanosvg.h>
+#include <nanosvg/nanosvgrast.h>
 
 #define LOG_MODULE "icon"
 #define LOG_ENABLE_DBG 0
@@ -326,8 +326,8 @@
 
         if (strcasecmp(key, "inherits") == 0) {
             char *ctx = NULL;
-            for (const char *theme_name = strtok_r(value, ",", &ctx);
-                 theme_name != NULL; theme_name = strtok_r(NULL, ",", &ctx))
+            for (const char *theme_name = strtok_r(value, ", ", &ctx);
+                 theme_name != NULL; theme_name = strtok_r(NULL, ", ", &ctx))
             {
                 tll_push_back(*themes_to_load, strdup(theme_name));
             }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/log.c new/fnott-1.7.0/log.c
--- old/fnott-1.6.0/log.c       2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/log.c       2024-08-02 11:48:03.000000000 +0200
@@ -30,7 +30,15 @@
         [LOG_CLASS_DEBUG] = LOG_DEBUG,
     };
 
-    colorize = _colorize == LOG_COLORIZE_NEVER ? false : _colorize == 
LOG_COLORIZE_ALWAYS ? true : isatty(STDERR_FILENO);
+    /* Don't use colors if NO_COLOR is defined and not empty */
+    const char *no_color_str = getenv("NO_COLOR");
+    const bool no_color = no_color_str != NULL && no_color_str[0] != '\0';
+
+    colorize = _colorize == LOG_COLORIZE_NEVER
+        ? false
+        : _colorize == LOG_COLORIZE_ALWAYS
+            ? true
+            : !no_color && isatty(STDERR_FILENO);
     do_syslog = _do_syslog;
 
     if (do_syslog) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/meson.build new/fnott-1.7.0/meson.build
--- old/fnott-1.6.0/meson.build 2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/meson.build 2024-08-02 11:48:03.000000000 +0200
@@ -1,5 +1,5 @@
 project('fnott', 'c',
-        version: '1.6.0',
+        version: '1.7.0',
         license: 'MIT',
         meson_version: '>=0.59.0',
         default_options: [
@@ -60,6 +60,8 @@
 wayland_cursor = dependency('wayland-cursor')
 dbus = dependency('dbus-1')
 fontconfig = dependency('fontconfig')
+system_nanosvg = cc.find_library('nanosvg', required: 
get_option('system-nanosvg'))
+system_nanosvgrast = cc.find_library('nanosvgrast', required: 
get_option('system-nanosvg'))
 
 tllist = dependency('tllist', version: '>=1.0.1', fallback: 'tllist')
 fcft = dependency('fcft', version: ['>=3.0.0', '<4.0.0'], fallback: 'fcft')
@@ -80,6 +82,7 @@
   wayland_protocols_datadir / 
'staging/fractional-scale/fractional-scale-v1.xml',
   wayland_protocols_datadir / 'unstable/tablet/tablet-unstable-v2.xml',  # 
required by cursor-shape-v1
   wayland_protocols_datadir / 'staging/cursor-shape/cursor-shape-v1.xml',
+  wayland_protocols_datadir / 'staging/xdg-activation/xdg-activation-v1.xml',
 ]
 
 wl_proto_headers = []
@@ -106,11 +109,19 @@
   output: 'version.h',
   command: [env, 'LC_ALL=C', generate_version_sh, meson.project_version(), 
'@CURRENT_SOURCE_DIR@', '@OUTPUT@'])
 
-nanosvg = declare_dependency(
-  sources: ['nanosvg.c', '3rd-party/nanosvg/src/nanosvg.h',
-            'nanosvgrast.c', '3rd-party/nanosvg/src/nanosvgrast.h'],
-  include_directories: '3rd-party/nanosvg/src',
-  dependencies: math)
+if system_nanosvg.found() and system_nanosvgrast.found()
+  nanosvg = declare_dependency(
+    dependencies: [system_nanosvg, system_nanosvgrast, math]
+  )
+  svg_lib = 'nanosvg (system)'
+else
+  nanosvg = declare_dependency(
+    sources: ['nanosvg.c', '3rd-party/nanosvg/src/nanosvg.h',
+              'nanosvgrast.c', '3rd-party/nanosvg/src/nanosvgrast.h'],
+    include_directories: '.',
+    dependencies: math)
+  svg_lib = 'nanosvg (bundled)'
+endif
 
 executable(
   'fnott',
@@ -162,6 +173,7 @@
 
 summary(
   {
+    'SVG': svg_lib,
     'Documentation': scdoc.found(),
   }
 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/meson_options.txt 
new/fnott-1.7.0/meson_options.txt
--- old/fnott-1.6.0/meson_options.txt   2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/meson_options.txt   2024-08-02 11:48:03.000000000 +0200
@@ -1,5 +1,6 @@
 option('docs', type: 'feature',
        description: 'Build and install documentation (man pages, example 
fnott.ini, readme, license etc).')
-
+option('system-nanosvg', type: 'feature', value: 'disabled',
+       description: 'use system\'s nanosvg instead of the bundled version')
 option('systemd-units-dir', type: 'string', value: '',
       description: 'Where to install the systemd service files (absolute 
path). Default: ${systemduserunitdir}')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/nanosvg/nanosvg.h 
new/fnott-1.7.0/nanosvg/nanosvg.h
--- old/fnott-1.6.0/nanosvg/nanosvg.h   1970-01-01 01:00:00.000000000 +0100
+++ new/fnott-1.7.0/nanosvg/nanosvg.h   2024-08-02 11:48:03.000000000 +0200
@@ -0,0 +1 @@
+#include <3rd-party/nanosvg/src/nanosvg.h>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/nanosvg/nanosvgrast.h 
new/fnott-1.7.0/nanosvg/nanosvgrast.h
--- old/fnott-1.6.0/nanosvg/nanosvgrast.h       1970-01-01 01:00:00.000000000 
+0100
+++ new/fnott-1.7.0/nanosvg/nanosvgrast.h       2024-08-02 11:48:03.000000000 
+0200
@@ -0,0 +1 @@
+#include <3rd-party/nanosvg/src/nanosvgrast.h>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/nanosvg.c new/fnott-1.7.0/nanosvg.c
--- old/fnott-1.6.0/nanosvg.c   2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/nanosvg.c   2024-08-02 11:48:03.000000000 +0200
@@ -3,4 +3,4 @@
 #include <math.h>
 #define NANOSVG_ALL_COLOR_KEYWORDS
 #define NANOSVG_IMPLEMENTATION
-#include <nanosvg.h>
+#include <3rd-party/nanosvg/src/nanosvg.h>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/nanosvgrast.c 
new/fnott-1.7.0/nanosvgrast.c
--- old/fnott-1.6.0/nanosvgrast.c       2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/nanosvgrast.c       2024-08-02 11:48:03.000000000 +0200
@@ -1,6 +1,6 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include <nanosvg.h>
+#include <3rd-party/nanosvg/src/nanosvg.h>
 #define NANOSVGRAST_IMPLEMENTATION
-#include <nanosvgrast.h>
+#include <3rd-party/nanosvg/src/nanosvgrast.h>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/notification.c 
new/fnott-1.7.0/notification.c
--- old/fnott-1.6.0/notification.c      2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/notification.c      2024-08-02 11:48:03.000000000 +0200
@@ -76,11 +76,12 @@
     bool is_configured;
 
     uint32_t id;
-    enum urgency urgency;
+    char *synchronous_tag;  /* x-canonical-private-synchronous */
 
     char32_t *app;
     char32_t *summary;
     char32_t *body;
+    enum urgency urgency;
     tll(struct action) actions;
 
     int8_t progress;
@@ -180,6 +181,8 @@
 
     regfree(&mgr->html_entity_re);
 
+    notif_mgr_dismiss_all(mgr);
+
     tll_foreach(mgr->notifs, it)
         notif_destroy(it->item);
     tll_free(mgr->notifs);
@@ -401,6 +404,23 @@
 }
 
 struct notif *
+notif_mgr_get_notif_for_sync_tag(struct notif_mgr *mgr, const char *tag)
+{
+    if (tag == NULL)
+        return NULL;
+
+    tll_foreach(mgr->notifs, it) {
+        if (it->item->synchronous_tag == NULL)
+            continue;
+
+        if (strcmp(it->item->synchronous_tag, tag) == 0)
+            return it->item;
+    }
+
+    return NULL;
+}
+
+struct notif *
 notif_mgr_get_notif_for_surface(struct notif_mgr *mgr,
                                 const struct wl_surface *surface)
 {
@@ -415,10 +435,17 @@
 /* Instantiates a new notification. You *must* call
  * notif_mgr_refresh() "soon" (after configuring the notification). */
 struct notif *
-notif_mgr_create_notif(struct notif_mgr *mgr, uint32_t replaces_id)
+notif_mgr_create_notif(struct notif_mgr *mgr, uint32_t replaces_id,
+                       const char *sync_tag)
 {
     int notif_id;
 
+    {
+        struct notif *old_notif = notif_mgr_get_notif_for_sync_tag(mgr, 
sync_tag);
+        if (old_notif != NULL)
+            return old_notif;
+    }
+
     if (replaces_id != 0) {
         struct notif *old_notif = notif_mgr_get_notif(mgr, replaces_id);
         if (old_notif != NULL)
@@ -432,10 +459,11 @@
     *notif = (struct notif) {
         .mgr = mgr,
         .id = notif_id,
-        .urgency = URGENCY_NORMAL,
+        .synchronous_tag = sync_tag != NULL ? strdup(sync_tag) : NULL,
         .app = c32dup(U""),
         .summary = c32dup(U""),
         .body = c32dup(U""),
+        .urgency = URGENCY_NORMAL,
         .actions = tll_init(),
         .timeout_ms = -1,  /* -1, up to us, 0 - never expire */
         .timeout_fd = -1,
@@ -529,6 +557,7 @@
         tll_remove(notif->text_run_cache, it);
     }
 
+    free(notif->synchronous_tag);
     free(notif->app);
     free(notif->summary);
     free(notif->body);
@@ -1712,6 +1741,33 @@
     return ret;
 }
 
+static inline void
+fill_rounded_rectangle(pixman_op_t op, pixman_image_t* dest,
+                       const pixman_color_t* color, int16_t x, int16_t y,
+                       uint16_t width, uint16_t height, uint16_t radius)
+{
+    int rect_count = ( radius + radius ) + 1;
+    pixman_rectangle16_t rects[rect_count];
+
+    for (int i = 0; i <= radius; i++){
+        uint16_t ydist = radius - i;
+        uint16_t curve = sqrt(radius * radius - ydist * ydist);
+
+        rects[i] = (pixman_rectangle16_t){
+            x + radius - curve, y + i, width - radius * 2 + curve * 2, 1
+        };
+
+        rects[radius + i] = (pixman_rectangle16_t){
+            x + radius - curve, y + height - i, width - radius * 2 + curve * 
2, 1
+        };
+    }
+
+    rects[(radius * 2)] = (pixman_rectangle16_t){
+        x, y + radius, width, height + 1 - radius * 2
+    };
+    pixman_image_fill_rectangles(op, dest, color,rect_count, rects);
+}
+
 static int
 notif_show(struct notif *notif, int y)
 {
@@ -1881,30 +1937,71 @@
         roundf(conf->margins.horizontal / scale));  /* left */
 
     struct buffer *buf = wayl_get_buffer(wayl, width, height);
-    const int brd_sz = urgency->border.size;;
+    const int brd_sz = urgency->border.size;
+    const int brd_rad = min(min(urgency->border.radius, buf->width*0.5), 
buf->height*0.5);
 
     pixman_region32_t clip;
     pixman_region32_init_rect(&clip, 0, 0, width, height);
     pixman_image_set_clip_region32(buf->pix, &clip);
     pixman_region32_fini(&clip);
 
-    /* Border */
-    pixman_image_fill_rectangles(
-        PIXMAN_OP_SRC, buf->pix, &urgency->border.color,
-        4, (pixman_rectangle16_t []){
-            {0, 0, buf->width, brd_sz},                     /* top */
-            {buf->width - brd_sz, 0, brd_sz, buf->height},  /* right */
-            {0, buf->height - brd_sz, buf->width, brd_sz},  /* bottom */
-            {0, 0, brd_sz, buf->height},                    /* left */
-    });
-
-    /* Background */
-    pixman_image_fill_rectangles(
-        PIXMAN_OP_SRC, buf->pix, &urgency->bg,
-        1, &(pixman_rectangle16_t){
-            brd_sz, brd_sz,
-            buf->width - 2 * brd_sz, buf->height - 2 * brd_sz}
-    );
+    if (brd_rad == 0){
+        /* Border */
+        pixman_image_fill_rectangles(
+            PIXMAN_OP_SRC, buf->pix, &urgency->border.color,
+            4, (pixman_rectangle16_t []){
+                {0, 0, buf->width, brd_sz},                     /* top */
+                {buf->width - brd_sz, 0, brd_sz, buf->height},  /* right */
+                {0, buf->height - brd_sz, buf->width, brd_sz},  /* bottom */
+                {0, 0, brd_sz, buf->height},                    /* left */
+            });
+
+        /* Background */
+        pixman_image_fill_rectangles(
+            PIXMAN_OP_SRC, buf->pix, &urgency->bg,
+            1, &(pixman_rectangle16_t){
+                brd_sz, brd_sz,
+                buf->width - 2 * brd_sz, buf->height - 2 * brd_sz}
+          );
+    } else {
+        const int msaa_scale = 2;
+        const double brd_sz_scaled = brd_sz * msaa_scale;
+        const double brd_rad_scaled = brd_rad * msaa_scale;
+        int w = buf->width * msaa_scale;
+        int h = buf->height * msaa_scale;
+
+        pixman_image_t *bg;
+        if (msaa_scale != 1){
+            bg = pixman_image_create_bits(PIXMAN_a8r8g8b8, w, h, NULL, w*4);
+        } else {
+            bg = buf->pix;
+        }
+
+        /* Border */
+        fill_rounded_rectangle(
+            PIXMAN_OP_SRC, bg, &urgency->border.color,
+            0, 0, w, h, brd_rad_scaled);
+
+        /* Background */
+        fill_rounded_rectangle(
+            PIXMAN_OP_SRC, bg, &urgency->bg, brd_sz_scaled, brd_sz_scaled,
+            w-(brd_sz_scaled*2),
+            h-(brd_sz_scaled*2),
+            brd_rad_scaled - brd_sz_scaled);
+
+        if (msaa_scale != 1){
+            pixman_f_transform_t ftrans;
+            pixman_transform_t trans;
+            pixman_f_transform_init_scale(&ftrans, msaa_scale, msaa_scale);
+            pixman_transform_from_pixman_f_transform(&trans, &ftrans);
+            pixman_image_set_transform(bg, &trans);
+            pixman_image_set_filter(bg, PIXMAN_FILTER_BILINEAR, NULL, 0);
+
+            pixman_image_composite32(
+                PIXMAN_OP_SRC, bg, NULL, buf->pix, 0, 0, 0, 0, 0, 0, 
buf->width, buf->height);
+            pixman_image_unref(bg);
+        }
+    }
 
     /* Image */
     if (notif->pix != NULL) {
@@ -2273,6 +2370,9 @@
         async->input_len - async->input_idx);
 
     if (count < 0) {
+        if (errno == EINTR)
+            return true;
+
         LOG_ERRNO("could not write actions to actions selection helper");
         goto done;
     }
@@ -2297,25 +2397,33 @@
 {
     struct action_async *async = data;
 
-    const size_t chunk_sz = 128;
+    const size_t chunk_sz = 1024;
     char buf[chunk_sz];
 
     ssize_t count = read(async->from_child, buf, chunk_sz);
     if (count < 0) {
+        if (errno == EINTR)
+            return true;
+
         LOG_ERRNO("failed to read from actions selection helper");
-        goto check_pollhup;
-    }
 
-    /* Append to previously received response */
-    size_t new_len = async->output_len + count;
-    async->output = realloc(async->output, new_len);
-    memcpy(&async->output[async->output_len], buf, count);
-    async->output_len = new_len;
+        /* FIXME: leaks memory (the async context) */
+        return false;
+    }
 
-check_pollhup:
+    if (count > 0) {
+        /* Append to previously received response */
+        size_t new_len = async->output_len + count;
+        async->output = realloc(async->output, new_len);
+        memcpy(&async->output[async->output_len], buf, count);
+        async->output_len = new_len;
 
-    if (!(events & EPOLLHUP))
+        /* There may be more data to read */
         return true;
+    }
+
+    /* No more data to read */
+    assert(count == 0);
 
     /* Strip trailing spaces/newlines */
     while (async->output_len > 0
@@ -2382,7 +2490,7 @@
     LOG_WARN("could not map chosen action label to action ID: %.*s", 
(int)chosen_len, chosen);
 
 done:
-    completion_cb(notif_id, action_id, cb_data);
+    completion_cb(notif, action_id, cb_data);
     free(chosen);
 
     if (notif->deferred_expiral == EXPIRE_DELAYED) {
@@ -2485,6 +2593,28 @@
     return tll_length(notif->actions);
 }
 
+bool
+notif_signal_action(const struct notif *notif, const char *action_id)
+{
+    if (action_id == NULL)
+        return false;
+
+    tll_foreach(notif->actions, it) {
+        if (strcmp(it->item.id, action_id) == 0) {
+            char *activation_token =
+                wayl_get_activation_token(notif->mgr->wayl, notif->surface);
+
+            if (activation_token != NULL)
+                dbus_signal_token(notif->mgr->bus, notif->id, 
activation_token);
+            free(activation_token);
+
+            return dbus_signal_action(notif->mgr->bus, notif->id, action_id);
+        }
+    }
+
+    return false;
+}
+
 void
 notif_select_action(
     struct notif *notif, notif_select_action_cb completion_cb, void *data)
@@ -2625,7 +2755,7 @@
     free(copy);
     free(argv);
 
-    completion_cb(notif->id, NULL, data);
+    completion_cb(notif, NULL, data);
     notif->deferred_dismissal = DISMISS_IMMEDIATELY;
     notif->deferred_expiral = EXPIRE_IMMEDIATELY;
     return;
@@ -2635,7 +2765,7 @@
     free(input);
     fdm_del(notif->mgr->fdm, to_child[1]);
     fdm_del(notif->mgr->fdm, from_child[0]);
-    completion_cb(notif->id, NULL, data);
+    completion_cb(notif, NULL, data);
     notif->deferred_dismissal = DISMISS_IMMEDIATELY;
     notif->deferred_expiral = EXPIRE_IMMEDIATELY;
     return;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/notification.h 
new/fnott-1.7.0/notification.h
--- old/fnott-1.6.0/notification.h      2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/notification.h      2024-08-02 11:48:03.000000000 +0200
@@ -43,8 +43,11 @@
 enum urgency { URGENCY_LOW, URGENCY_NORMAL, URGENCY_CRITICAL };
 
 struct notif;
-struct notif *notif_mgr_create_notif(struct notif_mgr *mgr, uint32_t 
replaces_id);
+struct notif *notif_mgr_create_notif(
+    struct notif_mgr *mgr, uint32_t replaces_id, const char *sync_tag);
 struct notif *notif_mgr_get_notif(struct notif_mgr *mgr, uint32_t id);
+struct notif *notif_mgr_get_notif_for_sync_tag(
+    struct notif_mgr *mgr, const char *tag);
 struct notif *notif_mgr_get_notif_for_surface(
     struct notif_mgr *mgr, const struct wl_surface *surface);
 bool notif_mgr_del_notif(struct notif_mgr *mgr, uint32_t id);
@@ -67,8 +70,9 @@
 char *notif_get_summary(const struct notif *notif);
 
 typedef void (*notif_select_action_cb)(
-    uint32_t notif_id, const char *action_id, void *data);
+    struct notif *notif, const char *action_id, void *data);
 
 size_t notif_action_count(const struct notif *notif);
+bool notif_signal_action(const struct notif *notif, const char *action_id);
 void notif_select_action(
     struct notif *notif, notif_select_action_cb completion_cb, void *data);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/svg.c new/fnott-1.7.0/svg.c
--- old/fnott-1.6.0/svg.c       2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/svg.c       2024-08-02 11:48:03.000000000 +0200
@@ -6,8 +6,8 @@
 #define LOG_ENABLE_DBG 0
 #include "log.h"
 
-#include <nanosvg.h>
-#include <nanosvgrast.h>
+#include <nanosvg/nanosvg.h>
+#include <nanosvg/nanosvgrast.h>
 
 pixman_image_t *
 svg_load(const char *path, int size)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/wayland.c new/fnott-1.7.0/wayland.c
--- old/fnott-1.6.0/wayland.c   2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/wayland.c   2024-08-02 11:48:03.000000000 +0200
@@ -14,6 +14,7 @@
 #include <wayland-client.h>
 #include <wayland-cursor.h>
 #include <wlr-layer-shell-unstable-v1.h>
+#include <xdg-activation-v1.h>
 #include <xdg-output-unstable-v1.h>
 
 #include <tllist.h>
@@ -79,6 +80,7 @@
     struct wp_viewporter *viewporter;
     struct wp_fractional_scale_manager_v1 *fractional_scale_manager;
     struct wp_cursor_shape_manager_v1 *cursor_shape_manager;
+    struct xdg_activation_v1 *xdg_activation;
 
     bool have_argb8888;
 
@@ -426,6 +428,77 @@
 }
 
 static void
+xdg_activation_token_done(
+    void *data, struct xdg_activation_token_v1 *xdg_activation_token_v1,
+    const char *token)
+{
+    char **out = data;
+    *out = token != NULL ? strdup(token) : NULL;
+}
+
+
+static const struct xdg_activation_token_v1_listener 
xdg_activation_token_listener = {
+    .done = &xdg_activation_token_done,
+};
+
+char *
+wayl_get_activation_token(struct wayland *wayl, struct wl_surface *surface)
+{
+    if (wayl->xdg_activation == NULL)
+        return NULL;
+
+    struct seat *seat = NULL;
+    tll_foreach(wayl->seats, it) {
+        if (it->item.pointer.on_surface == surface) {
+            seat = &it->item;
+            break;
+        }
+    }
+
+    if (seat == NULL ||
+        seat->pointer.serial == 0 ||
+        seat->pointer.on_surface == NULL)
+    {
+        return NULL;
+    }
+
+    struct xdg_activation_token_v1 *token =
+        xdg_activation_v1_get_activation_token(wayl->xdg_activation);
+
+    if (token == NULL)
+        return NULL;
+
+    char *token_str = NULL;
+    xdg_activation_token_v1_add_listener(
+        token, &xdg_activation_token_listener, &token_str);
+
+    xdg_activation_token_v1_set_serial(token, seat->pointer.serial, 
seat->wl_seat);
+    xdg_activation_token_v1_set_surface(token, seat->pointer.on_surface);
+    xdg_activation_token_v1_commit(token);
+    wl_display_flush(wayl->display);
+
+    while (token_str == NULL) {
+        while (wl_display_prepare_read(wayl->display) != 0) {
+            if (wl_display_dispatch_pending(wayl->display) < 0) {
+                LOG_ERRNO("failed to dispatch pending Wayland events");
+                goto out;
+            }
+        }
+
+        if (wl_display_read_events(wayl->display) < 0) {
+            LOG_ERRNO("failed to read events from the Wayland socket");
+            goto out;
+        }
+    }
+
+out:
+    xdg_activation_token_v1_destroy(token);
+
+    LOG_DBG("XDG activation token: %s", token_str != NULL ? token_str : 
"<failed>");
+    return token_str;
+}
+
+static void
 wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
                   uint32_t serial, uint32_t time, uint32_t button, uint32_t 
state)
 {
@@ -434,14 +507,23 @@
     struct seat *seat = data;
     struct wayland *wayl = seat->wayl;
 
+    seat->pointer.serial = serial;
+
     switch (state) {
     case WL_POINTER_BUTTON_STATE_PRESSED: {
-        if (button == BTN_LEFT) {
-            struct notif *notif = notif_mgr_get_notif_for_surface(
-                wayl->notif_mgr, seat->pointer.on_surface);
+        struct notif *notif = notif_mgr_get_notif_for_surface(
+            wayl->notif_mgr, seat->pointer.on_surface);
+
+        if (notif != NULL) {
+            if (button == BTN_LEFT) {
+                notif_signal_action(notif, "default");
+                notif_mgr_dismiss_id(wayl->notif_mgr, notif_id(notif));
+            }
 
-            if (notif != NULL)
+            else if (button == BTN_RIGHT) {
+                /* Dismiss without triggering the default action */
                 notif_mgr_dismiss_id(wayl->notif_mgr, notif_id(notif));
+            }
         }
         break;
     }
@@ -848,6 +930,14 @@
             wayl->registry, name, &wp_cursor_shape_manager_v1_interface, 
required);
     }
 
+    else if (strcmp(interface, xdg_activation_v1_interface.name) == 0) {
+        const uint32_t required = 1;
+        if (!verify_iface_version(interface, version, required))
+            return;
+
+        wayl->xdg_activation = wl_registry_bind(
+            wayl->registry, name, &xdg_activation_v1_interface, required);
+    }
 }
 
 static void
@@ -1038,6 +1128,8 @@
         seat_destroy(&it->item);
     tll_free(wayl->seats);
 
+    if (wayl->xdg_activation != NULL)
+        xdg_activation_v1_destroy(wayl->xdg_activation);
     if (wayl->cursor_shape_manager != NULL)
         wp_cursor_shape_manager_v1_destroy(wayl->cursor_shape_manager);
     if (wayl->fractional_scale_manager != NULL)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fnott-1.6.0/wayland.h new/fnott-1.7.0/wayland.h
--- old/fnott-1.6.0/wayland.h   2024-04-30 08:30:46.000000000 +0200
+++ new/fnott-1.7.0/wayland.h   2024-08-02 11:48:03.000000000 +0200
@@ -82,6 +82,8 @@
 struct wp_fractional_scale_manager_v1 *wayl_fractional_scale_manager(const 
struct wayland *wayl);
 struct wp_viewporter *wayl_viewporter(const struct wayland *wayl);
 
+char *wayl_get_activation_token(struct wayland *wayl, struct wl_surface 
*surface);
+
 bool wayl_is_idle_for_urgency(const struct wayland *wayl, const enum urgency 
urgency);
 
 struct buffer *wayl_get_buffer(const struct wayland *wayl, int width, int 
height);

Reply via email to