]> www.infradead.org Git - pidgin-chime.git/commitdiff
Add plugin to show when messages are seen
authorDavid Woodhouse <dwmw@amazon.co.uk>
Fri, 27 Apr 2018 12:31:17 +0000 (13:31 +0100)
committerDavid Woodhouse <dwmw@amazon.co.uk>
Fri, 27 Apr 2018 12:31:19 +0000 (13:31 +0100)
We could actually do delivered vs. seen if we wanted to but this
is a good start.

Makefile.am
chime/chime-conversation.c
chimeseen/Makefile.am [new file with mode: 0644]
chimeseen/chimeseen.c [new file with mode: 0644]
configure.ac
debian/pidgin-chime.install
prpl/chime.c
prpl/conversations.c

index 2bcc5e2d3fc45f67e1ff435ebafd4a7f3c8bc18a..1ae7d55184803faad8ce7bc5f4180ab7968b9ffa 100644 (file)
@@ -1,5 +1,5 @@
 
-SUBDIRS = po pixmaps fs-app-transmitter gst-chime
+SUBDIRS = po pixmaps fs-app-transmitter gst-chime chimeseen
 
 if BUILD_EVOPLUGIN
 SUBDIRS += evolution-plugin
@@ -7,7 +7,7 @@ endif
 
 AM_CPPFLAGS = @WFLAGS@
 
-plugin_LTLIBRARIES = libchimeprpl.la
+purple_plugin_LTLIBRARIES = libchimeprpl.la
 
 PROTOBUF_SRCS = protobuf/auth_message.pb-c.c protobuf/auth_message.pb-c.h \
                protobuf/data_message.pb-c.c protobuf/data_message.pb-c.h \
index 6bd1b59cc9f9d70b0be3a49a6a821220261f46c0..e082816e5aa5128a2a193a57c135fb4966d61132 100644 (file)
@@ -52,6 +52,7 @@ static GParamSpec *props[LAST_PROP];
 enum {
        TYPING,
        MESSAGE,
+       MEMBERSHIP,
        LAST_SIGNAL
 };
 
@@ -200,6 +201,11 @@ static void chime_conversation_class_init(ChimeConversationClass *klass)
                g_signal_new ("message",
                              G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST,
                              0, NULL, NULL, NULL, G_TYPE_NONE, 1, JSON_TYPE_NODE);
+
+       signals[MEMBERSHIP] =
+               g_signal_new ("membership",
+                             G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST,
+                             0, NULL, NULL, NULL, G_TYPE_NONE, 1, JSON_TYPE_NODE);
 }
 
 static void unref_member(gpointer obj)
