summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorOlivier CrĂȘte <olivier.crete@collabora.co.uk>2009-02-23 16:47:42 -0500
committerOlivier CrĂȘte <olivier.crete@collabora.co.uk>2009-02-23 16:49:13 -0500
commit7ebd18f5fa4c235107f35a76adf7c3262749b912 (patch)
tree0e2d1ce82161d6955d4c9e8f4b637b979eaecbe6 /examples
parent548f9520d56d7a0efd23482e1b02ff63d2a3e5be (diff)
downloadfarstream-7ebd18f5fa4c235107f35a76adf7c3262749b912.tar.gz
Move examples to examples dir
Diffstat (limited to 'examples')
-rw-r--r--examples/Makefile.am2
-rw-r--r--examples/commandline/Makefile.am14
-rw-r--r--examples/commandline/simple-call.c339
-rw-r--r--examples/gui/Makefile.am1
-rw-r--r--examples/gui/fs2-gui.glade705
-rwxr-xr-xexamples/gui/fs2-gui.py929
-rw-r--r--examples/gui/fs2_gui_net.py456
7 files changed, 2446 insertions, 0 deletions
diff --git a/examples/Makefile.am b/examples/Makefile.am
new file mode 100644
index 00000000..3099ae23
--- /dev/null
+++ b/examples/Makefile.am
@@ -0,0 +1,2 @@
+
+SUBDIRS = gui commandline
diff --git a/examples/commandline/Makefile.am b/examples/commandline/Makefile.am
new file mode 100644
index 00000000..d374e674
--- /dev/null
+++ b/examples/commandline/Makefile.am
@@ -0,0 +1,14 @@
+
+noinst_PROGRAMS = simple-call
+
+
+AM_CFLAGS = \
+ -I$(top_srcdir)/gst/fsrtpconference/ \
+ $(FS2_INTERNAL_CFLAGS) \
+ $(FS2_CFLAGS) \
+ $(GST_CFLAGS) \
+ $(CFLAGS)
+
+LDADD = \
+ $(top_builddir)/gst-libs/gst/farsight/libgstfarsight-0.10.la \
+ $(GST_LIBS)
diff --git a/examples/commandline/simple-call.c b/examples/commandline/simple-call.c
new file mode 100644
index 00000000..fbc56fbf
--- /dev/null
+++ b/examples/commandline/simple-call.c
@@ -0,0 +1,339 @@
+/* Farsight 2 ad-hoc test for simple calls.
+ *
+ * Copyright (C) 2008 Collabora, Nokia
+ * @author: Olivier Crete <olivier.crete@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * WARNING:
+ *
+ * Do not use this as an example of a proper use of farsight, it assumes that
+ * both ends have the EXACT same list of codec installed in the EXACT same order
+ */
+
+
+#include <stdlib.h>
+
+#include <glib.h>
+#include <gst/gst.h>
+#include <gst/farsight/fs-conference-iface.h>
+
+#define DEFAULT_AUDIOSRC "alsasrc"
+#define DEFAULT_AUDIOSINK "audioconvert ! audioresample ! audioconvert ! alsasink"
+
+typedef struct _TestSession
+{
+ FsSession *session;
+ FsStream *stream;
+} TestSession;
+
+
+static void
+print_error (GError *error)
+{
+ if (error)
+ {
+ g_error ("Error: %s:%d : %s", g_quark_to_string (error->domain),
+ error->code, error->message);
+ }
+}
+
+static void
+src_pad_added_cb (FsStream *stream, GstPad *pad, FsCodec *codec,
+ gpointer user_data)
+{
+ GstElement *pipeline = GST_ELEMENT_CAST (user_data);
+ GstElement *sink = NULL;
+ GError *error = NULL;
+ GstPad *pad2;
+
+ g_print ("Adding receive pipeline\n");
+
+ if (g_getenv ("AUDIOSINK"))
+ sink = gst_parse_bin_from_description (g_getenv ("AUDIOSINK"), TRUE,
+ &error);
+ else
+ sink = gst_parse_bin_from_description (DEFAULT_AUDIOSINK, TRUE,
+ &error);
+ print_error (error);
+ g_assert (sink);
+
+ g_assert (gst_bin_add (GST_BIN (pipeline), sink));
+
+
+ pad2 = gst_element_get_static_pad (sink, "sink");
+ g_assert (pad2);
+
+ g_assert (GST_PAD_LINK_SUCCESSFUL (gst_pad_link (pad, pad2)));
+
+ g_assert (gst_element_set_state (sink, GST_STATE_PLAYING) !=
+ GST_STATE_CHANGE_FAILURE);
+
+ gst_object_unref (pad2);
+}
+
+static TestSession*
+add_audio_session (GstElement *pipeline, FsConference *conf, guint id,
+ FsParticipant *part, guint localport, const gchar *remoteip,
+ guint remoteport)
+{
+ TestSession *ses = g_slice_new0 (TestSession);
+ GError *error = NULL;
+ GstPad *pad = NULL, *pad2 = NULL;
+ GstElement *src = NULL;
+ GList *cands = NULL;
+ GList *codecs = NULL;
+ GParameter param = {0};
+ gboolean res;
+
+ ses->session = fs_conference_new_session (conf, FS_MEDIA_TYPE_AUDIO, &error);
+ print_error (error);
+ g_assert (ses->session);
+
+ g_object_get (ses->session, "sink-pad", &pad, NULL);
+
+ if (g_getenv ("AUDIOSRC"))
+ src = gst_parse_bin_from_description (g_getenv ("AUDIOSRC"), TRUE,
+ &error);
+ else
+ src = gst_parse_bin_from_description (DEFAULT_AUDIOSRC, TRUE,
+ &error);
+ print_error (error);
+ g_assert (src);
+
+ g_assert (gst_bin_add (GST_BIN (pipeline), src));
+
+ pad2 = gst_element_get_static_pad (src, "src");
+ g_assert (pad2);
+
+ g_assert (GST_PAD_LINK_SUCCESSFUL (gst_pad_link (pad2, pad)));
+
+ gst_object_unref (pad2);
+ gst_object_unref (pad);
+
+
+ cands = g_list_prepend (NULL, fs_candidate_new ("", FS_COMPONENT_RTP,
+ FS_CANDIDATE_TYPE_HOST, FS_NETWORK_PROTOCOL_UDP, NULL, localport));
+
+ param.name = "preferred-local-candidates";
+ g_value_init (&param.value, FS_TYPE_CANDIDATE_LIST);
+ g_value_take_boxed (&param.value, cands);
+
+ ses->stream = fs_session_new_stream (ses->session, part, FS_DIRECTION_BOTH,
+ "rawudp", 1, &param, &error);
+ print_error (error);
+ g_assert (ses->stream);
+
+ g_value_unset (&param.value);
+
+ g_signal_connect (ses->stream, "src-pad-added",
+ G_CALLBACK (src_pad_added_cb), pipeline);
+
+ cands = g_list_prepend (NULL, fs_candidate_new ("", FS_COMPONENT_RTP,
+ FS_CANDIDATE_TYPE_HOST, FS_NETWORK_PROTOCOL_UDP, remoteip,
+ remoteport));
+
+ res = fs_stream_set_remote_candidates (ses->stream, cands, &error);
+ print_error (error);
+ g_assert (res);
+
+ fs_candidate_list_destroy (cands);
+
+ codecs = g_list_prepend (NULL,
+ fs_codec_new (FS_CODEC_ID_ANY, "PCMA", FS_MEDIA_TYPE_AUDIO, 0));
+ codecs = g_list_prepend (codecs,
+ fs_codec_new (FS_CODEC_ID_ANY, "PCMU", FS_MEDIA_TYPE_AUDIO, 0));
+
+ res = fs_session_set_codec_preferences (ses->session, codecs, &error);
+ print_error (error);
+ fs_codec_list_destroy (codecs);
+
+
+ g_object_get (ses->session, "codecs", &codecs, NULL);
+ res = fs_stream_set_remote_codecs (ses->stream, codecs, &error);
+ print_error (error);
+ g_assert (res);
+
+ return ses;
+}
+
+static gboolean
+async_bus_cb (GstBus *bus, GstMessage *message, gpointer user_data)
+{
+ switch (GST_MESSAGE_TYPE(message))
+ {
+ case GST_MESSAGE_ERROR:
+ {
+ GError *error = NULL;
+ gchar *debug_str = NULL;
+
+ gst_message_parse_error (message, &error, &debug_str);
+ g_error ("Got gst message: %s %s", error->message, debug_str);
+ }
+ break;
+ case GST_MESSAGE_WARNING:
+ {
+ GError *error = NULL;
+ gchar *debug_str = NULL;
+
+ gst_message_parse_warning (message, &error, &debug_str);
+ g_warning ("Got gst message: %s %s", error->message, debug_str);
+ }
+ break;
+ case GST_MESSAGE_ELEMENT:
+ {
+ const GstStructure *s = gst_message_get_structure (message);
+
+ if (gst_structure_has_name (s, "farsight-error"))
+ {
+ gint error;
+ const gchar *error_msg = gst_structure_get_string (s, "error-msg");
+ const gchar *debug_msg = gst_structure_get_string (s, "debug-msg");
+
+ g_assert (gst_structure_get_enum (s, "error-no", FS_TYPE_ERROR,
+ &error));
+
+ if (FS_ERROR_IS_FATAL (error))
+ g_error ("Farsight fatal error: %d %s %s", error, error_msg,
+ debug_msg);
+ else
+ g_warning ("Farsight non-fatal error: %d %s %s", error, error_msg,
+ debug_msg);
+ }
+ else if (gst_structure_has_name (s, "farsight-new-local-candidate"))
+ {
+ const GValue *val = gst_structure_get_value (s, "candidate");
+ FsCandidate *cand = NULL;
+
+ g_assert (val);
+ cand = g_value_get_boxed (val);
+
+ g_print ("New candidate: %s %d\n", cand->ip, cand->port);
+ }
+ else if (gst_structure_has_name (s,
+ "farsight-local-candidates-prepared"))
+ {
+ g_print ("Local candidates prepared\n");
+ }
+ else if (gst_structure_has_name (s, "farsight-recv-codecs-changed"))
+ {
+ const GValue *val = gst_structure_get_value (s, "codecs");
+ GList *codecs = NULL;
+
+ g_assert (val);
+ codecs = g_value_get_boxed (val);
+
+ g_print ("Recv codecs changed:\n");
+ for (; codecs; codecs = g_list_next (codecs))
+ {
+ FsCodec *codec = codecs->data;
+ gchar *tmp = fs_codec_to_string (codec);
+ g_print ("%s\n", tmp);
+ g_free (tmp);
+ }
+ }
+ else if (gst_structure_has_name (s, "farsight-send-codec-changed"))
+ {
+ const GValue *val = gst_structure_get_value (s, "codec");
+ FsCodec *codec = NULL;
+ gchar *tmp;
+ g_assert (val);
+ codec = g_value_get_boxed (val);
+ tmp = fs_codec_to_string (codec);
+
+ g_print ("Send codec changed: %s\n", tmp);
+ g_free (tmp);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+int main (int argc, char **argv)
+{
+ GMainLoop *loop = NULL;
+ GstElement *pipeline = NULL;
+ GstBus *bus = NULL;
+ const gchar *remoteip;
+ guint localport = 0;
+ guint remoteport = 0;
+ GstElement *conf = NULL;
+ FsParticipant *part = NULL;
+ GError *error = NULL;
+
+ gst_init (&argc, &argv);
+
+ if (argc != 4)
+ {
+ g_print ("Usage: %s <local port> <remoteip> <remoteport>\n",
+ argv[0]);
+ return 1;
+ }
+
+ localport = atoi (argv[1]);
+ remoteip = argv[2];
+ remoteport = atoi (argv[3]);
+
+ if (!localport || !remoteip || !remoteport)
+ {
+ g_print ("Usage: %s <local port> <remoteip> <remoteport>\n",
+ argv[0]);
+ return 2;
+ }
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ pipeline = gst_pipeline_new (NULL);
+
+ bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
+ gst_bus_add_watch (bus, async_bus_cb, pipeline);
+ gst_object_unref (bus);
+
+ conf = gst_element_factory_make ("fsrtpconference", NULL);
+ g_assert (conf);
+
+ part = fs_conference_new_participant (FS_CONFERENCE (conf), "test@ignore",
+ &error);
+ print_error (error);
+ g_assert (part);
+
+ g_assert (gst_bin_add (GST_BIN (pipeline), conf));
+
+
+ add_audio_session (pipeline, FS_CONFERENCE (conf), 1, part, localport,
+ remoteip, remoteport);
+
+
+ g_assert (gst_element_set_state (pipeline, GST_STATE_PLAYING) !=
+ GST_STATE_CHANGE_FAILURE);
+
+ g_main_loop_run (loop);
+
+ g_assert (gst_element_set_state (pipeline, GST_STATE_NULL) !=
+ GST_STATE_CHANGE_FAILURE);
+
+ g_object_unref (part);
+
+ gst_object_unref (pipeline);
+ g_main_loop_unref (loop);
+
+ return 0;
+}
diff --git a/examples/gui/Makefile.am b/examples/gui/Makefile.am
new file mode 100644
index 00000000..10a8a396
--- /dev/null
+++ b/examples/gui/Makefile.am
@@ -0,0 +1 @@
+EXTRA_DIST = fs2-gui.glade fs2_gui_net.py fs2-gui.py
diff --git a/examples/gui/fs2-gui.glade b/examples/gui/fs2-gui.glade
new file mode 100644
index 00000000..b1b41517
--- /dev/null
+++ b/examples/gui/fs2-gui.glade
@@ -0,0 +1,705 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--Generated with glade3 3.4.0 on Tue Jun 17 16:01:07 2008 -->
+<glade-interface>
+ <widget class="GtkWindow" id="main_window">
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="border_width">6</property>
+ <property name="resizable">False</property>
+ <signal name="destroy" handler="shutdown"/>
+ <child>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="spacing">12</property>
+ <child>
+ <widget class="GtkLabel" id="info_label">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">You are now connected to</property>
+ <property name="use_markup">True</property>
+ <property name="use_underline">True</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkTable" id="users_table">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <widget class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Video:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="x_padding">6</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkComboBox" id="video_combobox">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <signal name="changed" handler="video_combobox_changed_cb"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options">GTK_EXPAND</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkComboBox" id="audio_combobox">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <signal name="changed" handler="audio_combobox_changed_cb"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options">GTK_EXPAND</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Audio:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="x_padding">6</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAspectFrame" id="preview_aspectframe">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="border_width">6</property>
+ <property name="label_xalign">0</property>
+ <property name="ratio">1.3300000429153442</property>
+ <child>
+ <widget class="GtkDrawingArea" id="preview_drawingarea">
+ <property name="width_request">160</property>
+ <property name="height_request">120</property>
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <signal name="expose_event" handler="exposed"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="frame_label">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">&lt;b&gt;User&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="spacing">6</property>
+ <property name="layout_style">GTK_BUTTONBOX_EDGE</property>
+ <child>
+ <widget class="GtkButton" id="dtmf_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">DTMF</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="show_dtmf"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="is_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">gtk-quit</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-4</property>
+ <signal name="clicked" handler="shutdown"/>
+ </widget>
+ <packing>
+ <property name="pack_type">GTK_PACK_END</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkWindow" id="window1">
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <child>
+ <widget class="GtkAspectFrame" id="user_frame">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="border_width">6</property>
+ <property name="label_xalign">0</property>
+ <property name="ratio">1.3300000429153442</property>
+ <child>
+ <widget class="GtkDrawingArea" id="user_drawingarea">
+ <property name="width_request">160</property>
+ <property name="height_request">120</property>
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <signal name="expose_event" handler="exposed"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="frame_label">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">&lt;b&gt;User&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkDialog" id="neworconnect_dialog">
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="border_width">5</property>
+ <property name="resizable">False</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="has_separator">False</property>
+ <signal name="close" handler="quit"/>
+ <signal name="destroy" handler="quit"/>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox2">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkTable" id="table3">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="border_width">6</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">12</property>
+ <child>
+ <widget class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkImage" id="image5">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="yalign">0</property>
+ <property name="icon_size">6</property>
+ <property name="icon_name">start-here</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="newip_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="text" translatable="yes">127.0.0.1</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Port: </property>
+ </widget>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">IP address: </property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Enter the IP address and the port of the server to connect to, or only the port if you want to start a new server</property>
+ <property name="wrap">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkSpinButton" id="newport_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="max_length">5</property>
+ <property name="width_chars">5</property>
+ <property name="adjustment">9392 0 65535 1 10 10</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="numeric">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_SHRINK</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area2">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <widget class="GtkButton" id="button7">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-4</property>
+ <signal name="clicked" handler="quit"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button6">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="response_id">2</property>
+ <signal name="clicked" handler="new_server"/>
+ <child>
+ <widget class="GtkHBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <child>
+ <widget class="GtkImage" id="image3">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="stock">gtk-new</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">_New server</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button5">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">gtk-connect</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">1</property>
+ <signal name="clicked" handler="connect"/>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkWindow" id="dtmf_window">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="title" translatable="yes">Send DTMF</property>
+ <child>
+ <widget class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="spacing">12</property>
+ <child>
+ <widget class="GtkVBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <child>
+ <widget class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Send DTMF as:</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioButton" id="dtmf_as_event">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">RFC-4733 DTMF Event </property>
+ <property name="response_id">0</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkRadioButton" id="dtmf_as_sound">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Sound</property>
+ <property name="response_id">0</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">dtmf_as_event</property>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">6</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <widget class="GtkButton" id="dtmf_pound">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">#</property>
+ <property name="response_id">0</property>
+ <signal name="pressed" handler="dtmf_start" object="#"/>
+ <signal name="released" handler="dtmf_stop" object="#"/>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="dtmf_0">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">0</property>
+ <property name="response_id">0</property>
+ <signal name="pressed" handler="dtmf_start" object="0"/>
+ <signal name="released" handler="dtmf_stop" object="0"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="dtmf_star">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">*</property>
+ <property name="response_id">0</property>
+ <signal name="pressed" handler="dtmf_start" object="*"/>
+ <signal name="released" handler="dtmf_stop" object="*"/>
+ </widget>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="dtmf_9">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">9</property>
+ <property name="response_id">0</property>
+ <signal name="pressed" handler="dtmf_start" object="9"/>
+ <signal name="released" handler="dtmf_stop" object="9"/>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="dtmf_8">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">8</property>
+ <property name="response_id">0</property>
+ <signal name="pressed" handler="dtmf_start" object="8"/>
+ <signal name="released" handler="dtmf_stop" object="8"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="dtmf_7">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">7</property>
+ <property name="response_id">0</property>
+ <signal name="pressed" handler="dtmf_start" object="7"/>
+ <signal name="released" handler="dtmf_stop" object="7"/>
+ </widget>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="dtmf_6">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">6</property>
+ <property name="response_id">0</property>
+ <signal name="pressed" handler="dtmf_start" object="6"/>
+ <signal name="released" handler="dtmf_stop" object="6"/>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="dtmf_5">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">5</property>
+ <property name="response_id">0</property>
+ <signal name="pressed" handler="dtmf_start" object="5"/>
+ <signal name="released" handler="dtmf_stop" object="5"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="dtmf_4">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">4</property>
+ <property name="response_id">0</property>
+ <signal name="pressed" handler="dtmf_start" object="4"/>
+ <signal name="released" handler="dtmf_stop" object="4"/>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="dtmf_3">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">3</property>
+ <property name="response_id">0</property>
+ <signal name="pressed" handler="dtmf_start" object="3"/>
+ <signal name="released" handler="dtmf_stop" object="3"/>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="dtmf_2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">2</property>
+ <property name="response_id">0</property>
+ <signal name="pressed" handler="dtmf_start" object="2"/>
+ <signal name="released" handler="dtmf_stop" object="2"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="dtmf_1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">1</property>
+ <property name="response_id">0</property>
+ <signal name="pressed" handler="dtmf_start" object="1"/>
+ <signal name="released" handler="dtmf_stop" object="1"/>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkButton" id="button1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="dtmf_destroy"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">6</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">6</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+</glade-interface>
diff --git a/examples/gui/fs2-gui.py b/examples/gui/fs2-gui.py
new file mode 100755
index 00000000..f7446188
--- /dev/null
+++ b/examples/gui/fs2-gui.py
@@ -0,0 +1,929 @@
+#!/usr/bin/python
+
+# Farsight 2 demo GUI program
+#
+# Copyright (C) 2007 Collabora, Nokia
+# @author: Olivier Crete <olivier.crete@collabora.co.uk>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+import sys, os, pwd, os.path
+import socket
+import threading
+import weakref
+
+import signal
+
+try:
+ import pygtk
+ pygtk.require("2.0")
+
+ import gtk, gtk.glade, gobject, gtk.gdk
+ import gobject
+except ImportError, e:
+ raise SystemExit("PyGTK couldn't be found ! (%s)" % (e[0]))
+
+try:
+ import pygst
+ pygst.require('0.10')
+
+ import gst
+except ImportError, e:
+ raise SystemExit("Gst-Python couldn't be found! (%s)" % (e[0]))
+try:
+ import farsight
+except:
+ try:
+ sys.path.append(os.path.join(os.path.dirname(__file__),
+ '..', '..', 'python', '.libs'))
+ import farsight
+ except ImportError, e:
+ raise SystemExit("Farsight couldn't be found! (%s)" % (e[0]))
+
+
+
+from fs2_gui_net import FsUIClient, FsUIListener, FsUIServer
+
+CAMERA=False
+
+AUDIO=True
+VIDEO=True
+
+CLIENT=1
+SERVER=2
+
+TRANSMITTER="rawudp"
+
+mycname = "".join((pwd.getpwuid(os.getuid())[0],
+ "-" ,
+ str(os.getpid()),
+ "@",
+ socket.gethostname()))
+
+gladefile = os.path.join(os.path.dirname(__file__),"fs2-gui.glade")
+
+
+def make_video_sink(pipeline, xid, name, async=True):
+ "Make a bin with a video sink in it, that will be displayed on xid."
+ bin = gst.Bin("videosink_%d" % xid)
+ sink = gst.element_factory_make("ximagesink", name)
+ sink.set_property("sync", async)
+ sink.set_property("async", async)
+ bin.add(sink)
+ colorspace = gst.element_factory_make("ffmpegcolorspace")
+ bin.add(colorspace)
+ videoscale = gst.element_factory_make("videoscale")
+ bin.add(videoscale)
+ videoscale.link(colorspace)
+ colorspace.link(sink)
+ bin.add_pad(gst.GhostPad("sink", videoscale.get_pad("sink")))
+ sink.set_data("xid", xid)
+ return bin
+
+
+class FsUIPipeline:
+ "Object to wrap the GstPipeline"
+
+ def int_handler(self, sig, frame):
+ try:
+ gst.DEBUG_BIN_TO_DOT_FILE(self.pipeline, 0, "pipelinedump")
+ except:
+ pass
+ sys.exit(2)
+
+ def __init__(self, elementname="fsrtpconference"):
+ self.pipeline = gst.Pipeline()
+ signal.signal(signal.SIGINT, self.int_handler)
+ notifier = farsight.ElementAddedNotifier()
+ notifier.connect("element-added", self.element_added_cb)
+ notifier.add(self.pipeline)
+ self.pipeline.get_bus().set_sync_handler(self.sync_handler)
+ self.pipeline.get_bus().add_watch(self.async_handler)
+ self.conf = gst.element_factory_make(elementname)
+ # Sets lets our own cname
+ self.conf.set_property("sdes-cname", mycname)
+ self.pipeline.add(self.conf)
+ if VIDEO:
+ self.videosource = FsUIVideoSource(self.pipeline)
+ self.videosession = FsUISession(self.conf, self.videosource)
+ if AUDIO:
+ self.audiosource = FsUIAudioSource(self.pipeline)
+ self.audiosession = FsUISession(self.conf, self.audiosource)
+ self.adder = None
+ self.pipeline.set_state(gst.STATE_PLAYING)
+
+ def __del__(self):
+ self.pipeline.set_state(gst.STATE_NULL)
+
+ def sync_handler(self, bus, message):
+ "Message handler to get the prepare-xwindow-id event"
+ if message.type == gst.MESSAGE_ELEMENT and \
+ message.structure.has_name("prepare-xwindow-id"):
+ xid = None
+ element = message.src
+ # We stored the XID on the element or its parent on the expose event
+ # Now lets look it up
+ while not xid and element:
+ xid = element.get_data("xid")
+ element = element.get_parent()
+ if xid:
+ message.src.set_xwindow_id(xid)
+ return gst.BUS_DROP
+ return gst.BUS_PASS
+
+ def async_handler(self, bus, message):
+ "Async handler to print messages"
+ if message.type == gst.MESSAGE_ERROR:
+ print message.src.get_name(), ": ", message.parse_error()
+ elif message.type == gst.MESSAGE_WARNING:
+ print message.src.get_name(), ": ", message.parse_warning()
+ elif message.type == gst.MESSAGE_ELEMENT:
+ if message.structure.has_name("dtmf-event"):
+ print "dtmf-event: %d" % message.structure["number"]
+ elif message.structure.has_name("farsight-local-candidates-prepared"):
+ message.structure["stream"].uistream.local_candidates_prepared()
+
+ elif message.structure.has_name("farsight-new-local-candidate"):
+ message.structure["stream"].uistream.new_local_candidate(
+ message.structure["candidate"])
+ elif message.structure.has_name("farsight-codecs-changed"):
+ print message.src.get_name(), ": ", message.structure.get_name()
+ message.structure["session"].uisession.codecs_changed()
+ if AUDIO and message.structure["session"] == self.audiosession.fssession:
+ self.codecs_changed_audio()
+ if VIDEO and message.structure["session"] == self.videosession.fssession:
+ self.codecs_changed_video()
+ elif message.structure.has_name("farsight-send-codec-changed"):
+ print message.src.get_name(), ": ", message.structure.get_name()
+ print "send codec changed: " + message.structure["codec"].to_string()
+ if AUDIO and message.structure["session"] == self.audiosession.fssession:
+ self.codecs_changed_audio()
+ if VIDEO and message.structure["session"] == self.videosession.fssession:
+ self.codecs_changed_video()
+ elif message.structure.has_name("farsight-recv-codecs-changed"):
+ print message.src.get_name(), ": ", message.structure.get_name()
+ message.structure["stream"].uistream.recv_codecs_changed( \
+ message.structure["codecs"])
+
+
+ elif message.structure.has_name("farsight-error"):
+ print "Async error ("+ str(message.structure["error-no"]) +"): " + message.structure["error-msg"] +" --- "+ message.structure["debug-msg"]
+ else:
+ print message.src.get_name(), ": ", message.structure.get_name()
+ elif message.type != gst.MESSAGE_STATE_CHANGED \
+ and message.type != gst.MESSAGE_ASYNC_DONE:
+ print message.type
+
+ return True
+
+ def make_video_preview(self, xid, newsize_callback):
+ "Creates the preview sink"
+ self.previewsink = make_video_sink(self.pipeline, xid,
+ "previewvideosink", False)
+ self.pipeline.add(self.previewsink)
+ #Add a probe to wait for the first buffer to find the image size
+ self.havesize = self.previewsink.get_pad("sink").add_buffer_probe(self.have_size,
+ newsize_callback)
+
+ self.previewsink.set_state(gst.STATE_PLAYING)
+ self.videosource.tee.link(self.previewsink)
+ self.pipeline.set_state(gst.STATE_PLAYING)
+ return self.previewsink
+
+ def have_size(self, pad, buffer, callback):
+ "Callback on the first buffer to know the drawingarea size"
+ x = buffer.caps[0]["width"]
+ y = buffer.caps[0]["height"]
+ callback(x,y)
+ self.previewsink.get_pad("sink").remove_buffer_probe(self.havesize)
+ return True
+
+ def link_audio_sink(self, pad):
+ "Link the audio sink to the pad"
+ print >>sys.stderr, "LINKING AUDIO SINK"
+ if not self.adder:
+ audiosink = gst.element_factory_make("alsasink")
+ audiosink.set_property("buffer-time", 50000)
+ self.pipeline.add(audiosink)
+
+ try:
+ self.adder = gst.element_factory_make("liveadder")
+ except gst.ElementNotFoundError:
+ audiosink.set_state(gst.STATE_PLAYING)
+ pad.link(audiosink.get_pad("sink"))
+ return
+ self.pipeline.add(self.adder)
+ audiosink.set_state(gst.STATE_PLAYING)
+ self.adder.link(audiosink)
+ self.adder.set_state(gst.STATE_PLAYING)
+ convert1 = gst.element_factory_make("audioconvert")
+ self.pipeline.add(convert1)
+ resample = gst.element_factory_make("audioresample")
+ self.pipeline.add(resample)
+ convert2 = gst.element_factory_make("audioconvert")
+ self.pipeline.add(convert2)
+ convert1.link(resample)
+ resample.link(convert2)
+ convert2.link(self.adder)
+ pad.link(convert1.get_pad("sink"))
+ convert2.set_state(gst.STATE_PLAYING)
+ resample.set_state(gst.STATE_PLAYING)
+ convert1.set_state(gst.STATE_PLAYING)
+
+ def element_added_cb(self, notifier, bin, element):
+ if element.get_factory().get_name() == "x264enc":
+ element.set_property("byte-stream", True)
+ element.set_property("bitrate", 128)
+ elif element.get_factory().get_name() == "gstrtpbin":
+ element.set_property("latency", 100)
+
+
+class FsUISource:
+ "An abstract generic class for media sources"
+
+ def __init__(self, pipeline):
+ self.pipeline = pipeline
+ self.tee = gst.element_factory_make("tee")
+ pipeline.add(self.tee)
+ self.tee.set_state(gst.STATE_PLAYING)
+
+ self.source = self.make_source()
+ pipeline.add(self.source)
+ self.source.link(self.tee)
+ self.playcount = 0
+
+ def __del__(self):
+ self.source.set_state(gst.STATE_NULL)
+ self.tee.set_state(gst.STATE_NULL)
+ self.pipeline.remove(self.source)
+ self.pipeline.remove(self.tee)
+
+
+ def make_source(self):
+ "Creates and returns the source GstElement"
+ raise NotImplementedError()
+
+
+ def get_type(self):
+ "Returns the FsMediaType of the source."
+ raise NotImplementedError()
+
+ def get_src_pad(self, name="src%d"):
+ "Gets a source pad from the source"
+ queue = gst.element_factory_make("queue")
+ queue.set_property("leaky", 2)
+ queue.set_property("max-size-time", 50*gst.MSECOND)
+ requestpad = self.tee.get_request_pad(name)
+ self.pipeline.add(queue)
+ requestpad.link(queue.get_static_pad("sink"))
+ pad = queue.get_static_pad("src")
+ pad.set_data("requestpad", requestpad)
+ pad.set_data("queue", queue)
+ return pad
+
+ def put_src_pad(self, pad):
+ "Puts the source pad from the source"
+ self.pipeline.remove(pad.get_data("queue"))
+ self.tee.release_request_pad(pad.get_data("requestpad"))
+
+
+class FsUIVideoSource(FsUISource):
+ "A Video source"
+
+ def get_type(self):
+ return farsight.MEDIA_TYPE_VIDEO
+
+ def make_source(self):
+ bin = gst.Bin()
+ if CAMERA:
+ source = gst.element_factory_make("v4l2src")
+ source.set_property("device", CAMERA)
+ bin.add(source)
+ else:
+ source = gst.element_factory_make("videotestsrc")
+ source.set_property("is-live", 1)
+ bin.add(source)
+ overlay = gst.element_factory_make("timeoverlay")
+ overlay.set_property("font-desc", "Sans 32")
+ bin.add(overlay)
+ source.link(overlay)
+ source=overlay
+
+ filter = gst.element_factory_make("capsfilter")
+ filter.set_property("caps", gst.Caps("video/x-raw-yuv , width=[300,500] , height=[200,500], framerate=[20/1,30/1]"))
+ bin.add(filter)
+ source.link(filter)
+
+ videoscale = gst.element_factory_make("videoscale")
+ bin.add(videoscale)
+ filter.link(videoscale)
+
+ bin.add_pad(gst.GhostPad("src", videoscale.get_pad("src")))
+ return bin
+
+
+
+class FsUIAudioSource(FsUISource):
+ "An audio source"
+
+ def get_type(self):
+ return farsight.MEDIA_TYPE_AUDIO
+
+ def make_source(self):
+ source = gst.element_factory_make("audiotestsrc")
+ source.set_property("is-live", True)
+ source.set_property("wave", 5)
+ return source
+ #return gst.element_factory_make("alsasrc")
+ #return gst.element_factory_make("gconfaudiosrc")
+
+
+
+class FsUISession:
+ "This is one session (audio or video depending on the source)"
+
+ def __init__(self, conference, source):
+ self.conference = conference
+ self.source = source
+ self.streams = []
+ self.fssession = conference.new_session(source.get_type())
+ self.fssession.uisession = self
+ if source.get_type() == farsight.MEDIA_TYPE_VIDEO:
+ # We prefer H263-1998 because we know it works
+ # We don't know if the others do work
+ # We know H264 doesn't work for now or anything else
+ # that needs to send config data
+ self.fssession.set_codec_preferences( [ \
+ farsight.Codec(farsight.CODEC_ID_ANY,
+ "THEORA",
+ farsight.MEDIA_TYPE_VIDEO,
+ 90000),
+ farsight.Codec(farsight.CODEC_ID_ANY,
+ "H264",
+ farsight.MEDIA_TYPE_VIDEO,
+ 0),
+ farsight.Codec(farsight.CODEC_ID_ANY,
+ "H263-1998",
+ farsight.MEDIA_TYPE_VIDEO,
+ 0),
+ farsight.Codec(farsight.CODEC_ID_ANY,
+ "H263",
+ farsight.MEDIA_TYPE_VIDEO,
+ 0)
+ ])
+ elif source.get_type() == farsight.MEDIA_TYPE_AUDIO:
+ self.fssession.set_codec_preferences( [ \
+ farsight.Codec(farsight.CODEC_ID_ANY,
+ "PCMA",
+ farsight.MEDIA_TYPE_AUDIO,
+ 0),
+ farsight.Codec(farsight.CODEC_ID_ANY,
+ "PCMU",
+ farsight.MEDIA_TYPE_AUDIO,
+ 0),
+ # The gst speexenc element breaks timestamps
+ farsight.Codec(farsight.CODEC_ID_DISABLE,
+ "SPEEX",
+ farsight.MEDIA_TYPE_AUDIO,
+ 16000),
+ # Sadly, vorbis is not currently compatible with live streaming :-(
+ farsight.Codec(farsight.CODEC_ID_DISABLE,
+ "VORBIS",
+ farsight.MEDIA_TYPE_AUDIO,
+ 0),
+ ])
+
+ self.sourcepad = self.source.get_src_pad()
+ self.sourcepad.link(self.fssession.get_property("sink-pad"))
+
+ def __del__(self):
+ self.sourcepad(unlink)
+ self.source.put_src_pad(self.sourcepad)
+ def __stream_finalized(self, s):
+ self.streams.remove(s)
+
+ def new_stream(self, id, participant):
+ "Creates a new stream for a specific participant"
+ transmitter_params = {}
+ # If its video, we start at port 9078, to make it more easy
+ # to differentiate it in a tcpdump log
+ if self.source.get_type() == farsight.MEDIA_TYPE_VIDEO and \
+ TRANSMITTER == "rawudp":
+ cand = farsight.Candidate()
+ cand.component_id = farsight.COMPONENT_RTP
+ cand.port = 9078
+ transmitter_params["preferred-local-candidates"] = [cand]
+ realstream = self.fssession.new_stream(participant.fsparticipant,
+ farsight.DIRECTION_BOTH,
+ TRANSMITTER, transmitter_params)
+ stream = FsUIStream(id, self, participant, realstream)
+ self.streams.append(weakref.ref(stream, self.__stream_finalized))
+ return stream
+
+ def dtmf_start(self, event, method):
+ if (event == "*"):
+ event = farsight.DTMF_EVENT_STAR
+ elif (event == "#"):
+ event = farsight.DTMF_EVENT_POUND
+ else:
+ event = int(event)
+ self.fssession.start_telephony_event(event, 2, method)
+
+ def dtmf_stop(self, method):
+ self.fssession.stop_telephony_event(method)
+
+ def codecs_changed(self):
+ "Callback from FsSession"
+ for s in self.streams:
+ try:
+ s().codecs_changed()
+ except AttributeError:
+ pass
+
+ def send_stream_codecs(self, codecs, sourcestream):
+ for s in self.streams:
+ stream = s()
+ if stream and stream is not sourcestream:
+ stream.connect.send_codecs(stream.participant.id,
+ sourcestream.id,
+ codecs,
+ sourcestream.participant.id)
+
+class FsUIStream:
+ "One participant in one session"
+
+ def __init__(self, id, session, participant, fsstream):
+ self.id = id
+ self.session = session
+ self.participant = participant
+ self.fsstream = fsstream
+ self.connect = participant.connect
+ self.fsstream.uistream = self
+ self.fsstream.connect("src-pad-added", self.__src_pad_added)
+ self.send_codecs = False
+ self.last_codecs = None
+ self.last_stream_codecs = None
+ self.candidates = []
+
+ def local_candidates_prepared(self):
+ "Callback from FsStream"
+ self.connect.send_candidates_done(self.participant.id, self.id)
+ def new_local_candidate(self, candidate):
+ "Callback from FsStream"
+ self.connect.send_candidate(self.participant.id, self.id, candidate)
+ def __src_pad_added(self, stream, pad, codec):
+ "Callback from FsStream"
+ if self.session.source.get_type() == farsight.MEDIA_TYPE_VIDEO:
+ self.participant.link_video_sink(pad)
+ else:
+ self.participant.pipeline.link_audio_sink(pad)
+
+ def candidate(self, candidate):
+ "Callback for the network object."
+ self.candidates.append(candidate)
+ def candidates_done(self):
+ "Callback for the network object."
+ self.fsstream.set_remote_candidates(self.candidates)
+ self.candidates = []
+ def codecs(self, codecs):
+ "Callback for the network object. Set the codecs"
+
+ print "Remote codecs"
+ for c in codecs:
+ print "Got remote codec from %s/%s %s" % \
+ (self.participant.id, self.id, c.to_string())
+ oldcodecs = self.fsstream.get_property("remote-codecs")
+ if oldcodecs == codecs:
+ return
+ try:
+ self.fsstream.set_remote_codecs(codecs)
+ except AttributeError:
+ print "Tried to set codecs with 0 codec"
+ self.send_local_codecs()
+ self.send_stream_codecs()
+
+
+ def send_local_codecs(self):
+ "Callback for the network object."
+ self.send_codecs = True
+ self.check_send_local_codecs()
+
+ def codecs_changed(self):
+ self.check_send_local_codecs()
+ self.send_stream_codecs()
+
+ def check_send_local_codecs(self):
+ "Internal function to send our local codecs when they're ready"
+ if not self.send_codecs:
+ return
+ if not self.session.fssession.get_property("codecs-ready"):
+ print "Codecs are not ready"
+ return
+ codecs = self.session.fssession.get_property("codecs")
+ assert(codecs is not None and len(codecs) > 0)
+ if (codecs == self.last_codecs):
+ return
+ self.last_codecs = codecs
+ print "sending local codecs"
+ self.connect.send_codecs(self.participant.id, self.id, codecs)
+
+ def send_stream_codecs(self):
+ if not self.connect.is_server:
+ return
+ if not self.session.fssession.get_property("codecs-ready"):
+ return
+ codecs = self.fsstream.get_property("negotiated-codecs")
+ if codecs:
+ self.session.send_stream_codecs(codecs, self)
+
+ def recv_codecs_changed(self, codecs):
+ self.participant.recv_codecs_changed()
+
+
+ def __remove_from_send_codecs_to(self, participant):
+ self.send_codecs_to.remote(participant)
+
+
+ def send_codecs_to(self, participant):
+ codecs = self.fsstream.get_property("negotiated-codecs")
+ print "sending stream %s codecs from %s to %s" % \
+ (self.id, self.participant.id, participant.id)
+ if codecs:
+ participant.connect.send_codecs(participant.id, self.id, codecs,
+ self.participant.id)
+
+
+class FsUIParticipant:
+ "Wraps one FsParticipant, is one user remote contact"
+
+ def __init__(self, connect, id, cname, pipeline, mainui):
+ self.connect = connect
+ self.id = id
+ self.cname = cname
+ self.pipeline = pipeline
+ self.mainui = mainui
+ self.fsparticipant = pipeline.conf.new_participant(cname)
+ self.outcv = threading.Condition()
+ self.funnel = None
+ self.make_widget()
+ self.streams = {}
+ if VIDEO:
+ self.streams[int(farsight.MEDIA_TYPE_VIDEO)] = \
+ pipeline.videosession.new_stream(
+ int(farsight.MEDIA_TYPE_VIDEO), self)
+ if AUDIO:
+ self.streams[int(farsight.MEDIA_TYPE_AUDIO)] = \
+ pipeline.audiosession.new_stream(
+ int(farsight.MEDIA_TYPE_AUDIO), self)
+
+ def candidate(self, media, candidate):
+ "Callback for the network object."
+ self.streams[media].candidate(candidate)
+ def candidates_done(self, media):
+ "Callback for the network object."
+ self.streams[media].candidates_done()
+ def codecs(self, media, codecs):
+ "Callback for the network object."
+ self.streams[media].codecs(codecs)
+ def send_local_codecs(self):
+ "Callback for the network object."
+ for id in self.streams:
+ self.streams[id].send_local_codecs()
+
+ def make_widget(self):
+ "Make the widget of the participant's video stream."
+ gtk.gdk.threads_enter()
+ self.glade = gtk.glade.XML(gladefile, "user_frame")
+ self.userframe = self.glade.get_widget("user_frame")
+ self.glade.get_widget("frame_label").set_text(self.cname)
+ self.glade.signal_autoconnect(self)
+ self.label = gtk.Label()
+ self.label.set_alignment(0,0)
+ self.label.show()
+ self.mainui.hbox_add(self.userframe, self.label)
+ gtk.gdk.threads_leave()
+
+ def exposed(self, widget, *args):
+ """From the exposed signal, used to create the video sink
+ The video sink will be created here, but will only be linked when the
+ pad arrives and link_video_sink() is called.
+ """
+ if not VIDEO:
+ return
+ try:
+ self.videosink.get_by_interface(gst.interfaces.XOverlay).expose()
+ except AttributeError:
+ try:
+ self.outcv.acquire()
+ self.videosink = make_video_sink(self.pipeline.pipeline,
+ widget.window.xid,
+ "uservideosink")
+ self.pipeline.pipeline.add(self.videosink)
+ self.funnel = gst.element_factory_make("fsfunnel")
+ self.pipeline.pipeline.add(self.funnel)
+ self.funnel.link(self.videosink)
+ self.havesize = self.videosink.get_pad("sink").add_buffer_probe(self.have_size)
+
+ self.videosink.set_state(gst.STATE_PLAYING)
+ self.funnel.set_state(gst.STATE_PLAYING)
+ self.outcv.notifyAll()
+ finally:
+ self.outcv.release()
+
+
+ def have_size(self, pad, buffer):
+ "Callback on the first buffer to know the drawingarea size"
+ x = buffer.caps[0]["width"]
+ y = buffer.caps[0]["height"]
+ gtk.gdk.threads_enter()
+ self.glade.get_widget("user_drawingarea").set_size_request(x,y)
+ gtk.gdk.threads_leave()
+ self.videosink.get_pad("sink").remove_buffer_probe(self.havesize)
+ del self.havesize
+ return True
+
+
+
+ def link_video_sink(self, pad):
+ """Link the video sink
+
+ Wait for the funnnel for the video sink to be created, when it has been
+ created, link it.
+ """
+ try:
+ self.outcv.acquire()
+ while self.funnel is None:
+ self.outcv.wait()
+ print >>sys.stderr, "LINKING VIDEO SINK"
+ pad.link(self.funnel.get_pad("sink%d"))
+ finally:
+ self.outcv.release()
+
+ def destroy(self):
+ if VIDEO:
+ try:
+ self.videosink.get_pad("sink").disconnect_handler(self.havesize)
+ pass
+ except AttributeError:
+ pass
+ self.glade.get_widget("user_drawingarea").disconnect_by_func(self.exposed)
+ self.streams = {}
+ self.outcv.acquire()
+ self.videosink.set_locked_state(True)
+ self.funnel.set_locked_state(True)
+ self.videosink.set_state(gst.STATE_NULL)
+ self.funnel.set_state(gst.STATE_NULL)
+ self.pipeline.pipeline.remove(self.videosink)
+ self.pipeline.pipeline.remove(self.funnel)
+ del self.videosink
+ del self.funnel
+ self.outcv.release()
+ gtk.gdk.threads_enter()
+ self.userframe.destroy()
+ self.label.destroy()
+ gtk.gdk.threads_leave()
+
+ def error(self):
+ "Callback for the network object."
+ if self.id == 1:
+ self.mainui.fatal_error("<b>Disconnected from server</b>")
+ else:
+ print "ERROR ON %d" % (self.id)
+
+ def recv_codecs_changed(self):
+ codecs = {}
+ for s in self.streams:
+ codec = self.streams[s].fsstream.get_property("current-recv-codecs")
+ mediatype = self.streams[s].session.fssession.get_property("media-type")
+ if len(codec):
+ if mediatype in codecs:
+ codecs[mediatype] += codec
+ else:
+ codecs[mediatype] = codec
+ str = ""
+ for mt in codecs:
+ str += "<big>" +mt.value_nick.title() + "</big>:\n"
+ for c in codecs[mt]:
+ str += " <b>%s</b>: %s %s\n" % (c.id,
+ c.encoding_name,
+ c.clock_rate)
+ self.label.set_markup(str)
+
+ def send_codecs_to(self, participant):
+ for sid in self.streams:
+ self.streams[sid].send_codecs_to(participant)
+
+
+class FsMainUI:
+ "The main UI and its different callbacks"
+
+ def __init__(self, mode, ip, port):
+ self.mode = mode
+ self.pipeline = FsUIPipeline()
+ self.pipeline.codecs_changed_audio = self.reset_audio_codecs
+ self.pipeline.codecs_changed_video = self.reset_video_codecs
+ self.glade = gtk.glade.XML(gladefile, "main_window")
+ self.glade.signal_autoconnect(self)
+ self.mainwindow = self.glade.get_widget("main_window")
+ self.audio_combobox = self.glade.get_widget("audio_combobox")
+ self.video_combobox = self.glade.get_widget("video_combobox")
+ liststore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
+ self.audio_combobox.set_model(liststore)
+ cell = gtk.CellRendererText()
+ self.audio_combobox.pack_start(cell, True)
+ self.audio_combobox.add_attribute(cell, 'text', 0)
+ self.reset_audio_codecs()
+ liststore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
+ self.video_combobox.set_model(liststore)
+ cell = gtk.CellRendererText()
+ self.video_combobox.pack_start(cell, True)
+ self.video_combobox.add_attribute(cell, 'text', 0)
+ self.reset_video_codecs()
+
+ if mode == CLIENT:
+ self.client = FsUIClient(ip, port, mycname, FsUIParticipant,
+ self.pipeline, self)
+ self.glade.get_widget("info_label").set_markup(
+ "<b>%s</b>\nConnected to %s:%s" % (mycname, ip, port))
+ elif mode == SERVER:
+ self.server = FsUIListener(port, FsUIServer, mycname,
+ FsUIParticipant, self.pipeline, self)
+ self.glade.get_widget("info_label").set_markup(
+ "<b>%s</b>\nExpecting connections on port %s" %
+ (mycname, self.server.port))
+
+
+ self.mainwindow.show()
+
+ def reset_codecs(self, combobox, fssession):
+ liststore = combobox.get_model()
+ current = fssession.get_property("current-send-codec")
+ liststore.clear()
+ for c in fssession.get_property("codecs"):
+ str = ("%s: %s/%s %s" % (c.id,
+ c.media_type.value_nick,
+ c.encoding_name,
+ c.clock_rate))
+ iter = liststore.append([str, c])
+ if current and c and current.id == c.id:
+ combobox.set_active_iter(iter)
+ print "active: "+ c.to_string()
+
+ def reset_audio_codecs(self):
+ if AUDIO:
+ self.reset_codecs(self.audio_combobox,
+ self.pipeline.audiosession.fssession)
+
+ def reset_video_codecs(self):
+ if VIDEO:
+ self.reset_codecs(self.video_combobox,
+ self.pipeline.videosession.fssession)
+
+ def combobox_changed_cb(self, combobox, fssession):
+ liststore = combobox.get_model()
+ iter = combobox.get_active_iter()
+ if iter:
+ codec = liststore.get_value(iter, 1)
+ fssession.set_send_codec(codec)
+
+ def audio_combobox_changed_cb(self, combobox):
+ self.combobox_changed_cb(combobox, self.pipeline.audiosession.fssession)
+
+ def video_combobox_changed_cb(self, combobox):
+ self.combobox_changed_cb(combobox, self.pipeline.videosession.fssession)
+
+
+ def exposed(self, widget, *args):
+ "Callback from the exposed event of the widget to make the preview sink"
+ if not VIDEO:
+ return
+ try:
+ self.preview.get_by_interface(gst.interfaces.XOverlay).expose()
+ except AttributeError:
+ self.preview = self.pipeline.make_video_preview(widget.window.xid,
+ self.newsize)
+
+ def newsize (self, x, y):
+ self.glade.get_widget("preview_drawingarea").set_size_request(x,y)
+
+ def shutdown(self, widget=None):
+ gtk.main_quit()
+
+ def hbox_add(self, widget, label):
+ table = self.glade.get_widget("users_table")
+ x = table.get_properties("n-columns")[0]
+ table.attach(widget, x, x+1, 0, 1)
+ table.attach(label, x, x+1, 1, 3, xpadding=6)
+
+ def __del__(self):
+ self.mainwindow.destroy()
+
+ def fatal_error(self, errormsg):
+ gtk.gdk.threads_enter()
+ dialog = gtk.MessageDialog(self.mainwindow,
+ gtk.DIALOG_MODAL,
+ gtk.MESSAGE_ERROR,
+ gtk.BUTTONS_OK)
+ dialog.set_markup(errormsg);
+ dialog.run()
+ dialog.destroy()
+ gtk.main_quit()
+ gtk.gdk.threads_leave()
+
+ def show_dtmf(self, button):
+ try:
+ self.dtmf.present()
+ except AttributeError:
+ self.dtmf = gtk.glade.XML(gladefile, "dtmf_window")
+ self.dtmf.signal_autoconnect(self)
+
+ def dtmf_start(self, button):
+ if (self.dtmf.get_widget("dtmf_as_event").get_active()):
+ self.dtmf_last_method = farsight.DTMF_METHOD_RTP_RFC4733
+ elif (self.dtmf.get_widget("dtmf_as_sound").get_active()):
+ self.dtmf_last_method = farsight.DTMF_METHOD_IN_BAND
+ else:
+ print "Invalid DTMF Method"
+ return
+ self.pipeline.audiosession.dtmf_start(button.get_label(), \
+ self.dtmf_last_method)
+
+ def dtmf_stop(self, button):
+ try:
+ self.pipeline.audiosession.dtmf_stop(self.dtmf_last_method)
+ del self.dtmf_last_method
+ except AttributeError:
+ pass
+ def dtmf_destroy(self, button):
+ self.dtmf.get_widget("dtmf_window").destroy()
+ del self.dtmf
+
+
+
+class FsUIStartup:
+ "Displays the startup window and then creates the FsMainUI"
+
+ def __init__(self):
+ self.glade = gtk.glade.XML(gladefile, "neworconnect_dialog")
+ self.dialog = self.glade.get_widget("neworconnect_dialog")
+ self.glade.get_widget("newport_spinbutton").set_value(9893)
+ self.glade.signal_autoconnect(self)
+ self.dialog.show()
+ self.acted = False
+
+ def action(self, mode):
+ port = self.glade.get_widget("newport_spinbutton").get_value_as_int()
+ ip = self.glade.get_widget("newip_entry").get_text()
+ try:
+ self.ui = FsMainUI(mode, ip, port)
+ self.acted = True
+ self.dialog.destroy()
+ del self.glade
+ del self.dialog
+ except socket.error, e:
+ dialog = gtk.MessageDialog(self.dialog,
+ gtk.DIALOG_MODAL,
+ gtk.MESSAGE_ERROR,
+ gtk.BUTTONS_OK)
+ dialog.set_markup("<b>Could not connect to %s %d</b>" % (ip,port))
+ dialog.format_secondary_markup(e[1])
+ dialog.run()
+ dialog.destroy()
+
+ def new_server(self, widget):
+ self.action(SERVER)
+
+ def connect(self, widget):
+ self.action(CLIENT)
+
+
+ def quit(self, widget):
+ if not self.acted:
+ gtk.main_quit()
+
+
+
+
+if __name__ == "__main__":
+ if len(sys.argv) >= 2:
+ CAMERA = sys.argv[1]
+ else:
+ CAMERA = None
+
+ gobject.threads_init()
+ gtk.gdk.threads_init()
+ startup = FsUIStartup()
+ gtk.main()
diff --git a/examples/gui/fs2_gui_net.py b/examples/gui/fs2_gui_net.py
new file mode 100644
index 00000000..c834d77a
--- /dev/null
+++ b/examples/gui/fs2_gui_net.py
@@ -0,0 +1,456 @@
+#!/usr/bin/python
+
+# Farsight 2 simple network signalling library for the demo GUI
+#
+# Copyright (C) 2007 Collabora, Nokia
+# @author: Olivier Crete <olivier.crete@collabora.co.uk>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+#
+# This is the signalling code used by fs2-gui.py
+#
+
+import sys, os, pwd, os.path
+import socket, struct
+import gc
+
+
+try:
+ import pygst
+ pygst.require('0.10')
+
+ import gst
+except ImportError, e:
+ raise SystemExit("Gst-Python couldn't be found! (%s)" % (e[0]))
+
+try:
+ import farsight
+except:
+ sys.path.append(os.path.join(os.path.dirname(__file__),
+ '..', '..', 'python', '.libs'))
+ import farsight
+
+import gobject
+
+class FsUIConnect:
+ ERROR = 0
+ CODECS = 1
+ CANDIDATE = 2
+ CANDIDATES_DONE = 3
+ INTRO = 4
+
+ def __reset(self):
+ self.type = None
+ self.media = None
+ self.size = struct.calcsize("!IIIIII")
+ self.data = ""
+ self.dest = -1
+ self.src = -1
+
+
+ def __init__(self, sock, callbacks, myid=0):
+ self.sock = sock
+ self.__reset()
+ self.callbacks = callbacks
+ self.myid = myid
+ self.partid = 1
+ self.is_server = True
+ sock.setblocking(0)
+ gobject.io_add_watch(self.sock.fileno(), gobject.IO_IN,
+ self.__data_in)
+ gobject.io_add_watch(self.sock.fileno(),
+ gobject.IO_ERR | gobject.IO_HUP,
+ self.__error)
+
+ def __error(self, source, condition):
+ print "have error"
+ if (self.src >= 0):
+ self.callbacks[self.ERROR](self.src)
+ else:
+ self.callbacks[self.ERROR](self.partid)
+ return False
+
+ def __data_in(self, source, condition):
+ data = self.sock.recv(self.size-len(self.data))
+
+ if len(data) == 0:
+ print "received nothing"
+ if (self.src >= 0):
+ self.callbacks[self.ERROR](self.src)
+ else:
+ self.callbacks[self.ERROR](self.partid)
+ return False
+
+ self.data += data
+ if len(self.data) == self.size:
+ if self.type is not None:
+ if self.type == self.CODECS:
+ data = self.__codecs_from_string(data)
+ elif self.type == self.CANDIDATE:
+ data = self.__candidate_from_string(data)
+ else:
+ data = self.data
+ self.callbacks[self.type](self.src, self.dest,
+ self.media, data)
+ self.__reset()
+ else:
+ (check,
+ self.src,
+ self.dest,
+ self.type,
+ self.media,
+ self.size) = struct.unpack("!IIIIII", self.data)
+ if check != 0xDEADBEEF:
+ print "CORRUPTION"
+ sys.exit(1)
+ if self.myid > 1 and self.dest != self.myid:
+ print "GOT MESSAGE FOR %d, but I am %d" % (self.dest,
+ self.myid)
+ sys.exit(1)
+ self.data=""
+ if self.size == 0:
+ self.callbacks[self.type](self.src, self.dest,
+ self.media, None)
+ self.__reset()
+ return True
+
+ def __send_data(self, dest, type, media=0, data="", src=None):
+ if src is None: src = self.myid
+ if src == 0 and type != self.INTRO: raise Exception
+ try:
+ self.sock.sendall(struct.pack("!IIIIII",
+ 0xDEADBEEF,
+ int(src),
+ int(dest),
+ int(type),
+ int(media),
+ len(data)))
+ self.sock.sendall(data)
+ except socket.error:
+ print "have error"
+ self.callbacks[self.ERROR](self.partid)
+
+
+ def send_error(self, dest, src):
+ self.__send_data(dest, self.ERROR, src=src)
+ def send_intro(self, dest, cname, src=None):
+ self.__send_data(dest, self.INTRO, data=cname, src=src)
+ def send_codecs(self, dest, media, codecs, src=None):
+ self.__send_data(dest, self.CODECS,
+ media=media,
+ data=self.__codecs_to_string(codecs), src=src)
+ def send_candidate(self, dest, media, candidate, src=None):
+ self.__send_data(dest, self.CANDIDATE, media=media,
+ data=self.__candidate_to_string(candidate), src=src)
+ def send_candidates_done(self, dest, media, src=None):
+ self.__send_data(dest, self.CANDIDATES_DONE, media=media, src=src)
+
+ def __del__(self):
+ try:
+ self.sock.close()
+ except AttributeError:
+ pass
+
+
+ def __candidate_to_string(self, candidate):
+ return "|".join((
+ candidate.foundation,
+ str(candidate.component_id),
+ candidate.ip,
+ str(candidate.port),
+ candidate.base_ip,
+ str(candidate.base_port),
+ str(int(candidate.proto)),
+ str(candidate.priority),
+ str(int(candidate.type)),
+ candidate.username,
+ candidate.password))
+
+ def __candidate_from_string(self, string):
+ candidate = farsight.Candidate()
+ (candidate.foundation,
+ component_id,
+ candidate.ip,
+ port,
+ candidate.base_ip,
+ base_port,
+ proto,
+ priority,
+ type,
+ candidate.username,
+ candidate.password) = string.split("|")
+ candidate.component_id = int(component_id)
+ candidate.port = int(port)
+ candidate.base_port = int(base_port)
+ candidate.proto = int(proto)
+ candidate.priority = int(priority)
+ candidate.type = int(type)
+ return candidate
+
+ def __codecs_to_string(self, codecs):
+ codec_strings = []
+ for codec in codecs:
+ start = " ".join((str(codec.id),
+ codec.encoding_name,
+ str(int(codec.media_type)),
+ str(codec.clock_rate),
+ str(codec.channels)))
+ codec = "".join((start,
+ "|",
+ ";".join(["=".join(i) for i in codec.optional_params])))
+ codec_strings.append(codec)
+
+ return "\n".join(codec_strings)
+
+
+ def __codecs_from_string(self, string):
+ codecs = []
+ for substring in string.split("\n"):
+ (start,end) = substring.split("|")
+ (id, encoding_name, media_type, clock_rate, channels) = start.split(" ")
+ codec = farsight.Codec(int(id), encoding_name, int(media_type),
+ int(clock_rate))
+ codec.channels = int(channels)
+ if len(end):
+ codec.optional_params = \
+ [tuple(x.split("=",1)) for x in end.split(";") if len(x) > 0]
+ codecs.append(codec)
+ return codecs
+
+class FsUIConnectClient (FsUIConnect):
+ def __init__(self, ip, port, callbacks):
+ sock = socket.socket()
+ sock.connect((ip, port))
+ FsUIConnect.__init__(self, sock, callbacks)
+ self.is_server = False
+
+class FsUIListener:
+ def __init__(self, port, callback, *args):
+ self.sock = socket.socket()
+ self.callback = callback
+ self.args = args
+ bound = False
+ while not bound:
+ try:
+ self.sock.bind(("", port))
+ bound = True
+ except socket.error, e:
+ port += 1
+ self.port = port
+ print "Bound to port ", port
+ self.sock.setblocking(0)
+ gobject.io_add_watch(self.sock.fileno(), gobject.IO_IN, self.data_in)
+ gobject.io_add_watch(self.sock.fileno(),
+ gobject.IO_ERR | gobject.IO_HUP,
+ self.error)
+ self.sock.listen(3)
+
+ def error(self, source, condition):
+ print "Error on listen"
+ sys.exit(1)
+ return False
+
+ def data_in(self, source, condition):
+ (sock,addr) = self.sock.accept()
+ self.callback(sock, *self.args)
+ return True
+
+class FsUIClient:
+ def __init__(self, ip, port, cname, get_participant, *args):
+ self.participants = {}
+ self.get_participant = get_participant
+ self.args = args
+ self.cname = cname
+ self.connect = FsUIConnectClient(ip, port, (self.__error,
+ self.__codecs,
+ self.__candidate,
+ self.__candidate_done,
+ self.__intro))
+ self.connect.send_intro(1, cname)
+
+ def __codecs(self, src, dest, media, data):
+ print "Got codec Src:%d dest:%d data:%s" % (src, dest, data)
+ self.participants[src].codecs(media, data)
+ def __candidate(self, src, dest, media, data):
+ self.participants[src].candidate(media, data)
+ def __candidate_done(self, src, dest, media, data):
+ self.participants[src].candidates_done(media)
+ def __intro(self, src, dest, media, cname):
+ print "Got Intro from %s, I am %d" % (src, dest)
+ if src == 1:
+ self.connect.myid = dest
+ if not self.participants.has_key(src):
+ if src != 1:
+ self.connect.send_intro(src, self.cname)
+ self.participants[src] = self.get_participant(self.connect, src,
+ cname,
+ *self.args)
+ def __error(self, participantid, *arg):
+ print "Client Error", participantid
+ if participantid == 1:
+ # Communication error with server, its over
+ self.participants[participantid].error()
+ else:
+ self.participants[participantid].destroy()
+ del self.participants[participantid]
+ gc.collect()
+
+
+class FsUIServer:
+ nextid = 2
+ participants = {}
+
+ def __init__(self, sock, cname, get_participant, *args):
+ self.cname = cname
+ self.get_participant = get_participant
+ self.args = args
+ self.connect = FsUIConnect(sock, (self.__error,
+ self.__codecs,
+ self.__candidate,
+ self.__candidate_done,
+ self.__intro), 1)
+ def __codecs(self, src, dest, media, data):
+ FsUIServer.participants[src].codecs(media, data)
+ def __candidate(self, src, dest, media, data):
+ if dest == 1:
+ FsUIServer.participants[src].candidate(media, data)
+ else:
+ print data
+ FsUIServer.participants[dest].connect.send_candidate(dest,
+ media,
+ data,
+ src)
+ def __candidate_done(self, src, dest, media, data):
+ if dest == 1:
+ FsUIServer.participants[src].candidates_done(media)
+ else:
+ FsUIServer.participants[dest].connect.send_candidates_done(dest,
+ media,
+ src)
+ def __intro(self, src, dest, media, cname):
+ print "Got Intro from %s to %s" % (src, dest)
+ if src == 0 and dest == 1:
+ newid = FsUIServer.nextid
+ # Forward the introduction to all other participants
+ for pid in FsUIServer.participants:
+ print "Sending from %d to %d" % (newid, pid)
+ FsUIServer.participants[pid].connect.send_intro(pid, cname,
+ newid)
+ self.connect.send_intro(newid, self.cname)
+ self.connect.partid = newid
+ FsUIServer.participants[newid] = self.get_participant(self.connect,
+ newid,
+ cname,
+ *self.args)
+ FsUIServer.participants[newid].send_local_codecs()
+ FsUIServer.nextid += 1
+ elif dest != 1:
+ FsUIServer.participants[dest].connect.send_intro(dest,
+ cname,
+ src)
+ FsUIServer.participants[src].send_codecs_to(
+ FsUIServer.participants[dest])
+ else:
+ print "ERROR SRC != 0"
+
+ def __error(self, participantid, *args):
+ print "Server Error", participantid
+ FsUIServer.participants[participantid].destroy()
+ del FsUIServer.participants[participantid]
+ gc.collect()
+ for pid in FsUIServer.participants:
+ FsUIServer.participants[pid].connect.send_error(pid, participantid)
+
+if __name__ == "__main__":
+ class TestMedia:
+ def __init__(self, pid, id, connect):
+ self.pid = pid
+ self.id = id
+ self.connect = connect
+ candidate = farsight.Candidate()
+ candidate.component_id = 1
+ connect.send_candidate(self.pid, self.id, candidate)
+ connect.send_candidates_done(self.pid, self.id)
+ def candidate(self, candidate):
+ print "Got candidate", candidate
+ def candidates_done(self):
+ print "Got candidate done"
+ def codecs(self, codecs):
+ if self.connect.myid != 1:
+ self.connect.send_codecs(1, self.id,
+ [farsight.Codec(self.connect.myid,
+ "codec-name",
+ self.pid,
+ self.id)])
+
+ def send_local_codecs(self):
+ print "Send local codecs to %s for media %s" % (self.pid, self.id)
+ self.connect.send_codecs(self.pid, self.id,
+ [farsight.Codec(self.connect.myid,
+ "local_codec",
+ self.pid,
+ self.id)])
+ def get_codecs(self):
+ return [farsight.Codec(self.connect.myid,
+ "nego-codecs",
+ self.pid,
+ self.id)]
+
+
+ class TestParticipant:
+ def __init__(self, connect, id, cname, *args):
+ self.id = id
+ self.streams = {1: TestMedia(id, 1, connect),
+ 2: TestMedia(id, 2, connect)}
+ self.cname = cname
+ self.connect = connect
+ print "New Participant %s and cname %s" % (id,cname)
+ def candidate(self, media, candidate):
+ self.streams[media].candidate(candidate)
+ def candidates_done(self, media):
+ self.streams[media].candidates_done()
+ def codecs(self, media, codecs):
+ self.streams[media].codecs(codecs)
+ def send_local_codecs(self):
+ for id in self.streams:
+ self.streams[id].send_local_codecs()
+ def destroy(self):
+ pass
+ def send_codecs_to(self, participant):
+ for sid in self.streams:
+ print "to: %s from: %s" % (str(participant.id), (self.id))
+ participant.connect.send_codecs(participant.id,
+ self.streams[sid].id,
+ self.streams[sid].get_codecs(),
+ self.id)
+ def error(self):
+ print "ERROR"
+ sys.exit(1)
+ def destroy(self):
+ passs
+
+
+ mycname = "test"
+ mainloop = gobject.MainLoop()
+ gobject.threads_init()
+ if len(sys.argv) > 1:
+ client = FsUIClient("127.0.0.1", int(sys.argv[1]),
+ "cname" + sys.argv[1],
+ TestParticipant)
+ else:
+ listener = FsUIListener(9893, FsUIServer, "cnameServ", TestParticipant)
+ mainloop.run()