diff --git a/libpurple/purpleavatar.h b/libpurple/purpleavatar.h
--- a/libpurple/purpleavatar.h
+++ b/libpurple/purpleavatar.h
@@ -45,17 +45,30 @@
  */
 
 /**
- * purple_avatar_new:
- * @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.
  *
  * Since: 3.0.0
  */
-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.
+ *
+ * Since: 3.0.0
+ */
+PurpleAvatar *purple_avatar_new_from_resource(const char *resource_path, GError **error);
 
 /**
  * purple_avatar_get_filename:
@@ -70,22 +83,15 @@
 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.
- *
- * Since: 3.0.0
- */
-void purple_avatar_set_filename(PurpleAvatar *avatar, const char *filename);
-
-/**
  * purple_avatar_get_pixbuf:
  * @avatar: The instance.
  *
  * Gets the [class@GdkPixbuf.Pixbuf] of @avatar.
  *
+ * 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: The pixbuf of the avatar which could be %NULL.
  *
  * Since: 3.0.0
@@ -93,16 +99,29 @@
 GdkPixbuf *purple_avatar_get_pixbuf(PurpleAvatar *avatar);
 
 /**
- * purple_avatar_set_pixbuf:
+ * purple_avatar_get_animated:
  * @avatar: The instance.
- * @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.
  *
  * Since: 3.0.0
  */
-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.
+ *
+ * Since: 3.0.0
+ */
+GdkPixbufAnimation *purple_avatar_get_animation(PurpleAvatar *avatar);
 
 /**
  * purple_avatar_get_tags:
@@ -116,36 +135,6 @@
  */
 PurpleTags *purple_avatar_get_tags(PurpleAvatar *avatar);
 
-/**
- * purple_avatar_load:
- * @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.
- *
- * Since: 3.0.0
- */
-gboolean purple_avatar_load(PurpleAvatar *avatar, GError **error);
-
-/**
- * purple_avatar_save:
- * @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.
- *
- * Since: 3.0.0
- */
-gboolean purple_avatar_save(PurpleAvatar *avatar, GError **error);
-
 G_END_DECLS
 
 #endif /* PURPLE_AVATAR_H */
# HG changeset patch
# Node ID 703d25556a61
# Parent  0b26ebc842db
diff --git a/libpurple/purpleavatar.c b/libpurple/purpleavatar.c
--- a/libpurple/purpleavatar.c
+++ b/libpurple/purpleavatar.c
@@ -22,7 +22,12 @@
 	GObject parent;
 
 	char *filename;
+
 	GdkPixbuf *pixbuf;
+
+	gboolean animated;
+	GdkPixbufAnimation *animation;
+
 	PurpleTags *tags;
 };
 
@@ -30,6 +35,8 @@
 	PROP_0,
 	PROP_FILENAME,
 	PROP_PIXBUF,
+	PROP_ANIMATED,
+	PROP_ANIMATION,
 	PROP_TAGS,
 	N_PROPERTIES,
 };
@@ -38,6 +45,45 @@
 G_DEFINE_TYPE(PurpleAvatar, purple_avatar, G_TYPE_OBJECT)
 
 /******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+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]);
+}
+
+static PurpleAvatar *
+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);
+	} else {
+		/* If we did load an animation, set the appropriate properties and
+		 * return the avatar.
+		 */
+		avatar->animated = TRUE;
+		avatar->animation = animation;
+	}
+
+	return avatar;
+}
+
+/******************************************************************************
  * GObject Implementation
  *****************************************************************************/
 static void
@@ -53,6 +99,12 @@
 		case PROP_PIXBUF:
 			g_value_set_object(value, purple_avatar_get_pixbuf(avatar));
 			break;
+		case PROP_ANIMATED:
+			g_value_set_boolean(value, purple_avatar_get_animated(avatar));
+			break;
+		case PROP_ANIMATION:
+			g_value_set_object(value, purple_avatar_get_animation(avatar));
+			break;
 		case PROP_TAGS:
 			g_value_set_object(value, purple_avatar_get_tags(avatar));
 			break;
@@ -72,9 +124,6 @@
 		case PROP_FILENAME:
 			purple_avatar_set_filename(avatar, g_value_get_string(value));
 			break;
-		case PROP_PIXBUF:
-			purple_avatar_set_pixbuf(avatar, g_value_get_object(value));
-			break;
 		default:
 			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
 			break;
@@ -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 @@
 	/**
 	 * PurpleAvatar:filename:
 	 *
-	 * The filename to save/load the avatar to/from.
+	 * The filename that this avatar was created from.
 	 *
 	 * Since: 3.0.0
 	 */
