comparison src/protocols/sametime/sametime.c @ 10977:2ce8ec01a064

[gaim-migrate @ 12803] adding sametime support to the build committer: Tailor Script <tailor@pidgin.im>
author Christopher O'Brien <siege@pidgin.im>
date Tue, 07 Jun 2005 02:52:39 +0000
parents
children a4611130e3eb
comparison
equal deleted inserted replaced
10976:be22eb8fa671 10977:2ce8ec01a064
1
2 /*
3 Meanwhile Protocol Plugin for Gaim
4 Adds Lotus Sametime support to Gaim using the Meanwhile library
5
6 Copyright (C) 2004 Christopher (siege) O'Brien <siege@preoccupied.net>
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or (at
11 your option) any later version.
12
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
21 USA.
22 */
23
24 #include <stdlib.h>
25
26 #include <internal.h>
27 #include <gaim.h>
28 #include <config.h>
29
30 #include <account.h>
31 #include <accountopt.h>
32 #include <conversation.h>
33 #include <debug.h>
34 #include <ft.h>
35 #include <imgstore.h>
36 #include <mime.h>
37 #include <notify.h>
38 #include <plugin.h>
39 #include <privacy.h>
40 #include <prpl.h>
41 #include <request.h>
42 #include <util.h>
43 #include <version.h>
44
45 #include <glib.h>
46 #include <glib/ghash.h>
47 #include <glib/glist.h>
48
49 #include <mw_cipher.h>
50 #include <mw_common.h>
51 #include <mw_error.h>
52 #include <mw_service.h>
53 #include <mw_session.h>
54 #include <mw_srvc_aware.h>
55 #include <mw_srvc_conf.h>
56 #include <mw_srvc_ft.h>
57 #include <mw_srvc_im.h>
58 #include <mw_srvc_resolve.h>
59 #include <mw_srvc_store.h>
60 #include <mw_st_list.h>
61
62 #include "sametime.h"
63
64
65 /* considering that there's no display of this information for prpls,
66 I don't know why I even bother providing these. Oh valiant reader,
67 I do it all for you. */
68 /* scratch that, I just added it to the prpl options panel */
69 #define PLUGIN_ID "prpl-meanwhile"
70 #define PLUGIN_NAME "Sametime"
71 #define PLUGIN_SUMMARY "Sametime Protocol Plugin"
72 #define PLUGIN_DESC "Open implementation of a Lotus Sametime client"
73 #define PLUGIN_AUTHOR "Christopher (siege) O'Brien <siege@preoccupied.net>"
74 #define PLUGIN_HOMEPAGE "http://meanwhile.sourceforge.net/"
75
76
77 /* plugin preference names */
78 #define MW_PRPL_OPT_BASE "/plugins/prpl/meanwhile"
79 #define MW_PRPL_OPT_BLIST_ACTION MW_PRPL_OPT_BASE "/blist_action"
80 #define MW_PRPL_OPT_PSYCHIC MW_PRPL_OPT_BASE "/psychic"
81 #define MW_PRPL_OPT_FORCE_LOGIN MW_PRPL_OPT_BASE "/force_login"
82 #define MW_PRPL_OPT_SAVE_DYNAMIC MW_PRPL_OPT_BASE "/save_dynamic"
83
84
85 /* stages of connecting-ness */
86 #define MW_CONNECT_STEPS 9
87
88
89 /* stages of conciousness */
90 #define MW_STATE_OFFLINE "offline"
91 #define MW_STATE_ONLINE "online"
92 #define MW_STATE_ACTIVE "available"
93 #define MW_STATE_AWAY "away"
94 #define MW_STATE_BUSY "busy"
95 #define MW_STATE_IDLE "idle"
96 #define MW_STATE_UNKNOWN "unknown"
97 #define MW_STATE_BUDHA "enlightened"
98
99 #define MW_STATE_MESSAGE "message"
100
101
102 /* keys to get/set chat information */
103 #define CHAT_KEY_CREATOR "chat.creator"
104 #define CHAT_KEY_NAME "chat.name"
105 #define CHAT_KEY_TOPIC "chat.topic"
106 #define CHAT_KEY_INVITE "chat.invite"
107
108
109 /* key for associating a mwLoginType with a buddy */
110 #define BUDDY_KEY_CLIENT "meanwhile.client"
111
112 /* store the remote alias so that we can re-create it easily */
113 #define BUDDY_KEY_NAME "meanwhile.shortname"
114
115 /* enum mwSametimeUserType */
116 #define BUDDY_KEY_TYPE "meanwhile.type"
117
118
119 /* key for the real group name for a meanwhile group */
120 #define GROUP_KEY_NAME "meanwhile.group"
121
122 /* enum mwSametimeGroupType */
123 #define GROUP_KEY_TYPE "meanwhile.type"
124
125 /* NAB group owning account */
126 #define GROUP_KEY_OWNER "meanwhile.account"
127
128 /* key gtk blist uses to indicate a collapsed group */
129 #define GROUP_KEY_COLLAPSED "collapsed"
130
131
132 #define SESSION_NO_SECRET "meanwhile.no_secret"
133
134
135 /* keys to get/set gaim plugin information */
136 #define MW_KEY_HOST "server"
137 #define MW_KEY_PORT "port"
138 #define MW_KEY_ACTIVE_MSG "active_msg"
139 #define MW_KEY_AWAY_MSG "away_msg"
140 #define MW_KEY_BUSY_MSG "busy_msg"
141 #define MW_KEY_MSG_PROMPT "msg_prompt"
142 #define MW_KEY_INVITE "conf_invite"
143
144
145 /** number of seconds from the first blist change before a save to the
146 storage service occurs. */
147 #define BLIST_SAVE_SECONDS 15
148
149
150 /** blist storage option, local only */
151 #define BLIST_CHOICE_NONE 1
152
153 /** blist storage option, load from server */
154 #define BLIST_CHOICE_LOAD 2
155
156 /** blist storage option, load and save to server */
157 #define BLIST_CHOICE_SAVE 3
158
159 /** blist storage option, server only */
160 #define BLIST_CHOICE_SERVER 4
161
162
163 /** the default blist storage option */
164 #define BLIST_CHOICE_DEFAULT BLIST_CHOICE_SAVE
165
166
167 /* testing for the above */
168 #define BLIST_CHOICE_IS(n) (gaim_prefs_get_int(MW_PRPL_OPT_BLIST_ACTION)==(n))
169 #define BLIST_CHOICE_IS_NONE() BLIST_CHOICE_IS(BLIST_CHOICE_NONE)
170 #define BLIST_CHOICE_IS_LOAD() BLIST_CHOICE_IS(BLIST_CHOICE_LOAD)
171 #define BLIST_CHOICE_IS_SAVE() BLIST_CHOICE_IS(BLIST_CHOICE_SAVE)
172
173
174 /** warning text placed next to blist option */
175 #define BLIST_WARNING \
176 ("Please note:\n" \
177 "The 'merge and save' option above is still mildly experimental." \
178 " You should back-up your buddy list with an official client" \
179 " before enabling this option. Loading takes effect at login.")
180
181
182 /* debugging output */
183 #define DEBUG_ERROR(a...) gaim_debug_error(G_LOG_DOMAIN, a)
184 #define DEBUG_INFO(a...) gaim_debug_info(G_LOG_DOMAIN, a)
185 #define DEBUG_MISC(a...) gaim_debug_misc(G_LOG_DOMAIN, a)
186 #define DEBUG_WARN(a...) gaim_debug_warning(G_LOG_DOMAIN, a)
187
188
189 /** ensure non-null strings */
190 #ifndef NSTR
191 # define NSTR(str) ((str)? (str): "(null)")
192 #endif
193
194
195 /** calibrates distinct secure channel nomenclature */
196 static const unsigned char no_secret[] = {
197 0x2d, 0x2d, 0x20, 0x73, 0x69, 0x65, 0x67, 0x65,
198 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x73, 0x20, 0x6a,
199 0x65, 0x6e, 0x6e, 0x69, 0x20, 0x61, 0x6e, 0x64,
200 0x20, 0x7a, 0x6f, 0x65, 0x20, 0x2d, 0x2d, 0x00,
201 };
202
203
204 /** handler IDs from g_log_set_handler in mw_plugin_init */
205 static guint log_handler[2] = { 0, 0 };
206
207
208 /** the gaim plugin data.
209 available as gc->proto_data and mwSession_getClientData */
210 struct mwGaimPluginData {
211 struct mwSession *session;
212
213 struct mwServiceAware *srvc_aware;
214 struct mwServiceConference *srvc_conf;
215 struct mwServiceFileTransfer *srvc_ft;
216 struct mwServiceIm *srvc_im;
217 struct mwServiceResolve *srvc_resolve;
218 struct mwServiceStorage *srvc_store;
219
220 /** map of GaimGroup:mwAwareList and mwAwareList:GaimGroup */
221 GHashTable *group_list_map;
222
223 /** event id for the buddy list save callback */
224 guint save_event;
225
226 /** socket fd */
227 int socket;
228
229 GaimConnection *gc;
230 };
231
232
233 /* blist and aware functions */
234
235 static void blist_export(GaimConnection *gc, struct mwSametimeList *stlist);
236
237 static void blist_store(struct mwGaimPluginData *pd);
238
239 static void blist_schedule(struct mwGaimPluginData *pd);
240
241 static void blist_import(GaimConnection *gc, struct mwSametimeList *stlist);
242
243 static void buddy_add(struct mwGaimPluginData *pd, GaimBuddy *buddy);
244
245 static GaimBuddy *
246 buddy_ensure(GaimConnection *gc, GaimGroup *group,
247 struct mwSametimeUser *stuser);
248
249 static void group_add(struct mwGaimPluginData *pd, GaimGroup *group);
250
251 static GaimGroup *
252 group_ensure(GaimConnection *gc, struct mwSametimeGroup *stgroup);
253
254 static struct mwAwareList *
255 list_ensure(struct mwGaimPluginData *pd, GaimGroup *group);
256
257
258 /* session functions */
259
260 static struct mwSession *
261 gc_to_session(GaimConnection *gc);
262
263 static GaimConnection *session_to_gc(struct mwSession *session);
264
265
266 /* conference functions */
267
268 static struct mwConference *
269 conf_find_by_id(struct mwGaimPluginData *pd, int id);
270
271
272 /* conversation functions */
273
274 struct convo_msg {
275 enum mwImSendType type;
276 gpointer data;
277 GDestroyNotify clear;
278 };
279
280
281 struct convo_data {
282 struct mwConversation *conv;
283 GList *queue; /**< outgoing message queue, list of convo_msg */
284 };
285
286 static void convo_data_new(struct mwConversation *conv);
287
288 static void convo_data_free(struct convo_data *conv);
289
290 static void convo_features(struct mwConversation *conv);
291
292 static GaimConversation *convo_get_gconv(struct mwConversation *conv);
293
294
295 /* resolved id */
296
297 struct resolved_id {
298 char *id;
299 char *name;
300 };
301
302
303 /* ----- session ------ */
304
305
306 /** resolves a mwSession from a GaimConnection */
307 static struct mwSession *gc_to_session(GaimConnection *gc) {
308 struct mwGaimPluginData *pd;
309
310 g_return_val_if_fail(gc != NULL, NULL);
311
312 pd = gc->proto_data;
313 g_return_val_if_fail(pd != NULL, NULL);
314
315 return pd->session;
316 }
317
318
319 /** resolves a GaimConnection from a mwSession */
320 static GaimConnection *session_to_gc(struct mwSession *session) {
321 struct mwGaimPluginData *pd;
322
323 g_return_val_if_fail(session != NULL, NULL);
324
325 pd = mwSession_getClientData(session);
326 g_return_val_if_fail(pd != NULL, NULL);
327
328 return pd->gc;
329 }
330
331
332 static int mw_session_io_write(struct mwSession *session,
333 const char *buf, gsize len) {
334 struct mwGaimPluginData *pd;
335 int ret = 0;
336
337 pd = mwSession_getClientData(session);
338
339 /* socket was already closed. */
340 if(pd->socket == 0)
341 return 1;
342
343 while(len) {
344 ret = write(pd->socket, buf, len);
345 if(ret <= 0) break;
346 len -= ret;
347 }
348
349 if(len > 0) {
350 DEBUG_ERROR("write returned %i, %i bytes left unwritten\n", ret, len);
351 gaim_connection_error(pd->gc, "Connection closed (writing)");
352 close(pd->socket);
353 pd->socket = 0;
354 return -1;
355 }
356
357 return 0;
358 }
359
360
361 static void mw_session_io_close(struct mwSession *session) {
362 struct mwGaimPluginData *pd;
363
364 pd = mwSession_getClientData(session);
365 if(pd->socket) {
366 close(pd->socket);
367 pd->socket = 0;
368 }
369 }
370
371
372 static void mw_session_clear(struct mwSession *session) {
373 ; /* nothing for now */
374 }
375
376
377 /* ----- aware list ----- */
378
379
380 static void blist_resolve_alias_cb(struct mwServiceResolve *srvc,
381 guint32 id, guint32 code, GList *results,
382 gpointer data) {
383 struct mwResolveResult *result;
384 struct mwResolveMatch *match;
385
386 g_return_if_fail(results != NULL);
387
388 result = results->data;
389 g_return_if_fail(result != NULL);
390 g_return_if_fail(result->matches != NULL);
391
392 match = result->matches->data;
393 g_return_if_fail(match != NULL);
394
395 gaim_blist_server_alias_buddy(data, match->name);
396 gaim_blist_node_set_string(data, BUDDY_KEY_NAME, match->name);
397 }
398
399
400 static void mw_aware_list_on_aware(struct mwAwareList *list,
401 struct mwAwareSnapshot *aware) {
402
403 GaimConnection *gc;
404 struct mwGaimPluginData *pd;
405
406 time_t idle = 0;
407 guint stat = aware->status.status;
408
409 const char *id = aware->id.user;
410
411 gc = mwAwareList_getClientData(list);
412 pd = gc->proto_data;
413
414 switch(stat) {
415 case mwStatus_IDLE:
416 idle = -1;
417 break;
418
419 case mwStatus_AWAY:
420 case mwStatus_BUSY:
421 /* need to let gaim know that these are 'unavailable' states */
422
423 /* XXX */
424 /* stat |= UC_UNAVAILABLE; */
425
426 break;
427 }
428
429 if(aware->group) {
430 GaimAccount *acct;
431 GaimGroup *group;
432 GaimBuddy *buddy;
433 GaimBlistNode *bnode;
434
435 acct = gaim_connection_get_account(gc);
436 group = g_hash_table_lookup(pd->group_list_map, list);
437 buddy = gaim_find_buddy_in_group(acct, id, group);
438 bnode = (GaimBlistNode *) buddy;
439
440 if(! buddy) {
441 struct mwServiceResolve *srvc;
442 GList *query;
443
444 buddy = gaim_buddy_new(acct, id, NULL);
445 gaim_blist_add_buddy(buddy, NULL, group, NULL);
446
447 bnode = (GaimBlistNode *) buddy;
448
449 /* mark buddy as transient if preferences do not indicate that
450 we should save the buddy between gaim sessions */
451 if(! gaim_prefs_get_bool(MW_PRPL_OPT_SAVE_DYNAMIC))
452 bnode->flags |= GAIM_BLIST_NODE_FLAG_NO_SAVE;
453
454 srvc = pd->srvc_resolve;
455 query = g_list_append(NULL, (char *) id);
456
457 mwServiceResolve_resolve(srvc, query, mwResolveFlag_USERS,
458 blist_resolve_alias_cb, buddy, NULL);
459 }
460
461 gaim_blist_node_set_int(bnode, BUDDY_KEY_TYPE, mwSametimeUser_NORMAL);
462 }
463
464 /* XXX */
465 /* serv_got_update(gc, id, aware->online, 0, 0, idle, stat); */
466 }
467
468
469 static void mw_aware_list_on_attrib(struct mwAwareList *list,
470 struct mwAwareIdBlock *id,
471 struct mwAwareAttribute *attrib) {
472
473 ; /* nothing. We'll get attribute data as we need it */
474 }
475
476
477 static void mw_aware_list_clear(struct mwAwareList *list) {
478 ; /* nothing for now */
479 }
480
481
482 static struct mwAwareListHandler mw_aware_list_handler = {
483 .on_aware = mw_aware_list_on_aware,
484 .on_attrib = mw_aware_list_on_attrib,
485 .clear = mw_aware_list_clear,
486 };
487
488
489 /** Ensures that an Aware List is associated with the given group, and
490 returns that list. */
491 static struct mwAwareList *
492 list_ensure(struct mwGaimPluginData *pd, GaimGroup *group) {
493
494 struct mwAwareList *list;
495
496 g_return_val_if_fail(pd != NULL, NULL);
497 g_return_val_if_fail(group != NULL, NULL);
498
499 list = g_hash_table_lookup(pd->group_list_map, group);
500 if(! list) {
501 list = mwAwareList_new(pd->srvc_aware, &mw_aware_list_handler);
502 mwAwareList_setClientData(list, pd->gc, NULL);
503
504 mwAwareList_watchAttributes(list,
505 mwAttribute_AV_PREFS_SET,
506 mwAttribute_MICROPHONE,
507 mwAttribute_SPEAKERS,
508 mwAttribute_VIDEO_CAMERA,
509 mwAttribute_FILE_TRANSFER,
510 NULL);
511
512 g_hash_table_replace(pd->group_list_map, group, list);
513 g_hash_table_insert(pd->group_list_map, list, group);
514 }
515
516 return list;
517 }
518
519
520 static void blist_export(GaimConnection *gc, struct mwSametimeList *stlist) {
521 /* - find the account for this connection
522 - iterate through the buddy list
523 - add each buddy matching this account to the stlist
524 */
525
526 GaimAccount *acct;
527 GaimBuddyList *blist;
528 GaimBlistNode *gn, *cn, *bn;
529 GaimGroup *grp;
530 GaimBuddy *bdy;
531
532 struct mwSametimeGroup *stg = NULL;
533 struct mwIdBlock idb = { NULL, NULL };
534
535 acct = gaim_connection_get_account(gc);
536 g_return_if_fail(acct != NULL);
537
538 blist = gaim_get_blist();
539 g_return_if_fail(blist != NULL);
540
541 for(gn = blist->root; gn; gn = gn->next) {
542 const char *owner;
543 const char *gname;
544 enum mwSametimeGroupType gtype;
545 gboolean gopen;
546
547 if(! GAIM_BLIST_NODE_IS_GROUP(gn)) continue;
548 grp = (GaimGroup *) gn;
549
550 /* the group's type (normal or dynamic) */
551 gtype = gaim_blist_node_get_int(gn, GROUP_KEY_TYPE);
552 if(! gtype) gtype = mwSametimeGroup_NORMAL;
553
554 /* if it's a normal group with none of our people in it, skip it */
555 if(gtype == mwSametimeGroup_NORMAL && !gaim_group_on_account(grp, acct))
556 continue;
557
558 /* if the group has an owner and we're not it, skip it */
559 owner = gaim_blist_node_get_string(gn, GROUP_KEY_OWNER);
560 if(owner && strcmp(owner, gaim_account_get_username(acct)))
561 continue;
562
563 /* the group's actual name may be different from the gaim group's
564 name. Find whichever is there */
565 gname = gaim_blist_node_get_string(gn, GROUP_KEY_NAME);
566 if(! gname) gname = grp->name;
567
568 /* we save this, but never actually honor it */
569 gopen = ! gaim_blist_node_get_bool(gn, GROUP_KEY_COLLAPSED);
570
571 stg = mwSametimeGroup_new(stlist, gtype, gname);
572 mwSametimeGroup_setAlias(stg, grp->name);
573 mwSametimeGroup_setOpen(stg, gopen);
574
575 for(cn = gn->child; cn; cn = cn->next) {
576 if(! GAIM_BLIST_NODE_IS_CONTACT(cn)) continue;
577
578 for(bn = cn->child; bn; bn = bn->next) {
579 if(! GAIM_BLIST_NODE_IS_BUDDY(bn)) continue;
580 if(! GAIM_BLIST_NODE_SHOULD_SAVE(bn)) continue;
581
582 bdy = (GaimBuddy *) bn;
583
584 if(bdy->account == acct) {
585 struct mwSametimeUser *stu;
586 enum mwSametimeUserType utype;
587
588 idb.user = bdy->name;
589
590 utype = gaim_blist_node_get_int(bn, BUDDY_KEY_TYPE);
591 if(! utype) utype = mwSametimeUser_NORMAL;
592
593 stu = mwSametimeUser_new(stg, utype, &idb);
594 mwSametimeUser_setShortName(stu, bdy->server_alias);
595 mwSametimeUser_setAlias(stu, bdy->alias);
596 }
597 }
598 }
599 }
600 }
601
602
603 static void blist_store(struct mwGaimPluginData *pd) {
604
605 struct mwSametimeList *stlist;
606 struct mwServiceStorage *srvc;
607 struct mwStorageUnit *unit;
608
609 GaimConnection *gc;
610
611 struct mwPutBuffer *b;
612 struct mwOpaque *o;
613
614 g_return_if_fail(pd != NULL);
615
616 srvc = pd->srvc_store;
617 g_return_if_fail(srvc != NULL);
618
619 gc = pd->gc;
620
621 /* check if we should do this, according to user prefs */
622 if(! BLIST_CHOICE_IS_SAVE()) {
623 DEBUG_INFO("preferences indicate not to save remote blist\n");
624 return;
625
626 } else if(MW_SERVICE_IS_DEAD(srvc)) {
627 DEBUG_INFO("aborting save of blist: storage service is not alive\n");
628 return;
629
630 } else {
631 DEBUG_INFO("saving remote blist\n");
632 }
633
634 /* create and export to a list object */
635 stlist = mwSametimeList_new();
636 blist_export(gc, stlist);
637
638 /* write it to a buffer */
639 b = mwPutBuffer_new();
640 mwSametimeList_put(b, stlist);
641 mwSametimeList_free(stlist);
642
643 /* put the buffer contents into a storage unit */
644 unit = mwStorageUnit_new(mwStore_AWARE_LIST);
645 o = mwStorageUnit_asOpaque(unit);
646 mwPutBuffer_finalize(o, b);
647
648 /* save the storage unit to the service */
649 mwServiceStorage_save(srvc, unit, NULL, NULL, NULL);
650 }
651
652
653 static gboolean blist_save_cb(gpointer data) {
654 struct mwGaimPluginData *pd = data;
655
656 blist_store(pd);
657 pd->save_event = 0;
658 return FALSE;
659 }
660
661
662 /** schedules the buddy list to be saved to the server */
663 static void blist_schedule(struct mwGaimPluginData *pd) {
664 if(pd->save_event) return;
665
666 pd->save_event = gaim_timeout_add(BLIST_SAVE_SECONDS * 1000,
667 blist_save_cb, pd);
668 }
669
670
671 /** Actually add a buddy to the aware service, and schedule the buddy
672 list to be saved to the server */
673 static void buddy_add(struct mwGaimPluginData *pd,
674 GaimBuddy *buddy) {
675
676 struct mwAwareIdBlock idb = { mwAware_USER, (char *) buddy->name, NULL };
677 struct mwAwareList *list;
678
679 GaimGroup *group;
680 GList *add;
681
682 add = g_list_prepend(NULL, &idb);
683
684 group = gaim_find_buddys_group(buddy);
685 list = list_ensure(pd, group);
686
687 if(mwAwareList_addAware(list, add)) {
688 gaim_blist_remove_buddy(buddy);
689 }
690
691 blist_schedule(pd);
692
693 g_list_free(add);
694 }
695
696
697 /** ensure that a GaimBuddy exists in the group with data
698 appropriately matching the st user entry from the st list */
699 static GaimBuddy *buddy_ensure(GaimConnection *gc, GaimGroup *group,
700 struct mwSametimeUser *stuser) {
701
702 struct mwGaimPluginData *pd = gc->proto_data;
703 GaimBuddy *buddy;
704 GaimAccount *acct = gaim_connection_get_account(gc);
705
706 const char *id = mwSametimeUser_getUser(stuser);
707 const char *name = mwSametimeUser_getShortName(stuser);
708 const char *alias = mwSametimeUser_getAlias(stuser);
709 enum mwSametimeUserType type = mwSametimeUser_getType(stuser);
710
711 g_return_val_if_fail(id != NULL, NULL);
712 g_return_val_if_fail(strlen(id) > 0, NULL);
713
714 buddy = gaim_find_buddy_in_group(acct, id, group);
715 if(! buddy) {
716 buddy = gaim_buddy_new(acct, id, alias);
717
718 gaim_blist_add_buddy(buddy, NULL, group, NULL);
719 buddy_add(pd, buddy);
720 }
721
722 gaim_blist_alias_buddy(buddy, alias);
723 gaim_blist_server_alias_buddy(buddy, name);
724 gaim_blist_node_set_string((GaimBlistNode *) buddy, BUDDY_KEY_NAME, name);
725 gaim_blist_node_set_int((GaimBlistNode *) buddy, BUDDY_KEY_TYPE, type);
726
727 return buddy;
728 }
729
730
731 static void group_add(struct mwGaimPluginData *pd,
732 GaimGroup *group) {
733
734 struct mwAwareIdBlock idb = { mwAware_GROUP, NULL, NULL };
735 struct mwAwareList *list;
736 const char *n;
737 GList *add;
738
739 n = gaim_blist_node_get_string((GaimBlistNode *) group, GROUP_KEY_NAME);
740 if(! n) n = group->name;
741
742 idb.user = (char *) n;
743 add = g_list_prepend(NULL, &idb);
744
745 list = list_ensure(pd, group);
746 mwAwareList_addAware(list, add);
747 g_list_free(add);
748 }
749
750
751 /** ensure that a GaimGroup exists in the blist with data
752 appropriately matching the st group entry from the st list */
753 static GaimGroup *group_ensure(GaimConnection *gc,
754 struct mwSametimeGroup *stgroup) {
755 GaimAccount *acct;
756 GaimGroup *group;
757 GaimBlistNode *gn;
758 const char *name = mwSametimeGroup_getName(stgroup);
759 const char *alias = mwSametimeGroup_getAlias(stgroup);
760 const char *owner;
761 enum mwSametimeGroupType type = mwSametimeGroup_getType(stgroup);
762
763 acct = gaim_connection_get_account(gc);
764 owner = gaim_account_get_username(acct);
765
766 group = gaim_find_group(alias);
767 if(! group) {
768 group = gaim_group_new(alias);
769 gaim_blist_add_group(group, NULL);
770 }
771
772 gn = (GaimBlistNode *) group;
773
774 gaim_blist_node_set_string(gn, GROUP_KEY_NAME, name);
775 gaim_blist_node_set_int(gn, GROUP_KEY_TYPE, type);
776
777 if(type == mwSametimeGroup_DYNAMIC) {
778 gaim_blist_node_set_string(gn, GROUP_KEY_OWNER, owner);
779 group_add(gc->proto_data, group);
780 }
781
782 return group;
783 }
784
785
786 /** merge the entries from a st list into the gaim blist */
787 static void blist_import(GaimConnection *gc, struct mwSametimeList *stlist) {
788 struct mwSametimeGroup *stgroup;
789 struct mwSametimeUser *stuser;
790
791 GaimGroup *group;
792 GaimBuddy *buddy;
793
794 GList *gl, *gtl, *ul, *utl;
795
796 gl = gtl = mwSametimeList_getGroups(stlist);
797 for(; gl; gl = gl->next) {
798
799 stgroup = (struct mwSametimeGroup *) gl->data;
800 group = group_ensure(gc, stgroup);
801
802 ul = utl = mwSametimeGroup_getUsers(stgroup);
803 for(; ul; ul = ul->next) {
804
805 stuser = (struct mwSametimeUser *) ul->data;
806 buddy = buddy_ensure(gc, group, stuser);
807 }
808 g_list_free(utl);
809 }
810 g_list_free(gtl);
811 }
812
813
814 /** callback passed to the storage service when it's told to load the
815 st list */
816 static void fetch_blist_cb(struct mwServiceStorage *srvc,
817 guint32 result, struct mwStorageUnit *item,
818 gpointer data) {
819
820 struct mwGaimPluginData *pd = data;
821 struct mwSametimeList *stlist;
822 struct mwSession *s;
823
824 struct mwGetBuffer *b;
825
826 g_return_if_fail(result == ERR_SUCCESS);
827
828 /* check our preferences for loading */
829 if(BLIST_CHOICE_IS_NONE()) {
830 DEBUG_INFO("preferences indicate not to load remote buddy list\n");
831 return;
832 }
833
834 b = mwGetBuffer_wrap(mwStorageUnit_asOpaque(item));
835
836 stlist = mwSametimeList_new();
837 mwSametimeList_get(b, stlist);
838
839 s = mwService_getSession(MW_SERVICE(srvc));
840 blist_import(pd->gc, stlist);
841
842 mwSametimeList_free(stlist);
843 }
844
845
846 /** callback passed to the storage service when it's told to load one
847 of the default status messages */
848 static void fetch_msg_cb(struct mwServiceStorage *srvc,
849 guint32 result, struct mwStorageUnit *item,
850 gpointer data) {
851
852 struct mwGaimPluginData *pd = data;
853 GaimConnection *gc;
854 GaimAccount *acct;
855 struct mwSession *session;
856 char *msg, *m;
857
858 g_return_if_fail(result == ERR_SUCCESS);
859
860 g_return_if_fail(pd != NULL);
861
862 gc = pd->gc;
863 g_return_if_fail(gc != NULL);
864
865 acct = gaim_connection_get_account(gc);
866 g_return_if_fail(acct != NULL);
867
868 session = pd->session;
869 g_return_if_fail(session != NULL);
870
871 m = msg = mwStorageUnit_asString(item);
872
873 /* only load the first (non-empty) line of the collection of
874 status messages */
875 if(m && *m) {
876 while(*m && isspace(*m)) m++;
877 if(*m) {
878 char *tail;
879
880 tail = strchr(m, '\r');
881 if(tail) *tail = '\0';
882 tail = strchr(m, '\n');
883 if(tail) *tail = '\0';
884 }
885 }
886
887 switch(mwStorageUnit_getKey(item)) {
888 case mwStore_AWAY_MESSAGES:
889 DEBUG_INFO("setting away message to \"%s\"\n", NSTR(m));
890 gaim_account_set_string(acct, MW_KEY_AWAY_MSG, m);
891 break;
892
893 case mwStore_BUSY_MESSAGES:
894 DEBUG_INFO("setting busy message to \"%s\"\n", NSTR(m));
895 gaim_account_set_string(acct, MW_KEY_BUSY_MSG, m);
896 break;
897
898 case mwStore_ACTIVE_MESSAGES:
899 DEBUG_INFO("setting active message to \"%s\"\n", NSTR(m));
900 gaim_account_set_string(acct, MW_KEY_ACTIVE_MSG, m);
901 break;
902
903 default:
904 g_free(msg);
905 g_return_if_reached();
906 }
907
908 g_free(msg);
909 msg = NULL;
910
911 #if 0
912 /* XXX */
913 if(!gc->away_state || !strcmp(gc->away_state, MW_STATE_ACTIVE)) {
914 msg = MW_STATE_ACTIVE;
915 } else if(gc->away_state && !strcmp(gc->away_state, MW_STATE_AWAY)) {
916 msg = MW_STATE_AWAY;
917 } else if(gc->away_state && !strcmp(gc->away_state, MW_STATE_BUSY)) {
918 msg = MW_STATE_BUSY;
919 }
920
921 if(msg)
922 serv_set_away(gc, msg, NULL);
923 #endif
924 }
925
926
927 /** signal triggered when a conversation is opened in Gaim */
928 static void conversation_created_cb(GaimConversation *g_conv,
929 struct mwGaimPluginData *pd) {
930
931 /* we need to tell the IM service to negotiate features for the
932 conversation right away, otherwise it'll wait until the first
933 message is sent before offering NotesBuddy features. Therefore
934 whenever Gaim creates a conversation, we'll immediately open the
935 channel to the other side and figure out what the target can
936 handle. Unfortunately, this makes us vulnerable to Psychic Mode,
937 whereas a more lazy negotiation based on the first message
938 isn't */
939
940 GaimConnection *gc;
941 struct mwIdBlock who = { 0, 0 };
942 struct mwConversation *conv;
943
944 gc = gaim_conversation_get_gc(g_conv);
945 if(pd->gc != gc)
946 return; /* not ours */
947
948 if(gaim_conversation_get_type(g_conv) != GAIM_CONV_IM)
949 return; /* wrong type */
950
951 who.user = (char *) gaim_conversation_get_name(g_conv);
952 conv = mwServiceIm_getConversation(pd->srvc_im, &who);
953
954 convo_features(conv);
955
956 if(mwConversation_isClosed(conv))
957 mwConversation_open(conv);
958 }
959
960
961 static void blist_menu_nab(GaimBlistNode *node, gpointer data) {
962 struct mwGaimPluginData *pd = data;
963 GaimConnection *gc;
964
965 GaimGroup *group = (GaimGroup *) node;
966
967 GString *str;
968 char *tmp;
969
970 g_return_if_fail(pd != NULL);
971
972 gc = pd->gc;
973 g_return_if_fail(gc != NULL);
974
975 g_return_if_fail(GAIM_BLIST_NODE_IS_GROUP(node));
976
977 str = g_string_new(NULL);
978
979 tmp = (char *) gaim_blist_node_get_string(node, GROUP_KEY_NAME);
980
981 g_string_append_printf(str, "<b>Group Title:</b> %s<br>", group->name);
982 g_string_append_printf(str, "<b>Notes Group ID:</b> %s<br>", tmp);
983
984 tmp = g_strdup_printf("Info for Group %s", group->name);
985
986 gaim_notify_formatted(gc, tmp, "Notes Address Book Information",
987 NULL, str->str, NULL, NULL);
988
989 g_free(tmp);
990 g_string_free(str, TRUE);
991 }
992
993
994 /** The normal blist menu prpl function doesn't get called for groups,
995 so we use the blist-node-extended-menu signal to trigger this
996 handler */
997 static void blist_node_menu_cb(GaimBlistNode *node,
998 GList **menu, struct mwGaimPluginData *pd) {
999 GaimBlistNodeAction *act;
1000
1001 if(GAIM_BLIST_NODE_IS_GROUP(node)) {
1002 const char *owner;
1003 GaimAccount *acct;
1004
1005 owner = gaim_blist_node_get_string(node, GROUP_KEY_OWNER);
1006 if(! owner) return;
1007
1008 acct = gaim_accounts_find(owner, PLUGIN_ID);
1009 if(! acct) return;
1010 if(! gaim_account_is_connected(acct)) return;
1011 if(acct != gaim_connection_get_account(pd->gc)) return;
1012
1013 act = gaim_blist_node_action_new("Get Notes Address Book Info",
1014 blist_menu_nab, pd, NULL);
1015
1016 *menu = g_list_append(*menu, NULL);
1017 *menu = g_list_append(*menu, act);
1018 }
1019 }
1020
1021
1022 /** Last thing to happen from a started session */
1023 static void services_starting(struct mwGaimPluginData *pd) {
1024
1025 GaimConnection *gc;
1026 GaimAccount *acct;
1027 struct mwStorageUnit *unit;
1028 GaimBuddyList *blist;
1029 GaimBlistNode *l;
1030
1031 gc = pd->gc;
1032 acct = gaim_connection_get_account(gc);
1033
1034 /* grab the buddy list from the server */
1035 unit = mwStorageUnit_new(mwStore_AWARE_LIST);
1036 mwServiceStorage_load(pd->srvc_store, unit, fetch_blist_cb, pd, NULL);
1037
1038 /* fetch the away/busy/active messages from the server */
1039 unit = mwStorageUnit_new(mwStore_AWAY_MESSAGES);
1040 mwServiceStorage_load(pd->srvc_store, unit, fetch_msg_cb, pd, NULL);
1041
1042 unit = mwStorageUnit_new(mwStore_BUSY_MESSAGES);
1043 mwServiceStorage_load(pd->srvc_store, unit, fetch_msg_cb, pd, NULL);
1044
1045 unit = mwStorageUnit_new(mwStore_ACTIVE_MESSAGES);
1046 mwServiceStorage_load(pd->srvc_store, unit, fetch_msg_cb, pd, NULL);
1047
1048 /* start watching for new conversations */
1049 gaim_signal_connect(gaim_conversations_get_handle(),
1050 "conversation-created", gc,
1051 GAIM_CALLBACK(conversation_created_cb), pd);
1052
1053 /* watch for group extended menu items */
1054 gaim_signal_connect(gaim_blist_get_handle(),
1055 "blist-node-extended-menu", gc,
1056 GAIM_CALLBACK(blist_node_menu_cb), pd);
1057
1058 /* find all the NAB groups and subscribe to them */
1059 blist = gaim_get_blist();
1060 for(l = blist->root; l; l = l->next) {
1061 GaimGroup *group = (GaimGroup *) l;
1062 enum mwSametimeGroupType gt;
1063 const char *owner;
1064
1065 if(! GAIM_BLIST_NODE_IS_GROUP(l)) continue;
1066
1067 /* if the group is ownerless, or has an owner and we're not it,
1068 skip it */
1069 owner = gaim_blist_node_get_string(l, GROUP_KEY_OWNER);
1070 if(!owner || strcmp(owner, gaim_account_get_username(acct)))
1071 continue;
1072
1073 gt = gaim_blist_node_get_int(l, GROUP_KEY_TYPE);
1074 if(gt == mwSametimeGroup_DYNAMIC)
1075 group_add(pd, group);
1076 }
1077
1078 /* set the aware attributes */
1079 /* indicate we understand what AV prefs are, but don't support any */
1080 mwServiceAware_setAttributeBoolean(pd->srvc_aware,
1081 mwAttribute_AV_PREFS_SET, TRUE);
1082 mwServiceAware_unsetAttribute(pd->srvc_aware, mwAttribute_MICROPHONE);
1083 mwServiceAware_unsetAttribute(pd->srvc_aware, mwAttribute_SPEAKERS);
1084 mwServiceAware_unsetAttribute(pd->srvc_aware, mwAttribute_VIDEO_CAMERA);
1085
1086 /* ... but we can do file transfers! */
1087 mwServiceAware_setAttributeBoolean(pd->srvc_aware,
1088 mwAttribute_FILE_TRANSFER, TRUE);
1089 }
1090
1091
1092 /** called from mw_session_stateChange when the session's state is
1093 mwSession_STARTED. Any finalizing of start-up stuff should go
1094 here */
1095 static void session_started(struct mwGaimPluginData *pd) {
1096
1097 /* XXX setup status */
1098
1099 /* use our services to do neat things */
1100 services_starting(pd);
1101 }
1102
1103
1104 static void mw_session_stateChange(struct mwSession *session,
1105 enum mwSessionState state, guint32 info) {
1106 struct mwGaimPluginData *pd;
1107 GaimConnection *gc;
1108 char *msg = NULL;
1109
1110 pd = mwSession_getClientData(session);
1111 gc = pd->gc;
1112
1113 switch(state) {
1114 case mwSession_STARTING:
1115 msg = _("Sending Handshake");
1116 gaim_connection_update_progress(gc, msg, 2, MW_CONNECT_STEPS);
1117 break;
1118
1119 case mwSession_HANDSHAKE:
1120 msg = _("Waiting for Handshake Acknowledgement");
1121 gaim_connection_update_progress(gc, msg, 3, MW_CONNECT_STEPS);
1122 break;
1123
1124 case mwSession_HANDSHAKE_ACK:
1125 msg = _("Handshake Acknowledged, Sending Login");
1126 gaim_connection_update_progress(gc, msg, 4, MW_CONNECT_STEPS);
1127 break;
1128
1129 case mwSession_LOGIN:
1130 msg = _("Waiting for Login Acknowledgement");
1131 gaim_connection_update_progress(gc, msg, 5, MW_CONNECT_STEPS);
1132 break;
1133
1134 case mwSession_LOGIN_REDIR:
1135 msg = _("Login Redirected");
1136 gaim_connection_update_progress(gc, msg, 6, MW_CONNECT_STEPS);
1137 break;
1138
1139 case mwSession_LOGIN_ACK:
1140 msg = _("Login Acknowledged");
1141 gaim_connection_update_progress(gc, msg, 7, MW_CONNECT_STEPS);
1142 break;
1143
1144 case mwSession_STARTED:
1145 msg = _("Connected to Sametime Community Server");
1146 gaim_connection_update_progress(gc, msg, 8, MW_CONNECT_STEPS);
1147 gaim_connection_set_state(gc, GAIM_CONNECTED);
1148 /* XXX serv_finish_login(gc); */
1149
1150 session_started(pd);
1151 break;
1152
1153 case mwSession_STOPPING:
1154 if(info & ERR_FAILURE) {
1155 msg = mwError(info);
1156 gaim_connection_error(gc, msg);
1157 g_free(msg);
1158 }
1159 break;
1160
1161 case mwSession_STOPPED:
1162 break;
1163
1164 case mwSession_UNKNOWN:
1165 default:
1166 DEBUG_WARN("session in unknown state\n");
1167 }
1168 }
1169
1170
1171 static void mw_session_setPrivacyInfo(struct mwSession *session) {
1172 /** @todo implement privacy one of these days */
1173
1174 struct mwGaimPluginData *pd;
1175 GaimConnection *gc;
1176 GaimAccount *acct;
1177 struct mwPrivacyInfo *privacy;
1178 GSList *l, **ll;
1179 guint count;
1180
1181 DEBUG_INFO("privacy information set from server\n");
1182
1183 g_return_if_fail(session != NULL);
1184
1185 pd = mwSession_getClientData(session);
1186 g_return_if_fail(pd != NULL);
1187
1188 gc = pd->gc;
1189 g_return_if_fail(gc != NULL);
1190
1191 acct = gaim_connection_get_account(gc);
1192 g_return_if_fail(acct != NULL);
1193
1194 privacy = mwSession_getPrivacyInfo(session);
1195 count = privacy->count;
1196
1197 ll = (privacy->deny)? &acct->deny: &acct->permit;
1198 for(l = *ll; l; l = l->next) g_free(l->data);
1199 g_slist_free(*ll);
1200 l = *ll = NULL;
1201
1202 while(count--) {
1203 struct mwUserItem *u = privacy->users + count;
1204 l = g_slist_prepend(l, g_strdup(u->id));
1205 }
1206 *ll = l;
1207 }
1208
1209
1210 static void mw_session_setUserStatus(struct mwSession *session) {
1211 struct mwGaimPluginData *pd;
1212 GaimConnection *gc;
1213 struct mwAwareIdBlock idb = { mwAware_USER, NULL, NULL };
1214 struct mwUserStatus *stat;
1215
1216 g_return_if_fail(session != NULL);
1217
1218 pd = mwSession_getClientData(session);
1219 g_return_if_fail(pd != NULL);
1220
1221 gc = pd->gc;
1222 g_return_if_fail(gc != NULL);
1223
1224 idb.user = mwSession_getProperty(session, mwSession_AUTH_USER_ID);
1225 stat = mwSession_getUserStatus(session);
1226
1227 /* trigger an update of our own status if we're in the buddy list */
1228 mwServiceAware_setStatus(pd->srvc_aware, &idb, stat);
1229 }
1230
1231
1232 static void mw_session_admin(struct mwSession *session,
1233 const char *text) {
1234
1235 GaimConnection *gc = session_to_gc(session);
1236 g_return_if_fail(gc != NULL);
1237
1238 /** @todo Admin alerts should probably be in a conversation window
1239 rather than a gaim_notify_message. Or in some sort of updating
1240 dialog, or something. */
1241
1242 gaim_notify_message(gc, GAIM_NOTIFY_MSG_INFO, _("Admin Alert"),
1243 text, NULL, NULL, NULL);
1244 }
1245
1246
1247 /** called from read_cb, attempts to read available data from sock and
1248 pass it to the session, passing back the return code from the read
1249 call for handling in read_cb */
1250 static int read_recv(struct mwSession *session, int sock) {
1251 char buf[BUF_LEN];
1252 int len;
1253
1254 len = read(sock, buf, BUF_LEN);
1255 if(len > 0) mwSession_recv(session, buf, len);
1256
1257 return len;
1258 }
1259
1260
1261 /** callback triggered from gaim_input_add, watches the socked for
1262 available data to be processed by the session */
1263 static void read_cb(gpointer data, gint source,
1264 GaimInputCondition cond) {
1265
1266 struct mwGaimPluginData *pd = data;
1267 int ret = 0, err = 0;
1268
1269 g_return_if_fail(pd != NULL);
1270
1271 if(cond & GAIM_INPUT_READ) {
1272 ret = read_recv(pd->session, pd->socket);
1273 }
1274
1275 /* normal operation ends here */
1276 if(ret > 0) return;
1277
1278 err = errno;
1279
1280 /* read problem occured if we're here, so we'll need to take care of
1281 it and clean up internal state */
1282
1283 if(pd->socket) {
1284 close(pd->socket);
1285 pd->socket = 0;
1286 }
1287
1288 if(pd->gc->inpa) {
1289 gaim_input_remove(pd->gc->inpa);
1290 pd->gc->inpa = 0;
1291 }
1292
1293 if(! ret) {
1294 DEBUG_INFO("connection reset\n");
1295 gaim_connection_error(pd->gc, "Connection reset");
1296
1297 } else if(ret < 0) {
1298 char *msg = strerror(err);
1299 DEBUG_INFO("error in read callback: %s\n", msg);
1300
1301 msg = g_strdup_printf("Error reading from socket: %s", msg);
1302 gaim_connection_error(pd->gc, msg);
1303 g_free(msg);
1304 }
1305 }
1306
1307
1308 /** Callback passed to gaim_proxy_connect when an account is logged
1309 in, and if the session logging in receives a redirect message */
1310 static void connect_cb(gpointer data, gint source,
1311 GaimInputCondition cond) {
1312
1313 struct mwGaimPluginData *pd = data;
1314 GaimConnection *gc = pd->gc;
1315
1316 if(! g_list_find(gaim_connections_get_all(), pd->gc)) {
1317 close(source);
1318 g_return_if_reached();
1319 }
1320
1321 if(source < 0) {
1322 /* connection failed */
1323
1324 if(pd->socket) {
1325 /* this is a redirect connect, force login on existing socket */
1326 mwSession_forceLogin(pd->session);
1327
1328 } else {
1329 /* this is a regular connect, error out */
1330 gaim_connection_error(pd->gc, "Unable to connect to host");
1331 }
1332
1333 return;
1334 }
1335
1336 if(pd->socket) {
1337 /* stop any existing login attempt */
1338 mwSession_stop(pd->session, ERR_SUCCESS);
1339 }
1340
1341 pd->socket = source;
1342 gc->inpa = gaim_input_add(source, GAIM_INPUT_READ, read_cb, pd);
1343
1344 mwSession_start(pd->session);
1345 }
1346
1347
1348 static void mw_session_loginRedirect(struct mwSession *session,
1349 const char *host) {
1350
1351 struct mwGaimPluginData *pd;
1352 GaimConnection *gc;
1353 GaimAccount *account;
1354 guint port;
1355
1356 pd = mwSession_getClientData(session);
1357 gc = pd->gc;
1358 account = gaim_connection_get_account(gc);
1359 port = gaim_account_get_int(account, "port", MW_PLUGIN_DEFAULT_PORT);
1360
1361 if(gaim_prefs_get_bool(MW_PRPL_OPT_FORCE_LOGIN) ||
1362 gaim_proxy_connect(account, host, port, connect_cb, pd)) {
1363
1364 mwSession_forceLogin(session);
1365 }
1366 }
1367
1368
1369 static struct mwSessionHandler mw_session_handler = {
1370 .io_write = mw_session_io_write,
1371 .io_close = mw_session_io_close,
1372 .clear = mw_session_clear,
1373 .on_stateChange = mw_session_stateChange,
1374 .on_setPrivacyInfo = mw_session_setPrivacyInfo,
1375 .on_setUserStatus = mw_session_setUserStatus,
1376 .on_admin = mw_session_admin,
1377 .on_loginRedirect = mw_session_loginRedirect,
1378 };
1379
1380
1381 static void mw_aware_on_attrib(struct mwServiceAware *srvc,
1382 struct mwAwareAttribute *attrib) {
1383
1384 ; /** @todo handle server attributes. There may be some stuff we
1385 actually want to look for, but I'm not aware of anything right
1386 now.*/
1387 }
1388
1389
1390 static void mw_aware_clear(struct mwServiceAware *srvc) {
1391 ; /* nothing for now */
1392 }
1393
1394
1395 static struct mwAwareHandler mw_aware_handler = {
1396 .on_attrib = mw_aware_on_attrib,
1397 .clear = mw_aware_clear,
1398 };
1399
1400
1401 static struct mwServiceAware *mw_srvc_aware_new(struct mwSession *s) {
1402 struct mwServiceAware *srvc;
1403 srvc = mwServiceAware_new(s, &mw_aware_handler);
1404 return srvc;
1405 };
1406
1407
1408 static void mw_conf_invited(struct mwConference *conf,
1409 struct mwLoginInfo *inviter,
1410 const char *invitation) {
1411
1412 struct mwServiceConference *srvc;
1413 struct mwSession *session;
1414 struct mwGaimPluginData *pd;
1415 GaimConnection *gc;
1416
1417 char *c_inviter, *c_name, *c_topic, *c_invitation;
1418 GHashTable *ht;
1419
1420 srvc = mwConference_getService(conf);
1421 session = mwService_getSession(MW_SERVICE(srvc));
1422 pd = mwSession_getClientData(session);
1423 gc = pd->gc;
1424
1425 ht = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
1426
1427 c_inviter = g_strdup(inviter->user_id);
1428 g_hash_table_insert(ht, CHAT_KEY_CREATOR, c_inviter);
1429
1430 c_name = g_strdup(mwConference_getName(conf));
1431 g_hash_table_insert(ht, CHAT_KEY_NAME, c_name);
1432
1433 c_topic = g_strdup(mwConference_getTitle(conf));
1434 g_hash_table_insert(ht, CHAT_KEY_TOPIC, c_topic);
1435
1436 c_invitation = g_strdup(invitation);
1437 g_hash_table_insert(ht, CHAT_KEY_INVITE, c_invitation);
1438
1439 DEBUG_INFO("received invitation from '%s' to join ('%s','%s'): '%s'\n",
1440 NSTR(c_inviter), NSTR(c_name),
1441 NSTR(c_topic), NSTR(c_invitation));
1442
1443 serv_got_chat_invite(gc, c_topic, c_inviter, c_invitation, ht);
1444 }
1445
1446
1447 /* The following mess helps us relate a mwConference to a GaimConvChat
1448 in the various forms by which either may be indicated */
1449
1450 #define CONF_TO_ID(conf) (GPOINTER_TO_INT(conf))
1451 #define ID_TO_CONF(pd, id) (conf_find_by_id((pd), (id)))
1452
1453 #define CHAT_TO_ID(chat) (gaim_conv_chat_get_id(chat))
1454 #define ID_TO_CHAT(id) (gaim_find_chat(id))
1455
1456 #define CHAT_TO_CONF(pd, chat) \
1457 (ID_TO_CONF((pd), CHAT_TO_ID(chat)))
1458
1459 #define CONF_TO_CHAT(conf) \
1460 (ID_TO_CHAT(CONF_TO_ID(conf)))
1461
1462
1463 static struct mwConference *
1464 conf_find_by_id(struct mwGaimPluginData *pd, int id) {
1465
1466 struct mwServiceConference *srvc = pd->srvc_conf;
1467 struct mwConference *conf = NULL;
1468 GList *l, *ll;
1469
1470 ll = mwServiceConference_getConferences(srvc);
1471 for(l = ll; l; l = l->next) {
1472 struct mwConference *c = l->data;
1473 GaimConvChat *h = mwConference_getClientData(c);
1474
1475 if(CHAT_TO_ID(h) == id) {
1476 conf = c;
1477 break;
1478 }
1479 }
1480 g_list_free(ll);
1481
1482 return conf;
1483 }
1484
1485
1486 static void mw_conf_opened(struct mwConference *conf, GList *members) {
1487 struct mwServiceConference *srvc;
1488 struct mwSession *session;
1489 struct mwGaimPluginData *pd;
1490 GaimConnection *gc;
1491 GaimConversation *g_conf;
1492
1493 const char *n = mwConference_getName(conf);
1494
1495 DEBUG_INFO("conf %s opened, %u initial members\n",
1496 NSTR(n), g_list_length(members));
1497
1498 srvc = mwConference_getService(conf);
1499 session = mwService_getSession(MW_SERVICE(srvc));
1500 pd = mwSession_getClientData(session);
1501 gc = pd->gc;
1502
1503 g_conf = serv_got_joined_chat(gc, CONF_TO_ID(conf),
1504 mwConference_getTitle(conf));
1505
1506 mwConference_setClientData(conf, GAIM_CONV_CHAT(g_conf), NULL);
1507
1508 for(; members; members = members->next) {
1509 struct mwLoginInfo *peer = members->data;
1510 gaim_conv_chat_add_user(GAIM_CONV_CHAT(g_conf), peer->user_id,
1511 NULL, GAIM_CBFLAGS_NONE, FALSE);
1512 }
1513 }
1514
1515
1516 static void mw_conf_closed(struct mwConference *conf, guint32 reason) {
1517 struct mwServiceConference *srvc;
1518 struct mwSession *session;
1519 struct mwGaimPluginData *pd;
1520 GaimConnection *gc;
1521
1522 const char *n = mwConference_getName(conf);
1523 char *msg = mwError(reason);
1524
1525 DEBUG_INFO("conf %s closed, 0x%08x\n", NSTR(n), reason);
1526
1527 srvc = mwConference_getService(conf);
1528 session = mwService_getSession(MW_SERVICE(srvc));
1529 pd = mwSession_getClientData(session);
1530 gc = pd->gc;
1531
1532 serv_got_chat_left(gc, CONF_TO_ID(conf));
1533
1534 gaim_notify_error(gc, "Conference Closed", NULL, msg);
1535 g_free(msg);
1536 }
1537
1538
1539 static void mw_conf_peer_joined(struct mwConference *conf,
1540 struct mwLoginInfo *peer) {
1541
1542 struct mwServiceConference *srvc;
1543 struct mwSession *session;
1544 struct mwGaimPluginData *pd;
1545 GaimConnection *gc;
1546 GaimConvChat *g_conf;
1547
1548 const char *n = mwConference_getName(conf);
1549
1550 DEBUG_INFO("%s joined conf %s\n", NSTR(peer->user_id), NSTR(n));
1551
1552 srvc = mwConference_getService(conf);
1553 session = mwService_getSession(MW_SERVICE(srvc));
1554 pd = mwSession_getClientData(session);
1555 gc = pd->gc;
1556
1557 g_conf = mwConference_getClientData(conf);
1558 g_return_if_fail(g_conf != NULL);
1559
1560 gaim_conv_chat_add_user(g_conf, peer->user_id,
1561 NULL, GAIM_CBFLAGS_NONE, TRUE);
1562 }
1563
1564
1565 static void mw_conf_peer_parted(struct mwConference *conf,
1566 struct mwLoginInfo *peer) {
1567
1568 struct mwServiceConference *srvc;
1569 struct mwSession *session;
1570 struct mwGaimPluginData *pd;
1571 GaimConnection *gc;
1572 GaimConvChat *g_conf;
1573
1574 const char *n = mwConference_getName(conf);
1575
1576 DEBUG_INFO("%s left conf %s\n", NSTR(peer->user_id), NSTR(n));
1577
1578 srvc = mwConference_getService(conf);
1579 session = mwService_getSession(MW_SERVICE(srvc));
1580 pd = mwSession_getClientData(session);
1581 gc = pd->gc;
1582
1583 g_conf = mwConference_getClientData(conf);
1584 g_return_if_fail(g_conf != NULL);
1585
1586 gaim_conv_chat_remove_user(g_conf, peer->user_id, NULL);
1587 }
1588
1589
1590 static void mw_conf_text(struct mwConference *conf,
1591 struct mwLoginInfo *who, const char *text) {
1592
1593 struct mwServiceConference *srvc;
1594 struct mwSession *session;
1595 struct mwGaimPluginData *pd;
1596 GaimConnection *gc;
1597 char *esc;
1598
1599 srvc = mwConference_getService(conf);
1600 session = mwService_getSession(MW_SERVICE(srvc));
1601 pd = mwSession_getClientData(session);
1602 gc = pd->gc;
1603
1604 esc = g_markup_escape_text(text, -1);
1605 serv_got_chat_in(gc, CONF_TO_ID(conf), who->user_id, 0, esc, time(NULL));
1606 g_free(esc);
1607 }
1608
1609
1610 static void mw_conf_typing(struct mwConference *conf,
1611 struct mwLoginInfo *who, gboolean typing) {
1612
1613 /* gaim really has no good way to expose this to the user. */
1614
1615 const char *n = mwConference_getName(conf);
1616 const char *w = who->user_id;
1617
1618 if(typing) {
1619 DEBUG_INFO("%s in conf %s: <typing>\n", NSTR(w), NSTR(n));
1620
1621 } else {
1622 DEBUG_INFO("%s in conf %s: <stopped typing>\n", NSTR(w), NSTR(n));
1623 }
1624 }
1625
1626
1627 static void mw_conf_clear(struct mwServiceConference *srvc) {
1628 ;
1629 }
1630
1631
1632 static struct mwConferenceHandler mw_conference_handler = {
1633 .on_invited = mw_conf_invited,
1634 .conf_opened = mw_conf_opened,
1635 .conf_closed = mw_conf_closed,
1636 .on_peer_joined = mw_conf_peer_joined,
1637 .on_peer_parted = mw_conf_peer_parted,
1638 .on_text = mw_conf_text,
1639 .on_typing = mw_conf_typing,
1640 .clear = mw_conf_clear,
1641 };
1642
1643
1644 static struct mwServiceConference *mw_srvc_conf_new(struct mwSession *s) {
1645 struct mwServiceConference *srvc;
1646 srvc = mwServiceConference_new(s, &mw_conference_handler);
1647 return srvc;
1648 }
1649
1650
1651 static void ft_incoming_cancel(GaimXfer *xfer) {
1652 /* incoming transfer rejected or canceled in-progress */
1653 struct mwFileTransfer *ft = xfer->data;
1654 if(ft) mwFileTransfer_reject(ft);
1655 }
1656
1657
1658 static void ft_incoming_init(GaimXfer *xfer) {
1659 /* incoming transfer accepted */
1660
1661 /* - accept the mwFileTransfer
1662 - open/create the local FILE "wb"
1663 - stick the FILE's fp in xfer->dest_fp
1664 */
1665
1666 struct mwFileTransfer *ft;
1667 FILE *fp;
1668
1669 ft = xfer->data;
1670
1671 fp = g_fopen(xfer->local_filename, "wb");
1672 if(! fp) {
1673 mwFileTransfer_cancel(ft);
1674 return;
1675 }
1676
1677 xfer->dest_fp = fp;
1678 mwFileTransfer_accept(ft);
1679 }
1680
1681
1682 static void mw_ft_offered(struct mwFileTransfer *ft) {
1683 /*
1684 - create a gaim ft object
1685 - offer it
1686 */
1687
1688 struct mwServiceFileTransfer *srvc;
1689 struct mwSession *session;
1690 struct mwGaimPluginData *pd;
1691 GaimConnection *gc;
1692 GaimAccount *acct;
1693 const char *who;
1694 GaimXfer *xfer;
1695
1696 /* @todo add some safety checks */
1697 srvc = mwFileTransfer_getService(ft);
1698 session = mwService_getSession(MW_SERVICE(srvc));
1699 pd = mwSession_getClientData(session);
1700 gc = pd->gc;
1701 acct = gaim_connection_get_account(gc);
1702
1703 who = mwFileTransfer_getUser(ft)->user;
1704
1705 DEBUG_INFO("file transfer %p offered\n", ft);
1706 DEBUG_INFO(" from: %s\n", NSTR(who));
1707 DEBUG_INFO(" file: %s\n", NSTR(mwFileTransfer_getFileName(ft)));
1708 DEBUG_INFO(" size: %u\n", mwFileTransfer_getFileSize(ft));
1709 DEBUG_INFO(" text: %s\n", NSTR(mwFileTransfer_getMessage(ft)));
1710
1711 xfer = gaim_xfer_new(acct, GAIM_XFER_RECEIVE, who);
1712
1713 gaim_xfer_ref(xfer);
1714 mwFileTransfer_setClientData(ft, xfer, (GDestroyNotify) gaim_xfer_unref);
1715 xfer->data = ft;
1716
1717 gaim_xfer_set_init_fnc(xfer, ft_incoming_init);
1718 gaim_xfer_set_cancel_recv_fnc(xfer, ft_incoming_cancel);
1719 gaim_xfer_set_request_denied_fnc(xfer, ft_incoming_cancel);
1720
1721 gaim_xfer_set_filename(xfer, mwFileTransfer_getFileName(ft));
1722 gaim_xfer_set_size(xfer, mwFileTransfer_getFileSize(ft));
1723 gaim_xfer_set_message(xfer, mwFileTransfer_getMessage(ft));
1724
1725 gaim_xfer_request(xfer);
1726 }
1727
1728
1729 static void ft_send(struct mwFileTransfer *ft, FILE *fp) {
1730 char buf[BUF_LONG];
1731 struct mwOpaque o = { .data = buf, .len = BUF_LONG };
1732 guint32 rem;
1733 GaimXfer *xfer;
1734
1735 xfer = mwFileTransfer_getClientData(ft);
1736
1737 rem = mwFileTransfer_getRemaining(ft);
1738 if(rem < BUF_LONG) o.len = rem;
1739
1740 if(fread(buf, (size_t) o.len, 1, fp)) {
1741
1742 /* calculate progress first. update is displayed upon ack */
1743 xfer->bytes_sent += o.len;
1744 xfer->bytes_remaining -= o.len;
1745
1746 /* ... send data second */
1747 mwFileTransfer_send(ft, &o);
1748
1749 } else {
1750 int err = errno;
1751 DEBUG_WARN("problem reading from file %s: %s",
1752 NSTR(mwFileTransfer_getFileName(ft)), strerror(err));
1753
1754 mwFileTransfer_cancel(ft);
1755 }
1756 }
1757
1758
1759 static gboolean ft_idle_cb(struct mwFileTransfer *ft) {
1760 GaimXfer *xfer = mwFileTransfer_getClientData(ft);
1761 g_return_val_if_fail(xfer != NULL, FALSE);
1762
1763 xfer->watcher = 0;
1764 ft_send(ft, xfer->dest_fp);
1765
1766 return FALSE;
1767 }
1768
1769
1770 static void mw_ft_opened(struct mwFileTransfer *ft) {
1771 /*
1772 - get gaim ft from client data in ft
1773 - set the state to active
1774 */
1775
1776 GaimXfer *xfer;
1777
1778 xfer = mwFileTransfer_getClientData(ft);
1779
1780 if(! xfer) {
1781 mwFileTransfer_cancel(ft);
1782 mwFileTransfer_free(ft);
1783 g_return_if_reached();
1784 }
1785
1786 gaim_xfer_update_progress(xfer);
1787
1788 if(gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) {
1789 xfer->watcher = g_idle_add((GSourceFunc)ft_idle_cb, ft);
1790 xfer->dest_fp = g_fopen(xfer->local_filename, "rb");
1791 }
1792 }
1793
1794
1795 static void mw_ft_closed(struct mwFileTransfer *ft, guint32 code) {
1796 /*
1797 - get gaim ft from client data in ft
1798 - indicate rejection/cancelation/completion
1799 - free the file transfer itself
1800 */
1801
1802 GaimXfer *xfer;
1803
1804 xfer = mwFileTransfer_getClientData(ft);
1805 if(xfer) {
1806 xfer->data = NULL;
1807
1808 if(mwFileTransfer_isDone(ft)) {
1809 gaim_xfer_set_completed(xfer, TRUE);
1810 gaim_xfer_end(xfer);
1811
1812 } else if(mwFileTransfer_isCancelLocal(ft)) {
1813 /* calling gaim_xfer_cancel_local is redundant, since that's
1814 probably what triggered this function to be called */
1815 ;
1816
1817 } else if(mwFileTransfer_isCancelRemote(ft)) {
1818 /* steal the reference for the xfer */
1819 mwFileTransfer_setClientData(ft, NULL, NULL);
1820 gaim_xfer_cancel_remote(xfer);
1821
1822 /* drop the stolen reference */
1823 gaim_xfer_unref(xfer);
1824 return;
1825 }
1826 }
1827
1828 mwFileTransfer_free(ft);
1829 }
1830
1831
1832 static void mw_ft_recv(struct mwFileTransfer *ft,
1833 struct mwOpaque *data) {
1834 /*
1835 - get gaim ft from client data in ft
1836 - update transfered percentage
1837 - if done, destroy the ft, disassociate from gaim ft
1838 */
1839
1840 GaimXfer *xfer;
1841 FILE *fp;
1842
1843 xfer = mwFileTransfer_getClientData(ft);
1844 g_return_if_fail(xfer != NULL);
1845
1846 fp = xfer->dest_fp;
1847 g_return_if_fail(fp != NULL);
1848
1849 /* we must collect and save our precious data */
1850 fwrite(data->data, 1, data->len, fp);
1851
1852 /* update the progress */
1853 xfer->bytes_sent += data->len;
1854 xfer->bytes_remaining -= data->len;
1855 gaim_xfer_update_progress(xfer);
1856
1857 /* let the other side know we got it, and to send some more */
1858 mwFileTransfer_ack(ft);
1859 }
1860
1861
1862 static void mw_ft_ack(struct mwFileTransfer *ft) {
1863 GaimXfer *xfer;
1864
1865 xfer = mwFileTransfer_getClientData(ft);
1866 g_return_if_fail(xfer != NULL);
1867 g_return_if_fail(xfer->watcher == 0);
1868
1869 gaim_xfer_update_progress(xfer);
1870
1871 if(mwFileTransfer_isOpen(ft))
1872 xfer->watcher = g_idle_add((GSourceFunc)ft_idle_cb, ft);
1873 }
1874
1875
1876 static void mw_ft_clear(struct mwServiceFileTransfer *srvc) {
1877 ;
1878 }
1879
1880
1881 static struct mwFileTransferHandler mw_ft_handler = {
1882 .ft_offered = mw_ft_offered,
1883 .ft_opened = mw_ft_opened,
1884 .ft_closed = mw_ft_closed,
1885 .ft_recv = mw_ft_recv,
1886 .ft_ack = mw_ft_ack,
1887 .clear = mw_ft_clear,
1888 };
1889
1890
1891 static struct mwServiceFileTransfer *mw_srvc_ft_new(struct mwSession *s) {
1892 struct mwServiceFileTransfer *srvc;
1893 GHashTable *ft_map;
1894
1895 ft_map = g_hash_table_new(g_direct_hash, g_direct_equal);
1896
1897 srvc = mwServiceFileTransfer_new(s, &mw_ft_handler);
1898 mwService_setClientData(MW_SERVICE(srvc), ft_map,
1899 (GDestroyNotify) g_hash_table_destroy);
1900
1901 return srvc;
1902 }
1903
1904
1905 static void convo_data_free(struct convo_data *cd) {
1906 GList *l;
1907
1908 /* clean the queue */
1909 for(l = cd->queue; l; l = g_list_delete_link(l, l)) {
1910 struct convo_msg *m = l->data;
1911 if(m->clear) m->clear(m->data);
1912 g_free(m);
1913 }
1914
1915 g_free(cd);
1916 }
1917
1918
1919 /** allocates a convo_data structure and associates it with the
1920 conversation in the client data slot */
1921 static void convo_data_new(struct mwConversation *conv) {
1922 struct convo_data *cd;
1923
1924 g_return_if_fail(conv != NULL);
1925
1926 if(mwConversation_getClientData(conv))
1927 return;
1928
1929 cd = g_new0(struct convo_data, 1);
1930 cd->conv = conv;
1931
1932 mwConversation_setClientData(conv, cd, (GDestroyNotify) convo_data_free);
1933 }
1934
1935
1936 static GaimConversation *convo_get_gconv(struct mwConversation *conv) {
1937 struct mwServiceIm *srvc;
1938 struct mwSession *session;
1939 struct mwGaimPluginData *pd;
1940 GaimConnection *gc;
1941 GaimAccount *acct;
1942
1943 struct mwIdBlock *idb;
1944
1945 srvc = mwConversation_getService(conv);
1946 session = mwService_getSession(MW_SERVICE(srvc));
1947 pd = mwSession_getClientData(session);
1948 gc = pd->gc;
1949 acct = gaim_connection_get_account(gc);
1950
1951 idb = mwConversation_getTarget(conv);
1952
1953 return gaim_find_conversation_with_account(GAIM_CONV_IM,idb->user, acct);
1954 }
1955
1956
1957 static void convo_queue(struct mwConversation *conv,
1958 enum mwImSendType type, gconstpointer data) {
1959
1960 struct convo_data *cd;
1961 struct convo_msg *m;
1962
1963 convo_data_new(conv);
1964 cd = mwConversation_getClientData(conv);
1965
1966 m = g_new0(struct convo_msg, 1);
1967 m->type = type;
1968
1969 switch(type) {
1970 case mwImSend_PLAIN:
1971 m->data = g_strdup(data);
1972 m->clear = g_free;
1973 break;
1974
1975 case mwImSend_TYPING:
1976 default:
1977 m->data = (gpointer) data;
1978 m->clear = NULL;
1979 }
1980
1981 cd->queue = g_list_append(cd->queue, m);
1982 }
1983
1984
1985 /* Does what it takes to get an error displayed for a conversation */
1986 static void convo_error(struct mwConversation *conv, guint32 err) {
1987 GaimConversation *gconv;
1988 char *tmp, *text;
1989 struct mwIdBlock *idb;
1990
1991 idb = mwConversation_getTarget(conv);
1992
1993 tmp = mwError(err);
1994 text = g_strconcat("Unable to send message: ", tmp, NULL);
1995
1996 gconv = convo_get_gconv(conv);
1997 if(gconv && !gaim_conv_present_error(idb->user, gconv->account, text)) {
1998
1999 g_free(text);
2000 text = g_strdup_printf("Unable to send message to %s:",
2001 (idb->user)? idb->user: "(unknown)");
2002 gaim_notify_error(gaim_account_get_connection(gconv->account),
2003 NULL, text, tmp);
2004 }
2005
2006 g_free(tmp);
2007 g_free(text);
2008 }
2009
2010
2011 static void convo_queue_send(struct mwConversation *conv) {
2012 struct convo_data *cd;
2013 GList *l;
2014
2015 cd = mwConversation_getClientData(conv);
2016
2017 for(l = cd->queue; l; l = g_list_delete_link(l, l)) {
2018 struct convo_msg *m = l->data;
2019
2020 mwConversation_send(conv, m->type, m->data);
2021
2022 if(m->clear) m->clear(m->data);
2023 g_free(m);
2024 }
2025
2026 cd->queue = NULL;
2027 }
2028
2029
2030 /** called when a mw conversation leaves a gaim conversation to
2031 inform the gaim conversation that it's unsafe to offer any *cool*
2032 features. */
2033 static void convo_nofeatures(struct mwConversation *conv) {
2034 GaimConversation *gconv;
2035 GaimConnection *gc;
2036
2037 gconv = convo_get_gconv(conv);
2038 if(! gconv) return;
2039
2040 gc = gaim_conversation_get_gc(gconv);
2041
2042 /* If the account is disconnecting, then the conversation's closing
2043 will call this, and gc will be NULL */
2044 if(gc) {
2045 gaim_conversation_set_features(gconv, gc->flags);
2046 }
2047 }
2048
2049
2050 /** called when a mw conversation and gaim conversation come together,
2051 to inform the gaim conversation of what features to offer the
2052 user */
2053 static void convo_features(struct mwConversation *conv) {
2054 GaimConversation *gconv;
2055 GaimConnectionFlags feat;
2056
2057 gconv = convo_get_gconv(conv);
2058 if(! gconv) return;
2059
2060 feat = gaim_conversation_get_features(gconv);
2061
2062 if(mwConversation_isOpen(conv)) {
2063 if(mwConversation_supports(conv, mwImSend_HTML)) {
2064 feat |= GAIM_CONNECTION_HTML;
2065 } else {
2066 feat &= ~GAIM_CONNECTION_HTML;
2067 }
2068
2069 if(mwConversation_supports(conv, mwImSend_MIME)) {
2070 feat &= ~GAIM_CONNECTION_NO_IMAGES;
2071 } else {
2072 feat |= GAIM_CONNECTION_NO_IMAGES;
2073 }
2074
2075 DEBUG_INFO("conversation features set to 0x%04x\n", feat);
2076 gaim_conversation_set_features(gconv, feat);
2077
2078 } else {
2079 convo_nofeatures(conv);
2080 }
2081 }
2082
2083
2084 /** triggered from mw_conversation_opened if the appropriate plugin
2085 preference is set. This will open a window for the conversation
2086 before the first message is sent. */
2087 static void convo_do_psychic(struct mwConversation *conv) {
2088 struct mwServiceIm *srvc;
2089 struct mwSession *session;
2090 struct mwGaimPluginData *pd;
2091 GaimConnection *gc;
2092 GaimAccount *acct;
2093
2094 struct mwIdBlock *idb;
2095
2096 GaimConversation *gconv;
2097 GaimConvWindow *win;
2098
2099 srvc = mwConversation_getService(conv);
2100 session = mwService_getSession(MW_SERVICE(srvc));
2101 pd = mwSession_getClientData(session);
2102 gc = pd->gc;
2103 acct = gaim_connection_get_account(gc);
2104
2105 idb = mwConversation_getTarget(conv);
2106
2107 gconv = gaim_find_conversation_with_account(GAIM_CONV_IM, idb->user, acct);
2108 if(! gconv) {
2109 gconv = gaim_conversation_new(GAIM_CONV_IM, acct, idb->user);
2110 }
2111
2112 g_return_if_fail(gconv != NULL);
2113
2114 win = gaim_conversation_get_window(gconv);
2115 g_return_if_fail(win != NULL);
2116
2117 gaim_conv_window_show(win);
2118 }
2119
2120
2121 static void mw_conversation_opened(struct mwConversation *conv) {
2122 struct mwServiceIm *srvc;
2123 struct mwSession *session;
2124 struct mwGaimPluginData *pd;
2125 GaimConnection *gc;
2126 GaimAccount *acct;
2127
2128 struct convo_dat *cd;
2129
2130 srvc = mwConversation_getService(conv);
2131 session = mwService_getSession(MW_SERVICE(srvc));
2132 pd = mwSession_getClientData(session);
2133 gc = pd->gc;
2134 acct = gaim_connection_get_account(gc);
2135
2136 /* set up the queue */
2137 cd = mwConversation_getClientData(conv);
2138 if(cd) {
2139 convo_queue_send(conv);
2140
2141 if(! convo_get_gconv(conv)) {
2142 mwConversation_free(conv);
2143 return;
2144 }
2145
2146 } else {
2147 convo_data_new(conv);
2148
2149 if(gaim_prefs_get_bool(MW_PRPL_OPT_PSYCHIC)) {
2150 convo_do_psychic(conv);
2151 }
2152 }
2153
2154 { /* record the client key for the buddy */
2155 GaimBuddy *buddy;
2156 struct mwLoginInfo *info;
2157 info = mwConversation_getTargetInfo(conv);
2158
2159 buddy = gaim_find_buddy(acct, info->user_id);
2160 if(buddy) {
2161 gaim_blist_node_set_int((GaimBlistNode *) buddy,
2162 BUDDY_KEY_CLIENT, info->type);
2163 }
2164 }
2165
2166 convo_features(conv);
2167 }
2168
2169
2170 static void mw_conversation_closed(struct mwConversation *conv,
2171 guint32 reason) {
2172
2173 struct convo_data *cd;
2174
2175 g_return_if_fail(conv != NULL);
2176
2177 cd = mwConversation_getClientData(conv);
2178 if(reason && cd && cd->queue) {
2179 convo_error(conv, reason);
2180 }
2181
2182 #if 0
2183 /* don't do this, to prevent the occasional weird sending of
2184 formatted messages as plaintext when the other end closes the
2185 conversation after we've begun composing the message */
2186 convo_nofeatures(conv);
2187 #endif
2188
2189 mwConversation_removeClientData(conv);
2190 }
2191
2192
2193 static void im_recv_text(struct mwConversation *conv,
2194 struct mwGaimPluginData *pd,
2195 const char *msg) {
2196
2197 struct mwIdBlock *idb;
2198 char *txt, *esc;
2199
2200 idb = mwConversation_getTarget(conv);
2201 txt = gaim_utf8_try_convert(msg);
2202 esc = g_markup_escape_text(txt, -1);
2203
2204 serv_got_im(pd->gc, idb->user, esc, 0, time(NULL));
2205
2206 g_free(txt);
2207 g_free(esc);
2208 }
2209
2210
2211 static void im_recv_typing(struct mwConversation *conv,
2212 struct mwGaimPluginData *pd,
2213 gboolean typing) {
2214
2215 struct mwIdBlock *idb;
2216 idb = mwConversation_getTarget(conv);
2217
2218 serv_got_typing(pd->gc, idb->user, 0,
2219 typing? GAIM_TYPING: GAIM_NOT_TYPING);
2220 }
2221
2222
2223 static void im_recv_html(struct mwConversation *conv,
2224 struct mwGaimPluginData *pd,
2225 const char *msg) {
2226
2227 struct mwIdBlock *idb;
2228 char *txt;
2229
2230 idb = mwConversation_getTarget(conv);
2231 txt = gaim_utf8_try_convert(msg);
2232
2233 serv_got_im(pd->gc, idb->user, txt, 0, time(NULL));
2234
2235 g_free(txt);
2236 }
2237
2238
2239 static void im_recv_subj(struct mwConversation *conv,
2240 struct mwGaimPluginData *pd,
2241 const char *subj) {
2242
2243 /** @todo somehow indicate receipt of a conversation subject. It
2244 would also be nice if we added a /topic command for the
2245 protocol */
2246 ;
2247 }
2248
2249
2250 /** generate "cid:908@20582notesbuddy" from "<908@20582notesbuddy>" */
2251 static char *make_cid(const char *cid) {
2252 gsize n;
2253 char *c, *d;
2254
2255 g_return_val_if_fail(cid != NULL, NULL);
2256
2257 n = strlen(cid);
2258 g_return_val_if_fail(n > 2, NULL);
2259
2260 c = g_strndup(cid+1, n-2);
2261 d = g_strdup_printf("cid:%s", c);
2262
2263 g_free(c);
2264 return d;
2265 }
2266
2267
2268 static void im_recv_mime(struct mwConversation *conv,
2269 struct mwGaimPluginData *pd,
2270 const char *data) {
2271
2272 struct mwIdBlock *idb;
2273
2274 GHashTable *img_by_cid;
2275 GList *images;
2276
2277 GString *str;
2278
2279 GaimMimeDocument *doc;
2280 const GList *parts;
2281
2282 idb = mwConversation_getTarget(conv);
2283
2284 img_by_cid = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
2285 images = NULL;
2286
2287 /* don't want the contained string to ever be NULL */
2288 str = g_string_new("");
2289
2290 doc = gaim_mime_document_parse(data);
2291
2292 /* handle all the MIME parts */
2293 parts = gaim_mime_document_get_parts(doc);
2294 for(; parts; parts = parts->next) {
2295 GaimMimePart *part = parts->data;
2296 const char *type;
2297
2298 type = gaim_mime_part_get_field(part, "content-type");
2299 DEBUG_INFO("MIME part Content-Type: %s\n", NSTR(type));
2300
2301 if(! type) {
2302 ; /* feh */
2303
2304 } else if(g_str_has_prefix(type, "image")) {
2305 /* put images into the image store */
2306
2307 char *d_dat;
2308 gsize d_len;
2309 char *cid;
2310 int img;
2311
2312 /* obtain and unencode the data */
2313 gaim_mime_part_get_data_decoded(part, &d_dat, &d_len);
2314
2315 /* look up the content id */
2316 cid = (char *) gaim_mime_part_get_field(part, "Content-ID");
2317 cid = make_cid(cid);
2318
2319 /* add image to the gaim image store */
2320 img = gaim_imgstore_add(d_dat, d_len, cid);
2321
2322 /* map the cid to the image store identifier */
2323 g_hash_table_insert(img_by_cid, cid, GINT_TO_POINTER(img));
2324
2325 /* recall the image for dereferencing later */
2326 images = g_list_append(images, GINT_TO_POINTER(img));
2327
2328 } else if(g_str_has_prefix(type, "text")) {
2329
2330 /* concatenate all the text parts together */
2331 char *data, *txt;
2332 gsize len;
2333
2334 gaim_mime_part_get_data_decoded(part, &data, &len);
2335 txt = gaim_utf8_try_convert(data);
2336 g_string_append(str, txt);
2337 g_free(txt);
2338 }
2339 }
2340
2341 gaim_mime_document_free(doc);
2342
2343 { /* replace each IMG tag's SRC attribute with an ID attribute. This
2344 actually modifies the contents of str */
2345 GData *attribs;
2346 char *start, *end;
2347 char *tmp = str->str;
2348
2349 while(*tmp && gaim_markup_find_tag("img", tmp, (const char **) &start,
2350 (const char **) &end, &attribs)) {
2351
2352 char *alt, *align, *border, *src;
2353 int img;
2354
2355 alt = g_datalist_get_data(&attribs, "alt");
2356 align = g_datalist_get_data(&attribs, "align");
2357 border = g_datalist_get_data(&attribs, "border");
2358 src = g_datalist_get_data(&attribs, "src");
2359
2360 img = GPOINTER_TO_INT(g_hash_table_lookup(img_by_cid, src));
2361 if(img) {
2362 GString *atstr;
2363 gsize len = (end - start);
2364 gsize mov;
2365
2366 atstr = g_string_new("");
2367 if(alt) g_string_append_printf(atstr, " alt=\"%s\"", alt);
2368 if(align) g_string_append_printf(atstr, " align=\"%s\"", align);
2369 if(border) g_string_append_printf(atstr, " border=\"%s\"", border);
2370
2371 mov = g_snprintf(start, len, "<img%s id=\"%i\"", atstr->str, img);
2372 while(mov < len) start[mov++] = ' ';
2373
2374 g_string_free(atstr, TRUE);
2375 }
2376
2377 g_datalist_clear(&attribs);
2378 tmp = end + 1;
2379 }
2380 }
2381
2382 /* actually display the message */
2383 serv_got_im(pd->gc, idb->user, str->str, 0, time(NULL));
2384
2385 g_string_free(str, TRUE);
2386
2387 /* clean up the cid table */
2388 g_hash_table_destroy(img_by_cid);
2389
2390 /* dereference all the imgages */
2391 while(images) {
2392 gaim_imgstore_unref(GPOINTER_TO_INT(images->data));
2393 images = g_list_delete_link(images, images);
2394 }
2395 }
2396
2397
2398 static void mw_conversation_recv(struct mwConversation *conv,
2399 enum mwImSendType type,
2400 gconstpointer msg) {
2401 struct mwServiceIm *srvc;
2402 struct mwSession *session;
2403 struct mwGaimPluginData *pd;
2404
2405 srvc = mwConversation_getService(conv);
2406 session = mwService_getSession(MW_SERVICE(srvc));
2407 pd = mwSession_getClientData(session);
2408
2409 switch(type) {
2410 case mwImSend_PLAIN:
2411 im_recv_text(conv, pd, msg);
2412 break;
2413
2414 case mwImSend_TYPING:
2415 im_recv_typing(conv, pd, !! msg);
2416 break;
2417
2418 case mwImSend_HTML:
2419 im_recv_html(conv, pd, msg);
2420 break;
2421
2422 case mwImSend_SUBJECT:
2423 im_recv_subj(conv, pd, msg);
2424 break;
2425
2426 case mwImSend_MIME:
2427 im_recv_mime(conv, pd, msg);
2428 break;
2429
2430 default:
2431 DEBUG_INFO("conversation received strange type, 0x%04x\n", type);
2432 ; /* erm... */
2433 }
2434 }
2435
2436
2437 #if 0
2438 /* this will be appropriate when meanwhile supports the Place service */
2439 static void mw_place_invite(struct mwConversation *conv,
2440 const char *message,
2441 const char *title, const char *name) {
2442 struct mwServiceIm *srvc;
2443 struct mwSession *session;
2444 struct mwGaimPluginData *pd;
2445
2446 struct mwIdBlock *idb;
2447 GHashTable *ht;
2448
2449 srvc = mwConversation_getService(conv);
2450 session = mwService_getSession(MW_SERVICE(srvc));
2451 pd = mwSession_getClientData(session);
2452
2453 idb = mwConversation_getTarget(conv);
2454
2455 ht = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
2456 g_hash_table_insert(ht, CHAT_KEY_CREATOR, g_strdup(idb->user));
2457 g_hash_table_insert(ht, CHAT_KEY_NAME, g_strdup(name));
2458 g_hash_table_insert(ht, CHAT_KEY_TOPIC, g_strdup(title));
2459 g_hash_table_insert(ht, CHAT_KEY_INVITE, g_strdup(message));
2460
2461 serv_got_chat_invite(pd->gc, title, idb->user, message, ht);
2462 }
2463 #endif
2464
2465
2466 static void mw_im_clear(struct mwServiceIm *srvc) {
2467 ;
2468 }
2469
2470
2471 static struct mwImHandler mw_im_handler = {
2472 .conversation_opened = mw_conversation_opened,
2473 .conversation_closed = mw_conversation_closed,
2474 .conversation_recv = mw_conversation_recv,
2475 .place_invite = NULL, /* = mw_place_invite, */
2476 .clear = mw_im_clear,
2477 };
2478
2479
2480 static struct mwServiceIm *mw_srvc_im_new(struct mwSession *s) {
2481 struct mwServiceIm *srvc;
2482 srvc = mwServiceIm_new(s, &mw_im_handler);
2483 mwServiceIm_setClientType(srvc, mwImClient_NOTESBUDDY);
2484 return srvc;
2485 }
2486
2487
2488 static struct mwServiceResolve *mw_srvc_resolve_new(struct mwSession *s) {
2489 struct mwServiceResolve *srvc;
2490 srvc = mwServiceResolve_new(s);
2491 return srvc;
2492 }
2493
2494
2495 static struct mwServiceStorage *mw_srvc_store_new(struct mwSession *s) {
2496 struct mwServiceStorage *srvc;
2497 srvc = mwServiceStorage_new(s);
2498 return srvc;
2499 }
2500
2501
2502 /** allocate and associate a mwGaimPluginData with a GaimConnection */
2503 static struct mwGaimPluginData *mwGaimPluginData_new(GaimConnection *gc) {
2504 struct mwGaimPluginData *pd;
2505
2506 g_return_val_if_fail(gc != NULL, NULL);
2507
2508 pd = g_new0(struct mwGaimPluginData, 1);
2509 pd->gc = gc;
2510 pd->session = mwSession_new(&mw_session_handler);
2511 pd->srvc_aware = mw_srvc_aware_new(pd->session);
2512 pd->srvc_conf = mw_srvc_conf_new(pd->session);
2513 pd->srvc_ft = mw_srvc_ft_new(pd->session);
2514 pd->srvc_im = mw_srvc_im_new(pd->session);
2515 pd->srvc_resolve = mw_srvc_resolve_new(pd->session);
2516 pd->srvc_store = mw_srvc_store_new(pd->session);
2517 pd->group_list_map = g_hash_table_new(g_direct_hash, g_direct_equal);
2518
2519 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_aware));
2520 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_conf));
2521 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_ft));
2522 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_im));
2523 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_resolve));
2524 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_store));
2525
2526 mwSession_addCipher(pd->session, mwCipher_new_RC2_40(pd->session));
2527
2528 mwSession_setClientData(pd->session, pd, NULL);
2529 gc->proto_data = pd;
2530
2531 return pd;
2532 }
2533
2534
2535 static void mwGaimPluginData_free(struct mwGaimPluginData *pd) {
2536 g_return_if_fail(pd != NULL);
2537
2538 pd->gc->proto_data = NULL;
2539
2540 mwSession_removeService(pd->session, SERVICE_AWARE);
2541 mwSession_removeService(pd->session, SERVICE_CONFERENCE);
2542 mwSession_removeService(pd->session, SERVICE_IM);
2543 mwSession_removeService(pd->session, SERVICE_RESOLVE);
2544 mwSession_removeService(pd->session, SERVICE_STORAGE);
2545
2546 mwService_free(MW_SERVICE(pd->srvc_aware));
2547 mwService_free(MW_SERVICE(pd->srvc_conf));
2548 mwService_free(MW_SERVICE(pd->srvc_im));
2549 mwService_free(MW_SERVICE(pd->srvc_resolve));
2550 mwService_free(MW_SERVICE(pd->srvc_store));
2551
2552 mwCipher_free(mwSession_getCipher(pd->session, mwCipher_RC2_40));
2553
2554 mwSession_free(pd->session);
2555
2556 g_hash_table_destroy(pd->group_list_map);
2557
2558 g_free(pd);
2559 }
2560
2561
2562 static const char *mw_prpl_list_icon(GaimAccount *a, GaimBuddy *b) {
2563 /* my little green dude is a chopped up version of the aim running
2564 guy. First, cut off the head and store someplace safe. Then,
2565 take the left-half side of the body and throw it away. Make a
2566 copy of the remaining body, and flip it horizontally. Now attach
2567 the two pieces into an X shape, and drop the head back on the
2568 top, being careful to center it. Then, just change the color
2569 saturation to bring the red down a bit, and voila! */
2570
2571 /* then, throw all of that away and use sodipodi to make a new
2572 icon. You know, LIKE A REAL MAN. */
2573
2574 return "meanwhile";
2575 }
2576
2577
2578 static void mw_prpl_list_emblems(GaimBuddy *b,
2579 const char **se, const char **sw,
2580 const char **nw, const char **ne) {
2581
2582 /* we have to add the UC_UNAVAILABLE flag so that Gaim will recognie
2583 certain away states as indicating the buddy is unavailable */
2584
2585 if(! GAIM_BUDDY_IS_ONLINE(b)) {
2586 *se = "offline";
2587 } else if(b->uc == (mwStatus_AWAY /* XXX | UC_UNAVAILABLE */)) {
2588 *se = "away";
2589 } else if(b->uc == (mwStatus_BUSY /* XXX | UC_UNAVAILABLE */)) {
2590 *se = "dnd";
2591 }
2592 }
2593
2594
2595 static char *mw_prpl_status_text(GaimBuddy *b) {
2596 GaimConnection *gc;
2597 struct mwGaimPluginData *pd;
2598 struct mwAwareIdBlock t = { mwAware_USER, b->name, NULL };
2599 const char *ret;
2600
2601 gc = b->account->gc;
2602 pd = gc->proto_data;
2603
2604 ret = mwServiceAware_getText(pd->srvc_aware, &t);
2605 return (ret)? g_strdup(ret): NULL;
2606 }
2607
2608
2609 static const char *status_text(GaimBuddy *b) {
2610 guint status = b->uc;
2611
2612 if(! GAIM_BUDDY_IS_ONLINE(b) ) {
2613 return MW_STATE_OFFLINE;
2614
2615 } else if(status == (mwStatus_AWAY /* XXX | UC_UNAVAILABLE */)) {
2616 return MW_STATE_AWAY;
2617
2618 } else if(status == (mwStatus_BUSY /* XXX | UC_UNAVAILABLE */)) {
2619 return MW_STATE_BUSY;
2620
2621 } else if(status == mwStatus_IDLE) {
2622 return MW_STATE_IDLE;
2623
2624 } else if(status == mwStatus_ACTIVE) {
2625 return MW_STATE_ACTIVE;
2626
2627 } else {
2628 return MW_STATE_UNKNOWN;
2629 }
2630 }
2631
2632
2633 static gboolean user_supports(struct mwServiceAware *srvc,
2634 const char *who, guint32 feature) {
2635
2636 const struct mwAwareAttribute *attr;
2637 struct mwAwareIdBlock idb = { mwAware_USER, (char *) who, NULL };
2638
2639 attr = mwServiceAware_getAttribute(srvc, &idb, feature);
2640 return (attr != NULL) && mwAwareAttribute_asBoolean(attr);
2641 }
2642
2643
2644 char *user_supports_text(struct mwServiceAware *srvc, const char *who) {
2645 char *feat[] = {NULL, NULL, NULL, NULL, NULL};
2646 char **f = feat;
2647
2648 if(user_supports(srvc, who, mwAttribute_AV_PREFS_SET)) {
2649 gboolean mic, speak, video;
2650
2651 mic = user_supports(srvc, who, mwAttribute_MICROPHONE);
2652 speak = user_supports(srvc, who, mwAttribute_SPEAKERS);
2653 video = user_supports(srvc, who, mwAttribute_VIDEO_CAMERA);
2654
2655 if(mic) *f++ = "Microphone";
2656 if(speak) *f++ = "Speakers";
2657 if(video) *f++ = "Video Camera";
2658 }
2659
2660 if(user_supports(srvc, who, mwAttribute_FILE_TRANSFER))
2661 *f++ = "File Transfer";
2662
2663 return (*feat)? g_strjoinv(", ", feat): NULL;
2664 /* jenni loves siege */
2665 }
2666
2667
2668 static char *mw_prpl_tooltip_text(GaimBuddy *b) {
2669 GaimConnection *gc;
2670 struct mwGaimPluginData *pd;
2671 struct mwAwareIdBlock idb = { mwAware_USER, b->name, NULL };
2672
2673 GString *str;
2674 const char *tmp;
2675
2676 gc = b->account->gc;
2677 pd = gc->proto_data;
2678
2679 str = g_string_new(NULL);
2680
2681 tmp = status_text(b);
2682 g_string_append_printf(str, "\n<b>Status</b>: %s", tmp);
2683
2684 tmp = mwServiceAware_getText(pd->srvc_aware, &idb);
2685 if(tmp) g_string_append_printf(str, "\n<b>Message</b>: %s", tmp);
2686
2687 tmp = user_supports_text(pd->srvc_aware, b->name);
2688 if(tmp) {
2689 g_string_append_printf(str, "\n<b>Supports</b>: %s", tmp);
2690 g_free((char *) tmp);
2691 }
2692
2693 tmp = str->str;
2694 g_string_free(str, FALSE);
2695 return (char *) tmp;
2696 }
2697
2698
2699 static GList *mw_prpl_status_types(GaimAccount *acct) {
2700 GList *types = NULL;
2701 GaimStatusType *type;
2702
2703 type = gaim_status_type_new(GAIM_STATUS_OFFLINE, MW_STATE_OFFLINE,
2704 _("Offline"), TRUE);
2705 types = g_list_append(types, type);
2706
2707 type = gaim_status_type_new(GAIM_STATUS_ONLINE, MW_STATE_ONLINE,
2708 _("Online"), TRUE);
2709
2710 type = gaim_status_type_new(GAIM_STATUS_AVAILABLE, MW_STATE_ACTIVE,
2711 _("Active"), TRUE);
2712 gaim_status_type_add_attr(type, MW_STATE_MESSAGE, _("Message"),
2713 gaim_value_new(GAIM_TYPE_STRING));
2714 types = g_list_append(types, type);
2715
2716 type = gaim_status_type_new(GAIM_STATUS_AWAY, MW_STATE_AWAY,
2717 _("Away"), TRUE);
2718 gaim_status_type_add_attr(type, MW_STATE_MESSAGE, _("Message"),
2719 gaim_value_new(GAIM_TYPE_STRING));
2720 types = g_list_append(types, type);
2721
2722 type = gaim_status_type_new(GAIM_STATUS_UNAVAILABLE, MW_STATE_BUSY,
2723 _("Do Not Disturb"), TRUE);
2724 gaim_status_type_add_attr(type, MW_STATE_MESSAGE, _("Message"),
2725 gaim_value_new(GAIM_TYPE_STRING));
2726 types = g_list_append(types, type);
2727
2728 return types;
2729 }
2730
2731
2732 #if 0
2733 static GList *mw_prpl_away_states(GaimConnection *gc) {
2734 GList *l = NULL;
2735
2736 l = g_list_append(l, MW_STATE_ACTIVE);
2737 l = g_list_append(l, MW_STATE_AWAY);
2738 l = g_list_append(l, MW_STATE_BUSY);
2739 l = g_list_append(l, (char *) GAIM_AWAY_CUSTOM);
2740
2741 return l;
2742 }
2743 #endif
2744
2745
2746 static void conf_create_prompt_cancel(GaimBuddy *buddy,
2747 GaimRequestFields *fields) {
2748 ;
2749 }
2750
2751
2752 static void conf_create_prompt_join(GaimBuddy *buddy,
2753 GaimRequestFields *fields) {
2754 GaimAccount *acct;
2755 GaimConnection *gc;
2756 struct mwGaimPluginData *pd;
2757 struct mwServiceConference *srvc;
2758
2759 GaimRequestField *f;
2760
2761 const char *topic, *invite;
2762 struct mwConference *conf;
2763 struct mwIdBlock idb = { NULL, NULL };
2764
2765 acct = buddy->account;
2766 gc = gaim_account_get_connection(acct);
2767 pd = gc->proto_data;
2768 srvc = pd->srvc_conf;
2769
2770 f = gaim_request_fields_get_field(fields, CHAT_KEY_TOPIC);
2771 topic = gaim_request_field_string_get_value(f);
2772
2773 f = gaim_request_fields_get_field(fields, CHAT_KEY_INVITE);
2774 invite = gaim_request_field_string_get_value(f);
2775
2776 conf = mwConference_new(srvc, topic);
2777 mwConference_open(conf);
2778
2779 idb.user = buddy->name;
2780 mwConference_invite(conf, &idb, invite);
2781 }
2782
2783
2784 static void blist_menu_conf_create(GaimBuddy *buddy, const char *msg) {
2785
2786 GaimRequestFields *fields;
2787 GaimRequestFieldGroup *g;
2788 GaimRequestField *f;
2789
2790 GaimAccount *acct;
2791 GaimConnection *gc;
2792
2793 char *msgA, *msgB;
2794
2795 g_return_if_fail(buddy != NULL);
2796
2797 acct = buddy->account;
2798 g_return_if_fail(acct != NULL);
2799
2800 gc = gaim_account_get_connection(acct);
2801 g_return_if_fail(gc != NULL);
2802
2803 fields = gaim_request_fields_new();
2804
2805 g = gaim_request_field_group_new(NULL);
2806 gaim_request_fields_add_group(fields, g);
2807
2808 f = gaim_request_field_string_new(CHAT_KEY_TOPIC, "Topic", NULL, FALSE);
2809 gaim_request_field_group_add_field(g, f);
2810
2811 f = gaim_request_field_string_new(CHAT_KEY_INVITE, "Message", msg, FALSE);
2812 gaim_request_field_group_add_field(g, f);
2813
2814 msgA = ("Create conference with user");
2815 msgB = ("Please enter a topic for the new conference, and an invitation"
2816 " message to be sent to %s");
2817 msgB = g_strdup_printf(msgB, buddy->name);
2818
2819 gaim_request_fields(gc, "New Conference",
2820 msgA, msgB, fields,
2821 "Create", G_CALLBACK(conf_create_prompt_join),
2822 "Cancel", G_CALLBACK(conf_create_prompt_cancel),
2823 buddy);
2824 g_free(msgB);
2825 }
2826
2827
2828 static void conf_select_prompt_cancel(GaimBuddy *buddy,
2829 GaimRequestFields *fields) {
2830 ;
2831 }
2832
2833
2834 static void conf_select_prompt_invite(GaimBuddy *buddy,
2835 GaimRequestFields *fields) {
2836 GaimRequestField *f;
2837 const GList *l;
2838 const char *msg;
2839
2840 f = gaim_request_fields_get_field(fields, CHAT_KEY_INVITE);
2841 msg = gaim_request_field_string_get_value(f);
2842
2843 f = gaim_request_fields_get_field(fields, "conf");
2844 l = gaim_request_field_list_get_selected(f);
2845
2846 if(l) {
2847 gpointer d = gaim_request_field_list_get_data(f, l->data);
2848
2849 if(GPOINTER_TO_INT(d) == 0x01) {
2850 blist_menu_conf_create(buddy, msg);
2851
2852 } else {
2853 struct mwIdBlock idb = { buddy->name, NULL };
2854 mwConference_invite(d, &idb, msg);
2855 }
2856 }
2857 }
2858
2859
2860 static void blist_menu_conf_list(GaimBuddy *buddy,
2861 GList *confs) {
2862
2863 GaimRequestFields *fields;
2864 GaimRequestFieldGroup *g;
2865 GaimRequestField *f;
2866
2867 GaimAccount *acct;
2868 GaimConnection *gc;
2869
2870 char *msgA, *msgB;
2871
2872 acct = buddy->account;
2873 g_return_if_fail(acct != NULL);
2874
2875 gc = gaim_account_get_connection(acct);
2876 g_return_if_fail(gc != NULL);
2877
2878 fields = gaim_request_fields_new();
2879
2880 g = gaim_request_field_group_new(NULL);
2881 gaim_request_fields_add_group(fields, g);
2882
2883 f = gaim_request_field_list_new("conf", "Available Conferences");
2884 gaim_request_field_list_set_multi_select(f, FALSE);
2885 for(; confs; confs = confs->next) {
2886 struct mwConference *c = confs->data;
2887 gaim_request_field_list_add(f, mwConference_getTitle(c), c);
2888 }
2889 gaim_request_field_list_add(f, "Create New Conference...",
2890 GINT_TO_POINTER(0x01));
2891 gaim_request_field_group_add_field(g, f);
2892
2893 f = gaim_request_field_string_new(CHAT_KEY_INVITE, "Message", NULL, FALSE);
2894 gaim_request_field_group_add_field(g, f);
2895
2896 msgA = "Invite user to a conference";
2897 msgB = ("Select a conference from the list below to send an invite to"
2898 " user %s. Select \"Create New Conference\" if you'd like to"
2899 " create a new conference to invite this user to.");
2900 msgB = g_strdup_printf(msgB, buddy->name);
2901
2902 gaim_request_fields(gc, "Invite to Conference",
2903 msgA, msgB, fields,
2904 "Invite", G_CALLBACK(conf_select_prompt_invite),
2905 "Cancel", G_CALLBACK(conf_select_prompt_cancel),
2906 buddy);
2907 g_free(msgB);
2908 }
2909
2910
2911 static void blist_menu_conf(GaimBlistNode *node, gpointer data) {
2912 GaimBuddy *buddy = (GaimBuddy *) node;
2913 GaimAccount *acct;
2914 GaimConnection *gc;
2915 struct mwGaimPluginData *pd;
2916 GList *l;
2917
2918 g_return_if_fail(node != NULL);
2919 g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
2920
2921 acct = buddy->account;
2922 g_return_if_fail(acct != NULL);
2923
2924 gc = gaim_account_get_connection(acct);
2925 g_return_if_fail(gc != NULL);
2926
2927 pd = gc->proto_data;
2928 g_return_if_fail(pd != NULL);
2929
2930 /* @todo prompt for which conference to join
2931
2932 - get a list of all conferences on this session
2933 - if none, prompt to create one, and invite buddy to it
2934 - else, prompt to select a conference or create one
2935 */
2936
2937 l = mwServiceConference_getConferences(pd->srvc_conf);
2938 if(l) {
2939 blist_menu_conf_list(buddy, l);
2940 g_list_free(l);
2941
2942 } else {
2943 blist_menu_conf_create(buddy, NULL);
2944 }
2945 }
2946
2947
2948 static GList *mw_prpl_blist_node_menu(GaimBlistNode *node) {
2949 GList *l = NULL;
2950 GaimBlistNodeAction *act;
2951
2952 if(! GAIM_BLIST_NODE_IS_BUDDY(node))
2953 return l;
2954
2955 l = g_list_append(l, NULL);
2956
2957 act = gaim_blist_node_action_new("Invite to Conference...",
2958 blist_menu_conf, NULL, NULL);
2959 l = g_list_append(l, act);
2960
2961 /** note: this never gets called for a GaimGroup, have to use the
2962 blist-node-extended-menu signal for that. The function
2963 blist_node_menu_cb is assigned to this signal in the function
2964 services_starting */
2965
2966 return l;
2967 }
2968
2969
2970 static GList *mw_prpl_chat_info(GaimConnection *gc) {
2971 GList *l = NULL;
2972 struct proto_chat_entry *pce;
2973
2974 pce = g_new0(struct proto_chat_entry, 1);
2975 pce->label = "Topic:";
2976 pce->identifier = CHAT_KEY_TOPIC;
2977 l = g_list_append(l, pce);
2978
2979 return l;
2980 }
2981
2982
2983 static GHashTable *mw_prpl_chat_info_defaults(GaimConnection *gc,
2984 const char *name) {
2985 GHashTable *table;
2986
2987 g_return_val_if_fail(gc != NULL, NULL);
2988
2989 DEBUG_INFO("mw_prpl_chat_info_defaults for %s\n", NSTR(name));
2990
2991 table = g_hash_table_new_full(g_str_hash, g_str_equal,
2992 NULL, g_free);
2993
2994 g_hash_table_insert(table, CHAT_KEY_NAME, g_strdup(name));
2995 g_hash_table_insert(table, CHAT_KEY_INVITE, NULL);
2996
2997 return table;
2998 }
2999
3000
3001 static void mw_prpl_login(GaimAccount *acct, GaimStatus *stat);
3002
3003
3004 static void prompt_host_cancel_cb(GaimConnection *gc) {
3005 gaim_connection_error(gc, "No Sametime Community Server specified");
3006 }
3007
3008
3009 static void prompt_host_ok_cb(GaimConnection *gc, const char *host) {
3010 if(host && *host) {
3011 GaimAccount *acct;
3012 GaimPresence *pres;
3013 GaimStatus *stat;
3014
3015 acct = gaim_connection_get_account(gc);
3016 gaim_account_set_string(acct, MW_KEY_HOST, host);
3017
3018 pres = gaim_account_get_presence(acct);
3019 stat = gaim_presence_get_active_status(pres);
3020
3021 mw_prpl_login(acct, stat);
3022
3023 } else {
3024 prompt_host_cancel_cb(gc);
3025 }
3026 }
3027
3028
3029 static void prompt_host(GaimConnection *gc) {
3030 GaimAccount *acct;
3031 char *msg;
3032
3033 acct = gaim_connection_get_account(gc);
3034 msg = ("No host or IP address has been configured for the"
3035 " Meanwhile account %s. Please enter one below to"
3036 " continue logging in.");
3037 msg = g_strdup_printf(msg, NSTR(gaim_account_get_username(acct)));
3038
3039 gaim_request_input(gc, "Meanwhile Connection Setup",
3040 "No Sametime Community Server Specified", msg,
3041 MW_PLUGIN_DEFAULT_HOST, FALSE, FALSE, NULL,
3042 "Connect", G_CALLBACK(prompt_host_ok_cb),
3043 "Cancel", G_CALLBACK(prompt_host_cancel_cb),
3044 gc);
3045
3046 g_free(msg);
3047 }
3048
3049
3050 static void mw_prpl_login(GaimAccount *account, GaimStatus *stat) {
3051 GaimConnection *gc;
3052 struct mwGaimPluginData *pd;
3053
3054 char *user, *pass, *host;
3055 guint port;
3056
3057 gc = gaim_account_get_connection(account);
3058 pd = mwGaimPluginData_new(gc);
3059
3060 /* while we do support images, the default is to not offer it */
3061 gc->flags |= GAIM_CONNECTION_NO_IMAGES;
3062
3063 user = g_strdup(gaim_account_get_username(account));
3064 pass = (char *) gaim_account_get_password(account);
3065
3066 DEBUG_INFO("mw_prpl_login\n");
3067
3068 #if 1
3069 host = strrchr(user, ':');
3070 if(host) {
3071 /* annoying user split from 1.2.0, need to undo it */
3072 *host++ = '\0';
3073 gaim_account_set_string(account, MW_KEY_HOST, host);
3074 gaim_account_set_username(account, user);
3075
3076 } else {
3077 host = (char *) gaim_account_get_string(account, MW_KEY_HOST,
3078 MW_PLUGIN_DEFAULT_HOST);
3079 }
3080
3081 #else
3082 /* the 1.2.0 way to obtain the host string from an account split. I
3083 didn't like this, it didn't solve a problem, but it created a
3084 few. The above code undoes it. */
3085 host = strrchr(user, ':');
3086 if(host) *host++ = '\0';
3087
3088 if(! host) {
3089 const char *h;
3090 char *t;
3091
3092 /* for those without the host string, let's see if they have host
3093 specified in the account setting instead. */
3094
3095 h = gaim_account_get_string(account, MW_KEY_HOST, MW_PLUGIN_DEFAULT_HOST);
3096 if(h) {
3097 t = g_strdup_printf("%s:%s", user, h);
3098 gaim_account_set_username(account, t);
3099 g_free(t);
3100 host = (char *) h;
3101 }
3102 }
3103
3104 /* de-uglify */
3105 if(! gaim_account_get_alias(account))
3106 gaim_account_set_alias(account, user);
3107 #endif
3108
3109 if(! host || ! *host) {
3110 /* somehow, we don't have a host to connect to. Well, we need one
3111 to actually continue, so let's ask the user directly. */
3112 prompt_host(gc);
3113 return;
3114 }
3115
3116 port = gaim_account_get_int(account, MW_KEY_PORT, MW_PLUGIN_DEFAULT_PORT);
3117
3118 DEBUG_INFO("user: '%s'\n", user);
3119 DEBUG_INFO("host: '%s'\n", host);
3120 DEBUG_INFO("port: %u\n", port);
3121
3122 mwSession_setProperty(pd->session, SESSION_NO_SECRET,
3123 (char *) no_secret, NULL);
3124 mwSession_setProperty(pd->session, mwSession_AUTH_USER_ID, user, g_free);
3125 mwSession_setProperty(pd->session, mwSession_AUTH_PASSWORD, pass, NULL);
3126 mwSession_setProperty(pd->session, mwSession_CLIENT_TYPE_ID,
3127 GUINT_TO_POINTER(MW_CLIENT_TYPE_ID), NULL);
3128
3129 gaim_connection_update_progress(gc, "Connecting", 1, MW_CONNECT_STEPS);
3130
3131 if(gaim_proxy_connect(account, host, port, connect_cb, pd)) {
3132 gaim_connection_error(gc, "Unable to connect to host");
3133 }
3134 }
3135
3136
3137 static void mw_prpl_close(GaimConnection *gc) {
3138 struct mwGaimPluginData *pd;
3139
3140 g_return_if_fail(gc != NULL);
3141
3142 pd = gc->proto_data;
3143 g_return_if_fail(pd != NULL);
3144
3145 /* get rid of the blist save timeout */
3146 if(pd->save_event) {
3147 gaim_timeout_remove(pd->save_event);
3148 pd->save_event = 0;
3149 blist_store(pd);
3150 }
3151
3152 /* stop the session */
3153 mwSession_stop(pd->session, 0x00);
3154
3155 /* no longer necessary */
3156 gc->proto_data = NULL;
3157
3158 /* stop watching the socket */
3159 if(gc->inpa) {
3160 gaim_input_remove(gc->inpa);
3161 gc->inpa = 0;
3162 }
3163
3164 /* clean up the rest */
3165 mwGaimPluginData_free(pd);
3166 }
3167
3168
3169 static char *im_mime_content_id() {
3170 const char *c = "%03x@%05xmeanwhile";
3171 srand(time(0) ^ rand());
3172 return g_strdup_printf(c, rand() & 0xfff, rand() & 0xfffff);
3173 }
3174
3175
3176 static char *im_mime_content_type() {
3177 const char *c = "multipart/related; boundary=related_MW%03x_%04x";
3178 srand(time(0) ^ rand());
3179 return g_strdup_printf(c, rand() & 0xfff, rand() & 0xffff);
3180 }
3181
3182
3183 static const char *im_mime_img_content_type(GaimStoredImage *img) {
3184 const char *fn = gaim_imgstore_get_filename(img);
3185
3186 fn = strrchr(fn, '.');
3187 if(! fn) {
3188 return "image";
3189
3190 } else if(! strcmp(".png", fn)) {
3191 return "image/png";
3192
3193 } else if(! strcmp(".jpg", fn)) {
3194 return "image/jpeg";
3195
3196 } else if(! strcmp(".jpeg", fn)) {
3197 return "image/jpeg";
3198
3199 } else if(! strcmp(".gif", fn)) {
3200 return "image/gif";
3201
3202 } else {
3203 return "image";
3204 }
3205 }
3206
3207
3208 static char *im_mime_img_content_disp(GaimStoredImage *img) {
3209 const char *fn = gaim_imgstore_get_filename(img);
3210 return g_strdup_printf("attachment; filename=\"%s\"", fn);
3211 }
3212
3213
3214 static char *im_mime_convert(const char *message) {
3215 GString *str;
3216 GaimMimeDocument *doc;
3217 GaimMimePart *part;
3218
3219 GData *attr;
3220 char *tmp, *start, *end;
3221
3222 str = g_string_new(NULL);
3223
3224 doc = gaim_mime_document_new();
3225
3226 gaim_mime_document_set_field(doc, "Mime-Version", "1.0");
3227 gaim_mime_document_set_field(doc, "Content-Disposition", "inline");
3228
3229 tmp = im_mime_content_type();
3230 gaim_mime_document_set_field(doc, "Content-Type", tmp);
3231 g_free(tmp);
3232
3233 tmp = (char *) message;
3234 while(*tmp && gaim_markup_find_tag("img", tmp, (const char **) &start,
3235 (const char **) &end, &attr)) {
3236 char *id;
3237 GaimStoredImage *img = NULL;
3238
3239 gsize len = (start - tmp);
3240
3241 /* append the in-between-tags text */
3242 if(len) g_string_append_len(str, tmp, len);
3243
3244 /* find the imgstore data by the id tag */
3245 id = g_datalist_get_data(&attr, "id");
3246 if(id && *id)
3247 img = gaim_imgstore_get(atoi(id));
3248
3249 if(img) {
3250 char *cid;
3251 gpointer data;
3252 size_t size;
3253
3254 part = gaim_mime_part_new(doc);
3255
3256 data = im_mime_img_content_disp(img);
3257 gaim_mime_part_set_field(part, "Content-Disposition", data);
3258 g_free(data);
3259
3260 cid = im_mime_content_id();
3261 data = g_strdup_printf("<%s>", cid);
3262 gaim_mime_part_set_field(part, "Content-ID", data);
3263 g_free(data);
3264
3265 gaim_mime_part_set_field(part, "Content-transfer-encoding", "base64");
3266 gaim_mime_part_set_field(part, "Content-Type",
3267 im_mime_img_content_type(img));
3268
3269
3270 /* obtain and base64 encode the image data, and put it in the
3271 mime part */
3272 data = gaim_imgstore_get_data(img);
3273 size = gaim_imgstore_get_size(img);
3274 data = gaim_base64_encode(data, (gsize) size);
3275 gaim_mime_part_set_data(part, data);
3276 g_free(data);
3277
3278 /* append the modified tag */
3279 g_string_append_printf(str, "<img src=\"cid:%s\">", cid);
3280 g_free(cid);
3281
3282 } else {
3283 /* append the literal image tag, since we couldn't find a
3284 relative imgstore object */
3285 gsize len = (end - start) + 1;
3286 g_string_append_len(str, start, len);
3287 }
3288
3289 g_datalist_clear(&attr);
3290 tmp = end + 1;
3291 }
3292
3293 /* append left-overs */
3294 g_string_append(str, tmp);
3295
3296 part = gaim_mime_part_new(doc);
3297 gaim_mime_part_set_field(part, "Content-Type", "text/html");
3298 gaim_mime_part_set_field(part, "Content-Disposition", "inline");
3299 gaim_mime_part_set_field(part, "Content-Transfer-Encoding", "8bit");
3300
3301 gaim_mime_part_set_data(part, str->str);
3302 g_string_free(str, TRUE);
3303
3304 str = g_string_new(NULL);
3305 gaim_mime_document_write(doc, str);
3306 tmp = str->str;
3307 g_string_free(str, FALSE);
3308
3309 return tmp;
3310 }
3311
3312
3313 static int mw_prpl_send_im(GaimConnection *gc,
3314 const char *name,
3315 const char *message,
3316 GaimConvImFlags flags) {
3317
3318 struct mwGaimPluginData *pd;
3319 struct mwIdBlock who = { (char *) name, NULL };
3320 struct mwConversation *conv;
3321
3322 g_return_val_if_fail(gc != NULL, 0);
3323 pd = gc->proto_data;
3324
3325 g_return_val_if_fail(pd != NULL, 0);
3326
3327 conv = mwServiceIm_getConversation(pd->srvc_im, &who);
3328
3329 /* this detection of features to determine how to send the message
3330 (plain, html, or mime) is flawed because the other end of the
3331 conversation could close their channel at any time, rendering any
3332 existing formatting in an outgoing message innapropriate. The end
3333 result is that it may be possible that the other side of the
3334 conversation will receive a plaintext message with html contents,
3335 which is bad. I'm not sure how to fix this correctly. */
3336
3337 /* @todo support chunking messages over a certain size into multiple
3338 smaller messages */
3339
3340 if(strstr(message, "<img ") || strstr(message, "<IMG "))
3341 flags |= GAIM_CONV_IM_IMAGES;
3342
3343 if(mwConversation_isOpen(conv)) {
3344 char *msg = NULL;
3345 int ret;
3346
3347 if((flags & GAIM_CONV_IM_IMAGES) &&
3348 mwConversation_supports(conv, mwImSend_MIME)) {
3349
3350 msg = im_mime_convert(message);
3351 ret = mwConversation_send(conv, mwImSend_MIME, msg);
3352
3353 } else if(mwConversation_supports(conv, mwImSend_HTML)) {
3354
3355 /* need to do this to get the \n to <br> conversion */
3356 msg = gaim_strdup_withhtml(message);
3357 ret = mwConversation_send(conv, mwImSend_HTML, msg);
3358
3359 } else {
3360 ret = mwConversation_send(conv, mwImSend_PLAIN, message);
3361 }
3362
3363 g_free(msg);
3364 return !ret;
3365 }
3366
3367 /* queue up the message */
3368 convo_queue(conv, mwImSend_PLAIN, message);
3369
3370 if(! mwConversation_isPending(conv))
3371 mwConversation_open(conv);
3372
3373 return 1;
3374 }
3375
3376
3377 static int mw_prpl_send_typing(GaimConnection *gc, const char *name,
3378 int typing) {
3379
3380 struct mwGaimPluginData *pd;
3381 struct mwIdBlock who = { (char *) name, NULL };
3382 struct mwConversation *conv;
3383
3384 gpointer t = GINT_TO_POINTER(!! typing);
3385
3386 g_return_val_if_fail(gc != NULL, 0);
3387 pd = gc->proto_data;
3388
3389 g_return_val_if_fail(pd != NULL, 0);
3390
3391 conv = mwServiceIm_getConversation(pd->srvc_im, &who);
3392
3393 if(mwConversation_isOpen(conv))
3394 return ! mwConversation_send(conv, mwImSend_TYPING, t);
3395
3396 if(typing) {
3397 /* let's only open a channel for typing, not for not-typing.
3398 Otherwise two users in psychic mode will continually open
3399 conversations to each other, never able to get rid of them, as
3400 when the other person closes, it psychicaly opens again */
3401
3402 convo_queue(conv, mwImSend_TYPING, t);
3403
3404 if(! mwConversation_isPending(conv))
3405 mwConversation_open(conv);
3406 }
3407
3408 return 1;
3409 }
3410
3411
3412 static void mw_prpl_get_info(GaimConnection *gc, const char *who) {
3413
3414 struct mwGaimPluginData *pd;
3415 struct mwAwareIdBlock idb = { mwAware_USER, (char *) who, NULL };
3416
3417 GaimAccount *acct;
3418 GaimBuddy *b;
3419
3420 GString *str;
3421 const char *tmp;
3422 guint32 type;
3423
3424 pd = gc->proto_data;
3425
3426 acct = gaim_connection_get_account(gc);
3427 b = gaim_find_buddy(acct, who);
3428
3429 g_return_if_fail(b != NULL);
3430
3431 str = g_string_new(NULL);
3432
3433 g_string_append_printf(str, "<b>User ID:</b> %s<br>", b->name);
3434
3435 if(b->server_alias) {
3436 g_string_append_printf(str, "<b>Full Name:</b> %s<br>",
3437 b->server_alias);
3438 }
3439
3440 type = gaim_blist_node_get_int((GaimBlistNode *) b, BUDDY_KEY_CLIENT);
3441 if(type) {
3442 g_string_append(str, "<b>Last Known Client:</b> ");
3443
3444 tmp = mwLoginType_getName(type);
3445 if(tmp) {
3446 g_string_append(str, tmp);
3447 g_string_append(str, "<br>");
3448
3449 } else {
3450 g_string_append_printf(str, "Unknown (0x%04x)<br>", type);
3451 }
3452 }
3453
3454 tmp = user_supports_text(pd->srvc_aware, who);
3455 if(tmp) {
3456 g_string_append_printf(str, "<b>Supports:</b> %s<br>", tmp);
3457 g_free((char *) tmp);
3458 }
3459
3460 tmp = status_text(b);
3461 g_string_append_printf(str, "<b>Status:</b> %s", tmp);
3462
3463 g_string_append(str, "<hr>");
3464
3465 tmp = mwServiceAware_getText(pd->srvc_aware, &idb);
3466 g_string_append(str, tmp);
3467
3468 /* @todo emit a signal to allow a plugin to override the display of
3469 this notification, so that it can create its own */
3470
3471 gaim_notify_userinfo(gc, who, "Buddy Information",
3472 "Meanwhile User Status", NULL, str->str, NULL, NULL);
3473
3474 g_string_free(str, TRUE);
3475 }
3476
3477
3478 #if 0
3479 static void mw_prpl_set_away(GaimConnection *gc,
3480 const char *state,
3481 const char *message) {
3482 GaimAccount *acct;
3483 struct mwSession *session;
3484 struct mwUserStatus stat;
3485
3486 acct = gaim_connection_get_account(gc);
3487 g_return_if_fail(acct != NULL);
3488
3489 session = gc_to_session(gc);
3490 g_return_if_fail(session != NULL);
3491
3492 /* get a working copy of the current status */
3493 mwUserStatus_clone(&stat, mwSession_getUserStatus(session));
3494
3495 /* determine the state */
3496 if(state) {
3497 if(! strcmp(state, GAIM_AWAY_CUSTOM)) {
3498 if(message) {
3499 stat.status = mwStatus_AWAY;
3500 } else {
3501 stat.status = mwStatus_ACTIVE;
3502 }
3503
3504 } else if(! strcmp(state, MW_STATE_AWAY)) {
3505 stat.status = mwStatus_AWAY;
3506
3507 } else if(! strcmp(state, MW_STATE_BUSY)) {
3508 stat.status = mwStatus_BUSY;
3509
3510 } else if(! strcmp(state, MW_STATE_ACTIVE)) {
3511 stat.status = mwStatus_ACTIVE;
3512 }
3513
3514 } else {
3515 stat.status = mwStatus_ACTIVE;
3516 }
3517
3518 /* determine the message */
3519 if(! message) {
3520 switch(stat.status) {
3521 case mwStatus_AWAY:
3522 message = gaim_account_get_string(acct, MW_KEY_AWAY_MSG,
3523 MW_PLUGIN_DEFAULT_AWAY_MSG);
3524 break;
3525
3526 case mwStatus_BUSY:
3527 message = gaim_account_get_string(acct, MW_KEY_BUSY_MSG,
3528 MW_PLUGIN_DEFAULT_BUSY_MSG);
3529 break;
3530
3531 case mwStatus_ACTIVE:
3532 message = gaim_account_get_string(acct, MW_KEY_ACTIVE_MSG,
3533 MW_PLUGIN_DEFAULT_ACTIVE_MSG);
3534 stat.time = 0;
3535 break;
3536 }
3537 }
3538
3539 if(message) {
3540 /* all the possible non-NULL values of message up to this point
3541 are const, so we don't need to free them */
3542 message = gaim_markup_strip_html(message);
3543 }
3544
3545 /* out with the old */
3546 g_free(stat.desc);
3547 g_free(gc->away);
3548
3549 /* in with the new */
3550 stat.desc = (char *) message;
3551 gc->away = g_strdup(message);
3552
3553 mwSession_setUserStatus(session, &stat);
3554 mwUserStatus_clear(&stat);
3555 }
3556 #endif
3557
3558
3559 static void mw_prpl_set_idle(GaimConnection *gc, int time) {
3560 struct mwSession *session;
3561 struct mwUserStatus stat;
3562
3563 session = gc_to_session(gc);
3564 g_return_if_fail(session != NULL);
3565
3566 mwUserStatus_clone(&stat, mwSession_getUserStatus(session));
3567
3568 if(time > 0 && stat.status == mwStatus_ACTIVE) {
3569 stat.status = mwStatus_IDLE;
3570
3571 } else if(time == 0 && stat.status == mwStatus_IDLE) {
3572 stat.status = mwStatus_ACTIVE;
3573 }
3574
3575 /** @todo actually put the idle time in the user status */
3576
3577 mwSession_setUserStatus(session, &stat);
3578 mwUserStatus_clear(&stat);
3579 }
3580
3581
3582 static void add_resolved_done(const char *id, const char *name,
3583 GaimBuddy *buddy) {
3584 GaimAccount *acct;
3585 GaimConnection *gc;
3586 struct mwGaimPluginData *pd;
3587
3588 g_return_if_fail(id != NULL);
3589
3590 g_return_if_fail(buddy != NULL);
3591 acct = buddy->account;
3592
3593 g_return_if_fail(acct != NULL);
3594 gc = gaim_account_get_connection(acct);
3595
3596 g_return_if_fail(gc != NULL);
3597 pd = gc->proto_data;
3598
3599 gaim_blist_rename_buddy(buddy, id);
3600
3601 gaim_blist_server_alias_buddy(buddy, name);
3602 gaim_blist_node_set_string((GaimBlistNode *) buddy, BUDDY_KEY_NAME, name);
3603
3604 buddy_add(pd, buddy);
3605 }
3606
3607
3608 static void multi_resolved_cleanup(GaimRequestFields *fields) {
3609
3610 GaimRequestField *f;
3611 const GList *l;
3612
3613 f = gaim_request_fields_get_field(fields, "user");
3614 l = gaim_request_field_list_get_items(f);
3615
3616 for(; l; l = l->next) {
3617 const char *i = l->data;
3618 struct resolved_id *res;
3619
3620 res = gaim_request_field_list_get_data(f, i);
3621
3622 g_free(res->id);
3623 g_free(res->name);
3624 g_free(res);
3625 }
3626 }
3627
3628
3629 static void multi_resolved_cancel(GaimBuddy *buddy,
3630 GaimRequestFields *fields) {
3631 GaimConnection *gc;
3632 struct mwGaimPluginData *pd;
3633
3634 gc = gaim_account_get_connection(buddy->account);
3635 pd = gc->proto_data;
3636
3637 gaim_blist_remove_buddy(buddy);
3638 multi_resolved_cleanup(fields);
3639
3640 blist_schedule(pd);
3641 }
3642
3643
3644 static void multi_resolved_cb(GaimBuddy *buddy,
3645 GaimRequestFields *fields) {
3646 GaimRequestField *f;
3647 const GList *l;
3648
3649 f = gaim_request_fields_get_field(fields, "user");
3650 l = gaim_request_field_list_get_selected(f);
3651
3652 if(l) {
3653 const char *i = l->data;
3654 struct resolved_id *res;
3655
3656 res = gaim_request_field_list_get_data(f, i);
3657
3658 add_resolved_done(res->id, res->name, buddy);
3659 multi_resolved_cleanup(fields);
3660
3661 } else {
3662 multi_resolved_cancel(buddy, fields);
3663 }
3664 }
3665
3666
3667 static void multi_resolved_query(struct mwResolveResult *result,
3668 GaimBuddy *buddy) {
3669 GaimRequestFields *fields;
3670 GaimRequestFieldGroup *g;
3671 GaimRequestField *f;
3672 GList *l;
3673 char *msgA, *msgB;
3674
3675 GaimAccount *acct;
3676 GaimConnection *gc;
3677
3678 g_return_if_fail(buddy != NULL);
3679
3680 acct = buddy->account;
3681 g_return_if_fail(acct != NULL);
3682
3683 gc = gaim_account_get_connection(acct);
3684 g_return_if_fail(gc != NULL);
3685
3686 fields = gaim_request_fields_new();
3687
3688 g = gaim_request_field_group_new(NULL);
3689
3690 /* note that Gaim segfaults if you don't add the group to the fields
3691 before you add a required field to the group. Feh. */
3692 gaim_request_fields_add_group(fields, g);
3693
3694 f = gaim_request_field_list_new("user", "Possible Matches");
3695 gaim_request_field_list_set_multi_select(f, FALSE);
3696 gaim_request_field_set_required(f, TRUE);
3697
3698 for(l = result->matches; l; l = l->next) {
3699 struct mwResolveMatch *match = l->data;
3700 struct resolved_id *res = g_new0(struct resolved_id, 1);
3701 char *label;
3702
3703 res->id = g_strdup(match->id);
3704 res->name = g_strdup(match->name);
3705
3706 /* fixes bug 1178603 by making the selection label a combination
3707 of the full name and the user id. Problems arrise when multiple
3708 entries have identical labels */
3709 label = g_strdup_printf("%s (%s)", NSTR(res->name), NSTR(res->id));
3710 gaim_request_field_list_add(f, label, res);
3711 g_free(label);
3712 }
3713
3714 gaim_request_field_group_add_field(g, f);
3715
3716 msgA = ("An ambiguous user ID was entered");
3717 msgB = ("The identifier '%s' may possibly refer to any of the following"
3718 " users. Please select the correct user from the list below to"
3719 " add them to your buddy list.");
3720 msgB = g_strdup_printf(msgB, result->name);
3721
3722 gaim_request_fields(gc, "Select User to Add",
3723 msgA, msgB, fields,
3724 "Add User", G_CALLBACK(multi_resolved_cb),
3725 "Cancel", G_CALLBACK(multi_resolved_cancel),
3726 buddy);
3727 g_free(msgB);
3728 }
3729
3730
3731 static void add_buddy_resolved(struct mwServiceResolve *srvc,
3732 guint32 id, guint32 code, GList *results,
3733 gpointer b) {
3734
3735 struct mwResolveResult *res = NULL;
3736 GaimBuddy *buddy = b;
3737 GaimConnection *gc;
3738 struct mwGaimPluginData *pd;
3739
3740 gc = gaim_account_get_connection(buddy->account);
3741 pd = gc->proto_data;
3742
3743 if(results)
3744 res = results->data;
3745
3746 if(!code && res && res->matches) {
3747 if(g_list_length(res->matches) == 1) {
3748 struct mwResolveMatch *match = res->matches->data;
3749
3750 DEBUG_INFO("searched for %s, got only %s\n",
3751 NSTR(res->name), NSTR(match->id));
3752
3753 /* only one? that might be the right one! */
3754 if(strcmp(res->name, match->id)) {
3755 /* uh oh, the single result isn't identical to the search
3756 term, better safe then sorry, so let's make sure it's who
3757 the user meant to add */
3758 multi_resolved_query(res, buddy);
3759
3760 } else {
3761 /* same person, add 'em */
3762 add_resolved_done(match->id, match->name, buddy);
3763 }
3764
3765 } else {
3766 /* prompt user if more than one match was returned */
3767 multi_resolved_query(res, buddy);
3768 }
3769
3770 return;
3771 }
3772
3773 /* fall-through indicates that we couldn't find a matching user in
3774 the resolve service (ether error or zero results), so we remove
3775 this buddy */
3776
3777 DEBUG_INFO("no such buddy in community\n");
3778 gaim_blist_remove_buddy(buddy);
3779 blist_schedule(pd);
3780
3781 if(res && res->name) {
3782 /* compose and display an error message */
3783 char *msgA, *msgB;
3784
3785 msgA = "Unable to add user: user not found";
3786
3787 msgB = ("The identifier '%s' did not match any users in your"
3788 " Sametime community. This entry has been removed from"
3789 " your buddy list.");
3790 msgB = g_strdup_printf(msgB, NSTR(res->name));
3791
3792 gaim_notify_error(gc, "Unable to add user", msgA, msgB);
3793
3794 g_free(msgB);
3795 }
3796 }
3797
3798
3799 static void mw_prpl_add_buddy(GaimConnection *gc,
3800 GaimBuddy *buddy,
3801 GaimGroup *group) {
3802
3803 struct mwGaimPluginData *pd;
3804 struct mwServiceResolve *srvc;
3805 GList *query;
3806 enum mwResolveFlag flags;
3807 guint32 req;
3808
3809 pd = gc->proto_data;
3810 srvc = pd->srvc_resolve;
3811
3812 query = g_list_prepend(NULL, buddy->name);
3813 flags = mwResolveFlag_FIRST | mwResolveFlag_USERS;
3814
3815 req = mwServiceResolve_resolve(srvc, query, flags, add_buddy_resolved,
3816 buddy, NULL);
3817 g_list_free(query);
3818
3819 if(req == SEARCH_ERROR) {
3820 gaim_blist_remove_buddy(buddy);
3821 blist_schedule(pd);
3822 }
3823 }
3824
3825
3826 static void foreach_add_buddies(GaimGroup *group, GList *buddies,
3827 struct mwGaimPluginData *pd) {
3828
3829 struct mwAwareList *list;
3830
3831 list = list_ensure(pd, group);
3832 mwAwareList_addAware(list, buddies);
3833 g_list_free(buddies);
3834 }
3835
3836
3837 static void mw_prpl_add_buddies(GaimConnection *gc,
3838 GList *buddies,
3839 GList *groups) {
3840
3841 /** @todo make this use a single call to each mwAwareList */
3842
3843 struct mwGaimPluginData *pd;
3844 GHashTable *group_sets;
3845 struct mwAwareIdBlock *idbs, *idb;
3846
3847 pd = gc->proto_data;
3848
3849 /* map GaimGroup:GList of mwAwareIdBlock */
3850 group_sets = g_hash_table_new(g_direct_hash, g_direct_equal);
3851
3852 /* bunch of mwAwareIdBlock allocated at once, free'd at once */
3853 idb = idbs = g_new(struct mwAwareIdBlock, g_list_length(buddies));
3854
3855 /* first pass collects mwAwareIdBlock lists for each group */
3856 for(; buddies; buddies = buddies->next) {
3857 GaimBuddy *b = buddies->data;
3858 GaimGroup *g;
3859 const char *fn;
3860 GList *l;
3861
3862 /* nab the saved server alias and stick it on the buddy */
3863 fn = gaim_blist_node_get_string((GaimBlistNode *) b, BUDDY_KEY_NAME);
3864 gaim_blist_server_alias_buddy(b, fn);
3865
3866 /* convert GaimBuddy into a mwAwareIdBlock */
3867 idb->type = mwAware_USER;
3868 idb->user = (char *) b->name;
3869 idb->community = NULL;
3870
3871 /* put idb into the list associated with the buddy's group */
3872 g = gaim_find_buddys_group(b);
3873 l = g_hash_table_lookup(group_sets, g);
3874 l = g_list_prepend(l, idb++);
3875 g_hash_table_insert(group_sets, g, l);
3876 }
3877
3878 /* each group's buddies get added in one shot, and schedule the blist
3879 for saving */
3880 g_hash_table_foreach(group_sets, (GHFunc) foreach_add_buddies, pd);
3881 blist_schedule(pd);
3882
3883 /* cleanup */
3884 g_hash_table_destroy(group_sets);
3885 g_free(idbs);
3886 }
3887
3888
3889 static void mw_prpl_remove_buddy(GaimConnection *gc,
3890 GaimBuddy *buddy, GaimGroup *group) {
3891
3892 struct mwGaimPluginData *pd;
3893 struct mwAwareIdBlock idb = { mwAware_USER, buddy->name, NULL };
3894 struct mwAwareList *list;
3895
3896 GList *rem = g_list_prepend(NULL, &idb);
3897
3898 pd = gc->proto_data;
3899 group = gaim_find_buddys_group(buddy);
3900 list = list_ensure(pd, group);
3901
3902 mwAwareList_removeAware(list, rem);
3903 blist_schedule(pd);
3904
3905 g_list_free(rem);
3906 }
3907
3908
3909 static void privacy_fill(struct mwPrivacyInfo *priv,
3910 GSList *members) {
3911
3912 struct mwUserItem *u;
3913 guint count;
3914
3915 DEBUG_INFO("privacy_fill\n");
3916
3917 count = g_slist_length(members);
3918 DEBUG_INFO(" %u (%i) members\n", count, (int) count);
3919
3920 priv->count = count;
3921 priv->users = g_new0(struct mwUserItem, count);
3922
3923 while(count--) {
3924 u = priv->users + count;
3925 u->id = members->data;
3926 members = members->next;
3927 }
3928 }
3929
3930
3931 static void mw_prpl_set_permit_deny(GaimConnection *gc) {
3932 GaimAccount *acct;
3933 struct mwGaimPluginData *pd;
3934 struct mwSession *session;
3935
3936 struct mwPrivacyInfo privacy = {
3937 .deny = FALSE,
3938 .count = 0,
3939 .users = NULL,
3940 };
3941
3942 g_return_if_fail(gc != NULL);
3943
3944 acct = gaim_connection_get_account(gc);
3945 g_return_if_fail(acct != NULL);
3946
3947 pd = gc->proto_data;
3948 g_return_if_fail(pd != NULL);
3949
3950 session = pd->session;
3951 g_return_if_fail(session != NULL);
3952
3953 switch(acct->perm_deny) {
3954 case GAIM_PRIVACY_DENY_USERS:
3955 DEBUG_INFO("GAIM_PRIVACY_DENY_USERS\n");
3956 privacy_fill(&privacy, acct->deny);
3957 /* fall-through */
3958
3959 case GAIM_PRIVACY_ALLOW_ALL:
3960 DEBUG_INFO("GAIM_PRIVACY_ALLOW_ALL\n");
3961 privacy.deny = TRUE;
3962 break;
3963
3964 case GAIM_PRIVACY_ALLOW_USERS:
3965 DEBUG_INFO("GAIM_PRIVACY_ALLOW_USERS\n");
3966 privacy_fill(&privacy, acct->permit);
3967 /* fall-through */
3968
3969 case GAIM_PRIVACY_DENY_ALL:
3970 DEBUG_INFO("GAIM_PRIVACY_DENY_ALL\n");
3971 privacy.deny = FALSE;
3972 break;
3973
3974 default:
3975 DEBUG_INFO("acct->perm_deny is 0x%x\n", acct->perm_deny);
3976 g_return_if_reached();
3977 }
3978
3979 mwSession_setPrivacyInfo(session, &privacy);
3980 g_free(privacy.users);
3981 }
3982
3983
3984 static void mw_prpl_add_permit(GaimConnection *gc, const char *name) {
3985 mw_prpl_set_permit_deny(gc);
3986 }
3987
3988
3989 static void mw_prpl_add_deny(GaimConnection *gc, const char *name) {
3990 mw_prpl_set_permit_deny(gc);
3991 }
3992
3993
3994 static void mw_prpl_rem_permit(GaimConnection *gc, const char *name) {
3995 mw_prpl_set_permit_deny(gc);
3996 }
3997
3998
3999 static void mw_prpl_rem_deny(GaimConnection *gc, const char *name) {
4000 mw_prpl_set_permit_deny(gc);
4001 }
4002
4003
4004 static struct mwConference *conf_find(struct mwServiceConference *srvc,
4005 const char *name) {
4006 GList *l, *ll;
4007 struct mwConference *conf = NULL;
4008
4009 ll = mwServiceConference_getConferences(srvc);
4010 for(l = ll; l; l = l->next) {
4011 struct mwConference *c = l->data;
4012 if(! strcmp(name, mwConference_getName(c))) {
4013 conf = c;
4014 break;
4015 }
4016 }
4017 g_list_free(ll);
4018
4019 return conf;
4020 }
4021
4022
4023 static void mw_prpl_join_chat(GaimConnection *gc,
4024 GHashTable *components) {
4025
4026 struct mwGaimPluginData *pd;
4027 struct mwServiceConference *srvc;
4028 struct mwConference *conf = NULL;
4029 char *c, *t;
4030
4031 pd = gc->proto_data;
4032 srvc = pd->srvc_conf;
4033
4034 c = g_hash_table_lookup(components, CHAT_KEY_NAME);
4035 t = g_hash_table_lookup(components, CHAT_KEY_TOPIC);
4036
4037 if(c) conf = conf_find(srvc, c);
4038
4039 if(conf) {
4040 DEBUG_INFO("accepting conference invitation\n");
4041 mwConference_accept(conf);
4042
4043 } else {
4044 DEBUG_INFO("creating new conference\n");
4045 conf = mwConference_new(srvc, t);
4046 mwConference_open(conf);
4047 }
4048 }
4049
4050
4051 static void mw_prpl_reject_chat(GaimConnection *gc,
4052 GHashTable *components) {
4053
4054 struct mwGaimPluginData *pd;
4055 struct mwServiceConference *srvc;
4056 char *c;
4057
4058 pd = gc->proto_data;
4059 srvc = pd->srvc_conf;
4060
4061 c = g_hash_table_lookup(components, CHAT_KEY_NAME);
4062 if(c) {
4063 struct mwConference *conf = conf_find(srvc, c);
4064 if(conf) mwConference_reject(conf, ERR_SUCCESS, "Declined");
4065 }
4066 }
4067
4068
4069 static char *mw_prpl_get_chat_name(GHashTable *components) {
4070 return g_hash_table_lookup(components, CHAT_KEY_NAME);
4071 }
4072
4073
4074 static void mw_prpl_chat_invite(GaimConnection *gc,
4075 int id,
4076 const char *invitation,
4077 const char *who) {
4078
4079 struct mwGaimPluginData *pd;
4080 struct mwConference *conf;
4081 struct mwIdBlock idb = { (char *) who, NULL };
4082
4083 pd = gc->proto_data;
4084
4085 g_return_if_fail(pd != NULL);
4086 conf = ID_TO_CONF(pd, id);
4087
4088 g_return_if_fail(conf != NULL);
4089
4090 mwConference_invite(conf, &idb, invitation);
4091 }
4092
4093
4094 static void mw_prpl_chat_leave(GaimConnection *gc,
4095 int id) {
4096
4097 struct mwGaimPluginData *pd;
4098 struct mwConference *conf;
4099
4100 pd = gc->proto_data;
4101
4102 g_return_if_fail(pd != NULL);
4103 conf = ID_TO_CONF(pd, id);
4104
4105 g_return_if_fail(conf != NULL);
4106
4107 mwConference_destroy(conf, ERR_SUCCESS, "Leaving");
4108 }
4109
4110
4111 static void mw_prpl_chat_whisper(GaimConnection *gc,
4112 int id,
4113 const char *who,
4114 const char *message) {
4115
4116 mw_prpl_send_im(gc, who, message, 0);
4117 }
4118
4119
4120 static int mw_prpl_chat_send(GaimConnection *gc,
4121 int id,
4122 const char *message) {
4123
4124 struct mwGaimPluginData *pd;
4125 struct mwConference *conf;
4126
4127 pd = gc->proto_data;
4128
4129 g_return_val_if_fail(pd != NULL, 0);
4130 conf = ID_TO_CONF(pd, id);
4131
4132 g_return_val_if_fail(conf != NULL, 0);
4133
4134 return ! mwConference_sendText(conf, message);
4135 }
4136
4137
4138 static void mw_prpl_keepalive(GaimConnection *gc) {
4139 struct mwSession *session;
4140
4141 g_return_if_fail(gc != NULL);
4142
4143 session = gc_to_session(gc);
4144 g_return_if_fail(session != NULL);
4145
4146 mwSession_sendKeepalive(session);
4147 }
4148
4149
4150 static void mw_prpl_alias_buddy(GaimConnection *gc,
4151 const char *who,
4152 const char *alias) {
4153
4154 struct mwGaimPluginData *pd = gc->proto_data;
4155 g_return_if_fail(pd != NULL);
4156
4157 /* it's a change to the buddy list, so we've gotta reflect that in
4158 the server copy */
4159
4160 blist_schedule(pd);
4161 }
4162
4163
4164 static void mw_prpl_group_buddy(GaimConnection *gc,
4165 const char *who,
4166 const char *old_group,
4167 const char *new_group) {
4168
4169 struct mwAwareIdBlock idb = { mwAware_USER, (char *) who, NULL };
4170 GList *gl = g_list_prepend(NULL, &idb);
4171
4172 struct mwGaimPluginData *pd = gc->proto_data;
4173 GaimGroup *group;
4174 struct mwAwareList *list;
4175
4176 /* add who to new_group's aware list */
4177 group = gaim_find_group(new_group);
4178 list = list_ensure(pd, group);
4179 mwAwareList_addAware(list, gl);
4180
4181 /* remove who from old_group's aware list */
4182 group = gaim_find_group(old_group);
4183 list = list_ensure(pd, group);
4184 mwAwareList_removeAware(list, gl);
4185
4186 g_list_free(gl);
4187
4188 /* schedule the changes to be saved */
4189 blist_schedule(pd);
4190 }
4191
4192
4193 static void mw_prpl_rename_group(GaimConnection *gc,
4194 const char *old,
4195 GaimGroup *group,
4196 GList *buddies) {
4197
4198 struct mwGaimPluginData *pd = gc->proto_data;
4199 g_return_if_fail(pd != NULL);
4200
4201 /* it's a change in the buddy list, so we've gotta reflect that in
4202 the server copy. Also, having this function should prevent all
4203 those buddies from being removed and re-added. We don't really
4204 give a crap what the group is named in Gaim other than to record
4205 that as the group name/alias */
4206
4207 blist_schedule(pd);
4208 }
4209
4210
4211 static void mw_prpl_buddy_free(GaimBuddy *buddy) {
4212 /* I don't think we have any cleanup for buddies yet */
4213 ;
4214 }
4215
4216
4217 static void mw_prpl_convo_closed(GaimConnection *gc, const char *who) {
4218 struct mwGaimPluginData *pd = gc->proto_data;
4219 struct mwServiceIm *srvc;
4220 struct mwConversation *conv;
4221 struct mwIdBlock idb = { (char *) who, NULL };
4222
4223 g_return_if_fail(pd != NULL);
4224
4225 srvc = pd->srvc_im;
4226 g_return_if_fail(srvc != NULL);
4227
4228 conv = mwServiceIm_findConversation(srvc, &idb);
4229 if(! conv) return;
4230
4231 if(mwConversation_isOpen(conv))
4232 mwConversation_free(conv);
4233 }
4234
4235
4236 static const char *mw_prpl_normalize(const GaimAccount *account,
4237 const char *id) {
4238
4239 /* code elsewhere assumes that the return value points to different
4240 memory than the passed value, but it won't free the normalized
4241 data. wtf? */
4242
4243 static char buf[BUF_LEN];
4244 strncpy(buf, id, sizeof(buf));
4245 return buf;
4246 }
4247
4248
4249 static void mw_prpl_remove_group(GaimConnection *gc, GaimGroup *group) {
4250 struct mwGaimPluginData *pd;
4251 struct mwAwareList *list;
4252
4253 pd = gc->proto_data;
4254 g_return_if_fail(pd != NULL);
4255 g_return_if_fail(pd->group_list_map != NULL);
4256
4257 list = g_hash_table_lookup(pd->group_list_map, group);
4258
4259 if(list) {
4260 g_hash_table_remove(pd->group_list_map, list);
4261 g_hash_table_remove(pd->group_list_map, group);
4262 mwAwareList_free(list);
4263
4264 blist_schedule(pd);
4265 }
4266 }
4267
4268
4269 static gboolean mw_prpl_can_receive_file(GaimConnection *gc,
4270 const char *who) {
4271 struct mwGaimPluginData *pd;
4272 struct mwServiceAware *srvc;
4273 GaimAccount *acct;
4274
4275 g_return_val_if_fail(gc != NULL, FALSE);
4276
4277 pd = gc->proto_data;
4278 g_return_val_if_fail(pd != NULL, FALSE);
4279
4280 srvc = pd->srvc_aware;
4281 g_return_val_if_fail(srvc != NULL, FALSE);
4282
4283 acct = gaim_connection_get_account(gc);
4284 g_return_val_if_fail(acct != NULL, FALSE);
4285
4286 return gaim_find_buddy(acct, who) &&
4287 user_supports(srvc, who, mwAttribute_FILE_TRANSFER);
4288 }
4289
4290
4291 static void ft_outgoing_init(GaimXfer *xfer) {
4292 GaimAccount *acct;
4293 GaimConnection *gc;
4294
4295 struct mwGaimPluginData *pd;
4296 struct mwServiceFileTransfer *srvc;
4297 struct mwFileTransfer *ft;
4298
4299 const char *filename;
4300 gsize filesize;
4301 FILE *fp;
4302
4303 struct mwIdBlock idb = { NULL, NULL };
4304
4305 DEBUG_INFO("ft_outgoing_init\n");
4306
4307 acct = gaim_xfer_get_account(xfer);
4308 gc = gaim_account_get_connection(acct);
4309 pd = gc->proto_data;
4310 srvc = pd->srvc_ft;
4311
4312 filename = gaim_xfer_get_local_filename(xfer);
4313 filesize = gaim_xfer_get_size(xfer);
4314 idb.user = xfer->who;
4315
4316 /* test that we can actually send the file */
4317 fp = g_fopen(filename, "rb");
4318 if(! fp) {
4319 char *msg = g_strdup_printf("Error reading %s: \n%s\n",
4320 filename, strerror(errno));
4321 gaim_xfer_error(gaim_xfer_get_type(xfer), acct, xfer->who, msg);
4322 g_free(msg);
4323 return;
4324 }
4325 fclose(fp);
4326
4327 {
4328 char *tmp = strrchr(filename, G_DIR_SEPARATOR);
4329 if(tmp++) filename = tmp;
4330 }
4331
4332 ft = mwFileTransfer_new(srvc, &idb, NULL, filename, filesize);
4333
4334 gaim_xfer_ref(xfer);
4335 mwFileTransfer_setClientData(ft, xfer, (GDestroyNotify) gaim_xfer_unref);
4336 xfer->data = ft;
4337
4338 mwFileTransfer_offer(ft);
4339 }
4340
4341
4342 static void ft_outgoing_cancel(GaimXfer *xfer) {
4343 struct mwFileTransfer *ft = xfer->data;
4344 if(ft) mwFileTransfer_cancel(ft);
4345 }
4346
4347
4348 static void mw_prpl_send_file(GaimConnection *gc,
4349 const char *who, const char *file) {
4350
4351 /** @todo depends on the meanwhile implementation of the file
4352 transfer service */
4353
4354 GaimAccount *acct;
4355 GaimXfer *xfer;
4356
4357 DEBUG_INFO("mw_prpl_send_file\n");
4358
4359 acct = gaim_connection_get_account(gc);
4360
4361 xfer = gaim_xfer_new(acct, GAIM_XFER_SEND, who);
4362 gaim_xfer_set_init_fnc(xfer, ft_outgoing_init);
4363 gaim_xfer_set_cancel_send_fnc(xfer, ft_outgoing_cancel);
4364
4365 if(file) {
4366 DEBUG_INFO("file != NULL\n");
4367 gaim_xfer_request_accepted(xfer, file);
4368
4369 } else {
4370 DEBUG_INFO("file == NULL\n");
4371 gaim_xfer_request(xfer);
4372 }
4373 }
4374
4375
4376 static GaimPluginProtocolInfo mw_prpl_info = {
4377 .options = OPT_PROTO_IM_IMAGE,
4378 .user_splits = NULL, /*< set in mw_plugin_init */
4379 .protocol_options = NULL, /*< set in mw_plugin_init */
4380 .icon_spec = NO_BUDDY_ICONS,
4381 .list_icon = mw_prpl_list_icon,
4382 .list_emblems = mw_prpl_list_emblems,
4383 .status_text = mw_prpl_status_text,
4384 .tooltip_text = mw_prpl_tooltip_text,
4385 .status_types = mw_prpl_status_types,
4386 .blist_node_menu = mw_prpl_blist_node_menu,
4387 .chat_info = mw_prpl_chat_info,
4388 .chat_info_defaults = mw_prpl_chat_info_defaults,
4389 .login = mw_prpl_login,
4390 .close = mw_prpl_close,
4391 .send_im = mw_prpl_send_im,
4392 .set_info = NULL,
4393 .send_typing = mw_prpl_send_typing,
4394 .get_info = mw_prpl_get_info,
4395 .set_idle = mw_prpl_set_idle,
4396 .change_passwd = NULL,
4397 .add_buddy = mw_prpl_add_buddy,
4398 .add_buddies = mw_prpl_add_buddies,
4399 .remove_buddy = mw_prpl_remove_buddy,
4400 .remove_buddies = NULL,
4401 .add_permit = mw_prpl_add_permit,
4402 .add_deny = mw_prpl_add_deny,
4403 .rem_permit = mw_prpl_rem_permit,
4404 .rem_deny = mw_prpl_rem_deny,
4405 .set_permit_deny = mw_prpl_set_permit_deny,
4406 .warn = NULL,
4407 .join_chat = mw_prpl_join_chat,
4408 .reject_chat = mw_prpl_reject_chat,
4409 .get_chat_name = mw_prpl_get_chat_name,
4410 .chat_invite = mw_prpl_chat_invite,
4411 .chat_leave = mw_prpl_chat_leave,
4412 .chat_whisper = mw_prpl_chat_whisper,
4413 .chat_send = mw_prpl_chat_send,
4414 .keepalive = mw_prpl_keepalive,
4415 .register_user = NULL,
4416 .get_cb_info = NULL,
4417 .get_cb_away = NULL,
4418 .alias_buddy = mw_prpl_alias_buddy,
4419 .group_buddy = mw_prpl_group_buddy,
4420 .rename_group = mw_prpl_rename_group,
4421 .buddy_free = mw_prpl_buddy_free,
4422 .convo_closed = mw_prpl_convo_closed,
4423 .normalize = mw_prpl_normalize,
4424 .set_buddy_icon = NULL,
4425 .remove_group = mw_prpl_remove_group,
4426 .get_cb_real_name = NULL,
4427 .set_chat_topic = NULL,
4428 .find_blist_chat = NULL,
4429 .roomlist_get_list = NULL,
4430 .roomlist_expand_category = NULL,
4431 .can_receive_file = mw_prpl_can_receive_file,
4432 .send_file = mw_prpl_send_file,
4433 };
4434
4435
4436 static GaimPluginPrefFrame *
4437 mw_plugin_get_plugin_pref_frame(GaimPlugin *plugin) {
4438 GaimPluginPrefFrame *frame;
4439 GaimPluginPref *pref;
4440
4441 #if 0
4442 char *msg;
4443 #endif
4444
4445 frame = gaim_plugin_pref_frame_new();
4446
4447 pref = gaim_plugin_pref_new_with_label("Remotely Stored Buddy List");
4448 gaim_plugin_pref_frame_add(frame, pref);
4449
4450
4451 pref = gaim_plugin_pref_new_with_name(MW_PRPL_OPT_BLIST_ACTION);
4452 gaim_plugin_pref_set_label(pref, "Buddy List Storage Mode");
4453
4454 gaim_plugin_pref_set_type(pref, GAIM_PLUGIN_PREF_CHOICE);
4455 gaim_plugin_pref_add_choice(pref, "Local Buddy List Only",
4456 GINT_TO_POINTER(BLIST_CHOICE_NONE));
4457 gaim_plugin_pref_add_choice(pref, "Merge List from Server",
4458 GINT_TO_POINTER(BLIST_CHOICE_LOAD));
4459 gaim_plugin_pref_add_choice(pref, "Merge and Save List to Server",
4460 GINT_TO_POINTER(BLIST_CHOICE_SAVE));
4461
4462 #if 0
4463 /* possible ways to handle:
4464 - mark all buddies as NO_SAVE
4465 - load server list, delete all local buddies not in server list
4466 */
4467 gaim_plugin_pref_add_choice(pref, "Server Buddy List Only",
4468 GINT_TO_POINTER(BLIST_CHOISE_SERVER));
4469 #endif
4470
4471 gaim_plugin_pref_frame_add(frame, pref);
4472
4473 pref = gaim_plugin_pref_new();
4474 gaim_plugin_pref_set_type(pref, GAIM_PLUGIN_PREF_INFO);
4475 gaim_plugin_pref_set_label(pref, BLIST_WARNING);
4476 gaim_plugin_pref_frame_add(frame, pref);
4477
4478 pref = gaim_plugin_pref_new_with_label("General Options");
4479 gaim_plugin_pref_frame_add(frame, pref);
4480
4481 pref = gaim_plugin_pref_new_with_name(MW_PRPL_OPT_FORCE_LOGIN);
4482 gaim_plugin_pref_set_type(pref, GAIM_PLUGIN_PREF_NONE);
4483 gaim_plugin_pref_set_label(pref, "Force Login (Ignore Login Redirects)");
4484 gaim_plugin_pref_frame_add(frame, pref);
4485
4486 pref = gaim_plugin_pref_new_with_name(MW_PRPL_OPT_PSYCHIC);
4487 gaim_plugin_pref_set_type(pref, GAIM_PLUGIN_PREF_NONE);
4488 gaim_plugin_pref_set_label(pref, "Enable Psychic Mode");
4489 gaim_plugin_pref_frame_add(frame, pref);
4490
4491 pref = gaim_plugin_pref_new_with_name(MW_PRPL_OPT_SAVE_DYNAMIC);
4492 gaim_plugin_pref_set_type(pref, GAIM_PLUGIN_PREF_NONE);
4493 gaim_plugin_pref_set_label(pref, "Save NAB group members locally");
4494 gaim_plugin_pref_frame_add(frame, pref);
4495
4496 #if 0
4497 pref = gaim_plugin_pref_new_with_label("Credits");
4498 gaim_plugin_pref_frame_add(frame, pref);
4499
4500 msg = ( PLUGIN_NAME " - " PLUGIN_DESC "\n"
4501 "Version " VERSION "\n"
4502 PLUGIN_AUTHOR "\n"
4503 PLUGIN_HOMEPAGE );
4504
4505 pref = gaim_plugin_pref_new();
4506 gaim_plugin_pref_set_type(pref, GAIM_PLUGIN_PREF_INFO);
4507 gaim_plugin_pref_set_label(pref, msg);
4508 gaim_plugin_pref_frame_add(frame, pref);
4509 #endif
4510
4511 return frame;
4512 }
4513
4514
4515 static GaimPluginUiInfo mw_plugin_ui_info = {
4516 .get_plugin_pref_frame = mw_plugin_get_plugin_pref_frame,
4517 };
4518
4519
4520 static void status_msg_action_cb(GaimConnection *gc,
4521 GaimRequestFields *fields) {
4522 GaimAccount *acct;
4523 GaimRequestField *f;
4524 const char *msg;
4525 /* gboolean prompt; */
4526
4527 struct mwGaimPluginData *pd;
4528 struct mwServiceStorage *srvc;
4529 struct mwStorageUnit *unit;
4530
4531 pd = gc->proto_data;
4532 srvc = pd->srvc_store;
4533
4534 acct = gaim_connection_get_account(gc);
4535
4536 f = gaim_request_fields_get_field(fields, "active");
4537 msg = gaim_request_field_string_get_value(f);
4538 gaim_account_set_string(acct, MW_KEY_ACTIVE_MSG, msg);
4539 unit = mwStorageUnit_newString(mwStore_ACTIVE_MESSAGES, msg);
4540 mwServiceStorage_save(srvc, unit, NULL, NULL, NULL);
4541
4542 f = gaim_request_fields_get_field(fields, "away");
4543 msg = gaim_request_field_string_get_value(f);
4544 gaim_account_set_string(acct, MW_KEY_AWAY_MSG, msg);
4545 unit = mwStorageUnit_newString(mwStore_AWAY_MESSAGES, msg);
4546 mwServiceStorage_save(srvc, unit, NULL, NULL, NULL);
4547
4548 f = gaim_request_fields_get_field(fields, "busy");
4549 msg = gaim_request_field_string_get_value(f);
4550 gaim_account_set_string(acct, MW_KEY_BUSY_MSG, msg);
4551 unit = mwStorageUnit_newString(mwStore_BUSY_MESSAGES, msg);
4552 mwServiceStorage_save(srvc, unit, NULL, NULL, NULL);
4553
4554 #if 0
4555 /** @todo not yet used. It should be possible to prompt the user for
4556 a message (ala the Sametime Connect client) when changing to one
4557 of the default states, and that preference is here */
4558 f = gaim_request_fields_get_field(fields, "prompt");
4559 prompt = gaim_request_field_bool_get_value(f);
4560 gaim_account_set_bool(acct, MW_KEY_MSG_PROMPT, prompt);
4561 #endif
4562
4563 #if 0
4564 /* XXX */
4565 /* need to propagate the message change if we're in any of those
4566 default states */
4567 msg = NULL;
4568 if(!gc->away_state || !strcmp(gc->away_state, MW_STATE_ACTIVE)) {
4569 msg = MW_STATE_ACTIVE;
4570 } else if(gc->away_state && !strcmp(gc->away_state, MW_STATE_AWAY)) {
4571 msg = MW_STATE_AWAY;
4572 } else if(gc->away_state && !strcmp(gc->away_state, MW_STATE_BUSY)) {
4573 msg = MW_STATE_BUSY;
4574 }
4575 if(msg)
4576 serv_set_away(gc, msg, NULL);
4577 #endif
4578 }
4579
4580
4581 /** Prompt for messages for the three default status types. These
4582 values should be mirrored as strings in the storage service */
4583 static void status_msg_action(GaimPluginAction *act) {
4584 GaimConnection *gc;
4585 GaimAccount *acct;
4586
4587 GaimRequestFields *fields;
4588 GaimRequestFieldGroup *g;
4589 GaimRequestField *f;
4590
4591 char *msgA, *msgB;
4592 const char *val;
4593 /* gboolean prompt; */
4594
4595 gc = act->context;
4596 acct = gaim_connection_get_account(gc);
4597
4598 fields = gaim_request_fields_new();
4599
4600 g = gaim_request_field_group_new(NULL);
4601 gaim_request_fields_add_group(fields, g);
4602
4603 val = gaim_account_get_string(acct, MW_KEY_ACTIVE_MSG,
4604 MW_PLUGIN_DEFAULT_ACTIVE_MSG);
4605 f = gaim_request_field_string_new("active", "Active Message", val, FALSE);
4606 gaim_request_field_set_required(f, FALSE);
4607 gaim_request_field_group_add_field(g, f);
4608
4609 val = gaim_account_get_string(acct, MW_KEY_AWAY_MSG,
4610 MW_PLUGIN_DEFAULT_AWAY_MSG);
4611 f = gaim_request_field_string_new("away", "Away Message", val, FALSE);
4612 gaim_request_field_set_required(f, FALSE);
4613 gaim_request_field_group_add_field(g, f);
4614
4615 val = gaim_account_get_string(acct, MW_KEY_BUSY_MSG,
4616 MW_PLUGIN_DEFAULT_BUSY_MSG);
4617 f = gaim_request_field_string_new("busy", "Busy Message", val, FALSE);
4618 gaim_request_field_set_required(f, FALSE);
4619 gaim_request_field_group_add_field(g, f);
4620
4621 #if 0
4622 /** @todo not yet used. It should be possible to prompt the user for
4623 a message (ala the Sametime Connect client) when changing to one
4624 of the default states, and that preference is here */
4625 prompt = gaim_account_get_bool(acct, MW_KEY_MSG_PROMPT, FALSE);
4626 f = gaim_request_field_bool_new("prompt",
4627 ("Prompt for message when changing"
4628 " to one of these states?"), FALSE);
4629 gaim_request_field_group_add_field(g, f);
4630 #endif
4631
4632 msgA = ("Default status messages");
4633 msgB = ("");
4634
4635 gaim_request_fields(gc, "Edit Status Messages",
4636 msgA, msgB, fields,
4637 _("OK"), G_CALLBACK(status_msg_action_cb),
4638 _("Cancel"), NULL,
4639 gc);
4640 }
4641
4642
4643 static void st_import_action_cb(GaimConnection *gc, char *filename) {
4644 struct mwSametimeList *l;
4645
4646 FILE *file;
4647 char buf[BUF_LEN];
4648 size_t len;
4649
4650 GString *str;
4651
4652 file = fopen(filename, "r");
4653 g_return_if_fail(file != NULL);
4654
4655 str = g_string_new(NULL);
4656 while( (len = fread(buf, 1, BUF_LEN, file)) ) {
4657 g_string_append_len(str, buf, len);
4658 }
4659
4660 fclose(file);
4661
4662 l = mwSametimeList_load(str->str);
4663 g_string_free(str, TRUE);
4664
4665 blist_import(gc, l);
4666 mwSametimeList_free(l);
4667 }
4668
4669
4670 /** prompts for a file to import blist from */
4671 static void st_import_action(GaimPluginAction *act) {
4672 GaimConnection *gc;
4673 GaimAccount *account;
4674 char *title;
4675
4676 gc = act->context;
4677 account = gaim_connection_get_account(gc);
4678 title = g_strdup_printf("Import Sametime List for Account %s",
4679 gaim_account_get_username(account));
4680
4681 gaim_request_file(gc, title, NULL, FALSE,
4682 G_CALLBACK(st_import_action_cb), NULL,
4683 gc);
4684
4685 g_free(title);
4686 }
4687
4688
4689 static void st_export_action_cb(GaimConnection *gc, char *filename) {
4690 struct mwSametimeList *l;
4691 char *str;
4692 FILE *file;
4693
4694 file = fopen(filename, "w");
4695 g_return_if_fail(file != NULL);
4696
4697 l = mwSametimeList_new();
4698 blist_export(gc, l);
4699 str = mwSametimeList_store(l);
4700 mwSametimeList_free(l);
4701
4702 fprintf(file, "%s", str);
4703 fclose(file);
4704
4705 g_free(str);
4706 }
4707
4708
4709 /** prompts for a file to export blist to */
4710 static void st_export_action(GaimPluginAction *act) {
4711 GaimConnection *gc;
4712 GaimAccount *account;
4713 char *title;
4714
4715 gc = act->context;
4716 account = gaim_connection_get_account(gc);
4717 title = g_strdup_printf("Export Sametime List for Account %s",
4718 gaim_account_get_username(account));
4719
4720 gaim_request_file(gc, title, NULL, TRUE,
4721 G_CALLBACK(st_export_action_cb), NULL,
4722 gc);
4723
4724 g_free(title);
4725 }
4726
4727
4728 static void remote_group_multi_cleanup(gpointer ignore,
4729 GaimRequestFields *fields) {
4730
4731 GaimRequestField *f;
4732 const GList *l;
4733
4734 f = gaim_request_fields_get_field(fields, "group");
4735 l = gaim_request_field_list_get_items(f);
4736
4737 for(; l; l = l->next) {
4738 const char *i = l->data;
4739 struct resolved_id *res;
4740
4741 res = gaim_request_field_list_get_data(f, i);
4742
4743 g_free(res->id);
4744 g_free(res->name);
4745 g_free(res);
4746 }
4747 }
4748
4749
4750 static void remote_group_done(struct mwGaimPluginData *pd,
4751 const char *id, const char *name) {
4752 GaimConnection *gc;
4753 GaimAccount *acct;
4754 GaimGroup *group;
4755 GaimBlistNode *gn;
4756 const char *owner;
4757
4758 g_return_if_fail(pd != NULL);
4759
4760 gc = pd->gc;
4761 acct = gaim_connection_get_account(gc);
4762
4763 /* collision checking */
4764 group = gaim_find_group(name);
4765 if(group) {
4766 char *msgA, *msgB;
4767
4768 msgA = "Unable to add group: group exists";
4769 msgB = "A group named '%s' already exists in your buddy list.";
4770 msgB = g_strdup_printf(msgB, name);
4771
4772 gaim_notify_error(gc, "Unable to add group", msgA, msgB);
4773
4774 g_free(msgB);
4775 return;
4776 }
4777
4778 group = gaim_group_new(name);
4779 gn = (GaimBlistNode *) group;
4780
4781 owner = gaim_account_get_username(acct);
4782
4783 gaim_blist_node_set_string(gn, GROUP_KEY_NAME, id);
4784 gaim_blist_node_set_int(gn, GROUP_KEY_TYPE, mwSametimeGroup_DYNAMIC);
4785 gaim_blist_node_set_string(gn, GROUP_KEY_OWNER, owner);
4786 gaim_blist_add_group(group, NULL);
4787
4788 group_add(pd, group);
4789 blist_schedule(pd);
4790 }
4791
4792
4793 static void remote_group_multi_cb(struct mwGaimPluginData *pd,
4794 GaimRequestFields *fields) {
4795 GaimRequestField *f;
4796 const GList *l;
4797
4798 f = gaim_request_fields_get_field(fields, "group");
4799 l = gaim_request_field_list_get_selected(f);
4800
4801 if(l) {
4802 const char *i = l->data;
4803 struct resolved_id *res;
4804
4805 res = gaim_request_field_list_get_data(f, i);
4806 remote_group_done(pd, res->id, res->name);
4807 }
4808
4809 remote_group_multi_cleanup(NULL, fields);
4810 }
4811
4812
4813 static void remote_group_multi(struct mwResolveResult *result,
4814 struct mwGaimPluginData *pd) {
4815
4816 GaimRequestFields *fields;
4817 GaimRequestFieldGroup *g;
4818 GaimRequestField *f;
4819 GList *l;
4820 char *msgA, *msgB;
4821
4822 GaimConnection *gc = pd->gc;
4823
4824 fields = gaim_request_fields_new();
4825
4826 g = gaim_request_field_group_new(NULL);
4827 gaim_request_fields_add_group(fields, g);
4828
4829 f = gaim_request_field_list_new("group", "Possible Matches");
4830 gaim_request_field_list_set_multi_select(f, FALSE);
4831 gaim_request_field_set_required(f, TRUE);
4832
4833 for(l = result->matches; l; l = l->next) {
4834 struct mwResolveMatch *match = l->data;
4835 struct resolved_id *res = g_new0(struct resolved_id, 1);
4836
4837 res->id = g_strdup(match->id);
4838 res->name = g_strdup(match->name);
4839
4840 gaim_request_field_list_add(f, res->name, res);
4841 }
4842
4843 gaim_request_field_group_add_field(g, f);
4844
4845 msgA = ("Notes Address Book group results");
4846 msgB = ("The identifier '%s' may possibly refer to any of the following"
4847 " Notes Address Book groups. Please select the correct group from"
4848 " the list below to add it to your buddy list.");
4849 msgB = g_strdup_printf(msgB, result->name);
4850
4851 gaim_request_fields(gc, "Select Notes Address Book",
4852 msgA, msgB, fields,
4853 "Add Group", G_CALLBACK(remote_group_multi_cb),
4854 "Cancel", G_CALLBACK(remote_group_multi_cleanup),
4855 pd);
4856
4857 g_free(msgB);
4858 }
4859
4860
4861 static void remote_group_resolved(struct mwServiceResolve *srvc,
4862 guint32 id, guint32 code, GList *results,
4863 gpointer b) {
4864 struct mwSession *session;
4865 struct mwGaimPluginData *pd;
4866 GaimConnection *gc;
4867
4868 struct mwResolveResult *res = NULL;
4869
4870 session = mwService_getSession(MW_SERVICE(srvc));
4871 g_return_if_fail(session != NULL);
4872
4873 pd = mwSession_getClientData(session);
4874 g_return_if_fail(pd != NULL);
4875
4876 gc = pd->gc;
4877 g_return_if_fail(gc != NULL);
4878
4879 if(!code && results) {
4880 res = results->data;
4881
4882 if(res->matches) {
4883 remote_group_multi(res, pd);
4884 return;
4885 }
4886 }
4887
4888 if(res && res->name) {
4889 char *msgA, *msgB;
4890
4891 msgA = "Unable to add group: group not found";
4892
4893 msgB = ("The identifier '%s' did not match any Notes Address Book"
4894 " groups in your Sametime community.");
4895 msgB = g_strdup_printf(msgB, res->name);
4896
4897 gaim_notify_error(gc, "Unable to add group", msgA, msgB);
4898
4899 g_free(msgB);
4900 }
4901 }
4902
4903
4904 static void remote_group_action_cb(GaimConnection *gc, const char *name) {
4905 struct mwGaimPluginData *pd;
4906 struct mwServiceResolve *srvc;
4907 GList *query;
4908 enum mwResolveFlag flags;
4909 guint32 req;
4910
4911 pd = gc->proto_data;
4912 srvc = pd->srvc_resolve;
4913
4914 query = g_list_prepend(NULL, (char *) name);
4915 flags = mwResolveFlag_FIRST | mwResolveFlag_GROUPS;
4916
4917 req = mwServiceResolve_resolve(srvc, query, flags, remote_group_resolved,
4918 NULL, NULL);
4919 g_list_free(query);
4920
4921 if(req == SEARCH_ERROR) {
4922 /** @todo display error */
4923 }
4924 }
4925
4926
4927 static void remote_group_action(GaimPluginAction *act) {
4928 GaimConnection *gc;
4929 const char *msgA, *msgB;
4930
4931 gc = act->context;
4932
4933 msgA = "Notes Address Book Group";
4934 msgB = ("Enter the name of a Notes Address Book group in the field below"
4935 " to add the group and its members to your buddy list.");
4936
4937 gaim_request_input(gc, "Add Group", msgA, msgB, NULL,
4938 FALSE, FALSE, NULL,
4939 "Add", G_CALLBACK(remote_group_action_cb),
4940 "Cancel", NULL,
4941 gc);
4942 }
4943
4944
4945 static GList *mw_plugin_actions(GaimPlugin *plugin, gpointer context) {
4946 GaimPluginAction *act;
4947 GList *l = NULL;
4948
4949 act = gaim_plugin_action_new("Set Status Messages...", status_msg_action);
4950 l = g_list_append(l, act);
4951
4952 act = gaim_plugin_action_new("Import Sametime List...", st_import_action);
4953 l = g_list_append(l, act);
4954
4955 act = gaim_plugin_action_new("Export Sametime List...", st_export_action);
4956 l = g_list_append(l, act);
4957
4958 act = gaim_plugin_action_new("Add Notes Address Book Group...",
4959 remote_group_action);
4960 l = g_list_append(l, act);
4961
4962 return l;
4963 }
4964
4965
4966 static gboolean mw_plugin_load(GaimPlugin *plugin) {
4967 return TRUE;
4968 }
4969
4970
4971 static gboolean mw_plugin_unload(GaimPlugin *plugin) {
4972 return TRUE;
4973 }
4974
4975
4976 static void mw_plugin_destroy(GaimPlugin *plugin) {
4977 g_log_remove_handler(G_LOG_DOMAIN, log_handler[0]);
4978 g_log_remove_handler("meanwhile", log_handler[1]);
4979 }
4980
4981
4982 static GaimPluginInfo mw_plugin_info = {
4983 .magic = GAIM_PLUGIN_MAGIC,
4984 .major_version = GAIM_MAJOR_VERSION,
4985 .minor_version = GAIM_MINOR_VERSION,
4986 .type = GAIM_PLUGIN_PROTOCOL,
4987 .ui_requirement = NULL,
4988 .flags = 0,
4989 .dependencies = NULL,
4990 .priority = GAIM_PRIORITY_DEFAULT,
4991 .id = PLUGIN_ID,
4992 .name = PLUGIN_NAME,
4993 .version = VERSION,
4994 .summary = PLUGIN_SUMMARY,
4995 .description = PLUGIN_DESC,
4996 .author = PLUGIN_AUTHOR,
4997 .homepage = PLUGIN_HOMEPAGE,
4998 .load = mw_plugin_load,
4999 .unload = mw_plugin_unload,
5000 .destroy = mw_plugin_destroy,
5001 .ui_info = NULL,
5002 .extra_info = &mw_prpl_info,
5003 .prefs_info = &mw_plugin_ui_info,
5004 .actions = mw_plugin_actions,
5005 };
5006
5007
5008 static void mw_log_handler(const gchar *domain, GLogLevelFlags flags,
5009 const gchar *msg, gpointer data) {
5010 char *nl;
5011
5012 if(! msg) return;
5013
5014 /* annoying! */
5015 nl = g_strdup_printf("%s\n", msg);
5016
5017 /* handle g_log requests via gaim's built-in debug logging */
5018 if(flags & G_LOG_LEVEL_ERROR) {
5019 gaim_debug_error(domain, nl);
5020
5021 } else if(flags & G_LOG_LEVEL_WARNING) {
5022 gaim_debug_warning(domain, nl);
5023
5024 } else {
5025 gaim_debug_info(domain, nl);
5026 }
5027
5028 g_free(nl);
5029 }
5030
5031
5032 static void mw_plugin_init(GaimPlugin *plugin) {
5033 GaimAccountOption *opt;
5034 GList *l = NULL;
5035
5036 GLogLevelFlags logflags =
5037 G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION;
5038
5039 /* host to connect to */
5040 opt = gaim_account_option_string_new("Server", MW_KEY_HOST,
5041 MW_PLUGIN_DEFAULT_HOST);
5042 l = g_list_append(l, opt);
5043
5044 /* port to connect to */
5045 opt = gaim_account_option_int_new("Port", MW_KEY_PORT,
5046 MW_PLUGIN_DEFAULT_PORT);
5047 l = g_list_append(l, opt);
5048
5049 mw_prpl_info.protocol_options = l;
5050 l = NULL;
5051
5052 /* set up the prefs for blist options */
5053 gaim_prefs_add_none(MW_PRPL_OPT_BASE);
5054 gaim_prefs_add_int(MW_PRPL_OPT_BLIST_ACTION, BLIST_CHOICE_DEFAULT);
5055 gaim_prefs_add_bool(MW_PRPL_OPT_PSYCHIC, FALSE);
5056 gaim_prefs_add_bool(MW_PRPL_OPT_FORCE_LOGIN, FALSE);
5057 gaim_prefs_add_bool(MW_PRPL_OPT_SAVE_DYNAMIC, TRUE);
5058
5059 /* forward all our g_log messages to gaim. Generally all the logging
5060 calls are using gaim_log directly, but the g_return macros will
5061 get caught here */
5062 log_handler[0] = g_log_set_handler(G_LOG_DOMAIN, logflags,
5063 mw_log_handler, NULL);
5064
5065 /* redirect meanwhile's logging to gaim's */
5066 log_handler[1] = g_log_set_handler("meanwhile", logflags,
5067 mw_log_handler, NULL);
5068 }
5069
5070
5071 GAIM_INIT_PLUGIN(meanwhile, mw_plugin_init, mw_plugin_info);
5072 /* The End. */
5073