GNotification: Add sound property

Allow applications to specify whether a sound should be played
when it is shown. And allow setting custom sound.

The default behavior is to not play any sound, in order to not break current
apps that play the sound themself.
This commit is contained in:
Julian Sparber 2024-11-27 13:12:52 +01:00
parent a172f22c95
commit 13e0deefc5
11 changed files with 489 additions and 0 deletions

View File

@ -101,6 +101,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GNetworkAddress, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GNetworkMonitor, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GNetworkService, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GNotification, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GNotificationSound, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GOutputStream, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GPermission, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GPollableInputStream, g_object_unref)

View File

@ -119,6 +119,7 @@
#include <gio/gnetworkmonitor.h>
#include <gio/gnetworkservice.h>
#include <gio/gnotification.h>
#include <gio/gnotificationsound.h>
#include <gio/goutputstream.h>
#include <gio/gpermission.h>
#include <gio/gpollableinputstream.h>

View File

@ -64,6 +64,7 @@ typedef struct _GPermission GPermission;
typedef struct _GMenuModel GMenuModel;
typedef struct _GNotification GNotification;
typedef struct _GNotificationSound GNotificationSound;
typedef struct _GDrive GDrive; /* Dummy typedef */
typedef struct _GFileEnumerator GFileEnumerator;

View File

@ -22,6 +22,7 @@
#ifndef __G_NOTIFICATION_PRIVATE_H__
#define __G_NOTIFICATION_PRIVATE_H__
#include "gnotificationsound-private.h"
#include "gnotification.h"
const gchar * g_notification_get_id (GNotification *notification);
@ -34,6 +35,8 @@ const gchar * g_notification_get_category (GNotifi
GIcon * g_notification_get_icon (GNotification *notification);
GNotificationSound * g_notification_get_sound (GNotification *notification);
GNotificationPriority g_notification_get_priority (GNotification *notification);
guint g_notification_get_n_buttons (GNotification *notification);

View File

@ -88,6 +88,7 @@ struct _GNotification
gchar *title;
gchar *body;
GIcon *icon;
GNotificationSound *sound;
GNotificationPriority priority;
gchar *category;
GPtrArray *buttons;
@ -123,6 +124,7 @@ g_notification_dispose (GObject *object)
GNotification *notification = G_NOTIFICATION (object);
g_clear_object (&notification->icon);
g_clear_object (&notification->sound);
G_OBJECT_CLASS (g_notification_parent_class)->dispose (object);
}
@ -303,6 +305,45 @@ g_notification_set_icon (GNotification *notification,
notification->icon = g_object_ref (icon);
}
/**
* g_notification_set_sound:
* @notification: a [class@Gio.Notification]
* @sound: (nullable): a [class@Gio.NotificationSound]
*
* Sets the sound that will be played when @notification is shown.
* If %NULL no sound will be played if the platform supports it.
*
* Since: 2.85
*/
void
g_notification_set_sound (GNotification *notification,
GNotificationSound *sound)
{
g_return_if_fail (G_IS_NOTIFICATION (notification));
g_return_if_fail (G_IS_NOTIFICATION_SOUND (sound) || sound == NULL);
g_set_object (&notification->sound, sound);
}
/*< private >
* g_notification_get_sound:
* @notification: a [class@Gio.Notification]
*
* Gets the sound currently set on @notification.
*
* Returns: (nullable): (transfer none): the sound associated with @notification
*
* Since: 2.85
*/
GNotificationSound *
g_notification_get_sound (GNotification *notification)
{
g_return_val_if_fail (G_IS_NOTIFICATION (notification), NULL);
return notification->sound;
}
/*< private >
* g_notification_get_priority:
* @notification: a #GNotification

View File

@ -53,6 +53,10 @@ GIO_AVAILABLE_IN_2_40
void g_notification_set_icon (GNotification *notification,
GIcon *icon);
GIO_AVAILABLE_IN_2_85
void g_notification_set_sound (GNotification *notification,
GNotificationSound *sound);
GIO_DEPRECATED_IN_2_42_FOR(g_notification_set_priority)
void g_notification_set_urgent (GNotification *notification,
gboolean urgent);

View File

@ -0,0 +1,35 @@
/*
* Copyright © 2024 GNOME Foundation Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Julian Sparber <jsparber@gnome.org>
*/
#ifndef __G_NOTIFICATION_SOUND_PRIVATE_H__
#define __G_NOTIFICATION_SOUND_PRIVATE_H__
#include "gnotificationsound.h"
GBytes * g_notification_sound_get_bytes (GNotificationSound *sound);
GFile * g_notification_sound_get_file (GNotificationSound *sound);
gboolean g_notification_sound_is_default (GNotificationSound *sound);
gboolean g_notification_sound_get_custom (GNotificationSound *sound,
gchar **action,
GVariant **target);
#endif

