1 /*
2  * dbus-properties-mixin.c - D-Bus core Properties
3  * Copyright (C) 2008 Collabora Ltd.
4  * Copyright (C) 2008 Nokia Corporation
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
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20 
21 #include "config.h"
22 
23 #include <telepathy-glib/dbus-properties-mixin.h>
24 
25 #include <telepathy-glib/errors.h>
26 #include <telepathy-glib/svc-generic.h>
27 #include <telepathy-glib/util.h>
28 
29 #define DEBUG_FLAG TP_DEBUG_PROPERTIES
30 #include "telepathy-glib/debug-internal.h"
31 
32 /**
33  * SECTION:dbus-properties-mixin
34  * @title: TpDBusPropertiesMixin
35  * @short_description: a mixin implementation of the DBus.Properties interface
36  * @see_also: #TpSvcDBusProperties
37  *
38  * This mixin provides an implementation of the org.freedesktop.DBus.Properties
39  * interface. It relies on the auto-generated service-side GInterfaces from
40  * telepathy-glib >= 0.7.3, or something similar, to register the abstract
41  * properties and their GTypes; classes with the mixin can then register
42  * an implementation of the properties.
43  *
44  * To register D-Bus properties in a GInterface to be implementable with this
45  * mixin, either use the code-generation tools from telepathy-glib >= 0.7.3,
46  * or call tp_svc_interface_set_dbus_properties_info() from a section of the
47  * base_init function that only runs once.
48  *
49  * To use this mixin, include a #TpDBusPropertiesMixinClass somewhere
50  * in your class structure, populate it with pointers to statically allocated
51  * (or duplicated and never freed) data, and call
52  * tp_dbus_properties_mixin_class_init() from your class_init implementation.
53  *
54  * To use this mixin as the implementation of #TpSvcDBusProperties,
55  * call <literal>G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
56  * tp_dbus_properties_mixin_iface_init)</literal> in the fourth argument to
57  * <literal>G_DEFINE_TYPE_WITH_CODE</literal>.
58  *
59  * Since: 0.7.3
60  */
61 
62 /**
63  * TpDBusPropertiesMixinFlags:
64  * @TP_DBUS_PROPERTIES_MIXIN_FLAG_READ: The property can be read using Get and
65  *  GetAll
66  * @TP_DBUS_PROPERTIES_MIXIN_FLAG_WRITE: The property can be written using Set
67  * @TP_DBUS_PROPERTIES_MIXIN_FLAG_EMITS_CHANGED: The property's new value is
68  *  included in emissions of PropertiesChanged
69  * @TP_DBUS_PROPERTIES_MIXIN_FLAG_EMITS_INVALIDATED: The property is announced
70  *  as invalidated, without its value, in emissions of PropertiesChanged
71  *
72  * Bitfield representing allowed access to a property. At most one of
73  * %TP_DBUS_PROPERTIES_MIXIN_FLAG_EMITS_CHANGED and
74  * %TP_DBUS_PROPERTIES_MIXIN_FLAG_EMITS_INVALIDATED may be specified for a
75  * property.
76  *
77  * Since 0.11.5, there is a corresponding #GFlagsClass type,
78  * %TP_TYPE_DBUS_PROPERTIES_MIXIN_FLAGS.
79  *
80  * Since: 0.7.3
81  */
82 
83 /**
84  * TP_TYPE_DBUS_PROPERTIES_MIXIN_FLAGS:
85  *
86  * The #GFlagsClass type of #TpDBusPropertiesMixinFlags.
87  *
88  * Since: 0.11.5
89  */
90 
91 /**
92  * TpDBusPropertiesMixinPropInfo:
93  * @name: Quark representing the property's name
94  * @flags: Flags representing read/write access to the property
95  * @dbus_signature: The D-Bus signature of the property
96  * @type: The GType used in a GValue to implement the property
97  *
98  * Semi-abstract description of a property, as attached to a service
99  * GInterface. This structure must either be statically allocated, or
100  * duplicated and never freed, so it always remains valid.
101  *
102  * In addition to the documented members, there are two private pointers
103  * for future expansion, which must always be initialized to %NULL.
104  *
105  * Since: 0.7.3
106  */
107 
108 /**
109  * TpDBusPropertiesMixinIfaceInfo:
110  * @dbus_interface: Quark representing the interface's name
111  * @props: Array of property descriptions, terminated by one with
112  *  @name == %NULL
113  *
114  * Semi-abstract description of an interface. Each service GInterface that
115  * has properties must have one of these attached to it via
116  * tp_svc_interface_set_dbus_properties_info() in its base_init function;
117  * service GInterfaces that do not have properties may have one of these
118  * with no properties.
119  *
120  * This structure must either be statically allocated, or duplicated and never
121  * freed, so it always remains valid.
122  *
123  * In addition to the documented members, there are two private pointers
124  * for future expansion, which must always be initialized to %NULL.
125  *
126  * Since: 0.7.3
127  */
128 
129 static GQuark
_iface_prop_info_quark(void)130 _iface_prop_info_quark (void)
131 {
132   static GQuark q = 0;
133 
134   if (G_UNLIKELY (q == 0))
135     q = g_quark_from_static_string
136         ("tp_svc_interface_get_dbus_properties_info@TELEPATHY_GLIB_0.7.3");
137 
138   return q;
139 }
140 
141 /**
142  * tp_svc_interface_set_dbus_properties_info:
143  * @g_interface: The #GType of a service interface
144  * @info: an interface description
145  *
146  * Declare that @g_interface implements the given D-Bus interface, with the
147  * given properties. This may only be called once per GInterface, usually from
148  * a section of its base_init function that only runs once.
149  *
150  * This is typically only used within generated code; there is normally no
151  * reason to call it manually.
152  *
153  * Since: 0.7.3
154  */
155 void
tp_svc_interface_set_dbus_properties_info(GType g_interface,TpDBusPropertiesMixinIfaceInfo * info)156 tp_svc_interface_set_dbus_properties_info (GType g_interface,
157     TpDBusPropertiesMixinIfaceInfo *info)
158 {
159   GQuark q = _iface_prop_info_quark ();
160   TpDBusPropertiesMixinPropInfo *prop;
161 
162   g_return_if_fail (G_TYPE_IS_INTERFACE (g_interface));
163   g_return_if_fail (g_type_get_qdata (g_interface, q) == NULL);
164   g_return_if_fail (info->dbus_interface != 0);
165   g_return_if_fail (info->props != NULL);
166 
167   for (prop = info->props; prop->name != 0; prop++)
168     {
169       g_return_if_fail (prop->flags != 0);
170       g_return_if_fail (
171         (prop->flags & ~( TP_DBUS_PROPERTIES_MIXIN_FLAG_READ
172                         | TP_DBUS_PROPERTIES_MIXIN_FLAG_WRITE
173                         | TP_DBUS_PROPERTIES_MIXIN_FLAG_EMITS_CHANGED
174                         | TP_DBUS_PROPERTIES_MIXIN_FLAG_EMITS_INVALIDATED
175                         )) == 0);
176 
177       /* Check that at most one change-related flag is set. */
178       if ((prop->flags & TP_DBUS_PROPERTIES_MIXIN_FLAG_EMITS_CHANGED) &&
179           (prop->flags & TP_DBUS_PROPERTIES_MIXIN_FLAG_EMITS_INVALIDATED))
180         {
181           CRITICAL ("at most one of EMITS_CHANGED and EMITS_INVALIDATED may be "
182               "specified for a property, but %s.%s has both",
183               g_quark_to_string (info->dbus_interface),
184               g_quark_to_string (prop->name));
185           g_return_if_reached ();
186         }
187 
188       g_return_if_fail (prop->dbus_signature != NULL);
189       g_return_if_fail (prop->dbus_signature[0] != '\0');
190       g_return_if_fail (prop->type != 0);
191     }
192 
193   g_type_set_qdata (g_interface, q, info);
194 }
195 
196 /**
197  * tp_svc_interface_get_dbus_properties_info: (skip)
198  * @g_interface: The #GType of a service interface
199  *
200  * Retrieves the D-Bus property metadata for the given interface, if any.
201  * This function is typically not useful outside telepathy-glib itself, but may
202  * be useful for domain-specific variations on the theme of SetProperty. If in
203  * doubt, you probably don't need this function.
204  *
205  * Returns: D-Bus property metadata for @g_interface, or %NULL if it has
206  *  none.
207  * Since: 0.15.8
208  */
209 TpDBusPropertiesMixinIfaceInfo *
tp_svc_interface_get_dbus_properties_info(GType g_interface)210 tp_svc_interface_get_dbus_properties_info (GType g_interface)
211 {
212   return g_type_get_qdata (g_interface, _iface_prop_info_quark ());
213 }
214 
215 /**
216  * TpDBusPropertiesMixinGetter:
217  * @object: The exported object with the properties
218  * @iface: A quark representing the D-Bus interface name
219  * @name: A quark representing the D-Bus property name
220  * @value: A GValue pre-initialized to the right type, into which to put
221  *  the value
222  * @getter_data: The getter_data from the #TpDBusPropertiesMixinPropImpl
223  *
224  * Signature of a callback used to get the value of a property.
225  *
226  * For simplicity, in this mixin we don't allow getting a property to fail;
227  * implementations must always be prepared to return *something*.
228  */
229 
230 /**
231  * tp_dbus_properties_mixin_getter_gobject_properties:
232  * @object: The exported object with the properties
233  * @iface: A quark representing the D-Bus interface name
234  * @name: A quark representing the D-Bus property name
235  * @value: A GValue pre-initialized to the right type, into which to put
236  *  the value
237  * @getter_data: The getter_data from the #TpDBusPropertiesMixinPropImpl,
238  *  which must be a string containing the GObject property's name
239  *
240  * An implementation of #TpDBusPropertiesMixinGetter which assumes that
241  * the @getter_data is the name of a readable #GObject property of an
242  * appropriate type, and uses it for the value of the D-Bus property.
243  */
244 void
tp_dbus_properties_mixin_getter_gobject_properties(GObject * object,GQuark iface,GQuark name,GValue * value,gpointer getter_data)245 tp_dbus_properties_mixin_getter_gobject_properties (GObject *object,
246                                                     GQuark iface,
247                                                     GQuark name,
248                                                     GValue *value,
249                                                     gpointer getter_data)
250 {
251   g_object_get_property (object, getter_data, value);
252 }
253 
254 /**
255  * TpDBusPropertiesMixinSetter:
256  * @object: The exported object with the properties
257  * @iface: A quark representing the D-Bus interface name
258  * @name: A quark representing the D-Bus property name
259  * @value: The new value for the property
260  * @setter_data: The setter_data from the #TpDBusPropertiesMixinPropImpl
261  * @error: Used to return an error on failure
262  *
263  * Signature of a callback used to get the value of a property.
264  *
265  * Returns: %TRUE on success, %FALSE (setting @error) on failure
266  */
267 
268 /**
269  * tp_dbus_properties_mixin_setter_gobject_properties:
270  * @object: The exported object with the properties
271  * @iface: A quark representing the D-Bus interface name
272  * @name: A quark representing the D-Bus property name
273  * @value: The new value for the property
274  * @setter_data: The setter_data from the #TpDBusPropertiesMixinPropImpl,
275  *  which must be a string containing the GObject property's name
276  * @error: Not used
277  *
278  * An implementation of #TpDBusPropertiesMixinSetter which assumes that the
279  * @setter_data is the name of a writable #GObject property of an appropriate
280  * type, and sets that property to the given value.
281  *
282  * Returns: %TRUE
283  */
284 gboolean
tp_dbus_properties_mixin_setter_gobject_properties(GObject * object,GQuark iface,GQuark name,const GValue * value,gpointer setter_data,GError ** error)285 tp_dbus_properties_mixin_setter_gobject_properties (GObject *object,
286                                                     GQuark iface,
287                                                     GQuark name,
288                                                     const GValue *value,
289                                                     gpointer setter_data,
290                                                     GError **error)
291 {
292   g_object_set_property (object, setter_data, value);
293   return TRUE;
294 }
295 
296 /**
297  * TpDBusPropertiesMixinPropImpl:
298  * @name: The name of the property as it appears on D-Bus
299  * @getter_data: Arbitrary user-supplied data for the getter function
300  * @setter_data: Arbitrary user-supplied data for the setter function
301  *
302  * Structure representing an implementation of a property.
303  *
304  * In addition to the documented fields, there are three pointers which must
305  * be initialized to %NULL.
306  *
307  * This structure must either be statically allocated, or duplicated and never
308  * freed, so it always remains valid.
309  *
310  * Since: 0.7.3
311  */
312 
313 /**
314  * TpDBusPropertiesMixinIfaceImpl:
315  * @name: The name of the interface
316  * @getter: A callback to get the current value of the property, to which
317  *  the @getter_data from each property implementation will be passed
318  * @setter: A callback to set a new value for the property, to which
319  *  the @setter_data from each property implementation will be passed
320  * @props: An array of property implementations, terminated by one with
321  *  @name equal to %NULL
322  *
323  * Structure representing an implementation of an interface's properties.
324  *
325  * In addition to the documented fields, there are four pointers which must
326  * be initialized to %NULL.
327  *
328  * This structure must either be statically allocated, or duplicated and never
329  * freed, so it always remains valid.
330  *
331  * Since: 0.7.3
332  */
333 
334 /**
335  * TpDBusPropertiesMixinClass:
336  * @interfaces: An array of interface implementations, terminated by one with
337  *  @name equal to %NULL
338  *
339  * Structure representing all of a class's property implementations. One of
340  * these structures may be placed in the layout of an object class structure.
341  *
342  * In addition to the documented fields, there are 7 pointers reserved for
343  * future use, which must be initialized to %NULL.
344  *
345  * Since: 0.7.3
346  */
347 
348 static GQuark
_prop_mixin_offset_quark(void)349 _prop_mixin_offset_quark (void)
350 {
351   static GQuark q = 0;
352 
353   if (G_UNLIKELY (q == 0))
354     q = g_quark_from_static_string
355         ("tp_dbus_properties_mixin_class_init@TELEPATHY_GLIB_0.7.3");
356 
357   return q;
358 }
359 
360 static GQuark
_extra_prop_impls_quark(void)361 _extra_prop_impls_quark (void)
362 {
363   static GQuark q = 0;
364 
365   if (G_UNLIKELY (q == 0))
366     q = g_quark_from_static_string
367         ("tp_dbus_properties_mixin_implement_interface@TELEPATHY_GLIB_0.7.9");
368 
369   return q;
370 }
371 
372 
373 static gboolean
link_interface(GType type,const GType * interfaces,GQuark iface_quark,TpDBusPropertiesMixinIfaceImpl * iface_impl)374 link_interface (GType type,
375                 const GType *interfaces,
376                 GQuark iface_quark,
377                 TpDBusPropertiesMixinIfaceImpl *iface_impl)
378 {
379   TpDBusPropertiesMixinIfaceInfo *iface_info = NULL;
380   TpDBusPropertiesMixinPropImpl *prop_impl;
381 
382   g_return_val_if_fail (iface_impl->props != NULL, FALSE);
383 
384   /* no point bothering if there is no quark for the interface name */
385   if (iface_quark != 0)
386     {
387       const GType *iface;
388 
389       for (iface = interfaces; *iface != 0; iface++)
390         {
391           iface_info = tp_svc_interface_get_dbus_properties_info (*iface);
392 
393           if (iface_info != NULL &&
394               iface_info->dbus_interface == iface_quark)
395             break;
396           else
397             iface_info = NULL;
398         }
399     }
400 
401   if (iface_info == NULL)
402     {
403       CRITICAL ("%s tried to implement undefined interface %s "
404           "(perhaps you forgot to call G_IMPLEMENT_INTERFACE?)",
405           g_type_name (type), iface_impl->name);
406       return FALSE;
407     }
408 
409   iface_impl->mixin_priv = iface_info;
410 
411   for (prop_impl = iface_impl->props; prop_impl->name != NULL; prop_impl++)
412     {
413       TpDBusPropertiesMixinPropInfo *prop_info;
414       GQuark name_quark = g_quark_try_string (prop_impl->name);
415 
416       prop_impl->mixin_priv = NULL;
417 
418       /* no point bothering if there is no quark for this name */
419       if (name_quark != 0)
420         {
421           for (prop_info = iface_info->props;
422                prop_info->name != 0;
423                prop_info++)
424             {
425               if (prop_info->name == name_quark)
426                 {
427                   prop_impl->mixin_priv = prop_info;
428                   break;
429                 }
430             }
431         }
432 
433       if (prop_impl->mixin_priv == NULL)
434         {
435           CRITICAL ("%s tried to implement nonexistent property %s"
436               " on interface %s", g_type_name (type), prop_impl->name,
437               iface_impl->name);
438           return FALSE;
439         }
440     }
441 
442   return TRUE;
443 }
444 
445 /* if this assertion fails, TpDBusPropertiesMixinIfaceImpl.mixin_next (which
446  * used to be a GCallback but is now a gpointer) will be an ABI break on this
447  * architecture, so do some evil trick with unions or something */
448 G_STATIC_ASSERT (sizeof (GCallback) == sizeof (gpointer));
449 
450 /* FIXME: GNOME#556489: getter and setter should be (scope infinite) if that
451  * existed */
452 /**
453  * tp_dbus_properties_mixin_implement_interface: (skip)
454  * @cls: a subclass of #GObjectClass
455  * @iface: a quark representing the the name of the interface to implement
456  * @getter: a callback to get properties on this interface, or %NULL if they
457  *  are all write-only
458  * @setter: a callback to set properties on this interface, or %NULL if they
459  *  are all read-only
460  * @props: an array of #TpDBusPropertiesMixinPropImpl representing individual
461  *  properties, terminated by one with @name == %NULL
462  *
463  * Declare that, in addition to any interfaces set in
464  * tp_dbus_properties_mixin_class_init(), the given class (and its subclasses)
465  * will implement the properties of the interface @iface using the callbacks
466  * @getter and @setter and the properties given by @props.
467  *
468  * This function should be called from the class_init callback in such a way
469  * that it will only be called once, even if the class is subclassed.
470  *
471  * Typically, the static array @interfaces in the #TpDBusPropertiesMixinClass
472  * should be used for interfaces whose properties are implemented directly by
473  * the class @cls, and this function should be used for interfaces whose
474  * properties are implemented by mixins.
475  *
476  * It is an error for the same interface to appear in the array @interfaces
477  * in the #TpDBusPropertiesMixinClass, and also be set up by this function.
478  *
479  * If a class C and a subclass S both implement the properties of the same
480  * interface, only the implementations from the subclass S will be used,
481  * regardless of whether the implementations in C and/or S were set up by
482  * this function or via the array @interfaces in the
483  * #TpDBusPropertiesMixinClass.
484  */
485 void
tp_dbus_properties_mixin_implement_interface(GObjectClass * cls,GQuark iface,TpDBusPropertiesMixinGetter getter,TpDBusPropertiesMixinSetter setter,TpDBusPropertiesMixinPropImpl * props)486 tp_dbus_properties_mixin_implement_interface (GObjectClass *cls,
487     GQuark iface,
488     TpDBusPropertiesMixinGetter getter,
489     TpDBusPropertiesMixinSetter setter,
490     TpDBusPropertiesMixinPropImpl *props)
491 {
492   GQuark extras_quark = _extra_prop_impls_quark ();
493   GType type = G_OBJECT_CLASS_TYPE (cls);
494   GType *interfaces = g_type_interfaces (type, NULL);
495   TpDBusPropertiesMixinIfaceImpl *iface_impl;
496 
497   g_return_if_fail (G_IS_OBJECT_CLASS (cls));
498 
499   /* never freed - intentional per-class leak */
500   iface_impl = g_new0 (TpDBusPropertiesMixinIfaceImpl, 1);
501   iface_impl->name = g_quark_to_string (iface);
502   iface_impl->getter = getter;
503   iface_impl->setter = setter;
504   iface_impl->props = props;
505 
506   /* align property implementations with abstract properties */
507   if (G_LIKELY (link_interface (type, interfaces, iface, iface_impl)))
508     {
509       TpDBusPropertiesMixinIfaceImpl *next = g_type_get_qdata (type,
510           extras_quark);
511 #ifdef ENABLE_DEBUG
512       GQuark offset_quark = _prop_mixin_offset_quark ();
513       gpointer offset_qdata = g_type_get_qdata (type, offset_quark);
514       TpDBusPropertiesMixinClass *mixin = NULL;
515       TpDBusPropertiesMixinIfaceImpl *iter;
516 
517       if (offset_qdata != NULL)
518         mixin = &G_STRUCT_MEMBER (TpDBusPropertiesMixinClass, cls,
519             GPOINTER_TO_SIZE (offset_qdata));
520 
521       /* assert that we're not trying to implement the same interface twice */
522       for (iter = next;
523            iter != NULL && iter->name != NULL;
524            iter = iter->mixin_next)
525         {
526           TpDBusPropertiesMixinIfaceInfo *other_info = iter->mixin_priv;
527 
528           g_assert (other_info != NULL);
529 
530           if (G_UNLIKELY (other_info->dbus_interface == iface))
531             {
532               CRITICAL ("type %s tried to implement interface %s with %s "
533                   "twice", g_type_name (type), g_quark_to_string (iface),
534                   G_STRFUNC);
535               goto out;
536             }
537         }
538 
539       /* assert that we're not trying to implement the same interface via
540        * this function and the static data */
541       if (mixin != NULL && mixin->interfaces != NULL)
542         {
543           for (iter = mixin->interfaces;
544                iter->name != NULL;
545                iter++)
546             {
547               TpDBusPropertiesMixinIfaceInfo *other_info = iter->mixin_priv;
548 
549               g_assert (other_info != NULL);
550 
551               if (G_UNLIKELY (other_info->dbus_interface == iface))
552                 {
553                   CRITICAL ("type %s tried to implement interface %s with %s "
554                       "and also in static data", g_type_name (type),
555                       g_quark_to_string (iface), G_STRFUNC);
556                   goto out;
557                 }
558             }
559         }
560 #endif
561 
562       /* form a linked list */
563       iface_impl->mixin_next = next;
564       g_type_set_qdata (type, extras_quark, iface_impl);
565     }
566 
567 #ifdef ENABLE_DEBUG
568 out:
569 #endif
570   g_free (interfaces);
571 }
572 
573 
574 /**
575  * tp_dbus_properties_mixin_class_init:
576  * @cls: a subclass of #GObjectClass
577  * @offset: the offset within @cls of a TpDBusPropertiesMixinClass structure
578  *
579  * Initialize the class @cls to use the D-Bus Properties mixin.
580  * The given struct member, of size sizeof(TpDBusPropertiesMixinClass),
581  * will be used to store property implementation information.
582  *
583  * Each property and each interface must have been declared as a member of
584  * a GInterface implemented by @cls, using
585  * tp_svc_interface_set_dbus_properties_info().
586  *
587  * Before calling this function, the array @interfaces must have been
588  * placed in the #TpDBusPropertiesMixinClass structure; if it would be empty,
589  * it may instead be %NULL.
590  *
591  * This function should be called from the class_init callback in such a way
592  * that it will only be called once, even if the class is subclassed.
593  *
594  * Changed in 0.7.9: TpDBusPropertiesMixinClass::interfaces may now be %NULL,
595  * which means that only interfaces whose properties are set up using
596  * tp_dbus_properties_mixin_implement_interface() will be used.
597  *
598  * Changed in 0.7.15: @offset may now be 0, in which case the
599  * #TpDBusPropertiesMixinClass can be omitted from @cls.  It is treated as if
600  * it were present, but with all fields (including
601  * TpDBusPropertiesMixinClass::interfaces) being %NULL, so only interfaces
602  * whose properties are set using
603  * tp_dbus_properties_mixin_implement_interface() will be used.
604  *
605  * Since: 0.7.3
606  */
607 void
tp_dbus_properties_mixin_class_init(GObjectClass * cls,gsize offset)608 tp_dbus_properties_mixin_class_init (GObjectClass *cls,
609                                      gsize offset)
610 {
611   GQuark q = _prop_mixin_offset_quark ();
612   GType type = G_OBJECT_CLASS_TYPE (cls);
613   TpDBusPropertiesMixinClass *mixin;
614   TpDBusPropertiesMixinIfaceImpl *iface_impl;
615   GType *interfaces;
616 
617   g_return_if_fail (G_IS_OBJECT_CLASS (cls));
618   g_return_if_fail (g_type_get_qdata (type, q) == NULL);
619   g_type_set_qdata (type, q, GSIZE_TO_POINTER (offset));
620 
621   if (offset == 0)
622     return;
623 
624   mixin = &G_STRUCT_MEMBER (TpDBusPropertiesMixinClass, cls, offset);
625 
626   if (mixin->interfaces == NULL)
627     return;
628 
629   interfaces = g_type_interfaces (type, NULL);
630 
631   for (iface_impl = mixin->interfaces;
632        iface_impl->name != NULL;
633        iface_impl++)
634     {
635       GQuark iface_quark = g_quark_try_string (iface_impl->name);
636 #ifdef ENABLE_DEBUG
637       TpDBusPropertiesMixinIfaceImpl *other_impl;
638 #endif
639 
640       if (G_UNLIKELY (!link_interface (type, interfaces, iface_quark,
641               iface_impl)))
642         goto out;
643 
644 #ifdef ENABLE_DEBUG
645       for (other_impl = mixin->interfaces;
646            other_impl != iface_impl;
647            other_impl++)
648         {
649           TpDBusPropertiesMixinIfaceInfo *other_info = other_impl->mixin_priv;
650 
651           if (G_UNLIKELY (iface_quark == other_info->dbus_interface))
652             {
653               CRITICAL ("type %s tried to implement interface %s in static "
654                   "data twice", g_type_name (type), iface_impl->name);
655               goto out;
656             }
657         }
658 #endif
659     }
660 
661 out:
662 
663   g_free (interfaces);
664 }
665 
666 static TpDBusPropertiesMixinIfaceImpl *
_tp_dbus_properties_mixin_find_iface_impl(GObject * self,const gchar * name)667 _tp_dbus_properties_mixin_find_iface_impl (GObject *self,
668                                            const gchar *name)
669 {
670   GType type;
671   GQuark offset_quark = _prop_mixin_offset_quark ();
672   GQuark extras_quark = _extra_prop_impls_quark ();
673   GQuark iface_quark = g_quark_try_string (name);
674 
675   if (iface_quark == 0)
676     return NULL;
677 
678   for (type = G_OBJECT_TYPE (self);
679        type != 0;
680        type = g_type_parent (type))
681     {
682       gpointer offset = g_type_get_qdata (type, offset_quark);
683       TpDBusPropertiesMixinClass *mixin = NULL;
684       TpDBusPropertiesMixinIfaceImpl *iface_impl;
685       TpDBusPropertiesMixinIfaceInfo *iface_info;
686 
687       if (offset != NULL)
688         mixin = &G_STRUCT_MEMBER (TpDBusPropertiesMixinClass,
689             G_OBJECT_GET_CLASS (self), GPOINTER_TO_SIZE (offset));
690 
691       if (mixin != NULL && mixin->interfaces != NULL)
692         {
693           for (iface_impl = mixin->interfaces;
694                iface_impl->name != NULL;
695                iface_impl++)
696             {
697               iface_info = iface_impl->mixin_priv;
698 
699               if (iface_info->dbus_interface == iface_quark)
700                 return iface_impl;
701             }
702         }
703 
704       for (iface_impl = g_type_get_qdata (type, extras_quark);
705            iface_impl != NULL;
706            iface_impl = iface_impl->mixin_next)
707         {
708           iface_info = iface_impl->mixin_priv;
709 
710           if (iface_info->dbus_interface == iface_quark)
711             return iface_impl;
712         }
713     }
714 
715   return NULL;
716 }
717 
718 static TpDBusPropertiesMixinPropImpl *
_tp_dbus_properties_mixin_find_prop_impl(TpDBusPropertiesMixinIfaceImpl * iface_impl,const gchar * name)719 _tp_dbus_properties_mixin_find_prop_impl
720     (TpDBusPropertiesMixinIfaceImpl *iface_impl,
721      const gchar *name)
722 {
723   GQuark prop_quark = g_quark_try_string (name);
724   TpDBusPropertiesMixinPropImpl *prop_impl;
725 
726   if (prop_quark == 0)
727     return NULL;
728 
729   for (prop_impl = iface_impl->props;
730        prop_impl->name != NULL;
731        prop_impl++)
732     {
733       TpDBusPropertiesMixinPropInfo *prop_info = prop_impl->mixin_priv;
734 
735       if (prop_info->name == prop_quark)
736         return prop_impl;
737     }
738 
739   return NULL;
740 }
741 
742 static TpDBusPropertiesMixinPropImpl *
_iface_impl_get_property_impl(GObject * self,TpDBusPropertiesMixinIfaceImpl * iface_impl,const gchar * interface_name,const gchar * property_name,GError ** error)743 _iface_impl_get_property_impl (
744     GObject *self,
745     TpDBusPropertiesMixinIfaceImpl *iface_impl,
746     const gchar *interface_name,
747     const gchar *property_name,
748     GError **error)
749 {
750   TpDBusPropertiesMixinPropImpl *prop_impl;
751   TpDBusPropertiesMixinPropInfo *prop_info;
752 
753   prop_impl = _tp_dbus_properties_mixin_find_prop_impl (iface_impl,
754       property_name);
755 
756   if (prop_impl == NULL)
757     {
758       g_set_error (error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
759           "Unknown property %s on %s", property_name, interface_name);
760       return FALSE;
761     }
762 
763   prop_info = prop_impl->mixin_priv;
764 
765   if ((prop_info->flags & TP_DBUS_PROPERTIES_MIXIN_FLAG_READ) == 0)
766     {
767       g_set_error (error, TP_ERROR, TP_ERROR_PERMISSION_DENIED,
768           "Property %s on %s is write-only", property_name, interface_name);
769       return FALSE;
770     }
771 
772   if (iface_impl->getter == NULL)
773     {
774       g_set_error (error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
775           "Getting properties on %s is unimplemented", interface_name);
776       return FALSE;
777     }
778 
779   return prop_impl;
780 }
781 
782 /**
783  * tp_dbus_properties_mixin_get:
784  * @self: an object with this mixin
785  * @interface_name: a D-Bus interface name
786  * @property_name: a D-Bus property name
787  * @value: an unset GValue (initialized to all zeroes)
788  * @error: used to return an error on failure
789  *
790  * Initialize @value with the type of the property @property_name on
791  * @interface_name, and write the value of that property into it as if
792  * by calling the D-Bus method org.freedesktop.DBus.Properties.Get.
793  *
794  * If Get would return a D-Bus error, @value remains unset and @error
795  * is filled in instead.
796  *
797  * Returns: %TRUE (filling @value) on success, %FALSE (setting @error)
798  *  on failure
799  * Since: 0.7.13
800  */
801 gboolean
tp_dbus_properties_mixin_get(GObject * self,const gchar * interface_name,const gchar * property_name,GValue * value,GError ** error)802 tp_dbus_properties_mixin_get (GObject *self,
803                               const gchar *interface_name,
804                               const gchar *property_name,
805                               GValue *value,
806                               GError **error)
807 {
808   TpDBusPropertiesMixinIfaceImpl *iface_impl;
809   TpDBusPropertiesMixinPropImpl *prop_impl;
810 
811   g_return_val_if_fail (G_IS_OBJECT (self), FALSE);
812   g_return_val_if_fail (interface_name != NULL, FALSE);
813   g_return_val_if_fail (property_name != NULL, FALSE);
814   g_return_val_if_fail (value != NULL, FALSE);
815 
816   iface_impl = _tp_dbus_properties_mixin_find_iface_impl (self,
817       interface_name);
818 
819   if (iface_impl == NULL)
820     {
821       g_set_error (error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
822           "No properties known for interface %s", interface_name);
823       return FALSE;
824     }
825 
826   prop_impl = _iface_impl_get_property_impl (self, iface_impl, interface_name,
827       property_name, error);
828 
829   if (prop_impl != NULL)
830     {
831       TpDBusPropertiesMixinIfaceInfo *iface_info = iface_impl->mixin_priv;
832       TpDBusPropertiesMixinPropInfo *prop_info = prop_impl->mixin_priv;
833 
834       g_value_init (value, prop_info->type);
835       iface_impl->getter (self, iface_info->dbus_interface,
836           prop_info->name, value, prop_impl->getter_data);
837       return TRUE;
838     }
839   else
840     {
841       return FALSE;
842     }
843 }
844 
845 
846 static void
tp_dbus_properties_mixin_fill_properties_hash_va(GObject * object,GHashTable * table,const gchar * first_interface,const gchar * first_property,va_list ap)847 tp_dbus_properties_mixin_fill_properties_hash_va (
848     GObject *object,
849     GHashTable *table,
850     const gchar *first_interface,
851     const gchar *first_property,
852     va_list ap)
853 {
854   const gchar *iface, *property;
855   gboolean first = TRUE;
856 
857   for (iface = first_interface;
858        iface != NULL;
859        iface = va_arg (ap, gchar *))
860     {
861       GValue *value = g_slice_new0 (GValue);
862       GError *error = NULL;
863 
864       if (first)
865         {
866           property = first_property;
867           first = FALSE;
868         }
869       else
870         {
871           property = va_arg (ap, gchar *);
872         }
873 
874       /* If property is NULL, the caller might have omitted a comma or
875        * something; in any case, it shouldn't be.
876        */
877       g_assert (property != NULL);
878 
879       if (tp_dbus_properties_mixin_get (object, iface, property, value,
880               &error))
881         {
882           g_assert (G_IS_VALUE (value));
883           g_hash_table_insert (table,
884               g_strdup_printf ("%s.%s", iface, property), value);
885         }
886       else
887         {
888           /* This is bad and definitely indicates a programming error. */
889           CRITICAL ("Couldn't fetch '%s' on interface '%s': %s",
890               property, iface, error->message);
891           g_clear_error (&error);
892         }
893 
894     }
895 }
896 
897 /**
898  * tp_dbus_properties_mixin_fill_properties_hash: (skip)
899  * @object: an object which uses the D-Bus properties mixin
900  * @table: (element-type utf8 GObject.Value): a hash table where the keys are
901  *  strings copied with g_strdup() and the values are slice-allocated
902  *  #GValue<!-- -->s
903  * @first_interface: the interface of the first property to be retrieved
904  * @first_property: the name of the first property to be retrieved
905  * @...: more (interface name, property name) pairs, terminated by %NULL.
906  *
907  * Retrieves the values of several D-Bus properties from an object, and adds
908  * them to a hash mapping the fully-qualified name of the property to its
909  * value. This is equivalent to calling tp_dbus_properties_mixin_get() for
910  * each property and adding it to the table yourself, with the proviso that
911  * this function will g_assert() if retrieving a property fails (for instance,
912  * because it does not exist).
913  *
914  * Note that in particular, @table does not have the same memory-allocation
915  * model as the hash tables required by tp_asv_set_string() and similar
916  * functions.
917  *
918  * Since: 0.11.11
919  */
920 void
tp_dbus_properties_mixin_fill_properties_hash(GObject * object,GHashTable * table,const gchar * first_interface,const gchar * first_property,...)921 tp_dbus_properties_mixin_fill_properties_hash (
922     GObject *object,
923     GHashTable *table,
924     const gchar *first_interface,
925     const gchar *first_property,
926     ...)
927 {
928   va_list ap;
929 
930   va_start (ap, first_property);
931   tp_dbus_properties_mixin_fill_properties_hash_va (object, table,
932       first_interface, first_property, ap);
933   va_end (ap);
934 }
935 
936 /**
937  * tp_dbus_properties_mixin_make_properties_hash: (skip)
938  * @object: an object which uses the D-Bus properties mixin
939  * @first_interface: the interface of the first property to be retrieved
940  * @first_property: the name of the first property to be retrieved
941  * @...: more (interface name, property name) pairs, terminated by %NULL.
942  *
943  * Retrieves the values of several D-Bus properties from an object, and builds
944  * a hash mapping the fully-qualified name of the property to its value.  This
945  * is equivalent to calling tp_dbus_properties_mixin_get() for each property
946  * and building the table yourself, with the proviso that this function will
947  * g_assert() if retrieving a property fails (for instance, because it does not
948  * exist).
949  *
950  * Additional keys and values can be inserted into the returned hash table;
951  * if this is done, the inserted keys and values will be freed when the
952  * hash table is destroyed. The keys must be allocated with g_strdup() or
953  * equivalent, and the values must be slice-allocated (for instance with
954  * tp_g_value_slice_new_string() or a similar function).
955  *
956  * Note that in particular, tp_asv_set_string() and similar functions should
957  * not be used with this hash table.
958  *
959  * Returns: a hash table mapping (gchar *) fully-qualified property names to
960  *          GValues, which must be freed by the caller (at which point its
961  *          contents will also be freed).
962  */
963 GHashTable *
tp_dbus_properties_mixin_make_properties_hash(GObject * object,const gchar * first_interface,const gchar * first_property,...)964 tp_dbus_properties_mixin_make_properties_hash (
965     GObject *object,
966     const gchar *first_interface,
967     const gchar *first_property,
968     ...)
969 {
970   GHashTable *table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
971       (GDestroyNotify) tp_g_value_slice_free);
972   va_list ap;
973 
974   va_start (ap, first_property);
975   tp_dbus_properties_mixin_fill_properties_hash_va (object, table,
976       first_interface, first_property, ap);
977   va_end (ap);
978 
979   return table;
980 }
981 
982 /**
983  * tp_dbus_properties_mixin_emit_properties_changed:
984  * @object: an object which uses the D-Bus properties mixin
985  * @interface_name: the interface on which properties have changed
986  * @properties: (allow-none): a %NULL-terminated array of (unqualified)
987  *  property names whose values have changed.
988  *
989  * Emits the PropertiesChanged signal for the provided properties. Depending on
990  * the EmitsChangedSignal annotations in the introspection XML, either the new
991  * value of the property will be included in the signal, or merely the fact
992  * that the property has changed.
993  *
994  * For example, the MPRIS specification defines a TrackList interface with two
995  * properties, one of which is annotated with EmitsChangedSignal=true and one
996  * annotated with EmitsChangedSignal=invalidates. The following call would
997  * include the new value of CanEditTracks and list Tracks as invalidated:
998  *
999  * |[
1000  *    const gchar *properties[] = { "CanEditTracks", "Tracks", NULL };
1001  *
1002  *    tp_dbus_properties_mixin_emit_properties_changed (G_OBJECT (self),
1003  *        "org.mpris.MediaPlayer2.TrackList", properties);
1004  * ]|
1005  *
1006  * It is an error to pass a property to this
1007  * function if the property is annotated with EmitsChangedSignal=false, or is
1008  * unannotated.
1009  *
1010  * Since: 0.15.6
1011  */
1012 void
tp_dbus_properties_mixin_emit_properties_changed(GObject * object,const gchar * interface_name,const gchar * const * properties)1013 tp_dbus_properties_mixin_emit_properties_changed (
1014     GObject *object,
1015     const gchar *interface_name,
1016     const gchar * const *properties)
1017 {
1018   TpDBusPropertiesMixinIfaceImpl *iface_impl;
1019   TpDBusPropertiesMixinIfaceInfo *iface_info;
1020   GHashTable *changed_properties;
1021   GPtrArray *invalidated_properties;
1022   const gchar * const *prop_name;
1023 
1024   g_return_if_fail (interface_name != NULL);
1025   iface_impl = _tp_dbus_properties_mixin_find_iface_impl (object,
1026       interface_name);
1027   g_return_if_fail (iface_impl != NULL);
1028 
1029   iface_info = iface_impl->mixin_priv;
1030 
1031   /* If someone passes no property names, well … that's fine, we have nothing
1032    * to do.
1033    */
1034   if (properties == NULL || properties[0] == NULL)
1035     return;
1036 
1037   changed_properties = g_hash_table_new_full (g_str_hash, g_str_equal,
1038       NULL, (GDestroyNotify) tp_g_value_slice_free);
1039   invalidated_properties = g_ptr_array_new ();
1040 
1041   for (prop_name = properties; *prop_name != NULL; prop_name++)
1042     {
1043       TpDBusPropertiesMixinPropImpl *prop_impl;
1044       TpDBusPropertiesMixinPropInfo *prop_info;
1045       GError *error = NULL;
1046 
1047       prop_impl = _iface_impl_get_property_impl (object, iface_impl,
1048           interface_name, *prop_name, &error);
1049 
1050       if (prop_impl == NULL)
1051         {
1052           WARNING ("Couldn't get value for '%s.%s': %s", interface_name,
1053               *prop_name, error->message);
1054           g_clear_error (&error);
1055           g_return_if_reached ();
1056         }
1057 
1058       prop_info = prop_impl->mixin_priv;
1059 
1060       if (prop_info->flags & TP_DBUS_PROPERTIES_MIXIN_FLAG_EMITS_CHANGED)
1061         {
1062           GValue v = { 0, };
1063 
1064           g_value_init (&v, prop_info->type);
1065           iface_impl->getter (object, iface_info->dbus_interface,
1066               prop_info->name, &v, prop_impl->getter_data);
1067           g_hash_table_insert (changed_properties, (gchar *) *prop_name,
1068               tp_g_value_slice_dup (&v));
1069 
1070           g_value_unset (&v);
1071         }
1072       else if (prop_info->flags &
1073                   TP_DBUS_PROPERTIES_MIXIN_FLAG_EMITS_INVALIDATED)
1074         {
1075           g_ptr_array_add (invalidated_properties, (gchar *) *prop_name);
1076         }
1077       else
1078         {
1079           WARNING ("'%s.%s' is not annotated with EmitsChangedSignal'",
1080               interface_name, *prop_name);
1081         }
1082     }
1083 
1084   g_ptr_array_add (invalidated_properties, NULL);
1085 
1086   tp_svc_dbus_properties_emit_properties_changed (object, interface_name,
1087       changed_properties, (const gchar **) invalidated_properties->pdata);
1088   g_hash_table_unref (changed_properties);
1089   g_ptr_array_unref (invalidated_properties);
1090 }
1091 
1092 /**
1093  * tp_dbus_properties_mixin_emit_properties_changed_varargs: (skip)
1094  * @object: an object which uses the D-Bus properties mixin
1095  * @interface_name: the interface on which properties have changed
1096  * @...: property names (unqualified) whose values have changed, terminated by
1097  *  %NULL.
1098  *
1099  * A shortcut for calling tp_dbus_properties_mixin_emit_properties_changed().
1100  *
1101  * Since: 0.15.6
1102  */
1103 void
tp_dbus_properties_mixin_emit_properties_changed_varargs(GObject * object,const gchar * interface_name,...)1104 tp_dbus_properties_mixin_emit_properties_changed_varargs (
1105     GObject *object,
1106     const gchar *interface_name,
1107     ...)
1108 {
1109   GPtrArray *property_names = g_ptr_array_new ();
1110   char *property_name;
1111   va_list ap;
1112 
1113   va_start (ap, interface_name);
1114   do
1115     {
1116       property_name = va_arg (ap, char *);
1117       g_ptr_array_add (property_names, property_name);
1118     }
1119   while (property_name != NULL);
1120   va_end (ap);
1121 
1122   tp_dbus_properties_mixin_emit_properties_changed (object, interface_name,
1123       (const gchar * const *) property_names->pdata);
1124   g_ptr_array_unref (property_names);
1125 }
1126 
1127 static void
_tp_dbus_properties_mixin_get(TpSvcDBusProperties * iface,const gchar * interface_name,const gchar * property_name,DBusGMethodInvocation * context)1128 _tp_dbus_properties_mixin_get (TpSvcDBusProperties *iface,
1129                                const gchar *interface_name,
1130                                const gchar *property_name,
1131                                DBusGMethodInvocation *context)
1132 {
1133   GObject *self = G_OBJECT (iface);
1134   GValue value = { 0 };
1135   GError *error = NULL;
1136 
1137   if (tp_dbus_properties_mixin_get (self, interface_name, property_name,
1138         &value, &error))
1139     {
1140       tp_svc_dbus_properties_return_from_get (context, &value);
1141       g_value_unset (&value);
1142     }
1143   else
1144     {
1145       dbus_g_method_return_error (context, error);
1146       g_error_free (error);
1147     }
1148 }
1149 
1150 /**
1151  * tp_dbus_properties_mixin_dup_all:
1152  * @self: an object with this mixin
1153  * @interface_name: a D-Bus interface name
1154  *
1155  * Get all the properties of a particular interface. This implementation
1156  * never returns an error: it will return an empty map if the interface
1157  * is unknown.
1158  *
1159  * Returns: (transfer container) (element-type utf8 GObject.Value): a map
1160  *  from property name (without the interface name) to value
1161  * Since: 0.21.2
1162  */
1163 GHashTable *
tp_dbus_properties_mixin_dup_all(GObject * self,const gchar * interface_name)1164 tp_dbus_properties_mixin_dup_all (GObject *self,
1165     const gchar *interface_name)
1166 {
1167   TpDBusPropertiesMixinIfaceImpl *iface_impl;
1168   TpDBusPropertiesMixinIfaceInfo *iface_info;
1169   TpDBusPropertiesMixinPropImpl *prop_impl;
1170   /* no key destructor needed - the keys are immortal */
1171   GHashTable *values = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
1172       (GDestroyNotify) tp_g_value_slice_free);
1173 
1174   iface_impl = _tp_dbus_properties_mixin_find_iface_impl (self,
1175       interface_name);
1176 
1177   if (iface_impl == NULL || iface_impl->getter == NULL)
1178     return values;
1179 
1180   iface_info = iface_impl->mixin_priv;
1181 
1182   for (prop_impl = iface_impl->props;
1183        prop_impl->name != NULL;
1184        prop_impl++)
1185     {
1186       TpDBusPropertiesMixinPropInfo *prop_info = prop_impl->mixin_priv;
1187       GValue *value;
1188 
1189       if ((prop_info->flags & TP_DBUS_PROPERTIES_MIXIN_FLAG_READ) == 0)
1190         continue;
1191 
1192       value = tp_g_value_slice_new (prop_info->type);
1193       iface_impl->getter (self, iface_info->dbus_interface,
1194           prop_info->name, value, prop_impl->getter_data);
1195       g_hash_table_insert (values, (gchar *) prop_impl->name, value);
1196     }
1197 
1198   return values;
1199 }
1200 
1201 static void
_tp_dbus_properties_mixin_get_all_dbus(TpSvcDBusProperties * iface,const gchar * interface_name,DBusGMethodInvocation * context)1202 _tp_dbus_properties_mixin_get_all_dbus (TpSvcDBusProperties *iface,
1203     const gchar *interface_name,
1204     DBusGMethodInvocation *context)
1205 {
1206   GHashTable *values = tp_dbus_properties_mixin_dup_all (G_OBJECT (iface),
1207       interface_name);
1208 
1209   tp_svc_dbus_properties_return_from_get_all (context, values);
1210   g_hash_table_unref (values);
1211 }
1212 
1213 /**
1214  * tp_dbus_properties_mixin_set:
1215  * @self: an object with this mixin
1216  * @interface_name: a D-Bus interface name
1217  * @property_name: a D-Bus property name
1218  * @value: a GValue containing the new value for this property.
1219  * @error: used to return an error on failure
1220  *
1221  * Sets a property to the value specified by @value, as if by
1222  * calling the D-Bus method org.freedesktop.DBus.Properties.Set.
1223  *
1224  * If Set would return a D-Bus error, sets @error and returns %FALSE
1225  *
1226  * Returns: %TRUE on success; %FALSE (setting @error) on failure
1227  * Since: 0.15.8
1228  */
1229 gboolean
tp_dbus_properties_mixin_set(GObject * self,const gchar * interface_name,const gchar * property_name,const GValue * value,GError ** error)1230 tp_dbus_properties_mixin_set (
1231     GObject *self,
1232     const gchar *interface_name,
1233     const gchar *property_name,
1234     const GValue *value,
1235     GError **error)
1236 {
1237   TpDBusPropertiesMixinIfaceImpl *iface_impl;
1238   TpDBusPropertiesMixinIfaceInfo *iface_info;
1239   TpDBusPropertiesMixinPropImpl *prop_impl;
1240   TpDBusPropertiesMixinPropInfo *prop_info;
1241   GValue copy = { 0 };
1242   gboolean ret;
1243 
1244   g_return_val_if_fail (G_IS_OBJECT (self), FALSE);
1245   g_return_val_if_fail (interface_name != NULL, FALSE);
1246   g_return_val_if_fail (property_name != NULL, FALSE);
1247   g_return_val_if_fail (G_IS_VALUE (value), FALSE);
1248 
1249   iface_impl = _tp_dbus_properties_mixin_find_iface_impl (self,
1250       interface_name);
1251 
1252   if (iface_impl == NULL)
1253     {
1254       g_set_error (error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
1255           "No properties known for interface '%s'", interface_name);
1256       return FALSE;
1257     }
1258 
1259   iface_info = iface_impl->mixin_priv;
1260 
1261   prop_impl = _tp_dbus_properties_mixin_find_prop_impl (iface_impl,
1262       property_name);
1263 
1264   if (prop_impl == NULL)
1265     {
1266       g_set_error (error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
1267           "Unknown property '%s' on interface '%s'", property_name,
1268           interface_name);
1269       return FALSE;
1270     }
1271 
1272   prop_info = prop_impl->mixin_priv;
1273 
1274   if ((prop_info->flags & TP_DBUS_PROPERTIES_MIXIN_FLAG_WRITE) == 0)
1275     {
1276       g_set_error (error, TP_ERROR, TP_ERROR_PERMISSION_DENIED,
1277           "'%s.%s' is read-only", interface_name, property_name);
1278       return FALSE;
1279     }
1280 
1281   if (iface_impl->setter == NULL)
1282     {
1283       g_set_error (error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
1284           "Setting properties on '%s' is unimplemented", interface_name);
1285       return FALSE;
1286     }
1287 
1288   if (G_VALUE_TYPE (value) != prop_info->type)
1289     {
1290       g_value_init (&copy, prop_info->type);
1291 
1292       if (!g_value_transform (value, &copy))
1293         {
1294           g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1295               "Cannot convert %s to %s for property %s",
1296               g_type_name (G_VALUE_TYPE (value)),
1297               g_type_name (prop_info->type),
1298               property_name);
1299           ret = FALSE;
1300           goto out;
1301         }
1302 
1303       /* use copy instead of value from now on */
1304       value = &copy;
1305     }
1306 
1307   ret = iface_impl->setter (self, iface_info->dbus_interface,
1308         prop_info->name, value, prop_impl->setter_data, error);
1309 
1310 out:
1311   if (G_IS_VALUE (&copy))
1312     g_value_unset (&copy);
1313 
1314   return ret;
1315 }
1316 
1317 static void
_tp_dbus_properties_mixin_set(TpSvcDBusProperties * iface,const gchar * interface_name,const gchar * property_name,const GValue * value,DBusGMethodInvocation * context)1318 _tp_dbus_properties_mixin_set (TpSvcDBusProperties *iface,
1319                                const gchar *interface_name,
1320                                const gchar *property_name,
1321                                const GValue *value,
1322                                DBusGMethodInvocation *context)
1323 {
1324   GObject *self = G_OBJECT (iface);
1325   GError *error = NULL;
1326 
1327   if (tp_dbus_properties_mixin_set (self, interface_name, property_name, value,
1328           &error))
1329     {
1330       tp_svc_dbus_properties_return_from_set (context);
1331     }
1332   else
1333     {
1334       dbus_g_method_return_error (context, error);
1335       g_error_free (error);
1336     }
1337 }
1338 
1339 /**
1340  * tp_dbus_properties_mixin_iface_init:
1341  * @g_iface: a pointer to a #TpSvcDBusPropertiesClass structure
1342  * @iface_data: ignored
1343  *
1344  * Declare that the DBus.Properties interface represented by @g_iface
1345  * is implemented using this mixin.
1346  */
1347 void
tp_dbus_properties_mixin_iface_init(gpointer g_iface,gpointer iface_data)1348 tp_dbus_properties_mixin_iface_init (gpointer g_iface,
1349                                      gpointer iface_data)
1350 {
1351   TpSvcDBusPropertiesClass *cls = g_iface;
1352 
1353 #define IMPLEMENT(x, suffix) \
1354     tp_svc_dbus_properties_implement_##x (cls, \
1355         _tp_dbus_properties_mixin_##x##suffix)
1356   IMPLEMENT (get,);
1357   IMPLEMENT (get_all,_dbus);
1358   IMPLEMENT (set,);
1359 #undef IMPLEMENT
1360 }
1361