Partly fix handling of transport-info messages
[emacs-jabber-tox.git] / tox-session.c
blobf3735983cfffc66da85ade2f7c08562f50ffe302
1 #include <glib.h>
2 #include <farsight/farsight.h>
3 #include <farsight/farsight-transport.h>
4 #include <dbus/dbus-glib.h>
5 #include <glib/gprintf.h>
6 #include <string.h>
7 #include "tox-session.h"
8 #include "tox-marshal.h"
10 G_DEFINE_TYPE(ToxSession, tox_session, G_TYPE_OBJECT)
12 typedef struct _ToxSessionPrivate {
13 FarsightSession *session;
14 /* for now, one stream is enough */
15 FarsightStream *stream;
17 int have_source, have_sink;
19 gboolean dispose_has_run;
20 } ToxSessionPrivate;
22 gboolean tox_session_destroy(ToxSession *obj, GError **error);
23 gboolean tox_session_set_default_audio_sink(ToxSession *obj, GError **error);
24 gboolean tox_session_set_ogg_vorbis_audio_source(ToxSession *obj, char *filename, GError **error);
25 gboolean tox_session_add_remote_candidate(ToxSession *obj, GPtrArray *candidate, GError **error);
26 gboolean tox_session_set_remote_codecs(ToxSession *obj, GPtrArray *codecs, GError **error);
27 gboolean tox_session_get_local_codecs(ToxSession *obj, GPtrArray **codecs, GError **error);
28 gboolean tox_session_get_codec_intersection(ToxSession *obj, GPtrArray **codecs, GError **error);
30 /* properties */
31 enum {
32 DIRECTION = 1
34 static void tox_session_set_property(GObject *obj, guint property_id, const GValue *value, GParamSpec *pspec);
36 /* signals */
37 enum {
38 NEW_NATIVE_CANDIDATE,
39 NATIVE_CANDIDATES_PREPARED,
40 STATE_CHANGED,
41 NEW_ACTIVE_CANDIDATE_PAIR,
42 LAST_SIGNAL
45 static guint signals[LAST_SIGNAL];
47 #include "tox-session-glue.h"
49 static GstElement *prepare_source(const char *filename);
50 static void new_pad(GstElement *, GstPad *, gpointer);
51 static GstElement *prepare_sink(void);
52 static void stream_done(ToxSession *);
54 static GValueArray * candidate_list_to_dbus_array(const GList *candidates);
56 static void tox_session_native_candidates_prepared(FarsightStream *stream, gpointer user_data);
57 static void tox_session_new_native_candidate(FarsightStream *stream, gchar *candidate_id, ToxSession *self);
58 static void tox_session_state_changed(FarsightStream *stream, gint state, gint direction, ToxSession *self);
59 static void tox_session_new_active_candidate_pair(FarsightStream *stream, gchar *native_candidate_id, gchar *remote_candidate_id, ToxSession *self);
61 void
62 tox_session_init(ToxSession *obj)
64 ToxSessionPrivate *priv;
65 priv = g_new0(ToxSessionPrivate, 1);
66 obj->priv = priv;
68 priv->session = farsight_session_factory_make("rtp");
69 g_assert(priv->session);
71 /* we need to know the direction to create a stream,
72 so we do that when that property is set.
77 static GObjectClass *parent_class = NULL;
79 static void
80 tox_session_dispose(GObject *obj)
82 ToxSession *self = (ToxSession *)obj;
84 if (self->priv->dispose_has_run) {
85 return;
87 g_debug("in tox_session_dispose\n");
88 self->priv->dispose_has_run = TRUE;
90 if (self->priv->stream)
91 g_object_unref(self->priv->stream);
92 if (self->priv->session)
93 g_object_unref(self->priv->session);
95 self->priv->stream = NULL;
96 self->priv->session = NULL;
98 G_OBJECT_CLASS(parent_class)->dispose(obj);
101 static void
102 tox_session_finalize(GObject *obj)
104 ToxSession *self = (ToxSession *)obj;
106 g_debug("in tox_session_finalize\n");
107 g_free(self->priv);
109 G_OBJECT_CLASS(parent_class)->finalize(obj);
112 void
113 tox_session_class_init(ToxSessionClass *klass)
115 GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
116 GParamSpec *direction_param_spec;
118 gobject_class->dispose = tox_session_dispose;
119 gobject_class->finalize = tox_session_finalize;
121 gobject_class->set_property = tox_session_set_property;
123 direction_param_spec = g_param_spec_uint("direction",
124 "stream direction",
125 "1 means send-only, 2 means receive-only, 3 means both",
129 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE);
130 g_object_class_install_property(gobject_class,
131 DIRECTION,
132 direction_param_spec);
134 signals[NEW_NATIVE_CANDIDATE] =
135 g_signal_new("new-native-candidate",
136 G_OBJECT_CLASS_TYPE(klass),
137 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
139 NULL, NULL,
140 tox_marshal_VOID__BOXED,
141 G_TYPE_NONE,
143 G_TYPE_VALUE_ARRAY);
145 signals[NATIVE_CANDIDATES_PREPARED] =
146 g_signal_new("native-candidates-prepared",
147 G_OBJECT_CLASS_TYPE(klass),
148 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
150 NULL, NULL,
151 tox_marshal_VOID__BOXED,
152 G_TYPE_NONE,
154 G_TYPE_VALUE_ARRAY);
156 signals[STATE_CHANGED] =
157 g_signal_new("state-changed",
158 G_OBJECT_CLASS_TYPE(klass),
159 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
161 NULL, NULL,
162 tox_marshal_VOID__UCHAR_UCHAR,
163 G_TYPE_NONE,
165 G_TYPE_UCHAR,
166 G_TYPE_UCHAR);
168 signals[NEW_ACTIVE_CANDIDATE_PAIR] =
169 g_signal_new("new-active-candidate-pair",
170 G_OBJECT_CLASS_TYPE(klass),
171 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
173 NULL, NULL,
174 tox_marshal_VOID__STRING_STRING,
175 G_TYPE_NONE,
177 G_TYPE_STRING,
178 G_TYPE_STRING);
180 dbus_g_object_type_install_info(TOX_TYPE_SESSION, &dbus_glib_tox_session_object_info);
182 parent_class = g_type_class_peek_parent (klass);
185 static void
186 tox_session_set_property(GObject *obj, guint property_id, const GValue *value, GParamSpec *pspec)
188 ToxSession *self = (ToxSession *)obj;
189 guint dir;
191 switch(property_id) {
192 case DIRECTION:
193 if (self->priv->stream)
194 g_object_unref(self->priv->stream);
195 /* Now we know the direction, so we create a stream. */
196 dir = g_value_get_uint(value);
197 self->priv->stream = farsight_session_create_stream(self->priv->session,
198 FARSIGHT_MEDIA_TYPE_AUDIO,
199 dir);
200 g_assert(self->priv->stream);
202 g_object_set(G_OBJECT(self->priv->stream), "transmitter", "libjingle", NULL);
204 /* XXX: should we set null source/sink here? */
205 switch (dir) {
206 case 1:
207 /* send-only, we need no sink */
208 self->priv->have_sink = 1;
209 break;
210 case 2:
211 /* receive-only, we need no source */
212 self->priv->have_source = 1;
213 break;
216 /* start preparing native candidates */
217 g_debug("About to prepare native candidates...\n");
218 g_signal_connect(self->priv->stream, "new-native-candidate",
219 (GCallback)tox_session_new_native_candidate, (gpointer)self);
220 g_signal_connect(self->priv->stream, "native-candidates-prepared",
221 (GCallback)tox_session_native_candidates_prepared, (gpointer)self);
222 /* but we can't actually do it until we have a pipeline.
223 so, we'll call stream_done when we do. */
225 /* Other signals we want to forward */
226 g_signal_connect(self->priv->stream, "state-changed",
227 (GCallback)tox_session_state_changed, (gpointer)self);
228 g_signal_connect(self->priv->stream, "new-active-candidate-pair",
229 (GCallback)tox_session_new_active_candidate_pair, (gpointer)self);
231 break;
232 default:
233 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
237 gboolean
238 tox_session_destroy(ToxSession *obj, GError **error)
240 g_object_unref(obj);
241 return TRUE;
244 gboolean
245 tox_session_set_default_audio_sink(ToxSession *obj, GError **error)
247 GstElement *sink = prepare_sink();
248 farsight_stream_set_sink(obj->priv->stream, sink);
249 obj->priv->have_sink = 1;
251 if (obj->priv->have_sink && obj->priv->have_source)
252 stream_done(obj);
254 return TRUE;
257 gboolean
258 tox_session_set_ogg_vorbis_audio_source(ToxSession *obj, char *filename, GError **error)
260 GstElement *source = prepare_source(filename);
261 farsight_stream_set_source(obj->priv->stream, source);
262 obj->priv->have_source = 1;
264 if (obj->priv->have_sink && obj->priv->have_source)
265 stream_done(obj);
267 return TRUE;
270 static GstElement *
271 prepare_source(const char *filename)
273 GstElement *bin, *filesrc, *demux, *decode;
275 bin = gst_bin_new("mysource");
276 filesrc = gst_element_factory_make("filesrc", "file-source");
277 g_object_set(G_OBJECT(filesrc), "location", filename, NULL);
279 demux = gst_element_factory_make("oggdemux", "ogg-parser");
280 decode = gst_element_factory_make("vorbisdec", "vorbis-decoder");
282 gst_element_link(filesrc, demux);
283 g_signal_connect(demux, "pad-added", G_CALLBACK(new_pad), decode);
285 gst_bin_add_many(GST_BIN(bin), filesrc, demux, decode, NULL);
287 return bin;
290 static void
291 new_pad(GstElement *demux, GstPad *pad, gpointer data)
293 GstElement *decode = (GstElement*)data;
294 GstPad *decoder_pad;
296 decoder_pad = gst_element_get_pad(decode, "sink");
297 gst_pad_link(pad, decoder_pad);
299 gst_object_unref(decoder_pad);
302 static GstElement *
303 prepare_sink(void)
305 GstElement *bin, *converter, *audiosink;
307 bin = gst_bin_new("mysink");
308 converter = gst_element_factory_make("audioconvert", "converter");
309 audiosink = gst_element_factory_make("autoaudiosink", "audiosink");
310 gst_element_link(converter, audiosink);
312 gst_bin_add_many(GST_BIN(bin), converter, audiosink, NULL);
314 return bin;
317 static void
318 stream_done(ToxSession *self)
320 farsight_stream_prepare_transports(self->priv->stream);
323 gboolean
324 tox_session_add_remote_candidate(ToxSession *self, GPtrArray *candidate, GError **error)
326 int i;
327 guint n;
328 GList *candidate_list;
330 candidate_list = NULL;
332 /* Here we convert the array of structs into a GList of
333 FarsightTransportInfo. The argument list is described in
334 tox-session.xml. */
335 for (i = 0; i < candidate->len; i++) {
336 GValueArray *component;
337 FarsightTransportInfo *info;
338 gchar *s;
340 component = g_ptr_array_index(candidate, i);
341 info = g_new0(FarsightTransportInfo, 1);
343 info->candidate_id =
344 g_value_dup_string(
345 g_value_array_get_nth(component, 0));
346 info->component =
347 g_value_get_uint(
348 g_value_array_get_nth(component, 1));
349 info->ip =
350 g_value_dup_string(
351 g_value_array_get_nth(component, 2));
352 info->port =
353 g_value_get_uint(
354 g_value_array_get_nth(component, 3));
356 s = g_value_dup_string(g_value_array_get_nth(component, 4));
357 if (strcmp(s, "tcp") == 0)
358 info->proto = FARSIGHT_NETWORK_PROTOCOL_TCP;
359 else if (strcmp(s, "udp") == 0)
360 info->proto = FARSIGHT_NETWORK_PROTOCOL_UDP;
361 else {
362 g_set_error(error, DBUS_GERROR,
363 DBUS_GERROR_REMOTE_EXCEPTION,
364 "Unexpected protocol '%s'",
366 g_free(s);
367 g_free(info);
368 goto fail;
370 g_free(s);
372 /* this should be "RTP" */
373 info->proto_subtype =
374 g_value_dup_string(
375 g_value_array_get_nth(component, 5));
376 /* this should be "AVP" */
377 info->proto_profile =
378 g_value_dup_string(
379 g_value_array_get_nth(component, 6));
381 info->preference =
382 g_value_get_uint(
383 g_value_array_get_nth(component, 7))
384 * 0.01;
386 n = g_value_get_uint(g_value_array_get_nth(component, 8));
387 switch (n) {
388 case 0:
389 info->type = FARSIGHT_CANDIDATE_TYPE_LOCAL;
390 break;
391 case 1:
392 info->type = FARSIGHT_CANDIDATE_TYPE_DERIVED;
393 break;
394 case 2:
395 info->type = FARSIGHT_CANDIDATE_TYPE_RELAY;
396 break;
397 default:
398 g_set_error(error, DBUS_GERROR,
399 DBUS_GERROR_REMOTE_EXCEPTION,
400 "Unexpected type %u",
402 g_free(info);
403 goto fail;
406 info->username = g_value_dup_string(
407 g_value_array_get_nth(component, 9));
408 info->password = g_value_dup_string(
409 g_value_array_get_nth(component, 10));
411 candidate_list = g_list_append(candidate_list, info);
414 farsight_stream_add_remote_candidate(self->priv->stream, candidate_list);
415 return TRUE;
417 fail:
418 farsight_transport_list_destroy(candidate_list);
419 return FALSE;
422 gboolean
423 tox_session_set_remote_codecs(ToxSession *obj, GPtrArray *codecs, GError **error)
425 GList *codec_list;
426 int i;
427 /* GList *j; */
429 codec_list = NULL;
431 for (i = 0; i < codecs->len; i++) {
432 GValueArray *codec_in;
433 FarsightCodec *codec_out;
435 codec_in = g_ptr_array_index(codecs, i);
436 codec_out = g_new0(FarsightCodec, 1);
438 codec_out->id = g_value_get_int(g_value_array_get_nth(codec_in, 0));
439 codec_out->encoding_name = g_value_dup_string(g_value_array_get_nth(codec_in, 1));
440 /* maybe check range of media_type... */
441 codec_out->media_type = g_value_get_uchar(g_value_array_get_nth(codec_in, 2));
442 codec_out->clock_rate = g_value_get_uint(g_value_array_get_nth(codec_in, 3));
443 codec_out->channels = g_value_get_uint(g_value_array_get_nth(codec_in, 4));
445 codec_list = g_list_append(codec_list, codec_out);
448 farsight_stream_set_remote_codecs(obj->priv->stream, codec_list);
450 /* should the elements be freed, or just the list itself? */
451 /*for (j = codec_list; j; j = g_list_next(j)) {
452 g_free(j->data);
454 g_list_free(codec_list);
456 return TRUE;
459 gboolean
460 tox_session_get_local_codecs(ToxSession *obj, GPtrArray **codecs, GError **error)
462 FarsightStream *stream;
463 const GList *codec_list;
465 GType hash_string_string;
467 stream = obj->priv->stream;
468 codec_list = farsight_stream_get_local_codecs(stream);
470 hash_string_string = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_STRING);
472 *codecs = g_ptr_array_sized_new(g_list_length(codec_list));
473 for (; codec_list; codec_list = g_list_next(codec_list)) {
474 GValueArray *codec_struct;
475 const FarsightCodec *codec;
476 GValue value;
477 GHashTable *parameters;
478 GList *p;
480 memset(&value, 0, sizeof value);
482 codec = (const FarsightCodec*)codec_list->data;
483 codec_struct = g_value_array_new(2);
485 g_value_init(&value, G_TYPE_INT);
486 g_value_set_int(&value, codec->id);
487 g_value_array_append(codec_struct, &value);
488 g_value_unset(&value);
490 g_value_init(&value, G_TYPE_STRING);
491 g_value_set_string(&value, codec->encoding_name);
492 g_value_array_append(codec_struct, &value);
493 g_value_unset(&value);
495 /* XXX: why is this the same as id? */
496 g_value_init(&value, G_TYPE_UCHAR);
497 g_value_set_uchar(&value, codec->media_type);
498 g_value_array_append(codec_struct, &value);
499 g_value_unset(&value);
501 g_value_init(&value, G_TYPE_UINT);
502 g_value_set_uint(&value, codec->clock_rate);
503 g_value_array_append(codec_struct, &value);
504 g_value_unset(&value);
506 g_value_init(&value, G_TYPE_UINT);
507 g_value_set_uint(&value, codec->channels);
508 g_value_array_append(codec_struct, &value);
509 g_value_unset(&value);
511 /* optional parameters - this should be a hash table, I think */
512 /* parameters = g_hash_table_new(g_str_hash, g_str_equal); */
513 parameters = dbus_g_type_specialized_construct(hash_string_string);
514 for (p = codec->optional_params; p; p = g_list_next(p)) {
515 FarsightCodecParameter *param = p->data;
517 g_hash_table_insert(parameters, param->name, param->value);
519 g_value_init(&value, hash_string_string);
520 g_value_set_boxed(&value, parameters);
521 g_value_array_append(codec_struct, &value);
522 g_value_unset(&value);
523 g_hash_table_unref(parameters);
525 g_assert(codec_struct->n_values == 6);
527 g_ptr_array_add(*codecs, codec_struct);
529 g_debug("Local codec: %s\n", codec->encoding_name);
532 return TRUE;
535 gboolean
536 tox_session_get_codec_intersection(ToxSession *obj, GPtrArray **codecs, GError **error)
538 FarsightStream *stream;
539 GList *codec_list;
541 stream = obj->priv->stream;
542 codec_list = farsight_stream_get_codec_intersection(stream);
544 *codecs = g_ptr_array_sized_new(g_list_length(codec_list));
545 for (; codec_list; codec_list = g_list_next(codec_list)) {
546 GValueArray *codec_struct;
547 FarsightCodec *codec;
548 GValue *value;
550 codec = (FarsightCodec*)codec_list->data;
551 codec_struct = g_value_array_new(6);
553 value = g_new0(GValue, 1);
554 g_value_init(value, G_TYPE_INT);
555 g_value_set_int(value, codec->id);
556 g_value_array_append(codec_struct, value);
558 value = g_new0(GValue, 1);
559 g_value_init(value, G_TYPE_STRING);
560 g_value_set_string(value, codec->encoding_name);
561 g_value_array_append(codec_struct, value);
563 value = g_new0(GValue, 1);
564 g_value_init(value, G_TYPE_UCHAR);
565 g_value_set_uchar(value, codec->media_type);
566 g_value_array_append(codec_struct, value);
568 value = g_new0(GValue, 1);
569 g_value_init(value, G_TYPE_UINT);
570 g_value_set_uint(value, codec->clock_rate);
571 g_value_array_append(codec_struct, value);
573 value = g_new0(GValue, 1);
574 g_value_init(value, G_TYPE_UINT);
575 g_value_set_uint(value, codec->channels);
576 g_value_array_append(codec_struct, value);
578 /* XXX: do something about optional parameters */
579 value = g_new0(GValue, 1);
580 g_value_init(value, DBUS_TYPE_G_STRING_STRING_HASHTABLE);
581 g_value_set_boxed(value, g_hash_table_new(g_str_hash, g_str_equal));
582 g_value_array_append(codec_struct, value);
584 g_assert(codec_struct->n_values == 6);
586 g_ptr_array_add(*codecs, codec_struct);
589 return TRUE;
592 static void
593 tox_session_native_candidates_prepared(FarsightStream *stream, gpointer user_data)
595 ToxSession *self = (ToxSession *)user_data;
596 const GList *candidates;
597 GValueArray *array;
599 candidates = farsight_stream_get_native_candidate_list(stream);
601 array = candidate_list_to_dbus_array(candidates);
603 g_debug("Sending signal NativeCandidatesPrepared!\n");
604 g_signal_emit(self, signals[NATIVE_CANDIDATES_PREPARED], 0, array);
607 static void
608 tox_session_new_native_candidate(FarsightStream *stream, gchar *candidate_id, ToxSession *self)
610 GList *candidate =
611 farsight_stream_get_native_candidate (stream, candidate_id);
612 FarsightTransportInfo *trans = candidate->data;
613 GValueArray *array;
615 g_debug ("tox_session_new_native_candidate: New native candidate"
616 " with %d components, the first being: "
617 "<id: %s, "
618 "component: %d, "
619 "ip: %s port: %d "
620 "proto: %d, "
621 "proto_subtype: %s, "
622 "proto_profile: %s, "
623 "preference: %f, "
624 "type: %d "
625 "username: %s password: %s>",
626 g_list_length(candidate),
627 trans->candidate_id, trans->component,
628 trans->ip, trans->port, trans->proto, trans->proto_subtype,
629 trans->proto_profile, trans->preference,
630 trans->type, trans->username, trans->password);
632 array = candidate_list_to_dbus_array(candidate);
633 g_debug("Sending signal NewNativeCandidate!\n");
634 g_signal_emit(self, signals[NEW_NATIVE_CANDIDATE], 0, array);
637 static GValueArray *
638 candidate_list_to_dbus_array(const GList *candidates)
640 GValueArray *array;
642 array = g_value_array_new(1);
644 for (; candidates; candidates = g_list_next(candidates)) {
645 GValueArray *candidate;
646 FarsightTransportInfo *info;
647 GValue value;
649 info = (FarsightTransportInfo*)candidates->data;
650 candidate = g_value_array_new(11);
652 memset(&value, 0, sizeof value);
654 g_value_init(&value, G_TYPE_STRING);
655 g_value_set_string(&value, info->candidate_id);
656 g_value_array_append(candidate, &value);
657 g_value_unset(&value);
659 g_value_init(&value, G_TYPE_UINT);
660 g_value_set_uint(&value, info->component);
661 g_value_array_append(candidate, &value);
662 g_value_unset(&value);
664 g_value_init(&value, G_TYPE_STRING);
665 g_value_set_string(&value, info->ip);
666 g_value_array_append(candidate, &value);
667 g_value_unset(&value);
669 g_value_init(&value, G_TYPE_UINT);
670 g_value_set_uint(&value, info->port);
671 g_value_array_append(candidate, &value);
672 g_value_unset(&value);
674 g_value_init(&value, G_TYPE_STRING);
675 switch(info->proto) {
676 case FARSIGHT_NETWORK_PROTOCOL_UDP:
677 g_value_set_static_string(&value, "udp");
678 break;
679 case FARSIGHT_NETWORK_PROTOCOL_TCP:
680 g_value_set_static_string(&value, "tcp");
681 break;
682 default:
683 g_error("Unknown protocol value %u\n", info->proto);
685 g_value_array_append(candidate, &value);
686 g_value_unset(&value);
688 g_value_init(&value, G_TYPE_STRING);
689 g_value_set_string(&value, info->proto_subtype);
690 g_value_array_append(candidate, &value);
691 g_value_unset(&value);
693 g_value_init(&value, G_TYPE_STRING);
694 g_value_set_string(&value, info->proto_profile);
695 g_value_array_append(candidate, &value);
696 g_value_unset(&value);
698 g_value_init(&value, G_TYPE_UINT);
699 g_value_set_uint(&value, (guint)(info->preference * 100));
700 g_value_array_append(candidate, &value);
701 g_value_unset(&value);
703 g_value_init(&value, G_TYPE_UINT);
704 switch(info->type) {
705 case FARSIGHT_CANDIDATE_TYPE_LOCAL:
706 g_value_set_uint(&value, 0);
707 break;
708 case FARSIGHT_CANDIDATE_TYPE_DERIVED:
709 g_value_set_uint(&value, 1);
710 break;
711 case FARSIGHT_CANDIDATE_TYPE_RELAY:
712 g_value_set_uint(&value, 2);
713 break;
714 default:
715 g_error("Unknown candidate type %u\n", info->proto);
717 g_value_array_append(candidate, &value);
718 g_value_unset(&value);
720 g_value_init(&value, G_TYPE_STRING);
721 g_value_set_string(&value, info->username);
722 g_value_array_append(candidate, &value);
723 g_value_unset(&value);
725 g_value_init(&value, G_TYPE_STRING);
726 g_value_set_string(&value, info->password);
727 g_value_array_append(candidate, &value);
728 g_value_unset(&value);
730 g_assert(candidate->n_values == 11);
732 g_value_init(&value, G_TYPE_VALUE_ARRAY);
733 /* apparently GValueArray is a "boxed" type */
734 g_value_set_boxed(&value, candidate);
735 g_value_array_append(array, &value);
736 g_value_unset(&value);
739 return array;
742 static void
743 tox_session_state_changed(FarsightStream *stream, gint state, gint direction, ToxSession *self)
745 g_signal_emit(self, signals[STATE_CHANGED], 0, state, direction);
748 static void
749 tox_session_new_active_candidate_pair(FarsightStream *stream, gchar *native_candidate_id, gchar *remote_candidate_id, ToxSession *self)
751 g_signal_emit(self, signals[NEW_ACTIVE_CANDIDATE_PAIR], 0, native_candidate_id, remote_candidate_id);