# HG changeset patch
# Node ID b4cb09c0466bae2a70ecf5558cf5eda4a11cb0c4
# Parent  71964dd67641badfbc6c54b4c0cb44b29ce193fc
diff --git a/pidgin/pidginconversation.c b/pidgin/pidginconversation.c
--- a/pidgin/pidginconversation.c
+++ b/pidgin/pidginconversation.c
@@ -33,6 +33,7 @@
 #include "pidginconversation.h"
 #include "pidgininfopane.h"
 #include "pidginmessage.h"
+#include "pidginnotifiable.h"
 
 #define PIDGIN_CONVERSATION_DATA ("pidgin-conversation")
 
@@ -40,6 +41,9 @@
 	PROP_0,
 	PROP_CONVERSATION,
 	N_PROPERTIES,
+	/* Overrides */
+	PROP_NEEDS_ATTENTION = N_PROPERTIES,
+	PROP_NOTIFICATION_COUNT,
 };
 static GParamSpec *properties[N_PROPERTIES] = {NULL, };
 
@@ -61,21 +65,64 @@
 
 	GtkWidget *typing_label;
 	GtkWidget *status_label;
+
+	/* This is a temporary work around to get new message notifications working
+	 * until we implement Purple.History properly.
+	 * -- gk 2025-03-11
+	 */
+	guint notification_count;
 };
 
-G_DEFINE_FINAL_TYPE(PidginConversation, pidgin_conversation, GTK_TYPE_BOX)
+static void
+pidgin_conversation_messages_changed_cb(GListModel *model, guint position,
+                                        guint removed, guint added,
+                                        gpointer data);
 
 /******************************************************************************
  * Helpers
  *****************************************************************************/
 static void
