Compare commits

..

1 Commits

Author SHA1 Message Date
Logan Rathbone 72d66bd305 Revert "Merge branch 'readme' into 'master'"
This reverts merge request !21
2022-03-21 22:15:30 +00:00
18 changed files with 100 additions and 1404 deletions

View File

@ -26,12 +26,4 @@ build:ubuntu:
- pip3 install meson
script:
- meson _build -Ddocbook_docs=enabled
- 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
- ninja -C _build

68
NEWS
View File

@ -1,71 +1,3 @@
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]

View File

@ -1,20 +0,0 @@
# 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

View File

@ -3,5 +3,4 @@
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>

View File

@ -1272,65 +1272,6 @@
</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>

View File

@ -58,18 +58,6 @@
<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>
@ -101,31 +89,7 @@
<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>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>
<para>Specifies basic extra data to pass. Valid types are <literal>INT</literal>, <literal>DOUBLE</literal>, <literal>STRING</literal> and <literal>BYTE</literal>.</para>
</listitem>
</varlistentry>
</variablelist>
@ -133,6 +97,6 @@
<refsection>
<info><title>See also</title></info>
<para>The Desktop Notification Spec on <link xlink:href="https://specifications.freedesktop.org/notification-spec/">https://specifications.freedesktop.org/notification-spec/</link>.</para>
<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>
</refsection>
</refentry>

View File

@ -4,7 +4,6 @@ NOTIFY_EXPIRES_DEFAULT
NOTIFY_EXPIRES_NEVER
<TITLE>NotifyNotification</TITLE>
NotifyNotification
NotifyClosedReason
NotifyUrgency
NotifyActionCallback
NOTIFY_ACTION_CALLBACK
@ -28,7 +27,6 @@ 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

View File

@ -8,21 +8,18 @@
<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>Marco Trevisan</foaf:name>
<foaf:mbox rdf:resource="mailto:mail@3v1n0.net" />
<gnome:userid>marcotrevi</gnome:userid>
<foaf:name>Christian Hammond</foaf:name>
<foaf:mbox rdf:resource="mailto:chipx86@chipx86.com" />
<gnome:userid>chammond</gnome:userid>
</foaf:Person>
</maintainer>
<maintainer>
<foaf:Person>
<foaf:name>Logan Rathbone</foaf:name>
<foaf:mbox rdf:resource="mailto:poprocks@gmail.com" />
<gnome:userid>larathbone</gnome:userid>
<foaf:name>William Jon McCann</foaf:name>
<foaf:mbox rdf:resource="mailto:william.jon.mccann@gmail.com" />
<gnome:userid>mccann</gnome:userid>
</foaf:Person>
</maintainer>
</Project>

View File

@ -26,10 +26,6 @@
#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);
@ -40,14 +36,6 @@ 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_ */

View File