281
gio/gnotificationsound.c Normal file
View File

@ -0,0 +1,281 @@
/*
* Copyright © 2024 GNOME Foundation Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Julian Sparber <jsparber@gnome.org>
*/
#include "gnotificationsound-private.h"
#include "gaction.h"
#include "gfile.h"
#include "gbytes.h"
/**
* GNotificationSound:
*
* [class@Gio.NotificationSound] holds the sound that should be played when a notification
* is displayed. Use [method@Gio.Notification.set_sound] to set it for a notification.
*
* Since: 2.85
**/
typedef GObjectClass GNotificationSoundClass;
typedef enum
{
SOUND_TYPE_DEFAULT,
SOUND_TYPE_FILE,
SOUND_TYPE_BYTES,
SOUND_TYPE_CUSTOM,
} SoundType;
struct _GNotificationSound
{
GObject parent;
SoundType sound_type;
union {
GFile *file;
GBytes *bytes;
struct {
gchar *action;
GVariant *target;
} custom;
};
};
G_DEFINE_TYPE (GNotificationSound, g_notification_sound, G_TYPE_OBJECT)
static void
g_notification_sound_dispose (GObject *object)
{
GNotificationSound *sound = G_NOTIFICATION_SOUND (object);
if (sound->sound_type == SOUND_TYPE_FILE)
{
g_clear_object (&sound->file);
}
else if (sound->sound_type == SOUND_TYPE_BYTES)
{
g_clear_pointer (&sound->bytes, g_bytes_unref);
}
else if (sound->sound_type == SOUND_TYPE_CUSTOM)
{
g_clear_pointer (&sound->custom.action, g_free);
g_clear_pointer (&sound->custom.target, g_variant_unref);
}
G_OBJECT_CLASS (g_notification_sound_parent_class)->dispose (object);
}
static void
g_notification_sound_class_init (GNotificationSoundClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = g_notification_sound_dispose;
}
static void
g_notification_sound_init (GNotificationSound *notification)
{
}
/**
* g_notification_sound_new_from_file:
* @file: A [iface@Gio.File] containing a sound in a common audio format
*
* [class@Gio.Notification] using this [class@Gio.NotificationSound] will play the sound in @file
* when displayed.
*
* The sound format `ogg/opus`, `ogg/vorbis` and `wav/pcm` are guaranteed to be supported.
* Other audio formats may be supported in future.
*
* Returns: a new [class@Gio.NotificationSound] instance
*
* Since: 2.85
*/
GNotificationSound *
g_notification_sound_new_from_file (GFile *file)
{
GNotificationSound *sound;
g_return_val_if_fail (G_IS_FILE (file), NULL);
sound = g_object_new (G_TYPE_NOTIFICATION_SOUND, NULL);
sound->file = g_object_ref (file);
sound->sound_type = SOUND_TYPE_FILE;
return sound;
}
/**
* g_notification_sound_new_from_bytes:
* @bytes: [struct@GLib.Bytes] containing a sound in common audio format
*
* [class@Gio.Notification] using this [class@Gio.NotificationSound] will play the sound in @bytes
* when displayed.
*
* The sound format `ogg/opus`, `ogg/vorbis` and `wav/pcm` are guaranteed to be supported.
* Other audio formats may be supported in future.
*
* Returns: a new [class@Gio.NotificationSound] instance
*
* Since: 2.85
*/
GNotificationSound *
g_notification_sound_new_from_bytes (GBytes *bytes)
{
GNotificationSound *sound;
g_return_val_if_fail (bytes != NULL, NULL);
sound = g_object_new (G_TYPE_NOTIFICATION_SOUND, NULL);
sound->bytes = g_bytes_ref (bytes);
sound->sound_type = SOUND_TYPE_BYTES;
return sound;
}
/**
* g_notification_sound_new_default:
*
* [class@Gio.Notification] using this [class@Gio.NotificationSound] will play the default sound when displayed.
*
* Returns: a new [class@Gio.NotificationSound] instance.
*
* Since: 2.85
*/
GNotificationSound *
g_notification_sound_new_default (void)
{
GNotificationSound *sound;
sound = g_object_new (G_TYPE_NOTIFICATION_SOUND, NULL);
sound->sound_type = SOUND_TYPE_DEFAULT;
return sound;
}
/**
* g_notification_sound_new_custom:
*
* [class@Gio.Notification] using this [class@Gio.NotificationSound]
* will call @action with @target when the notification is presented to the user
* and the app should play a sound immediately.
*
* Returns: a new [class@Gio.NotificationSound] instance.
*
* Since: 2.85
*/
GNotificationSound *
g_notification_sound_new_custom (const char *action,
GVariant *target)
{
GNotificationSound *sound;
g_return_val_if_fail (action != NULL && g_action_name_is_valid (action), NULL);
if (!g_str_has_prefix (action, "app."))
{
g_warning ("%s: action '%s' does not start with 'app.'."
"This is unlikely to work properly.", G_STRFUNC, action);
}
sound = g_object_new (G_TYPE_NOTIFICATION_SOUND, NULL);
sound->sound_type = SOUND_TYPE_CUSTOM;
g_set_str (&sound->custom.action, action);
g_clear_pointer (&sound->custom.target, g_variant_unref);
sound->custom.target = g_variant_ref_sink (target);
return sound;
}
/*< private >
* g_notification_sound_get_bytes:
* @sound: a [class@Gio.NotificationSound]
*
* Returns: (nullable): (transfer none): the bytes associated with @sound
*/
GBytes *
g_notification_sound_get_bytes (GNotificationSound *sound)
{
g_return_val_if_fail (G_IS_NOTIFICATION_SOUND (sound), NULL);
if (sound->sound_type == SOUND_TYPE_BYTES)
return sound->bytes;
else
return NULL;
}
/*< private >
* g_notification_sound_get_file:
* @sound: a [class@Gio.NotificationSound]
*
* Returns: (nullable): (transfer none): the [iface@Gio.File] associated with @sound
*/
GFile *
g_notification_sound_get_file (GNotificationSound *sound)
{
g_return_val_if_fail (G_IS_NOTIFICATION_SOUND (sound), NULL);
if (sound->sound_type == SOUND_TYPE_FILE)
return sound->file;
else
return NULL;
}
/*< private >
* g_notification_sound_is_default:
* @sound: a [class@Gio.NotificationSound]
*
* Returns: whether this @sound uses should play the default sound.
*/
gboolean
g_notification_sound_is_default (GNotificationSound *sound)
{
g_return_val_if_fail (G_IS_NOTIFICATION_SOUND (sound), FALSE);
return sound->sound_type == SOUND_TYPE_DEFAULT;
}
/*< private >
* g_notification_sound_get_custom:
* @sound: a [class@Gio.NotificationSound]
*
* Returns: whether this @sound uses a custom action that is called to play a sound.
*/
gboolean
g_notification_sound_get_custom (GNotificationSound *sound,
gchar **action,
GVariant **target)
{
g_return_val_if_fail (G_IS_NOTIFICATION_SOUND (sound), FALSE);
if (sound->sound_type == SOUND_TYPE_CUSTOM)
{
if (action)
*action = g_strdup (sound->custom.action);
if (target)
*target = sound->custom.target ? g_variant_ref (sound->custom.target) : NULL;
return TRUE;
}
return FALSE;
}

