pidgin/pidgin

Parents 86e0a26348a1
Children d3fe2b899c89
Add PurpleTypingState and add it as a property to PurpleConversationMember

This is a simple API to allow use to track typing state for contacts for any
conversation type. It also has built-in timeout support to reset a state back
to the PURPLE_TYPING_STATE_NONE if not other state changes have happened.

Testing Done:
Ran the unit tests and verified the docs.

Bugs closed: PIDGIN-17763

Reviewed at https://reviews.imfreedom.org/r/2288/
--- a/libpurple/meson.build Fri Mar 03 01:22:08 2023 -0600
+++ b/libpurple/meson.build Fri Mar 03 01:23:31 2023 -0600
@@ -194,6 +194,7 @@
'purplesqlite3.h',
'purplesqlitehistoryadapter.h',
'purpletags.h',
+ 'purpletyping.h',
'purpleui.h',
'purplewhiteboard.h',
'purplewhiteboardmanager.h',
@@ -271,6 +272,7 @@
'purplepresence.h',
'purpleprotocol.h',
'purpleproxyinfo.h',
+ 'purpletyping.h',
'roomlist.h',
'status.h',
'xfer.h',
--- a/libpurple/purpleconversationmember.c Fri Mar 03 01:22:08 2023 -0600
+++ b/libpurple/purpleconversationmember.c Fri Mar 03 01:23:31 2023 -0600
@@ -18,17 +18,23 @@
#include "purpleconversationmember.h"
+#include "purpleenums.h"
+
struct _PurpleConversationMember {
GObject parent;
PurpleContactInfo *contact_info;
PurpleTags *tags;
+
+ guint typing_timeout;
+ PurpleTypingState typing_state;
};
enum {
PROP_0,
PROP_CONTACT_INFO,
PROP_TAGS,
+ PROP_TYPING_STATE,
N_PROPERTIES
};
static GParamSpec *properties[N_PROPERTIES] = {NULL, };
@@ -53,6 +59,20 @@
}
/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static gboolean
+purple_conversation_member_reset_typing_state(gpointer data) {
+ PurpleConversationMember *member = data;
+
+ purple_conversation_member_set_typing_state(member,
+ PURPLE_TYPING_STATE_NONE,
+ 0);
+
+ return G_SOURCE_REMOVE;
+}
+
+/******************************************************************************
* GObject Implementation
*****************************************************************************/
static void
@@ -70,6 +90,10 @@
g_value_set_object(value,
purple_conversation_member_get_tags(member));
break;
+ case PROP_TYPING_STATE:
+ g_value_set_enum(value,
+ purple_conversation_member_get_typing_state(member));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
break;
@@ -87,6 +111,11 @@
purple_conversation_member_set_contact_info(member,
g_value_get_object(value));
break;
+ case PROP_TYPING_STATE:
+ purple_conversation_member_set_typing_state(member,
+ g_value_get_enum(value),
+ 0);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
break;
@@ -99,6 +128,11 @@
g_clear_object(&member->contact_info);
+ if(member->typing_timeout != 0) {
+ g_source_remove(member->typing_timeout);
+ member->typing_timeout = 0;
+ }
+
G_OBJECT_CLASS(purple_conversation_member_parent_class)->dispose(obj);
}
@@ -151,6 +185,20 @@
PURPLE_TYPE_TAGS,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ /**
+ * PurpleConversationMember:typing-state:
+ *
+ * The [enum@Purple.TypingState] for this member.
+ *
+ * Since: 3.0.0
+ */
+ properties[PROP_TYPING_STATE] = g_param_spec_enum(
+ "typing-state", "typing-state",
+ "The typing state for this member",
+ PURPLE_TYPE_TYPING_STATE,
+ PURPLE_TYPING_STATE_NONE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
}
@@ -180,3 +228,45 @@
return member->tags;
}
+
+PurpleTypingState
+purple_conversation_member_get_typing_state(PurpleConversationMember *member)
+{
+ g_return_val_if_fail(PURPLE_IS_CONVERSATION_MEMBER(member),
+ PURPLE_TYPING_STATE_NONE);
+
+ return member->typing_state;
+}
+
+void
+purple_conversation_member_set_typing_state(PurpleConversationMember *member,
+ PurpleTypingState state,
+ guint seconds)
+{
+ g_return_if_fail(PURPLE_IS_CONVERSATION_MEMBER(member));
+
+ /* Remove an existing timeout if necessary. */
+ if(member->typing_timeout != 0) {
+ g_source_remove(member->typing_timeout);
+ member->typing_timeout = 0;
+ }
+
+ /* If the state has changed, notify. */
+ if(state != member->typing_state) {
+ member->typing_state = state;
+
+ g_object_notify_by_pspec(G_OBJECT(member),
+ properties[PROP_TYPING_STATE]);
+ }
+
+ /* If we got a timeout, add it. */
+ if(seconds > 0) {
+ guint source = 0;
+
+ source = g_timeout_add_seconds(seconds,
+ purple_conversation_member_reset_typing_state,
+ member);
+
+ member->typing_timeout = source;
+ }
+}
--- a/libpurple/purpleconversationmember.h Fri Mar 03 01:22:08 2023 -0600
+++ b/libpurple/purpleconversationmember.h Fri Mar 03 01:23:31 2023 -0600
@@ -28,6 +28,7 @@
#include <libpurple/purplecontactinfo.h>
#include <libpurple/purpletags.h>
+#include <libpurple/purpletyping.h>
G_BEGIN_DECLS
@@ -89,6 +90,33 @@
*/
PurpleTags *purple_conversation_member_get_tags(PurpleConversationMember *conversation_member);
+/**
+ * purple_conversation_member_get_typing_state:
+ * @member: The instance.
+ *
+ * Gets the current [enum@Purple.TypingState] for @conversation_member.
+ *
+ * Returns: The current typing state for @conversation_member.
+ *
+ * Since: 3.0.0
+ */
+PurpleTypingState purple_conversation_member_get_typing_state(PurpleConversationMember *member);
+
+/**
+ * purple_conversation_member_set_typing_state:
+ * @member: The instance.
+ * @state: The new typing state.
+ * @seconds: The number of seconds before resetting the state.
+ *
+ * Sets the typing state of @conversation_member to @state.
+ *
+ * If @seconds is greater than %0, a timeout will be added for @seconds to
+ * reset the state to none.
+ *
+ * Since: 3.0.0
+ */
+void purple_conversation_member_set_typing_state(PurpleConversationMember *member, PurpleTypingState state, guint seconds);
+
G_END_DECLS
#endif /* PURPLE_CONVERSATION_MEMBER_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/purpletyping.h Fri Mar 03 01:23:31 2023 -0600
@@ -0,0 +1,44 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * 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(PURPLE_GLOBAL_HEADER_INSIDE) && !defined(PURPLE_COMPILATION)
+# error "only <pidgin.h> may be included directly"
+#endif
+
+#ifndef PURPLE_TYPING_H
+#define PURPLE_TYPING_H
+
+/**
+ * PurpleTypingState:
+ * @PURPLE_TYPING_STATE_NONE: The user is not currently typing and has nothing
+ * in their buffer.
+ * @PURPLE_TYPING_STATE_TYPING: The user is currently typing.
+ * @PURPLE_TYPING_STATE_PAUSED: The user has typed some text, but stopped
+ * without deleting it.
+ *
+ * Defines the state of a user composing a message.
+ *
+ * Since: 3.0.0
+ */
+typedef enum {
+ PURPLE_TYPING_STATE_NONE,
+ PURPLE_TYPING_STATE_TYPING,
+ PURPLE_TYPING_STATE_PAUSED,
+} PurpleTypingState;
+
+#endif /* PURPLE_TYPING_H */
--- a/libpurple/tests/test_conversation_member.c Fri Mar 03 01:22:08 2023 -0600
+++ b/libpurple/tests/test_conversation_member.c Fri Mar 03 01:23:31 2023 -0600
@@ -44,6 +44,7 @@
PurpleContactInfo *info1 = NULL;
PurpleConversationMember *member = NULL;
PurpleTags *tags = NULL;
+ PurpleTypingState typing_state = PURPLE_TYPING_STATE_NONE;
info = purple_contact_info_new("abc123");
@@ -54,17 +55,20 @@
member = g_object_new(
PURPLE_TYPE_CONVERSATION_MEMBER,
"contact-info", info,
+ "typing-state", PURPLE_TYPING_STATE_TYPING,
NULL);
/* Now use g_object_get to read all of the properties. */
g_object_get(member,
"contact-info", &info1,
"tags", &tags,
+ "typing-state", &typing_state,
NULL);
/* Compare all the things. */
g_assert_true(info1 == info);
g_assert_true(PURPLE_IS_TAGS(tags));
+ g_assert_cmpint(typing_state, ==, PURPLE_TYPING_STATE_TYPING);
/* Free/unref all the things. */
g_clear_object(&info1);
@@ -75,6 +79,79 @@
}
/******************************************************************************
+ * Typing State Timeout
+ *****************************************************************************/
+static void
+test_purple_conversation_manager_timeout_notify(G_GNUC_UNUSED GObject *obj,
+ G_GNUC_UNUSED GParamSpec *pspec,
+ gpointer data)
+{
+ GMainLoop *loop = data;
+ static guint count = 0;
+
+ /* Increment count each time we're called. We're expecting to be called
+ * twice, so after that quit the main loop.
+ */
+ count++;
+ if(count >= 2) {
+ g_main_loop_quit(loop);
+ }
+}
+
+static gboolean
+test_purple_conversation_manager_timeout_fail_safe(gpointer data) {
+ GMainLoop *loop = data;
+
+ g_warning("fail safe triggered");
+
+ g_main_loop_quit(loop);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+test_purple_conversation_member_typing_state_timeout(void) {
+ PurpleContactInfo *info = NULL;
+ PurpleConversationMember *member = NULL;
+ PurpleTypingState typing_state = PURPLE_TYPING_STATE_TYPING;
+ GMainLoop *loop = NULL;
+
+ /* Create the main loop as we'll need it to let the timeout fire. */
+ loop = g_main_loop_new(NULL, FALSE);
+
+ /* Create the member and add a notify callback on the typing-state property
+ * so we can check it and exit the main loop.
+ */
+ info = purple_contact_info_new(NULL);
+ member = purple_conversation_member_new(info);
+ g_signal_connect(member, "notify::typing-state",
+ G_CALLBACK(test_purple_conversation_manager_timeout_notify),
+ loop);
+
+ /* Set the state to typing with a timeout of 1 second. */
+ purple_conversation_member_set_typing_state(member,
+ PURPLE_TYPING_STATE_TYPING, 1);
+
+ /* Add a fail safe timeout at 2 seconds to make sure the test won't get
+ * stuck waiting forever.
+ */
+ g_timeout_add_seconds(2,
+ test_purple_conversation_manager_timeout_fail_safe,
+ loop);
+
+ /* Run the main loop and let the timeouts fire. */
+ g_main_loop_run(loop);
+
+ /* Verify that our state got reset back to PURPLE_TYPING_STATE_NONE. */
+ typing_state = purple_conversation_member_get_typing_state(member);
+ g_assert_cmpint(typing_state, ==, PURPLE_TYPING_STATE_NONE);
+
+ /* Clean everything up. */
+ g_clear_object(&info);
+ g_clear_object(&member);
+}
+
+/******************************************************************************
* Main
*****************************************************************************/
gint
@@ -86,5 +163,8 @@
g_test_add_func("/conversation-member/properties",
test_purple_conversation_member_properties);
+ g_test_add_func("/conversation-member/typing-state/timeout",
+ test_purple_conversation_member_typing_state_timeout);
+
return g_test_run();
}