@ -69,9 +69,8 @@ pkgconfig.generate(libnotify_lib,
)
introspection = get_option('introspection')
g_ir_scanner = find_program('g-ir-scanner', required: introspection.enabled())
if g_ir_scanner.found() and not introspection.disabled()
if not introspection.disabled()
find_program('g-ir-scanner', required: introspection.enabled())
gnome.generate_gir(libnotify_lib,
sources: headers + sources + enum_types,
namespace: 'Notify',

View File

@ -53,7 +53,6 @@
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
{
@ -69,11 +68,9 @@ 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
@ -81,14 +78,12 @@ 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;
@ -153,7 +148,6 @@ 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;
/**
@ -238,9 +232,9 @@ notify_notification_class_init (NotifyNotificationClass *klass)
g_param_spec_int ("closed-reason",
"Closed Reason",
"The reason code for why the notification was closed",
NOTIFY_CLOSED_REASON_UNSET,
-1,
G_MAXINT32,
NOTIFY_CLOSED_REASON_UNSET,
-1,
G_PARAM_READABLE
| G_PARAM_STATIC_NAME
| G_PARAM_STATIC_NICK
@ -361,7 +355,7 @@ notify_notification_init (NotifyNotification *obj)
{
obj->priv = g_new0 (NotifyNotificationPrivate, 1);
obj->priv->timeout = NOTIFY_EXPIRES_DEFAULT;
obj->priv->closed_reason = NOTIFY_CLOSED_REASON_UNSET;
obj->priv->closed_reason = -1;
obj->priv->hints = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
@ -373,22 +367,6 @@ 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)
{
@ -402,7 +380,6 @@ 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);
@ -425,18 +402,6 @@ 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.
@ -466,52 +431,30 @@ 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);
}
}
}
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);
}
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);
@ -520,32 +463,33 @@ try_prepend_path (const char *base_path,
}
static gchar *
try_prepend_snap_desktop (NotifyNotification *notification,
const gchar *desktop)
try_prepend_desktop (const gchar *desktop)
{
gchar *ret = NULL;
gchar *ret;
/*
* 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, _notify_get_snap_path ());
ret = try_prepend_path (desktop, g_getenv ("SNAP"));
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);
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);
}
}
return ret;
}
static gchar *
try_prepend_snap (NotifyNotification *notification,
const gchar *value)
try_prepend_snap (const gchar *value)
{
/* hardcoded paths to icons might be relocated under $SNAP */
return try_prepend_path (value, _notify_get_snap_path ());
return try_prepend_path (value, g_getenv ("SNAP"));
}
@ -580,8 +524,7 @@ 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_snap_desktop (notification,
notification->priv->icon_name);
snapped_icon = try_prepend_desktop (notification->priv->icon_name);
if (snapped_icon != NULL) {
g_debug ("Icon updated in snap environment: '%s' -> '%s'\n",
notification->priv->icon_name, snapped_icon);
@ -624,80 +567,6 @@ 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,
@ -705,12 +574,8 @@ 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;
@ -719,332 +584,33 @@ proxy_g_signal_cb (GDBusProxy *proxy,
if (id != notification->priv->id)
return;
close_notification (notification, reason);
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));
} 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;
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;
pair = (CallbackPair *) g_hash_table_lookup (notification->priv->action_map,
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, &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 (pair == NULL) {
if (g_ascii_strcasecmp (action, "default")) {
g_warning ("Received unknown action %s", action);
}
}
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);
} else {
pair->cb (notification, (char *) action, pair->user_data);
}
}
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;
}
/**
@ -1068,7 +634,6 @@ 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);
@ -1092,10 +657,6 @@ 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);
@ -1107,41 +668,6 @@ 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",
@ -1219,10 +745,6 @@ 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",
@ -1300,18 +822,11 @@ notify_notification_set_image_from_pixbuf (NotifyNotification *notification,
hint_name = "icon_data";
}
g_clear_object (&notification->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,
@ -1340,20 +855,13 @@ notify_notification_set_image_from_pixbuf (NotifyNotification *notification,
notify_notification_set_hint (notification, hint_name, value);
}
typedef gchar * (*StringParserFunc) (NotifyNotification *, const gchar *);
static GVariant *
get_parsed_variant (NotifyNotification *notification,
const char *key,
GVariant *variant,
StringParserFunc str_parser)
get_parsed_variant (GVariant *variant,
gchar *(*str_parser)(const gchar *))
{
const char *str = g_variant_get_string (variant, NULL);
gchar *parsed = str_parser (notification, str);
gchar *parsed = str_parser (g_variant_get_string (variant, NULL));
if (parsed != NULL && g_strcmp0 (str, parsed) != 0) {
g_debug ("Hint %s updated in snap environment: '%s' -> '%s'\n",
key, str, parsed);
if (parsed != NULL) {
g_variant_unref (variant);
variant = g_variant_new_take_string (parsed);
}
@ -1362,28 +870,18 @@ get_parsed_variant (NotifyNotification *notification,
}
static GVariant *
maybe_parse_snap_hint_value (NotifyNotification *notification,
const gchar *key,
maybe_parse_snap_hint_value (const gchar *key,
GVariant *value)
{
StringParserFunc parse_func = NULL;
if (!_notify_get_snap_path ())
return value;
if (g_strcmp0 (key, "desktop-entry") == 0) {
parse_func = try_prepend_snap_desktop;
value = get_parsed_variant (value, try_prepend_desktop);
} else if (g_strcmp0 (key, "image-path") == 0 ||
g_strcmp0 (key, "image_path") == 0 ||
g_strcmp0 (key, "sound-file") == 0) {
parse_func = try_prepend_snap;
value = get_parsed_variant (value, try_prepend_snap);
}
if (parse_func == NULL) {
return value;
}
return get_parsed_variant (notification, key, value, parse_func);
return value;
}
/**
@ -1408,7 +906,7 @@ notify_notification_set_hint (NotifyNotification *notification,
g_return_if_fail (key != NULL && *key != '\0');
if (value != NULL) {
value = maybe_parse_snap_hint_value (notification, key, value);
value = maybe_parse_snap_hint_value (key, value);
g_hash_table_insert (notification->priv->hints,
g_strdup (key),
g_variant_ref_sink (value));
@ -1435,10 +933,6 @@ 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);
@ -1544,11 +1038,7 @@ 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,
@ -1677,28 +1167,6 @@ 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)
{
@ -1735,12 +1203,6 @@ 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",
@ -1765,17 +1227,13 @@ notify_notification_close (NotifyNotification *notification,
* Returns the closed reason code for the notification. This is valid only
* after the "closed" signal is emitted.
*
* 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).
* Returns: The closed reason code.
*/
gint
notify_notification_get_closed_reason (const NotifyNotification *notification)
{
g_return_val_if_fail (notification != NULL, NOTIFY_CLOSED_REASON_UNSET);
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification),
NOTIFY_CLOSED_REASON_UNSET);
g_return_val_if_fail (notification != NULL, -1);
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), -1);
return notification->priv->closed_reason;
}