@@ -311,6 +317,8 @@ static gboolean conv_membership_jugg_cb(ChimeConnection *cxn, gpointer _conv, Js
        if (!member_node)
                return FALSE;
 
+       g_signal_emit(conv, signals[MEMBERSHIP], 0, member_node);
+
        ChimeContact *member = chime_connection_parse_conversation_contact(cxn, member_node, NULL);
        if (member) {
                const gchar *id = chime_contact_get_profile_id(member);
diff --git a/chimeseen/Makefile.am b/chimeseen/Makefile.am
new file mode 100644 (file)
index 0000000..dee2abf
--- /dev/null
@@ -0,0 +1,12 @@
+# @PIDGIN_PLUGIN_RULE@
+
+pidgin_plugin_LTLIBRARIES = chimeseen.la
+
+chimeseen_la_CFLAGS = $(PIDGIN_CFLAGS) $(JSON_CFLAGS)
+
+chimeseen_la_SOURCES = chimeseen.c
+
+chimeseen_la_LDFLAGS = -module -avoid-version -no-undefined
+
+chimeseen_la_LIBADD = $(PIDGIN_LIBS) $(JSON_LIBS)
+
diff --git a/chimeseen/chimeseen.c b/chimeseen/chimeseen.c
new file mode 100644 (file)
index 0000000..1c5df50
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * Pidgin/libpurple Chime client plugin
+ *
+ * Copyright © 2018 Amazon.com, Inc. or its affiliates.
+ *
+ * Author: David Woodhouse <dwmw2@infradead.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ */
+
+#define PURPLE_PLUGINS
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n.h>
+#include <debug.h>
+#include <version.h>
+#include <request.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "gtkutils.h"
+#include "gtkimhtml.h"
+
+#include <json-glib/json-glib.h>
+
+struct conv_data {
+       GList *l;
+};
+
+struct msg_mark {
+       GTimeVal created;
+       GtkTextMark *mark;
+};
+
+static gboolean parse_string(JsonNode *parent, const gchar *name, const gchar **res)
+{
+        JsonObject *obj;
+        JsonNode *node;
+        const gchar *str;
+
+        if (!parent)
+                return FALSE;
+
+        obj = json_node_get_object(parent);
+        if (!obj)
+                return FALSE;
+
+        node = json_object_get_member(obj, name);
+        if (!node)
+                return FALSE;
+
+        str = json_node_get_string(node);
+        if (!str)
+                return FALSE;
+
+        *res = str;
+
+        return TRUE;
+}
+
+
+static void
+conv_seen_cb(PurpleConversation *conv, JsonNode *member)
+{
+       const gchar *lastread;
+       GTimeVal tv;
+       if (!parse_string(member, "LastRead", &lastread) ||
+           !g_time_val_from_iso8601(lastread, &tv))
+               return;
+
+       struct conv_data *cd = purple_conversation_get_data(conv, "chime-seen");
+       if (!cd)
+               return;
+
+       while (cd->l) {
+               struct msg_mark *m = cd->l->data;
+
+               if (tv.tv_sec < m->created.tv_sec ||
+                   (tv.tv_sec == m->created.tv_sec &&
+                    tv.tv_usec < m->created.tv_usec)) {
+                       break;
+               }
+
+               cd->l = g_list_remove(cd->l, m);
+
+               GtkIMHtml *imhtml = GTK_IMHTML(PIDGIN_CONVERSATION(conv)->imhtml);
+               GtkTextIter iter;
+               gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer,
+                                                &iter, m->mark);
+               gtk_text_buffer_insert(imhtml->text_buffer,
+                                      &iter, "  ✓", -1);
+               gtk_text_buffer_delete_mark(imhtml->text_buffer, m->mark);
+               g_free(m);
+       }
+}
+
+static void
+got_convmsg_cb(PurpleConversation *conv, gboolean outbound, JsonNode *msgnode)
+{
+       const gchar *created_on, *msgid;
+
+       if (!outbound || !parse_string(msgnode, "MessageId", &msgid) ||
+           !parse_string(msgnode, "CreatedOn", &created_on))
+               return;
+
+       struct msg_mark *m = g_new0(struct msg_mark, 1);
+
+       if (!g_time_val_from_iso8601(created_on, &m->created)) {
+               g_free(m);
+               return;
+       }
+
+       GtkIMHtml *imhtml = GTK_IMHTML(PIDGIN_CONVERSATION(conv)->imhtml);
+       GtkTextIter end;
+
+       gtk_text_buffer_get_end_iter(imhtml->text_buffer, &end);
+
+       m->mark = gtk_text_buffer_create_mark(imhtml->text_buffer,
+                                             msgid, &end, TRUE);
+
+       struct conv_data *cd = purple_conversation_get_data(conv, "chime-seen");
+       if (!cd) {
+               cd = g_new0(struct conv_data, 1);
+               purple_conversation_set_data(conv, "chime-seen", cd);
+       };
+       cd->l = g_list_append(cd->l, m);
+}
+
+static void
+deleting_conv_cb(PurpleConversation *conv)
+{
+       struct conv_data *cd = purple_conversation_get_data(conv, "chime-seen");
+       if (!cd)
+               return;
+
+       /* The marks themselves will die with the GtkTextBuffer */
+       g_list_free_full(cd->l, g_free);
+       g_free(cd);
+       purple_conversation_set_data(conv, "chime-seen", NULL);
+}
+
+
+static gboolean
+chimeseen_plugin_load(PurplePlugin *plugin)
+{
+       PurplePlugin *chimeprpl = purple_find_prpl("prpl-chime");
+       if (!chimeprpl)
+               return FALSE;
+
+       purple_signal_connect(chimeprpl, "chime-got-convmsg", plugin,
+                             PURPLE_CALLBACK(got_convmsg_cb), NULL);
+       purple_signal_connect(chimeprpl, "chime-conv-membership", plugin,
+                             PURPLE_CALLBACK(conv_seen_cb), NULL);
+       purple_signal_connect(pidgin_conversations_get_handle(),
+                             "deleting-conversation", plugin,
+                             PURPLE_CALLBACK(deleting_conv_cb), NULL);
+       return TRUE;
+}
+
+static gboolean
+chimeseen_plugin_unload(PurplePlugin *plugin)
+{
+       purple_signals_disconnect_by_handle(plugin);
+
+       GList *ims;
+       for (ims = purple_get_ims(); ims; ims = ims->next)
+               deleting_conv_cb(ims->data);
+
+       return TRUE;
+}
+
+static void
+chimeseen_plugin_destroy(PurplePlugin *plugin)
+{
+}
+
+
+static PurplePluginInfo info =
+{
+       .magic = PURPLE_PLUGIN_MAGIC,
+       .major_version = PURPLE_MAJOR_VERSION,
+       .minor_version = PURPLE_MINOR_VERSION,
+       .type = PURPLE_PLUGIN_STANDARD,
+       .priority = PURPLE_PRIORITY_DEFAULT,
+       .id = (char *)"chimeseen",
+       .name = (char *)"Chime Seen",
+       .version = PACKAGE_VERSION,
+       .summary = (char *)"Chime seen message handling",
+       .description = (char *)"Displays when messages are seen",
+       .author = (char *)"David Woodhouse <dwmw2@infradead.org>",
+       .load = chimeseen_plugin_load,
+       .unload = chimeseen_plugin_unload,
+       .destroy = chimeseen_plugin_destroy,
+};
+
+static void
+init_plugin(PurplePlugin *plugin)
+{
+       info.dependencies = g_list_append(NULL, "prpl-chime");
+}
+
+PURPLE_INIT_PLUGIN(chimeseen_plugin, init_plugin, info)
index 69c06a109684e23eca8fb9ab925c2730cdf7beca..4ed3794b08be86ddd649f80442f19ffef8cc5082 100644 (file)
@@ -31,21 +31,25 @@ AS_COMPILER_FLAGS(WFLAGS,
         -Wno-declaration-after-statement")
 AC_SUBST(WFLAGS, [$WFLAGS])
 
-plugindir=
+purple_plugindir=
 PKG_CHECK_MODULES(PURPLE, [purple >= 2.4.0],
                          [purple_pkg=purple],
                          [PKG_CHECK_MODULES(PURPLE, purple-3,
                                            [AC_MSG_ERROR([Pidgin 3.0 is not yet released. Do not use it. It has nothing you need for Lync support that I didn't already backport to Pidgin 2.11.])])])
 
 
-plugindir="$($PKG_CONFIG --variable=plugindir $purple_pkg)"
-purple_datadir="$($PKG_CONFIG --variable=datadir $purple_pkg)"
+purple_plugindir="$($PKG_CONFIG --variable=plugindir $purple_pkg)"
+AC_SUBST(purple_plugindir, $purple_plugindir)
 
-AC_SUBST(plugindir, $plugindir)
+PKG_CHECK_MODULES(PIDGIN, [pidgin], [pidgin_pkg=pidgin])
+
+pidgin_datadir="$($PKG_CONFIG --variable=datadir $pidgin_pkg)"
+pidgin_plugindir="$($PKG_CONFIG --variable=plugindir $pidgin_pkg)"
 if test "$purple_datadir" = "" ; then
    purple_datadir="$datadir"
 fi
-AC_SUBST(pixmapdir, ${purple_datadir}/pixmaps/pidgin/protocols)
+AC_SUBST(pixmapdir, ${pidgin_datadir}/pixmaps/pidgin/protocols)
+AC_SUBST(pidgin_plugindir, $pidgin_plugindir)
 
 PKG_CHECK_MODULES(GNUTLS, [gnutls >= 3.2.0])
 PKG_CHECK_MODULES(FARSTREAM, [farstream-0.2])
@@ -123,5 +127,6 @@ AC_CONFIG_FILES([
        evolution-plugin/Makefile
        fs-app-transmitter/Makefile
        gst-chime/Makefile
+       chimeseen/Makefile
        ])
 AC_OUTPUT
index 312c50880bcd9f29d1b86c1e245a446684a3841b..bff6aecb0582f037a450541749b0a4afe20485d5 100644 (file)
@@ -3,4 +3,4 @@ usr/share/* usr/share
 usr/lib/x86_64-linux-gnu/farstream-0.2/* usr/lib/x86_64-linux-gnu/farstream-0.2
 usr/lib/x86_64-linux-gnu/gstreamer-1.0/*.so usr/lib/x86_64-linux-gnu/gstreamer-1.0
 usr/lib/evolution/modules/* usr/lib/evolution/modules
-
+usr/lib/pidgin/*.so usr/lib/pidgin
index 497f6ee9d2a7f0115fa79ccf7ac8df372b01ab8c..a790cede13e5da4bda260ca79853592731e32461 100644 (file)
 static gboolean chime_purple_plugin_load(PurplePlugin *plugin)
 {
        setvbuf(stdout, NULL, _IONBF, 0);
+
+       purple_signal_register(plugin, "chime-got-convmsg",
+                              purple_marshal_VOID__POINTER_POINTER_POINTER, NULL, 3,
+       /* conv */             purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONVERSATION),
+       /* outbound? */        purple_value_new(PURPLE_TYPE_BOOLEAN),
+       /* Message node */     purple_value_new(PURPLE_TYPE_POINTER));
+
+       purple_signal_register(plugin, "chime-conv-membership",
+                              purple_marshal_VOID__POINTER_POINTER, NULL, 2,
+       /* conv */             purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONVERSATION),
+       /* Member node */      purple_value_new(PURPLE_TYPE_POINTER));
+
        return TRUE;
 }
 
 static gboolean chime_purple_plugin_unload(PurplePlugin *plugin)
 {
+       purple_signals_unregister_by_instance(plugin);
+
        return TRUE;
 }
 
index dcfc64ed9a756a1dda9b8a7c96a1b30a4fcd3e88..df1253e764ff20bd82f042cf297c4b09d4bf22bd 100644 (file)
@@ -86,6 +86,8 @@ static gboolean do_conv_deliver_msg(ChimeConnection *cxn, struct chime_im *im,
                        }
                }
                purple_conversation_write(pconv, NULL, escaped, flags | PURPLE_MESSAGE_SEND, msg_time);
+               purple_signal_emit(purple_connection_get_prpl(account->gc), "chime-got-convmsg",
+                                  pconv, TRUE, record);
        } else {
                serv_got_im(im->m.conn, email, escaped, flags | PURPLE_MESSAGE_RECV, msg_time);
 
@@ -94,13 +96,39 @@ static gboolean do_conv_deliver_msg(ChimeConnection *cxn, struct chime_im *im,
                   (still) zero and tell the server it's read. */
                PurpleConversation *pconv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
                                                                                  email, im->m.conn->account);
-               if (pconv)
+               if (pconv) {
                        purple_conversation_update(pconv, PURPLE_CONV_UPDATE_UNSEEN);
+                       purple_signal_emit(purple_connection_get_prpl(im->m.conn),
+                                          "chime-got-convmsg", pconv, FALSE, record);
+               }
+
        }
        g_free(escaped);
        return TRUE;
 }
 
+static void on_conv_membership(ChimeConversation *conv, JsonNode *member, struct chime_im *im)
+{
+
+       const gchar *profile_id;
+       if (!parse_string(member, "ProfileId", &profile_id))
+               return;
+
+       /* We only care about the peer, not our own status */
+       if (!strcmp(profile_id, chime_connection_get_profile_id(PURPLE_CHIME_CXN(im->m.conn))))
+               return;
+
+       PurpleConversation *pconv;
+       pconv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
+                                                     chime_contact_get_email(im->peer),
+                                                     im->m.conn->account);
+       if (!pconv)
+               return;
+
+       purple_signal_emit(purple_connection_get_prpl(im->m.conn),
+                          "chime-conv-membership", pconv, member);
+}
+
 static void on_conv_typing(ChimeConversation *conv, ChimeContact *contact, gboolean state, struct chime_im *im)
 {
        const gchar *email = chime_contact_get_email(contact);
@@ -165,6 +193,7 @@ void on_chime_new_conversation(ChimeConnection *cxn, ChimeConversation *conv, Pu
        g_hash_table_insert(pc->ims_by_profile_id, (void *)chime_contact_get_profile_id(im->peer), im);
 
        g_signal_connect(conv, "typing", G_CALLBACK(on_conv_typing), im);
+       g_signal_connect(conv, "membership", G_CALLBACK(on_conv_membership), im);
 
        purple_debug(PURPLE_DEBUG_INFO, "chime", "New conversation %s with %s\n", chime_object_get_id(CHIME_OBJECT(im->peer)),
                     chime_contact_get_email(im->peer));