From 94232c5fab5e6b1db79f38f1c2bb9ac4e903a631 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 10 Nov 2009 19:08:58 -0500 Subject: [PATCH] Translate incoming typing notifications from libpurple into chatstates. --- Makefile | 1 + perl/lib/Thrasher/Constants.pm | 2 + perl/lib/Thrasher/Plugin.pm | 6 +++ perl/lib/Thrasher/Protocol.pm | 39 ++++++++++++++++++- perl/lib/Thrasher/Protocol/Purple.pm | 39 ++++++++++++++++++- perl/lib/Thrasher/XMPPStreamOut.pm | 1 + perl/tests/component.pl | 7 +++- thconversations.c | 72 ++++++++++++++++++++++++++++++++++++ thconversations.h | 24 ++++++++++++ thperl.c | 51 ++++++++++++++++++++++++- thperl.h | 6 ++- thrasher.c | 2 + 12 files changed, 242 insertions(+), 8 deletions(-) create mode 100644 thconversations.c create mode 100644 thconversations.h diff --git a/Makefile b/Makefile index 410cbdd..8af7866 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,7 @@ OBJS = thperl.o \ thpresence.o \ thconnection.o \ threquest.o \ + thconversations.o \ thft.o COMPILE = gcc -DHAVE_CONFIG_H -I. $(GLIB_CFLAGS) $(DEBUG_CFLAGS) $(CFLAGS) diff --git a/perl/lib/Thrasher/Constants.pm b/perl/lib/Thrasher/Constants.pm index f0d0ebb..90465f0 100644 --- a/perl/lib/Thrasher/Constants.pm +++ b/perl/lib/Thrasher/Constants.pm @@ -25,6 +25,7 @@ our @EXPORT_OK = qw($NS_XML $NS_XMLNS $NS_XHTML $NS_NICK $NS_BYTESTREAMS $NS_FILE_TRANSFER $NS_FEATURE_NEG $NS_DATA $NS_STREAM_INITIATION + $NS_CHATSTATES $NS_THRASHER_PRESENCE ); @@ -80,6 +81,7 @@ our $NS_FILE_TRANSFER = 'http://jabber.org/protocol/si/profile/file-transfer'; our $NS_FEATURE_NEG = 'http://jabber.org/protocol/feature-neg'; our $NS_DATA = 'jabber:x:data'; our $NS_STREAM_INITIATION = 'http://jabber.org/protocol/si'; +our $NS_CHATSTATES = 'http://jabber.org/protocol/chatstates'; our $NS_THRASHER_PRESENCE = 'xmpp:x:thrasher:presence'; diff --git a/perl/lib/Thrasher/Plugin.pm b/perl/lib/Thrasher/Plugin.pm index f0c8783..64e8901 100644 --- a/perl/lib/Thrasher/Plugin.pm +++ b/perl/lib/Thrasher/Plugin.pm @@ -33,6 +33,7 @@ use Thrasher::Log qw(log); use Carp qw(confess); use Thrasher::Callbacks qw(:all); +use Thrasher::Constants; use base 'Exporter'; @@ -52,6 +53,11 @@ sub method_for_iq_namespace { $QUERY_FUNCTIONS{$_[0]}->{$_[1]}->{$_[2]}; } my %SUPPORTED_FEATURES; my @SUPPORTED_FEATURES_SORTED; +# Features always supported: +register_plugin({features => [ + $Thrasher::Constants::NS_CHATSTATES, +]}); + register_callback('plugins_changed', 'default', \&update_supported_features); sub update_supported_features { diff --git a/perl/lib/Thrasher/Protocol.pm b/perl/lib/Thrasher/Protocol.pm index 8185752..ab9dd14 100644 --- a/perl/lib/Thrasher/Protocol.pm +++ b/perl/lib/Thrasher/Protocol.pm @@ -747,8 +747,10 @@ sub sending_message { ($jid_to, $legacy_from, $component->{component_name}, 'en'); # FIXME: Should know lang in component - $component->send_message($jid_from, $jid_to, $message, - {is_xhtml_ish => $is_xhtml_ish}); + $component->send_message($jid_from, $jid_to, $message, { + is_xhtml_ish => $is_xhtml_ish, + children => [ [[ $NS_CHATSTATES, 'active' ], {}, []] ], + }); } =pod @@ -775,6 +777,39 @@ sub initial_login { =item * +C($session, $sender, $state_tag): Send a message +from the legacy $sender with only the given chatstate $state_tag for +the $session. + +=cut + +sub incoming_chatstate { + my ($self, $session, $sender, $state_tag) = @_; + + my $clean_sender = $self->process_remote_username($sender); + my $component = $session->{'component'}; + my $jid_from = $self->{'backend'}->legacy_name_to_jid( + $session->{'jid'}, + $clean_sender, + $component->{'component_name'}, + # FIXME: Should know lang in component + 'en', + ); + + $component->xml_out([ + [$NS_COMPONENT, 'message'], { + from => $jid_from, + to => $session->{'full_jid'}, + type => 'chat', + }, + [ [[ $NS_CHATSTATES, $state_tag ], {}, []] ] + ]); +} + +=pod + +=item * + C($session, $state): The session's state is one of several strings indicating what the state of the session is. diff --git a/perl/lib/Thrasher/Protocol/Purple.pm b/perl/lib/Thrasher/Protocol/Purple.pm index a90745f..6a78c4c 100644 --- a/perl/lib/Thrasher/Protocol/Purple.pm +++ b/perl/lib/Thrasher/Protocol/Purple.pm @@ -49,7 +49,9 @@ THPPW::thrasher_wrapper_init Thrasher::error_wrap("subscription_add", \&_subscription_add), Thrasher::error_wrap("legacy_user_adding_user", \&_legacy_user_adding_user), Thrasher::error_wrap("connection_error", \&_connection_error), - Thrasher::error_wrap("connection", \&_connection)); + Thrasher::error_wrap("connection", \&_connection), + Thrasher::error_wrap("incoming_chatstate", \&_incoming_chatstate), +); # Internal debugging @@ -232,6 +234,41 @@ sub _presence_in { return 1; } +sub _incoming_chatstate { + my ($orig_jid, $orig_sender, $state) = @_; + debug("_incoming_chatstate($orig_jid, $orig_sender, $state) called\n"); + + my $state_tag; + # loosely + if ($state == $THPPW::PURPLE_TYPING) { + $state_tag = 'composing'; + } + elsif ($state == $THPPW::PURPLE_TYPED) { + $state_tag = 'paused'; + } + elsif ($state == $THPPW::PURPLE_NOT_TYPING) { + $state_tag = 'inactive'; + } + else { + return; + } + + my $jid = Encode::decode('UTF-8', $orig_jid); + my $sender = Encode::decode('UTF-8', $orig_sender); + + my $session = $global_component->session_for($jid); + if (! $session) { + debug("No session?!!\n"); + return; + } + if (! $session->{'protocol'}) { + debug("No session protocol?!!\n"); + return; + } + $session->{'protocol'}->incoming_chatstate($session, $sender, $state_tag); + return; +} + sub _connection { my ($orig_jid) = @_; my $jid = Encode::decode("UTF-8", $orig_jid); diff --git a/perl/lib/Thrasher/XMPPStreamOut.pm b/perl/lib/Thrasher/XMPPStreamOut.pm index e53b780..e7aa547 100644 --- a/perl/lib/Thrasher/XMPPStreamOut.pm +++ b/perl/lib/Thrasher/XMPPStreamOut.pm @@ -73,6 +73,7 @@ my %CONVENTIONAL_PREFIXES = $NS_DATA => '', $NS_STREAM_INITIATION => '', $NS_FILE_TRANSFER => '', + $NS_CHATSTATES => '', $NS_THRASHER_PRESENCE => 'thrasher'); diff --git a/perl/tests/component.pl b/perl/tests/component.pl index 923e62c..3618f42 100644 --- a/perl/tests/component.pl +++ b/perl/tests/component.pl @@ -173,6 +173,7 @@ DISCO + @@ -241,8 +242,9 @@ DISCO DISCO_RESULT - is(output, $disco_result, 'proper discovery reply for ' - .'the virtual clients'); + is(output(), + $disco_result, + 'proper discovery reply for the virtual clients'); $comp->xml_in(< sucks! + EXPECTED is(output, $expected, diff --git a/thconversations.c b/thconversations.c new file mode 100644 index 0000000..1f9b478 --- /dev/null +++ b/thconversations.c @@ -0,0 +1,72 @@ +/* + * Thrasher Bird - XMPP transport via libpurple + * Copyright (C) 2008 Barracuda Networks, Inc. + * + * 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 Thrasher Bird; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include +#include "thrasher.h" +#include "thperl.h" + +#include + +#include "thconversations.h" + +/* incoming_chatstate_cb(pa, who): + * + * Account "pa" got a typing notification state change from the contact "who". + */ +static void +incoming_chatstate_cb(PurpleAccount *pa, + const char *who) { + PurpleConversation *conv + = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, + who, + pa); + if (! conv + || purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_IM) { + return; + } + + PurpleConvIm *im = PURPLE_CONV_IM(conv); + if (! im) { + return; + } + + thrasher_wrapper_incoming_chatstate(pa, + who, + purple_conv_im_get_typing_state(im)); +} + +static gpointer +thrasher_conversations_get_handle() { + static int handle; + return &handle; +} + +void +thrasher_conversations_init(void) { + purple_signal_connect(purple_conversations_get_handle(), + "buddy-typing", + thrasher_conversations_get_handle(), + PURPLE_CALLBACK(incoming_chatstate_cb), + NULL); + purple_signal_connect(purple_conversations_get_handle(), + "buddy-typing-stopped", + thrasher_conversations_get_handle(), + PURPLE_CALLBACK(incoming_chatstate_cb), + NULL); +} diff --git a/thconversations.h b/thconversations.h new file mode 100644 index 0000000..91b4171 --- /dev/null +++ b/thconversations.h @@ -0,0 +1,24 @@ +/* + * Thrasher Bird - XMPP transport via libpurple + * Copyright (C) 2008 Barracuda Networks, Inc. + * + * 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 Thrasher Bird; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ +#ifndef THCONVERSATIONS_H +#define THCONVERSATIONS_H + +void thrasher_conversations_init(void); + +#endif /* ifdef THCONVERSATIONS_H */ diff --git a/thperl.c b/thperl.c index 9b24a0e..1fa198e 100644 --- a/thperl.c +++ b/thperl.c @@ -477,7 +477,8 @@ void thrasher_wrapper_init (SV *timeout_add_cb, SV *subscription_add_cb, SV *legacy_user_add_user_cb, SV *connection_error_cb, - SV *connection_cb) + SV *connection_cb, + SV *incoming_chatstate_cb) { g_return_if_fail(timeout_add_cb); @@ -489,6 +490,7 @@ void thrasher_wrapper_init (SV *timeout_add_cb, g_return_if_fail(legacy_user_add_user_cb); g_return_if_fail(connection_error_cb); g_return_if_fail(connection_cb); + g_return_if_fail(incoming_chatstate_cb); /* Set the bindings for our function table */ callback_funcs = g_hash_table_new_full(g_direct_hash, @@ -511,8 +513,8 @@ void thrasher_wrapper_init (SV *timeout_add_cb, validate_callback(legacy_user_add_user_cb); validate_callback(connection_error_cb); validate_callback(connection_cb); + validate_callback(incoming_chatstate_cb); - dSP; ENTER; @@ -530,6 +532,7 @@ void thrasher_wrapper_init (SV *timeout_add_cb, callback.legacy_user_add_user = newSVsv(legacy_user_add_user_cb); callback.connection_error = newSVsv(connection_error_cb); callback.connection = newSVsv(connection_cb); + callback.incoming_chatstate = newSVsv(incoming_chatstate_cb); SPAGAIN; PUTBACK; @@ -1206,6 +1209,50 @@ void thrasher_remove_account (const char *jid) } /** + * @brief Forwards typing notification state changes from libpurple buddies. + * @param pa Account receiving the notification + * @param who Buddy name sending the notification + * @param state + */ +void thrasher_wrapper_incoming_chatstate(PurpleAccount* pa, + const char* who, + PurpleTypingState state) { + /* @exception pa and who cannot be NULL */ + g_return_if_fail(pa != NULL); + g_return_if_fail(who != NULL); + + /* @exception bail if our callback doesn't exist */ + g_return_if_fail(callback.incoming_chatstate != NULL); + + gchar* jid = thrasher_account_get_jid(pa); + /* @exception bail if account has no JID */ + g_return_if_fail(jid != NULL); + + dSP; + + ENTER; + SAVETMPS; + + PUSHMARK(sp); + + /* Push the args onto the stack */ + XPUSHs(clean_newSVpv(jid)); + XPUSHs(clean_newSVpv(who)); + XPUSHs(sv_2mortal(newSViv(state))); + + PUTBACK; + + call_sv(callback.incoming_chatstate, G_EVAL | G_SCALAR); + + SPAGAIN; + + FREETMPS; + PUTBACK; + + LEAVE; +} + +/** * @begin External trigger to flush the internal function tables */ void thrasher_wrapper_destructor () diff --git a/thperl.h b/thperl.h index 48e440c..dc599d7 100644 --- a/thperl.h +++ b/thperl.h @@ -120,6 +120,7 @@ typedef struct _perl_callbacks { SV *legacy_user_add_user; SV *connection_error; SV *connection; + SV *incoming_chatstate; } perl_callbacks; typedef struct _perl_ft_callbacks { @@ -186,7 +187,8 @@ void thrasher_wrapper_init (SV *timeout_add_cb, SV *subscription_add_cb, SV *legacy_user_add_user_cb, SV *connection_error_cb, - SV *connection_cb); + SV *connection_cb, + SV *incoming_chatstate_cb); void thrasher_wrapper_ft_init(SV *ft_recv_request_cb, SV *ft_recv_accept_cb, @@ -230,6 +232,8 @@ int thrasher_wrapper_trigger_timeout_func(long key); int thrasher_wrapper_trigger_input_func(long fd, SV *cond, long key); void thrasher_wrapper_destructor (); +void thrasher_wrapper_incoming_chatstate(PurpleAccount* pa, const char* who, PurpleTypingState state); + /* Core wrapper */ int thrasher_wrapper_call_source_remove (long key); long thrasher_wrapper_set_timeout_add (guint interval, GSourceFunc function, gpointer data); diff --git a/thrasher.c b/thrasher.c index 6dc89b6..17fd234 100644 --- a/thrasher.c +++ b/thrasher.c @@ -29,6 +29,7 @@ #include "thperl.h" #include "thft.h" #include "threquest.h" +#include "thconversations.h" GHashTable *account_to_connection = NULL; GHashTable *jid_to_account = NULL; @@ -129,6 +130,7 @@ void thrasher_init() thrasher_xfer_init(); thrasher_perl_init(); thrasher_request_init(); + thrasher_conversations_init(); // This hash table has no ownership on the pointers stored in it account_to_connection = g_hash_table_new(g_direct_hash, -- 2.11.4.GIT