Compare commits

..

32 Commits

Author SHA1 Message Date
Andre Klapper 80121a3b8c docs: Fix URL to Desktop Notification Spec
Fixes #29
2022-09-04 11:59:49 +02:00
Marco Trevisan (Treviño) 650f2f123e
Releasing version 0.8.1
Update NEWS.
2022-07-17 15:00:39 +02:00
Marco Trevisan (Treviño) a8374698c4 build: Hardcode API version to 0.7
Fixes: #27
2022-07-17 14:58:13 +02:00
Marco Trevisan (Treviño) 359443f70f
Releasing version 0.8.0
Update NEWS.
2022-07-14 17:18:41 +02:00
Marco Trevisan (Treviño) 3d81394cd2 NEWS: Fix style on previous announcement 2022-07-14 17:18:25 +02:00
Marco Trevisan (Treviño) 5e6a52fa4f README: Add reference to Portal Notification APIs 2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) 532608248e build: Pre-release bump to 0.8.0 2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) adb3e0c7bf notification: Warn if trying to get the activation token outside activation
This API is intended to be used only during a NotifyActionCallback, so
ensure that this is the case, by warn in case it's not the case.
2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) 13718b6ea0 notify: Also try to use the flatpak app name as notify app 2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) f3eb807464 notification: Use Desktop Portal notification when running in sandbox
When an application is running from a snap or a flatpak, libnotify
should just work as a FDO Portal Notification wrapper.

As per this, make the library to try use the portal API in sandboxed
applications, by emulating the needed missing pieces.

Not everything can be achieved 1:1, but it's just safer to use in such
contexts.
2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) c6534a144c build: Bump dependency on glib 2.38
It's almost 10 years old, so I think we can safely depend on that to
support the features we're optionally depending on, plus the ones we're
going to add
2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) 543d636153 notification: Move action activation and closing in separate functions
Move handling of such events in different functions so that can be
re-used
2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) 9e9cb423b9 Notification: Add NotifyClosedReason enum to define closed reasons
We don't change the formal type of notify_notification_get_closed_reason
but it's safe enough to compare the two returned values.
2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) cc30d8b8dd notify: Support passing a NULL application name to notify_init()
We can find its name in some scenarios, so make it clearer from in the API.

Also, ensure that notify_set_app_name() is not used with a NULL App
name.
2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) e8ea0c39d0 notify: Use lazy snap readings and once per notify instance
An application under snap confinement won't have a way of escaping from
it, so we can just initialize the snap variables at the global notify
scope once and just re-use them when needed.
2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) 74f87250a3 notify-send: Handle Ctrl+C by closing the notification before exiting
Avoid leaving notifications we're waiting for around on SIGNINT
2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) 07a2b2e32c notify-send: Add debug logs about the notification daemon 2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) 8ce5daac4f notification: Add docs description for NotifyNotification 2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) 9d28fa3600 notification: Document get_activation_token argument 2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) 4e40a460ce notification: Fix documentation and transfer info for NotifyActionCallback 2022-06-07 03:18:38 +02:00
Logan Rathbone 8162b1d397 Bump version to 0.7.12
Update NEWS.
2022-05-05 12:03:27 -04:00
Logan Rathbone 4d68baf5ef Merge branch 'fix-electron-crash' into 'master'
Closes #25

See merge request GNOME/libnotify!26
2022-05-05 02:10:25 +00:00
Logan Rathbone 596d09a78f notification: Bookend calling NotifyActionCallback with temporary ref
Starting with d0778595, we access the Notification object after the
NotifyActionCallback; some clients (eg, Electron) have historically
unref'd the object at the end of the NotifyActionCallback. They probably
should not have been doing so, but this hotfix adds an additional ref
before and after the callback so that we don't break existing code.

Fixes #25
2022-05-04 21:04:25 -04:00
Logan Rathbone f5b9619b2f doap: Add self as co-maintainer; remove chammond and mccann
Per:
- Discussions on #release-team
- No response to email to mccann in over 10 days
- Email response from chammond confirming he should no longer be listed
  as maintainer

See also: afef1e98
Closes: #14
2022-05-04 11:36:37 -04:00
Marco Trevisan (Treviño) afef1e98b3 Adding myself as maintainer
As discussed on #release-team:

  21:12:18 <ebassi> Trevinho: You could already add yourself to the DOAP file
2022-05-04 16:22:23 +02:00
Marco Trevisan (Treviño) ecb1d4ec08 libnotify.doap: Add missing description field 2022-05-04 16:22:23 +02:00
Patrick Griffis 6d8e38bc69 Merge branch 'pgriffis/delete-unused-file' into 'master'
Delete unused notifynotification.xml

See merge request GNOME/libnotify!25
2022-05-03 20:37:41 +00:00
Patrick Griffis 6e6d52340b Delete unused notifynotification.xml 2022-05-03 20:35:50 +00:00
Marco Trevisan (Treviño) 1fba04bc03 notification: Include sender-pid hint by default if not provided
It's used by various daemons including GNOME Shell to figure out the
parent application.
2022-04-28 01:06:58 +02:00
Marco Trevisan (Treviño) a674e610ee notify-send: Add debug message about server not supporting persistence
In case a transient notification is requested but persistence is not
supported, it's not an error but it is useful to show a debug message
to clarify that.
2022-04-27 21:32:56 +02:00
Marco Trevisan (Treviño) c99aacdb91 notify-send: Move server capabilities check to a separate function 2022-04-27 21:32:30 +02:00
Marco Trevisan (Treviño) 997115b459 docs/notify-send: Add --transient option to manpage 2022-04-27 21:25:10 +02:00
14 changed files with 966 additions and 205 deletions

38
NEWS
View File

