Release 1.25.0 -- Buddy Idle Time, RTF
[siplcs.git] / src / purple / purple-chat.c
blob468be559302ccc3d8501038d661a8926154516c8
1 /**
2 * @file purple-chat.c
4 * pidgin-sipe
6 * Copyright (C) 2010-2019 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
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
29 #include <time.h>
31 #include <glib.h>
33 #include "conversation.h"
34 #include "server.h"
35 /* for ENOTCONN */
36 #ifdef _WIN32
37 #include "win32/win32dep.h"
38 #else
39 #include <errno.h>
40 #endif
42 #include "version.h"
43 #if PURPLE_VERSION_CHECK(3,0,0)
44 #include "action.h"
45 #include "buddylist.h"
46 #include "conversations.h"
47 #define BACKEND_SESSION_TO_PURPLE_CONV_CHAT(s) ((PurpleChatConversation *) s)
48 #define PURPLE_CONV_CHAT(c) c
49 #define PURPLE_CONV_TO_SIPE_CORE_PUBLIC ((struct sipe_core_public *) purple_connection_get_protocol_data(purple_conversation_get_connection(conv)))
50 #else
51 #include "blist.h"
52 #define purple_action_menu_new(l, c, d, ch) purple_menu_action_new(l, c, d, ch)
53 #define purple_chat_conversation_add_user(c, n, m, f, b) purple_conv_chat_add_user(c, n, m, f, b)
54 #define purple_chat_conversation_clear_users(c) purple_conv_chat_clear_users(c)
55 #define purple_chat_conversation_get_id(c) purple_conv_chat_get_id(c)
56 #define purple_chat_conversation_remove_user(c, n, s) purple_conv_chat_remove_user(c, n, s)
57 #define purple_chat_conversation_set_nick(c, n) purple_conv_chat_set_nick(c, n)
58 #define purple_chat_conversation_set_topic(c, n, s) purple_conv_chat_set_topic(c, n, s)
59 #define purple_chat_get_components(chat) chat->components
60 #define purple_conversations_find_chat(g, n) purple_find_chat(g, n)
61 #define purple_conversations_get_chats purple_get_chats
62 #define purple_conversation_get_connection(c) purple_conversation_get_gc(c)
63 #define purple_serv_got_chat_in(c, i, w, f, m, t) serv_got_chat_in(c, i, w, f, m, t)
64 #define purple_serv_got_joined_chat(c, i, n) serv_got_joined_chat(c, i, n)
65 #define PurpleActionMenu PurpleMenuAction
66 #define BACKEND_SESSION_TO_PURPLE_CONV_CHAT(s) (PURPLE_CONV_CHAT(((PurpleConversation *)s)))
67 #define PURPLE_CHAT_USER_NONE PURPLE_CBFLAGS_NONE
68 #define PURPLE_CONV_TO_SIPE_CORE_PUBLIC ((struct sipe_core_public *) conv->account->gc->proto_data)
69 #define PURPLE_CONVERSATION_UPDATE_TOPIC PURPLE_CONV_UPDATE_TOPIC
70 #endif
72 #include "sipe-common.h"
73 #include "sipe-backend.h"
74 #include "sipe-core.h"
75 #include "sipe-nls.h"
77 #define _PurpleMessageFlags PurpleMessageFlags
78 #include "purple-private.h"
80 /**
81 * Mapping between chat sessions in SIPE core and libpurple backend
83 * PurpleAccount
84 * This data structure is created when the user creates the account or at
85 * startup. It lives as long as the account exists, i.e. until the user
86 * deletes it or shutdown.
88 * Value does not change when connection is dropped & re-created.
89 * HAS: gc (PurpleConnection *)
91 * PurpleConversation / PurpleConvChat (sub-type)
92 * This data structure is created by serv_got_join_chat(). It lives as long
93 * as the user doesn't leave the chat or until shutdown.
95 * Value does not change when connection is dropped & re-created.
96 * HAS: account (PurpleAccount *)
97 * HAS: chat ID (int), must be unique
98 * HAS: name (char *), must be unique
99 * HAS: data (GHashTable *)
101 * PurpleConnection
102 * This data structure is created when the connection to the service is
103 * set up. It lives as long as the connection stays open, the user disables
104 * the account or until shutdown.
106 * Value *DOES NOT* survive a connection drop & re-creation.
107 * ASSOCIATED TO: account
109 * SIPE -> libpurple API
110 * add user: purple_conv_chat_add_user(conv, ...)
111 * create: serv_got_joined_chat(gc, chat ID, name)
112 * find user: purple_conv_chat_find_user(conv, ...)
113 * message: serv_got_chat_in(gc, chat ID, ...)
114 * remove user: purple_conv_chat_remove_user(conv, ...)
115 * topic: purple_conv_chat_set_topic(conv, ...)
117 * libpurple -> SIPE API
118 * join_chat(gc, params (GHashTable *))
119 * request to join a channel (again) [only Group Chat]
120 * SIPE must call serv_got_joined_chat() on join response
122 * reject_chat(gc, params (GHashTable *)) NOT IMPLEMENTED
123 * get_chat_name(params (GHashTable *)) NOT IMPLEMENTED
125 * chat_invite(gc, chat ID,...)
126 * invite a user to a join a chat
128 * chat_leave(gc, chat ID)
129 * request to leave a channel, also called on conversation destroy
130 * SIPE must call serv_got_chat_left() immediately!
132 * chat_whisper(gc, chat ID, ...) NOT IMPLEMENTED
134 * chat_send(gc, chat ID, ...)
135 * send a message to the channel
137 * set_chat_topic(gc, chat ID, ...) NOT IMPLEMENTED
138 * set channel topic [@TODO: for Group Chat]
141 * struct sipe_chat_session
142 * Same life span as PurpleConversation
143 * Pointer stored under key "sipe" in PurpleConversation->data
144 * Contains information private to core to identify chat session on server
146 * If connection is closed and THEN the conversation, then libpurple will
147 * not call chat_leave() and this will be a dangling data structure! Core
148 * must take care to release them at unload.
150 * HAS: backend_session (gpointer) -> PurpleConversation
152 * struct sipe_backend_private
154 * HAS: rejoin_chats (GList *)
155 * created on login() for existing chats
156 * initiate re-join calls to core (sipe_backend_chat_rejoin_all)
159 #define SIPE_PURPLE_KEY_CHAT_SESSION "sipe"
161 struct sipe_chat_session *sipe_purple_chat_get_session(PurpleConversation *conv)
163 return(
164 #if PURPLE_VERSION_CHECK(3,0,0)
165 g_object_get_data(G_OBJECT(conv),
166 #else
167 purple_conversation_get_data(conv,
168 #endif
169 SIPE_PURPLE_KEY_CHAT_SESSION));
172 static struct sipe_chat_session *sipe_purple_chat_find(PurpleConnection *gc,
173 int id)
175 PurpleConversation *conv = (PurpleConversation *) purple_conversations_find_chat(gc, id);
177 if (!conv) {
178 SIPE_DEBUG_ERROR("sipe_purple_chat_find: can't find chat with ID %d?!?",
179 id);
180 return NULL;
183 return sipe_purple_chat_get_session(conv);
186 void sipe_purple_chat_setup_rejoin(struct sipe_backend_private *purple_private)
188 GList *entry = purple_conversations_get_chats();
190 while (entry) {
191 PurpleConversation *conv = entry->data;
192 if (purple_conversation_get_connection(conv) == purple_private->gc)
193 purple_private->rejoin_chats = g_list_prepend(purple_private->rejoin_chats,
194 sipe_purple_chat_get_session(conv));
195 entry = entry->next;
199 void sipe_purple_chat_destroy_rejoin(struct sipe_backend_private *purple_private)
201 g_list_free(purple_private->rejoin_chats);
202 purple_private->rejoin_chats = NULL;
205 void sipe_purple_chat_invite(PurpleConnection *gc, int id,
206 SIPE_UNUSED_PARAMETER const char *message,
207 const char *name)
209 struct sipe_chat_session *session = sipe_purple_chat_find(gc, id);
210 if (!session) return;
212 sipe_core_chat_invite(PURPLE_GC_TO_SIPE_CORE_PUBLIC, session, name);
215 void sipe_purple_chat_leave(PurpleConnection *gc, int id)
217 struct sipe_chat_session *session = sipe_purple_chat_find(gc, id);
218 if (!session) return;
220 sipe_core_chat_leave(PURPLE_GC_TO_SIPE_CORE_PUBLIC, session);
223 int sipe_purple_chat_send(PurpleConnection *gc,
224 int id,
225 #if PURPLE_VERSION_CHECK(3,0,0)
226 PurpleMessage *msg)
227 #else
228 const char *what,
229 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
230 #endif
232 struct sipe_chat_session *session = sipe_purple_chat_find(gc, id);
233 if (!session) return -ENOTCONN;
234 sipe_core_chat_send(PURPLE_GC_TO_SIPE_CORE_PUBLIC, session,
235 #if PURPLE_VERSION_CHECK(3,0,0)
236 purple_message_get_contents(msg));
237 #else
238 what);
239 #endif
240 return 1;
243 static void sipe_purple_chat_menu_unlock_cb(SIPE_UNUSED_PARAMETER PurpleChat *chat,
244 PurpleConversation *conv)
246 struct sipe_core_public *sipe_public = PURPLE_CONV_TO_SIPE_CORE_PUBLIC;
247 struct sipe_chat_session *chat_session = sipe_purple_chat_get_session(conv);
248 SIPE_DEBUG_INFO("sipe_purple_chat_menu_lock_cb: %p %p", conv, chat_session);
249 sipe_core_chat_modify_lock(sipe_public, chat_session, FALSE);
252 static void sipe_purple_chat_menu_lock_cb(SIPE_UNUSED_PARAMETER PurpleChat *chat,
253 PurpleConversation *conv)
255 struct sipe_core_public *sipe_public = PURPLE_CONV_TO_SIPE_CORE_PUBLIC;
256 struct sipe_chat_session *chat_session = sipe_purple_chat_get_session(conv);
257 SIPE_DEBUG_INFO("sipe_purple_chat_menu_lock_cb: %p %p", conv, chat_session);
258 sipe_core_chat_modify_lock(sipe_public, chat_session, TRUE);
261 #ifdef HAVE_VV
263 static void sipe_purple_chat_menu_join_call_cb(SIPE_UNUSED_PARAMETER PurpleChat *chat,
264 PurpleConversation *conv)
266 struct sipe_core_public *sipe_public = PURPLE_CONV_TO_SIPE_CORE_PUBLIC;
267 struct sipe_chat_session *chat_session = sipe_purple_chat_get_session(conv);
268 SIPE_DEBUG_INFO("sipe_purple_chat_join_call_cb: %p %p", conv, chat_session);
269 sipe_core_media_connect_conference(sipe_public, chat_session);
272 #ifdef HAVE_APPSHARE
273 static void
274 sipe_purple_chat_menu_show_presentation_cb(SIPE_UNUSED_PARAMETER PurpleChat *chat,
275 PurpleConversation *conv)
277 sipe_appshare_role role;
279 role = sipe_core_conf_get_appshare_role(PURPLE_CONV_TO_SIPE_CORE_PUBLIC,
280 sipe_purple_chat_get_session(conv));
282 if (role == SIPE_APPSHARE_ROLE_VIEWER) {
283 return;
286 sipe_core_appshare_connect_conference(PURPLE_CONV_TO_SIPE_CORE_PUBLIC,
287 sipe_purple_chat_get_session(conv),
288 FALSE);
291 #ifdef HAVE_APPSHARE_SERVER
292 static void
293 sipe_purple_chat_menu_share_desktop_cb(SIPE_UNUSED_PARAMETER PurpleChat *chat,
294 PurpleConversation *conv)
296 sipe_core_conf_share_desktop(PURPLE_CONV_TO_SIPE_CORE_PUBLIC,
297 sipe_purple_chat_get_session(conv));
299 #endif
300 #endif
301 #endif // HAVE_VV
303 static void sipe_purple_chat_menu_entry_info_cb(SIPE_UNUSED_PARAMETER PurpleChat *chat,
304 PurpleConversation *conv)
306 gchar *tmp = sipe_core_conf_entry_info(PURPLE_CONV_TO_SIPE_CORE_PUBLIC,
307 sipe_purple_chat_get_session(conv));
308 purple_notify_formatted(NULL, NULL, "", NULL, tmp, NULL, NULL);
309 g_free(tmp);
312 GList *
313 sipe_purple_chat_menu(PurpleChat *chat)
315 PurpleConversation *conv = g_hash_table_lookup(purple_chat_get_components(chat),
316 SIPE_PURPLE_COMPONENT_KEY_CONVERSATION);
317 GList *menu = NULL;
319 if (conv) {
320 PurpleActionMenu *act = NULL;
321 struct sipe_chat_session *chat_session;
322 #ifdef HAVE_APPSHARE
323 sipe_appshare_role role;
324 #endif
326 SIPE_DEBUG_INFO("sipe_purple_chat_menu: %p", conv);
328 chat_session = sipe_purple_chat_get_session(conv);
330 switch (sipe_core_chat_lock_status(PURPLE_CONV_TO_SIPE_CORE_PUBLIC,
331 chat_session)) {
332 case SIPE_CHAT_LOCK_STATUS_UNLOCKED:
333 act = purple_action_menu_new(_("Lock"),
334 PURPLE_CALLBACK(sipe_purple_chat_menu_lock_cb),
335 conv, NULL);
336 break;
337 case SIPE_CHAT_LOCK_STATUS_LOCKED:
338 act = purple_action_menu_new(_("Unlock"),
339 PURPLE_CALLBACK(sipe_purple_chat_menu_unlock_cb),
340 conv, NULL);
341 break;
342 default:
343 /* Not allowed */
344 break;
347 if (act)
348 menu = g_list_prepend(menu, act);
350 switch (sipe_core_chat_type(chat_session)) {
351 case SIPE_CHAT_TYPE_CONFERENCE:
352 case SIPE_CHAT_TYPE_MULTIPARTY:
353 #ifdef HAVE_VV
354 if (!sipe_core_media_get_call(PURPLE_CONV_TO_SIPE_CORE_PUBLIC)) {
355 act = NULL;
356 act = purple_action_menu_new(_("Join conference call"),
357 PURPLE_CALLBACK(sipe_purple_chat_menu_join_call_cb),
358 conv, NULL);
359 if (act)
360 menu = g_list_prepend(menu, act);
362 #ifdef HAVE_APPSHARE
363 role = sipe_core_conf_get_appshare_role(PURPLE_CONV_TO_SIPE_CORE_PUBLIC,
364 chat_session);
365 if (role == SIPE_APPSHARE_ROLE_NONE) {
366 act = purple_action_menu_new(_("Show presentation"),
367 PURPLE_CALLBACK(sipe_purple_chat_menu_show_presentation_cb),
368 conv, NULL);
369 menu = g_list_prepend(menu, act);
371 #ifdef HAVE_APPSHARE_SERVER
372 if (role != SIPE_APPSHARE_ROLE_PRESENTER) {
373 act = purple_action_menu_new(_("Share my desktop"),
374 PURPLE_CALLBACK(sipe_purple_chat_menu_share_desktop_cb),
375 conv, NULL);
376 menu = g_list_prepend(menu, act);
378 #endif
379 #endif
380 #endif // HAVE_VV
381 act = purple_action_menu_new(_("Meeting entry info"),
382 PURPLE_CALLBACK(sipe_purple_chat_menu_entry_info_cb),
383 conv, NULL);
384 menu = g_list_append(menu, act);
385 break;
386 default:
387 break;
391 return menu;
394 void sipe_backend_chat_session_destroy(SIPE_UNUSED_PARAMETER struct sipe_backend_chat_session *session)
396 /* Nothing to do here */
399 void sipe_backend_chat_add(struct sipe_backend_chat_session *backend_session,
400 const gchar *uri,
401 gboolean is_new)
403 purple_chat_conversation_add_user(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session),
404 uri,
405 NULL,
406 PURPLE_CHAT_USER_NONE,
407 is_new);
410 void sipe_backend_chat_close(struct sipe_backend_chat_session *backend_session)
412 purple_chat_conversation_clear_users(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session));
415 static int sipe_purple_chat_id(PurpleConnection *gc)
418 * A non-volatile ID counter.
419 * Should survive connection drop & reconnect.
421 static int chat_id = 0;
423 /* Find next free ID */
424 do {
425 if (++chat_id < 0) chat_id = 0;
426 } while (purple_conversations_find_chat(gc, chat_id) != NULL)
428 return chat_id;
431 struct sipe_backend_chat_session *sipe_backend_chat_create(struct sipe_core_public *sipe_public,
432 struct sipe_chat_session *session,
433 const gchar *title,
434 const gchar *nick)
436 struct sipe_backend_private *purple_private = sipe_public->backend_private;
437 #if PURPLE_VERSION_CHECK(3,0,0)
438 PurpleChatConversation *conv;
439 #else
440 PurpleConversation *conv;
441 #endif
444 * Adium calls back into SIPE code during execution of the following
445 * libpurple API. That code needs access to "session". As "conv" is
446 * still being initialized we can't use sipe_purple_chat_get_session().
448 purple_private->adium_chat_session = session;
449 conv = purple_serv_got_joined_chat(purple_private->gc,
450 sipe_purple_chat_id(purple_private->gc),
451 title);
452 purple_private->adium_chat_session = NULL;
453 #if PURPLE_VERSION_CHECK(3,0,0)
454 g_object_set_data(G_OBJECT(conv),
455 #else
456 purple_conversation_set_data(conv,
457 #endif
458 SIPE_PURPLE_KEY_CHAT_SESSION,
459 session);
460 purple_chat_conversation_set_nick(PURPLE_CONV_CHAT(conv), nick);
461 return((struct sipe_backend_chat_session *) conv);
464 gboolean sipe_backend_chat_find(struct sipe_backend_chat_session *backend_session,
465 const gchar *uri)
467 #if PURPLE_VERSION_CHECK(3,0,0)
468 return(purple_chat_conversation_find_user(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session),
469 uri) != NULL);
470 #else
471 return purple_conv_chat_find_user(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session),
472 uri);
473 #endif
476 gboolean sipe_backend_chat_is_operator(struct sipe_backend_chat_session *backend_session,
477 const gchar *uri)
479 #if PURPLE_VERSION_CHECK(3,0,0)
480 return((purple_chat_user_get_flags(
481 purple_chat_conversation_find_user(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session),
482 uri))
483 & PURPLE_CHAT_USER_OP)
484 == PURPLE_CHAT_USER_OP);
485 #else
486 return (purple_conv_chat_user_get_flags(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session),
487 uri) & PURPLE_CBFLAGS_OP)
488 == PURPLE_CBFLAGS_OP;
489 #endif
492 void sipe_backend_chat_message(struct sipe_core_public *sipe_public,
493 struct sipe_backend_chat_session *backend_session,
494 const gchar *from,
495 time_t when,
496 const gchar *html)
498 struct sipe_backend_private *purple_private = sipe_public->backend_private;
499 purple_serv_got_chat_in(purple_private->gc,
500 purple_chat_conversation_get_id(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session)),
501 from,
502 PURPLE_MESSAGE_RECV,
503 html,
504 when ? when : time(NULL));
507 void sipe_backend_chat_operator(struct sipe_backend_chat_session *backend_session,
508 const gchar *uri)
510 #if PURPLE_VERSION_CHECK(3,0,0)
511 purple_chat_user_set_flags(
512 purple_chat_conversation_find_user(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session),
513 uri),
514 PURPLE_CHAT_USER_NONE | PURPLE_CHAT_USER_OP);
515 #else
516 purple_conv_chat_user_set_flags(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session),
517 uri,
518 PURPLE_CBFLAGS_NONE | PURPLE_CBFLAGS_OP);
519 #endif
522 void sipe_backend_chat_rejoin(struct sipe_core_public *sipe_public,
523 struct sipe_backend_chat_session *backend_session,
524 const gchar *nick,
525 const gchar *title)
527 struct sipe_backend_private *purple_private = sipe_public->backend_private;
528 #if PURPLE_VERSION_CHECK(3,0,0)
529 PurpleChatConversation *chat = BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session);
530 PurpleChatConversation *new;
531 #else
532 PurpleConvChat *chat = BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session);
533 PurpleConversation *new;
534 #endif
537 * As the chat is marked as "left", serv_got_joined_chat() will
538 * do a "rejoin cleanup" and return the same conversation.
540 new = purple_serv_got_joined_chat(purple_private->gc,
541 purple_chat_conversation_get_id(chat),
542 title);
543 SIPE_DEBUG_INFO("sipe_backend_chat_rejoin: old %p (%p) == new %p (%p)",
544 backend_session, chat,
545 new, PURPLE_CONV_CHAT(new));
546 purple_chat_conversation_set_nick(chat, nick);
550 * Connection re-established: tell core what chats need to be rejoined
552 void sipe_backend_chat_rejoin_all(struct sipe_core_public *sipe_public)
554 struct sipe_backend_private *purple_private = sipe_public->backend_private;
555 GList *entry = purple_private->rejoin_chats;
557 while (entry) {
558 sipe_core_chat_rejoin(sipe_public, entry->data);
559 entry = entry->next;
561 sipe_purple_chat_destroy_rejoin(purple_private);
564 void sipe_backend_chat_remove(struct sipe_backend_chat_session *backend_session,
565 const gchar *uri)
567 purple_chat_conversation_remove_user(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session),
568 uri,
569 NULL /* reason */);
572 void sipe_backend_chat_show(struct sipe_backend_chat_session *backend_session)
574 /* Bring existing purple chat to the front */
575 /* @TODO: This seems to the trick, but is it the correct way? */
576 purple_conversation_update((PurpleConversation *) backend_session,
577 PURPLE_CONVERSATION_UPDATE_TOPIC);
580 void sipe_backend_chat_topic(struct sipe_backend_chat_session *backend_session,
581 const gchar *topic)
583 purple_chat_conversation_set_topic(BACKEND_SESSION_TO_PURPLE_CONV_CHAT(backend_session),
584 NULL,
585 topic);
589 Local Variables:
590 mode: c
591 c-file-style: "bsd"
592 indent-tabs-mode: t
593 tab-width: 8
594 End: