--- a/libpurple/purpleavatar.c Fri Jan 06 01:53:58 2023 -0600
+++ b/libpurple/purpleavatar.c Fri Jan 06 02:10:17 2023 -0600
@@ -22,7 +22,12 @@
+ GdkPixbufAnimation *animation; @@ -30,6 +35,8 @@
@@ -38,6 +45,45 @@
G_DEFINE_TYPE(PurpleAvatar, purple_avatar, G_TYPE_OBJECT)
/******************************************************************************
+ *****************************************************************************/ +purple_avatar_set_filename(PurpleAvatar *avatar, const char *filename) { + g_return_if_fail(PURPLE_IS_AVATAR(avatar)); + g_free(avatar->filename); + avatar->filename = g_strdup(filename); + g_object_notify_by_pspec(G_OBJECT(avatar), properties[PROP_FILENAME]); +purple_avatar_new_common(const char *filename, GdkPixbufAnimation *animation) { + PurpleAvatar *avatar = NULL; + avatar = g_object_new(PURPLE_TYPE_AVATAR, "filename", filename, NULL); + if(gdk_pixbuf_animation_is_static_image(animation)) { + /* If we loaded a static image, grab the static image and set it to our + * pixbuf member, clear the animation, and return the new avatar. + avatar->pixbuf = gdk_pixbuf_animation_get_static_image(animation); + g_object_ref(avatar->pixbuf); + g_clear_object(&animation); + /* If we did load an animation, set the appropriate properties and + avatar->animated = TRUE; + avatar->animation = animation; +/****************************************************************************** *****************************************************************************/
@@ -53,6 +99,12 @@
g_value_set_object(value, purple_avatar_get_pixbuf(avatar));
+ g_value_set_boolean(value, purple_avatar_get_animated(avatar)); + g_value_set_object(value, purple_avatar_get_animation(avatar)); g_value_set_object(value, purple_avatar_get_tags(avatar));
@@ -72,9 +124,6 @@
purple_avatar_set_filename(avatar, g_value_get_string(value));
- purple_avatar_set_pixbuf(avatar, g_value_get_object(value));
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
@@ -87,6 +136,7 @@
g_clear_pointer(&avatar->filename, g_free);
g_clear_object(&avatar->pixbuf);
+ g_clear_object(&avatar->animation); g_clear_object(&avatar->tags);
G_OBJECT_CLASS(purple_avatar_parent_class)->finalize(obj);
@@ -108,7 +158,7 @@
- * The filename to save/load the avatar to/from.
+ * The filename that this avatar was created from. @@ -116,13 +166,14 @@
"The filename to save/load the avatar from.",
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); - * The [class@GdkPixbuf.Pixbuf]. This may be %NULL if
- * [method@Purple.Avatar.load] has not yet been called.
+ * The [class@GdkPixbuf.Pixbuf] of the avatar. If + * [property@Purple.Avatar:animated] is %TRUE, this will be a static frame @@ -130,7 +181,34 @@
"The pixbuf of the avatar.",
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + * PurpleAvatar:animated: + * Whether or not this avatar is animated. + properties[PROP_ANIMATED] = g_param_spec_boolean( + "animated", "animated", + "Whether or not the avatar is animated.", + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + * PurpleAvatar:animation: + * The [class@GdkPixbuf.PixbufAnimation] if + * [property@Purple.Avatar:animated] is %TRUE. + properties[PROP_ANIMATION] = g_param_spec_object( + "animation", "animation", + "The animation of the avatar.", + GDK_TYPE_PIXBUF_ANIMATION, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); @@ -143,7 +221,7 @@
"The tags for the avatar.",
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
@@ -152,12 +230,42 @@
*****************************************************************************/
-purple_avatar_new(const char *filename, GdkPixbuf *pixbuf) {
+purple_avatar_new_from_filename(const char *filename, GError **error) { + GdkPixbufAnimation *animation = NULL; + GError *local_error = NULL; + g_return_val_if_fail(filename != NULL, NULL); + animation = gdk_pixbuf_animation_new_from_file(filename, &local_error); + if(!GDK_IS_PIXBUF_ANIMATION(animation) || local_error != NULL) { + g_clear_object(&animation); + g_propagate_error(error, local_error); + return purple_avatar_new_common(filename, animation); +purple_avatar_new_from_resource(const char *resource_path, GError **error) { + GdkPixbufAnimation *animation = NULL; + GError *local_error = NULL; + g_return_val_if_fail(resource_path != NULL, NULL); + animation = gdk_pixbuf_animation_new_from_resource(resource_path, + if(!GDK_IS_PIXBUF_ANIMATION(animation) || local_error != NULL) { + g_clear_object(&animation); + g_propagate_error(error, local_error); + return purple_avatar_new_common(NULL, animation); @@ -167,30 +275,29 @@
-purple_avatar_set_filename(PurpleAvatar *avatar, const char *filename) {
- g_return_if_fail(PURPLE_IS_AVATAR(avatar));
- g_free(avatar->filename);
- avatar->filename = g_strdup(filename);
- g_object_notify_by_pspec(G_OBJECT(avatar), properties[PROP_FILENAME]);
purple_avatar_get_pixbuf(PurpleAvatar *avatar) {
g_return_val_if_fail(PURPLE_IS_AVATAR(avatar), NULL);
+ return gdk_pixbuf_animation_get_static_image(avatar->animation);
-purple_avatar_set_pixbuf(PurpleAvatar *avatar, GdkPixbuf *pixbuf) {
- g_return_if_fail(PURPLE_IS_AVATAR(avatar));
+purple_avatar_get_animated(PurpleAvatar *avatar) { + g_return_val_if_fail(PURPLE_IS_AVATAR(avatar), FALSE); - if(g_set_object(&avatar->pixbuf, pixbuf)) {
- g_object_notify_by_pspec(G_OBJECT(avatar), properties[PROP_PIXBUF]);
+ return avatar->animated; +purple_avatar_get_animation(PurpleAvatar *avatar) { + g_return_val_if_fail(PURPLE_IS_AVATAR(avatar), NULL); + return avatar->animation; @@ -199,30 +306,3 @@
-purple_avatar_load(PurpleAvatar *avatar, GError **error) {
- GError *local_error = NULL;
- g_return_val_if_fail(PURPLE_IS_AVATAR(avatar), FALSE);
- g_clear_object(&avatar->pixbuf);
- avatar->pixbuf = gdk_pixbuf_new_from_file(avatar->filename, &local_error);
- if(avatar->pixbuf == NULL) {
- g_propagate_error(error, local_error);
-purple_avatar_save(PurpleAvatar *avatar, GError **error) {
- g_return_val_if_fail(PURPLE_IS_AVATAR(avatar), FALSE);
- return gdk_pixbuf_save(avatar->pixbuf, avatar->filename, "png", error,
- "quality", "100", NULL);
--- a/libpurple/purpleavatar.h Fri Jan 06 01:53:58 2023 -0600
+++ b/libpurple/purpleavatar.h Fri Jan 06 02:10:17 2023 -0600
@@ -45,17 +45,30 @@
- * @filename: (nullable): The filename for the avatar.
- * @pixbuf: (nullable): The [class@GdkPixbuf.Pixbuf] to use.
+ * purple_avatar_new_from_filename: + * @filename: The filename for the avatar. + * @error: Return address for a #GError, or %NULL. - * Creates a new avatar with @filename and @pixbuf.
+ * Creates a new avatar from @filename. * Returns: (transfer full): The new instance.
-PurpleAvatar *purple_avatar_new(const char *filename, GdkPixbuf *pixbuf);
+PurpleAvatar *purple_avatar_new_from_filename(const char *filename, GError **error); + * purple_avatar_new_from_resource: + * @resource_path: The path of the resource file. + * @error: Return address for a #GError, or %NULL. + * Creates a new avatar from the resource at @resource_path. + * Returns: (transfer full): The new instance. +PurpleAvatar *purple_avatar_new_from_resource(const char *resource_path, GError **error); * purple_avatar_get_filename:
@@ -70,39 +83,45 @@
const char *purple_avatar_get_filename(PurpleAvatar *avatar);
- * purple_avatar_set_filename:
- * @avatar: The instance.
- * @filename: The new filename.
- * Sets the filename of @avatar to @filename.
-void purple_avatar_set_filename(PurpleAvatar *avatar, const char *filename);
* purple_avatar_get_pixbuf:
* Gets the [class@GdkPixbuf.Pixbuf] of @avatar.
- * Returns: The pixbuf of the avatar which could be %NULL.
+ * If [property@Purple.Avatar:animated] is %TRUE, this returns a single frame + * of the animation. To get the animation see + * [method@Purple.Avatar.get_animation]. + * Returns: (transfer none): The pixbuf of the avatar which could be %NULL. GdkPixbuf *purple_avatar_get_pixbuf(PurpleAvatar *avatar);
- * purple_avatar_set_pixbuf:
+ * purple_avatar_get_animated: - * @pixbuf: (nullable): The new [class@GdkPixbuf.Pixbuf].
- * Sets the [class@GdkPixbuf.Pixbuf] for @avatar to @pixbuf. If @pixbuf is
- * %NULL, the pixbuf will be cleared.
+ * Gets whether or not @avatar is animated. + * Returns: %TRUE if @avatar is animated, %FALSE otherwise. -void purple_avatar_set_pixbuf(PurpleAvatar *avatar, GdkPixbuf *pixbuf);
+gboolean purple_avatar_get_animated(PurpleAvatar *avatar); + * purple_avatar_get_animation: + * @avatar: The instance. + * Gets the [class@GdkPixbuf.PixbufAnimation] if + * [property@Purple.Avatar:animated] is %TRUE, otherwise %NULL. + * Returns: (transfer none): The animation or %NULL. +GdkPixbufAnimation *purple_avatar_get_animation(PurpleAvatar *avatar); * purple_avatar_get_tags:
@@ -116,36 +135,6 @@
PurpleTags *purple_avatar_get_tags(PurpleAvatar *avatar);
- * @avatar: The instance.
- * @error: Return address for a #GError, or %NULL.
- * Attempts to load @avatar from disk from [property@Purple.Avatar:filename].
- * If successful, %TRUE is returned, otherwise %FALSE will be returned with
- * @error potentially set.
- * Returns: %TRUE on success or %FALSE on error with @error potentialy set.
-gboolean purple_avatar_load(PurpleAvatar *avatar, GError **error);
- * @avatar: The instance.
- * @error: Return address for a #GError, or %NULL.
- * Attempts to save @avatar to disk using [property@Purple.Avatar:filename].
- * If successful, %TRUE is returned, otherwise %FALSE will be returned with
- * @error potentially set.
- * Returns: %TRUE on success or %FALSE on error with @error potentialy set.
-gboolean purple_avatar_save(PurpleAvatar *avatar, GError **error);
#endif /* PURPLE_AVATAR_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/avatar/meson.build Fri Jan 06 02:10:17 2023 -0600
@@ -0,0 +1,17 @@
+TEST_PURPLE_AVATAR_SOURCES = [ +TEST_PURPLE_AVATAR_RESOURCES = gnome.compile_resources( + 'test_purple_avatar_resources', + 'test_purple_avatar.gresource.xml', + c_name : 'test_purple_avatar') +TEST_PURPLE_AVATAR_SOURCES += TEST_PURPLE_AVATAR_RESOURCES +test_purple_avatar = executable( + TEST_PURPLE_AVATAR_SOURCES, + dependencies : [libpurple_dep, glib, gdk_pixbuf]) +test('avatar', test_purple_avatar) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/avatar/test_purple_avatar.c Fri Jan 06 02:10:17 2023 -0600
@@ -0,0 +1,84 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * 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 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 <https://www.gnu.org/licenses/>. +/****************************************************************************** + *****************************************************************************/ +test_purple_avatar_new_static(void) { + PurpleAvatar *avatar = NULL; + GdkPixbuf *pixbuf = NULL; + const char *resource = "/im/pidgin/libpurple/tests/avatar/static.png"; + avatar = purple_avatar_new_from_resource(resource, &error); + g_assert_no_error(error); + g_assert_true(PURPLE_IS_AVATAR(avatar)); + g_assert_null(purple_avatar_get_filename(avatar)); + g_assert_false(purple_avatar_get_animated(avatar)); + g_assert_null(purple_avatar_get_animation(avatar)); + pixbuf = purple_avatar_get_pixbuf(avatar); + g_assert_true(GDK_IS_PIXBUF(pixbuf)); + g_clear_object(&avatar); +test_purple_avatar_new_animated(void) { + PurpleAvatar *avatar = NULL; + GdkPixbuf *pixbuf = NULL; + GdkPixbufAnimation *animation = NULL; + const char *resource = "/im/pidgin/libpurple/tests/avatar/animated.gif"; + avatar = purple_avatar_new_from_resource(resource, &error); + g_assert_no_error(error); + g_assert_true(PURPLE_IS_AVATAR(avatar)); + g_assert_null(purple_avatar_get_filename(avatar)); + g_assert_true(purple_avatar_get_animated(avatar)); + pixbuf = purple_avatar_get_pixbuf(avatar); + g_assert_true(GDK_IS_PIXBUF(pixbuf)); + animation = purple_avatar_get_animation(avatar); + g_assert_true(GDK_IS_PIXBUF_ANIMATION(animation)); + g_clear_object(&avatar); +/****************************************************************************** + *****************************************************************************/ +main(gint argc, gchar *argv[]) { + g_test_init(&argc, &argv, NULL); + g_test_add_func("/avatar/new/static", + test_purple_avatar_new_static); + g_test_add_func("/avatar/new/animated", + test_purple_avatar_new_animated); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/avatar/test_purple_avatar.gresource.xml Fri Jan 06 02:10:17 2023 -0600
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?> + <gresource prefix="/im/pidgin/libpurple/tests/avatar/"> + <file>static.png</file> + <file>animated.gif</file> --- a/libpurple/tests/meson.build Fri Jan 06 01:53:58 2023 -0600
+++ b/libpurple/tests/meson.build Fri Jan 06 02:10:17 2023 -0600
@@ -2,7 +2,6 @@
@@ -56,4 +55,5 @@
--- a/libpurple/tests/test_avatar.c Fri Jan 06 01:53:58 2023 -0600
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * 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 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 <https://www.gnu.org/licenses/>.
-/******************************************************************************
- *****************************************************************************/
-test_purple_avatar_new(void) {
- PurpleAvatar *avatar = NULL;
- GdkPixbuf *pixbuf = NULL;
- pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
- avatar = purple_avatar_new("filename", pixbuf);
- g_assert_true(PURPLE_IS_AVATAR(avatar));
- g_assert_cmpstr(purple_avatar_get_filename(avatar), ==, "filename");
- g_assert_true(purple_avatar_get_pixbuf(avatar) == pixbuf);
- g_clear_object(&avatar);
- g_clear_object(&pixbuf);
-test_purple_avatar_properties(void) {
- PurpleAvatar *avatar = NULL;
- PurpleTags *tags = NULL;
- GdkPixbuf *pixbuf = NULL;
- GdkPixbuf *pixbuf1 = NULL;
- gchar *filename = NULL;
- pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
- /* Use g_object_new so we can test setting properties by name. All of them
- * call the setter methods, so by doing it this way we exercise more of the
- "filename", "filename",
- /* Now use g_object_get to read all of the properties. */
- /* Compare all the things. */
- g_assert_cmpstr(filename, ==, "filename");
- g_assert_true(pixbuf1 == pixbuf);
- g_assert_nonnull(tags);
- /* Free/unref all the things. */
- g_clear_pointer(&filename, g_free);
- g_clear_object(&pixbuf1);
- g_clear_object(&avatar);
- g_clear_object(&pixbuf);
-/******************************************************************************
- *****************************************************************************/
-main(gint argc, gchar *argv[]) {
- g_test_init(&argc, &argv, NULL);
- g_test_add_func("/avatar/new",
- test_purple_avatar_new);
- g_test_add_func("/avatar/properties",
- test_purple_avatar_properties);