1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138 |
- # HG changeset patch
- # User Youness Alaoui <kakaroto@kakaroto.homelinux.net>
- # Date 1405979621 14400
- # Node ID 4fe1034f3dce1c5cd3c929ab8c58db8e27655beb
- # Parent d729a9b2126594df3e38647e926ac7c0a7db807b
- Add application media type and APIs
- Fixes #16315
- Index: pidgin-2.10.11/configure.ac
- ===================================================================
- --- pidgin-2.10.11.orig/configure.ac
- +++ pidgin-2.10.11/configure.ac
- @@ -916,6 +916,20 @@ fi
- AM_CONDITIONAL(USE_VV, test "x$enable_vv" != "xno")
-
- dnl #######################################################################
- +dnl # Check for Raw data streams support in Farstream
- +dnl #######################################################################
- +if test "x$enable_vv" != "xno" -a "x$with_gstreamer" == "x1.0"; then
- + AC_MSG_CHECKING(for raw data support in Farstream)
- + PKG_CHECK_MODULES(GSTAPP, [gstreamer-app-1.0], [
- + AC_DEFINE(USE_GSTAPP, 1, [Use GStreamer Video Overlay support])
- + AC_SUBST(GSTAPP_CFLAGS)
- + AC_SUBST(GSTAPP_LIBS)
- + AC_DEFINE(HAVE_MEDIA_APPLICATION, 1, [Define if we have support for application media type.])
- + AC_MSG_RESULT(yes)
- + ], [AC_MSG_RESULT(no)])
- +fi
- +
- +dnl #######################################################################
- dnl # Check for Internationalized Domain Name support
- dnl #######################################################################
-
- Index: pidgin-2.10.11/libpurple/Makefile.am
- ===================================================================
- --- pidgin-2.10.11.orig/libpurple/Makefile.am
- +++ pidgin-2.10.11/libpurple/Makefile.am
- @@ -314,6 +314,7 @@ libpurple_la_LIBADD = \
- $(FARSTREAM_LIBS) \
- $(GSTREAMER_LIBS) \
- $(GSTVIDEO_LIBS) \
- + $(GSTAPP_LIBS) \
- $(GSTINTERFACES_LIBS) \
- $(IDN_LIBS) \
- ciphers/libpurple-ciphers.la \
- @@ -331,6 +332,7 @@ AM_CPPFLAGS = \
- $(FARSTREAM_CFLAGS) \
- $(GSTREAMER_CFLAGS) \
- $(GSTVIDEO_CFLAGS) \
- + $(GSTAPP_CFLAGS) \
- $(GSTINTERFACES_CFLAGS) \
- $(IDN_CFLAGS) \
- $(NETWORKMANAGER_CFLAGS) \
- Index: pidgin-2.10.11/libpurple/media-gst.h
- ===================================================================
- --- pidgin-2.10.11.orig/libpurple/media-gst.h
- +++ pidgin-2.10.11/libpurple/media-gst.h
- @@ -71,6 +71,7 @@ typedef enum {
-
- PURPLE_MEDIA_ELEMENT_SRC = 1 << 9, /** can be set as an active src */
- PURPLE_MEDIA_ELEMENT_SINK = 1 << 10, /** can be set as an active sink */
- + PURPLE_MEDIA_ELEMENT_APPLICATION = 1 << 11, /** supports application data */
- } PurpleMediaElementType;
-
- #ifdef __cplusplus
- Index: pidgin-2.10.11/libpurple/media/backend-fs2.c
- ===================================================================
- --- pidgin-2.10.11.orig/libpurple/media/backend-fs2.c
- +++ pidgin-2.10.11/libpurple/media/backend-fs2.c
- @@ -543,6 +543,10 @@ session_type_to_fs_media_type(PurpleMedi
- return FS_MEDIA_TYPE_AUDIO;
- else if (type & PURPLE_MEDIA_VIDEO)
- return FS_MEDIA_TYPE_VIDEO;
- +#ifdef HAVE_MEDIA_APPLICATION
- + else if (type & PURPLE_MEDIA_APPLICATION)
- + return FS_MEDIA_TYPE_APPLICATION;
- +#endif
- else
- return 0;
- }
- @@ -551,7 +555,7 @@ static FsStreamDirection
- session_type_to_fs_stream_direction(PurpleMediaSessionType type)
- {
- if ((type & PURPLE_MEDIA_AUDIO) == PURPLE_MEDIA_AUDIO ||
- - (type & PURPLE_MEDIA_VIDEO) == PURPLE_MEDIA_VIDEO)
- + (type & PURPLE_MEDIA_VIDEO) == PURPLE_MEDIA_VIDEO)
- return FS_DIRECTION_BOTH;
- else if ((type & PURPLE_MEDIA_SEND_AUDIO) ||
- (type & PURPLE_MEDIA_SEND_VIDEO))
- @@ -559,6 +563,14 @@ session_type_to_fs_stream_direction(Purp
- else if ((type & PURPLE_MEDIA_RECV_AUDIO) ||
- (type & PURPLE_MEDIA_RECV_VIDEO))
- return FS_DIRECTION_RECV;
- +#ifdef HAVE_MEDIA_APPLICATION
- + else if ((type & PURPLE_MEDIA_APPLICATION) == PURPLE_MEDIA_APPLICATION)
- + return FS_DIRECTION_BOTH;
- + else if (type & PURPLE_MEDIA_SEND_APPLICATION)
- + return FS_DIRECTION_SEND;
- + else if (type & PURPLE_MEDIA_RECV_APPLICATION)
- + return FS_DIRECTION_RECV;
- +#endif
- else
- return FS_DIRECTION_NONE;
- }
- @@ -577,6 +589,13 @@ session_type_from_fs(FsMediaType type, F
- result |= PURPLE_MEDIA_SEND_VIDEO;
- if (direction & FS_DIRECTION_RECV)
- result |= PURPLE_MEDIA_RECV_VIDEO;
- +#ifdef HAVE_MEDIA_APPLICATION
- + } else if (type == FS_MEDIA_TYPE_APPLICATION) {
- + if (direction & FS_DIRECTION_SEND)
- + result |= PURPLE_MEDIA_SEND_APPLICATION;
- + if (direction & FS_DIRECTION_RECV)
- + result |= PURPLE_MEDIA_RECV_APPLICATION;
- +#endif
- }
- return result;
- }
- @@ -1333,7 +1352,8 @@ gst_handle_message_error(GstBus *bus, Gs
- & PURPLE_MEDIA_AUDIO)
- purple_media_error(priv->media,
- _("Error with your microphone"));
- - else
- + else if (purple_media_get_session_type(priv->media,
- + sessions->data) & PURPLE_MEDIA_VIDEO)
- purple_media_error(priv->media,
- _("Error with your webcam"));
-
- @@ -1756,6 +1776,21 @@ create_session(PurpleMediaBackendFs2 *se
- session->session = fs_conference_new_session(priv->conference,
- session_type_to_fs_media_type(type), &err);
-
- +#ifdef HAVE_MEDIA_APPLICATION
- + if (type == PURPLE_MEDIA_APPLICATION) {
- + GstCaps *caps;
- + GObject *rtpsession = NULL;
- +
- + caps = gst_caps_new_empty_simple ("application/octet-stream");
- + fs_session_set_allowed_caps (session->session, caps, caps, NULL);
- + gst_caps_unref (caps);
- + g_object_get (session->session, "internal-session", &rtpsession, NULL);
- + if (rtpsession) {
- + g_object_set (rtpsession, "probation", 0, NULL);
- + g_object_unref (rtpsession);
- + }
- + }
- +#endif
- if (err != NULL) {
- purple_media_error(priv->media,
- _("Error creating session: %s"),
- @@ -1970,6 +2005,21 @@ src_pad_added_cb(FsStream *fsstream, Gst
- gst_bin_add(GST_BIN(priv->confbin), sink);
- gst_element_set_state(sink, GST_STATE_PLAYING);
- stream->fakesink = sink;
- +#ifdef HAVE_MEDIA_APPLICATION
- + } else if (codec->media_type == FS_MEDIA_TYPE_APPLICATION) {
- +#if GST_CHECK_VERSION(1,0,0)
- + stream->src = gst_element_factory_make("funnel", NULL);
- +#else
- + stream->src = gst_element_factory_make("fsfunnel", NULL);
- +#endif
- + sink = purple_media_manager_get_element(
- + purple_media_get_manager(priv->media),
- + PURPLE_MEDIA_RECV_APPLICATION, priv->media,
- + stream->session->id,
- + stream->participant);
- + gst_bin_add(GST_BIN(priv->confbin), sink);
- + gst_element_set_state(sink, GST_STATE_PLAYING);
- +#endif
- }
- stream->tee = gst_element_factory_make("tee", NULL);
- gst_bin_add_many(GST_BIN(priv->confbin),
- @@ -2332,6 +2382,9 @@ purple_media_backend_fs2_codecs_ready(Pu
- return FALSE;
-
- if (session->type & (PURPLE_MEDIA_SEND_AUDIO |
- +#ifdef HAVE_MEDIA_APPLICATION
- + PURPLE_MEDIA_SEND_APPLICATION |
- +#endif
- PURPLE_MEDIA_SEND_VIDEO)) {
- #ifdef HAVE_FARSIGHT
- g_object_get(session->session,
- @@ -2355,6 +2408,9 @@ purple_media_backend_fs2_codecs_ready(Pu
- PurpleMediaBackendFs2Session *session = values->data;
-
- if (session->type & (PURPLE_MEDIA_SEND_AUDIO |
- +#ifdef HAVE_MEDIA_APPLICATION
- + PURPLE_MEDIA_SEND_APPLICATION |
- +#endif
- PURPLE_MEDIA_SEND_VIDEO)) {
- #ifdef HAVE_FARSIGHT
- g_object_get(session->session,
- Index: pidgin-2.10.11/libpurple/media/codec.c
- ===================================================================
- --- pidgin-2.10.11.orig/libpurple/media/codec.c
- +++ pidgin-2.10.11/libpurple/media/codec.c
- @@ -188,7 +188,7 @@ purple_media_codec_class_init(PurpleMedi
- g_object_class_install_property(gobject_class, PROP_MEDIA_TYPE,
- g_param_spec_flags("media-type",
- "Media Type",
- - "Whether this is an audio of video codec.",
- + "Whether this is an audio, video or application codec.",
- PURPLE_TYPE_MEDIA_SESSION_TYPE,
- PURPLE_MEDIA_NONE,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
- @@ -402,6 +402,8 @@ purple_media_codec_to_string(const Purpl
- media_type_str = "audio";
- else if (priv->media_type & PURPLE_MEDIA_VIDEO)
- media_type_str = "video";
- + else if (priv->media_type & PURPLE_MEDIA_APPLICATION)
- + media_type_str = "application";
-
- g_string_printf(string, "%d: %s %s clock:%d channels:%d", priv->id,
- media_type_str, priv->encoding_name,
- Index: pidgin-2.10.11/libpurple/media/enum-types.c
- ===================================================================
- --- pidgin-2.10.11.orig/libpurple/media/enum-types.c
- +++ pidgin-2.10.11/libpurple/media/enum-types.c
- @@ -176,10 +176,16 @@ purple_media_session_type_get_type()
- "PURPLE_MEDIA_RECV_VIDEO", "recv-video" },
- { PURPLE_MEDIA_SEND_VIDEO,
- "PURPLE_MEDIA_SEND_VIDEO", "send-video" },
- + { PURPLE_MEDIA_RECV_APPLICATION,
- + "PURPLE_MEDIA_RECV_APPLICATION", "recv-application" },
- + { PURPLE_MEDIA_SEND_APPLICATION,
- + "PURPLE_MEDIA_SEND_APPLICATION", "send-application" },
- { PURPLE_MEDIA_AUDIO,
- "PURPLE_MEDIA_AUDIO", "audio" },
- { PURPLE_MEDIA_VIDEO,
- "PURPLE_MEDIA_VIDEO", "video" },
- + { PURPLE_MEDIA_APPLICATION,
- + "PURPLE_MEDIA_APPLICATION", "application" },
- { 0, NULL, NULL }
- };
- type = g_flags_register_static(
- Index: pidgin-2.10.11/libpurple/media/enum-types.h
- ===================================================================
- --- pidgin-2.10.11.orig/libpurple/media/enum-types.h
- +++ pidgin-2.10.11/libpurple/media/enum-types.h
- @@ -92,8 +92,12 @@ typedef enum {
- PURPLE_MEDIA_SEND_AUDIO = 1 << 1,
- PURPLE_MEDIA_RECV_VIDEO = 1 << 2,
- PURPLE_MEDIA_SEND_VIDEO = 1 << 3,
- + PURPLE_MEDIA_RECV_APPLICATION = 1 << 4,
- + PURPLE_MEDIA_SEND_APPLICATION = 1 << 5,
- PURPLE_MEDIA_AUDIO = PURPLE_MEDIA_RECV_AUDIO | PURPLE_MEDIA_SEND_AUDIO,
- - PURPLE_MEDIA_VIDEO = PURPLE_MEDIA_RECV_VIDEO | PURPLE_MEDIA_SEND_VIDEO
- + PURPLE_MEDIA_VIDEO = PURPLE_MEDIA_RECV_VIDEO | PURPLE_MEDIA_SEND_VIDEO,
- + PURPLE_MEDIA_APPLICATION = PURPLE_MEDIA_RECV_APPLICATION |
- + PURPLE_MEDIA_SEND_APPLICATION
- } PurpleMediaSessionType;
-
- /** Media state-changed types */
- Index: pidgin-2.10.11/libpurple/mediamanager.c
- ===================================================================
- --- pidgin-2.10.11.orig/libpurple/mediamanager.c
- +++ pidgin-2.10.11/libpurple/mediamanager.c
- @@ -44,6 +44,9 @@
- #else
- #include <farstream/fs-element-added-notifier.h>
- #endif
- +#ifdef HAVE_MEDIA_APPLICATION
- +#include <gst/app/app.h>
- +#endif
-
- #if GST_CHECK_VERSION(1,0,0)
- #include <gst/video/videooverlay.h>
- @@ -97,14 +100,45 @@ struct _PurpleMediaManagerPrivate
- PurpleMediaElementInfo *video_sink;
- PurpleMediaElementInfo *audio_src;
- PurpleMediaElementInfo *audio_sink;
- +
- +#ifdef HAVE_MEDIA_APPLICATION
- + /* Application data streams */
- + GList *appdata_info; /* holds PurpleMediaAppDataInfo */
- + GMutex appdata_mutex;
- +#endif
- };
-
- +#ifdef HAVE_MEDIA_APPLICATION
- +typedef struct {
- + PurpleMedia *media;
- + GWeakRef media_ref;
- + gchar *session_id;
- + gchar *participant;
- + PurpleMediaAppDataCallbacks callbacks;
- + gpointer user_data;
- + GDestroyNotify notify;
- + GstAppSrc *appsrc;
- + GstAppSink *appsink;
- + gint num_samples;
- + GstSample *current_sample;
- + guint sample_offset;
- + gboolean writable;
- + gboolean connected;
- + guint writable_timer_id;
- + guint readable_timer_id;
- + GCond readable_cond;
- +} PurpleMediaAppDataInfo;
- +#endif
- +
- #define PURPLE_MEDIA_MANAGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA_MANAGER, PurpleMediaManagerPrivate))
- #define PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA_ELEMENT_INFO, PurpleMediaElementInfoPrivate))
-
- static void purple_media_manager_class_init (PurpleMediaManagerClass *klass);
- static void purple_media_manager_init (PurpleMediaManager *media);
- static void purple_media_manager_finalize (GObject *object);
- +#ifdef HAVE_MEDIA_APPLICATION
- +static void free_appdata_info_locked (PurpleMediaAppDataInfo *info);
- +#endif
-
- static GObjectClass *parent_class = NULL;
-
- @@ -190,8 +224,10 @@ purple_media_manager_init (PurpleMediaMa
- media->priv->medias = NULL;
- media->priv->private_medias = NULL;
- media->priv->next_output_window_id = 1;
- -#ifdef USE_VV
- media->priv->backend_type = PURPLE_TYPE_MEDIA_BACKEND_FS2;
- +#ifdef HAVE_MEDIA_APPLICATION
- + media->priv->appdata_info = NULL;
- + g_mutex_init (&media->priv->appdata_mutex);
- #endif
-
- purple_prefs_add_none("/purple/media");
- @@ -220,6 +256,13 @@ purple_media_manager_finalize (GObject *
- }
- if (priv->video_caps)
- gst_caps_unref(priv->video_caps);
- +#ifdef HAVE_MEDIA_APPLICATION
- + if (priv->appdata_info)
- + g_list_free_full (priv->appdata_info,
- + (GDestroyNotify) free_appdata_info_locked);
- + g_mutex_clear (&priv->appdata_mutex);
- +#endif
- +
- parent_class->finalize(media);
- }
- #endif
- @@ -440,8 +483,23 @@ purple_media_manager_remove_media(Purple
- medias = &manager->priv->private_medias;
- }
-
- - if (list)
- + if (list) {
- *medias = g_list_delete_link(*medias, list);
- +
- +#ifdef HAVE_MEDIA_APPLICATION
- + g_mutex_lock (&manager->priv->appdata_mutex);
- + for (list = manager->priv->appdata_info; list; list = list->next) {
- + PurpleMediaAppDataInfo *info = list->data;
- +
- + if (info->media == media) {
- + manager->priv->appdata_info = g_list_delete_link (
- + manager->priv->appdata_info, list);
- + free_appdata_info_locked (info);
- + }
- + }
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- +#endif
- + }
- #endif
- }
-
- @@ -493,6 +551,92 @@ purple_media_manager_get_private_media_b
- return get_media_by_account (manager, account, TRUE);
- }
-
- +#ifdef HAVE_MEDIA_APPLICATION
- +static void
- +free_appdata_info_locked (PurpleMediaAppDataInfo *info)
- +{
- + if (info->notify)
- + info->notify (info->user_data);
- +
- + /* Make sure no other thread is using the structure */
- + g_free (info->session_id);
- + g_free (info->participant);
- +
- + if (info->readable_timer_id) {
- + purple_timeout_remove (info->readable_timer_id);
- + info->readable_timer_id = 0;
- + }
- +
- + if (info->writable_timer_id) {
- + purple_timeout_remove (info->writable_timer_id);
- + info->writable_timer_id = 0;
- + }
- +
- + if (info->current_sample)
- + gst_sample_unref (info->current_sample);
- + info->current_sample = NULL;
- +
- + /* Unblock any reading thread before destroying the GCond */
- + g_cond_broadcast (&info->readable_cond);
- +
- + g_cond_clear (&info->readable_cond);
- +
- + g_slice_free (PurpleMediaAppDataInfo, info);
- +}
- +
- +/*
- + * Get an app data info struct associated with a session and lock the mutex
- + * We don't want to return an info struct and unlock then it gets destroyed
- + * so we need to return it with the lock still taken
- + */
- +static PurpleMediaAppDataInfo *
- +get_app_data_info_and_lock (PurpleMediaManager *manager,
- + PurpleMedia *media, const gchar *session_id, const gchar *participant)
- +{
- + GList *i;
- +
- + g_mutex_lock (&manager->priv->appdata_mutex);
- + for (i = manager->priv->appdata_info; i; i = i->next) {
- + PurpleMediaAppDataInfo *info = i->data;
- +
- + if (info->media == media &&
- + g_strcmp0 (info->session_id, session_id) == 0 &&
- + (participant == NULL ||
- + g_strcmp0 (info->participant, participant) == 0)) {
- + return info;
- + }
- + }
- +
- + return NULL;
- +}
- +
- +/*
- + * Get an app data info struct associated with a session and lock the mutex
- + * if it doesn't exist, we create it.
- + */
- +static PurpleMediaAppDataInfo *
- +ensure_app_data_info_and_lock (PurpleMediaManager *manager, PurpleMedia *media,
- + const gchar *session_id, const gchar *participant)
- +{
- + PurpleMediaAppDataInfo * info = get_app_data_info_and_lock (manager, media,
- + session_id, participant);
- +
- + if (info == NULL) {
- + info = g_slice_new0 (PurpleMediaAppDataInfo);
- + info->media = media;
- + g_weak_ref_init (&info->media_ref, media);
- + info->session_id = g_strdup (session_id);
- + info->participant = g_strdup (participant);
- + g_cond_init (&info->readable_cond);
- + manager->priv->appdata_info = g_list_prepend (
- + manager->priv->appdata_info, info);
- + }
- +
- + return info;
- +}
- +#endif
- +
- +
- #ifdef USE_VV
- static void
- request_pad_unlinked_cb(GstPad *pad, GstPad *peer, gpointer user_data)
- @@ -577,6 +721,351 @@ purple_media_manager_get_video_caps(Purp
- #endif
- }
-
- +#ifdef HAVE_MEDIA_APPLICATION
- +/*
- + * Calls the appdata writable callback from the main thread.
- + * This needs to grab the appdata lock and make sure it didn't get destroyed
- + * before calling the callback.
- + */
- +static gboolean
- +appsrc_writable (gpointer user_data)
- +{
- + PurpleMediaManager *manager = purple_media_manager_get ();
- + PurpleMediaAppDataInfo *info = user_data;
- + void (*writable_cb) (PurpleMediaManager *manager, PurpleMedia *media,
- + const gchar *session_id, const gchar *participant, gboolean writable,
- + gpointer user_data);
- + PurpleMedia *media;
- + gchar *session_id;
- + gchar *participant;
- + gboolean writable;
- + gpointer cb_data;
- + guint *timer_id_ptr = &info->writable_timer_id;
- + guint timer_id = *timer_id_ptr;
- +
- +
- + g_mutex_lock (&manager->priv->appdata_mutex);
- + if (timer_id == 0 || timer_id != *timer_id_ptr) {
- + /* In case info was freed while we were waiting for the mutex to unlock
- + * we still have a pointer to the timer_id which should still be
- + * accessible since it's in the Glib slice allocator. It gets set to 0
- + * just after the timeout is canceled which happens also before the
- + * AppDataInfo is freed, so even if that memory slice gets reused, the
- + * timer_id would be different from its previous value (unless
- + * extremely unlucky). So checking if the value for the timer_id changed
- + * should be enough to prevent any kind of race condition in which the
- + * media/AppDataInfo gets destroyed in one thread while the timeout was
- + * triggered and is waiting on the mutex to get unlocked in this thread
- + */
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- + return FALSE;
- + }
- + writable_cb = info->callbacks.writable;
- + media = g_weak_ref_get (&info->media_ref);
- + session_id = g_strdup (info->session_id);
- + participant = g_strdup (info->participant);
- + writable = info->writable && info->connected;
- + cb_data = info->user_data;
- +
- + info->writable_timer_id = 0;
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- +
- +
- + if (writable_cb && media)
- + writable_cb (manager, media, session_id, participant, writable,
- + cb_data);
- +
- + g_object_unref (media);
- + g_free (session_id);
- + g_free (participant);
- +
- + return FALSE;
- +}
- +
- +/*
- + * Schedule a writable callback to be called from the main thread.
- + * We need to do this because need-data/enough-data signals from appsrc
- + * will come from the streaming thread and we need to create
- + * a source that we attach to the main context but we can't use
- + * g_main_context_invoke since we need to be able to cancel the source if the
- + * media gets destroyed.
- + * We use a timeout source instead of idle source, so the callback gets a higher
- + * priority
- + */
- +static void
- +call_appsrc_writable_locked (PurpleMediaAppDataInfo *info)
- +{
- + /* We already have a writable callback scheduled, don't create another one */
- + if (info->writable_timer_id || info->callbacks.writable == NULL)
- + return;
- +
- + info->writable_timer_id = purple_timeout_add (0, appsrc_writable, info);
- +}
- +
- +static void
- +appsrc_need_data (GstAppSrc *appsrc, guint length, gpointer user_data)
- +{
- + PurpleMediaAppDataInfo *info = user_data;
- + PurpleMediaManager *manager = purple_media_manager_get ();
- +
- + g_mutex_lock (&manager->priv->appdata_mutex);
- + if (!info->writable) {
- + info->writable = TRUE;
- + /* Only signal writable if we also established a connection */
- + if (info->connected)
- + call_appsrc_writable_locked (info);
- + }
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- +}
- +
- +static void
- +appsrc_enough_data (GstAppSrc *appsrc, gpointer user_data)
- +{
- + PurpleMediaAppDataInfo *info = user_data;
- + PurpleMediaManager *manager = purple_media_manager_get ();
- +
- + g_mutex_lock (&manager->priv->appdata_mutex);
- + if (info->writable) {
- + info->writable = FALSE;
- + call_appsrc_writable_locked (info);
- + }
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- +}
- +
- +static gboolean
- +appsrc_seek_data (GstAppSrc *appsrc, guint64 offset, gpointer user_data)
- +{
- + return FALSE;
- +}
- +
- +static void
- +appsrc_destroyed (PurpleMediaAppDataInfo *info)
- +{
- + PurpleMediaManager *manager = purple_media_manager_get ();
- +
- + g_mutex_lock (&manager->priv->appdata_mutex);
- + info->appsrc = NULL;
- + if (info->writable) {
- + info->writable = FALSE;
- + call_appsrc_writable_locked (info);
- + }
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- +}
- +
- +static void
- +media_established_cb (PurpleMedia *media,const gchar *session_id,
- + const gchar *participant, PurpleMediaCandidate *local_candidate,
- + PurpleMediaCandidate *remote_candidate, PurpleMediaAppDataInfo *info)
- +{
- + PurpleMediaManager *manager = purple_media_manager_get ();
- +
- + g_mutex_lock (&manager->priv->appdata_mutex);
- + info->connected = TRUE;
- + /* We established the connection, if we were writable, then we need to
- + * signal it now */
- + if (info->writable)
- + call_appsrc_writable_locked (info);
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- +}
- +
- +static GstElement *
- +create_send_appsrc(PurpleMedia *media,
- + const gchar *session_id, const gchar *participant)
- +{
- + PurpleMediaManager *manager = purple_media_manager_get ();
- + PurpleMediaAppDataInfo * info = ensure_app_data_info_and_lock (manager,
- + media, session_id, participant);
- + GstElement *appsrc = (GstElement *)info->appsrc;
- +
- + if (appsrc == NULL) {
- + GstAppSrcCallbacks callbacks = {appsrc_need_data, appsrc_enough_data,
- + appsrc_seek_data, {NULL}};
- + GstCaps *caps = gst_caps_new_empty_simple ("application/octet-stream");
- +
- + appsrc = gst_element_factory_make("appsrc", NULL);
- +
- + info->appsrc = (GstAppSrc *)appsrc;
- +
- + gst_app_src_set_caps (info->appsrc, caps);
- + gst_app_src_set_callbacks (info->appsrc,
- + &callbacks, info, (GDestroyNotify) appsrc_destroyed);
- + g_signal_connect (media, "candidate-pair-established",
- + (GCallback) media_established_cb, info);
- + gst_caps_unref (caps);
- + }
- +
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- + return appsrc;
- +}
- +
- +static void
- +appsink_eos (GstAppSink *appsink, gpointer user_data)
- +{
- +}
- +
- +static GstFlowReturn
- +appsink_new_preroll (GstAppSink *appsink, gpointer user_data)
- +{
- + return GST_FLOW_OK;
- +}
- +
- +static gboolean
- +appsink_readable (gpointer user_data)
- +{
- + PurpleMediaManager *manager = purple_media_manager_get ();
- + PurpleMediaAppDataInfo *info = user_data;
- + void (*readable_cb) (PurpleMediaManager *manager, PurpleMedia *media,
- + const gchar *session_id, const gchar *participant, gpointer user_data);
- + PurpleMedia *media;
- + gchar *session_id;
- + gchar *participant;
- + gpointer cb_data;
- + guint *timer_id_ptr = &info->readable_timer_id;
- + guint timer_id = *timer_id_ptr;
- +
- + g_mutex_lock (&manager->priv->appdata_mutex);
- + if (timer_id == 0 || timer_id != *timer_id_ptr) {
- + /* Avoided a race condition (see writable callback) */
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- + return FALSE;
- + }
- + /* We need to signal readable until there are no more samples */
- + while (info->callbacks.readable &&
- + (info->num_samples > 0 || info->current_sample != NULL)) {
- + readable_cb = info->callbacks.readable;
- + media = g_weak_ref_get (&info->media_ref);
- + session_id = g_strdup (info->session_id);
- + participant = g_strdup (info->participant);
- + cb_data = info->user_data;
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- +
- + if (readable_cb)
- + readable_cb (manager, media, session_id, participant, cb_data);
- +
- + g_mutex_lock (&manager->priv->appdata_mutex);
- + g_object_unref (media);
- + g_free (session_id);
- + g_free (participant);
- + if (timer_id == 0 || timer_id != *timer_id_ptr) {
- + /* We got cancelled */
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- + return FALSE;
- + }
- + }
- + info->readable_timer_id = 0;
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- + return FALSE;
- +}
- +
- +static void
- +call_appsink_readable_locked (PurpleMediaAppDataInfo *info)
- +{
- + /* We must signal that a new sample has arrived to release blocking reads */
- + g_cond_broadcast (&info->readable_cond);
- +
- + /* We already have a writable callback scheduled, don't create another one */
- + if (info->readable_timer_id || info->callbacks.readable == NULL)
- + return;
- +
- + info->readable_timer_id = purple_timeout_add (0, appsink_readable, info);
- +}
- +
- +static GstFlowReturn
- +appsink_new_sample (GstAppSink *appsink, gpointer user_data)
- +{
- + PurpleMediaManager *manager = purple_media_manager_get ();
- + PurpleMediaAppDataInfo *info = user_data;
- +
- + g_mutex_lock (&manager->priv->appdata_mutex);
- + info->num_samples++;
- + call_appsink_readable_locked (info);
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- +
- + return GST_FLOW_OK;
- +}
- +
- +static void
- +appsink_destroyed (PurpleMediaAppDataInfo *info)
- +{
- + PurpleMediaManager *manager = purple_media_manager_get ();
- +
- + g_mutex_lock (&manager->priv->appdata_mutex);
- + info->appsink = NULL;
- + info->num_samples = 0;
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- +}
- +
- +static GstElement *
- +create_recv_appsink(PurpleMedia *media,
- + const gchar *session_id, const gchar *participant)
- +{
- + PurpleMediaManager *manager = purple_media_manager_get ();
- + PurpleMediaAppDataInfo * info = ensure_app_data_info_and_lock (manager,
- + media, session_id, participant);
- + GstElement *appsink = (GstElement *)info->appsink;
- +
- + if (appsink == NULL) {
- + GstAppSinkCallbacks callbacks = {appsink_eos, appsink_new_preroll,
- + appsink_new_sample, {NULL}};
- + GstCaps *caps = gst_caps_new_empty_simple ("application/octet-stream");
- +
- + appsink = gst_element_factory_make("appsink", NULL);
- +
- + info->appsink = (GstAppSink *)appsink;
- +
- + gst_app_sink_set_caps (info->appsink, caps);
- + gst_app_sink_set_callbacks (info->appsink,
- + &callbacks, info, (GDestroyNotify) appsink_destroyed);
- + gst_caps_unref (caps);
- +
- + }
- +
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- + return appsink;
- +}
- +#endif
- +
- +static PurpleMediaElementInfo *
- +get_send_application_element_info ()
- +{
- + static PurpleMediaElementInfo *info = NULL;
- +
- +#ifdef HAVE_MEDIA_APPLICATION
- + if (info == NULL) {
- + info = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
- + "id", "pidginappsrc",
- + "name", "Pidgin Application Source",
- + "type", PURPLE_MEDIA_ELEMENT_APPLICATION
- + | PURPLE_MEDIA_ELEMENT_SRC
- + | PURPLE_MEDIA_ELEMENT_ONE_SRC,
- + "create-cb", create_send_appsrc, NULL);
- + }
- +#endif
- +
- + return info;
- +}
- +
- +
- +static PurpleMediaElementInfo *
- +get_recv_application_element_info ()
- +{
- + static PurpleMediaElementInfo *info = NULL;
- +
- +#ifdef HAVE_MEDIA_APPLICATION
- + if (info == NULL) {
- + info = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
- + "id", "pidginappsink",
- + "name", "Pidgin Application Sink",
- + "type", PURPLE_MEDIA_ELEMENT_APPLICATION
- + | PURPLE_MEDIA_ELEMENT_SINK
- + | PURPLE_MEDIA_ELEMENT_ONE_SINK,
- + "create-cb", create_recv_appsink, NULL);
- + }
- +#endif
- +
- + return info;
- +}
- +
- GstElement *
- purple_media_manager_get_element(PurpleMediaManager *manager,
- PurpleMediaSessionType type, PurpleMedia *media,
- @@ -595,6 +1084,10 @@ purple_media_manager_get_element(PurpleM
- info = manager->priv->video_src;
- else if (type & PURPLE_MEDIA_RECV_VIDEO)
- info = manager->priv->video_sink;
- + else if (type & PURPLE_MEDIA_SEND_APPLICATION)
- + info = get_send_application_element_info ();
- + else if (type & PURPLE_MEDIA_RECV_APPLICATION)
- + info = get_recv_application_element_info ();
-
- if (info == NULL)
- return NULL;
- @@ -834,11 +1327,16 @@ purple_media_manager_get_active_element(
- return manager->priv->audio_src;
- else if (type & PURPLE_MEDIA_ELEMENT_VIDEO)
- return manager->priv->video_src;
- + else if (type & PURPLE_MEDIA_ELEMENT_APPLICATION)
- + return get_send_application_element_info ();
- } else if (type & PURPLE_MEDIA_ELEMENT_SINK) {
- if (type & PURPLE_MEDIA_ELEMENT_AUDIO)
- return manager->priv->audio_sink;
- else if (type & PURPLE_MEDIA_ELEMENT_VIDEO)
- return manager->priv->video_sink;
- + else if (type & PURPLE_MEDIA_ELEMENT_APPLICATION)
- + return get_recv_application_element_info ();
- +
- }
- #endif
-
- @@ -1138,6 +1636,174 @@ purple_media_manager_get_backend_type(Pu
- #endif
- }
-
- +void
- +purple_media_manager_set_application_data_callbacks(PurpleMediaManager *manager,
- + PurpleMedia *media, const gchar *session_id,
- + const gchar *participant, PurpleMediaAppDataCallbacks *callbacks,
- + gpointer user_data, GDestroyNotify notify)
- +{
- +#ifdef HAVE_MEDIA_APPLICATION
- + PurpleMediaAppDataInfo * info = ensure_app_data_info_and_lock (manager,
- + media, session_id, participant);
- +
- + if (info->notify)
- + info->notify (info->user_data);
- +
- + if (info->readable_timer_id) {
- + purple_timeout_remove (info->readable_timer_id);
- + info->readable_timer_id = 0;
- + }
- +
- + if (info->writable_timer_id) {
- + purple_timeout_remove (info->writable_timer_id);
- + info->writable_timer_id = 0;
- + }
- +
- + if (callbacks) {
- + info->callbacks = *callbacks;
- + } else {
- + info->callbacks.writable = NULL;
- + info->callbacks.readable = NULL;
- + }
- + info->user_data = user_data;
- + info->notify = notify;
- +
- + call_appsrc_writable_locked (info);
- + if (info->num_samples > 0 || info->current_sample != NULL)
- + call_appsink_readable_locked (info);
- +
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- +#endif
- +}
- +
- +gint
- +purple_media_manager_send_application_data (
- + PurpleMediaManager *manager, PurpleMedia *media, const gchar *session_id,
- + const gchar *participant, gpointer buffer, guint size, gboolean blocking)
- +{
- +#ifdef HAVE_MEDIA_APPLICATION
- + PurpleMediaAppDataInfo * info = get_app_data_info_and_lock (manager,
- + media, session_id, participant);
- +
- + if (info && info->appsrc && info->connected) {
- + GstBuffer *gstbuffer = gst_buffer_new_wrapped (g_memdup (buffer, size),
- + size);
- + GstAppSrc *appsrc = gst_object_ref (info->appsrc);
- +
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- + if (gst_app_src_push_buffer (appsrc, gstbuffer) == GST_FLOW_OK) {
- + if (blocking) {
- + GstPad *srcpad;
- +
- + srcpad = gst_element_get_static_pad (GST_ELEMENT (appsrc),
- + "src");
- + if (srcpad) {
- + gst_pad_peer_query (srcpad, gst_query_new_drain ());
- + gst_object_unref (srcpad);
- + }
- + }
- + gst_object_unref (appsrc);
- + return size;
- + } else {
- + gst_object_unref (appsrc);
- + return -1;
- + }
- + }
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- + return -1;
- +#else
- + return -1;
- +#endif
- +}
- +
- +gint
- +purple_media_manager_receive_application_data (
- + PurpleMediaManager *manager, PurpleMedia *media, const gchar *session_id,
- + const gchar *participant, gpointer buffer, guint max_size,
- + gboolean blocking)
- +{
- +#ifdef HAVE_MEDIA_APPLICATION
- + PurpleMediaAppDataInfo * info = get_app_data_info_and_lock (manager,
- + media, session_id, participant);
- + guint bytes_read = 0;
- +
- + if (info) {
- + /* If we are in a blocking read, we need to loop until max_size data
- + * is read into the buffer, if we're not, then we need to read as much
- + * data as possible
- + */
- + do {
- + if (!info->current_sample && info->appsink && info->num_samples > 0) {
- + info->current_sample = gst_app_sink_pull_sample (info->appsink);
- + info->sample_offset = 0;
- + if (info->current_sample)
- + info->num_samples--;
- + }
- +
- + if (info->current_sample) {
- + GstBuffer *gstbuffer = gst_sample_get_buffer (
- + info->current_sample);
- +
- + if (gstbuffer) {
- + GstMapInfo mapinfo;
- + guint bytes_to_copy;
- +
- + gst_buffer_map (gstbuffer, &mapinfo, GST_MAP_READ);
- + /* We must copy only the data remaining in the buffer without
- + * overflowing the buffer */
- + bytes_to_copy = max_size - bytes_read;
- + if (bytes_to_copy > mapinfo.size - info->sample_offset)
- + bytes_to_copy = mapinfo.size - info->sample_offset;
- + memcpy ((guint8 *)buffer + bytes_read,
- + mapinfo.data + info->sample_offset, bytes_to_copy);
- +
- + gst_buffer_unmap (gstbuffer, &mapinfo);
- + info->sample_offset += bytes_to_copy;
- + bytes_read += bytes_to_copy;
- + if (info->sample_offset == mapinfo.size) {
- + gst_sample_unref (info->current_sample);
- + info->current_sample = NULL;
- + info->sample_offset = 0;
- + }
- + } else {
- + /* In case there's no buffer in the sample (should never
- + * happen), we need to at least unref it */
- + gst_sample_unref (info->current_sample);
- + info->current_sample = NULL;
- + info->sample_offset = 0;
- + }
- + }
- +
- + /* If blocking, wait until there's an available sample */
- + while (bytes_read < max_size && blocking &&
- + info->current_sample == NULL && info->num_samples == 0) {
- + g_cond_wait (&info->readable_cond, &manager->priv->appdata_mutex);
- +
- + /* We've been signaled, we need to unlock and regrab the info
- + * struct to make sure nothing changed */
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- + info = get_app_data_info_and_lock (manager,
- + media, session_id, participant);
- + if (info == NULL || info->appsink == NULL) {
- + /* The session was destroyed while we were waiting, we
- + * should return here */
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- + return bytes_read;
- + }
- + }
- + } while (bytes_read < max_size &&
- + (blocking || info->num_samples > 0));
- +
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- + return bytes_read;
- + }
- + g_mutex_unlock (&manager->priv->appdata_mutex);
- + return -1;
- +#else
- + return -1;
- +#endif
- +}
- +
- #ifdef USE_GSTREAMER
-
- /*
- @@ -1185,6 +1851,8 @@ purple_media_element_type_get_type()
- "PURPLE_MEDIA_ELEMENT_SRC", "src" },
- { PURPLE_MEDIA_ELEMENT_SINK,
- "PURPLE_MEDIA_ELEMENT_SINK", "sink" },
- + { PURPLE_MEDIA_ELEMENT_APPLICATION,
- + "PURPLE_MEDIA_ELEMENT_APPLICATION", "application" },
- { 0, NULL, NULL }
- };
- type = g_flags_register_static(
- @@ -1410,5 +2078,6 @@ purple_media_element_info_call_create(Pu
- return NULL;
- }
-
- +
- #endif /* USE_GSTREAMER */
-
- Index: pidgin-2.10.11/libpurple/mediamanager.h
- ===================================================================
- --- pidgin-2.10.11.orig/libpurple/mediamanager.h
- +++ pidgin-2.10.11/libpurple/mediamanager.h
- @@ -38,6 +38,30 @@ typedef struct _PurpleMediaManagerClass
- #include "account.h"
- #include "media.h"
-
- +/**
- + * PurpleMediaAppDataCallbacks:
- + * @readable: Called when the stream has received data and is readable.
- + * @writable: Called when the stream has become writable or has stopped being
- + * writable.
- + *
- + * A set of callbacks that can be installed on an Application data session with
- + * purple_media_manager_set_application_data_callbacks()
- + *
- + * Once installed the @readable callback will get called as long as data is
- + * available to read, so the data must be read completely.
- + * The @writable callback will only be called when the writable state of the
- + * stream changes. The @writable argument defines whether the stream has
- + * become writable or stopped being writable.
- + *
- + */
- +typedef struct {
- + void (*readable) (PurpleMediaManager *manager, PurpleMedia *media,
- + const gchar *session_id, const gchar *participant, gpointer user_data);
- + void (*writable) (PurpleMediaManager *manager, PurpleMedia *media,
- + const gchar *session_id, const gchar *participant, gboolean writable,
- + gpointer user_data);
- +} PurpleMediaAppDataCallbacks;
- +
- G_BEGIN_DECLS
-
- #define PURPLE_TYPE_MEDIA_MANAGER (purple_media_manager_get_type())
- @@ -285,6 +309,71 @@ void purple_media_manager_set_backend_ty
- */
- GType purple_media_manager_get_backend_type(PurpleMediaManager *manager);
-
- +/**
- + * purple_media_manager_set_application_data_callbacks:
- + * @manager: The manager to register the callbacks with.
- + * @media: The media instance to register the callbacks with.
- + * @session_id: The session to register the callbacks with.
- + * @participant: The participant to register the callbacks with.
- + * @callbacks: The callbacks to be set on the session.
- + * @user_data: a user_data argument for the callbacks.
- + * @notify: a destroy notify function.
- + *
- + * Set callbacks on a session to be called when the stream becomes writable
- + * or readable for media sessions of type #PURPLE_MEDIA_APPLICATION
- + */
- +void purple_media_manager_set_application_data_callbacks(
- + PurpleMediaManager *manager, PurpleMedia *media, const gchar *session_id,
- + const gchar *participant, PurpleMediaAppDataCallbacks *callbacks,
- + gpointer user_data, GDestroyNotify notify);
- +
- +/**
- + * purple_media_manager_send_application_data:
- + * @manager: The manager to send data with.
- + * @media: The media instance to which the session belongs.
- + * @session_id: The session to send data to.
- + * @participant: The participant to send data to.
- + * @buffer: The buffer of data to send.
- + * @size: The size of @buffer
- + * @blocking: Whether to block until the data was send or not.
- + *
- + * Sends a buffer of data to a #PURPLE_MEDIA_APPLICATION session.
- + * If @blocking is set, unless an error occured, the function will not return
- + * until the data has been flushed into the network.
- + * If the stream is not writable, the data will be queued. It is the
- + * responsability of the user to stop sending data when the stream isn't
- + * writable anymore. It is also the responsability of the user to only start
- + * sending data after the stream has been configured correctly (encryption
- + * parameters for example).
- + *
- + * Returns: Number of bytes sent or -1 in case of error.
- + */
- +gint purple_media_manager_send_application_data (
- + PurpleMediaManager *manager, PurpleMedia *media, const gchar *session_id,
- + const gchar *participant, gpointer buffer, guint size, gboolean blocking);
- +
- +/**
- + * purple_media_manager_receive_application_data:
- + * @manager: The manager to receive data with.
- + * @media: The media instance to which the session belongs.
- + * @session_id: The session to receive data from.
- + * @participant: The participant to receive data from.
- + * @buffer: The buffer to receive data into.
- + * @max_size: The max_size of @buffer
- + * @blocking: Whether to block until the buffer is entirely filled or return
- + * with currently available data.
- + *
- + * Receive a buffer of data from a #PURPLE_MEDIA_APPLICATION session.
- + * If @blocking is set, unless an error occured, the function will not return
- + * until @max_size bytes are read.
- + *
- + * Returns: Number of bytes received or -1 in case of error.
- + */
- +gint purple_media_manager_receive_application_data (
- + PurpleMediaManager *manager, PurpleMedia *media, const gchar *session_id,
- + const gchar *participant, gpointer buffer, guint max_size,
- + gboolean blocking);
- +
- /*}@*/
-
- #ifdef __cplusplus
|