diff --git a/console/Makefile.am b/console/Makefile.am index 3a975e9c4..314f5d612 100644 --- a/console/Makefile.am +++ b/console/Makefile.am @@ -13,7 +13,8 @@ COMMON_CFLAGS=\ $(MEDIASTREAMER_CFLAGS) \ $(VIDEO_CFLAGS) \ $(READLINE_CFLAGS) \ - $(SQLITE3_CFLAGS) + $(SQLITE3_CFLAGS) \ + $(LIBXML2_CFLAGS) if BUILD_CONSOLE @@ -29,7 +30,8 @@ linphonec_LDADD=$(top_builddir)/coreapi/liblinphone.la \ $(READLINE_LIBS) \ $(SQLITE3_LIBS) \ $(X11_LIBS) \ - $(BELLESIP_LIBS) + $(BELLESIP_LIBS) \ + $(LIBXML2_LIBS) if BUILD_WIN32 #special build of linphonec to detach from the windows console diff --git a/coreapi/Makefile.am b/coreapi/Makefile.am index 3933ddb05..61d240464 100644 --- a/coreapi/Makefile.am +++ b/coreapi/Makefile.am @@ -51,6 +51,7 @@ liblinphone_la_SOURCES=\ contactprovider.c contactprovider.h contact_providers_priv.h \ ldap/ldapprovider.c ldap/ldapprovider.h \ dict.c \ + xml.c \ $(GITVERSION_FILE) if BUILD_UPNP diff --git a/coreapi/bellesip_sal/sal_impl.c b/coreapi/bellesip_sal/sal_impl.c index 08986eb89..f15b07ffb 100644 --- a/coreapi/bellesip_sal/sal_impl.c +++ b/coreapi/bellesip_sal/sal_impl.c @@ -464,6 +464,8 @@ void sal_set_callbacks(Sal *ctx, const SalCallbacks *cbs){ ctx->callbacks.subscribe_presence_received=(SalOnSubscribePresenceReceived)unimplemented_stub; if (ctx->callbacks.text_received==NULL) ctx->callbacks.text_received=(SalOnTextReceived)unimplemented_stub; + if (ctx->callbacks.is_composing_received==NULL) + ctx->callbacks.is_composing_received=(SalOnIsComposingReceived)unimplemented_stub; if (ctx->callbacks.ping_reply==NULL) ctx->callbacks.ping_reply=(SalOnPingReply)unimplemented_stub; if (ctx->callbacks.auth_requested==NULL) @@ -915,5 +917,12 @@ unsigned char * sal_get_random_bytes(unsigned char *ret, size_t size){ return belle_sip_random_bytes(ret,size); } +belle_sip_source_t * sal_create_timer(Sal *sal, belle_sip_source_func_t func, void *data, unsigned int timeout_value_ms, const char* timer_name) { + belle_sip_main_loop_t *ml = belle_sip_stack_get_main_loop(sal->stack); + return belle_sip_main_loop_create_timeout(ml, func, data, timeout_value_ms, timer_name); +} - +void sal_cancel_timer(Sal *sal, belle_sip_source_t *timer) { + belle_sip_main_loop_t *ml = belle_sip_stack_get_main_loop(sal->stack); + belle_sip_main_loop_remove_source(ml, timer); +} diff --git a/coreapi/bellesip_sal/sal_op_message.c b/coreapi/bellesip_sal/sal_op_message.c index 1e3dc12f0..25402aef2 100644 --- a/coreapi/bellesip_sal/sal_op_message.c +++ b/coreapi/bellesip_sal/sal_op_message.c @@ -76,6 +76,10 @@ static bool_t is_external_body(belle_sip_header_content_type_t* content_type) { return strcmp("message",belle_sip_header_content_type_get_type(content_type))==0 && strcmp("external-body",belle_sip_header_content_type_get_subtype(content_type))==0; } +static bool_t is_im_iscomposing(belle_sip_header_content_type_t* content_type) { + return strcmp("application",belle_sip_header_content_type_get_type(content_type))==0 + && strcmp("im-iscomposing+xml",belle_sip_header_content_type_get_subtype(content_type))==0; +} static void process_request_event(void *op_base, const belle_sip_request_event_t *event) { SalOp* op = (SalOp*)op_base; @@ -88,8 +92,6 @@ static void process_request_event(void *op_base, const belle_sip_request_event_t belle_sip_header_call_id_t* call_id = belle_sip_message_get_header_by_type(req,belle_sip_header_call_id_t); belle_sip_header_cseq_t* cseq = belle_sip_message_get_header_by_type(req,belle_sip_header_cseq_t); belle_sip_header_date_t *date=belle_sip_message_get_header_by_type(req,belle_sip_header_date_t); - SalMessage salmsg; - char message_id[256]={0}; int response_code=501; char* from; bool_t plain_text=FALSE; @@ -99,7 +101,8 @@ static void process_request_event(void *op_base, const belle_sip_request_event_t content_type=belle_sip_message_get_header_by_type(BELLE_SIP_MESSAGE(req),belle_sip_header_content_type_t); if (content_type && ((plain_text=is_plain_text(content_type)) || (external_body=is_external_body(content_type)))) { - + SalMessage salmsg; + char message_id[256]={0}; address=belle_sip_header_address_create(belle_sip_header_address_get_displayname(BELLE_SIP_HEADER_ADDRESS(from_header)) ,belle_sip_header_address_get_uri(BELLE_SIP_HEADER_ADDRESS(from_header))); from=belle_sip_object_to_string(BELLE_SIP_OBJECT(address)); @@ -121,6 +124,17 @@ static void process_request_event(void *op_base, const belle_sip_request_event_t belle_sip_free(from); if (salmsg.url) ms_free((char*)salmsg.url); response_code=200; + } else if (content_type && is_im_iscomposing(content_type)) { + SalIsComposing saliscomposing; + address=belle_sip_header_address_create(belle_sip_header_address_get_displayname(BELLE_SIP_HEADER_ADDRESS(from_header)) + ,belle_sip_header_address_get_uri(BELLE_SIP_HEADER_ADDRESS(from_header))); + from=belle_sip_object_to_string(BELLE_SIP_OBJECT(address)); + saliscomposing.from=from; + saliscomposing.text=belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)); + op->base.root->callbacks.is_composing_received(op,&saliscomposing); + belle_sip_object_unref(address); + belle_sip_free(from); + response_code=200; } else { ms_error("Unsupported MESSAGE with content type [%s/%s]",belle_sip_header_content_type_get_type(content_type) ,belle_sip_header_content_type_get_subtype(content_type)); diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index a9c364c52..3ebce7488 100644 --- a/coreapi/callbacks.c +++ b/coreapi/callbacks.c @@ -843,6 +843,11 @@ static void text_received(SalOp *op, const SalMessage *msg){ } } +static void is_composing_received(SalOp *op, const SalIsComposing *is_composing) { + LinphoneCore *lc = (LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op)); + linphone_core_is_composing_received(lc, op, is_composing); +} + static void parse_presence_requested(SalOp *op, const char *content_type, const char *content_subtype, const char *body, SalPresenceModel **result) { linphone_notify_parse_presence(op, content_type, content_subtype, body, result); } @@ -1001,8 +1006,14 @@ static int op_equals(LinphoneCall *a, SalOp *b) { static void text_delivery_update(SalOp *op, SalTextDeliveryStatus status){ LinphoneChatMessage *chat_msg=(LinphoneChatMessage* )sal_op_get_user_pointer(op); - const MSList* calls = linphone_core_get_calls(chat_msg->chat_room->lc); - + const MSList* calls; + + if (chat_msg == NULL) { + // Do not handle delivery status for isComposing messages. + return; + } + calls = linphone_core_get_calls(chat_msg->chat_room->lc); + chat_msg->state=chatStatusSal2Linphone(status); linphone_chat_message_store_state(chat_msg); if (chat_msg && chat_msg->cb) { @@ -1123,6 +1134,7 @@ SalCallbacks linphone_sal_callbacks={ refer_received, text_received, text_delivery_update, + is_composing_received, notify_refer, subscribe_received, subscribe_closed, diff --git a/coreapi/chat.c b/coreapi/chat.c index c2e6a9cf5..ddb6ba262 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -26,6 +26,12 @@ #include "private.h" #include "lpconfig.h" +#include + +#define COMPOSING_DEFAULT_IDLE_TIMEOUT 15 +#define COMPOSING_DEFAULT_REFRESH_TIMEOUT 60 +#define COMPOSING_DEFAULT_REMOTE_REFRESH_TIMEOUT 120 + /** * @addtogroup chatroom * @{ @@ -85,13 +91,40 @@ LinphoneChatRoom* linphone_core_get_or_create_chat_room(LinphoneCore* lc, const } return ret; } - + +static void linphone_chat_room_delete_composing_idle_timer(LinphoneChatRoom *cr) { + if (cr->composing_idle_timer) { + sal_cancel_timer(cr->lc->sal, cr->composing_idle_timer); + belle_sip_object_unref(cr->composing_idle_timer); + cr->composing_idle_timer = NULL; + } +} + +static void linphone_chat_room_delete_composing_refresh_timer(LinphoneChatRoom *cr) { + if (cr->composing_refresh_timer) { + sal_cancel_timer(cr->lc->sal, cr->composing_refresh_timer); + belle_sip_object_unref(cr->composing_refresh_timer); + cr->composing_refresh_timer = NULL; + } +} + +static void linphone_chat_room_delete_remote_composing_refresh_timer(LinphoneChatRoom *cr) { + if (cr->remote_composing_refresh_timer) { + sal_cancel_timer(cr->lc->sal, cr->remote_composing_refresh_timer); + belle_sip_object_unref(cr->remote_composing_refresh_timer); + cr->remote_composing_refresh_timer = NULL; + } +} + /** * Destroy a LinphoneChatRoom. * @param cr #LinphoneChatRoom object */ void linphone_chat_room_destroy(LinphoneChatRoom *cr){ LinphoneCore *lc=cr->lc; + linphone_chat_room_delete_composing_idle_timer(cr); + linphone_chat_room_delete_composing_refresh_timer(cr); + linphone_chat_room_delete_remote_composing_refresh_timer(cr); lc->chatrooms=ms_list_remove(lc->chatrooms,(void *) cr); linphone_address_destroy(cr->peer_url); ms_free(cr->peer); @@ -142,6 +175,12 @@ static void _linphone_chat_room_send_message(LinphoneChatRoom *cr, LinphoneChatM msg->dir=LinphoneChatMessageOutgoing; msg->from=linphone_address_new(identity); msg->storage_id=linphone_chat_message_store(msg); + + if (cr->is_composing == LinphoneIsComposingActive) { + cr->is_composing = LinphoneIsComposingIdle; + } + linphone_chat_room_delete_composing_idle_timer(cr); + linphone_chat_room_delete_composing_refresh_timer(cr); } /** @@ -225,6 +264,89 @@ void linphone_core_message_received(LinphoneCore *lc, SalOp *op, const SalMessag ms_free(from); } +static int linphone_chat_room_remote_refresh_composing_expired(void *data, unsigned int revents) { + LinphoneChatRoom *cr = (LinphoneChatRoom *)data; + belle_sip_object_unref(cr->remote_composing_refresh_timer); + cr->remote_composing_refresh_timer = NULL; + cr->remote_is_composing = LinphoneIsComposingIdle; + if (cr->lc->vtable.is_composing_received != NULL) + cr->lc->vtable.is_composing_received(cr->lc, cr); + return BELLE_SIP_STOP; +} + +static const char *iscomposing_prefix = "/xsi:isComposing"; + +static void process_im_is_composing_notification(LinphoneChatRoom *cr, xmlparsing_context_t *xml_ctx) { + char xpath_str[MAX_XPATH_LENGTH]; + xmlXPathObjectPtr iscomposing_object; + const char *state_str = NULL; + const char *refresh_str = NULL; + int refresh_duration = COMPOSING_DEFAULT_REMOTE_REFRESH_TIMEOUT; + int i; + LinphoneIsComposingState state = LinphoneIsComposingIdle; + + if (linphone_create_xml_xpath_context(xml_ctx) < 0) return; + + xmlXPathRegisterNs(xml_ctx->xpath_ctx, (const xmlChar *)"xsi", (const xmlChar *)"urn:ietf:params:xml:ns:im-iscomposing"); + iscomposing_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, iscomposing_prefix); + if ((iscomposing_object != NULL) && (iscomposing_object->nodesetval != NULL)) { + for (i = 1; i <= iscomposing_object->nodesetval->nodeNr; i++) { + snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/xsi:state", iscomposing_prefix, i); + state_str = linphone_get_xml_text_content(xml_ctx, xpath_str); + if (state_str == NULL) continue; + snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/xsi:refresh", iscomposing_prefix, i); + refresh_str = linphone_get_xml_text_content(xml_ctx, xpath_str); + } + } + + if (state_str != NULL) { + if (strcmp(state_str, "active") == 0) { + state = LinphoneIsComposingActive; + if (refresh_str != NULL) { + refresh_duration = atoi(refresh_str); + } + if (!cr->remote_composing_refresh_timer) { + cr->remote_composing_refresh_timer = sal_create_timer(cr->lc->sal, linphone_chat_room_remote_refresh_composing_expired, cr, refresh_duration * 1000, "composing remote refresh timeout"); + } else { + belle_sip_source_set_timeout(cr->remote_composing_refresh_timer, refresh_duration * 1000); + } + } else { + linphone_chat_room_delete_remote_composing_refresh_timer(cr); + } + + cr->remote_is_composing = state; + if (cr->lc->vtable.is_composing_received != NULL) + cr->lc->vtable.is_composing_received(cr->lc, cr); + } +} + +static void linphone_chat_room_notify_is_composing(LinphoneChatRoom *cr, const char *text) { + xmlparsing_context_t *xml_ctx = linphone_xmlparsing_context_new(); + xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error); + xml_ctx->doc = xmlReadDoc((const unsigned char*)text, 0, NULL, 0); + if (xml_ctx->doc != NULL) { + process_im_is_composing_notification(cr, xml_ctx); + } else { + ms_warning("Wrongly formatted presence XML: %s", xml_ctx->errorBuffer); + } + linphone_xmlparsing_context_destroy(xml_ctx); +} + +void linphone_core_is_composing_received(LinphoneCore *lc, SalOp *op, const SalIsComposing *is_composing) { + LinphoneChatRoom *cr = NULL; + LinphoneAddress *addr = linphone_address_new(is_composing->from); + linphone_address_clean(addr); + cr = linphone_core_get_chat_room(lc, addr); + if (cr != NULL) { + linphone_chat_room_notify_is_composing(cr, is_composing->text); + } + linphone_address_destroy(addr); +} + +bool_t linphone_chat_room_is_remote_composing(const LinphoneChatRoom *cr) { + return (cr->remote_is_composing == LinphoneIsComposingActive) ? TRUE : FALSE; +} + /** * Returns back pointer to LinphoneCore object. **/ @@ -319,6 +441,128 @@ void linphone_chat_room_send_message2(LinphoneChatRoom *cr, LinphoneChatMessage* _linphone_chat_room_send_message(cr, msg); } +static char * linphone_chat_room_create_is_composing_xml(LinphoneChatRoom *cr) { + xmlBufferPtr buf; + xmlTextWriterPtr writer; + int err; + char *content = NULL; + + buf = xmlBufferCreate(); + if (buf == NULL) { + ms_error("Error creating the XML buffer"); + return content; + } + writer = xmlNewTextWriterMemory(buf, 0); + if (writer == NULL) { + ms_error("Error creating the XML writer"); + return content; + } + + err = xmlTextWriterStartDocument(writer, "1.0", "UTF-8", NULL); + if (err >= 0) { + err = xmlTextWriterStartElementNS(writer, NULL, (const xmlChar *)"isComposing", (const xmlChar *)"urn:ietf:params:xml:ns:im-iscomposing"); + } + if (err >= 0) { + err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xmlns", (const xmlChar *)"xsi", + NULL, (const xmlChar *)"http://www.w3.org/2001/XMLSchema-instance"); + } + if (err >= 0) { + err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xsi", (const xmlChar *)"schemaLocation", + NULL, (const xmlChar *)"urn:ietf:params:xml:ns:im-composing iscomposing.xsd"); + } + if (err >= 0) { + err = xmlTextWriterWriteElement(writer, (const xmlChar *)"state", + (cr->is_composing == LinphoneIsComposingActive) ? (const xmlChar *)"active" : (const xmlChar *)"idle"); + } + if ((err >= 0) && (cr->is_composing == LinphoneIsComposingActive)) { + char refresh_str[4] = { 0 }; + snprintf(refresh_str, sizeof(refresh_str), "%u", COMPOSING_DEFAULT_REFRESH_TIMEOUT); + err = xmlTextWriterWriteElement(writer, (const xmlChar *)"refresh", (const xmlChar *)refresh_str); + } + if (err >= 0) { + /* Close the "isComposing" element. */ + err = xmlTextWriterEndElement(writer); + } + if (err >= 0) { + err = xmlTextWriterEndDocument(writer); + } + if (err > 0) { + /* xmlTextWriterEndDocument returns the size of the content. */ + content = ms_strdup((char *)buf->content); + } + xmlFreeTextWriter(writer); + xmlBufferFree(buf); + return content; +} + +static void linphone_chat_room_send_is_composing_notification(LinphoneChatRoom *cr) { + SalOp *op = NULL; + LinphoneCall *call; + const char *identity = NULL; + char *content = NULL; + + if (lp_config_get_int(cr->lc->config, "sip", "chat_use_call_dialogs", 0)) { + if ((call = linphone_core_get_call_by_remote_address(cr->lc, cr->peer)) != NULL) { + if (call->state == LinphoneCallConnected || + call->state == LinphoneCallStreamsRunning || + call->state == LinphoneCallPaused || + call->state == LinphoneCallPausing || + call->state == LinphoneCallPausedByRemote) { + ms_message("send SIP message through the existing call."); + op = call->op; + identity = linphone_core_find_best_identity(cr->lc, linphone_call_get_remote_address(call)); + } + } + } + if (op == NULL) { + LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(cr->lc, cr->peer_url); + if (proxy) + identity = linphone_proxy_config_get_identity(proxy); + else + identity = linphone_core_get_primary_contact(cr->lc); + /*sending out of calls*/ + op = sal_op_new(cr->lc->sal); + linphone_configure_op(cr->lc, op, cr->peer_url, NULL, lp_config_get_int(cr->lc->config, "sip", "chat_msg_with_contact", 0)); + } + content = linphone_chat_room_create_is_composing_xml(cr); + if (content != NULL) { + sal_message_send(op, identity, cr->peer, "application/im-iscomposing+xml", content); + ms_free(content); + } +} + +static int linphone_chat_room_stop_composing(void *data, unsigned int revents) { + LinphoneChatRoom *cr = (LinphoneChatRoom *)data; + cr->is_composing = LinphoneIsComposingIdle; + linphone_chat_room_send_is_composing_notification(cr); + linphone_chat_room_delete_composing_refresh_timer(cr); + belle_sip_object_unref(cr->composing_idle_timer); + cr->composing_idle_timer = NULL; + return BELLE_SIP_STOP; +} + +static int linphone_chat_room_refresh_composing(void *data, unsigned int revents) { + LinphoneChatRoom *cr = (LinphoneChatRoom *)data; + linphone_chat_room_send_is_composing_notification(cr); + return BELLE_SIP_CONTINUE; +} + +void linphone_chat_room_compose(LinphoneChatRoom *cr) { + if (cr->is_composing == LinphoneIsComposingIdle) { + cr->is_composing = LinphoneIsComposingActive; + linphone_chat_room_send_is_composing_notification(cr); + if (!cr->composing_refresh_timer) { + cr->composing_refresh_timer = sal_create_timer(cr->lc->sal, linphone_chat_room_refresh_composing, cr, COMPOSING_DEFAULT_REFRESH_TIMEOUT * 1000, "composing refresh timeout"); + } else { + belle_sip_source_set_timeout(cr->composing_refresh_timer, COMPOSING_DEFAULT_REFRESH_TIMEOUT * 1000); + } + if (!cr->composing_idle_timer) { + cr->composing_idle_timer = sal_create_timer(cr->lc->sal, linphone_chat_room_stop_composing, cr, COMPOSING_DEFAULT_IDLE_TIMEOUT * 1000, "composing idle timeout"); + } + } + belle_sip_source_set_timeout(cr->composing_idle_timer, COMPOSING_DEFAULT_IDLE_TIMEOUT * 1000); +} + /** * Returns a #LinphoneChatMessageState as a string. */ diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index c6a3332df..4aa4d0942 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -977,6 +977,20 @@ LINPHONE_PUBLIC MSList *linphone_chat_room_get_history(LinphoneChatRoom *cr,int LINPHONE_PUBLIC void linphone_chat_room_mark_as_read(LinphoneChatRoom *cr); LINPHONE_PUBLIC void linphone_chat_room_delete_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg); LINPHONE_PUBLIC void linphone_chat_room_delete_history(LinphoneChatRoom *cr); + +/** + * Notify the destination of the chat message being composed that the user is typing a new message. + * @param[in] cr The #LinphoneChatRoom object corresponding to the conversation for which a new message is being typed. + */ +LINPHONE_PUBLIC void linphone_chat_room_compose(LinphoneChatRoom *cr); + +/** + * Tells whether the remote is currently composing a message. + * @param[in] cr The "LinphoneChatRoom object corresponding to the conversation. + * @return TRUE if the remote is currently composing a message, FALSE otherwise. + */ +LINPHONE_PUBLIC bool_t linphone_chat_room_is_remote_composing(const LinphoneChatRoom *cr); + LINPHONE_PUBLIC int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr); LINPHONE_PUBLIC LinphoneCore* linphone_chat_room_get_lc(LinphoneChatRoom *cr); LINPHONE_PUBLIC void linphone_chat_room_set_user_data(LinphoneChatRoom *cr, void * ud); @@ -1130,6 +1144,14 @@ typedef void (*LinphoneCoreTextMessageReceivedCb)(LinphoneCore *lc, LinphoneChat */ typedef void (*LinphoneCoreMessageReceivedCb)(LinphoneCore *lc, LinphoneChatRoom *room, LinphoneChatMessage *message); +/** + * Is composing notification callback prototype. + * + * @param[in] lc #LinphoneCore object + * @param[in] room #LinphoneChatRoom involved in the conversation. + */ +typedef void (*LinphoneCoreIsComposingReceivedCb)(LinphoneCore *lc, LinphoneChatRoom *room); + /** * Callback for being notified of DTMFs received. * @param lc the linphone core @@ -1179,6 +1201,7 @@ typedef struct _LinphoneCoreVTable{ LinphoneCoreAuthInfoRequestedCb auth_info_requested; /**< Ask the application some authentication information */ LinphoneCoreCallLogUpdatedCb call_log_updated; /**< Notifies that call log list has been updated */ LinphoneCoreMessageReceivedCb message_received; /** a message is received, can be text or external body*/ + LinphoneCoreIsComposingReceivedCb is_composing_received; /**< An is-composing notification has been received */ LinphoneCoreDtmfReceivedCb dtmf_received; /**< A dtmf has been received received */ LinphoneCoreReferReceivedCb refer_received; /**< An out of call refer was received */ LinphoneCoreCallEncryptionChangedCb call_encryption_changed; /** -#include -#include -#include - - -#define XMLPARSING_BUFFER_LEN 2048 -#define MAX_XPATH_LENGTH 256 - extern const char *__policy_enum_to_str(LinphoneSubscribePolicy pol); @@ -83,13 +74,6 @@ struct _LinphonePresenceModel { MSList *notes; /**< A list of _LinphonePresenceNote structures. */ }; -typedef struct _xmlparsing_context { - xmlDoc *doc; - xmlXPathContextPtr xpath_ctx; - char errorBuffer[XMLPARSING_BUFFER_LEN]; - char warningBuffer[XMLPARSING_BUFFER_LEN]; -} xmlparsing_context_t; - static const char *person_prefix = "/pidf:presence/dm:person"; @@ -98,38 +82,6 @@ static const char *person_prefix = "/pidf:presence/dm:person"; * PRIVATE FUNCTIONS * ****************************************************************************/ -static xmlparsing_context_t * xmlparsing_context_new() { - xmlparsing_context_t *xmlCtx = (xmlparsing_context_t *)malloc(sizeof(xmlparsing_context_t)); - if (xmlCtx != NULL) { - xmlCtx->doc = NULL; - xmlCtx->xpath_ctx = NULL; - xmlCtx->errorBuffer[0] = '\0'; - xmlCtx->warningBuffer[0] = '\0'; - } - return xmlCtx; -} - -static void xmlparsing_context_destroy(xmlparsing_context_t *ctx) { - if (ctx->doc != NULL) { - xmlFreeDoc(ctx->doc); - ctx->doc = NULL; - } - if (ctx->xpath_ctx != NULL) { - xmlXPathFreeContext(ctx->xpath_ctx); - ctx->xpath_ctx = NULL; - } - free(ctx); -} - -static void xmlparsing_genericxml_error(void *ctx, const char *fmt, ...) { - xmlparsing_context_t *xmlCtx = (xmlparsing_context_t *)ctx; - int sl = strlen(xmlCtx->errorBuffer); - va_list args; - va_start(args, fmt); - vsnprintf(xmlCtx->errorBuffer + sl, XMLPARSING_BUFFER_LEN - sl, fmt, args); - va_end(args); -} - static char presence_id_valid_characters[] = "0123456789abcdefghijklmnopqrstuvwxyz"; static char * generate_presence_id(void) { @@ -1183,45 +1135,6 @@ void * linphone_presence_note_get_user_data(LinphonePresenceNote *note) { * XML PRESENCE INTERNAL HANDLING * ****************************************************************************/ -static int create_xml_xpath_context(xmlparsing_context_t *xml_ctx) { - if (xml_ctx->xpath_ctx != NULL) { - xmlXPathFreeContext(xml_ctx->xpath_ctx); - } - xml_ctx->xpath_ctx = xmlXPathNewContext(xml_ctx->doc); - if (xml_ctx->xpath_ctx == NULL) return -1; - return 0; -} - -static char * get_xml_text_content(xmlparsing_context_t *xml_ctx, const char *xpath_expression) { - xmlXPathObjectPtr xpath_obj; - xmlChar *text = NULL; - int i; - - xpath_obj = xmlXPathEvalExpression((const xmlChar *)xpath_expression, xml_ctx->xpath_ctx); - if (xpath_obj != NULL) { - if (xpath_obj->nodesetval != NULL) { - xmlNodeSetPtr nodes = xpath_obj->nodesetval; - for (i = 0; i < nodes->nodeNr; i++) { - xmlNodePtr node = nodes->nodeTab[i]; - if (node->children != NULL) { - text = xmlNodeListGetString(xml_ctx->doc, node->children, 1); - } - } - } - xmlXPathFreeObject(xpath_obj); - } - - return (char *)text; -} - -static void free_xml_text_content(const char *text) { - xmlFree((xmlChar *)text); -} - -static xmlXPathObjectPtr get_xml_xpath_object_for_node_list(xmlparsing_context_t *xml_ctx, const char *xpath_expression) { - return xmlXPathEvalExpression((const xmlChar *)xpath_expression, xml_ctx->xpath_ctx); -} - static const char *service_prefix = "/pidf:presence/pidf:tuple"; static int process_pidf_xml_presence_service_notes(xmlparsing_context_t *xml_ctx, LinphonePresenceService *service, unsigned int service_idx) { @@ -1233,19 +1146,19 @@ static int process_pidf_xml_presence_service_notes(xmlparsing_context_t *xml_ctx int i; snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/pidf:note", service_prefix, service_idx); - note_object = get_xml_xpath_object_for_node_list(xml_ctx, xpath_str); + note_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, xpath_str); if ((note_object != NULL) && (note_object->nodesetval != NULL)) { for (i = 1; i <= note_object->nodesetval->nodeNr; i++) { snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/pidf:note[%i]", service_prefix, service_idx, i); - note_str = get_xml_text_content(xml_ctx, xpath_str); + note_str = linphone_get_xml_text_content(xml_ctx, xpath_str); if (note_str == NULL) continue; snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/pidf:note[%i]/@xml:lang", service_prefix, service_idx, i); - lang = get_xml_text_content(xml_ctx, xpath_str); + lang = linphone_get_xml_text_content(xml_ctx, xpath_str); note = linphone_presence_note_new(note_str, lang); presence_service_add_note(service, note); - if (lang != NULL) free_xml_text_content(lang); - free_xml_text_content(note_str); + if (lang != NULL) linphone_free_xml_text_content(lang); + linphone_free_xml_text_content(note_str); } } if (note_object != NULL) xmlXPathFreeObject(note_object); @@ -1264,11 +1177,11 @@ static int process_pidf_xml_presence_services(xmlparsing_context_t *xml_ctx, Lin LinphonePresenceBasicStatus basic_status; int i; - service_object = get_xml_xpath_object_for_node_list(xml_ctx, service_prefix); + service_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, service_prefix); if ((service_object != NULL) && (service_object->nodesetval != NULL)) { for (i = 1; i <= service_object->nodesetval->nodeNr; i++) { snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/pidf:status/pidf:basic", service_prefix, i); - basic_status_str = get_xml_text_content(xml_ctx, xpath_str); + basic_status_str = linphone_get_xml_text_content(xml_ctx, xpath_str); if (basic_status_str == NULL) continue; @@ -1278,33 +1191,33 @@ static int process_pidf_xml_presence_services(xmlparsing_context_t *xml_ctx, Lin basic_status = LinphonePresenceBasicStatusClosed; } else { /* Invalid value for basic status. */ - free_xml_text_content(basic_status_str); + linphone_free_xml_text_content(basic_status_str); return -1; } snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/pidf:timestamp", service_prefix, i); - timestamp_str = get_xml_text_content(xml_ctx, xpath_str); + timestamp_str = linphone_get_xml_text_content(xml_ctx, xpath_str); snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/pidf:contact", service_prefix, i); - contact_str = get_xml_text_content(xml_ctx, xpath_str); + contact_str = linphone_get_xml_text_content(xml_ctx, xpath_str); snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/@id", service_prefix, i); - service_id_str = get_xml_text_content(xml_ctx, xpath_str); + service_id_str = linphone_get_xml_text_content(xml_ctx, xpath_str); service = presence_service_new(service_id_str, basic_status); if (service != NULL) { if (timestamp_str != NULL) { presence_service_set_timestamp(service, parse_timestamp(timestamp_str)); - free_xml_text_content(timestamp_str); + linphone_free_xml_text_content(timestamp_str); } if (contact_str != NULL) { linphone_presence_service_set_contact(service, contact_str); - free_xml_text_content(contact_str); + linphone_free_xml_text_content(contact_str); } process_pidf_xml_presence_service_notes(xml_ctx, service, i); linphone_presence_model_add_service(model, service); } - free_xml_text_content(basic_status_str); - if (service_id_str != NULL) free_xml_text_content(service_id_str); + linphone_free_xml_text_content(basic_status_str); + if (service_id_str != NULL) linphone_free_xml_text_content(service_id_str); } } if (service_object != NULL) xmlXPathFreeObject(service_object); @@ -1333,11 +1246,11 @@ static int process_pidf_xml_presence_person_activities(xmlparsing_context_t *xml int err = 0; snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/rpid:activities", person_prefix, person_idx); - activities_nodes_object = get_xml_xpath_object_for_node_list(xml_ctx, xpath_str); + activities_nodes_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, xpath_str); if ((activities_nodes_object != NULL) && (activities_nodes_object->nodesetval != NULL)) { for (i = 1; i <= activities_nodes_object->nodesetval->nodeNr; i++) { snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/rpid:activities[%i]/rpid:*", person_prefix, person_idx, i); - activities_object = get_xml_xpath_object_for_node_list(xml_ctx, xpath_str); + activities_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, xpath_str); if ((activities_object != NULL) && (activities_object->nodesetval != NULL)) { for (j = 0; j < activities_object->nodesetval->nodeNr; j++) { activity_node = activities_object->nodesetval->nodeTab[j]; @@ -1345,14 +1258,14 @@ static int process_pidf_xml_presence_person_activities(xmlparsing_context_t *xml LinphonePresenceActivityType acttype; description = (const char *)xmlNodeGetContent(activity_node); if ((description != NULL) && (description[0] == '\0')) { - free_xml_text_content(description); + linphone_free_xml_text_content(description); description = NULL; } err = activity_name_to_presence_activity_type((const char *)activity_node->name, &acttype); if (err < 0) break; activity = linphone_presence_activity_new(acttype, description); linphone_presence_person_add_activity(person, activity); - if (description != NULL) free_xml_text_content(description); + if (description != NULL) linphone_free_xml_text_content(description); } } } @@ -1374,37 +1287,37 @@ static int process_pidf_xml_presence_person_notes(xmlparsing_context_t *xml_ctx, int i; snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/rpid:activities/rpid:note", person_prefix, person_idx); - note_object = get_xml_xpath_object_for_node_list(xml_ctx, xpath_str); + note_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, xpath_str); if ((note_object != NULL) && (note_object->nodesetval != NULL)) { for (i = 1; i <= note_object->nodesetval->nodeNr; i++) { snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/rpid:activities/rpid:note[%i]", person_prefix, person_idx, i); - note_str = get_xml_text_content(xml_ctx, xpath_str); + note_str = linphone_get_xml_text_content(xml_ctx, xpath_str); if (note_str == NULL) continue; snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/rpid:activities/rpid:note[%i]/@xml:lang", person_prefix, person_idx, i); - lang = get_xml_text_content(xml_ctx, xpath_str); + lang = linphone_get_xml_text_content(xml_ctx, xpath_str); note = linphone_presence_note_new(note_str, lang); presence_person_add_activities_note(person, note); - if (lang != NULL) free_xml_text_content(lang); - free_xml_text_content(note_str); + if (lang != NULL) linphone_free_xml_text_content(lang); + linphone_free_xml_text_content(note_str); } } if (note_object != NULL) xmlXPathFreeObject(note_object); snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/dm:note", person_prefix, person_idx); - note_object = get_xml_xpath_object_for_node_list(xml_ctx, xpath_str); + note_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, xpath_str); if ((note_object != NULL) && (note_object->nodesetval != NULL)) { for (i = 1; i <= note_object->nodesetval->nodeNr; i++) { snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/dm:note[%i]", person_prefix, person_idx, i); - note_str = get_xml_text_content(xml_ctx, xpath_str); + note_str = linphone_get_xml_text_content(xml_ctx, xpath_str); if (note_str == NULL) continue; snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/dm:note[%i]/@xml:lang", person_prefix, person_idx, i); - lang = get_xml_text_content(xml_ctx, xpath_str); + lang = linphone_get_xml_text_content(xml_ctx, xpath_str); note = linphone_presence_note_new(note_str, lang); presence_person_add_note(person, note); - if (lang != NULL) free_xml_text_content(lang); - free_xml_text_content(note_str); + if (lang != NULL) linphone_free_xml_text_content(lang); + linphone_free_xml_text_content(note_str); } } if (note_object != NULL) xmlXPathFreeObject(note_object); @@ -1422,13 +1335,13 @@ static int process_pidf_xml_presence_persons(xmlparsing_context_t *xml_ctx, Linp int i; int err = 0; - person_object = get_xml_xpath_object_for_node_list(xml_ctx, person_prefix); + person_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, person_prefix); if ((person_object != NULL) && (person_object->nodesetval != NULL)) { for (i = 1; i <= person_object->nodesetval->nodeNr; i++) { snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/@id", person_prefix, i); - person_id_str = get_xml_text_content(xml_ctx, xpath_str); + person_id_str = linphone_get_xml_text_content(xml_ctx, xpath_str); snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/pidf:timestamp", person_prefix, i); - person_timestamp_str = get_xml_text_content(xml_ctx, xpath_str); + person_timestamp_str = linphone_get_xml_text_content(xml_ctx, xpath_str); if (person_timestamp_str == NULL) timestamp = time(NULL); else @@ -1446,8 +1359,8 @@ static int process_pidf_xml_presence_persons(xmlparsing_context_t *xml_ctx, Linp break; } } - if (person_id_str != NULL) free_xml_text_content(person_id_str); - if (person_timestamp_str != NULL) free_xml_text_content(person_timestamp_str); + if (person_id_str != NULL) linphone_free_xml_text_content(person_id_str); + if (person_timestamp_str != NULL) linphone_free_xml_text_content(person_timestamp_str); } } if (person_object != NULL) xmlXPathFreeObject(person_object); @@ -1467,19 +1380,19 @@ static int process_pidf_xml_presence_notes(xmlparsing_context_t *xml_ctx, Linpho const char *lang; int i; - note_object = get_xml_xpath_object_for_node_list(xml_ctx, "/pidf:presence/pidf:note"); + note_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, "/pidf:presence/pidf:note"); if ((note_object != NULL) && (note_object->nodesetval != NULL)) { for (i = 1; i <= note_object->nodesetval->nodeNr; i++) { snprintf(xpath_str, sizeof(xpath_str), "/pidf:presence/pidf:note[%i]", i); - note_str = get_xml_text_content(xml_ctx, xpath_str); + note_str = linphone_get_xml_text_content(xml_ctx, xpath_str); if (note_str == NULL) continue; snprintf(xpath_str, sizeof(xpath_str), "/pidf:presence/pidf:note[%i]/@xml:lang", i); - lang = get_xml_text_content(xml_ctx, xpath_str); + lang = linphone_get_xml_text_content(xml_ctx, xpath_str); note = linphone_presence_note_new(note_str, lang); presence_model_add_note(model, note); - if (lang != NULL) free_xml_text_content(lang); - free_xml_text_content(note_str); + if (lang != NULL) linphone_free_xml_text_content(lang); + linphone_free_xml_text_content(note_str); } } if (note_object != NULL) xmlXPathFreeObject(note_object); @@ -1491,7 +1404,7 @@ static LinphonePresenceModel * process_pidf_xml_presence_notification(xmlparsing LinphonePresenceModel *model = NULL; int err; - if (create_xml_xpath_context(xml_ctx) < 0) + if (linphone_create_xml_xpath_context(xml_ctx) < 0) return NULL; model = linphone_presence_model_new(); @@ -1606,15 +1519,15 @@ void linphone_notify_parse_presence(SalOp *op, const char *content_type, const c } if (strcmp(content_subtype, "pidf+xml") == 0) { - xml_ctx = xmlparsing_context_new(); - xmlSetGenericErrorFunc(xml_ctx, xmlparsing_genericxml_error); + xml_ctx = linphone_xmlparsing_context_new(); + xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error); xml_ctx->doc = xmlReadDoc((const unsigned char*)body, 0, NULL, 0); if (xml_ctx->doc != NULL) { model = process_pidf_xml_presence_notification(xml_ctx); } else { ms_warning("Wrongly formatted presence XML: %s", xml_ctx->errorBuffer); } - xmlparsing_context_destroy(xml_ctx); + linphone_xmlparsing_context_destroy(xml_ctx); } else { ms_error("Unknown content type '%s/%s' for presence", content_type, content_subtype); } diff --git a/coreapi/private.h b/coreapi/private.h index 5225b6a3c..012484c1d 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -338,6 +338,7 @@ void linphone_proxy_config_write_to_config_file(struct _LpConfig* config,Linphon int linphone_proxy_config_normalize_number(LinphoneProxyConfig *cfg, const char *username, char *result, size_t result_len); void linphone_core_message_received(LinphoneCore *lc, SalOp *op, const SalMessage *msg); +void linphone_core_is_composing_received(LinphoneCore *lc, SalOp *op, const SalIsComposing *is_composing); void linphone_core_play_tone(LinphoneCore *lc); @@ -426,12 +427,22 @@ struct _LinphoneAuthInfo bool_t works; }; +typedef enum _LinphoneIsComposingState { + LinphoneIsComposingIdle, + LinphoneIsComposingActive +} LinphoneIsComposingState; + struct _LinphoneChatRoom{ struct _LinphoneCore *lc; char *peer; LinphoneAddress *peer_url; void * user_data; MSList *messages_hist; + LinphoneIsComposingState remote_is_composing; + LinphoneIsComposingState is_composing; + belle_sip_source_t *remote_composing_refresh_timer; + belle_sip_source_t *composing_idle_timer; + belle_sip_source_t *composing_refresh_timer; }; @@ -788,6 +799,35 @@ LinphoneSubscriptionState linphone_subscription_state_from_sal(SalSubscribeStatu const LinphoneContent *linphone_content_from_sal_body(LinphoneContent *obj, const SalBody *ref); void linphone_core_invalidate_friend_subscriptions(LinphoneCore *lc); + +/***************************************************************************** + * XML UTILITY FUNCTIONS * + ****************************************************************************/ + +#include +#include +#include +#include + +#define XMLPARSING_BUFFER_LEN 2048 +#define MAX_XPATH_LENGTH 256 + +typedef struct _xmlparsing_context { + xmlDoc *doc; + xmlXPathContextPtr xpath_ctx; + char errorBuffer[XMLPARSING_BUFFER_LEN]; + char warningBuffer[XMLPARSING_BUFFER_LEN]; +} xmlparsing_context_t; + +xmlparsing_context_t * linphone_xmlparsing_context_new(void); +void linphone_xmlparsing_context_destroy(xmlparsing_context_t *ctx); +void linphone_xmlparsing_genericxml_error(void *ctx, const char *fmt, ...); +int linphone_create_xml_xpath_context(xmlparsing_context_t *xml_ctx); +char * linphone_get_xml_text_content(xmlparsing_context_t *xml_ctx, const char *xpath_expression); +void linphone_free_xml_text_content(const char *text); +xmlXPathObjectPtr linphone_get_xml_xpath_object_for_node_list(xmlparsing_context_t *xml_ctx, const char *xpath_expression); + + #ifdef __cplusplus } #endif diff --git a/coreapi/xml.c b/coreapi/xml.c new file mode 100644 index 000000000..b4b994174 --- /dev/null +++ b/coreapi/xml.c @@ -0,0 +1,98 @@ +/* +linphone +Copyright (C) 2010-2013 Belledonne Communications SARL + +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, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + + +#include "private.h" + +#include +#include +#include +#include + + +xmlparsing_context_t * linphone_xmlparsing_context_new(void) { + xmlparsing_context_t *xmlCtx = (xmlparsing_context_t *)malloc(sizeof(xmlparsing_context_t)); + if (xmlCtx != NULL) { + xmlCtx->doc = NULL; + xmlCtx->xpath_ctx = NULL; + xmlCtx->errorBuffer[0] = '\0'; + xmlCtx->warningBuffer[0] = '\0'; + } + return xmlCtx; +} + +void linphone_xmlparsing_context_destroy(xmlparsing_context_t *ctx) { + if (ctx->doc != NULL) { + xmlFreeDoc(ctx->doc); + ctx->doc = NULL; + } + if (ctx->xpath_ctx != NULL) { + xmlXPathFreeContext(ctx->xpath_ctx); + ctx->xpath_ctx = NULL; + } + free(ctx); +} + +void linphone_xmlparsing_genericxml_error(void *ctx, const char *fmt, ...) { + xmlparsing_context_t *xmlCtx = (xmlparsing_context_t *)ctx; + int sl = strlen(xmlCtx->errorBuffer); + va_list args; + va_start(args, fmt); + vsnprintf(xmlCtx->errorBuffer + sl, XMLPARSING_BUFFER_LEN - sl, fmt, args); + va_end(args); +} + +int linphone_create_xml_xpath_context(xmlparsing_context_t *xml_ctx) { + if (xml_ctx->xpath_ctx != NULL) { + xmlXPathFreeContext(xml_ctx->xpath_ctx); + } + xml_ctx->xpath_ctx = xmlXPathNewContext(xml_ctx->doc); + if (xml_ctx->xpath_ctx == NULL) return -1; + return 0; +} + +char * linphone_get_xml_text_content(xmlparsing_context_t *xml_ctx, const char *xpath_expression) { + xmlXPathObjectPtr xpath_obj; + xmlChar *text = NULL; + int i; + + xpath_obj = xmlXPathEvalExpression((const xmlChar *)xpath_expression, xml_ctx->xpath_ctx); + if (xpath_obj != NULL) { + if (xpath_obj->nodesetval != NULL) { + xmlNodeSetPtr nodes = xpath_obj->nodesetval; + for (i = 0; i < nodes->nodeNr; i++) { + xmlNodePtr node = nodes->nodeTab[i]; + if (node->children != NULL) { + text = xmlNodeListGetString(xml_ctx->doc, node->children, 1); + } + } + } + xmlXPathFreeObject(xpath_obj); + } + + return (char *)text; +} + +void linphone_free_xml_text_content(const char *text) { + xmlFree((xmlChar *)text); +} + +xmlXPathObjectPtr linphone_get_xml_xpath_object_for_node_list(xmlparsing_context_t *xml_ctx, const char *xpath_expression) { + return xmlXPathEvalExpression((const xmlChar *)xpath_expression, xml_ctx->xpath_ctx); +} diff --git a/gtk/chat.c b/gtk/chat.c index b44605ae3..0c9aa400b 100644 --- a/gtk/chat.c +++ b/gtk/chat.c @@ -279,6 +279,16 @@ static void on_chat_state_changed(LinphoneChatMessage *msg, LinphoneChatMessageS update_chat_state_message(state,msg); } +void linphone_gtk_compose_text(void) { + GtkWidget *main_window=linphone_gtk_get_main_window(); + GtkWidget *friendlist=linphone_gtk_get_widget(main_window,"contact_list"); + GtkWidget *w=(GtkWidget*)g_object_get_data(G_OBJECT(friendlist),"chatview"); + LinphoneChatRoom *cr=g_object_get_data(G_OBJECT(w),"cr"); + if (cr) { + linphone_chat_room_compose(cr); + } +} + void linphone_gtk_send_text(){ GtkWidget *main_window=linphone_gtk_get_main_window(); GtkWidget *friendlist=linphone_gtk_get_widget(main_window,"contact_list"); @@ -293,7 +303,11 @@ void linphone_gtk_send_text(){ linphone_chat_room_send_message2(cr,msg,on_chat_state_changed,NULL); linphone_gtk_push_text(w,linphone_chat_message_get_from(msg), TRUE,cr,msg,FALSE); + + // Disconnect and reconnect the "changed" signal to prevent triggering it when clearing the text entry. + g_signal_handlers_disconnect_by_func(G_OBJECT(entry),(GCallback)linphone_gtk_compose_text,NULL); gtk_entry_set_text(GTK_ENTRY(entry),""); + g_signal_connect_swapped(G_OBJECT(entry),"changed",(GCallback)linphone_gtk_compose_text,NULL); } } @@ -410,6 +424,7 @@ GtkWidget* linphone_gtk_init_chatroom(LinphoneChatRoom *cr, const LinphoneAddres g_signal_connect_swapped(G_OBJECT(button),"clicked",(GCallback)linphone_gtk_send_text,NULL); entry = linphone_gtk_get_widget(chat_view,"text_entry"); g_signal_connect_swapped(G_OBJECT(entry),"activate",(GCallback)linphone_gtk_send_text,NULL); + g_signal_connect_swapped(G_OBJECT(entry),"changed",(GCallback)linphone_gtk_compose_text,NULL); g_signal_connect(G_OBJECT(notebook),"switch_page",(GCallback)linphone_gtk_notebook_tab_select,NULL); ms_free(with_str); return chat_view; @@ -417,7 +432,7 @@ GtkWidget* linphone_gtk_init_chatroom(LinphoneChatRoom *cr, const LinphoneAddres LinphoneChatRoom * linphone_gtk_create_chatroom(const LinphoneAddress *with){ char *tmp=linphone_address_as_string(with); - LinphoneChatRoom *cr=linphone_core_create_chat_room(linphone_gtk_get_core(),tmp); + LinphoneChatRoom *cr=linphone_core_get_or_create_chat_room(linphone_gtk_get_core(),tmp); ms_free(tmp); return cr; } @@ -516,3 +531,7 @@ void linphone_gtk_text_received ( LinphoneCore *lc, LinphoneChatRoom *room, linphone_gtk_show_friends(); } + +void linphone_gtk_is_composing_received(LinphoneCore *lc, LinphoneChatRoom *room) { + linphone_gtk_friend_list_update_chat_picture(); +} diff --git a/gtk/friendlist.c b/gtk/friendlist.c index 523cf13b3..0316dd699 100644 --- a/gtk/friendlist.c +++ b/gtk/friendlist.c @@ -87,6 +87,18 @@ static GdkPixbuf *create_chat_picture(){ return pixbuf; } +static GdkPixbuf *create_composing_unread_msg(){ + GdkPixbuf *pixbuf; + pixbuf = create_pixbuf("composing_active_chat.png"); + return pixbuf; +} + +static GdkPixbuf *create_composing_chat_picture(){ + GdkPixbuf *pixbuf; + pixbuf = create_pixbuf("composing_chat.png"); + return pixbuf; +} + /* void linphone_gtk_set_friend_status(GtkWidget *friendlist , LinphoneFriend * fid, const gchar *url, const gchar *status, const gchar *img){ GtkTreeIter iter; @@ -227,15 +239,23 @@ void linphone_gtk_friend_list_update_chat_picture(){ GtkWidget *friendlist=linphone_gtk_get_widget(w,"contact_list"); GtkTreeModel *model=gtk_tree_view_get_model(GTK_TREE_VIEW(friendlist)); LinphoneChatRoom *cr=NULL; + bool_t is_composing; int nbmsg=0; if (gtk_tree_model_get_iter_first(model,&iter)) { do{ gtk_tree_model_get (model, &iter,FRIEND_CHATROOM , &cr, -1); nbmsg=linphone_chat_room_get_unread_messages_count(cr); + is_composing=linphone_chat_room_is_remote_composing(cr); if(nbmsg != 0){ - gtk_list_store_set(GTK_LIST_STORE(model),&iter,FRIEND_CHAT,create_unread_msg(),-1); + if (is_composing == TRUE) + gtk_list_store_set(GTK_LIST_STORE(model),&iter,FRIEND_CHAT,create_composing_unread_msg(),-1); + else + gtk_list_store_set(GTK_LIST_STORE(model),&iter,FRIEND_CHAT,create_unread_msg(),-1); } else { - gtk_list_store_set(GTK_LIST_STORE(model),&iter,FRIEND_CHAT,create_chat_picture(),-1); + if (is_composing == TRUE) + gtk_list_store_set(GTK_LIST_STORE(model),&iter,FRIEND_CHAT,create_composing_chat_picture(),-1); + else + gtk_list_store_set(GTK_LIST_STORE(model),&iter,FRIEND_CHAT,create_chat_picture(),-1); } }while(gtk_tree_model_iter_next(model,&iter)); } diff --git a/gtk/linphone.h b/gtk/linphone.h index 1d674f876..f9d918c51 100644 --- a/gtk/linphone.h +++ b/gtk/linphone.h @@ -116,6 +116,7 @@ void linphone_gtk_send_text(); GtkWidget * linphone_gtk_init_chatroom(LinphoneChatRoom *cr, const LinphoneAddress *with); LinphoneChatRoom * linphone_gtk_create_chatroom(const LinphoneAddress *with); void linphone_gtk_text_received(LinphoneCore *lc, LinphoneChatRoom *room, LinphoneChatMessage *msg); +void linphone_gtk_is_composing_received(LinphoneCore *lc, LinphoneChatRoom *room); void linphone_gtk_friend_list_update_chat_picture(); void linphone_gtk_friend_list_set_chat_conversation(const LinphoneAddress *la); diff --git a/gtk/main.c b/gtk/main.c index 135dfe5f4..e47af7f3a 100644 --- a/gtk/main.c +++ b/gtk/main.c @@ -266,6 +266,7 @@ static void linphone_gtk_init_liblinphone(const char *config_file, vtable.call_log_updated=linphone_gtk_call_log_updated; //vtable.text_received=linphone_gtk_text_received; vtable.message_received=linphone_gtk_text_received; + vtable.is_composing_received=linphone_gtk_is_composing_received; vtable.refer_received=linphone_gtk_refer_received; vtable.buddy_info_updated=linphone_gtk_buddy_info_updated; vtable.call_encryption_changed=linphone_gtk_call_encryption_changed; diff --git a/include/sal/sal.h b/include/sal/sal.h index b76d70b05..6044205be 100644 --- a/include/sal/sal.h +++ b/include/sal/sal.h @@ -32,6 +32,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "mediastreamer2/mscommon.h" #include "ortp/ortp_srtp.h" +#include "belle-sip/belle-sip.h" #ifndef LINPHONE_PUBLIC #define LINPHONE_PUBLIC MS2_PUBLIC @@ -224,6 +225,11 @@ typedef struct SalMessage{ time_t time; }SalMessage; +typedef struct SalIsComposing { + const char *from; + const char *text; +} SalIsComposing; + #define SAL_MEDIA_DESCRIPTION_MAX_MESSAGE_ATTRIBUTES 5 SalMediaDescription *sal_media_description_new(); @@ -390,6 +396,7 @@ typedef void (*SalOnDtmfReceived)(SalOp *op, char dtmf); typedef void (*SalOnRefer)(Sal *sal, SalOp *op, const char *referto); typedef void (*SalOnTextReceived)(SalOp *op, const SalMessage *msg); typedef void (*SalOnTextDeliveryUpdate)(SalOp *op, SalTextDeliveryStatus status); +typedef void (*SalOnIsComposingReceived)(SalOp *op, const SalIsComposing *is_composing); typedef void (*SalOnNotifyRefer)(SalOp *op, SalReferStatus state); typedef void (*SalOnSubscribeResponse)(SalOp *op, SalSubscribeStatus status, SalError error, SalReason reason); typedef void (*SalOnNotify)(SalOp *op, SalSubscribeStatus status, const char *event, const SalBody *body); @@ -425,6 +432,7 @@ typedef struct SalCallbacks{ SalOnRefer refer_received; SalOnTextReceived text_received; SalOnTextDeliveryUpdate text_delivery_update; + SalOnIsComposingReceived is_composing_received; SalOnNotifyRefer notify_refer; SalOnSubscribeReceived subscribe_received; SalOnSubscribeClosed subscribe_closed; @@ -690,6 +698,8 @@ LINPHONE_PUBLIC bool_t sal_dns_srv_enabled(const Sal *sal); LINPHONE_PUBLIC void sal_set_dns_user_hosts_file(Sal *sal, const char *hosts_file); LINPHONE_PUBLIC const char *sal_get_dns_user_hosts_file(const Sal *sal); unsigned char * sal_get_random_bytes(unsigned char *ret, size_t size); +belle_sip_source_t * sal_create_timer(Sal *sal, belle_sip_source_func_t func, void *data, unsigned int timeout_value_ms, const char* timer_name); +void sal_cancel_timer(Sal *sal, belle_sip_source_t *timer); int sal_body_has_type(const SalBody *body, const char *type, const char *subtype); /*this function parses a document with key=value pairs separated by new lines, and extracts the value for a given key*/ diff --git a/pixmaps/Makefile.am b/pixmaps/Makefile.am index 2386d68cf..5e9b7a177 100644 --- a/pixmaps/Makefile.am +++ b/pixmaps/Makefile.am @@ -11,7 +11,7 @@ status-orange.png \ status-red.png \ status-offline.png \ call.png \ -chat.png active_chat.png\ +chat.png active_chat.png composing_chat.png composing_active_chat.png\ chat_message_inprogress.png chat_message_delivered.png chat_message_not_delivered.png\ contact-orange.png dialer-orange.png history-orange.png\ startcall-green.png startcall-small.png stopcall-red.png stopcall-small.png addcall-green.png linphone.icns \ diff --git a/pixmaps/active_chat.png b/pixmaps/active_chat.png index d82b7c595..ef3cedd1d 100644 Binary files a/pixmaps/active_chat.png and b/pixmaps/active_chat.png differ diff --git a/pixmaps/composing_active_chat.png b/pixmaps/composing_active_chat.png new file mode 100644 index 000000000..77da9e37f Binary files /dev/null and b/pixmaps/composing_active_chat.png differ diff --git a/pixmaps/composing_chat.png b/pixmaps/composing_chat.png new file mode 100644 index 000000000..2d329ed21 Binary files /dev/null and b/pixmaps/composing_chat.png differ diff --git a/tester/Makefile.am b/tester/Makefile.am index 4388ac483..ba8432bfa 100644 --- a/tester/Makefile.am +++ b/tester/Makefile.am @@ -23,11 +23,11 @@ liblinphone_tester_SOURCES= liblinphone_tester.c liblinphone_tester.h\ AM_CPPFLAGS=-I$(top_srcdir)/include -I$(top_srcdir)/coreapi -LDADD=$(top_builddir)/coreapi/liblinphone.la $(BELLESIP_LIBS) +LDADD=$(top_builddir)/coreapi/liblinphone.la $(BELLESIP_LIBS) $(LIBXML2_LIBS) AM_LDFLAGS=$(CUNIT_LIBS) -AM_CFLAGS=$(STRICT_OPTIONS) -DIN_LINPHONE $(ORTP_CFLAGS) $(MEDIASTREAMER_CFLAGS) $(CUNIT_CFLAGS) $(BELLESIP_CFLAGS) +AM_CFLAGS=$(STRICT_OPTIONS) -DIN_LINPHONE $(ORTP_CFLAGS) $(MEDIASTREAMER_CFLAGS) $(CUNIT_CFLAGS) $(BELLESIP_CFLAGS) $(LIBXML2_CFLAGS) test: liblinphone_tester ./liblinphone_tester --config $(abs_srcdir)