1 /* vi: set et sw=4 ts=8 cino=t0,(0: */
2 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 8 -*- */
3 /*
4  * Mission Control client proxy.
5  *
6  * Copyright (C) 2009-2010 Nokia Corporation
7  * Copyright (C) 2009-2010 Collabora Ltd.
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22  * 02110-1301 USA
23  *
24  */
25 
26 #include "config.h"
27 #include "mcd-client-priv.h"
28 
29 #include <errno.h>
30 
31 #include <telepathy-glib/telepathy-glib.h>
32 #include <telepathy-glib/telepathy-glib-dbus.h>
33 
34 #include <telepathy-glib/proxy-subclass.h>
35 
36 #include "channel-utils.h"
37 #include "mcd-channel-priv.h"
38 #include "mcd-debug.h"
39 
40 G_DEFINE_TYPE (McdClientProxy, _mcd_client_proxy, TP_TYPE_CLIENT);
41 
42 enum
43 {
44     PROP_0,
45     PROP_ACTIVATABLE,
46     PROP_STRING_POOL,
47     PROP_UNIQUE_NAME,
48 };
49 
50 enum
51 {
52     S_READY,
53     S_IS_HANDLING_CHANNEL,
54     S_HANDLER_CAPABILITIES_CHANGED,
55     S_GONE,
56     S_NEED_RECOVERY,
57     N_SIGNALS
58 };
59 
60 static guint signals[N_SIGNALS] = { 0 };
61 
62 struct _McdClientProxyPrivate
63 {
64     GStrv capability_tokens;
65 
66     gchar *unique_name;
67     guint ready_lock;
68     gboolean introspect_started;
69     gboolean ready;
70     gboolean bypass_approval;
71     gboolean bypass_observers;
72     gboolean delay_approvers;
73     gboolean recover;
74 
75     /* If a client was in the ListActivatableNames list, it must not be
76      * removed when it disappear from the bus.
77      */
78     gboolean activatable;
79 
80     /* Channel filters
81      * A channel filter is a GHashTable of
82      * - key: gchar *property_name
83      * - value: GValue of one of the allowed types on the ObserverChannelFilter
84      *          spec. The following matching is observed:
85      *           * G_TYPE_STRING: 's'
86      *           * G_TYPE_BOOLEAN: 'b'
87      *           * DBUS_TYPE_G_OBJECT_PATH: 'o'
88      *           * G_TYPE_UINT64: 'y' (8b), 'q' (16b), 'u' (32b), 't' (64b)
89      *           * G_TYPE_INT64:            'n' (16b), 'i' (32b), 'x' (64b)
90      *
91      * The list can be NULL if there is no filter, or the filters are not yet
92      * retrieven from the D-Bus *ChannelFitler properties. In the last case,
93      * the dispatcher just don't dispatch to this client.
94      */
95     GList *approver_filters;
96     GList *handler_filters;
97     GList *observer_filters;
98 
99     gboolean disposed;
100 };
101 
102 typedef enum
103 {
104     MCD_CLIENT_APPROVER,
105     MCD_CLIENT_HANDLER,
106     MCD_CLIENT_OBSERVER
107 } McdClientInterface;
108 
109 void
_mcd_client_proxy_inc_ready_lock(McdClientProxy * self)110 _mcd_client_proxy_inc_ready_lock (McdClientProxy *self)
111 {
112     g_return_if_fail (MCD_IS_CLIENT_PROXY (self));
113 
114     if (self->priv->ready)
115         return;
116 
117     g_return_if_fail (self->priv->ready_lock > 0);
118 
119     self->priv->ready_lock++;
120 }
121 
122 void
_mcd_client_proxy_dec_ready_lock(McdClientProxy * self)123 _mcd_client_proxy_dec_ready_lock (McdClientProxy *self)
124 {
125     g_return_if_fail (MCD_IS_CLIENT_PROXY (self));
126 
127     if (self->priv->ready)
128         return;
129 
130     g_return_if_fail (self->priv->ready_lock > 0);
131 
132     if (--self->priv->ready_lock == 0)
133     {
134         self->priv->ready = TRUE;
135         g_signal_emit (self, signals[S_READY], 0);
136 
137         /* Activatable Observers needing recovery have already
138          * been called (in order to reactivate them). */
139         if (self->priv->recover && !self->priv->activatable)
140             g_signal_emit (self, signals[S_NEED_RECOVERY], 0);
141     }
142 }
143 
144 static void _mcd_client_proxy_take_approver_filters
145     (McdClientProxy *self, GList *filters);
146 static void _mcd_client_proxy_take_observer_filters
147     (McdClientProxy *self, GList *filters);
148 static void _mcd_client_proxy_take_handler_filters
149     (McdClientProxy *self, GList *filters);
150 
151 static gchar *
_mcd_client_proxy_find_client_file(const gchar * client_name)152 _mcd_client_proxy_find_client_file (const gchar *client_name)
153 {
154     const gchar * const *dirs;
155     const gchar *dirname;
156     const gchar *env_dirname;
157     gchar *filename, *absolute_filepath;
158 
159     /*
160      * The full path is $XDG_DATA_DIRS/telepathy/clients/clientname.client
161      * or $XDG_DATA_HOME/telepathy/clients/clientname.client
162      * For testing purposes, we also look for $MC_CLIENTS_DIR/clientname.client
163      * if $MC_CLIENTS_DIR is set.
164      */
165     filename = g_strdup_printf ("%s.client", client_name);
166     env_dirname = g_getenv ("MC_CLIENTS_DIR");
167     if (env_dirname)
168     {
169         absolute_filepath = g_build_filename (env_dirname, filename, NULL);
170         if (g_file_test (absolute_filepath, G_FILE_TEST_IS_REGULAR))
171             goto finish;
172         g_free (absolute_filepath);
173     }
174 
175     dirname = g_get_user_data_dir ();
176     if (G_LIKELY (dirname))
177     {
178         absolute_filepath = g_build_filename (dirname, "telepathy/clients",
179                                               filename, NULL);
180         if (g_file_test (absolute_filepath, G_FILE_TEST_IS_REGULAR))
181             goto finish;
182         g_free (absolute_filepath);
183     }
184 
185     dirs = g_get_system_data_dirs ();
186     for (dirname = *dirs; dirname != NULL; dirs++, dirname = *dirs)
187     {
188         absolute_filepath = g_build_filename (dirname, "telepathy/clients",
189                                               filename, NULL);
190         if (g_file_test (absolute_filepath, G_FILE_TEST_IS_REGULAR))
191             goto finish;
192         g_free (absolute_filepath);
193     }
194 
195     absolute_filepath = NULL;
196 finish:
197     g_free (filename);
198     return absolute_filepath;
199 }
200 
201 static GHashTable *
parse_client_filter(GKeyFile * file,const gchar * group)202 parse_client_filter (GKeyFile *file, const gchar *group)
203 {
204     GHashTable *filter;
205     gchar **keys;
206     gsize len;
207     guint i;
208 
209     filter = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
210                                     (GDestroyNotify) tp_g_value_slice_free);
211 
212     keys = g_key_file_get_keys (file, group, &len, NULL);
213 
214     if (keys == NULL)
215         len = 0;
216 
217     for (i = 0; i < len; i++)
218     {
219         const gchar *key;
220         const gchar *space;
221         gchar *file_property;
222         gchar file_property_type;
223 
224         key = keys[i];
225         space = g_strrstr (key, " ");
226 
227         if (space == NULL || space[1] == '\0' || space[2] != '\0')
228         {
229             g_warning ("Invalid key %s in client file", key);
230             continue;
231         }
232         file_property_type = space[1];
233         file_property = g_strndup (key, space - key);
234 
235         switch (file_property_type)
236         {
237         case 'q':
238         case 'u':
239         case 't': /* unsigned integer */
240             {
241                 /* g_key_file_get_integer cannot be used because we need
242                  * to support 64 bits */
243                 guint x;
244                 GValue *value = tp_g_value_slice_new (G_TYPE_UINT64);
245                 gchar *str = g_key_file_get_string (file, group, key,
246                                                     NULL);
247                 errno = 0;
248                 x = g_ascii_strtoull (str, NULL, 0);
249                 if (errno != 0)
250                 {
251                     g_warning ("Invalid unsigned integer '%s' in client"
252                                " file", str);
253                 }
254                 else
255                 {
256                     g_value_set_uint64 (value, x);
257                     g_hash_table_insert (filter, file_property, value);
258                 }
259                 g_free (str);
260                 break;
261             }
262 
263         case 'y':
264         case 'n':
265         case 'i':
266         case 'x': /* signed integer */
267             {
268                 gint x;
269                 GValue *value = tp_g_value_slice_new (G_TYPE_INT64);
270                 gchar *str = g_key_file_get_string (file, group, key, NULL);
271                 errno = 0;
272                 x = g_ascii_strtoll (str, NULL, 0);
273                 if (errno != 0)
274                 {
275                     g_warning ("Invalid signed integer '%s' in client"
276                                " file", str);
277                 }
278                 else
279                 {
280                     g_value_set_int64 (value, x);
281                     g_hash_table_insert (filter, file_property, value);
282                 }
283                 g_free (str);
284                 break;
285             }
286 
287         case 'b':
288             {
289                 GValue *value = tp_g_value_slice_new (G_TYPE_BOOLEAN);
290                 gboolean b = g_key_file_get_boolean (file, group, key, NULL);
291                 g_value_set_boolean (value, b);
292                 g_hash_table_insert (filter, file_property, value);
293                 break;
294             }
295 
296         case 's':
297             {
298                 GValue *value = tp_g_value_slice_new (G_TYPE_STRING);
299                 gchar *str = g_key_file_get_string (file, group, key, NULL);
300 
301                 g_value_take_string (value, str);
302                 g_hash_table_insert (filter, file_property, value);
303                 break;
304             }
305 
306         case 'o':
307             {
308                 GValue *value = tp_g_value_slice_new
309                     (DBUS_TYPE_G_OBJECT_PATH);
310                 gchar *str = g_key_file_get_string (file, group, key, NULL);
311 
312                 g_value_take_boxed (value, str);
313                 g_hash_table_insert (filter, file_property, value);
314                 break;
315             }
316 
317         default:
318             g_warning ("Invalid key %s in client file", key);
319             continue;
320         }
321     }
322     g_strfreev (keys);
323 
324     return filter;
325 }
326 
327 static void _mcd_client_proxy_set_cap_tokens (McdClientProxy *self,
328                                               GStrv cap_tokens);
329 static void _mcd_client_proxy_add_interfaces (McdClientProxy *self,
330                                               const gchar * const *interfaces);
331 
332 static void
parse_client_file(McdClientProxy * client,GKeyFile * file)333 parse_client_file (McdClientProxy *client,
334                    GKeyFile *file)
335 {
336     gchar **iface_names, **groups, **cap_tokens;
337     guint i;
338     gsize len = 0;
339     gboolean is_approver, is_handler, is_observer;
340     GList *approver_filters = NULL;
341     GList *observer_filters = NULL;
342     GList *handler_filters = NULL;
343 
344     iface_names = g_key_file_get_string_list (file, TP_IFACE_CLIENT,
345                                               "Interfaces", 0, NULL);
346     if (!iface_names)
347         return;
348 
349     _mcd_client_proxy_add_interfaces (client,
350                                       (const gchar * const *) iface_names);
351     g_strfreev (iface_names);
352 
353     is_approver = tp_proxy_has_interface_by_id (client,
354                                                 TP_IFACE_QUARK_CLIENT_APPROVER);
355     is_observer = tp_proxy_has_interface_by_id (client,
356                                                 TP_IFACE_QUARK_CLIENT_OBSERVER);
357     is_handler = tp_proxy_has_interface_by_id (client,
358                                                TP_IFACE_QUARK_CLIENT_HANDLER);
359 
360     /* parse filtering rules */
361     groups = g_key_file_get_groups (file, &len);
362     for (i = 0; i < len; i++)
363     {
364         if (is_approver &&
365             g_str_has_prefix (groups[i], TP_IFACE_CLIENT_APPROVER
366                               ".ApproverChannelFilter "))
367         {
368             approver_filters =
369                 g_list_prepend (approver_filters,
370                                 parse_client_filter (file, groups[i]));
371         }
372         else if (is_handler &&
373             g_str_has_prefix (groups[i], TP_IFACE_CLIENT_HANDLER
374                               ".HandlerChannelFilter "))
375         {
376             handler_filters =
377                 g_list_prepend (handler_filters,
378                                 parse_client_filter (file, groups[i]));
379         }
380         else if (is_observer &&
381             g_str_has_prefix (groups[i], TP_IFACE_CLIENT_OBSERVER
382                               ".ObserverChannelFilter "))
383         {
384             observer_filters =
385                 g_list_prepend (observer_filters,
386                                 parse_client_filter (file, groups[i]));
387         }
388     }
389     g_strfreev (groups);
390 
391     _mcd_client_proxy_take_approver_filters (client,
392                                              approver_filters);
393     _mcd_client_proxy_take_observer_filters (client,
394                                              observer_filters);
395     _mcd_client_proxy_take_handler_filters (client,
396                                             handler_filters);
397 
398     /* Other client options */
399     client->priv->bypass_approval =
400         g_key_file_get_boolean (file, TP_IFACE_CLIENT_HANDLER,
401                                 "BypassApproval", NULL);
402 
403     client->priv->bypass_observers =
404         g_key_file_get_boolean (file, TP_IFACE_CLIENT_HANDLER,
405                                 "BypassObservers", NULL);
406 
407     client->priv->delay_approvers =
408         g_key_file_get_boolean (file, TP_IFACE_CLIENT_OBSERVER,
409                                 "DelayApprovers", NULL);
410 
411     client->priv->recover =
412         g_key_file_get_boolean (file, TP_IFACE_CLIENT_OBSERVER,
413                                 "Recover", NULL);
414 
415     cap_tokens = g_key_file_get_keys (file,
416                                       TP_IFACE_CLIENT_HANDLER ".Capabilities",
417                                       NULL,
418                                       NULL);
419     _mcd_client_proxy_set_cap_tokens (client, cap_tokens);
420     g_strfreev (cap_tokens);
421 }
422 
423 static void
_mcd_client_proxy_set_filters(McdClientProxy * client,McdClientInterface interface,GPtrArray * filters)424 _mcd_client_proxy_set_filters (McdClientProxy *client,
425                                McdClientInterface interface,
426                                GPtrArray *filters)
427 {
428     GList *client_filters = NULL;
429     guint i;
430 
431     for (i = 0 ; i < filters->len ; i++)
432     {
433         GHashTable *channel_class = g_ptr_array_index (filters, i);
434         GHashTable *new_channel_class;
435         GHashTableIter iter;
436         gchar *property_name;
437         GValue *property_value;
438         gboolean valid_filter = TRUE;
439 
440         new_channel_class = g_hash_table_new_full
441             (g_str_hash, g_str_equal, g_free,
442              (GDestroyNotify) tp_g_value_slice_free);
443 
444         g_hash_table_iter_init (&iter, channel_class);
445         while (g_hash_table_iter_next (&iter, (gpointer *) &property_name,
446                                        (gpointer *) &property_value))
447         {
448             GValue *filter_value;
449             GType property_type = G_VALUE_TYPE (property_value);
450 
451             if (property_type == G_TYPE_BOOLEAN ||
452                 property_type == G_TYPE_STRING ||
453                 property_type == DBUS_TYPE_G_OBJECT_PATH)
454             {
455                 filter_value = tp_g_value_slice_new
456                     (G_VALUE_TYPE (property_value));
457                 g_value_copy (property_value, filter_value);
458             }
459             else if (property_type == G_TYPE_UCHAR ||
460                      property_type == G_TYPE_UINT ||
461                      property_type == G_TYPE_UINT64)
462             {
463                 filter_value = tp_g_value_slice_new (G_TYPE_UINT64);
464                 g_value_transform (property_value, filter_value);
465             }
466             else if (property_type == G_TYPE_INT ||
467                      property_type == G_TYPE_INT64)
468             {
469                 filter_value = tp_g_value_slice_new (G_TYPE_INT64);
470                 g_value_transform (property_value, filter_value);
471             }
472             else
473             {
474                 /* invalid type, do not add this filter */
475                 g_warning ("%s: Property %s has an invalid type (%s)",
476                            G_STRFUNC, property_name,
477                            g_type_name (G_VALUE_TYPE (property_value)));
478                 valid_filter = FALSE;
479                 break;
480             }
481 
482             g_hash_table_insert (new_channel_class, g_strdup (property_name),
483                                  filter_value);
484         }
485 
486         if (valid_filter)
487             client_filters = g_list_prepend (client_filters,
488                                              new_channel_class);
489         else
490             g_hash_table_unref (new_channel_class);
491     }
492 
493     switch (interface)
494     {
495         case MCD_CLIENT_OBSERVER:
496             _mcd_client_proxy_take_observer_filters (client,
497                                                      client_filters);
498             break;
499 
500         case MCD_CLIENT_APPROVER:
501             _mcd_client_proxy_take_approver_filters (client,
502                                                      client_filters);
503             break;
504 
505         case MCD_CLIENT_HANDLER:
506             _mcd_client_proxy_take_handler_filters (client,
507                                                     client_filters);
508             break;
509 
510         default:
511             g_assert_not_reached ();
512     }
513 }
514 
515 /* This is NULL-safe for the last argument, for ease of use with
516  * tp_asv_get_boxed */
517 static void
_mcd_client_proxy_set_cap_tokens(McdClientProxy * self,GStrv cap_tokens)518 _mcd_client_proxy_set_cap_tokens (McdClientProxy *self,
519                                   GStrv cap_tokens)
520 {
521     g_strfreev (self->priv->capability_tokens);
522     self->priv->capability_tokens = g_strdupv (cap_tokens);
523 }
524 
525 static void
_mcd_client_proxy_add_interfaces(McdClientProxy * self,const gchar * const * interfaces)526 _mcd_client_proxy_add_interfaces (McdClientProxy *self,
527                                   const gchar * const *interfaces)
528 {
529     guint i;
530 
531     if (interfaces == NULL)
532         return;
533 
534     for (i = 0; interfaces[i] != NULL; i++)
535     {
536         if (tp_dbus_check_valid_interface_name (interfaces[i], NULL))
537         {
538             GQuark q = g_quark_from_string (interfaces[i]);
539 
540             DEBUG ("%s: %s", tp_proxy_get_bus_name (self), interfaces[i]);
541             tp_proxy_add_interface_by_id ((TpProxy *) self, q);
542         }
543     }
544 }
545 
546 static void
_mcd_client_proxy_init(McdClientProxy * self)547 _mcd_client_proxy_init (McdClientProxy *self)
548 {
549     self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MCD_TYPE_CLIENT_PROXY,
550                                               McdClientProxyPrivate);
551     /* paired with first call to mcd_client_proxy_introspect */
552     self->priv->ready_lock = 1;
553 }
554 
555 gboolean
_mcd_client_proxy_is_ready(McdClientProxy * self)556 _mcd_client_proxy_is_ready (McdClientProxy *self)
557 {
558     g_return_val_if_fail (MCD_IS_CLIENT_PROXY (self), FALSE);
559 
560     return self->priv->ready;
561 }
562 
563 gboolean
_mcd_client_proxy_is_active(McdClientProxy * self)564 _mcd_client_proxy_is_active (McdClientProxy *self)
565 {
566     g_return_val_if_fail (MCD_IS_CLIENT_PROXY (self), FALSE);
567 
568     return self->priv->unique_name != NULL &&
569         self->priv->unique_name[0] != '\0';
570 }
571 
572 gboolean
_mcd_client_proxy_is_activatable(McdClientProxy * self)573 _mcd_client_proxy_is_activatable (McdClientProxy *self)
574 {
575     g_return_val_if_fail (MCD_IS_CLIENT_PROXY (self), FALSE);
576 
577     return self->priv->activatable;
578 }
579 
580 const gchar *
_mcd_client_proxy_get_unique_name(McdClientProxy * self)581 _mcd_client_proxy_get_unique_name (McdClientProxy *self)
582 {
583     g_return_val_if_fail (MCD_IS_CLIENT_PROXY (self), NULL);
584 
585     return self->priv->unique_name;
586 }
587 
588 void
_mcd_client_recover_observer(McdClientProxy * self,TpChannel * channel,const gchar * account_path)589 _mcd_client_recover_observer (McdClientProxy *self, TpChannel *channel,
590     const gchar *account_path)
591 {
592     GPtrArray *satisfied_requests;
593     GHashTable *observer_info;
594     TpConnection *conn;
595     const gchar *connection_path;
596     GPtrArray *channels_array;
597 
598     satisfied_requests = g_ptr_array_new ();
599     observer_info = g_hash_table_new (g_str_hash, g_str_equal);
600     tp_asv_set_boolean (observer_info, "recovering", TRUE);
601     tp_asv_set_boxed (observer_info, "request-properties",
602         TP_HASH_TYPE_OBJECT_IMMUTABLE_PROPERTIES_MAP,
603         g_hash_table_new (NULL, NULL));
604 
605     channels_array = _mcd_tp_channel_details_build_from_tp_chan (channel);
606     conn = tp_channel_get_connection (channel);
607     connection_path = tp_proxy_get_object_path (conn);
608 
609     DEBUG ("calling ObserveChannels on %s for channel %p",
610            tp_proxy_get_bus_name (self), channel);
611 
612     tp_cli_client_observer_call_observe_channels (
613         (TpClient *) self, -1, account_path,
614         connection_path, channels_array,
615         "/", satisfied_requests, observer_info,
616         NULL, NULL, NULL, NULL);
617 
618     _mcd_tp_channel_details_free (channels_array);
619     g_ptr_array_unref (satisfied_requests);
620     g_hash_table_unref (observer_info);
621 }
622 
623 static void
_mcd_client_proxy_handler_get_all_cb(TpProxy * proxy,GHashTable * properties,const GError * error,gpointer p G_GNUC_UNUSED,GObject * o G_GNUC_UNUSED)624 _mcd_client_proxy_handler_get_all_cb (TpProxy *proxy,
625                                       GHashTable *properties,
626                                       const GError *error,
627                                       gpointer p G_GNUC_UNUSED,
628                                       GObject *o G_GNUC_UNUSED)
629 {
630     McdClientProxy *self = MCD_CLIENT_PROXY (proxy);
631     const gchar *bus_name = tp_proxy_get_bus_name (self);
632     GPtrArray *filters;
633     GPtrArray *handled_channels;
634     gboolean bypass;
635 
636     if (error != NULL)
637     {
638         DEBUG ("GetAll(Handler) for client %s failed: %s #%d: %s",
639                bus_name, g_quark_to_string (error->domain), error->code,
640                error->message);
641         goto finally;
642     }
643 
644     /* by now, we at least know whether the client is running or not */
645     g_assert (self->priv->unique_name != NULL);
646 
647     filters = tp_asv_get_boxed (properties, "HandlerChannelFilter",
648                                 TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST);
649 
650     if (filters != NULL)
651     {
652         DEBUG ("%s has %u HandlerChannelFilter entries", bus_name,
653                filters->len);
654         _mcd_client_proxy_set_filters (self, MCD_CLIENT_HANDLER, filters);
655     }
656     else
657     {
658         DEBUG ("%s HandlerChannelFilter absent or wrong type, assuming "
659                "no channels can match", bus_name);
660     }
661 
662     /* if wrong type or absent, assuming False is reasonable */
663     bypass = tp_asv_get_boolean (properties, "BypassApproval", NULL);
664     self->priv->bypass_approval = bypass;
665     DEBUG ("%s has BypassApproval=%c", bus_name, bypass ? 'T' : 'F');
666 
667     bypass = tp_asv_get_boolean (properties, "BypassObservers", NULL);
668     self->priv->bypass_observers = bypass;
669     DEBUG ("%s has BypassObservers=%c", bus_name, bypass ? 'T' : 'F');
670 
671     /* don't emit handler-capabilities-changed if we're not actually available
672      * any more - if that's the case, then we already signalled our loss of
673      * any capabilities */
674     if (self->priv->unique_name[0] != '\0' || self->priv->activatable)
675     {
676         _mcd_client_proxy_set_cap_tokens (self,
677             tp_asv_get_boxed (properties, "Capabilities", G_TYPE_STRV));
678         g_signal_emit (self, signals[S_HANDLER_CAPABILITIES_CHANGED], 0);
679     }
680 
681     /* If our unique name is "", then we're not *really* handling these
682      * channels - they're the last known information from before the
683      * client exited - so don't claim them.
684      *
685      * At the moment, McdDispatcher deals with the transition from active
686      * to inactive in a centralized way, so we don't need to signal that. */
687     if (self->priv->unique_name[0] != '\0')
688     {
689         guint i;
690 
691         handled_channels = tp_asv_get_boxed (properties, "HandledChannels",
692                                              TP_ARRAY_TYPE_OBJECT_PATH_LIST);
693 
694         if (handled_channels != NULL)
695         {
696             for (i = 0; i < handled_channels->len; i++)
697             {
698                 const gchar *path = g_ptr_array_index (handled_channels, i);
699 
700                 g_signal_emit (self, signals[S_IS_HANDLING_CHANNEL], 0, path);
701             }
702         }
703     }
704 
705 finally:
706     _mcd_client_proxy_dec_ready_lock (self);
707 }
708 
709 static void
_mcd_client_proxy_get_channel_filter_cb(TpProxy * proxy,const GValue * value,const GError * error,gpointer user_data,GObject * o G_GNUC_UNUSED)710 _mcd_client_proxy_get_channel_filter_cb (TpProxy *proxy,
711                                          const GValue *value,
712                                          const GError *error,
713                                          gpointer user_data,
714                                          GObject *o G_GNUC_UNUSED)
715 {
716     McdClientProxy *self = MCD_CLIENT_PROXY (proxy);
717     McdClientInterface iface = GPOINTER_TO_UINT (user_data);
718 
719     if (error != NULL)
720     {
721         DEBUG ("error getting a filter list for client %s: %s #%d: %s",
722                tp_proxy_get_object_path (self),
723                g_quark_to_string (error->domain), error->code, error->message);
724         goto finally;
725     }
726 
727     if (!G_VALUE_HOLDS (value, TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST))
728     {
729         DEBUG ("wrong type for filter property on client %s: %s",
730                tp_proxy_get_object_path (self), G_VALUE_TYPE_NAME (value));
731         goto finally;
732     }
733 
734     _mcd_client_proxy_set_filters (self, iface, g_value_get_boxed (value));
735 
736 finally:
737     _mcd_client_proxy_dec_ready_lock (self);
738 }
739 
740 static void
_mcd_client_proxy_observer_get_all_cb(TpProxy * proxy,GHashTable * properties,const GError * error,gpointer p G_GNUC_UNUSED,GObject * o G_GNUC_UNUSED)741 _mcd_client_proxy_observer_get_all_cb (TpProxy *proxy,
742                                        GHashTable *properties,
743                                        const GError *error,
744                                        gpointer p G_GNUC_UNUSED,
745                                        GObject *o G_GNUC_UNUSED)
746 {
747     McdClientProxy *self = MCD_CLIENT_PROXY (proxy);
748     const gchar *bus_name = tp_proxy_get_bus_name (self);
749     gboolean recover;
750     GPtrArray *filters;
751 
752     if (error != NULL)
753     {
754         DEBUG ("GetAll(Observer) for client %s failed: %s #%d: %s",
755                bus_name, g_quark_to_string (error->domain), error->code,
756                error->message);
757         goto finally;
758     }
759 
760     /* by now, we at least know whether the client is running or not */
761     g_assert (self->priv->unique_name != NULL);
762 
763     /* FALSE if DelayApprovers is invalid or missing is a good fallback */
764     self->priv->delay_approvers = tp_asv_get_boolean (
765         properties, "DelayApprovers", NULL);
766     DEBUG ("%s has DelayApprovers=%c", bus_name,
767         self->priv->delay_approvers ? 'T' : 'F');
768 
769     filters = tp_asv_get_boxed (properties, "ObserverChannelFilter",
770                                 TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST);
771 
772     if (filters != NULL)
773     {
774         DEBUG ("%s has %u ObserverChannelFilter entries", bus_name,
775                filters->len);
776         _mcd_client_proxy_set_filters (self, MCD_CLIENT_OBSERVER, filters);
777     }
778     else
779     {
780         DEBUG ("%s ObserverChannelFilter absent or wrong type, assuming "
781                "no channels can match", bus_name);
782     }
783 
784     /* if wrong type or absent, assuming False is reasonable */
785     recover = tp_asv_get_boolean (properties, "Recover", NULL);
786     self->priv->recover = recover;
787     DEBUG ("%s has Recover=%c", bus_name, recover ? 'T' : 'F');
788 
789 finally:
790     _mcd_client_proxy_dec_ready_lock (self);
791 }
792 
793 static void
_mcd_client_proxy_get_interfaces_cb(TpProxy * proxy,const GValue * out_Value,const GError * error,gpointer user_data G_GNUC_UNUSED,GObject * weak_object G_GNUC_UNUSED)794 _mcd_client_proxy_get_interfaces_cb (TpProxy *proxy,
795                                      const GValue *out_Value,
796                                      const GError *error,
797                                      gpointer user_data G_GNUC_UNUSED,
798                                      GObject *weak_object G_GNUC_UNUSED)
799 {
800     McdClientProxy *self = MCD_CLIENT_PROXY (proxy);
801     const gchar *bus_name = tp_proxy_get_bus_name (proxy);
802 
803     if (error != NULL)
804     {
805         DEBUG ("Error getting Interfaces for Client %s, assuming none: "
806                "%s %d %s", bus_name,
807                g_quark_to_string (error->domain), error->code, error->message);
808         goto finally;
809     }
810 
811     if (!G_VALUE_HOLDS (out_Value, G_TYPE_STRV))
812     {
813         DEBUG ("Wrong type getting Interfaces for Client %s, assuming none: "
814                "%s", bus_name, G_VALUE_TYPE_NAME (out_Value));
815         goto finally;
816     }
817 
818     _mcd_client_proxy_add_interfaces (self, g_value_get_boxed (out_Value));
819 
820     DEBUG ("Client %s", bus_name);
821 
822     if (tp_proxy_has_interface_by_id (proxy, TP_IFACE_QUARK_CLIENT_APPROVER))
823     {
824         _mcd_client_proxy_inc_ready_lock (self);
825 
826         DEBUG ("%s is an Approver", bus_name);
827 
828         tp_cli_dbus_properties_call_get
829             (self, -1, TP_IFACE_CLIENT_APPROVER,
830              "ApproverChannelFilter", _mcd_client_proxy_get_channel_filter_cb,
831              GUINT_TO_POINTER (MCD_CLIENT_APPROVER), NULL, NULL);
832     }
833 
834     if (tp_proxy_has_interface_by_id (proxy, TP_IFACE_QUARK_CLIENT_HANDLER))
835     {
836         _mcd_client_proxy_inc_ready_lock (self);
837 
838         DEBUG ("%s is a Handler", bus_name);
839 
840         tp_cli_dbus_properties_call_get_all
841             (self, -1, TP_IFACE_CLIENT_HANDLER,
842              _mcd_client_proxy_handler_get_all_cb, NULL, NULL, NULL);
843     }
844 
845     if (tp_proxy_has_interface_by_id (proxy, TP_IFACE_QUARK_CLIENT_OBSERVER))
846     {
847         _mcd_client_proxy_inc_ready_lock (self);
848 
849         DEBUG ("%s is an Observer", bus_name);
850 
851         tp_cli_dbus_properties_call_get_all
852             (self, -1, TP_IFACE_CLIENT_OBSERVER,
853              _mcd_client_proxy_observer_get_all_cb, NULL, NULL, NULL);
854     }
855 
856 finally:
857     _mcd_client_proxy_dec_ready_lock (self);
858 }
859 
860 static gboolean
_mcd_client_proxy_parse_client_file(McdClientProxy * self)861 _mcd_client_proxy_parse_client_file (McdClientProxy *self)
862 {
863     gboolean file_found = FALSE;
864     gchar *filename;
865     const gchar *bus_name = tp_proxy_get_bus_name (self);
866 
867     filename = _mcd_client_proxy_find_client_file (
868         bus_name + MC_CLIENT_BUS_NAME_BASE_LEN);
869 
870     if (filename)
871     {
872         GKeyFile *file;
873         GError *error = NULL;
874 
875         file = g_key_file_new ();
876         g_key_file_load_from_file (file, filename, 0, &error);
877         if (G_LIKELY (!error))
878         {
879             DEBUG ("File found for %s: %s", bus_name, filename);
880             parse_client_file (self, file);
881             file_found = TRUE;
882         }
883         else
884         {
885             g_warning ("Loading file %s failed: %s", filename, error->message);
886             g_error_free (error);
887         }
888         g_key_file_free (file);
889         g_free (filename);
890     }
891 
892     return file_found;
893 }
894 
895 static gboolean
mcd_client_proxy_introspect(gpointer data)896 mcd_client_proxy_introspect (gpointer data)
897 {
898     McdClientProxy *self = data;
899     const gchar *bus_name = tp_proxy_get_bus_name (self);
900 
901     if (self->priv->introspect_started)
902     {
903         return FALSE;
904     }
905 
906     self->priv->introspect_started = TRUE;
907 
908     /* The .client file is not mandatory as per the spec. However if it
909      * exists, it is better to read it than activating the service to read the
910      * D-Bus properties.
911      */
912     if (!_mcd_client_proxy_parse_client_file (self))
913     {
914         DEBUG ("No .client file for %s. Ask on D-Bus.", bus_name);
915 
916         _mcd_client_proxy_inc_ready_lock (self);
917 
918         tp_cli_dbus_properties_call_get (self, -1,
919             TP_IFACE_CLIENT, "Interfaces", _mcd_client_proxy_get_interfaces_cb,
920             NULL, NULL, NULL);
921     }
922     else
923     {
924         if (tp_proxy_has_interface_by_id (self, TP_IFACE_QUARK_CLIENT_HANDLER))
925         {
926             if (_mcd_client_proxy_is_active (self))
927             {
928                 DEBUG ("%s is an active, activatable Handler", bus_name);
929 
930                 /* We need to investigate whether it is handling any channels */
931 
932                 _mcd_client_proxy_inc_ready_lock (self);
933 
934                 tp_cli_dbus_properties_call_get_all (self, -1,
935                     TP_IFACE_CLIENT_HANDLER,
936                     _mcd_client_proxy_handler_get_all_cb,
937                     NULL, NULL, NULL);
938             }
939             else
940             {
941                 /* for us to have ever started introspecting, it must be
942                  * activatable */
943                 DEBUG ("%s is a Handler but not active", bus_name);
944 
945                 /* FIXME: we emit this even if the capabilities we got from the
946                  * .client file match those we already had, possibly causing
947                  * redundant UpdateCapabilities calls - however, those are
948                  * harmless */
949                 g_signal_emit (self,
950                                signals[S_HANDLER_CAPABILITIES_CHANGED], 0);
951             }
952         }
953     }
954 
955     _mcd_client_proxy_dec_ready_lock (self);
956     return FALSE;
957 }
958 
959 static void
mcd_client_proxy_unique_name_cb(TpDBusDaemon * dbus_daemon,const gchar * well_known_name G_GNUC_UNUSED,const gchar * unique_name,gpointer user_data)960 mcd_client_proxy_unique_name_cb (TpDBusDaemon *dbus_daemon,
961                                  const gchar *well_known_name G_GNUC_UNUSED,
962                                  const gchar *unique_name,
963                                  gpointer user_data)
964 {
965     McdClientProxy *self = MCD_CLIENT_PROXY (user_data);
966     gboolean should_recover = FALSE;
967 
968     g_object_ref (self);
969 
970     if (unique_name == NULL || unique_name[0] == '\0')
971     {
972         _mcd_client_proxy_set_inactive (self);
973 
974         /* To recover activatable Observers, we just need to call
975          * ObserveChannels on them. */
976         should_recover = self->priv->recover && self->priv->activatable;
977     }
978     else
979     {
980         _mcd_client_proxy_set_active (self, unique_name);
981     }
982 
983     mcd_client_proxy_introspect (self);
984 
985     if (should_recover)
986         g_signal_emit (self, signals[S_NEED_RECOVERY], 0);
987 
988     g_object_unref (self);
989 }
990 
991 static void
mcd_client_proxy_dispose(GObject * object)992 mcd_client_proxy_dispose (GObject *object)
993 {
994     McdClientProxy *self = MCD_CLIENT_PROXY (object);
995     void (*chain_up) (GObject *) =
996         ((GObjectClass *) _mcd_client_proxy_parent_class)->dispose;
997 
998     if (self->priv->disposed)
999         return;
1000 
1001     self->priv->disposed = TRUE;
1002 
1003     tp_dbus_daemon_cancel_name_owner_watch (tp_proxy_get_dbus_daemon (self),
1004                                             tp_proxy_get_bus_name (self),
1005                                             mcd_client_proxy_unique_name_cb,
1006                                             self);
1007 
1008     tp_clear_pointer (&self->priv->capability_tokens, g_strfreev);
1009 
1010     if (chain_up != NULL)
1011     {
1012         chain_up (object);
1013     }
1014 }
1015 
1016 static void
mcd_client_proxy_finalize(GObject * object)1017 mcd_client_proxy_finalize (GObject *object)
1018 {
1019     McdClientProxy *self = MCD_CLIENT_PROXY (object);
1020     void (*chain_up) (GObject *) =
1021         ((GObjectClass *) _mcd_client_proxy_parent_class)->finalize;
1022 
1023     g_free (self->priv->unique_name);
1024 
1025     _mcd_client_proxy_take_approver_filters (self, NULL);
1026     _mcd_client_proxy_take_observer_filters (self, NULL);
1027     _mcd_client_proxy_take_handler_filters (self, NULL);
1028 
1029     if (chain_up != NULL)
1030     {
1031         chain_up (object);
1032     }
1033 }
1034 
1035 static void
mcd_client_proxy_constructed(GObject * object)1036 mcd_client_proxy_constructed (GObject *object)
1037 {
1038     McdClientProxy *self = MCD_CLIENT_PROXY (object);
1039     void (*chain_up) (GObject *) =
1040         ((GObjectClass *) _mcd_client_proxy_parent_class)->constructed;
1041     const gchar *bus_name;
1042 
1043     if (chain_up != NULL)
1044     {
1045         chain_up (object);
1046     }
1047 
1048     bus_name = tp_proxy_get_bus_name (self);
1049 
1050     self->priv->capability_tokens = NULL;
1051 
1052     DEBUG ("%s", bus_name);
1053 
1054     tp_dbus_daemon_watch_name_owner (tp_proxy_get_dbus_daemon (self),
1055                                      bus_name,
1056                                      mcd_client_proxy_unique_name_cb,
1057                                      self, NULL);
1058 
1059     if (self->priv->unique_name != NULL)
1060     {
1061         /* we already know who we are, so we can skip straight to the
1062          * introspection. It's safe to call mcd_client_proxy_introspect
1063          * any number of times, so we don't need to guard against
1064          * duplication */
1065         g_idle_add_full (G_PRIORITY_HIGH, mcd_client_proxy_introspect,
1066                          g_object_ref (self), g_object_unref);
1067     }
1068 }
1069 
1070 static void
mcd_client_proxy_set_property(GObject * object,guint property,const GValue * value,GParamSpec * param_spec)1071 mcd_client_proxy_set_property (GObject *object,
1072                                guint property,
1073                                const GValue *value,
1074                                GParamSpec *param_spec)
1075 {
1076     McdClientProxy *self = MCD_CLIENT_PROXY (object);
1077 
1078     switch (property)
1079     {
1080         case PROP_ACTIVATABLE:
1081             self->priv->activatable = g_value_get_boolean (value);
1082             break;
1083 
1084         case PROP_UNIQUE_NAME:
1085             g_assert (self->priv->unique_name == NULL);
1086             self->priv->unique_name = g_value_dup_string (value);
1087             break;
1088 
1089         default:
1090             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property, param_spec);
1091     }
1092 }
1093 
1094 static void
_mcd_client_proxy_class_init(McdClientProxyClass * klass)1095 _mcd_client_proxy_class_init (McdClientProxyClass *klass)
1096 {
1097     GObjectClass *object_class = G_OBJECT_CLASS (klass);
1098 
1099     g_type_class_add_private (object_class, sizeof (McdClientProxyPrivate));
1100 
1101     object_class->constructed = mcd_client_proxy_constructed;
1102     object_class->dispose = mcd_client_proxy_dispose;
1103     object_class->finalize = mcd_client_proxy_finalize;
1104     object_class->set_property = mcd_client_proxy_set_property;
1105 
1106     signals[S_READY] = g_signal_new ("ready",
1107         G_OBJECT_CLASS_TYPE (klass),
1108         G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1109         0, NULL, NULL,
1110         g_cclosure_marshal_VOID__VOID,
1111         G_TYPE_NONE, 0);
1112 
1113     signals[S_GONE] = g_signal_new ("gone",
1114         G_OBJECT_CLASS_TYPE (klass),
1115         G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1116         0, NULL, NULL,
1117         g_cclosure_marshal_VOID__VOID,
1118         G_TYPE_NONE, 0);
1119 
1120     /* Never emitted until after the unique name is known */
1121     signals[S_IS_HANDLING_CHANNEL] = g_signal_new ("is-handling-channel",
1122         G_OBJECT_CLASS_TYPE (klass),
1123         G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1124         0, NULL, NULL,
1125         g_cclosure_marshal_VOID__STRING,
1126         G_TYPE_NONE, 1, G_TYPE_STRING);
1127 
1128     /* Never emitted until after the unique name is known */
1129     signals[S_HANDLER_CAPABILITIES_CHANGED] = g_signal_new (
1130         "handler-capabilities-changed",
1131         G_OBJECT_CLASS_TYPE (klass),
1132         G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1133         0, NULL, NULL,
1134         g_cclosure_marshal_VOID__VOID,
1135         G_TYPE_NONE, 0);
1136 
1137     signals[S_NEED_RECOVERY] = g_signal_new ("need-recovery",
1138         G_OBJECT_CLASS_TYPE (klass),
1139         G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1140         0, NULL, NULL,
1141         g_cclosure_marshal_VOID__VOID,
1142         G_TYPE_NONE, 0);
1143 
1144     g_object_class_install_property (object_class, PROP_ACTIVATABLE,
1145         g_param_spec_boolean ("activatable", "Activatable?",
1146             "TRUE if this client can be service-activated", FALSE,
1147             G_PARAM_WRITABLE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
1148 
1149     g_object_class_install_property (object_class, PROP_UNIQUE_NAME,
1150         g_param_spec_string ("unique-name", "Unique name",
1151             "The D-Bus unique name of this client, \"\" if not running or "
1152             "NULL if unknown",
1153             NULL,
1154             G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
1155             G_PARAM_STATIC_STRINGS));
1156 
1157 }
1158 
1159 gboolean
_mcd_client_check_valid_name(const gchar * name_suffix,GError ** error)1160 _mcd_client_check_valid_name (const gchar *name_suffix,
1161                               GError **error)
1162 {
1163     guint i;
1164 
1165     if (!g_ascii_isalpha (*name_suffix))
1166     {
1167         g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1168                      "Client names must start with a letter");
1169         return FALSE;
1170     }
1171 
1172     for (i = 1; name_suffix[i] != '\0'; i++)
1173     {
1174         if (i > (255 - MC_CLIENT_BUS_NAME_BASE_LEN))
1175         {
1176             g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1177                          "Client name too long");
1178         }
1179 
1180         if (name_suffix[i] == '_' || g_ascii_isalpha (name_suffix[i]))
1181         {
1182             continue;
1183         }
1184 
1185         if (name_suffix[i] == '.' || g_ascii_isdigit (name_suffix[i]))
1186         {
1187             if (name_suffix[i-1] == '.')
1188             {
1189                 g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1190                              "Client names must not have a digit or dot "
1191                              "following a dot");
1192                 return FALSE;
1193             }
1194         }
1195         else
1196         {
1197             g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1198                          "Client names must not contain '%c'", name_suffix[i]);
1199             return FALSE;
1200         }
1201     }
1202 
1203     if (name_suffix[i-1] == '.')
1204     {
1205         g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1206                      "Client names must not end with a dot");
1207         return FALSE;
1208     }
1209 
1210     return TRUE;
1211 }
1212 
1213 McdClientProxy *
_mcd_client_proxy_new(TpDBusDaemon * dbus_daemon,const gchar * well_known_name,const gchar * unique_name_if_known,gboolean activatable)1214 _mcd_client_proxy_new (TpDBusDaemon *dbus_daemon,
1215                        const gchar *well_known_name,
1216                        const gchar *unique_name_if_known,
1217                        gboolean activatable)
1218 {
1219     McdClientProxy *self;
1220     const gchar *name_suffix;
1221     gchar *object_path;
1222 
1223     g_return_val_if_fail (g_str_has_prefix (well_known_name,
1224                                             TP_CLIENT_BUS_NAME_BASE), NULL);
1225     name_suffix = well_known_name + MC_CLIENT_BUS_NAME_BASE_LEN;
1226     g_return_val_if_fail (_mcd_client_check_valid_name (name_suffix, NULL),
1227                           NULL);
1228 
1229     object_path = g_strconcat ("/", well_known_name, NULL);
1230     g_strdelimit (object_path, ".", '/');
1231 
1232     g_assert (tp_dbus_check_valid_bus_name (well_known_name,
1233                                             TP_DBUS_NAME_TYPE_WELL_KNOWN,
1234                                             NULL));
1235     g_assert (tp_dbus_check_valid_object_path (object_path, NULL));
1236 
1237     self = g_object_new (MCD_TYPE_CLIENT_PROXY,
1238                          "dbus-daemon", dbus_daemon,
1239                          "object-path", object_path,
1240                          "bus-name", well_known_name,
1241                          "unique-name", unique_name_if_known,
1242                          "activatable", activatable,
1243                          NULL);
1244 
1245     g_free (object_path);
1246 
1247     return self;
1248 }
1249 
1250 static void _mcd_client_proxy_become_incapable (McdClientProxy *self);
1251 
1252 void
_mcd_client_proxy_set_inactive(McdClientProxy * self)1253 _mcd_client_proxy_set_inactive (McdClientProxy *self)
1254 {
1255     g_return_if_fail (MCD_IS_CLIENT_PROXY (self));
1256 
1257     /* if unique name is already "" (i.e. known to be inactive), do nothing */
1258     if (self->priv->unique_name != NULL && self->priv->unique_name[0] == '\0')
1259     {
1260         return;
1261     }
1262 
1263     g_free (self->priv->unique_name);
1264     self->priv->unique_name = g_strdup ("");
1265 
1266     if (!self->priv->activatable)
1267     {
1268         /* in ContactCapabilities we indicate the disappearance
1269          * of a client by giving it an empty set of capabilities and
1270          * filters */
1271         _mcd_client_proxy_become_incapable (self);
1272 
1273         g_signal_emit (self, signals[S_GONE], 0);
1274     }
1275 }
1276 
1277 void
_mcd_client_proxy_set_active(McdClientProxy * self,const gchar * unique_name)1278 _mcd_client_proxy_set_active (McdClientProxy *self,
1279                               const gchar *unique_name)
1280 {
1281     g_return_if_fail (MCD_IS_CLIENT_PROXY (self));
1282     g_return_if_fail (unique_name != NULL);
1283 
1284     g_free (self->priv->unique_name);
1285     self->priv->unique_name = g_strdup (unique_name);
1286 }
1287 
1288 void
_mcd_client_proxy_set_activatable(McdClientProxy * self)1289 _mcd_client_proxy_set_activatable (McdClientProxy *self)
1290 {
1291     g_return_if_fail (MCD_IS_CLIENT_PROXY (self));
1292 
1293     self->priv->activatable = TRUE;
1294 }
1295 
1296 const GList *
_mcd_client_proxy_get_approver_filters(McdClientProxy * self)1297 _mcd_client_proxy_get_approver_filters (McdClientProxy *self)
1298 {
1299     g_return_val_if_fail (MCD_IS_CLIENT_PROXY (self), NULL);
1300 
1301     return self->priv->approver_filters;
1302 }
1303 
1304 const GList *
_mcd_client_proxy_get_observer_filters(McdClientProxy * self)1305 _mcd_client_proxy_get_observer_filters (McdClientProxy *self)
1306 {
1307     g_return_val_if_fail (MCD_IS_CLIENT_PROXY (self), NULL);
1308 
1309     return self->priv->observer_filters;
1310 }
1311 
1312 const GList *
_mcd_client_proxy_get_handler_filters(McdClientProxy * self)1313 _mcd_client_proxy_get_handler_filters (McdClientProxy *self)
1314 {
1315     g_return_val_if_fail (MCD_IS_CLIENT_PROXY (self), NULL);
1316 
1317     return self->priv->handler_filters;
1318 }
1319 
1320 static void
mcd_client_proxy_free_client_filters(GList ** client_filters)1321 mcd_client_proxy_free_client_filters (GList **client_filters)
1322 {
1323     g_assert (client_filters != NULL);
1324 
1325     if (*client_filters != NULL)
1326     {
1327         g_list_foreach (*client_filters, (GFunc) g_hash_table_unref, NULL);
1328         g_list_free (*client_filters);
1329         *client_filters = NULL;
1330     }
1331 }
1332 
1333 void
_mcd_client_proxy_take_approver_filters(McdClientProxy * self,GList * filters)1334 _mcd_client_proxy_take_approver_filters (McdClientProxy *self,
1335                                          GList *filters)
1336 {
1337     g_return_if_fail (MCD_IS_CLIENT_PROXY (self));
1338 
1339     mcd_client_proxy_free_client_filters (&(self->priv->approver_filters));
1340     self->priv->approver_filters = filters;
1341 }
1342 
1343 void
_mcd_client_proxy_take_observer_filters(McdClientProxy * self,GList * filters)1344 _mcd_client_proxy_take_observer_filters (McdClientProxy *self,
1345                                          GList *filters)
1346 {
1347     g_return_if_fail (MCD_IS_CLIENT_PROXY (self));
1348 
1349     mcd_client_proxy_free_client_filters (&(self->priv->observer_filters));
1350     self->priv->observer_filters = filters;
1351 }
1352 
1353 void
_mcd_client_proxy_take_handler_filters(McdClientProxy * self,GList * filters)1354 _mcd_client_proxy_take_handler_filters (McdClientProxy *self,
1355                                         GList *filters)
1356 {
1357     g_return_if_fail (MCD_IS_CLIENT_PROXY (self));
1358 
1359     mcd_client_proxy_free_client_filters (&(self->priv->handler_filters));
1360     self->priv->handler_filters = filters;
1361 }
1362 
1363 gboolean
_mcd_client_proxy_get_bypass_approval(McdClientProxy * self)1364 _mcd_client_proxy_get_bypass_approval (McdClientProxy *self)
1365 {
1366     g_return_val_if_fail (MCD_IS_CLIENT_PROXY (self), FALSE);
1367 
1368     return self->priv->bypass_approval;
1369 }
1370 
1371 gboolean
_mcd_client_proxy_get_bypass_observers(McdClientProxy * self)1372 _mcd_client_proxy_get_bypass_observers (McdClientProxy *self)
1373 {
1374     g_return_val_if_fail (MCD_IS_CLIENT_PROXY (self), FALSE);
1375 
1376     return self->priv->bypass_observers;
1377 }
1378 
1379 gboolean
_mcd_client_proxy_get_delay_approvers(McdClientProxy * self)1380 _mcd_client_proxy_get_delay_approvers (McdClientProxy *self)
1381 {
1382     g_return_val_if_fail (MCD_IS_CLIENT_PROXY (self), FALSE);
1383 
1384     return self->priv->delay_approvers;
1385 }
1386 
1387 static void
_mcd_client_proxy_become_incapable(McdClientProxy * self)1388 _mcd_client_proxy_become_incapable (McdClientProxy *self)
1389 {
1390     gboolean handler_was_capable = (self->priv->handler_filters != NULL);
1391 
1392     if (self->priv->capability_tokens != NULL &&
1393         self->priv->capability_tokens[0] != NULL)
1394     {
1395         handler_was_capable = TRUE;
1396     }
1397 
1398     _mcd_client_proxy_take_approver_filters (self, NULL);
1399     _mcd_client_proxy_take_observer_filters (self, NULL);
1400     _mcd_client_proxy_take_handler_filters (self, NULL);
1401     tp_clear_pointer (&self->priv->capability_tokens, g_strfreev);
1402 
1403     if (handler_was_capable)
1404     {
1405         g_signal_emit (self, signals[S_HANDLER_CAPABILITIES_CHANGED], 0);
1406     }
1407 }
1408 
1409 GValueArray *
_mcd_client_proxy_dup_handler_capabilities(McdClientProxy * self)1410 _mcd_client_proxy_dup_handler_capabilities (McdClientProxy *self)
1411 {
1412     GPtrArray *filters;
1413     GStrv cap_tokens;
1414     GValueArray *va;
1415     const GList *list;
1416     gchar *empty_strv[] = { NULL };
1417 
1418     g_return_val_if_fail (MCD_IS_CLIENT_PROXY (self), NULL);
1419 
1420     filters = g_ptr_array_sized_new (
1421         g_list_length (self->priv->handler_filters));
1422 
1423     for (list = self->priv->handler_filters; list != NULL; list = list->next)
1424     {
1425         GHashTable *copy = g_hash_table_new_full (g_str_hash, g_str_equal,
1426             g_free, (GDestroyNotify) tp_g_value_slice_free);
1427 
1428         tp_g_hash_table_update (copy, list->data,
1429                                 (GBoxedCopyFunc) g_strdup,
1430                                 (GBoxedCopyFunc) tp_g_value_slice_dup);
1431         g_ptr_array_add (filters, copy);
1432     }
1433 
1434     cap_tokens = self->priv->capability_tokens;
1435 
1436     if (cap_tokens == NULL)
1437         cap_tokens = empty_strv;
1438 
1439     if (DEBUGGING)
1440     {
1441         guint i;
1442 
1443         DEBUG ("%s:", tp_proxy_get_bus_name (self));
1444 
1445         DEBUG ("- %u channel filters", filters->len);
1446         DEBUG ("- %u capability tokens:", g_strv_length (cap_tokens));
1447 
1448         for (i = 0; cap_tokens[i] != NULL; i++)
1449         {
1450             DEBUG ("    %s", cap_tokens[i]);
1451         }
1452 
1453         DEBUG ("-end-");
1454     }
1455 
1456     va = g_value_array_new (3);
1457     g_value_array_append (va, NULL);
1458     g_value_array_append (va, NULL);
1459     g_value_array_append (va, NULL);
1460 
1461     g_value_init (va->values + 0, G_TYPE_STRING);
1462     g_value_init (va->values + 1, TP_ARRAY_TYPE_CHANNEL_CLASS_LIST);
1463     g_value_init (va->values + 2, G_TYPE_STRV);
1464 
1465     g_value_set_string (va->values + 0, tp_proxy_get_bus_name (self));
1466     g_value_take_boxed (va->values + 1, filters);
1467     g_value_set_boxed (va->values + 2, cap_tokens);
1468 
1469     return va;
1470 }
1471 
1472 /* returns TRUE if the channel matches one property criteria
1473  */
1474 static gboolean
_mcd_client_match_property(GVariant * channel_properties,gchar * property_name,GValue * filter_value)1475 _mcd_client_match_property (GVariant *channel_properties,
1476                             gchar *property_name,
1477                             GValue *filter_value)
1478 {
1479     GType filter_type = G_VALUE_TYPE (filter_value);
1480 
1481     g_return_val_if_fail (g_variant_is_of_type (channel_properties,
1482             G_VARIANT_TYPE_VARDICT), FALSE);
1483 
1484     g_assert (G_IS_VALUE (filter_value));
1485 
1486     if (filter_type == G_TYPE_STRING)
1487     {
1488         const gchar *string;
1489 
1490         string = tp_vardict_get_string (channel_properties, property_name);
1491         if (!string)
1492             return FALSE;
1493 
1494         return !tp_strdiff (string, g_value_get_string (filter_value));
1495     }
1496 
1497     if (filter_type == DBUS_TYPE_G_OBJECT_PATH)
1498     {
1499         const gchar *path;
1500 
1501         path = tp_vardict_get_object_path (channel_properties, property_name);
1502         if (!path)
1503             return FALSE;
1504 
1505         return !tp_strdiff (path, g_value_get_boxed (filter_value));
1506     }
1507 
1508     if (filter_type == G_TYPE_BOOLEAN)
1509     {
1510         gboolean valid;
1511         gboolean b;
1512 
1513         b = tp_vardict_get_boolean (channel_properties, property_name, &valid);
1514         if (!valid)
1515             return FALSE;
1516 
1517         return !!b == !!g_value_get_boolean (filter_value);
1518     }
1519 
1520     if (filter_type == G_TYPE_UCHAR || filter_type == G_TYPE_UINT ||
1521         filter_type == G_TYPE_UINT64)
1522     {
1523         gboolean valid;
1524         guint64 i;
1525 
1526         i = tp_vardict_get_uint64 (channel_properties, property_name, &valid);
1527         if (!valid)
1528             return FALSE;
1529 
1530         if (filter_type == G_TYPE_UCHAR)
1531             return i == g_value_get_uchar (filter_value);
1532         else if (filter_type == G_TYPE_UINT)
1533             return i == g_value_get_uint (filter_value);
1534         else
1535             return i == g_value_get_uint64 (filter_value);
1536     }
1537 
1538     if (filter_type == G_TYPE_INT || filter_type == G_TYPE_INT64)
1539     {
1540         gboolean valid;
1541         gint64 i;
1542 
1543         i = tp_vardict_get_int64 (channel_properties, property_name, &valid);
1544         if (!valid)
1545             return FALSE;
1546 
1547         if (filter_type == G_TYPE_INT)
1548             return i == g_value_get_int (filter_value);
1549         else
1550             return i == g_value_get_int64 (filter_value);
1551     }
1552 
1553     g_warning ("%s: Invalid type: %s",
1554                G_STRFUNC, g_type_name (filter_type));
1555     return FALSE;
1556 }
1557 
1558 /* if the channel matches one of the channel filters, returns a positive
1559  * number that increases with more specific matches; otherwise, returns 0
1560  *
1561  * (implementation detail: the positive number is 1 + the number of keys in the
1562  * largest filter that matched)
1563  */
1564 guint
_mcd_client_match_filters(GVariant * channel_properties,const GList * filters,gboolean assume_requested)1565 _mcd_client_match_filters (GVariant *channel_properties,
1566                            const GList *filters,
1567                            gboolean assume_requested)
1568 {
1569     const GList *list;
1570     guint best_quality = 0;
1571 
1572     g_return_val_if_fail (g_variant_is_of_type (channel_properties,
1573             G_VARIANT_TYPE_VARDICT), 0);
1574 
1575     for (list = filters; list != NULL; list = list->next)
1576     {
1577         GHashTable *filter = list->data;
1578         GHashTableIter filter_iter;
1579         gboolean filter_matched = TRUE;
1580         gchar *property_name;
1581         GValue *filter_value;
1582         guint quality;
1583 
1584         /* +1 because the empty hash table matches everything :-) */
1585         quality = g_hash_table_size (filter) + 1;
1586 
1587         if (quality <= best_quality)
1588         {
1589             /* even if this filter matches, there's no way it can be a
1590              * better-quality match than the best one we saw so far */
1591             continue;
1592         }
1593 
1594         g_hash_table_iter_init (&filter_iter, filter);
1595         while (g_hash_table_iter_next (&filter_iter,
1596                                        (gpointer *) &property_name,
1597                                        (gpointer *) &filter_value))
1598         {
1599             if (assume_requested &&
1600                 ! tp_strdiff (property_name, TP_IFACE_CHANNEL ".Requested"))
1601             {
1602                 if (! G_VALUE_HOLDS_BOOLEAN (filter_value) ||
1603                     ! g_value_get_boolean (filter_value))
1604                 {
1605                     filter_matched = FALSE;
1606                     break;
1607                 }
1608             }
1609             else if (! _mcd_client_match_property (channel_properties,
1610                                                    property_name,
1611                                                    filter_value))
1612             {
1613                 filter_matched = FALSE;
1614                 break;
1615             }
1616         }
1617 
1618         if (filter_matched)
1619         {
1620             best_quality = quality;
1621         }
1622     }
1623 
1624     return best_quality;
1625 }
1626 
1627 static const gchar *
borrow_channel_account_path(McdChannel * channel)1628 borrow_channel_account_path (McdChannel *channel)
1629 {
1630     McdAccount *account;
1631     const gchar *account_path;
1632 
1633     account = mcd_channel_get_account (channel);
1634     account_path = account == NULL ? "/"
1635         : mcd_account_get_object_path (account);
1636 
1637     if (G_UNLIKELY (account_path == NULL))    /* can't happen? */
1638         account_path = "/";
1639 
1640     return account_path;
1641 }
1642 
1643 static const gchar *
borrow_channel_connection_path(McdChannel * channel)1644 borrow_channel_connection_path (McdChannel *channel)
1645 {
1646     TpChannel *tp_channel;
1647     TpConnection *tp_connection;
1648     const gchar *connection_path;
1649 
1650     tp_channel = mcd_channel_get_tp_channel (channel);
1651     g_return_val_if_fail (tp_channel != NULL, "/");
1652     tp_connection = tp_channel_get_connection (tp_channel);
1653     g_return_val_if_fail (tp_connection != NULL, "/");
1654     connection_path = tp_proxy_get_object_path (tp_connection);
1655     g_return_val_if_fail (connection_path != NULL, "/");
1656     return connection_path;
1657 }
1658 
1659 void
_mcd_client_proxy_handle_channels(McdClientProxy * self,gint timeout_ms,const GList * channels,gint64 user_action_time,GHashTable * handler_info,tp_cli_client_handler_callback_for_handle_channels callback,gpointer user_data,GDestroyNotify destroy,GObject * weak_object)1660 _mcd_client_proxy_handle_channels (McdClientProxy *self,
1661     gint timeout_ms,
1662     const GList *channels,
1663     gint64 user_action_time,
1664     GHashTable *handler_info,
1665     tp_cli_client_handler_callback_for_handle_channels callback,
1666     gpointer user_data,
1667     GDestroyNotify destroy,
1668     GObject *weak_object)
1669 {
1670     GPtrArray *channel_details;
1671     GPtrArray *requests_satisfied;
1672     const GList *iter;
1673 
1674     g_return_if_fail (MCD_IS_CLIENT_PROXY (self));
1675     g_return_if_fail (channels != NULL);
1676 
1677     DEBUG ("calling HandleChannels on %s", tp_proxy_get_bus_name (self));
1678 
1679     channel_details = _mcd_tp_channel_details_build_from_list (channels);
1680     requests_satisfied = g_ptr_array_new_with_free_func (g_free);
1681 
1682     if (handler_info == NULL)
1683     {
1684         handler_info = g_hash_table_new (g_str_hash, g_str_equal);
1685     }
1686     else
1687     {
1688         g_hash_table_ref (handler_info);
1689     }
1690 
1691     for (iter = channels; iter != NULL; iter = iter->next)
1692     {
1693         gint64 req_time = 0;
1694         GHashTable *requests;
1695         GHashTableIter it;
1696         gpointer path;
1697 
1698         requests = _mcd_channel_get_satisfied_requests (iter->data,
1699                                                              &req_time);
1700 
1701         g_hash_table_iter_init (&it, requests);
1702         while (g_hash_table_iter_next (&it, &path, NULL))
1703         {
1704             g_ptr_array_add (requests_satisfied, g_strdup (path));
1705         }
1706 
1707         g_hash_table_unref (requests);
1708 
1709         /* Numerical order is correct for all currently supported values:
1710          *
1711          * (TP_USER_ACTION_TIME_NOT_USER_ACTION == 0) is less than
1712          * (normal X11 timestamps, which are 1 to G_MAXUINT32) are less than
1713          * (TP_USER_ACTION_TIME_CURRENT_TIME == G_MAXINT64) */
1714         if (req_time > user_action_time)
1715             user_action_time = req_time;
1716 
1717         _mcd_channel_set_status (iter->data,
1718                                  MCD_CHANNEL_STATUS_HANDLER_INVOKED);
1719     }
1720 
1721     tp_cli_client_handler_call_handle_channels ((TpClient *) self,
1722         timeout_ms, borrow_channel_account_path (channels->data),
1723         borrow_channel_connection_path (channels->data), channel_details,
1724         requests_satisfied, user_action_time, handler_info,
1725         callback, user_data, destroy, weak_object);
1726 
1727     _mcd_tp_channel_details_free (channel_details);
1728     g_ptr_array_unref (requests_satisfied);
1729     g_hash_table_unref (handler_info);
1730 }
1731