| |
1 /* |
| |
2 * Evolution integration plugin for Gaim |
| |
3 * |
| |
4 * Copyright (C) 2003 Christian Hammond. |
| |
5 * |
| |
6 * This program is free software; you can redistribute it and/or |
| |
7 * modify it under the terms of the GNU General Public License as |
| |
8 * published by the Free Software Foundation; either version 2 of the |
| |
9 * License, or (at your option) any later version. |
| |
10 * |
| |
11 * This program is distributed in the hope that it will be useful, but |
| |
12 * WITHOUT ANY WARRANTY; without even the implied warranty of |
| |
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| |
14 * General Public License for more details. |
| |
15 * |
| |
16 * You should have received a copy of the GNU General Public License |
| |
17 * along with this program; if not, write to the Free Software |
| |
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA |
| |
19 * 02111-1307, USA. |
| |
20 */ |
| |
21 #include "internal.h" |
| |
22 #include "gtkgaim.h" |
| |
23 |
| |
24 #include "connection.h" |
| |
25 #include "debug.h" |
| |
26 #include "prefs.h" |
| |
27 #include "notify.h" |
| |
28 #include "signals.h" |
| |
29 #include "util.h" |
| |
30 #include "version.h" |
| |
31 |
| |
32 #include "gtkblist.h" |
| |
33 #include "gtkconv.h" |
| |
34 #include "gtkplugin.h" |
| |
35 #include "gtkutils.h" |
| |
36 |
| |
37 #include "gevolution.h" |
| |
38 |
| |
39 #include <libedata-book/Evolution-DataServer-Addressbook.h> |
| |
40 |
| |
41 #include <libebook/e-book-listener.h> |
| |
42 #include <libedata-book/e-data-book-factory.h> |
| |
43 #include <bonobo/bonobo-main.h> |
| |
44 |
| |
45 #include <glib.h> |
| |
46 |
| |
47 #define GEVOLUTION_PLUGIN_ID "gtk-x11-gevolution" |
| |
48 |
| |
49 #define E_DATA_BOOK_FACTORY_OAF_ID \ |
| |
50 "OAFIID:GNOME_Evolution_DataServer_BookFactory" |
| |
51 |
| |
52 enum |
| |
53 { |
| |
54 COLUMN_AUTOADD, |
| |
55 COLUMN_ICON, |
| |
56 COLUMN_SCREENNAME, |
| |
57 COLUMN_DATA, |
| |
58 NUM_COLUMNS |
| |
59 }; |
| |
60 |
| |
61 static GaimBlistUiOps *backup_blist_ui_ops = NULL; |
| |
62 static GaimBlistUiOps *blist_ui_ops = NULL; |
| |
63 static EBook *book = NULL; |
| |
64 static gulong timer = 0; |
| |
65 static gulong book_view_tag = 0; |
| |
66 static EBookView *book_view = NULL; |
| |
67 |
| |
68 static void |
| |
69 update_ims_from_contact(EContact *contact, const char *name, |
| |
70 const char *prpl_id, EContactField field) |
| |
71 { |
| |
72 GList *ims = e_contact_get(contact, field); |
| |
73 GList *l, *l2; |
| |
74 |
| |
75 if (ims == NULL) |
| |
76 return; |
| |
77 |
| |
78 for (l = gaim_connections_get_all(); l != NULL; l = l->next) |
| |
79 { |
| |
80 GaimConnection *gc = (GaimConnection *)l->data; |
| |
81 GaimAccount *account = gaim_connection_get_account(gc); |
| |
82 char *me; |
| |
83 |
| |
84 if (strcmp(gaim_account_get_protocol_id(account), prpl_id)) |
| |
85 continue; |
| |
86 |
| |
87 if (!gaim_account_get_bool(account, "gevo-autoadd", FALSE)) |
| |
88 continue; |
| |
89 |
| |
90 me = g_strdup(gaim_normalize(account, gaim_account_get_username(account))); |
| |
91 for (l2 = ims; l2 != NULL; l2 = l2->next) |
| |
92 { |
| |
93 if (gaim_find_buddy(account, l2->data) != NULL || |
| |
94 !strcmp(me, gaim_normalize(account, l2->data))) |
| |
95 continue; |
| |
96 |
| |
97 gevo_add_buddy(account, _("Buddies"), l2->data, name); |
| |
98 } |
| |
99 g_free(me); |
| |
100 } |
| |
101 |
| |
102 g_list_foreach(ims, (GFunc)g_free, NULL); |
| |
103 g_list_free(ims); |
| |
104 } |
| |
105 |
| |
106 static void |
| |
107 update_buddies_from_contact(EContact *contact) |
| |
108 { |
| |
109 const char *name; |
| |
110 |
| |
111 name = e_contact_get_const(contact, E_CONTACT_FULL_NAME); |
| |
112 |
| |
113 update_ims_from_contact(contact, name, "prpl-oscar", E_CONTACT_IM_AIM); |
| |
114 update_ims_from_contact(contact, name, "prpl-jabber", E_CONTACT_IM_JABBER); |
| |
115 update_ims_from_contact(contact, name, "prpl-yahoo", E_CONTACT_IM_YAHOO); |
| |
116 update_ims_from_contact(contact, name, "prpl-msn", E_CONTACT_IM_MSN); |
| |
117 update_ims_from_contact(contact, name, "prpl-oscar", E_CONTACT_IM_ICQ); |
| |
118 update_ims_from_contact(contact, name, "prpl-novell", E_CONTACT_IM_GROUPWISE); |
| |
119 } |
| |
120 |
| |
121 static void |
| |
122 contacts_changed_cb(EBookView *book_view, const GList *contacts) |
| |
123 { |
| |
124 const GList *l; |
| |
125 |
| |
126 if (gaim_connections_get_all() == NULL) |
| |
127 return; |
| |
128 |
| |
129 for (l = contacts; l != NULL; l = l->next) |
| |
130 { |
| |
131 EContact *contact = (EContact *)l->data; |
| |
132 |
| |
133 update_buddies_from_contact(contact); |
| |
134 } |
| |
135 } |
| |
136 |
| |
137 static void |
| |
138 request_add_buddy(GaimAccount *account, const char *username, |
| |
139 const char *group, const char *alias) |
| |
140 { |
| |
141 if (book == NULL) |
| |
142 { |
| |
143 backup_blist_ui_ops->request_add_buddy(account, username, group, |
| |
144 alias); |
| |
145 } |
| |
146 else |
| |
147 { |
| |
148 gevo_add_buddy_dialog_show(account, username, group, alias); |
| |
149 } |
| |
150 } |
| |
151 |
| |
152 static void |
| |
153 got_book_view_cb(EBook *book, EBookStatus status, EBookView *view, |
| |
154 gpointer user_data) |
| |
155 { |
| |
156 book_view_tag = 0; |
| |
157 |
| |
158 if (status != E_BOOK_ERROR_OK) |
| |
159 { |
| |
160 gaim_debug_error("evolution", "Unable to retrieve book view! :(\n"); |
| |
161 |
| |
162 return; |
| |
163 } |
| |
164 |
| |
165 book_view = view; |
| |
166 |
| |
167 g_object_ref(book_view); |
| |
168 |
| |
169 g_signal_connect(G_OBJECT(book_view), "contacts_changed", |
| |
170 G_CALLBACK(contacts_changed_cb), book); |
| |
171 |
| |
172 g_signal_connect(G_OBJECT(book_view), "contacts_added", |
| |
173 G_CALLBACK(contacts_changed_cb), book); |
| |
174 |
| |
175 e_book_view_start(view); |
| |
176 } |
| |
177 |
| |
178 static void |
| |
179 signed_on_cb(GaimConnection *gc) |
| |
180 { |
| |
181 EBookQuery *query; |
| |
182 gboolean status; |
| |
183 GList *contacts; |
| |
184 GList *l; |
| |
185 |
| |
186 if (book == NULL) |
| |
187 return; |
| |
188 |
| |
189 query = e_book_query_any_field_contains(""); |
| |
190 |
| |
191 status = e_book_get_contacts(book, query, &contacts, NULL); |
| |
192 |
| |
193 e_book_query_unref(query); |
| |
194 |
| |
195 if (!status) |
| |
196 return; |
| |
197 |
| |
198 for (l = contacts; l != NULL; l = l->next) |
| |
199 { |
| |
200 EContact *contact = E_CONTACT(l->data); |
| |
201 |
| |
202 update_buddies_from_contact(contact); |
| |
203 |
| |
204 g_object_unref(contact); |
| |
205 } |
| |
206 |
| |
207 g_list_free(contacts); |
| |
208 } |
| |
209 |
| |
210 static void |
| |
211 menu_item_activate_cb(GaimBlistNode *node, gpointer user_data) |
| |
212 { |
| |
213 GaimBuddy *buddy = (GaimBuddy *)node; |
| |
214 gevo_associate_buddy_dialog_new(buddy); |
| |
215 } |
| |
216 |
| |
217 static void |
| |
218 menu_item_send_mail_activate_cb(GaimBlistNode *node, gpointer user_data) |
| |
219 { |
| |
220 GaimBuddy *buddy = (GaimBuddy *)node; |
| |
221 char *mail = NULL; |
| |
222 |
| |
223 mail = gevo_get_email_for_buddy(buddy); |
| |
224 |
| |
225 if (mail != NULL) |
| |
226 { |
| |
227 char *app = g_find_program_in_path("evolution"); |
| |
228 if (app != NULL) |
| |
229 { |
| |
230 char *command_line = g_strdup_printf("%s mailto:%s", app, mail); |
| |
231 g_free(app); |
| |
232 g_free(mail); |
| |
233 |
| |
234 g_spawn_command_line_async(command_line, NULL); |
| |
235 g_free(command_line); |
| |
236 } |
| |
237 else |
| |
238 { |
| |
239 gaim_notify_error(NULL, NULL, _("Unable to send e-mail"), |
| |
240 _("The evolution executable was not found in the PATH.")); |
| |
241 } |
| |
242 } |
| |
243 else |
| |
244 { |
| |
245 gaim_notify_error(NULL, NULL, _("Unable to send e-mail"), |
| |
246 _("An e-mail address was not found for this buddy.")); |
| |
247 } |
| |
248 } |
| |
249 |
| |
250 static void |
| |
251 blist_node_extended_menu_cb(GaimBlistNode *node, GList **menu) |
| |
252 { |
| |
253 GaimMenuAction *act; |
| |
254 GaimBuddy *buddy; |
| |
255 GaimAccount *account; |
| |
256 EContact *contact; |
| |
257 char *mail; |
| |
258 |
| |
259 if (!GAIM_BLIST_NODE_IS_BUDDY(node)) |
| |
260 return; |
| |
261 |
| |
262 buddy = (GaimBuddy *)node; |
| |
263 account = gaim_buddy_get_account(buddy); |
| |
264 |
| |
265 if (!gevo_prpl_is_supported(account, buddy)) |
| |
266 return; |
| |
267 |
| |
268 contact = gevo_search_buddy_in_contacts(buddy, NULL); |
| |
269 |
| |
270 if (contact == NULL) |
| |
271 { |
| |
272 act = gaim_menu_action_new(_("Add to Address Book"), |
| |
273 GAIM_CALLBACK(menu_item_activate_cb), |
| |
274 NULL, NULL); |
| |
275 *menu = g_list_append(*menu, act); |
| |
276 } |
| |
277 else |
| |
278 g_object_unref(contact); |
| |
279 |
| |
280 mail = gevo_get_email_for_buddy(buddy); |
| |
281 |
| |
282 if (mail != NULL) |
| |
283 { |
| |
284 act = gaim_menu_action_new(_("Send E-Mail"), |
| |
285 GAIM_CALLBACK(menu_item_send_mail_activate_cb), NULL, NULL); |
| |
286 *menu = g_list_append(*menu, act); |
| |
287 g_free(mail); |
| |
288 } |
| |
289 } |
| |
290 |
| |
291 /* TODO: Something in here leaks 1 reference to a bonobo object! */ |
| |
292 static gboolean |
| |
293 load_timeout(gpointer data) |
| |
294 { |
| |
295 GaimPlugin *plugin = (GaimPlugin *)data; |
| |
296 EBookQuery *query; |
| |
297 |
| |
298 timer = 0; |
| |
299 |
| |
300 /* Maybe this is it? */ |
| |
301 if (!gevo_load_addressbook(NULL, &book, NULL)) |
| |
302 return FALSE; |
| |
303 |
| |
304 query = e_book_query_any_field_contains(""); |
| |
305 |
| |
306 /* Is it this? */ |
| |
307 book_view_tag = e_book_async_get_book_view(book, query, NULL, -1, |
| |
308 got_book_view_cb, NULL); |
| |
309 |
| |
310 e_book_query_unref(query); |
| |
311 |
| |
312 gaim_signal_connect(gaim_blist_get_handle(), "blist-node-extended-menu", |
| |
313 plugin, GAIM_CALLBACK(blist_node_extended_menu_cb), NULL); |
| |
314 |
| |
315 return FALSE; |
| |
316 } |
| |
317 |
| |
318 static gboolean |
| |
319 plugin_load(GaimPlugin *plugin) |
| |
320 { |
| |
321 bonobo_activate(); |
| |
322 |
| |
323 backup_blist_ui_ops = gaim_blist_get_ui_ops(); |
| |
324 |
| |
325 blist_ui_ops = g_memdup(backup_blist_ui_ops, sizeof(GaimBlistUiOps)); |
| |
326 blist_ui_ops->request_add_buddy = request_add_buddy; |
| |
327 |
| |
328 gaim_blist_set_ui_ops(blist_ui_ops); |
| |
329 |
| |
330 gaim_signal_connect(gaim_connections_get_handle(), "signed-on", |
| |
331 plugin, GAIM_CALLBACK(signed_on_cb), NULL); |
| |
332 |
| |
333 timer = g_timeout_add(1, load_timeout, plugin); |
| |
334 |
| |
335 return TRUE; |
| |
336 } |
| |
337 |
| |
338 static gboolean |
| |
339 plugin_unload(GaimPlugin *plugin) |
| |
340 { |
| |
341 gaim_blist_set_ui_ops(backup_blist_ui_ops); |
| |
342 |
| |
343 g_free(blist_ui_ops); |
| |
344 |
| |
345 backup_blist_ui_ops = NULL; |
| |
346 blist_ui_ops = NULL; |
| |
347 |
| |
348 if (book_view != NULL) |
| |
349 { |
| |
350 e_book_view_stop(book_view); |
| |
351 g_object_unref(book_view); |
| |
352 book_view = NULL; |
| |
353 } |
| |
354 |
| |
355 if (book != NULL) |
| |
356 { |
| |
357 g_object_unref(book); |
| |
358 book = NULL; |
| |
359 } |
| |
360 |
| |
361 return TRUE; |
| |
362 } |
| |
363 |
| |
364 static void |
| |
365 plugin_destroy(GaimPlugin *plugin) |
| |
366 { |
| |
367 bonobo_debug_shutdown(); |
| |
368 } |
| |
369 |
| |
370 static void |
| |
371 autoadd_toggled_cb(GtkCellRendererToggle *renderer, gchar *path_str, |
| |
372 gpointer data) |
| |
373 { |
| |
374 GaimAccount *account; |
| |
375 GtkTreeModel *model = (GtkTreeModel *)data; |
| |
376 GtkTreeIter iter; |
| |
377 gboolean autoadd; |
| |
378 |
| |
379 gtk_tree_model_get_iter_from_string(model, &iter, path_str); |
| |
380 gtk_tree_model_get(model, &iter, |
| |
381 COLUMN_DATA, &account, |
| |
382 COLUMN_AUTOADD, &autoadd, |
| |
383 -1); |
| |
384 |
| |
385 gaim_account_set_bool(account, "gevo-autoadd", !autoadd); |
| |
386 |
| |
387 gtk_list_store_set(GTK_LIST_STORE(model), &iter, |
| |
388 COLUMN_AUTOADD, !autoadd, |
| |
389 -1); |
| |
390 } |
| |
391 |
| |
392 static GtkWidget * |
| |
393 get_config_frame(GaimPlugin *plugin) |
| |
394 { |
| |
395 GtkWidget *ret; |
| |
396 GtkWidget *vbox; |
| |
397 GtkWidget *label; |
| |
398 GtkWidget *sw; |
| |
399 GtkWidget *treeview; |
| |
400 GtkTreeViewColumn *column; |
| |
401 GtkCellRenderer *renderer; |
| |
402 GdkPixbuf *pixbuf; |
| |
403 GtkListStore *model; |
| |
404 GList *l; |
| |
405 |
| |
406 /* Outside container */ |
| |
407 ret = gtk_vbox_new(FALSE, 18); |
| |
408 gtk_container_set_border_width(GTK_CONTAINER(ret), 12); |
| |
409 |
| |
410 /* Configuration frame */ |
| |
411 vbox = gaim_gtk_make_frame(ret, _("Evolution Integration Configuration")); |
| |
412 |
| |
413 /* Label */ |
| |
414 label = gtk_label_new(_("Select all accounts that buddies should be " |
| |
415 "auto-added to.")); |
| |
416 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); |
| |
417 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); |
| |
418 gtk_widget_show(label); |
| |
419 |
| |
420 /* Scrolled window */ |
| |
421 sw = gtk_scrolled_window_new(0, 0); |
| |
422 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), |
| |
423 GTK_POLICY_AUTOMATIC, |
| |
424 GTK_POLICY_ALWAYS); |
| |
425 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), |
| |
426 GTK_SHADOW_IN); |
| |
427 gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0); |
| |
428 gtk_widget_set_size_request(sw, 300, 300); |
| |
429 gtk_widget_show(sw); |
| |
430 |
| |
431 /* Create the list model for the treeview. */ |
| |
432 model = gtk_list_store_new(NUM_COLUMNS, |
| |
433 G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, |
| |
434 G_TYPE_STRING, G_TYPE_POINTER); |
| |
435 |
| |
436 /* Setup the treeview */ |
| |
437 treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); |
| |
438 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE); |
| |
439 gtk_container_add(GTK_CONTAINER(sw), treeview); |
| |
440 gtk_widget_show(treeview); |
| |
441 |
| |
442 /* Setup the column */ |
| |
443 column = gtk_tree_view_column_new(); |
| |
444 gtk_tree_view_column_set_title(column, _("Account")); |
| |
445 gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1); |
| |
446 |
| |
447 /* Checkbox */ |
| |
448 renderer = gtk_cell_renderer_toggle_new(); |
| |
449 |
| |
450 g_signal_connect(G_OBJECT(renderer), "toggled", |
| |
451 G_CALLBACK(autoadd_toggled_cb), model); |
| |
452 |
| |
453 gtk_tree_view_column_pack_start(column, renderer, FALSE); |
| |
454 gtk_tree_view_column_add_attribute(column, renderer, |
| |
455 "active", COLUMN_AUTOADD); |
| |
456 |
| |
457 /* Icon */ |
| |
458 renderer = gtk_cell_renderer_pixbuf_new(); |
| |
459 gtk_tree_view_column_pack_start(column, renderer, FALSE); |
| |
460 gtk_tree_view_column_add_attribute(column, renderer, |
| |
461 "pixbuf", COLUMN_ICON); |
| |
462 |
| |
463 /* Screenname */ |
| |
464 renderer = gtk_cell_renderer_text_new(); |
| |
465 gtk_tree_view_column_pack_start(column, renderer, TRUE); |
| |
466 gtk_tree_view_column_add_attribute(column, renderer, |
| |
467 "text", COLUMN_SCREENNAME); |
| |
468 |
| |
469 |
| |
470 /* Populate */ |
| |
471 for (l = gaim_accounts_get_all(); l != NULL; l = l->next) |
| |
472 { |
| |
473 GaimAccount *account = (GaimAccount *)l->data; |
| |
474 GtkTreeIter iter; |
| |
475 |
| |
476 gaim_debug_info("evolution", "Adding account\n"); |
| |
477 |
| |
478 gtk_list_store_append(model, &iter); |
| |
479 |
| |
480 pixbuf = gaim_gtk_create_prpl_icon(account, 0.5); |
| |
481 if ((pixbuf != NULL) && (!gaim_account_is_connected(account))) |
| |
482 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE); |
| |
483 |
| |
484 gtk_list_store_set(model, &iter, |
| |
485 COLUMN_AUTOADD, |
| |
486 gaim_account_get_bool(account, "gevo-autoadd", |
| |
487 FALSE), |
| |
488 COLUMN_ICON, pixbuf, |
| |
489 COLUMN_SCREENNAME, |
| |
490 gaim_account_get_username(account), |
| |
491 COLUMN_DATA, account, |
| |
492 -1); |
| |
493 |
| |
494 if (pixbuf != NULL) |
| |
495 g_object_unref(G_OBJECT(pixbuf)); |
| |
496 } |
| |
497 |
| |
498 gtk_widget_show_all(ret); |
| |
499 |
| |
500 return ret; |
| |
501 } |
| |
502 |
| |
503 static GaimGtkPluginUiInfo ui_info = |
| |
504 { |
| |
505 get_config_frame, /**< get_config_frame */ |
| |
506 0 /**< page_num */ |
| |
507 }; |
| |
508 |
| |
509 static GaimPluginInfo info = |
| |
510 { |
| |
511 GAIM_PLUGIN_MAGIC, |
| |
512 GAIM_MAJOR_VERSION, |
| |
513 GAIM_MINOR_VERSION, |
| |
514 GAIM_PLUGIN_STANDARD, /**< type */ |
| |
515 GAIM_GTK_PLUGIN_TYPE, /**< ui_requirement */ |
| |
516 0, /**< flags */ |
| |
517 NULL, /**< dependencies */ |
| |
518 GAIM_PRIORITY_DEFAULT, /**< priority */ |
| |
519 |
| |
520 GEVOLUTION_PLUGIN_ID, /**< id */ |
| |
521 N_("Evolution Integration"), /**< name */ |
| |
522 VERSION, /**< version */ |
| |
523 /** summary */ |
| |
524 N_("Provides integration with Evolution."), |
| |
525 /** description */ |
| |
526 N_("Provides integration with Evolution."), |
| |
527 "Christian Hammond <chipx86@chipx86.com>", /**< author */ |
| |
528 GAIM_WEBSITE, /**< homepage */ |
| |
529 |
| |
530 plugin_load, /**< load */ |
| |
531 plugin_unload, /**< unload */ |
| |
532 plugin_destroy, /**< destroy */ |
| |
533 |
| |
534 &ui_info, /**< ui_info */ |
| |
535 NULL, /**< extra_info */ |
| |
536 NULL, |
| |
537 NULL |
| |
538 }; |
| |
539 |
| |
540 static void |
| |
541 init_plugin(GaimPlugin *plugin) |
| |
542 { |
| |
543 /* TODO: Change to core-remote when possible. */ |
| |
544 /* info.dependencies = g_list_append(info.dependencies, "gtk-remote"); */ |
| |
545 |
| |
546 /* |
| |
547 * I'm going to rant a little bit here... |
| |
548 * |
| |
549 * For some reason, when we init bonobo from inside a plugin, it will |
| |
550 * segfault when destroyed. The backtraces are within gmodule somewhere. |
| |
551 * There's not much I can do, and I'm not sure where the bug lies. |
| |
552 * However, plugins are only destroyed when Gaim is shutting down. After |
| |
553 * destroying the plugins, gaim ends, and anything else is of course |
| |
554 * freed. That includes this, if we make the module resident, which |
| |
555 * prevents us from being able to actually unload it. |
| |
556 * |
| |
557 * So, in conclusion, this is an evil hack, but it doesn't harm anything |
| |
558 * and it works. |
| |
559 */ |
| |
560 g_module_make_resident(plugin->handle); |
| |
561 |
| |
562 if (!bonobo_init_full(NULL, NULL, bonobo_activation_orb_get(), |
| |
563 CORBA_OBJECT_NIL, CORBA_OBJECT_NIL)) |
| |
564 { |
| |
565 gaim_debug_error("evolution", "Unable to initialize bonobo.\n"); |
| |
566 } |
| |
567 } |
| |
568 |
| |
569 GAIM_INIT_PLUGIN(gevolution, init_plugin, info) |