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