1 /* Map containing registered Telepathy clients
2  *
3  * Copyright © 2007-2011 Nokia Corporation.
4  * Copyright © 2009-2011 Collabora Ltd.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public License
8  * version 2.1 as published by the Free Software Foundation.
9  *
10  * This library is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18  * 02110-1301 USA
19  *
20  */
21 
22 #include "config.h"
23 
24 #include "client-registry.h"
25 
26 #include <telepathy-glib/telepathy-glib.h>
27 
28 #include "mcd-debug.h"
29 
30 #include <dbus/dbus.h>
31 #include <dbus/dbus-glib.h>
32 #include <dbus/dbus-glib-lowlevel.h>
33 
34 G_DEFINE_TYPE (McdClientRegistry, _mcd_client_registry, G_TYPE_OBJECT)
35 
36 enum
37 {
38   PROP_0,
39   PROP_DBUS_DAEMON
40 };
41 
42 enum
43 {
44     S_CLIENT_ADDED,
45     S_READY,
46     N_SIGNALS
47 };
48 
49 static guint signals[N_SIGNALS] = { 0 };
50 
51 struct _McdClientRegistryPrivate
52 {
53   /* hash table containing clients
54    * owned gchar * well_known_name -> owned McdClientProxy */
55   GHashTable *clients;
56 
57   TpDBusDaemon *dbus_daemon;
58 
59   /* We don't want to start dispatching until startup has finished. This
60    * is defined as:
61    * - activatable clients have been enumerated (ListActivatableNames)
62    *   (1 lock)
63    * - running clients have been enumerated (ListNames) (1 lock)
64    * - each client found that way is ready (1 lock per client)
65    * When nothing more is stopping us from dispatching channels, we signal
66    * ready.
67    * */
68   gsize startup_lock;
69   gboolean startup_completed;
70 };
71 
72 static void
_mcd_client_registry_inc_startup_lock(McdClientRegistry * self)73 _mcd_client_registry_inc_startup_lock (McdClientRegistry *self)
74 {
75   if (!self->priv->startup_completed)
76     {
77       DEBUG ("%" G_GSIZE_FORMAT " -> %" G_GSIZE_FORMAT,
78           self->priv->startup_lock, self->priv->startup_lock + 1);
79       g_return_if_fail (self->priv->startup_lock > 0);
80       self->priv->startup_lock++;
81     }
82 }
83 
84 static void
_mcd_client_registry_dec_startup_lock(McdClientRegistry * self)85 _mcd_client_registry_dec_startup_lock (McdClientRegistry *self)
86 {
87   if (self->priv->startup_completed)
88     return;
89 
90   DEBUG ("%" G_GSIZE_FORMAT " -> %" G_GSIZE_FORMAT,
91       self->priv->startup_lock, self->priv->startup_lock - 1);
92 
93   g_return_if_fail (self->priv->startup_lock > 0);
94 
95   self->priv->startup_lock--;
96 
97   if (self->priv->startup_lock == 0)
98     {
99       self->priv->startup_completed = TRUE;
100       g_signal_emit (self, signals[S_READY], 0);
101     }
102 }
103 
104 static void mcd_client_registry_ready_cb (McdClientProxy *client,
105     McdClientRegistry *self);
106 static void mcd_client_registry_gone_cb (McdClientProxy *client,
107     McdClientRegistry *self);
108 
109 static void
_mcd_client_registry_found_name(McdClientRegistry * self,const gchar * well_known_name,const gchar * unique_name_if_known,gboolean activatable)110 _mcd_client_registry_found_name (McdClientRegistry *self,
111     const gchar *well_known_name,
112     const gchar *unique_name_if_known,
113     gboolean activatable)
114 {
115   McdClientProxy *client;
116 
117   if (!g_str_has_prefix (well_known_name, TP_CLIENT_BUS_NAME_BASE))
118     {
119       /* This is not a Telepathy Client */
120       return;
121     }
122 
123   if (!_mcd_client_check_valid_name (
124         well_known_name + MC_CLIENT_BUS_NAME_BASE_LEN, NULL))
125     {
126       /* This is probably meant to be a Telepathy Client, but it's not */
127       DEBUG ("Ignoring invalid Client name: %s",
128           well_known_name + MC_CLIENT_BUS_NAME_BASE_LEN);
129       return;
130     }
131 
132   client = g_hash_table_lookup (self->priv->clients, well_known_name);
133 
134   if (client != NULL)
135     {
136       if (activatable)
137         {
138           /* We already knew that it was active, but now we also know that
139            * it is activatable */
140           _mcd_client_proxy_set_activatable (client);
141         }
142       else if (unique_name_if_known != NULL)
143         {
144           /* We already knew that it was activatable, but now we also know
145            * that it is active */
146           _mcd_client_proxy_set_active (client, unique_name_if_known);
147         }
148 
149       return;
150     }
151 
152   DEBUG ("Registering client %s", well_known_name);
153 
154   client = _mcd_client_proxy_new (self->priv->dbus_daemon,
155       well_known_name, unique_name_if_known, activatable);
156   g_hash_table_insert (self->priv->clients, g_strdup (well_known_name),
157       client);
158 
159   /* paired with one in mcd_client_registry_ready_cb, when the
160    * McdClientProxy is ready */
161   _mcd_client_registry_inc_startup_lock (self);
162 
163   g_signal_connect (client, "ready",
164                     G_CALLBACK (mcd_client_registry_ready_cb),
165                     self);
166 
167   g_signal_connect (client, "gone",
168                     G_CALLBACK (mcd_client_registry_gone_cb),
169                     self);
170 
171   g_signal_emit (self, signals[S_CLIENT_ADDED], 0, client);
172 }
173 
174 McdClientProxy *
_mcd_client_registry_lookup(McdClientRegistry * self,const gchar * well_known_name)175 _mcd_client_registry_lookup (McdClientRegistry *self,
176     const gchar *well_known_name)
177 {
178   g_return_val_if_fail (MCD_IS_CLIENT_REGISTRY (self), NULL);
179   return g_hash_table_lookup (self->priv->clients, well_known_name);
180 }
181 
182 static void
mcd_client_registry_disconnect_client_signals(gpointer k G_GNUC_UNUSED,gpointer v,gpointer data)183 mcd_client_registry_disconnect_client_signals (gpointer k G_GNUC_UNUSED,
184     gpointer v,
185     gpointer data)
186 {
187   g_signal_handlers_disconnect_by_func (v, mcd_client_registry_ready_cb, data);
188   g_signal_handlers_disconnect_by_func (v, mcd_client_registry_gone_cb, data);
189 
190   if (!_mcd_client_proxy_is_ready (v))
191     {
192       /* we'll never receive the ready signal now, so release the lock that
193        * it would otherwise have released */
194       DEBUG ("client %s disappeared before it became ready - treating it "
195              "as ready for our purposes", tp_proxy_get_bus_name (v));
196       mcd_client_registry_ready_cb (v, data);
197     }
198 }
199 
200 static void
_mcd_client_registry_remove(McdClientRegistry * self,const gchar * well_known_name)201 _mcd_client_registry_remove (McdClientRegistry *self,
202     const gchar *well_known_name)
203 {
204   McdClientProxy *client;
205 
206   client = g_hash_table_lookup (self->priv->clients, well_known_name);
207 
208   if (client != NULL)
209     {
210       mcd_client_registry_disconnect_client_signals (NULL,
211           client, self);
212     }
213 
214   g_hash_table_remove (self->priv->clients, well_known_name);
215 }
216 
_mcd_client_registry_init_hash_iter(McdClientRegistry * self,GHashTableIter * iter)217 void _mcd_client_registry_init_hash_iter (McdClientRegistry *self,
218     GHashTableIter *iter)
219 {
220   g_return_if_fail (MCD_IS_CLIENT_REGISTRY (self));
221   g_hash_table_iter_init (iter, self->priv->clients);
222 }
223 
224 static void
_mcd_client_registry_init(McdClientRegistry * self)225 _mcd_client_registry_init (McdClientRegistry *self)
226 {
227   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MCD_TYPE_CLIENT_REGISTRY,
228       McdClientRegistryPrivate);
229 
230   self->priv->startup_completed = FALSE;
231   /* the ListNames call we'll make in _constructed is the initial lock */
232   self->priv->startup_lock = 1;
233   self->priv->clients = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
234       g_object_unref);
235 }
236 
237 static void
mcd_client_registry_list_activatable_names_cb(TpDBusDaemon * proxy,const gchar ** names,const GError * error,gpointer user_data,GObject * weak_object)238 mcd_client_registry_list_activatable_names_cb (TpDBusDaemon *proxy,
239     const gchar **names,
240     const GError *error,
241     gpointer user_data,
242     GObject *weak_object)
243 {
244   McdClientRegistry *self = MCD_CLIENT_REGISTRY (weak_object);
245 
246   if (error != NULL)
247     {
248       DEBUG ("ListActivatableNames returned error, assuming none: %s %d: %s",
249           g_quark_to_string (error->domain), error->code, error->message);
250     }
251   else if (names != NULL)
252     {
253       const gchar **iter = names;
254 
255       DEBUG ("ListActivatableNames returned");
256 
257       while (*iter != NULL)
258         {
259           _mcd_client_registry_found_name (self, *iter, NULL, TRUE);
260           iter++;
261         }
262     }
263 
264   /* paired with the lock taken when the McdClientRegistry was constructed */
265   _mcd_client_registry_dec_startup_lock (self);
266 }
267 
268 static void
mcd_client_registry_list_names_cb(TpDBusDaemon * proxy,const gchar ** names,const GError * error,gpointer user_data,GObject * weak_object)269 mcd_client_registry_list_names_cb (TpDBusDaemon *proxy,
270     const gchar **names,
271     const GError *error,
272     gpointer user_data,
273     GObject *weak_object)
274 {
275   McdClientRegistry *self = MCD_CLIENT_REGISTRY (weak_object);
276 
277   if (error != NULL)
278     {
279       DEBUG ("ListNames returned error, assuming none: %s %d: %s",
280           g_quark_to_string (error->domain), error->code, error->message);
281     }
282   else if (names != NULL)
283     {
284       const gchar **iter = names;
285 
286       DEBUG ("ListNames returned");
287 
288       while (*iter != NULL)
289         {
290           _mcd_client_registry_found_name (self, *iter, NULL, FALSE);
291           iter++;
292         }
293     }
294 
295   tp_cli_dbus_daemon_call_list_activatable_names (proxy, -1,
296       mcd_client_registry_list_activatable_names_cb,
297       NULL, NULL, weak_object);
298   /* deliberately not calling _mcd_client_registry_dec_startup_lock here -
299    * this function is "lock-neutral", similarly to list_names_cb (we would
300    * take a lock for ListActivatableNames then release the one used for
301    * ReloadConfig), so simplify by doing nothing */
302 }
303 
304 static DBusHandlerResult
mcd_client_registry_name_owner_filter(DBusConnection * conn,DBusMessage * msg,gpointer data)305 mcd_client_registry_name_owner_filter (DBusConnection *conn,
306     DBusMessage *msg,
307     gpointer data)
308 {
309   McdClientRegistry *self = MCD_CLIENT_REGISTRY (data);
310 
311   /* make sure this is the right kind of signal: */
312   if (dbus_message_is_signal (msg, DBUS_INTERFACE_DBUS, "NameOwnerChanged"))
313     {
314       const gchar *dbus_name = NULL;
315       const gchar *old_owner = NULL;
316       const gchar *new_owner = NULL;
317       gboolean ok = dbus_message_get_args (msg, NULL,
318           DBUS_TYPE_STRING, &dbus_name,
319           DBUS_TYPE_STRING, &old_owner,
320           DBUS_TYPE_STRING, &new_owner,
321           DBUS_TYPE_INVALID);
322 
323       /* could not unpack args -> invalid -> stop processing right here */
324       if (!ok)
325         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
326 
327       if (tp_str_empty (old_owner) && !tp_str_empty (new_owner))
328         _mcd_client_registry_found_name (self, dbus_name, new_owner, FALSE);
329     }
330 
331   /* in case somebody else is also interested */
332   return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
333 }
334 
335 static void
watch_clients(McdClientRegistry * self)336 watch_clients (McdClientRegistry *self)
337 {
338   TpDBusDaemon *dbus_daemon = self->priv->dbus_daemon;
339   DBusGConnection *gconn = tp_proxy_get_dbus_connection (dbus_daemon);
340   DBusConnection *dconn = dbus_g_connection_get_connection (gconn);
341   DBusError error = { 0 };
342 
343 #define MATCH_ITEM(t,x) #t "='" x "'"
344 
345 #define NAME_OWNER_RULE \
346     MATCH_ITEM (type,      "signal")            "," \
347     MATCH_ITEM (sender,    DBUS_SERVICE_DBUS)   "," \
348     MATCH_ITEM (interface, DBUS_INTERFACE_DBUS) "," \
349     MATCH_ITEM (member,    "NameOwnerChanged")
350 
351 #define CLIENT_MATCH_RULE \
352     NAME_OWNER_RULE "," \
353     MATCH_ITEM (arg0namespace, "org.freedesktop.Telepathy.Client")
354 
355   if (!dbus_connection_add_filter (dconn, mcd_client_registry_name_owner_filter,
356         self, NULL))
357     g_critical ("Could not add filter for NameOwnerChanged (out of memory?)");
358 
359   dbus_error_init (&error);
360 
361   dbus_bus_add_match (dconn, CLIENT_MATCH_RULE, &error);
362   if (dbus_error_is_set (&error))
363     {
364       DEBUG ("Could not add client names match rule (D-Bus 1.6 required): %s",
365           error.message);
366 
367       dbus_error_free (&error);
368 
369       dbus_bus_add_match (dconn, NAME_OWNER_RULE, &error);
370       if (dbus_error_is_set (&error))
371         {
372           g_critical ("Could not add all dbus names match rule: %s",
373               error.message);
374           dbus_error_free (&error);
375         }
376     }
377 }
378 
379 static void
mcd_client_registry_constructed(GObject * object)380 mcd_client_registry_constructed (GObject *object)
381 {
382   McdClientRegistry *self = MCD_CLIENT_REGISTRY (object);
383   void (*chain_up) (GObject *) =
384     G_OBJECT_CLASS (_mcd_client_registry_parent_class)->constructed;
385 
386   if (chain_up != NULL)
387     chain_up (object);
388 
389   g_return_if_fail (self->priv->dbus_daemon != NULL);
390 
391   DEBUG ("Starting to look for clients");
392 
393   watch_clients (self);
394 
395   tp_cli_dbus_daemon_call_list_names (self->priv->dbus_daemon, -1,
396       mcd_client_registry_list_names_cb, NULL, NULL, object);
397 }
398 
399 static void
mcd_client_registry_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)400 mcd_client_registry_set_property (GObject *object,
401     guint prop_id,
402     const GValue *value,
403     GParamSpec *pspec)
404 {
405   McdClientRegistry *self = MCD_CLIENT_REGISTRY (object);
406 
407   switch (prop_id)
408     {
409     case PROP_DBUS_DAEMON:
410       g_assert (self->priv->dbus_daemon == NULL); /* it's construct-only */
411       self->priv->dbus_daemon = TP_DBUS_DAEMON (g_value_dup_object (value));
412       break;
413 
414     default:
415       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
416       break;
417     }
418 }
419 
420 static void
mcd_client_registry_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)421 mcd_client_registry_get_property (GObject *object,
422     guint prop_id,
423     GValue *value,
424     GParamSpec *pspec)
425 {
426   McdClientRegistry *self = MCD_CLIENT_REGISTRY (object);
427 
428   switch (prop_id)
429     {
430     case PROP_DBUS_DAEMON:
431       g_value_set_object (value, self->priv->dbus_daemon);
432       break;
433 
434     default:
435       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
436       break;
437     }
438 }
439 
440 static void
mcd_client_registry_dispose(GObject * object)441 mcd_client_registry_dispose (GObject *object)
442 {
443   McdClientRegistry *self = MCD_CLIENT_REGISTRY (object);
444   void (*chain_up) (GObject *) =
445     G_OBJECT_CLASS (_mcd_client_registry_parent_class)->dispose;
446 
447   if (self->priv->dbus_daemon != NULL)
448     {
449       DBusGConnection *gconn =
450         tp_proxy_get_dbus_connection (self->priv->dbus_daemon);
451       DBusConnection *dconn = dbus_g_connection_get_connection (gconn);
452 
453       dbus_connection_remove_filter (dconn,
454           mcd_client_registry_name_owner_filter,
455           self);
456     }
457 
458   tp_clear_object (&self->priv->dbus_daemon);
459 
460   if (self->priv->clients != NULL)
461     {
462       g_hash_table_foreach (self->priv->clients,
463           mcd_client_registry_disconnect_client_signals, self);
464 
465     }
466 
467   tp_clear_pointer (&self->priv->clients, g_hash_table_unref);
468 
469   if (chain_up != NULL)
470     chain_up (object);
471 }
472 
473 static void
_mcd_client_registry_class_init(McdClientRegistryClass * cls)474 _mcd_client_registry_class_init (McdClientRegistryClass *cls)
475 {
476   GObjectClass *object_class = G_OBJECT_CLASS (cls);
477 
478   g_type_class_add_private (cls, sizeof (McdClientRegistryPrivate));
479 
480   object_class->constructed = mcd_client_registry_constructed;
481   object_class->get_property = mcd_client_registry_get_property;
482   object_class->set_property = mcd_client_registry_set_property;
483   object_class->dispose = mcd_client_registry_dispose;
484 
485   g_object_class_install_property (object_class, PROP_DBUS_DAEMON,
486       g_param_spec_object ("dbus-daemon", "D-Bus daemon", "D-Bus daemon",
487         TP_TYPE_DBUS_DAEMON,
488         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
489 
490   signals[S_CLIENT_ADDED] = g_signal_new ("client-added",
491       G_OBJECT_CLASS_TYPE (cls),
492       G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
493       0, NULL, NULL,
494       g_cclosure_marshal_VOID__OBJECT,
495       G_TYPE_NONE, 1, MCD_TYPE_CLIENT_PROXY);
496 
497   signals[S_READY] = g_signal_new ("ready",
498       G_OBJECT_CLASS_TYPE (cls),
499       G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
500       0, NULL, NULL,
501       g_cclosure_marshal_VOID__VOID,
502       G_TYPE_NONE, 0);
503 }
504 
505 McdClientRegistry *
_mcd_client_registry_new(TpDBusDaemon * dbus_daemon)506 _mcd_client_registry_new (TpDBusDaemon *dbus_daemon)
507 {
508   return g_object_new (MCD_TYPE_CLIENT_REGISTRY,
509       "dbus-daemon", dbus_daemon,
510       NULL);
511 }
512 
513 static void
mcd_client_registry_ready_cb(McdClientProxy * client,McdClientRegistry * self)514 mcd_client_registry_ready_cb (McdClientProxy *client,
515     McdClientRegistry *self)
516 {
517   DEBUG ("%s", tp_proxy_get_bus_name (client));
518 
519   g_signal_handlers_disconnect_by_func (client,
520       mcd_client_registry_ready_cb, self);
521 
522   /* paired with the one in _mcd_client_registry_found_name */
523   _mcd_client_registry_dec_startup_lock (self);
524 }
525 
526 static void
mcd_client_registry_gone_cb(McdClientProxy * client,McdClientRegistry * self)527 mcd_client_registry_gone_cb (McdClientProxy *client,
528     McdClientRegistry *self)
529 {
530   _mcd_client_registry_remove (self, tp_proxy_get_bus_name (client));
531 }
532 
533 GPtrArray *
_mcd_client_registry_dup_client_caps(McdClientRegistry * self)534 _mcd_client_registry_dup_client_caps (McdClientRegistry *self)
535 {
536   GPtrArray *vas;
537   GHashTableIter iter;
538   gpointer p;
539 
540   g_return_val_if_fail (MCD_IS_CLIENT_REGISTRY (self), NULL);
541 
542   vas = g_ptr_array_sized_new (g_hash_table_size (self->priv->clients));
543 
544   g_hash_table_iter_init (&iter, self->priv->clients);
545 
546   while (g_hash_table_iter_next (&iter, NULL, &p))
547     {
548       g_ptr_array_add (vas,
549           _mcd_client_proxy_dup_handler_capabilities (p));
550     }
551 
552   return vas;
553 }
554 
555 gboolean
_mcd_client_registry_is_ready(McdClientRegistry * self)556 _mcd_client_registry_is_ready (McdClientRegistry *self)
557 {
558   g_return_val_if_fail (MCD_IS_CLIENT_REGISTRY (self), FALSE);
559   return self->priv->startup_completed;
560 }
561 
562 typedef struct
563 {
564     McdClientProxy *client;
565     gboolean bypass;
566     gsize quality;
567 } PossibleHandler;
568 
569 static gint
possible_handler_cmp(gconstpointer a_,gconstpointer b_)570 possible_handler_cmp (gconstpointer a_,
571                       gconstpointer b_)
572 {
573   const PossibleHandler *a = a_;
574   const PossibleHandler *b = b_;
575 
576   if (a->bypass)
577     {
578       if (!b->bypass)
579         {
580           /* BypassApproval wins, so a is better than b */
581           return 1;
582         }
583     }
584   else if (b->bypass)
585     {
586       /* BypassApproval wins, so b is better than a */
587       return -1;
588     }
589 
590   if (a->quality < b->quality)
591     {
592       return -1;
593     }
594 
595   if (b->quality < a->quality)
596     {
597       return 1;
598     }
599 
600   return 0;
601 }
602 
603 GList *
_mcd_client_registry_list_possible_handlers(McdClientRegistry * self,const gchar * preferred_handler,GVariant * request_props,TpChannel * channel,const gchar * must_have_unique_name)604 _mcd_client_registry_list_possible_handlers (McdClientRegistry *self,
605     const gchar *preferred_handler,
606     GVariant *request_props,
607     TpChannel *channel,
608     const gchar *must_have_unique_name)
609 {
610   GList *handlers = NULL;
611   GList *handlers_iter;
612   GHashTableIter client_iter;
613   gpointer client_p;
614 
615   _mcd_client_registry_init_hash_iter (self, &client_iter);
616 
617   while (g_hash_table_iter_next (&client_iter, NULL, &client_p))
618     {
619       McdClientProxy *client = MCD_CLIENT_PROXY (client_p);
620       gsize quality;
621 
622       if (must_have_unique_name != NULL &&
623           tp_strdiff (must_have_unique_name,
624             _mcd_client_proxy_get_unique_name (client)))
625         {
626           /* we're trying to redispatch to an existing handler, and this is
627            * not it */
628           continue;
629         }
630 
631       if (!tp_proxy_has_interface_by_id (client,
632             TP_IFACE_QUARK_CLIENT_HANDLER))
633         {
634             /* not a handler at all */
635             continue;
636         }
637 
638       if (channel == NULL)
639         {
640           /* We don't know the channel's properties (the next part will not
641            * execute), so we must work out the quality of match from the
642            * channel request. We can assume that the request will return one
643            * channel, with the requested properties, plus Requested == TRUE.
644            */
645           g_assert (request_props != NULL);
646           quality = _mcd_client_match_filters (request_props,
647               _mcd_client_proxy_get_handler_filters (client), TRUE);
648         }
649       else
650         {
651           GVariant *properties;
652 
653           g_assert (TP_IS_CHANNEL (channel));
654           properties = tp_channel_dup_immutable_properties (channel);
655           quality = _mcd_client_match_filters (properties,
656               _mcd_client_proxy_get_handler_filters (client), FALSE);
657           g_variant_unref (properties);
658         }
659 
660       if (quality > 0)
661         {
662           PossibleHandler *ph = g_slice_new0 (PossibleHandler);
663 
664           ph->client = client;
665           ph->bypass = _mcd_client_proxy_get_bypass_approval (client);
666           ph->quality = quality;
667 
668           handlers = g_list_prepend (handlers, ph);
669         }
670     }
671 
672   /* if no handlers can take them all, fail - unless we're operating on
673    * a request that specified a preferred handler, in which case assume
674    * it's suitable */
675   if (handlers == NULL)
676     {
677       McdClientProxy *client;
678 
679       if (preferred_handler == NULL || preferred_handler[0] == '\0')
680         {
681           return NULL;
682         }
683 
684       client = _mcd_client_registry_lookup (self, preferred_handler);
685 
686       if (client == NULL)
687         {
688           return NULL;
689         }
690 
691       return g_list_append (NULL, client);
692     }
693 
694   /* We have at least one handler that can take the whole batch. Sort
695    * the possible handlers, most preferred first (i.e. sort by ascending
696    * quality then reverse) */
697   handlers = g_list_sort (handlers, possible_handler_cmp);
698   handlers = g_list_reverse (handlers);
699 
700   /* convert in-place from a list of PossibleHandler to a list of
701    * McdClientProxy */
702   for (handlers_iter = handlers;
703        handlers_iter != NULL;
704        handlers_iter = handlers_iter->next)
705     {
706       PossibleHandler *ph = handlers_iter->data;
707 
708       handlers_iter->data = ph->client;
709       g_slice_free (PossibleHandler, ph);
710     }
711 
712   return handlers;
713 }
714 
715 TpDBusDaemon *
_mcd_client_registry_get_dbus_daemon(McdClientRegistry * self)716 _mcd_client_registry_get_dbus_daemon (McdClientRegistry *self)
717 {
718     g_return_val_if_fail (MCD_IS_CLIENT_REGISTRY (self), NULL);
719     return self->priv->dbus_daemon;
720 }
721