pidgin/pidgin

Parents 46d10c72c137
Children 12ddbb897723
Create PidginPresenceIcon that displays the correct icon for its bound PurplePresence

Testing Done:
Tested all presence changes except invisible as I don't have an account that supports it.

Reviewed at https://reviews.imfreedom.org/r/213/
--- a/ChangeLog.API Sat Nov 14 03:15:43 2020 -0600
+++ b/ChangeLog.API Sat Nov 14 03:39:06 2020 -0600
@@ -632,6 +632,7 @@
* pidgin_check_if_dir
* PidginConversation.sg
* PidginConvPlacementFunc
+ * pidgin_conv_get_tab_icon, use PidginPresenceIcon instead.
* pidgin_conv_placement_get_name
* pidgin_conv_placement_add_fnc
* pidgin_conv_placement_remove_fnc
--- a/doc/reference/pidgin/pidgin-docs.xml Sat Nov 14 03:15:43 2020 -0600
+++ b/doc/reference/pidgin/pidgin-docs.xml Sat Nov 14 03:39:06 2020 -0600
@@ -77,6 +77,7 @@
<xi:include href="xml/pidginplugininfo.xml" />
<xi:include href="xml/pidginpluginsdialog.xml" />
<xi:include href="xml/pidginpluginsmenu.xml" />
+ <xi:include href="xml/pidginpresenceicon.xml" />
<xi:include href="xml/pidginstock.xml" />
<xi:include href="xml/pidginstyle.xml" />
<xi:include href="xml/pidgintalkatu.xml" />
Binary file pidgin/data/icons/hicolor/16x16/status/pidgin-user-available.png has changed
Binary file pidgin/data/icons/hicolor/16x16/status/pidgin-user-away.png has changed
Binary file pidgin/data/icons/hicolor/16x16/status/pidgin-user-busy.png has changed
Binary file pidgin/data/icons/hicolor/16x16/status/pidgin-user-extended-away.png has changed
Binary file pidgin/data/icons/hicolor/16x16/status/pidgin-user-invisible.png has changed
Binary file pidgin/data/icons/hicolor/16x16/status/pidgin-user-offline.png has changed
Binary file pidgin/data/icons/hicolor/16x16/status/pidgin-user-unavailable.png has changed
--- a/pidgin/glade/pidgin3.xml.in Sat Nov 14 03:15:43 2020 -0600
+++ b/pidgin/glade/pidgin3.xml.in Sat Nov 14 03:39:06 2020 -0600
@@ -13,6 +13,7 @@
<glade-widget-class name="PidginInviteDialog" generic-name="invite_dialog" title="InviteDialog"/>
<glade-widget-class name="PidginMenuTray" generic-name="menu_tray" title="MenuTray"/>
<glade-widget-class name="PidginPluginsMenu" generic-name="plugins_menu" title="PluginsMenu"/>
+ <glade-widget-class name="PidginPresenceIcon" generic-name="presence_icon" title="PresenceIcon"/>
<glade-widget-class name="PidginScrollBook" generic-name="scroll_book" title="ScrollBook"/>
<glade-widget-class name="PidginStatusBox" generic-name="status_box" title="StatusBox"/>
<glade-widget-class name="PidginWindow" generic-name="window" title="Window"/>
@@ -30,6 +31,7 @@
<glade-widget-class-ref name="PidginInviteDialog"/>
<glade-widget-class-ref name="PidginMenuTray"/>
<glade-widget-class-ref name="PidginPluginsMenu"/>
+ <glade-widget-class-ref name="PidginPresenceIcon"/>
<glade-widget-class-ref name="PidginScrollBook"/>
<glade-widget-class-ref name="PidginStatusBox"/>
<glade-widget-class-ref name="PidginWindow"/>
--- a/pidgin/gtkconv.c Sat Nov 14 03:15:43 2020 -0600
+++ b/pidgin/gtkconv.c Sat Nov 14 03:39:06 2020 -0600
@@ -58,6 +58,7 @@
#include "pidginlog.h"
#include "pidginmenutray.h"
#include "pidginmessage.h"
+#include "pidginpresenceicon.h"
#include "pidginstock.h"
#include "pidginstyle.h"
#include "pidgintooltip.h"
@@ -1871,89 +1872,11 @@
/**************************************************************************
* A bunch of buddy icon functions
**************************************************************************/
-
-static const char *
-pidgin_conv_get_icon_stock(PurpleConversation *conv)
-{
- PurpleAccount *account = NULL;
- const char *stock = NULL;
-
- g_return_val_if_fail(conv != NULL, NULL);
-
- account = purple_conversation_get_account(conv);
- g_return_val_if_fail(account != NULL, NULL);
-
- /* Use the buddy icon, if possible */
- if (PURPLE_IS_IM_CONVERSATION(conv)) {
- const char *name = NULL;
- PurpleBuddy *b;
- name = purple_conversation_get_name(conv);
- b = purple_blist_find_buddy(account, name);
- if (b != NULL) {
- PurplePresence *p = purple_buddy_get_presence(b);
- PurpleStatus *active = purple_presence_get_active_status(p);
- PurpleStatusType *type = purple_status_get_status_type(active);
- PurpleStatusPrimitive prim = purple_status_type_get_primitive(type);
- stock = pidgin_stock_id_from_status_primitive(prim);
- } else {
- stock = PIDGIN_STOCK_STATUS_PERSON;
- }
- } else {
- stock = PIDGIN_STOCK_STATUS_CHAT;
- }
-
- return stock;
-}
-
-static GdkPixbuf *
-pidgin_conv_get_icon(PurpleConversation *conv, GtkWidget *parent, const char *icon_size)
-{
- PurpleAccount *account = NULL;
- const char *name = NULL;
- const char *stock = NULL;
- GdkPixbuf *status = NULL;
- GtkIconSize size;
-
- g_return_val_if_fail(conv != NULL, NULL);
-
- account = purple_conversation_get_account(conv);
- name = purple_conversation_get_name(conv);
-
- g_return_val_if_fail(account != NULL, NULL);
- g_return_val_if_fail(name != NULL, NULL);
-
- /* Use the buddy icon, if possible */
- if (PURPLE_IS_IM_CONVERSATION(conv)) {
- PurpleBuddy *b = purple_blist_find_buddy(account, name);
- if (b != NULL) {
- /* I hate this hack. It fixes a bug where the pending message icon
- * displays in the conv tab even though it shouldn't.
- * A better solution would be great. */
- purple_blist_update_node(NULL, PURPLE_BLIST_NODE(b));
- }
- }
-
- stock = pidgin_conv_get_icon_stock(conv);
- size = gtk_icon_size_from_name(icon_size);
- status = gtk_widget_render_icon (parent, stock, size, "GtkWidget");
- return status;
-}
-
-GdkPixbuf *
-pidgin_conv_get_tab_icon(PurpleConversation *conv, gboolean small_icon)
-{
- const char *icon_size = small_icon ? PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC : PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL;
- return pidgin_conv_get_icon(conv, PIDGIN_CONVERSATION(conv)->icon, icon_size);
-}
-
-
static void
update_tab_icon(PurpleConversation *conv)
{
PidginConversation *gtkconv;
GdkPixbuf *emblem = NULL;
- const char *status = NULL;
- const char *infopane_status = NULL;
g_return_if_fail(conv != NULL);
@@ -1961,22 +1884,21 @@
if (conv != gtkconv->active_conv)
return;
- status = infopane_status = pidgin_conv_get_icon_stock(conv);
-
if (PURPLE_IS_IM_CONVERSATION(conv)) {
PurpleBuddy *b = purple_blist_find_buddy(purple_conversation_get_account(conv), purple_conversation_get_name(conv));
if (b)
emblem = pidgin_blist_get_emblem((PurpleBlistNode*)b);
}
- g_return_if_fail(status != NULL);
-
- g_object_set(G_OBJECT(gtkconv->icon), "stock", status, NULL);
- g_object_set(G_OBJECT(gtkconv->menu_icon), "stock", status, NULL);
-
+#if 0
+ /* This was purposely left busted rather than providing an intermitent fix,
+ * as the infopane will be split out and rewritten as a proper widget
+ * shortly.
+ */
gtk_list_store_set(GTK_LIST_STORE(gtkconv->infopane_model),
&(gtkconv->infopane_iter),
CONV_ICON_COLUMN, infopane_status, -1);
+#endif
gtk_list_store_set(GTK_LIST_STORE(gtkconv->infopane_model),
&(gtkconv->infopane_iter),
@@ -2440,14 +2362,15 @@
for (l = convs; l != NULL ; l = l->next) {
PurpleConversation *conv = (PurpleConversation*)l->data;
PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
-
- GtkWidget *icon = gtk_image_new_from_stock(pidgin_conv_get_icon_stock(conv),
- gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC));
+ GtkWidget *icon = NULL;
GtkWidget *item;
gchar *text = g_strdup_printf("%s (%d)",
gtk_label_get_text(GTK_LABEL(gtkconv->tab_label)),
gtkconv->unseen_count);
+ icon = pidgin_presence_icon_new(NULL, "chat",
+ GTK_ICON_SIZE_MENU);
+
item = gtk_image_menu_item_new_with_label(text);
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), icon);
g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(unseen_conv_menu_cb), conv);
@@ -7857,16 +7780,28 @@
g_signal_connect(gtkconv->close, "clicked", G_CALLBACK (close_conv_cb), gtkconv);
/* Status icon. */
- gtkconv->icon = gtk_image_new();
- gtkconv->menu_icon = gtk_image_new();
- g_object_set(G_OBJECT(gtkconv->icon),
- "icon-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC),
- NULL);
- g_object_set(G_OBJECT(gtkconv->menu_icon),
- "icon-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC),
- NULL);
+ if(PURPLE_IS_IM_CONVERSATION(conv)) {
+ PurpleBuddy *buddy = NULL;
+ PurplePresence *presence = NULL;
+
+ buddy = purple_blist_find_buddy(purple_conversation_get_account(conv),
+ purple_conversation_get_name(conv));
+
+ if(PURPLE_IS_BUDDY(buddy)) {
+ presence = purple_buddy_get_presence(buddy);
+ }
+
+ gtkconv->icon = pidgin_presence_icon_new(presence, "person",
+ GTK_ICON_SIZE_MENU);
+ gtkconv->menu_icon = pidgin_presence_icon_new(presence, "person",
+ GTK_ICON_SIZE_MENU);
+ } else {
+ gtkconv->icon = pidgin_presence_icon_new(NULL, "chat",
+ GTK_ICON_SIZE_MENU);
+ gtkconv->menu_icon = pidgin_presence_icon_new(NULL, "chat",
+ GTK_ICON_SIZE_MENU);
+ }
gtk_widget_show(gtkconv->icon);
- update_tab_icon(conv);
/* Tab label. */
gtkconv->tab_label = gtk_label_new(tmp_lab = purple_conversation_get_title(conv));
--- a/pidgin/gtkconv.h Sat Nov 14 03:15:43 2020 -0600
+++ b/pidgin/gtkconv.h Sat Nov 14 03:39:06 2020 -0600
@@ -298,15 +298,6 @@
PidginConvWindow *pidgin_conv_get_window(PidginConversation *gtkconv);
/**
- * pidgin_conv_get_tab_icon:
- * @conv: The conversation.
- * @small_icon: Whether to get the small icon.
- *
- * Returns: (transfer full): The tab icon.
- */
-GdkPixbuf *pidgin_conv_get_tab_icon(PurpleConversation *conv, gboolean small_icon);
-
-/**
* pidgin_conv_new:
* @conv: The conversation.
*
--- a/pidgin/libpidgin.c Sat Nov 14 03:15:43 2020 -0600
+++ b/pidgin/libpidgin.c Sat Nov 14 03:39:06 2020 -0600
@@ -168,7 +168,8 @@
gchar *path;
path = g_build_filename(PURPLE_DATADIR, "pidgin", "icons", NULL);
- gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), path);
+ gtk_icon_theme_prepend_search_path(gtk_icon_theme_get_default(), path);
+ gtk_icon_theme_add_resource_path(gtk_icon_theme_get_default(), path);
g_free(path);
pidgin_stock_init();
--- a/pidgin/meson.build Sat Nov 14 03:15:43 2020 -0600
+++ b/pidgin/meson.build Sat Nov 14 03:39:06 2020 -0600
@@ -57,6 +57,7 @@
'pidginplugininfo.c',
'pidginpluginsdialog.c',
'pidginpluginsmenu.c',
+ 'pidginpresenceicon.c',
'pidginprotocolchooser.c',
'pidginprotocolstore.c',
'pidginstyle.c',
@@ -127,6 +128,7 @@
'pidginplugininfo.h',
'pidginpluginsdialog.h',
'pidginpluginsmenu.h',
+ 'pidginpresenceicon.h',
'pidginprotocolchooser.h',
'pidginprotocolstore.h',
'pidginstyle.h',
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidginpresenceicon.c Sat Nov 14 03:39:06 2020 -0600
@@ -0,0 +1,321 @@
+/*
+ * Pidgin - Internet Messenger
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "pidgin/pidginpresenceicon.h"
+
+struct _PidginPresenceIcon {
+ GtkImage parent;
+
+ PurplePresence *presence;
+ gchar *fallback;
+ GtkIconSize icon_size;
+};
+
+enum {
+ PROP_0,
+ PROP_PRESENCE,
+ PROP_FALLBACK,
+ PROP_ICON_SIZE,
+ N_PROPERTIES
+};
+static GParamSpec *properties[N_PROPERTIES] = { NULL, };
+
+G_DEFINE_TYPE(PidginPresenceIcon, pidgin_presence_icon, GTK_TYPE_IMAGE)
+
+/******************************************************************************
+ * Implementation
+ *****************************************************************************/
+static void
+pidgin_presence_icon_update(PidginPresenceIcon *icon) {
+ const gchar *icon_name = icon->fallback;
+
+ /* Sorry for the pyramid of doom, couldn't come up with a better way to do
+ * this...
+ */
+ if(PURPLE_IS_PRESENCE(icon->presence)) {
+ PurpleStatus *status = NULL;
+
+ status = purple_presence_get_active_status(icon->presence);
+ if(PURPLE_IS_STATUS(status)) {
+ PurpleStatusType *type = purple_status_get_status_type(status);
+
+ if(type != NULL) {
+ switch(purple_status_type_get_primitive(type)) {
+ case PURPLE_STATUS_OFFLINE:
+ icon_name = "pidgin-user-offline";
+ break;
+ case PURPLE_STATUS_AVAILABLE:
+ icon_name = "pidgin-user-available";
+ break;
+ case PURPLE_STATUS_UNAVAILABLE:
+ icon_name = "pidgin-user-unavailable";
+ break;
+ case PURPLE_STATUS_AWAY:
+ icon_name = "pidgin-user-away";
+ break;
+ case PURPLE_STATUS_INVISIBLE:
+ icon_name = "pidgin-user-invisible";
+ break;
+ case PURPLE_STATUS_EXTENDED_AWAY:
+ icon_name = "pidgin-user-extended-away";
+ break;
+ case PURPLE_STATUS_MOBILE:
+ case PURPLE_STATUS_TUNE:
+ case PURPLE_STATUS_MOOD:
+ case PURPLE_STATUS_UNSET:
+ default:
+ /* Use the fallback which was set above. */
+ break;
+ }
+ }
+ }
+ }
+
+ gtk_image_set_from_icon_name(GTK_IMAGE(icon), icon_name, icon->icon_size);
+}
+
+static void
+pidgin_presence_icon_active_status_changed_cb(GObject *obj, GParamSpec *pspec,
+ gpointer data)
+{
+ pidgin_presence_icon_update(PIDGIN_PRESENCE_ICON(data));
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+pidgin_presence_icon_get_property(GObject *obj, guint param_id, GValue *value,
+ GParamSpec *pspec)
+{
+ PidginPresenceIcon *icon = PIDGIN_PRESENCE_ICON(obj);
+
+ switch(param_id) {
+ case PROP_PRESENCE:
+ g_value_set_object(value, pidgin_presence_icon_get_presence(icon));
+ break;
+ case PROP_FALLBACK:
+ g_value_set_string(value, pidgin_presence_icon_get_fallback(icon));
+ break;
+ case PROP_ICON_SIZE:
+ g_value_set_enum(value, pidgin_presence_icon_get_icon_size(icon));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+ break;
+ }
+}
+
+static void
+pidgin_presence_icon_set_property(GObject *obj, guint param_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ PidginPresenceIcon *icon = PIDGIN_PRESENCE_ICON(obj);
+
+ switch(param_id) {
+ case PROP_PRESENCE:
+ pidgin_presence_icon_set_presence(icon, g_value_get_object(value));
+ break;
+ case PROP_FALLBACK:
+ pidgin_presence_icon_set_fallback(icon, g_value_get_string(value));
+ break;
+ case PROP_ICON_SIZE:
+ pidgin_presence_icon_set_icon_size(icon, g_value_get_enum(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+ break;
+ }
+}
+
+static void
+pidgin_presence_icon_finalize(GObject *obj) {
+ PidginPresenceIcon *icon = PIDGIN_PRESENCE_ICON(obj);
+
+ g_clear_object(&icon->presence);
+ g_clear_pointer(&icon->fallback, g_free);
+
+ G_OBJECT_CLASS(pidgin_presence_icon_parent_class)->finalize(obj);
+}
+
+static void
+pidgin_presence_icon_init(PidginPresenceIcon *presenceicon) {
+ /* Use the icon name fallback that we're expecting. */
+ g_object_set(G_OBJECT(presenceicon), "use-fallback", TRUE, NULL);
+}
+
+static void
+pidgin_presence_icon_class_init(PidginPresenceIconClass *klass) {
+ GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+ obj_class->finalize = pidgin_presence_icon_finalize;
+ obj_class->get_property = pidgin_presence_icon_get_property;
+ obj_class->set_property = pidgin_presence_icon_set_property;
+
+ /**
+ * PidginPresenceIcon::presence:
+ *
+ * The #PurplePresence that this icon will be representing.
+ */
+ properties[PROP_PRESENCE] = g_param_spec_object(
+ "presence", "presence",
+ "The presence that this icon is representing",
+ PURPLE_TYPE_PRESENCE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * PidginPresenceIcon::fallback:
+ *
+ * The name of the icon to use as a fallback when no presence is set.
+ */
+ properties[PROP_FALLBACK] = g_param_spec_string(
+ "fallback", "fallback",
+ "The name of the icon to use as a fallback",
+ "user-invisible",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * PidginPresenceIcon::icon-size:
+ *
+ * The #GtkIconSize that should be used.
+ */
+ properties[PROP_ICON_SIZE] = g_param_spec_enum(
+ "icon-size", "icon-size",
+ "The GtkIconSize to use",
+ GTK_TYPE_ICON_SIZE,
+ GTK_ICON_SIZE_MENU,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+GtkWidget *
+pidgin_presence_icon_new(PurplePresence *presence, const gchar *fallback,
+ GtkIconSize icon_size)
+{
+ return g_object_new(
+ PIDGIN_TYPE_PRESENCE_ICON,
+ "presence", presence,
+ "fallback", fallback,
+ "icon_size", icon_size,
+ NULL);
+}
+
+PurplePresence *
+pidgin_presence_icon_get_presence(PidginPresenceIcon *icon) {
+ g_return_val_if_fail(PIDGIN_IS_PRESENCE_ICON(icon), NULL);
+
+ return icon->presence;
+}
+
+void
+pidgin_presence_icon_set_presence(PidginPresenceIcon *icon,
+ PurplePresence *presence)
+{
+ PurplePresence *old = NULL;
+
+ g_return_if_fail(PIDGIN_IS_PRESENCE_ICON(icon));
+
+ /* We only want to disconnect signal handlers if the presence was changed,
+ * but to do that, we need to keep a reference to the old presence.
+ */
+ if(PURPLE_IS_PRESENCE(icon->presence)) {
+ old = g_object_ref(G_OBJECT(icon->presence));
+ }
+
+ if(g_set_object(&icon->presence, presence)) {
+ if(G_IS_OBJECT(old)) {
+ /* If we previously had a presence, disconnect our signal handlers.
+ */
+ g_signal_handlers_disconnect_by_data(old, icon);
+ }
+
+ if(PURPLE_IS_PRESENCE(icon->presence)) {
+ g_signal_connect(icon->presence, "notify::active-status",
+ G_CALLBACK(pidgin_presence_icon_active_status_changed_cb),
+ icon);
+ }
+
+ g_object_freeze_notify(G_OBJECT(icon));
+
+ pidgin_presence_icon_update(icon);
+
+ g_object_notify_by_pspec(G_OBJECT(icon), properties[PROP_PRESENCE]);
+
+ g_object_thaw_notify(G_OBJECT(icon));
+ }
+
+ g_clear_object(&old);
+}
+
+const gchar *
+pidgin_presence_icon_get_fallback(PidginPresenceIcon *icon) {
+ g_return_val_if_fail(PIDGIN_IS_PRESENCE_ICON(icon), NULL);
+
+ return icon->fallback;
+}
+
+void
+pidgin_presence_icon_set_fallback(PidginPresenceIcon *icon,
+ const gchar *fallback)
+{
+ g_return_if_fail(PIDGIN_IS_PRESENCE_ICON(icon));
+ g_return_if_fail(fallback != NULL);
+
+ g_free(icon->fallback);
+ icon->fallback = g_strdup(fallback);
+
+ g_object_freeze_notify(G_OBJECT(icon));
+
+ pidgin_presence_icon_update(icon);
+
+ g_object_notify_by_pspec(G_OBJECT(icon), properties[PROP_FALLBACK]);
+
+ g_object_thaw_notify(G_OBJECT(icon));
+}
+
+GtkIconSize
+pidgin_presence_icon_get_icon_size(PidginPresenceIcon *icon) {
+ g_return_val_if_fail(PIDGIN_IS_PRESENCE_ICON(icon), GTK_ICON_SIZE_INVALID);
+
+ return icon->icon_size;
+}
+
+void
+pidgin_presence_icon_set_icon_size(PidginPresenceIcon *icon,
+ GtkIconSize icon_size)
+{
+ g_return_if_fail(PIDGIN_IS_PRESENCE_ICON(icon));
+
+ icon->icon_size = icon_size;
+
+ g_object_freeze_notify(G_OBJECT(icon));
+
+ pidgin_presence_icon_update(icon);
+
+ g_object_notify_by_pspec(G_OBJECT(icon), properties[PROP_ICON_SIZE]);
+
+ g_object_thaw_notify(G_OBJECT(icon));
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidginpresenceicon.h Sat Nov 14 03:39:06 2020 -0600
@@ -0,0 +1,135 @@
+/*
+ * Pidgin - Internet Messenger
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PIDGIN_GLOBAL_HEADER_INSIDE) && !defined(PIDGIN_COMPILATION)
+# error "only <pidgin.h> may be included directly"
+#endif
+
+#ifndef PIDGIN_PRESENCE_ICON_H
+#define PIDGIN_PRESENCE_ICON_H
+
+/**
+ * SECTION:pidginpresenceicon
+ * @section_id: pidgin-pidginpresenceicon
+ * @short_description: Icons for presences
+ * @title: Presence Icons
+ *
+ * A #GtkImage subclass that will automatically update when the given presence
+ * changes.
+ */
+
+#include <gtk/gtk.h>
+
+#include <purple.h>
+
+#define PIDGIN_TYPE_PRESENCE_ICON (pidgin_presence_icon_get_type())
+G_DECLARE_FINAL_TYPE(PidginPresenceIcon, pidgin_presence_icon,
+ PIDGIN, PRESENCE_ICON, GtkImage)
+
+G_BEGIN_DECLS
+
+/**
+ * pidgin_presence_icon_new:
+ * @presence: (nullable): The #PurplePresence to represent.
+ * @fallback: The name of the icon to use as a fallback.
+ * @icon_size: The #GtkIconSize used to render the icon.
+ *
+ * Creates a new #PidginPresenceIcon.
+ *
+ * Returns: (transfer full): The new presence icon.
+ *
+ * Since: 3.0.0
+ */
+GtkWidget *pidgin_presence_icon_new(PurplePresence *presence, const gchar *fallback, GtkIconSize icon_size);
+
+/**
+ * pidgin_presence_icon_get_presence:
+ * @icon: The #PidginPresenceIcon instance.
+ *
+ * Gets the #PurplePresence that @icon is displaying.
+ *
+ * Returns: (transfer none): The #PurplePresence if set otherwise %NULL.
+ *
+ * Since: 3.0.0
+ */
+PurplePresence *pidgin_presence_icon_get_presence(PidginPresenceIcon *icon);
+
+/**
+ * pidgin_presence_icon_set_presence:
+ * @icon: The #PidginPresenceIcon instance.
+ * @presence: (nullable): The new #PurplePresence to set, or %NULL to unset.
+ *
+ * Sets the #PurplePresence for @icon to display.
+ *
+ * Since: 3.0.0
+ */
+void pidgin_presence_icon_set_presence(PidginPresenceIcon *icon, PurplePresence *presence);
+
+/**
+ * pidgin_presence_icon_get_fallback:
+ * @icon: The #PidginPresenceIcon instance.
+ *
+ * Gets the name of the fallback icon from @icon.
+ *
+ * Returns: The fallback icon name for @icon.
+ *
+ * Since: 3.0.0
+ */
+const gchar *pidgin_presence_icon_get_fallback(PidginPresenceIcon *icon);
+
+/**
+ * pidgin_presence_icon_set_fallback:
+ * @icon: The #PidginPresenceIcon instance.
+ * @fallback: The name of the fallback icon name.
+ *
+ * Sets the fallback icon name for @icon to @fallback.
+ *
+ * Since: 3.0.0
+ */
+void pidgin_presence_icon_set_fallback(PidginPresenceIcon *icon, const gchar *fallback);
+
+/**
+ * pidgin_presence_icon_get_icon_size:
+ * @icon: The #PidginPresenceIcon instance.
+ *
+ * Gets the #GtkIconSize that @icon is using to render the icon.
+ *
+ * Returns: The #GtkIconSize that @icon is using to render.
+ *
+ * Since: 3.0.0
+ */
+GtkIconSize pidgin_presence_icon_get_icon_size(PidginPresenceIcon *icon);
+
+/**
+ * pidgin_presence_icon_set_icon_size:
+ * @icon: The #PidginPresenceIcon instance.
+ * @icon_size: The #GtkIconSize to render the icon at.
+ *
+ * Sets the #GtkIconSize that @icon will use to render the icon.
+ *
+ * Since: 3.0.0
+ */
+void pidgin_presence_icon_set_icon_size(PidginPresenceIcon *icon, GtkIconSize icon_size);
+
+G_END_DECLS
+
+#endif /* PIDGIN_PRESENCE_ICON_H */
--- a/po/POTFILES.in Sat Nov 14 03:15:43 2020 -0600
+++ b/po/POTFILES.in Sat Nov 14 03:39:06 2020 -0600
@@ -352,6 +352,7 @@
pidgin/pidginlog.c
pidgin/pidginmenutray.c
pidgin/pidginmessage.c
+pidgin/pidginpresenceicon.c
pidgin/pidginstock.c
pidgin/pidginstyle.c
pidgin/pidgintalkatu.c