Compare commits
64 Commits
revert-e35
...
master
Author | SHA1 | Date |
---|---|---|
Andre Klapper | 80121a3b8c | |
Marco Trevisan (Treviño) | 650f2f123e | |
Marco Trevisan (Treviño) | a8374698c4 | |
Marco Trevisan (Treviño) | 359443f70f | |
Marco Trevisan (Treviño) | 3d81394cd2 | |
Marco Trevisan (Treviño) | 5e6a52fa4f | |
Marco Trevisan (Treviño) | 532608248e | |
Marco Trevisan (Treviño) | adb3e0c7bf | |
Marco Trevisan (Treviño) | 13718b6ea0 | |
Marco Trevisan (Treviño) | f3eb807464 | |
Marco Trevisan (Treviño) | c6534a144c | |
Marco Trevisan (Treviño) | 543d636153 | |
Marco Trevisan (Treviño) | 9e9cb423b9 | |
Marco Trevisan (Treviño) | cc30d8b8dd | |
Marco Trevisan (Treviño) | e8ea0c39d0 | |
Marco Trevisan (Treviño) | 74f87250a3 | |
Marco Trevisan (Treviño) | 07a2b2e32c | |
Marco Trevisan (Treviño) | 8ce5daac4f | |
Marco Trevisan (Treviño) | 9d28fa3600 | |
Marco Trevisan (Treviño) | 4e40a460ce | |
Logan Rathbone | 8162b1d397 | |
Logan Rathbone | 4d68baf5ef | |
Logan Rathbone | 596d09a78f | |
Logan Rathbone | f5b9619b2f | |
Marco Trevisan (Treviño) | afef1e98b3 | |
Marco Trevisan (Treviño) | ecb1d4ec08 | |
Patrick Griffis | 6d8e38bc69 | |
Patrick Griffis | 6e6d52340b | |
Marco Trevisan (Treviño) | 1fba04bc03 | |
Marco Trevisan (Treviño) | a674e610ee | |
Marco Trevisan (Treviño) | c99aacdb91 | |
Marco Trevisan (Treviño) | 997115b459 | |
Marco Trevisan (Treviño) | e8a57f7245 | |
Marco Trevisan (Treviño) | ed43da3479 | |
Marco Trevisan (Treviño) | 02f2f91c3d | |
Marco Trevisan (Treviño) | d6f4734adf | |
Marco Trevisan (Treviño) | 6e871a4047 | |
Marco Trevisan (Treviño) | 59553a5af2 | |
Marco Trevisan (Treviño) | 2674c76f53 | |
Marco Trevisan (Treviño) | d138596c78 | |
Marco Trevisan (Treviño) | d07785959c | |
Marco Trevisan (Treviño) | 47400a2ff7 | |
Marco Trevisan (Treviño) | 1da6996491 | |
Ben Blain | e05030875e | |
Ben Blain | 12d4b90623 | |
Ben Blain | 6c707d2dfd | |
Ben Blain | a396dd9af9 | |
Matthias Sweertvaegher | 00a7e74774 | |
Paul Collins | 652b4d6911 | |
Marco Trevisan (Treviño) | f9c1cf1b71 | |
Marco Trevisan (Treviño) | 997e2e374a | |
Marco Trevisan (Treviño) | 0d86d8c8d5 | |
Marco Trevisan (Treviño) | b2d6712b15 | |
Marco Trevisan (Treviño) | 961f45f44c | |
Marco Trevisan (Treviño) | e72396e8ca | |
Marco Trevisan (Treviño) | d21b8140a8 | |
Marco Trevisan (Treviño) | c4d5ea8baf | |
Marco Trevisan (Treviño) | d348a36a05 | |
Marco Trevisan (Treviño) | d5a888fa31 | |
Marco Trevisan (Treviño) | 736fcea1d0 | |
Marco Trevisan (Treviño) | 2accd4ceab | |
Corentin Noël | 333f7c5d32 | |
David King | 7f31e29e62 | |
Logan Rathbone | 1ad4630107 |
|
@ -26,4 +26,12 @@ build:ubuntu:
|
|||
- pip3 install meson
|
||||
script:
|
||||
- meson _build -Ddocbook_docs=enabled
|
||||
- ninja -C _build
|
||||
- ninja -C _build install
|
||||
artifacts:
|
||||
expose_as: "Build artifacts"
|
||||
paths:
|
||||
- _build/docs/notification-spec.html
|
||||
- _build/docs/reference/html
|
||||
- _build/docs/reference/html/index.html
|
||||
- _build/meson-logs
|
||||
|
||||
|
|
68
NEWS
68
NEWS
|
@ -1,3 +1,71 @@
|
|||
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
|
||||
=============
|
||||
* Fix potential build errors with old glib version we require
|
||||
* notify-send: Add support for boolean hints
|
||||
* notify-send: Support passing any hint value, by parsing variant strings
|
||||
* notify-send: Add explicit option to create transient notifications
|
||||
|
||||
Contributors:
|
||||
Marco Trevisan
|
||||
|
||||
New in 0.7.10
|
||||
=============
|
||||
* notify-send: Support commas in icon filenames [Thorsten; !15]
|
||||
* notify-send: Give failing exit code if showing notification fails [Ray, !13]
|
||||
* notify-send: Support for replacing an existing notification [Paul; !17]
|
||||
* notify-send: Add option to wait until notification has been closed [Ben; !18]
|
||||
* notify-send: Add support for notification actions and responses [Ben; !18]
|
||||
* notification: Send the application ID when possible [Corentin; !1]
|
||||
* notification: Use g_memdup2 when available [Marco; !22]
|
||||
* notification: Improve SNAP detection and confined desktop ID [Marco; !23]
|
||||
* notification: Add support for getting actions activation token [Marco; !24]
|
||||
* notify: Use application ID if any to set the fallback app name [Marco; !18]
|
||||
* Build fixes and improvements [Marco; !22]
|
||||
* Docs updates [Boris, David; !14, !20]
|
||||
|
||||
Contributors:
|
||||
Marco Trevisan, Boris Shtrasman, Matthias Sweertvaegher, Thorsten Wißmann,
|
||||
Ray Strode, Maximiliano Sandoval R, David King, Corentin Noël, Paul Collins,
|
||||
Matthias Sweertvaegher, Ben Blain
|
||||
|
||||
New in 0.7.9
|
||||
============
|
||||
* Fixed linking in darwin [Iain, Marco; !5]
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# libnotify
|
||||
|
||||
## Description
|
||||
|
||||
libnotify is a library for sending desktop notifications to a notification
|
||||
daemon, as defined in the [org.freedesktop.Notifications][fdo-spec] Desktop
|
||||
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.
|
||||
|
||||
It is also a simple wrapper to send cross-desktop Notifications for sandboxed
|
||||
applications using the [XDG Portal Notification API][portal].
|
||||
|
||||
## Notice
|
||||
|
||||
For GLib based applications the [GNotification][gnotif] API should be used
|
||||
instead.
|
||||
|
||||
[fdo-spec]: https://specifications.freedesktop.org/notification-spec/notification-spec-latest.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
|
|
@ -3,4 +3,5 @@
|
|||
xmlns:fo="http://www.w3.org/1999/XSL/Format"
|
||||
version="1.0">
|
||||
<xsl:param name="html.stylesheet" select="'docbook.css'"/>
|
||||
<xsl:param name="generate.consistent.ids" select="1"/>
|
||||
</xsl:stylesheet>
|
||||
|
|
|
@ -1272,6 +1272,65 @@
|
|||
</para>
|
||||
</note>
|
||||
</sect3>
|
||||
|
||||
<sect3 id="signal-activation-token">
|
||||
<title><literal>org.freedesktop.Notifications.ActivationToken</literal></title>
|
||||
<funcsynopsis>
|
||||
<funcprototype>
|
||||
<funcdef>
|
||||
<function>org.freedesktop.Notifications.ActivationToken</function>
|
||||
</funcdef>
|
||||
<paramdef>UINT32 <parameter>id</parameter></paramdef>
|
||||
<paramdef>STRING <parameter>activation_token</parameter></paramdef>
|
||||
</funcprototype>
|
||||
</funcsynopsis>
|
||||
<para>
|
||||
This signal can be emitted before a <literal>ActionInvoked</literal>
|
||||
signal. It carries an activation token that can be used to activate a
|
||||
toplevel.
|
||||
</para>
|
||||
<table>
|
||||
<title>ActivationToken Parameters</title>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Name</entry>
|
||||
<entry>Type</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody valign="top">
|
||||
<row>
|
||||
<entry><parameter>id</parameter></entry>
|
||||
<entry>UINT32</entry>
|
||||
<entry>
|
||||
The ID of the notification emitting the <literal>ActionInvoked</literal>
|
||||
signal.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><parameter>activation_token</parameter></entry>
|
||||
<entry>STRING</entry>
|
||||
<entry>
|
||||
An activation token. This can be either an X11-style startup ID (see
|
||||
<ulink url="https://specifications.freedesktop.org/startup-notification-spec/startup-notification-latest.txt">Startup notification protocol</ulink>)
|
||||
or a
|
||||
<ulink url="https://gitlab.freedesktop.org/wayland/wayland-protocols/-/tree/main/staging/xdg-activation">Wayland xdg-activation</ulink>
|
||||
token.
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
<note>
|
||||
<para>
|
||||
Clients should not assume the server will generate this signal. Some
|
||||
servers may not support user interaction at all, or may not support
|
||||
the concept of being able to generate an activation token for a
|
||||
notification.
|
||||
</para>
|
||||
</note>
|
||||
</sect3>
|
||||
</sect2>
|
||||
</sect1>
|
||||
</article>
|
||||
|
|
|
@ -58,6 +58,18 @@
|
|||
<para>Show help and exit.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>-a</option>, <option>--app-name</option>=<replaceable>APP_NAME</replaceable></term>
|
||||
<listitem>
|
||||
<para>Specifies the app name for the notification.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>-A</option>, <option>--action</option>=[<replaceable>NAME</replaceable>=]<replaceable>Text...</replaceable></term>
|
||||
<listitem>
|
||||
<para>Specifies the actions to display to the user. Implies <option>--wait</option> to wait for user input. May be set multiple times. The <replaceable>NAME</replaceable> of the action is output to <literal>stdout</literal>. If <replaceable>NAME</replaceable> is not specified, the numerical index of the option is used (starting with <literal>1</literal>).</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>-u</option>, <option>--urgency</option>=<replaceable>LEVEL</replaceable></term>
|
||||
<listitem>
|
||||
|
@ -89,7 +101,31 @@
|
|||
<varlistentry>
|
||||
<term><option>-h</option>, <option>--hint</option>=<replaceable>TYPE</replaceable>:<replaceable>NAME</replaceable>:<replaceable>VALUE</replaceable> </term>
|
||||
<listitem>
|
||||
<para>Specifies basic extra data to pass. Valid types are <literal>INT</literal>, <literal>DOUBLE</literal>, <literal>STRING</literal> and <literal>BYTE</literal>.</para>
|
||||
<para>Specifies basic extra data to pass. Valid types are <literal>BOOLEAN</literal>, <literal>INT</literal>, <literal>DOUBLE</literal>, <literal>STRING</literal>, <literal>BYTE</literal> and <literal>VARIANT</literal>.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>-p</option>, <option>--print-id</option></term>
|
||||
<listitem>
|
||||
<para>Print the notification ID.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>-r</option>, <option>--replace-id</option>=<replaceable>REPLACE_ID</replaceable></term>
|
||||
<listitem>
|
||||
<para>The ID of the notification to replace.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>-w</option>, <option>--wait</option></term>
|
||||
<listitem>
|
||||
<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>
|
||||
</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>
|
||||
|
@ -97,6 +133,6 @@
|
|||
<refsection>
|
||||
<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>
|
||||
</refentry>
|
||||
|
|
|
@ -4,6 +4,7 @@ NOTIFY_EXPIRES_DEFAULT
|
|||
NOTIFY_EXPIRES_NEVER
|
||||
<TITLE>NotifyNotification</TITLE>
|
||||
NotifyNotification
|
||||
NotifyClosedReason
|
||||
NotifyUrgency
|
||||
NotifyActionCallback
|
||||
NOTIFY_ACTION_CALLBACK
|
||||
|
@ -27,6 +28,7 @@ notify_notification_clear_hints
|
|||
notify_notification_add_action
|
||||
notify_notification_clear_actions
|
||||
notify_notification_close
|
||||
notify_notification_get_activation_token
|
||||
notify_notification_get_closed_reason
|
||||
<SUBSECTION Standard>
|
||||
NotifyNotificationPrivate
|
||||
|
|
|
@ -8,18 +8,21 @@
|
|||
<category rdf:resource="http://api.gnome.org/doap-extensions#core" />
|
||||
<programming-language>C</programming-language>
|
||||
<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>
|
||||
<foaf:Person>
|
||||
<foaf:name>Christian Hammond</foaf:name>
|
||||
<foaf:mbox rdf:resource="mailto:chipx86@chipx86.com" />
|
||||
<gnome:userid>chammond</gnome:userid>
|
||||
<foaf:name>Marco Trevisan</foaf:name>
|
||||
<foaf:mbox rdf:resource="mailto:mail@3v1n0.net" />
|
||||
<gnome:userid>marcotrevi</gnome:userid>
|
||||
</foaf:Person>
|
||||
</maintainer>
|
||||
<maintainer>
|
||||
<foaf:Person>
|
||||
<foaf:name>William Jon McCann</foaf:name>
|
||||
<foaf:mbox rdf:resource="mailto:william.jon.mccann@gmail.com" />
|
||||
<gnome:userid>mccann</gnome:userid>
|
||||
<foaf:name>Logan Rathbone</foaf:name>
|
||||
<foaf:mbox rdf:resource="mailto:poprocks@gmail.com" />
|
||||
<gnome:userid>larathbone</gnome:userid>
|
||||
</foaf:Person>
|
||||
</maintainer>
|
||||
</Project>
|
||||
|
|
|
@ -26,6 +26,10 @@
|
|||
#define NOTIFY_DBUS_CORE_INTERFACE "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
|
||||
|
||||
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_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
|
||||
|
||||
#endif /* _LIBNOTIFY_INTERNAL_H_ */
|
||||
|
|
|
@ -69,8 +69,9 @@ pkgconfig.generate(libnotify_lib,
|
|||
)
|
||||
|
||||
introspection = get_option('introspection')
|
||||
if not introspection.disabled()
|
||||
find_program('g-ir-scanner', required: introspection.enabled())
|
||||
g_ir_scanner = find_program('g-ir-scanner', required: introspection.enabled())
|
||||
|
||||
if g_ir_scanner.found() and not introspection.disabled()
|
||||
gnome.generate_gir(libnotify_lib,
|
||||
sources: headers + sources + enum_types,
|
||||
namespace: 'Notify',
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
static void notify_notification_class_init (NotifyNotificationClass *klass);
|
||||
static void notify_notification_init (NotifyNotification *sp);
|
||||
static void notify_notification_finalize (GObject *object);
|
||||
static void notify_notification_dispose (GObject *object);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
|
@ -68,9 +69,11 @@ struct _NotifyNotificationPrivate
|
|||
char *app_name;
|
||||
char *summary;
|
||||
char *body;
|
||||
char *activation_token;
|
||||
|
||||
/* NULL to use icon data. Anything else to have server lookup icon */
|
||||
char *icon_name;
|
||||
GdkPixbuf *icon_pixbuf;
|
||||
|
||||
/*
|
||||
* -1 = use server default
|
||||
|
@ -78,12 +81,14 @@ struct _NotifyNotificationPrivate
|
|||
* > 0 = Number of milliseconds before we timeout
|
||||
*/
|
||||
gint timeout;
|
||||
guint portal_timeout_id;
|
||||
|
||||
GSList *actions;
|
||||
GHashTable *action_map;
|
||||
GHashTable *hints;
|
||||
|
||||
gboolean has_nondefault_actions;
|
||||
gboolean activating;
|
||||
gboolean updates_pending;
|
||||
|
||||
gulong proxy_signal_handler;
|
||||
|
@ -148,6 +153,7 @@ notify_notification_class_init (NotifyNotificationClass *klass)
|
|||
object_class->constructor = notify_notification_constructor;
|
||||
object_class->get_property = notify_notification_get_property;
|
||||
object_class->set_property = notify_notification_set_property;
|
||||
object_class->dispose = notify_notification_dispose;
|
||||
object_class->finalize = notify_notification_finalize;
|
||||
|
||||
/**
|
||||
|
@ -232,9 +238,9 @@ notify_notification_class_init (NotifyNotificationClass *klass)
|
|||
g_param_spec_int ("closed-reason",
|
||||
"Closed Reason",
|
||||
"The reason code for why the notification was closed",
|
||||
-1,
|
||||
NOTIFY_CLOSED_REASON_UNSET,
|
||||
G_MAXINT32,
|
||||
-1,
|
||||
NOTIFY_CLOSED_REASON_UNSET,
|
||||
G_PARAM_READABLE
|
||||
| G_PARAM_STATIC_NAME
|
||||
| G_PARAM_STATIC_NICK
|
||||
|
@ -355,7 +361,7 @@ notify_notification_init (NotifyNotification *obj)
|
|||
{
|
||||
obj->priv = g_new0 (NotifyNotificationPrivate, 1);
|
||||
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,
|
||||
g_str_equal,
|
||||
g_free,
|
||||
|
@ -367,6 +373,22 @@ notify_notification_init (NotifyNotification *obj)
|
|||
(GDestroyNotify) destroy_pair);
|
||||
}
|
||||
|
||||
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
|
||||
notify_notification_finalize (GObject *object)
|
||||
{
|
||||
|
@ -380,6 +402,7 @@ notify_notification_finalize (GObject *object)
|
|||
g_free (priv->summary);
|
||||
g_free (priv->body);
|
||||
g_free (priv->icon_name);
|
||||
g_free (priv->activation_token);
|
||||
|
||||
if (priv->actions != NULL) {
|
||||
g_slist_foreach (priv->actions, (GFunc) g_free, NULL);
|
||||
|
@ -402,6 +425,18 @@ notify_notification_finalize (GObject *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:
|
||||
* @summary: The required summary text.
|
||||
|
@ -431,30 +466,52 @@ try_prepend_path (const char *base_path,
|
|||
{
|
||||
gchar *path_filename;
|
||||
gchar *path_ret;
|
||||
gboolean was_uri;
|
||||
|
||||
if (!path || *path == '\0')
|
||||
return NULL;
|
||||
|
||||
was_uri = TRUE;
|
||||
path_ret = NULL;
|
||||
path_filename = g_filename_from_uri (base_path, NULL, NULL);
|
||||
|
||||
if (path_filename == NULL) {
|
||||
was_uri = FALSE;
|
||||
|
||||
if (base_path && base_path[0] == G_DIR_SEPARATOR) {
|
||||
path_filename = g_strdup (base_path);
|
||||
} else {
|
||||
path_filename = realpath (base_path, NULL);
|
||||
|
||||
if (path_filename == NULL) {
|
||||
/* File path is not existing, but let's check
|
||||
* if it's under the base path before giving up
|
||||
*/
|
||||
path_filename = g_strdup (base_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_debug ("Trying to look at file '%s' in the '%s' prefix.",
|
||||
base_path,
|
||||
path);
|
||||
|
||||
path_ret = g_build_filename (path, path_filename, NULL);
|
||||
if (g_str_has_prefix (path_filename, path)) {
|
||||
path_ret = g_strdup (path_filename);
|
||||
} else {
|
||||
g_debug ("Trying to look at file '%s' in the '%s' prefix.",
|
||||
base_path,
|
||||
path);
|
||||
path_ret = g_build_filename (path, path_filename, NULL);
|
||||
}
|
||||
|
||||
if (!g_file_test (path_ret, G_FILE_TEST_EXISTS)) {
|
||||
g_debug ("Nothing found at %s", path_ret);
|
||||
g_free (path_ret);
|
||||
path_ret = NULL;
|
||||
} else if (was_uri) {
|
||||
gchar *uri = g_filename_to_uri (path_ret, NULL, NULL);
|
||||
|
||||
if (uri != NULL) {
|
||||
g_free (path_ret);
|
||||
path_ret = uri;
|
||||
}
|
||||
}
|
||||
|
||||
g_free (path_filename);
|
||||
|
@ -463,33 +520,32 @@ try_prepend_path (const char *base_path,
|
|||
}
|
||||
|
||||
static gchar *
|
||||
try_prepend_desktop (const gchar *desktop)
|
||||
try_prepend_snap_desktop (NotifyNotification *notification,
|
||||
const gchar *desktop)
|
||||
{
|
||||
gchar *ret;
|
||||
gchar *ret = NULL;
|
||||
|
||||
/*
|
||||
* if it's an absolute path, try prepending $SNAP, otherwise try
|
||||
* $SNAP_NAME_; snap .desktop files are in the format
|
||||
* ${SNAP_NAME}_; snap .desktop files are in the format
|
||||
* ${SNAP_NAME}_desktop_file_name
|
||||
*/
|
||||
ret = try_prepend_path (desktop, g_getenv ("SNAP"));
|
||||
ret = try_prepend_path (desktop, _notify_get_snap_path ());
|
||||
|
||||
if (ret == NULL) {
|
||||
const gchar *snap_name = g_getenv ("SNAP_NAME");
|
||||
|
||||
if (snap_name != NULL && snap_name[0] != '\0') {
|
||||
ret = g_strdup_printf ("%s_%s", snap_name, desktop);
|
||||
}
|
||||
if (ret == NULL && _notify_get_snap_name () != NULL &&
|
||||
strchr (desktop, G_DIR_SEPARATOR) == NULL) {
|
||||
ret = g_strdup_printf ("%s_%s", _notify_get_snap_name (), desktop);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gchar *
|
||||
try_prepend_snap (const gchar *value)
|
||||
try_prepend_snap (NotifyNotification *notification,
|
||||
const gchar *value)
|
||||
{
|
||||
/* hardcoded paths to icons might be relocated under $SNAP */
|
||||
return try_prepend_path (value, g_getenv ("SNAP"));
|
||||
return try_prepend_path (value, _notify_get_snap_path ());
|
||||
}
|
||||
|
||||
|
||||
|
@ -524,7 +580,8 @@ notify_notification_update_internal (NotifyNotification *notification,
|
|||
g_free (notification->priv->icon_name);
|
||||
notification->priv->icon_name = (icon != NULL
|
||||
&& *icon != '\0' ? g_strdup (icon) : NULL);
|
||||
snapped_icon = try_prepend_desktop (notification->priv->icon_name);
|
||||
snapped_icon = try_prepend_snap_desktop (notification,
|
||||
notification->priv->icon_name);
|
||||
if (snapped_icon != NULL) {
|
||||
g_debug ("Icon updated in snap environment: '%s' -> '%s'\n",
|
||||
notification->priv->icon_name, snapped_icon);
|
||||
|
@ -567,6 +624,80 @@ notify_notification_update (NotifyNotification *notification,
|
|||
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
|
||||
proxy_g_signal_cb (GDBusProxy *proxy,
|
||||
const char *sender_name,
|
||||
|
@ -574,8 +705,12 @@ proxy_g_signal_cb (GDBusProxy *proxy,
|
|||
GVariant *parameters,
|
||||
NotifyNotification *notification)
|
||||
{
|
||||
const char *interface;
|
||||
|
||||
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
|
||||
|
||||
interface = g_dbus_proxy_get_interface_name (proxy);
|
||||
|
||||
if (g_strcmp0 (signal_name, "NotificationClosed") == 0 &&
|
||||
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uu)"))) {
|
||||
guint32 id, reason;
|
||||
|
@ -584,33 +719,332 @@ proxy_g_signal_cb (GDBusProxy *proxy,
|
|||
if (id != notification->priv->id)
|
||||
return;
|
||||
|
||||
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));
|
||||
close_notification (notification, reason);
|
||||
} 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)"))) {
|
||||
guint32 id;
|
||||
const char *action;
|
||||
CallbackPair *pair;
|
||||
|
||||
g_variant_get (parameters, "(u&s)", &id, &action);
|
||||
|
||||
if (id != notification->priv->id)
|
||||
return;
|
||||
|
||||
pair = (CallbackPair *) g_hash_table_lookup (notification->priv->action_map,
|
||||
action);
|
||||
if (!activate_action (notification, action) &&
|
||||
g_ascii_strcasecmp (action, "default")) {
|
||||
g_warning ("Received unknown action %s", action);
|
||||
}
|
||||
} else if (g_strcmp0 (signal_name, "ActivationToken") == 0 &&
|
||||
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(us)"))) {
|
||||
guint32 id;
|
||||
const char *activation_token;
|
||||
|
||||
if (pair == NULL) {
|
||||
if (g_ascii_strcasecmp (action, "default")) {
|
||||
g_warning ("Received unknown action %s", action);
|
||||
g_variant_get (parameters, "(u&s)", &id, &activation_token);
|
||||
|
||||
if (id != notification->priv->id)
|
||||
return;
|
||||
|
||||
g_free (notification->priv->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, ¶meter);
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
pair->cb (notification, (char *) action, pair->user_data);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -634,6 +1068,7 @@ notify_notification_show (NotifyNotification *notification,
|
|||
GHashTableIter iter;
|
||||
gpointer key, data;
|
||||
GVariant *result;
|
||||
GApplication *application = NULL;
|
||||
|
||||
g_return_val_if_fail (notification != NULL, FALSE);
|
||||
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), FALSE);
|
||||
|
@ -657,6 +1092,10 @@ notify_notification_show (NotifyNotification *notification,
|
|||
notification);
|
||||
}
|
||||
|
||||
if (_notify_uses_portal_notifications ()) {
|
||||
return add_portal_notification (proxy, notification, error);
|
||||
}
|
||||
|
||||
g_variant_builder_init (&actions_builder, G_VARIANT_TYPE ("as"));
|
||||
for (l = priv->actions; l != NULL; l = l->next) {
|
||||
g_variant_builder_add (&actions_builder, "s", l->data);
|
||||
|
@ -668,6 +1107,41 @@ notify_notification_show (NotifyNotification *notification,
|
|||
g_variant_builder_add (&hints_builder, "{sv}", key, data);
|
||||
}
|
||||
|
||||
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) {
|
||||
gchar *snap_desktop;
|
||||
|
||||
snap_desktop = g_strdup_printf ("%s_%s",
|
||||
_notify_get_snap_name (),
|
||||
_notify_get_snap_app ());
|
||||
|
||||
g_debug ("Using desktop entry: %s", snap_desktop);
|
||||
g_variant_builder_add (&hints_builder, "{sv}",
|
||||
"desktop-entry",
|
||||
g_variant_new_take_string (snap_desktop));
|
||||
}
|
||||
|
||||
if (!_notify_get_snap_app ()) {
|
||||
application = g_application_get_default ();
|
||||
}
|
||||
|
||||
if (application != NULL) {
|
||||
GVariant *desktop_entry = g_hash_table_lookup (priv->hints, "desktop-entry");
|
||||
|
||||
if (desktop_entry == NULL) {
|
||||
const char *application_id = g_application_get_application_id (application);
|
||||
|
||||
g_debug ("Using desktop entry: %s", application_id);
|
||||
g_variant_builder_add (&hints_builder, "{sv}", "desktop-entry",
|
||||
g_variant_new_string (application_id));
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: make this nonblocking */
|
||||
result = g_dbus_proxy_call_sync (proxy,
|
||||
"Notify",
|
||||
|
@ -745,6 +1219,10 @@ notify_notification_set_category (NotifyNotification *notification,
|
|||
g_return_if_fail (notification != NULL);
|
||||
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
|
||||
|
||||
if (maybe_warn_portal_unsupported_feature ("Category")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (category != NULL && category[0] != '\0') {
|
||||
notify_notification_set_hint_string (notification,
|
||||
"category",
|
||||
|
@ -822,11 +1300,18 @@ notify_notification_set_image_from_pixbuf (NotifyNotification *notification,
|
|||
hint_name = "icon_data";
|
||||
}
|
||||
|
||||
g_clear_object (¬ification->priv->icon_pixbuf);
|
||||
|
||||
if (pixbuf == NULL) {
|
||||
notify_notification_set_hint (notification, hint_name, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_notify_uses_portal_notifications ()) {
|
||||
notification->priv->icon_pixbuf = g_object_ref (pixbuf);
|
||||
return;
|
||||
}
|
||||
|
||||
g_object_get (pixbuf,
|
||||
"width", &width,
|
||||
"height", &height,
|
||||
|
@ -855,13 +1340,20 @@ notify_notification_set_image_from_pixbuf (NotifyNotification *notification,
|
|||
notify_notification_set_hint (notification, hint_name, value);
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
get_parsed_variant (GVariant *variant,
|
||||
gchar *(*str_parser)(const gchar *))
|
||||
{
|
||||
gchar *parsed = str_parser (g_variant_get_string (variant, NULL));
|
||||
typedef gchar * (*StringParserFunc) (NotifyNotification *, const gchar *);
|
||||
|
||||
if (parsed != NULL) {
|
||||
static GVariant *
|
||||
get_parsed_variant (NotifyNotification *notification,
|
||||
const char *key,
|
||||
GVariant *variant,
|
||||
StringParserFunc str_parser)
|
||||
{
|
||||
const char *str = g_variant_get_string (variant, NULL);
|
||||
gchar *parsed = str_parser (notification, str);
|
||||
|
||||
if (parsed != NULL && g_strcmp0 (str, parsed) != 0) {
|
||||
g_debug ("Hint %s updated in snap environment: '%s' -> '%s'\n",
|
||||
key, str, parsed);
|
||||
g_variant_unref (variant);
|
||||
variant = g_variant_new_take_string (parsed);
|
||||
}
|
||||
|
@ -870,18 +1362,28 @@ get_parsed_variant (GVariant *variant,
|
|||
}
|
||||
|
||||
static GVariant *
|
||||
maybe_parse_snap_hint_value (const gchar *key,
|
||||
maybe_parse_snap_hint_value (NotifyNotification *notification,
|
||||
const gchar *key,
|
||||
GVariant *value)
|
||||
{
|
||||
StringParserFunc parse_func = NULL;
|
||||
|
||||
if (!_notify_get_snap_path ())
|
||||
return value;
|
||||
|
||||
if (g_strcmp0 (key, "desktop-entry") == 0) {
|
||||
value = get_parsed_variant (value, try_prepend_desktop);
|
||||
parse_func = try_prepend_snap_desktop;
|
||||
} else if (g_strcmp0 (key, "image-path") == 0 ||
|
||||
g_strcmp0 (key, "image_path") == 0 ||
|
||||
g_strcmp0 (key, "sound-file") == 0) {
|
||||
value = get_parsed_variant (value, try_prepend_snap);
|
||||
parse_func = try_prepend_snap;
|
||||
}
|
||||
|
||||
return value;
|
||||
if (parse_func == NULL) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return get_parsed_variant (notification, key, value, parse_func);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -906,7 +1408,7 @@ notify_notification_set_hint (NotifyNotification *notification,
|
|||
g_return_if_fail (key != NULL && *key != '\0');
|
||||
|
||||
if (value != NULL) {
|
||||
value = maybe_parse_snap_hint_value (key, value);
|
||||
value = maybe_parse_snap_hint_value (notification, key, value);
|
||||
g_hash_table_insert (notification->priv->hints,
|
||||
g_strdup (key),
|
||||
g_variant_ref_sink (value));
|
||||
|
@ -933,6 +1435,10 @@ notify_notification_set_app_name (NotifyNotification *notification,
|
|||
{
|
||||
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
|
||||
|
||||
if (maybe_warn_portal_unsupported_feature ("App Name")) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_free (notification->priv->app_name);
|
||||
notification->priv->app_name = g_strdup (app_name);
|
||||
|
||||
|
@ -1038,7 +1544,11 @@ notify_notification_set_hint_byte_array (NotifyNotification *notification,
|
|||
|
||||
g_return_if_fail (value != NULL || len == 0);
|
||||
|
||||
#ifdef GLIB_VERSION_2_68
|
||||
value_dup = g_memdup2 (value, len);
|
||||
#else
|
||||
value_dup = g_memdup (value, len);
|
||||
#endif
|
||||
notify_notification_set_hint (notification, key,
|
||||
g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
|
||||
value_dup,
|
||||
|
@ -1167,6 +1677,28 @@ notify_notification_add_action (NotifyNotification *notification,
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* notify_notification_get_activation_token:
|
||||
* @notification: The notification.
|
||||
*
|
||||
* If an an action is currently being activated, return the activation token.
|
||||
* This function is intended to be used in a #NotifyActionCallback to get
|
||||
* the activation token for the activated action, if the notification daemon
|
||||
* supports it.
|
||||
*
|
||||
* Return value: (transfer none): The current activation token, or %NULL if none
|
||||
*
|
||||
* Since: 0.7.10
|
||||
*/
|
||||
const char *
|
||||
notify_notification_get_activation_token (NotifyNotification *notification)
|
||||
{
|
||||
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), NULL);
|
||||
g_return_val_if_fail (notification->priv->activating, NULL);
|
||||
|
||||
return notification->priv->activation_token;
|
||||
}
|
||||
|
||||
gboolean
|
||||
_notify_notification_has_nondefault_actions (const NotifyNotification *n)
|
||||
{
|
||||
|
@ -1203,6 +1735,12 @@ notify_notification_close (NotifyNotification *notification,
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
if (_notify_uses_portal_notifications ()) {
|
||||
return remove_portal_notification (proxy, notification,
|
||||
NOTIFY_CLOSED_REASON_API_REQUEST,
|
||||
error);
|
||||
}
|
||||
|
||||
/* FIXME: make this nonblocking! */
|
||||
result = g_dbus_proxy_call_sync (proxy,
|
||||
"CloseNotification",
|
||||
|
@ -1227,13 +1765,17 @@ notify_notification_close (NotifyNotification *notification,
|
|||
* Returns the closed reason code for the notification. This is valid only
|
||||
* 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
|
||||
notify_notification_get_closed_reason (const NotifyNotification *notification)
|
||||
{
|
||||
g_return_val_if_fail (notification != NULL, -1);
|
||||
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), -1);
|
||||
g_return_val_if_fail (notification != NULL, NOTIFY_CLOSED_REASON_UNSET);
|
||||
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification),
|
||||
NOTIFY_CLOSED_REASON_UNSET);
|
||||
|
||||
return notification->priv->closed_reason;
|
||||
}
|
||||
|
|
|
@ -55,6 +55,11 @@ typedef struct _NotifyNotification NotifyNotification;
|
|||
typedef struct _NotifyNotificationClass NotifyNotificationClass;
|
||||
typedef struct _NotifyNotificationPrivate NotifyNotificationPrivate;
|
||||
|
||||
/**
|
||||
* NotifyNotification:
|
||||
*
|
||||
* A passive pop-up notification.
|
||||
*/
|
||||
struct _NotifyNotification
|
||||
{
|
||||
/*< private >*/
|
||||
|
@ -88,11 +93,34 @@ typedef enum
|
|||
|
||||
} 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:
|
||||
* @notification:
|
||||
* @action:
|
||||
* @user_data:
|
||||
* @notification: a #NotifyActionCallback notification
|
||||
* @action: (transfer none): The activated action name
|
||||
* @user_data: (nullable) (transfer none): User provided data
|
||||
*
|
||||
* An action callback function.
|
||||
*/
|
||||
|
@ -180,6 +208,8 @@ void notify_notification_add_action (NotifyNotificatio
|
|||
gpointer user_data,
|
||||
GFreeFunc free_func);
|
||||
|
||||
const char *notify_notification_get_activation_token (NotifyNotification *notification);
|
||||
|
||||
void notify_notification_clear_actions (NotifyNotification *notification);
|
||||
gboolean notify_notification_close (NotifyNotification *notification,
|
||||
GError **error);
|
||||
|
|
|
@ -43,10 +43,14 @@
|
|||
|
||||
static gboolean _initted = FALSE;
|
||||
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 GList *_active_notifications = NULL;
|
||||
static int _spec_version_major = 0;
|
||||
static int _spec_version_minor = 0;
|
||||
static int _portal_version = 0;
|
||||
|
||||
gboolean
|
||||
_notify_check_spec_version (int major,
|
||||
|
@ -74,6 +78,26 @@ _notify_get_server_info (char **ret_name,
|
|||
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,
|
||||
"GetServerInformation",
|
||||
g_variant_new ("()"),
|
||||
|
@ -119,6 +143,18 @@ _notify_update_spec_version (GError **error)
|
|||
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:
|
||||
|
@ -130,38 +166,267 @@ _notify_update_spec_version (GError **error)
|
|||
void
|
||||
notify_set_app_name (const char *app_name)
|
||||
{
|
||||
g_free (_app_name);
|
||||
_app_name = g_strdup (app_name);
|
||||
set_app_name (app_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
gboolean
|
||||
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)
|
||||
return TRUE;
|
||||
|
||||
notify_set_app_name (app_name);
|
||||
if (app_name == NULL) {
|
||||
GApplication *application;
|
||||
|
||||
#if !GLIB_CHECK_VERSION (2, 36, 0)
|
||||
g_type_init ();
|
||||
#endif
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (!set_app_name (app_name)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
_initted = 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:
|
||||
*
|
||||
|
@ -211,6 +476,15 @@ notify_uninit (void)
|
|||
_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;
|
||||
}
|
||||
|
||||
|
@ -227,6 +501,46 @@ notify_is_initted (void)
|
|||
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:
|
||||
* @error: (allow-none): a location to store a #GError, or %NULL
|
||||
|
@ -242,6 +556,14 @@ _notify_get_proxy (GError **error)
|
|||
if (_proxy != NULL)
|
||||
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,
|
||||
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
|
||||
NULL,
|
||||
|
@ -250,6 +572,8 @@ _notify_get_proxy (GError **error)
|
|||
NOTIFY_DBUS_CORE_INTERFACE,
|
||||
NULL,
|
||||
error);
|
||||
|
||||
out:
|
||||
if (_proxy == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
@ -287,6 +611,15 @@ notify_get_server_caps (void)
|
|||
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,
|
||||
"GetCapabilities",
|
||||
g_variant_new ("()"),
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<node name="/org/freedesktop/Notification">
|
||||
|
||||
<interface name="org.freedesktop.NotificationListener">
|
||||
|
||||
</interface>
|
||||
</node>
|
11
meson.build
11
meson.build
|
@ -1,6 +1,6 @@
|
|||
project('libnotify',
|
||||
'c',
|
||||
version: '0.7.9',
|
||||
version: '0.8.1',
|
||||
meson_version: '>= 0.47.0')
|
||||
|
||||
gnome = import('gnome')
|
||||
|
@ -22,8 +22,11 @@ LT_CURRENT=4
|
|||
LT_REVISION=0
|
||||
LT_AGE=0
|
||||
|
||||
API_VERSION = 7
|
||||
|
||||
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]
|
||||
|
||||
default_includes = include_directories('.')
|
||||
|
@ -36,7 +39,7 @@ man1dir = join_paths(prefix, get_option('mandir'), 'man1')
|
|||
libnotify_deps = []
|
||||
extra_deps = []
|
||||
|
||||
glib_req_version = '>= 2.26.0'
|
||||
glib_req_version = '>= 2.38.0'
|
||||
|
||||
gdk_pixbuf_dep = dependency('gdk-pixbuf-2.0')
|
||||
glib_dep = dependency('glib-2.0', version: glib_req_version)
|
||||
|
@ -66,7 +69,7 @@ if get_option('man')
|
|||
'@INPUT@',
|
||||
]
|
||||
|
||||
testrun = run_command(xsltproc, '--nonet', stylesheet)
|
||||
testrun = run_command(xsltproc, '--nonet', stylesheet, check: false)
|
||||
|
||||
if testrun.returncode() != 0
|
||||
error('DocBook stylesheet for generating man pages not found, you need to install docbook-xsl-ns or similar package.')
|
||||
|
|
|
@ -29,10 +29,6 @@ main ()
|
|||
{
|
||||
NotifyNotification *n;
|
||||
|
||||
#if !GLIB_CHECK_VERSION (2, 36, 0)
|
||||
g_type_init ();
|
||||
#endif
|
||||
|
||||
notify_init ("Error Handling");
|
||||
|
||||
n = notify_notification_new ("Summary", "Content", NULL);
|
||||
|
|
|
@ -28,10 +28,6 @@ main ()
|
|||
GError *error;
|
||||
error = NULL;
|
||||
|
||||
#if !GLIB_CHECK_VERSION (2, 36, 0)
|
||||
g_type_init ();
|
||||
#endif
|
||||
|
||||
notify_init ("Replace Test");
|
||||
|
||||
n = notify_notification_new ("Summary",
|
||||
|
|
|
@ -26,12 +26,14 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <glib.h>
|
||||
#include <glib-unix.h>
|
||||
#include <glib/gprintf.h>
|
||||
|
||||
#define N_(x) (x)
|
||||
#define GETTEXT_PACKAGE NULL
|
||||
|
||||
static NotifyUrgency urgency = NOTIFY_URGENCY_NORMAL;
|
||||
static GMainLoop *loop = NULL;
|
||||
|
||||
static gboolean
|
||||
g_option_arg_urgency_cb (const char *option_name,
|
||||
|
@ -99,6 +101,26 @@ notify_notification_set_hint_variant (NotifyNotification *notification,
|
|||
key,
|
||||
(guchar) h_byte);
|
||||
}
|
||||
} else if (g_ascii_strcasecmp (type, "boolean") == 0) {
|
||||
gboolean h_boolean = FALSE;
|
||||
|
||||
if (g_ascii_strcasecmp (value, "true") == 0) {
|
||||
h_boolean = TRUE;
|
||||
} else if (g_ascii_isdigit (*value)) {
|
||||
h_boolean = !!g_ascii_strtoull (value, NULL, 10);
|
||||
}
|
||||
|
||||
notify_notification_set_hint (notification, key,
|
||||
g_variant_new_boolean (h_boolean));
|
||||
} else if (g_ascii_strcasecmp (type, "variant") == 0) {
|
||||
GVariant *variant = g_variant_parse (NULL, value, NULL, NULL, NULL);
|
||||
|
||||
if (variant != NULL) {
|
||||
notify_notification_set_hint (notification, key, variant);
|
||||
} else {
|
||||
conv_error = TRUE;
|
||||
}
|
||||
|
||||
} else {
|
||||
*error = g_error_new (G_OPTION_ERROR,
|
||||
G_OPTION_ERROR_BAD_VALUE,
|
||||
|
@ -120,6 +142,70 @@ notify_notification_set_hint_variant (NotifyNotification *notification,
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
handle_closed (NotifyNotification *notify,
|
||||
gpointer user_data)
|
||||
{
|
||||
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
|
||||
handle_action (NotifyNotification *notify,
|
||||
char *action,
|
||||
gpointer user_data)
|
||||
{
|
||||
const char *action_name = user_data;
|
||||
const char *activation_token;
|
||||
|
||||
activation_token = notify_notification_get_activation_token (notify);
|
||||
|
||||
g_printf ("%s\n", action_name);
|
||||
|
||||
if (activation_token) {
|
||||
g_debug ("Activation Token: %s", activation_token);
|
||||
}
|
||||
|
||||
notify_notification_close (notify, NULL);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_wait_timeout (gpointer data)
|
||||
{
|
||||
fprintf (stderr, "Wait timeout expired\n");
|
||||
g_main_loop_quit (loop);
|
||||
|
||||
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
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
|
@ -130,8 +216,17 @@ main (int argc, char *argv[])
|
|||
static char *icon_str = NULL;
|
||||
static char **n_text = NULL;
|
||||
static char **hints = 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 gint notification_id = 0;
|
||||
static gboolean do_version = FALSE;
|
||||
static gboolean hint_error = FALSE, show_error = FALSE;
|
||||
static gboolean transient = FALSE;
|
||||
static gboolean wait = FALSE;
|
||||
static glong expire_timeout = NOTIFY_EXPIRES_DEFAULT;
|
||||
GOptionContext *opt_ctx;
|
||||
NotifyNotification *notify;
|
||||
|
@ -155,10 +250,26 @@ main (int argc, char *argv[])
|
|||
{"category", 'c', 0, G_OPTION_ARG_FILENAME, &type,
|
||||
N_("Specifies the notification category."),
|
||||
N_("TYPE[,TYPE...]")},
|
||||
{"transient", 'e', 0, G_OPTION_ARG_NONE, &transient,
|
||||
N_("Create a transient notification"),
|
||||
NULL},
|
||||
{"hint", 'h', 0, G_OPTION_ARG_FILENAME_ARRAY, &hints,
|
||||
N_
|
||||
("Specifies basic extra data to pass. Valid types are int, double, string and byte."),
|
||||
("Specifies basic extra data to pass. Valid types are boolean, int, double, string, byte and variant."),
|
||||
N_("TYPE:NAME:VALUE")},
|
||||
{"print-id", 'p', 0, G_OPTION_ARG_NONE, &print_id,
|
||||
N_ ("Print the notification ID."), NULL},
|
||||
{"replace-id", 'r', 0, G_OPTION_ARG_INT, ¬ification_id,
|
||||
N_ ("The ID of the notification to replace."), N_("REPLACE_ID")},
|
||||
{"wait", 'w', 0, G_OPTION_ARG_NONE, &wait,
|
||||
N_("Wait for the notification to be closed before exiting."),
|
||||
NULL},
|
||||
{"action", 'A', 0, G_OPTION_ARG_FILENAME_ARRAY, &actions,
|
||||
N_
|
||||
("Specifies the actions to display to the user. Implies --wait to wait for user input."
|
||||
" May be set multiple times. The name of the action is output to stdout. If NAME is "
|
||||
"not specified, the numerical index of the option is used (starting with 0)."),
|
||||
N_("[NAME=]Text...")},
|
||||
{"version", 'v', 0, G_OPTION_ARG_NONE, &do_version,
|
||||
N_("Version of the package."),
|
||||
NULL},
|
||||
|
@ -172,10 +283,6 @@ main (int argc, char *argv[])
|
|||
|
||||
setlocale (LC_ALL, "");
|
||||
|
||||
#if !GLIB_CHECK_VERSION (2, 36, 0)
|
||||
g_type_init ();
|
||||
#endif
|
||||
|
||||
g_set_prgname (argv[0]);
|
||||
g_log_set_always_fatal (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
|
||||
|
||||
|
@ -217,14 +324,41 @@ main (int argc, char *argv[])
|
|||
if (!notify_init ("notify-send"))
|
||||
exit (1);
|
||||
|
||||
notify = notify_notification_new (summary,
|
||||
body,
|
||||
icon_str);
|
||||
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,
|
||||
"summary", summary,
|
||||
"body", body,
|
||||
"icon-name", icon_str,
|
||||
"id", notification_id,
|
||||
NULL);
|
||||
|
||||
notify_notification_set_category (notify, type);
|
||||
notify_notification_set_urgency (notify, urgency);
|
||||
notify_notification_set_timeout (notify, expire_timeout);
|
||||
notify_notification_set_app_name (notify, app_name);
|
||||
|
||||
if (transient) {
|
||||
notify_notification_set_hint (notify, "transient",
|
||||
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);
|
||||
|
||||
/* Set hints */
|
||||
|
@ -252,7 +386,7 @@ main (int argc, char *argv[])
|
|||
|
||||
if (!retval) {
|
||||
fprintf (stderr, "%s\n", error->message);
|
||||
g_error_free (error);
|
||||
g_clear_error (&error);
|
||||
hint_error = TRUE;
|
||||
}
|
||||
}
|
||||
|
@ -263,16 +397,84 @@ main (int argc, char *argv[])
|
|||
}
|
||||
}
|
||||
|
||||
if (actions != NULL) {
|
||||
gint i = 0;
|
||||
char *action = NULL;
|
||||
gchar **spl = NULL;
|
||||
gboolean have_actions;
|
||||
|
||||
have_actions = server_has_capability ("actions");
|
||||
if (!have_actions) {
|
||||
g_printerr (N_("Actions are not supported by this "
|
||||
"notifications server. "
|
||||
"Displaying non-interactively.\n"));
|
||||
show_error = TRUE;
|
||||
}
|
||||
|
||||
while (have_actions && (action = actions[i++])) {
|
||||
gchar *name;
|
||||
const gchar *label;
|
||||
|
||||
spl = g_strsplit (action, "=", 2);
|
||||
|
||||
if (g_strv_length (spl) == 1) {
|
||||
name = g_strdup_printf ("%d", i - 1);
|
||||
label = g_strstrip (spl[0]);
|
||||
} else {
|
||||
name = g_strdup (g_strstrip (spl[0]));
|
||||
label = g_strstrip (spl[1]);
|
||||
}
|
||||
|
||||
if (*label != '\0' && *name != '\0') {
|
||||
notify_notification_add_action (notify,
|
||||
name,
|
||||
label,
|
||||
handle_action,
|
||||
name,
|
||||
g_free);
|
||||
wait = TRUE;
|
||||
}
|
||||
|
||||
g_strfreev (spl);
|
||||
}
|
||||
|
||||
g_strfreev (actions);
|
||||
}
|
||||
|
||||
if (wait) {
|
||||
g_signal_connect (G_OBJECT (notify),
|
||||
"closed",
|
||||
G_CALLBACK (handle_closed),
|
||||
NULL);
|
||||
|
||||
if (expire_timeout > 0) {
|
||||
g_timeout_add (expire_timeout, on_wait_timeout, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hint_error) {
|
||||
retval = notify_notification_show (notify, &error);
|
||||
|
||||
if (!retval) {
|
||||
fprintf (stderr, "%s\n", error->message);
|
||||
g_error_free (error);
|
||||
g_clear_error (&error);
|
||||
show_error = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (print_id) {
|
||||
g_object_get (notify, "id", ¬ification_id, NULL);
|
||||
g_printf ("%d\n", notification_id);
|
||||
}
|
||||
|
||||
if (wait) {
|
||||
g_unix_signal_add (SIGINT, on_sigint, notify);
|
||||
loop = g_main_loop_new (NULL, FALSE);
|
||||
g_main_loop_run (loop);
|
||||
g_main_loop_unref (loop);
|
||||
loop = NULL;
|
||||
}
|
||||
|
||||
g_object_unref (G_OBJECT (notify));
|
||||
|
||||
notify_uninit ();
|
||||
|
|
Loading…
Reference in New Issue