56
gio/gnotificationsound.h Normal file
View File

@ -0,0 +1,56 @@
/*
* Copyright © 2024 GNOME Foundation Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Julian Sparber <jsparber@gnome.org>
*/
#ifndef __G_NOTIFICATION_SOUND_H__
#define __G_NOTIFICATION_SOUND_H__
#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION)
#error "Only <gio/gio.h> can be included directly."
#endif
#include <gio/giotypes.h>
G_BEGIN_DECLS
#define G_TYPE_NOTIFICATION_SOUND (g_notification_sound_get_type ())
#define G_NOTIFICATION_SOUND(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_NOTIFICATION_SOUND, GNotificationSound))
#define G_IS_NOTIFICATION_SOUND(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_NOTIFICATION_SOUND))
GIO_AVAILABLE_IN_2_85
GType g_notification_sound_get_type (void) G_GNUC_CONST;
GIO_AVAILABLE_IN_2_85
GNotificationSound * g_notification_sound_new_from_file (GFile *file);
GIO_AVAILABLE_IN_2_85
GNotificationSound * g_notification_sound_new_from_bytes (GBytes *bytes);
GIO_AVAILABLE_IN_2_85
GNotificationSound * g_notification_sound_new_default (void);
GIO_AVAILABLE_IN_2_85
GNotificationSound * g_notification_sound_new_custom (const char *action,
GVariant *target);
G_END_DECLS
#endif /* __G_NOTIFICATION_SOUND_H__ */

