]> git.0d.be Git - empathy.git/blob - libempathy/empathy-utils.c
No need to get RequestableChannelClasses if ContactCapability is implemented
[empathy.git] / libempathy / empathy-utils.c
1 /*
2  * Copyright (C) 2003-2007 Imendio AB
3  * Copyright (C) 2007-2008 Collabora Ltd.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA  02110-1301  USA
19  *
20  * Authors: Richard Hult <richard@imendio.com>
21  *          Martyn Russell <martyn@imendio.com>
22  *          Xavier Claessens <xclaesse@gmail.com>
23  */
24
25 #include "config.h"
26
27 #include <string.h>
28 #include <time.h>
29 #include <sys/types.h>
30
31 #include <glib/gi18n-lib.h>
32
33 #include <libxml/uri.h>
34
35 #include <telepathy-glib/account-manager.h>
36 #include <telepathy-glib/connection.h>
37 #include <telepathy-glib/channel.h>
38 #include <telepathy-glib/dbus.h>
39 #include <telepathy-glib/util.h>
40
41 #include "empathy-utils.h"
42 #include "empathy-contact-manager.h"
43 #include "empathy-dispatcher.h"
44 #include "empathy-dispatch-operation.h"
45 #include "empathy-idle.h"
46 #include "empathy-tp-call.h"
47
48 #include <extensions/extensions.h>
49
50 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
51 #include "empathy-debug.h"
52
53 /* Translation between presence types and string */
54 static struct {
55         gchar *name;
56         TpConnectionPresenceType type;
57 } presence_types[] = {
58         { "available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE },
59         { "busy",      TP_CONNECTION_PRESENCE_TYPE_BUSY },
60         { "away",      TP_CONNECTION_PRESENCE_TYPE_AWAY },
61         { "ext_away",  TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY },
62         { "hidden",    TP_CONNECTION_PRESENCE_TYPE_HIDDEN },
63         { "offline",   TP_CONNECTION_PRESENCE_TYPE_OFFLINE },
64         { "unset",     TP_CONNECTION_PRESENCE_TYPE_UNSET },
65         { "unknown",   TP_CONNECTION_PRESENCE_TYPE_UNKNOWN },
66         { "error",     TP_CONNECTION_PRESENCE_TYPE_ERROR },
67         /* alternative names */
68         { "dnd",      TP_CONNECTION_PRESENCE_TYPE_BUSY },
69         { "brb",      TP_CONNECTION_PRESENCE_TYPE_AWAY },
70         { "xa",       TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY },
71         { NULL, },
72 };
73
74
75
76 void
77 empathy_init (void)
78 {
79         static gboolean initialized = FALSE;
80
81         if (initialized)
82                 return;
83
84         g_type_init ();
85
86         /* Setup gettext */
87         bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
88         bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
89
90         /* Setup debug output for empathy and telepathy-glib */
91         if (g_getenv ("EMPATHY_TIMING") != NULL) {
92                 g_log_set_default_handler (tp_debug_timestamped_log_handler, NULL);
93         }
94         empathy_debug_set_flags (g_getenv ("EMPATHY_DEBUG"));
95         tp_debug_divert_messages (g_getenv ("EMPATHY_LOGFILE"));
96
97         emp_cli_init ();
98
99         initialized = TRUE;
100 }
101
102 gchar *
103 empathy_substring (const gchar *str,
104                   gint         start,
105                   gint         end)
106 {
107         return g_strndup (str + start, end - start);
108 }
109
110 gint
111 empathy_strcasecmp (const gchar *s1,
112                    const gchar *s2)
113 {
114         return empathy_strncasecmp (s1, s2, -1);
115 }
116
117 gint
118 empathy_strncasecmp (const gchar *s1,
119                     const gchar *s2,
120                     gsize        n)
121 {
122         gchar *u1, *u2;
123         gint   ret_val;
124
125         u1 = g_utf8_casefold (s1, n);
126         u2 = g_utf8_casefold (s2, n);
127
128         ret_val = g_utf8_collate (u1, u2);
129         g_free (u1);
130         g_free (u2);
131
132         return ret_val;
133 }
134
135 gboolean
136 empathy_xml_validate (xmlDoc      *doc,
137                      const gchar *dtd_filename)
138 {
139         gchar        *path;
140         xmlChar      *escaped;
141         xmlValidCtxt  cvp;
142         xmlDtd       *dtd;
143         gboolean      ret;
144
145         path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), "libempathy",
146                                  dtd_filename, NULL);
147         if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
148                 g_free (path);
149                 path = g_build_filename (DATADIR, "empathy", dtd_filename, NULL);
150         }
151         DEBUG ("Loading dtd file %s", path);
152
153         /* The list of valid chars is taken from libxml. */
154         escaped = xmlURIEscapeStr ((const xmlChar *) path,
155                 (const xmlChar *)":@&=+$,/?;");
156         g_free (path);
157
158         memset (&cvp, 0, sizeof (cvp));
159         dtd = xmlParseDTD (NULL, escaped);
160         ret = xmlValidateDtd (&cvp, doc, dtd);
161
162         xmlFree (escaped);
163         xmlFreeDtd (dtd);
164
165         return ret;
166 }
167
168 xmlNodePtr
169 empathy_xml_node_get_child (xmlNodePtr   node,
170                            const gchar *child_name)
171 {
172         xmlNodePtr l;
173
174         g_return_val_if_fail (node != NULL, NULL);
175         g_return_val_if_fail (child_name != NULL, NULL);
176
177         for (l = node->children; l; l = l->next) {
178                 if (l->name && strcmp ((const gchar *) l->name, child_name) == 0) {
179                         return l;
180                 }
181         }
182
183         return NULL;
184 }
185
186 xmlChar *
187 empathy_xml_node_get_child_content (xmlNodePtr   node,
188                                    const gchar *child_name)
189 {
190         xmlNodePtr l;
191
192         g_return_val_if_fail (node != NULL, NULL);
193         g_return_val_if_fail (child_name != NULL, NULL);
194
195         l = empathy_xml_node_get_child (node, child_name);
196         if (l) {
197                 return xmlNodeGetContent (l);
198         }
199
200         return NULL;
201 }
202
203 xmlNodePtr
204 empathy_xml_node_find_child_prop_value (xmlNodePtr   node,
205                                        const gchar *prop_name,
206                                        const gchar *prop_value)
207 {
208         xmlNodePtr l;
209         xmlNodePtr found = NULL;
210
211         g_return_val_if_fail (node != NULL, NULL);
212         g_return_val_if_fail (prop_name != NULL, NULL);
213         g_return_val_if_fail (prop_value != NULL, NULL);
214
215         for (l = node->children; l && !found; l = l->next) {
216                 xmlChar *prop;
217
218                 if (!xmlHasProp (l, (const xmlChar *) prop_name)) {
219                         continue;
220                 }
221
222                 prop = xmlGetProp (l, (const xmlChar *) prop_name);
223                 if (prop && strcmp ((const gchar *) prop, prop_value) == 0) {
224                         found = l;
225                 }
226
227                 xmlFree (prop);
228         }
229
230         return found;
231 }
232
233 const gchar *
234 empathy_presence_get_default_message (TpConnectionPresenceType presence)
235 {
236         switch (presence) {
237         case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
238                 return _("Available");
239         case TP_CONNECTION_PRESENCE_TYPE_BUSY:
240                 return _("Busy");
241         case TP_CONNECTION_PRESENCE_TYPE_AWAY:
242         case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
243                 return _("Away");
244         case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
245                 return _("Hidden");
246         case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
247                 return _("Offline");
248         case TP_CONNECTION_PRESENCE_TYPE_UNSET:
249         case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
250         case TP_CONNECTION_PRESENCE_TYPE_ERROR:
251                 return NULL;
252         }
253
254         return NULL;
255 }
256
257 const gchar *
258 empathy_presence_to_str (TpConnectionPresenceType presence)
259 {
260         int i;
261
262         for (i = 0 ; presence_types[i].name != NULL; i++)
263                 if (presence == presence_types[i].type)
264                         return presence_types[i].name;
265
266         return NULL;
267 }
268
269 TpConnectionPresenceType
270 empathy_presence_from_str (const gchar *str)
271 {
272         int i;
273
274         for (i = 0 ; presence_types[i].name != NULL; i++)
275                 if (!tp_strdiff (str, presence_types[i].name))
276                         return presence_types[i].type;
277
278         return TP_CONNECTION_PRESENCE_TYPE_UNSET;
279 }
280
281 const gchar *
282 empathy_status_reason_get_default_message (TpConnectionStatusReason reason)
283 {
284         switch (reason) {
285         case TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED:
286                 return _("No reason specified");
287         case TP_CONNECTION_STATUS_REASON_REQUESTED:
288                 return _("User requested disconnect");
289         case TP_CONNECTION_STATUS_REASON_NETWORK_ERROR:
290                 return _("Network error");
291         case TP_CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED:
292                 return _("Authentication failed");
293         case TP_CONNECTION_STATUS_REASON_ENCRYPTION_ERROR:
294                 return _("Encryption error");
295         case TP_CONNECTION_STATUS_REASON_NAME_IN_USE:
296                 return _("Name in use");
297         case TP_CONNECTION_STATUS_REASON_CERT_NOT_PROVIDED:
298                 return _("Certificate not provided");
299         case TP_CONNECTION_STATUS_REASON_CERT_UNTRUSTED:
300                 return _("Certificate untrusted");
301         case TP_CONNECTION_STATUS_REASON_CERT_EXPIRED:
302                 return _("Certificate expired");
303         case TP_CONNECTION_STATUS_REASON_CERT_NOT_ACTIVATED:
304                 return _("Certificate not activated");
305         case TP_CONNECTION_STATUS_REASON_CERT_HOSTNAME_MISMATCH:
306                 return _("Certificate hostname mismatch");
307         case TP_CONNECTION_STATUS_REASON_CERT_FINGERPRINT_MISMATCH:
308                 return _("Certificate fingerprint mismatch");
309         case TP_CONNECTION_STATUS_REASON_CERT_SELF_SIGNED:
310                 return _("Certificate self-signed");
311         case TP_CONNECTION_STATUS_REASON_CERT_OTHER_ERROR:
312                 return _("Certificate error");
313         default:
314                 return _("Unknown reason");
315         }
316 }
317
318 gchar *
319 empathy_file_lookup (const gchar *filename, const gchar *subdir)
320 {
321         gchar *path;
322
323         if (!subdir) {
324                 subdir = ".";
325         }
326
327         path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), subdir, filename, NULL);
328         if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
329                 g_free (path);
330                 path = g_build_filename (DATADIR, "empathy", filename, NULL);
331         }
332
333         return path;
334 }
335
336 guint
337 empathy_proxy_hash (gconstpointer key)
338 {
339         TpProxy      *proxy = TP_PROXY (key);
340         TpProxyClass *proxy_class = TP_PROXY_GET_CLASS (key);
341
342         g_return_val_if_fail (TP_IS_PROXY (proxy), 0);
343         g_return_val_if_fail (proxy_class->must_have_unique_name, 0);
344
345         return g_str_hash (proxy->object_path) ^ g_str_hash (proxy->bus_name);
346 }
347
348 gboolean
349 empathy_proxy_equal (gconstpointer a,
350                      gconstpointer b)
351 {
352         TpProxy *proxy_a = TP_PROXY (a);
353         TpProxy *proxy_b = TP_PROXY (b);
354         TpProxyClass *proxy_a_class = TP_PROXY_GET_CLASS (a);
355         TpProxyClass *proxy_b_class = TP_PROXY_GET_CLASS (b);
356
357         g_return_val_if_fail (TP_IS_PROXY (proxy_a), FALSE);
358         g_return_val_if_fail (TP_IS_PROXY (proxy_b), FALSE);
359         g_return_val_if_fail (proxy_a_class->must_have_unique_name, 0);
360         g_return_val_if_fail (proxy_b_class->must_have_unique_name, 0);
361
362         return g_str_equal (proxy_a->object_path, proxy_b->object_path) &&
363                g_str_equal (proxy_a->bus_name, proxy_b->bus_name);
364 }
365
366 gboolean
367 empathy_check_available_state (void)
368 {
369         TpConnectionPresenceType presence;
370         EmpathyIdle *idle;
371
372         idle = empathy_idle_dup_singleton ();
373         presence = empathy_idle_get_state (idle);
374         g_object_unref (idle);
375
376         if (presence != TP_CONNECTION_PRESENCE_TYPE_AVAILABLE &&
377                 presence != TP_CONNECTION_PRESENCE_TYPE_UNSET) {
378                 return FALSE;
379         }
380
381         return TRUE;
382 }
383
384 gint
385 empathy_uint_compare (gconstpointer a,
386                       gconstpointer b)
387 {
388         return *(guint *) a - *(guint *) b;
389 }
390
391 gchar *
392 empathy_protocol_icon_name (const gchar *protocol)
393 {
394   if (!tp_strdiff (protocol, "yahoojp"))
395     /* Yahoo Japan use the same icon as Yahoo */
396     protocol = "yahoo";
397
398   return g_strdup_printf ("im-%s", protocol);
399 }
400
401 GType
402 empathy_type_dbus_ao (void)
403 {
404   static GType t = 0;
405
406   if (G_UNLIKELY (t == 0))
407      t = dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH);
408
409   return t;
410 }
411
412 const char *
413 empathy_protocol_name_to_display_name (const gchar *proto_name)
414 {
415   int i;
416   static struct {
417     const gchar *proto;
418     const gchar *display;
419     gboolean translated;
420   } names[] = {
421     { "jabber", "Jabber", FALSE },
422     { "gtalk", "Google Talk", FALSE },
423     { "msn", "MSN", FALSE, },
424     { "local-xmpp", N_("People Nearby"), TRUE },
425     { "irc", "IRC", FALSE },
426     { "icq", "ICQ", FALSE },
427     { "aim", "AIM", FALSE },
428     { "yahoo", "Yahoo!", FALSE },
429     { "yahoojp", N_("Yahoo! Japan"), TRUE },
430     { "facebook", N_("Facebook Chat"), TRUE },
431     { "groupwise", "GroupWise", FALSE },
432     { "sip", "SIP", FALSE },
433     { NULL, NULL }
434   };
435
436   for (i = 0; names[i].proto != NULL; i++)
437     {
438       if (!tp_strdiff (proto_name, names[i].proto))
439         {
440           if (names[i].translated)
441             return _(names[i].display);
442           else
443             return names[i].display;
444         }
445     }
446
447   return NULL;
448 }
449
450 typedef struct {
451     GObject *instance;
452     GObject *user_data;
453     gulong handler_id;
454 } WeakHandlerCtx;
455
456 static WeakHandlerCtx *
457 whc_new (GObject *instance,
458          GObject *user_data)
459 {
460   WeakHandlerCtx *ctx = g_slice_new0 (WeakHandlerCtx);
461
462   ctx->instance = instance;
463   ctx->user_data = user_data;
464
465   return ctx;
466 }
467
468 static void
469 whc_free (WeakHandlerCtx *ctx)
470 {
471   g_slice_free (WeakHandlerCtx, ctx);
472 }
473
474 static void user_data_destroyed_cb (gpointer, GObject *);
475
476 static void
477 instance_destroyed_cb (gpointer ctx_,
478                        GObject *where_the_instance_was)
479 {
480   WeakHandlerCtx *ctx = ctx_;
481
482   DEBUG ("instance for %p destroyed; cleaning up", ctx);
483
484   /* No need to disconnect the signal here, the instance has gone away. */
485   g_object_weak_unref (ctx->user_data, user_data_destroyed_cb, ctx);
486   whc_free (ctx);
487 }
488
489 static void
490 user_data_destroyed_cb (gpointer ctx_,
491                         GObject *where_the_user_data_was)
492 {
493   WeakHandlerCtx *ctx = ctx_;
494
495   DEBUG ("user_data for %p destroyed; disconnecting", ctx);
496
497   g_signal_handler_disconnect (ctx->instance, ctx->handler_id);
498   g_object_weak_unref (ctx->instance, instance_destroyed_cb, ctx);
499   whc_free (ctx);
500 }
501
502 /* This function is copied from telepathy-gabble: util.c */
503 /**
504  * empathy_signal_connect_weak:
505  * @instance: the instance to connect to.
506  * @detailed_signal: a string of the form "signal-name::detail".
507  * @c_handler: the GCallback to connect.
508  * @user_data: an object to pass as data to c_handler calls.
509  *
510  * Connects a #GCallback function to a signal for a particular object, as if
511  * with g_signal_connect(). Additionally, arranges for the signal handler to be
512  * disconnected if @user_data is destroyed.
513  *
514  * This is intended to be a convenient way for objects to use themselves as
515  * user_data for callbacks without having to explicitly disconnect all the
516  * handlers in their finalizers.
517  */
518 void
519 empathy_signal_connect_weak (gpointer instance,
520     const gchar *detailed_signal,
521     GCallback c_handler,
522     GObject *user_data)
523 {
524   GObject *instance_obj = G_OBJECT (instance);
525   WeakHandlerCtx *ctx = whc_new (instance_obj, user_data);
526
527   DEBUG ("connecting to %p:%s with context %p", instance, detailed_signal, ctx);
528
529   ctx->handler_id = g_signal_connect (instance, detailed_signal, c_handler,
530       user_data);
531
532   g_object_weak_ref (instance_obj, instance_destroyed_cb, ctx);
533   g_object_weak_ref (user_data, user_data_destroyed_cb, ctx);
534 }
535
536 /* Note: this function depends on the account manager having its core feature
537  * prepared. */
538 TpAccount *
539 empathy_get_account_for_connection (TpConnection *connection)
540 {
541   TpAccountManager *manager;
542   TpAccount *account = NULL;
543   GList *accounts, *l;
544
545   manager = tp_account_manager_dup ();
546
547   accounts = tp_account_manager_get_valid_accounts (manager);
548
549   for (l = accounts; l != NULL; l = l->next)
550     {
551       TpAccount *a = l->data;
552
553       if (tp_account_get_connection (a) == connection)
554         {
555           account = a;
556           break;
557         }
558     }
559
560   g_list_free (accounts);
561   g_object_unref (manager);
562
563   return account;
564 }
565
566 gboolean
567 empathy_account_manager_get_accounts_connected (gboolean *connecting)
568 {
569   TpAccountManager *manager;
570   GList *accounts, *l;
571   gboolean out_connecting = FALSE;
572   gboolean out_connected = FALSE;
573
574   manager = tp_account_manager_dup ();
575
576   if (G_UNLIKELY (!tp_account_manager_is_prepared (manager,
577           TP_ACCOUNT_MANAGER_FEATURE_CORE)))
578     g_critical (G_STRLOC ": %s called before AccountManager ready", G_STRFUNC);
579
580   accounts = tp_account_manager_get_valid_accounts (manager);
581
582   for (l = accounts; l != NULL; l = l->next)
583     {
584       TpConnectionStatus s = tp_account_get_connection_status (
585           TP_ACCOUNT (l->data), NULL);
586
587       if (s == TP_CONNECTION_STATUS_CONNECTING)
588         out_connecting = TRUE;
589       else if (s == TP_CONNECTION_STATUS_CONNECTED)
590         out_connected = TRUE;
591
592       if (out_connecting && out_connected)
593         break;
594     }
595
596   g_list_free (accounts);
597   g_object_unref (manager);
598
599   if (connecting != NULL)
600     *connecting = out_connecting;
601
602   return out_connected;
603 }