@@ -116,13 +166,14 @@
 		"filename", "filename",
 		"The filename to save/load the avatar from.",
 		NULL,
-		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
 
 	/**
 	 * PurpleAvatar:pixbuf:
 	 *
-	 * 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 from
+	 * from the animation.
 	 *
 	 * Since: 3.0.0
 	 */
@@ -130,7 +181,34 @@
 		"pixbuf", "pixbuf",
 		"The pixbuf of the avatar.",
 		GDK_TYPE_PIXBUF,
-		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleAvatar:animated:
+	 *
+	 * Whether or not this avatar is animated.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_ANIMATED] = g_param_spec_boolean(
+		"animated", "animated",
+		"Whether or not the avatar is animated.",
+		FALSE,
+		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleAvatar:animation:
+	 *
+	 * The [class@GdkPixbuf.PixbufAnimation] if
+	 * [property@Purple.Avatar:animated] is %TRUE.
+	 *
+	 * Since: 3.0.0
+	 */
+	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);
 
 	/**
 	 * PurpleAvatar:tags:
@@ -143,7 +221,7 @@
 		"tags", "tags",
 		"The tags for the avatar.",
 		PURPLE_TYPE_TAGS,
-		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 @@
  * Public API
  *****************************************************************************/
 PurpleAvatar *
-purple_avatar_new(const char *filename, GdkPixbuf *pixbuf) {
-	return g_object_new(
-		PURPLE_TYPE_AVATAR,
-		"filename", filename,
-		"pixbuf", pixbuf,
-		NULL);
+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 NULL;
+	}
+
+	return purple_avatar_new_common(filename, animation);
+}
+
+PurpleAvatar *
+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,
+	                                                   &local_error);
+	if(!GDK_IS_PIXBUF_ANIMATION(animation) || local_error != NULL) {
+		g_clear_object(&animation);
+
+		g_propagate_error(error, local_error);
+
+		return NULL;
+	}
+
+	return purple_avatar_new_common(NULL, animation);
 }
 
 const char *
@@ -167,30 +275,29 @@
 	return avatar->filename;
 }
 
-void
-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]);
-}
-
 GdkPixbuf *
 purple_avatar_get_pixbuf(PurpleAvatar *avatar) {
 	g_return_val_if_fail(PURPLE_IS_AVATAR(avatar), NULL);
 
+	if(avatar->animated) {
+		return gdk_pixbuf_animation_get_static_image(avatar->animation);
+	}
+
 	return avatar->pixbuf;
 }
 
-void
-purple_avatar_set_pixbuf(PurpleAvatar *avatar, GdkPixbuf *pixbuf) {
-	g_return_if_fail(PURPLE_IS_AVATAR(avatar));
+gboolean
+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;
+}
+
+GdkPixbufAnimation *
+purple_avatar_get_animation(PurpleAvatar *avatar) {
+	g_return_val_if_fail(PURPLE_IS_AVATAR(avatar), NULL);
+
+	return avatar->animation;
 }
 
 PurpleTags *
@@ -199,30 +306,3 @@
 
 	return avatar->tags;
 }
-
-gboolean
-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);
-
-		return FALSE;
-	}
-
-	return TRUE;
-}
-
-gboolean
-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);
-}
diff --git a/libpurple/tests/avatar/animated.gif b/libpurple/tests/avatar/animated.gif
new file mode 100644
index 0000000000000000000000000000000000000000..c3fa8899f7b271d512fd01039923d0da711f6723
GIT binary patch
diff --git a/libpurple/tests/avatar/meson.build b/libpurple/tests/avatar/meson.build
new file mode 100644
--- /dev/null
+++ b/libpurple/tests/avatar/meson.build
@@ -0,0 +1,17 @@
+TEST_PURPLE_AVATAR_SOURCES = [
+	'test_purple_avatar.c'
+]
+
+TEST_PURPLE_AVATAR_RESOURCES = gnome.compile_resources(
+	'test_purple_avatar_resources',
+	'test_purple_avatar.gresource.xml',
+	source_dir : '.',
+	c_name : 'test_purple_avatar')
+TEST_PURPLE_AVATAR_SOURCES += TEST_PURPLE_AVATAR_RESOURCES
+
+test_purple_avatar = executable(
+	'test_purple_avatar',
+	TEST_PURPLE_AVATAR_SOURCES,
+	dependencies : [libpurple_dep, glib, gdk_pixbuf])
+
+test('avatar', test_purple_avatar)
diff --git a/libpurple/tests/avatar/static.png b/libpurple/tests/avatar/static.png
new file mode 100644
index 0000000000000000000000000000000000000000..4a446bc28cc8e7b0973eb8907e99c9560e7ffcb4
GIT binary patch
diff --git a/libpurple/tests/avatar/test_purple_avatar.gresource.xml b/libpurple/tests/avatar/test_purple_avatar.gresource.xml
new file mode 100644
--- /dev/null
+++ b/libpurple/tests/avatar/test_purple_avatar.gresource.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/im/pidgin/libpurple/tests/avatar/">
+    <file>static.png</file>
+    <file>wave.png</file>
+    <file>animated.gif</file>
+  </gresource>
+</gresources>
diff --git a/libpurple/tests/avatar/wave.png b/libpurple/tests/avatar/wave.png
new file mode 100644
index 0000000000000000000000000000000000000000..b426f06e844a2f62f17099ef1e0011c1d518899d
GIT binary patch
diff --git a/libpurple/tests/meson.build b/libpurple/tests/meson.build
--- a/libpurple/tests/meson.build
+++ b/libpurple/tests/meson.build
@@ -2,7 +2,6 @@
     'account_option',
     'account_manager',
     'authorization_request',
