6 * Copyright (C) 2010-2015 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
33 #include "conversation.h"
37 #include "win32/win32dep.h"
43 #if PURPLE_VERSION_CHECK(3,0,0)
44 #include "buddylist.h"
45 #include "conversations.h"
46 #define BACKEND_SESSION_TO_PURPLE_CONV_CHAT(s) ((PurpleChatConversation *) s)
47 #define PURPLE_CONV_CHAT(c) c
48 #define PURPLE_CONV_TO_SIPE_CORE_PUBLIC ((struct sipe_core_public *) purple_connection_get_protocol_data(purple_conversation_get_connection(conv)))
51 #define purple_chat_conversation_add_user(c, n, m, f, b) purple_conv_chat_add_user(c, n, m, f, b)
52 #define purple_chat_conversation_clear_users(c) purple_conv_chat_clear_users(c)
53 #define purple_chat_conversation_get_id(c) purple_conv_chat_get_id(c)
54 #define purple_chat_conversation_remove_user(c, n, s) purple_conv_chat_remove_user(c, n, s)
55 #define purple_chat_conversation_set_nick(c, n) purple_conv_chat_set_nick(c, n)
56 #define purple_chat_conversation_set_topic(c, n, s) purple_conv_chat_set_topic(c, n, s)
57 #define purple_chat_get_components(chat) chat->components
58 #define purple_conversations_find_chat(g, n) purple_find_chat(g, n)
59 #define purple_conversations_get_chats purple_get_chats
60 #define purple_conversation_get_connection(c) purple_conversation_get_gc(c)
61 #define purple_serv_got_chat_in(c, i, w, f, m, t) serv_got_chat_in(c, i, w, f, m, t)
62 #define purple_serv_got_joined_chat(c, i, n) serv_got_joined_chat(c, i, n)
63 #define BACKEND_SESSION_TO_PURPLE_CONV_CHAT(s) (PURPLE_CONV_CHAT(((PurpleConversation *)s)))
64 #define PURPLE_CHAT_USER_NONE PURPLE_CBFLAGS_NONE
65 #define PURPLE_CONV_TO_SIPE_CORE_PUBLIC ((struct sipe_core_public *) conv->account->gc->proto_data)
66 #define PURPLE_CONVERSATION_UPDATE_TOPIC PURPLE_CONV_UPDATE_TOPIC
69 #include "sipe-common.h"
70 #include "sipe-backend.h"
71 #include "sipe-core.h"
74 #define _PurpleMessageFlags PurpleMessageFlags
75 #include "purple-private.h"
78 * Mapping between chat sessions in SIPE core and libpurple backend
81 * This data structure is created when the user creates the account or at
82 * startup. It lives as long as the account exists, i.e. until the user
83 * deletes it or shutdown.
85 * Value does not change when connection is dropped & re-created.
86 * HAS: gc (PurpleConnection *)
88 * PurpleConversation / PurpleConvChat (sub-type)
89 * This data structure is created by serv_got_join_chat(). It lives as long
90 * as the user doesn't leave the chat or until shutdown.
92 * Value does not change when connection is dropped & re-created.
93 * HAS: account (PurpleAccount *)
94 * HAS: chat ID (int), must be unique
95 * HAS: name (char *), must be unique
96 * HAS: data (GHashTable *)
99 * This data structure is created when the connection to the service is
100 * set up. It lives as long as the connection stays open, the user disables
101 * the account or until shutdown.
103 * Value *DOES NOT* survive a connection drop & re-creation.
104 * ASSOCIATED TO: account
106 * SIPE -> libpurple API
107 * add user: purple_conv_chat_add_user(conv, ...)
108 * create: serv_got_joined_chat(gc, chat ID, name)
109 * find user: purple_conv_chat_find_user(conv, ...)
110 * message: serv_got_chat_in(gc, chat ID, ...)
111 * remove user: purple_conv_chat_remove_user(conv, ...)
112 * topic: purple_conv_chat_set_topic(conv, ...)
114 * libpurple -> SIPE API
115 * join_chat(gc, params (GHashTable *))
116 * request to join a channel (again) [only Group Chat]
117 * SIPE must call serv_got_joined_chat() on join response
119 * reject_chat(gc, params (GHashTable *)) NOT IMPLEMENTED
120 * get_chat_name(params (GHashTable *)) NOT IMPLEMENTED
122 * chat_invite(gc, chat ID,...)
123 * invite a user to a join a chat
125 * chat_leave(gc, chat ID)
126 * request to leave a channel, also called on conversation destroy
127 * SIPE must call serv_got_chat_left() immediately!
129 * chat_whisper(gc, chat ID, ...) NOT IMPLEMENTED
131 * chat_send(gc, chat ID, ...)
132 * send a message to the channel
134 * set_chat_topic(gc, chat ID, ...) NOT IMPLEMENTED
135 * set channel topic [@TODO: for Group Chat]
138 * struct sipe_chat_session
139 * Same life span as PurpleConversation
140 * Pointer stored under key "sipe" in PurpleConversation->data
141 * Contains information private to core to identify chat session on server
143 * If connection is closed and THEN the conversation, then libpurple will
144 * not call chat_leave() and this will be a dangling data structure! Core
145 * must take care to release them at unload.
147 * HAS: backend_session (gpointer) -> PurpleConversation
149 * struct sipe_backend_private
151 * HAS: rejoin_chats (GList *)
152 * created on login() for existing chats
153 * initiate re-join calls to core (sipe_backend_chat_rejoin_all)
156 #define SIPE_PURPLE_KEY_CHAT_SESSION "sipe"
158 struct sipe_chat_session
*sipe_purple_chat_get_session(PurpleConversation
*conv
)
161 #if PURPLE_VERSION_CHECK(3,0,0)
162 g_object_get_data(G_OBJECT(conv
),
164 purple_conversation_get_data(conv
,
166 SIPE_PURPLE_KEY_CHAT_SESSION
));
169 static struct sipe_chat_session
*sipe_purple_chat_find(PurpleConnection
*gc
,
172 PurpleConversation
*conv
= (PurpleConversation
*) purple_conversations_find_chat(gc
, id
);
175 SIPE_DEBUG_ERROR("sipe_purple_chat_find: can't find chat with ID %d?!?",
180 return sipe_purple_chat_get_session(conv
);
183 void sipe_purple_chat_setup_rejoin(struct sipe_backend_private
*purple_private
)
185 GList
*entry
= purple_conversations_get_chats();
188 PurpleConversation
*conv
= entry
->data
;
189 if (purple_conversation_get_connection(conv
) == purple_private
->gc
)
190 purple_private
->rejoin_chats
= g_list_prepend(purple_private
->rejoin_chats
,
191 sipe_purple_chat_get_session(conv
));
196 void sipe_purple_chat_destroy_rejoin(struct sipe_backend_private
*purple_private
)
198 g_list_free(purple_private
->rejoin_chats
);
199 purple_private
->rejoin_chats
= NULL
;
202 void sipe_purple_chat_invite(PurpleConnection
*gc
, int id
,
203 SIPE_UNUSED_PARAMETER
const char *message
,
206 struct sipe_chat_session
*session
= sipe_purple_chat_find(gc
, id
);
207 if (!session
) return;
209 sipe_core_chat_invite(PURPLE_GC_TO_SIPE_CORE_PUBLIC
, session
, name
);
212 void sipe_purple_chat_leave(PurpleConnection
*gc
, int id
)
214 struct sipe_chat_session
*session
= sipe_purple_chat_find(gc
, id
);
215 if (!session
) return;
217 sipe_core_chat_leave(PURPLE_GC_TO_SIPE_CORE_PUBLIC
, session
);
220 int sipe_purple_chat_send(PurpleConnection
*gc
,
222 #if PURPLE_VERSION_CHECK(3,0,0)
226 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags
)
229 struct sipe_chat_session
*session
= sipe_purple_chat_find(gc
, id
);
230 if (!session
) return -ENOTCONN
;
231 sipe_core_chat_send(PURPLE_GC_TO_SIPE_CORE_PUBLIC
, session
,
232 #if PURPLE_VERSION_CHECK(3,0,0)
233 purple_message_get_contents(msg
));
240 static void sipe_purple_chat_menu_unlock_cb(SIPE_UNUSED_PARAMETER PurpleChat
*chat
,
241 PurpleConversation
*conv
)
243 struct sipe_core_public
*sipe_public
= PURPLE_CONV_TO_SIPE_CORE_PUBLIC
;
244 struct sipe_chat_session
*chat_session
= sipe_purple_chat_get_session(conv
);
245 SIPE_DEBUG_INFO("sipe_purple_chat_menu_lock_cb: %p %p", conv
, chat_session
);
246 sipe_core_chat_modify_lock(sipe_public
, chat_session
, FALSE
);
249 static void sipe_purple_chat_menu_lock_cb(SIPE_UNUSED_PARAMETER PurpleChat
*chat
,
250 PurpleConversation
*conv
)
252 struct sipe_core_public
*sipe_public
= PURPLE_CONV_TO_SIPE_CORE_PUBLIC
;
253 struct sipe_chat_session
*chat_session
= sipe_purple_chat_get_session(conv
);
254 SIPE_DEBUG_INFO("sipe_purple_chat_menu_lock_cb: %p %p", conv
, chat_session
);
255 sipe_core_chat_modify_lock(sipe_public
, chat_session
, TRUE
);
260 static void sipe_purple_chat_menu_join_call_cb(SIPE_UNUSED_PARAMETER PurpleChat
*chat
,
261 PurpleConversation
*conv
)
263 struct sipe_core_public
*sipe_public
= PURPLE_CONV_TO_SIPE_CORE_PUBLIC
;
264 struct sipe_chat_session
*chat_session
= sipe_purple_chat_get_session(conv
);
265 SIPE_DEBUG_INFO("sipe_purple_chat_join_call_cb: %p %p", conv
, chat_session
);
266 sipe_core_media_connect_conference(sipe_public
, chat_session
);
271 static void sipe_purple_chat_menu_entry_info_cb(SIPE_UNUSED_PARAMETER PurpleChat
*chat
,
272 PurpleConversation
*conv
)
274 gchar
*tmp
= sipe_core_conf_entry_info(PURPLE_CONV_TO_SIPE_CORE_PUBLIC
,
275 sipe_purple_chat_get_session(conv
));
276 purple_notify_formatted(NULL
, NULL
, "", NULL
, tmp
, NULL
, NULL
);
281 sipe_purple_chat_menu(PurpleChat
*chat
)
283 PurpleConversation
*conv
= g_hash_table_lookup(purple_chat_get_components(chat
),
284 SIPE_PURPLE_COMPONENT_KEY_CONVERSATION
);
288 PurpleMenuAction
*act
= NULL
;
290 SIPE_DEBUG_INFO("sipe_purple_chat_menu: %p", conv
);
292 switch (sipe_core_chat_lock_status(PURPLE_CONV_TO_SIPE_CORE_PUBLIC
,
293 sipe_purple_chat_get_session(conv
))) {
294 case SIPE_CHAT_LOCK_STATUS_UNLOCKED
:
295 act
= purple_menu_action_new(_("Lock"),
296 PURPLE_CALLBACK(sipe_purple_chat_menu_lock_cb
),
299 case SIPE_CHAT_LOCK_STATUS_LOCKED
:
300 act
= purple_menu_action_new(_("Unlock"),
301 PURPLE_CALLBACK(sipe_purple_chat_menu_unlock_cb
),
310 menu
= g_list_prepend(menu
, act
);
312 if (!sipe_core_media_get_call(PURPLE_CONV_TO_SIPE_CORE_PUBLIC
)) {
314 act
= purple_menu_action_new(_("Join conference call"),
315 PURPLE_CALLBACK(sipe_purple_chat_menu_join_call_cb
),
318 menu
= g_list_prepend(menu
, act
);
321 act
= purple_menu_action_new(_("Meeting entry info"),
322 PURPLE_CALLBACK(sipe_purple_chat_menu_entry_info_cb
),
324 menu
= g_list_append(menu
, act
);
330 void sipe_backend_chat_session_destroy(SIPE_UNUSED_PARAMETER
struct sipe_backend_chat_session
*session
)
332 /* Nothing to do here */
335 void sipe_backend_chat_add(struct sipe_backend_chat_session
*backend_session
,
339 purple_chat_conversation_add_user(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
),
342 PURPLE_CHAT_USER_NONE
,
346 void sipe_backend_chat_close(struct sipe_backend_chat_session
*backend_session
)
348 purple_chat_conversation_clear_users(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
));
351 static int sipe_purple_chat_id(PurpleConnection
*gc
)
354 * A non-volatile ID counter.
355 * Should survive connection drop & reconnect.
357 static int chat_id
= 0;
359 /* Find next free ID */
361 if (++chat_id
< 0) chat_id
= 0;
362 } while (purple_conversations_find_chat(gc
, chat_id
) != NULL
)
367 struct sipe_backend_chat_session
*sipe_backend_chat_create(struct sipe_core_public
*sipe_public
,
368 struct sipe_chat_session
*session
,
372 struct sipe_backend_private
*purple_private
= sipe_public
->backend_private
;
373 #if PURPLE_VERSION_CHECK(3,0,0)
374 PurpleChatConversation
*conv
;
376 PurpleConversation
*conv
;
380 * Adium calls back into SIPE code during execution of the following
381 * libpurple API. That code needs access to "session". As "conv" is
382 * still being initialized we can't use sipe_purple_chat_get_session().
384 purple_private
->adium_chat_session
= session
;
385 conv
= purple_serv_got_joined_chat(purple_private
->gc
,
386 sipe_purple_chat_id(purple_private
->gc
),
388 purple_private
->adium_chat_session
= NULL
;
389 #if PURPLE_VERSION_CHECK(3,0,0)
390 g_object_set_data(G_OBJECT(conv
),
392 purple_conversation_set_data(conv
,
394 SIPE_PURPLE_KEY_CHAT_SESSION
,
396 purple_chat_conversation_set_nick(PURPLE_CONV_CHAT(conv
), nick
);
397 return((struct sipe_backend_chat_session
*) conv
);
400 gboolean
sipe_backend_chat_find(struct sipe_backend_chat_session
*backend_session
,
403 #if PURPLE_VERSION_CHECK(3,0,0)
404 return(purple_chat_conversation_find_user(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
),
407 return purple_conv_chat_find_user(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
),
412 gboolean
sipe_backend_chat_is_operator(struct sipe_backend_chat_session
*backend_session
,
415 #if PURPLE_VERSION_CHECK(3,0,0)
416 return((purple_chat_user_get_flags(
417 purple_chat_conversation_find_user(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
),
419 & PURPLE_CHAT_USER_OP
)
420 == PURPLE_CHAT_USER_OP
);
422 return (purple_conv_chat_user_get_flags(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
),
423 uri
) & PURPLE_CBFLAGS_OP
)
424 == PURPLE_CBFLAGS_OP
;
428 void sipe_backend_chat_message(struct sipe_core_public
*sipe_public
,
429 struct sipe_backend_chat_session
*backend_session
,
434 struct sipe_backend_private
*purple_private
= sipe_public
->backend_private
;
435 purple_serv_got_chat_in(purple_private
->gc
,
436 purple_chat_conversation_get_id(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
)),
440 when
? when
: time(NULL
));
443 void sipe_backend_chat_operator(struct sipe_backend_chat_session
*backend_session
,
446 #if PURPLE_VERSION_CHECK(3,0,0)
447 purple_chat_user_set_flags(
448 purple_chat_conversation_find_user(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
),
450 PURPLE_CHAT_USER_NONE
| PURPLE_CHAT_USER_OP
);
452 purple_conv_chat_user_set_flags(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
),
454 PURPLE_CBFLAGS_NONE
| PURPLE_CBFLAGS_OP
);
458 void sipe_backend_chat_rejoin(struct sipe_core_public
*sipe_public
,
459 struct sipe_backend_chat_session
*backend_session
,
463 struct sipe_backend_private
*purple_private
= sipe_public
->backend_private
;
464 #if PURPLE_VERSION_CHECK(3,0,0)
465 PurpleChatConversation
*chat
= BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
);
466 PurpleChatConversation
*new;
468 PurpleConvChat
*chat
= BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
);
469 PurpleConversation
*new;
473 * As the chat is marked as "left", serv_got_joined_chat() will
474 * do a "rejoin cleanup" and return the same conversation.
476 new = purple_serv_got_joined_chat(purple_private
->gc
,
477 purple_chat_conversation_get_id(chat
),
479 SIPE_DEBUG_INFO("sipe_backend_chat_rejoin: old %p (%p) == new %p (%p)",
480 backend_session
, chat
,
481 new, PURPLE_CONV_CHAT(new));
482 purple_chat_conversation_set_nick(chat
, nick
);
486 * Connection re-established: tell core what chats need to be rejoined
488 void sipe_backend_chat_rejoin_all(struct sipe_core_public
*sipe_public
)
490 struct sipe_backend_private
*purple_private
= sipe_public
->backend_private
;
491 GList
*entry
= purple_private
->rejoin_chats
;
494 sipe_core_chat_rejoin(sipe_public
, entry
->data
);
497 sipe_purple_chat_destroy_rejoin(purple_private
);
500 void sipe_backend_chat_remove(struct sipe_backend_chat_session
*backend_session
,
503 purple_chat_conversation_remove_user(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
),
508 void sipe_backend_chat_show(struct sipe_backend_chat_session
*backend_session
)
510 /* Bring existing purple chat to the front */
511 /* @TODO: This seems to the trick, but is it the correct way? */
512 purple_conversation_update((PurpleConversation
*) backend_session
,
513 PURPLE_CONVERSATION_UPDATE_TOPIC
);
516 void sipe_backend_chat_topic(struct sipe_backend_chat_session
*backend_session
,
519 purple_chat_conversation_set_topic(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session
),