View File

@ -298,6 +298,7 @@ application_headers = files(
'gmenuexporter.h',
'gdbusmenumodel.h',
'gnotification.h',
'gnotificationsound.h',
)
application_sources = files(
@ -320,6 +321,7 @@ application_sources = files(
'gmenuexporter.c',
'gdbusmenumodel.c',
'gnotification.c',
'gnotificationsound.c',
'gnotificationbackend.c',
)

View File

@ -170,6 +170,7 @@ struct _GNotification
gchar *title;
gchar *body;
GIcon *icon;
GNotificationSound *sound;
GNotificationPriority priority;
gchar *category;
GPtrArray *buttons;
@ -184,14 +185,40 @@ typedef struct
GVariant *target;
} Button;
typedef enum
{
SOUND_TYPE_DEFAULT,
SOUND_TYPE_FILE,
SOUND_TYPE_BYTES,
SOUND_TYPE_CUSTOM,
} SoundType;
struct _GNotificationSound
{
GObject parent;
SoundType sound_type;
union {
GFile *file;
GBytes *bytes;
struct {
gchar *action;
GVariant *target;
} custom;
};
};
static void
test_properties (void)
{
GNotification *n;
GNotificationSound *sound;
struct _GNotification *rn;
struct _GNotificationSound *rns;
GIcon *icon;
const gchar * const *names;
Button *b;
GVariant *target;
n = g_notification_new ("Test");
@ -227,6 +254,43 @@ test_properties (void)
g_assert_cmpstr (rn->default_action, ==, "app.action2");
g_assert_cmpstr (g_variant_get_string (rn->default_action_target, NULL), ==, "target2");
GFile *file = g_file_new_for_uri ("file:///someuri");
sound = g_notification_sound_new_from_file (file);
g_notification_set_sound (n, sound);
rns = (struct _GNotificationSound *)n->sound;
g_assert_true (rns->sound_type == SOUND_TYPE_FILE);
g_assert_true (rns->file == file);
g_clear_object (&sound);
g_clear_object (&file);
GBytes *bytes = g_bytes_new_static (NULL, 0);
sound = g_notification_sound_new_from_bytes (bytes);
g_notification_set_sound (n, sound);
rns = (struct _GNotificationSound *)n->sound;
g_assert_true (rns->sound_type == SOUND_TYPE_BYTES);
g_assert_true (rns->bytes == bytes);
g_clear_object (&sound);
g_clear_pointer (&bytes, g_bytes_unref);
sound = g_notification_sound_new_default ();
g_notification_set_sound (n, sound);
rns = (struct _GNotificationSound *)n->sound;
g_assert_true (rns->sound_type == SOUND_TYPE_DEFAULT);
g_notification_set_sound (n, NULL);
g_assert_null (rn->sound);
g_clear_object (&sound);
target = g_variant_new_string ("some target");
sound = g_notification_sound_new_custom ("app.play-custom-sound", target);
g_notification_set_sound (n, sound);
rns = (struct _GNotificationSound *)n->sound;
g_assert_true (rns->sound_type == SOUND_TYPE_CUSTOM);
g_assert_cmpstr (rns->custom.action, ==, "app.play-custom-sound");
g_assert_true (rns->custom.target == target);
g_notification_set_sound (n, NULL);
g_assert_null (rn->sound);
g_clear_object (&sound);
g_object_unref (n);
}