diff --git a/include/linphone/api/c-callbacks.h b/include/linphone/api/c-callbacks.h index 359cfa9db..60ba99bc7 100644 --- a/include/linphone/api/c-callbacks.h +++ b/include/linphone/api/c-callbacks.h @@ -144,9 +144,10 @@ typedef void (*LinphoneChatMessageCbsFileTransferProgressIndicationCb)(LinphoneC /** * Is composing notification callback prototype. * @param[in] cr #LinphoneChatRoom involved in the conversation - * @param[in] participant The #LinphoneParticipant that has sent the is-composing notification + * @param[in] remoteAddr The address that has sent the is-composing notification + * @param[in] isComposing A boolean value telling whether the remote is composing or not */ -typedef void (*LinphoneChatRoomCbsIsComposingReceivedCb) (LinphoneChatRoom *cr, const LinphoneParticipant *participant); +typedef void (*LinphoneChatRoomCbsIsComposingReceivedCb) (LinphoneChatRoom *cr, const LinphoneAddress *remoteAddr, bool_t isComposing); /** * Callback used to notify a chat room that a message has been received. diff --git a/src/chat/chat-room/chat-room-p.h b/src/chat/chat-room/chat-room-p.h index 0074c3767..c434b0bcc 100644 --- a/src/chat/chat-room/chat-room-p.h +++ b/src/chat/chat-room/chat-room-p.h @@ -20,6 +20,8 @@ #ifndef _CHAT_ROOM_P_H_ #define _CHAT_ROOM_P_H_ +#include + #include "chat/notification/is-composing.h" #include "chat-room.h" #include "object/object-p.h" @@ -74,17 +76,18 @@ public: protected: void chatMessageReceived (const std::shared_ptr &msg); void imdnReceived (const std::string &text); - void isComposingReceived (const std::string &text); + void isComposingReceived (const Address &remoteAddr, const std::string &text); private: void notifyChatMessageReceived (const std::shared_ptr &msg); + void notifyIsComposingReceived (const Address &remoteAddr, bool isComposing); void notifyStateChanged (); void notifyUndecryptableMessageReceived (const std::shared_ptr &msg); private: /* IsComposingListener */ void onIsComposingStateChanged (bool isComposing) override; - void onIsRemoteComposingStateChanged (bool isComposing) override; + void onIsRemoteComposingStateChanged (const Address &remoteAddr, bool isComposing) override; void onIsComposingRefreshNeeded () override; public: @@ -94,7 +97,7 @@ public: Address peerAddress; int unreadCount = -1; bool isComposing = false; - bool remoteIsComposing = false; + std::unordered_set remoteIsComposing; std::list> messages; std::list> transientMessages; std::list> weakMessages; diff --git a/src/chat/chat-room/chat-room.cpp b/src/chat/chat-room/chat-room.cpp index 96eb6b1c8..238e2fcb2 100644 --- a/src/chat/chat-room/chat-room.cpp +++ b/src/chat/chat-room/chat-room.cpp @@ -400,7 +400,7 @@ LinphoneReason ChatRoomPrivate::messageReceived (SalOp *op, const SalMessage *sa } if (msg->getPrivate()->getContentType() == ContentType::ImIsComposing) { - isComposingReceived(msg->getPrivate()->getText()); + isComposingReceived(msg->getFromAddress(), msg->getPrivate()->getText()); increaseMsgCount = FALSE; if (lp_config_get_int(core->config, "sip", "deliver_imdn", 0) != 1) { goto end; @@ -435,11 +435,11 @@ end: // ----------------------------------------------------------------------------- void ChatRoomPrivate::chatMessageReceived (const shared_ptr &msg) { - L_Q(); if ((msg->getPrivate()->getContentType() != ContentType::Imdn) && (msg->getPrivate()->getContentType() != ContentType::ImIsComposing)) { notifyChatMessageReceived(msg); - remoteIsComposing = false; - linphone_core_notify_is_composing_received(core, L_GET_C_BACK_PTR(q)); + remoteIsComposing.erase(msg->getFromAddress().asStringUriOnly()); + isComposingHandler.stopRemoteRefreshTimer(msg->getFromAddress().asStringUriOnly()); + notifyIsComposingReceived(msg->getFromAddress(), false); msg->sendDeliveryNotification(LinphoneReasonNone); } } @@ -449,8 +449,8 @@ void ChatRoomPrivate::imdnReceived (const string &text) { Imdn::parse(*q, text); } -void ChatRoomPrivate::isComposingReceived (const string &text) { - isComposingHandler.parse(text); +void ChatRoomPrivate::isComposingReceived (const Address &remoteAddr, const string &text) { + isComposingHandler.parse(remoteAddr, text); } // ----------------------------------------------------------------------------- @@ -469,6 +469,20 @@ void ChatRoomPrivate::notifyChatMessageReceived (const shared_ptr & linphone_core_notify_message_received(core, cr, L_GET_C_BACK_PTR(msg)); } +void ChatRoomPrivate::notifyIsComposingReceived (const Address &remoteAddr, bool isComposing) { + L_Q(); + LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q); + LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(cr); + LinphoneChatRoomCbsIsComposingReceivedCb cb = linphone_chat_room_cbs_get_is_composing_received(cbs); + if (cb) { + LinphoneAddress *lAddr = linphone_address_new(remoteAddr.asString().c_str()); + cb(cr, lAddr, !!isComposing); + linphone_address_unref(lAddr); + } + // Legacy notification + linphone_core_notify_is_composing_received(core, cr); +} + void ChatRoomPrivate::notifyStateChanged () { L_Q(); LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q); @@ -495,10 +509,12 @@ void ChatRoomPrivate::onIsComposingStateChanged (bool isComposing) { sendIsComposingNotification(); } -void ChatRoomPrivate::onIsRemoteComposingStateChanged (bool isComposing) { - L_Q(); - remoteIsComposing = isComposing; - linphone_core_notify_is_composing_received(core, L_GET_C_BACK_PTR(q)); +void ChatRoomPrivate::onIsRemoteComposingStateChanged (const Address &remoteAddr, bool isComposing) { + if (isComposing) + remoteIsComposing.insert(remoteAddr.asStringUriOnly()); + else + remoteIsComposing.erase(remoteAddr.asStringUriOnly()); + notifyIsComposingReceived(remoteAddr, isComposing); } void ChatRoomPrivate::onIsComposingRefreshNeeded () { @@ -670,7 +686,7 @@ int ChatRoom::getUnreadMessagesCount () { bool ChatRoom::isRemoteComposing () const { L_D(); - return d->remoteIsComposing; + return d->remoteIsComposing.size() > 0; } void ChatRoom::markAsRead () { diff --git a/src/chat/chat-room/real-time-text-chat-room.cpp b/src/chat/chat-room/real-time-text-chat-room.cpp index 705fc0172..1c47fd0c1 100644 --- a/src/chat/chat-room/real-time-text-chat-room.cpp +++ b/src/chat/chat-room/real-time-text-chat-room.cpp @@ -59,7 +59,7 @@ void RealTimeTextChatRoomPrivate::realtimeTextReceived (uint32_t character, Linp cmc->has_been_read = FALSE; receivedRttCharacters.push_back(cmc); - remoteIsComposing = true; + remoteIsComposing.insert(peerAddress.asStringUriOnly()); linphone_core_notify_is_composing_received(core, L_GET_C_BACK_PTR(q)); if ((character == new_line) || (character == crlf) || (character == lf)) { diff --git a/src/chat/notification/is-composing-listener.h b/src/chat/notification/is-composing-listener.h index cf6314ecb..0097bc29f 100644 --- a/src/chat/notification/is-composing-listener.h +++ b/src/chat/notification/is-composing-listener.h @@ -26,12 +26,14 @@ LINPHONE_BEGIN_NAMESPACE +class Address; + class IsComposingListener { public: virtual ~IsComposingListener() = default; virtual void onIsComposingStateChanged (bool isComposing) = 0; - virtual void onIsRemoteComposingStateChanged (bool isComposing) = 0; + virtual void onIsRemoteComposingStateChanged (const Address &remoteAddr, bool isComposing) = 0; virtual void onIsComposingRefreshNeeded () = 0; }; diff --git a/src/chat/notification/is-composing.cpp b/src/chat/notification/is-composing.cpp index a488fdb77..dd2a935a7 100644 --- a/src/chat/notification/is-composing.cpp +++ b/src/chat/notification/is-composing.cpp @@ -17,6 +17,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include + #include "linphone/utils/utils.h" #include "chat/chat-room/chat-room-p.h" @@ -30,6 +32,16 @@ using namespace std; LINPHONE_BEGIN_NAMESPACE +struct IsRemoteComposingData { + IsRemoteComposingData (IsComposing *isComposingHandler, string uri) + : isComposingHandler(isComposingHandler), uri(uri) {} + + IsComposing *isComposingHandler; + string uri; +}; + +// ----------------------------------------------------------------------------- + const string IsComposing::isComposingPrefix = "/xsi:isComposing"; // ----------------------------------------------------------------------------- @@ -94,12 +106,12 @@ string IsComposing::marshal (bool isComposing) { return content; } -void IsComposing::parse (const string &text) { +void IsComposing::parse (const Address &remoteAddr, const string &text) { xmlparsing_context_t *xmlCtx = linphone_xmlparsing_context_new(); xmlSetGenericErrorFunc(xmlCtx, linphone_xmlparsing_genericxml_error); xmlCtx->doc = xmlReadDoc((const unsigned char *)text.c_str(), 0, nullptr, 0); if (xmlCtx->doc) - parse(xmlCtx); + parse(xmlCtx, remoteAddr); else lWarning() << "Wrongly formatted presence XML: " << xmlCtx->errorBuffer; linphone_xmlparsing_context_destroy(xmlCtx); @@ -125,29 +137,10 @@ void IsComposing::startRefreshTimer () { } } -void IsComposing::startRemoteRefreshTimer (const char *refreshStr) { - unsigned int duration = getRemoteRefreshTimerDuration(); - if (refreshStr) - duration = static_cast(Utils::stoi(refreshStr)); - if (!remoteRefreshTimer) { - remoteRefreshTimer = core->sal->create_timer(remoteRefreshTimerExpired, this, - duration * 1000, "composing remote refresh timeout"); - } else { - belle_sip_source_set_timeout(remoteRefreshTimer, duration * 1000); - } -} - -#if 0 -void IsComposing::idleTimerExpired () { - stopRefreshTimer(); - stopIdleTimer(); -} -#endif - void IsComposing::stopTimers () { stopIdleTimer(); stopRefreshTimer(); - stopRemoteRefreshTimer(); + stopAllRemoteRefreshTimers(); } // ----------------------------------------------------------------------------- @@ -170,13 +163,10 @@ void IsComposing::stopRefreshTimer () { } } -void IsComposing::stopRemoteRefreshTimer () { - if (remoteRefreshTimer) { - if (core && core->sal) - core->sal->cancel_timer(remoteRefreshTimer); - belle_sip_object_unref(remoteRefreshTimer); - remoteRefreshTimer = nullptr; - } +void IsComposing::stopRemoteRefreshTimer (const string &uri) { + auto it = remoteRefreshTimers.find(uri); + if (it != remoteRefreshTimers.end()) + stopRemoteRefreshTimer(it); } // ----------------------------------------------------------------------------- @@ -196,7 +186,7 @@ unsigned int IsComposing::getRemoteRefreshTimerDuration () { return remoteRefreshTimerDuration < 0 ? 0 : static_cast(remoteRefreshTimerDuration); } -void IsComposing::parse (xmlparsing_context_t *xmlCtx) { +void IsComposing::parse (xmlparsing_context_t *xmlCtx, const Address &remoteAddr) { char xpathStr[MAX_XPATH_LENGTH]; char *stateStr = nullptr; char *refreshStr = nullptr; @@ -225,47 +215,78 @@ void IsComposing::parse (xmlparsing_context_t *xmlCtx) { if (stateStr) { if (strcmp(stateStr, "active") == 0) { state = true; - startRemoteRefreshTimer(refreshStr); + startRemoteRefreshTimer(remoteAddr.asStringUriOnly(), refreshStr); } else { - stopRemoteRefreshTimer(); + stopRemoteRefreshTimer(remoteAddr.asStringUriOnly()); } - listener->onIsRemoteComposingStateChanged(state); + listener->onIsRemoteComposingStateChanged(remoteAddr, state); linphone_free_xml_text_content(stateStr); } if (refreshStr) linphone_free_xml_text_content(refreshStr); } -int IsComposing::idleTimerExpired (unsigned int revents) { +int IsComposing::idleTimerExpired () { + stopRefreshTimer(); + stopIdleTimer(); listener->onIsComposingStateChanged(false); return BELLE_SIP_STOP; } -int IsComposing::refreshTimerExpired (unsigned int revents) { +int IsComposing::refreshTimerExpired () { listener->onIsComposingRefreshNeeded(); return BELLE_SIP_CONTINUE; } -int IsComposing::remoteRefreshTimerExpired (unsigned int revents) { - stopRemoteRefreshTimer(); - listener->onIsRemoteComposingStateChanged(false); +int IsComposing::remoteRefreshTimerExpired (const string &uri) { + stopRemoteRefreshTimer(uri); + listener->onIsRemoteComposingStateChanged(Address(uri), false); return BELLE_SIP_STOP; } +void IsComposing::startRemoteRefreshTimer (const string &uri, const char *refreshStr) { + unsigned int duration = getRemoteRefreshTimerDuration(); + if (refreshStr) + duration = static_cast(Utils::stoi(refreshStr)); + auto it = remoteRefreshTimers.find(uri); + if (it == remoteRefreshTimers.end()) { + IsRemoteComposingData *data = new IsRemoteComposingData(this, uri); + belle_sip_source_t *timer = core->sal->create_timer(remoteRefreshTimerExpired, data, + duration * 1000, "composing remote refresh timeout"); + pair p(uri, timer); + remoteRefreshTimers.insert(p); + } else + belle_sip_source_set_timeout(it->second, duration * 1000); +} + +void IsComposing::stopAllRemoteRefreshTimers () { + for (auto it = remoteRefreshTimers.begin(); it != remoteRefreshTimers.end();) + it = stopRemoteRefreshTimer(it); +} + +unordered_map::iterator IsComposing::stopRemoteRefreshTimer (const unordered_map::const_iterator it) { + if (core && core->sal) + core->sal->cancel_timer(it->second); + belle_sip_object_unref(it->second); + return remoteRefreshTimers.erase(it); +} + int IsComposing::idleTimerExpired (void *data, unsigned int revents) { IsComposing *d = reinterpret_cast(data); - return d->idleTimerExpired(revents); + return d->idleTimerExpired(); } int IsComposing::refreshTimerExpired (void *data, unsigned int revents) { IsComposing *d = reinterpret_cast(data); - return d->refreshTimerExpired(revents); + return d->refreshTimerExpired(); } int IsComposing::remoteRefreshTimerExpired (void *data, unsigned int revents) { - IsComposing *d = reinterpret_cast(data); - return d->remoteRefreshTimerExpired(revents); + IsRemoteComposingData *d = reinterpret_cast(data); + int result = d->isComposingHandler->remoteRefreshTimerExpired(d->uri); + delete d; + return result; } LINPHONE_END_NAMESPACE diff --git a/src/chat/notification/is-composing.h b/src/chat/notification/is-composing.h index fda928ae1..729b015bf 100644 --- a/src/chat/notification/is-composing.h +++ b/src/chat/notification/is-composing.h @@ -20,6 +20,8 @@ #ifndef _IS_COMPOSING_H_ #define _IS_COMPOSING_H_ +#include + #include "linphone/utils/general.h" #include "chat/notification/is-composing-listener.h" @@ -36,24 +38,25 @@ public: ~IsComposing (); std::string marshal (bool isComposing); - void parse (const std::string &content); + void parse (const Address &remoteAddr, const std::string &content); void startIdleTimer (); void startRefreshTimer (); - void startRemoteRefreshTimer (const char *refreshStr); - void stopComposing (); void stopIdleTimer (); void stopRefreshTimer (); - void stopRemoteRefreshTimer (); + void stopRemoteRefreshTimer (const std::string &uri); void stopTimers (); private: unsigned int getIdleTimerDuration (); unsigned int getRefreshTimerDuration (); unsigned int getRemoteRefreshTimerDuration (); - void parse (xmlparsing_context_t *xmlCtx); - int idleTimerExpired (unsigned int revents); - int refreshTimerExpired (unsigned int revents); - int remoteRefreshTimerExpired (unsigned int revents); + void parse (xmlparsing_context_t *xmlCtx, const Address &remoteAddr); + int idleTimerExpired (); + int refreshTimerExpired (); + int remoteRefreshTimerExpired (const std::string &uri); + void startRemoteRefreshTimer (const std::string &uri, const char *refreshStr); + void stopAllRemoteRefreshTimers (); + std::unordered_map::iterator stopRemoteRefreshTimer (const std::unordered_map::const_iterator it); static int idleTimerExpired (void *data, unsigned int revents); static int refreshTimerExpired (void *data, unsigned int revents); @@ -67,7 +70,7 @@ private: LinphoneCore *core = nullptr; IsComposingListener *listener = nullptr; - belle_sip_source_t *remoteRefreshTimer = nullptr; + std::unordered_mapremoteRefreshTimers; belle_sip_source_t *idleTimer = nullptr; belle_sip_source_t *refreshTimer = nullptr; };