-    'avatar',
     'circular_buffer',
     'contact',
     'contact_info',
@@ -56,4 +55,5 @@
     )
 endforeach
 
+subdir('avatar')
 subdir('sqlite3')
diff --git a/libpurple/tests/test_avatar.c b/libpurple/tests/avatar/test_purple_avatar.c
rename from libpurple/tests/test_avatar.c
rename to libpurple/tests/avatar/test_purple_avatar.c
--- a/libpurple/tests/test_avatar.c
+++ b/libpurple/tests/avatar/test_purple_avatar.c
@@ -24,61 +24,48 @@
  * Tests
  *****************************************************************************/
 static void
-test_purple_avatar_new(void) {
+test_purple_avatar_new_static(void) {
 	PurpleAvatar *avatar = NULL;
 	GdkPixbuf *pixbuf = NULL;
+	GError *error = NULL;
+	const char *resource = "/im/pidgin/libpurple/tests/avatar/static.png";
 
-	pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
-	avatar = purple_avatar_new("filename", pixbuf);
-
+	avatar = purple_avatar_new_from_resource(resource, &error);
+	g_assert_no_error(error);
 	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_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);
-	g_clear_object(&pixbuf);
 }
 
 static void
-test_purple_avatar_properties(void) {
+test_purple_avatar_new_animated(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);
+	GdkPixbufAnimation *animation = NULL;
+	GError *error = NULL;
+	const char *resource = "/im/pidgin/libpurple/tests/avatar/animated.gif";
 
-	/* 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
-	 * code.
-	 */
-	avatar = g_object_new(
-		PURPLE_TYPE_AVATAR,
-		"filename", "filename",
-		"pixbuf", pixbuf,
-		NULL);
+	avatar = purple_avatar_new_from_resource(resource, &error);
+	g_assert_no_error(error);
+	g_assert_true(PURPLE_IS_AVATAR(avatar));
 
-	/* Now use g_object_get to read all of the properties. */
-	g_object_get(avatar,
-		"filename", &filename,
-		"pixbuf", &pixbuf1,
-		"tags", &tags,
-		NULL);
+	g_assert_null(purple_avatar_get_filename(avatar));
+	g_assert_true(purple_avatar_get_animated(avatar));
 
-	/* Compare all the things. */
-	g_assert_cmpstr(filename, ==, "filename");
-	g_assert_true(pixbuf1 == pixbuf);
-	g_assert_nonnull(tags);
+	pixbuf = purple_avatar_get_pixbuf(avatar);
+	g_assert_true(GDK_IS_PIXBUF(pixbuf));
 
-	/* Free/unref all the things. */
-	g_clear_pointer(&filename, g_free);
-	g_clear_object(&pixbuf1);
-	g_clear_object(&tags);
+	animation = purple_avatar_get_animation(avatar);
+	g_assert_true(GDK_IS_PIXBUF_ANIMATION(animation));
 
 	g_clear_object(&avatar);
-	g_clear_object(&pixbuf);
 }
 
 /******************************************************************************
@@ -88,10 +75,10 @@
 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);
+	g_test_add_func("/avatar/new/static",
+	                test_purple_avatar_new_static);
+	g_test_add_func("/avatar/new/animated",
+	                test_purple_avatar_new_animated);
 
 	return g_test_run();
 }