+pidgin_conversation_set_notification_count(PidginConversation *conversation,
+                                           guint notification_count)
+{
+	if(conversation->notification_count != notification_count) {
+		GObject *obj = G_OBJECT(conversation);
+
+		conversation->notification_count = notification_count;
+
+		g_object_freeze_notify(obj);
+		g_object_notify(obj, "needs-attention");
+		g_object_notify(obj, "notification-count");
+		g_object_thaw_notify(obj);
+	}
+}
+
+static void
 pidgin_conversation_set_conversation(PidginConversation *conversation,
                                      PurpleConversation *purple_conversation)
 {
+	/* Disconnect our old signal. */
+	if(PURPLE_IS_CONVERSATION(conversation->conversation)) {
+		GListModel *model = NULL;
+
+		model = purple_conversation_get_messages(conversation->conversation);
+		g_signal_handlers_disconnect_by_func(model,
+		                                     G_CALLBACK(pidgin_conversation_messages_changed_cb),
+		                                     conversation);
+	}
+
 	if(g_set_object(&conversation->conversation, purple_conversation)) {
 		if(PURPLE_IS_CONVERSATION(purple_conversation)) {
+			GListModel *model = NULL;
+
 			g_object_set_data(G_OBJECT(purple_conversation),
 			                  PIDGIN_CONVERSATION_DATA, conversation);
+
+			model = purple_conversation_get_messages(conversation->conversation);
+
+			g_signal_connect_object(model, "items-changed",
+			                        G_CALLBACK(pidgin_conversation_messages_changed_cb),
+			                        conversation, G_CONNECT_DEFAULT);
 		}
 
 		g_object_notify_by_pspec(G_OBJECT(conversation),
@@ -172,9 +219,39 @@
 	gtk_text_buffer_set_text(buffer, "", -1);
 }
 
+static gboolean
+pidgin_conversation_get_needs_attention(PidginConversation *conversation)
+{
+	return conversation->notification_count > 0;
+}
+
+static guint
+pidgin_conversation_get_notification_count(PidginConversation *conversation) {
+	return conversation->notification_count;
+}
+
 /******************************************************************************
  * Callbacks
  *****************************************************************************/
+static void
+pidgin_conversation_messages_changed_cb(G_GNUC_UNUSED GListModel *model,
+                                        G_GNUC_UNUSED guint position,
+                                        guint removed,
+                                        guint added,
+                                        gpointer data)
+{
+	PidginConversation *conversation = data;
+	guint current = conversation->notification_count;
+
+	if(removed > 0 && current - removed < current) {
+		current -= removed;
+	}
+
+	current += added;
+
+	pidgin_conversation_set_notification_count(conversation, current);
+}
+
 static char *
 pidgin_conversation_get_status_label(G_GNUC_UNUSED PidginConversation *conversation,
                                      GError *error, gboolean online)
@@ -358,9 +435,29 @@
 	pidgin_message_set_message(PIDGIN_MESSAGE(message), NULL);
 }
 
+static void
+pidgin_conversation_map_cb(GtkWidget *self, G_GNUC_UNUSED gpointer data) {
+	pidgin_conversation_set_notification_count(PIDGIN_CONVERSATION(self), 0);
+}
+
+/******************************************************************************
+ * PidginNotifiable Implementation
+ *****************************************************************************/
+static void
+pidgin_conversation_notifiable_init(G_GNUC_UNUSED PidginNotifiableInterface *iface)
+{
+}
+
 /******************************************************************************
  * GObject Implementation
  *****************************************************************************/
+G_DEFINE_FINAL_TYPE_WITH_CODE(PidginConversation,
+                              pidgin_conversation,
+                              GTK_TYPE_BOX,
+                              G_IMPLEMENT_INTERFACE(PIDGIN_TYPE_NOTIFIABLE,
+                                                    pidgin_conversation_notifiable_init))
+
+
 static void
 pidgin_conversation_dispose(GObject *obj) {
 	PidginConversation *conversation = PIDGIN_CONVERSATION(obj);
@@ -383,6 +480,14 @@
 		g_value_set_object(value,
 		                   pidgin_conversation_get_conversation(conversation));
 		break;
+	case PROP_NEEDS_ATTENTION:
+		g_value_set_boolean(value,
+		                    pidgin_conversation_get_needs_attention(conversation));
+		break;
+	case PROP_NOTIFICATION_COUNT:
+		g_value_set_uint(value,
+		                 pidgin_conversation_get_notification_count(conversation));
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
 		break;
@@ -435,6 +540,10 @@
 	spelling_text_buffer_adapter_set_enabled(adapter, TRUE);
 
 	g_clear_object(&adapter);
+
+	/* Connect to our show signal to reset the notification-count property. */
+	g_signal_connect(conversation, "map",
+	                 G_CALLBACK(pidgin_conversation_map_cb), NULL);
 }
 
 static void
@@ -460,6 +569,11 @@
 
 	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
 
+	g_object_class_override_property(obj_class, PROP_NEEDS_ATTENTION,
+	                                 "needs-attention");
+	g_object_class_override_property(obj_class, PROP_NOTIFICATION_COUNT,
+	                                 "notification-count");
+
 	/* Template stuff. */
 	gtk_widget_class_set_template_from_resource(
 	    widget_class,
diff --git a/pidgin/pidgindisplayitem.c b/pidgin/pidgindisplayitem.c
--- a/pidgin/pidgindisplayitem.c
+++ b/pidgin/pidgindisplayitem.c
@@ -22,6 +22,8 @@
 
 #include "pidgindisplayitem.h"
 
+#include "pidginnotifiable.h"
+
 struct _PidginDisplayItem {
 	GObject parent;
 
@@ -64,6 +66,16 @@
 	g_return_if_fail(PIDGIN_IS_DISPLAY_ITEM(item));
 
 	if(g_set_object(&item->widget, widget)) {
+		if(PIDGIN_IS_NOTIFIABLE(item->widget)) {
+			g_object_bind_property(item->widget, "needs-attention",
+			                       item, "needs-attention",
+			                       G_BINDING_SYNC_CREATE);
+
+			g_object_bind_property(item->widget, "notification-count",
+			                       item, "badge-number",
+			                       G_BINDING_SYNC_CREATE);
+		}
+
 		g_object_notify_by_pspec(G_OBJECT(item), properties[PROP_WIDGET]);
 	}
 }