@ -1,3 +1,41 @@
New in 0.8.1
============
* Keep version of bindings at 0.7 (#27) [Marco]
Contributors:
Marco Trevisan
New in 0.8.0
============
* Use Desktop Portal Notification when running confined (snap and flatpak)
Now the library acts like a wrapper in such scenario, with some limited
capabilities, but this will enforce security and user control over the
allowed notifications. [Marco]
* notify-send: Handles SIGINT gracefully, closing waiting notification [Marco]
* Use NotifyClosedReason enum as closed reason return value [Marco]
* Bump dependency on GLib 2.38 [Marco]
* Various introspection docs improvements and fixes [Marco]
Contributors:
Marco Trevisan
New in 0.7.12
=============
* docs/notify-send: Add --transient option to manpage [Marco]
* notify-send: Move server capabilities check to a separate function [Marco]
* notify-send: Add debug message about server not supporting persistence
[Marco]
* notification: Include sender-pid hint by default if not provided [Marco]
* Delete unused notifynotification.xml [Patrick; !25]
* notification: Bookend calling NotifyActionCallback with temporary ref
[Logan; #25, !26]
Contributors:
Marco Trevisan, Patrick Griffis, Logan Rathbone
New in 0.7.11 New in 0.7.11
============= =============
* Fix potential build errors with old glib version we require * Fix potential build errors with old glib version we require

View File

@ -7,6 +7,9 @@ daemon, as defined in the [org.freedesktop.Notifications][fdo-spec] Desktop
Specification. These notifications can be used to inform the user about an event Specification. These notifications can be used to inform the user about an event
or display some form of information without getting in the user's way. or display some form of information without getting in the user's way.
It is also a simple wrapper to send cross-desktop Notifications for sandboxed
applications using the [XDG Portal Notification API][portal].
## Notice ## Notice
For GLib based applications the [GNotification][gnotif] API should be used For GLib based applications the [GNotification][gnotif] API should be used
@ -14,3 +17,4 @@ instead.
[fdo-spec]: https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html [fdo-spec]: https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html
[gnotif]: https://docs.gtk.org/gio/class.Notification.html [gnotif]: https://docs.gtk.org/gio/class.Notification.html
[portal]: https://github.com/flatpak/xdg-desktop-portal/blob/main/data/org.freedesktop.portal.Notification.xml

View File

@ -122,11 +122,17 @@
<para>Wait for the notification to be closed before exiting. If the <option>expire-time</option> is set, it will be used as the maximum waiting time.</para> <para>Wait for the notification to be closed before exiting. If the <option>expire-time</option> is set, it will be used as the maximum waiting time.</para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><option>-e</option>, <option>--transient</option></term>
<listitem>
<para>Show a transient notification. Transient notifications by-pass the server's persistence capability, if any. And so it won't be preserved until the user acknowledges it.</para>
</listitem>
</varlistentry>
</variablelist> </variablelist>
</refsection> </refsection>
<refsection> <refsection>
<info><title>See also</title></info> <info><title>See also</title></info>
<para>The Desktop Notification Spec on <link xlink:href="http://www.galago-project.org/specs/notification/">http://www.galago-project.org/specs/notification/</link>.</para> <para>The Desktop Notification Spec on <link xlink:href="https://specifications.freedesktop.org/notification-spec/">https://specifications.freedesktop.org/notification-spec/</link>.</para>
</refsection> </refsection>
</refentry> </refentry>

View File

@ -4,6 +4,7 @@ NOTIFY_EXPIRES_DEFAULT
NOTIFY_EXPIRES_NEVER NOTIFY_EXPIRES_NEVER
<TITLE>NotifyNotification</TITLE> <TITLE>NotifyNotification</TITLE>
NotifyNotification NotifyNotification
NotifyClosedReason
NotifyUrgency NotifyUrgency
NotifyActionCallback NotifyActionCallback
NOTIFY_ACTION_CALLBACK NOTIFY_ACTION_CALLBACK

View File

@ -8,18 +8,21 @@
<category rdf:resource="http://api.gnome.org/doap-extensions#core" /> <category rdf:resource="http://api.gnome.org/doap-extensions#core" />
<programming-language>C</programming-language> <programming-language>C</programming-language>
<shortdesc xml:lang="en">libnotify is a library for sending desktop notifications</shortdesc> <shortdesc xml:lang="en">libnotify is a library for sending desktop notifications</shortdesc>
<description>Library to send desktop notifications that follows the FDO
specifications:
https://gitlab.freedesktop.org/xdg/xdg-specs/-/blob/master/notification/notification-spec.xml</description>
<maintainer> <maintainer>
<foaf:Person> <foaf:Person>
<foaf:name>Christian Hammond</foaf:name> <foaf:name>Marco Trevisan</foaf:name>
<foaf:mbox rdf:resource="mailto:chipx86@chipx86.com" /> <foaf:mbox rdf:resource="mailto:mail@3v1n0.net" />
<gnome:userid>chammond</gnome:userid> <gnome:userid>marcotrevi</gnome:userid>
</foaf:Person> </foaf:Person>
</maintainer> </maintainer>
<maintainer> <maintainer>
<foaf:Person> <foaf:Person>
<foaf:name>William Jon McCann</foaf:name> <foaf:name>Logan Rathbone</foaf:name>
<foaf:mbox rdf:resource="mailto:william.jon.mccann@gmail.com" /> <foaf:mbox rdf:resource="mailto:poprocks@gmail.com" />
<gnome:userid>mccann</gnome:userid> <gnome:userid>larathbone</gnome:userid>
</foaf:Person> </foaf:Person>
</maintainer> </maintainer>
</Project> </Project>

View File

@ -26,6 +26,10 @@
#define NOTIFY_DBUS_CORE_INTERFACE "org.freedesktop.Notifications" #define NOTIFY_DBUS_CORE_INTERFACE "org.freedesktop.Notifications"
#define NOTIFY_DBUS_CORE_OBJECT "/org/freedesktop/Notifications" #define NOTIFY_DBUS_CORE_OBJECT "/org/freedesktop/Notifications"
#define NOTIFY_PORTAL_DBUS_NAME "org.freedesktop.portal.Desktop"
#define NOTIFY_PORTAL_DBUS_CORE_INTERFACE "org.freedesktop.portal.Notification"
#define NOTIFY_PORTAL_DBUS_CORE_OBJECT "/org/freedesktop/portal/desktop"
G_BEGIN_DECLS G_BEGIN_DECLS
GDBusProxy * _notify_get_proxy (GError **error); GDBusProxy * _notify_get_proxy (GError **error);
@ -36,6 +40,14 @@ gint _notify_notification_get_timeout (const NotifyNotific
gboolean _notify_notification_has_nondefault_actions (const NotifyNotification *n); gboolean _notify_notification_has_nondefault_actions (const NotifyNotification *n);
gboolean _notify_check_spec_version (int major, int minor); gboolean _notify_check_spec_version (int major, int minor);
const char * _notify_get_snap_name (void);
const char * _notify_get_snap_path (void);
const char * _notify_get_snap_app (void);
const char * _notify_get_flatpak_app (void);
gboolean _notify_uses_portal_notifications (void);
G_END_DECLS G_END_DECLS
#endif /* _LIBNOTIFY_INTERNAL_H_ */ #endif /* _LIBNOTIFY_INTERNAL_H_ */

View File

@ -53,6 +53,7 @@
static void notify_notification_class_init (NotifyNotificationClass *klass); static void notify_notification_class_init (NotifyNotificationClass *klass);
static void notify_notification_init (NotifyNotification *sp); static void notify_notification_init (NotifyNotification *sp);
static void notify_notification_finalize (GObject *object); static void notify_notification_finalize (GObject *object);
static void notify_notification_dispose (GObject *object);
typedef struct typedef struct
{ {
@ -70,12 +71,9 @@ struct _NotifyNotificationPrivate
char *body; char *body;
char *activation_token; char *activation_token;
const char *snap_path;
const char *snap_name;
char *snap_app;
/* NULL to use icon data. Anything else to have server lookup icon */ /* NULL to use icon data. Anything else to have server lookup icon */
char *icon_name; char *icon_name;
GdkPixbuf *icon_pixbuf;
/* /*
* -1 = use server default * -1 = use server default
@ -83,6 +81,7 @@ struct _NotifyNotificationPrivate
* > 0 = Number of milliseconds before we timeout * > 0 = Number of milliseconds before we timeout
*/ */
gint timeout; gint timeout;
guint portal_timeout_id;
GSList *actions; GSList *actions;
GHashTable *action_map; GHashTable *action_map;
@ -154,6 +153,7 @@ notify_notification_class_init (NotifyNotificationClass *klass)
object_class->constructor = notify_notification_constructor; object_class->constructor = notify_notification_constructor;
object_class->get_property = notify_notification_get_property; object_class->get_property = notify_notification_get_property;
object_class->set_property = notify_notification_set_property; object_class->set_property = notify_notification_set_property;
object_class->dispose = notify_notification_dispose;
object_class->finalize = notify_notification_finalize; object_class->finalize = notify_notification_finalize;
/** /**
@ -238,9 +238,9 @@ notify_notification_class_init (NotifyNotificationClass *klass)
g_param_spec_int ("closed-reason", g_param_spec_int ("closed-reason",
"Closed Reason", "Closed Reason",
"The reason code for why the notification was closed", "The reason code for why the notification was closed",
-1, NOTIFY_CLOSED_REASON_UNSET,
G_MAXINT32, G_MAXINT32,
-1, NOTIFY_CLOSED_REASON_UNSET,
G_PARAM_READABLE G_PARAM_READABLE
| G_PARAM_STATIC_NAME | G_PARAM_STATIC_NAME
| G_PARAM_STATIC_NICK | G_PARAM_STATIC_NICK
@ -356,110 +356,12 @@ destroy_pair (CallbackPair *pair)
g_free (pair); g_free (pair);
} }
static void
maybe_initialize_snap (NotifyNotification *obj)
{
NotifyNotificationPrivate *priv = obj->priv;
gchar *cgroup_contents = NULL;
priv->snap_path = g_getenv ("SNAP");
if (priv->snap_path == NULL)
return;
if (*priv->snap_path == '\0' ||
!strchr (priv->snap_path, G_DIR_SEPARATOR)) {
priv->snap_path = NULL;
return;
}
priv->snap_name = g_getenv ("SNAP_NAME");
if (priv->snap_name && *priv->snap_name == '\0') {
priv->snap_name = NULL;
}
if (g_file_get_contents ("/proc/self/cgroup", &cgroup_contents,
NULL, NULL)) {
gchar **lines = g_strsplit (cgroup_contents, "\n", -1);
gchar *found_snap_name = NULL;
gint i;
for (i = 0; lines[i]; ++i) {
gchar **parts = g_strsplit (lines[i], ":", 3);
gchar *basename;
gchar **ns;
guint ns_length;
if (g_strv_length (parts) != 3) {
g_strfreev (parts);
continue;
}
basename = g_path_get_basename (parts[2]);
g_strfreev (parts);
if (!basename) {
continue;
}
ns = g_strsplit (basename, ".", -1);
ns_length = g_strv_length (ns);
g_free (basename);
if (ns_length < 2 || !g_str_equal (ns[0], "snap")) {
g_strfreev (ns);
continue;
}
if (priv->snap_name == NULL) {
g_free (found_snap_name);
found_snap_name = g_strdup (ns[1]);
}
if (ns_length < 3) {
g_strfreev (ns);
continue;
}
if (priv->snap_name == NULL) {
priv->snap_name = found_snap_name;
found_snap_name = NULL;
}
if (g_str_equal (ns[1], priv->snap_name)) {
priv->snap_app = g_strdup (ns[2]);
g_strfreev (ns);
break;
}
g_strfreev (ns);
}
if (priv->snap_name == NULL && found_snap_name != NULL) {
priv->snap_name = found_snap_name;
found_snap_name = NULL;
}
g_strfreev (lines);
g_free (found_snap_name);
}
if (priv->snap_app == NULL) {
priv->snap_app = g_strdup (priv->snap_name);
}
g_debug ("SNAP path: %s", priv->snap_path);
g_debug ("SNAP name: %s", priv->snap_name);
g_debug ("SNAP app: %s", priv->snap_app);
g_free (cgroup_contents);
}
static void static void
notify_notification_init (NotifyNotification *obj) notify_notification_init (NotifyNotification *obj)
{ {
obj->priv = g_new0 (NotifyNotificationPrivate, 1); obj->priv = g_new0 (NotifyNotificationPrivate, 1);
obj->priv->timeout = NOTIFY_EXPIRES_DEFAULT; obj->priv->timeout = NOTIFY_EXPIRES_DEFAULT;
obj->priv->closed_reason = -1; obj->priv->closed_reason = NOTIFY_CLOSED_REASON_UNSET;
obj->priv->hints = g_hash_table_new_full (g_str_hash, obj->priv->hints = g_hash_table_new_full (g_str_hash,
g_str_equal, g_str_equal,
g_free, g_free,
@ -469,8 +371,22 @@ notify_notification_init (NotifyNotification *obj)
g_str_equal, g_str_equal,
g_free, g_free,
(GDestroyNotify) destroy_pair); (GDestroyNotify) destroy_pair);
}
maybe_initialize_snap (obj); static void
notify_notification_dispose (GObject *object)
{
NotifyNotification *obj = NOTIFY_NOTIFICATION (object);
NotifyNotificationPrivate *priv = obj->priv;
if (priv->portal_timeout_id) {
g_source_remove (priv->portal_timeout_id);
priv->portal_timeout_id = 0;
}
g_clear_object (&priv->icon_pixbuf);
G_OBJECT_CLASS (parent_class)->dispose (object);
} }
static void static void
@ -487,7 +403,6 @@ notify_notification_finalize (GObject *object)
g_free (priv->body); g_free (priv->body);
g_free (priv->icon_name); g_free (priv->icon_name);
g_free (priv->activation_token); g_free (priv->activation_token);
g_free (priv->snap_app);
if (priv->actions != NULL) { if (priv->actions != NULL) {
g_slist_foreach (priv->actions, (GFunc) g_free, NULL); g_slist_foreach (priv->actions, (GFunc) g_free, NULL);
@ -510,6 +425,18 @@ notify_notification_finalize (GObject *object)
G_OBJECT_CLASS (parent_class)->finalize (object); G_OBJECT_CLASS (parent_class)->finalize (object);
} }
static gboolean
maybe_warn_portal_unsupported_feature (const char *feature_name)
{
if (!_notify_uses_portal_notifications ()) {
return FALSE;
}
g_message ("%s is not available when using Portal Notifications",
feature_name);
return TRUE;
}
/** /**
* notify_notification_new: * notify_notification_new:
* @summary: The required summary text. * @summary: The required summary text.
@ -596,7 +523,6 @@ static gchar *
try_prepend_snap_desktop (NotifyNotification *notification, try_prepend_snap_desktop (NotifyNotification *notification,
const gchar *desktop) const gchar *desktop)
{ {
NotifyNotificationPrivate *priv = notification->priv;
gchar *ret = NULL; gchar *ret = NULL;
/* /*
@ -604,11 +530,11 @@ try_prepend_snap_desktop (NotifyNotification *notification,
* ${SNAP_NAME}_; snap .desktop files are in the format * ${SNAP_NAME}_; snap .desktop files are in the format
* ${SNAP_NAME}_desktop_file_name * ${SNAP_NAME}_desktop_file_name
*/ */
ret = try_prepend_path (desktop, priv->snap_path); ret = try_prepend_path (desktop, _notify_get_snap_path ());
if (ret == NULL && priv->snap_name != NULL && if (ret == NULL && _notify_get_snap_name () != NULL &&
strchr (desktop, G_DIR_SEPARATOR) == NULL) { strchr (desktop, G_DIR_SEPARATOR) == NULL) {
ret = g_strdup_printf ("%s_%s", priv->snap_name, desktop); ret = g_strdup_printf ("%s_%s", _notify_get_snap_name (), desktop);
} }
return ret; return ret;
@ -619,7 +545,7 @@ try_prepend_snap (NotifyNotification *notification,
const gchar *value) const gchar *value)
{ {
/* hardcoded paths to icons might be relocated under $SNAP */ /* hardcoded paths to icons might be relocated under $SNAP */
return try_prepend_path (value, notification->priv->snap_path); return try_prepend_path (value, _notify_get_snap_path ());
} }
@ -698,6 +624,80 @@ notify_notification_update (NotifyNotification *notification,
return TRUE; return TRUE;
} }
static char *
get_portal_notification_id (NotifyNotification *notification)
{
char *app_id;
char *notification_id;
g_assert (_notify_uses_portal_notifications ());
if (_notify_get_snap_name ()) {
app_id = g_strdup_printf ("snap.%s_%s",
_notify_get_snap_name (),
_notify_get_snap_app ());
} else {
app_id = g_strdup_printf ("flatpak.%s",
_notify_get_flatpak_app ());
}
notification_id = g_strdup_printf ("libnotify-%s-%s-%u",
app_id,
notify_get_app_name (),
notification->priv->id);
g_free (app_id);
return notification_id;
}
static gboolean
activate_action (NotifyNotification *notification,
const gchar *action)
{
CallbackPair *pair;
pair = g_hash_table_lookup (notification->priv->action_map, action);
if (!pair) {
return FALSE;
}
/* Some clients have assumed it is safe to unref the
* Notification at the end of their NotifyActionCallback
* so we add a temporary ref until we're done with it.
*/
g_object_ref (notification);
notification->priv->activating = TRUE;
pair->cb (notification, (char *) action, pair->user_data);
notification->priv->activating = FALSE;
g_free (notification->priv->activation_token);
notification->priv->activation_token = NULL;
g_object_unref (notification);
return TRUE;
}
static gboolean
close_notification (NotifyNotification *notification,
NotifyClosedReason reason)
{
if (notification->priv->closed_reason != NOTIFY_CLOSED_REASON_UNSET ||
reason == NOTIFY_CLOSED_REASON_UNSET) {
return FALSE;
}
g_object_ref (G_OBJECT (notification));
notification->priv->closed_reason = reason;
g_signal_emit (notification, signals[SIGNAL_CLOSED], 0);
notification->priv->id = 0;
g_object_unref (G_OBJECT (notification));
return TRUE;
}
static void static void
proxy_g_signal_cb (GDBusProxy *proxy, proxy_g_signal_cb (GDBusProxy *proxy,
const char *sender_name, const char *sender_name,
@ -705,8 +705,12 @@ proxy_g_signal_cb (GDBusProxy *proxy,
GVariant *parameters, GVariant *parameters,
NotifyNotification *notification) NotifyNotification *notification)
{ {
const char *interface;
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification)); g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
interface = g_dbus_proxy_get_interface_name (proxy);
if (g_strcmp0 (signal_name, "NotificationClosed") == 0 && if (g_strcmp0 (signal_name, "NotificationClosed") == 0 &&
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uu)"))) { g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uu)"))) {
guint32 id, reason; guint32 id, reason;
@ -715,36 +719,21 @@ proxy_g_signal_cb (GDBusProxy *proxy,
if (id != notification->priv->id) if (id != notification->priv->id)
return; return;
g_object_ref (G_OBJECT (notification)); close_notification (notification, reason);
notification->priv->closed_reason = reason;
g_signal_emit (notification, signals[SIGNAL_CLOSED], 0);
notification->priv->id = 0;
g_object_unref (G_OBJECT (notification));
} else if (g_strcmp0 (signal_name, "ActionInvoked") == 0 && } else if (g_strcmp0 (signal_name, "ActionInvoked") == 0 &&
g_str_equal (interface, NOTIFY_DBUS_CORE_INTERFACE) &&
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(us)"))) { g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(us)"))) {
guint32 id; guint32 id;
const char *action; const char *action;
CallbackPair *pair;
g_variant_get (parameters, "(u&s)", &id, &action); g_variant_get (parameters, "(u&s)", &id, &action);
if (id != notification->priv->id) if (id != notification->priv->id)
return; return;
pair = (CallbackPair *) g_hash_table_lookup (notification->priv->action_map, if (!activate_action (notification, action) &&
action); g_ascii_strcasecmp (action, "default")) {
g_warning ("Received unknown action %s", action);
if (pair == NULL) {
if (g_ascii_strcasecmp (action, "default")) {
g_warning ("Received unknown action %s", action);
}
} else {
notification->priv->activating = TRUE;
pair->cb (notification, (char *) action, pair->user_data);
notification->priv->activating = FALSE;
g_free (notification->priv->activation_token);
notification->priv->activation_token = NULL;
} }
} else if (g_strcmp0 (signal_name, "ActivationToken") == 0 && } else if (g_strcmp0 (signal_name, "ActivationToken") == 0 &&
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(us)"))) { g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(us)"))) {
@ -758,9 +747,306 @@ proxy_g_signal_cb (GDBusProxy *proxy,
g_free (notification->priv->activation_token); g_free (notification->priv->activation_token);
notification->priv->activation_token = g_strdup (activation_token); notification->priv->activation_token = g_strdup (activation_token);
} else if (g_str_equal (signal_name, "ActionInvoked") &&
g_str_equal (interface, NOTIFY_PORTAL_DBUS_CORE_INTERFACE) &&
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(ssav)"))) {
char *notification_id;
const char *id;
const char *action;
GVariant *parameter;
g_variant_get (parameters, "(&s&s@av)", &id, &action, &parameter);
g_variant_unref (parameter);
notification_id = get_portal_notification_id (notification);
if (!g_str_equal (notification_id, id)) {
g_free (notification_id);
return;
}
if (!activate_action (notification, action) &&
g_str_equal (action, "default-action") &&
!_notify_get_snap_app ()) {
g_warning ("Received unknown action %s", action);
}
close_notification (notification, NOTIFY_CLOSED_REASON_DISMISSED);
g_free (notification_id);
} else {
g_debug ("Unhandled signal '%s.%s'", interface, signal_name);
} }
} }
static gboolean
remove_portal_notification (GDBusProxy *proxy,
NotifyNotification *notification,
NotifyClosedReason reason,
GError **error)
{
GVariant *ret;
gchar *notification_id;
if (notification->priv->portal_timeout_id) {
g_source_remove (notification->priv->portal_timeout_id);
notification->priv->portal_timeout_id = 0;
}
notification_id = get_portal_notification_id (notification);
ret = g_dbus_proxy_call_sync (proxy,
"RemoveNotification",
g_variant_new ("(s)", notification_id),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
error);
g_free (notification_id);
if (!ret) {
return FALSE;
}
close_notification (notification, reason);
g_variant_unref (ret);
return TRUE;
}
static gboolean
on_portal_timeout (gpointer data)
{
NotifyNotification *notification = data;
GDBusProxy *proxy;
notification->priv->portal_timeout_id = 0;
proxy = _notify_get_proxy (NULL);
if (proxy == NULL) {
return FALSE;
}
remove_portal_notification (proxy, notification,
NOTIFY_CLOSED_REASON_EXPIRED, NULL);
return FALSE;
}
static GIcon *
get_notification_gicon (NotifyNotification *notification,
GError **error)
{
NotifyNotificationPrivate *priv = notification->priv;
GFileInputStream *input;
GFile *file = NULL;
GIcon *gicon = NULL;
if (priv->icon_pixbuf) {
return G_ICON (g_object_ref (priv->icon_pixbuf));
}
if (!priv->icon_name) {
return NULL;
}
if (strstr (priv->icon_name, "://")) {
file = g_file_new_for_uri (priv->icon_name);
} else if (g_file_test (priv->icon_name, G_FILE_TEST_EXISTS)) {
file = g_file_new_for_path (priv->icon_name);
} else {
gicon = g_themed_icon_new (priv->icon_name);
}
if (!file) {
return gicon;
}
input = g_file_read (file, NULL, error);
if (input) {
GByteArray *bytes_array = g_byte_array_new ();
guint8 buf[1024];
while (TRUE) {
gssize read;
read = g_input_stream_read (G_INPUT_STREAM (input),
buf,
G_N_ELEMENTS (buf),
NULL, NULL);
if (read > 0) {
g_byte_array_append (bytes_array, buf, read);
} else {
if (read < 0) {
g_byte_array_unref (bytes_array);
bytes_array = NULL;
}
break;
}
}
if (bytes_array && bytes_array->len) {
GBytes *bytes;
bytes = g_byte_array_free_to_bytes (bytes_array);
bytes_array = NULL;
gicon = g_bytes_icon_new (bytes);
} else if (bytes_array) {
g_byte_array_unref (bytes_array);
}
}
g_clear_object (&input);
g_clear_object (&file);
return gicon;
}
static gboolean
add_portal_notification (GDBusProxy *proxy,
NotifyNotification *notification,
GError **error)
{
GIcon *icon;
GVariant *urgency;
GVariant *ret;
GVariantBuilder builder;
NotifyNotificationPrivate *priv = notification->priv;
GError *local_error = NULL;
static guint32 portal_notification_count = 0;
char *notification_id;
g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&builder, "{sv}", "title",
g_variant_new_string (priv->summary ? priv->summary : ""));
g_variant_builder_add (&builder, "{sv}", "body",
g_variant_new_string (priv->body ? priv->body : ""));
if (g_hash_table_lookup (priv->action_map, "default")) {
g_variant_builder_add (&builder, "{sv}", "default-action",
g_variant_new_string ("default"));
} else if (g_hash_table_lookup (priv->action_map, "DEFAULT")) {
g_variant_builder_add (&builder, "{sv}", "default-action",
g_variant_new_string ("DEFAULT"));
} else if (_notify_get_snap_app ()) {
/* In the snap case we may need to ensure that a default-action
* is set to ensure that we will use the FDO notification daemon
* and won't fallback to GTK one, as app-id won't match.
* See: https://github.com/flatpak/xdg-desktop-portal/issues/769
*/
g_variant_builder_add (&builder, "{sv}", "default-action",
g_variant_new_string ("snap-fake-default-action"));
}
if (priv->has_nondefault_actions) {
GVariantBuilder buttons;
GSList *l;
g_variant_builder_init (&buttons, G_VARIANT_TYPE ("aa{sv}"));
for (l = priv->actions; l && l->next; l = l->next->next) {
GVariantBuilder button;
const char *action;
const char *label;
g_variant_builder_init (&button, G_VARIANT_TYPE_VARDICT);
action = l->data;
label = l->next->data;
g_variant_builder_add (&button, "{sv}", "action",
g_variant_new_string (action));
g_variant_builder_add (&button, "{sv}", "label",
g_variant_new_string (label));
g_variant_builder_add (&buttons, "@a{sv}",
g_variant_builder_end (&button));
}
g_variant_builder_add (&builder, "{sv}", "buttons",
g_variant_builder_end (&buttons));
}
urgency = g_hash_table_lookup (notification->priv->hints, "urgency");
if (urgency) {
switch (g_variant_get_byte (urgency)) {
case NOTIFY_URGENCY_LOW:
g_variant_builder_add (&builder, "{sv}", "priority",
g_variant_new_string ("low"));
break;
case NOTIFY_URGENCY_NORMAL:
g_variant_builder_add (&builder, "{sv}", "priority",
g_variant_new_string ("normal"));
break;
case NOTIFY_URGENCY_CRITICAL:
g_variant_builder_add (&builder, "{sv}", "priority",
g_variant_new_string ("urgent"));
break;
default:
g_warn_if_reached ();
}
}
icon = get_notification_gicon (notification, &local_error);
if (icon) {
GVariant *serialized_icon = g_icon_serialize (icon);
g_variant_builder_add (&builder, "{sv}", "icon",
serialized_icon);
g_variant_unref (serialized_icon);
g_clear_object (&icon);
} else if (local_error) {
g_propagate_error (error, local_error);
return FALSE;
}
if (!priv->id) {
priv->id = ++portal_notification_count;
} else if (priv->closed_reason == NOTIFY_CLOSED_REASON_UNSET) {
remove_portal_notification (proxy, notification,
NOTIFY_CLOSED_REASON_UNSET, NULL);
}
notification_id = get_portal_notification_id (notification);
ret = g_dbus_proxy_call_sync (proxy,
"AddNotification",
g_variant_new ("(s@a{sv})",
notification_id,
g_variant_builder_end (&builder)),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
error);
if (priv->portal_timeout_id) {
g_source_remove (priv->portal_timeout_id);
priv->portal_timeout_id = 0;
}
g_free (notification_id);
if (!ret) {
return FALSE;
}
if (priv->timeout > 0) {
priv->portal_timeout_id = g_timeout_add (priv->timeout,
on_portal_timeout,
notification);
}
g_variant_unref (ret);
return TRUE;
}
/** /**
* notify_notification_show: * notify_notification_show:
* @notification: The notification. * @notification: The notification.
@ -782,9 +1068,7 @@ notify_notification_show (NotifyNotification *notification,
GHashTableIter iter; GHashTableIter iter;
gpointer key, data; gpointer key, data;
GVariant *result; GVariant *result;
#ifdef GLIB_VERSION_2_32
GApplication *application = NULL; GApplication *application = NULL;
#endif
g_return_val_if_fail (notification != NULL, FALSE); g_return_val_if_fail (notification != NULL, FALSE);
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), FALSE); g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), FALSE);
@ -808,6 +1092,10 @@ notify_notification_show (NotifyNotification *notification,
notification); notification);
} }
if (_notify_uses_portal_notifications ()) {
return add_portal_notification (proxy, notification, error);
}
g_variant_builder_init (&actions_builder, G_VARIANT_TYPE ("as")); g_variant_builder_init (&actions_builder, G_VARIANT_TYPE ("as"));
for (l = priv->actions; l != NULL; l = l->next) { for (l = priv->actions; l != NULL; l = l->next) {
g_variant_builder_add (&actions_builder, "s", l->data); g_variant_builder_add (&actions_builder, "s", l->data);
@ -819,13 +1107,18 @@ notify_notification_show (NotifyNotification *notification,
g_variant_builder_add (&hints_builder, "{sv}", key, data); g_variant_builder_add (&hints_builder, "{sv}", key, data);
} }
if (priv->snap_app && if (g_hash_table_lookup (priv->hints, "sender-pid") == NULL) {
g_variant_builder_add (&hints_builder, "{sv}", "sender-pid",
g_variant_new_int64 (getpid ()));
}
if (_notify_get_snap_app () &&
g_hash_table_lookup (priv->hints, "desktop-entry") == NULL) { g_hash_table_lookup (priv->hints, "desktop-entry") == NULL) {
gchar *snap_desktop; gchar *snap_desktop;
snap_desktop = g_strdup_printf ("%s_%s", snap_desktop = g_strdup_printf ("%s_%s",
priv->snap_name, _notify_get_snap_name (),
priv->snap_app); _notify_get_snap_app ());
g_debug ("Using desktop entry: %s", snap_desktop); g_debug ("Using desktop entry: %s", snap_desktop);
g_variant_builder_add (&hints_builder, "{sv}", g_variant_builder_add (&hints_builder, "{sv}",
@ -833,8 +1126,7 @@ notify_notification_show (NotifyNotification *notification,
g_variant_new_take_string (snap_desktop)); g_variant_new_take_string (snap_desktop));
} }
#ifdef GLIB_VERSION_2_32 if (!_notify_get_snap_app ()) {
if (!priv->snap_app) {
application = g_application_get_default (); application = g_application_get_default ();
} }
@ -849,7 +1141,6 @@ notify_notification_show (NotifyNotification *notification,
g_variant_new_string (application_id)); g_variant_new_string (application_id));
} }
} }
#endif
/* TODO: make this nonblocking */ /* TODO: make this nonblocking */
result = g_dbus_proxy_call_sync (proxy, result = g_dbus_proxy_call_sync (proxy,
@ -928,6 +1219,10 @@ notify_notification_set_category (NotifyNotification *notification,
g_return_if_fail (notification != NULL); g_return_if_fail (notification != NULL);
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification)); g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
if (maybe_warn_portal_unsupported_feature ("Category")) {
return;
}
if (category != NULL && category[0] != '\0') { if (category != NULL && category[0] != '\0') {
notify_notification_set_hint_string (notification, notify_notification_set_hint_string (notification,
"category", "category",
@ -1005,11 +1300,18 @@ notify_notification_set_image_from_pixbuf (NotifyNotification *notification,
hint_name = "icon_data"; hint_name = "icon_data";
} }
g_clear_object (&notification->priv->icon_pixbuf);
if (pixbuf == NULL) { if (pixbuf == NULL) {
notify_notification_set_hint (notification, hint_name, NULL); notify_notification_set_hint (notification, hint_name, NULL);
return; return;
} }
if (_notify_uses_portal_notifications ()) {
notification->priv->icon_pixbuf = g_object_ref (pixbuf);
return;
}
g_object_get (pixbuf, g_object_get (pixbuf,
"width", &width, "width", &width,
"height", &height, "height", &height,
@ -1066,7 +1368,7 @@ maybe_parse_snap_hint_value (NotifyNotification *notification,
{ {
StringParserFunc parse_func = NULL; StringParserFunc parse_func = NULL;
if (!notification->priv->snap_path) if (!_notify_get_snap_path ())
return value; return value;
if (g_strcmp0 (key, "desktop-entry") == 0) { if (g_strcmp0 (key, "desktop-entry") == 0) {
@ -1133,6 +1435,10 @@ notify_notification_set_app_name (NotifyNotification *notification,
{ {
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification)); g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
if (maybe_warn_portal_unsupported_feature ("App Name")) {
return;
}
g_free (notification->priv->app_name); g_free (notification->priv->app_name);
notification->priv->app_name = g_strdup (app_name); notification->priv->app_name = g_strdup (app_name);
@ -1373,6 +1679,7 @@ notify_notification_add_action (NotifyNotification *notification,
/** /**
* notify_notification_get_activation_token: * notify_notification_get_activation_token:
* @notification: The notification.
* *
* If an an action is currently being activated, return the activation token. * If an an action is currently being activated, return the activation token.
* This function is intended to be used in a #NotifyActionCallback to get * This function is intended to be used in a #NotifyActionCallback to get
@ -1387,11 +1694,9 @@ const char *
notify_notification_get_activation_token (NotifyNotification *notification) notify_notification_get_activation_token (NotifyNotification *notification)
{ {
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), NULL); g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), NULL);
g_return_val_if_fail (notification->priv->activating, NULL);
if (notification->priv->activating) return notification->priv->activation_token;
return notification->priv->activation_token;
return NULL;
} }
gboolean gboolean
@ -1430,6 +1735,12 @@ notify_notification_close (NotifyNotification *notification,
return FALSE; return FALSE;
} }
if (_notify_uses_portal_notifications ()) {
return remove_portal_notification (proxy, notification,
NOTIFY_CLOSED_REASON_API_REQUEST,
error);
}
/* FIXME: make this nonblocking! */ /* FIXME: make this nonblocking! */
result = g_dbus_proxy_call_sync (proxy, result = g_dbus_proxy_call_sync (proxy,
"CloseNotification", "CloseNotification",
@ -1454,13 +1765,17 @@ notify_notification_close (NotifyNotification *notification,
* Returns the closed reason code for the notification. This is valid only * Returns the closed reason code for the notification. This is valid only
* after the "closed" signal is emitted. * after the "closed" signal is emitted.
* *
* Returns: The closed reason code. * Since version 0.8.0 the returned value is of type #NotifyClosedReason.
*
* Returns: An integer representing the closed reason code
* (Since 0.8.0 it's also a #NotifyClosedReason).
*/ */
gint gint
notify_notification_get_closed_reason (const NotifyNotification *notification) notify_notification_get_closed_reason (const NotifyNotification *notification)
{ {
g_return_val_if_fail (notification != NULL, -1); g_return_val_if_fail (notification != NULL, NOTIFY_CLOSED_REASON_UNSET);
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), -1); g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification),
NOTIFY_CLOSED_REASON_UNSET);
return notification->priv->closed_reason; return notification->priv->closed_reason;
} }

View File

@ -55,6 +55,11 @@ typedef struct _NotifyNotification NotifyNotification;
typedef struct _NotifyNotificationClass NotifyNotificationClass; typedef struct _NotifyNotificationClass NotifyNotificationClass;
typedef struct _NotifyNotificationPrivate NotifyNotificationPrivate; typedef struct _NotifyNotificationPrivate NotifyNotificationPrivate;
/**
* NotifyNotification:
*
* A passive pop-up notification.
*/
struct _NotifyNotification struct _NotifyNotification
{ {
/*< private >*/ /*< private >*/
@ -88,11 +93,34 @@ typedef enum
} NotifyUrgency; } NotifyUrgency;
/**
* NotifyClosedReason:
* @NOTIFY_CLOSED_REASON_UNSET: Notification not closed.
* @NOTIFY_CLOSED_REASON_EXPIRED: Timeout has expired.
* @NOTIFY_CLOSED_REASON_DISMISSED: It has been dismissed by the user.
* @NOTIFY_CLOSED_REASON_API_REQUEST: It has been closed by a call to
* notify_notification_close().
* @NOTIFY_CLOSED_REASON_UNDEFIEND: Closed by undefined/reserved reasons.
*
* The reason for which the notification has been closed.
*
* Since: 0.8.0
*/
typedef enum
{
NOTIFY_CLOSED_REASON_UNSET = -1,
NOTIFY_CLOSED_REASON_EXPIRED = 1,
NOTIFY_CLOSED_REASON_DISMISSED = 2,
NOTIFY_CLOSED_REASON_API_REQUEST = 3,
NOTIFY_CLOSED_REASON_UNDEFIEND = 4,
} NotifyClosedReason;
/** /**
* NotifyActionCallback: * NotifyActionCallback:
* @notification: * @notification: a #NotifyActionCallback notification
* @action: * @action: (transfer none): The activated action name
* @user_data: * @user_data: (nullable) (transfer none): User provided data
* *
* An action callback function. * An action callback function.
*/ */

View File

@ -43,10 +43,14 @@
static gboolean _initted = FALSE; static gboolean _initted = FALSE;
static char *_app_name = NULL; static char *_app_name = NULL;
static char *_snap_name = NULL;
static char *_snap_app = NULL;
static char *_flatpak_app = NULL;
static GDBusProxy *_proxy = NULL; static GDBusProxy *_proxy = NULL;
static GList *_active_notifications = NULL; static GList *_active_notifications = NULL;
static int _spec_version_major = 0; static int _spec_version_major = 0;
static int _spec_version_minor = 0; static int _spec_version_minor = 0;
static int _portal_version = 0;
gboolean gboolean
_notify_check_spec_version (int major, _notify_check_spec_version (int major,
@ -74,6 +78,26 @@ _notify_get_server_info (char **ret_name,
return FALSE; return FALSE;
} }
if (_notify_uses_portal_notifications ()) {
if (ret_name) {
*ret_name = g_strdup ("Portal Notification");
}
if (ret_vendor) {
*ret_vendor = g_strdup ("Freedesktop");
}
if (ret_version) {
*ret_version = g_strdup_printf ("%u", _portal_version);
}
if (ret_spec_version) {
*ret_spec_version = g_strdup ("1.2");
}
return TRUE;
}
result = g_dbus_proxy_call_sync (proxy, result = g_dbus_proxy_call_sync (proxy,
"GetServerInformation", "GetServerInformation",
g_variant_new ("()"), g_variant_new ("()"),
@ -119,6 +143,18 @@ _notify_update_spec_version (GError **error)
return TRUE; return TRUE;
} }
static gboolean
set_app_name (const char *app_name)
{
g_return_val_if_fail (app_name != NULL, FALSE);
g_return_val_if_fail (*app_name != '\0', FALSE);
g_free (_app_name);
_app_name = g_strdup (app_name);
return TRUE;
}
/** /**
* notify_set_app_name: * notify_set_app_name:
@ -130,46 +166,267 @@ _notify_update_spec_version (GError **error)
void void
notify_set_app_name (const char *app_name) notify_set_app_name (const char *app_name)
{ {
g_free (_app_name); set_app_name (app_name);
_app_name = g_strdup (app_name);
} }
/** /**
* notify_init: * notify_init:
* @app_name: The name of the application initializing libnotify. * @app_name: (nullable): The name of the application initializing libnotify.
* *
* Initialized libnotify. This must be called before any other functions. * Initialized libnotify. This must be called before any other functions.
* *
* Starting from 0.8, if the provided @app_name is %NULL, libnotify will
* try to figure it out from the running application.
* Before it was not allowed, and was causing libnotify not to be initialized.
*
* Returns: %TRUE if successful, or %FALSE on error. * Returns: %TRUE if successful, or %FALSE on error.
*/ */
gboolean gboolean
notify_init (const char *app_name) notify_init (const char *app_name)
{ {
g_return_val_if_fail (app_name != NULL, FALSE);
g_return_val_if_fail (*app_name != '\0', FALSE);
if (_initted) if (_initted)
return TRUE; return TRUE;
#ifdef GLIB_VERSION_2_32 if (app_name == NULL) {
if (app_name == NULL && g_application_get_default ()) { GApplication *application;
GApplication *application = g_application_get_default ();
app_name = g_application_get_application_id (application); app_name = _notify_get_snap_app ();
if (app_name == NULL) {
app_name = _notify_get_flatpak_app ();
}
if (app_name == NULL &&
(application = g_application_get_default ())) {
app_name = g_application_get_application_id (application);
}
} }
#endif
notify_set_app_name (app_name); if (!set_app_name (app_name)) {
return FALSE;
#ifndef GLIB_VERSION_2_36 }
g_type_init ();
#endif
_initted = TRUE; _initted = TRUE;
return TRUE; return TRUE;
} }
static void
_initialize_snap_names (void)
{
gchar *cgroup_contents = NULL;
gchar *found_snap_name = NULL;
gchar **lines;
gint i;
if (!g_file_get_contents ("/proc/self/cgroup", &cgroup_contents,
NULL, NULL)) {
g_free (cgroup_contents);
return;
}
lines = g_strsplit (cgroup_contents, "\n", -1);
g_free (cgroup_contents);
for (i = 0; lines[i]; ++i) {
gchar **parts = g_strsplit (lines[i], ":", 3);
gchar *basename;
gchar **ns;
guint ns_length;
if (g_strv_length (parts) != 3) {
g_strfreev (parts);
continue;
}
basename = g_path_get_basename (parts[2]);
g_strfreev (parts);
if (!basename) {
continue;
}
ns = g_strsplit (basename, ".", -1);
ns_length = g_strv_length (ns);
g_free (basename);
if (ns_length < 2 || !g_str_equal (ns[0], "snap")) {
g_strfreev (ns);
continue;
}
if (_snap_name == NULL) {
g_free (found_snap_name);
found_snap_name = g_strdup (ns[1]);
}
if (ns_length < 3) {
g_strfreev (ns);
continue;
}
if (_snap_name == NULL) {
_snap_name = found_snap_name;
found_snap_name = NULL;
g_debug ("SNAP name: %s", _snap_name);
}
if (g_str_equal (ns[1], _snap_name)) {
_snap_app = g_strdup (ns[2]);
g_strfreev (ns);
break;
}
g_strfreev (ns);
}
if (_snap_name == NULL && found_snap_name != NULL) {
_snap_name = found_snap_name;
found_snap_name = NULL;
g_debug ("SNAP name: %s", _snap_name);
}
if (_snap_app == NULL) {
_snap_app = g_strdup (_snap_name);
}
g_debug ("SNAP app: %s", _snap_app);
g_strfreev (lines);
g_free (found_snap_name);
}
const char *
_notify_get_snap_path (void)
{
static const char *snap_path = NULL;
static gsize snap_path_set = FALSE;
if (g_once_init_enter (&snap_path_set)) {
snap_path = g_getenv ("SNAP");
if (!snap_path || *snap_path == '\0' ||
!strchr (snap_path, G_DIR_SEPARATOR)) {
snap_path = NULL;
} else {
g_debug ("SNAP path: %s", snap_path);
}
g_once_init_leave (&snap_path_set, TRUE);
}
return snap_path;
}
const char *
_notify_get_snap_name (void)
{
static gsize snap_name_set = FALSE;
if (g_once_init_enter (&snap_name_set)) {
if (!_snap_name) {
const char *snap_name_env = g_getenv ("SNAP_NAME");
if (!snap_name_env || *snap_name_env == '\0')
snap_name_env = NULL;
_snap_name = g_strdup (snap_name_env);
g_debug ("SNAP name: %s", _snap_name);
}
g_once_init_leave (&snap_name_set, TRUE);
}
return _snap_name;
}
const char *
_notify_get_snap_app (void)
{
static gsize snap_app_set = FALSE;
if (g_once_init_enter (&snap_app_set)) {
_initialize_snap_names ();
g_once_init_leave (&snap_app_set, TRUE);
}
return _snap_app;
}
const char *
_notify_get_flatpak_app (void)
{
static gsize flatpak_app_set = FALSE;
if (g_once_init_enter (&flatpak_app_set)) {
GKeyFile *info = g_key_file_new ();
if (g_key_file_load_from_file (info, "/.flatpak-info",
G_KEY_FILE_NONE, NULL)) {
const char *group = "Application";
if (g_key_file_has_group (info, "Runtime")) {
group = "Runtime";
}
_flatpak_app = g_key_file_get_string (info, group,
"name", NULL);
}
g_key_file_free (info);
g_once_init_leave (&flatpak_app_set, TRUE);
}
return _flatpak_app;
}
static gboolean
_notify_is_running_under_flatpak (void)
{
return !!_notify_get_flatpak_app ();
}
static gboolean
_notify_is_running_under_snap (void)
{
return !!_notify_get_snap_app ();
}
static gboolean
_notify_is_running_in_sandbox (void)
{
static gsize use_portal = 0;
enum {
IGNORE_PORTAL = 1,
TRY_USE_PORTAL = 2,
FORCE_PORTAL = 3
};
if (g_once_init_enter (&use_portal)) {
if (G_UNLIKELY (g_getenv ("NOTIFY_IGNORE_PORTAL"))) {
g_once_init_leave (&use_portal, IGNORE_PORTAL);
} else if (G_UNLIKELY (g_getenv ("NOTIFY_FORCE_PORTAL"))) {
g_once_init_leave (&use_portal, FORCE_PORTAL);
} else {
g_once_init_leave (&use_portal, TRY_USE_PORTAL);
}
}
if (use_portal == IGNORE_PORTAL) {
return FALSE;
}
return use_portal == FORCE_PORTAL ||
_notify_is_running_under_flatpak () ||
_notify_is_running_under_snap ();
}
gboolean
_notify_uses_portal_notifications (void)
{
return _portal_version != 0;
}
/** /**
* notify_get_app_name: * notify_get_app_name:
* *
@ -219,6 +476,15 @@ notify_uninit (void)
_proxy = NULL; _proxy = NULL;
} }
g_free (_snap_name);
_snap_name = NULL;
g_free (_snap_app);
_snap_app = NULL;
g_free (_flatpak_app);
_flatpak_app = NULL;
_initted = FALSE; _initted = FALSE;
} }
@ -235,6 +501,46 @@ notify_is_initted (void)
return _initted; return _initted;
} }
GDBusProxy *
_get_portal_proxy (GError **error)
{
GError *local_error = NULL;
GDBusProxy *proxy;
GVariant *res;
proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
NOTIFY_PORTAL_DBUS_NAME,
NOTIFY_PORTAL_DBUS_CORE_OBJECT,
NOTIFY_PORTAL_DBUS_CORE_INTERFACE,
NULL,
&local_error);
if (proxy == NULL) {
g_debug ("Failed to get portal proxy: %s", local_error->message);
g_clear_error (&local_error);
return NULL;
}
res = g_dbus_proxy_get_cached_property (proxy, "version");
if (!res) {
g_object_unref (proxy);
return NULL;
}
_portal_version = g_variant_get_uint32 (res);
g_assert (_portal_version > 0);
g_warning ("Running in confined mode, using Portal notifications. "
"Some features and hints won't be supported");
g_variant_unref (res);
return proxy;
}
/* /*
* _notify_get_proxy: * _notify_get_proxy:
* @error: (allow-none): a location to store a #GError, or %NULL * @error: (allow-none): a location to store a #GError, or %NULL
@ -250,6 +556,14 @@ _notify_get_proxy (GError **error)
if (_proxy != NULL) if (_proxy != NULL)
return _proxy; return _proxy;
if (_notify_is_running_in_sandbox ()) {
_proxy = _get_portal_proxy (error);
if (_proxy != NULL) {
goto out;
}
}
_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, _proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
NULL, NULL,
@ -258,6 +572,8 @@ _notify_get_proxy (GError **error)
NOTIFY_DBUS_CORE_INTERFACE, NOTIFY_DBUS_CORE_INTERFACE,
NULL, NULL,
error); error);
out:
if (_proxy == NULL) { if (_proxy == NULL) {
return NULL; return NULL;
} }
@ -295,6 +611,15 @@ notify_get_server_caps (void)
return NULL; return NULL;
} }
if (_notify_uses_portal_notifications ()) {
list = g_list_prepend (list, g_strdup ("actions"));
list = g_list_prepend (list, g_strdup ("body"));
list = g_list_prepend (list, g_strdup ("body-images"));
list = g_list_prepend (list, g_strdup ("icon-static"));
return list;
}
result = g_dbus_proxy_call_sync (proxy, result = g_dbus_proxy_call_sync (proxy,
"GetCapabilities", "GetCapabilities",
g_variant_new ("()"), g_variant_new ("()"),

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<node name="/org/freedesktop/Notification">
<interface name="org.freedesktop.NotificationListener">
</interface>
</node>

View File

@ -1,6 +1,6 @@
project('libnotify', project('libnotify',
'c', 'c',
version: '0.7.11', version: '0.8.1',
meson_version: '>= 0.47.0') meson_version: '>= 0.47.0')
gnome = import('gnome') gnome = import('gnome')
@ -22,8 +22,11 @@ LT_CURRENT=4
LT_REVISION=0 LT_REVISION=0
LT_AGE=0 LT_AGE=0
API_VERSION = 7
VERSION_ARRAY = meson.project_version().split('.') VERSION_ARRAY = meson.project_version().split('.')
MODULE_VERSION = '@0@.@1@'.format(VERSION_ARRAY[0], VERSION_ARRAY[1]) # Minor version is hardcoded until we have a real API break
MODULE_VERSION = '@0@.@1@'.format(VERSION_ARRAY[0], API_VERSION)
LIBNAME = meson.project_name().split('lib')[1] LIBNAME = meson.project_name().split('lib')[1]
default_includes = include_directories('.') default_includes = include_directories('.')
@ -36,7 +39,7 @@ man1dir = join_paths(prefix, get_option('mandir'), 'man1')
libnotify_deps = [] libnotify_deps = []
extra_deps = [] extra_deps = []
glib_req_version = '>= 2.26.0' glib_req_version = '>= 2.38.0'
gdk_pixbuf_dep = dependency('gdk-pixbuf-2.0') gdk_pixbuf_dep = dependency('gdk-pixbuf-2.0')
glib_dep = dependency('glib-2.0', version: glib_req_version) glib_dep = dependency('glib-2.0', version: glib_req_version)

View File

@ -29,10 +29,6 @@ main ()
{ {
NotifyNotification *n; NotifyNotification *n;
#ifndef GLIB_VERSION_2_36
g_type_init ();
#endif
notify_init ("Error Handling"); notify_init ("Error Handling");
n = notify_notification_new ("Summary", "Content", NULL); n = notify_notification_new ("Summary", "Content", NULL);

View File

@ -28,10 +28,6 @@ main ()
GError *error; GError *error;
error = NULL; error = NULL;
#ifndef GLIB_VERSION_2_36
g_type_init ();
#endif
notify_init ("Replace Test"); notify_init ("Replace Test");
n = notify_notification_new ("Summary", n = notify_notification_new ("Summary",

View File

@ -26,6 +26,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <glib.h> #include <glib.h>
#include <glib-unix.h>
#include <glib/gprintf.h> #include <glib/gprintf.h>
#define N_(x) (x) #define N_(x) (x)
@ -148,6 +149,19 @@ handle_closed (NotifyNotification *notify,
g_main_loop_quit (loop); g_main_loop_quit (loop);
} }
static gboolean
on_sigint (gpointer data)
{
NotifyNotification *notification = data;
g_printerr ("Wait cancelled, closing notification\n");
notify_notification_close (notification, NULL);
g_main_loop_quit (loop);
return FALSE;
}
static void static void
handle_action (NotifyNotification *notify, handle_action (NotifyNotification *notify,
char *action, char *action,
@ -176,6 +190,22 @@ on_wait_timeout (gpointer data)
return FALSE; return FALSE;
} }
static gboolean
server_has_capability (const gchar *capability)
{
GList *server_caps = notify_get_server_caps ();
gboolean supported;
supported = !!g_list_find_custom (server_caps,
capability,
(GCompareFunc) g_ascii_strcasecmp);
g_list_foreach (server_caps, (GFunc) g_free, NULL);
g_list_free (server_caps);
return supported;
}
int int
main (int argc, char *argv[]) main (int argc, char *argv[])
{ {
@ -187,6 +217,10 @@ main (int argc, char *argv[])
static char **n_text = NULL; static char **n_text = NULL;
static char **hints = NULL; static char **hints = NULL;
static char **actions = NULL; static char **actions = NULL;
static char *server_name = NULL;
static char *server_vendor = NULL;
static char *server_version = NULL;
static char *server_spec_version = NULL;
static gboolean print_id = FALSE; static gboolean print_id = FALSE;
static gint notification_id = 0; static gint notification_id = 0;
static gboolean do_version = FALSE; static gboolean do_version = FALSE;
@ -249,10 +283,6 @@ main (int argc, char *argv[])
setlocale (LC_ALL, ""); setlocale (LC_ALL, "");
#ifndef GLIB_VERSION_2_36
g_type_init ();
#endif
g_set_prgname (argv[0]); g_set_prgname (argv[0]);
g_log_set_always_fatal (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); g_log_set_always_fatal (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
@ -294,6 +324,18 @@ main (int argc, char *argv[])
if (!notify_init ("notify-send")) if (!notify_init ("notify-send"))
exit (1); exit (1);
notify_get_server_info (&server_name,
&server_vendor,
&server_version,
&server_spec_version);
g_debug ("Using sever %s %s, v%s - Supporting Notification Spec %s",
server_name, server_vendor, server_version, server_spec_version);
g_free (server_name);
g_free (server_vendor);
g_free (server_version);
g_free (server_spec_version);
notify = g_object_new (NOTIFY_TYPE_NOTIFICATION, notify = g_object_new (NOTIFY_TYPE_NOTIFICATION,
"summary", summary, "summary", summary,
"body", body, "body", body,
@ -309,6 +351,12 @@ main (int argc, char *argv[])
if (transient) { if (transient) {
notify_notification_set_hint (notify, "transient", notify_notification_set_hint (notify, "transient",
g_variant_new_boolean (TRUE)); g_variant_new_boolean (TRUE));
if (!server_has_capability ("persistence")) {
g_debug ("Persistence is not supported by the "
"notifications server. "
"All notifications are transient.");
}
} }
g_free (body); g_free (body);
@ -350,19 +398,12 @@ main (int argc, char *argv[])
} }
if (actions != NULL) { if (actions != NULL) {
GList *server_caps = notify_get_server_caps ();
gint i = 0; gint i = 0;
char *action = NULL; char *action = NULL;
gchar **spl = NULL; gchar **spl = NULL;
gboolean have_actions; gboolean have_actions;
have_actions = have_actions = server_has_capability ("actions");
!!g_list_find_custom (server_caps,
"actions",
(GCompareFunc) g_ascii_strcasecmp);
g_list_foreach (server_caps, (GFunc) g_free, NULL);
g_list_free (server_caps);
if (!have_actions) { if (!have_actions) {
g_printerr (N_("Actions are not supported by this " g_printerr (N_("Actions are not supported by this "
"notifications server. " "notifications server. "
@ -427,6 +468,7 @@ main (int argc, char *argv[])
} }
if (wait) { if (wait) {
g_unix_signal_add (SIGINT, on_sigint, notify);
loop = g_main_loop_new (NULL, FALSE); loop = g_main_loop_new (NULL, FALSE);
g_main_loop_run (loop); g_main_loop_run (loop);
g_main_loop_unref (loop); g_main_loop_unref (loop);