View File

@ -55,11 +55,6 @@ typedef struct _NotifyNotification NotifyNotification;
typedef struct _NotifyNotificationClass NotifyNotificationClass;
typedef struct _NotifyNotificationPrivate NotifyNotificationPrivate;
/**
* NotifyNotification:
*
* A passive pop-up notification.
*/
struct _NotifyNotification
{
/*< private >*/
@ -93,34 +88,11 @@ 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: a #NotifyActionCallback notification
* @action: (transfer none): The activated action name
* @user_data: (nullable) (transfer none): User provided data
* @notification:
* @action:
* @user_data:
*
* An action callback function.
*/
@ -208,8 +180,6 @@ 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);

View File

@ -43,14 +43,10 @@
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,
@ -78,26 +74,6 @@ _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 ("()"),
@ -143,18 +119,6 @@ _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:
@ -166,267 +130,38 @@ set_app_name (const char *app_name)
void
notify_set_app_name (const char *app_name)
{
set_app_name (app_name);
g_free (_app_name);
_app_name = g_strdup (app_name);
}
/**
* notify_init:
* @app_name: (nullable): The name of the application initializing libnotify.
* @app_name: 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;
if (app_name == NULL) {
GApplication *application;
notify_set_app_name (app_name);
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;
}
#if !GLIB_CHECK_VERSION (2, 36, 0)
g_type_init ();
#endif
_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:
*
@ -476,15 +211,6 @@ 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;
}
@ -501,46 +227,6 @@ 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
@ -556,14 +242,6 @@ _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,
@ -572,8 +250,6 @@ _notify_get_proxy (GError **error)
NOTIFY_DBUS_CORE_INTERFACE,
NULL,
error);
out:
if (_proxy == NULL) {
return NULL;
}
@ -611,15 +287,6 @@ 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 ("()"),

View File

@ -0,0 +1,8 @@
<?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',
'c',
version: '0.8.1',
version: '0.7.9',
meson_version: '>= 0.47.0')
gnome = import('gnome')
@ -22,11 +22,8 @@ LT_CURRENT=4
LT_REVISION=0
LT_AGE=0
API_VERSION = 7
VERSION_ARRAY = meson.project_version().split('.')
# Minor version is hardcoded until we have a real API break
MODULE_VERSION = '@0@.@1@'.format(VERSION_ARRAY[0], API_VERSION)
MODULE_VERSION = '@0@.@1@'.format(VERSION_ARRAY[0], VERSION_ARRAY[1])
LIBNAME = meson.project_name().split('lib')[1]
default_includes = include_directories('.')
@ -39,7 +36,7 @@ man1dir = join_paths(prefix, get_option('mandir'), 'man1')
libnotify_deps = []
extra_deps = []
glib_req_version = '>= 2.38.0'
glib_req_version = '>= 2.26.0'
gdk_pixbuf_dep = dependency('gdk-pixbuf-2.0')
glib_dep = dependency('glib-2.0', version: glib_req_version)
@ -69,7 +66,7 @@ if get_option('man')
'@INPUT@',
]
testrun = run_command(xsltproc, '--nonet', stylesheet, check: false)
testrun = run_command(xsltproc, '--nonet', stylesheet)
if testrun.returncode() != 0
error('DocBook stylesheet for generating man pages not found, you need to install docbook-xsl-ns or similar package.')

View File

@ -29,6 +29,10 @@ 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);

View File

@ -28,6 +28,10 @@ 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",

View File

@ -26,14 +26,12 @@
#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,
@ -101,26 +99,6 @@ 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,
@ -142,70 +120,6 @@ 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[])
{
@ -216,17 +130,8 @@ 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;
@ -250,26 +155,10 @@ 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 boolean, int, double, string, byte and variant."),
("Specifies basic extra data to pass. Valid types are int, double, string and byte."),
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, &notification_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},
@ -283,6 +172,10 @@ 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);
@ -324,41 +217,14 @@ main (int argc, char *argv[])
if (!notify_init ("notify-send"))
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,
"summary", summary,
"body", body,
"icon-name", icon_str,
"id", notification_id,
NULL);
notify = notify_notification_new (summary,
body,
icon_str);
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 */
@ -386,7 +252,7 @@ main (int argc, char *argv[])
if (!retval) {
fprintf (stderr, "%s\n", error->message);
g_clear_error (&error);
g_error_free (error);
hint_error = TRUE;
}
}
@ -397,84 +263,16 @@ 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_clear_error (&error);
g_error_free (error);
show_error = TRUE;
}
}
if (print_id) {
g_object_get (notify, "id", &notification_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 ();