1 /*
2 * presence-mixin.c - Source for TpPresenceMixin
3 * Copyright (C) 2005-2008 Collabora Ltd.
4 * Copyright (C) 2005-2007 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 /**
22 * SECTION:presence-mixin
23 * @title: TpPresenceMixin
24 * @short_description: a mixin implementation of the Presence connection
25 * interface
26 * @see_also: #TpSvcConnectionInterfacePresence
27 *
28 * This mixin can be added to a #TpBaseConnection subclass to implement the
29 * SimplePresence and/or Presence interfaces. Implementing both interfaces
30 * (as described below) is recommended. In particular, you must implement the
31 * old-style Presence interface if compatibility with telepathy-glib
32 * versions older than 0.11.13 is required.
33 *
34 * To use the presence mixin, include a #TpPresenceMixinClass somewhere in your
35 * class structure and a #TpPresenceMixin somewhere in your instance structure,
36 * and call tp_presence_mixin_class_init() from your class_init function,
37 * tp_presence_mixin_init() from your init function or constructor, and
38 * tp_presence_mixin_finalize() from your dispose or finalize function.
39 *
40 * <section>
41 * <title>Implementing SimplePresence</title>
42 * <para>
43 * Since 0.7.13 this mixin supports the entire SimplePresence interface.
44 * You can implement #TpSvcConnectionInterfaceSimplePresence as follows:
45 * <itemizedlist>
46 * <listitem>
47 * <para>use the #TpContactsMixin and
48 * <link linkend="telepathy-glib-dbus-properties-mixin">TpDBusPropertiesMixin</link>;</para>
49 * </listitem>
50 * <listitem>
51 * <para>pass tp_presence_mixin_simple_presence_iface_init() as an
52 * argument to G_IMPLEMENT_INTERFACE(), like so:
53 * </para>
54 * |[
55 * G_DEFINE_TYPE_WITH_CODE (MyConnection, my_connection,
56 * TP_TYPE_BASE_CONNECTION,
57 * // ...
58 * G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
59 * tp_presence_mixin_simple_presence_iface_init);
60 * // ...
61 * )
62 * ]|
63 * </listitem>
64 * <listitem>
65 * <para>
66 * call tp_presence_mixin_simple_presence_init_dbus_properties() in the
67 * #GTypeInfo class_init function;
68 * </para>
69 * </listitem>
70 * <listitem>
71 * <para>
72 * call tp_presence_mixin_simple_presence_register_with_contacts_mixin()
73 * in the #GObjectClass constructed function.
74 * </para>
75 * </listitem>
76 * </itemizedlist>
77 * </para>
78 * </section> <!-- Simple Presence -->
79 * <section>
80 * <title>Implementing old-style Presence</title>
81 * <para>
82 * This mixin also supports a large subset of the deprecated Presence
83 * interface. It does not support protocols where it is possible to set
84 * multiple statuses on yourself at once (all presence statuses will have the
85 * exclusive flag set), or last-activity-time information.
86 * </para>
87 * <para>
88 * To use the presence mixin as the implementation of
89 * #TpSvcConnectionInterfacePresence, use tp_presence_mixin_iface_init() as
90 * the function you pass to G_IMPLEMENT_INTERFACE(), as in the following
91 * example. The presence mixin implements all of the D-Bus methods in the
92 * Presence interface.
93 * </para>
94 * |[
95 * G_DEFINE_TYPE_WITH_CODE (MyConnection, my_connection,
96 * TP_TYPE_BASE_CONNECTION,
97 * // ...
98 * G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE,
99 * tp_presence_mixin_iface_init);
100 * // ...
101 * )
102 * ]|
103 * <para>
104 * In telepathy-glib versions older than 0.11.13, every connection
105 * that used the #TpPresenceMixin was required to implement
106 * #TpSvcConnectionInterfacePresence; failing to do so would lead to an
107 * assertion failure. Since 0.11.13, this is no longer required.
108 * </para>
109 * </section> <!-- complex Presence -->
110 *
111 * Since: 0.5.13
112 */
113
114 /**
115 * TpPresenceStatusOptionalArgumentSpec:
116 * @name: Name of the argument as passed over D-Bus
117 * @dtype: D-Bus type signature of the argument
118 *
119 * Structure specifying a supported optional argument for a presence status.
120 *
121 * In addition to the fields documented here, there are two gpointer fields
122 * which must currently be %NULL. A meaning may be defined for these in a
123 * future version of telepathy-glib.
124 */
125
126 /**
127 * TpPresenceStatusSpec:
128 * @name: String identifier of the presence status
129 * @presence_type: A type value, as specified by #TpConnectionPresenceType
130 * @self: Indicates if this status may be set on yourself
131 * @optional_arguments: An array of #TpPresenceStatusOptionalArgumentSpec
132 * structures representing the optional arguments for this status, terminated
133 * by a NULL name. If there are no optional arguments for a status, this can
134 * be NULL. In modern Telepathy connection managers, the only optional
135 * argument should be a string (type "s") named "message" on statuses
136 * that have an optional human-readable message. All other optional arguments
137 * are deprecated.
138 *
139 * Structure specifying a supported presence status.
140 *
141 * In addition to the fields documented here, there are two gpointer fields
142 * which must currently be %NULL. A meaning may be defined for these in a
143 * future version of telepathy-glib.
144 */
145
146 /**
147 * TpPresenceStatus:
148 * @index: Index of the presence status in the provided supported presence
149 * statuses array
150 * @optional_arguments: A GHashTable mapping of string identifiers to GValues
151 * of the optional status arguments, if any. If there are no optional
152 * arguments, this pointer may be NULL.
153 *
154 * Structure representing a presence status.
155 *
156 * In addition to the fields documented here, there are two gpointer fields
157 * which must currently be %NULL. A meaning may be defined for these in a
158 * future version of telepathy-glib.
159 *
160 * In modern Telepathy connection managers, the only optional
161 * argument should be a %G_TYPE_STRING named "message", on statuses
162 * that have an optional human-readable message. All other optional arguments
163 * are deprecated.
164 */
165
166 /**
167 * TpPresenceMixinStatusAvailableFunc:
168 * @obj: An instance of a #TpBaseConnection subclass implementing the presence
169 * interface with this mixin
170 * @which: An index into the array of #TpPresenceStatusSpec provided to
171 * tp_presence_mixin_class_init()
172 *
173 * Signature of a callback to be used to determine if a given presence
174 * status can be set on the connection. Most users of this mixin do not need to
175 * supply an implementation of this callback: the value of
176 * #TpPresenceStatusSpec.self is enough to determine whether this is a
177 * user-settable presence, so %NULL should be passed to
178 * tp_presence_mixin_class_init() for this callback.
179 *
180 * One place where this callback may be needed is on XMPP: not all server
181 * implementation support the user becoming invisible. So an XMPP
182 * implementation would implement this function, so that—once connected—the
183 * hidden status is only available if the server supports it. Before the
184 * connection is connected, this callback should return %TRUE for every status
185 * that might possibly be supported: this allows the user to at least try to
186 * sign in as invisible.
187 *
188 * Returns: %TRUE if the status can be set on this connection; %FALSE if not.
189 */
190
191 /**
192 * TpPresenceMixinGetContactStatusesFunc:
193 * @obj: An object with this mixin.
194 * @contacts: An array of #TpHandle for the contacts to get presence status for
195 * @error: Used to return a Telepathy D-Bus error if %NULL is returned
196 *
197 * Signature of the callback used to get the stored presence status of
198 * contacts. The returned hash table should have contact handles mapped to
199 * their respective presence statuses in #TpPresenceStatus structs.
200 *
201 * The returned hash table will be freed with g_hash_table_unref. The
202 * callback is responsible for ensuring that this does any cleanup that
203 * may be necessary.
204 *
205 * Returns: (transfer full): The contact presence on success, %NULL with
206 * error set on error
207 */
208
209 /**
210 * TpPresenceMixinSetOwnStatusFunc:
211 * @obj: An object with this mixin.
212 * @status: The status to set, or NULL for whatever the protocol defines as a
213 * "default" status
214 * @error: Used to return a Telepathy D-Bus error if %FALSE is returned
215 *
216 * Signature of the callback used to commit changes to the user's own presence
217 * status in SetStatuses. It is also used in ClearStatus and RemoveStatus to
218 * reset the user's own status back to the "default" one with a %NULL status
219 * argument.
220 *
221 * The optional_arguments hash table in @status, if not NULL, will have been
222 * filtered so it only contains recognised parameters, so the callback
223 * need not (and cannot) check for unrecognised parameters. However, the
224 * types of the parameters are not currently checked, so the callback is
225 * responsible for doing so.
226 *
227 * The callback is responsible for emitting PresenceUpdate, if appropriate,
228 * by calling tp_presence_mixin_emit_presence_update().
229 *
230 * Returns: %TRUE if the operation was successful, %FALSE if not.
231 */
232
233 /**
234 * TpPresenceMixinGetMaximumStatusMessageLengthFunc:
235 * @obj: An object with this mixin.
236 *
237 * Signature of a callback used to determine the maximum length of status
238 * messages. If this callback is provided and returns non-zero, the
239 * #TpPresenceMixinSetOwnStatusFunc implementation is responsible for
240 * truncating the message to fit this limit, if necessary.
241 *
242 * Returns: the maximum number of UTF-8 characters which may appear in a status
243 * message, or 0 if there is no limit.
244 * Since: 0.14.5
245 */
246
247 /**
248 * TpPresenceMixinClass:
249 * @status_available: The status-available function that was passed to
250 * tp_presence_mixin_class_init()
251 * @get_contact_statuses: The get-contact-statuses function that was passed to
252 * tp_presence_mixin_class_init()
253 * @set_own_status: The set-own-status function that was passed to
254 * tp_presence_mixin_class_init()
255 * @statuses: The presence statuses array that was passed to
256 * tp_presence_mixin_class_init()
257 * @get_maximum_status_message_length: The callback used to discover the
258 * the limit for status messages length, if any. Since: 0.14.5
259 *
260 * Structure to be included in the class structure of objects that
261 * use this mixin. Initialize it with tp_presence_mixin_class_init().
262 *
263 * If the protocol imposes a limit on the length of status messages, one should
264 * implement @get_maximum_status_message_length. If this callback is not
265 * implemented, it is assumed that there is no limit. The callback function
266 * should be set after calling tp_presence_mixin_class_init(), like so:
267 *
268 * |[
269 * TpPresenceMixinClass *mixin_class;
270 *
271 * tp_presence_mixin_class_init ((GObjectClass *) klass,
272 * G_STRUCT_OFFSET (SomeObjectClass, presence_mixin));
273 * mixin_class = TP_PRESENCE_MIXIN_CLASS (klass);
274 * mixin_class->get_maximum_status_message_length =
275 * some_object_get_maximum_status_message_length;
276 * ]|
277 *
278 * All other fields should be considered read-only.
279 */
280
281 /**
282 * TpPresenceMixin:
283 *
284 * Structure to be included in the instance structure of objects that
285 * use this mixin. Initialize it with tp_presence_mixin_init().
286 *
287 * There are no public fields.
288 */
289
290 #include "config.h"
291
292 #include <telepathy-glib/presence-mixin.h>
293
294 #include <dbus/dbus-glib.h>
295 #include <string.h>
296
297 #include <telepathy-glib/base-connection.h>
298 #include <telepathy-glib/enums.h>
299 #include <telepathy-glib/errors.h>
300 #include <telepathy-glib/gtypes.h>
301 #include <telepathy-glib/interfaces.h>
302 #include <telepathy-glib/contacts-mixin.h>
303
304 #define DEBUG_FLAG TP_DEBUG_PRESENCE
305
306 #include "debug-internal.h"
307
308
309 static GHashTable *construct_simple_presence_hash (
310 const TpPresenceStatusSpec *supported_statuses,
311 GHashTable *contact_statuses);
312
313 /*
314 * deep_copy_hashtable
315 *
316 * Make a deep copy of a GHashTable.
317 */
318 static GHashTable *
deep_copy_hashtable(GHashTable * hash_table)319 deep_copy_hashtable (GHashTable *hash_table)
320 {
321 GValue value = {0, };
322
323 if (!hash_table)
324 return NULL;
325
326 g_value_init (&value, TP_HASH_TYPE_STRING_VARIANT_MAP);
327 g_value_take_boxed (&value, hash_table);
328 return g_value_dup_boxed (&value);
329 }
330
331
332 /**
333 * tp_presence_status_new: (skip)
334 * @which: Index of the presence status in the provided supported presence
335 * statuses array
336 * @optional_arguments: Optional arguments for the presence statuses. Can be
337 * NULL if there are no optional arguments. The presence status object makes a
338 * copy of the hashtable, so you should free the original.
339 *
340 * Construct a presence status structure. You should free the returned
341 * structure with #tp_presence_status_free.
342 *
343 * In modern Telepathy connection managers, the only optional
344 * argument should be a %G_TYPE_STRING named "message", on statuses
345 * that have an optional human-readable message. All other optional arguments
346 * are deprecated.
347 *
348 * Returns: A pointer to the newly allocated presence status structure.
349 */
350 TpPresenceStatus *
tp_presence_status_new(guint which,GHashTable * optional_arguments)351 tp_presence_status_new (guint which,
352 GHashTable *optional_arguments)
353 {
354 TpPresenceStatus *status = g_slice_new (TpPresenceStatus);
355
356 status->index = which;
357 status->optional_arguments = deep_copy_hashtable (optional_arguments);
358
359 return status;
360 }
361
362
363 /**
364 * tp_presence_status_free: (skip)
365 * @status: A pointer to the presence status structure to free.
366 *
367 * Deallocate all resources associated with a presence status structure.
368 */
369 void
tp_presence_status_free(TpPresenceStatus * status)370 tp_presence_status_free (TpPresenceStatus *status)
371 {
372 if (!status)
373 return;
374
375 if (status->optional_arguments)
376 g_hash_table_unref (status->optional_arguments);
377
378 g_slice_free (TpPresenceStatus, status);
379 }
380
381
382 /**
383 * tp_presence_mixin_class_get_offset_quark: (skip)
384 *
385 * <!--no documentation beyond Returns: needed-->
386 *
387 * Returns: the quark used for storing mixin offset on a GObjectClass
388 */
389 GQuark
tp_presence_mixin_class_get_offset_quark()390 tp_presence_mixin_class_get_offset_quark ()
391 {
392 static GQuark offset_quark = 0;
393 if (!offset_quark)
394 offset_quark = g_quark_from_static_string ("TpPresenceMixinClassOffsetQuark");
395 return offset_quark;
396 }
397
398 /**
399 * tp_presence_mixin_get_offset_quark: (skip)
400 *
401 * <!--no documentation beyond Returns: needed-->
402 *
403 * Returns: the quark used for storing mixin offset on a GObject
404 */
405 GQuark
tp_presence_mixin_get_offset_quark()406 tp_presence_mixin_get_offset_quark ()
407 {
408 static GQuark offset_quark = 0;
409 if (!offset_quark)
410 offset_quark = g_quark_from_static_string ("TpPresenceMixinOffsetQuark");
411 return offset_quark;
412 }
413
414 /**
415 * tp_presence_mixin_class_init: (skip)
416 * @obj_cls: The class of the implementation that uses this mixin
417 * @offset: The byte offset of the TpPresenceMixinClass within the class
418 * structure
419 * @status_available: A callback to be used to determine if a given presence
420 * status can be set on a particular connection. Should usually be %NULL, to
421 * consider all statuses with #TpPresenceStatusSpec.self set to %TRUE to be
422 * settable.
423 * @get_contact_statuses: A callback to be used get the current presence status
424 * for contacts. This is used in implementations of various D-Bus methods and
425 * hence must be provided.
426 * @set_own_status: A callback to be used to commit changes to the user's own
427 * presence status to the server. This is used in implementations of various
428 * D-Bus methods and hence must be provided.
429 * @statuses: An array of #TpPresenceStatusSpec structures representing all
430 * presence statuses supported by the protocol, terminated by a NULL name.
431 *
432 * Initialize the presence mixin. Should be called from the implementation's
433 * class_init function like so:
434 *
435 * <informalexample><programlisting>
436 * tp_presence_mixin_class_init ((GObjectClass *) klass,
437 * G_STRUCT_OFFSET (SomeObjectClass,
438 * presence_mixin));
439 * </programlisting></informalexample>
440 */
441
442 void
tp_presence_mixin_class_init(GObjectClass * obj_cls,glong offset,TpPresenceMixinStatusAvailableFunc status_available,TpPresenceMixinGetContactStatusesFunc get_contact_statuses,TpPresenceMixinSetOwnStatusFunc set_own_status,const TpPresenceStatusSpec * statuses)443 tp_presence_mixin_class_init (GObjectClass *obj_cls,
444 glong offset,
445 TpPresenceMixinStatusAvailableFunc status_available,
446 TpPresenceMixinGetContactStatusesFunc get_contact_statuses,
447 TpPresenceMixinSetOwnStatusFunc set_own_status,
448 const TpPresenceStatusSpec *statuses)
449 {
450 TpPresenceMixinClass *mixin_cls;
451 guint i;
452
453 DEBUG ("called.");
454
455 g_assert (get_contact_statuses != NULL);
456 g_assert (set_own_status != NULL);
457 g_assert (statuses != NULL);
458
459 g_assert (G_IS_OBJECT_CLASS (obj_cls));
460
461 g_type_set_qdata (G_OBJECT_CLASS_TYPE (obj_cls),
462 TP_PRESENCE_MIXIN_CLASS_OFFSET_QUARK,
463 GINT_TO_POINTER (offset));
464
465 mixin_cls = TP_PRESENCE_MIXIN_CLASS (obj_cls);
466
467 mixin_cls->status_available = status_available;
468 mixin_cls->get_contact_statuses = get_contact_statuses;
469 mixin_cls->set_own_status = set_own_status;
470 mixin_cls->statuses = statuses;
471 mixin_cls->get_maximum_status_message_length = NULL;
472
473 for (i = 0; statuses[i].name != NULL; i++)
474 {
475 if (statuses[i].self)
476 {
477 switch (statuses[i].presence_type)
478 {
479 case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
480 case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
481 case TP_CONNECTION_PRESENCE_TYPE_ERROR:
482 WARNING ("Status \"%s\" of type %u should not be available "
483 "to set on yourself", statuses[i].name,
484 statuses[i].presence_type);
485 break;
486
487 default:
488 break;
489 }
490 }
491 }
492 }
493
494 /**
495 * tp_presence_mixin_init: (skip)
496 * @obj: An instance of the implementation that uses this mixin
497 * @offset: The byte offset of the TpPresenceMixin within the object structure
498 *
499 * Initialize the presence mixin. Should be called from the implementation's
500 * instance init function like so:
501 *
502 * <informalexample><programlisting>
503 * tp_presence_mixin_init ((GObject *) self,
504 * G_STRUCT_OFFSET (SomeObject, presence_mixin));
505 * </programlisting></informalexample>
506 */
507 void
tp_presence_mixin_init(GObject * obj,glong offset)508 tp_presence_mixin_init (GObject *obj,
509 glong offset)
510 {
511 DEBUG ("called.");
512
513 g_assert (G_IS_OBJECT (obj));
514
515 g_type_set_qdata (G_OBJECT_TYPE (obj),
516 TP_PRESENCE_MIXIN_OFFSET_QUARK,
517 GINT_TO_POINTER (offset));
518 }
519
520 /**
521 * tp_presence_mixin_finalize: (skip)
522 * @obj: An object with this mixin.
523 *
524 * Free resources held by the presence mixin.
525 */
526 void
tp_presence_mixin_finalize(GObject * obj)527 tp_presence_mixin_finalize (GObject *obj)
528 {
529 DEBUG ("%p", obj);
530
531 /* free any data held directly by the object here */
532 }
533
534 static void
construct_presence_hash_foreach(GHashTable * presence_hash,const TpPresenceStatusSpec * supported_statuses,TpHandle handle,TpPresenceStatus * status)535 construct_presence_hash_foreach (
536 GHashTable *presence_hash,
537 const TpPresenceStatusSpec *supported_statuses,
538 TpHandle handle,
539 TpPresenceStatus *status)
540 {
541 GHashTable *parameters;
542 GHashTable *contact_status;
543 GValueArray *vals;
544
545 contact_status = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
546 (GDestroyNotify) g_hash_table_unref);
547
548 parameters = deep_copy_hashtable (status->optional_arguments);
549
550 if (!parameters)
551 parameters = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
552
553 g_hash_table_insert (contact_status,
554 (gpointer) supported_statuses[status->index].name, parameters);
555
556 vals = tp_value_array_build (2,
557 G_TYPE_UINT, 0,
558 TP_HASH_TYPE_MULTIPLE_STATUS_MAP, contact_status,
559 G_TYPE_INVALID);
560 g_hash_table_unref (contact_status);
561
562 g_hash_table_insert (presence_hash, GUINT_TO_POINTER (handle), vals);
563 }
564
565
566 static GHashTable *
construct_presence_hash(const TpPresenceStatusSpec * supported_statuses,GHashTable * contact_statuses)567 construct_presence_hash (const TpPresenceStatusSpec *supported_statuses,
568 GHashTable *contact_statuses)
569 {
570 GHashTable *presence_hash = g_hash_table_new_full (NULL, NULL, NULL,
571 (GDestroyNotify) tp_value_array_free);
572 GHashTableIter iter;
573 gpointer key, value;
574
575 DEBUG ("called.");
576
577 g_hash_table_iter_init (&iter, contact_statuses);
578 while (g_hash_table_iter_next (&iter, &key, &value))
579 construct_presence_hash_foreach (presence_hash, supported_statuses,
580 GPOINTER_TO_UINT (key), value);
581
582 return presence_hash;
583 }
584
585
586 /**
587 * tp_presence_mixin_emit_presence_update: (skip)
588 * @obj: A connection object with this mixin
589 * @contact_presences: A mapping of contact handles to #TpPresenceStatus
590 * structures with the presence data to emit
591 *
592 * Emit the PresenceUpdate signal for multiple contacts. For emitting
593 * PresenceUpdate for a single contact, there is a convenience wrapper called
594 * #tp_presence_mixin_emit_one_presence_update.
595 */
596 void
tp_presence_mixin_emit_presence_update(GObject * obj,GHashTable * contact_statuses)597 tp_presence_mixin_emit_presence_update (GObject *obj,
598 GHashTable *contact_statuses)
599 {
600 TpPresenceMixinClass *mixin_cls =
601 TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
602 GHashTable *presence_hash;
603
604 DEBUG ("called.");
605
606 if (g_type_interface_peek (G_OBJECT_GET_CLASS (obj),
607 TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE) != NULL)
608 {
609 presence_hash = construct_presence_hash (mixin_cls->statuses,
610 contact_statuses);
611 tp_svc_connection_interface_presence_emit_presence_update (obj,
612 presence_hash);
613
614 g_hash_table_unref (presence_hash);
615 }
616
617 if (g_type_interface_peek (G_OBJECT_GET_CLASS (obj),
618 TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE) != NULL)
619 {
620 presence_hash = construct_simple_presence_hash (mixin_cls->statuses,
621 contact_statuses);
622 tp_svc_connection_interface_simple_presence_emit_presences_changed (obj,
623 presence_hash);
624
625 g_hash_table_unref (presence_hash);
626 }
627 }
628
629
630 /**
631 * tp_presence_mixin_emit_one_presence_update: (skip)
632 * @obj: A connection object with this mixin
633 * @handle: The handle of the contact to emit the signal for
634 * @status: The new status to emit
635 *
636 * Emit the PresenceUpdate signal for a single contact. This method is just a
637 * convenience wrapper around #tp_presence_mixin_emit_presence_update.
638 */
639 void
tp_presence_mixin_emit_one_presence_update(GObject * obj,TpHandle handle,const TpPresenceStatus * status)640 tp_presence_mixin_emit_one_presence_update (GObject *obj,
641 TpHandle handle,
642 const TpPresenceStatus *status)
643 {
644 GHashTable *contact_statuses;
645
646 DEBUG ("called.");
647
648 contact_statuses = g_hash_table_new (NULL, NULL);
649 g_hash_table_insert (contact_statuses, GUINT_TO_POINTER (handle),
650 (gpointer) status);
651 tp_presence_mixin_emit_presence_update (obj, contact_statuses);
652
653 g_hash_table_unref (contact_statuses);
654 }
655
656
657 /*
658 * tp_presence_mixin_add_status:
659 *
660 * Implements D-Bus method AddStatus
661 * on interface org.freedesktop.Telepathy.Connection.Interface.Presence
662 */
663 static void
tp_presence_mixin_add_status(TpSvcConnectionInterfacePresence * iface,const gchar * status,GHashTable * parms,DBusGMethodInvocation * context)664 tp_presence_mixin_add_status (TpSvcConnectionInterfacePresence *iface,
665 const gchar *status,
666 GHashTable *parms,
667 DBusGMethodInvocation *context)
668 {
669 TpBaseConnection *conn = TP_BASE_CONNECTION (iface);
670 GError error = { TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
671 "Only one status is possible at a time with this protocol!" };
672
673 DEBUG ("called.");
674
675 TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
676
677 dbus_g_method_return_error (context, &error);
678 }
679
680
681 /*
682 * tp_presence_mixin_clear_status:
683 *
684 * Implements D-Bus method ClearStatus
685 * on interface org.freedesktop.Telepathy.Connection.Interface.Presence
686 */
687 static void
tp_presence_mixin_clear_status(TpSvcConnectionInterfacePresence * iface,DBusGMethodInvocation * context)688 tp_presence_mixin_clear_status (TpSvcConnectionInterfacePresence *iface,
689 DBusGMethodInvocation *context)
690 {
691 GObject *obj = (GObject *) iface;
692 TpBaseConnection *conn = TP_BASE_CONNECTION (iface);
693 TpPresenceMixinClass *mixin_cls =
694 TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
695 GError *error = NULL;
696
697 DEBUG ("called.");
698
699 TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
700
701 if (!mixin_cls->set_own_status (obj, NULL, &error))
702 {
703 dbus_g_method_return_error (context, error);
704 g_error_free (error);
705 return;
706 }
707
708 tp_svc_connection_interface_presence_return_from_clear_status (context);
709 }
710
711
712 /*
713 * tp_presence_mixin_get_presence:
714 *
715 * Implements D-Bus method GetPresence
716 * on interface org.freedesktop.Telepathy.Connection.Interface.Presence
717 */
718 static void
tp_presence_mixin_get_presence(TpSvcConnectionInterfacePresence * iface,const GArray * contacts,DBusGMethodInvocation * context)719 tp_presence_mixin_get_presence (TpSvcConnectionInterfacePresence *iface,
720 const GArray *contacts,
721 DBusGMethodInvocation *context)
722 {
723 GObject *obj = (GObject *) iface;
724 TpBaseConnection *conn = TP_BASE_CONNECTION (obj);
725 TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
726 TP_HANDLE_TYPE_CONTACT);
727 TpPresenceMixinClass *mixin_cls =
728 TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
729 GHashTable *contact_statuses;
730 GHashTable *presence_hash;
731 GError *error = NULL;
732
733 DEBUG ("called.");
734
735 TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
736
737 if (contacts->len == 0)
738 {
739 presence_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
740 tp_svc_connection_interface_presence_return_from_get_presence (context,
741 presence_hash);
742 g_hash_table_unref (presence_hash);
743 return;
744 }
745
746 if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
747 {
748 dbus_g_method_return_error (context, error);
749 g_error_free (error);
750 return;
751 }
752
753 contact_statuses = mixin_cls->get_contact_statuses (obj, contacts, &error);
754
755 if (!contact_statuses)
756 {
757 dbus_g_method_return_error (context, error);
758 g_error_free (error);
759 return;
760 }
761
762 presence_hash = construct_presence_hash (mixin_cls->statuses,
763 contact_statuses);
764 tp_svc_connection_interface_presence_return_from_get_presence (context,
765 presence_hash);
766 g_hash_table_unref (presence_hash);
767 g_hash_table_unref (contact_statuses);
768 }
769
770
771 static GHashTable *
get_statuses_arguments(const TpPresenceStatusOptionalArgumentSpec * specs)772 get_statuses_arguments (const TpPresenceStatusOptionalArgumentSpec *specs)
773 {
774 GHashTable *arguments = g_hash_table_new (g_str_hash, g_str_equal);
775 int i;
776
777 for (i=0; specs != NULL && specs[i].name != NULL; i++)
778 g_hash_table_insert (arguments, (gchar *) specs[i].name,
779 (gchar *) specs[i].dtype);
780
781 return arguments;
782 }
783
784 static gboolean
check_status_available(GObject * object,TpPresenceMixinClass * mixin_cls,guint i,GError ** error,gboolean for_self)785 check_status_available (GObject *object,
786 TpPresenceMixinClass *mixin_cls,
787 guint i,
788 GError **error,
789 gboolean for_self)
790 {
791 if (for_self)
792 {
793 if (!mixin_cls->statuses[i].self)
794 {
795 g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
796 "cannot set status '%s' on yourself",
797 mixin_cls->statuses[i].name);
798 return FALSE;
799 }
800
801 /* never allow OFFLINE, UNKNOWN or ERROR - if the CM says they're
802 * OK to set on yourself, then it's wrong */
803 switch (mixin_cls->statuses[i].presence_type)
804 {
805 case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
806 case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
807 case TP_CONNECTION_PRESENCE_TYPE_ERROR:
808 g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
809 "cannot set offline/unknown/error status '%s' on yourself",
810 mixin_cls->statuses[i].name);
811 return FALSE;
812
813 default:
814 break;
815 }
816 }
817
818 if (mixin_cls->status_available
819 && !mixin_cls->status_available (object, i))
820 {
821 DEBUG ("requested status %s is not available",
822 mixin_cls->statuses[i].name);
823 g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
824 "requested status '%s' is not available on this connection",
825 mixin_cls->statuses[i].name);
826 return FALSE;
827 }
828
829 return TRUE;
830 }
831
832 /*
833 * tp_presence_mixin_get_statuses:
834 *
835 * Implements D-Bus method GetStatuses
836 * on interface org.freedesktop.Telepathy.Connection.Interface.Presence
837 */
838 static void
tp_presence_mixin_get_statuses(TpSvcConnectionInterfacePresence * iface,DBusGMethodInvocation * context)839 tp_presence_mixin_get_statuses (TpSvcConnectionInterfacePresence *iface,
840 DBusGMethodInvocation *context)
841 {
842 TpBaseConnection *conn = TP_BASE_CONNECTION (iface);
843 GObject *obj = (GObject *) conn;
844 TpPresenceMixinClass *mixin_cls =
845 TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
846 GHashTable *ret;
847 GValueArray *status;
848 int i;
849
850 DEBUG ("called.");
851
852 TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
853
854 ret = g_hash_table_new_full (g_str_hash, g_str_equal,
855 NULL, (GDestroyNotify) tp_value_array_free);
856
857 for (i=0; mixin_cls->statuses[i].name != NULL; i++)
858 {
859 GHashTable *args;
860
861 /* the spec says we include statuses here even if they're not available
862 * to set on yourself */
863 if (!check_status_available (obj, mixin_cls, i, NULL, FALSE))
864 continue;
865
866 args = get_statuses_arguments (mixin_cls->statuses[i].optional_arguments);
867 status = tp_value_array_build (4,
868 G_TYPE_UINT, (guint) mixin_cls->statuses[i].presence_type,
869 G_TYPE_BOOLEAN, mixin_cls->statuses[i].self,
870 G_TYPE_BOOLEAN, TRUE, /* exclusive */
871 DBUS_TYPE_G_STRING_STRING_HASHTABLE, args,
872 G_TYPE_INVALID);
873 g_hash_table_unref (args);
874
875 g_hash_table_insert (ret, (gchar *) mixin_cls->statuses[i].name,
876 status);
877 }
878
879 tp_svc_connection_interface_presence_return_from_get_statuses (context, ret);
880 g_hash_table_unref (ret);
881 }
882
883
884 /*
885 * tp_presence_mixin_set_last_activity_time:
886 *
887 * Implements D-Bus method SetLastActivityTime
888 * on interface org.freedesktop.Telepathy.Connection.Interface.Presence
889 */
890 static void
tp_presence_mixin_set_last_activity_time(TpSvcConnectionInterfacePresence * iface,guint timestamp,DBusGMethodInvocation * context)891 tp_presence_mixin_set_last_activity_time (TpSvcConnectionInterfacePresence *iface,
892 guint timestamp,
893 DBusGMethodInvocation *context)
894 {
895 TpBaseConnection *conn = TP_BASE_CONNECTION (iface);
896
897 TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
898
899 tp_svc_connection_interface_presence_return_from_set_last_activity_time (
900 context);
901 }
902
903
904 /*
905 * tp_presence_mixin_remove_status:
906 *
907 * Implements D-Bus method RemoveStatus
908 * on interface org.freedesktop.Telepathy.Connection.Interface.Presence
909 */
910 static void
tp_presence_mixin_remove_status(TpSvcConnectionInterfacePresence * iface,const gchar * status,DBusGMethodInvocation * context)911 tp_presence_mixin_remove_status (TpSvcConnectionInterfacePresence *iface,
912 const gchar *status,
913 DBusGMethodInvocation *context)
914 {
915 GObject *obj = (GObject *) iface;
916 TpBaseConnection *conn = TP_BASE_CONNECTION (iface);
917 TpPresenceMixinClass *mixin_cls =
918 TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
919 GArray *self_contacts;
920 GError *error = NULL;
921 GHashTable *self_contact_statuses;
922 TpPresenceStatus *self_status;
923 TpHandle self_handle;
924
925 DEBUG ("called.");
926
927 TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
928
929 self_contacts = g_array_sized_new (TRUE, TRUE, sizeof (TpHandle), 1);
930 self_handle = tp_base_connection_get_self_handle (conn);
931 g_array_append_val (self_contacts, self_handle);
932 self_contact_statuses = mixin_cls->get_contact_statuses (obj, self_contacts,
933 &error);
934
935 if (!self_contact_statuses)
936 {
937 dbus_g_method_return_error (context, error);
938 g_error_free (error);
939 g_array_unref (self_contacts);
940 return;
941 }
942
943 self_status = (TpPresenceStatus *) g_hash_table_lookup (self_contact_statuses,
944 GUINT_TO_POINTER (tp_base_connection_get_self_handle (conn)));
945
946 if (!self_status)
947 {
948 DEBUG ("Got no self status, assuming we already have default status");
949 g_array_unref (self_contacts);
950 g_hash_table_unref (self_contact_statuses);
951 tp_svc_connection_interface_presence_return_from_remove_status (context);
952 return;
953 }
954
955 if (!tp_strdiff (status, mixin_cls->statuses[self_status->index].name))
956 {
957 if (mixin_cls->set_own_status (obj, NULL, &error))
958 {
959 tp_svc_connection_interface_presence_return_from_remove_status (context);
960 }
961 else
962 {
963 dbus_g_method_return_error (context, error);
964 g_error_free (error);
965 }
966 }
967 else
968 {
969 GError nonexistent = { TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
970 "Attempting to remove non-existent presence." };
971 dbus_g_method_return_error (context, &nonexistent);
972 }
973
974 g_array_unref (self_contacts);
975 g_hash_table_unref (self_contact_statuses);
976 }
977
978
979 /*
980 * tp_presence_mixin_request_presence:
981 *
982 * Implements D-Bus method RequestPresence
983 * on interface org.freedesktop.Telepathy.Connection.Interface.Presence
984 */
985 static void
tp_presence_mixin_request_presence(TpSvcConnectionInterfacePresence * iface,const GArray * contacts,DBusGMethodInvocation * context)986 tp_presence_mixin_request_presence (TpSvcConnectionInterfacePresence *iface,
987 const GArray *contacts,
988 DBusGMethodInvocation *context)
989 {
990 GObject *obj = (GObject *) iface;
991 TpPresenceMixinClass *mixin_cls =
992 TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
993 TpBaseConnection *conn = TP_BASE_CONNECTION (iface);
994 TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
995 TP_HANDLE_TYPE_CONTACT);
996 GHashTable *contact_statuses;
997 GError *error = NULL;
998
999 DEBUG ("called.");
1000
1001 TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
1002
1003 if (contacts->len == 0)
1004 {
1005 tp_svc_connection_interface_presence_return_from_request_presence (context);
1006 return;
1007 }
1008
1009 if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
1010 {
1011 dbus_g_method_return_error (context, error);
1012 g_error_free (error);
1013 return;
1014 }
1015
1016 contact_statuses = mixin_cls->get_contact_statuses (obj, contacts, &error);
1017
1018 if (!contact_statuses)
1019 {
1020 dbus_g_method_return_error (context, error);
1021 g_error_free (error);
1022 return;
1023 }
1024
1025 tp_presence_mixin_emit_presence_update (obj, contact_statuses);
1026 tp_svc_connection_interface_presence_return_from_request_presence (context);
1027
1028 g_hash_table_unref (contact_statuses);
1029 }
1030
1031 static int
check_for_status(GObject * object,const gchar * status,GError ** error)1032 check_for_status (GObject *object, const gchar *status, GError **error)
1033 {
1034 TpPresenceMixinClass *mixin_cls =
1035 TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (object));
1036 int i;
1037
1038 for (i = 0; mixin_cls->statuses[i].name != NULL; i++)
1039 {
1040 if (!tp_strdiff (mixin_cls->statuses[i].name, status))
1041 break;
1042 }
1043
1044 if (mixin_cls->statuses[i].name != NULL)
1045 {
1046 DEBUG ("Found status \"%s\", checking if it's available...",
1047 (const gchar *) status);
1048
1049 if (!check_status_available (object, mixin_cls, i, error, TRUE))
1050 return -1;
1051 }
1052 else
1053 {
1054 DEBUG ("got unknown status identifier %s", status);
1055 g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1056 "unknown status identifier: %s", status);
1057 return -1;
1058 }
1059
1060 return i;
1061 }
1062
1063 static gboolean
set_status(GObject * obj,const gchar * status_name,GHashTable * provided_arguments,GError ** error)1064 set_status (
1065 GObject *obj,
1066 const gchar *status_name,
1067 GHashTable *provided_arguments,
1068 GError **error)
1069 {
1070 TpPresenceMixinClass *mixin_cls =
1071 TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
1072 TpPresenceStatus status_to_set = { 0, };
1073 int status;
1074 GHashTable *optional_arguments = NULL;
1075 gboolean ret = TRUE;
1076
1077 DEBUG ("called.");
1078
1079 /* This function will actually only be invoked once for one SetStatus request,
1080 * since we check that the hash table has size 1 in
1081 * tp_presence_mixin_set_status(). Therefore there are no problems with
1082 * sharing the foreach data like this.
1083 */
1084 status = check_for_status (obj, status_name, error);
1085
1086 if (status == -1)
1087 return FALSE;
1088
1089 DEBUG ("The status is available.");
1090
1091 if (provided_arguments != NULL)
1092 {
1093 int j;
1094 const TpPresenceStatusOptionalArgumentSpec *specs =
1095 mixin_cls->statuses[status].optional_arguments;
1096
1097 for (j=0; specs != NULL && specs[j].name != NULL; j++)
1098 {
1099 GValue *provided_value =
1100 g_hash_table_lookup (provided_arguments, specs[j].name);
1101 GValue *new_value;
1102
1103 if (!provided_value)
1104 continue;
1105 new_value = tp_g_value_slice_dup (provided_value);
1106
1107 if (!optional_arguments)
1108 optional_arguments =
1109 g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
1110 (GDestroyNotify) tp_g_value_slice_free);
1111
1112 if (DEBUGGING)
1113 {
1114 gchar *value_contents = g_strdup_value_contents (new_value);
1115 DEBUG ("Got optional argument (\"%s\", %s)", specs[j].name,
1116 value_contents);
1117 g_free (value_contents);
1118 }
1119
1120 g_hash_table_insert (optional_arguments,
1121 (gpointer) specs[j].name, new_value);
1122 }
1123 }
1124
1125 status_to_set.index = status;
1126 status_to_set.optional_arguments = optional_arguments;
1127
1128 DEBUG ("About to try setting status \"%s\"",
1129 mixin_cls->statuses[status].name);
1130
1131 ret = mixin_cls->set_own_status (obj, &status_to_set, error);
1132
1133 if (optional_arguments)
1134 g_hash_table_unref (optional_arguments);
1135
1136 return ret;
1137 }
1138
1139
1140 /*
1141 * tp_presence_mixin_set_status:
1142 *
1143 * Implements D-Bus method SetStatus
1144 * on interface org.freedesktop.Telepathy.Connection.Interface.Presence
1145 */
1146 static void
tp_presence_mixin_set_status(TpSvcConnectionInterfacePresence * iface,GHashTable * statuses,DBusGMethodInvocation * context)1147 tp_presence_mixin_set_status (TpSvcConnectionInterfacePresence *iface,
1148 GHashTable *statuses,
1149 DBusGMethodInvocation *context)
1150 {
1151 GObject *obj = (GObject *) iface;
1152 TpBaseConnection *conn = TP_BASE_CONNECTION (iface);
1153 GHashTableIter iter;
1154 gpointer key, value;
1155 GError *error = NULL;
1156
1157 DEBUG ("called.");
1158
1159 TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
1160
1161 g_hash_table_iter_init (&iter, statuses);
1162 if (!g_hash_table_iter_next (&iter, &key, &value) ||
1163 g_hash_table_iter_next (&iter, NULL, NULL))
1164 {
1165 GError invalid = { TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1166 "Only one status may be set at a time in this protocol" };
1167 DEBUG ("got more than one status");
1168 dbus_g_method_return_error (context, &invalid);
1169 return;
1170 }
1171
1172 if (set_status (obj, key, value, &error))
1173 {
1174 tp_svc_connection_interface_presence_return_from_set_status (context);
1175 }
1176 else
1177 {
1178 DEBUG ("failed: %s", error->message);
1179 dbus_g_method_return_error (context, error);
1180 g_error_free (error);
1181 }
1182 }
1183
1184
1185 /**
1186 * tp_presence_mixin_iface_init: (skip)
1187 * @g_iface: A pointer to the #TpSvcConnectionInterfacePresenceClass in an
1188 * object class
1189 * @iface_data: Ignored
1190 *
1191 * Fill in the vtable entries needed to implement the presence interface using
1192 * this mixin. This function should usually be called via G_IMPLEMENT_INTERFACE.
1193 */
1194 void
tp_presence_mixin_iface_init(gpointer g_iface,gpointer iface_data)1195 tp_presence_mixin_iface_init (gpointer g_iface, gpointer iface_data)
1196 {
1197 TpSvcConnectionInterfacePresenceClass *klass = g_iface;
1198
1199 #define IMPLEMENT(x) tp_svc_connection_interface_presence_implement_##x (klass,\
1200 tp_presence_mixin_##x)
1201 IMPLEMENT(add_status);
1202 IMPLEMENT(clear_status);
1203 IMPLEMENT(get_presence);
1204 IMPLEMENT(get_statuses);
1205 IMPLEMENT(remove_status);
1206 IMPLEMENT(request_presence);
1207 IMPLEMENT(set_last_activity_time);
1208 IMPLEMENT(set_status);
1209 #undef IMPLEMENT
1210 }
1211
1212 enum {
1213 MIXIN_DP_SIMPLE_STATUSES,
1214 MIXIN_DP_SIMPLE_MAX_STATUS_MESSAGE_LENGTH,
1215 NUM_MIXIN_SIMPLE_DBUS_PROPERTIES
1216 };
1217
1218 static TpDBusPropertiesMixinPropImpl known_simple_presence_props[] = {
1219 { "Statuses", NULL, NULL },
1220 { "MaximumStatusMessageLength", NULL, NULL },
1221 { NULL }
1222 };
1223
1224 static void
tp_presence_mixin_get_simple_presence_dbus_property(GObject * object,GQuark interface,GQuark name,GValue * value,gpointer unused G_GNUC_UNUSED)1225 tp_presence_mixin_get_simple_presence_dbus_property (GObject *object,
1226 GQuark interface,
1227 GQuark name,
1228 GValue *value,
1229 gpointer unused
1230 G_GNUC_UNUSED)
1231 {
1232 TpPresenceMixinClass *mixin_cls =
1233 TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (object));
1234 static GQuark q[NUM_MIXIN_SIMPLE_DBUS_PROPERTIES] = { 0, };
1235
1236 DEBUG ("called.");
1237
1238 if (G_UNLIKELY (q[0] == 0))
1239 {
1240 q[MIXIN_DP_SIMPLE_STATUSES] = g_quark_from_static_string ("Statuses");
1241 q[MIXIN_DP_SIMPLE_MAX_STATUS_MESSAGE_LENGTH] =
1242 g_quark_from_static_string ("MaximumStatusMessageLength");
1243 }
1244
1245 g_return_if_fail (object != NULL);
1246
1247 if (name == q[MIXIN_DP_SIMPLE_STATUSES])
1248 {
1249 GHashTable *ret;
1250 GValueArray *status;
1251 int i;
1252
1253 g_return_if_fail (G_VALUE_HOLDS_BOXED (value));
1254
1255 ret = g_hash_table_new_full (g_str_hash, g_str_equal,
1256 NULL, (GDestroyNotify) tp_value_array_free);
1257
1258 for (i=0; mixin_cls->statuses[i].name != NULL; i++)
1259 {
1260 gboolean message;
1261
1262 /* we include statuses here even if they're not available
1263 * to set on yourself */
1264 if (!check_status_available (object, mixin_cls, i, NULL, FALSE))
1265 continue;
1266
1267 message = tp_presence_status_spec_has_message (
1268 &mixin_cls->statuses[i]);
1269
1270 status = tp_value_array_build (3,
1271 G_TYPE_UINT, (guint) mixin_cls->statuses[i].presence_type,
1272 G_TYPE_BOOLEAN, mixin_cls->statuses[i].self,
1273 G_TYPE_BOOLEAN, message,
1274 G_TYPE_INVALID);
1275
1276 g_hash_table_insert (ret, (gchar *) mixin_cls->statuses[i].name,
1277 status);
1278 }
1279 g_value_take_boxed (value, ret);
1280 }
1281 else if (name == q[MIXIN_DP_SIMPLE_MAX_STATUS_MESSAGE_LENGTH])
1282 {
1283 guint max_status_message_length = 0;
1284
1285 g_assert (G_VALUE_HOLDS (value, G_TYPE_UINT));
1286
1287 if (mixin_cls->get_maximum_status_message_length != NULL)
1288 max_status_message_length =
1289 mixin_cls->get_maximum_status_message_length (object);
1290
1291 g_value_set_uint (value, max_status_message_length);
1292 }
1293 else
1294 {
1295 g_return_if_reached ();
1296 }
1297
1298 }
1299
1300 /**
1301 * tp_presence_mixin_simple_presence_init_dbus_properties: (skip)
1302 * @cls: The class of an object with this mixin
1303 *
1304 * Set up #TpDBusPropertiesMixinClass to use this mixin's implementation of
1305 * the SimplePresence interface's properties.
1306 *
1307 * This automatically sets up a list of the supported properties for the
1308 * SimplePresence interface.
1309 *
1310 * Since: 0.7.13
1311 */
1312 void
tp_presence_mixin_simple_presence_init_dbus_properties(GObjectClass * cls)1313 tp_presence_mixin_simple_presence_init_dbus_properties (GObjectClass *cls)
1314 {
1315
1316 tp_dbus_properties_mixin_implement_interface (cls,
1317 TP_IFACE_QUARK_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
1318 tp_presence_mixin_get_simple_presence_dbus_property,
1319 NULL, known_simple_presence_props);
1320 }
1321
1322 /*
1323 * tp_presence_mixin_simple_presence_set_presence:
1324 *
1325 * Implements D-Bus method SetPresence
1326 * on interface org.freedesktop.Telepathy.Connection.Interface.SimplePresence
1327 */
1328 static void
tp_presence_mixin_simple_presence_set_presence(TpSvcConnectionInterfaceSimplePresence * iface,const gchar * status,const gchar * message,DBusGMethodInvocation * context)1329 tp_presence_mixin_simple_presence_set_presence (
1330 TpSvcConnectionInterfaceSimplePresence *iface,
1331 const gchar *status,
1332 const gchar *message,
1333 DBusGMethodInvocation *context)
1334 {
1335 GObject *obj = (GObject *) iface;
1336 TpPresenceMixinClass *mixin_cls =
1337 TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
1338 TpPresenceStatus status_to_set = { 0, };
1339 int s;
1340 GError *error = NULL;
1341 GHashTable *optional_arguments = NULL;
1342
1343 DEBUG ("called.");
1344
1345 s = check_for_status (obj, status, &error);
1346 if (s == -1)
1347 goto out;
1348
1349 status_to_set.index = s;
1350
1351 if (*message != '\0')
1352 {
1353 optional_arguments = g_hash_table_new_full (g_str_hash, g_str_equal,
1354 NULL, (GDestroyNotify) tp_g_value_slice_free);
1355 g_hash_table_insert (optional_arguments, "message",
1356 tp_g_value_slice_new_string (message));
1357 status_to_set.optional_arguments = optional_arguments;
1358 }
1359
1360 mixin_cls->set_own_status (obj, &status_to_set, &error);
1361
1362 out:
1363 if (error == NULL)
1364 {
1365 tp_svc_connection_interface_simple_presence_return_from_set_presence (
1366 context);
1367 }
1368 else
1369 {
1370 dbus_g_method_return_error (context, error);
1371 g_error_free (error);
1372 }
1373
1374 if (optional_arguments != NULL)
1375 g_hash_table_unref (optional_arguments);
1376 }
1377
1378 static GValueArray *
construct_simple_presence_value_array(TpPresenceStatus * status,const TpPresenceStatusSpec * supported_statuses)1379 construct_simple_presence_value_array (TpPresenceStatus *status,
1380 const TpPresenceStatusSpec *supported_statuses)
1381 {
1382 TpConnectionPresenceType status_type;
1383 const gchar *status_name;
1384 const gchar *message = NULL;
1385 GValueArray *presence;
1386
1387 status_name = supported_statuses[status->index].name;
1388 status_type = supported_statuses[status->index].presence_type;
1389
1390 if (status->optional_arguments != NULL)
1391 {
1392 GValue *val;
1393 val = g_hash_table_lookup (status->optional_arguments, "message");
1394 if (val != NULL)
1395 message = g_value_get_string (val);
1396 }
1397
1398 if (message == NULL)
1399 message = "";
1400
1401 presence = tp_value_array_build (3,
1402 G_TYPE_UINT, status_type,
1403 G_TYPE_STRING, status_name,
1404 G_TYPE_STRING, message,
1405 G_TYPE_INVALID);
1406
1407 return presence;
1408 }
1409
1410 static void
construct_simple_presence_hash_foreach(GHashTable * presence_hash,const TpPresenceStatusSpec * supported_statuses,TpHandle handle,TpPresenceStatus * status)1411 construct_simple_presence_hash_foreach (
1412 GHashTable *presence_hash,
1413 const TpPresenceStatusSpec *supported_statuses,
1414 TpHandle handle,
1415 TpPresenceStatus *status)
1416 {
1417 GValueArray *presence;
1418
1419 presence = construct_simple_presence_value_array (status, supported_statuses);
1420 g_hash_table_insert (presence_hash, GUINT_TO_POINTER (handle), presence);
1421 }
1422
1423 static GHashTable *
construct_simple_presence_hash(const TpPresenceStatusSpec * supported_statuses,GHashTable * contact_statuses)1424 construct_simple_presence_hash (const TpPresenceStatusSpec *supported_statuses,
1425 GHashTable *contact_statuses)
1426 {
1427 GHashTable *presence_hash = g_hash_table_new_full (NULL, NULL, NULL,
1428 (GDestroyNotify) tp_value_array_free);
1429 GHashTableIter iter;
1430 gpointer key, value;
1431
1432 DEBUG ("called.");
1433
1434 g_hash_table_iter_init (&iter, contact_statuses);
1435 while (g_hash_table_iter_next (&iter, &key, &value))
1436 construct_simple_presence_hash_foreach (presence_hash, supported_statuses,
1437 GPOINTER_TO_UINT (key), value);
1438
1439 return presence_hash;
1440 }
1441
1442 /*
1443 * tp_presence_mixin_get_simple_presence:
1444 *
1445 * Implements D-Bus method GetPresence
1446 * on interface org.freedesktop.Telepathy.Connection.Interface.SimplePresence
1447 */
1448 static void
tp_presence_mixin_simple_presence_get_presences(TpSvcConnectionInterfaceSimplePresence * iface,const GArray * contacts,DBusGMethodInvocation * context)1449 tp_presence_mixin_simple_presence_get_presences (
1450 TpSvcConnectionInterfaceSimplePresence *iface,
1451 const GArray *contacts,
1452 DBusGMethodInvocation *context)
1453 {
1454 GObject *obj = (GObject *) iface;
1455 TpBaseConnection *conn = TP_BASE_CONNECTION (obj);
1456 TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
1457 TP_HANDLE_TYPE_CONTACT);
1458 TpPresenceMixinClass *mixin_cls =
1459 TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
1460 GHashTable *contact_statuses;
1461 GHashTable *presence_hash;
1462 GError *error = NULL;
1463
1464 DEBUG ("called.");
1465
1466 TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
1467
1468 if (contacts->len == 0)
1469 {
1470 presence_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
1471 tp_svc_connection_interface_simple_presence_return_from_get_presences (
1472 context, presence_hash);
1473 g_hash_table_unref (presence_hash);
1474 return;
1475 }
1476
1477 if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
1478 {
1479 dbus_g_method_return_error (context, error);
1480 g_error_free (error);
1481 return;
1482 }
1483
1484 contact_statuses = mixin_cls->get_contact_statuses (obj, contacts, &error);
1485
1486 if (!contact_statuses)
1487 {
1488 dbus_g_method_return_error (context, error);
1489 g_error_free (error);
1490 return;
1491 }
1492
1493 presence_hash = construct_simple_presence_hash (mixin_cls->statuses,
1494 contact_statuses);
1495 tp_svc_connection_interface_simple_presence_return_from_get_presences (
1496 context, presence_hash);
1497 g_hash_table_unref (presence_hash);
1498 g_hash_table_unref (contact_statuses);
1499 }
1500
1501 /**
1502 * tp_presence_mixin_simple_presence_iface_init: (skip)
1503 * @g_iface: A pointer to the #TpSvcConnectionInterfaceSimplePresenceClass in
1504 * an object class
1505 * @iface_data: Ignored
1506 *
1507 * Fill in the vtable entries needed to implement the simple presence interface
1508 * using this mixin. This function should usually be called via
1509 * G_IMPLEMENT_INTERFACE.
1510 *
1511 * Since: 0.7.13
1512 */
1513 void
tp_presence_mixin_simple_presence_iface_init(gpointer g_iface,gpointer iface_data)1514 tp_presence_mixin_simple_presence_iface_init (gpointer g_iface,
1515 gpointer iface_data)
1516 {
1517 TpSvcConnectionInterfaceSimplePresenceClass *klass = g_iface;
1518
1519 #define IMPLEMENT(x) tp_svc_connection_interface_simple_presence_implement_##x\
1520 (klass, tp_presence_mixin_simple_presence_##x)
1521 IMPLEMENT(set_presence);
1522 IMPLEMENT(get_presences);
1523 #undef IMPLEMENT
1524 }
1525
1526 static void
tp_presence_mixin_simple_presence_fill_contact_attributes(GObject * obj,const GArray * contacts,GHashTable * attributes_hash)1527 tp_presence_mixin_simple_presence_fill_contact_attributes (GObject *obj,
1528 const GArray *contacts, GHashTable *attributes_hash)
1529 {
1530 TpPresenceMixinClass *mixin_cls =
1531 TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
1532 GHashTable *contact_statuses;
1533 GError *error = NULL;
1534
1535 contact_statuses = mixin_cls->get_contact_statuses (obj, contacts, &error);
1536
1537 if (contact_statuses == NULL)
1538 {
1539 DEBUG ("get_contact_statuses failed: %s", error->message);
1540 g_error_free (error);
1541 }
1542 else
1543 {
1544 GHashTableIter iter;
1545 gpointer key, value;
1546 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
1547 GType type = G_TYPE_VALUE_ARRAY;
1548 G_GNUC_END_IGNORE_DEPRECATIONS
1549
1550 g_hash_table_iter_init (&iter, contact_statuses);
1551 while (g_hash_table_iter_next (&iter, &key, &value))
1552 {
1553 TpHandle handle = GPOINTER_TO_UINT (key);
1554 TpPresenceStatus *status = value;
1555 GValueArray *presence = construct_simple_presence_value_array (
1556 status, mixin_cls->statuses);
1557
1558 tp_contacts_mixin_set_contact_attribute (attributes_hash, handle,
1559 TP_TOKEN_CONNECTION_INTERFACE_SIMPLE_PRESENCE_PRESENCE,
1560 tp_g_value_slice_new_take_boxed (type, presence));
1561 }
1562
1563 g_hash_table_unref (contact_statuses);
1564 }
1565 }
1566
1567 /**
1568 * tp_presence_mixin_simple_presence_register_with_contacts_mixin: (skip)
1569 * @obj: An instance that of the implementation that uses both the Contacts
1570 * mixin and this mixin
1571 *
1572 * Register the SimplePresence interface with the Contacts interface to make it
1573 * inspectable. The Contacts mixin should be initialized before this function
1574 * is called
1575 */
1576 void
tp_presence_mixin_simple_presence_register_with_contacts_mixin(GObject * obj)1577 tp_presence_mixin_simple_presence_register_with_contacts_mixin (GObject *obj)
1578 {
1579 tp_contacts_mixin_add_contact_attributes_iface (obj,
1580 TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
1581 tp_presence_mixin_simple_presence_fill_contact_attributes);
1582 }
1583
1584 /* For now, self->priv is just self if heap-allocated, NULL if not. */
1585 static gboolean
_tp_presence_status_spec_is_heap_allocated(const TpPresenceStatusSpec * self)1586 _tp_presence_status_spec_is_heap_allocated (const TpPresenceStatusSpec *self)
1587 {
1588 return (self->priv == (TpPresenceStatusSpecPrivate *) self);
1589 }
1590
1591 /**
1592 * tp_presence_status_spec_get_presence_type:
1593 * @self: a presence status specification
1594 *
1595 * Return the category into which this presence type falls. For instance,
1596 * for XMPP's "" (do not disturb) status, this would return
1597 * %TP_CONNECTION_PRESENCE_TYPE_BUSY.
1598 *
1599 * Returns: a #TpConnectionPresenceType
1600 * Since: 0.23.1
1601 */
1602 TpConnectionPresenceType
tp_presence_status_spec_get_presence_type(const TpPresenceStatusSpec * self)1603 tp_presence_status_spec_get_presence_type (const TpPresenceStatusSpec *self)
1604 {
1605 g_return_val_if_fail (self != NULL, TP_CONNECTION_PRESENCE_TYPE_UNSET);
1606
1607 return self->presence_type;
1608 }
1609
1610 /**
1611 * tp_presence_status_spec_get_name:
1612 * @self: a presence status specification
1613 *
1614 * <!-- -->
1615 *
1616 * Returns: (transfer none): the name of this presence status,
1617 * such as "available" or "out-to-lunch".
1618 * Since: 0.23.1
1619 */
1620 const gchar *
tp_presence_status_spec_get_name(const TpPresenceStatusSpec * self)1621 tp_presence_status_spec_get_name (const TpPresenceStatusSpec *self)
1622 {
1623 g_return_val_if_fail (self != NULL, NULL);
1624
1625 return self->name;
1626 }
1627
1628 /**
1629 * tp_presence_status_spec_can_set_on_self:
1630 * @self: a presence status specification
1631 *
1632 * <!-- -->
1633 *
1634 * Returns: %TRUE if the user can set this presence status on themselves (most
1635 * statuses), or %FALSE if they cannot directly set it on
1636 * themselves (typically used for %TP_CONNECTION_PRESENCE_TYPE_OFFLINE
1637 * and %TP_CONNECTION_PRESENCE_TYPE_ERROR)
1638 * Since: 0.23.1
1639 */
1640 gboolean
tp_presence_status_spec_can_set_on_self(const TpPresenceStatusSpec * self)1641 tp_presence_status_spec_can_set_on_self (const TpPresenceStatusSpec *self)
1642 {
1643 g_return_val_if_fail (self != NULL, FALSE);
1644
1645 return self->self;
1646 }
1647
1648 /**
1649 * tp_presence_status_spec_has_message:
1650 * @self: a presence status specification
1651 *
1652 * <!-- -->
1653 *
1654 * Returns: %TRUE if this presence status is accompanied by an optional
1655 * human-readable message
1656 * Since: 0.23.1
1657 */
1658 gboolean
tp_presence_status_spec_has_message(const TpPresenceStatusSpec * self)1659 tp_presence_status_spec_has_message (const TpPresenceStatusSpec *self)
1660 {
1661 const TpPresenceStatusOptionalArgumentSpec *arg;
1662
1663 g_return_val_if_fail (self != NULL, FALSE);
1664
1665 if (self->optional_arguments == NULL)
1666 return FALSE;
1667
1668 for (arg = self->optional_arguments; arg->name != NULL; arg++)
1669 {
1670 if (!tp_strdiff (arg->name, "message") && !tp_strdiff (arg->dtype, "s"))
1671 return TRUE;
1672 }
1673
1674 return FALSE;
1675 }
1676
1677 /**
1678 * tp_presence_status_spec_new:
1679 * @name: the name of the new presence status
1680 * @type: the category into which this presence status falls
1681 * @can_set_on_self: %TRUE if the user can set this presence status
1682 * on themselves
1683 * @has_message: %TRUE if this presence status is accompanied by an
1684 * optional human-readable message
1685 *
1686 * <!-- -->
1687 *
1688 * Returns: (transfer full): a new #TpPresenceStatusSpec
1689 * Since: 0.23.1
1690 */
1691 TpPresenceStatusSpec *
tp_presence_status_spec_new(const gchar * name,TpConnectionPresenceType type,gboolean can_set_on_self,gboolean has_message)1692 tp_presence_status_spec_new (const gchar *name,
1693 TpConnectionPresenceType type,
1694 gboolean can_set_on_self,
1695 gboolean has_message)
1696 {
1697 TpPresenceStatusSpec *ret;
1698 static const TpPresenceStatusOptionalArgumentSpec yes_it_has_a_message[] = {
1699 { "message", "s" },
1700 { NULL }
1701 };
1702
1703 g_return_val_if_fail (!tp_str_empty (name), NULL);
1704 g_return_val_if_fail (type >= 0 && type < TP_NUM_CONNECTION_PRESENCE_TYPES,
1705 NULL);
1706
1707 ret = g_slice_new0 (TpPresenceStatusSpec);
1708
1709 ret->name = g_strdup (name);
1710 ret->presence_type = type;
1711 ret->self = can_set_on_self;
1712
1713 if (has_message)
1714 ret->optional_arguments = yes_it_has_a_message;
1715 else
1716 ret->optional_arguments = NULL;
1717
1718 /* dummy marker for "this is on the heap" rather than a real struct */
1719 ret->priv = (TpPresenceStatusSpecPrivate *) ret;
1720
1721 return ret;
1722 }
1723
1724 /**
1725 * tp_presence_status_spec_copy:
1726 * @self: a presence status specification
1727 *
1728 * Copy a presence status specification.
1729 *
1730 * If @self has optional arguments other than a string named "message",
1731 * they are not copied. Optional arguments with other names or types
1732 * are deprecated.
1733 *
1734 * Returns: (transfer full): a new #TpPresenceStatusSpec resembling @self
1735 * Since: 0.23.1
1736 */
1737 TpPresenceStatusSpec *
tp_presence_status_spec_copy(const TpPresenceStatusSpec * self)1738 tp_presence_status_spec_copy (const TpPresenceStatusSpec *self)
1739 {
1740 g_return_val_if_fail (self != NULL, NULL);
1741
1742 return tp_presence_status_spec_new (self->name, self->presence_type,
1743 self->self, tp_presence_status_spec_has_message (self));
1744 }
1745
1746 /**
1747 * tp_presence_status_spec_free:
1748 * @self: (transfer full): a presence status specification
1749 *
1750 * Free a presence status specification produced by
1751 * tp_presence_status_spec_new() or tp_presence_status_spec_copy().
1752 *
1753 * Since: 0.23.1
1754 */
1755 void
tp_presence_status_spec_free(TpPresenceStatusSpec * self)1756 tp_presence_status_spec_free (TpPresenceStatusSpec *self)
1757 {
1758 g_return_if_fail (_tp_presence_status_spec_is_heap_allocated (self));
1759
1760 /* This struct was designed to always be on the stack, so freeing this
1761 * needs a non-const-correct cast */
1762 g_free ((gchar *) self->name);
1763
1764 g_slice_free (TpPresenceStatusSpec, self);
1765 }
1766
1767 G_DEFINE_BOXED_TYPE (TpPresenceStatusSpec, tp_presence_status_spec,
1768 tp_presence_status_spec_copy, tp_presence_status_spec_free)
1769