]> git.0d.be Git - empathy.git/blob - libempathy/empathy-utils.c
Add a function to request a channel from a string handle
[empathy.git] / libempathy / empathy-utils.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2003-2007 Imendio AB
4  * Copyright (C) 2007-2008 Collabora Ltd.
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,
12  * but 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
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  *
21  * Authors: Richard Hult <richard@imendio.com>
22  *          Martyn Russell <martyn@imendio.com>
23  *          Xavier Claessens <xclaesse@gmail.com>
24  */
25
26 #include "config.h"
27
28 #include <string.h>
29 #include <time.h>
30 #include <sys/types.h>
31 #include <regex.h>
32
33 #include <glib/gi18n.h>
34
35 #include <libxml/uri.h>
36 #include <telepathy-glib/connection.h>
37 #include <telepathy-glib/channel.h>
38 #include <telepathy-glib/dbus.h>
39
40 #include "empathy-utils.h"
41 #include "empathy-contact-factory.h"
42 #include "empathy-contact-manager.h"
43
44 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
45 #include "empathy-debug.h"
46
47 static void regex_init (void);
48
49 gchar *
50 empathy_substring (const gchar *str,
51                   gint         start,
52                   gint         end)
53 {
54         return g_strndup (str + start, end - start);
55 }
56
57 /*
58  * Regular Expression code to match urls.
59  */
60 #define APTCHARS  "-A-Za-z0-9,-."
61 #define USERCHARS "-A-Za-z0-9"
62 #define PASSCHARS "-A-Za-z0-9,?;.:/!%$^*&~\"#'"
63 #define HOSTCHARS "-A-Za-z0-9_"
64 #define PATHCHARS "-A-Za-z0-9_$.+!*(),;:@&=?/~#%"
65 #define SCHEME    "(news:|telnet:|nntp:|file:/|https?:|ftps?:|webcal:)"
66 #define USER      "[" USERCHARS "]+(:["PASSCHARS "]+)?"
67 #define URLPATH   "/[" PATHCHARS "]*[^]'.}>) \t\r\n,\\\"]"
68
69 static regex_t dingus[EMPATHY_REGEX_ALL];
70
71 static void
72 regex_init (void)
73 {
74         static gboolean  inited = FALSE;
75         const gchar     *expression;
76         gint             i;
77
78         if (inited) {
79                 return;
80         }
81
82         for (i = 0; i < EMPATHY_REGEX_ALL; i++) {
83                 switch (i) {
84                 case EMPATHY_REGEX_AS_IS:
85                         expression =
86                                 SCHEME "//(" USER "@)?[" HOSTCHARS ".]+"
87                                 "(:[0-9]+)?(" URLPATH ")?";
88                         break;
89                 case EMPATHY_REGEX_BROWSER:
90                         expression =
91                                 "(www|ftp)[" HOSTCHARS "]*\\.[" HOSTCHARS ".]+"
92                                 "(:[0-9]+)?(" URLPATH ")?";
93                         break;
94                 case EMPATHY_REGEX_APT:
95                         expression =
96                                 "apt://[" APTCHARS "]*";
97                         break;
98                 case EMPATHY_REGEX_EMAIL:
99                         expression =
100                                 "(mailto:)?[a-z0-9][a-z0-9.-]*@[a-z0-9]"
101                                 "[a-z0-9-]*(\\.[a-z0-9][a-z0-9-]*)+";
102                         break;
103                 case EMPATHY_REGEX_OTHER:
104                         expression =
105                                 "news:[-A-Z\\^_a-z{|}~!\"#$%&'()*+,./0-9;:=?`]+"
106                                 "@[" HOSTCHARS ".]+(:[0-9]+)?";
107                         break;
108                 default:
109                         /* Silence the compiler. */
110                         expression = NULL;
111                         continue;
112                 }
113
114                 memset (&dingus[i], 0, sizeof (regex_t));
115                 regcomp (&dingus[i], expression, REG_EXTENDED | REG_ICASE);
116         }
117
118         inited = TRUE;
119 }
120
121 gint
122 empathy_regex_match (EmpathyRegExType  type,
123                     const gchar     *msg,
124                     GArray          *start,
125                     GArray          *end)
126 {
127         regmatch_t matches[1];
128         gint       ret = 0;
129         gint       num_matches = 0;
130         gint       offset = 0;
131         gint       i;
132
133         g_return_val_if_fail (type >= 0 || type <= EMPATHY_REGEX_ALL, 0);
134
135         regex_init ();
136
137         while (!ret && type != EMPATHY_REGEX_ALL) {
138                 ret = regexec (&dingus[type], msg + offset, 1, matches, 0);
139                 if (ret == 0) {
140                         gint s;
141
142                         num_matches++;
143
144                         s = matches[0].rm_so + offset;
145                         offset = matches[0].rm_eo + offset;
146
147                         g_array_append_val (start, s);
148                         g_array_append_val (end, offset);
149                 }
150         }
151
152         if (type != EMPATHY_REGEX_ALL) {
153                 DEBUG ("Found %d matches for regex type:%d", num_matches, type);
154                 return num_matches;
155         }
156
157         /* If EMPATHY_REGEX_ALL then we run ALL regex's on the string. */
158         for (i = 0; i < EMPATHY_REGEX_ALL; i++, ret = 0) {
159                 while (!ret) {
160                         ret = regexec (&dingus[i], msg + offset, 1, matches, 0);
161                         if (ret == 0) {
162                                 gint s;
163
164                                 num_matches++;
165
166                                 s = matches[0].rm_so + offset;
167                                 offset = matches[0].rm_eo + offset;
168
169                                 g_array_append_val (start, s);
170                                 g_array_append_val (end, offset);
171                         }
172                 }
173         }
174
175         DEBUG ("Found %d matches for ALL regex types", num_matches);
176
177         return num_matches;
178 }
179
180 gint
181 empathy_strcasecmp (const gchar *s1,
182                    const gchar *s2)
183 {
184         return empathy_strncasecmp (s1, s2, -1);
185 }
186
187 gint
188 empathy_strncasecmp (const gchar *s1,
189                     const gchar *s2,
190                     gsize        n)
191 {
192         gchar *u1, *u2;
193         gint   ret_val;
194
195         u1 = g_utf8_casefold (s1, n);
196         u2 = g_utf8_casefold (s2, n);
197
198         ret_val = g_utf8_collate (u1, u2);
199         g_free (u1);
200         g_free (u2);
201
202         return ret_val;
203 }
204
205 gboolean
206 empathy_xml_validate (xmlDoc      *doc,
207                      const gchar *dtd_filename)
208 {
209         gchar        *path, *escaped;
210         xmlValidCtxt  cvp;
211         xmlDtd       *dtd;
212         gboolean      ret;
213
214         path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), "libempathy",
215                                  dtd_filename, NULL);
216         if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
217                 g_free (path);
218                 path = g_build_filename (DATADIR, "empathy", dtd_filename, NULL);
219         }
220         DEBUG ("Loading dtd file %s", path);
221
222         /* The list of valid chars is taken from libxml. */
223         escaped = xmlURIEscapeStr (path, ":@&=+$,/?;");
224         g_free (path);
225
226         memset (&cvp, 0, sizeof (cvp));
227         dtd = xmlParseDTD (NULL, escaped);
228         ret = xmlValidateDtd (&cvp, doc, dtd);
229
230         xmlFree (escaped);
231         xmlFreeDtd (dtd);
232
233         return ret;
234 }
235
236 xmlNodePtr
237 empathy_xml_node_get_child (xmlNodePtr   node, 
238                            const gchar *child_name)
239 {
240         xmlNodePtr l;
241
242         g_return_val_if_fail (node != NULL, NULL);
243         g_return_val_if_fail (child_name != NULL, NULL);
244
245         for (l = node->children; l; l = l->next) {
246                 if (l->name && strcmp (l->name, child_name) == 0) {
247                         return l;
248                 }
249         }
250
251         return NULL;
252 }
253
254 xmlChar *
255 empathy_xml_node_get_child_content (xmlNodePtr   node, 
256                                    const gchar *child_name)
257 {
258         xmlNodePtr l;
259
260         g_return_val_if_fail (node != NULL, NULL);
261         g_return_val_if_fail (child_name != NULL, NULL);
262
263         l = empathy_xml_node_get_child (node, child_name);
264         if (l) {
265                 return xmlNodeGetContent (l);
266         }
267                 
268         return NULL;
269 }
270
271 xmlNodePtr
272 empathy_xml_node_find_child_prop_value (xmlNodePtr   node, 
273                                        const gchar *prop_name,
274                                        const gchar *prop_value)
275 {
276         xmlNodePtr l;
277         xmlNodePtr found = NULL;
278
279         g_return_val_if_fail (node != NULL, NULL);
280         g_return_val_if_fail (prop_name != NULL, NULL);
281         g_return_val_if_fail (prop_value != NULL, NULL);
282
283         for (l = node->children; l && !found; l = l->next) {
284                 xmlChar *prop;
285
286                 if (!xmlHasProp (l, prop_name)) {
287                         continue;
288                 }
289
290                 prop = xmlGetProp (l, prop_name);
291                 if (prop && strcmp (prop, prop_value) == 0) {
292                         found = l;
293                 }
294                 
295                 xmlFree (prop);
296         }
297                 
298         return found;
299 }
300
301 guint
302 empathy_account_hash (gconstpointer key)
303 {
304         g_return_val_if_fail (MC_IS_ACCOUNT (key), 0);
305
306         return g_str_hash (mc_account_get_unique_name (MC_ACCOUNT (key)));
307 }
308
309 gboolean
310 empathy_account_equal (gconstpointer a,
311                        gconstpointer b)
312 {
313         const gchar *name_a;
314         const gchar *name_b;
315
316         g_return_val_if_fail (MC_IS_ACCOUNT (a), FALSE);
317         g_return_val_if_fail (MC_IS_ACCOUNT (b), FALSE);
318
319         name_a = mc_account_get_unique_name (MC_ACCOUNT (a));
320         name_b = mc_account_get_unique_name (MC_ACCOUNT (b));
321
322         return g_str_equal (name_a, name_b);
323 }
324
325 MissionControl *
326 empathy_mission_control_new (void)
327 {
328         static MissionControl *mc = NULL;
329
330         if (!mc) {
331                 mc = mission_control_new (tp_get_bus ());
332                 g_object_add_weak_pointer (G_OBJECT (mc), (gpointer) &mc);
333         } else {
334                 g_object_ref (mc);
335         }
336
337         return mc;
338 }
339
340 const gchar *
341 empathy_presence_get_default_message (McPresence presence)
342 {
343         switch (presence) {
344         case MC_PRESENCE_AVAILABLE:
345                 return _("Available");
346         case MC_PRESENCE_DO_NOT_DISTURB:
347                 return _("Busy");
348         case MC_PRESENCE_AWAY:
349         case MC_PRESENCE_EXTENDED_AWAY:
350                 return _("Away");
351         case MC_PRESENCE_HIDDEN:
352                 return _("Hidden");
353         case MC_PRESENCE_OFFLINE:
354         case MC_PRESENCE_UNSET:
355                 return _("Offline");
356         default:
357                 g_assert_not_reached ();
358         }
359
360         return NULL;
361 }
362
363 const gchar *
364 empathy_presence_to_str (McPresence presence)
365 {
366         switch (presence) {
367         case MC_PRESENCE_AVAILABLE:
368                 return "available";
369         case MC_PRESENCE_DO_NOT_DISTURB:
370                 return "busy";
371         case MC_PRESENCE_AWAY:
372                 return "away";
373         case MC_PRESENCE_EXTENDED_AWAY:
374                 return "ext_away";
375         case MC_PRESENCE_HIDDEN:
376                 return "hidden";
377         case MC_PRESENCE_OFFLINE:
378                 return "offline";
379         case MC_PRESENCE_UNSET:
380                 return "unset";
381         default:
382                 g_assert_not_reached ();
383         }
384
385         return NULL;
386 }
387
388 McPresence
389 empathy_presence_from_str (const gchar *str)
390 {
391         if (strcmp (str, "available") == 0) {
392                 return MC_PRESENCE_AVAILABLE;
393         } else if ((strcmp (str, "dnd") == 0) || (strcmp (str, "busy") == 0)) {
394                 return MC_PRESENCE_DO_NOT_DISTURB;
395         } else if ((strcmp (str, "away") == 0) || (strcmp (str, "brb") == 0)) {
396                 return MC_PRESENCE_AWAY;
397         } else if ((strcmp (str, "xa") == 0) || (strcmp (str, "ext_away") == 0)) {
398                 return MC_PRESENCE_EXTENDED_AWAY;
399         } else if (strcmp (str, "hidden") == 0) {
400                 return MC_PRESENCE_HIDDEN;
401         } else if (strcmp (str, "offline") == 0) {
402                 return MC_PRESENCE_OFFLINE;
403         } else if (strcmp (str, "unset") == 0) {
404                 return MC_PRESENCE_UNSET;
405         }
406
407         return MC_PRESENCE_UNSET;
408 }
409
410 gchar *
411 empathy_file_lookup (const gchar *filename, const gchar *subdir)
412 {
413         gchar *path;
414
415         if (!subdir) {
416                 subdir = ".";
417         }
418
419         path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), subdir, filename, NULL);
420         if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
421                 g_free (path);
422                 path = g_build_filename (DATADIR, "empathy", filename, NULL);
423         }
424
425         return path;
426 }
427
428 typedef struct {
429         EmpathyRunUntilReadyFunc  func;
430         gpointer                  user_data;
431         GObject                  *object;
432         GMainLoop                *loop;
433 } RunUntilReadyData;
434
435 static void
436 run_until_ready_cb (RunUntilReadyData *data)
437 {
438         if (!data->func || data->func (data->object, data->user_data)) {
439                 DEBUG ("Object %p is ready", data->object);
440                 g_main_loop_quit (data->loop);
441         }
442 }
443
444 static gboolean
445 object_is_ready (GObject *object,
446                  gpointer user_data)
447 {
448         gboolean ready;
449
450         g_object_get (object, "ready", &ready, NULL);
451
452         return ready;
453 }
454
455 void
456 empathy_run_until_ready_full (gpointer                  object,
457                               const gchar              *signal,
458                               EmpathyRunUntilReadyFunc  func,
459                               gpointer                  user_data,
460                               GMainLoop               **loop)
461 {
462         RunUntilReadyData  data;
463         gulong             signal_id;
464
465         g_return_if_fail (G_IS_OBJECT (object));
466         g_return_if_fail (signal != NULL);
467
468         if (func && func (object, user_data)) {
469                 return;
470         }
471
472         DEBUG ("Starting run until ready for object %p", object);
473
474         data.func = func;
475         data.user_data = user_data;
476         data.object = object;
477         data.loop = g_main_loop_new (NULL, FALSE);
478
479         signal_id = g_signal_connect_swapped (object, signal,
480                                               G_CALLBACK (run_until_ready_cb),
481                                               &data);
482         if (loop != NULL) {
483                 *loop = data.loop;
484         }
485
486         g_main_loop_run (data.loop);
487
488         if (loop != NULL) {
489                 *loop = NULL;
490         }
491
492         g_signal_handler_disconnect (object, signal_id);
493         g_main_loop_unref (data.loop);
494 }
495
496 void
497 empathy_run_until_ready (gpointer object)
498 {
499         empathy_run_until_ready_full (object, "notify::ready", object_is_ready,
500                                       NULL, NULL);
501 }
502
503 McAccount *
504 empathy_channel_get_account (TpChannel *channel)
505 {
506         TpConnection   *connection;
507         McAccount      *account;
508         MissionControl *mc;
509
510         g_object_get (channel, "connection", &connection, NULL);
511         mc = empathy_mission_control_new ();
512         account = mission_control_get_account_for_tpconnection (mc, connection, NULL);
513         g_object_unref (connection);
514         g_object_unref (mc);
515
516         return account;
517 }
518
519 typedef void (*AccountStatusChangedFunc) (MissionControl           *mc,
520                                           TpConnectionStatus        status,
521                                           McPresence                presence,
522                                           TpConnectionStatusReason  reason,
523                                           const gchar              *unique_name,
524                                           gpointer                  user_data);
525
526 typedef struct {
527         AccountStatusChangedFunc handler;
528         gpointer                 user_data;
529         GClosureNotify           free_func;
530         MissionControl          *mc;
531 } AccountStatusChangedData;
532
533 typedef struct {
534         TpConnectionStatus        status;
535         McPresence                presence;
536         TpConnectionStatusReason  reason;
537         gchar                    *unique_name;
538         AccountStatusChangedData *data;
539 } InvocationData;
540
541 static void
542 account_status_changed_data_free (gpointer ptr,
543                                   GClosure *closure)
544 {
545         AccountStatusChangedData *data = ptr;
546
547         if (data->free_func) {
548                 data->free_func (data->user_data, closure);
549         }
550         g_object_unref (data->mc);
551         g_slice_free (AccountStatusChangedData, data);
552 }
553
554 static gboolean
555 account_status_changed_invoke_callback (gpointer data)
556 {
557         InvocationData *invocation_data = data;
558
559         invocation_data->data->handler (invocation_data->data->mc,
560                                         invocation_data->status,
561                                         invocation_data->presence,
562                                         invocation_data->reason,
563                                         invocation_data->unique_name,
564                                         invocation_data->data->user_data);
565
566         g_free (invocation_data->unique_name);
567         g_slice_free (InvocationData, invocation_data);
568
569         return FALSE;
570 }
571
572 static void
573 account_status_changed_cb (MissionControl           *mc,
574                            TpConnectionStatus        status,
575                            McPresence                presence,
576                            TpConnectionStatusReason  reason,
577                            const gchar              *unique_name,
578                            AccountStatusChangedData *data)
579 {
580         InvocationData *invocation_data;
581
582         invocation_data = g_slice_new (InvocationData);
583         invocation_data->status = status;
584         invocation_data->presence = presence;
585         invocation_data->reason = reason;
586         invocation_data->unique_name = g_strdup (unique_name);
587         invocation_data->data = data;
588
589         g_idle_add_full (G_PRIORITY_HIGH,
590                          account_status_changed_invoke_callback,
591                          invocation_data, NULL);
592 }
593
594 gpointer
595 empathy_connect_to_account_status_changed (MissionControl *mc,
596                                            GCallback       handler,
597                                            gpointer        user_data,
598                                            GClosureNotify  free_func)
599 {
600         AccountStatusChangedData *data;
601
602         g_return_val_if_fail (IS_MISSIONCONTROL (mc), NULL);
603         g_return_val_if_fail (handler != NULL, NULL);
604         
605         data = g_slice_new (AccountStatusChangedData);
606         data->handler = (AccountStatusChangedFunc) handler;
607         data->user_data = user_data;
608         data->free_func = free_func;
609         data->mc = g_object_ref (mc);
610
611         dbus_g_proxy_connect_signal (DBUS_G_PROXY (mc), "AccountStatusChanged",
612                                      G_CALLBACK (account_status_changed_cb),
613                                      data, account_status_changed_data_free);
614
615         return data;
616 }
617
618 void
619 empathy_disconnect_account_status_changed (gpointer token)
620 {
621         AccountStatusChangedData *data = token;
622
623         dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (data->mc),
624                                         "AccountStatusChanged",
625                                         G_CALLBACK (account_status_changed_cb),
626                                         data);
627 }
628
629 guint
630 empathy_proxy_hash (gconstpointer key)
631 {
632         TpProxy      *proxy = TP_PROXY (key);
633         TpProxyClass *proxy_class = TP_PROXY_GET_CLASS (key);
634
635         g_return_val_if_fail (TP_IS_PROXY (proxy), 0);
636         g_return_val_if_fail (proxy_class->must_have_unique_name, 0);
637
638         return g_str_hash (proxy->object_path) ^ g_str_hash (proxy->bus_name);
639 }
640
641 gboolean
642 empathy_proxy_equal (gconstpointer a,
643                      gconstpointer b)
644 {
645         TpProxy *proxy_a = TP_PROXY (a);
646         TpProxy *proxy_b = TP_PROXY (b);
647         TpProxyClass *proxy_a_class = TP_PROXY_GET_CLASS (a);
648         TpProxyClass *proxy_b_class = TP_PROXY_GET_CLASS (b);
649
650         g_return_val_if_fail (TP_IS_PROXY (proxy_a), FALSE);
651         g_return_val_if_fail (TP_IS_PROXY (proxy_b), FALSE);
652         g_return_val_if_fail (proxy_a_class->must_have_unique_name, 0);
653         g_return_val_if_fail (proxy_b_class->must_have_unique_name, 0);
654
655         return g_str_equal (proxy_a->object_path, proxy_b->object_path) &&
656                g_str_equal (proxy_a->bus_name, proxy_b->bus_name);
657 }
658
659 typedef struct {
660         gint timeout_ms;
661         gchar *channel_type;
662         guint handle_type;
663         empathy_connection_callback_for_request_channel callback;
664         gpointer user_data;
665         GDestroyNotify destroy;
666         TpHandle handle;
667         gboolean suppress_handler;
668         guint ref_count;
669 } ConnectionRequestChannelData;
670
671 static void
672 connection_request_channel_data_unref (gpointer user_data)
673 {
674         ConnectionRequestChannelData *data = (ConnectionRequestChannelData*) user_data;
675
676         if (--data->ref_count == 0) {
677                 g_free (data->channel_type);
678                 if (data->destroy) {
679                         data->destroy (data->user_data);
680                 }
681                 g_slice_free (ConnectionRequestChannelData, data);
682         }
683 }
684
685 static void
686 connection_request_channel_cb (TpConnection *connection,
687                                const gchar *object_path,
688                                const GError *error,
689                                gpointer user_data,
690                                GObject *weak_object)
691 {
692         ConnectionRequestChannelData *data = (ConnectionRequestChannelData*) user_data;
693         TpChannel *channel;
694
695         if (!data->callback) {
696                 return;
697         }
698
699         if (error) {
700                 data->callback (connection, NULL, error, data->user_data, weak_object);
701                 return;
702         }
703
704         channel = tp_channel_new (connection, object_path,
705                                   data->channel_type,
706                                   data->handle_type,
707                                   data->handle, NULL);
708
709         data->callback (connection, channel, NULL, data->user_data, weak_object);
710         g_object_unref (channel);
711 }
712
713 static void
714 connection_request_handles_cb (TpConnection *connection,
715                                const GArray *handles,
716                                const GError *error,
717                                gpointer user_data,
718                                GObject *weak_object)
719 {
720         ConnectionRequestChannelData *data = (ConnectionRequestChannelData*) user_data;
721
722         if (error) {
723                 if (data->callback) {
724                         data->callback (connection, NULL, error, data->user_data, weak_object);
725                 }
726                 return;
727         }
728
729         data->handle = g_array_index (handles, guint, 0);
730         data->ref_count++;
731         tp_cli_connection_call_request_channel (connection, data->timeout_ms,
732                                                 data->channel_type,
733                                                 data->handle_type,
734                                                 data->handle,
735                                                 data->suppress_handler,
736                                                 connection_request_channel_cb,
737                                                 data,
738                                                 (GDestroyNotify) connection_request_channel_data_unref,
739                                                 weak_object);
740 }
741
742 void
743 empathy_connection_request_channel (TpConnection *connection,
744                                     gint timeout_ms,
745                                     const gchar *channel_type,
746                                     guint handle_type,
747                                     const gchar *name,
748                                     gboolean suppress_handler,
749                                     empathy_connection_callback_for_request_channel callback,
750                                     gpointer user_data,
751                                     GDestroyNotify destroy,
752                                     GObject *weak_object)
753 {
754         const gchar *names[] = {name, NULL};
755         ConnectionRequestChannelData *data;
756
757         data = g_slice_new (ConnectionRequestChannelData);
758         data->timeout_ms = timeout_ms;
759         data->channel_type = g_strdup (channel_type);
760         data->handle_type = handle_type;
761         data->callback = callback;
762         data->user_data = user_data;
763         data->destroy = destroy;
764         data->handle = 0;
765         data->suppress_handler = suppress_handler;
766         data->ref_count = 1;
767         tp_cli_connection_call_request_handles (connection,
768                                                 timeout_ms,
769                                                 handle_type,
770                                                 names,
771                                                 connection_request_handles_cb,
772                                                 data,
773                                                 (GDestroyNotify) connection_request_channel_data_unref,
774                                                 weak_object);
775 }
776