summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonny Lamb <jonny.lamb@collabora.co.uk>2011-11-30 16:29:53 +0000
committerJonny Lamb <jonny.lamb@collabora.co.uk>2011-11-30 16:29:53 +0000
commite6463976a330fadb28c2595a1b8cddcbe4439bd5 (patch)
tree8e645212281ddc329638a9092c8f1685bcde314e
parentf5a6ffcdabbb6fa1c62a923611ee637753650375 (diff)
parent07203ce78f7f2703054067cbc27893933770f6b4 (diff)
Merge remote-tracking branch 'farstream/testing'
-rw-r--r--farstream/.gitignore66
-rw-r--r--farstream/AUTHORS0
-rw-r--r--farstream/COPYING463
-rw-r--r--farstream/ChangeLog0
-rw-r--r--farstream/Makefile.am34
-rw-r--r--farstream/NEWS105
-rw-r--r--farstream/README70
-rwxr-xr-xfarstream/autogen.sh33
-rw-r--r--farstream/configure.ac154
-rw-r--r--farstream/doc/Makefile.am1
-rw-r--r--farstream/doc/lib/Makefile.am106
-rw-r--r--farstream/doc/lib/telepathy-farstream-docs.sgml18
-rw-r--r--farstream/doc/lib/telepathy-farstream-sections.txt43
-rw-r--r--farstream/doc/lib/telepathy-farstream.types2
-rw-r--r--farstream/examples/Makefile.am20
-rw-r--r--farstream/examples/call-handler.c534
-rw-r--r--farstream/extensions/Call_Content.xml255
-rw-r--r--farstream/extensions/Call_Content_Codec_Offer.xml87
-rw-r--r--farstream/extensions/Call_Content_Interface_Media.xml367
-rw-r--r--farstream/extensions/Call_Content_Interface_Mute.xml85
-rw-r--r--farstream/extensions/Call_Content_Interface_Video_Control.xml137
-rw-r--r--farstream/extensions/Call_Stream.xml261
-rw-r--r--farstream/extensions/Call_Stream_Endpoint.xml182
-rw-r--r--farstream/extensions/Call_Stream_Interface_Media.xml447
-rw-r--r--farstream/extensions/Channel_Type_Call.xml1429
-rw-r--r--farstream/extensions/Makefile.am166
-rw-r--r--farstream/extensions/all.xml12
-rw-r--r--farstream/extensions/call-content.c161
-rw-r--r--farstream/extensions/call-content.h62
-rw-r--r--farstream/extensions/call-content.xml11
-rw-r--r--farstream/extensions/call-stream.c161
-rw-r--r--farstream/extensions/call-stream.h62
-rw-r--r--farstream/extensions/call-stream.xml10
-rw-r--r--farstream/extensions/channel.xml9
-rw-r--r--farstream/extensions/extensions-cli.c35
-rw-r--r--farstream/extensions/extensions.c6
-rw-r--r--farstream/extensions/extensions.h23
-rw-r--r--farstream/extensions/misc.xml10
-rw-r--r--farstream/m4/Makefile.am5
-rw-r--r--farstream/m4/ac-python-headers.m430
-rw-r--r--farstream/m4/as-ac-expand.m440
-rw-r--r--farstream/m4/as-compiler-flag.m433
-rw-r--r--farstream/m4/as-version.m466
-rw-r--r--farstream/python/Makefile.am48
-rw-r--r--farstream/python/codegen/Makefile.am15
-rw-r--r--farstream/python/codegen/__init__.py15
-rw-r--r--farstream/python/codegen/argtypes.py1075
-rwxr-xr-xfarstream/python/codegen/code-coverage.py42
-rw-r--r--farstream/python/codegen/codegen.py1572
-rw-r--r--farstream/python/codegen/definitions.py607
-rw-r--r--farstream/python/codegen/defsparser.py143
-rw-r--r--farstream/python/codegen/docextract.py185
-rw-r--r--farstream/python/codegen/docgen.py752
-rwxr-xr-xfarstream/python/codegen/h2def.py536
-rwxr-xr-xfarstream/python/codegen/mergedefs.py26
-rwxr-xr-xfarstream/python/codegen/mkskel.py89
-rw-r--r--farstream/python/codegen/override.py288
-rw-r--r--farstream/python/codegen/reversewrapper.py771
-rw-r--r--farstream/python/codegen/scmexpr.py144
-rw-r--r--farstream/python/common.h79
-rw-r--r--farstream/python/examples/Makefile.am7
-rw-r--r--farstream/python/examples/README5
-rw-r--r--farstream/python/examples/callchannel.py180
-rw-r--r--farstream/python/examples/callhandler.py116
-rw-r--r--farstream/python/examples/callui.py285
-rw-r--r--farstream/python/examples/constants.py66
-rw-r--r--farstream/python/examples/element-properties62
-rw-r--r--farstream/python/examples/util.py40
-rw-r--r--farstream/python/pygstminiobject.c373
-rw-r--r--farstream/python/pygstminiobject.h55
-rw-r--r--farstream/python/pytpfarstream-filter.defs24
-rw-r--r--farstream/python/pytpfarstream.defs134
-rw-r--r--farstream/python/pytpfarstream.override107
-rw-r--r--farstream/python/pytpfarstreammodule.c30
-rwxr-xr-xfarstream/python/rebuild-defs.sh18
-rw-r--r--farstream/telepathy-farstream/Makefile.am88
-rw-r--r--farstream/telepathy-farstream/call-channel.c723
-rw-r--r--farstream/telepathy-farstream/call-channel.h118
-rw-r--r--farstream/telepathy-farstream/call-content.c1722
-rw-r--r--farstream/telepathy-farstream/call-content.h106
-rw-r--r--farstream/telepathy-farstream/call-stream.c1140
-rw-r--r--farstream/telepathy-farstream/call-stream.h122
-rw-r--r--farstream/telepathy-farstream/channel-priv.h28
-rw-r--r--farstream/telepathy-farstream/channel.c668
-rw-r--r--farstream/telepathy-farstream/channel.h62
-rw-r--r--farstream/telepathy-farstream/content-priv.h43
-rw-r--r--farstream/telepathy-farstream/content.c424
-rw-r--r--farstream/telepathy-farstream/content.h64
-rw-r--r--farstream/telepathy-farstream/media-signalling-channel.c729
-rw-r--r--farstream/telepathy-farstream/media-signalling-channel.h87
-rw-r--r--farstream/telepathy-farstream/media-signalling-content.c324
-rw-r--r--farstream/telepathy-farstream/media-signalling-content.h86
-rw-r--r--farstream/telepathy-farstream/session-priv.h69
-rw-r--r--farstream/telepathy-farstream/session.c456
-rw-r--r--farstream/telepathy-farstream/stream-priv.h75
-rw-r--r--farstream/telepathy-farstream/stream.c3097
-rw-r--r--farstream/telepathy-farstream/stream.h61
-rw-r--r--farstream/telepathy-farstream/telepathy-farstream-uninstalled.pc.in11
-rw-r--r--farstream/telepathy-farstream/telepathy-farstream.c34
-rw-r--r--farstream/telepathy-farstream/telepathy-farstream.h33
-rw-r--r--farstream/telepathy-farstream/telepathy-farstream.pc.in11
-rw-r--r--farstream/telepathy-farstream/utils.h28
-rw-r--r--farstream/tools/Makefile.am21
-rw-r--r--farstream/tools/c-constants-gen.py169
-rw-r--r--farstream/tools/glib-client-gen.py1224
-rw-r--r--farstream/tools/glib-client-marshaller-gen.py59
-rw-r--r--farstream/tools/glib-ginterface-gen.py820
-rw-r--r--farstream/tools/glib-gtypes-generator.py298
-rw-r--r--farstream/tools/glib-interfaces-gen.py197
-rw-r--r--farstream/tools/glib-signals-marshal-gen.py55
-rw-r--r--farstream/tools/libglibcodegen.py172
-rw-r--r--farstream/tools/libtpcodegen.py215
-rw-r--r--farstream/tools/telepathy.am27
-rw-r--r--farstream/tools/xincludator.py39
114 files changed, 27395 insertions, 0 deletions
diff --git a/farstream/.gitignore b/farstream/.gitignore
new file mode 100644
index 000000000..e917fd778
--- /dev/null
+++ b/farstream/.gitignore
@@ -0,0 +1,66 @@
+*.lo
+*.loT
+*.o
+*.la
+*.py[co]
+Makefile
+Makefile.in
+.deps
+.libs
+*~
+*#
+.#*
+*.stamp
+
+telepathy-farstream/tf-signals-marshal.[ch]
+telepathy-farstream/tf-signals-marshal.list
+telepathy-farstream/telepathy-farstream.pc
+telepathy-farstream/telepathy-farstream-uninstalled.pc
+
+doc/lib/*.bak
+doc/lib/html/
+doc/lib/telepathy-farstream.args
+doc/lib/telepathy-farstream-decl-list.txt
+doc/lib/telepathy-farstream-decl.txt
+doc/lib/telepathy-farstream.hierarchy
+doc/lib/telepathy-farstream.interfaces
+doc/lib/telepathy-farstream-overrides.txt
+doc/lib/telepathy-farstream.prerequisites
+doc/lib/telepathy-farstream.signals
+doc/lib/telepathy-farstream-undeclared.txt
+doc/lib/telepathy-farstream-undocumented.txt
+doc/lib/telepathy-farstream-unused.txt
+doc/lib/tmpl/
+doc/lib/xml/
+
+INSTALL
+aclocal.m4
+autom4te.cache
+autoregen.sh
+config.guess
+config.h
+config.h.in
+config.log
+config.status
+config.sub
+configure
+compile
+depcomp
+diff
+install-sh
+libtool
+ltmain.sh
+missing
+stamp-h.in
+stamp-h1
+gtk-doc.make
+m4/gtk-doc.m4
+python/pytpfarstream.c
+examples/call-handler
+extensions/_gen/
+
+m4/libtool.m4
+m4/ltoptions.m4
+m4/ltsugar.m4
+m4/ltversion.m4
+m4/lt~obsolete.m4
diff --git a/farstream/AUTHORS b/farstream/AUTHORS
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/farstream/AUTHORS
diff --git a/farstream/COPYING b/farstream/COPYING
new file mode 100644
index 000000000..68e307cb6
--- /dev/null
+++ b/farstream/COPYING
@@ -0,0 +1,463 @@
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations
+below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it
+becomes a de-facto standard. To achieve this, non-free programs must
+be allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control
+compilation and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at least
+ three years, to give the same user the materials specified in
+ Subsection 6a, above, for a charge no more than the cost of
+ performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply, and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License
+may add an explicit geographical distribution limitation excluding those
+countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+
diff --git a/farstream/ChangeLog b/farstream/ChangeLog
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/farstream/ChangeLog
diff --git a/farstream/Makefile.am b/farstream/Makefile.am
new file mode 100644
index 000000000..18cffacf9
--- /dev/null
+++ b/farstream/Makefile.am
@@ -0,0 +1,34 @@
+ACLOCAL_AMFLAGS = -I m4
+
+
+if WANT_PYTHON
+ PYTHON_SUBDIR = python
+endif
+
+SUBDIRS = m4 \
+ tools \
+ extensions \
+ telepathy-farstream \
+ doc \
+ $(PYTHON_SUBDIR) \
+ examples
+
+DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc
+
+EXTRA_DIST = \
+ autogen.sh \
+ gtk-doc.make
+
+maintainer-upload-release: _maintainer-upload-release-local
+_maintainer-upload-release-local: _maintainer-upload-release-check
+ rsync -rtvzPp --chmod=Dg+s,ug+rwX,o=rX doc/lib/html/ \
+ telepathy.freedesktop.org:/srv/telepathy.freedesktop.org/www/doc/telepathy-farstream/
+
+BRANCH = misc
+UPLOAD_BRANCH_TO = people.freedesktop.org:public_html/telepathy-farstream
+
+upload-branch-docs: all
+ rsync -rtzvPp --chmod=a+rX doc/lib/html/ \
+ $(UPLOAD_BRANCH_TO)-$(BRANCH)/
+
+include tools/telepathy.am
diff --git a/farstream/NEWS b/farstream/NEWS
new file mode 100644
index 000000000..1376a6daa
--- /dev/null
+++ b/farstream/NEWS
@@ -0,0 +1,105 @@
+telepathy-farstream 0.1.1 (14 Jul 2011)
+======================================
+- Fix the python bindings
+- Make the VideoControl interface actually work
+
+telepathy-farstream 0.1.0 (27 Jun 2011)
+======================================
+- Rename from telepathy-farsight to telepathy-farstream
+- Implement Call API
+- Also implement Streamed Media API under the same C api
+
+telepathy-farsight 0.0.16 (22 Dec 2010)
+=======================================
+- Emit the NewActiveTransportPair signal
+- Emit CodecsUpdated more often
+- Various bug fixes
+
+telepathy-farsight 0.0.15 (30 Sep 2010)
+=======================================
+- Release sending resource when SetStreamSending(False) is called
+
+telepathy-farsight 0.0.14 (26 May 2010)
+=======================================
+- Add properties to get the Farsight2 session and stream
+- Recognize the shm transmitter
+- Ignore invalidly empty strings in tp properties
+- Fix -Wshadow warnings
+- Use silent rules if automake >= 1.11
+
+telepathy-farsight 0.0.13 (5 Jan 2010)
+======================================
+- Export held resource in a property
+- Transfer the ptime/maxptime
+
+telepathy-farsight 0.0.12 (15 Oct 2009)
+=======================================
+- Fix mixup between GSlice and malloc
+- Fix potential race between src-pad-added and dispose
+- The connected state in farsight is a lie, ignore it
+
+telepathy-farsight 0.0.11 (10 Sep 2009)
+=======================================
+- Fix double free
+- Fix more leaks
+
+telepathy-farsight 0.0.10 (08 Sep 2009)
+======================================
+- Fix some leaks
+- Fix possible deadlocks
+- Emit different error codes depending on the error
+- Emit stream state changes when the stream state changes according to ICE
+
+telepathy-farsight 0.0.9 (03 Aug 2009)
+======================================
+- Emit session-invalidated on channel dispose (prevents leak)
+- Fix ICE priority mangling (so not all candidates get 0)
+- Use new error numbers from the the 0.17.27 spec
+
+telepathy-farsight 0.0.8 (03 Aug 2009)
+======================================
+- Set ToS property on streams
+- Set ICE controlling according to the CreatedLocally property
+- Work around bug in the dbus-glib 0.80 of dbus properties
+- Fix bugs
+
+telepathy-farsight 0.0.7 (06 May 2009)
+======================================
+- Remove pygtk requirement
+- Print errors in its own domain
+- Update tp-glib dependency to 0.7.26 and fs2 dependency to 0.0.7
+- Make it more resilient in case of errors from other layers
+
+telepathy-farsight 0.0.6 (17 March 2009)
+========================================
+- Add support of the RelayInfo property
+
+telepathy-farsight 0.0.5 (16 March 2009)
+========================================
+- Recognize ice-udp
+- Improve error handling
+- Support the new CodecsUpdated method
+
+telepathy-farsight 0.0.4 (14 January 2009)
+==========================================
+- Add python bindings for tpfarsight
+- Fix hold
+- Make the "request-resource" signal optional, assumes
+ the resource is always there if there is no handler.
+
+telepathy-farsight 0.0.3 (21 November 2008)
+===========================================
+- Fix small brown-paper bug in last release
+- Rename tf_channel_new_from_proxy to tf_channel_new,
+ and leave the proxy creation to the client
+
+telepathy-farsight 0.0.2 (21 November 2008)
+===========================================
+
+- Added various makefile niceties from telepathy-glib
+
+telepathy-farsight 0.0.1 (21 November 2008)
+===========================================
+
+- Initial version
+- Split from stream-engine after 0.5.3
diff --git a/farstream/README b/farstream/README
new file mode 100644
index 000000000..3a18752db
--- /dev/null
+++ b/farstream/README
@@ -0,0 +1,70 @@
+=======================
+telepathy-farstream
+=======================
+
+Telepathy Farstream is a Telepathy client library that uses Farstream
+to handle Call channels.
+
+Telepathy is a D-Bus framework for unifying real time communication,
+including instant messaging, voice calls and video calls. It abstracts
+differences between protocols to provide a unified interface for
+applications.
+
+Requirements
+============
+
+telepathy-farstream requires:
+ telepathy-glib <http://telepathy.freedesktop.org/releases/telepathy-glib/>
+ Farstream <http://farsight.freedesktop.org/releases/farstream/>
+ GStreamer base plugins <http://gstreamer.freedesktop.org/>
+ GLib, GObject <http://ftp.gnome.org/pub/GNOME/sources/glib/>
+ libdbus <http://dbus.freedesktop.org/releases/dbus/>
+ The D-Bus GLib bindings <http://dbus.freedesktop.org/releases/dbus-glib/>
+
+At build time, it also requires:
+ GNU make <http://www.gnu.org/software/make/>
+ pkg-config <http://ftp.gnome.org/pub/GNOME/sources/pkg-config/>
+ libxslt, xsltproc <http://xmlsoft.org/XSLT/>
+ Python <http://www.python.org/>
+
+See configure.ac for full details, including versions required.
+
+Building from Darcs also requires the GNU build system (Autoconf, Automake,
+libtool).
+
+Bugs, feature requests and to-do list
+=====================================
+
+Report all bugs, feature requests and "to-do" items here:
+ <https://bugs.freedesktop.org/enter_bug.cgi?product=Telepathy&component=tp-farstream>
+
+Versioning policy
+=================
+
+We use an "odd/even" versioning scheme where the minor version (the y in
+x.y.z) determines stability - stable branches have y even, development
+branches have y odd.
+
+Unreleased builds straight from Darcs identify themselves as version
+"x.y.z.1". These are compiled with -Werror, so they might stop working
+if your gcc version issues more warnings than ours. If this is a problem
+for you, use a release tarball.
+
+Contact info
+============
+
+This package is maintained by the Telepathy project:
+ <http://telepathy.freedesktop.org/>
+ <mailto:telepathy@lists.freedesktop.org>
+ <irc://irc.freenode.net/telepathy>
+
+Telepathy development is supported by Collabora Ltd.
+ <http://www.collabora.co.uk/>.
+
+Hacking
+=======
+
+The current version of telepathy-farstream is always available from:
+ <http://git.collabora.co.uk/?p=telepathy-farstream.git;a=summary>
+
+Please follow <http://telepathy.freedesktop.org/wiki/Style>.
diff --git a/farstream/autogen.sh b/farstream/autogen.sh
new file mode 100755
index 000000000..822ebf863
--- /dev/null
+++ b/farstream/autogen.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+set -e
+
+gtkdocize --copy
+
+if test -n "$AUTOMAKE"; then
+ : # don't override an explicit user request
+elif automake-1.9 --version >/dev/null 2>/dev/null && \
+ aclocal-1.9 --version >/dev/null 2>/dev/null; then
+ # If we have automake-1.9, use it. This helps to ensure that our build
+ # system doesn't accidentally grow automake-1.10 dependencies.
+ AUTOMAKE=automake-1.9
+ export AUTOMAKE
+ ACLOCAL=aclocal-1.9
+ export ACLOCAL
+fi
+
+autoreconf -i -f
+
+run_configure=true
+for arg in $*; do
+ case $arg in
+ --no-configure)
+ run_configure=false
+ ;;
+ *)
+ ;;
+ esac
+done
+
+if test $run_configure = true; then
+ ./configure "$@"
+fi
diff --git a/farstream/configure.ac b/farstream/configure.ac
new file mode 100644
index 000000000..67cf5b47a
--- /dev/null
+++ b/farstream/configure.ac
@@ -0,0 +1,154 @@
+AC_PREREQ([2.59])
+
+# Making releases:
+# set the new version number:
+# odd minor -> development series
+# even minor -> stable series
+# increment micro for each release within a series
+# set tp_farstream_release to 1
+
+m4_define([tp_farstream_major_version], [0])
+m4_define([tp_farstream_minor_version], [1])
+m4_define([tp_farstream_micro_version], [1])
+m4_define([tp_farstream_nano_version], [1])
+
+dnl CURRENT, REVISION, AGE
+dnl - library source changed -> increment REVISION
+dnl - interfaces added/removed/changed -> increment CURRENT, REVISION = 0
+dnl - interfaces added -> increment AGE
+dnl - interfaces removed -> AGE = 0
+m4_define([tp_farstream_lt_current], [0])
+m4_define([tp_farstream_lt_revision], [1])
+m4_define([tp_farstream_lt_age], [0])
+
+
+# Some magic
+m4_define([tp_farstream_base_version],
+ [tp_farstream_major_version.tp_farstream_minor_version.tp_farstream_micro_version])
+m4_define([tp_farstream_version],
+ [m4_if(tp_farstream_nano_version, 0, [tp_farstream_base_version],
+ [tp_farstream_base_version].[tp_farstream_nano_version])])
+
+AC_INIT([Telepathy-Farstream], [tp_farstream_version],
+ [https://bugs.freedesktop.org/enter_bug.cgi?product=Telepathy&component=tp-farstream])
+
+AC_CONFIG_MACRO_DIR([m4])
+
+AM_INIT_AUTOMAKE([1.9 -Wno-portability])
+AM_CONFIG_HEADER(config.h)
+
+dnl use pretty build output with automake >= 1.11
+m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])],
+ [AM_DEFAULT_VERBOSITY=1
+ AC_SUBST(AM_DEFAULT_VERBOSITY)])
+
+dnl check for tools
+AC_PROG_CC
+AC_PROG_CC_STDC
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+
+dnl decide error flags
+AS_COMPILER_FLAG(-Wall, ERROR_CFLAGS="-Wall", ERROR_CFLAGS="")
+AS_COMPILER_FLAG(-Werror, werror=yes, werror=no)
+
+AC_ARG_ENABLE(Werror,
+ AC_HELP_STRING([--disable-Werror],[compile without -Werror (normally enabled in development builds)]),
+ werror=$enableval, :)
+
+AS_COMPILER_FLAG(-Wextra, wextra=yes, wextra=no)
+AS_COMPILER_FLAG(-Wno-missing-field-initializers,
+ wno_missing_field_initializers=yes,
+ wno_missing_field_initializers=no)
+AS_COMPILER_FLAG(-Wno-unused-parameter,
+ wno_unused_parameter=yes,
+ wno_unused_parameter=no)
+
+ifelse(tp_farstream_nano_version, 0, [],
+ [
+ if test x$werror = xyes; then
+ ERROR_CFLAGS="$ERROR_CFLAGS -Werror"
+ fi
+ if test x$wextra = xyes -a \
+ x$wno_missing_field_initializers = xyes -a \
+ x$wno_unused_parameter = xyes; then
+ ERROR_CFLAGS="$ERROR_CFLAGS -Wextra -Wno-missing-field-initializers -Wno-unused-parameter"
+ fi
+ ])
+
+AC_SUBST(ERROR_CFLAGS)
+
+AC_ARG_ENABLE(coverage, [ --enable-coverage compile with coverage info],[enable_coverage=${enableval}],enable_coverage=no)
+if test "x$enable_coverage" = "xyes"; then
+ CFLAGS="$CFLAGS -g -fprofile-arcs -ftest-coverage"
+fi
+
+dnl Check for Glib
+PKG_CHECK_MODULES(GLIB, gobject-2.0 >= 2.16 glib-2.0 >= 2.16 gio-2.0)
+
+GLIB_GENMARSHAL=`$PKG_CONFIG --variable=glib_genmarshal glib-2.0`
+AC_SUBST(GLIB_CFLAGS)
+AC_SUBST(GLIB_LIBS)
+AC_SUBST(GLIB_GENMARSHAL)
+
+dnl Check for DBus
+PKG_CHECK_MODULES(DBUS, [dbus-1 >= 0.60, dbus-glib-1 >= 0.60])
+
+AC_SUBST(DBUS_CFLAGS)
+AC_SUBST(DBUS_LIBS)
+
+dnl Check for Telepathy libraries
+PKG_CHECK_MODULES([TELEPATHY], [telepathy-glib >= 0.13.4])
+
+AC_SUBST(TELEPATHY_CFLAGS)
+AC_SUBST(TELEPATHY_LIBS)
+
+dnl Check for farstream
+PKG_CHECK_MODULES(FARSTREAM, [farstream-0.1 >= 0.1.0])
+
+AC_SUBST(FARSTREAM_CFLAGS)
+AC_SUBST(FARSTREAM_LIBS)
+
+dnl Always required to generate extensions
+AM_PATH_PYTHON([2.5])
+
+AC_ARG_ENABLE([python],
+ AC_HELP_STRING([--disable-python], [Disable Python bindings]),
+ [case "${enableval}" in
+ yes) WANT_PYTHON=yes ;;
+ no) WANT_PYTHON=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --enable-python) ;;
+ esac],
+ WANT_PYTHON=yes)
+
+if test "x$WANT_PYTHON" = "xyes"; then
+ AM_CHECK_PYTHON_HEADERS(,[AC_MSG_ERROR(could not find Python headers)])
+ PKG_CHECK_MODULES(PYTPFARSTREAM, [ pygobject-2.0 >= 2.12.0
+ gst-python-0.10 >= 0.10.10 ])
+fi
+AM_CONDITIONAL(WANT_PYTHON, test "x$WANT_PYTHON" = "xyes")
+
+GTK_DOC_CHECK([1.10])
+
+LT_CURRENT=tp_farstream_lt_current
+LT_REVISION=tp_farstream_lt_revision
+LT_AGE=tp_farstream_lt_age
+AC_SUBST([LT_CURRENT])
+AC_SUBST([LT_REVISION])
+AC_SUBST([LT_AGE])
+
+
+AC_OUTPUT( Makefile \
+ doc/Makefile \
+ doc/lib/Makefile \
+ m4/Makefile \
+ extensions/Makefile \
+ examples/Makefile \
+ telepathy-farstream/Makefile \
+ telepathy-farstream/telepathy-farstream.pc \
+ telepathy-farstream/telepathy-farstream-uninstalled.pc
+ tools/Makefile \
+ python/Makefile \
+ python/codegen/Makefile \
+ python/examples/Makefile \
+)
diff --git a/farstream/doc/Makefile.am b/farstream/doc/Makefile.am
new file mode 100644
index 000000000..0262e4ddc
--- /dev/null
+++ b/farstream/doc/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = lib
diff --git a/farstream/doc/lib/Makefile.am b/farstream/doc/lib/Makefile.am
new file mode 100644
index 000000000..59bfce77c
--- /dev/null
+++ b/farstream/doc/lib/Makefile.am
@@ -0,0 +1,106 @@
+## Process this file with automake to produce Makefile.in
+
+abs_top_builddir = @abs_top_builddir@
+
+# We require automake 1.6 at least.
+AUTOMAKE_OPTIONS = 1.6
+
+# The name of the module, e.g. 'glib'.
+DOC_MODULE=telepathy-farstream
+
+# The top-level SGML file. You can change this if you want to.
+DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.sgml
+
+# The directory containing the source code. Relative to $(srcdir).
+# gtk-doc will search all .c & .h files beneath here for inline comments
+# documenting the functions and macros.
+# e.g. DOC_SOURCE_DIR=../../../gtk
+DOC_SOURCE_DIR=../../telepathy-farstream
+
+# Extra options to pass to gtkdoc-scangobj. Not normally needed.
+SCANGOBJ_OPTIONS=
+
+# Extra options to supply to gtkdoc-scan.
+# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED"
+SCAN_OPTIONS=
+
+# Extra options to supply to gtkdoc-mkdb.
+# e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml
+MKDB_OPTIONS=--sgml-mode --output-format=xml
+
+# Extra options to supply to gtkdoc-mktmpl
+# e.g. MKTMPL_OPTIONS=--only-section-tmpl
+MKTMPL_OPTIONS=
+
+# Extra options to supply to gtkdoc-fixref. Not normally needed.
+# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html
+FIXXREF_OPTIONS=
+
+# Used for dependencies. The docs will be rebuilt if any of these change.
+# e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h
+# e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c
+HFILE_GLOB=$(top_srcdir)/telepathy-farstream/channel.h $(top_srcdir)/telepathy-farstream/content.h
+CFILE_GLOB=$(top_srcdir)/telepathy-farstream/channel.c $(top_srcdir)/telepathy-farstream/content.c
+
+# Header files to ignore when scanning.
+# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h
+IGNORE_HFILES=channel-priv.h content-priv.h session-priv.h stream-priv.h \
+ stream.h call-channel.h call-content.h call-stream.h tf-signals-marshal.h \
+ media-signalling-channel.h media-signalling-content.h
+
+# Images to copy into HTML directory.
+# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
+HTML_IMAGES=
+
+# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
+# e.g. content_files=running.sgml building.sgml changes-2.0.sgml
+content_files=
+
+# SGML files where gtk-doc abbrevations (#GtkWidget) are expanded
+# These files must be listed here *and* in content_files
+# e.g. expand_content_files=running.sgml
+expand_content_files=
+
+# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library.
+# Only needed if you are using gtkdoc-scangobj to dynamically query widget
+# signals and properties.
+# e.g. INCLUDES=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS)
+# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
+INCLUDES= \
+ $(GLIB_CFLAGS) \
+ $(DBUS_CFLAGS) \
+ $(X11_CFLAGS) \
+ $(GST_CFLAGS) \
+ $(GST_INTERFACES_CFLAGS) \
+ $(FARSTREAM_CFLAGS) \
+ $(TELEPATHY_CFLAGS) \
+ -I$(top_srcdir) -I$(top_builddir)
+GTKDOC_LIBS= \
+ $(GLIB_LIBS) \
+ $(DBUS_LIBS) \
+ $(X11_LIBS) \
+ $(GST_LIBS) \
+ $(GST_INTERFACES_LIBS) \
+ $(TELEPATHY_LIBS) \
+ $(FARSTREAM_LIBS) \
+ $(top_builddir)/telepathy-farstream/libtelepathy-farstream.la
+
+# This includes the standard gtk-doc make rules, copied by gtkdocize.
+include $(top_srcdir)/gtk-doc.make
+
+if ENABLE_GTK_DOC
+check-local:
+ @if grep '^0 symbols incomplete' \
+ $(srcdir)/telepathy-farstream-undocumented.txt; then\
+ :; else \
+ cat $(srcdir)/telepathy-farstream-undocumented.txt; exit 1; fi
+ @if grep '^0 not documented' \
+ $(srcdir)/telepathy-farstream-undocumented.txt; then\
+ :; else \
+ cat $(srcdir)/telepathy-farstream-undocumented.txt; exit 1; fi
+ @if grep . $(srcdir)/telepathy-farstream-unused.txt; then\
+ echo "^^^ Unused symbols" >&2; exit 1; fi
+ @if test -e $(srcdir)/telepathy-farstream-undeclared.txt &&\
+ grep . $(srcdir)/telepathy-farstream-undeclared.txt; then\
+ echo "^^^ Undeclared symbols" >&2; exit 1; fi
+endif
diff --git a/farstream/doc/lib/telepathy-farstream-docs.sgml b/farstream/doc/lib/telepathy-farstream-docs.sgml
new file mode 100644
index 000000000..11bd7951e
--- /dev/null
+++ b/farstream/doc/lib/telepathy-farstream-docs.sgml
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd">
+<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude">
+ <bookinfo>
+ <title>telepathy-farstream Reference Manual</title>
+ <releaseinfo>
+ The latest version of this documentation can be found on-line at
+ <ulink role="online-location" url="http://telepathy.freedesktop.org/doc/telepathy-farstream/">http://telepathy.freedesktop.org/telepathy-farstream/</ulink>.
+ </releaseinfo>
+ </bookinfo>
+
+ <chapter>
+ <title>Telepathy Farstream Helper Classes</title>
+ <xi:include href="xml/channel.xml"/>
+ <xi:include href="xml/content.xml"/>
+ </chapter>
+</book>
diff --git a/farstream/doc/lib/telepathy-farstream-sections.txt b/farstream/doc/lib/telepathy-farstream-sections.txt
new file mode 100644
index 000000000..4a1382da8
--- /dev/null
+++ b/farstream/doc/lib/telepathy-farstream-sections.txt
@@ -0,0 +1,43 @@
+<SECTION>
+<TITLE>Core</TITLE>
+tf_init
+tp_media_type_to_fs
+</SECTION>
+
+<SECTION>
+<FILE>content</FILE>
+<TITLE>TfContent</TITLE>
+TfContent
+TfContentClass
+tf_content_iterate_src_pads
+tf_content_error
+tf_content_error_literal
+<SUBSECTION Private>
+TfContentPrivate
+<SUBSECTION Standard>
+TF_CONTENT
+TF_TYPE_CONTENT
+tf_content_get_type
+TF_CONTENT_CLASS
+TF_CONTENT_GET_CLASS
+TF_IS_CONTENT
+TF_IS_CONTENT_CLASS
+</SECTION>
+
+<SECTION>
+<FILE>channel</FILE>
+<TITLE>TfChannel</TITLE>
+TfChannel
+tf_channel_new_async
+tf_channel_bus_message
+<SUBSECTION Private>
+TfChannelPrivate
+<SUBSECTION Standard>
+TF_CHANNEL
+TF_TYPE_CHANNEL
+tf_channel_get_type
+TF_CHANNEL_CLASS
+TF_CHANNEL_GET_CLASS
+TF_IS_CHANNEL
+TF_IS_CHANNEL_CLASS
+</SECTION>
diff --git a/farstream/doc/lib/telepathy-farstream.types b/farstream/doc/lib/telepathy-farstream.types
new file mode 100644
index 000000000..a2e28ba0d
--- /dev/null
+++ b/farstream/doc/lib/telepathy-farstream.types
@@ -0,0 +1,2 @@
+tf_content_get_type
+tf_channel_get_type
diff --git a/farstream/examples/Makefile.am b/farstream/examples/Makefile.am
new file mode 100644
index 000000000..ff2e7c34e
--- /dev/null
+++ b/farstream/examples/Makefile.am
@@ -0,0 +1,20 @@
+noinst_PROGRAMS = call-handler
+
+LDADD = \
+ ../telepathy-farstream/libtelepathy-farstream.la \
+ ../extensions/libfuture-extensions.la \
+ $(GLIB_LIBS) \
+ $(DBUS_LIBS) \
+ $(GST_LIBS) \
+ $(FARSTREAM_LIBS) \
+ $(TELEPATHY_LIBS)
+
+AM_CFLAGS = \
+ $(ERROR_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(DBUS_CFLAGS) \
+ $(GST_CFLAGS) \
+ $(FARSTREAM_CFLAGS) \
+ $(TELEPATHY_CFLAGS) \
+ -I$(top_srcdir) \
+ -I$(top_builddir)
diff --git a/farstream/examples/call-handler.c b/farstream/examples/call-handler.c
new file mode 100644
index 000000000..5385a26a9
--- /dev/null
+++ b/farstream/examples/call-handler.c
@@ -0,0 +1,534 @@
+/*
+ * call-handler.c
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <gst/gst.h>
+#include <telepathy-glib/telepathy-glib.h>
+#include <extensions/extensions.h>
+#include <farstream/fs-element-added-notifier.h>
+#include <farstream/fs-utils.h>
+#include <telepathy-farstream/telepathy-farstream.h>
+
+typedef struct {
+ GstElement *pipeline;
+ guint buswatch;
+ TpChannel *proxy;
+ TfChannel *channel;
+ GList *notifiers;
+
+ GstElement *video_input;
+ GstElement *video_capsfilter;
+
+ guint width;
+ guint height;
+ guint framerate;
+} ChannelContext;
+
+GMainLoop *loop;
+
+static gboolean
+bus_watch_cb (GstBus *bus,
+ GstMessage *message,
+ gpointer user_data)
+{
+ ChannelContext *context = user_data;
+
+ if (context->channel != NULL)
+ tf_channel_bus_message (context->channel, message);
+
+ if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR)
+ {
+ GError *error = NULL;
+ gchar *debug = NULL;
+ gst_message_parse_error (message, &error, &debug);
+ g_printerr ("ERROR from element %s: %s\n",
+ GST_OBJECT_NAME (message->src), error->message);
+ g_printerr ("Debugging info: %s\n", (debug) ? debug : "none");
+ g_error_free (error);
+ g_free (debug);
+ }
+
+ return TRUE;
+}
+
+static void
+src_pad_added_cb (TfContent *content,
+ TpHandle handle,
+ FsStream *stream,
+ GstPad *pad,
+ FsCodec *codec,
+ gpointer user_data)
+{
+ ChannelContext *context = user_data;
+ gchar *cstr = fs_codec_to_string (codec);
+ FsMediaType mtype;
+ GstPad *sinkpad;
+ GstElement *element;
+ GstStateChangeReturn ret;
+
+ g_debug ("New src pad: %s", cstr);
+ g_object_get (content, "media-type", &mtype, NULL);
+
+ switch (mtype)
+ {
+ case FS_MEDIA_TYPE_AUDIO:
+ element = gst_parse_bin_from_description (
+ "audioconvert ! audioresample ! audioconvert ! autoaudiosink",
+ TRUE, NULL);
+ break;
+ case FS_MEDIA_TYPE_VIDEO:
+ element = gst_parse_bin_from_description (
+ "ffmpegcolorspace ! videoscale ! autovideosink",
+ TRUE, NULL);
+ break;
+ default:
+ g_warning ("Unknown media type");
+ return;
+ }
+
+ gst_bin_add (GST_BIN (context->pipeline), element);
+ sinkpad = gst_element_get_pad (element, "sink");
+ ret = gst_element_set_state (element, GST_STATE_PLAYING);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ {
+ tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL);
+ g_warning ("Failed to start sink pipeline !?");
+ return;
+ }
+
+ if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sinkpad)))
+ {
+ tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL);
+ g_warning ("Couldn't link sink pipeline !?");
+ return;
+ }
+
+ g_object_unref (sinkpad);
+}
+
+static void
+update_video_parameters (ChannelContext *context, gboolean restart)
+{
+ GstCaps *caps;
+ GstClock *clock;
+
+ if (restart)
+ {
+ /* Assuming the pipeline is in playing state */
+ gst_element_set_locked_state (context->video_input, TRUE);
+ gst_element_set_state (context->video_input, GST_STATE_NULL);
+ }
+
+ g_object_get (context->video_capsfilter, "caps", &caps, NULL);
+ caps = gst_caps_make_writable (caps);
+
+ gst_caps_set_simple (caps,
+ "framerate", GST_TYPE_FRACTION, context->framerate, 1,
+ "width", G_TYPE_INT, context->width,
+ "height", G_TYPE_INT, context->height,
+ NULL);
+
+ g_object_set (context->video_capsfilter, "caps", caps, NULL);
+
+ if (restart)
+ {
+ clock = gst_pipeline_get_clock (GST_PIPELINE (context->pipeline));
+ /* Need to reset the clock if we set the pipeline back to ready by hand */
+ if (clock != NULL)
+ {
+ gst_element_set_clock (context->video_input, clock);
+ g_object_unref (clock);
+ }
+
+ gst_element_set_locked_state (context->video_input, FALSE);
+ gst_element_sync_state_with_parent (context->video_input);
+ }
+}
+
+static void
+on_video_framerate_changed (TfContent *content,
+ GParamSpec *spec,
+ ChannelContext *context)
+{
+ guint framerate;
+
+ g_object_get (content, "framerate", &framerate, NULL);
+
+ if (framerate != 0)
+ context->framerate = framerate;
+
+ update_video_parameters (context, FALSE);
+}
+
+static void
+on_video_resolution_changed (TfContent *content,
+ guint width,
+ guint height,
+ ChannelContext *context)
+{
+ g_assert (width > 0 && height > 0);
+
+ context->width = width;
+ context->height = height;
+
+ update_video_parameters (context, TRUE);
+}
+
+static GstElement *
+setup_video_source (ChannelContext *context, TfContent *content)
+{
+ GstElement *result, *input, *rate, *scaler, *colorspace, *capsfilter;
+ GstCaps *caps;
+ guint framerate = 0, width = 0, height = 0;
+ GstPad *pad, *ghost;
+
+ result = gst_bin_new ("video_input");
+ input = gst_element_factory_make ("autovideosrc", NULL);
+ rate = gst_element_factory_make ("videomaxrate", NULL);
+ scaler = gst_element_factory_make ("videoscale", NULL);
+ colorspace = gst_element_factory_make ("colorspace", NULL);
+ capsfilter = gst_element_factory_make ("capsfilter", NULL);
+
+ g_assert (input && rate && scaler && colorspace && capsfilter);
+ g_object_get (content,
+ "framerate", &framerate,
+ "width", &width,
+ "height", &height,
+ NULL);
+
+ if (framerate == 0)
+ framerate = 15;
+
+ if (width == 0 || height == 0)
+ {
+ width = 320;
+ height = 240;
+ }
+
+ context->framerate = framerate;
+ context->width = width;
+ context->height = height;
+
+ caps = gst_caps_new_simple ("video/x-raw-yuv",
+ "width", G_TYPE_INT, width,
+ "height", G_TYPE_INT, height,
+ "framerate", GST_TYPE_FRACTION, framerate, 1,
+ NULL);
+
+ g_object_set (G_OBJECT (capsfilter), "caps", caps, NULL);
+
+ gst_bin_add_many (GST_BIN (result), input, rate, scaler,
+ colorspace, capsfilter, NULL);
+ g_assert (gst_element_link_many (input, rate, scaler,
+ colorspace, capsfilter, NULL));
+
+ pad = gst_element_get_static_pad (capsfilter, "src");
+ g_assert (pad != NULL);
+
+ ghost = gst_ghost_pad_new ("src", pad);
+ gst_element_add_pad (result, ghost);
+
+ g_object_unref (pad);
+
+ context->video_input = result;
+ context->video_capsfilter = capsfilter;
+
+ g_signal_connect (content, "notify::framerate",
+ G_CALLBACK (on_video_framerate_changed),
+ context);
+
+ g_signal_connect (content, "resolution-changed",
+ G_CALLBACK (on_video_resolution_changed),
+ context);
+
+ return result;
+}
+
+
+static void
+content_added_cb (TfChannel *channel,
+ TfContent *content,
+ gpointer user_data)
+{
+ GstPad *srcpad, *sinkpad;
+ FsMediaType mtype;
+ GstElement *element;
+ GstStateChangeReturn ret;
+ ChannelContext *context = user_data;
+
+ g_debug ("Content added");
+
+ g_object_get (content,
+ "sink-pad", &sinkpad,
+ "media-type", &mtype,
+ NULL);
+
+ switch (mtype)
+ {
+ case FS_MEDIA_TYPE_AUDIO:
+ element = gst_parse_bin_from_description (
+ "audiotestsrc is-live=1 ! audio/x-raw-int,rate=8000 ! queue"
+ " ! audioconvert ! audioresample ! audioconvert ",
+
+ TRUE, NULL);
+ break;
+ case FS_MEDIA_TYPE_VIDEO:
+ element = setup_video_source (context, content);
+ break;
+ default:
+ g_warning ("Unknown media type");
+ goto out;
+ }
+
+ g_signal_connect (content, "src-pad-added",
+ G_CALLBACK (src_pad_added_cb), context);
+
+ gst_bin_add (GST_BIN (context->pipeline), element);
+ srcpad = gst_element_get_pad (element, "src");
+
+ if (GST_PAD_LINK_FAILED (gst_pad_link (srcpad, sinkpad)))
+ {
+ tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL);
+ g_warning ("Couldn't link source pipeline !?");
+ return;
+ }
+
+ ret = gst_element_set_state (element, GST_STATE_PLAYING);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ {
+ tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL);
+ g_warning ("source pipeline failed to start!?");
+ return;
+ }
+
+ g_object_unref (srcpad);
+out:
+ g_object_unref (sinkpad);
+}
+
+static void
+conference_added_cb (TfChannel *channel,
+ GstElement *conference,
+ gpointer user_data)
+{
+ ChannelContext *context = user_data;
+ GKeyFile *keyfile;
+
+ g_debug ("Conference added");
+
+ /* Add notifier to set the various element properties as needed */
+ keyfile = fs_utils_get_default_element_properties (conference);
+ if (keyfile != NULL)
+ {
+ FsElementAddedNotifier *notifier;
+ g_debug ("Loaded default codecs for %s", GST_ELEMENT_NAME (conference));
+
+ notifier = fs_element_added_notifier_new ();
+ fs_element_added_notifier_set_properties_from_keyfile (notifier, keyfile);
+ fs_element_added_notifier_add (notifier, GST_BIN (context->pipeline));
+
+ context->notifiers = g_list_prepend (context->notifiers, notifier);
+ }
+
+
+ gst_bin_add (GST_BIN (context->pipeline), conference);
+ gst_element_set_state (conference, GST_STATE_PLAYING);
+}
+
+static gboolean
+dump_pipeline_cb (gpointer data)
+{
+ ChannelContext *context = data;
+
+ GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (context->pipeline),
+ GST_DEBUG_GRAPH_SHOW_ALL,
+ "call-handler");
+
+ return TRUE;
+}
+
+static void
+new_tf_channel_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ChannelContext *context = user_data;
+
+ g_debug ("New TfChannel");
+
+ context->channel = TF_CHANNEL (g_async_initable_new_finish (
+ G_ASYNC_INITABLE (source), result, NULL));
+
+
+ if (context->channel == NULL)
+ {
+ g_warning ("Failed to create channel");
+ return;
+ }
+
+ g_debug ("Adding timeout");
+ g_timeout_add_seconds (5, dump_pipeline_cb, context);
+
+ g_signal_connect (context->channel, "fs-conference-added",
+ G_CALLBACK (conference_added_cb), context);
+
+ g_signal_connect (context->channel, "content-added",
+ G_CALLBACK (content_added_cb), context);
+}
+
+static void
+proxy_invalidated_cb (TpProxy *proxy,
+ guint domain,
+ gint code,
+ gchar *message,
+ gpointer user_data)
+{
+ ChannelContext *context = user_data;
+
+ g_debug ("Channel closed");
+ if (context->pipeline != NULL)
+ {
+ gst_element_set_state (context->pipeline, GST_STATE_NULL);
+ g_object_unref (context->pipeline);
+ }
+
+ if (context->channel != NULL)
+ g_object_unref (context->channel);
+
+ g_list_foreach (context->notifiers, (GFunc) g_object_unref, NULL);
+ g_list_free (context->notifiers);
+
+ g_object_unref (context->proxy);
+
+ g_slice_free (ChannelContext, context);
+
+ g_main_loop_quit (loop);
+}
+
+static void
+new_call_channel_cb (TpSimpleHandler *handler,
+ TpAccount *account,
+ TpConnection *connection,
+ GList *channels,
+ GList *requests_satisfied,
+ gint64 user_action_time,
+ TpHandleChannelsContext *handler_context,
+ gpointer user_data)
+{
+ ChannelContext *context;
+ TpChannel *proxy;
+ GstBus *bus;
+ GstElement *pipeline;
+ GstStateChangeReturn ret;
+
+ g_debug ("New channel");
+
+ proxy = channels->data;
+
+ pipeline = gst_pipeline_new (NULL);
+
+ ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ {
+ tp_channel_close_async (TP_CHANNEL (proxy), NULL, NULL);
+ g_object_unref (pipeline);
+ g_warning ("Failed to start an empty pipeline !?");
+ return;
+ }
+
+ context = g_slice_new0 (ChannelContext);
+ context->pipeline = pipeline;
+
+ bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
+ context->buswatch = gst_bus_add_watch (bus, bus_watch_cb, context);
+ g_object_unref (bus);
+
+ tf_channel_new_async (proxy, new_tf_channel_cb, context);
+
+ tp_handle_channels_context_accept (handler_context);
+
+ tf_future_cli_channel_type_call_call_accept (proxy, -1,
+ NULL, NULL, NULL, NULL);
+
+ context->proxy = g_object_ref (proxy);
+ g_signal_connect (proxy, "invalidated",
+ G_CALLBACK (proxy_invalidated_cb),
+ context);
+}
+
+int
+main (int argc, char **argv)
+{
+ TpBaseClient *client;
+ TpDBusDaemon *bus;
+
+ g_type_init ();
+ tf_init ();
+ gst_init (&argc, &argv);
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ bus = tp_dbus_daemon_dup (NULL);
+
+ client = tp_simple_handler_new (bus,
+ FALSE,
+ FALSE,
+ "TpFsCallHandlerDemo",
+ TRUE,
+ new_call_channel_cb,
+ NULL,
+ NULL);
+
+ tp_base_client_take_handler_filter (client,
+ tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+ TF_FUTURE_IFACE_CHANNEL_TYPE_CALL,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
+ TP_HANDLE_TYPE_CONTACT,
+ TF_FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, G_TYPE_BOOLEAN,
+ TRUE,
+ NULL));
+
+ tp_base_client_take_handler_filter (client,
+ tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+ TF_FUTURE_IFACE_CHANNEL_TYPE_CALL,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
+ TP_HANDLE_TYPE_CONTACT,
+ TF_FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, G_TYPE_BOOLEAN,
+ TRUE,
+ NULL));
+
+ tp_base_client_add_handler_capabilities_varargs (client,
+ TF_FUTURE_IFACE_CHANNEL_TYPE_CALL "/video/h264",
+ TF_FUTURE_IFACE_CHANNEL_TYPE_CALL "/shm",
+ TF_FUTURE_IFACE_CHANNEL_TYPE_CALL "/ice",
+ TF_FUTURE_IFACE_CHANNEL_TYPE_CALL "/gtalk-p2p",
+ NULL);
+
+ tp_base_client_register (client, NULL);
+
+ g_main_loop_run (loop);
+
+ g_object_unref (bus);
+ g_object_unref (client);
+ g_main_loop_unref (loop);
+
+ return 0;
+}
diff --git a/farstream/extensions/Call_Content.xml b/farstream/extensions/Call_Content.xml
new file mode 100644
index 000000000..270d99b08
--- /dev/null
+++ b/farstream/extensions/Call_Content.xml
@@ -0,0 +1,255 @@
+<?xml version="1.0" ?>
+<node name="/Call_Content"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>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.</p>
+
+ <p>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.</p>
+
+ <p>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.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Content.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ This object represents one Content inside a <tp:dbus-ref
+ namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref>. For
+ example, in an audio/video call there would be one audio content
+ and one video content. Each content has one or more <tp:dbus-ref
+ namespace="ofdT.Call">Stream.DRAFT</tp:dbus-ref> objects which
+ represent the actual transport to one or more remote contacts.
+ </tp:docstring>
+
+ <tp:enum name="Content_Removal_Reason" type="u">
+ <tp:added version="0.21.2"/>
+ <tp:docstring>
+ A representation of the reason for a content to be removed,
+ which may be used by simple clients, or used as a fallback
+ when the DBus_Reason is not understood. This enum will be
+ extended with future reasons as and when appropriate, so
+ clients SHOULD keep up to date with its values, but also be
+ happy to fallback to the Unknown value when an unknown value
+ is encountered.
+ </tp:docstring>
+
+ <tp:enumvalue suffix="Unknown" value="0">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ We just don't know. Unknown values of this enum SHOULD also be
+ treated like this.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="User_Requested" value="1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The local user requests that this content is removed
+ from the call.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Error" value="2">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>There is an error with the content which means that it
+ has to be removed from the call.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Unsupported" value="3">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Some aspect of the content is unsupported so has to be
+ removed from the call.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <method name="Remove" tp:name-for-bindings="Remove">
+ <tp:changed version="0.21.2">previously there were no
+ arguments</tp:changed>
+ <tp:docstring>
+ Remove the content from the call.
+ </tp:docstring>
+
+ <arg direction="in" name="Reason" type="u"
+ tp:type="Content_Removal_Reason">
+ <tp:docstring>
+ A generic hangup reason.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Detailed_Removal_Reason" type="s"
+ tp:type="DBus_Error_Name">
+ <tp:docstring>
+ A more specific reason for the content removal, if one is
+ available, or an empty string.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Message" type="s">
+ <tp:docstring>
+ A human-readable message for the reason of removing the
+ content, such as "Fatal streaming failure" or "no codec
+ intersection". This property can be left empty if no reason
+ is to be given.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError" />
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ Raised when a Call doesn't support removing contents
+ (e.g. a Google Talk video call).
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="Removed" tp:name-for-bindings="Removed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when the content is removed from the call. This
+ is the same as the <tp:dbus-ref
+ namespace="ofdT.Channel.Type">Call.DRAFT.ContentRemoved</tp:dbus-ref>
+ signal.</p>
+ </tp:docstring>
+ </signal>
+
+ <property name="Interfaces" tp:name-for-bindings="Interfaces"
+ type="as" tp:type="DBus_Interface[]" access="read" tp:immutable="yes">
+ <tp:added version="0.19.11"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Extra interfaces provided by this content, such as <tp:dbus-ref
+ namespace="ofdT.Call">Content.Interface.Media.DRAFT</tp:dbus-ref> or
+ <tp:dbus-ref namespace="ofdT.Call">Content.Interface.Mute.DRAFT</tp:dbus-ref>.
+ This SHOULD NOT include the Content interface itself, and cannot
+ change once the content has been created.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="Name" tp:name-for-bindings="Name" type="s" access="read"
+ tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The name of the content.</p>
+
+ <tp:rationale>
+ The content name property should be meaningful, so should be
+ given a name which is significant to the user. The name
+ could be the "audio" or "video" string localized, or perhaps
+ include some string identifying the source, such as a webcam
+ identifier.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="Type" tp:name-for-bindings="Type"
+ type="u" tp:type="Media_Stream_Type" access="read" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The media type of this content.</p>
+ </tp:docstring>
+ </property>
+
+ <tp:enum name="Call_Content_Disposition" type="u">
+ <tp:docstring>
+ The disposition of this content, which defines whether to
+ automatically start sending data on the streams when
+ <tp:dbus-ref
+ namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref> is
+ called on the channel.
+ </tp:docstring>
+
+ <tp:enumvalue suffix="None" value="0">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The content has no specific disposition
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Initial" value="1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The content was initially part of the call. When
+ <tp:dbus-ref
+ namespace="ofdT.Channel.Type.Call.DRAFT">Accept</tp:dbus-ref>
+ is called on the channel, all streams of this content with
+ <tp:dbus-ref
+ namespace="ofdT.Call.Stream.DRAFT">LocalSendingState</tp:dbus-ref>
+ set to <tp:type>Sending_State</tp:type>_Pending_Send will be
+ moved to <tp:type>Sending_State</tp:type>_Sending as if
+ <tp:dbus-ref
+ namespace="ofdT.Call.Stream.DRAFT">SetSending</tp:dbus-ref>
+ (True) had been called.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <property name="Disposition" tp:name-for-bindings="Disposition"
+ type="u" tp:type="Call_Content_Disposition" access="read"
+ tp:immutable="yes">
+ <tp:docstring>
+ The disposition of this content.
+ </tp:docstring>
+ </property>
+
+ <signal name="StreamsAdded" tp:name-for-bindings="Streams_Added">
+ <tp:changed version="0.21.2">plural version, renamed from
+ StreamAdded</tp:changed>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when streams are added to a call.</p>
+ </tp:docstring>
+ <arg name="Streams" type="ao">
+ <tp:docstring>
+ The <tp:dbus-ref
+ namespace="ofdT.Call">Stream.DRAFT</tp:dbus-ref>s which were
+ added.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="StreamsRemoved" tp:name-for-bindings="Streams_Removed">
+ <tp:changed version="0.21.2">plural version, renamed from
+ StreamRemoved</tp:changed>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when streams are removed from a call</p>
+ </tp:docstring>
+ <arg name="Streams" type="ao">
+ <tp:docstring>
+ The <tp:dbus-ref
+ namespace="ofdT.Call">Stream.DRAFT</tp:dbus-ref>s which were
+ removed.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="Streams" tp:name-for-bindings="Streams"
+ type="ao" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The list of <tp:dbus-ref namespace="ofdT.Call"
+ >Stream.DRAFT</tp:dbus-ref> objects that exist in this
+ content.</p>
+
+ <tp:rationale>
+ In a conference call multiple parties can share one media
+ content (say, audio), but the streaming of that media can
+ either be shared or separate. For example, in a multicast
+ conference all contacts would share one stream, while in a
+ Muji conference there would be a stream for each
+ participant.
+ </tp:rationale>
+
+ <p>Change notification is through the
+ <tp:member-ref>StreamsAdded</tp:member-ref> and
+ <tp:member-ref>StreamsRemoved</tp:member-ref> signals.</p>
+ </tp:docstring>
+ </property>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/farstream/extensions/Call_Content_Codec_Offer.xml b/farstream/extensions/Call_Content_Codec_Offer.xml
new file mode 100644
index 000000000..f88143f69
--- /dev/null
+++ b/farstream/extensions/Call_Content_Codec_Offer.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" ?>
+<node name="/Call_Content_Codec_Offer"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>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.</p>
+
+ <p>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.</p>
+
+ <p>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.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Content.CodecOffer.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ This object represents an offer of a Codec payload mapping.
+ </tp:docstring>
+
+ <method name="Accept" tp:name-for-bindings="Accept">
+ <arg name="Codecs" direction="in"
+ type="a(usuua{ss})" tp:type="Codec[]">
+ <tp:docstring>
+ The local codec mapping to send to the remote contacts and
+ to use in the <tp:dbus-ref
+ namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref>.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Accept the updated Codec mapping and update the local mapping.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The codecs given as the argument are invalid in some way.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="Reject" tp:name-for-bindings="Reject">
+ <tp:docstring>
+ Reject the proposed update to the codecs
+ FIXME add error codes and strings here
+ </tp:docstring>
+ </method>
+
+ <property name="Interfaces" tp:name-for-bindings="Interfaces"
+ type="as" tp:type="DBus_Interface[]" access="read" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Extra interfaces provided by this codec offer. This SHOULD
+ NOT include the CodecOffer interface itself, and cannot change
+ once the content has been created.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="RemoteContactCodecs"
+ tp:name-for-bindings="Remote_Contact_Codecs"
+ type="a(usuua{ss})" tp:type="Codec[]" access="read"
+ tp:immutable="yes">
+ <tp:docstring>
+ A list of codecs the remote contact supports.
+ </tp:docstring>
+ </property>
+
+ <property name="RemoteContact" tp:name-for-bindings="Remote_Contact"
+ type="u" tp:type="Contact_Handle" access="read" tp:immutable="yes">
+ <tp:docstring>
+ The contact handle that this codec offer applies to.
+ </tp:docstring>
+ </property>
+
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/farstream/extensions/Call_Content_Interface_Media.xml b/farstream/extensions/Call_Content_Interface_Media.xml
new file mode 100644
index 000000000..038ce8c7a
--- /dev/null
+++ b/farstream/extensions/Call_Content_Interface_Media.xml
@@ -0,0 +1,367 @@
+<?xml version="1.0" ?>
+<node name="/Call_Content_Interface_Media"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>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.</p>
+
+ <p>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.</p>
+
+ <p>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.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Content.Interface.Media.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Call.Content.DRAFT"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Interface to use by a software implementation of media
+ streaming. The reason behind splitting the members of this
+ interface out from the main <tp:dbus-ref
+ namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref> interface is
+ that the software is not necessarily what controls the
+ media. An example of this is in GSM phones, where the CM just
+ tells the phone to dial a number and it does the audio routing
+ in a device specific hardware way and the CM does not need
+ to concern itself with codecs.</p>
+
+ <h4>Codec negotiation</h4>
+
+ <p>When a new <tp:dbus-ref
+ namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref> channel
+ appears, whether it was requested or not, a <tp:dbus-ref
+ namespace="ofdT.Call.Content">CodecOffer.DRAFT</tp:dbus-ref>
+ will either be waiting in the
+ <tp:member-ref>CodecOffer</tp:member-ref> property, or will
+ appear at some point via the
+ <tp:member-ref>NewCodecOffer</tp:member-ref> signal.</p>
+
+ <p>The <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT">RemoteContactCodecs</tp:dbus-ref>
+ property on the codec offer lists the codecs which are
+ supported by the remote contact, and so will determine the
+ codecs that should be proposed by the local user's streaming
+ implementation. An empty list means all codecs can be proposed.</p>
+
+ <p>For incoming calls on protocols where codecs are proposed when
+ starting the call (for example, <a
+ href="http://xmpp.org/extensions/xep-0166.html">Jingle</a>),
+ the <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT">RemoteContactCodecs</tp:dbus-ref>
+ will contain information on the codecs that have already been
+ proposed by the remote contact, otherwise the codec map will
+ be the empty list.</p>
+
+ <p>The streaming implementation should look at the remote codec
+ map and the codecs known by the local user and call
+ <tp:dbus-ref
+ namespace="ofdT.Call.Content">CodecOffer.DRAFT.Accept</tp:dbus-ref>
+ on the intersection of these two codec lists.</p>
+
+ <p>This means that in practice, outgoing calls will have a codec
+ offer pop up with no information in the <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT">RemoteContactCodecs</tp:dbus-ref>,
+ so the local user will call <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT">Accept</tp:dbus-ref>
+ with the list of all codecs supported. If this codec offer is
+ accepted, then <tp:member-ref>CodecsChanged</tp:member-ref>
+ will fire with the details of the codecs passed into
+ <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT">Accept</tp:dbus-ref>. If
+ the call is incoming, then the <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT">RemoteContactCodecs</tp:dbus-ref>
+ will contain details of the remote contact's codecs and the
+ local user will call <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT">Accept</tp:dbus-ref>
+ with the codecs that both sides understand. After the codec
+ set is accepted, <tp:member-ref>CodecsChanged</tp:member-ref>
+ will fire to signal this change.</p>
+
+ <h4>Protocols without codec negotiation</h4>
+
+ <p>For protocols where the codecs are not negotiable, instead of
+ popping up the initial content's <tp:dbus-ref
+ namespace="ofdT.Call.Content">CodecOffer.DRAFT</tp:dbus-ref>
+ object with an empty <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT">RemoteContactCodecs</tp:dbus-ref>,
+ the CM should set the supported codec values to known codec
+ values in the said object's codec map.</p>
+
+ <h4>Changing codecs mid-call</h4>
+
+ <p>To update the codec list used mid-call, the
+ <tp:member-ref>UpdateCodecs</tp:member-ref> method should be
+ called with details of the new codec list. If this is
+ accepted, then <tp:member-ref>CodecsChanged</tp:member-ref>
+ will be emitted with the new codec set.</p>
+
+ <p>If the other side decides to update his or her codec list
+ during a call, a new <tp:dbus-ref
+ namespace="ofdT.Call.Content">CodecOffer.DRAFT</tp:dbus-ref>
+ object will appear through
+ <tp:member-ref>NewCodecOffer</tp:member-ref> which should be
+ acted on as documented above.</p>
+
+ </tp:docstring>
+
+ <tp:struct name="Codec" array-name="Codec_List">
+ <tp:docstring>
+ A description of a codec.
+ </tp:docstring>
+ <tp:member name="Identifier" type="u">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Numeric identifier for the codec. This will be used as the PT in the
+ SDP or content description.
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Name" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The name of the codec.
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Clockrate" type="u">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The clockrate of the codec.
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Channels" type="u">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Number of channels of the codec if applicable, otherwise 0.
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Parameters" type="a{ss}" tp:type="String_String_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Extra parameters for this codec.
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:mapping name="Contact_Codec_Map">
+ <tp:docstring>
+ A map from contact to the list of codecs he or she supports.
+ </tp:docstring>
+ <tp:member name="Handle" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ A contact handle.
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Codecs" type="a(usuua{ss})" tp:type="Codec[]">
+ <tp:docstring>
+ The codecs that the contact supports.
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <tp:struct name="Codec_Offering">
+ <tp:docstring>
+ A codec offer and its corresponding remote contact codec map.
+ </tp:docstring>
+ <tp:member name="Codec_Offer" type="o">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The object path to the <tp:dbus-ref namespace="ofdT.Call.Content"
+ >CodecOffer.DRAFT</tp:dbus-ref>
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Remote_Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The contact handle that this codec offer applies to.
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Remote_Contact_Codecs" type="a(usuua{ss})"
+ tp:type="Codec[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The <tp:dbus-ref namespace="ofdT.Call.Content"
+ >CodecOffer.DRAFT.RemoteContactCodecs</tp:dbus-ref> property
+ of the codec offer.
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <signal name="CodecsChanged" tp:name-for-bindings="Codecs_Changed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when the codecs in use change.</p>
+
+ <p>As well as acting as change notification for the
+ <tp:member-ref>ContactCodecMap</tp:member-ref>, emission of this
+ signal implies that the <tp:member-ref>CodecOffer</tp:member-ref>
+ property has changed to <code>('/', {})</code>.</p>
+ </tp:docstring>
+ <arg name="Updated_Codecs" type="a{ua(usuua{ss})}"
+ tp:type="Contact_Codec_Map">
+ <tp:docstring>
+ A map from contact to his or her codecs. Each pair in this
+ map is added to the
+ <tp:member-ref>ContactCodecMap</tp:member-ref> property,
+ replacing any previous pair with that key.
+ </tp:docstring>
+ </arg>
+ <arg name="Removed_Contacts" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ A list of keys which were removed from the
+ <tp:member-ref>ContactCodecMap</tp:member-ref>, probably because
+ those contacts left the call.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <method name="UpdateCodecs" tp:name-for-bindings="Update_Codecs">
+ <tp:docstring>
+ Update the local codec mapping. This method should only be
+ used during an existing call to update the codec mapping.
+ </tp:docstring>
+ <arg name="Codecs" direction="in"
+ type="a(usuua{ss})" tp:type="Codec[]">
+ <tp:docstring>
+ The codecs now supported by the local user.
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ Raised when a <tp:dbus-ref
+ namespace="ofdT.Call.Content">CodecOffer.DRAFT</tp:dbus-ref>
+ object exists and is referred to in the
+ <tp:member-ref>CodecOffer</tp:member-ref> property which
+ should be used instead of calling this method, or before
+ the content's initial <tp:dbus-ref
+ namespace="ofdT.Call.Content">CodecOffer.DRAFT</tp:dbus-ref>
+ object has appeared.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <property name="ContactCodecMap" tp:name-for-bindings="Contact_Codec_Map"
+ type="a{ua(usuua{ss})}" tp:type="Contact_Codec_Map" access="read">
+ <tp:docstring>
+ <p>A map from contact handles (including the local user's own handle)
+ to the codecs supported by that contact.</p>
+
+ <p>Change notification is via the
+ <tp:member-ref>CodecsChanged</tp:member-ref> signal.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="NewCodecOffer" tp:name-for-bindings="New_Codec_Offer">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when a new <tp:dbus-ref namespace="ofdT.Call.Content"
+ >CodecOffer.DRAFT</tp:dbus-ref> appears. The streaming
+ implementation MUST respond by calling the <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT"
+ >Accept</tp:dbus-ref> or <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT"
+ >Reject</tp:dbus-ref> method on the codec offer object.</p>
+
+ <p>Emission of this signal indicates that the
+ <tp:member-ref>CodecOffer</tp:member-ref> property has changed to
+ <code>(Contact, Offer, Codecs)</code>.</p>
+ </tp:docstring>
+ <arg name="Contact" type="u">
+ <tp:docstring>
+ The contact the codec offer belongs to.
+ </tp:docstring>
+ </arg>
+ <arg name="Offer" type="o">
+ <tp:docstring>
+ The object path of the new codec offer. This replaces any previous
+ codec offer.
+ </tp:docstring>
+ </arg>
+ <arg name="Codecs" type="a(usuua{ss})" tp:type="Codec[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The <tp:dbus-ref namespace="ofdT.Call.Content"
+ >CodecOffer.DRAFT.RemoteContactCodecs</tp:dbus-ref> property
+ of the codec offer.</p>
+
+ <tp:rationale>
+ Having the <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT">RemoteContactCodecs</tp:dbus-ref>
+ property here saves a D-Bus round-trip - it shouldn't be
+ necessary to get the property from the CodecOffer object, in
+ practice.
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="CodecOffer" tp:name-for-bindings="Codec_Offer"
+ type="(oua(usuua{ss}))" tp:type="Codec_Offering" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The object path to the current
+ <tp:dbus-ref namespace="ofdT.Call.Content"
+ >CodecOffer.DRAFT</tp:dbus-ref> object, its
+ <tp:dbus-ref namespace="ofdT.Call.Content"
+ >CodecOffer.DRAFT.RemoteContact</tp:dbus-ref> and
+ <tp:dbus-ref namespace="ofdT.Call.Content"
+ >CodecOffer.DRAFT.RemoteContactCodecs</tp:dbus-ref> properties.
+ If the object path is "/" then there isn't an outstanding
+ codec offer, and the mapping MUST be empty.</p>
+
+ <tp:rationale>
+ Having the <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT"
+ >RemoteContact</tp:dbus-ref> and
+ <tp:dbus-ref namespace="ofdT.Call.Content.CodecOffer.DRAFT"
+ >RemoteContactCodecs</tp:dbus-ref>
+ properties here saves a D-Bus round-trip - it shouldn't be
+ necessary to get these properties from the CodecOffer object, in
+ practice.
+ </tp:rationale>
+
+ <p>Change notification is via the
+ <tp:member-ref>NewCodecOffer</tp:member-ref> (which replaces the
+ value of this property with a new codec offer), and
+ <tp:member-ref>CodecsChanged</tp:member-ref> (which implies that
+ there is no longer any active codec offer) signals.</p>
+ </tp:docstring>
+ </property>
+
+ <tp:enum name="Call_Content_Packetization_Type" type="u">
+ <tp:added version="0.21.2"/>
+ <tp:docstring>
+ A packetization method that can be used for a content.
+ </tp:docstring>
+
+ <tp:enumvalue suffix="RTP" value="0">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Real-time Transport Protocol, as documented by RFC 3550.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Raw" value="1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Raw media.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="MSN_Webcam" value="2">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ MSN webcam. This is the video-only one-way type which was
+ used in earlier versions of WLM. Although no longer used,
+ modern WLM clients still support the MSN webcam protocol.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <property name="Packetization" tp:name-for-bindings="Packetization"
+ type="u" tp:type="Call_Content_Packetization_Type" access="read"
+ tp:immutable="yes">
+ <tp:added version="0.21.2"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The packetization method in use for this content.</p>
+ </tp:docstring>
+ </property>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/farstream/extensions/Call_Content_Interface_Mute.xml b/farstream/extensions/Call_Content_Interface_Mute.xml
new file mode 100644
index 000000000..f926e03cd
--- /dev/null
+++ b/farstream/extensions/Call_Content_Interface_Mute.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" ?>
+<node name="/Call_Content_Interface_Mute" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright © 2005-2010 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright © 2005-2010 Collabora Ltd </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+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.
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Content.Interface.Mute.DRAFT" tp:causes-havoc="experimental">
+ <tp:added version="0.19.6">(draft version, not API-stable)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Call.Content.DRAFT"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Interface for calls which may be muted. This only makes sense
+ for channels where audio or video is streaming between members.</p>
+
+ <p>Muting a call content indicates that the user does not wish to send
+ outgoing audio or video.</p>
+
+ <p>Although it's client's responsibility to actually mute the microphone
+ or turn off the camera, using this interface the client can also
+ inform the CM and other clients of that fact.</p>
+
+ <tp:rationale>
+ For some protocols, the fact that the content is muted needs
+ to be transmitted to the peer; for others, the notification
+ to the peer is only informational (eg. XMPP), and some
+ protocols may have no notion of muting at all.
+ </tp:rationale>
+ </tp:docstring>
+
+ <signal name="MuteStateChanged" tp:name-for-bindings="Mute_State_Changed">
+ <tp:docstring>
+ Emitted to indicate that the mute state has changed for this call content.
+ This may occur as a consequence of the client calling
+ <tp:member-ref>SetMuted</tp:member-ref>, or as an indication that another
+ client has (un)muted the content.
+ </tp:docstring>
+ <arg name="MuteState" type="b">
+ <tp:docstring>
+ True if the content is now muted.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="MuteState" type="b"
+ access="read" tp:name-for-bindings="Mute_State">
+ <tp:docstring>
+ True if the content is muted.
+ </tp:docstring>
+ </property>
+
+ <method name="SetMuted" tp:name-for-bindings="Set_Muted">
+ <tp:changed version="0.21.2">renamed from SetMuted to Mute</tp:changed>
+ <tp:changed version="0.21.3">renamed back from Mute to SetMuted</tp:changed>
+ <arg direction="in" name="Muted" type="b">
+ <tp:docstring>
+ True if the client has muted the content.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ <p>Inform the CM that the call content has been muted or unmuted by
+ the client.</p>
+
+ <p>It is the client's responsibility to actually mute or unmute the
+ microphone or camera used for the content. However, the client
+ MUST call this whenever it mutes or unmutes the content.</p>
+ </tp:docstring>
+ </method>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/farstream/extensions/Call_Content_Interface_Video_Control.xml b/farstream/extensions/Call_Content_Interface_Video_Control.xml
new file mode 100644
index 000000000..b066de42b
--- /dev/null
+++ b/farstream/extensions/Call_Content_Interface_Video_Control.xml
@@ -0,0 +1,137 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node name="/Call_Content_Interface_Video_Control"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>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.</p>
+
+ <p>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.</p>
+
+ <p>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.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Content.Interface.VideoControl.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.21.10">(draft 1)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Call.Content.Interface.Media.DRAFT"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface that allows the connection manager to control the video
+ stream.</p>
+ <p>This interface is generally not needed. In cases where the connection
+ manager handles the network communication and the media is transferred
+ from the client to the connection manager via shared memory, it can
+ sometimes be beneficial for the connection manager to be able to
+ control certain aspects of the video stream.</p>
+ </tp:docstring>
+
+ <signal name="KeyFrameRequested" tp:name-for-bindings="Key_Frame_Requested">
+ <tp:docstring>
+ Request that the video encoder produce a new key frame as soon as
+ possible.
+ </tp:docstring>
+ </signal>
+
+ <tp:struct name="Video_Resolution"
+ array-name="Video_Resolution_Struct">
+ <tp:member type="u" name="Width">
+ <tp:docstring>
+ With of the video stream.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="u" name="Height">
+ <tp:docstring>
+ Height of the video stream.
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <property name="VideoResolution" type="(uu)" tp:type="Video_Resolution"
+ access="read" tp:name-for-bindings="Video_Resolution">
+ <tp:docstring>
+ The resolution at which the streaming engine should be sending.
+
+ <p>Change notification is via the
+ <tp:member-ref>VideoResolutionChanged</tp:member-ref> signal.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="VideoResolutionChanged"
+ tp:name-for-bindings="Video_Resolution_Changed">
+ <tp:docstring>
+ The desired video resolution has changed.
+ </tp:docstring>
+ <arg type="(uu)" tp:type="Video_Resolution" name="NewResolution" />
+ </signal>
+
+ <property name="Bitrate" type="u" access="read"
+ tp:name-for-bindings="Bitrate">
+ <tp:docstring>
+ The bitrate the streaming engine should be sending at.
+
+ <p>Change notification is via the
+ <tp:member-ref>BitrateChanged</tp:member-ref> signal.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="BitrateChanged"
+ tp:name-for-bindings="Bitrate_Changed">
+ <tp:docstring>
+ The desired bitrate has changed
+ </tp:docstring>
+ <arg type="u" name="NewBitrate" />
+ </signal>
+
+ <property name="Framerate" type="u" access="read"
+ tp:name-for-bindings="Framerate">
+ <tp:docstring>
+ The framerate the streaming engine should be sending at.
+
+ <p>Change notification is via the
+ <tp:member-ref>FramerateChanged</tp:member-ref> signal.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="FramerateChanged"
+ tp:name-for-bindings="Framerate_Changed">
+ <tp:docstring>
+ The desired framerate has changed
+ </tp:docstring>
+ <arg type="u" name="NewFramerate" />
+ </signal>
+
+ <property name="MTU" type="u" access="read"
+ tp:name-for-bindings="MTU">
+ <tp:docstring>
+ The Maximum Transmission Unit
+
+ <p>Change notification is via the
+ <tp:member-ref>MTUChanged</tp:member-ref> signal.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="MTUChanged" tp:name-for-bindings="MTU_Changed">
+ <tp:docstring>
+ The Maximum Transmission Unit has changed
+ </tp:docstring>
+ <arg type="u" name="NewMTU" />
+ </signal>
+
+ <property name="ManualKeyFrames" type="b" access="read"
+ tp:name-for-bindings="Manual_Key_Frames">
+ <tp:docstring>
+ Only send key frames when manually requested
+ </tp:docstring>
+ </property>
+ </interface>
+</node>
diff --git a/farstream/extensions/Call_Stream.xml b/farstream/extensions/Call_Stream.xml
new file mode 100644
index 000000000..1d7b28147
--- /dev/null
+++ b/farstream/extensions/Call_Stream.xml
@@ -0,0 +1,261 @@
+<?xml version="1.0" ?>
+<node name="/Call_Stream"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>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.</p>
+
+ <p>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.</p>
+
+ <p>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.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Stream.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ One stream inside a <tp:dbus-ref
+ namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref>.
+ </tp:docstring>
+
+ <method name="SetSending" tp:name-for-bindings="Set_Sending">
+ <tp:docstring>
+ Set the stream to start or stop sending media from the local
+ user to other contacts.
+ </tp:docstring>
+
+ <arg name="Send" type="b" direction="in">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If True, the
+ <tp:member-ref>LocalSendingState</tp:member-ref> should
+ change to <tp:type>Sending_State</tp:type>_Sending, if it isn't
+ already.</p>
+
+ <p>If False, the
+ <tp:member-ref>LocalSendingState</tp:member-ref> should
+ change to <tp:type>Sending_State</tp:type>_None, if it isn't
+ already.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented" />
+ </tp:possible-errors>
+ </method>
+
+ <method name="RequestReceiving" tp:name-for-bindings="Request_Receiving">
+ <tp:docstring>
+ <p>Request that a remote contact stops or starts sending on
+ this stream.</p>
+
+ <p>The <tp:member-ref>CanRequestReceiving</tp:member-ref>
+ property defines whether the protocol allows the local user to
+ request the other side start sending on this stream.</p>
+ </tp:docstring>
+
+ <arg name="Contact" type="u" tp:type="Contact_Handle" direction="in">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Contact from which sending is requested</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Receive" type="b" direction="in">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, request that the given contact starts to send media.
+ If false, request that the given contact stops sending media.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The request contact is valid but is not involved in this
+ stream.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The protocol does not allow the local user to request the
+ other side starts sending on this stream.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="RemoteMembersChanged"
+ tp:name-for-bindings="Remote_Members_Changed">
+ <tp:changed version="0.21.2">renamed from SendersChanged to MembersChanged</tp:changed>
+ <tp:changed version="0.21.3">renamed from MembersChanged to RemoteMembersChanged</tp:changed>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Emitted when <tp:member-ref>RemoteMembers</tp:member-ref> changes.
+ </tp:docstring>
+
+ <arg name="Updates" type="a{uu}" tp:type="Contact_Sending_State_Map">
+ <tp:docstring>
+ A mapping from channel-specific handles to their updated sending
+ state, whose keys include at least the members who were added,
+ and the members whose states changed.
+ </tp:docstring>
+ </arg>
+ <arg name="Removed" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ The channel-specific handles that were removed from the keys
+ of the <tp:member-ref>RemoteMembers</tp:member-ref>
+ property, as a result of the contact leaving this stream
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="LocalSendingStateChanged"
+ tp:name-for-bindings="Local_Sending_State_Changed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Emitted when <tp:member-ref>LocalSendingState</tp:member-ref> changes.
+ </tp:docstring>
+
+ <arg name="State" type="u" tp:type="Sending_State">
+ <tp:docstring>
+ The new value of
+ <tp:member-ref>LocalSendingState</tp:member-ref>.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <tp:enum name="Sending_State" type="u">
+ <tp:docstring>
+ Enum indicating whether a contact is sending media.
+ </tp:docstring>
+
+ <tp:enumvalue suffix="None" value="0">
+ <tp:docstring>
+ The contact is not sending media and has not been asked to
+ do so.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Pending_Send" value="1">
+ <tp:docstring>
+ The contact has been asked to start sending media.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Sending" value="2">
+ <tp:docstring>
+ The contact is sending media.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Pending_Stop_Sending" value="3">
+ <tp:docstring>
+ The contact has been asked to stop sending media.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:mapping name="Contact_Sending_State_Map">
+ <tp:docstring>
+ A map from a contact to his or her sending state.
+ </tp:docstring>
+ <tp:member name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The contact handle.
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Sending" type="u" tp:type="Sending_State">
+ <tp:docstring>
+ The sending state of the contact.
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <property name="Interfaces" tp:name-for-bindings="Interfaces"
+ type="as" tp:type="DBus_Interface[]" access="read" tp:immutable="yes">
+ <tp:added version="0.19.11"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Extra interfaces provided by this stream, such as <tp:dbus-ref
+ namespace="ofdT.Call">Stream.Interface.Media.DRAFT</tp:dbus-ref>.
+ This SHOULD NOT include the Stream interface itself, and cannot
+ change once the stream has been created.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="RemoteMembers" tp:name-for-bindings="Remote_Members"
+ type="a{uu}" access="read" tp:type="Contact_Sending_State_Map">
+ <tp:changed version="0.21.2">renamed from Senders</tp:changed>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A map from remote contacts to their sending state. The
+ local user's sending state is shown in
+ <tp:member-ref>LocalSendingState</tp:member-ref>.</p>
+
+ <p><tp:type>Sending_State</tp:type>_Pending_Send indicates
+ that another contact has asked the local user to send
+ media.</p>
+
+ <p>Other contacts' handles in this map indicate whether they are
+ sending media to the contacts in this stream.
+ Sending_State_Pending_Send indicates contacts who are not sending but
+ have been asked to do so.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="LocalSendingState" tp:name-for-bindings="Local_Sending_State"
+ type="u" access="read" tp:type="Sending_State">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The local user's sending state. Media sent on this stream
+ should be assumed to be received, directly or indirectly, by
+ every other contact in the
+ <tp:member-ref>RemoteMembers</tp:member-ref> mapping. Change
+ notification is given via the
+ <tp:member-ref>LocalSendingStateChanged</tp:member-ref>
+ signal.</p>
+
+ <tp:rationale>
+ Implementations of the first Call draft had the self handle
+ in the <tp:member-ref>RemoteMembers</tp:member-ref> (then
+ called Members) map and this showed that it's annoying
+ having to keep track of the self handle so that it can be
+ special-cased.
+ </tp:rationale>
+
+ <p>A value of <tp:type>Sending_State</tp:type>_Pending_Send for
+ this property indicates that the other side requested the
+ local user start sending media, which can be done by calling
+ <tp:member-ref>SetSending</tp:member-ref>. When a call is
+ accepted, all initial contents with streams that have a
+ local sending state of
+ <tp:type>Sending_State</tp:type>_Pending_Send are
+ automatically set to sending. For example, on an incoming
+ call it means you need to <tp:dbus-ref
+ namespace="ofdT.Channel.Type.Call.DRAFT">Accept</tp:dbus-ref>
+ to start the actual call, on an outgoing call it might mean
+ you need to call <tp:dbus-ref
+ namespace="ofdT.Channel.Type.Call.DRAFT">Accept</tp:dbus-ref>
+ before actually starting the call.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="CanRequestReceiving" tp:name-for-bindings="Can_Request_Receiving"
+ type="b" access="read" tp:immutable="yes">
+ <tp:added version="0.21.2"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, the user can request that a remote contact starts
+ sending on this stream.</p>
+
+ <tp:rationale>Not all protocols allow the user to ask the
+ other side to start sending media.</tp:rationale>
+ </tp:docstring>
+ </property>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/farstream/extensions/Call_Stream_Endpoint.xml b/farstream/extensions/Call_Stream_Endpoint.xml
new file mode 100644
index 000000000..4818168dc
--- /dev/null
+++ b/farstream/extensions/Call_Stream_Endpoint.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" ?>
+<node name="/Call_Stream_Endpoint"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>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.</p>
+
+ <p>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.</p>
+
+ <p>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.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Stream.Endpoint.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This object represents an endpoint for a stream. In a one-to-one
+ call, there will be one (bidirectional) stream per content and
+ one endpoint per stream (as there is only one remote
+ contact). In a multi-user call there is a stream for each remote
+ contact and each stream has one endpoint as it refers to the one
+ physical machine on the other end of the stream.</p>
+
+ <p>The multiple endpoint use case appears when SIP call forking
+ is used. Unlike jingle call forking (which is just making
+ multiple jingle calls to different resources appear as one
+ call), SIP call forking is actually done at the server so you
+ have one stream to the remote contact and then and endpoint for
+ each SIP client to be called.</p>
+ </tp:docstring>
+
+ <property name="RemoteCredentials"
+ tp:name-for-bindings="Remote_Credentials"
+ type="(ss)" tp:type="Stream_Credentials" access="read">
+ <tp:docstring>
+ The ICE credentials used for all candidates. If each candidate
+ has different credentials, then this property SHOULD be ("",
+ ""). Per-candidate credentials are set in the
+ <tp:type>Candidate</tp:type>'s
+ <tp:type>Candidate_Info</tp:type> a{sv}.
+ </tp:docstring>
+ </property>
+
+ <signal name="RemoteCredentialsSet"
+ tp:name-for-bindings="Remote_Credentials_Set">
+ <arg name="Username" type="s">
+ <tp:docstring>
+ The username set.
+ </tp:docstring>
+ </arg>
+ <arg name="Password" type="s">
+ <tp:docstring>
+ The password set.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Emitted when the remote ICE credentials for the endpoint are
+ set. If each candidate has different credentials, then this
+ signal will never be fired.
+ </tp:docstring>
+ </signal>
+
+ <property name="RemoteCandidates" tp:name-for-bindings="Remote_Candidates"
+ type="a(usqa{sv})" tp:type="Candidate[]" access="read">
+ <tp:docstring>
+ A list of candidates for this endpoint.
+ </tp:docstring>
+ </property>
+
+ <signal name="RemoteCandidatesAdded"
+ tp:name-for-bindings="Remote_Candidates_Added">
+ <tp:docstring>
+ Emitted when remote candidates are added to the
+ <tp:member-ref>RemoteCandidates</tp:member-ref> property.
+ </tp:docstring>
+ <arg name="Candidates"
+ type="a(usqa{sv})" tp:type="Candidate[]">
+ <tp:docstring>
+ The candidates that were added.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="CandidateSelected"
+ tp:name-for-bindings="Candidate_Selected">
+ <tp:docstring>
+ Emitted when a candidate is selected for use in the stream.
+ </tp:docstring>
+ <arg name="Candidate"
+ type="(usqa{sv})" tp:type="Candidate">
+ <tp:docstring>
+ The candidate that has been selected.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="SelectedCandidate"
+ tp:name-for-bindings="Selected_Candidate"
+ type="(usqa{sv})" tp:type="Candidate" access="read">
+ <tp:docstring>
+ The candidate that has been selected for use to stream packets
+ to the remote contact. Change notification is given via the
+ the <tp:member-ref>CandidateSelected</tp:member-ref> signal.
+ </tp:docstring>
+ </property>
+
+ <method name="SetSelectedCandidate"
+ tp:name-for-bindings="Set_Selected_Candidate">
+ <tp:docstring>
+ Set the value of
+ <tp:member-ref>CandidateSelected</tp:member-ref>.
+ </tp:docstring>
+ <arg name="Candidate"
+ type="(usqa{sv})" tp:type="Candidate" direction="in">
+ <tp:docstring>
+ The candidate that has been selected.
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ </tp:possible-errors>
+ </method>
+
+ <property name="StreamState" tp:name-for-bindings="Stream_State"
+ type="u" tp:type="Media_Stream_State"
+ access="read">
+ <tp:docstring>
+ The stream state of the endpoint.
+ </tp:docstring>
+ </property>
+
+ <signal name="StreamStateChanged"
+ tp:name-for-bindings="Stream_State_Changed">
+ <tp:docstring>
+ Emitted when the <tp:member-ref>StreamState</tp:member-ref>
+ property changes.
+ </tp:docstring>
+ <arg name="state" type="u" tp:type="Media_Stream_State">
+ <tp:docstring>
+ The new <tp:member-ref>StreamState</tp:member-ref> value.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <method name="SetStreamState"
+ tp:name-for-bindings="Set_Stream_State">
+ <tp:docstring>
+ Change the <tp:member-ref>StreamState</tp:member-ref> of the
+ endpoint.
+ </tp:docstring>
+ <arg direction="in" name="State" type="u" tp:type="Media_Stream_State">
+ <tp:docstring>
+ The requested stream state.
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ </tp:possible-errors>
+ </method>
+
+ <property name="Transport" tp:name-for-bindings="Transport"
+ type="u" tp:type="Stream_Transport_Type" access="read">
+ <tp:docstring>
+ The transport type for the stream endpoint.
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/farstream/extensions/Call_Stream_Interface_Media.xml b/farstream/extensions/Call_Stream_Interface_Media.xml
new file mode 100644
index 000000000..9e62c8744
--- /dev/null
+++ b/farstream/extensions/Call_Stream_Interface_Media.xml
@@ -0,0 +1,447 @@
+<?xml version="1.0" ?>
+<node name="/Call_Stream_Interface_Media"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>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.</p>
+
+ <p>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.</p>
+
+ <p>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.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Stream.Interface.Media.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Call.Stream.DRAFT"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ [FIXME]
+
+ <h4>ICE restarts</h4>
+
+ <p>If the <tp:dbus-ref
+ namespace="ofdT.Call.Stream.Endpoint.DRAFT">RemoteCredentialsSet</tp:dbus-ref>
+ signal is fired during a call once it has been called before
+ whilst setting up the call for initial use, and the
+ credentials have changed, then there has been an ICE
+ restart. In such a case, the CM SHOULD also empty the remote
+ candidate list in the <tp:dbus-ref
+ namespace="ofdT.Call.Stream">Endpoint.DRAFT</tp:dbus-ref>.</p>
+
+ <p>If the CM does an ICE restart, then the
+ <tp:member-ref>PleaseRestartICE</tp:member-ref> signal is
+ emitted and the streaming implementation should then call
+ <tp:member-ref>SetCredentials</tp:member-ref> again.</p>
+
+ <p>For more information on ICE restarts see
+ <a href="http://tools.ietf.org/html/rfc5245#section-9.1.1.1">RFC 5245
+ section 9.1.1.1</a></p>
+ </tp:docstring>
+
+ <method name="SetCredentials" tp:name-for-bindings="Set_Credentials">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Used to set the username fragment and password for streams that have
+ global credentials.</p>
+ </tp:docstring>
+ <arg name="Username" type="s" direction="in">
+ <tp:docstring>
+ The username to use when authenticating on the stream.
+ </tp:docstring>
+ </arg>
+ <arg name="Password" type="s" direction="in">
+ <tp:docstring>
+ The password to use when authenticating on the stream.
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <tp:mapping name="Candidate_Info">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Extra information about the candidate. Allowed and mandatory keys
+ depend on the transport protocol used. The following keys are commenly
+ used:</p>
+
+ <dl>
+ <dt>Type (u)</dt>
+ <dd>type of candidate (host, srflx, prflx, relay)</dd>
+
+ <dt>Foundation (s)</dt>
+ <dd>the foundation of this candiate</dd>
+
+ <dt>Protocol (u) </dt>
+ <dd>Underlying protocol of the candidate (udp, tcp) </dd>
+
+ <dt>Priority (u) </dt>
+ <dd>Priority of the candidate </dd>
+
+ <dt>BaseIP (u) </dt>
+ <dd>Base IP of this candidate </dd>
+
+ <dt>Username (s) </dt>
+ <dd>Username of this candidate
+ (only if credentials are per candidate)</dd>
+
+ <dt>Password (s) </dt>
+ <dd>Password of this candidate
+ (only if credentials are per candidate)</dd>
+
+ <dt>RawUDPFallback (b) </dt>
+ <dd>Indicate whether this candidate may be used to provide a UDP
+ fallback</dd>
+ </dl>
+ </tp:docstring>
+ <tp:member name="Key" type="s">
+ <tp:docstring>One of the well-known keys documented here, or an
+ implementation-specific key.</tp:docstring>
+ </tp:member>
+ <tp:member name="Value" type="v">
+ <tp:docstring>The value corresponding to that key.</tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <tp:struct name="Candidate" array-name="Candidate_List">
+ <tp:docstring>A Stream Candidate.</tp:docstring>
+ <tp:member name="Component" type="u">
+ <tp:docstring>The component number.</tp:docstring>
+ </tp:member>
+ <tp:member name="IP" type="s">
+ <tp:docstring>The IP address to use.</tp:docstring>
+ </tp:member>
+ <tp:member name="Port" type="q">
+ <tp:docstring>The port number to use.</tp:docstring>
+ </tp:member>
+ <tp:member name="Info" type="a{sv}" tp:type="Candidate_Info">
+ <tp:docstring>Additional information about the candidate.</tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <method name="AddCandidates" tp:name-for-bindings="Add_Candidates">
+ <tp:docstring>
+ Add candidates to the
+ <tp:member-ref>LocalCandidates</tp:member-ref> property and
+ signal them to the remote contact(s).
+ </tp:docstring>
+ <arg name="Candidates" direction="in"
+ type="a(usqa{sv})" tp:type="Candidate[]">
+ <tp:docstring>
+ The candidates to be added.
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <method name="CandidatesPrepared"
+ tp:name-for-bindings="Candidates_Prepared">
+ <tp:docstring>
+ This indicates to the CM that the initial batch of candidates
+ has been added.
+ </tp:docstring>
+ </method>
+
+ <tp:enum type="u" name="Stream_Transport_Type">
+ <tp:changed version="0.21.2">WLM_8_5 was removed</tp:changed>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ A transport that can be used for streaming.
+ </tp:docstring>
+ <tp:enumvalue suffix="Unknown" value="0">
+ <tp:docstring>
+ The stream transport type is unknown or not applicable
+ (for streams that do not have a configurable transport).
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Raw_UDP" value="1">
+ <tp:docstring>
+ Raw UDP, with or without STUN. All streaming clients are assumed to
+ support this transport, so there is no handler capability token for
+ it in the <tp:dbus-ref namespace="ofdT.Channel.Type"
+ >Call.DRAFT</tp:dbus-ref> interface.
+ [This corresponds to "none" or "stun" in the old Media.StreamHandler
+ interface.]
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="ICE" value="2">
+ <tp:docstring>
+ Interactive Connectivity Establishment, as defined by RFC
+ 5245. Note that this value covers ICE-UDP only.
+ [This corresponds to "ice-udp" in the old
+ Media.StreamHandler interface.]
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="GTalk_P2P" value="3">
+ <tp:docstring>
+ Google Talk peer-to-peer connectivity establishment, as implemented
+ by libjingle 0.3.
+ [This corresponds to "gtalk-p2p" in the old Media.StreamHandler
+ interface.]
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="WLM_2009" value="4">
+ <tp:docstring>
+ The transport used by Windows Live Messenger 2009 or later, which
+ resembles ICE draft 19.
+ [This corresponds to "wlm-2009" in the old Media.StreamHandler
+ interface.]
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="SHM" value="5">
+ <tp:added version="0.21.2"/>
+ <tp:docstring>
+ Shared memory transport, as implemented by the GStreamer
+ shmsrc and shmsink plugins.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Multicast" value="6">
+ <tp:added version="0.21.5"/>
+ <tp:docstring>
+ Multicast transport.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <property name="Transport" tp:name-for-bindings="Transport"
+ type="u" tp:type="Stream_Transport_Type" access="read" tp:immutable="yes">
+ <tp:docstring>
+ The transport for this stream.
+ </tp:docstring>
+ </property>
+
+ <property name="LocalCandidates" tp:name-for-bindings="Local_Candidates"
+ type="a(usqa{sv})" tp:type="Candidate[]" access="read">
+ <tp:docstring>
+ [FIXME]. Change notification is via the
+ <tp:member-ref>LocalCandidatesAdded</tp:member-ref> signal.
+ </tp:docstring>
+ </property>
+
+ <signal name="LocalCandidatesAdded"
+ tp:name-for-bindings="Local_Candidates_Added">
+ <tp:docstring>
+ Emitted when local candidates are added to the
+ <tp:member-ref>LocalCandidates</tp:member-ref> property.
+ </tp:docstring>
+ <arg name="Candidates" type="a(usqa{sv})" tp:type="Candidate[]">
+ <tp:docstring>
+ Candidates that have been added.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <tp:struct name="Stream_Credentials">
+ <tp:docstring>A username and password pair.</tp:docstring>
+
+ <tp:member name="Username" type="s">
+ <tp:docstring>The username.</tp:docstring>
+ </tp:member>
+
+ <tp:member name="Password" type="s">
+ <tp:docstring>The password.</tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <property name="LocalCredentials" tp:name-for-bindings="Local_Credentials"
+ type="(ss)" tp:type="Stream_Credentials" access="read">
+ <tp:docstring>
+ [FIXME]. Change notification is via the
+ <tp:member-ref>LocalCredentialsChanged</tp:member-ref> signal.
+ </tp:docstring>
+ </property>
+
+ <signal name="LocalCredentialsChanged"
+ tp:name-for-bindings="Local_Credentials_Changed">
+ <tp:changed version="0.21.2">renamed from LocalCredentailsSet</tp:changed>
+ <tp:docstring>
+ Emitted when the value of
+ <tp:member-ref>LocalCredentials</tp:member-ref> changes.
+ </tp:docstring>
+ <arg name="Username" type="s" />
+ <arg name="Password" type="s" />
+ </signal>
+
+ <signal name="RelayInfoChanged"
+ tp:name-for-bindings="Relay_Info_Changed">
+ <tp:docstring>
+ Emitted when the value of
+ <tp:member-ref>RelayInfo</tp:member-ref> changes.
+ </tp:docstring>
+ <arg name="Relay_Info" type="aa{sv}" tp:type="String_Variant_Map[]" />
+ </signal>
+
+ <signal name="STUNServersChanged"
+ tp:name-for-bindings="STUN_Servers_Changed">
+ <tp:docstring>
+ Emitted when the value of
+ <tp:member-ref>STUNServers</tp:member-ref> changes.
+ </tp:docstring>
+ <arg name="Servers" type="a(sq)" tp:type="Socket_Address_IP[]" />
+ </signal>
+
+ <property name="STUNServers" tp:name-for-bindings="STUN_Servers"
+ type="a(sq)" tp:type="Socket_Address_IP[]" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The IP addresses of possible STUN servers to use for NAT
+ traversal, as dotted-quad IPv4 address literals or RFC2373
+ IPv6 address literals. Change notification is via the
+ <tp:member-ref>STUNServersChanged</tp:member-ref>
+ signal. The IP addresses MUST NOT be given as DNS hostnames.</p>
+
+ <tp:rationale>
+ High-quality connection managers already need an asynchronous
+ DNS resolver, so they might as well resolve this name to an IP
+ to make life easier for streaming implementations.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="RelayInfo" type="aa{sv}" access="read"
+ tp:type="String_Variant_Map[]" tp:name-for-bindings="Relay_Info">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of mappings describing TURN or Google relay servers
+ available for the client to use in its candidate gathering, as
+ determined from the protocol. Map keys are:</p>
+
+ <dl>
+ <dt><code>ip</code> - s</dt>
+ <dd>The IP address of the relay server as a dotted-quad IPv4
+ address literal or an RFC2373 IPv6 address literal. This MUST NOT
+ be a DNS hostname.
+
+ <tp:rationale>
+ High-quality connection managers already need an asynchronous
+ DNS resolver, so they might as well resolve this name to an IP
+ and make life easier for streaming implementations.
+ </tp:rationale>
+ </dd>
+
+ <dt><code>type</code> - s</dt>
+ <dd>
+ <p>Either <code>udp</code> for UDP (UDP MUST be assumed if this
+ key is omitted), <code>tcp</code> for TCP, or
+ <code>tls</code>.</p>
+
+ <p>The precise meaning of this key depends on the
+ <tp:member-ref>Transport</tp:member-ref> property: if
+ Transport is ICE, <code>tls</code> means
+ TLS over TCP as referenced by ICE draft 19, and if
+ Transport is GTalk_P2P, <code>tls</code> means
+ a fake SSL session over TCP as implemented by libjingle.</p>
+ </dd>
+
+ <dt><code>port</code> - q</dt>
+ <dd>The UDP or TCP port of the relay server as an ASCII unsigned
+ integer</dd>
+
+ <dt><code>username</code> - s</dt>
+ <dd>The username to use</dd>
+
+ <dt><code>password</code> - s</dt>
+ <dd>The password to use</dd>
+
+ <dt><code>component</code> - u</dt>
+ <dd>The component number to use this relay server for, as an
+ ASCII unsigned integer; if not included, this relay server
+ may be used for any or all components.
+
+ <tp:rationale>
+ In ICE draft 6, as used by Google Talk, credentials are only
+ valid once, so each component needs relaying separately.
+ </tp:rationale>
+ </dd>
+ </dl>
+
+ <tp:rationale>
+ <p>An equivalent of the gtalk-p2p-relay-token property on
+ MediaSignalling channels is not included here. The connection
+ manager should be responsible for making the necessary HTTP
+ requests to turn the token into a username and password.</p>
+ </tp:rationale>
+
+ <p>The type of relay server that this represents depends on
+ the value of the <tp:member-ref>Transport</tp:member-ref>
+ property. If Transport is ICE, this is a TURN server;
+ if Transport is GTalk_P2P, this is a Google relay server;
+ otherwise, the meaning of RelayInfo is undefined.</p>
+
+ <p>If relaying is not possible for this stream, the list is
+ empty.</p>
+
+ <p>Change notification is given via the
+ <tp:member-ref>RelayInfoChanged</tp:member-ref> signal.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="ServerInfoRetrieved"
+ tp:name-for-bindings="Server_Info_Retrieved">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Signals that the initial information about STUN and Relay servers
+ has been retrieved, i.e. the
+ <tp:member-ref>HasServerInfo</tp:member-ref> property is
+ now true.</p>
+ </tp:docstring>
+ </signal>
+
+ <property name="HasServerInfo" type="b"
+ tp:name-for-bindings="Has_Server_Info" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>True if all the initial information about STUN servers and Relay
+ servers has been retrieved. Change notification is via the
+ <tp:member-ref>ServerInfoRetrieved</tp:member-ref> signal.</p>
+
+ <tp:rationale>
+ Streaming implementations that can't cope with STUN and
+ relay servers being added later SHOULD wait for this
+ property to become true before proceeding.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <signal name="EndpointsChanged"
+ tp:name-for-bindings="Endpoints_Changed">
+ <tp:docstring>
+ Emitted when the <tp:member-ref>Endpoints</tp:member-ref> property
+ changes.
+ </tp:docstring>
+ <arg name="Endpoints_Added" type="ao">
+ <tp:docstring>
+ Endpoints that were added.
+ </tp:docstring>
+ </arg>
+ <arg name="Endpoints_Removed" type="ao">
+ <tp:docstring>
+ Endpoints that no longer exist.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="Endpoints" tp:name-for-bindings="Endpoints"
+ type="ao" access="read">
+ <tp:docstring>
+ <p>The list of <tp:dbus-ref namespace="ofdT.Call.Stream"
+ >Endpoint.DRAFT</tp:dbus-ref> objects that exist for this
+ stream.</p>
+
+ <p>Change notification is via the
+ <tp:member-ref>EndpointsChanged</tp:member-ref> signal.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="PleaseRestartICE"
+ tp:name-for-bindings="Please_Restart_ICE">
+ <tp:docstring>
+ Emitted when the CM does an ICE restart to notify the
+ streaming implementation that it should call
+ <tp:member-ref>SetCredentials</tp:member-ref> again.
+ </tp:docstring>
+ </signal>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/farstream/extensions/Channel_Type_Call.xml b/farstream/extensions/Channel_Type_Call.xml
new file mode 100644
index 000000000..045d41693
--- /dev/null
+++ b/farstream/extensions/Channel_Type_Call.xml
@@ -0,0 +1,1429 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Type_Call" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2010 Collabora Limited</tp:copyright>
+ <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright>
+ <tp:license>
+ 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.
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Type.Call.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A channel type for making audio and video calls. Call
+ channels supersede the old <tp:dbus-ref
+ namespace="ofdT.Channel.Type">StreamedMedia</tp:dbus-ref>
+ channel type. Call channels are much more flexible than its
+ predecessor and allow more than two participants.</p>
+
+ <p>Handlers are advised against executing all the media
+ signalling, codec and candidate negotiation themselves but
+ instead use a helper library such as <a
+ href="http://telepathy.freedesktop.org/doc/telepathy-farstream/">telepathy-farstream</a>
+ which when given a new Call channel will set up the
+ transports and codecs and create GStreamer pads which
+ can be added to the handler UI. This is useful as it means
+ the handler does not have to worry how exactly the
+ connection between the call participants is being made.</p>
+
+ <p>The <tp:dbus-ref
+ namespace="ofdT.Channel">TargetHandle</tp:dbus-ref> and
+ <tp:dbus-ref namespace="ofdT.Channel">TargetID</tp:dbus-ref>
+ properties in a Call channel refer to the contact that the
+ user initially called, or which contact initially called the
+ user. Even in a conference call, where there are multiple
+ contacts in the call, these properties refer to the
+ initial contact, who might have left the conference since
+ then. As a result, handlers should not rely on these
+ properties.</p>
+
+ <h4>Contents</h4>
+
+ <p><tp:dbus-ref namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref>
+ objects represent the actual media that forms the Call (for
+ example an audio content and a video content). Calls always
+ have one or more Content objects associated with them. As a
+ result, a new Call channel request MUST have either
+ <tp:member-ref>InitialAudio</tp:member-ref>=True, or
+ <tp:member-ref>InitialVideo</tp:member-ref>=True, or both,
+ as the Requestable Channel Classes will document.</p>
+
+ <p><tp:dbus-ref
+ namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref> objects have
+ one or more stream associated with them. More information on
+ these streams and how to maniuplate them can be found on the
+ <tp:dbus-ref namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref>
+ interface page.</p>
+
+ <h4>Outgoing calls</h4>
+
+ <p>To make an audio-only call to a contact <tt>foo@example.com</tt>
+ handlers should call:</p>
+
+ <blockquote>
+ <pre>
+<tp:dbus-ref namespace="ofdT.Connection.Interface.Requests">CreateChannel</tp:dbus-ref>({
+ ...<tp:dbus-ref namespace="ofdT.Channel">ChannelType</tp:dbus-ref>: ...<tp:dbus-ref
+ namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref>,
+ ...<tp:dbus-ref namespace="ofdT.Channel">TargetHandleType</tp:dbus-ref>: Contact,
+ ...<tp:dbus-ref namespace="ofdT.Channel">TargetID</tp:dbus-ref>: 'foo@example.com',
+ ...<tp:member-ref>InitialAudio</tp:member-ref>: True,
+})</pre></blockquote>
+
+ <p>As always, <tp:dbus-ref
+ namespace="ofdT.Channel">TargetHandle</tp:dbus-ref> may be used
+ in place of
+ <tp:dbus-ref namespace="ofdT.Channel">TargetID</tp:dbus-ref>
+ if the contact's handle is already known. To make an audio
+ and video call, the handler should also specify
+ <tp:member-ref>InitialVideo</tp:member-ref> The
+ connection manager SHOULD return a channel whose immutable
+ properties contain the local user as the <tp:dbus-ref
+ namespace="ofdT.Channel">InitiatorHandle</tp:dbus-ref>, the
+ remote contact as the <tp:dbus-ref
+ namespace="ofdT.Channel">TargetHandle</tp:dbus-ref>,
+ <tp:dbus-ref namespace="ofdT.Channel">Requested</tp:dbus-ref> =
+ <code>True</code> (indicating the call is outgoing).</p>
+
+ <p>After a new Call channel is requested, the
+ <tp:member-ref>CallState</tp:member-ref> property will be
+ <tp:type>Call_State</tp:type>_Pending_Initiator. As the local
+ user is the initiator, the call must be accepted by the handler
+ by calling the <tp:member-ref>Accept</tp:member-ref> method.
+ At this point, <tp:member-ref>CallState</tp:member-ref> changes
+ to <tp:type>Call_State</tp:type>_Pending_Receiver which signifies
+ that the call is ringing, waiting for the remote contact to
+ accept the call. All changes to
+ <tp:member-ref>CallState</tp:member-ref> property are signalled
+ using the <tp:member-ref>CallStateChanged</tp:member-ref>
+ signal.</p>
+
+ <p>When the call is accepted by the remote contact, the
+ <tp:member-ref>CallStateChanged</tp:member-ref> signal fires
+ again to show that <tp:member-ref>CallState</tp:member-ref> =
+ <tp:type>Call_State</tp:type>_Accepted.</p>
+
+ <p>At this point <a
+ href="http://telepathy.freedesktop.org/doc/telepathy-farstream/">telepathy-farstream</a>
+ will signal that a pad is available for the handler to show
+ in the user interface.</p>
+
+ <h5>Missed calls</h5>
+
+ <p>If the remote contact does not accept the call in time, then
+ the call can be terminated by the server. Note that this only
+ happens in some protocols. Most XMPP clients, for example, do
+ not do this and rely on the call initiator terminating the call.
+ A missed call is shown in a Call channel by the
+ <tp:member-ref>CallState</tp:member-ref> property changing to
+ <tp:type>Call_State</tp:type>_Ended, and the
+ <tp:member-ref>CallStateReason</tp:member-ref> property changing
+ to (remote contact,
+ <tp:type>Call_State_Change_Reason</tp:type>_No_Answer, "").</p>
+
+ <h5>Rejected calls</h5>
+
+ <p>If the remote contact decides he or she does not feel like
+ talking to the local user, he or she can reject his or her
+ incoming call. This will be shown in the Call channel by
+ <tp:member-ref>CallState</tp:member-ref> changing to
+ <tp:type>Call_State</tp:type>_Ended and the
+ <tp:member-ref>CallStateReason</tp:member-ref> property
+ changing to (remote contact,
+ <tp:type>Call_State</tp:type>_Change_Reason_User_Requested,
+ "org.freedesktop.Telepathy.Error.Rejected").</p>
+
+ <h4>Incoming calls</h4>
+
+ <p>When an incoming call occurs, something like the following
+ <tp:dbus-ref
+ namespace="ofdT.Connection.Interface.Requests">NewChannels</tp:dbus-ref>
+ signal will occur:</p>
+
+ <blockquote>
+ <pre>
+<tp:dbus-ref namespace="ofdT.Connection.Interface.Requests">NewChannels</tp:dbus-ref>([
+ /org/freedesktop/Telepathy/Connection/foo/bar/foo_40bar_2ecom/CallChannel,
+ {
+ ...<tp:dbus-ref namespace="ofdT.Channel">ChannelType</tp:dbus-ref>: ...<tp:dbus-ref
+ namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref>,
+ ...<tp:dbus-ref namespace="ofdT.Channel">TargetHandleType</tp:dbus-ref>: Contact,
+ ...<tp:dbus-ref namespace="ofdT.Channel">TargetID</tp:dbus-ref>: 'foo@example.com',
+ ...<tp:dbus-ref namespace="ofdT.Channel">TargetHandle</tp:dbus-ref>: 42,
+ ...<tp:dbus-ref namespace="ofdT.Channel">Requested</tp:dbus-ref>: False,
+ ...<tp:member-ref>InitialAudio</tp:member-ref>: True,
+ ...<tp:member-ref>InitialVideo</tp:member-ref>: True,
+ ...<tp:member-ref>InitialAudioName</tp:member-ref>: "audio",
+ ...<tp:member-ref>InitialVideoName</tp:member-ref>: "video",
+ ...<tp:member-ref>MutableContents</tp:member-ref>: True,
+ }])</pre></blockquote>
+
+ <p>The <tp:member-ref>InitialAudio</tp:member-ref> and
+ <tp:member-ref>InitialVideo</tp:member-ref> properties show that
+ the call has been started with two contents: one for audio
+ streaming and one for video streaming. The
+ <tp:member-ref>InitialAudioName</tp:member-ref> and
+ <tp:member-ref>InitialVideoName</tp:member-ref> properties also
+ show that the aforementioned audio and video contents have names
+ "audio" and "video".</p>
+
+ <p>Once the handler has notified the local user that there is an
+ incoming call waiting for acceptance, the handler should call
+ <tp:member-ref>SetRinging</tp:member-ref> to let the CM know.
+ The new channel should also be given to telepathy-farstream to
+ work out how the two participants will connect together.
+ telepathy-farstream will call the appropriate methods on the call's
+ <tp:dbus-ref namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref>s
+ to negotiate codecs and transports.</p>
+
+ <p>To pick up the call, the handler should call
+ <tp:member-ref>Accept</tp:member-ref>. The
+ <tp:member-ref>CallState</tp:member-ref> property changes to
+ <tp:type>Call_State</tp:type>_Accepted and once media is
+ being transferred, telepathy-farstream will notify the
+ handler of a new pad to be shown to the local user in the
+ UI</p>
+
+ <p>To reject the call, the handler should call the
+ <tp:member-ref>Hangup</tp:member-ref> method. The
+ <tp:member-ref>CallState</tp:member-ref> property will change to
+ <tp:type>Call_State</tp:type>_Ended and the
+ <tp:member-ref>CallStateReason</tp:member-ref> property will
+ change to (self handle,
+ <tp:type>Call_State_Change_Reason</tp:type>_User_Requested,
+ "org.freedesktop.Telepathy.Error.Rejected").</p>
+
+ <h4>Ongoing calls</h4>
+
+ <h5>Adding and removing contents</h5>
+
+ <p>When a call is open, new contents can be added as long as the
+ CM supports it. The
+ <tp:member-ref>MutableContents</tp:member-ref> property will let
+ the handler know whether further contents can be added or
+ existing contents removed. An example of this is starting a
+ voice call between a contact and then adding a video content.
+ To do this, the should call
+ <tp:member-ref>AddContent</tp:member-ref> like this:</p>
+
+ <blockquote>
+ <pre><tp:member-ref>AddContent</tp:member-ref>("video",
+ <tp:type>Media_Stream_Type</tp:type>_Video)</pre>
+ </blockquote>
+
+ <p>Assuming no errors, the new video content will be added to
+ the call. telepathy-farstream will pick up the new content and
+ perform the transport and codec negotiation automatically.
+ telpathy-farstream will signal when the video is ready to
+ show in the handler's user interface.</p>
+
+ <p>A similar method is used for removing contents from a call,
+ except that the <tp:dbus-ref
+ namespace="ofdT.Call.Content.DRAFT">Remove</tp:dbus-ref> method
+ is on the <tp:dbus-ref
+ namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref> object.</p>
+
+ <h5>Ending the call</h5>
+
+ <p>To end the call, the handler should call the
+ <tp:member-ref>Hangup</tp:member-ref> method. The
+ <tp:member-ref>CallState</tp:member-ref> property will change to
+ <tp:type>Call_State</tp:type>_Ended and
+ <tp:member-ref>CallStateReason</tp:member-ref> will change
+ to (self handle,
+ <tp:type>Call_State_Change_Reason</tp:type>_User_Requested,
+ "org.freedesktop.Telepathy.Error.Cancelled").</p>
+
+ <p>If the other participant hangs up first then the
+ <tp:member-ref>CallState</tp:member-ref> property will change to
+ <tp:type>Call_State</tp:type>_Ended and
+ <tp:member-ref>CallStateReason</tp:member-ref> will change
+ to (remote contact,
+ <tp:type>Call_State_Change_Reason</tp:type>_User_Requested,
+ "org.freedesktop.Telepathy.Error.Terminated").</p>
+
+ <h4>Multi-party calls</h4>
+
+ [TODO]
+
+ <h4>Call states</h4>
+
+ <p>There are many combinations of the
+ <tp:member-ref>CallState</tp:member-ref> and
+ <tp:member-ref>CallStateReason</tp:member-ref> properties which
+ mean different things. Here is a table to try to make these
+ meanings clearer:</p>
+
+ <table>
+ <tr>
+ <th rowspan="2"><tp:dbus-ref namespace="ofdT.Channel">Requested</tp:dbus-ref></th>
+ <th rowspan="2"><tp:member-ref>CallState</tp:member-ref></th>
+ <th colspan="3"><tp:member-ref>CallStateReason</tp:member-ref></th>
+ <th rowspan="2">Meaning</th>
+ </tr>
+ <tr>
+ <th>Actor</th>
+ <th>Reason</th>
+ <th>DBus_Reason</th>
+ </tr>
+ <!-- Pending_Initiator -->
+ <tr>
+ <td>True</td>
+ <td><tp:type>Call_State</tp:type>_Pending_Initiator</td>
+ <td>Self handle</td>
+ <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td>
+ <td>""</td>
+ <td>The outgoing call channel is waiting for the local user to call <tp:member-ref>Accept</tp:member-ref>.</td>
+ </tr>
+ <!-- Pending_Receiver -->
+ <tr>
+ <td>True</td>
+ <td><tp:type>Call_State</tp:type>_Pending_Receiver</td>
+ <td>Self handle</td>
+ <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td>
+ <td>""</td>
+ <td>The outgoing call is waiting for the remote contact to pick up the call.</td>
+ </tr>
+ <tr>
+ <td>False</td>
+ <td><tp:type>Call_State</tp:type>_Pending_Receiver</td>
+ <td>0</td>
+ <td><tp:type>Call_State_Change_Reason</tp:type>_Unknown</td>
+ <td>""</td>
+ <td>The incoming call is waiting for the local user to call <tp:member-ref>Accept</tp:member-ref> on the call.</td>
+ </tr>
+ <!-- Accepted -->
+ <tr>
+ <td>True</td>
+ <td><tp:type>Call_State</tp:type>_Accepted</td>
+ <td>Remote contact handle</td>
+ <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td>
+ <td>""</td>
+ <td>The remote contact accepted the outgoing call.</td>
+ </tr>
+ <tr>
+ <td>False</td>
+ <td><tp:type>Call_State</tp:type>_Accepted</td>
+ <td>Self handle</td>
+ <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td>
+ <td>""</td>
+ <td>The local user accepted the incoming call.</td>
+ </tr>
+ <!-- Ended -->
+ <tr>
+ <td>True or False</td>
+ <td><tp:type>Call_State</tp:type>_Ended</td>
+ <td>Self handle</td>
+ <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td>
+ <td><tp:error-ref>Cancelled</tp:error-ref></td>
+ <td>The local user hung up the incoming or outgoing call.</td>
+ </tr>
+ <tr>
+ <td>True or False</td>
+ <td><tp:type>Call_State</tp:type>_Ended</td>
+ <td>Remote contact handle</td>
+ <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td>
+ <td><tp:error-ref>Terminated</tp:error-ref></td>
+ <td>The remote contact hung up the incoming or outgoing call.</td>
+ </tr>
+ <tr>
+ <td>True</td>
+ <td><tp:type>Call_State</tp:type>_Ended</td>
+ <td>Remote contact handle</td>
+ <td><tp:type>Call_State_Change_Reason</tp:type>_No_Answer</td>
+ <td>""</td>
+ <td>The outgoing call was not picked up and the call ended.</td>
+ </tr>
+ <tr>
+ <td>False</td>
+ <td><tp:type>Call_State</tp:type>_Ended</td>
+ <td>Remote contact handle</td>
+ <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td>
+ <td><tp:error-ref>PickedUpElsewhere</tp:error-ref></td>
+ <td>The incoming call was ended because it was picked up elsewhere.</td>
+ </tr>
+ </table>
+
+ <h4>Requestable channel classes</h4>
+
+ <p>The <tp:dbus-ref
+ namespace="ofdT.Connection.Interface.Requests">RequestableChannelClasses</tp:dbus-ref>
+ for <tp:dbus-ref
+ namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref> channels
+ can be:</p>
+
+ <blockquote>
+ <pre>
+[( Fixed = { ...<tp:dbus-ref namespace="ofdT.Channel">ChannelType</tp:dbus-ref>: ...<tp:dbus-ref namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref>,
+ ...<tp:dbus-ref namespace="ofdT.Channel">TargetHandleType</tp:dbus-ref>: Contact,
+ ...<tp:member-ref>InitialVideo</tp:member-ref>: True
+ },
+ Allowed = [ ...<tp:member-ref>InitialVideoName</tp:member-ref>,
+ ...<tp:member-ref>InitialAudio</tp:member-ref>,
+ ...<tp:member-ref>InitialAudioName</tp:member-ref>
+ ]
+),
+( Fixed = { ...<tp:dbus-ref namespace="ofdT.Channel">ChannelType</tp:dbus-ref>: ...<tp:dbus-ref namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref>,
+ ...<tp:dbus-ref namespace="ofdT.Channel">TargetHandleType</tp:dbus-ref>: Contact,
+ ...<tp:member-ref>InitialAudio</tp:member-ref>: True
+ },
+ Allowed = [ ...<tp:member-ref>InitialAudioName</tp:member-ref>,
+ ...<tp:member-ref>InitialVideo</tp:member-ref>,
+ ...<tp:member-ref>InitialVideoName</tp:member-ref>
+ ]
+)]</pre></blockquote>
+
+ <p>Clients aren't allowed to make outgoing calls that have
+ neither initial audio nor initial video. Clearly, CMs
+ which don't support video should leave out the first class and
+ omit <tp:member-ref>InitialVideo</tp:member-ref> from the second
+ class, and vice versa for CMs without audio support.</p>
+
+ <p>Handlers should not close <tp:dbus-ref
+ namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref> channels
+ without first calling <tp:member-ref>Hangup</tp:member-ref> on
+ the channel. If a Call handler crashes, the <tp:dbus-ref
+ namespace="ofdT">ChannelDispatcher</tp:dbus-ref> will call
+ <tp:dbus-ref namespace="ofdT.Channel">Close</tp:dbus-ref> on the
+ channel which SHOULD also imply a call to
+ <tp:member-ref>Hangup</tp:member-ref>(<tp:type>Call_State_Change_Reason</tp:type>_User_Requested,
+ "org.freedesktop.Telepathy.Error.Terminated", "") before
+ actually closing the channel.</p>
+
+ </tp:docstring>
+
+ <method name="SetRinging" tp:name-for-bindings="Set_Ringing">
+ <tp:changed version="0.21.2">renamed from Ringing</tp:changed>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Indicate that the local user has been alerted about the incoming
+ call.</p>
+
+ <p>This method is only useful if the
+ channel's <tp:dbus-ref namespace="ofdT.Channel">Requested</tp:dbus-ref>
+ property is False, and
+ the <tp:member-ref>CallState</tp:member-ref> is
+ <tp:type>Call_State</tp:type>_Pending_Receiver (an incoming
+ call waiting on the local user to pick up). While this is
+ the case, this method SHOULD change the
+ <tp:member-ref>CallFlags</tp:member-ref> to include
+ <tp:type>Call_Flags</tp:type>_Locally_Ringing, and notify the
+ remote contact that the local user has been alerted (if the
+ protocol implements this); repeated calls to this method
+ SHOULD succeed, but have no further effect.</p>
+
+ <p>In all other states, this method SHOULD fail with the error
+ NotAvailable.</p>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The call was <tp:dbus-ref namespace="ofdT.Channel"
+ >Requested</tp:dbus-ref>, so ringing does not make sense.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The call is no longer in state
+ <tp:type>Call_State</tp:type>_Pending_Receiver.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="Accept" tp:name-for-bindings="Accept">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>For incoming calls in state
+ <tp:type>Call_State</tp:type>_Pending_Receiver, accept the
+ incoming call; this changes the
+ <tp:member-ref>CallState</tp:member-ref> to
+ <tp:type>Call_State</tp:type>_Accepted.</p>
+
+ <p>For outgoing calls in state
+ <tp:type>Call_State</tp:type>_Pending_Initiator, actually
+ call the remote contact; this changes the
+ <tp:member-ref>CallState</tp:member-ref> to
+ <tp:type>Call_State</tp:type>_Pending_Receiver.</p>
+
+ <p>Otherwise, this method SHOULD fail with the error NotAvailable.</p>
+
+ <p>This method should be called exactly once per Call, by whatever
+ client (user interface) is handling the channel.</p>
+
+ <p>When this method is called, for each <tp:dbus-ref
+ namespace="ofdT.Call" >Content.DRAFT</tp:dbus-ref> whose
+ <tp:dbus-ref namespace="ofdT.Call.Content.DRAFT"
+ >Disposition</tp:dbus-ref> is
+ <tp:type>Call_Content_Disposition</tp:type>_Initial, any
+ streams where the <tp:dbus-ref
+ namespace="ofdT.Call.Stream.DRAFT">LocalSendingState</tp:dbus-ref>
+ is <tp:type>Sending_State</tp:type>_Pending_Send will be
+ moved to <tp:type>Sending_State</tp:type>_Sending as if
+ <tp:dbus-ref namespace="ofdT.Call.Stream.DRAFT"
+ >SetSending</tp:dbus-ref>(True) had been called.</p>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The call is not in one of the states where this method makes sense.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="Hangup" tp:name-for-bindings="Hangup">
+ <tp:docstring>
+ Request that the call is ended. All contents will be removed
+ from the Call so that the
+ <tp:member-ref>Contents</tp:member-ref> property will be the
+ empty list.
+ </tp:docstring>
+
+ <arg direction="in" name="Reason"
+ type="u" tp:type="Call_State_Change_Reason">
+ <tp:docstring>
+ A generic hangup reason.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Detailed_Hangup_Reason"
+ type="s" tp:type="DBus_Error_Name">
+ <tp:docstring>
+ A more specific reason for the call hangup, if one is available, or
+ an empty string otherwise.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Message" type="s">
+ <tp:docstring>
+ A human-readable message to be sent to the remote contact(s).
+
+ <tp:rationale>
+ XMPP Jingle allows calls to be terminated with a human-readable
+ message.
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The call has already been ended.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="AddContent" tp:name-for-bindings="Add_Content">
+ <tp:docstring>
+ Request that a new <tp:dbus-ref
+ namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref> of type
+ Content_Type is added to the Call. Handlers should check the
+ value of the <tp:member-ref>MutableContents</tp:member-ref>
+ property before trying to add another content as it might not
+ be allowed.
+ </tp:docstring>
+ <arg direction="in" name="Content_Name" type="s">
+ <tp:docstring>
+ <p>The suggested name of the content to add.</p>
+
+ <tp:rationale>
+ The content name property should be meaningful, so should
+ be given a name which is significant to the user. The name
+ could be a localized "audio", "video" or perhaps include
+ some string identifying the source, such as a webcam
+ identifier.
+ </tp:rationale>
+
+ <p>If there is already a content with the same name as this
+ property then a sensible suffix should be added. For example,
+ if this argument is "audio" but a content of the same name
+ already exists, a sensible suffix such as " (1)" is appended
+ to name the new content "audio (1)". A further content with the
+ name "audio" would then be named "audio (2)".</p>
+
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Content_Type" type="u"
+ tp:type="Media_Stream_Type">
+ <tp:docstring>
+ The media stream type of the content to be added to the
+ call.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="Content" type="o">
+ <tp:docstring>
+ Path to the newly-created <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy"
+ >Call.Content.DRAFT</tp:dbus-ref> object.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The media stream type given is invalid.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The media stream type requested is not implemented by the
+ CM.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotCapable">
+ <tp:docstring>
+ The content type requested cannot be added to this
+ call. Examples of why this might be the case include
+ because a second video stream cannot be added, or a
+ content cannot be added when the content set isn't
+ mutable.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="ContentAdded"
+ tp:name-for-bindings="Content_Added">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when a new <tp:dbus-ref namespace="ofdT.Call"
+ >Content.DRAFT</tp:dbus-ref> is added to the call.</p>
+ </tp:docstring>
+ <arg name="Content" type="o">
+ <tp:docstring>
+ Path to the newly-created <tp:dbus-ref namespace="ofdT.Call"
+ >Content.DRAFT</tp:dbus-ref> object.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="ContentRemoved" tp:name-for-bindings="Content_Removed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when a <tp:dbus-ref namespace="ofdT.Call"
+ >Content.DRAFT</tp:dbus-ref> is removed from the call.</p>
+ </tp:docstring>
+ <arg name="Content" type="o">
+ <tp:docstring>
+ The <tp:dbus-ref namespace="ofdT.Call"
+ >Content.DRAFT</tp:dbus-ref> which was removed.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="Contents" type="ao" access="read"
+ tp:name-for-bindings="Contents">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The list of <tp:dbus-ref
+ namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref> objects that
+ are part of this call. Change notification is via the
+ <tp:member-ref>ContentAdded</tp:member-ref> and
+ <tp:member-ref>ContentRemoved</tp:member-ref> signals.
+ </p>
+ </tp:docstring>
+ </property>
+
+ <tp:enum type="u" name="Call_State">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The state of a call, as a whole.</p>
+
+ <p>The allowed transitions are:</p>
+
+ <ul>
+ <li>Pending_Initiator → Pending_Receiver (for outgoing calls,
+ when <tp:member-ref>Accept</tp:member-ref> is called)</li>
+ <li>Pending_Receiver → Accepted (for incoming calls, when
+ <tp:member-ref>Accept</tp:member-ref> is called; for outgoing
+ calls to a contact, when the remote contact accepts the call;
+ for joining a conference call, when the local user successfully
+ joins the conference)</li>
+ <li>Accepted → Pending_Receiver (when transferred to another
+ contact)</li>
+ <li>any state → Ended (when the call is terminated normally, or
+ when an error occurs)</li>
+ </ul>
+
+ <p>Clients MAY consider unknown values from this enum to be an
+ error - additional values will not be defined after the Call
+ specification is declared to be stable.</p>
+ </tp:docstring>
+
+ <tp:enumvalue suffix="Unknown" value = "0">
+ <tp:docstring>
+ The call state is not known. This call state MUST NOT appear as a
+ value of the <tp:member-ref>CallState</tp:member-ref> property, but
+ MAY be used by client code to represent calls whose state is as yet
+ unknown.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Pending_Initiator" value = "1">
+ <tp:docstring>
+ The initiator of the call hasn't accepted the call yet. This state
+ only makes sense for outgoing calls, where it means that the local
+ user has not yet sent any signalling messages to the remote user(s),
+ and will not do so until <tp:member-ref>Accept</tp:member-ref> is
+ called.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Pending_Receiver" value = "2">
+ <tp:docstring>
+ The receiver (the contact being called) hasn't accepted the call yet.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Accepted" value = "3">
+ <tp:docstring>
+ The contact being called has accepted the call.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Ended" value = "4">
+ <tp:docstring>
+ The call has ended, either via normal termination or an error.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:flags name="Call_Flags" value-prefix="Call_Flag" type="u">
+ <tp:docstring>
+ A set of flags representing the status of the call as a whole,
+ providing more specific information than the
+ <tp:member-ref>CallState</tp:member-ref>. Many of these flags only make
+ sense in a particular state.
+ </tp:docstring>
+
+ <tp:flag suffix="Locally_Ringing" value="1">
+ <tp:docstring>
+ The local contact has been alerted about the call but has not
+ responded; if possible, the remote contact(s) have been informed of
+ this fact. This flag only makes sense on incoming calls in
+ state <tp:type>Call_State</tp:type>_Pending_Receiver. It SHOULD
+ be set when <tp:member-ref>SetRinging</tp:member-ref> is
+ called successfully, and unset when the state changes.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Queued" value="2">
+ <tp:docstring>
+ The contact is temporarily unavailable, and the call has been placed
+ in a queue (e.g. 182 Queued in SIP, or call-waiting in telephony).
+ This flag only makes sense on outgoing 1-1 calls in
+ state <tp:type>Call_State</tp:type>_Pending_Receiver. It SHOULD be
+ set or unset according to informational messages from other
+ contacts.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Locally_Held" value="4">
+ <tp:docstring>
+ The call has been put on hold by the local user, e.g. using
+ the <tp:dbus-ref namespace="ofdT.Channel.Interface"
+ >Hold</tp:dbus-ref> interface. This flag SHOULD only be set
+ if there is at least one Content, and all Contents are
+ locally held; it makes sense on calls in state
+ <tp:type>Call_State</tp:type>_Pending_Receiver
+ or <tp:type>Call_State</tp:type>_Accepted.
+
+ <tp:rationale>
+ Otherwise, in transient situations where some but not all contents
+ are on hold, UIs would falsely indicate that the call as a whole
+ is on hold, which could lead to the user saying something they'll
+ regret, while under the impression that the other contacts can't
+ hear them!
+ </tp:rationale>
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Forwarded" value="8">
+ <tp:docstring>
+ The initiator of the call originally called a contact other than the
+ current recipient of the call, but the call was then forwarded or
+ diverted. This flag only makes sense on outgoing calls, in state
+ <tp:type>Call_State</tp:type>_Pending_Receiver or
+ <tp:type>Call_State</tp:type>_Accepted. It SHOULD be set or unset
+ according to informational messages from other contacts.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="In_Progress" value="16">
+ <tp:docstring>
+ Progress has been made in placing the outgoing call, but the
+ contact may not have been made aware of the call yet
+ (so the Ringing state is not appropriate). This corresponds to SIP's
+ status code 183 Session Progress, and could be used when the
+ outgoing call has reached a gateway, for instance.
+ This flag only makes sense on outgoing calls in state
+ <tp:type>Call_State</tp:type>_Pending_Receiver, and SHOULD be set
+ or unset according to informational messages from servers, gateways
+ and other infrastructure.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Clearing" value="32">
+ <tp:docstring>
+ This flag only occurs when the CallState is Ended. The call with
+ this flag set has ended, but not all resources corresponding to the
+ call have been freed yet.
+
+ Depending on the protocol there might be some audible feedback while
+ the clearing flag is set.
+
+ <tp:rationale>
+ In calls following the ITU-T Q.931 standard there is a period of
+ time between the call ending and the underlying channel being
+ completely free for re-use.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Muted" value="64">
+ <tp:docstring>
+ The call has been muted by the local user, e.g. using the
+ <tp:dbus-ref namespace="ofdT.Call.Content.Interface"
+ >Mute.DRAFT</tp:dbus-ref> interface. This flag SHOULD only
+ be set if there is at least one Content, and all Contents
+ are locally muted; it makes sense on calls in state
+ <tp:type>Call_State</tp:type>_Pending_Receiver or
+ <tp:type>Call_State</tp:type>_Accepted.
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ <property name="CallStateDetails"
+ tp:name-for-bindings="Call_State_Details" type="a{sv}" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A map used to provide optional extensible details for the
+ <tp:member-ref>CallState</tp:member-ref>,
+ <tp:member-ref>CallFlags</tp:member-ref> and/or
+ <tp:member-ref>CallStateReason</tp:member-ref>.</p>
+
+ <p>Well-known keys and their corresponding value types include:</p>
+
+ <dl>
+ <dt>hangup-message - s</dt>
+ <dd>An optional human-readable message sent when the call was ended,
+ corresponding to the Message argument to the
+ <tp:member-ref>Hangup</tp:member-ref> method. This is only
+ applicable when the call state is <tp:type>Call_State</tp:type>_Ended.
+ <tp:rationale>
+ XMPP Jingle can send such messages.
+ </tp:rationale>
+ </dd>
+
+ <dt>queue-message - s</dt>
+ <dd>An optional human-readable message sent when the local contact
+ is being held in a queue. This is only applicable when
+ <tp:type>Call_Flags</tp:type>_Queued is in the call flags.
+ <tp:rationale>
+ SIP 182 notifications can have human-readable messages attached.
+ </tp:rationale>
+ </dd>
+
+ <dt>debug-message - s</dt>
+ <dd>A message giving further details of any error indicated by the
+ <tp:member-ref>CallStateReason</tp:member-ref>. This will not
+ normally be localized or suitable for display to users, and is only
+ applicable when the call state is
+ <tp:type>Call_State</tp:type>_Ended.</dd>
+ </dl>
+ </tp:docstring>
+ </property>
+
+ <property name="CallState" type="u" access="read"
+ tp:name-for-bindings="Call_State" tp:type="Call_State">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The current high-level state of this call. The
+ <tp:member-ref>CallFlags</tp:member-ref> provide additional
+ information, and the <tp:member-ref>CallStateReason</tp:member-ref>
+ and <tp:member-ref>CallStateDetails</tp:member-ref> explain the
+ reason for the current values for those properties.</p>
+
+ <p>Note that when in a conference call, this property is
+ purely to show your state in joining the call. The receiver
+ (or remote contact) in this context is the conference server
+ itself. The property does not change when other call members'
+ states change.</p>
+
+ <p>Clients MAY consider unknown values in this property to be an
+ error.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="CallFlags" type="u" access="read"
+ tp:name-for-bindings="Call_Flags" tp:type="Call_Flags">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Flags representing the status of the call as a whole,
+ providing more specific information than the
+ <tp:member-ref>CallState</tp:member-ref>.</p>
+
+ <p>Clients are expected to ignore unknown flags in this property,
+ without error.</p>
+
+ <p>When an ongoing call is active and not on hold or has any
+ other problems, this property will be 0.</p>
+ </tp:docstring>
+ </property>
+
+ <tp:enum name="Call_State_Change_Reason" type="u">
+ <tp:docstring>
+ A simple representation of the reason for a change in the call's
+ state, which may be used by simple clients, or used as a fallback
+ when the DBus_Reason member of a <tp:type>Call_State_Reason</tp:type>
+ struct is not understood.
+ </tp:docstring>
+
+ <tp:enumvalue suffix="Unknown" value="0">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ We just don't know. Unknown values of this enum SHOULD also be
+ treated like this.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="User_Requested" value="1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The change was requested by the contact indicated by the Actor
+ member of a <tp:type>Call_State_Reason</tp:type> struct.</p>
+
+ <p>If the Actor is the local user, the DBus_Reason SHOULD be the
+ empty string.</p>
+
+ <p>If the Actor is a remote user, the DBus_Reason SHOULD be the empty
+ string if the call was terminated normally, but MAY be a non-empty
+ error name to indicate error-like call termination reasons (call
+ rejected as busy, kicked from a conference by a moderator, etc.).</p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Forwarded" value="2">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The call was forwarded. If known, the handle of the contact
+ the call was forwarded to will be indicated by the Actor member
+ of a <tp:type>Call_State_Reason</tp:type> struct.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="No_Answer" value="3">
+ <tp:added version="0.21.2"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The <tp:member-ref>CallState</tp:member-ref> changed from
+ <tp:type>Call_State</tp:type>_Pending_Receiver to
+ <tp:type>Call_State</tp:type>_Ended because the initiator
+ ended the call before the receiver accepted it. With an
+ incoming call this state change reason signifies a missed
+ call.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:struct name="Call_State_Reason">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A description of the reason for a change to the
+ <tp:member-ref>CallState</tp:member-ref> and/or
+ <tp:member-ref>CallFlags</tp:member-ref>.</p>
+ </tp:docstring>
+
+ <tp:member type="u" tp:type="Contact_Handle" name="Actor">
+ <tp:docstring>
+ The contact responsible for the change, or 0 if no contact was
+ responsible.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member type="u" tp:type="Call_State_Change_Reason" name="Reason">
+ <tp:docstring>
+ The reason, chosen from a limited set of possibilities defined by
+ the Telepathy specification. If
+ <tp:type>Call_State_Change_Reason</tp:type>_User_Requested then
+ the Actor member will dictate whether it was the local user or
+ a remote contact responsible.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member type="s" tp:type="DBus_Error_Name" name="DBus_Reason">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A specific reason for the change, which may be a D-Bus error in
+ the Telepathy namespace, a D-Bus error in any other namespace
+ (for implementation-specific errors), or the empty string to
+ indicate that the state change was not an error.</p>
+
+ <p>This SHOULD be an empty string for changes to any state other
+ than Ended.</p>
+
+ <p>The errors Cancelled and Terminated SHOULD NOT be used here;
+ an empty string SHOULD be used instead.</p>
+
+ <tp:rationale>
+ <p>Those error names are used to indicate normal call
+ termination by the local user or another user, respectively,
+ in contexts where a D-Bus error name must appear.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <property name="CallStateReason" tp:name-for-bindings="Call_State_Reason"
+ type="(uus)" access="read" tp:type="Call_State_Reason">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The reason for the last change to the
+ <tp:member-ref>CallState</tp:member-ref> and/or
+ <tp:member-ref>CallFlags</tp:member-ref>. The
+ <tp:member-ref>CallStateDetails</tp:member-ref> MAY provide additional
+ information.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="CallStateChanged"
+ tp:name-for-bindings="Call_State_Changed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when the state of the call as a whole changes.</p>
+
+ <p>This signal is emitted for any change in the properties
+ corresponding to its arguments, even if the other properties
+ referenced remain unchanged.</p>
+ </tp:docstring>
+
+ <arg name="Call_State" type="u" tp:type="Call_State">
+ <tp:docstring>
+ The new value of the <tp:member-ref>CallState</tp:member-ref>
+ property.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Call_Flags" type="u" tp:type="Call_Flags">
+ <tp:docstring>
+ The new value of the <tp:member-ref>CallFlags</tp:member-ref>
+ property.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Call_State_Reason" type="(uus)" tp:type="Call_State_Reason">
+ <tp:docstring>
+ The new value of the <tp:member-ref>CallStateReason</tp:member-ref>
+ property.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Call_State_Details" type="a{sv}">
+ <tp:docstring>
+ The new value of the <tp:member-ref>CallStateDetails</tp:member-ref>
+ property.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="HardwareStreaming" tp:name-for-bindings="Hardware_Streaming"
+ type="b" access="read" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If this property is True, all of the media streaming is done by some
+ mechanism outside the scope of Telepathy.</p>
+
+ <tp:rationale>
+ <p>A connection manager might be intended for a specialized hardware
+ device, which will take care of the audio streaming (e.g.
+ telepathy-yafono, which uses GSM hardware which does the actual
+ audio streaming for the call).</p>
+ </tp:rationale>
+
+ <p>If this is False, the handler is responsible for doing the actual
+ media streaming for at least some contents itself. Those contents
+ will have the <tp:dbus-ref namespace="ofdT.Call.Content.Interface"
+ >Media.DRAFT</tp:dbus-ref> interface, to communicate the necessary
+ information to a streaming implementation. Connection managers SHOULD
+ operate like this, if possible.</p>
+
+ <tp:rationale>
+ <p>Many connection managers (such as telepathy-gabble) only do the
+ call signalling, and expect the client to do the actual streaming
+ using something like
+ <a href="http://farsight.freedesktop.org/">Farsight</a>, to improve
+ latency and allow better UI integration.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <tp:flags type="u" name="Call_Member_Flags" value-prefix="Call_Member_Flag">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A set of flags representing the status of a remote contact in a
+ call.</p>
+
+ <p>It is protocol- and client-specific whether a particular contact
+ will ever have a particular flag set on them, and Telepathy clients
+ SHOULD NOT assume that a flag will ever be set.</p>
+
+ <tp:rationale>
+ <p>180 Ringing in SIP, and its equivalent in XMPP, are optional
+ informational messages, and implementations are not required
+ to send them. The same applies to the messages used to indicate
+ hold state.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <tp:flag suffix="Ringing" value = "1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The remote contact's client has told us that the contact has been
+ alerted about the call but has not responded.</p>
+
+ <tp:rationale>
+ <p>This is a flag per member, not a flag for the call as a whole,
+ because in Muji conference calls, you could invite someone and
+ have their state be "ringing" for a while.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Held" value = "2">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The call member has put this call on hold.</p>
+
+ <tp:rationale>
+ <p>This is a flag per member, not a flag for the call as a whole,
+ because in conference calls, any member could put the conference
+ on hold.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Conference_Host" value="4">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ This contact has merged this call into a conference. Note that GSM
+ provides a notification when the remote party merges a call into a
+ conference, but not when it is split out again; thus, this flag can
+ only indicate that the call has been part of a conference at some
+ point. If a GSM connection manager receives a notification that a
+ call has been merged into a conference a second time, it SHOULD
+ represent this by clearing and immediately re-setting this flag on
+ the remote contact.
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ <tp:mapping name="Call_Member_Map" array-name="Call_Member_Map_List">
+ <tp:docstring>A mapping from handles to their current state in the call.
+ </tp:docstring>
+ <tp:member type="u" tp:type="Handle" name="key"/>
+ <tp:member type="u" tp:type="Call_Member_Flags" name="Flag"/>
+ </tp:mapping>
+
+ <signal name="CallMembersChanged"
+ tp:name-for-bindings="Call_Members_Changed">
+ <tp:docstring>
+ Emitted when the <tp:member-ref>CallMembers</tp:member-ref> property
+ changes in any way, either because contacts have been added to the
+ call, contacts have been removed from the call, or contacts' flags
+ have changed.
+ </tp:docstring>
+
+ <arg name="Flags_Changed" type="a{uu}" tp:type="Call_Member_Map">
+ <tp:docstring>
+ A map from members of the call to their new call member flags,
+ including at least the members who have been added to
+ <tp:member-ref>CallMembers</tp:member-ref>, and the members whose
+ flags have changed.
+ </tp:docstring>
+ </arg>
+ <arg name="Removed" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ A list of members who have left the call, i.e. keys to be removed
+ from <tp:member-ref>CallMembers</tp:member-ref>.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="CallMembers" tp:name-for-bindings="Call_Members"
+ type="a{uu}" access="read" tp:type="Call_Member_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A mapping from the remote contacts that are part of this call to flags
+ describing their status. This mapping never has the local user's handle
+ as a key.</p>
+
+ <p>When the call ends, this property should be an empty list,
+ and notified with
+ <tp:member-ref>CallMembersChanged</tp:member-ref></p>
+
+ <p>If the Call implements
+ <tp:dbus-ref namespace="ofdT.Channel.Interface"
+ >Group</tp:dbus-ref> and the Group members are
+ channel-specific handles, then this call SHOULD also use
+ channel-specific handles.</p>
+
+ <p>Anonymous members are exposed as channel-specific handles
+ with no owner.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="InitialTransport" tp:name-for-bindings="Initial_Transport"
+ type="u" tp:type="Stream_Transport_Type" access="read"
+ tp:requestable="yes" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If set on a requested channel, this indicates the transport that
+ should be used for this call. Where not applicable, this property
+ is defined to be <tp:type>Stream_Transport_Type</tp:type>_Unknown,
+ in particular, on CMs with hardware streaming.</p>
+
+ <tp:rationale>
+ When implementing a voip gateway one wants the outgoing leg of the
+ gatewayed to have the same transport as the incoming leg. This
+ property allows the gateway to request a Call with the right
+ transport from the CM.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="InitialAudio" tp:name-for-bindings="Initial_Audio"
+ type="b" access="read" tp:immutable="yes" tp:requestable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If set to True in a channel request that will create a new channel,
+ the connection manager should immediately attempt to establish an
+ audio stream to the remote contact, making it unnecessary for the
+ client to call <tp:dbus-ref
+ namespace="ofdT.Channel.Type.Call.DRAFT">AddContent</tp:dbus-ref>.</p>
+
+ <p>If this property, or InitialVideo, is passed to EnsureChannel
+ (as opposed to CreateChannel), the connection manager SHOULD ignore
+ these properties when checking whether it can return an existing
+ channel as suitable; these properties only become significant when
+ the connection manager has decided to create a new channel.</p>
+
+ <p>If True on a requested channel, this indicates that the audio
+ stream has already been requested and the client does not need to
+ call RequestStreams, although it MAY still do so.</p>
+
+ <p>If True on an unrequested (incoming) channel, this indicates that
+ the remote contact initially requested an audio stream; this does
+ not imply that that audio stream is still active (as indicated by
+ <tp:dbus-ref namespace="ofdT.Channel.Type.Call.DRAFT"
+ >Contents</tp:dbus-ref>).</p>
+
+ <p>The name of this new content can be decided by using the
+ <tp:member-ref>InitialAudioName</tp:member-ref> property.</p>
+
+ <p>Connection managers that support the <tp:dbus-ref
+ namespace="ofdT.Connection.Interface">ContactCapabilities</tp:dbus-ref>
+ interface SHOULD represent the capabilities of receiving audio
+ and/or video calls by including a channel class in
+ a contact's capabilities with ChannelType = Call
+ in the fixed properties dictionary, and InitialAudio and/or
+ InitialVideo in the allowed properties list. Clients wishing to
+ discover whether a particular contact is likely to be able to
+ receive audio and/or video calls SHOULD use this information.</p>
+
+ <tp:rationale>
+ <p>Not all clients support video calls, and it would also be
+ possible (although unlikely) to have a client which could only
+ stream video, not audio.</p>
+ </tp:rationale>
+
+ <p>Clients that are willing to receive audio and/or video calls
+ SHOULD include the following among their channel classes if
+ calling <tp:dbus-ref
+ namespace="ofdT.Connection.Interface.ContactCapabilities">UpdateCapabilities</tp:dbus-ref>
+ (clients of a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelDispatcher</tp:dbus-ref>
+ SHOULD instead arrange for the ChannelDispatcher to do this,
+ by including the filters in their <tp:dbus-ref
+ namespace="ofdT.Client.Handler">HandlerChannelFilter</tp:dbus-ref>
+ properties):</p>
+
+ <ul>
+ <li>{ ChannelType = Call }</li>
+ <li>{ ChannelType = Call, InitialAudio = True }
+ if receiving calls with audio is supported</li>
+ <li>{ ChannelType = Call, InitialVideo = True }
+ if receiving calls with video is supported</li>
+ </ul>
+
+ <tp:rationale>
+ <p>Connection managers for protocols with capability discovery,
+ like XMPP, need this information to advertise the appropriate
+ capabilities for their protocol.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="InitialVideo" tp:name-for-bindings="Initial_Video"
+ type="b" access="read" tp:immutable="yes" tp:requestable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The same as <tp:member-ref>InitialAudio</tp:member-ref>, but for
+ a video stream. This property is immutable (cannot change).</p>
+
+ <p>In particular, note that if this property is False, this does not
+ imply that an active video stream has not been added, only that no
+ video stream was active at the time the channel appeared.</p>
+
+ <p>This property is the correct way to discover whether connection
+ managers, contacts etc. support video calls; it appears in
+ capabilities structures in the same way as InitialAudio.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="InitialAudioName" tp:name-for-bindings="Initial_Audio_Name"
+ type="s" access="read" tp:immutable="yes" tp:requestable="yes">
+ <tp:added version="0.21.2"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If <tp:member-ref>InitialAudio</tp:member-ref> is set to
+ True, then this property will name the intial audio content
+ with the value of this property.</p>
+
+ <tp:rationale>
+ <p>Content names are meant to be significant, but if no name
+ can be given to initial audio content, then its name cannot
+ be meaningful or even localized.</p>
+ </tp:rationale>
+
+ <p>If this property is empty or missing from the channel
+ request and InitialAudio is True, then the CM must come up
+ with a sensible for the content, such as "audio".</p>
+
+ <p>If the protocol has no concept of stream names then this
+ property will not show up in the allowed properties list of
+ the Requestable Channel Classes for call channels.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="InitialVideoName" tp:name-for-bindings="Initial_Video_Name"
+ type="s" access="read" tp:immutable="yes" tp:requestable="yes">
+ <tp:added version="0.21.2"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The same as
+ <tp:member-ref>InitialAudioName</tp:member-ref>, but for a
+ video stream created by setting
+ <tp:member-ref>InitialVideo</tp:member-ref> to True. This
+ property is immutable and so cannot change.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="MutableContents" tp:name-for-bindings="Mutable_Contents"
+ type="b" access="read" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If True, a stream of a different content type can be added
+ after the Channel has been requested </p>
+
+ <p>If this property is missing, clients SHOULD assume that it is False,
+ and thus that the channel's streams cannot be changed once the call
+ has started.</p>
+
+ <p>If this property isn't present in the "allowed" set in any of the
+ Call entries contact capabilities, then user interfaces MAY choose to
+ show a separate "call" option for each class of call.</p>
+
+ <tp:rationale>
+ <p>For example, once an audio-only Google Talk call has started,
+ it is not possible to add a video stream; both audio and video
+ must be requested at the start of the call if video is desired.
+ User interfaces may use this pseudo-capability as a hint to
+ display separate "Audio call" and "Video call" buttons, rather
+ than a single "Call" button with the option to add and remove
+ video once the call has started for contacts without this flag.
+ </p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <tp:hct name="audio">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This client supports audio calls.</p>
+ </tp:docstring>
+ </tp:hct>
+
+ <tp:hct name="video">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This client supports video calls.</p>
+ </tp:docstring>
+ </tp:hct>
+
+ <tp:hct name="gtalk-p2p">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The client can implement streaming for streams whose <tp:dbus-ref
+ namespace="ofdT.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref>
+ property is <tp:type>Stream_Transport_Type</tp:type>_GTalk_P2P.</p>
+ </tp:docstring>
+ </tp:hct>
+
+ <tp:hct name="ice">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The client can implement streaming for streams whose <tp:dbus-ref
+ namespace="ofdT.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref>
+ property is <tp:type>Stream_Transport_Type</tp:type>_ICE.</p>
+ </tp:docstring>
+ </tp:hct>
+
+ <tp:hct name="wlm-2009">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The client can implement streaming for streams whose <tp:dbus-ref
+ namespace="ofdT.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref>
+ property is <tp:type>Stream_Transport_Type</tp:type>_WLM_2009.</p>
+ </tp:docstring>
+ </tp:hct>
+
+ <tp:hct name="shm">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The client can implement streaming for streams whose <tp:dbus-ref
+ namespace="ofdT.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref>
+ property is <tp:type>Stream_Transport_Type</tp:type>_SHM.</p>
+ </tp:docstring>
+ </tp:hct>
+
+ <tp:hct name="video/h264" is-family="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The client supports media streaming with H264 (etc.).</p>
+
+ <p>This handler capability token is a one of a family
+ of similar tokens: for any other audio or video codec whose MIME
+ type is audio/<em>subtype</em> or video/<em>subtype</em>, a handler
+ capability token of this form may exist (the subtype MUST appear
+ in lower case in this context). Clients MAY support more
+ codecs than they explicitly advertise support for; clients SHOULD
+ explicitly advertise support for their preferred codec(s), and
+ for codecs like H264 that are, in practice, significant in codec
+ negotiation.</p>
+
+ <tp:rationale>
+ <p>For instance, the XMPP capability used by the Google Video
+ Chat web client to determine whether a client is compatible
+ with it requires support for H264 video, so an XMPP
+ connection manager that supports this version of Jingle should
+ not advertise the Google Video Chat capability unless there
+ is at least one installed client that declares that it supports
+ <code>video/h264</code> on Call channels.</p>
+ </tp:rationale>
+
+ <p>For example, a client could advertise support for audio and video
+ calls using Speex, Theora and H264 by having five handler capability
+ tokens in its <tp:dbus-ref
+ namespace="ofdT.Client.Handler">Capabilities</tp:dbus-ref>
+ property:</p>
+
+ <ul>
+ <li><code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/audio</code></li>
+ <li><code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/audio/speex</code></li>
+ <li><code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/video</code></li>
+ <li><code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/video/theora</code></li>
+ <li><code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/video/h264</code></li>
+ </ul>
+
+ <p>Clients MAY have media signalling abilities without explicitly
+ supporting any particular codec, and connection managers SHOULD
+ support this usage.</p>
+
+ <tp:rationale>
+ <p>This is necessary to support gatewaying between two Telepathy
+ connections, in which case the available codecs might not be
+ known to the gatewaying process.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:hct>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/farstream/extensions/Makefile.am b/farstream/extensions/Makefile.am
new file mode 100644
index 000000000..24f71c8ef
--- /dev/null
+++ b/farstream/extensions/Makefile.am
@@ -0,0 +1,166 @@
+# This directory re-uses telepathy-glib's code generation mechanisms to
+# generate code for interfaces that aren't stable enough for telepathy-glib
+# yet, so we can start to adapt example code to use them.
+
+tools_dir = $(top_srcdir)/tools
+
+AM_CFLAGS = \
+ $(ERROR_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(TELEPATHY_CFLAGS) \
+ $(DBUS_CFLAGS) \
+ -I${top_builddir} -I${top_srcdir}
+
+EXTRA_DIST = \
+ all.xml \
+ Call_Content_Codec_Offer.xml \
+ Call_Content_Interface_Media.xml \
+ Call_Content_Interface_Mute.xml \
+ Call_Content_Interface_Video_Control.xml \
+ call-content.xml \
+ Call_Content.xml \
+ Call_Stream_Endpoint.xml \
+ Call_Stream_Interface_Media.xml \
+ call-stream.xml \
+ Call_Stream.xml \
+ Channel_Type_Call.xml \
+ channel.xml \
+ misc.xml
+
+noinst_LTLIBRARIES = libfuture-extensions.la
+
+# In an external project you'd use $(TP_GLIB_LIBS) (obtained from
+# pkg-config via autoconf) instead of the .la path
+libfuture_extensions_la_LIBADD = \
+ $(GLIB_LIBS) \
+ $(TELEPATHY_LIBS)
+
+libfuture_extensions_la_SOURCES = \
+ call-content.c \
+ call-content.h \
+ call-stream.c \
+ call-stream.h \
+ extensions.c \
+ extensions-cli.c \
+ extensions.h
+
+nodist_libfuture_extensions_la_SOURCES = \
+ _gen/signals-marshal.c \
+ _gen/signals-marshal.h \
+ _gen/signals-marshal.list \
+ _gen/register-dbus-glib-marshallers-body.h \
+ _gen/enums.h \
+ _gen/gtypes.h \
+ _gen/gtypes-body.h \
+ _gen/interfaces.h \
+ _gen/interfaces-body.h \
+ _gen/cli-call-content.h \
+ _gen/cli-call-content-body.h \
+ _gen/cli-call-stream.h \
+ _gen/cli-call-stream-body.h \
+ _gen/cli-channel.h \
+ _gen/cli-channel-body.h \
+ _gen/cli-misc.h \
+ _gen/cli-misc-body.h
+
+BUILT_SOURCES = \
+ _gen/all.xml \
+ _gen/call-content.xml \
+ _gen/call-stream.xml \
+ _gen/channel.xml \
+ _gen/misc.xml \
+ $(nodist_libfuture_extensions_la_SOURCES)
+
+CLEANFILES = $(BUILT_SOURCES)
+
+clean-local:
+ rm -rf _gen
+
+XSLTPROCFLAGS = --nonet --novalid
+
+# Generated files which can be generated for all categories simultaneously
+
+_gen/all.xml: all.xml $(wildcard $(srcdir)/*.xml) $(wildcard $(top_srcdir)/spec/*.xml) $(tools_dir)/xincludator.py
+ $(mkdir_p) _gen
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/xincludator.py $< > $@
+
+_gen/gtypes.h _gen/gtypes-body.h: _gen/all.xml \
+ $(top_srcdir)/tools/glib-gtypes-generator.py
+ $(AM_V_GEN)$(PYTHON) $(top_srcdir)/tools/glib-gtypes-generator.py \
+ $< _gen/gtypes Tf_Future
+
+_gen/signals-marshal.list: _gen/all.xml \
+ $(tools_dir)/glib-signals-marshal-gen.py
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-signals-marshal-gen.py $< > $@
+
+_gen/signals-marshal.h: _gen/signals-marshal.list Makefile.am
+ $(AM_V_GEN)$(GLIB_GENMARSHAL) --header --prefix=_tf_future_ext_marshal $< > $@
+
+_gen/signals-marshal.c: _gen/signals-marshal.list Makefile.am
+ $(AM_V_GEN){ echo '#include "_gen/signals-marshal.h"' && \
+ $(GLIB_GENMARSHAL) --body --prefix=_tf_future_ext_marshal $< ; } > $@
+
+_gen/register-dbus-glib-marshallers-body.h: _gen/all.xml \
+ $(tools_dir)/glib-client-marshaller-gen.py
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-marshaller-gen.py $< \
+ _tf_future_ext > $@
+
+_gen/enums.h: _gen/all.xml \
+ $(tools_dir)/c-constants-gen.py
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/c-constants-gen.py \
+ Tf_Future \
+ $< _gen/enums
+
+_gen/interfaces-body.h _gen/interfaces.h: _gen/all.xml \
+ $(tools_dir)/glib-interfaces-gen.py
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-interfaces-gen.py \
+ Tf_Future _gen/interfaces-body.h _gen/interfaces.h $<
+
+# Generated files which must be generated per "category". Each TpProxy
+# subclass you want to use with --subclass will need to have its own category,
+# although you can subdivide further if you want.
+
+_gen/%.xml: %.xml $(wildcard $(srcdir)/*.xml) $(wildcard $(top_srcdir)/spec/*.xml) $(tools_dir)/xincludator.py
+ $(mkdir_p) _gen
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/xincludator.py $< > $@
+
+_gen/cli-channel-body.h _gen/cli-channel.h: _gen/channel.xml \
+ $(tools_dir)/glib-client-gen.py Makefile.am
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-gen.py \
+ --group=channel \
+ --subclass=TpChannel \
+ --subclass-assert=TP_IS_CHANNEL \
+ --iface-quark-prefix=TF_FUTURE_IFACE_QUARK \
+ --tp-proxy-api=0.7.6 \
+ $< Tf_Future_Cli _gen/cli-channel
+
+_gen/cli-call-content-body.h _gen/cli-call-content.h: _gen/call-content.xml \
+ $(tools_dir)/glib-client-gen.py Makefile.am
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-gen.py \
+ --group=call_content \
+ --subclass=TfFutureCallContent \
+ --subclass-assert=TF_FUTURE_IS_CALL_CONTENT \
+ --iface-quark-prefix=TF_FUTURE_IFACE_QUARK \
+ --tp-proxy-api=0.7.6 \
+ $< Tf_Future_Cli _gen/cli-call-content
+
+_gen/cli-call-stream-body.h _gen/cli-call-stream.h: _gen/call-stream.xml \
+ $(tools_dir)/glib-client-gen.py Makefile.am
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-gen.py \
+ --group=call_stream \
+ --subclass=TfFutureCallStream \
+ --subclass-assert=TF_FUTURE_IS_CALL_STREAM \
+ --iface-quark-prefix=TF_FUTURE_IFACE_QUARK \
+ --tp-proxy-api=0.7.6 \
+ $< Tf_Future_Cli _gen/cli-call-stream
+
+# for now the Endpoint etc. interfaces are on every TpProxy - when we
+# have a TpCallEndpoint etc., they should appear on that
+
+_gen/cli-misc-body.h _gen/cli-misc.h: _gen/misc.xml \
+ $(tools_dir)/glib-client-gen.py Makefile.am
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-gen.py \
+ --group=misc \
+ --iface-quark-prefix=TF_FUTURE_IFACE_QUARK \
+ --tp-proxy-api=0.7.6 \
+ $< Tf_Future_Cli _gen/cli-misc
diff --git a/farstream/extensions/all.xml b/farstream/extensions/all.xml
new file mode 100644
index 000000000..618cef7b0
--- /dev/null
+++ b/farstream/extensions/all.xml
@@ -0,0 +1,12 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Extensions from the future</tp:title>
+
+<xi:include href="call-content.xml"/>
+<xi:include href="call-stream.xml"/>
+<xi:include href="channel.xml"/>
+<xi:include href="misc.xml"/>
+
+</tp:spec>
diff --git a/farstream/extensions/call-content.c b/farstream/extensions/call-content.c
new file mode 100644
index 000000000..deb36ecc3
--- /dev/null
+++ b/farstream/extensions/call-content.c
@@ -0,0 +1,161 @@
+/*
+ * call-content.c - proxy for a Content in a Call channel
+ *
+ * Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2009 Nokia Corporation
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "extensions/call-content.h"
+
+#include <telepathy-glib/proxy-subclass.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "extensions/extensions.h"
+
+/* Generated code */
+#include "_gen/cli-call-content-body.h"
+
+/**
+ * SECTION:call-content
+ * @title: TfFutureCallContent
+ * @short_description: proxy for a Content in a Call channel
+ * @see_also: #TpChannel
+ *
+ * FIXME
+ *
+ * Since: FIXME
+ */
+
+/**
+ * TfFutureCallContentClass:
+ *
+ * The class of a #TfFutureCallContent.
+ *
+ * Since: FIXME
+ */
+struct _TfFutureCallContentClass {
+ TpProxyClass parent_class;
+ /*<private>*/
+ gpointer priv;
+};
+
+/**
+ * TfFutureCallContent:
+ *
+ * A proxy object for a Telepathy connection manager.
+ *
+ * Since: FIXME
+ */
+struct _TfFutureCallContent {
+ TpProxy parent;
+ /*<private>*/
+ TfFutureCallContentPrivate *priv;
+};
+
+struct _TfFutureCallContentPrivate {
+ int dummy;
+};
+
+G_DEFINE_TYPE (TfFutureCallContent,
+ tf_future_call_content,
+ TP_TYPE_PROXY);
+
+static void
+tf_future_call_content_init (TfFutureCallContent *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TF_FUTURE_TYPE_CALL_CONTENT,
+ TfFutureCallContentPrivate);
+}
+
+static void
+tf_future_call_content_class_init (TfFutureCallContentClass *klass)
+{
+ TpProxyClass *proxy_class = (TpProxyClass *) klass;
+
+ g_type_class_add_private (klass, sizeof (TfFutureCallContentPrivate));
+
+ proxy_class->must_have_unique_name = TRUE;
+ proxy_class->interface = TF_FUTURE_IFACE_QUARK_CALL_CONTENT;
+ tf_future_call_content_init_known_interfaces ();
+}
+
+/**
+ * tf_future_call_content_new:
+ * @channel: the Call channel
+ * @object_path: the object path of the content; may not be %NULL
+ * @error: used to indicate the error if %NULL is returned
+ *
+ * <!-- -->
+ *
+ * Returns: a new content proxy, or %NULL on invalid arguments
+ *
+ * Since: FIXME
+ */
+TfFutureCallContent *
+tf_future_call_content_new (TpChannel *channel,
+ const gchar *object_path,
+ GError **error)
+{
+ TfFutureCallContent *ret = NULL;
+
+ g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL);
+ g_return_val_if_fail (object_path != NULL, NULL);
+
+ if (!tp_dbus_check_valid_object_path (object_path, error))
+ goto finally;
+
+ ret = TF_FUTURE_CALL_CONTENT (g_object_new (TF_FUTURE_TYPE_CALL_CONTENT,
+ /* FIXME: pass in the Channel as a property? */
+ "dbus-daemon", tp_proxy_get_dbus_daemon (channel),
+ "bus-name", tp_proxy_get_bus_name (channel),
+ "object-path", object_path,
+ NULL));
+
+finally:
+ return ret;
+}
+
+/**
+ * tf_future_call_content_init_known_interfaces:
+ *
+ * Ensure that the known interfaces for TfFutureCallContent have been set up.
+ * This is done automatically when necessary, but for correct
+ * overriding of library interfaces by local extensions, you should
+ * call this function before calling
+ * tp_proxy_or_subclass_hook_on_interface_add() with first argument
+ * %TF_FUTURE_TYPE_CALL_CONTENT.
+ *
+ * Since: 0.7.32
+ */
+void
+tf_future_call_content_init_known_interfaces (void)
+{
+ static gsize once = 0;
+
+ if (g_once_init_enter (&once))
+ {
+ GType tp_type = TF_FUTURE_TYPE_CALL_CONTENT;
+
+ tp_proxy_init_known_interfaces ();
+ tp_proxy_or_subclass_hook_on_interface_add (tp_type,
+ tf_future_cli_call_content_add_signals);
+ tp_proxy_subclass_add_error_mapping (tp_type,
+ TP_ERROR_PREFIX, TP_ERRORS, TP_TYPE_ERROR);
+
+ g_once_init_leave (&once, 1);
+ }
+}
diff --git a/farstream/extensions/call-content.h b/farstream/extensions/call-content.h
new file mode 100644
index 000000000..68873d5ca
--- /dev/null
+++ b/farstream/extensions/call-content.h
@@ -0,0 +1,62 @@
+/*
+ * call-content.h - proxy for a Content in a Call channel
+ *
+ * Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2009 Nokia Corporation
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef TF_FUTURE_CALL_CONTENT_H
+#define TF_FUTURE_CALL_CONTENT_H
+
+#include <telepathy-glib/channel.h>
+#include <telepathy-glib/proxy.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TfFutureCallContent TfFutureCallContent;
+typedef struct _TfFutureCallContentPrivate TfFutureCallContentPrivate;
+typedef struct _TfFutureCallContentClass TfFutureCallContentClass;
+
+GType tf_future_call_content_get_type (void);
+
+/* TYPE MACROS */
+#define TF_FUTURE_TYPE_CALL_CONTENT \
+ (tf_future_call_content_get_type ())
+#define TF_FUTURE_CALL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TF_FUTURE_TYPE_CALL_CONTENT, \
+ TfFutureCallContent))
+#define TF_FUTURE_CALL_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TF_FUTURE_TYPE_CALL_CONTENT, \
+ TfFutureCallContentClass))
+#define TF_FUTURE_IS_CALL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TF_FUTURE_TYPE_CALL_CONTENT))
+#define TF_FUTURE_IS_CALL_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TF_FUTURE_TYPE_CALL_CONTENT))
+#define TF_FUTURE_CALL_CONTENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TF_FUTURE_TYPE_CALL_CONTENT, \
+ TfFutureCallContentClass))
+
+TfFutureCallContent *tf_future_call_content_new (TpChannel *channel,
+ const gchar *object_path, GError **error);
+
+void tf_future_call_content_init_known_interfaces (void);
+
+G_END_DECLS
+
+#include "extensions/_gen/cli-call-content.h"
+
+#endif
diff --git a/farstream/extensions/call-content.xml b/farstream/extensions/call-content.xml
new file mode 100644
index 000000000..a543becb6
--- /dev/null
+++ b/farstream/extensions/call-content.xml
@@ -0,0 +1,11 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Call Content</tp:title>
+
+<xi:include href="Call_Content.xml"/>
+<xi:include href="Call_Content_Interface_Media.xml"/>
+<xi:include href="Call_Content_Interface_Video_Control.xml"/>
+
+</tp:spec>
diff --git a/farstream/extensions/call-stream.c b/farstream/extensions/call-stream.c
new file mode 100644
index 000000000..a9a0d1ccb
--- /dev/null
+++ b/farstream/extensions/call-stream.c
@@ -0,0 +1,161 @@
+/*
+ * call-stream.c - proxy for a Stream in a Call channel
+ *
+ * Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2009 Nokia Corporation
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "extensions/call-stream.h"
+
+#include <telepathy-glib/proxy-subclass.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "extensions/extensions.h"
+
+/* Generated code */
+#include "_gen/cli-call-stream-body.h"
+
+/**
+ * SECTION:call-stream
+ * @title: TfFutureCallStream
+ * @short_description: proxy for a Stream in a Call channel
+ * @see_also: #TpChannel
+ *
+ * FIXME
+ *
+ * Since: FIXME
+ */
+
+/**
+ * TfFutureCallStreamClass:
+ *
+ * The class of a #TfFutureCallStream.
+ *
+ * Since: FIXME
+ */
+struct _TfFutureCallStreamClass {
+ TpProxyClass parent_class;
+ /*<private>*/
+ gpointer priv;
+};
+
+/**
+ * TfFutureCallStream:
+ *
+ * A proxy object for a Telepathy connection manager.
+ *
+ * Since: FIXME
+ */
+struct _TfFutureCallStream {
+ TpProxy parent;
+ /*<private>*/
+ TfFutureCallStreamPrivate *priv;
+};
+
+struct _TfFutureCallStreamPrivate {
+ int dummy;
+};
+
+G_DEFINE_TYPE (TfFutureCallStream,
+ tf_future_call_stream,
+ TP_TYPE_PROXY);
+
+static void
+tf_future_call_stream_init (TfFutureCallStream *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TF_FUTURE_TYPE_CALL_STREAM,
+ TfFutureCallStreamPrivate);
+}
+
+static void
+tf_future_call_stream_class_init (TfFutureCallStreamClass *klass)
+{
+ TpProxyClass *proxy_class = (TpProxyClass *) klass;
+
+ g_type_class_add_private (klass, sizeof (TfFutureCallStreamPrivate));
+
+ proxy_class->must_have_unique_name = TRUE;
+ proxy_class->interface = TF_FUTURE_IFACE_QUARK_CALL_STREAM;
+ tf_future_call_stream_init_known_interfaces ();
+}
+
+/**
+ * tf_future_call_stream_new:
+ * @channel: the Call channel
+ * @object_path: the object path of the stream; may not be %NULL
+ * @error: used to indicate the error if %NULL is returned
+ *
+ * <!-- -->
+ *
+ * Returns: a new stream proxy, or %NULL on invalid arguments
+ *
+ * Since: FIXME
+ */
+TfFutureCallStream *
+tf_future_call_stream_new (TpChannel *channel,
+ const gchar *object_path,
+ GError **error)
+{
+ TfFutureCallStream *ret = NULL;
+
+ g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL);
+ g_return_val_if_fail (object_path != NULL, NULL);
+
+ if (!tp_dbus_check_valid_object_path (object_path, error))
+ goto finally;
+
+ ret = TF_FUTURE_CALL_STREAM (g_object_new (TF_FUTURE_TYPE_CALL_STREAM,
+ /* FIXME: pass in the Channel as a property? */
+ "dbus-daemon", tp_proxy_get_dbus_daemon (channel),
+ "bus-name", tp_proxy_get_bus_name (channel),
+ "object-path", object_path,
+ NULL));
+
+finally:
+ return ret;
+}
+
+/**
+ * tf_future_call_stream_init_known_interfaces:
+ *
+ * Ensure that the known interfaces for TfFutureCallStream have been set up.
+ * This is done automatically when necessary, but for correct
+ * overriding of library interfaces by local extensions, you should
+ * call this function before calling
+ * tp_proxy_or_subclass_hook_on_interface_add() with first argument
+ * %TF_FUTURE_TYPE_CALL_STREAM.
+ *
+ * Since: 0.7.32
+ */
+void
+tf_future_call_stream_init_known_interfaces (void)
+{
+ static gsize once = 0;
+
+ if (g_once_init_enter (&once))
+ {
+ GType tp_type = TF_FUTURE_TYPE_CALL_STREAM;
+
+ tp_proxy_init_known_interfaces ();
+ tp_proxy_or_subclass_hook_on_interface_add (tp_type,
+ tf_future_cli_call_stream_add_signals);
+ tp_proxy_subclass_add_error_mapping (tp_type,
+ TP_ERROR_PREFIX, TP_ERRORS, TP_TYPE_ERROR);
+
+ g_once_init_leave (&once, 1);
+ }
+}
diff --git a/farstream/extensions/call-stream.h b/farstream/extensions/call-stream.h
new file mode 100644
index 000000000..2cd82b554
--- /dev/null
+++ b/farstream/extensions/call-stream.h
@@ -0,0 +1,62 @@
+/*
+ * call-stream.h - proxy for a Stream in a Call channel
+ *
+ * Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2009 Nokia Corporation
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef TF_FUTURE_CALL_STREAM_H
+#define TF_FUTURE_CALL_STREAM_H
+
+#include <telepathy-glib/channel.h>
+#include <telepathy-glib/proxy.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TfFutureCallStream TfFutureCallStream;
+typedef struct _TfFutureCallStreamPrivate TfFutureCallStreamPrivate;
+typedef struct _TfFutureCallStreamClass TfFutureCallStreamClass;
+
+GType tf_future_call_stream_get_type (void);
+
+/* TYPE MACROS */
+#define TF_FUTURE_TYPE_CALL_STREAM \
+ (tf_future_call_stream_get_type ())
+#define TF_FUTURE_CALL_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TF_FUTURE_TYPE_CALL_STREAM, \
+ TfFutureCallStream))
+#define TF_FUTURE_CALL_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TF_FUTURE_TYPE_CALL_STREAM, \
+ TfFutureCallStreamClass))
+#define TF_FUTURE_IS_CALL_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TF_FUTURE_TYPE_CALL_STREAM))
+#define TF_FUTURE_IS_CALL_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TF_FUTURE_TYPE_CALL_STREAM))
+#define TF_FUTURE_CALL_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TF_FUTURE_TYPE_CALL_STREAM, \
+ TfFutureCallStreamClass))
+
+TfFutureCallStream *tf_future_call_stream_new (TpChannel *channel,
+ const gchar *object_path, GError **error);
+
+void tf_future_call_stream_init_known_interfaces (void);
+
+G_END_DECLS
+
+#include "extensions/_gen/cli-call-stream.h"
+
+#endif
diff --git a/farstream/extensions/call-stream.xml b/farstream/extensions/call-stream.xml
new file mode 100644
index 000000000..3e85b3791
--- /dev/null
+++ b/farstream/extensions/call-stream.xml
@@ -0,0 +1,10 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Call Stream</tp:title>
+
+<xi:include href="Call_Stream.xml"/>
+<xi:include href="Call_Stream_Interface_Media.xml"/>
+
+</tp:spec>
diff --git a/farstream/extensions/channel.xml b/farstream/extensions/channel.xml
new file mode 100644
index 000000000..b4e0a469c
--- /dev/null
+++ b/farstream/extensions/channel.xml
@@ -0,0 +1,9 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Channel extensions from the future</tp:title>
+
+<xi:include href="Channel_Type_Call.xml"/>
+
+</tp:spec>
diff --git a/farstream/extensions/extensions-cli.c b/farstream/extensions/extensions-cli.c
new file mode 100644
index 000000000..971669df0
--- /dev/null
+++ b/farstream/extensions/extensions-cli.c
@@ -0,0 +1,35 @@
+#include "extensions.h"
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/proxy-subclass.h>
+
+static void _tf_future_ext_register_dbus_glib_marshallers (void);
+
+/* include auto-generated stubs for client-specific code */
+#include "_gen/signals-marshal.h"
+#include "_gen/cli-channel-body.h"
+#include "_gen/cli-misc-body.h"
+#include "_gen/register-dbus-glib-marshallers-body.h"
+
+static gpointer
+tf_future_cli_once (gpointer data)
+{
+ _tf_future_ext_register_dbus_glib_marshallers ();
+
+ tp_channel_init_known_interfaces ();
+
+ tp_proxy_or_subclass_hook_on_interface_add (TP_TYPE_PROXY,
+ tf_future_cli_misc_add_signals);
+ tp_proxy_or_subclass_hook_on_interface_add (TP_TYPE_CHANNEL,
+ tf_future_cli_channel_add_signals);
+
+ return NULL;
+}
+
+void
+tf_future_cli_init (void)
+{
+ static GOnce once = G_ONCE_INIT;
+
+ g_once (&once, tf_future_cli_once, NULL);
+}
diff --git a/farstream/extensions/extensions.c b/farstream/extensions/extensions.c
new file mode 100644
index 000000000..eeda4623c
--- /dev/null
+++ b/farstream/extensions/extensions.c
@@ -0,0 +1,6 @@
+#include "extensions.h"
+
+/* include auto-generated stubs for things common to service and client */
+#include "_gen/gtypes-body.h"
+#include "_gen/interfaces-body.h"
+#include "_gen/signals-marshal.h"
diff --git a/farstream/extensions/extensions.h b/farstream/extensions/extensions.h
new file mode 100644
index 000000000..f6e080d5e
--- /dev/null
+++ b/farstream/extensions/extensions.h
@@ -0,0 +1,23 @@
+#ifndef FUTURE_EXTENSIONS_H
+#define FUTURE_EXTENSIONS_H
+
+#include <glib-object.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "extensions/_gen/enums.h"
+#include "extensions/_gen/cli-channel.h"
+#include "extensions/_gen/cli-misc.h"
+
+#include "extensions/call-content.h"
+#include "extensions/call-stream.h"
+
+G_BEGIN_DECLS
+
+#include "extensions/_gen/gtypes.h"
+#include "extensions/_gen/interfaces.h"
+
+void tf_future_cli_init (void);
+
+G_END_DECLS
+
+#endif
diff --git a/farstream/extensions/misc.xml b/farstream/extensions/misc.xml
new file mode 100644
index 000000000..0384410f6
--- /dev/null
+++ b/farstream/extensions/misc.xml
@@ -0,0 +1,10 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Miscellaneous extensions from the future</tp:title>
+
+<xi:include href="Call_Content_Codec_Offer.xml"/>
+<xi:include href="Call_Stream_Endpoint.xml"/>
+
+</tp:spec>
diff --git a/farstream/m4/Makefile.am b/farstream/m4/Makefile.am
new file mode 100644
index 000000000..893cef7b4
--- /dev/null
+++ b/farstream/m4/Makefile.am
@@ -0,0 +1,5 @@
+EXTRA_DIST = \
+as-compiler-flag.m4 \
+as-version.m4 \
+as-ac-expand.m4 \
+ac-python-headers.m4
diff --git a/farstream/m4/ac-python-headers.m4 b/farstream/m4/ac-python-headers.m4
new file mode 100644
index 000000000..585455fd4
--- /dev/null
+++ b/farstream/m4/ac-python-headers.m4
@@ -0,0 +1,30 @@
+dnl Copy pasted from gst-python's acinclude.m4 file
+
+dnl a macro to check for ability to create python extensions
+dnl AM_CHECK_PYTHON_HEADERS([ACTION-IF-POSSIBLE], [ACTION-IF-NOT-POSSIBLE])
+dnl function also defines PYTHON_INCLUDES
+AC_DEFUN([AM_CHECK_PYTHON_HEADERS],
+[AC_REQUIRE([AM_PATH_PYTHON])
+AC_MSG_CHECKING(for headers required to compile python extensions)
+dnl deduce PYTHON_INCLUDES
+py_prefix=`$PYTHON -c "import sys; print sys.prefix"`
+py_exec_prefix=`$PYTHON -c "import sys; print sys.exec_prefix"`
+if $PYTHON-config --help 2>/dev/null; then
+ PYTHON_INCLUDES=`$PYTHON-config --includes 2>/dev/null`
+else
+ PYTHON_INCLUDES="-I${py_prefix}/include/python${PYTHON_VERSION}"
+ if test "$py_prefix" != "$py_exec_prefix"; then
+ PYTHON_INCLUDES="$PYTHON_INCLUDES -I${py_exec_prefix}/include/python${PYTHON_VERSION}"
+ fi
+fi
+AC_SUBST(PYTHON_INCLUDES)
+dnl check if the headers exist:
+save_CPPFLAGS="$CPPFLAGS"
+CPPFLAGS="$CPPFLAGS $PYTHON_INCLUDES"
+AC_TRY_CPP([#include <Python.h>],dnl
+[AC_MSG_RESULT(found)
+$1],dnl
+[AC_MSG_RESULT(not found)
+$2])
+CPPFLAGS="$save_CPPFLAGS"
+])
diff --git a/farstream/m4/as-ac-expand.m4 b/farstream/m4/as-ac-expand.m4
new file mode 100644
index 000000000..0c7117393
--- /dev/null
+++ b/farstream/m4/as-ac-expand.m4
@@ -0,0 +1,40 @@
+dnl AS_AC_EXPAND(VAR, CONFIGURE_VAR)
+dnl
+dnl example
+dnl AS_AC_EXPAND(SYSCONFDIR, $sysconfdir)
+dnl will set SYSCONFDIR to /usr/local/etc if prefix=/usr/local
+
+AC_DEFUN([AS_AC_EXPAND],
+[
+ EXP_VAR=[$1]
+ FROM_VAR=[$2]
+
+ dnl first expand prefix and exec_prefix if necessary
+ prefix_save=$prefix
+ exec_prefix_save=$exec_prefix
+
+ dnl if no prefix given, then use /usr/local, the default prefix
+ if test "x$prefix" = "xNONE"; then
+ prefix=$ac_default_prefix
+ fi
+ dnl if no exec_prefix given, then use prefix
+ if test "x$exec_prefix" = "xNONE"; then
+ exec_prefix=$prefix
+ fi
+
+ full_var="$FROM_VAR"
+ dnl loop until it doesn't change anymore
+ while true; do
+ new_full_var="`eval echo $full_var`"
+ if test "x$new_full_var"="x$full_var"; then break; fi
+ full_var=$new_full_var
+ done
+
+ dnl clean up
+ full_var=$new_full_var
+ AC_SUBST([$1], "$full_var")
+
+ dnl restore prefix and exec_prefix
+ prefix=$prefix_save
+ exec_prefix=$exec_prefix_save
+])
diff --git a/farstream/m4/as-compiler-flag.m4 b/farstream/m4/as-compiler-flag.m4
new file mode 100644
index 000000000..605708a5a
--- /dev/null
+++ b/farstream/m4/as-compiler-flag.m4
@@ -0,0 +1,33 @@
+dnl as-compiler-flag.m4 0.1.0
+
+dnl autostars m4 macro for detection of compiler flags
+
+dnl David Schleef <ds@schleef.org>
+
+dnl $Id: as-compiler-flag.m4,v 1.1 2005/06/18 18:02:46 burgerman Exp $
+
+dnl AS_COMPILER_FLAG(CFLAGS, ACTION-IF-ACCEPTED, [ACTION-IF-NOT-ACCEPTED])
+dnl Tries to compile with the given CFLAGS.
+dnl Runs ACTION-IF-ACCEPTED if the compiler can compile with the flags,
+dnl and ACTION-IF-NOT-ACCEPTED otherwise.
+
+AC_DEFUN([AS_COMPILER_FLAG],
+[
+ AC_MSG_CHECKING([to see if compiler understands $1])
+
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $1"
+
+ AC_TRY_COMPILE([ ], [], [flag_ok=yes], [flag_ok=no])
+ CFLAGS="$save_CFLAGS"
+
+ if test "X$flag_ok" = Xyes ; then
+ $2
+ true
+ else
+ $3
+ true
+ fi
+ AC_MSG_RESULT([$flag_ok])
+])
+
diff --git a/farstream/m4/as-version.m4 b/farstream/m4/as-version.m4
new file mode 100644
index 000000000..defc887e0
--- /dev/null
+++ b/farstream/m4/as-version.m4
@@ -0,0 +1,66 @@
+dnl as-version.m4 0.1.0
+
+dnl autostars m4 macro for versioning
+
+dnl Thomas Vander Stichele <thomas at apestaart dot org>
+
+dnl $Id: as-version.m4,v 1.1 2005/06/18 18:02:46 burgerman Exp $
+
+dnl AS_VERSION(PACKAGE, PREFIX, MAJOR, MINOR, MICRO, NANO,
+dnl ACTION-IF-NO-NANO, [ACTION-IF-NANO])
+
+dnl example
+dnl AS_VERSION(gstreamer, GST_VERSION, 0, 3, 2,)
+dnl for a 0.3.2 release version
+
+dnl this macro
+dnl - defines [$PREFIX]_MAJOR, MINOR and MICRO
+dnl - if NANO is empty, then we're in release mode, else in cvs/dev mode
+dnl - defines [$PREFIX], VERSION, and [$PREFIX]_RELEASE
+dnl - executes the relevant action
+dnl - AC_SUBST's PACKAGE, VERSION, [$PREFIX] and [$PREFIX]_RELEASE
+dnl as well as the little ones
+dnl - doesn't call AM_INIT_AUTOMAKE anymore because it prevents
+dnl maintainer mode from running ok
+dnl
+dnl don't forget to put #undef [$2] and [$2]_RELEASE in acconfig.h
+dnl if you use acconfig.h
+
+AC_DEFUN([AS_VERSION],
+[
+ PACKAGE=[$1]
+ [$2]_MAJOR=[$3]
+ [$2]_MINOR=[$4]
+ [$2]_MICRO=[$5]
+ NANO=[$6]
+ [$2]_NANO=$NANO
+ if test "x$NANO" = "x" || test "x$NANO" = "x0";
+ then
+ AC_MSG_NOTICE(configuring [$1] for release)
+ VERSION=[$3].[$4].[$5]
+ [$2]_RELEASE=1
+ dnl execute action
+ ifelse([$7], , :, [$7])
+ else
+ AC_MSG_NOTICE(configuring [$1] for development with nano $NANO)
+ VERSION=[$3].[$4].[$5].$NANO
+ [$2]_RELEASE=0.`date +%Y%m%d.%H%M%S`
+ dnl execute action
+ ifelse([$8], , :, [$8])
+ fi
+
+ [$2]=$VERSION
+ AC_DEFINE_UNQUOTED([$2], "$[$2]", [Define the version])
+ AC_SUBST([$2])
+ AC_DEFINE_UNQUOTED([$2]_RELEASE, "$[$2]_RELEASE", [Define the release version])
+ AC_SUBST([$2]_RELEASE)
+
+ AC_SUBST([$2]_MAJOR)
+ AC_SUBST([$2]_MINOR)
+ AC_SUBST([$2]_MICRO)
+ AC_SUBST([$2]_NANO)
+ AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Define the package name])
+ AC_SUBST(PACKAGE)
+ AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Define the version])
+ AC_SUBST(VERSION)
+])
diff --git a/farstream/python/Makefile.am b/farstream/python/Makefile.am
new file mode 100644
index 000000000..d1f3686c4
--- /dev/null
+++ b/farstream/python/Makefile.am
@@ -0,0 +1,48 @@
+SUBDIRS = codegen examples
+
+PYDEFS=`pkg-config --variable=defsdir pygtk-2.0`
+GSTPYDEFS=`pkg-config --variable=defsdir gst-python-0.10`
+
+noinst_HEADERS = pygstminiobject.h common.h
+
+AM_CPPFLAGS = \
+ -I. \
+ -I$(top_srcdir) \
+ -DDATADIR=\""$(datadir)"\" \
+ $(TELEPATHY_CFLAGS) \
+ $(PYTHON_INCLUDES) \
+ $(PYTPFARSTREAM_CFLAGS) \
+ $(FARSTREAM_CFLAGS) \
+ $(DBUS_CFLAGS)
+
+BUILT_SOURCES = \
+ pytpfarstream.c
+
+pytpfarstreamdir = $(pyexecdir)
+pytpfarstream_LTLIBRARIES = tpfarstream.la
+
+tpfarstream_la_SOURCES = \
+ pytpfarstreammodule.c pygstminiobject.c
+
+nodist_tpfarstream_la_SOURCES = pytpfarstream.c
+
+tpfarstream_la_LIBADD = \
+ $(PYTPFARSTREAM_LIBS) \
+ $(top_builddir)/telepathy-farstream/libtelepathy-farstream.la
+
+tpfarstream_la_LDFLAGS = -module -avoid-version
+
+pytpfarstream.c: pytpfarstream.override pytpfarstream.defs
+ $(PYTHON) $(top_srcdir)/python/codegen/codegen.py \
+ --prefix tf \
+ --register $(GSTPYDEFS)/gst-types.defs \
+ --override $(srcdir)/pytpfarstream.override \
+ $(srcdir)/pytpfarstream.defs > $@
+
+EXTRA_DIST = \
+ pytpfarstream.override \
+ pytpfarstream.defs \
+ pytpfarstream-filter.defs \
+ rebuild-defs.sh
+
+CLEANFILES = $(BUILT_SOURCES)
diff --git a/farstream/python/codegen/Makefile.am b/farstream/python/codegen/Makefile.am
new file mode 100644
index 000000000..dd3eea064
--- /dev/null
+++ b/farstream/python/codegen/Makefile.am
@@ -0,0 +1,15 @@
+EXTRA_DIST = \
+ argtypes.py \
+ code-coverage.py \
+ codegen.py \
+ definitions.py \
+ defsparser.py \
+ docextract.py \
+ docgen.py \
+ h2def.py \
+ __init__.py \
+ mergedefs.py \
+ mkskel.py \
+ override.py \
+ reversewrapper.py \
+ scmexpr.py
diff --git a/farstream/python/codegen/__init__.py b/farstream/python/codegen/__init__.py
new file mode 100644
index 000000000..cfa896ee6
--- /dev/null
+++ b/farstream/python/codegen/__init__.py
@@ -0,0 +1,15 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+
+__all__ = [
+ 'argtypes',
+ 'codegen',
+ 'definitions',
+ 'defsparser',
+ 'docextract',
+ 'docgen',
+ 'h2def',
+ 'mergedefs',
+ 'mkskel',
+ 'override',
+ 'scmexpr'
+]
diff --git a/farstream/python/codegen/argtypes.py b/farstream/python/codegen/argtypes.py
new file mode 100644
index 000000000..948bae106
--- /dev/null
+++ b/farstream/python/codegen/argtypes.py
@@ -0,0 +1,1075 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+import string
+import keyword
+import struct
+
+class VarList:
+ """Nicely format a C variable list"""
+ def __init__(self):
+ self.vars = {}
+ def add(self, ctype, name):
+ if self.vars.has_key(ctype):
+ self.vars[ctype] = self.vars[ctype] + (name,)
+ else:
+ self.vars[ctype] = (name,)
+ def __str__(self):
+ ret = []
+ for type in self.vars.keys():
+ ret.append(' ')
+ ret.append(type)
+ ret.append(' ')
+ ret.append(string.join(self.vars[type], ', '))
+ ret.append(';\n')
+ if ret:
+ ret.append('\n')
+ return string.join(ret, '')
+ return ''
+
+class WrapperInfo:
+ """A class that holds information about variable defs, code
+ snippets, etcd for use in writing out the function/method
+ wrapper."""
+ def __init__(self):
+ self.varlist = VarList()
+ self.parsestr = ''
+ self.parselist = ['', 'kwlist']
+ self.codebefore = []
+ self.codeafter = []
+ self.arglist = []
+ self.kwlist = []
+ def get_parselist(self):
+ return string.join(self.parselist, ', ')
+ def get_codebefore(self):
+ return string.join(self.codebefore, '')
+ def get_codeafter(self):
+ return string.join(self.codeafter, '')
+ def get_arglist(self):
+ return string.join(self.arglist, ', ')
+ def get_varlist(self):
+ return str(self.varlist)
+ def get_kwlist(self):
+ ret = ' static char *kwlist[] = { %s };\n' % \
+ string.join(self.kwlist + [ 'NULL' ], ', ')
+ if not self.get_varlist():
+ ret = ret + '\n'
+ return ret
+
+ def add_parselist(self, codes, parseargs, keywords):
+ self.parsestr = self.parsestr + codes
+ for arg in parseargs:
+ self.parselist.append(arg)
+ for kw in keywords:
+ if keyword.iskeyword(kw):
+ kw = kw + '_'
+ self.kwlist.append('"%s"' % kw)
+
+class ArgType:
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ """Add code to the WrapperInfo instance to handle
+ parameter."""
+ raise RuntimeError, "write_param not implemented for %s" % \
+ self.__class__.__name__
+ def write_return(self, ptype, ownsreturn, info):
+ """Adds a variable named ret of the return type to
+ info.varlist, and add any required code to info.codeafter to
+ convert the return value to a python object."""
+ raise RuntimeError, "write_return not implemented for %s" % \
+ self.__class__.__name__
+
+class NoneArg(ArgType):
+ def write_return(self, ptype, ownsreturn, info):
+ info.codeafter.append(' Py_INCREF(Py_None);\n' +
+ ' return Py_None;')
+
+class StringArg(ArgType):
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pdflt:
+ if pdflt != 'NULL': pdflt = '"' + pdflt + '"'
+ info.varlist.add('char', '*' + pname + ' = ' + pdflt)
+ else:
+ info.varlist.add('char', '*' + pname)
+ info.arglist.append(pname)
+ if pnull:
+ info.add_parselist('z', ['&' + pname], [pname])
+ else:
+ info.add_parselist('s', ['&' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ if ownsreturn:
+ # have to free result ...
+ info.varlist.add('gchar', '*ret')
+ info.codeafter.append(' if (ret) {\n' +
+ ' PyObject *py_ret = PyString_FromString(ret);\n' +
+ ' g_free(ret);\n' +
+ ' return py_ret;\n' +
+ ' }\n' +
+ ' Py_INCREF(Py_None);\n' +
+ ' return Py_None;')
+ else:
+ info.varlist.add('const gchar', '*ret')
+ info.codeafter.append(' if (ret)\n' +
+ ' return PyString_FromString(ret);\n'+
+ ' Py_INCREF(Py_None);\n' +
+ ' return Py_None;')
+
+class UCharArg(ArgType):
+ # allows strings with embedded NULLs.
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pdflt:
+ info.varlist.add('guchar', '*' + pname + ' = "' + pdflt + '"')
+ else:
+ info.varlist.add('guchar', '*' + pname)
+ info.varlist.add('int', pname + '_len')
+ info.arglist.append(pname)
+ if pnull:
+ info.add_parselist('z#', ['&' + pname, '&' + pname + '_len'],
+ [pname])
+ else:
+ info.add_parselist('s#', ['&' + pname, '&' + pname + '_len'],
+ [pname])
+
+class CharArg(ArgType):
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pdflt:
+ info.varlist.add('char', pname + " = '" + pdflt + "'")
+ else:
+ info.varlist.add('char', pname)
+ info.arglist.append(pname)
+ info.add_parselist('c', ['&' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add('gchar', 'ret')
+ info.codeafter.append(' return PyString_FromStringAndSize(&ret, 1);')
+class GUniCharArg(ArgType):
+ ret_tmpl = ('#if !defined(Py_UNICODE_SIZE) || Py_UNICODE_SIZE == 2\n'
+ ' if (ret > 0xffff) {\n'
+ ' PyErr_SetString(PyExc_RuntimeError, "returned character can not be represented in 16-bit unicode");\n'
+ ' return NULL;\n'
+ ' }\n'
+ '#endif\n'
+ ' py_ret = (Py_UNICODE)ret;\n'
+ ' return PyUnicode_FromUnicode(&py_ret, 1);\n')
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pdflt:
+ info.varlist.add('gunichar', pname + " = '" + pdflt + "'")
+ else:
+ info.varlist.add('gunichar', pname)
+ info.arglist.append(pname)
+ info.add_parselist('O&', ['pyg_pyobj_to_unichar_conv', '&' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add('gunichar', 'ret')
+ info.varlist.add('Py_UNICODE', 'py_ret')
+ info.codeafter.append(self.ret_tmpl)
+
+
+class IntArg(ArgType):
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pdflt:
+ info.varlist.add('int', pname + ' = ' + pdflt)
+ else:
+ info.varlist.add('int', pname)
+ info.arglist.append(pname)
+ info.add_parselist('i', ['&' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add('int', 'ret')
+ info.codeafter.append(' return PyInt_FromLong(ret);')
+
+class UIntArg(ArgType):
+ dflt = (' if (py_%(name)s) {\n'
+ ' if (PyLong_Check(py_%(name)s))\n'
+ ' %(name)s = PyLong_AsUnsignedLong(py_%(name)s);\n'
+ ' else if (PyInt_Check(py_%(name)s))\n'
+ ' %(name)s = PyInt_AsLong(py_%(name)s);\n'
+ ' else\n'
+ ' PyErr_SetString(PyExc_TypeError, "Parameter \'%(name)s\' must be an int or a long");\n'
+ ' if (PyErr_Occurred())\n'
+ ' return NULL;\n'
+ ' }\n')
+ before = (' if (PyLong_Check(py_%(name)s))\n'
+ ' %(name)s = PyLong_AsUnsignedLong(py_%(name)s);\n'
+ ' else if (PyInt_Check(py_%(name)s))\n'
+ ' %(name)s = PyInt_AsLong(py_%(name)s);\n'
+ ' else\n'
+ ' PyErr_SetString(PyExc_TypeError, "Parameter \'%(name)s\' must be an int or a long");\n'
+ ' if (PyErr_Occurred())\n'
+ ' return NULL;\n')
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if not pdflt:
+ pdflt = '0';
+
+ info.varlist.add(ptype, pname + ' = ' + pdflt)
+ info.codebefore.append(self.dflt % {'name':pname})
+ info.varlist.add('PyObject', "*py_" + pname + ' = NULL')
+ info.arglist.append(pname)
+ info.add_parselist('O', ['&py_' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add(ptype, 'ret')
+ info.codeafter.append(' return PyLong_FromUnsignedLong(ret);')
+
+class SizeArg(ArgType):
+
+ if struct.calcsize('P') <= struct.calcsize('l'):
+ llp64 = True
+ else:
+ llp64 = False
+
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pdflt:
+ info.varlist.add(ptype, pname + ' = ' + pdflt)
+ else:
+ info.varlist.add(ptype, pname)
+ info.arglist.append(pname)
+ if self.llp64:
+ info.add_parselist('k', ['&' + pname], [pname])
+ else:
+ info.add_parselist('K', ['&' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add(ptype, 'ret')
+ if self.llp64:
+ info.codeafter.append(' return PyLong_FromUnsignedLongLong(ret);\n')
+ else:
+ info.codeafter.append(' return PyLong_FromUnsignedLong(ret);\n')
+
+class SSizeArg(ArgType):
+
+ if struct.calcsize('P') <= struct.calcsize('l'):
+ llp64 = True
+ else:
+ llp64 = False
+
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pdflt:
+ info.varlist.add(ptype, pname + ' = ' + pdflt)
+ else:
+ info.varlist.add(ptype, pname)
+ info.arglist.append(pname)
+ if self.llp64:
+ info.add_parselist('l', ['&' + pname], [pname])
+ else:
+ info.add_parselist('L', ['&' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add(ptype, 'ret')
+ if self.llp64:
+ info.codeafter.append(' return PyLong_FromLongLong(ret);\n')
+ else:
+ info.codeafter.append(' return PyLong_FromLong(ret);\n')
+
+class LongArg(ArgType):
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pdflt:
+ info.varlist.add(ptype, pname + ' = ' + pdflt)
+ else:
+ info.varlist.add(ptype, pname)
+ info.arglist.append(pname)
+ info.add_parselist('l', ['&' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add(ptype, 'ret')
+ info.codeafter.append(' return PyInt_FromLong(ret);\n')
+
+class BoolArg(IntArg):
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add('int', 'ret')
+ info.codeafter.append(' return PyBool_FromLong(ret);\n')
+
+class TimeTArg(ArgType):
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pdflt:
+ info.varlist.add('time_t', pname + ' = ' + pdflt)
+ else:
+ info.varlist.add('time_t', pname)
+ info.arglist.append(pname)
+ info.add_parselist('i', ['&' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add('time_t', 'ret')
+ info.codeafter.append(' return PyInt_FromLong(ret);')
+
+class ULongArg(ArgType):
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pdflt:
+ info.varlist.add('unsigned long', pname + ' = ' + pdflt)
+ else:
+ info.varlist.add('unsigned long', pname)
+ info.arglist.append(pname)
+ info.add_parselist('k', ['&' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add(ptype, 'ret')
+ info.codeafter.append(' return PyLong_FromUnsignedLong(ret);\n')
+
+class UInt32Arg(ULongArg):
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ ULongArg.write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info)
+ ## if sizeof(unsigned long) > sizeof(unsigned int), we need to
+ ## check the value is within guint32 range
+ if struct.calcsize('L') > struct.calcsize('I'):
+ info.codebefore.append((
+ ' if (%(pname)s > G_MAXUINT32) {\n'
+ ' PyErr_SetString(PyExc_ValueError,\n'
+ ' "Value out of range in conversion of"\n'
+ ' " %(pname)s parameter to unsigned 32 bit integer");\n'
+ ' return NULL;\n'
+ ' }\n') % vars())
+
+class Int64Arg(ArgType):
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pdflt:
+ info.varlist.add('gint64', pname + ' = ' + pdflt)
+ else:
+ info.varlist.add('gint64', pname)
+ info.arglist.append(pname)
+ info.add_parselist('L', ['&' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add('gint64', 'ret')
+ info.codeafter.append(' return PyLong_FromLongLong(ret);')
+
+class UInt64Arg(ArgType):
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pdflt:
+ info.varlist.add('guint64', pname + ' = ' + pdflt)
+ else:
+ info.varlist.add('guint64', pname)
+ info.arglist.append(pname)
+ info.add_parselist('K', ['&' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add('guint64', 'ret')
+ info.codeafter.append(' return PyLong_FromUnsignedLongLong(ret);')
+
+
+class DoubleArg(ArgType):
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pdflt:
+ info.varlist.add('double', pname + ' = ' + pdflt)
+ else:
+ info.varlist.add('double', pname)
+ info.arglist.append(pname)
+ info.add_parselist('d', ['&' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add('double', 'ret')
+ info.codeafter.append(' return PyFloat_FromDouble(ret);')
+
+class FileArg(ArgType):
+ nulldflt = (' if (py_%(name)s == Py_None)\n'
+ ' %(name)s = NULL;\n'
+ ' else if (py_%(name)s && PyFile_Check(py_%(name)s)\n'
+ ' %s = PyFile_AsFile(py_%(name)s);\n'
+ ' else if (py_%(name)s) {\n'
+ ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a file object or None");\n'
+ ' return NULL;\n'
+ ' }')
+ null = (' if (py_%(name)s && PyFile_Check(py_%(name)s)\n'
+ ' %(name)s = PyFile_AsFile(py_%(name)s);\n'
+ ' else if (py_%(name)s != Py_None) {\n'
+ ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a file object or None");\n'
+ ' return NULL;\n'
+ ' }\n')
+ dflt = (' if (py_%(name)s)\n'
+ ' %(name)s = PyFile_AsFile(py_%(name)s);\n')
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pnull:
+ if pdflt:
+ info.varlist.add('FILE', '*' + pname + ' = ' + pdflt)
+ info.varlist.add('PyObject', '*py_' + pname + ' = NULL')
+ info.codebefore.append(self.nulldflt % {'name':pname})
+ else:
+ info.varlist.add('FILE', '*' + pname + ' = NULL')
+ info.varlist.add('PyObject', '*py_' + pname)
+ info.codebefore.append(self.null & {'name':pname})
+ info.arglist.appned(pname)
+ info.add_parselist('O', ['&py_' + pname], [pname])
+ else:
+ if pdflt:
+ info.varlist.add('FILE', '*' + pname + ' = ' + pdflt)
+ info.varlist.add('PyObject', '*py_' + pname + ' = NULL')
+ info.codebefore.append(self.dflt % {'name':pname})
+ info.arglist.append(pname)
+ else:
+ info.varlist.add('PyObject', '*' + pname)
+ info.arglist.append('PyFile_AsFile(' + pname + ')')
+ info.add_parselist('O!', ['&PyFile_Type', '&' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add('FILE', '*ret')
+ info.codeafter.append(' if (ret)\n' +
+ ' return PyFile_FromFile(ret, "", "", fclose);\n' +
+ ' Py_INCREF(Py_None);\n' +
+ ' return Py_None;')
+
+class EnumArg(ArgType):
+ enum = (' if (pyg_enum_get_value(%(typecode)s, py_%(name)s, (gint *)&%(name)s))\n'
+ ' return NULL;\n')
+ def __init__(self, enumname, typecode):
+ self.enumname = enumname
+ self.typecode = typecode
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pdflt:
+ info.varlist.add(self.enumname, pname + ' = ' + pdflt)
+ else:
+ info.varlist.add(self.enumname, pname)
+ info.varlist.add('PyObject', '*py_' + pname + ' = NULL')
+ info.codebefore.append(self.enum % { 'typecode': self.typecode,
+ 'name': pname})
+ info.arglist.append(pname)
+ info.add_parselist('O', ['&py_' + pname], [pname]);
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add('gint', 'ret')
+ info.codeafter.append(' return pyg_enum_from_gtype(%s, ret);' % self.typecode)
+
+class FlagsArg(ArgType):
+ flag = (' if (%(default)spyg_flags_get_value(%(typecode)s, py_%(name)s, (gint *)&%(name)s))\n'
+ ' return NULL;\n')
+ def __init__(self, flagname, typecode):
+ self.flagname = flagname
+ self.typecode = typecode
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pdflt:
+ info.varlist.add(self.flagname, pname + ' = ' + pdflt)
+ default = "py_%s && " % (pname,)
+ else:
+ info.varlist.add(self.flagname, pname)
+ default = ""
+ info.varlist.add('PyObject', '*py_' + pname + ' = NULL')
+ info.codebefore.append(self.flag % {'default':default,
+ 'typecode':self.typecode,
+ 'name':pname})
+ info.arglist.append(pname)
+ info.add_parselist('O', ['&py_' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add('guint', 'ret')
+ info.codeafter.append(' return pyg_flags_from_gtype(%s, ret);' % self.typecode)
+
+class ObjectArg(ArgType):
+ # should change these checks to more typesafe versions that check
+ # a little further down in the class heirachy.
+ nulldflt = (' if ((PyObject *)py_%(name)s == Py_None)\n'
+ ' %(name)s = NULL;\n'
+ ' else if (py_%(name)s && pygobject_check(py_%(name)s, &Py%(type)s_Type))\n'
+ ' %(name)s = %(cast)s(py_%(name)s->obj);\n'
+ ' else if (py_%(name)s) {\n'
+ ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(type)s or None");\n'
+ ' return NULL;\n'
+ ' }\n')
+ null = (' if (py_%(name)s && pygobject_check(py_%(name)s, &Py%(type)s_Type))\n'
+ ' %(name)s = %(cast)s(py_%(name)s->obj);\n'
+ ' else if ((PyObject *)py_%(name)s != Py_None) {\n'
+ ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(type)s or None");\n'
+ ' return NULL;\n'
+ ' }\n')
+ dflt = ' if (py_%(name)s)\n' \
+ ' %(name)s = %(cast)s(py_%(name)s->obj);\n'
+ def __init__(self, objname, parent, typecode):
+ self.objname = objname
+ self.cast = string.replace(typecode, '_TYPE_', '_', 1)
+ self.parent = parent
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pnull:
+ if pdflt:
+ info.varlist.add(self.objname, '*' + pname + ' = ' + pdflt)
+ info.varlist.add('PyGObject', '*py_' + pname + ' = NULL')
+ info.codebefore.append(self.nulldflt % {'name':pname,
+ 'cast':self.cast,
+ 'type':self.objname})
+ else:
+ info.varlist.add(self.objname, '*' + pname + ' = NULL')
+ info.varlist.add('PyGObject', '*py_' + pname)
+ info.codebefore.append(self.null % {'name':pname,
+ 'cast':self.cast,
+ 'type':self.objname})
+ if ptype.endswith('*'):
+ typename = ptype[:-1]
+ try:
+ const, typename = typename.split('const-')
+ except ValueError:
+ const = ''
+ if typename != ptype:
+ info.arglist.append('(%s *) %s' % (ptype[:-1], pname))
+ else:
+ info.arglist.append(pname)
+
+ info.add_parselist('O', ['&py_' + pname], [pname])
+ else:
+ if pdflt:
+ info.varlist.add(self.objname, '*' + pname + ' = ' + pdflt)
+ info.varlist.add('PyGObject', '*py_' + pname + ' = NULL')
+ info.codebefore.append(self.dflt % {'name':pname,
+ 'cast':self.cast})
+ info.arglist.append(pname)
+ info.add_parselist('O!', ['&Py%s_Type' % self.objname,
+ '&py_' + pname], [pname])
+ else:
+ info.varlist.add('PyGObject', '*' + pname)
+ info.arglist.append('%s(%s->obj)' % (self.cast, pname))
+ info.add_parselist('O!', ['&Py%s_Type' % self.objname,
+ '&' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ if ptype.endswith('*'):
+ typename = ptype[:-1]
+ try:
+ const, typename = typename.split('const-')
+ except ValueError:
+ const = ''
+ info.varlist.add(typename, '*ret')
+ if ownsreturn:
+ info.varlist.add('PyObject', '*py_ret')
+ # < GLib 2.8: using our custom _new and _unref functions
+ # makes sure we update the proper GstObject refcount
+ info.codeafter.append(' py_ret = pygobject_new((GObject *)ret);\n'
+ ' if (ret != NULL)\n'
+ ' g_object_unref((GObject *)ret);\n'
+ ' return py_ret;')
+ else:
+ info.codeafter.append(' /* pygobject_new handles NULL checking */\n' +
+ ' return pygobject_new((GObject *)ret);')
+
+class MiniObjectArg(ArgType):
+ # should change these checks to more typesafe versions that check
+ # a little further down in the class heirachy.
+ nulldflt = (' if ((PyObject *)py_%(name)s == Py_None)\n'
+ ' %(name)s = NULL;\n'
+ ' else if (py_%(name)s) && pygstminiobject_check(py_%(name)s, &Py%(type)s_Type))\n'
+ ' %(name)s = %(cast)s(py_%(name)s->obj);\n'
+ ' else if (py_%(name)s) {\n'
+ ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(type)s or None");\n'
+ ' return NULL;\n'
+ ' }\n')
+ null = (' if (py_%(name)s && pygstminiobject_check(py_%(name)s, &Py%(type)s_Type))\n'
+ ' %(name)s = %(cast)s(py_%(name)s->obj);\n'
+ ' else if ((PyObject *)py_%(name)s != Py_None) {\n'
+ ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(type)s or None");\n'
+ ' return NULL;\n'
+ ' }\n')
+ dflt = ' if (py_%(name)s)\n' \
+ ' %(name)s = %(cast)s(py_%(name)s->obj);\n'
+ def __init__(self, objname, parent, typecode):
+ self.objname = objname
+ self.cast = string.replace(typecode, '_TYPE_', '_', 1)
+ self.parent = parent
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pnull:
+ if pdflt:
+ info.varlist.add(self.objname, '*' + pname + ' = ' + pdflt)
+ info.varlist.add('PyGstMiniObject', '*py_' + pname + ' = NULL')
+ info.codebefore.append(self.nulldflt % {'name':pname,
+ 'cast':self.cast,
+ 'type':self.objname})
+ else:
+ info.varlist.add(self.objname, '*' + pname + ' = NULL')
+ info.varlist.add('PyGstMiniObject', '*py_' + pname)
+ info.codebefore.append(self.null % {'name':pname,
+ 'cast':self.cast,
+ 'type':self.objname})
+ if ptype.endswith('*'):
+ typename = ptype[:-1]
+ try:
+ const, typename = typename.split('const-')
+ except ValueError:
+ const = ''
+ if typename != ptype:
+ info.arglist.append('(%s *) %s' % (ptype[:-1], pname))
+ else:
+ info.arglist.append(pname)
+
+ info.add_parselist('O', ['&py_' + pname], [pname])
+ else:
+ if pdflt:
+ info.varlist.add(self.objname, '*' + pname + ' = ' + pdflt)
+ info.varlist.add('PyGstMiniObject', '*py_' + pname + ' = NULL')
+ info.codebefore.append(self.dflt % {'name':pname,
+ 'cast':self.cast})
+ info.arglist.append(pname)
+ info.add_parselist('O', ['&Py%s_Type' % self.objname,
+ '&py_' + pname], [pname])
+ else:
+ info.varlist.add('PyGstMiniObject', '*' + pname)
+ info.arglist.append('%s(%s->obj)' % (self.cast, pname))
+ info.add_parselist('O!', ['&Py%s_Type' % self.objname,
+ '&' + pname], [pname])
+ if keeprefcount:
+ info.codebefore.append(' gst_mini_object_ref(GST_MINI_OBJECT(%s->obj));\n' % pname)
+ def write_return(self, ptype, ownsreturn, info):
+ if ptype.endswith('*'):
+ typename = ptype[:-1]
+ try:
+ const, typename = typename.split('const-')
+ except ValueError:
+ const = ''
+ info.varlist.add(typename, '*ret')
+ if ownsreturn:
+ info.varlist.add('PyObject', '*py_ret')
+ info.codeafter.append(' py_ret = pygstminiobject_new((GstMiniObject *)ret);\n'
+ ' if (ret != NULL)\n'
+ ' gst_mini_object_unref((GstMiniObject *)ret);\n'
+ ' return py_ret;')
+ else:
+ info.codeafter.append(' /* pygobject_new handles NULL checking */\n' +
+ ' return pygstminiobject_new((GstMiniObject *)ret);')
+
+class BoxedArg(ArgType):
+ # haven't done support for default args. Is it needed?
+ check = (' if (pyg_boxed_check(py_%(name)s, %(typecode)s))\n'
+ ' %(name)s = pyg_boxed_get(py_%(name)s, %(typename)s);\n'
+ ' else {\n'
+ ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(typename)s");\n'
+ ' return NULL;\n'
+ ' }\n')
+ null = (' if (pyg_boxed_check(py_%(name)s, %(typecode)s))\n'
+ ' %(name)s = pyg_boxed_get(py_%(name)s, %(typename)s);\n'
+ ' else if (py_%(name)s != Py_None) {\n'
+ ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(typename)s or None");\n'
+ ' return NULL;\n'
+ ' }\n')
+ def __init__(self, ptype, typecode):
+ self.typename = ptype
+ self.typecode = typecode
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pnull:
+ info.varlist.add(self.typename, '*' + pname + ' = NULL')
+ info.varlist.add('PyObject', '*py_' + pname + ' = Py_None')
+ info.codebefore.append(self.null % {'name': pname,
+ 'typename': self.typename,
+ 'typecode': self.typecode})
+ else:
+ info.varlist.add(self.typename, '*' + pname + ' = NULL')
+ info.varlist.add('PyObject', '*py_' + pname)
+ info.codebefore.append(self.check % {'name': pname,
+ 'typename': self.typename,
+ 'typecode': self.typecode})
+ if ptype[-1] == '*':
+ typename = ptype[:-1]
+ if typename[:6] == 'const-': typename = typename[6:]
+ if typename != self.typename:
+ info.arglist.append('(%s *)%s' % (ptype[:-1], pname))
+ else:
+ info.arglist.append(pname)
+ else:
+ info.arglist.append(pname)
+ info.add_parselist('O', ['&py_' + pname], [pname])
+ ret_tmpl = ' /* pyg_boxed_new handles NULL checking */\n' \
+ ' return pyg_boxed_new(%(typecode)s, %(ret)s, %(copy)s, TRUE);'
+ def write_return(self, ptype, ownsreturn, info):
+ if ptype[-1] == '*':
+ info.varlist.add(self.typename, '*ret')
+ ret = 'ret'
+ else:
+ info.varlist.add(self.typename, 'ret')
+ ret = '&ret'
+ ownsreturn = 0 # of course it can't own a ref to a local var ...
+ info.codeafter.append(self.ret_tmpl %
+ { 'typecode': self.typecode,
+ 'ret': ret,
+ 'copy': ownsreturn and 'FALSE' or 'TRUE'})
+
+class CustomBoxedArg(ArgType):
+ # haven't done support for default args. Is it needed?
+ null = (' if (%(check)s(py_%(name)s))\n'
+ ' %(name)s = %(get)s(py_%(name)s);\n'
+ ' else if (py_%(name)s != Py_None) {\n'
+ ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(type)s or None");\n'
+ ' return NULL;\n'
+ ' }\n')
+ def __init__(self, ptype, pytype, getter, new):
+ self.pytype = pytype
+ self.getter = getter
+ self.checker = 'Py' + ptype + '_Check'
+ self.new = new
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pnull:
+ info.varlist.add(ptype[:-1], '*' + pname + ' = NULL')
+ info.varlist.add('PyObject', '*py_' + pname + ' = Py_None')
+ info.codebefore.append(self.null % {'name': pname,
+ 'get': self.getter,
+ 'check': self.checker,
+ 'type': ptype[:-1]})
+ info.arglist.append(pname)
+ info.add_parselist('O', ['&py_' + pname], [pname])
+ else:
+ info.varlist.add('PyObject', '*' + pname)
+ info.arglist.append(self.getter + '(' + pname + ')')
+ info.add_parselist('O!', ['&' + self.pytype, '&' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add(ptype[:-1], '*ret')
+ info.codeafter.append(' if (ret)\n' +
+ ' return ' + self.new + '(ret);\n' +
+ ' Py_INCREF(Py_None);\n' +
+ ' return Py_None;')
+
+class PointerArg(ArgType):
+ # haven't done support for default args. Is it needed?
+ check = (' if (pyg_pointer_check(py_%(name)s, %(typecode)s))\n'
+ ' %(name)s = pyg_pointer_get(py_%(name)s, %(typename)s);\n'
+ ' else {\n'
+ ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(typename)s");\n'
+ ' return NULL;\n'
+ ' }\n')
+ null = (' if (pyg_pointer_check(py_%(name)s, %(typecode)s))\n'
+ ' %(name)s = pyg_pointer_get(py_%(name)s, %(typename)s);\n'
+ ' else if (py_%(name)s != Py_None) {\n'
+ ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(typename)s or None");\n'
+ ' return NULL;\n'
+ ' }\n')
+ def __init__(self, ptype, typecode):
+ self.typename = ptype
+ self.typecode = typecode
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pnull:
+ info.varlist.add(self.typename, '*' + pname + ' = NULL')
+ info.varlist.add('PyObject', '*py_' + pname + ' = Py_None')
+ info.codebefore.append(self.null % {'name': pname,
+ 'typename': self.typename,
+ 'typecode': self.typecode})
+ else:
+ info.varlist.add(self.typename, '*' + pname + ' = NULL')
+ info.varlist.add('PyObject', '*py_' + pname)
+ info.codebefore.append(self.check % {'name': pname,
+ 'typename': self.typename,
+ 'typecode': self.typecode})
+ info.arglist.append(pname)
+ info.add_parselist('O', ['&py_' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ if ptype[-1] == '*':
+ info.varlist.add(self.typename, '*ret')
+ info.codeafter.append(' /* pyg_pointer_new handles NULL checking */\n' +
+ ' return pyg_pointer_new(' + self.typecode + ', ret);')
+ else:
+ info.varlist.add(self.typename, 'ret')
+ info.codeafter.append(' /* pyg_pointer_new handles NULL checking */\n' +
+ ' return pyg_pointer_new(' + self.typecode + ', &ret);')
+
+class AtomArg(IntArg):
+ dflt = ' if (py_%(name)s) {\n' \
+ ' %(name)s = pygdk_atom_from_pyobject(py_%(name)s);\n' \
+ ' if (PyErr_Occurred())\n' \
+ ' return NULL;\n' \
+ ' }\n'
+ atom = (' %(name)s = pygdk_atom_from_pyobject(py_%(name)s);\n'
+ ' if (PyErr_Occurred())\n'
+ ' return NULL;\n')
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pdflt:
+ info.varlist.add('GdkAtom', pname + ' = ' + pdflt)
+ info.varlist.add('PyObject', '*py_' + pname + ' = NULL')
+ info.codebefore.append(self.dflt % {'name': pname})
+ else:
+ info.varlist.add('GdkAtom', pname)
+ info.varlist.add('PyObject', '*py_' + pname + ' = NULL')
+ info.codebefore.append(self.atom % {'name': pname})
+ info.arglist.append(pname)
+ info.add_parselist('O', ['&py_' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add('GdkAtom', 'ret')
+ info.varlist.add('PyObject *', 'py_ret')
+ info.varlist.add('gchar *', 'name')
+ info.codeafter.append(' name = gdk_atom_name(ret);\n'
+ ' py_ret = PyString_FromString(name);\n'
+ ' g_free(name);\n'
+ ' return py_ret;')
+
+class GTypeArg(ArgType):
+ gtype = (' if ((%(name)s = pyg_type_from_object(py_%(name)s)) == 0)\n'
+ ' return NULL;\n')
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ info.varlist.add('GType', pname)
+ info.varlist.add('PyObject', '*py_' + pname + ' = NULL')
+ info.codebefore.append(self.gtype % {'name': pname})
+ info.arglist.append(pname)
+ info.add_parselist('O', ['&py_' + pname], [pname])
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add('GType', 'ret')
+ info.codeafter.append(' return pyg_type_wrapper_new(ret);')
+
+# simple GError handler.
+class GErrorArg(ArgType):
+ handleerror = (' if (pyg_error_check(&%(name)s))\n'
+ ' return NULL;\n')
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ info.varlist.add('GError', '*' + pname + ' = NULL')
+ info.arglist.append('&' + pname)
+ info.codeafter.append(self.handleerror % { 'name': pname })
+
+class GtkTreePathArg(ArgType):
+ # haven't done support for default args. Is it needed?
+ normal = (' %(name)s = pygtk_tree_path_from_pyobject(py_%(name)s);\n'
+ ' if (!%(name)s) {\n'
+ ' PyErr_SetString(PyExc_TypeError, "could not convert %(name)s to a GtkTreePath");\n'
+ ' return NULL;\n'
+ ' }\n')
+ null = (' if (py_%(name)s != Py_None) {\n'
+ ' %(name)s = pygtk_tree_path_from_pyobject(py_%(name)s);\n'
+ ' if (!%(name)s) {\n'
+ ' PyErr_SetString(PyExc_TypeError, "could not convert %(name)s to a GtkTreePath");\n'
+ ' return NULL;\n'
+ ' }\n'
+ ' }\n')
+ freepath = (' if (%(name)s)\n'
+ ' gtk_tree_path_free(%(name)s);\n')
+ def __init__(self):
+ pass
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pnull:
+ info.varlist.add('GtkTreePath', '*' + pname + ' = NULL')
+ info.varlist.add('PyObject', '*py_' + pname + ' = Py_None')
+ info.codebefore.append(self.null % {'name': pname})
+ info.arglist.append(pname)
+ info.add_parselist('O', ['&py_' + pname], [pname])
+ else:
+ info.varlist.add('GtkTreePath', '*' + pname)
+ info.varlist.add('PyObject', '*py_' + pname)
+ info.codebefore.append(self.normal % {'name': pname})
+ info.arglist.append(pname)
+ info.add_parselist('O', ['&py_' + pname], [pname])
+ info.codeafter.append(self.freepath % {'name': pname})
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add('GtkTreePath', '*ret')
+ if ownsreturn:
+ info.codeafter.append(' if (ret) {\n'
+ ' PyObject *py_ret = pygtk_tree_path_to_pyobject(ret);\n'
+ ' gtk_tree_path_free(ret);\n'
+ ' return py_ret;\n'
+ ' }\n'
+ ' Py_INCREF(Py_None);\n'
+ ' return Py_None;')
+ else:
+ info.codeafter.append(' if (ret) {\n'
+ ' PyObject *py_ret = pygtk_tree_path_to_pyobject(ret);\n'
+ ' return py_ret;\n'
+ ' }\n'
+ ' Py_INCREF(Py_None);\n'
+ ' return Py_None;')
+
+class GdkRectanglePtrArg(ArgType):
+ normal = (' if (!pygdk_rectangle_from_pyobject(py_%(name)s, &%(name)s))\n'
+ ' return NULL;\n')
+ null = (' if (py_%(name)s == Py_None)\n'
+ ' %(name)s = NULL;\n'
+ ' else if (pygdk_rectangle_from_pyobject(py_%(name)s, &%(name)s_rect))\n'
+ ' %(name)s = &%(name)s_rect;\n'
+ ' else\n'
+ ' return NULL;\n')
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ if pnull:
+ info.varlist.add('GdkRectangle', pname + '_rect = { 0, 0, 0, 0 }')
+ info.varlist.add('GdkRectangle', '*' + pname)
+ info.varlist.add('PyObject', '*py_' + pname + ' = Py_None')
+ info.add_parselist('O', ['&py_' + pname], [pname])
+ info.arglist.append(pname)
+ info.codebefore.append(self.null % {'name': pname})
+ else:
+ info.varlist.add('GdkRectangle', pname + ' = { 0, 0, 0, 0 }')
+ info.varlist.add('PyObject', '*py_' + pname)
+ info.add_parselist('O', ['&py_' + pname], [pname])
+ info.arglist.append('&' + pname)
+ info.codebefore.append(self.normal % {'name': pname})
+
+class GdkRectangleArg(ArgType):
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add('GdkRectangle', 'ret')
+ info.codeafter.append(' return pyg_boxed_new(GDK_TYPE_RECTANGLE, &ret, TRUE, TRUE);')
+
+class PyObjectArg(ArgType):
+ def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info):
+ info.varlist.add('PyObject', '*' + pname)
+ info.add_parselist('O', ['&' + pname], [pname])
+ info.arglist.append(pname)
+ def write_return(self, ptype, ownsreturn, info):
+ info.varlist.add("PyObject", "*ret")
+ if ownsreturn:
+ info.codeafter.append(' if (ret) {\n'
+ ' return ret;\n'
+ ' }\n'
+ ' Py_INCREF(Py_None);\n'
+ ' return Py_None;')
+ else:
+ info.codeafter.append(' if (!ret) ret = Py_None;\n'
+ ' Py_INCREF(ret);\n'
+ ' return ret;')
+
+class ArgMatcher:
+ def __init__(self):
+ self.argtypes = {}
+ self.reverse_argtypes = {}
+ self.reverse_rettypes = {}
+
+ def register(self, ptype, handler):
+ self.argtypes[ptype] = handler
+ def register_reverse(self, ptype, handler):
+ self.reverse_argtypes[ptype] = handler
+ def register_reverse_ret(self, ptype, handler):
+ self.reverse_rettypes[ptype] = handler
+
+ def register_enum(self, ptype, typecode):
+ if typecode is None:
+ typecode = "G_TYPE_NONE"
+ self.register(ptype, EnumArg(ptype, typecode))
+ def register_flag(self, ptype, typecode):
+ if typecode is None:
+ typecode = "G_TYPE_NONE"
+ self.register(ptype, FlagsArg(ptype, typecode))
+ def register_object(self, ptype, parent, typecode):
+ oa = ObjectArg(ptype, parent, typecode)
+ self.register(ptype, oa) # in case I forget the * in the .defs
+ self.register(ptype+'*', oa)
+ self.register('const-'+ptype+'*', oa)
+ if ptype == 'GdkPixmap':
+ # hack to handle GdkBitmap synonym.
+ self.register('GdkBitmap', oa)
+ self.register('GdkBitmap*', oa)
+ def register_miniobject(self, ptype, parent, typecode):
+ oa = MiniObjectArg(ptype, parent, typecode)
+ self.register(ptype, oa) # in case I forget the * in the .defs
+ self.register(ptype+'*', oa)
+ def register_boxed(self, ptype, typecode):
+ if self.argtypes.has_key(ptype): return
+ arg = BoxedArg(ptype, typecode)
+ self.register(ptype, arg)
+ self.register(ptype+'*', arg)
+ self.register('const-'+ptype+'*', arg)
+ def register_custom_boxed(self, ptype, pytype, getter, new):
+ arg = CustomBoxedArg(ptype, pytype, getter, new)
+ self.register(ptype+'*', arg)
+ self.register('const-'+ptype+'*', arg)
+ def register_pointer(self, ptype, typecode):
+ arg = PointerArg(ptype, typecode)
+ self.register(ptype, arg)
+ self.register(ptype+'*', arg)
+ self.register('const-'+ptype+'*', arg)
+
+ def get(self, ptype):
+ try:
+ return self.argtypes[ptype]
+ except KeyError:
+ if ptype[:8] == 'GdkEvent' and ptype[-1] == '*':
+ return self.argtypes['GdkEvent*']
+ raise
+ def _get_reverse_common(self, ptype, registry):
+ props = dict(c_type=ptype)
+ try:
+ return registry[ptype], props
+ except KeyError:
+ try:
+ handler = self.argtypes[ptype]
+ except KeyError:
+ if ptype.startswith('GdkEvent') and ptype.endswith('*'):
+ handler = self.argtypes['GdkEvent*']
+ else:
+ raise
+ if isinstance(handler, ObjectArg):
+ return registry['GObject*'], props
+ elif isinstance(handler, EnumArg):
+ props['typecode'] = handler.typecode
+ props['enumname'] = handler.enumname
+ return registry['GEnum'], props
+ elif isinstance(handler, FlagsArg):
+ props['typecode'] = handler.typecode
+ props['flagname'] = handler.flagname
+ return registry['GFlags'], props
+ elif isinstance(handler, BoxedArg):
+ props['typecode'] = handler.typecode
+ props['typename'] = handler.typename
+ return registry['GBoxed'], props
+ else:
+ raise
+ def get_reverse(self, ptype):
+ return self._get_reverse_common(ptype, self.reverse_argtypes)
+ def get_reverse_ret(self, ptype):
+ return self._get_reverse_common(ptype, self.reverse_rettypes)
+
+ def object_is_a(self, otype, parent):
+ if otype == None: return 0
+ if otype == parent: return 1
+ if not self.argtypes.has_key(otype): return 0
+ return self.object_is_a(self.get(otype).parent, parent)
+
+matcher = ArgMatcher()
+
+arg = NoneArg()
+matcher.register(None, arg)
+matcher.register('none', arg)
+
+arg = StringArg()
+matcher.register('char*', arg)
+matcher.register('gchar*', arg)
+matcher.register('const-char*', arg)
+matcher.register('char-const*', arg)
+matcher.register('const-gchar*', arg)
+matcher.register('gchar-const*', arg)
+matcher.register('string', arg)
+matcher.register('static_string', arg)
+
+arg = UCharArg()
+matcher.register('unsigned-char*', arg)
+matcher.register('const-guchar*', arg)
+matcher.register('guchar*', arg)
+
+arg = CharArg()
+matcher.register('char', arg)
+matcher.register('gchar', arg)
+matcher.register('guchar', arg)
+
+arg = GUniCharArg()
+matcher.register('gunichar', arg)
+
+arg = IntArg()
+matcher.register('int', arg)
+matcher.register('gint', arg)
+matcher.register('short', arg)
+matcher.register('gshort', arg)
+matcher.register('gushort', arg)
+matcher.register('gsize', SizeArg())
+matcher.register('gssize', SSizeArg())
+matcher.register('guint8', arg)
+matcher.register('gint8', arg)
+matcher.register('guint16', arg)
+matcher.register('gint16', arg)
+matcher.register('gint32', arg)
+matcher.register('GTime', arg)
+
+arg = LongArg()
+matcher.register('long', arg)
+matcher.register('glong', arg)
+
+arg = UIntArg()
+matcher.register('guint', arg)
+
+arg = BoolArg()
+matcher.register('gboolean', arg)
+
+arg = TimeTArg()
+matcher.register('time_t', arg)
+
+matcher.register('guint32', UInt32Arg())
+
+arg = ULongArg()
+matcher.register('gulong', arg)
+
+arg = Int64Arg()
+matcher.register('gint64', arg)
+matcher.register('long-long', arg)
+
+arg = UInt64Arg()
+matcher.register('guint64', arg)
+matcher.register('unsigned-long-long', arg)
+
+arg = DoubleArg()
+matcher.register('double', arg)
+matcher.register('gdouble', arg)
+matcher.register('float', arg)
+matcher.register('gfloat', arg)
+
+arg = FileArg()
+matcher.register('FILE*', arg)
+
+# enums, flags, objects
+
+matcher.register('GdkAtom', AtomArg())
+
+matcher.register('GType', GTypeArg())
+matcher.register('GtkType', GTypeArg())
+
+matcher.register('GError**', GErrorArg())
+matcher.register('GtkTreePath*', GtkTreePathArg())
+matcher.register('GdkRectangle*', GdkRectanglePtrArg())
+matcher.register('GtkAllocation*', GdkRectanglePtrArg())
+matcher.register('GdkRectangle', GdkRectangleArg())
+matcher.register('PyObject*', PyObjectArg())
+
+matcher.register('GdkNativeWindow', ULongArg())
+
+matcher.register_object('GObject', None, 'G_TYPE_OBJECT')
+matcher.register_miniobject('GstMiniObject', None, 'GST_TYPE_MINI_OBJECT')
+
+del arg
diff --git a/farstream/python/codegen/code-coverage.py b/farstream/python/codegen/code-coverage.py
new file mode 100755
index 000000000..fd1503473
--- /dev/null
+++ b/farstream/python/codegen/code-coverage.py
@@ -0,0 +1,42 @@
+from __future__ import generators
+import sys, os
+
+def read_symbols(file, type=None, dynamic=0):
+ if dynamic:
+ cmd = 'nm -D %s' % file
+ else:
+ cmd = 'nm %s' % file
+ for line in os.popen(cmd, 'r'):
+ if line[0] != ' ': # has an address as first bit of line
+ while line[0] != ' ':
+ line = line[1:]
+ while line[0] == ' ':
+ line = line[1:]
+ # we should be up to "type symbolname" now
+ sym_type = line[0]
+ symbol = line[1:].strip()
+
+ if not type or type == sym_type:
+ yield symbol
+
+def main():
+ if len(sys.argv) != 3:
+ sys.stderr.write('usage: coverage-check library.so wrapper.so\n')
+ sys.exit(1)
+ library = sys.argv[1]
+ wrapper = sys.argv[2]
+
+ # first create a dict with all referenced symbols in the wrapper
+ # should really be a set, but a dict will do ...
+ wrapper_symbols = {}
+ for symbol in read_symbols(wrapper, type='U', dynamic=1):
+ wrapper_symbols[symbol] = 1
+
+ # now go through the library looking for matches on the defined symbols:
+ for symbol in read_symbols(library, type='T', dynamic=1):
+ if symbol[0] == '_': continue
+ if symbol not in wrapper_symbols:
+ print symbol
+
+if __name__ == '__main__':
+ main()
diff --git a/farstream/python/codegen/codegen.py b/farstream/python/codegen/codegen.py
new file mode 100644
index 000000000..8f20bf708
--- /dev/null
+++ b/farstream/python/codegen/codegen.py
@@ -0,0 +1,1572 @@
+import getopt
+import keyword
+import os
+import string
+import sys
+
+import argtypes
+import definitions
+import defsparser
+import override
+import reversewrapper
+
+class Coverage(object):
+ def __init__(self, name):
+ self.name = name
+ self.wrapped = 0
+ self.not_wrapped = 0
+
+ def declare_wrapped(self):
+ self.wrapped += 1
+
+ def declare_not_wrapped(self):
+ self.not_wrapped += 1
+
+ def printstats(self):
+ total = self.wrapped + self.not_wrapped
+ fd = sys.stderr
+ if total:
+ fd.write("***INFO*** The coverage of %s is %.2f%% (%i/%i)\n" %
+ (self.name,
+ float(self.wrapped*100)/total,
+ self.wrapped,
+ total))
+ else:
+ fd.write("***INFO*** There are no declared %s." % self.name)
+
+functions_coverage = Coverage("global functions")
+methods_coverage = Coverage("methods")
+vproxies_coverage = Coverage("virtual proxies")
+vaccessors_coverage = Coverage("virtual accessors")
+iproxies_coverage = Coverage("interface proxies")
+
+def exc_info():
+ #traceback.print_exc()
+ etype, value, tb = sys.exc_info()
+ ret = ""
+ try:
+ sval = str(value)
+ if etype == KeyError:
+ ret = "No ArgType for %s" % (sval,)
+ else:
+ ret = sval
+ finally:
+ del etype, value, tb
+ return ret
+
+def fixname(name):
+ if keyword.iskeyword(name):
+ return name + '_'
+ return name
+
+class FileOutput:
+ '''Simple wrapper for file object, that makes writing #line
+ statements easier.''' # "
+ def __init__(self, fp, filename=None):
+ self.fp = fp
+ self.lineno = 1
+ if filename:
+ self.filename = filename
+ else:
+ self.filename = self.fp.name
+ # handle writing to the file, and keep track of the line number ...
+ def write(self, str):
+ self.fp.write(str)
+ self.lineno = self.lineno + string.count(str, '\n')
+ def writelines(self, sequence):
+ for line in sequence:
+ self.write(line)
+ def close(self):
+ self.fp.close()
+ def flush(self):
+ self.fp.flush()
+
+ def setline(self, linenum, filename):
+ '''writes out a #line statement, for use by the C
+ preprocessor.''' # "
+ self.write('#line %d "%s"\n' % (linenum, filename))
+ def resetline(self):
+ '''resets line numbering to the original file'''
+ self.setline(self.lineno + 1, self.filename)
+
+class Wrapper:
+ type_tmpl = (
+ 'PyTypeObject Py%(typename)s_Type = {\n'
+ ' PyObject_HEAD_INIT(NULL)\n'
+ ' 0, /* ob_size */\n'
+ ' "%(classname)s", /* tp_name */\n'
+ ' sizeof(%(tp_basicsize)s), /* tp_basicsize */\n'
+ ' 0, /* tp_itemsize */\n'
+ ' /* methods */\n'
+ ' (destructor)%(tp_dealloc)s, /* tp_dealloc */\n'
+ ' (printfunc)0, /* tp_print */\n'
+ ' (getattrfunc)%(tp_getattr)s, /* tp_getattr */\n'
+ ' (setattrfunc)%(tp_setattr)s, /* tp_setattr */\n'
+ ' (cmpfunc)%(tp_compare)s, /* tp_compare */\n'
+ ' (reprfunc)%(tp_repr)s, /* tp_repr */\n'
+ ' (PyNumberMethods*)%(tp_as_number)s, /* tp_as_number */\n'
+ ' (PySequenceMethods*)%(tp_as_sequence)s, /* tp_as_sequence */\n'
+ ' (PyMappingMethods*)%(tp_as_mapping)s, /* tp_as_mapping */\n'
+ ' (hashfunc)%(tp_hash)s, /* tp_hash */\n'
+ ' (ternaryfunc)%(tp_call)s, /* tp_call */\n'
+ ' (reprfunc)%(tp_str)s, /* tp_str */\n'
+ ' (getattrofunc)%(tp_getattro)s, /* tp_getattro */\n'
+ ' (setattrofunc)%(tp_setattro)s, /* tp_setattro */\n'
+ ' (PyBufferProcs*)%(tp_as_buffer)s, /* tp_as_buffer */\n'
+ ' %(tp_flags)s, /* tp_flags */\n'
+ ' %(tp_doc)s, /* Documentation string */\n'
+ ' (traverseproc)%(tp_traverse)s, /* tp_traverse */\n'
+ ' (inquiry)%(tp_clear)s, /* tp_clear */\n'
+ ' (richcmpfunc)%(tp_richcompare)s, /* tp_richcompare */\n'
+ ' %(tp_weaklistoffset)s, /* tp_weaklistoffset */\n'
+ ' (getiterfunc)%(tp_iter)s, /* tp_iter */\n'
+ ' (iternextfunc)%(tp_iternext)s, /* tp_iternext */\n'
+ ' (struct PyMethodDef*)%(tp_methods)s, /* tp_methods */\n'
+ ' (struct PyMemberDef*)0, /* tp_members */\n'
+ ' (struct PyGetSetDef*)%(tp_getset)s, /* tp_getset */\n'
+ ' NULL, /* tp_base */\n'
+ ' NULL, /* tp_dict */\n'
+ ' (descrgetfunc)%(tp_descr_get)s, /* tp_descr_get */\n'
+ ' (descrsetfunc)%(tp_descr_set)s, /* tp_descr_set */\n'
+ ' %(tp_dictoffset)s, /* tp_dictoffset */\n'
+ ' (initproc)%(tp_init)s, /* tp_init */\n'
+ ' (allocfunc)%(tp_alloc)s, /* tp_alloc */\n'
+ ' (newfunc)%(tp_new)s, /* tp_new */\n'
+ ' (freefunc)%(tp_free)s, /* tp_free */\n'
+ ' (inquiry)%(tp_is_gc)s /* tp_is_gc */\n'
+ '};\n\n'
+ )
+
+ slots_list = [
+ 'tp_getattr', 'tp_setattr', 'tp_getattro', 'tp_setattro',
+ 'tp_compare', 'tp_repr',
+ 'tp_as_number', 'tp_as_sequence', 'tp_as_mapping', 'tp_hash',
+ 'tp_call', 'tp_str', 'tp_as_buffer', 'tp_richcompare', 'tp_iter',
+ 'tp_iternext', 'tp_descr_get', 'tp_descr_set', 'tp_init',
+ 'tp_alloc', 'tp_new', 'tp_free', 'tp_is_gc',
+ 'tp_traverse', 'tp_clear', 'tp_dealloc', 'tp_flags', 'tp_doc'
+ ]
+
+ getter_tmpl = (
+ 'static PyObject *\n'
+ '%(funcname)s(PyObject *self, void *closure)\n'
+ '{\n'
+ '%(varlist)s'
+ ' ret = %(field)s;\n'
+ '%(codeafter)s\n'
+ '}\n\n'
+ )
+
+ parse_tmpl = (
+ ' if (!PyArg_ParseTupleAndKeywords(args, kwargs,'
+ '"%(typecodes)s:%(name)s"%(parselist)s))\n'
+ ' return %(errorreturn)s;\n'
+ )
+
+ deprecated_tmpl = (
+ ' if (PyErr_Warn(PyExc_DeprecationWarning, '
+ '"%(deprecationmsg)s") < 0)\n'
+ ' return %(errorreturn)s;\n'
+ )
+
+ methdef_tmpl = (
+ ' { "%(name)s", (PyCFunction)%(cname)s, %(flags)s,\n'
+ ' %(docstring)s },\n'
+ )
+
+ noconstructor = (
+ 'static int\n'
+ 'pygobject_no_constructor(PyObject *self, PyObject *args, '
+ 'PyObject *kwargs)\n'
+ '{\n'
+ ' gchar buf[512];\n'
+ '\n'
+ ' g_snprintf(buf, sizeof(buf), "%s is an abstract widget", '
+ 'self->ob_type->tp_name);\n'
+ ' PyErr_SetString(PyExc_NotImplementedError, buf);\n'
+ ' return -1;\n'
+ '}\n\n'
+ )
+
+ function_tmpl = (
+ 'static PyObject *\n'
+ '_wrap_%(cname)s(PyObject *self%(extraparams)s)\n'
+ '{\n'
+ '%(varlist)s'
+ '%(parseargs)s'
+ '%(codebefore)s'
+ ' %(begin_allow_threads)s\n'
+ ' %(setreturn)s%(cname)s(%(arglist)s);\n'
+ ' %(end_allow_threads)s\n'
+ '%(codeafter)s\n'
+ '}\n\n'
+ )
+
+ virtual_accessor_tmpl = (
+ 'static PyObject *\n'
+ '_wrap_%(cname)s(PyObject *cls%(extraparams)s)\n'
+ '{\n'
+ ' gpointer klass;\n'
+ '%(varlist)s'
+ '%(parseargs)s'
+ '%(codebefore)s'
+ ' klass = g_type_class_ref(pyg_type_from_object(cls));\n'
+ ' if (%(class_cast_macro)s(klass)->%(virtual)s) {\n'
+ ' pyg_begin_allow_threads;\n'
+ ' %(setreturn)s%(class_cast_macro)s(klass)->'
+ '%(virtual)s(%(arglist)s);\n'
+ ' pyg_end_allow_threads;\n'
+ ' } else {\n'
+ ' PyErr_SetString(PyExc_NotImplementedError, '
+ '"virtual method %(name)s not implemented");\n'
+ ' g_type_class_unref(klass);\n'
+ ' return NULL;\n'
+ ' }\n'
+ ' g_type_class_unref(klass);\n'
+ '%(codeafter)s\n'
+ '}\n\n'
+ )
+
+ # template for method calls
+ constructor_tmpl = None
+ method_tmpl = None
+
+ def __init__(self, parser, objinfo, overrides, fp=FileOutput(sys.stdout)):
+ self.parser = parser
+ self.objinfo = objinfo
+ self.overrides = overrides
+ self.fp = fp
+
+ def get_lower_name(self):
+ return string.lower(string.replace(self.objinfo.typecode,
+ '_TYPE_', '_', 1))
+
+ def get_field_accessor(self, fieldname):
+ raise NotImplementedError
+
+ def get_initial_class_substdict(self): return {}
+
+ def get_initial_constructor_substdict(self, constructor):
+ return { 'name': '%s.__init__' % self.objinfo.c_name,
+ 'errorreturn': '-1' }
+ def get_initial_method_substdict(self, method):
+ substdict = { 'name': '%s.%s' % (self.objinfo.c_name, method.name) }
+ substdict['begin_allow_threads'] = 'pyg_begin_allow_threads;'
+ substdict['end_allow_threads'] = 'pyg_end_allow_threads;'
+ return substdict
+
+ def write_class(self):
+ if self.overrides.is_type_ignored(self.objinfo.c_name):
+ return
+ self.fp.write('\n/* ----------- %s ----------- */\n\n' %
+ self.objinfo.c_name)
+ substdict = self.get_initial_class_substdict()
+ if not substdict.has_key('tp_flags'):
+ substdict['tp_flags'] = 'Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE'
+ substdict['typename'] = self.objinfo.c_name
+ if self.overrides.modulename:
+ substdict['classname'] = '%s.%s' % (self.overrides.modulename,
+ self.objinfo.name)
+ else:
+ substdict['classname'] = self.objinfo.name
+ substdict['tp_doc'] = self.objinfo.docstring
+
+ # Maybe this could be done in a nicer way, but I'll leave it as it is
+ # for now: -- Johan
+ if not self.overrides.slot_is_overriden('%s.tp_init' %
+ self.objinfo.c_name):
+ substdict['tp_init'] = self.write_constructor()
+ substdict['tp_methods'] = self.write_methods()
+ substdict['tp_getset'] = self.write_getsets()
+
+ # handle slots ...
+ for slot in self.slots_list:
+
+ slotname = '%s.%s' % (self.objinfo.c_name, slot)
+ slotfunc = '_wrap_%s_%s' % (self.get_lower_name(), slot)
+ if slot[:6] == 'tp_as_':
+ slotfunc = '&' + slotfunc
+ if self.overrides.slot_is_overriden(slotname):
+ data = self.overrides.slot_override(slotname)
+ self.write_function(slotname, data)
+ substdict[slot] = slotfunc
+ else:
+ if not substdict.has_key(slot):
+ substdict[slot] = '0'
+
+ self.fp.write(self.type_tmpl % substdict)
+
+ self.write_virtuals()
+
+ def write_function_wrapper(self, function_obj, template,
+ handle_return=0, is_method=0, kwargs_needed=0,
+ substdict=None):
+ '''This function is the guts of all functions that generate
+ wrappers for functions, methods and constructors.'''
+ if not substdict: substdict = {}
+
+ info = argtypes.WrapperInfo()
+
+ substdict.setdefault('errorreturn', 'NULL')
+
+ # for methods, we want the leading comma
+ if is_method:
+ info.arglist.append('')
+
+ if function_obj.varargs:
+ raise ValueError, "varargs functions not supported"
+
+ for param in function_obj.params:
+ if param.pdflt and '|' not in info.parsestr:
+ info.add_parselist('|', [], [])
+ handler = argtypes.matcher.get(param.ptype)
+ handler.write_param(param.ptype, param.pname, param.pdflt,
+ param.pnull, param.keeprefcount, info)
+
+ substdict['setreturn'] = ''
+ if handle_return:
+ if function_obj.ret not in ('none', None):
+ substdict['setreturn'] = 'ret = '
+ handler = argtypes.matcher.get(function_obj.ret)
+ handler.write_return(function_obj.ret,
+ function_obj.caller_owns_return, info)
+
+ if function_obj.deprecated != None:
+ deprecated = self.deprecated_tmpl % {
+ 'deprecationmsg': function_obj.deprecated,
+ 'errorreturn': substdict['errorreturn'] }
+ else:
+ deprecated = ''
+
+ # if name isn't set, set it to function_obj.name
+ substdict.setdefault('name', function_obj.name)
+
+ substdict['begin_allow_threads'] = 'pyg_begin_allow_threads;'
+ substdict['end_allow_threads'] = 'pyg_end_allow_threads;'
+
+ if self.objinfo:
+ substdict['typename'] = self.objinfo.c_name
+ substdict.setdefault('cname', function_obj.c_name)
+ substdict['varlist'] = info.get_varlist()
+ substdict['typecodes'] = info.parsestr
+ substdict['parselist'] = info.get_parselist()
+ substdict['arglist'] = info.get_arglist()
+ substdict['codebefore'] = deprecated + (
+ string.replace(info.get_codebefore(),
+ 'return NULL', 'return ' + substdict['errorreturn'])
+ )
+ substdict['codeafter'] = (
+ string.replace(info.get_codeafter(),
+ 'return NULL',
+ 'return ' + substdict['errorreturn']))
+
+ if info.parsestr or kwargs_needed:
+ substdict['parseargs'] = self.parse_tmpl % substdict
+ substdict['extraparams'] = ', PyObject *args, PyObject *kwargs'
+ flags = 'METH_VARARGS|METH_KEYWORDS'
+
+ # prepend the keyword list to the variable list
+ substdict['varlist'] = info.get_kwlist() + substdict['varlist']
+ else:
+ substdict['parseargs'] = ''
+ substdict['extraparams'] = ''
+ flags = 'METH_NOARGS'
+
+ return template % substdict, flags
+
+ def write_constructor(self):
+ initfunc = '0'
+ constructor = self.parser.find_constructor(self.objinfo,self.overrides)
+ if not constructor:
+ return self.write_default_constructor()
+
+ funcname = constructor.c_name
+ try:
+ if self.overrides.is_overriden(funcname):
+ data = self.overrides.override(funcname)
+ self.write_function(funcname, data)
+ self.objinfo.has_new_constructor_api = (
+ self.objinfo.typecode in
+ self.overrides.newstyle_constructors)
+ else:
+ # ok, a hack to determine if we should use
+ # new-style constructores :P
+ property_based = getattr(self,
+ 'write_property_based_constructor',
+ None)
+ if property_based:
+ if (len(constructor.params) == 0 or
+ isinstance(constructor.params[0],
+ definitions.Property)):
+ # write_property_based_constructor is only
+ # implemented in GObjectWrapper
+ return self.write_property_based_constructor(
+ constructor)
+ else:
+ sys.stderr.write(
+ "Warning: generating old-style constructor for:" +
+ constructor.c_name + '\n')
+
+ # write constructor from template ...
+ code = self.write_function_wrapper(constructor,
+ self.constructor_tmpl,
+ handle_return=0, is_method=0, kwargs_needed=1,
+ substdict=self.get_initial_constructor_substdict(
+ constructor))[0]
+ self.fp.write(code)
+ initfunc = '_wrap_' + funcname
+ except:
+ sys.stderr.write('Could not write constructor for %s: %s\n'
+ % (self.objinfo.c_name, exc_info()))
+
+ initfunc = self.write_noconstructor()
+ return initfunc
+
+ def write_noconstructor(self):
+ # this is a hack ...
+ if not hasattr(self.overrides, 'no_constructor_written'):
+ self.fp.write(self.noconstructor)
+ self.overrides.no_constructor_written = 1
+ initfunc = 'pygobject_no_constructor'
+ return initfunc
+
+ def write_default_constructor(self):
+ return self.write_noconstructor()
+
+ def get_methflags(self, funcname):
+ if self.overrides.wants_kwargs(funcname):
+ flags = 'METH_VARARGS|METH_KEYWORDS'
+ elif self.overrides.wants_noargs(funcname):
+ flags = 'METH_NOARGS'
+ elif self.overrides.wants_onearg(funcname):
+ flags = 'METH_O'
+ else:
+ flags = 'METH_VARARGS'
+ if self.overrides.is_staticmethod(funcname):
+ flags += '|METH_STATIC'
+ elif self.overrides.is_classmethod(funcname):
+ flags += '|METH_CLASS'
+ return flags
+
+ def write_function(self, funcname, data):
+ lineno, filename = self.overrides.getstartline(funcname)
+ self.fp.setline(lineno, filename)
+ self.fp.write(data)
+ self.fp.resetline()
+ self.fp.write('\n\n')
+
+ def _get_class_virtual_substdict(self, meth, cname, parent):
+ substdict = self.get_initial_method_substdict(meth)
+ substdict['virtual'] = substdict['name'].split('.')[1]
+ substdict['cname'] = cname
+ substdict['class_cast_macro'] = parent.typecode.replace(
+ '_TYPE_', '_', 1) + "_CLASS"
+ substdict['typecode'] = self.objinfo.typecode
+ substdict['cast'] = string.replace(parent.typecode, '_TYPE_', '_', 1)
+ return substdict
+
+ def write_methods(self):
+ methods = []
+ klass = self.objinfo.c_name
+ # First, get methods from the defs files
+ for meth in self.parser.find_methods(self.objinfo):
+ method_name = meth.c_name
+ if self.overrides.is_ignored(method_name):
+ continue
+ try:
+ if self.overrides.is_overriden(method_name):
+ if not self.overrides.is_already_included(method_name):
+ data = self.overrides.override(method_name)
+ self.write_function(method_name, data)
+
+ methflags = self.get_methflags(method_name)
+ else:
+ # write constructor from template ...
+ code, methflags = self.write_function_wrapper(meth,
+ self.method_tmpl, handle_return=1, is_method=1,
+ substdict=self.get_initial_method_substdict(meth))
+ self.fp.write(code)
+ methods.append(self.methdef_tmpl %
+ { 'name': fixname(meth.name),
+ 'cname': '_wrap_' + method_name,
+ 'flags': methflags,
+ 'docstring': meth.docstring })
+ methods_coverage.declare_wrapped()
+ except:
+ methods_coverage.declare_not_wrapped()
+ sys.stderr.write('Could not write method %s.%s: %s\n'
+ % (klass, meth.name, exc_info()))
+
+ # Now try to see if there are any defined in the override
+ for method_name in self.overrides.get_defines_for(klass):
+ c_name = override.class2cname(klass, method_name)
+ if self.overrides.is_already_included(method_name):
+ continue
+
+ try:
+ data = self.overrides.define(klass, method_name)
+ self.write_function(method_name, data)
+ methflags = self.get_methflags(method_name)
+
+ methods.append(self.methdef_tmpl %
+ { 'name': method_name,
+ 'cname': '_wrap_' + c_name,
+ 'flags': methflags,
+ 'docstring': meth.docstring })
+ methods_coverage.declare_wrapped()
+ except:
+ methods_coverage.declare_not_wrapped()
+ sys.stderr.write('Could not write method %s.%s: %s\n'
+ % (klass, meth.name, exc_info()))
+
+ # Add GObject virtual method accessors, for chaining to parent
+ # virtuals from subclasses
+ methods += self.write_virtual_accessors()
+
+ if methods:
+ methoddefs = '_Py%s_methods' % self.objinfo.c_name
+ # write the PyMethodDef structure
+ methods.append(' { NULL, NULL, 0, NULL }\n')
+ self.fp.write('static const PyMethodDef %s[] = {\n' % methoddefs)
+ self.fp.write(string.join(methods, ''))
+ self.fp.write('};\n\n')
+ else:
+ methoddefs = 'NULL'
+ return methoddefs
+
+ def write_virtual_accessors(self):
+ klass = self.objinfo.c_name
+ methods = []
+ for meth in self.parser.find_virtuals(self.objinfo):
+ method_name = self.objinfo.c_name + "__do_" + meth.name
+ if self.overrides.is_ignored(method_name):
+ continue
+ try:
+ if self.overrides.is_overriden(method_name):
+ if not self.overrides.is_already_included(method_name):
+ data = self.overrides.override(method_name)
+ self.write_function(method_name, data)
+ methflags = self.get_methflags(method_name)
+ else:
+ # temporarily add a 'self' parameter as first argument
+ meth.params.insert(0, definitions.Parameter(
+ ptype=(self.objinfo.c_name + '*'),
+ pname='self', pdflt=None, pnull=None))
+ try:
+ # write method from template ...
+ code, methflags = self.write_function_wrapper(
+ meth, self.virtual_accessor_tmpl,
+ handle_return=True, is_method=False,
+ substdict=self._get_class_virtual_substdict(
+ meth, method_name, self.objinfo))
+ self.fp.write(code)
+ finally:
+ del meth.params[0]
+ methods.append(self.methdef_tmpl %
+ { 'name': "do_" + fixname(meth.name),
+ 'cname': '_wrap_' + method_name,
+ 'flags': methflags + '|METH_CLASS',
+ 'docstring': 'NULL'})
+ vaccessors_coverage.declare_wrapped()
+ except:
+ vaccessors_coverage.declare_not_wrapped()
+ sys.stderr.write(
+ 'Could not write virtual accessor method %s.%s: %s\n'
+ % (klass, meth.name, exc_info()))
+ return methods
+
+ def write_virtuals(self):
+ '''
+ Write _wrap_FooBar__proxy_do_zbr() reverse wrapers for
+ GObject virtuals
+ '''
+ klass = self.objinfo.c_name
+ virtuals = []
+ for meth in self.parser.find_virtuals(self.objinfo):
+ method_name = self.objinfo.c_name + "__proxy_do_" + meth.name
+ if self.overrides.is_ignored(method_name):
+ continue
+ try:
+ if self.overrides.is_overriden(method_name):
+ if not self.overrides.is_already_included(method_name):
+ data = self.overrides.override(method_name)
+ self.write_function(method_name, data)
+ else:
+ # write virtual proxy ...
+ ret, props = argtypes.matcher.get_reverse_ret(meth.ret)
+ wrapper = reversewrapper.ReverseWrapper(
+ '_wrap_' + method_name, is_static=True)
+ wrapper.set_return_type(ret(wrapper, **props))
+ wrapper.add_parameter(reversewrapper.PyGObjectMethodParam(
+ wrapper, "self", method_name="do_" + meth.name,
+ c_type=(klass + ' *')))
+ for param in meth.params:
+ handler, props = argtypes.matcher.get_reverse(
+ param.ptype)
+ props["direction"] = param.pdir
+ wrapper.add_parameter(handler(wrapper,
+ param.pname, **props))
+ buf = reversewrapper.MemoryCodeSink()
+ wrapper.generate(buf)
+ self.fp.write(buf.flush())
+ virtuals.append((fixname(meth.name), '_wrap_' + method_name))
+ vproxies_coverage.declare_wrapped()
+ except (KeyError, ValueError):
+ vproxies_coverage.declare_not_wrapped()
+ virtuals.append((fixname(meth.name), None))
+ sys.stderr.write('Could not write virtual proxy %s.%s: %s\n'
+ % (klass, meth.name, exc_info()))
+ if virtuals:
+ # Write a 'pygtk class init' function for this object,
+ # except when the object type is explicitly ignored (like
+ # GtkPlug and GtkSocket on win32).
+ if self.overrides.is_ignored(self.objinfo.typecode):
+ return
+ class_cast_macro = self.objinfo.typecode.replace(
+ '_TYPE_', '_', 1) + "_CLASS"
+ cast_macro = self.objinfo.typecode.replace('_TYPE_', '_', 1)
+ funcname = "__%s_class_init" % klass
+ self.objinfo.class_init_func = funcname
+ have_implemented_virtuals = not not [True
+ for name, cname in virtuals
+ if cname is not None]
+ self.fp.write(
+ ('\nstatic int\n'
+ '%(funcname)s(gpointer gclass, PyTypeObject *pyclass)\n'
+ '{\n') % vars())
+
+ if have_implemented_virtuals:
+ self.fp.write(' PyObject *o;\n')
+ self.fp.write(
+ ' %(klass)sClass *klass = '
+ '%(class_cast_macro)s(gclass);\n'
+ ' PyObject *gsignals = '
+ 'PyDict_GetItemString(pyclass->tp_dict, "__gsignals__");\n'
+ % vars())
+
+ for name, cname in virtuals:
+ do_name = 'do_' + name
+ if cname is None:
+ self.fp.write('\n /* overriding %(do_name)s '
+ 'is currently not supported */\n' % vars())
+ else:
+ self.fp.write('''
+ o = PyObject_GetAttrString((PyObject *) pyclass, "%(do_name)s");
+ if (o == NULL)
+ PyErr_Clear();
+ else {
+ if (!PyObject_TypeCheck(o, &PyCFunction_Type)
+ && !(gsignals && PyDict_GetItemString(gsignals, "%(name)s")))
+ klass->%(name)s = %(cname)s;
+ Py_DECREF(o);
+ }
+''' % vars())
+ self.fp.write(' return 0;\n}\n')
+
+ def write_getsets(self):
+ lower_name = self.get_lower_name()
+ getsets_name = lower_name + '_getsets'
+ getterprefix = '_wrap_' + lower_name + '__get_'
+ setterprefix = '_wrap_' + lower_name + '__set_'
+
+ # no overrides for the whole function. If no fields,
+ # don't write a func
+ if not self.objinfo.fields:
+ return '0'
+ getsets = []
+ for ftype, cfname in self.objinfo.fields:
+ fname = cfname.replace('.', '_')
+ gettername = '0'
+ settername = '0'
+ attrname = self.objinfo.c_name + '.' + fname
+ if self.overrides.attr_is_overriden(attrname):
+ code = self.overrides.attr_override(attrname)
+ self.write_function(attrname, code)
+ if string.find(code, getterprefix + fname) >= 0:
+ gettername = getterprefix + fname
+ if string.find(code, setterprefix + fname) >= 0:
+ settername = setterprefix + fname
+ if gettername == '0':
+ try:
+ funcname = getterprefix + fname
+ info = argtypes.WrapperInfo()
+ handler = argtypes.matcher.get(ftype)
+ # for attributes, we don't own the "return value"
+ handler.write_return(ftype, 0, info)
+ self.fp.write(self.getter_tmpl %
+ { 'funcname': funcname,
+ 'varlist': info.varlist,
+ 'field': self.get_field_accessor(cfname),
+ 'codeafter': info.get_codeafter() })
+ gettername = funcname
+ except:
+ sys.stderr.write(
+ "Could not write getter for %s.%s: %s\n"
+ % (self.objinfo.c_name, fname, exc_info()))
+ if gettername != '0' or settername != '0':
+ getsets.append(' { "%s", (getter)%s, (setter)%s },\n' %
+ (fixname(fname), gettername, settername))
+
+ if not getsets:
+ return '0'
+ self.fp.write('static const PyGetSetDef %s[] = {\n' % getsets_name)
+ for getset in getsets:
+ self.fp.write(getset)
+ self.fp.write(' { NULL, (getter)0, (setter)0 },\n')
+ self.fp.write('};\n\n')
+
+ return getsets_name
+
+ def write_functions(self, prefix):
+ self.fp.write('\n/* ----------- functions ----------- */\n\n')
+ functions = []
+
+ # First, get methods from the defs files
+ for func in self.parser.find_functions():
+ funcname = func.c_name
+ if self.overrides.is_ignored(funcname):
+ continue
+ try:
+ if self.overrides.is_overriden(funcname):
+ data = self.overrides.override(funcname)
+ self.write_function(funcname, data)
+
+ methflags = self.get_methflags(funcname)
+ else:
+ # write constructor from template ...
+ code, methflags = self.write_function_wrapper(func,
+ self.function_tmpl, handle_return=1, is_method=0)
+ self.fp.write(code)
+ functions.append(self.methdef_tmpl %
+ { 'name': func.name,
+ 'cname': '_wrap_' + funcname,
+ 'flags': methflags,
+ 'docstring': func.docstring })
+ functions_coverage.declare_wrapped()
+ except:
+ functions_coverage.declare_not_wrapped()
+ sys.stderr.write('Could not write function %s: %s\n'
+ % (func.name, exc_info()))
+
+ # Now try to see if there are any defined in the override
+ for funcname in self.overrides.get_functions():
+ try:
+ data = self.overrides.function(funcname)
+ self.write_function(funcname, data)
+ methflags = self.get_methflags(funcname)
+ functions.append(self.methdef_tmpl %
+ { 'name': funcname,
+ 'cname': '_wrap_' + funcname,
+ 'flags': methflags,
+ 'docstring': 'NULL'})
+ functions_coverage.declare_wrapped()
+ except:
+ functions_coverage.declare_not_wrapped()
+ sys.stderr.write('Could not write function %s: %s\n'
+ % (funcname, exc_info()))
+
+ # write the PyMethodDef structure
+ functions.append(' { NULL, NULL, 0, NULL }\n')
+
+ self.fp.write('const PyMethodDef ' + prefix + '_functions[] = {\n')
+ self.fp.write(string.join(functions, ''))
+ self.fp.write('};\n\n')
+
+class GObjectWrapper(Wrapper):
+ constructor_tmpl = (
+ 'static int\n'
+ '_wrap_%(cname)s(PyGObject *self%(extraparams)s)\n'
+ '{\n'
+ '%(varlist)s'
+ '%(parseargs)s'
+ '%(codebefore)s'
+ ' self->obj = (GObject *)%(cname)s(%(arglist)s);\n'
+ '%(codeafter)s\n'
+ ' if (!self->obj) {\n'
+ ' PyErr_SetString(PyExc_RuntimeError, '
+ '"could not create %(typename)s object");\n'
+ ' return -1;\n'
+ ' }\n'
+ '%(aftercreate)s'
+ ' pygobject_register_wrapper((PyObject *)self);\n'
+ ' return 0;\n'
+ '}\n\n'
+ )
+
+ method_tmpl = (
+ 'static PyObject *\n'
+ '_wrap_%(cname)s(PyGObject *self%(extraparams)s)\n'
+ '{\n'
+ '%(varlist)s'
+ '%(parseargs)s'
+ '%(codebefore)s'
+ ' %(begin_allow_threads)s\n'
+ ' %(setreturn)s%(cname)s(%(cast)s(self->obj)%(arglist)s);\n'
+ ' %(end_allow_threads)s\n'
+ '%(codeafter)s\n'
+ '}\n\n'
+ )
+ def __init__(self, parser, objinfo, overrides, fp=FileOutput(sys.stdout)):
+ Wrapper.__init__(self, parser, objinfo, overrides, fp)
+ if self.objinfo:
+ self.castmacro = string.replace(self.objinfo.typecode,
+ '_TYPE_', '_', 1)
+
+ def get_initial_class_substdict(self):
+ return { 'tp_basicsize' : 'PyGObject',
+ 'tp_weaklistoffset' : 'offsetof(PyGObject, weakreflist)',
+ 'tp_dictoffset' : 'offsetof(PyGObject, inst_dict)' }
+
+ def get_field_accessor(self, fieldname):
+ castmacro = string.replace(self.objinfo.typecode, '_TYPE_', '_', 1)
+ return '%s(pygobject_get(self))->%s' % (castmacro, fieldname)
+
+ def get_initial_constructor_substdict(self, constructor):
+ substdict = Wrapper.get_initial_constructor_substdict(self,
+ constructor)
+ if not constructor.caller_owns_return:
+ substdict['aftercreate'] = " g_object_ref(self->obj);\n"
+ else:
+ substdict['aftercreate'] = ''
+ return substdict
+
+ def get_initial_method_substdict(self, method):
+ substdict = Wrapper.get_initial_method_substdict(self, method)
+ substdict['cast'] = string.replace(self.objinfo.typecode,
+ '_TYPE_', '_', 1)
+ return substdict
+
+ def write_default_constructor(self):
+ try:
+ parent = self.parser.find_object(self.objinfo.parent)
+ except ValueError:
+ parent = None
+ if parent is not None:
+ ## just like the constructor is inheritted, we should
+ # inherit the new API compatibility flag
+ self.objinfo.has_new_constructor_api = (
+ parent.has_new_constructor_api)
+ elif self.objinfo.parent == 'GObject':
+ self.objinfo.has_new_constructor_api = True
+ return '0'
+
+ def write_property_based_constructor(self, constructor):
+ self.objinfo.has_new_constructor_api = True
+ out = self.fp
+ print >> out, "static int"
+ print >> out, '_wrap_%s(PyGObject *self, PyObject *args,' \
+ ' PyObject *kwargs)\n{' % constructor.c_name
+ if constructor.params:
+ s = " GType obj_type = pyg_type_from_object((PyObject *) self);"
+ print >> out, s
+
+ def py_str_list_to_c(arg):
+ if arg:
+ return "{" + ", ".join(
+ map(lambda s: '"' + s + '"', arg)) + ", NULL }"
+ else:
+ return "{ NULL }"
+
+ classname = '%s.%s' % (self.overrides.modulename,
+ self.objinfo.name)
+
+ if constructor.params:
+ mandatory_arguments = [param for param in constructor.params
+ if not param.optional]
+ optional_arguments = [param for param in constructor.params
+ if param.optional]
+ arg_names = py_str_list_to_c(
+ [param.argname
+ for param in mandatory_arguments + optional_arguments])
+
+ prop_names = py_str_list_to_c(
+ [param.pname
+ for param in mandatory_arguments + optional_arguments])
+
+ print >> out, " GParameter params[%i];" % \
+ len(constructor.params)
+ print >> out, " PyObject *parsed_args[%i] = {NULL, };" % \
+ len(constructor.params)
+ print >> out, " char *arg_names[] = %s;" % arg_names
+ print >> out, " char *prop_names[] = %s;" % prop_names
+ print >> out, " guint nparams, i;"
+ print >> out
+ if constructor.deprecated is not None:
+ out.write(
+ ' if (PyErr_Warn(PyExc_DeprecationWarning, '
+ '"%s") < 0)\n' %
+ constructor.deprecated)
+ print >> out, ' return -1;'
+ print >> out
+ out.write(" if (!PyArg_ParseTupleAndKeywords(args, kwargs, ")
+ template = '"'
+ if mandatory_arguments:
+ template += "O"*len(mandatory_arguments)
+ if optional_arguments:
+ template += "|" + "O"*len(optional_arguments)
+ template += ':%s.__init__"' % classname
+ print >> out, template, ", arg_names",
+ for i in range(len(constructor.params)):
+ print >> out, ", &parsed_args[%i]" % i,
+
+ out.write(
+ "))\n"
+ " return -1;\n"
+ "\n"
+ " memset(params, 0, sizeof(GParameter)*%i);\n"
+ " if (!pyg_parse_constructor_args(obj_type, arg_names,\n"
+ " prop_names, params, \n"
+ " &nparams, parsed_args))\n"
+ " return -1;\n"
+ " pygobject_constructv(self, nparams, params);\n"
+ " for (i = 0; i < nparams; ++i)\n"
+ " g_value_unset(&params[i].value);\n"
+ % len(constructor.params))
+ else:
+ out.write(
+ " static char* kwlist[] = { NULL };\n"
+ "\n")
+
+ if constructor.deprecated is not None:
+ out.write(
+ ' if (PyErr_Warn(PyExc_DeprecationWarning, "%s") < 0)\n'
+ ' return -1;\n'
+ '\n' % constructor.deprecated)
+
+ out.write(
+ ' if (!PyArg_ParseTupleAndKeywords(args, kwargs,\n'
+ ' ":%s.__init__",\n'
+ ' kwlist))\n'
+ ' return -1;\n'
+ '\n'
+ ' pygobject_constructv(self, 0, NULL);\n' % classname)
+ out.write(
+ ' if (!self->obj) {\n'
+ ' PyErr_SetString(\n'
+ ' PyExc_RuntimeError, \n'
+ ' "could not create %s object");\n'
+ ' return -1;\n'
+ ' }\n' % classname)
+
+ if not constructor.caller_owns_return:
+ print >> out, " g_object_ref(self->obj);\n"
+
+ out.write(
+ ' return 0;\n'
+ '}\n\n')
+
+ return "_wrap_%s" % constructor.c_name
+
+
+## TODO : Add GstMiniObjectWrapper(Wrapper)
+class GstMiniObjectWrapper(Wrapper):
+ constructor_tmpl = (
+ 'static int\n'
+ '_wrap_%(cname)s(PyGstMiniObject *self%(extraparams)s)\n'
+ '{\n'
+ '%(varlist)s'
+ '%(parseargs)s'
+ '%(codebefore)s'
+ ' self->obj = (GstMiniObject *)%(cname)s(%(arglist)s);\n'
+ '%(codeafter)s\n'
+ ' if (!self->obj) {\n'
+ ' PyErr_SetString(PyExc_RuntimeError, '
+ '"could not create %(typename)s miniobject");\n'
+ ' return -1;\n'
+ ' }\n'
+ '%(aftercreate)s'
+ ' pygstminiobject_register_wrapper((PyObject *)self);\n'
+ ' return 0;\n'
+ '}\n\n'
+ )
+ method_tmpl = (
+ 'static PyObject *\n'
+ '_wrap_%(cname)s(PyGstMiniObject *self%(extraparams)s)\n'
+ '{\n'
+ '%(varlist)s'
+ '%(parseargs)s'
+ '%(codebefore)s'
+ ' %(begin_allow_threads)s\n'
+ ' %(setreturn)s%(cname)s(%(cast)s(self->obj)%(arglist)s);\n'
+ ' %(end_allow_threads)s\n'
+ '%(codeafter)s\n'
+ '}\n\n'
+ )
+ def __init__(self, parser, objinfo, overrides, fp=FileOutput(sys.stdout)):
+ Wrapper.__init__(self, parser, objinfo, overrides, fp)
+ if self.objinfo:
+ self.castmacro = string.replace(self.objinfo.typecode,
+ '_TYPE_', '_', 1)
+
+ def get_initial_class_substdict(self):
+ return { 'tp_basicsize' : 'PyGstMiniObject',
+ 'tp_weaklistoffset' : 'offsetof(PyGstMiniObject, weakreflist)',
+ 'tp_dictoffset' : 'offsetof(PyGstMiniObject, inst_dict)' }
+
+ def get_field_accessor(self, fieldname):
+ castmacro = string.replace(self.objinfo.typecode, '_TYPE_', '_', 1)
+ return '%s(pygstminiobject_get(self))->%s' % (castmacro, fieldname)
+
+ def get_initial_constructor_substdict(self, constructor):
+ substdict = Wrapper.get_initial_constructor_substdict(self,
+ constructor)
+ if not constructor.caller_owns_return:
+ substdict['aftercreate'] = " gst_mini_object_ref(self->obj);\n"
+ else:
+ substdict['aftercreate'] = ''
+ return substdict
+
+ def get_initial_method_substdict(self, method):
+ substdict = Wrapper.get_initial_method_substdict(self, method)
+ substdict['cast'] = string.replace(self.objinfo.typecode,
+ '_TYPE_', '_', 1)
+ return substdict
+
+
+
+class GInterfaceWrapper(GObjectWrapper):
+ virtual_accessor_tmpl = (
+ 'static PyObject *\n'
+ '_wrap_%(cname)s(PyObject *cls%(extraparams)s)\n'
+ '{\n'
+ ' %(vtable)s *iface;\n'
+ '%(varlist)s'
+ '%(parseargs)s'
+ '%(codebefore)s'
+ ' iface = g_type_interface_peek('
+ 'g_type_class_peek(pyg_type_from_object(cls)), %(typecode)s);\n'
+ ' if (iface->%(virtual)s)\n'
+ ' %(setreturn)siface->%(virtual)s(%(arglist)s);\n'
+ ' else {\n'
+ ' PyErr_SetString(PyExc_NotImplementedError, '
+ '"interface method %(name)s not implemented");\n'
+ ' return NULL;\n'
+ ' }\n'
+ '%(codeafter)s\n'
+ '}\n\n'
+ )
+
+ def get_initial_class_substdict(self):
+ return { 'tp_basicsize' : 'PyObject',
+ 'tp_weaklistoffset' : '0',
+ 'tp_dictoffset' : '0'}
+
+ def write_constructor(self):
+ # interfaces have no constructors ...
+ return '0'
+ def write_getsets(self):
+ # interfaces have no fields ...
+ return '0'
+
+ def _get_class_virtual_substdict(self, meth, cname, parent):
+ substdict = self.get_initial_method_substdict(meth)
+ substdict['virtual'] = substdict['name'].split('.')[1]
+ substdict['cname'] = cname
+ substdict['typecode'] = self.objinfo.typecode
+ substdict['vtable'] = self.objinfo.vtable
+ return substdict
+
+ def write_virtuals(self):
+ ## Now write reverse method wrappers, which let python code
+ ## implement interface methods.
+ # First, get methods from the defs files
+ klass = self.objinfo.c_name
+ proxies = []
+ for meth in self.parser.find_virtuals(self.objinfo):
+ method_name = self.objinfo.c_name + "__proxy_do_" + meth.name
+ if self.overrides.is_ignored(method_name):
+ continue
+ try:
+ if self.overrides.is_overriden(method_name):
+ if not self.overrides.is_already_included(method_name):
+ data = self.overrides.override(method_name)
+ self.write_function(method_name, data)
+ else:
+ # write proxy ...
+ ret, props = argtypes.matcher.get_reverse_ret(meth.ret)
+ wrapper = reversewrapper.ReverseWrapper(
+ '_wrap_' + method_name, is_static=True)
+ wrapper.set_return_type(ret(wrapper, **props))
+ wrapper.add_parameter(reversewrapper.PyGObjectMethodParam(
+ wrapper, "self", method_name="do_" + meth.name,
+ c_type=(klass + ' *')))
+ for param in meth.params:
+ handler, props = argtypes.matcher.get_reverse(
+ param.ptype)
+ props["direction"] = param.pdir
+ wrapper.add_parameter(
+ handler(wrapper, param.pname, **props))
+ buf = reversewrapper.MemoryCodeSink()
+ wrapper.generate(buf)
+ self.fp.write(buf.flush())
+ proxies.append((fixname(meth.name), '_wrap_' + method_name))
+ iproxies_coverage.declare_wrapped()
+ except (KeyError, ValueError):
+ iproxies_coverage.declare_not_wrapped()
+ proxies.append((fixname(meth.name), None))
+ sys.stderr.write('Could not write interface proxy %s.%s: %s\n'
+ % (klass, meth.name, exc_info()))
+
+ if not proxies:
+ return
+
+ # Make sure we have at least one proxy function
+ if not [cname for name,cname in proxies if not cname is None]:
+ return
+
+ ## Write an interface init function for this object
+ funcname = "__%s__interface_init" % klass
+ vtable = self.objinfo.vtable
+ self.fp.write(
+ '\nstatic void\n'
+ '%(funcname)s(%(vtable)s *iface, PyTypeObject *pytype)\n'
+ '{\n'
+ ' %(vtable)s *parent_iface = '
+ 'g_type_interface_peek_parent(iface);\n'
+ ' PyObject *py_method;\n'
+ '\n'
+ % vars())
+
+ for name, cname in proxies:
+ do_name = 'do_' + name
+ if cname is None:
+ continue
+
+ self.fp.write((
+ ' py_method = pytype? PyObject_GetAttrString('
+ '(PyObject *) pytype, "%(do_name)s") : NULL;\n'
+ ' if (py_method && !PyObject_TypeCheck(py_method, '
+ '&PyCFunction_Type)) {\n'
+ ' iface->%(name)s = %(cname)s;\n'
+ ' } else {\n'
+ ' PyErr_Clear();\n'
+ ' if (parent_iface) {\n'
+ ' iface->%(name)s = parent_iface->%(name)s;\n'
+ ' }\n'
+ ' Py_XDECREF(py_method);\n'
+ ' }\n'
+ ) % vars())
+ self.fp.write('}\n\n')
+ interface_info = "__%s__iinfo" % klass
+ self.fp.write('''
+static const GInterfaceInfo %s = {
+ (GInterfaceInitFunc) %s,
+ NULL,
+ NULL
+};
+''' % (interface_info, funcname))
+ self.objinfo.interface_info = interface_info
+
+class GBoxedWrapper(Wrapper):
+ constructor_tmpl = (
+ 'static int\n'
+ '_wrap_%(cname)s(PyGBoxed *self%(extraparams)s)\n'
+ '{\n' \
+ '%(varlist)s'
+ '%(parseargs)s'
+ '%(codebefore)s'
+ ' self->gtype = %(typecode)s;\n'
+ ' self->free_on_dealloc = FALSE;\n'
+ ' self->boxed = %(cname)s(%(arglist)s);\n'
+ '%(codeafter)s\n'
+ ' if (!self->boxed) {\n'
+ ' PyErr_SetString(PyExc_RuntimeError, '
+ '"could not create %(typename)s object");\n'
+ ' return -1;\n'
+ ' }\n'
+ ' self->free_on_dealloc = TRUE;\n'
+ ' return 0;\n'
+ '}\n\n'
+ )
+
+ method_tmpl = (
+ 'static PyObject *\n'
+ '_wrap_%(cname)s(PyObject *self%(extraparams)s)\n'
+ '{\n'
+ '%(varlist)s'
+ '%(parseargs)s'
+ '%(codebefore)s'
+ ' %(begin_allow_threads)s\n'
+ ' %(setreturn)s%(cname)s(pyg_boxed_get(self, '
+ '%(typename)s)%(arglist)s);\n'
+ ' %(end_allow_threads)s\n'
+ '%(codeafter)s\n'
+ '}\n\n'
+ )
+
+ def get_initial_class_substdict(self):
+ return { 'tp_basicsize' : 'PyGBoxed',
+ 'tp_weaklistoffset' : '0',
+ 'tp_dictoffset' : '0' }
+
+ def get_field_accessor(self, fieldname):
+ return 'pyg_boxed_get(self, %s)->%s' % (self.objinfo.c_name, fieldname)
+
+ def get_initial_constructor_substdict(self, constructor):
+ substdict = Wrapper.get_initial_constructor_substdict(
+ self, constructor)
+ substdict['typecode'] = self.objinfo.typecode
+ return substdict
+
+class GPointerWrapper(GBoxedWrapper):
+ constructor_tmpl = (
+ 'static int\n'
+ '_wrap_%(cname)s(PyGPointer *self%(extraparams)s)\n'
+ '{\n'
+ '%(varlist)s'
+ '%(parseargs)s'
+ '%(codebefore)s'
+ ' self->gtype = %(typecode)s;\n'
+ ' self->pointer = %(cname)s(%(arglist)s);\n'
+ '%(codeafter)s\n'
+ ' if (!self->pointer) {\n'
+ ' PyErr_SetString(PyExc_RuntimeError, '
+ '"could not create %(typename)s object");\n'
+ ' return -1;\n'
+ ' }\n'
+ ' return 0;\n'
+ '}\n\n'
+ )
+
+ method_tmpl = (
+ 'static PyObject *\n'
+ '_wrap_%(cname)s(PyObject *self%(extraparams)s)\n'
+ '{\n'
+ '%(varlist)s'
+ '%(parseargs)s'
+ '%(codebefore)s'
+ ' %(setreturn)s%(cname)s(pyg_pointer_get(self, '
+ '%(typename)s)%(arglist)s);\n'
+ '%(codeafter)s\n'
+ '}\n\n'
+ )
+
+ def get_initial_class_substdict(self):
+ return { 'tp_basicsize' : 'PyGPointer',
+ 'tp_weaklistoffset' : '0',
+ 'tp_dictoffset' : '0' }
+
+ def get_field_accessor(self, fieldname):
+ return 'pyg_pointer_get(self, %s)->%s' % (self.objinfo.c_name,
+ fieldname)
+
+ def get_initial_constructor_substdict(self, constructor):
+ substdict = Wrapper.get_initial_constructor_substdict(
+ self, constructor)
+ substdict['typecode'] = self.objinfo.typecode
+ return substdict
+
+def write_headers(data, fp):
+ fp.write('/* -- THIS FILE IS GENERATED - DO NOT EDIT */')
+ fp.write('/* -*- Mode: C; c-basic-offset: 4 -*- */\n\n')
+ fp.write('#include <Python.h>\n\n\n')
+ fp.write(data)
+ fp.resetline()
+ fp.write('\n\n')
+
+def write_body(data, fp):
+ fp.write(data)
+ fp.resetline()
+ fp.write('\n\n')
+
+def write_imports(overrides, fp):
+ fp.write('/* ---------- types from other modules ---------- */\n')
+ for module, pyname, cname in overrides.get_imports():
+ fp.write('static PyTypeObject *_%s;\n' % cname)
+ fp.write('#define %s (*_%s)\n' % (cname, cname))
+ fp.write('\n\n')
+
+def write_type_declarations(parser, fp):
+ fp.write('/* ---------- forward type declarations ---------- */\n')
+ for obj in parser.boxes:
+ fp.write('PyTypeObject Py' + obj.c_name + '_Type;\n')
+ for obj in parser.objects:
+ fp.write('PyTypeObject Py' + obj.c_name + '_Type;\n')
+ for obj in parser.miniobjects:
+ fp.write('PyTypeObject Py' + obj.c_name + '_Type;\n')
+ for interface in parser.interfaces:
+ fp.write('PyTypeObject Py' + interface.c_name + '_Type;\n')
+ fp.write('\n')
+
+
+def sort_parent_children(objects):
+ objects = list(objects)
+ modified = True
+ while modified:
+ modified = False
+ parent_index = None
+ child_index = None
+ for i, obj in enumerate(objects):
+ if obj.parent == 'GObject':
+ continue
+ if obj.parent not in [info.c_name for info in objects[:i]]:
+ for j, info in enumerate(objects[i+1:]):
+ if info.c_name == obj.parent:
+ parent_index = i + 1 + j
+ child_index = i
+ break
+ else:
+ continue
+ break
+ if child_index is not None and parent_index is not None:
+ if child_index != parent_index:
+ objects.insert(child_index, objects.pop(parent_index))
+ modified = True
+ return objects
+
+def write_classes(parser, overrides, fp):
+ ## Sort the objects, so that we generate code for the parent types
+ ## before their children.
+ objects = sort_parent_children(parser.objects)
+ for klass, items in ((GBoxedWrapper, parser.boxes),
+ (GPointerWrapper, parser.pointers),
+ (GObjectWrapper, objects),
+ (GstMiniObjectWrapper, parser.miniobjects),
+ (GInterfaceWrapper, parser.interfaces)):
+ for item in items:
+ instance = klass(parser, item, overrides, fp)
+ instance.write_class()
+ fp.write('\n')
+
+def write_enums(parser, overrides, prefix, fp=sys.stdout):
+ if not parser.enums:
+ return
+ fp.write('\n/* ----------- enums and flags ----------- */\n\n')
+ fp.write(
+ 'void\n' + prefix +
+ '_add_constants(PyObject *module, const gchar *strip_prefix)\n{\n')
+
+ for enum in parser.enums:
+ if overrides.is_type_ignored(enum.c_name):
+ continue
+ if enum.typecode is None:
+ for nick, value in enum.values:
+ fp.write(
+ ' PyModule_AddIntConstant(module, '
+ '(char *) pyg_constant_strip_prefix("%s", strip_prefix), %s);\n'
+ % (value, value))
+ else:
+ if enum.deftype == 'enum':
+ fp.write(' pyg_enum_add(module, "%s", strip_prefix, %s);\n'
+ % (enum.name, enum.typecode))
+ else:
+ fp.write(' pyg_flags_add(module, "%s", strip_prefix, %s);\n'
+ % (enum.name, enum.typecode))
+
+ fp.write('\n')
+ fp.write(' if (PyErr_Occurred())\n')
+ fp.write(' PyErr_Print();\n')
+ fp.write('}\n\n')
+
+def write_extension_init(overrides, prefix, fp):
+ fp.write('/* initialise stuff extension classes */\n')
+ fp.write('void\n' + prefix + '_register_classes(PyObject *d)\n{\n')
+ imports = overrides.get_imports()[:]
+ if imports:
+ bymod = {}
+ for module, pyname, cname in imports:
+ bymod.setdefault(module, []).append((pyname, cname))
+ fp.write(' PyObject *module;\n\n')
+ for module in bymod:
+ fp.write(
+ ' if ((module = PyImport_ImportModule("%s")) != NULL) {\n'
+ % module)
+ fp.write(
+ ' PyObject *moddict = PyModule_GetDict(module);\n\n')
+ for pyname, cname in bymod[module]:
+ fp.write(
+ ' _%s = (PyTypeObject *)PyDict_GetItemString('
+ 'moddict, "%s");\n' % (cname, pyname))
+ fp.write(' if (_%s == NULL) {\n' % cname)
+ fp.write(' PyErr_SetString(PyExc_ImportError,\n')
+ fp.write(' "cannot import name %s from %s");\n'
+ % (pyname, module))
+ fp.write(' return;\n')
+ fp.write(' }\n')
+ fp.write(' } else {\n')
+ fp.write(' PyErr_SetString(PyExc_ImportError,\n')
+ fp.write(' "could not import %s");\n' % module)
+ fp.write(' return;\n')
+ fp.write(' }\n')
+ fp.write('\n')
+ fp.write(overrides.get_init() + '\n')
+ fp.resetline()
+
+def write_registers(parser, overrides, fp):
+ for boxed in parser.boxes:
+ if overrides.is_type_ignored(boxed.c_name):
+ continue
+ fp.write(' pyg_register_boxed(d, "' + boxed.name +
+ '", ' + boxed.typecode +
+ ', &Py' + boxed.c_name +
+ '_Type);\n')
+ for pointer in parser.pointers:
+ if overrides.is_type_ignored(pointer.c_name):
+ continue
+ fp.write(' pyg_register_pointer(d, "' + pointer.name +
+ '", ' + pointer.typecode +
+ ', &Py' + pointer.c_name + '_Type);\n')
+ for interface in parser.interfaces:
+ if overrides.is_type_ignored(interface.c_name):
+ continue
+ fp.write(' pyg_register_interface(d, "' + interface.name +
+ '", '+ interface.typecode + ', &Py' + interface.c_name +
+ '_Type);\n')
+ if interface.interface_info is not None:
+ fp.write(' pyg_register_interface_info(%s, &%s);\n' %
+ (interface.typecode, interface.interface_info))
+
+ objects = parser.objects[:]
+ pos = 0
+ while pos < len(objects):
+ parent = objects[pos].parent
+ for i in range(pos+1, len(objects)):
+ if objects[i].c_name == parent:
+ objects.insert(i+1, objects[pos])
+ del objects[pos]
+ break
+ else:
+ pos = pos + 1
+ for obj in objects:
+ if overrides.is_type_ignored(obj.c_name):
+ continue
+ bases = []
+ if obj.parent != None:
+ bases.append(obj.parent)
+ bases = bases + obj.implements
+ if bases:
+ fp.write(' pygobject_register_class(d, "' + obj.c_name +
+ '", ' + obj.typecode + ', &Py' + obj.c_name +
+ '_Type, Py_BuildValue("(' + 'O' * len(bases) + ')", ' +
+ string.join(map(lambda s: '&Py'+s+'_Type', bases), ', ') +
+ '));\n')
+ else:
+ fp.write(' pygobject_register_class(d, "' + obj.c_name +
+ '", ' + obj.typecode + ', &Py' + obj.c_name +
+ '_Type, NULL);\n')
+ if obj.has_new_constructor_api:
+ fp.write(' pyg_set_object_has_new_constructor(%s);\n' %
+ obj.typecode)
+ else:
+ print >> sys.stderr, (
+ "Warning: Constructor for %s needs to be updated to new API\n"
+ " See http://live.gnome.org/PyGTK_2fWhatsNew28"
+ "#update-constructors") % obj.c_name
+ if obj.class_init_func is not None:
+ fp.write(' pyg_register_class_init(%s, %s);\n' %
+ (obj.typecode, obj.class_init_func))
+ #TODO: register mini-objects
+ miniobjects = parser.miniobjects[:]
+ for obj in miniobjects:
+ bases = []
+ if obj.parent != None:
+ bases.append(obj.parent)
+ bases = bases + obj.implements
+ if bases:
+ fp.write(' pygstminiobject_register_class(d, "' + obj.c_name +
+ '", ' + obj.typecode + ', &Py' + obj.c_name +
+ '_Type, Py_BuildValue("(' + 'O' * len(bases) + ')", ' +
+ string.join(map(lambda s: '&Py'+s+'_Type', bases), ', ') +
+ '));\n')
+ else:
+ fp.write(' pygstminiobject_register_class(d, "' + obj.c_name +
+ '", ' + obj.typecode + ', &Py' + obj.c_name +
+ '_Type, NULL);\n')
+
+ fp.write('}\n')
+
+def write_source(parser, overrides, prefix, fp=FileOutput(sys.stdout)):
+ write_headers(overrides.get_headers(), fp)
+ write_imports(overrides, fp)
+ write_type_declarations(parser, fp)
+ write_body(overrides.get_body(), fp)
+ write_classes(parser, overrides, fp)
+
+ wrapper = Wrapper(parser, None, overrides, fp)
+ wrapper.write_functions(prefix)
+
+ write_enums(parser, overrides, prefix, fp)
+ write_extension_init(overrides, prefix, fp)
+ write_registers(parser, overrides, fp)
+
+def register_types(parser):
+ for boxed in parser.boxes:
+ argtypes.matcher.register_boxed(boxed.c_name, boxed.typecode)
+ for pointer in parser.pointers:
+ argtypes.matcher.register_pointer(pointer.c_name, pointer.typecode)
+ for obj in parser.objects:
+ argtypes.matcher.register_object(obj.c_name, obj.parent, obj.typecode)
+ for obj in parser.miniobjects:
+ argtypes.matcher.register_miniobject(obj.c_name, obj.parent, obj.typecode)
+ for obj in parser.interfaces:
+ argtypes.matcher.register_object(obj.c_name, None, obj.typecode)
+ for enum in parser.enums:
+ if enum.deftype == 'flags':
+ argtypes.matcher.register_flag(enum.c_name, enum.typecode)
+ else:
+ argtypes.matcher.register_enum(enum.c_name, enum.typecode)
+
+usage = 'usage: codegen.py [-o overridesfile] [-p prefix] defsfile'
+def main(argv):
+ o = override.Overrides()
+ prefix = 'pygtk'
+ outfilename = None
+ errorfilename = None
+ extendpath = []
+ opts, args = getopt.getopt(argv[1:], "o:p:r:t:D:x",
+ ["override=", "prefix=", "register=", "outfilename=",
+ "load-types=", "errorfilename=", "extendpath="])
+ defines = {} # -Dkey[=val] options
+
+ for opt, arg in opts:
+ if opt in ('-x', '--extendpath'):
+ extendpath.append(arg)
+ extendpath.insert(0, os.getcwd())
+ o = override.Overrides(path=extendpath)
+
+ for opt, arg in opts:
+ if opt in ('-o', '--override'):
+ o = override.Overrides(arg, path=extendpath)
+ elif opt in ('-p', '--prefix'):
+ prefix = arg
+ elif opt in ('-r', '--register'):
+ # Warning: user has to make sure all -D options appear before -r
+ p = defsparser.DefsParser(arg, defines)
+ p.startParsing()
+ register_types(p)
+ del p
+ elif opt == '--outfilename':
+ outfilename = arg
+ elif opt == '--errorfilename':
+ errorfilename = arg
+ elif opt in ('-t', '--load-types'):
+ globals = {}
+ execfile(arg, globals)
+ elif opt == '-D':
+ nameval = arg.split('=')
+ try:
+ defines[nameval[0]] = nameval[1]
+ except IndexError:
+ defines[nameval[0]] = None
+ if len(args) < 1:
+ print >> sys.stderr, usage
+ return 1
+ if errorfilename:
+ sys.stderr = open(errorfilename, "w")
+ p = defsparser.DefsParser(args[0], defines)
+ if not outfilename:
+ outfilename = os.path.splitext(args[0])[0] + '.c'
+
+ p.startParsing()
+
+ register_types(p)
+ write_source(p, o, prefix, FileOutput(sys.stdout, outfilename))
+
+ functions_coverage.printstats()
+ methods_coverage.printstats()
+ vproxies_coverage.printstats()
+ vaccessors_coverage.printstats()
+ iproxies_coverage.printstats()
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/farstream/python/codegen/definitions.py b/farstream/python/codegen/definitions.py
new file mode 100644
index 000000000..911c8ddb4
--- /dev/null
+++ b/farstream/python/codegen/definitions.py
@@ -0,0 +1,607 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+import copy
+import sys
+
+def get_valid_scheme_definitions(defs):
+ return [x for x in defs if isinstance(x, tuple) and len(x) >= 2]
+
+def unescape(s):
+ s = s.replace('\r\n', '\\r\\n').replace('\t', '\\t')
+ return s.replace('\r', '\\r').replace('\n', '\\n')
+
+def make_docstring(lines):
+ return "(char *) " + '\n'.join(['"%s"' % unescape(s) for s in lines])
+
+# New Parameter class, wich emulates a tuple for compatibility reasons
+class Parameter(object):
+ def __init__(self, ptype, pname, pdflt, pnull, pdir=None, keeprefcount = False):
+ self.ptype = ptype
+ self.pname = pname
+ self.pdflt = pdflt
+ self.pnull = pnull
+ self.pdir = pdir
+ self.keeprefcount = keeprefcount
+
+ def __len__(self): return 4
+ def __getitem__(self, i):
+ return (self.ptype, self.pname, self.pdflt, self.pnull)[i]
+
+ def merge(self, old):
+ if old.pdflt is not None:
+ self.pdflt = old.pdflt
+ if old.pnull is not None:
+ self.pnull = old.pnull
+
+# Parameter for property based constructors
+class Property(object):
+ def __init__(self, pname, optional, argname):
+ self.pname = pname
+ self.optional = optional
+ self.argname = argname
+
+ def merge(self, old):
+ if old.optional is not None:
+ self.optional = old.optional
+ if old.argname is not None:
+ self.argname = old.argname
+
+
+class Definition:
+ docstring = "NULL"
+ def __init__(self, *args):
+ """Create a new defs object of this type. The arguments are the
+ components of the definition"""
+ raise RuntimeError, "this is an abstract class"
+ def merge(self, old):
+ """Merge in customisations from older version of definition"""
+ raise RuntimeError, "this is an abstract class"
+ def write_defs(self, fp=sys.stdout):
+ """write out this definition in defs file format"""
+ raise RuntimeError, "this is an abstract class"
+
+ def guess_return_value_ownership(self):
+ "return 1 if caller owns return value"
+ if getattr(self, 'is_constructor_of', False):
+ self.caller_owns_return = True
+ elif self.ret in ('char*', 'gchar*', 'string'):
+ self.caller_owns_return = True
+ else:
+ self.caller_owns_return = False
+
+
+class ObjectDef(Definition):
+ def __init__(self, name, *args):
+ self.name = name
+ self.module = None
+ self.parent = None
+ self.c_name = None
+ self.typecode = None
+ self.fields = []
+ self.implements = []
+ self.class_init_func = None
+ self.has_new_constructor_api = False
+ for arg in get_valid_scheme_definitions(args):
+ if arg[0] == 'in-module':
+ self.module = arg[1]
+ elif arg[0] == 'docstring':
+ self.docstring = make_docstring(arg[1:])
+ elif arg[0] == 'parent':
+ self.parent = arg[1]
+ elif arg[0] == 'c-name':
+ self.c_name = arg[1]
+ elif arg[0] == 'gtype-id':
+ self.typecode = arg[1]
+ elif arg[0] == 'fields':
+ for parg in arg[1:]:
+ self.fields.append((parg[0], parg[1]))
+ elif arg[0] == 'implements':
+ self.implements.append(arg[1])
+ def merge(self, old):
+ # currently the .h parser doesn't try to work out what fields of
+ # an object structure should be public, so we just copy the list
+ # from the old version ...
+ self.fields = old.fields
+ self.implements = old.implements
+ def write_defs(self, fp=sys.stdout):
+ fp.write('(define-object ' + self.name + '\n')
+ if self.module:
+ fp.write(' (in-module "' + self.module + '")\n')
+ if self.parent != (None, None):
+ fp.write(' (parent "' + self.parent + '")\n')
+ for interface in self.implements:
+ fp.write(' (implements "' + interface + '")\n')
+ if self.c_name:
+ fp.write(' (c-name "' + self.c_name + '")\n')
+ if self.typecode:
+ fp.write(' (gtype-id "' + self.typecode + '")\n')
+ if self.fields:
+ fp.write(' (fields\n')
+ for (ftype, fname) in self.fields:
+ fp.write(' \'("' + ftype + '" "' + fname + '")\n')
+ fp.write(' )\n')
+ fp.write(')\n\n')
+
+class MiniObjectDef(Definition):
+ def __init__(self, name, *args):
+ self.name = name
+ self.module = None
+ self.parent = None
+ self.c_name = None
+ self.typecode = None
+ self.fields = []
+ self.implements = []
+ for arg in args:
+ if type(arg) != type(()) or len(arg) < 2:
+ continue
+ if arg[0] == 'in-module':
+ self.module = arg[1]
+ elif arg[0] == 'parent':
+ self.parent = arg[1]
+ elif arg[0] == 'c-name':
+ self.c_name = arg[1]
+ elif arg[0] == 'gtype-id':
+ self.typecode = arg[1]
+ elif arg[0] == 'fields':
+ for parg in arg[1:]:
+ self.fields.append((parg[0], parg[1]))
+ elif arg[0] == 'implements':
+ self.implements.append(arg[1])
+ def merge(self, old):
+ # currently the .h parser doesn't try to work out what fields of
+ # an object structure should be public, so we just copy the list
+ # from the old version ...
+ self.fields = old.fields
+ self.implements = old.implements
+ def write_defs(self, fp=sys.stdout):
+ fp.write('(define-object ' + self.name + '\n')
+ if self.module:
+ fp.write(' (in-module "' + self.module + '")\n')
+ if self.parent != (None, None):
+ fp.write(' (parent "' + self.parent + '")\n')
+ for interface in self.implements:
+ fp.write(' (implements "' + interface + '")\n')
+ if self.c_name:
+ fp.write(' (c-name "' + self.c_name + '")\n')
+ if self.typecode:
+ fp.write(' (gtype-id "' + self.typecode + '")\n')
+ if self.fields:
+ fp.write(' (fields\n')
+ for (ftype, fname) in self.fields:
+ fp.write(' \'("' + ftype + '" "' + fname + '")\n')
+ fp.write(' )\n')
+ fp.write(')\n\n')
+
+
+class InterfaceDef(Definition):
+ def __init__(self, name, *args):
+ self.name = name
+ self.module = None
+ self.c_name = None
+ self.typecode = None
+ self.vtable = None
+ self.fields = []
+ self.interface_info = None
+ for arg in get_valid_scheme_definitions(args):
+ if arg[0] == 'in-module':
+ self.module = arg[1]
+ elif arg[0] == 'docstring':
+ self.docstring = make_docstring(arg[1:])
+ elif arg[0] == 'c-name':
+ self.c_name = arg[1]
+ elif arg[0] == 'gtype-id':
+ self.typecode = arg[1]
+ elif arg[0] == 'vtable':
+ self.vtable = arg[1]
+ if self.vtable is None:
+ self.vtable = self.c_name + "Iface"
+ def write_defs(self, fp=sys.stdout):
+ fp.write('(define-interface ' + self.name + '\n')
+ if self.module:
+ fp.write(' (in-module "' + self.module + '")\n')
+ if self.c_name:
+ fp.write(' (c-name "' + self.c_name + '")\n')
+ if self.typecode:
+ fp.write(' (gtype-id "' + self.typecode + '")\n')
+ fp.write(')\n\n')
+
+class EnumDef(Definition):
+ def __init__(self, name, *args):
+ self.deftype = 'enum'
+ self.name = name
+ self.in_module = None
+ self.c_name = None
+ self.typecode = None
+ self.values = []
+ for arg in get_valid_scheme_definitions(args):
+ if arg[0] == 'in-module':
+ self.in_module = arg[1]
+ elif arg[0] == 'c-name':
+ self.c_name = arg[1]
+ elif arg[0] == 'gtype-id':
+ self.typecode = arg[1]
+ elif arg[0] == 'values':
+ for varg in arg[1:]:
+ self.values.append((varg[0], varg[1]))
+ def merge(self, old):
+ pass
+ def write_defs(self, fp=sys.stdout):
+ fp.write('(define-' + self.deftype + ' ' + self.name + '\n')
+ if self.in_module:
+ fp.write(' (in-module "' + self.in_module + '")\n')
+ fp.write(' (c-name "' + self.c_name + '")\n')
+ fp.write(' (gtype-id "' + self.typecode + '")\n')
+ if self.values:
+ fp.write(' (values\n')
+ for name, val in self.values:
+ fp.write(' \'("' + name + '" "' + val + '")\n')
+ fp.write(' )\n')
+ fp.write(')\n\n')
+
+class FlagsDef(EnumDef):
+ def __init__(self, *args):
+ apply(EnumDef.__init__, (self,) + args)
+ self.deftype = 'flags'
+
+class BoxedDef(Definition):
+ def __init__(self, name, *args):
+ self.name = name
+ self.module = None
+ self.c_name = None
+ self.typecode = None
+ self.copy = None
+ self.release = None
+ self.fields = []
+ for arg in get_valid_scheme_definitions(args):
+ if arg[0] == 'in-module':
+ self.module = arg[1]
+ elif arg[0] == 'c-name':
+ self.c_name = arg[1]
+ elif arg[0] == 'gtype-id':
+ self.typecode = arg[1]
+ elif arg[0] == 'copy-func':
+ self.copy = arg[1]
+ elif arg[0] == 'release-func':
+ self.release = arg[1]
+ elif arg[0] == 'fields':
+ for parg in arg[1:]:
+ self.fields.append((parg[0], parg[1]))
+ def merge(self, old):
+ # currently the .h parser doesn't try to work out what fields of
+ # an object structure should be public, so we just copy the list
+ # from the old version ...
+ self.fields = old.fields
+ def write_defs(self, fp=sys.stdout):
+ fp.write('(define-boxed ' + self.name + '\n')
+ if self.module:
+ fp.write(' (in-module "' + self.module + '")\n')
+ if self.c_name:
+ fp.write(' (c-name "' + self.c_name + '")\n')
+ if self.typecode:
+ fp.write(' (gtype-id "' + self.typecode + '")\n')
+ if self.copy:
+ fp.write(' (copy-func "' + self.copy + '")\n')
+ if self.release:
+ fp.write(' (release-func "' + self.release + '")\n')
+ if self.fields:
+ fp.write(' (fields\n')
+ for (ftype, fname) in self.fields:
+ fp.write(' \'("' + ftype + '" "' + fname + '")\n')
+ fp.write(' )\n')
+ fp.write(')\n\n')
+
+class PointerDef(Definition):
+ def __init__(self, name, *args):
+ self.name = name
+ self.module = None
+ self.c_name = None
+ self.typecode = None
+ self.fields = []
+ for arg in get_valid_scheme_definitions(args):
+ if arg[0] == 'in-module':
+ self.module = arg[1]
+ elif arg[0] == 'c-name':
+ self.c_name = arg[1]
+ elif arg[0] == 'gtype-id':
+ self.typecode = arg[1]
+ elif arg[0] == 'fields':
+ for parg in arg[1:]:
+ self.fields.append((parg[0], parg[1]))
+ def merge(self, old):
+ # currently the .h parser doesn't try to work out what fields of
+ # an object structure should be public, so we just copy the list
+ # from the old version ...
+ self.fields = old.fields
+ def write_defs(self, fp=sys.stdout):
+ fp.write('(define-pointer ' + self.name + '\n')
+ if self.module:
+ fp.write(' (in-module "' + self.module + '")\n')
+ if self.c_name:
+ fp.write(' (c-name "' + self.c_name + '")\n')
+ if self.typecode:
+ fp.write(' (gtype-id "' + self.typecode + '")\n')
+ if self.fields:
+ fp.write(' (fields\n')
+ for (ftype, fname) in self.fields:
+ fp.write(' \'("' + ftype + '" "' + fname + '")\n')
+ fp.write(' )\n')
+ fp.write(')\n\n')
+
+class MethodDefBase(Definition):
+ def __init__(self, name, *args):
+ dump = 0
+ self.name = name
+ self.ret = None
+ self.caller_owns_return = None
+ self.unblock_threads = None
+ self.c_name = None
+ self.typecode = None
+ self.of_object = None
+ self.params = [] # of form (type, name, default, nullok)
+ self.varargs = 0
+ self.deprecated = None
+ for arg in get_valid_scheme_definitions(args):
+ if arg[0] == 'of-object':
+ self.of_object = arg[1]
+ elif arg[0] == 'docstring':
+ self.docstring = make_docstring(arg[1:])
+ elif arg[0] == 'c-name':
+ self.c_name = arg[1]
+ elif arg[0] == 'gtype-id':
+ self.typecode = arg[1]
+ elif arg[0] == 'return-type':
+ self.ret = arg[1]
+ elif arg[0] == 'caller-owns-return':
+ self.caller_owns_return = arg[1] in ('t', '#t')
+ elif arg[0] == 'unblock-threads':
+ self.unblock_threads = arg[1] in ('t', '#t')
+ elif arg[0] == 'parameters':
+ for parg in arg[1:]:
+ ptype = parg[0]
+ pname = parg[1]
+ pdflt = None
+ pnull = 0
+ pdir = None
+ keeprefcount = False
+ for farg in parg[2:]:
+ assert isinstance(farg, tuple)
+ if farg[0] == 'default':
+ pdflt = farg[1]
+ elif farg[0] == 'null-ok':
+ pnull = 1
+ elif farg[0] == 'direction':
+ pdir = farg[1]
+ elif farg[0] == 'keep-refcount':
+ keeprefcount = True
+ self.params.append(Parameter(ptype, pname, pdflt, pnull, pdir,
+ keeprefcount=keeprefcount))
+ elif arg[0] == 'varargs':
+ self.varargs = arg[1] in ('t', '#t')
+ elif arg[0] == 'deprecated':
+ self.deprecated = arg[1]
+ else:
+ sys.stderr.write("Warning: %s argument unsupported.\n"
+ % (arg[0]))
+ dump = 1
+ if dump:
+ self.write_defs(sys.stderr)
+
+ if self.caller_owns_return is None and self.ret is not None:
+ self.guess_return_value_ownership()
+
+ def merge(self, old, parmerge):
+ self.caller_owns_return = old.caller_owns_return
+ self.varargs = old.varargs
+ # here we merge extra parameter flags accross to the new object.
+ if not parmerge:
+ self.params = copy.deepcopy(old.params)
+ return
+ for i in range(len(self.params)):
+ ptype, pname, pdflt, pnull = self.params[i]
+ for p2 in old.params:
+ if p2[1] == pname:
+ self.params[i] = (ptype, pname, p2[2], p2[3])
+ break
+ def _write_defs(self, fp=sys.stdout):
+ if self.of_object != (None, None):
+ fp.write(' (of-object "' + self.of_object + '")\n')
+ if self.c_name:
+ fp.write(' (c-name "' + self.c_name + '")\n')
+ if self.typecode:
+ fp.write(' (gtype-id "' + self.typecode + '")\n')
+ if self.caller_owns_return:
+ fp.write(' (caller-owns-return #t)\n')
+ if self.unblock_threads:
+ fp.write(' (unblock_threads #t)\n')
+ if self.ret:
+ fp.write(' (return-type "' + self.ret + '")\n')
+ if self.deprecated:
+ fp.write(' (deprecated "' + self.deprecated + '")\n')
+ if self.params:
+ fp.write(' (parameters\n')
+ for ptype, pname, pdflt, pnull in self.params:
+ fp.write(' \'("' + ptype + '" "' + pname +'"')
+ if pdflt: fp.write(' (default "' + pdflt + '")')
+ if pnull: fp.write(' (null-ok)')
+ fp.write(')\n')
+ fp.write(' )\n')
+ if self.varargs:
+ fp.write(' (varargs #t)\n')
+ fp.write(')\n\n')
+
+
+class MethodDef(MethodDefBase):
+ def __init__(self, name, *args):
+ MethodDefBase.__init__(self, name, *args)
+ for item in ('c_name', 'of_object'):
+ if self.__dict__[item] == None:
+ self.write_defs(sys.stderr)
+ raise RuntimeError, "definition missing required %s" % (item,)
+
+ def write_defs(self, fp=sys.stdout):
+ fp.write('(define-method ' + self.name + '\n')
+ self._write_defs(fp)
+
+class VirtualDef(MethodDefBase):
+ def write_defs(self, fp=sys.stdout):
+ fp.write('(define-virtual ' + self.name + '\n')
+ self._write_defs(fp)
+
+class FunctionDef(Definition):
+ def __init__(self, name, *args):
+ dump = 0
+ self.name = name
+ self.in_module = None
+ self.is_constructor_of = None
+ self.ret = None
+ self.caller_owns_return = None
+ self.unblock_threads = None
+ self.c_name = None
+ self.typecode = None
+ self.params = [] # of form (type, name, default, nullok)
+ self.varargs = 0
+ self.deprecated = None
+ for arg in get_valid_scheme_definitions(args):
+ if arg[0] == 'in-module':
+ self.in_module = arg[1]
+ elif arg[0] == 'docstring':
+ self.docstring = make_docstring(arg[1:])
+ elif arg[0] == 'is-constructor-of':
+ self.is_constructor_of = arg[1]
+ elif arg[0] == 'c-name':
+ self.c_name = arg[1]
+ elif arg[0] == 'gtype-id':
+ self.typecode = arg[1]
+ elif arg[0] == 'return-type':
+ self.ret = arg[1]
+ elif arg[0] == 'caller-owns-return':
+ self.caller_owns_return = arg[1] in ('t', '#t')
+ elif arg[0] == 'unblock-threads':
+ self.unblock_threads = arg[1] in ('t', '#t')
+ elif arg[0] == 'parameters':
+ for parg in arg[1:]:
+ ptype = parg[0]
+ pname = parg[1]
+ pdflt = None
+ pnull = 0
+ keeprefcount = False
+ for farg in parg[2:]:
+ if farg[0] == 'default':
+ pdflt = farg[1]
+ elif farg[0] == 'null-ok':
+ pnull = 1
+ elif farg[0] == 'keep-refcount':
+ keeprefcount = True
+ self.params.append(Parameter(ptype, pname, pdflt, pnull,
+ keeprefcount = keeprefcount))
+ elif arg[0] == 'properties':
+ if self.is_constructor_of is None:
+ print >> sys.stderr, "Warning: (properties ...) "\
+ "is only valid for constructors"
+ for prop in arg[1:]:
+ pname = prop[0]
+ optional = False
+ argname = pname
+ for farg in prop[1:]:
+ if farg[0] == 'optional':
+ optional = True
+ elif farg[0] == 'argname':
+ argname = farg[1]
+ self.params.append(Property(pname, optional, argname))
+ elif arg[0] == 'varargs':
+ self.varargs = arg[1] in ('t', '#t')
+ elif arg[0] == 'deprecated':
+ self.deprecated = arg[1]
+ else:
+ sys.stderr.write("Warning: %s argument unsupported\n"
+ % (arg[0],))
+ dump = 1
+ if dump:
+ self.write_defs(sys.stderr)
+
+ if self.caller_owns_return is None and self.ret is not None:
+ self.guess_return_value_ownership()
+ for item in ('c_name',):
+ if self.__dict__[item] == None:
+ self.write_defs(sys.stderr)
+ raise RuntimeError, "definition missing required %s" % (item,)
+
+ _method_write_defs = MethodDef.__dict__['write_defs']
+
+ def merge(self, old, parmerge):
+ self.caller_owns_return = old.caller_owns_return
+ self.varargs = old.varargs
+ if not parmerge:
+ self.params = copy.deepcopy(old.params)
+ return
+ # here we merge extra parameter flags accross to the new object.
+ def merge_param(param):
+ for old_param in old.params:
+ if old_param.pname == param.pname:
+ if isinstance(old_param, Property):
+ # h2def never scans Property's, therefore if
+ # we have one it was manually written, so we
+ # keep it.
+ return copy.deepcopy(old_param)
+ else:
+ param.merge(old_param)
+ return param
+ raise RuntimeError, "could not find %s in old_parameters %r" % (
+ param.pname, [p.pname for p in old.params])
+ try:
+ self.params = map(merge_param, self.params)
+ except RuntimeError:
+ # parameter names changed and we can't find a match; it's
+ # safer to keep the old parameter list untouched.
+ self.params = copy.deepcopy(old.params)
+
+ if not self.is_constructor_of:
+ try:
+ self.is_constructor_of = old.is_constructor_of
+ except AttributeError:
+ pass
+ if isinstance(old, MethodDef):
+ self.name = old.name
+ # transmogrify from function into method ...
+ self.write_defs = self._method_write_defs
+ self.of_object = old.of_object
+ del self.params[0]
+ def write_defs(self, fp=sys.stdout):
+ fp.write('(define-function ' + self.name + '\n')
+ if self.in_module:
+ fp.write(' (in-module "' + self.in_module + '")\n')
+ if self.is_constructor_of:
+ fp.write(' (is-constructor-of "' + self.is_constructor_of +'")\n')
+ if self.c_name:
+ fp.write(' (c-name "' + self.c_name + '")\n')
+ if self.typecode:
+ fp.write(' (gtype-id "' + self.typecode + '")\n')
+ if self.caller_owns_return:
+ fp.write(' (caller-owns-return #t)\n')
+ if self.unblock_threads:
+ fp.write(' (unblock-threads #t)\n')
+ if self.ret:
+ fp.write(' (return-type "' + self.ret + '")\n')
+ if self.deprecated:
+ fp.write(' (deprecated "' + self.deprecated + '")\n')
+ if self.params:
+ if isinstance(self.params[0], Parameter):
+ fp.write(' (parameters\n')
+ for ptype, pname, pdflt, pnull in self.params:
+ fp.write(' \'("' + ptype + '" "' + pname +'"')
+ if pdflt: fp.write(' (default "' + pdflt + '")')
+ if pnull: fp.write(' (null-ok)')
+ fp.write(')\n')
+ fp.write(' )\n')
+ elif isinstance(self.params[0], Property):
+ fp.write(' (properties\n')
+ for prop in self.params:
+ fp.write(' \'("' + prop.pname +'"')
+ if prop.optional: fp.write(' (optional)')
+ fp.write(')\n')
+ fp.write(' )\n')
+ else:
+ assert False, "strange parameter list %r" % self.params[0]
+ if self.varargs:
+ fp.write(' (varargs #t)\n')
+
+ fp.write(')\n\n')
diff --git a/farstream/python/codegen/defsparser.py b/farstream/python/codegen/defsparser.py
new file mode 100644
index 000000000..e24a96967
--- /dev/null
+++ b/farstream/python/codegen/defsparser.py
@@ -0,0 +1,143 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+import os, sys
+import scmexpr
+from definitions import BoxedDef, EnumDef, FlagsDef, FunctionDef, \
+ InterfaceDef, MethodDef, ObjectDef, MiniObjectDef, PointerDef, \
+ VirtualDef
+
+class IncludeParser(scmexpr.Parser):
+ """A simple parser that follows include statements automatically"""
+ def include(self, filename):
+ if not os.path.isabs(filename):
+ filename = os.path.join(os.path.dirname(self.filename), filename)
+
+ # set self.filename to the include name, to handle recursive includes
+ oldfile = self.filename
+ self.filename = filename
+ self.startParsing()
+ self.filename = oldfile
+
+class DefsParser(IncludeParser):
+ def __init__(self, arg, defines={}):
+ IncludeParser.__init__(self, arg)
+ self.objects = []
+ self.miniobjects = []
+ self.interfaces = []
+ self.enums = [] # enums and flags
+ self.boxes = [] # boxed types
+ self.pointers = [] # pointer types
+ self.functions = [] # functions and methods
+ self.virtuals = [] # virtual methods
+ self.c_name = {} # hash of c names of functions
+ self.methods = {} # hash of methods of particular objects
+ self.defines = defines # -Dfoo=bar options, as dictionary
+
+ def define_object(self, *args):
+ odef = apply(ObjectDef, args)
+ self.objects.append(odef)
+ self.c_name[odef.c_name] = odef
+ # TODO: define_mini_object
+ def define_miniobject(self, *args):
+ odef = apply(MiniObjectDef, args)
+ self.miniobjects.append(odef)
+ self.c_name[odef.c_name] = odef
+ def define_interface(self, *args):
+ idef = apply(InterfaceDef, args)
+ self.interfaces.append(idef)
+ self.c_name[idef.c_name] = idef
+ def define_enum(self, *args):
+ edef = apply(EnumDef, args)
+ self.enums.append(edef)
+ self.c_name[edef.c_name] = edef
+ def define_flags(self, *args):
+ fdef = apply(FlagsDef, args)
+ self.enums.append(fdef)
+ self.c_name[fdef.c_name] = fdef
+ def define_boxed(self, *args):
+ bdef = apply(BoxedDef, args)
+ self.boxes.append(bdef)
+ self.c_name[bdef.c_name] = bdef
+ def define_pointer(self, *args):
+ pdef = apply(PointerDef, args)
+ self.pointers.append(pdef)
+ self.c_name[pdef.c_name] = pdef
+ def define_function(self, *args):
+ fdef = apply(FunctionDef, args)
+ self.functions.append(fdef)
+ self.c_name[fdef.c_name] = fdef
+ def define_method(self, *args):
+ mdef = apply(MethodDef, args)
+ self.functions.append(mdef)
+ self.c_name[mdef.c_name] = mdef
+ def define_virtual(self, *args):
+ vdef = apply(VirtualDef, args)
+ self.virtuals.append(vdef)
+ def merge(self, old, parmerge):
+ for obj in self.objects:
+ if old.c_name.has_key(obj.c_name):
+ obj.merge(old.c_name[obj.c_name])
+ for f in self.functions:
+ if old.c_name.has_key(f.c_name):
+ f.merge(old.c_name[f.c_name], parmerge)
+
+ def printMissing(self, old):
+ for obj in self.objects:
+ if not old.c_name.has_key(obj.c_name):
+ obj.write_defs()
+ for f in self.functions:
+ if not old.c_name.has_key(f.c_name):
+ f.write_defs()
+
+ def write_defs(self, fp=sys.stdout):
+ for obj in self.objects:
+ obj.write_defs(fp)
+ # TODO: Add miniobject
+ for obj in self.miniobjects:
+ obj.write_defs(fp)
+ for enum in self.enums:
+ enum.write_defs(fp)
+ for boxed in self.boxes:
+ boxed.write_defs(fp)
+ for pointer in self.pointers:
+ pointer.write_defs(fp)
+ for func in self.functions:
+ func.write_defs(fp)
+
+ def find_object(self, c_name):
+ for obj in self.objects:
+ if obj.c_name == c_name:
+ return obj
+ else:
+ raise ValueError, 'object not found'
+
+ def find_constructor(self, obj, overrides):
+ for func in self.functions:
+ if isinstance(func, FunctionDef) and \
+ func.is_constructor_of == obj.c_name and \
+ not overrides.is_ignored(func.c_name):
+ return func
+
+ def find_methods(self, obj):
+ objname = obj.c_name
+ return filter(lambda func, on=objname: isinstance(func, MethodDef) and
+ func.of_object == on, self.functions)
+
+ def find_virtuals(self, obj):
+ objname = obj.c_name
+ retval = filter(lambda func, on=objname: isinstance(func, VirtualDef) and
+ func.of_object == on, self.virtuals)
+ return retval
+
+ def find_functions(self):
+ return filter(lambda func: isinstance(func, FunctionDef) and
+ not func.is_constructor_of, self.functions)
+
+ def ifdef(self, *args):
+ if args[0] in self.defines:
+ for arg in args[1:]:
+ self.handle(arg)
+
+ def ifndef(self, *args):
+ if args[0] not in self.defines:
+ for arg in args[1:]:
+ self.handle(arg)
diff --git a/farstream/python/codegen/docextract.py b/farstream/python/codegen/docextract.py
new file mode 100644
index 000000000..e6c65053d
--- /dev/null
+++ b/farstream/python/codegen/docextract.py
@@ -0,0 +1,185 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+'''Simple module for extracting GNOME style doc comments from C
+sources, so I can use them for other purposes.'''
+
+import sys, os, string, re
+
+__all__ = ['extract']
+
+class FunctionDoc:
+ def __init__(self):
+ self.name = None
+ self.params = []
+ self.description = ''
+ self.ret = ''
+ def set_name(self, name):
+ self.name = name
+ def add_param(self, name, description):
+ if name == '...':
+ name = 'Varargs'
+ self.params.append((name, description))
+ def append_to_last_param(self, extra):
+ self.params[-1] = (self.params[-1][0], self.params[-1][1] + extra)
+ def append_to_named_param(self, name, extra):
+ for i in range(len(self.params)):
+ if self.params[i][0] == name:
+ self.params[i] = (name, self.params[i][1] + extra)
+ return
+ # fall through to adding extra parameter ...
+ self.add_param(name, extra)
+ def append_description(self, extra):
+ self.description = self.description + extra
+ def append_return(self, extra):
+ self.ret = self.ret + extra
+
+ def get_param_description(self, name):
+ for param, description in self.params:
+ if param == name:
+ return description
+ else:
+ return ''
+
+comment_start_pat = re.compile(r'^\s*/\*\*\s')
+comment_end_pat = re.compile(r'^\s*\*+/')
+comment_line_lead = re.compile(r'^\s*\*\s*')
+funcname_pat = re.compile(r'^(\w+)\s*:?')
+return_pat = re.compile(r'^(returns:|return\s+value:|returns\s*)(.*\n?)$',
+ re.IGNORECASE)
+param_pat = re.compile(r'^@(\S+)\s*:(.*\n?)$')
+
+def parse_file(fp, doc_dict):
+ line = fp.readline()
+ in_comment_block = 0
+ while line:
+ if not in_comment_block:
+ if comment_start_pat.match(line):
+ in_comment_block = 1
+ cur_doc = FunctionDoc()
+ in_description = 0
+ in_return = 0
+ line = fp.readline()
+ continue
+
+ # we are inside a comment block ...
+ if comment_end_pat.match(line):
+ if not cur_doc.name:
+ sys.stderr.write("no function name found in doc comment\n")
+ else:
+ doc_dict[cur_doc.name] = cur_doc
+ in_comment_block = 0
+ line = fp.readline()
+ continue
+
+ # inside a comment block, and not the end of the block ...
+ line = comment_line_lead.sub('', line)
+ if not line: line = '\n'
+
+ if not cur_doc.name:
+ match = funcname_pat.match(line)
+ if match:
+ cur_doc.set_name(match.group(1))
+ elif in_return:
+ match = return_pat.match(line)
+ if match:
+ # assume the last return statement was really part of the
+ # description
+ return_start = match.group(1)
+ cur_doc.ret = match.group(2)
+ cur_doc.description = cur_doc.description + return_start + \
+ cur_doc.ret
+ else:
+ cur_doc.append_return(line)
+ elif in_description:
+ if line[:12] == 'Description:':
+ line = line[12:]
+ match = return_pat.match(line)
+ if match:
+ in_return = 1
+ return_start = match.group(1)
+ cur_doc.append_return(match.group(2))
+ else:
+ cur_doc.append_description(line)
+ elif line == '\n':
+ # end of parameters
+ in_description = 1
+ else:
+ match = param_pat.match(line)
+ if match:
+ param = match.group(1)
+ desc = match.group(2)
+ if param == 'returns':
+ cur_doc.ret = desc
+ else:
+ cur_doc.add_param(param, desc)
+ else:
+ # must be continuation
+ try:
+ if param == 'returns':
+ cur_doc.append_return(line)
+ else:
+ cur_doc.append_to_last_param(line)
+ except:
+ sys.stderr.write('something weird while reading param\n')
+ line = fp.readline()
+
+def parse_dir(dir, doc_dict):
+ for file in os.listdir(dir):
+ if file in ('.', '..'): continue
+ path = os.path.join(dir, file)
+ if os.path.isdir(path):
+ parse_dir(path, doc_dict)
+ if len(file) > 2 and file[-2:] == '.c':
+ parse_file(open(path, 'r'), doc_dict)
+
+def extract(dirs, doc_dict=None):
+ if not doc_dict: doc_dict = {}
+ for dir in dirs:
+ parse_dir(dir, doc_dict)
+ return doc_dict
+
+tmpl_section_pat = re.compile(r'^<!-- ##### (\w+) (\w+) ##### -->$')
+def parse_tmpl(fp, doc_dict):
+ cur_doc = None
+
+ line = fp.readline()
+ while line:
+ match = tmpl_section_pat.match(line)
+ if match:
+ cur_doc = None # new input shouldn't affect the old doc dict
+ sect_type = match.group(1)
+ sect_name = match.group(2)
+
+ if sect_type == 'FUNCTION':
+ cur_doc = doc_dict.get(sect_name)
+ if not cur_doc:
+ cur_doc = FunctionDoc()
+ cur_doc.set_name(sect_name)
+ doc_dict[sect_name] = cur_doc
+ elif line == '<!-- # Unused Parameters # -->\n':
+ cur_doc = None # don't worry about unused params.
+ elif cur_doc:
+ if line[:10] == '@Returns: ':
+ if string.strip(line[10:]):
+ cur_doc.append_return(line[10:])
+ elif line[0] == '@':
+ pos = string.find(line, ':')
+ if pos >= 0:
+ cur_doc.append_to_named_param(line[1:pos], line[pos+1:])
+ else:
+ cur_doc.append_description(line)
+ else:
+ cur_doc.append_description(line)
+
+ line = fp.readline()
+
+def extract_tmpl(dirs, doc_dict=None):
+ if not doc_dict: doc_dict = {}
+ for dir in dirs:
+ for file in os.listdir(dir):
+ if file in ('.', '..'): continue
+ path = os.path.join(dir, file)
+ if os.path.isdir(path):
+ continue
+ if len(file) > 2 and file[-2:] == '.sgml':
+ parse_tmpl(open(path, 'r'), doc_dict)
+ return doc_dict
diff --git a/farstream/python/codegen/docgen.py b/farstream/python/codegen/docgen.py
new file mode 100644
index 000000000..6905a14bf
--- /dev/null
+++ b/farstream/python/codegen/docgen.py
@@ -0,0 +1,752 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+import sys, os, string, re, getopt
+
+import defsparser
+import definitions
+import override
+import docextract
+
+class Node:
+ def __init__(self, name, interfaces=[]):
+ self.name = name
+ self.interfaces = interfaces
+ self.subclasses = []
+ def add_child(self, node):
+ self.subclasses.append(node)
+
+def build_object_tree(parser):
+ # reorder objects so that parent classes come first ...
+ objects = parser.objects[:]
+ pos = 0
+ while pos < len(objects):
+ parent = objects[pos].parent
+ for i in range(pos+1, len(objects)):
+ if objects[i].c_name == parent:
+ objects.insert(i+1, objects[pos])
+ del objects[pos]
+ break
+ else:
+ pos = pos + 1
+
+ root = Node(None)
+ nodes = { None: root }
+ for obj_def in objects:
+ print obj_def.name
+ parent_node = nodes[obj_def.parent]
+ node = Node(obj_def.c_name, obj_def.implements)
+ parent_node.add_child(node)
+ nodes[node.name] = node
+
+ if parser.interfaces:
+ interfaces = Node('gobject.GInterface')
+ root.add_child(interfaces)
+ nodes[interfaces.name] = interfaces
+ for obj_def in parser.interfaces:
+ node = Node(obj_def.c_name)
+ interfaces.add_child(node)
+ nodes[node.name] = node
+
+ if parser.boxes:
+ boxed = Node('gobject.GBoxed')
+ root.add_child(boxed)
+ nodes[boxed.name] = boxed
+ for obj_def in parser.boxes:
+ node = Node(obj_def.c_name)
+ boxed.add_child(node)
+ nodes[node.name] = node
+
+ if parser.pointers:
+ pointers = Node('gobject.GPointer')
+ root.add_child(pointers)
+ nodes[pointers.name] = pointers
+ for obj_def in parser.pointers:
+ node = Node(obj_def.c_name)
+ pointers.add_child(node)
+ nodes[node.name] = node
+
+ return root
+
+class DocWriter:
+ def __init__(self):
+ # parse the defs file
+ self.parser = defsparser.DefsParser(())
+ self.overrides = override.Overrides()
+ self.classmap = {}
+ self.docs = {}
+
+ def add_sourcedirs(self, source_dirs):
+ self.docs = docextract.extract(source_dirs, self.docs)
+ def add_tmpldirs(self, tmpl_dirs):
+ self.docs = docextract.extract_tmpl(tmpl_dirs, self.docs)
+
+ def add_docs(self, defs_file, overrides_file, module_name):
+ '''parse information about a given defs file'''
+ self.parser.filename = defs_file
+ self.parser.startParsing(defs_file)
+ if overrides_file:
+ self.overrides.handle_file(overrides_file)
+
+ for obj in self.parser.objects:
+ if not self.classmap.has_key(obj.c_name):
+ self.classmap[obj.c_name] = '%s.%s' % (module_name, obj.name)
+ for obj in self.parser.interfaces:
+ if not self.classmap.has_key(obj.c_name):
+ self.classmap[obj.c_name] = '%s.%s' % (module_name, obj.name)
+ for obj in self.parser.boxes:
+ if not self.classmap.has_key(obj.c_name):
+ self.classmap[obj.c_name] = '%s.%s' % (module_name, obj.name)
+ for obj in self.parser.pointers:
+ if not self.classmap.has_key(obj.c_name):
+ self.classmap[obj.c_name] = '%s.%s' % (module_name, obj.name)
+
+ def pyname(self, name):
+ return self.classmap.get(name, name)
+
+ def __compare(self, obja, objb):
+ return cmp(self.pyname(obja.c_name), self.pyname(objb.c_name))
+ def output_docs(self, output_prefix):
+ files = []
+
+ # class hierarchy
+ hierarchy = build_object_tree(self.parser)
+ filename = self.create_filename('hierarchy', output_prefix)
+ fp = open(filename, 'w')
+ self.write_full_hierarchy(hierarchy, fp)
+ fp.close()
+
+ obj_defs = self.parser.objects + self.parser.interfaces + \
+ self.parser.boxes + self.parser.pointers
+ obj_defs.sort(self.__compare)
+ for obj_def in obj_defs:
+ filename = self.create_filename(obj_def.c_name, output_prefix)
+ fp = open(filename, 'w')
+ if isinstance(obj_def, definitions.ObjectDef):
+ self.output_object_docs(obj_def, fp)
+ elif isinstance(obj_def, definitions.InterfaceDef):
+ self.output_interface_docs(obj_def, fp)
+ elif isinstance(obj_def, definitions.BoxedDef):
+ self.output_boxed_docs(obj_def, fp)
+ elif isinstance(obj_def, definitions.PointerDef):
+ self.output_boxed_docs(obj_def, fp)
+ fp.close()
+ files.append((os.path.basename(filename), obj_def))
+
+ if files:
+ filename = self.create_toc_filename(output_prefix)
+ fp = open(filename, 'w')
+ self.output_toc(files, fp)
+ fp.close()
+
+ def output_object_docs(self, obj_def, fp=sys.stdout):
+ self.write_class_header(obj_def.c_name, fp)
+
+ self.write_heading('Synopsis', fp)
+ self.write_synopsis(obj_def, fp)
+ self.close_section(fp)
+
+ # construct the inheritence hierarchy ...
+ ancestry = [ (obj_def.c_name, obj_def.implements) ]
+ try:
+ parent = obj_def.parent
+ while parent != None:
+ if parent == 'GObject':
+ ancestry.append(('GObject', []))
+ parent = None
+ else:
+ parent_def = self.parser.find_object(parent)
+ ancestry.append((parent_def.c_name, parent_def.implements))
+ parent = parent_def.parent
+ except ValueError:
+ pass
+ ancestry.reverse()
+ self.write_heading('Ancestry', fp)
+ self.write_hierarchy(obj_def.c_name, ancestry, fp)
+ self.close_section(fp)
+
+ constructor = self.parser.find_constructor(obj_def, self.overrides)
+ if constructor:
+ self.write_heading('Constructor', fp)
+ self.write_constructor(constructor,
+ self.docs.get(constructor.c_name, None),
+ fp)
+ self.close_section(fp)
+
+ methods = self.parser.find_methods(obj_def)
+ methods = filter(lambda meth, self=self:
+ not self.overrides.is_ignored(meth.c_name), methods)
+ if methods:
+ self.write_heading('Methods', fp)
+ for method in methods:
+ self.write_method(method, self.docs.get(method.c_name, None), fp)
+ self.close_section(fp)
+
+ self.write_class_footer(obj_def.c_name, fp)
+
+ def output_interface_docs(self, int_def, fp=sys.stdout):
+ self.write_class_header(int_def.c_name, fp)
+
+ self.write_heading('Synopsis', fp)
+ self.write_synopsis(int_def, fp)
+ self.close_section(fp)
+
+ methods = self.parser.find_methods(int_def)
+ methods = filter(lambda meth, self=self:
+ not self.overrides.is_ignored(meth.c_name), methods)
+ if methods:
+ self.write_heading('Methods', fp)
+ for method in methods:
+ self.write_method(method, self.docs.get(method.c_name, None), fp)
+ self.close_section(fp)
+
+ self.write_class_footer(int_def.c_name, fp)
+
+ def output_boxed_docs(self, box_def, fp=sys.stdout):
+ self.write_class_header(box_def.c_name, fp)
+
+ self.write_heading('Synopsis', fp)
+ self.write_synopsis(box_def, fp)
+ self.close_section(fp)
+
+ constructor = self.parser.find_constructor(box_def, self.overrides)
+ if constructor:
+ self.write_heading('Constructor', fp)
+ self.write_constructor(constructor,
+ self.docs.get(constructor.c_name, None),
+ fp)
+ self.close_section(fp)
+
+ methods = self.parser.find_methods(box_def)
+ methods = filter(lambda meth, self=self:
+ not self.overrides.is_ignored(meth.c_name), methods)
+ if methods:
+ self.write_heading('Methods', fp)
+ for method in methods:
+ self.write_method(method, self.docs.get(method.c_name, None), fp)
+ self.close_section(fp)
+
+ self.write_class_footer(box_def.c_name, fp)
+
+ def output_toc(self, files, fp=sys.stdout):
+ fp.write('TOC\n\n')
+ for filename, obj_def in files:
+ fp.write(obj_def.c_name + ' - ' + filename + '\n')
+
+ # override the following to create a more complex output format
+ def create_filename(self, obj_name, output_prefix):
+ '''Create output filename for this particular object'''
+ return output_prefix + '-' + string.lower(obj_name) + '.txt'
+ def create_toc_filename(self, output_prefix):
+ return self.create_filename(self, 'docs', output_prefix)
+
+ def write_full_hierarchy(self, hierarchy, fp):
+ def handle_node(node, fp, indent=''):
+ for child in node.subclasses:
+ fp.write(indent + node.name)
+ if node.interfaces:
+ fp.write(' (implements ')
+ fp.write(string.join(node.interfaces, ', '))
+ fp.write(')\n')
+ else:
+ fp.write('\n')
+ handle_node(child, fp, indent + ' ')
+ handle_node(hierarchy, fp)
+
+ # these need to handle default args ...
+ def create_constructor_prototype(self, func_def):
+ return func_def.is_constructor_of + '(' + \
+ string.join(map(lambda x: x[1], func_def.params), ', ') + \
+ ')'
+ def create_function_prototype(self, func_def):
+ return func_def.name + '(' + \
+ string.join(map(lambda x: x[1], func_def.params), ', ') + \
+ ')'
+ def create_method_prototype(self, meth_def):
+ return meth_def.of_object + '.' + \
+ meth_def.name + '(' + \
+ string.join(map(lambda x: x[1], meth_def.params), ', ') + \
+ ')'
+
+ def write_class_header(self, obj_name, fp):
+ fp.write('Class %s\n' % obj_name)
+ fp.write('======%s\n\n' % ('=' * len(obj_name)))
+ def write_class_footer(self, obj_name, fp):
+ pass
+ def write_heading(self, text, fp):
+ fp.write('\n' + text + '\n' + ('-' * len(text)) + '\n')
+ def close_section(self, fp):
+ pass
+ def write_synopsis(self, obj_def, fp):
+ fp.write('class %s' % obj_def.c_name)
+ if isinstance(obj_def, definitions.ObjectDef):
+ bases = []
+ if obj_def.parent: bases.append(obj_def.parent)
+ bases = bases = obj_def.implements
+ if bases:
+ fp.write('(%s)' % string.join(bases, ', '))
+ fp.write(':\n')
+
+ constructor = self.parser.find_constructor(obj_def, self.overrides)
+ if constructor:
+ prototype = self.create_constructor_prototype(constructor)
+ fp.write(' def %s\n' % prototype)
+ methods = self.parser.find_methods(obj_def)
+ methods = filter(lambda meth, self=self:
+ not self.overrides.is_ignored(meth.c_name), methods)
+ for meth in methods:
+ prototype = self.create_method_prototype(meth)
+ fp.write(' def %s\n' % prototype)
+
+ def write_hierarchy(self, obj_name, ancestry, fp):
+ indent = ''
+ for name, interfaces in ancestry:
+ fp.write(indent + '+-- ' + name)
+ if interfaces:
+ fp.write(' (implements ')
+ fp.write(string.join(interfaces, ', '))
+ fp.write(')\n')
+ else:
+ fp.write('\n')
+ indent = indent + ' '
+ fp.write('\n')
+ def write_constructor(self, func_def, func_doc, fp):
+ prototype = self.create_constructor_prototype(func_def)
+ fp.write(prototype + '\n\n')
+ for type, name, dflt, null in func_def.params:
+ if func_doc:
+ descr = func_doc.get_param_description(name)
+ else:
+ descr = 'a ' + type
+ fp.write(' ' + name + ': ' + descr + '\n')
+ if func_def.ret and func_def.ret != 'none':
+ if func_doc and func_doc.ret:
+ descr = func_doc.ret
+ else:
+ descr = 'a ' + func_def.ret
+ fp.write(' Returns: ' + descr + '\n')
+ if func_doc and func_doc.description:
+ fp.write(func_doc.description)
+ fp.write('\n\n\n')
+ def write_method(self, meth_def, func_doc, fp):
+ prototype = self.create_method_prototype(meth_def)
+ fp.write(prototype + '\n\n')
+ for type, name, dflt, null in meth_def.params:
+ if func_doc:
+ descr = func_doc.get_param_description(name)
+ else:
+ descr = 'a ' + type
+ fp.write(' ' + name + ': ' + descr + '\n')
+ if meth_def.ret and meth_def.ret != 'none':
+ if func_doc and func_doc.ret:
+ descr = func_doc.ret
+ else:
+ descr = 'a ' + meth_def.ret
+ fp.write(' Returns: ' + descr + '\n')
+ if func_doc and func_doc.description:
+ fp.write('\n')
+ fp.write(func_doc.description)
+ fp.write('\n\n')
+
+class DocbookDocWriter(DocWriter):
+ def __init__(self, use_xml=0):
+ DocWriter.__init__(self)
+ self.use_xml = use_xml
+
+ def create_filename(self, obj_name, output_prefix):
+ '''Create output filename for this particular object'''
+ stem = output_prefix + '-' + string.lower(obj_name)
+ if self.use_xml:
+ return stem + '.xml'
+ else:
+ return stem + '.sgml'
+ def create_toc_filename(self, output_prefix):
+ if self.use_xml:
+ return self.create_filename('classes', output_prefix)
+ else:
+ return self.create_filename('docs', output_prefix)
+
+ # make string -> reference translation func
+ __transtable = [ '-' ] * 256
+ for digit in '0123456789':
+ __transtable[ord(digit)] = digit
+ for letter in 'abcdefghijklmnopqrstuvwxyz':
+ __transtable[ord(letter)] = letter
+ __transtable[ord(string.upper(letter))] = letter
+ __transtable = string.join(__transtable, '')
+
+ def make_class_ref(self, obj_name):
+ return 'class-' + string.translate(obj_name, self.__transtable)
+ def make_method_ref(self, meth_def):
+ return 'method-' + string.translate(meth_def.of_object,
+ self.__transtable) + \
+ '--' + string.translate(meth_def.name, self.__transtable)
+
+ __function_pat = re.compile(r'(\w+)\s*\(\)')
+ def __format_function(self, match):
+ info = self.parser.c_name.get(match.group(1), None)
+ if info:
+ if isinstance(info, defsparser.FunctionDef):
+ if info.is_constructor_of is not None:
+ # should have a link here
+ return '<function>%s()</function>' % \
+ self.pyname(info.is_constructor_of)
+ else:
+ return '<function>' + info.name + '()</function>'
+ if isinstance(info, defsparser.MethodDef):
+ return '<link linkend="' + self.make_method_ref(info) + \
+ '"><function>' + self.pyname(info.of_object) + '.' + \
+ info.name + '()</function></link>'
+ # fall through through
+ return '<function>' + match.group(1) + '()</function>'
+ __parameter_pat = re.compile(r'\@(\w+)')
+ def __format_param(self, match):
+ return '<parameter>' + match.group(1) + '</parameter>'
+ __constant_pat = re.compile(r'\%(-?\w+)')
+ def __format_const(self, match):
+ return '<literal>' + match.group(1) + '</literal>'
+ __symbol_pat = re.compile(r'#([\w-]+)')
+ def __format_symbol(self, match):
+ info = self.parser.c_name.get(match.group(1), None)
+ if info:
+ if isinstance(info, defsparser.FunctionDef):
+ if info.is_constructor_of is not None:
+ # should have a link here
+ return '<methodname>' + self.pyname(info.is_constructor_of) + \
+ '</methodname>'
+ else:
+ return '<function>' + info.name + '</function>'
+ if isinstance(info, defsparser.MethodDef):
+ return '<link linkend="' + self.make_method_ref(info) + \
+ '"><methodname>' + self.pyname(info.of_object) + '.' + \
+ info.name + '</methodname></link>'
+ if isinstance(info, defsparser.ObjectDef) or \
+ isinstance(info, defsparser.InterfaceDef) or \
+ isinstance(info, defsparser.BoxedDef) or \
+ isinstance(info, defsparser.PointerDef):
+ return '<link linkend="' + self.make_class_ref(info.c_name) + \
+ '"><classname>' + self.pyname(info.c_name) + \
+ '</classname></link>'
+ # fall through through
+ return '<literal>' + match.group(1) + '</literal>'
+
+ def reformat_text(self, text, singleline=0):
+ # replace special strings ...
+ text = self.__function_pat.sub(self.__format_function, text)
+ text = self.__parameter_pat.sub(self.__format_param, text)
+ text = self.__constant_pat.sub(self.__format_const, text)
+ text = self.__symbol_pat.sub(self.__format_symbol, text)
+
+ # don't bother with <para> expansion for single line text.
+ if singleline: return text
+
+ lines = string.split(string.strip(text), '\n')
+ for index in range(len(lines)):
+ if string.strip(lines[index]) == '':
+ lines[index] = '</para>\n<para>'
+ continue
+ lines.insert(0, '<para>')
+ lines.append('</para>')
+ return string.join(lines, '\n')
+
+ # write out hierarchy
+ def write_full_hierarchy(self, hierarchy, fp):
+ def handle_node(node, fp, indent=''):
+ if node.name:
+ fp.write('%s<link linkend="%s">%s</link>' %
+ (indent, self.make_class_ref(node.name),
+ self.pyname(node.name)))
+ if node.interfaces:
+ fp.write(' (implements ')
+ for i in range(len(node.interfaces)):
+ fp.write('<link linkend="%s">%s</link>' %
+ (self.make_class_ref(node.interfaces[i]),
+ self.pyname(node.interfaces[i])))
+ if i != len(node.interfaces) - 1:
+ fp.write(', ')
+ fp.write(')\n')
+ else:
+ fp.write('\n')
+
+ indent = indent + ' '
+ node.subclasses.sort(lambda a,b:
+ cmp(self.pyname(a.name), self.pyname(b.name)))
+ for child in node.subclasses:
+ handle_node(child, fp, indent)
+ if self.use_xml:
+ fp.write('<?xml version="1.0" standalone="no"?>\n')
+ fp.write('<!DOCTYPE synopsis PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"\n')
+ fp.write(' "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd">\n')
+ fp.write('<synopsis>')
+ handle_node(hierarchy, fp)
+ fp.write('</synopsis>\n')
+
+ # these need to handle default args ...
+ def create_constructor_prototype(self, func_def):
+ sgml = [ '<constructorsynopsis language="python">\n']
+ sgml.append(' <methodname>__init__</methodname>\n')
+ for type, name, dflt, null in func_def.params:
+ sgml.append(' <methodparam><parameter>')
+ sgml.append(name)
+ sgml.append('</parameter>')
+ if dflt:
+ sgml.append('<initializer>')
+ sgml.append(dflt)
+ sgml.append('</initializer>')
+ sgml.append('</methodparam>\n')
+ if not func_def.params:
+ sgml.append(' <methodparam></methodparam>')
+ sgml.append(' </constructorsynopsis>')
+ return string.join(sgml, '')
+ def create_function_prototype(self, func_def):
+ sgml = [ '<funcsynopsis language="python">\n <funcprototype>\n']
+ sgml.append(' <funcdef><function>')
+ sgml.append(func_def.name)
+ sgml.append('</function></funcdef>\n')
+ for type, name, dflt, null in func_def.params:
+ sgml.append(' <paramdef><parameter>')
+ sgml.append(name)
+ sgml.append('</parameter>')
+ if dflt:
+ sgml.append('<initializer>')
+ sgml.append(dflt)
+ sgml.append('</initializer>')
+ sgml.append('</paramdef>\n')
+ if not func_def.params:
+ sgml.append(' <paramdef></paramdef')
+ sgml.append(' </funcprototype>\n </funcsynopsis>')
+ return string.join(sgml, '')
+ def create_method_prototype(self, meth_def, addlink=0):
+ sgml = [ '<methodsynopsis language="python">\n']
+ sgml.append(' <methodname>')
+ if addlink:
+ sgml.append('<link linkend="%s">' % self.make_method_ref(meth_def))
+ sgml.append(self.pyname(meth_def.name))
+ if addlink:
+ sgml.append('</link>')
+ sgml.append('</methodname>\n')
+ for type, name, dflt, null in meth_def.params:
+ sgml.append(' <methodparam><parameter>')
+ sgml.append(name)
+ sgml.append('</parameter>')
+ if dflt:
+ sgml.append('<initializer>')
+ sgml.append(dflt)
+ sgml.append('</initializer>')
+ sgml.append('</methodparam>\n')
+ if not meth_def.params:
+ sgml.append(' <methodparam></methodparam>')
+ sgml.append(' </methodsynopsis>')
+ return string.join(sgml, '')
+
+ def write_class_header(self, obj_name, fp):
+ if self.use_xml:
+ fp.write('<?xml version="1.0" standalone="no"?>\n')
+ fp.write('<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"\n')
+ fp.write(' "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd">\n')
+ fp.write('<refentry id="' + self.make_class_ref(obj_name) + '">\n')
+ fp.write(' <refmeta>\n')
+ fp.write(' <refentrytitle>%s</refentrytitle>\n'
+ % self.pyname(obj_name))
+ fp.write(' <manvolnum>3</manvolnum>\n')
+ fp.write(' <refmiscinfo>PyGTK Docs</refmiscinfo>\n')
+ fp.write(' </refmeta>\n\n')
+ fp.write(' <refnamediv>\n')
+ fp.write(' <refname>%s</refname><refpurpose></refpurpose>\n'
+ % self.pyname(obj_name))
+ fp.write(' </refnamediv>\n\n')
+ def write_class_footer(self, obj_name, fp):
+ fp.write('</refentry>\n')
+ def write_heading(self, text, fp):
+ fp.write(' <refsect1>\n')
+ fp.write(' <title>' + text + '</title>\n\n')
+ def close_section(self, fp):
+ fp.write(' </refsect1>\n')
+
+ def write_synopsis(self, obj_def, fp):
+ fp.write('<classsynopsis language="python">\n')
+ fp.write(' <ooclass><classname>%s</classname></ooclass>\n'
+ % self.pyname(obj_def.c_name))
+ if isinstance(obj_def, definitions.ObjectDef):
+ if obj_def.parent:
+ fp.write(' <ooclass><classname><link linkend="%s">%s'
+ '</link></classname></ooclass>\n'
+ % (self.make_class_ref(obj_def.parent),
+ self.pyname(obj_def.parent)))
+ for base in obj_def.implements:
+ fp.write(' <ooclass><classname><link linkend="%s">%s'
+ '</link></classname></ooclass>\n'
+ % (self.make_class_ref(base), self.pyname(base)))
+ elif isinstance(obj_def, definitions.InterfaceDef):
+ fp.write(' <ooclass><classname>gobject.GInterface'
+ '</classname></ooclass>\n')
+ elif isinstance(obj_def, definitions.BoxedDef):
+ fp.write(' <ooclass><classname>gobject.GBoxed'
+ '</classname></ooclass>\n')
+ elif isinstance(obj_def, definitions.PointerDef):
+ fp.write(' <ooclass><classname>gobject.GPointer'
+ '</classname></ooclass>\n')
+
+ constructor = self.parser.find_constructor(obj_def, self.overrides)
+ if constructor:
+ fp.write('%s\n' % self.create_constructor_prototype(constructor))
+ methods = self.parser.find_methods(obj_def)
+ methods = filter(lambda meth, self=self:
+ not self.overrides.is_ignored(meth.c_name), methods)
+ for meth in methods:
+ fp.write('%s\n' % self.create_method_prototype(meth, addlink=1))
+ fp.write('</classsynopsis>\n\n')
+
+ def write_hierarchy(self, obj_name, ancestry, fp):
+ fp.write('<synopsis>')
+ indent = ''
+ for name, interfaces in ancestry:
+ fp.write(indent + '+-- <link linkend="' +
+ self.make_class_ref(name) + '">'+ self.pyname(name) + '</link>')
+ if interfaces:
+ fp.write(' (implements ')
+ for i in range(len(interfaces)):
+ fp.write('<link linkend="%s">%s</link>' %
+ (self.make_class_ref(interfaces[i]),
+ self.pyname(interfaces[i])))
+ if i != len(interfaces) - 1:
+ fp.write(', ')
+ fp.write(')\n')
+ else:
+ fp.write('\n')
+ indent = indent + ' '
+ fp.write('</synopsis>\n\n')
+
+ def write_params(self, params, ret, func_doc, fp):
+ if not params and (not ret or ret == 'none'):
+ return
+ fp.write(' <variablelist>\n')
+ for type, name, dflt, null in params:
+ if func_doc:
+ descr = string.strip(func_doc.get_param_description(name))
+ else:
+ descr = 'a ' + type
+ fp.write(' <varlistentry>\n')
+ fp.write(' <term><parameter>%s</parameter>&nbsp;:</term>\n' % name)
+ fp.write(' <listitem><simpara>%s</simpara></listitem>\n' %
+ self.reformat_text(descr, singleline=1))
+ fp.write(' </varlistentry>\n')
+ if ret and ret != 'none':
+ if func_doc and func_doc.ret:
+ descr = string.strip(func_doc.ret)
+ else:
+ descr = 'a ' + ret
+ fp.write(' <varlistentry>\n')
+ fp.write(' <term><emphasis>Returns</emphasis>&nbsp;:</term>\n')
+ fp.write(' <listitem><simpara>%s</simpara></listitem>\n' %
+ self.reformat_text(descr, singleline=1))
+ fp.write(' </varlistentry>\n')
+ fp.write(' </variablelist>\n')
+
+ def write_constructor(self, func_def, func_doc, fp):
+ prototype = self.create_constructor_prototype(func_def)
+ fp.write('<programlisting>%s</programlisting>\n' % prototype)
+ self.write_params(func_def.params, func_def.ret, func_doc, fp)
+
+ if func_doc and func_doc.description:
+ fp.write(self.reformat_text(func_doc.description))
+ fp.write('\n\n\n')
+
+ def write_method(self, meth_def, func_doc, fp):
+ fp.write(' <refsect2 id="' + self.make_method_ref(meth_def) + '">\n')
+ fp.write(' <title>' + self.pyname(meth_def.of_object) + '.' +
+ meth_def.name + '</title>\n\n')
+ prototype = self.create_method_prototype(meth_def)
+ fp.write('<programlisting>%s</programlisting>\n' % prototype)
+ self.write_params(meth_def.params, meth_def.ret, func_doc, fp)
+ if func_doc and func_doc.description:
+ fp.write(self.reformat_text(func_doc.description))
+ fp.write(' </refsect2>\n\n\n')
+
+ def output_toc(self, files, fp=sys.stdout):
+ if self.use_xml:
+ fp.write('<?xml version="1.0" standalone="no"?>\n')
+ fp.write('<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"\n')
+ fp.write(' "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd">\n')
+ #for filename, obj_def in files:
+ # fp.write(' <!ENTITY ' + string.translate(obj_def.c_name,
+ # self.__transtable) +
+ # ' SYSTEM "' + filename + '" >\n')
+ #fp.write(']>\n\n')
+
+ #fp.write('<reference id="class-reference">\n')
+ #fp.write(' <title>Class Documentation</title>\n')
+ #for filename, obj_def in files:
+ # fp.write('&' + string.translate(obj_def.c_name,
+ # self.__transtable) + ';\n')
+ #fp.write('</reference>\n')
+
+ fp.write('<reference id="class-reference" xmlns:xi="http://www.w3.org/2001/XInclude">\n')
+ fp.write(' <title>Class Reference</title>\n')
+ for filename, obj_def in files:
+ fp.write(' <xi:include href="%s"/>\n' % filename)
+ fp.write('</reference>\n')
+ else:
+ fp.write('<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook V4.1.2//EN" [\n')
+ for filename, obj_def in files:
+ fp.write(' <!ENTITY ' + string.translate(obj_def.c_name,
+ self.__transtable) +
+ ' SYSTEM "' + filename + '" >\n')
+ fp.write(']>\n\n')
+
+ fp.write('<book id="index">\n\n')
+ fp.write(' <bookinfo>\n')
+ fp.write(' <title>PyGTK Docs</title>\n')
+ fp.write(' <authorgroup>\n')
+ fp.write(' <author>\n')
+ fp.write(' <firstname>James</firstname>\n')
+ fp.write(' <surname>Henstridge</surname>\n')
+ fp.write(' </author>\n')
+ fp.write(' </authorgroup>\n')
+ fp.write(' </bookinfo>\n\n')
+
+ fp.write(' <chapter id="class-hierarchy">\n')
+ fp.write(' <title>Class Hierarchy</title>\n')
+ fp.write(' <para>Not done yet</para>\n')
+ fp.write(' </chapter>\n\n')
+
+ fp.write(' <reference id="class-reference">\n')
+ fp.write(' <title>Class Documentation</title>\n')
+ for filename, obj_def in files:
+ fp.write('&' + string.translate(obj_def.c_name,
+ self.__transtable) + ';\n')
+
+ fp.write(' </reference>\n')
+ fp.write('</book>\n')
+
+if __name__ == '__main__':
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "d:s:o:",
+ ["defs-file=", "override=", "source-dir=",
+ "output-prefix="])
+ except getopt.error, e:
+ sys.stderr.write('docgen.py: %s\n' % e)
+ sys.stderr.write(
+ 'usage: docgen.py -d file.defs [-s /src/dir] [-o output-prefix]\n')
+ sys.exit(1)
+ defs_file = None
+ overrides_file = None
+ source_dirs = []
+ output_prefix = 'docs'
+ for opt, arg in opts:
+ if opt in ('-d', '--defs-file'):
+ defs_file = arg
+ if opt in ('--override',):
+ overrides_file = arg
+ elif opt in ('-s', '--source-dir'):
+ source_dirs.append(arg)
+ elif opt in ('-o', '--output-prefix'):
+ output_prefix = arg
+ if len(args) != 0 or not defs_file:
+ sys.stderr.write(
+ 'usage: docgen.py -d file.defs [-s /src/dir] [-o output-prefix]\n')
+ sys.exit(1)
+
+ d = DocbookDocWriter()
+ d.add_sourcedirs(source_dirs)
+ d.add_docs(defs_file, overrides_file, 'gtk')
+ d.output_docs(output_prefix)
diff --git a/farstream/python/codegen/h2def.py b/farstream/python/codegen/h2def.py
new file mode 100755
index 000000000..d4b21356c
--- /dev/null
+++ b/farstream/python/codegen/h2def.py
@@ -0,0 +1,536 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# Search through a header file looking for function prototypes.
+# For each prototype, generate a scheme style definition.
+# GPL'ed
+# Toby D. Reeves <toby@max.rl.plh.af.mil>
+#
+# Modified by James Henstridge <james@daa.com.au> to output stuff in
+# Havoc's new defs format. Info on this format can be seen at:
+# http://www.gnome.org/mailing-lists/archives/gtk-devel-list/2000-January/0085.shtml
+# Updated to be PEP-8 compatible and refactored to use OOP
+
+import getopt
+import os
+import re
+import string
+import sys
+
+import defsparser
+
+# ------------------ Create typecodes from typenames ---------
+
+_upperstr_pat1 = re.compile(r'([^A-Z])([A-Z])')
+_upperstr_pat2 = re.compile(r'([A-Z][A-Z])([A-Z][0-9a-z])')
+_upperstr_pat3 = re.compile(r'^([A-Z])([A-Z])')
+
+def to_upper_str(name):
+ """Converts a typename to the equivalent upercase and underscores
+ name. This is used to form the type conversion macros and enum/flag
+ name variables"""
+ name = _upperstr_pat1.sub(r'\1_\2', name)
+ name = _upperstr_pat2.sub(r'\1_\2', name)
+ name = _upperstr_pat3.sub(r'\1_\2', name, count=1)
+ return string.upper(name)
+
+def typecode(typename):
+ """create a typecode (eg. GTK_TYPE_WIDGET) from a typename"""
+ return string.replace(to_upper_str(typename), '_', '_TYPE_', 1)
+
+
+# ------------------ Find object definitions -----------------
+
+def strip_comments(buf):
+ parts = []
+ lastpos = 0
+ while 1:
+ pos = string.find(buf, '/*', lastpos)
+ if pos >= 0:
+ parts.append(buf[lastpos:pos])
+ pos = string.find(buf, '*/', pos)
+ if pos >= 0:
+ lastpos = pos + 2
+ else:
+ break
+ else:
+ parts.append(buf[lastpos:])
+ break
+ return string.join(parts, '')
+
+obj_name_pat = "[A-Z][a-z]*[A-Z][A-Za-z0-9]*"
+
+split_prefix_pat = re.compile('([A-Z]+[a-z]*)([A-Za-z0-9]+)')
+
+def find_obj_defs(buf, objdefs=[]):
+ """
+ Try to find object definitions in header files.
+ """
+
+ # filter out comments from buffer.
+ buf = strip_comments(buf)
+
+ maybeobjdefs = [] # contains all possible objects from file
+
+ # first find all structures that look like they may represent a GtkObject
+ pat = re.compile("struct _(" + obj_name_pat + ")\s*{\s*" +
+ "(" + obj_name_pat + ")\s+", re.MULTILINE)
+ pos = 0
+ while pos < len(buf):
+ m = pat.search(buf, pos)
+ if not m: break
+ maybeobjdefs.append((m.group(1), m.group(2)))
+ pos = m.end()
+
+ # handle typedef struct { ... } style struct defs.
+ pat = re.compile("typedef struct\s+[_\w]*\s*{\s*" +
+ "(" + obj_name_pat + ")\s+[^}]*}\s*" +
+ "(" + obj_name_pat + ")\s*;", re.MULTILINE)
+ pos = 0
+ while pos < len(buf):
+ m = pat.search(buf, pos)
+ if not m: break
+ maybeobjdefs.append((m.group(2), m.group(2)))
+ pos = m.end()
+
+ # now find all structures that look like they might represent a class:
+ pat = re.compile("struct _(" + obj_name_pat + ")Class\s*{\s*" +
+ "(" + obj_name_pat + ")Class\s+", re.MULTILINE)
+ pos = 0
+ while pos < len(buf):
+ m = pat.search(buf, pos)
+ if not m: break
+ t = (m.group(1), m.group(2))
+ # if we find an object structure together with a corresponding
+ # class structure, then we have probably found a GtkObject subclass.
+ if t in maybeobjdefs:
+ objdefs.append(t)
+ pos = m.end()
+
+ pat = re.compile("typedef struct\s+[_\w]*\s*{\s*" +
+ "(" + obj_name_pat + ")Class\s+[^}]*}\s*" +
+ "(" + obj_name_pat + ")Class\s*;", re.MULTILINE)
+ pos = 0
+ while pos < len(buf):
+ m = pat.search(buf, pos)
+ if not m: break
+ t = (m.group(2), m.group(1))
+ # if we find an object structure together with a corresponding
+ # class structure, then we have probably found a GtkObject subclass.
+ if t in maybeobjdefs:
+ objdefs.append(t)
+ pos = m.end()
+
+ # now find all structures that look like they might represent
+ # a class inherited from GTypeInterface:
+ pat = re.compile("struct _(" + obj_name_pat + ")Class\s*{\s*" +
+ "GTypeInterface\s+", re.MULTILINE)
+ pos = 0
+ while pos < len(buf):
+ m = pat.search(buf, pos)
+ if not m: break
+ t = (m.group(1), '')
+ t2 = (m.group(1)+'Class', 'GTypeInterface')
+ # if we find an object structure together with a corresponding
+ # class structure, then we have probably found a GtkObject subclass.
+ if t2 in maybeobjdefs:
+ objdefs.append(t)
+ pos = m.end()
+
+ # now find all structures that look like they might represent
+ # an Iface inherited from GTypeInterface:
+ pat = re.compile("struct _(" + obj_name_pat + ")Iface\s*{\s*" +
+ "GTypeInterface\s+", re.MULTILINE)
+ pos = 0
+ while pos < len(buf):
+ m = pat.search(buf, pos)
+ if not m: break
+ t = (m.group(1), '')
+ t2 = (m.group(1)+'Iface', 'GTypeInterface')
+ # if we find an object structure together with a corresponding
+ # class structure, then we have probably found a GtkObject subclass.
+ if t2 in maybeobjdefs:
+ objdefs.append(t)
+ pos = m.end()
+
+def sort_obj_defs(objdefs):
+ objdefs.sort() # not strictly needed, but looks nice
+ pos = 0
+ while pos < len(objdefs):
+ klass,parent = objdefs[pos]
+ for i in range(pos+1, len(objdefs)):
+ # parent below subclass ... reorder
+ if objdefs[i][0] == parent:
+ objdefs.insert(i+1, objdefs[pos])
+ del objdefs[pos]
+ break
+ else:
+ pos = pos + 1
+ return objdefs
+
+# ------------------ Find enum definitions -----------------
+
+def find_enum_defs(buf, enums=[]):
+ # strip comments
+ # bulk comments
+ buf = strip_comments(buf)
+
+ buf = re.sub('\n', ' ', buf)
+
+ enum_pat = re.compile(r'enum\s*{([^}]*)}\s*([A-Z][A-Za-z]*)(\s|;)')
+ splitter = re.compile(r'\s*,\s', re.MULTILINE)
+ pos = 0
+ while pos < len(buf):
+ m = enum_pat.search(buf, pos)
+ if not m: break
+
+ name = m.group(2)
+ vals = m.group(1)
+ isflags = string.find(vals, '<<') >= 0
+ entries = []
+ for val in splitter.split(vals):
+ if not string.strip(val): continue
+ entries.append(string.split(val)[0])
+ if name != 'GdkCursorType':
+ enums.append((name, isflags, entries))
+
+ pos = m.end()
+
+# ------------------ Find function definitions -----------------
+
+def clean_func(buf):
+ """
+ Ideally would make buf have a single prototype on each line.
+ Actually just cuts out a good deal of junk, but leaves lines
+ where a regex can figure prototypes out.
+ """
+ # bulk comments
+ buf = strip_comments(buf)
+
+ # compact continued lines
+ pat = re.compile(r"""\\\n""", re.MULTILINE)
+ buf = pat.sub('', buf)
+
+ # Preprocess directives
+ pat = re.compile(r"""^[#].*?$""", re.MULTILINE)
+ buf = pat.sub('', buf)
+
+ #typedefs, stucts, and enums
+ pat = re.compile(r"""^(typedef|struct|enum)(\s|.|\n)*?;\s*""",
+ re.MULTILINE)
+ buf = pat.sub('', buf)
+
+ #strip DECLS macros
+ pat = re.compile(r"""G_BEGIN_DECLS|BEGIN_LIBGTOP_DECLS""", re.MULTILINE)
+ buf = pat.sub('', buf)
+
+ #extern "C"
+ pat = re.compile(r"""^\s*(extern)\s+\"C\"\s+{""", re.MULTILINE)
+ buf = pat.sub('', buf)
+
+ #multiple whitespace
+ pat = re.compile(r"""\s+""", re.MULTILINE)
+ buf = pat.sub(' ', buf)
+
+ #clean up line ends
+ pat = re.compile(r""";\s*""", re.MULTILINE)
+ buf = pat.sub('\n', buf)
+ buf = buf.lstrip()
+
+ #associate *, &, and [] with type instead of variable
+ #pat = re.compile(r'\s+([*|&]+)\s*(\w+)')
+ pat = re.compile(r' \s* ([*|&]+) \s* (\w+)', re.VERBOSE)
+ buf = pat.sub(r'\1 \2', buf)
+ pat = re.compile(r'\s+ (\w+) \[ \s* \]', re.VERBOSE)
+ buf = pat.sub(r'[] \1', buf)
+
+ # make return types that are const work.
+ buf = string.replace(buf, 'G_CONST_RETURN ', 'const-')
+ buf = string.replace(buf, 'const ', 'const-')
+
+ return buf
+
+proto_pat=re.compile(r"""
+(?P<ret>(-|\w|\&|\*)+\s*) # return type
+\s+ # skip whitespace
+(?P<func>\w+)\s*[(] # match the function name until the opening (
+\s*(?P<args>.*?)\s*[)] # group the function arguments
+""", re.IGNORECASE|re.VERBOSE)
+#"""
+arg_split_pat = re.compile("\s*,\s*")
+
+get_type_pat = re.compile(r'(const-)?([A-Za-z0-9]+)\*?\s+')
+pointer_pat = re.compile('.*\*$')
+func_new_pat = re.compile('(\w+)_new$')
+
+class DefsWriter:
+ def __init__(self, fp=None, prefix=None, verbose=False,
+ defsfilter=None):
+ if not fp:
+ fp = sys.stdout
+
+ self.fp = fp
+ self.prefix = prefix
+ self.verbose = verbose
+
+ self._enums = {}
+ self._objects = {}
+ self._functions = {}
+ if defsfilter:
+ filter = defsparser.DefsParser(defsfilter)
+ filter.startParsing()
+ for func in filter.functions + filter.methods.values():
+ self._functions[func.c_name] = func
+ for obj in filter.objects + filter.boxes + filter.interfaces:
+ self._objects[obj.c_name] = func
+ for obj in filter.enums:
+ self._enums[obj.c_name] = func
+
+ def write_def(self, deffile):
+ buf = open(deffile).read()
+
+ self.fp.write('\n;; From %s\n\n' % os.path.basename(deffile))
+ self._define_func(buf)
+ self.fp.write('\n')
+
+ def write_enum_defs(self, enums, fp=None):
+ if not fp:
+ fp = self.fp
+
+ fp.write(';; Enumerations and flags ...\n\n')
+ trans = string.maketrans(string.uppercase + '_',
+ string.lowercase + '-')
+ filter = self._enums
+ for cname, isflags, entries in enums:
+ if filter:
+ if cname in filter:
+ continue
+ name = cname
+ module = None
+ m = split_prefix_pat.match(cname)
+ if m:
+ module = m.group(1)
+ name = m.group(2)
+ if isflags:
+ fp.write('(define-flags ' + name + '\n')
+ else:
+ fp.write('(define-enum ' + name + '\n')
+ if module:
+ fp.write(' (in-module "' + module + '")\n')
+ fp.write(' (c-name "' + cname + '")\n')
+ fp.write(' (gtype-id "' + typecode(cname) + '")\n')
+ prefix = entries[0]
+ for ent in entries:
+ # shorten prefix til we get a match ...
+ # and handle GDK_FONT_FONT, GDK_FONT_FONTSET case
+ while ent[:len(prefix)] != prefix or len(prefix) >= len(ent):
+ prefix = prefix[:-1]
+ prefix_len = len(prefix)
+ fp.write(' (values\n')
+ for ent in entries:
+ fp.write(' \'("%s" "%s")\n' %
+ (string.translate(ent[prefix_len:], trans), ent))
+ fp.write(' )\n')
+ fp.write(')\n\n')
+
+ def write_obj_defs(self, objdefs, fp=None):
+ if not fp:
+ fp = self.fp
+
+ fp.write(';; -*- scheme -*-\n')
+ fp.write('; object definitions ...\n')
+
+ filter = self._objects
+ for klass, parent in objdefs:
+ if filter:
+ if klass in filter:
+ continue
+ m = split_prefix_pat.match(klass)
+ cmodule = None
+ cname = klass
+ if m:
+ cmodule = m.group(1)
+ cname = m.group(2)
+ fp.write('(define-object ' + cname + '\n')
+ if cmodule:
+ fp.write(' (in-module "' + cmodule + '")\n')
+ if parent:
+ fp.write(' (parent "' + parent + '")\n')
+ fp.write(' (c-name "' + klass + '")\n')
+ fp.write(' (gtype-id "' + typecode(klass) + '")\n')
+ # should do something about accessible fields
+ fp.write(')\n\n')
+
+ def _define_func(self, buf):
+ buf = clean_func(buf)
+ buf = string.split(buf,'\n')
+ filter = self._functions
+ for p in buf:
+ if not p:
+ continue
+ m = proto_pat.match(p)
+ if m == None:
+ if self.verbose:
+ sys.stderr.write('No match:|%s|\n' % p)
+ continue
+ func = m.group('func')
+ if func[0] == '_':
+ continue
+ if filter:
+ if func in filter:
+ continue
+ ret = m.group('ret')
+ args = m.group('args')
+ args = arg_split_pat.split(args)
+ for i in range(len(args)):
+ spaces = string.count(args[i], ' ')
+ if spaces > 1:
+ args[i] = string.replace(args[i], ' ', '-', spaces - 1)
+
+ self._write_func(func, ret, args)
+
+ def _write_func(self, name, ret, args):
+ if len(args) >= 1:
+ # methods must have at least one argument
+ munged_name = name.replace('_', '')
+ m = get_type_pat.match(args[0])
+ if m:
+ obj = m.group(2)
+ if munged_name[:len(obj)] == obj.lower():
+ self._write_method(obj, name, ret, args)
+ return
+
+ if self.prefix:
+ l = len(self.prefix)
+ if name[:l] == self.prefix and name[l] == '_':
+ fname = name[l+1:]
+ else:
+ fname = name
+ else:
+ fname = name
+
+ # it is either a constructor or normal function
+ self.fp.write('(define-function ' + fname + '\n')
+ self.fp.write(' (c-name "' + name + '")\n')
+
+ # Hmmm... Let's asume that a constructor function name
+ # ends with '_new' and it returns a pointer.
+ m = func_new_pat.match(name)
+ if pointer_pat.match(ret) and m:
+ cname = ''
+ for s in m.group(1).split ('_'):
+ cname += s.title()
+ if cname != '':
+ self.fp.write(' (is-constructor-of "' + cname + '")\n')
+
+ self._write_return(ret)
+ self._write_arguments(args)
+
+ def _write_method(self, obj, name, ret, args):
+ regex = string.join(map(lambda x: x+'_?', string.lower(obj)),'')
+ mname = re.sub(regex, '', name, 1)
+ if self.prefix:
+ l = len(self.prefix) + 1
+ if mname[:l] == self.prefix and mname[l+1] == '_':
+ mname = mname[l+1:]
+ self.fp.write('(define-method ' + mname + '\n')
+ self.fp.write(' (of-object "' + obj + '")\n')
+ self.fp.write(' (c-name "' + name + '")\n')
+ self._write_return(ret)
+ self._write_arguments(args[1:])
+
+ def _write_return(self, ret):
+ if ret != 'void':
+ self.fp.write(' (return-type "' + ret + '")\n')
+ else:
+ self.fp.write(' (return-type "none")\n')
+
+ def _write_arguments(self, args):
+ is_varargs = 0
+ has_args = len(args) > 0
+ for arg in args:
+ if arg == '...':
+ is_varargs = 1
+ elif arg in ('void', 'void '):
+ has_args = 0
+ if has_args:
+ self.fp.write(' (parameters\n')
+ for arg in args:
+ if arg != '...':
+ tupleArg = tuple(string.split(arg))
+ if len(tupleArg) == 2:
+ self.fp.write(' \'("%s" "%s")\n' % tupleArg)
+ self.fp.write(' )\n')
+ if is_varargs:
+ self.fp.write(' (varargs #t)\n')
+ self.fp.write(')\n\n')
+
+# ------------------ Main function -----------------
+
+def main(args):
+ verbose = False
+ onlyenums = False
+ onlyobjdefs = False
+ separate = False
+ modulename = None
+ defsfilter = None
+ opts, args = getopt.getopt(args[1:], 'vs:m:f:',
+ ['onlyenums', 'onlyobjdefs',
+ 'modulename=', 'separate=',
+ 'defsfilter='])
+ for o, v in opts:
+ if o == '-v':
+ verbose = True
+ if o == '--onlyenums':
+ onlyenums = True
+ if o == '--onlyobjdefs':
+ onlyobjdefs = True
+ if o in ('-s', '--separate'):
+ separate = v
+ if o in ('-m', '--modulename'):
+ modulename = v
+ if o in ('-f', '--defsfilter'):
+ defsfilter = v
+
+ if not args[0:1]:
+ print 'Must specify at least one input file name'
+ return -1
+
+ # read all the object definitions in
+ objdefs = []
+ enums = []
+ for filename in args:
+ buf = open(filename).read()
+ find_obj_defs(buf, objdefs)
+ find_enum_defs(buf, enums)
+ objdefs = sort_obj_defs(objdefs)
+
+ if separate:
+ methods = file(separate + '.defs', 'w')
+ types = file(separate + '-types.defs', 'w')
+
+ dw = DefsWriter(methods, prefix=modulename, verbose=verbose,
+ defsfilter=defsfilter)
+ dw.write_obj_defs(objdefs, types)
+ dw.write_enum_defs(enums, types)
+ print "Wrote %s-types.defs" % separate
+
+ for filename in args:
+ dw.write_def(filename)
+ print "Wrote %s.defs" % separate
+ else:
+ dw = DefsWriter(prefix=modulename, verbose=verbose,
+ defsfilter=defsfilter)
+
+ if onlyenums:
+ dw.write_enum_defs(enums)
+ elif onlyobjdefs:
+ dw.write_obj_defs(objdefs)
+ else:
+ dw.write_obj_defs(objdefs)
+ dw.write_enum_defs(enums)
+
+ for filename in args:
+ dw.write_def(filename)
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/farstream/python/codegen/mergedefs.py b/farstream/python/codegen/mergedefs.py
new file mode 100755
index 000000000..773e499bb
--- /dev/null
+++ b/farstream/python/codegen/mergedefs.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+
+import optparse
+
+import defsparser
+
+parser = optparse.OptionParser(
+ usage="usage: %prog [options] generated-defs old-defs")
+parser.add_option("-p", "--merge-parameters",
+ help="Merge changes in function/methods parameter lists",
+ action="store_true", dest="parmerge", default=False)
+(options, args) = parser.parse_args()
+
+if len(args) != 2:
+ parser.error("wrong number of arguments")
+
+newp = defsparser.DefsParser(args[0])
+oldp = defsparser.DefsParser(args[1])
+
+newp.startParsing()
+oldp.startParsing()
+
+newp.merge(oldp, options.parmerge)
+
+newp.write_defs()
diff --git a/farstream/python/codegen/mkskel.py b/farstream/python/codegen/mkskel.py
new file mode 100755
index 000000000..61f520bf7
--- /dev/null
+++ b/farstream/python/codegen/mkskel.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+
+import sys, os, getopt
+
+module_init_template = \
+'/* -*- Mode: C; c-basic-offset: 4 -*- */\n' + \
+'#ifdef HAVE_CONFIG_H\n' + \
+'# include "config.h"\n' + \
+'#endif\n' + \
+'#include <Python.h>\n' + \
+'#include <pygtk.h>\n' + \
+'\n' + \
+'/* include any extra headers needed here */\n' + \
+'\n' + \
+'void %(prefix)s_register_classes(PyObject *d);\n' + \
+'extern PyMethodDef %(prefix)s_functions[];\n' + \
+'\n' + \
+'DL_EXPORT(void)\n' + \
+'init%(module)s(void)\n' + \
+'{\n' + \
+' PyObject *m, *d;\n' + \
+'\n' + \
+' /* perform any initialisation required by the library here */\n' + \
+'\n' + \
+' m = Py_InitModule("%(module)s", %(prefix)s_functions);\n' + \
+' d = PyModule_GetDict(m);\n' + \
+'\n' + \
+' init_pygtk();\n' + \
+'\n' + \
+' %(prefix)s_register_classes(d);\n' + \
+'\n' + \
+' /* add anything else to the module dictionary (such as constants) */\n' +\
+'\n' + \
+' if (PyErr_Occurred())\n' + \
+' Py_FatalError("could not initialise module %(module)s");\n' + \
+'}\n'
+
+override_template = \
+'/* -*- Mode: C; c-basic-offset: 4 -*- */\n' + \
+'%%%%\n' + \
+'headers\n' + \
+'/* include any required headers here */\n' + \
+'%%%%\n' + \
+'init\n' + \
+' /* include any code here that needs to be executed before the\n' + \
+' * extension classes get initialised */\n' + \
+'%%%%\n' + \
+'\n' + \
+'/* you should add appropriate ignore, ignore-glob and\n' + \
+' * override sections here */\n'
+
+def open_with_backup(file):
+ if os.path.exists(file):
+ try:
+ os.rename(file, file+'~')
+ except OSError:
+ # fail silently if we can't make a backup
+ pass
+ return open(file, 'w')
+
+def write_skels(fileprefix, prefix, module):
+ fp = open_with_backup(fileprefix+'module.c')
+ fp.write(module_init_template % { 'prefix': prefix, 'module': module })
+ fp.close()
+ fp = open_with_backup(fileprefix+'.override')
+ fp.write(override_template % { 'prefix': prefix, 'module': module })
+ fp.close()
+
+if __name__ == '__main__':
+ opts, args = getopt.getopt(sys.argv[1:], 'f:p:m:h',
+ ['file-prefix=', 'prefix=', 'module=', 'help'])
+ fileprefix = None
+ prefix = None
+ module = None
+ for opt, arg in opts:
+ if opt in ('-f', '--file-prefix'):
+ fileprefix = arg
+ elif opt in ('-p', '--prefix'):
+ prefix = arg
+ elif opt in ('-m', '--module'):
+ module = arg
+ elif opt in ('-h', '--help'):
+ print 'usage: mkskel.py -f fileprefix -p prefix -m module'
+ sys.exit(0)
+ if not fileprefix or not prefix or not module:
+ print 'usage: mkskel.py -f fileprefix -p prefix -m module'
+ sys.exit(1)
+ write_skels(fileprefix, prefix, module)
diff --git a/farstream/python/codegen/override.py b/farstream/python/codegen/override.py
new file mode 100644
index 000000000..2e8c6a4c3
--- /dev/null
+++ b/farstream/python/codegen/override.py
@@ -0,0 +1,288 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+
+# this file contains code for loading up an override file. The override file
+# provides implementations of functions where the code generator could not
+# do its job correctly.
+
+import fnmatch
+import os
+import re
+import string
+import sys
+
+def class2cname(klass, method):
+ c_name = ''
+ for c in klass:
+ if c.isupper():
+ c_name += '_' + c.lower()
+ else:
+ c_name += c
+ return c_name[1:] + '_' + method
+
+import_pat = re.compile(r'\s*import\s+(\S+)\.([^\s.]+)\s+as\s+(\S+)')
+
+class Overrides:
+ def __init__(self, filename=None, path=[]):
+ self.modulename = None
+ self.ignores = {}
+ self.glob_ignores = []
+ self.type_ignores = {}
+ self.overrides = {}
+ self.overridden = {}
+ self.kwargs = {}
+ self.noargs = {}
+ self.onearg = {}
+ self.staticmethod = {}
+ self.classmethod = {}
+ self.startlines = {}
+ self.override_attrs = {}
+ self.override_slots = {}
+ self.headers = ''
+ self.body = ''
+ self.init = ''
+ self.imports = []
+ self.defines = {}
+ self.functions = {}
+ self.newstyle_constructors = {}
+ self.path = [os.path.abspath(x) for x in path]
+ if filename:
+ self.handle_file(filename)
+
+ def handle_file(self, filename):
+ oldpath = os.getcwd()
+
+ fp = None
+ for path in self.path:
+ os.chdir(oldpath)
+ os.chdir(path)
+ try:
+ fp = open(filename, 'r')
+ break
+ except:
+ os.chdir(oldpath)
+ if not fp:
+ raise Exception, "Couldn't find file %s" % filename
+
+ dirname = path
+
+ if dirname != oldpath:
+ os.chdir(dirname)
+
+ # read all the components of the file ...
+ bufs = []
+ startline = 1
+ lines = []
+ line = fp.readline()
+ linenum = 1
+ while line:
+ if line == '%%\n' or line == '%%':
+ if lines:
+ bufs.append((string.join(lines, ''), startline))
+ startline = linenum + 1
+ lines = []
+ else:
+ lines.append(line)
+ line = fp.readline()
+ linenum = linenum + 1
+ if lines:
+ bufs.append((string.join(lines, ''), startline))
+ if not bufs: return
+
+ for buf, startline in bufs:
+ self.__parse_override(buf, startline, filename)
+
+ os.chdir(oldpath)
+
+ def __parse_override(self, buffer, startline, filename):
+ pos = string.find(buffer, '\n')
+ if pos >= 0:
+ line = buffer[:pos]
+ rest = buffer[pos+1:]
+ else:
+ line = buffer ; rest = ''
+ words = string.split(line)
+ command = words[0]
+ if (command == 'ignore' or
+ command == 'ignore-' + sys.platform):
+ "ignore/ignore-platform [functions..]"
+ for func in words[1:]:
+ self.ignores[func] = 1
+ for func in string.split(rest):
+ self.ignores[func] = 1
+ elif (command == 'ignore-glob' or
+ command == 'ignore-glob-' + sys.platform):
+ "ignore-glob/ignore-glob-platform [globs..]"
+ for func in words[1:]:
+ self.glob_ignores.append(func)
+ for func in string.split(rest):
+ self.glob_ignores.append(func)
+ elif (command == 'ignore-type' or
+ command == 'ignore-type-' + sys.platform):
+ "ignore-type/ignore-type-platform [typenames..]"
+ for typename in words[1:]:
+ self.type_ignores[typename] = 1
+ for typename in string.split(rest):
+ self.type_ignores[typename] = 1
+ elif command == 'override':
+ "override function/method [kwargs|noargs|onearg] [staticmethod|classmethod]"
+ func = words[1]
+ if 'kwargs' in words[1:]:
+ self.kwargs[func] = 1
+ elif 'noargs' in words[1:]:
+ self.noargs[func] = 1
+ elif 'onearg' in words[1:]:
+ self.onearg[func] = True
+
+ if 'staticmethod' in words[1:]:
+ self.staticmethod[func] = True
+ elif 'classmethod' in words[1:]:
+ self.classmethod[func] = True
+ if func in self.overrides:
+ raise RuntimeError("Function %s is being overridden more than once" % (func,))
+ self.overrides[func] = rest
+ self.startlines[func] = (startline + 1, filename)
+ elif command == 'override-attr':
+ "override-slot Class.attr"
+ attr = words[1]
+ self.override_attrs[attr] = rest
+ self.startlines[attr] = (startline + 1, filename)
+ elif command == 'override-slot':
+ "override-slot Class.slot"
+ slot = words[1]
+ self.override_slots[slot] = rest
+ self.startlines[slot] = (startline + 1, filename)
+ elif command == 'headers':
+ "headers"
+ self.headers = '%s\n#line %d "%s"\n%s' % \
+ (self.headers, startline + 1, filename, rest)
+ elif command == 'body':
+ "body"
+ self.body = '%s\n#line %d "%s"\n%s' % \
+ (self.body, startline + 1, filename, rest)
+ elif command == 'init':
+ "init"
+ self.init = '%s\n#line %d "%s"\n%s' % \
+ (self.init, startline + 1, filename, rest)
+ elif command == 'modulename':
+ "modulename name"
+ self.modulename = words[1]
+ elif command == 'include':
+ "include filename"
+ for filename in words[1:]:
+ self.handle_file(filename)
+ for filename in string.split(rest):
+ self.handle_file(filename)
+ elif command == 'import':
+ "import module1 [\n module2, \n module3 ...]"
+ for line in string.split(buffer, '\n'):
+ match = import_pat.match(line)
+ if match:
+ self.imports.append(match.groups())
+ elif command == 'define':
+ "define funcname [kwargs|noargs|onearg] [classmethod|staticmethod]"
+ "define Class.method [kwargs|noargs|onearg] [classmethod|staticmethod]"
+ func = words[1]
+ klass = None
+ if func.find('.') != -1:
+ klass, func = func.split('.', 1)
+
+ if not self.defines.has_key(klass):
+ self.defines[klass] = {}
+ self.defines[klass][func] = rest
+ else:
+ self.functions[func] = rest
+
+ if 'kwargs' in words[1:]:
+ self.kwargs[func] = 1
+ elif 'noargs' in words[1:]:
+ self.noargs[func] = 1
+ elif 'onearg' in words[1:]:
+ self.onearg[func] = 1
+
+ if 'staticmethod' in words[1:]:
+ self.staticmethod[func] = True
+ elif 'classmethod' in words[1:]:
+ self.classmethod[func] = True
+
+ self.startlines[func] = (startline + 1, filename)
+
+ elif command == 'new-constructor':
+ "new-constructor GType"
+ gtype, = words[1:]
+ self.newstyle_constructors[gtype] = True
+
+ def is_ignored(self, name):
+ if self.ignores.has_key(name):
+ return 1
+ for glob in self.glob_ignores:
+ if fnmatch.fnmatchcase(name, glob):
+ return 1
+ return 0
+
+ def is_type_ignored(self, name):
+ return name in self.type_ignores
+
+ def is_overriden(self, name):
+ return self.overrides.has_key(name)
+
+ def is_already_included(self, name):
+ return self.overridden.has_key(name)
+
+ def override(self, name):
+ self.overridden[name] = 1
+ return self.overrides[name]
+
+ def define(self, klass, name):
+ self.overridden[class2cname(klass, name)] = 1
+ return self.defines[klass][name]
+
+ def function(self, name):
+ return self.functions[name]
+
+ def getstartline(self, name):
+ return self.startlines[name]
+
+ def wants_kwargs(self, name):
+ return self.kwargs.has_key(name)
+
+ def wants_noargs(self, name):
+ return self.noargs.has_key(name)
+
+ def wants_onearg(self, name):
+ return self.onearg.has_key(name)
+
+ def is_staticmethod(self, name):
+ return self.staticmethod.has_key(name)
+
+ def is_classmethod(self, name):
+ return self.classmethod.has_key(name)
+
+ def attr_is_overriden(self, attr):
+ return self.override_attrs.has_key(attr)
+
+ def attr_override(self, attr):
+ return self.override_attrs[attr]
+
+ def slot_is_overriden(self, slot):
+ return self.override_slots.has_key(slot)
+
+ def slot_override(self, slot):
+ return self.override_slots[slot]
+
+ def get_headers(self):
+ return self.headers
+
+ def get_body(self):
+ return self.body
+
+ def get_init(self):
+ return self.init
+
+ def get_imports(self):
+ return self.imports
+
+ def get_defines_for(self, klass):
+ return self.defines.get(klass, {})
+
+ def get_functions(self):
+ return self.functions
diff --git a/farstream/python/codegen/reversewrapper.py b/farstream/python/codegen/reversewrapper.py
new file mode 100644
index 000000000..f528828ef
--- /dev/null
+++ b/farstream/python/codegen/reversewrapper.py
@@ -0,0 +1,771 @@
+### -*- python -*-
+### Code to generate "Reverse Wrappers", i.e. C->Python wrappers
+### (C) 2004 Gustavo Carneiro <gjc@gnome.org>
+import argtypes
+import os
+
+DEBUG_MODE = ('PYGTK_CODEGEN_DEBUG' in os.environ)
+
+def join_ctype_name(ctype, name):
+ '''Joins a C type and a variable name into a single string'''
+ if ctype[-1] != '*':
+ return " ".join((ctype, name))
+ else:
+ return "".join((ctype, name))
+
+
+class CodeSink(object):
+ def __init__(self):
+ self.indent_level = 0 # current indent level
+ self.indent_stack = [] # previous indent levels
+
+ def _format_code(self, code):
+ assert isinstance(code, str)
+ l = []
+ for line in code.split('\n'):
+ l.append(' '*self.indent_level + line)
+ if l[-1]:
+ l.append('')
+ return '\n'.join(l)
+
+ def writeln(self, line=''):
+ raise NotImplementedError
+
+ def indent(self, level=4):
+ '''Add a certain ammount of indentation to all lines written
+ from now on and until unindent() is called'''
+ self.indent_stack.append(self.indent_level)
+ self.indent_level += level
+
+ def unindent(self):
+ '''Revert indentation level to the value before last indent() call'''
+ self.indent_level = self.indent_stack.pop()
+
+
+class FileCodeSink(CodeSink):
+ def __init__(self, fp):
+ CodeSink.__init__(self)
+ assert isinstance(fp, file)
+ self.fp = fp
+
+ def writeln(self, line=''):
+ self.fp.write(self._format_code(line))
+
+class MemoryCodeSink(CodeSink):
+ def __init__(self):
+ CodeSink.__init__(self)
+ self.lines = []
+
+ def writeln(self, line=''):
+ self.lines.append(self._format_code(line))
+
+ def flush_to(self, sink):
+ assert isinstance(sink, CodeSink)
+ for line in self.lines:
+ sink.writeln(line.rstrip())
+ self.lines = []
+
+ def flush(self):
+ l = []
+ for line in self.lines:
+ l.append(self._format_code(line))
+ self.lines = []
+ return "".join(l)
+
+class ReverseWrapper(object):
+ '''Object that generates a C->Python wrapper'''
+ def __init__(self, cname, is_static=True):
+ assert isinstance(cname, str)
+
+ self.cname = cname
+ ## function object we will call, or object whose method we will call
+ self.called_pyobj = None
+ ## name of method of self.called_pyobj we will call
+ self.method_name = None
+ self.is_static = is_static
+
+ self.parameters = []
+ self.declarations = MemoryCodeSink()
+ self.post_return_code = MemoryCodeSink()
+ self.body = MemoryCodeSink()
+ self.cleanup_actions = []
+ self.pyargv_items = []
+ self.pyargv_optional_items = []
+ self.pyret_parse_items = [] # list of (format_spec, parameter)
+
+ def set_call_target(self, called_pyobj, method_name=None):
+ assert called_pyobj is not None
+ assert self.called_pyobj is None
+ self.called_pyobj = called_pyobj
+ self.method_name = method_name
+
+ def set_return_type(self, return_type):
+ assert isinstance(return_type, ReturnType)
+ self.return_type = return_type
+
+ def add_parameter(self, param):
+ assert isinstance(param, Parameter)
+ self.parameters.append(param)
+
+ def add_declaration(self, decl_code):
+ self.declarations.writeln(decl_code)
+
+ def add_pyargv_item(self, variable, optional=False):
+ if optional:
+ self.pyargv_optional_items.append(variable)
+ else:
+ self.pyargv_items.append(variable)
+
+ def add_pyret_parse_item(self, format_specifier, parameter, prepend=False):
+ if prepend:
+ self.pyret_parse_items.insert(0, (format_specifier, parameter))
+ else:
+ self.pyret_parse_items.append((format_specifier, parameter))
+
+ def write_code(self, code,
+ cleanup=None,
+ failure_expression=None,
+ failure_cleanup=None,
+ failure_exception=None,
+ code_sink=None):
+ '''Add a chunk of code with cleanup and error handling
+
+ This method is to be used by TypeHandlers when generating code
+
+ Keywork arguments:
+ code -- code to add
+ cleanup -- code to cleanup any dynamic resources created by @code
+ (except in case of failure) (default None)
+ failure_expression -- C boolean expression to indicate
+ if anything failed (default None)
+ failure_cleanup -- code to cleanup any dynamic resources
+ created by @code in case of failure (default None)
+ failure_exception -- code to raise an exception in case of
+ failure (which will be immediately
+ printed and cleared), (default None)
+ code_sink -- "code sink" to use; by default,
+ ReverseWrapper.body is used, which writes the
+ main body of the wrapper, before calling the
+ python method. Alternatively,
+ ReverseWrapper.after_pyret_parse can be used, to
+ write code after the PyArg_ParseTuple that
+ parses the python method return value.
+ '''
+ if code_sink is None:
+ code_sink = self.body
+ if code is not None:
+ code_sink.writeln(code)
+ if failure_expression is not None:
+ code_sink.writeln("if (%s) {" % (failure_expression,))
+ code_sink.indent()
+ if failure_exception is None:
+ code_sink.writeln("if (PyErr_Occurred())")
+ code_sink.indent()
+ code_sink.writeln("PyErr_Print();")
+ code_sink.unindent()
+ else:
+ code_sink.writeln(failure_exception)
+ code_sink.writeln("PyErr_Print();")
+ if failure_cleanup is not None:
+ code_sink.writeln(failure_cleanup)
+ for cleanup_action in self.cleanup_actions:
+ code_sink.writeln(cleanup_action)
+ self.return_type.write_error_return()
+ code_sink.unindent()
+ code_sink.writeln("}")
+ if cleanup is not None:
+ self.cleanup_actions.insert(0, cleanup)
+
+ def generate(self, sink):
+ '''Generate the code into a CodeSink object'''
+ assert isinstance(sink, CodeSink)
+
+ if DEBUG_MODE:
+ self.declarations.writeln("/* begin declarations */")
+ self.body.writeln("/* begin main body */")
+ self.post_return_code.writeln("/* begin post-return code */")
+
+ self.add_declaration("PyGILState_STATE __py_state;")
+ self.write_code(code="__py_state = pyg_gil_state_ensure();",
+ cleanup="pyg_gil_state_release(__py_state);")
+
+ for param in self.parameters:
+ param.convert_c2py()
+
+ assert self.called_pyobj is not None,\
+ "Parameters failed to provide a target function or method."
+
+ if self.is_static:
+ sink.writeln('static %s' % self.return_type.get_c_type())
+ else:
+ sink.writeln(self.return_type.get_c_type())
+ c_proto_params = map(Parameter.format_for_c_proto, self.parameters)
+ sink.writeln("%s(%s)\n{" % (self.cname, ", ".join(c_proto_params)))
+
+ self.return_type.write_decl()
+ self.add_declaration("PyObject *py_retval;")
+
+ ## Handle number of arguments
+ if self.pyargv_items:
+ self.add_declaration("PyObject *py_args;")
+ py_args = "py_args"
+ if self.pyargv_optional_items:
+ self.add_declaration("int argc = %i;" % len(self.pyargv_items))
+ argc = "argc"
+ for arg in self.pyargv_optional_items:
+ self.body.writeln("if (%s)" % arg)
+ self.body.indent()
+ self.body.writeln("++argc;")
+ self.body.unindent()
+ else:
+ argc = str(len(self.pyargv_items))
+ else:
+ if self.pyargv_optional_items:
+ self.add_declaration("PyObject *py_args;")
+ py_args = "py_args"
+ self.add_declaration("int argc = 0;")
+ argc = "argc"
+ for arg in self.pyargv_optional_items:
+ self.body.writeln("if (%s)" % arg)
+ self.body.indent()
+ self.body.writeln("++argc;")
+ self.body.unindent()
+ else:
+ py_args = "NULL"
+ argc = None
+
+ self.body.writeln()
+
+ if py_args != "NULL":
+ self.write_code("py_args = PyTuple_New(%s);" % argc,
+ cleanup="Py_DECREF(py_args);")
+ pos = 0
+ for arg in self.pyargv_items:
+ try: # try to remove the Py_DECREF cleanup action, if we can
+ self.cleanup_actions.remove("Py_DECREF(%s);" % arg)
+ except ValueError: # otherwise we have to Py_INCREF..
+ self.body.writeln("Py_INCREF(%s);" % arg)
+ self.body.writeln("PyTuple_SET_ITEM(%s, %i, %s);" % (py_args, pos, arg))
+ pos += 1
+ for arg in self.pyargv_optional_items:
+ self.body.writeln("if (%s) {" % arg)
+ self.body.indent()
+ try: # try to remove the Py_DECREF cleanup action, if we can
+ self.cleanup_actions.remove("Py_XDECREF(%s);" % arg)
+ except ValueError: # otherwise we have to Py_INCREF..
+ self.body.writeln("Py_INCREF(%s);" % arg)
+ self.body.writeln("PyTuple_SET_ITEM(%s, %i, %s);" % (py_args, pos, arg))
+ self.body.unindent()
+ self.body.writeln("}")
+ pos += 1
+
+ self.body.writeln()
+
+ ## Call the python method
+ if self.method_name is None:
+ self.write_code("py_retval = PyObject_Call(%s, %s);"
+ % (self.called_pyobj, py_args),
+ cleanup="Py_DECREF(py_retval);",
+ failure_expression="!py_retval")
+ else:
+ self.add_declaration("PyObject *py_method;")
+ self.write_code("py_method = PyObject_GetAttrString(%s, \"%s\");"
+ % (self.called_pyobj, self.method_name),
+ cleanup="Py_DECREF(py_method);",
+ failure_expression="!py_method")
+ self.write_code("py_retval = PyObject_CallObject(py_method, %s);"
+ % (py_args,),
+ cleanup="Py_DECREF(py_retval);",
+ failure_expression="!py_retval")
+
+ ## -- Handle the return value --
+
+ ## we need to check if the return_type object is prepared to cooperate with multiple return values
+ len_before = len(self.pyret_parse_items)
+ self.return_type.write_conversion()
+ len_after = len(self.pyret_parse_items)
+ assert (self.return_type.get_c_type() == 'void'
+ or not (len_before == len_after and len_after > 0)),\
+ ("Bug in reverse wrappers: return type handler %s"
+ " is not prepared to cooperate multiple return values") % (type(self.return_type),)
+
+ sink.indent()
+
+ if len(self.pyret_parse_items) == 1:
+ ## if retval is one item only, pack it in a tuple so we
+ ## can use PyArg_ParseTuple as usual..
+ self.write_code('py_retval = Py_BuildValue("(N)", py_retval);')
+ if len(self.pyret_parse_items) > 0:
+ ## Parse return values using PyArg_ParseTuple
+ self.write_code(code=None, failure_expression=(
+ '!PyArg_ParseTuple(py_retval, "%s", %s)' % (
+ "".join([format for format, param in self.pyret_parse_items]),
+ ", ".join([param for format, param in self.pyret_parse_items]))))
+
+ if DEBUG_MODE:
+ self.declarations.writeln("/* end declarations */")
+ self.declarations.flush_to(sink)
+ sink.writeln()
+ if DEBUG_MODE:
+ self.body.writeln("/* end main body */")
+ self.body.flush_to(sink)
+ sink.writeln()
+ if DEBUG_MODE:
+ self.post_return_code.writeln("/* end post-return code */")
+ self.post_return_code.flush_to(sink)
+ sink.writeln()
+
+ for cleanup_action in self.cleanup_actions:
+ sink.writeln(cleanup_action)
+ if self.return_type.get_c_type() != 'void':
+ sink.writeln()
+ sink.writeln("return retval;")
+ sink.unindent()
+ sink.writeln("}")
+
+class TypeHandler(object):
+ def __init__(self, wrapper, **props):
+ assert isinstance(wrapper, ReverseWrapper)
+ self.wrapper = wrapper
+ self.props = props
+
+class ReturnType(TypeHandler):
+
+ def get_c_type(self):
+ raise NotImplementedError
+
+ def write_decl(self):
+ raise NotImplementedError
+
+ def write_error_return(self):
+ '''Write "return <value>" code in case of error'''
+ raise NotImplementedError
+
+ def write_conversion(self):
+ '''Writes code to convert Python return value in 'py_retval'
+ into C 'retval'. Returns a string with C boolean expression
+ that determines if anything went wrong. '''
+ raise NotImplementedError
+
+class Parameter(TypeHandler):
+
+ def __init__(self, wrapper, name, **props):
+ TypeHandler.__init__(self, wrapper, **props)
+ self.name = name
+
+ def get_c_type(self):
+ raise NotImplementedError
+
+ def convert_c2py(self):
+ '''Write some code before calling the Python method.'''
+ pass
+
+ def format_for_c_proto(self):
+ return join_ctype_name(self.get_c_type(), self.name)
+
+
+###---
+class StringParam(Parameter):
+
+ def get_c_type(self):
+ return self.props.get('c_type', 'char *').replace('const-', 'const ')
+
+ def convert_c2py(self):
+ if self.props.get('optional', False):
+ self.wrapper.add_declaration("PyObject *py_%s = NULL;" % self.name)
+ self.wrapper.write_code(code=("if (%s)\n"
+ " py_%s = PyString_FromString(%s);\n"
+ % (self.name, self.name, self.name)),
+ cleanup=("Py_XDECREF(py_%s);" % self.name))
+ self.wrapper.add_pyargv_item("py_%s" % self.name, optional=True)
+ else:
+ self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
+ self.wrapper.write_code(code=("py_%s = PyString_FromString(%s);" %
+ (self.name, self.name)),
+ cleanup=("Py_DECREF(py_%s);" % self.name),
+ failure_expression=("!py_%s" % self.name))
+ self.wrapper.add_pyargv_item("py_%s" % self.name)
+
+for ctype in ('char*', 'gchar*', 'const-char*', 'char-const*', 'const-gchar*',
+ 'gchar-const*', 'string', 'static_string'):
+ argtypes.matcher.register_reverse(ctype, StringParam)
+del ctype
+
+class StringReturn(ReturnType):
+
+ def get_c_type(self):
+ return "char *"
+
+ def write_decl(self):
+ self.wrapper.add_declaration("char *retval;")
+
+ def write_error_return(self):
+ self.wrapper.write_code("return NULL;")
+
+ def write_conversion(self):
+ self.wrapper.add_pyret_parse_item("s", "&retval", prepend=True)
+ self.wrapper.write_code("retval = g_strdup(retval);", code_sink=self.wrapper.post_return_code)
+
+for ctype in ('char*', 'gchar*'):
+ argtypes.matcher.register_reverse_ret(ctype, StringReturn)
+del ctype
+
+
+class VoidReturn(ReturnType):
+
+ def get_c_type(self):
+ return "void"
+
+ def write_decl(self):
+ pass
+
+ def write_error_return(self):
+ self.wrapper.write_code("return;")
+
+ def write_conversion(self):
+ self.wrapper.write_code(
+ code=None,
+ failure_expression="py_retval != Py_None",
+ failure_cleanup='PyErr_SetString(PyExc_TypeError, "retval should be None");')
+
+argtypes.matcher.register_reverse_ret('void', VoidReturn)
+argtypes.matcher.register_reverse_ret('none', VoidReturn)
+
+class GObjectParam(Parameter):
+
+ def get_c_type(self):
+ return self.props.get('c_type', 'GObject *')
+
+ def convert_c2py(self):
+ self.wrapper.add_declaration("PyObject *py_%s = NULL;" % self.name)
+ self.wrapper.write_code(code=("if (%s)\n"
+ " py_%s = pygobject_new((GObject *) %s);\n"
+ "else {\n"
+ " Py_INCREF(Py_None);\n"
+ " py_%s = Py_None;\n"
+ "}"
+ % (self.name, self.name, self.name, self.name)),
+ cleanup=("Py_DECREF(py_%s);" % self.name))
+ self.wrapper.add_pyargv_item("py_%s" % self.name)
+
+argtypes.matcher.register_reverse('GObject*', GObjectParam)
+
+class GObjectReturn(ReturnType):
+
+ def get_c_type(self):
+ return self.props.get('c_type', 'GObject *')
+
+ def write_decl(self):
+ self.wrapper.add_declaration("%s retval;" % self.get_c_type())
+
+ def write_error_return(self):
+ self.wrapper.write_code("return NULL;")
+
+ def write_conversion(self):
+ self.wrapper.write_code(
+ code=None,
+ failure_expression="!PyObject_TypeCheck(py_retval, &PyGObject_Type)",
+ failure_exception='PyErr_SetString(PyExc_TypeError, "retval should be a GObject");')
+ self.wrapper.write_code("retval = (%s) pygobject_get(py_retval);"
+ % self.get_c_type())
+ self.wrapper.write_code("g_object_ref((GObject *) retval);")
+
+argtypes.matcher.register_reverse_ret('GObject*', GObjectReturn)
+
+
+
+class IntParam(Parameter):
+
+ def get_c_type(self):
+ return self.props.get('c_type', 'int')
+
+ def convert_c2py(self):
+ self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
+ self.wrapper.write_code(code=("py_%s = PyInt_FromLong(%s);" %
+ (self.name, self.name)),
+ cleanup=("Py_DECREF(py_%s);" % self.name))
+ self.wrapper.add_pyargv_item("py_%s" % self.name)
+
+class IntReturn(ReturnType):
+ def get_c_type(self):
+ return self.props.get('c_type', 'int')
+ def write_decl(self):
+ self.wrapper.add_declaration("%s retval;" % self.get_c_type())
+ def write_error_return(self):
+ self.wrapper.write_code("return -G_MAXINT;")
+ def write_conversion(self):
+ self.wrapper.add_pyret_parse_item("i", "&retval", prepend=True)
+
+for argtype in ('int', 'gint', 'guint', 'short', 'gshort', 'gushort', 'long',
+ 'glong', 'gsize', 'gssize', 'guint8', 'gint8', 'guint16',
+ 'gint16', 'gint32', 'GTime'):
+ argtypes.matcher.register_reverse(argtype, IntParam)
+ argtypes.matcher.register_reverse_ret(argtype, IntReturn)
+del argtype
+
+class IntPtrParam(Parameter):
+ def __init__(self, wrapper, name, **props):
+ if "direction" not in props:
+ raise ValueError("cannot use int* parameter without direction")
+ if props["direction"] not in ("out", "inout"):
+ raise ValueError("cannot use int* parameter with direction '%s'" % (props["direction"],))
+ Parameter.__init__(self, wrapper, name, **props)
+ def get_c_type(self):
+ return self.props.get('c_type', 'int*')
+ def convert_c2py(self):
+ if self.props["direction"] == "inout":
+ self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
+ self.wrapper.write_code(code=("py_%s = PyInt_FromLong(*%s);" %
+ (self.name, self.name)),
+ cleanup=("Py_DECREF(py_%s);" % self.name))
+ self.wrapper.add_pyargv_item("py_%s" % self.name)
+ self.wrapper.add_pyret_parse_item("i", self.name)
+for argtype in ('int*', 'gint*'):
+ argtypes.matcher.register_reverse(argtype, IntPtrParam)
+del argtype
+
+
+class GEnumReturn(IntReturn):
+ def write_conversion(self):
+ self.wrapper.write_code(
+ code=None,
+ failure_expression=("pyg_enum_get_value(%s, py_retval, (gint *)&retval)" %
+ self.props['typecode']))
+
+argtypes.matcher.register_reverse_ret("GEnum", GEnumReturn)
+
+class GEnumParam(IntParam):
+ def convert_c2py(self):
+ self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
+ self.wrapper.write_code(code=("py_%s = pyg_enum_from_gtype(%s, %s);" %
+ (self.name, self.props['typecode'], self.name)),
+ cleanup=("Py_DECREF(py_%s);" % self.name),
+ failure_expression=("!py_%s" % self.name))
+ self.wrapper.add_pyargv_item("py_%s" % self.name)
+
+argtypes.matcher.register_reverse("GEnum", GEnumParam)
+
+class GFlagsReturn(IntReturn):
+ def write_conversion(self):
+ self.wrapper.write_code(
+ code=None,
+ failure_expression=("pyg_flags_get_value(%s, py_retval, (gint *)&retval)" %
+ self.props['typecode']))
+
+argtypes.matcher.register_reverse_ret("GFlags", GFlagsReturn)
+
+class GFlagsParam(IntParam):
+ def convert_c2py(self):
+ self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
+ self.wrapper.write_code(code=("py_%s = pyg_flags_from_gtype(%s, %s);" %
+ (self.name, self.props['typecode'], self.name)),
+ cleanup=("Py_DECREF(py_%s);" % self.name),
+ failure_expression=("!py_%s" % self.name))
+ self.wrapper.add_pyargv_item("py_%s" % self.name)
+
+argtypes.matcher.register_reverse("GFlags", GFlagsParam)
+
+
+class GtkTreePathParam(IntParam):
+ def convert_c2py(self):
+ self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
+ self.wrapper.write_code(code=("py_%s = pygtk_tree_path_to_pyobject(%s);" %
+ (self.name, self.name)),
+ cleanup=("Py_DECREF(py_%s);" % self.name),
+ failure_expression=("!py_%s" % self.name))
+ self.wrapper.add_pyargv_item("py_%s" % self.name)
+
+argtypes.matcher.register_reverse("GtkTreePath*", GtkTreePathParam)
+
+
+class BooleanReturn(ReturnType):
+ def get_c_type(self):
+ return "gboolean"
+ def write_decl(self):
+ self.wrapper.add_declaration("gboolean retval;")
+ self.wrapper.add_declaration("PyObject *py_main_retval;")
+ def write_error_return(self):
+ self.wrapper.write_code("return FALSE;")
+ def write_conversion(self):
+ self.wrapper.add_pyret_parse_item("O", "&py_main_retval", prepend=True)
+ self.wrapper.write_code("retval = PyObject_IsTrue(py_main_retval)? TRUE : FALSE;",
+ code_sink=self.wrapper.post_return_code)
+argtypes.matcher.register_reverse_ret("gboolean", BooleanReturn)
+
+class BooleanParam(Parameter):
+ def get_c_type(self):
+ return "gboolean"
+ def convert_c2py(self):
+ self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
+ self.wrapper.write_code("py_%s = %s? Py_True : Py_False;"
+ % (self.name, self.name))
+ self.wrapper.add_pyargv_item("py_%s" % self.name)
+
+argtypes.matcher.register_reverse("gboolean", BooleanParam)
+
+
+class DoubleParam(Parameter):
+ def get_c_type(self):
+ return self.props.get('c_type', 'gdouble')
+ def convert_c2py(self):
+ self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
+ self.wrapper.write_code(code=("py_%s = PyFloat_FromDouble(%s);" %
+ (self.name, self.name)),
+ cleanup=("Py_DECREF(py_%s);" % self.name))
+ self.wrapper.add_pyargv_item("py_%s" % self.name)
+
+class DoublePtrParam(Parameter):
+ def __init__(self, wrapper, name, **props):
+ if "direction" not in props:
+ raise ValueError("cannot use double* parameter without direction")
+ if props["direction"] not in ("out", ): # inout not yet implemented
+ raise ValueError("cannot use double* parameter with direction '%s'" % (props["direction"],))
+ Parameter.__init__(self, wrapper, name, **props)
+ def get_c_type(self):
+ return self.props.get('c_type', 'double*')
+ def convert_c2py(self):
+ self.wrapper.add_pyret_parse_item("d", self.name)
+for argtype in ('double*', 'gdouble*'):
+ argtypes.matcher.register_reverse(argtype, DoublePtrParam)
+del argtype
+
+class DoubleReturn(ReturnType):
+ def get_c_type(self):
+ return self.props.get('c_type', 'gdouble')
+ def write_decl(self):
+ self.wrapper.add_declaration("%s retval;" % self.get_c_type())
+ def write_error_return(self):
+ self.wrapper.write_code("return -G_MAXFLOAT;")
+ def write_conversion(self):
+ self.wrapper.write_code(
+ code=None,
+ failure_expression="!PyFloat_AsDouble(py_retval)",
+ failure_cleanup='PyErr_SetString(PyExc_TypeError, "retval should be a float");')
+ self.wrapper.write_code("retval = PyFloat_AsDouble(py_retval);")
+
+for argtype in ('float', 'double', 'gfloat', 'gdouble'):
+ argtypes.matcher.register_reverse(argtype, DoubleParam)
+ argtypes.matcher.register_reverse_ret(argtype, DoubleReturn)
+
+
+class GBoxedParam(Parameter):
+ def get_c_type(self):
+ return self.props.get('c_type').replace('const-', 'const ')
+ def convert_c2py(self):
+ self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
+ ctype = self.get_c_type()
+ if ctype.startswith('const '):
+ ctype_no_const = ctype[len('const '):]
+ self.wrapper.write_code(
+ code=('py_%s = pyg_boxed_new(%s, (%s) %s, TRUE, TRUE);' %
+ (self.name, self.props['typecode'],
+ ctype_no_const, self.name)),
+ cleanup=("Py_DECREF(py_%s);" % self.name))
+ else:
+ self.wrapper.write_code(
+ code=('py_%s = pyg_boxed_new(%s, %s, FALSE, FALSE);' %
+ (self.name, self.props['typecode'], self.name)),
+ cleanup=("Py_DECREF(py_%s);" % self.name))
+ self.wrapper.add_pyargv_item("py_%s" % self.name)
+
+argtypes.matcher.register_reverse("GBoxed", GBoxedParam)
+
+class GBoxedReturn(ReturnType):
+ def get_c_type(self):
+ return self.props.get('c_type')
+ def write_decl(self):
+ self.wrapper.add_declaration("%s retval;" % self.get_c_type())
+ def write_error_return(self):
+ self.wrapper.write_code("return retval;")
+ def write_conversion(self):
+ self.wrapper.write_code(
+ failure_expression=("!pyg_boxed_check(py_retval, %s)" %
+ (self.props['typecode'],)),
+ failure_cleanup=('PyErr_SetString(PyExc_TypeError, "retval should be a %s");'
+ % (self.props['typename'],)))
+ self.wrapper.write_code('retval = pyg_boxed_get(py_retval, %s);' %
+ self.props['typename'])
+
+argtypes.matcher.register_reverse_ret("GBoxed", GBoxedReturn)
+
+
+class GdkRectanglePtrParam(Parameter):
+ def get_c_type(self):
+ return self.props.get('c_type').replace('const-', 'const ')
+ def convert_c2py(self):
+ self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
+ self.wrapper.write_code(
+ code=('py_%s = pyg_boxed_new(GDK_TYPE_RECTANGLE, %s, TRUE, TRUE);' %
+ (self.name, self.name)),
+ cleanup=("Py_DECREF(py_%s);" % self.name))
+ self.wrapper.add_pyargv_item("py_%s" % self.name)
+
+argtypes.matcher.register_reverse("GdkRectangle*", GdkRectanglePtrParam)
+argtypes.matcher.register_reverse('GtkAllocation*', GdkRectanglePtrParam)
+
+
+class PyGObjectMethodParam(Parameter):
+ def __init__(self, wrapper, name, method_name, **props):
+ Parameter.__init__(self, wrapper, name, **props)
+ self.method_name = method_name
+
+ def get_c_type(self):
+ return self.props.get('c_type', 'GObject *')
+
+ def convert_c2py(self):
+ self.wrapper.add_declaration("PyObject *py_%s;" % self.name)
+ self.wrapper.write_code(code=("py_%s = pygobject_new((GObject *) %s);" %
+ (self.name, self.name)),
+ cleanup=("Py_DECREF(py_%s);" % self.name),
+ failure_expression=("!py_%s" % self.name))
+ self.wrapper.set_call_target("py_%s" % self.name, self.method_name)
+
+class CallbackInUserDataParam(Parameter):
+ def __init__(self, wrapper, name, free_it, **props):
+ Parameter.__init__(self, wrapper, name, **props)
+ self.free_it = free_it
+
+ def get_c_type(self):
+ return "gpointer"
+
+ def convert_c2py(self):
+ self.wrapper.add_declaration("PyObject **_user_data;")
+ cleanup = self.free_it and ("g_free(%s);" % self.name) or None
+ self.wrapper.write_code(code=("_real_user_data = (PyObject **) %s;"
+ % self.name),
+ cleanup=cleanup)
+
+ self.wrapper.add_declaration("PyObject *py_func;")
+ cleanup = self.free_it and "Py_DECREF(py_func);" or None
+ self.wrapper.write_code(code="py_func = _user_data[0];",
+ cleanup=cleanup)
+ self.wrapper.set_call_target("py_func")
+
+ self.wrapper.add_declaration("PyObject *py_user_data;")
+ cleanup = self.free_it and "Py_XDECREF(py_user_data);" or None
+ self.wrapper.write_code(code="py_user_data = _user_data[1];",
+ cleanup=cleanup)
+ self.wrapper.add_pyargv_item("py_user_data", optional=True)
+
+def _test():
+ import sys
+
+ if 1:
+ wrapper = ReverseWrapper("this_is_the_c_function_name", is_static=True)
+ wrapper.set_return_type(StringReturn(wrapper))
+ wrapper.add_parameter(PyGObjectMethodParam(wrapper, "self", method_name="do_xxx"))
+ wrapper.add_parameter(StringParam(wrapper, "param2", optional=True))
+ wrapper.add_parameter(GObjectParam(wrapper, "param3"))
+ #wrapper.add_parameter(InoutIntParam(wrapper, "param4"))
+ wrapper.generate(FileCodeSink(sys.stderr))
+
+ if 0:
+ wrapper = ReverseWrapper("this_a_callback_wrapper")
+ wrapper.set_return_type(VoidReturn(wrapper))
+ wrapper.add_parameter(StringParam(wrapper, "param1", optional=False))
+ wrapper.add_parameter(GObjectParam(wrapper, "param2"))
+ wrapper.add_parameter(CallbackInUserDataParam(wrapper, "data", free_it=True))
+ wrapper.generate(FileCodeSink(sys.stderr))
+
+if __name__ == '__main__':
+ _test()
diff --git a/farstream/python/codegen/scmexpr.py b/farstream/python/codegen/scmexpr.py
new file mode 100644
index 000000000..d08c517ad
--- /dev/null
+++ b/farstream/python/codegen/scmexpr.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+from __future__ import generators
+
+import string
+import types
+from cStringIO import StringIO
+
+class error(Exception):
+ def __init__(self, filename, lineno, msg):
+ Exception.__init__(self, msg)
+ self.filename = filename
+ self.lineno = lineno
+ self.msg = msg
+ def __str__(self):
+ return '%s:%d: error: %s' % (self.filename, self.lineno, self.msg)
+
+trans = [' '] * 256
+for i in range(256):
+ if chr(i) in string.letters + string.digits + '_':
+ trans[i] = chr(i)
+ else:
+ trans[i] = '_'
+trans = string.join(trans, '')
+
+def parse(filename):
+ if isinstance(filename, str):
+ fp = open(filename, 'r')
+ else: # if not string, assume it is some kind of iterator
+ fp = filename
+ filename = getattr(fp, 'name', '<unknown>')
+ whitespace = ' \t\n\r\x0b\x0c'
+ nonsymbol = whitespace + '();\'"'
+ stack = []
+ openlines = []
+ lineno = 0
+ for line in fp:
+ pos = 0
+ lineno += 1
+ while pos < len(line):
+ if line[pos] in whitespace: # ignore whitespace
+ pass
+ elif line[pos] == ';': # comment
+ break
+ elif line[pos:pos+2] == "'(":
+ pass # the open parenthesis will be handled next iteration
+ elif line[pos] == '(':
+ stack.append(())
+ openlines.append(lineno)
+ elif line[pos] == ')':
+ if len(stack) == 0:
+ raise error(filename, lineno, 'close parenthesis found when none open')
+ closed = stack[-1]
+ del stack[-1]
+ del openlines[-1]
+ if stack:
+ stack[-1] += (closed,)
+ else:
+ yield closed
+ elif line[pos] == '"': # quoted string
+ if not stack:
+ raise error(filename, lineno,
+ 'string found outside of s-expression')
+ endpos = pos + 1
+ chars = []
+ while endpos < len(line):
+ if endpos+1 < len(line) and line[endpos] == '\\':
+ endpos += 1
+ if line[endpos] == 'n':
+ chars.append('\n')
+ elif line[endpos] == 'r':
+ chars.append('\r')
+ elif line[endpos] == 't':
+ chars.append('\t')
+ else:
+ chars.append('\\')
+ chars.append(line[endpos])
+ elif line[endpos] == '"':
+ break
+ else:
+ chars.append(line[endpos])
+ endpos += 1
+ if endpos >= len(line):
+ raise error(filename, lineno, "unclosed quoted string")
+ pos = endpos
+ stack[-1] += (''.join(chars),)
+ else: # symbol/number
+ if not stack:
+ raise error(filename, lineno,
+ 'identifier found outside of s-expression')
+ endpos = pos
+ while endpos < len(line) and line[endpos] not in nonsymbol:
+ endpos += 1
+ symbol = line[pos:endpos]
+ pos = max(pos, endpos-1)
+ try: symbol = int(symbol)
+ except ValueError:
+ try: symbol = float(symbol)
+ except ValueError: pass
+ stack[-1] += (symbol,)
+ pos += 1
+ if len(stack) != 0:
+ msg = '%d unclosed parentheses found at end of ' \
+ 'file (opened on line(s) %s)' % (len(stack),
+ ', '.join(map(str, openlines)))
+ raise error(filename, lineno, msg)
+
+class Parser:
+ def __init__(self, filename):
+ """Argument is either a string, a parse tree, or file object"""
+ self.filename = filename
+ def startParsing(self, filename=None):
+ statements = parse(filename or self.filename)
+ for statement in statements:
+ self.handle(statement)
+ def handle(self, tup):
+ cmd = string.translate(tup[0], trans)
+ if hasattr(self, cmd):
+ getattr(self, cmd)(*tup[1:])
+ else:
+ self.unknown(tup)
+ def unknown(self, tup):
+ pass
+
+_testString = """; a scheme file
+(define-func gdk_font_load ; a comment at end of line
+ GdkFont
+ ((string name)))
+
+(define-boxed GdkEvent
+ gdk_event_copy
+ gdk_event_free
+ "sizeof(GdkEvent)")
+"""
+
+if __name__ == '__main__':
+ import sys
+ if sys.argv[1:]:
+ fp = open(sys.argv[1])
+ else:
+ fp = StringIO(_testString)
+ statements = parse(fp)
+ for s in statements:
+ print `s`
diff --git a/farstream/python/common.h b/farstream/python/common.h
new file mode 100644
index 000000000..c2ffeb480
--- /dev/null
+++ b/farstream/python/common.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C; ; c-file-style: "python" -*- */
+/* gst-python
+ * Copyright (C) 2004 Johan Dahlin
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Johan Dahlin <johan@gnome.org>
+ */
+#ifndef __COMMON_H__
+#define __COMMON_H__
+
+#include <Python.h>
+#include <pygobject.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <gst/gst.h>
+
+#include <pygobject.h>
+#include "pygstminiobject.h"
+
+#if (defined HAVE_OLD_PYGTK && (PY_VERSION_HEX < 0x02030000))
+ typedef destructor freefunc;
+#endif
+
+#if PY_VERSION_HEX < 0x02050000
+#define lenfunc inquiry
+#define ssizeargfunc intargfunc
+#define ssizessizeargfunc intintargfunc
+#define ssizeobjargproc intobjargproc
+#define ssizessizeobjargproc intintobjargproc
+#endif
+
+typedef struct {
+ PyGObject *pad;
+ GClosure *link_function;
+ GClosure *event_function;
+ GClosure *chain_function;
+ GClosure *get_function;
+ GClosure *getcaps_function;
+ GClosure *setcaps_function;
+ GClosure *activate_function;
+ GClosure *activatepull_function;
+ GClosure *activatepush_function;
+ /* Query is not implemented as a closure to avoid refcounting
+ * making the query immutable and therefore useless */
+ PyObject *query_function;
+} PyGstPadPrivate;
+
+typedef struct {
+ PyObject *func, *data;
+} PyGstCustomNotify;
+
+typedef struct {
+ PyObject_HEAD
+ GstIterator *iter;
+} PyGstIterator;
+
+extern PyTypeObject PyGstIterator_Type;
+
+
+/* from gst-types.c */
+GstCaps *pygst_caps_from_pyobject (PyObject *object, gboolean *copy);
+PyObject* pygst_iterator_new(GstIterator *iter);
+
+
+#endif /* __COMMON_H__ */
diff --git a/farstream/python/examples/Makefile.am b/farstream/python/examples/Makefile.am
new file mode 100644
index 000000000..7b89027c2
--- /dev/null
+++ b/farstream/python/examples/Makefile.am
@@ -0,0 +1,7 @@
+EXTRA_DIST = \
+ README \
+ callchannel.py \
+ callhandler.py \
+ callui.py \
+ constants.py \
+ util.py
diff --git a/farstream/python/examples/README b/farstream/python/examples/README
new file mode 100644
index 000000000..8007df6b5
--- /dev/null
+++ b/farstream/python/examples/README
@@ -0,0 +1,5 @@
+Simple python example using telepathy-farstream in most minimal way possible.
+Two programs are included:
+
+callui.py: Doesn't do anything with tp-fs, but allows the start of a Call call
+callhandler.py: Simple handler that handles calls and handles the media
diff --git a/farstream/python/examples/callchannel.py b/farstream/python/examples/callchannel.py
new file mode 100644
index 000000000..f37c7243b
--- /dev/null
+++ b/farstream/python/examples/callchannel.py
@@ -0,0 +1,180 @@
+#!/usr/bin/env python
+#
+# callchannel.py
+# Copyright (C) 2008-2010 Collabora Ltd.
+#
+# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import dbus
+import dbus.glib
+import gobject
+import sys
+from glib import GError
+
+import pygst
+pygst.require("0.10")
+import gst
+
+import tpfarstream
+import farstream
+from util import *
+import gc
+
+from telepathy.client.channel import Channel
+from telepathy.constants import (
+ CONNECTION_HANDLE_TYPE_NONE, CONNECTION_HANDLE_TYPE_CONTACT,
+ CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_DISCONNECTED,
+ MEDIA_STREAM_STATE_CONNECTED
+ )
+from telepathy.interfaces import (
+ CHANNEL_INTERFACE, CONN_INTERFACE,
+ CONNECTION_INTERFACE_REQUESTS,
+ CONNECTION_INTERFACE_CONTACT_CAPABILITIES,
+ CLIENT)
+
+from constants import *
+
+class CallChannel:
+ def __init__ (self, bus, connection, object_path, properties):
+ self.bus = bus
+ self.conn = connection
+ self.tfchannel = None
+
+ self.obj = self.bus.get_object (self.conn.service_name, object_path)
+ self.obj.connect_to_signal ("CallStateChanged",
+ self.state_changed_cb, dbus_interface=CHANNEL_TYPE_CALL)
+
+ self.pipeline = gst.Pipeline()
+ self.pipeline.get_bus().add_watch(self.async_handler)
+
+ self.notifier = notifier = farstream.ElementAddedNotifier()
+ notifier.set_properties_from_file("element-properties")
+ notifier.add(self.pipeline)
+
+ tpfarstream.tf_channel_new_async (connection.service_name,
+ connection.object_path, object_path, self.tpfs_created)
+
+ def state_changed_cb(self, state, flags, reason, details):
+ print "* StateChanged:\n State: %s (%d)\n Flags: %s" % (
+ call_state_to_s (state), state, call_flags_to_s (flags))
+
+ print "\tReason: actor: %d reason: %d dbus_reason: '%s'" % (
+ reason[0], reason[1], reason[2])
+
+ print '\tDetails:'
+ for key, value in details.iteritems():
+ print "\t %s: %s" % (key, value)
+ else:
+ print '\t None'
+
+ if state == CALL_STATE_ENDED:
+ self.close()
+
+ def accept (self):
+ self.obj.Accept(dbus_interface=CHANNEL_TYPE_CALL)
+
+ def close (self):
+ print "Closing the channel"
+ # close and cleanup
+ self.obj.Close(dbus_interface=CHANNEL_INTERFACE)
+
+ self.pipeline.set_state (gst.STATE_NULL)
+ self.pipeline = None
+
+ self.tfchannel = None
+ self.notifier = None
+
+ def async_handler (self, bus, message):
+ if self.tfchannel != None:
+ self.tfchannel.bus_message(message)
+ return True
+
+ self.pipeline = gst.Pipeline()
+
+ def tpfs_created (self, source, result):
+ tfchannel = self.tfchannel = source.new_finish(result)
+ tfchannel.connect ("fs-conference-added", self.conference_added)
+ tfchannel.connect ("content-added", self.content_added)
+
+
+ def src_pad_added (self, content, handle, stream, pad, codec):
+ type = content.get_property ("media-type")
+ if type == farstream.MEDIA_TYPE_AUDIO:
+ sink = gst.parse_bin_from_description("audioconvert ! audioresample ! audioconvert ! autoaudiosink", True)
+ elif type == farstream.MEDIA_TYPE_VIDEO:
+ sink = gst.parse_bin_from_description("ffmpegcolorspace ! videoscale ! autovideosink", True)
+
+ self.pipeline.add(sink)
+ pad.link(sink.get_pad("sink"))
+ sink.set_state(gst.STATE_PLAYING)
+
+ def get_codec_config (self, media_type):
+ if media_type == farstream.MEDIA_TYPE_VIDEO:
+ codecs = [ farstream.Codec(farstream.CODEC_ID_ANY, "H264",
+ farstream.MEDIA_TYPE_VIDEO, 0) ]
+ if self.conn.GetProtocol() == "sip" :
+ codecs += [ farstream.Codec(farstream.CODEC_ID_DISABLE, "THEORA",
+ farstream.MEDIA_TYPE_VIDEO, 0) ]
+ else:
+ codecs += [ farstream.Codec(farstream.CODEC_ID_ANY, "THEORA",
+ farstream.MEDIA_TYPE_VIDEO, 0) ]
+ codecs += [
+ farstream.Codec(farstream.CODEC_ID_ANY, "H263",
+ farstream.MEDIA_TYPE_VIDEO, 0),
+ farstream.Codec(farstream.CODEC_ID_DISABLE, "DV",
+ farstream.MEDIA_TYPE_VIDEO, 0),
+ farstream.Codec(farstream.CODEC_ID_ANY, "JPEG",
+ farstream.MEDIA_TYPE_VIDEO, 0),
+ farstream.Codec(farstream.CODEC_ID_ANY, "MPV",
+ farstream.MEDIA_TYPE_VIDEO, 0),
+ ]
+
+ else:
+ codecs = [
+ farstream.Codec(farstream.CODEC_ID_ANY, "SPEEX",
+ farstream.MEDIA_TYPE_AUDIO, 16000 ),
+ farstream.Codec(farstream.CODEC_ID_ANY, "SPEEX",
+ farstream.MEDIA_TYPE_AUDIO, 8000 )
+ ]
+ return codecs
+
+ def content_added(self, channel, content):
+ sinkpad = content.get_property ("sink-pad")
+
+ mtype = content.get_property ("media-type")
+ prefs = self.get_codec_config (mtype)
+ if prefs != None:
+ try:
+ content.set_codec_preferences(prefs)
+ except GError, e:
+ print e.message
+
+ content.connect ("src-pad-added", self.src_pad_added)
+
+ if mtype == farstream.MEDIA_TYPE_AUDIO:
+ src = gst.parse_bin_from_description("audiotestsrc is-live=1 ! " \
+ "queue", True)
+ elif mtype == farstream.MEDIA_TYPE_VIDEO:
+ src = gst.parse_bin_from_description("videotestsrc is-live=1 ! " \
+ "capsfilter caps=video/x-raw-yuv,width=320,height=240", True)
+
+ self.pipeline.add(src)
+ src.get_pad("src").link(sinkpad)
+ src.set_state(gst.STATE_PLAYING)
+
+ def conference_added (self, channel, conference):
+ self.pipeline.add(conference)
+ self.pipeline.set_state(gst.STATE_PLAYING)
+
diff --git a/farstream/python/examples/callhandler.py b/farstream/python/examples/callhandler.py
new file mode 100644
index 000000000..71af24cd9
--- /dev/null
+++ b/farstream/python/examples/callhandler.py
@@ -0,0 +1,116 @@
+# callhandler.py
+# Copyright (C) 2008-2010 Collabora Ltd.
+#
+# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gobject
+# Need gio so GAsyncInitialbe is known
+import gio
+
+import dbus
+from dbus.mainloop.glib import DBusGMainLoop
+DBusGMainLoop(set_as_default=True)
+
+from constants import *
+from telepathy.interfaces import CHANNEL_INTERFACE, CLIENT, CLIENT_HANDLER
+from telepathy.constants import CONNECTION_HANDLE_TYPE_CONTACT, CONNECTION_HANDLE_TYPE_ROOM
+import telepathy
+
+from callchannel import CallChannel
+
+class CallHandler(dbus.service.Object, telepathy.server.DBusProperties):
+ def __init__(self, bus, bus_name = None):
+ self.bus = bus
+ if bus_name == None:
+ self.bus_name = "org.freedesktop.Telepathy.Client.CallDemo" \
+ + bus.get_unique_name().replace(":", "_").replace(".","_")
+ else:
+ self.bus_name = bus_name
+ self.path = "/" + self.bus_name.replace(".", "/")
+ self._interfaces = set([CLIENT, CLIENT_HANDLER])
+ self._prop_getters = {}
+ self._prop_setters = {}
+
+ dbus.service.Object.__init__(self, bus, self.path)
+ telepathy.server.DBusProperties.__init__(self)
+
+ self._name = dbus.service.BusName (self.bus_name, bus)
+
+ self._implement_property_get (CLIENT,
+ { "Interfaces": self._get_interfaces } )
+ self._implement_property_get (CLIENT_HANDLER,
+ { "HandlerChannelFilter": self._get_filters } )
+ self._implement_property_get (CLIENT_HANDLER,
+ { "Capabilities": self._get_capabilities } )
+
+ def _get_interfaces(self):
+ return dbus.Array(self._interfaces, signature='s')
+
+ def _get_filters(self):
+ return dbus.Array ([
+ { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL,
+ CHANNEL_INTERFACE + ".TargetHandleType":
+ CONNECTION_HANDLE_TYPE_CONTACT,
+ CALL_INITIAL_AUDIO: True,
+ },
+ { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL,
+ CHANNEL_INTERFACE + ".TargetHandleType":
+ CONNECTION_HANDLE_TYPE_CONTACT,
+ CALL_INITIAL_VIDEO: True,
+ },
+ { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL,
+ CHANNEL_INTERFACE + ".TargetHandleType":
+ CONNECTION_HANDLE_TYPE_ROOM,
+ CALL_INITIAL_AUDIO: True,
+ },
+ { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL,
+ CHANNEL_INTERFACE + ".TargetHandleType":
+ CONNECTION_HANDLE_TYPE_ROOM,
+ CALL_INITIAL_VIDEO: True,
+ }
+ ],
+ signature='a{sv}')
+
+ def _get_capabilities(self):
+ return dbus.Array ([
+ CHANNEL_TYPE_CALL + '/gtalk-p2p',
+ CHANNEL_TYPE_CALL + '/ice-udp',
+ CHANNEL_TYPE_CALL + '/video/h264',
+ ], signature='s')
+
+ def do_handle_call_channel (self, requests, bus, conn, channel, properties):
+ cchannel = CallChannel(self.bus, conn, channel, properties)
+ cchannel.accept()
+
+ @dbus.service.method(dbus_interface=CLIENT_HANDLER,
+ in_signature='ooa(oa{sv})aota{sv}',
+ async_callbacks= ('_success', '_error'))
+ def HandleChannels(self, account, connection, channels,
+ requests, time, info, _success, _error):
+
+ conn = telepathy.client.Connection (connection[1:].replace('/','.'),
+ connection)
+ # Assume there can be only one
+ (channel, properties) = channels[0]
+
+ _success()
+ self.do_handle_call_channel (requests,
+ self.bus, conn, channel, properties);
+
+if __name__ == '__main__':
+ gobject.threads_init()
+ loop = gobject.MainLoop()
+ CallHandler(dbus.SessionBus())
+ loop.run()
diff --git a/farstream/python/examples/callui.py b/farstream/python/examples/callui.py
new file mode 100644
index 000000000..9e7558f6c
--- /dev/null
+++ b/farstream/python/examples/callui.py
@@ -0,0 +1,285 @@
+#!/usr/bin/env python
+#
+# callui.py
+# Copyright (C) 2008-2010 Collabora Ltd.
+#
+# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gobject
+gobject.threads_init()
+
+import pygtk
+import gtk
+
+gtk.gdk.threads_init()
+
+import dbus
+from dbus.mainloop.glib import DBusGMainLoop
+DBusGMainLoop(set_as_default=True)
+
+import sys
+import time
+
+from telepathy.interfaces import *
+from telepathy.constants import *
+
+from constants import *
+
+class CallChannelRequest:
+ def __init__ (self, bus, account_path, contact,
+ preferred_handler = "", audio = True, video = False,
+ calltype = HANDLE_TYPE_CONTACT):
+ self.bus = bus
+ self.cd = bus.get_object (CHANNEL_DISPATCHER,
+ '/' + CHANNEL_DISPATCHER.replace('.', '/'))
+
+ props = {
+ CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL,
+ CHANNEL_INTERFACE + ".TargetHandleType": calltype,
+ CHANNEL_INTERFACE + ".TargetID": contact,
+ }
+
+ if audio:
+ props[CHANNEL_TYPE_CALL + ".InitialAudio"] = True
+ if video:
+ props[CHANNEL_TYPE_CALL + ".InitialVideo"] = True
+
+ self.request_path = req_path = self.cd.CreateChannel(account_path,
+ props,
+ 0,
+ preferred_handler,
+ dbus_interface = CHANNEL_DISPATCHER)
+
+ self.req = self.bus.get_object (CHANNEL_DISPATCHER, req_path)
+ self.req.connect_to_signal("Failed", self.req_failed)
+ self.req.connect_to_signal("Succeeded", self.req_succeeded)
+ self.req.Proceed(dbus_interface = CHANNEL_REQUEST)
+
+ def req_failed(self, error, message):
+ print "FAILURE: %s (%s)"% (error, message)
+
+ def req_succeeded(self):
+ pass
+
+class Account:
+ CALL_CLASS = {
+ CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_CALL,
+ CHANNEL_INTERFACE + '.TargetHandleType': HANDLE_TYPE_CONTACT
+ }
+
+ def __init__(self, bus, path):
+ self.bus = bus
+ self.path = path
+ self.obj = bus.get_object (ACCOUNT_MANAGER, path)
+ self.properties = self.obj.GetAll (ACCOUNT,
+ dbus_interface=dbus.PROPERTIES_IFACE)
+
+ def get_path(self):
+ return self.path
+
+ def name(self):
+ return self.properties["DisplayName"]
+
+ def has_connection(self):
+ return self.properties["Connection"] != "/"
+
+ def get_contacts(self):
+ path = self.properties["Connection"]
+ if path == "/":
+ return []
+
+ conn = self.bus.get_object (path[1:].replace("/","."), path)
+ yours, channel, properties = conn.EnsureChannel (
+ { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CONTACT_LIST,
+ CHANNEL_INTERFACE + ".TargetHandleType": HANDLE_TYPE_LIST,
+ CHANNEL_INTERFACE + ".TargetID": "subscribe"
+ },
+ dbus_interface = CONNECTION_INTERFACE_REQUESTS
+ )
+
+ subscribe = self.bus.get_object (conn.bus_name, channel)
+ members = subscribe.Get(CHANNEL_INTERFACE_GROUP, "Members",
+ dbus_interface = dbus.PROPERTIES_IFACE)
+
+ caps = conn.GetContactCapabilities (members,
+ dbus_interface = CONNECTION_INTERFACE_CONTACT_CAPABILITIES)
+ members = caps.keys()
+
+ for k, v in caps.iteritems():
+ for c in v:
+ if c[0][CHANNEL_TYPE] == CHANNEL_TYPE_CALL:
+ break
+ else:
+ members.remove (k)
+
+ attributes = conn.GetContactAttributes (
+ dbus.Array(members, signature="u"),
+ dbus.Array([], signature="s"),
+ True)
+
+ return map (lambda v: v[CONNECTION + "/contact-id"],
+ attributes.itervalues())
+
+ def supports_calls(self):
+ path = self.properties["Connection"]
+ if path == "/":
+ return False
+
+ conn = self.bus.get_object (path[1:].replace("/","."), path)
+ classes = conn.Get (CONNECTION_INTERFACE_REQUESTS,
+ 'RequestableChannelClasses', dbus_interface=dbus.PROPERTIES_IFACE)
+
+ return len ([c for c in classes if c[0] == self.CALL_CLASS]) > 0
+
+class UI(gtk.Window):
+ WIDTH=240
+ HEIGHT=-1
+ def __init__ (self, bus):
+ gtk.Window.__init__(self)
+ self.connect('destroy', lambda x: gtk.main_quit())
+ self.set_resizable(False)
+ self.set_size_request(self.WIDTH, self.HEIGHT)
+
+ vbox = gtk.VBox(False, 3)
+ self.add(vbox)
+
+ # call type combo box
+ self.type_store = gtk.ListStore (
+ gobject.TYPE_STRING,
+ gobject.TYPE_UINT)
+
+ self.type_store.append (("1-to-1", CONNECTION_HANDLE_TYPE_CONTACT))
+ self.type_store.append (("Conference",
+ CONNECTION_HANDLE_TYPE_ROOM))
+
+ self.type_combo = combobox = gtk.ComboBox (self.type_store)
+ vbox.pack_start(combobox, False)
+
+ renderer = gtk.CellRendererText()
+ combobox.pack_start(renderer, True)
+ combobox.set_attributes(renderer, text=0)
+ combobox.set_active (0)
+
+ # account combo box
+ self.store = gtk.ListStore (gobject.TYPE_STRING,
+ gobject.TYPE_BOOLEAN,
+ gobject.TYPE_PYOBJECT)
+ self.store.set_sort_func(0,
+ (lambda m, i0, i1:
+ { True: -1, False: 1}[m.get(i0, 0) < m.get(i1, 0)] ))
+ self.store.set_sort_column_id(0, gtk.SORT_ASCENDING)
+
+ f = self.store.filter_new()
+ f.set_visible_func(self.filter_visible)
+ self.account_combo = combobox = gtk.ComboBox(f)
+ vbox.pack_start(combobox, False)
+
+ renderer = gtk.CellRendererText()
+ combobox.pack_start(renderer, True)
+ combobox.set_attributes(renderer, text=0)
+ combobox.connect('changed', self.account_selected)
+
+ # contact entry box
+ self.contact_store = gtk.ListStore(gobject.TYPE_STRING)
+
+ completion = gtk.EntryCompletion ()
+ completion.set_model(self.contact_store)
+ completion.set_text_column(0)
+
+ self.contact_store.set_sort_func(0, self.contact_sort)
+ self.contact_store.set_sort_column_id(0, gtk.SORT_ASCENDING)
+
+ self.contact_combo = combobox = gtk.ComboBoxEntry(self.contact_store)
+ combobox.get_child().set_completion(completion)
+
+ vbox.pack_start(combobox, False)
+
+ bbox = gtk.HButtonBox()
+ bbox.set_layout(gtk.BUTTONBOX_END)
+ vbox.pack_start(bbox, True, False, 3)
+
+ call = gtk.Button("Audio call")
+ call.connect("clicked", self.start_call)
+ bbox.add(call)
+
+ call = gtk.Button("Video call")
+ call.connect("clicked",
+ lambda button: self.start_call(button, video=True))
+ bbox.add(call)
+
+ self.show_all()
+
+ self.bus = bus
+ self.account_mgr = bus.get_object (ACCOUNT_MANAGER,
+ '/' + ACCOUNT_MANAGER.replace('.', '/'))
+ self.get_accounts()
+
+ def start_call(self, button, audio=True, video=False):
+ i = self.type_combo.get_active_iter()
+ (calltype, ) = self.type_combo.get_model().get(i, 1)
+
+ i = self.account_combo.get_active_iter()
+ (account, ) = self.account_combo.get_model().get(i, 2)
+
+ contact = self.contact_combo.get_active_text().strip()
+
+ print "* starting %s call" % ('video' if video else 'audio')
+ CallChannelRequest (self.bus, account.path, contact,
+ audio=audio, video=video, calltype=calltype)
+
+ def contact_sort (self, model, i0, i1):
+ if model.get(i0, 0)[0] < model.get(i1, 0)[0]:
+ return -1
+ else:
+ return 0
+
+ def filter_visible(self, model, titer):
+ return model.get(titer, 1)[0]
+
+ def account_selected (self, combobox):
+ iter = combobox.get_active_iter()
+ if iter == None:
+ return None
+
+ (account,) = combobox.get_model().get(iter, 2)
+
+ self.contact_store.clear()
+
+ map(lambda x: self.contact_store.insert (0, (x,)),
+ account.get_contacts())
+
+ def bail (self, *args):
+ print "BAILING"
+ print args
+ gtk.main_quit()
+
+ def got_accounts(self, accounts):
+ for x in accounts:
+ a = Account(self.bus, x)
+ if a.supports_calls():
+ self.store.insert(0, (a.name(), a.has_connection(), a))
+ self.account_combo.set_active(0)
+
+ def get_accounts (self):
+ self.account_mgr.Get(ACCOUNT_MANAGER, "ValidAccounts",
+ dbus_interface = dbus.PROPERTIES_IFACE,
+ reply_handler = self.got_accounts,
+ error_handler = self.bail)
+
+if __name__ == '__main__':
+ bus = dbus.SessionBus()
+
+ UI(bus)
+ gtk.main()
diff --git a/farstream/python/examples/constants.py b/farstream/python/examples/constants.py
new file mode 100644
index 000000000..0def268d8
--- /dev/null
+++ b/farstream/python/examples/constants.py
@@ -0,0 +1,66 @@
+# constants.py
+# Copyright (C) 2008-2010 Collabora Ltd.
+#
+# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from telepathy.interfaces import CHANNEL_INTERFACE
+
+CHANNEL = CHANNEL_INTERFACE
+
+CHANNEL_TYPE = CHANNEL + ".ChannelType"
+CHANNEL_TYPE_CALL = CHANNEL + ".Type.Call.DRAFT"
+CALL_INITIAL_AUDIO = CHANNEL_TYPE_CALL + '.InitialAudio'
+CALL_INITIAL_VIDEO = CHANNEL_TYPE_CALL + '.InitialVideo'
+CALL_MUTABLE_CONTENTS = CHANNEL_TYPE_CALL + '.MutableContents'
+
+CALL_CONTENT = 'org.freedesktop.Telepathy.Call.Content.DRAFT'
+CALL_CONTENT_IFACE_MEDIA = \
+ 'org.freedesktop.Telepathy.Call.Content.Interface.Media.DRAFT'
+
+CALL_CONTENT_CODECOFFER = \
+ 'org.freedesktop.Telepathy.Call.Content.CodecOffer.DRAFT'
+
+CALL_STREAM = 'org.freedesktop.Telepathy.Call.Stream.DRAFT'
+CALL_STREAM_IFACE_MEDIA = \
+ 'org.freedesktop.Telepathy.Call.Stream.Interface.Media.DRAFT'
+
+CALL_STREAM_ENDPOINT = 'org.freedesktop.Telepathy.Call.Stream.Endpoint.DRAFT'
+
+STREAM_TRANSPORT_RAW_UDP = 1
+STREAM_TRANSPORT_ICE_UDP = 2
+STREAM_TRANSPORT_GTALK_P2P = 3
+STREAM_TRANSPORT_WLM_2009 = 4
+STREAM_TRANSPORT_SHM = 5
+STREAM_TRANSPORT_MULTICAST = 6
+STREAM_TRANSPOR_DUMMY = 0xff
+
+CALL_STATE_UNKNOWN = 0
+CALL_STATE_PENDING_INITIATOR = 1
+CALL_STATE_PENDING_RECEIVER = 2
+CALL_STATE_ACCEPTED = 3
+CALL_STATE_ENDED = 4
+
+CALL_FLAG_LOCALLY_RINGING = 1
+CALL_FLAG_QUEUED = 2
+CALL_FLAG_LOCALLY_HELD = 4
+CALL_FLAG_FORWARDED = 8
+CALL_FLAG_IN_PROGRESS = 16
+CALL_FLAG_CLEARING = 32
+
+CALL_STATE_CHANGE_REASON_UNKNOWN = 0
+CALL_STATE_CHANGE_REASON_REQUESTED = 1
+
+CONTENT_PACKETIZATION_RTP = 0
+CONTENT_PACKETIZATION_RAW = 1
diff --git a/farstream/python/examples/element-properties b/farstream/python/examples/element-properties
new file mode 100644
index 000000000..40f706d6e
--- /dev/null
+++ b/farstream/python/examples/element-properties
@@ -0,0 +1,62 @@
+# Put the desired properties in the style of
+#
+# [element name]
+# prop1=val1
+
+[gstrtpbin]
+latency=100
+
+[x264enc]
+byte-stream=1
+bframes=0
+b-adapt=0
+cabac=0
+dct8x8=0
+bitrate=256
+# tuned for zero latency
+tune=0x4
+profile=1
+speed-preset=3
+sliced-threads=false
+
+[ffenc_h263]
+rtp-payload-size=1
+
+[theoraenc]
+bitrate=256
+
+[vp8enc]
+bitrate=256000
+max-latency=1
+speed=2
+error-resilient=true
+
+# Work around bug in the re-timestamp slaving method in
+# GStreamer (2 is skew)
+[alsasrc]
+slave-method=2
+
+[osssrc]
+slave-method=2
+
+[oss4src]
+slave-method=2
+
+[sunaudiosrc]
+slave-method=2
+
+[rtph264pay]
+config-interval=5
+
+[rtppcmupay]
+ptime-multiple=20000000
+
+[rtppcmapay]
+ptime-multiple=20000000
+
+[gstrtpjitterbuffer]
+do-lost=1
+
+[ewh264enc]
+profile=baseline
+quality=5
diff --git a/farstream/python/examples/util.py b/farstream/python/examples/util.py
new file mode 100644
index 000000000..bbad9c852
--- /dev/null
+++ b/farstream/python/examples/util.py
@@ -0,0 +1,40 @@
+# util.py
+# Copyright (C) 2008-2010 Collabora Ltd.
+#
+# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from constants import *
+
+def call_state_to_s(state):
+ return {
+ CALL_STATE_UNKNOWN: 'Unknown',
+ CALL_STATE_PENDING_INITIATOR: 'Pending Initiator',
+ CALL_STATE_PENDING_RECEIVER: 'Pending Receiver',
+ CALL_STATE_ACCEPTED: 'Accepted',
+ CALL_STATE_ENDED: 'Ended'
+ }[state]
+
+def call_flags_to_s(flags):
+ flag_strs = {
+ CALL_FLAG_LOCALLY_RINGING: 'Locally Ringing',
+ CALL_FLAG_QUEUED: 'Queued',
+ CALL_FLAG_LOCALLY_HELD: 'Locally Held',
+ CALL_FLAG_FORWARDED: 'Forwarded',
+ CALL_FLAG_IN_PROGRESS: 'In Progress',
+ CALL_FLAG_CLEARING: 'Clearing'
+ }
+
+ return ' | '.join([ '%s (%d)' % (flag_strs[i], i)
+ for i in flag_strs.keys() if flags & i ]) or 'None'
diff --git a/farstream/python/pygstminiobject.c b/farstream/python/pygstminiobject.c
new file mode 100644
index 000000000..dcd4ded44
--- /dev/null
+++ b/farstream/python/pygstminiobject.c
@@ -0,0 +1,373 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pygtk- Python bindings for the GTK toolkit.
+ * Copyright (C) 1998-2003 James Henstridge
+ *
+ * pygobject.c: wrapper for the GObject type.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#define NO_IMPORT_PYGOBJECT
+#include "pygstminiobject.h"
+#include <gst/gst.h>
+
+static const gchar pygstminiobject_class_id[] = "PyGstMiniObject::class";
+static GQuark pygstminiobject_class_key = 0;
+/* static const gchar pygstminiobject_wrapper_id[] = "PyGstMiniObject::wrapper"; */
+/* static GQuark pygstminiobject_wrapper_key = 0; */
+
+static void pygstminiobject_dealloc(PyGstMiniObject *self);
+/* static int pygstminiobject_traverse(PyGstMiniObject *self, visitproc visit, void *arg); */
+/* static int pygstminiobject_clear(PyGstMiniObject *self); */
+
+GST_DEBUG_CATEGORY_EXTERN (pygst_debug);
+#define GST_CAT_DEFAULT pygst_debug
+
+/**
+ * pygstminiobject_lookup_class:
+ * @gtype: the GType of the GstMiniObject subclass.
+ *
+ * This function looks up the wrapper class used to represent
+ * instances of a GstMiniObject represented by @gtype. If no wrapper class
+ * or interface has been registered for the given GType, then a new
+ * type will be created.
+ *
+ * Returns: The wrapper class for the GstMiniObject or NULL if the
+ * GType has no registered type and a new type couldn't be created
+ */
+PyTypeObject *
+pygstminiobject_lookup_class(GType gtype)
+{
+ PyTypeObject *py_type = NULL;
+ GType ctype = gtype;
+
+ while (!py_type && ctype) {
+ py_type = g_type_get_qdata(ctype, pygstminiobject_class_key);
+ ctype = g_type_parent(ctype);
+ }
+ if (!ctype)
+ g_error ("Couldn't find a good base type!!");
+
+ return py_type;
+}
+
+/**
+ * pygstminiobject_register_class:
+ * @dict: the module dictionary. A reference to the type will be stored here.
+ * @type_name: not used ?
+ * @gtype: the GType of the Gstminiobject subclass.
+ * @type: the Python type object for this wrapper.
+ * @bases: a tuple of Python type objects that are the bases of this type.
+ *
+ * This function is used to register a Python type as the wrapper for
+ * a particular Gstminiobject subclass. It will also insert a reference to
+ * the wrapper class into the module dictionary passed as a reference,
+ * which simplifies initialisation.
+ */
+void
+pygstminiobject_register_class(PyObject *dict, const gchar *type_name,
+ GType gtype, PyTypeObject *type,
+ PyObject *bases)
+{
+ PyObject *o;
+ const char *class_name, *s;
+
+ if (!pygstminiobject_class_key)
+ pygstminiobject_class_key = g_quark_from_static_string(pygstminiobject_class_id);
+
+ class_name = type->tp_name;
+ s = strrchr(class_name, '.');
+ if (s != NULL)
+ class_name = s + 1;
+
+ type->ob_type = &PyType_Type;
+ type->tp_alloc = PyType_GenericAlloc;
+ type->tp_new = PyType_GenericNew;
+ if (bases) {
+ type->tp_bases = bases;
+ type->tp_base = (PyTypeObject *)PyTuple_GetItem(bases, 0);
+ }
+
+ if (PyType_Ready(type) < 0) {
+ g_warning ("couldn't make the type `%s' ready", type->tp_name);
+ return;
+ }
+
+ if (gtype) {
+ o = pyg_type_wrapper_new(gtype);
+ PyDict_SetItemString(type->tp_dict, "__gtype__", o);
+ Py_DECREF(o);
+
+ /* stash a pointer to the python class with the GType */
+ Py_INCREF(type);
+ g_type_set_qdata(gtype, pygstminiobject_class_key, type);
+ }
+
+ PyDict_SetItemString(dict, (char *)class_name, (PyObject *)type);
+}
+
+void
+pygstminiobject_register_wrapper (PyObject *self)
+{
+}
+
+
+/**
+ * pygstminiobject_new:
+ * @obj: a GstMiniObject instance.
+ *
+ * This function gets a reference to a wrapper for the given GstMiniObject
+ * instance. A new wrapper will always be created.
+ *
+ * Returns: a reference to the wrapper for the GstMiniObject.
+ */
+PyObject *
+pygstminiobject_new (GstMiniObject *obj)
+{
+ PyGstMiniObject *self = NULL;
+ PyGILState_STATE state;
+ PyTypeObject *tp = NULL;
+
+ if (obj == NULL) {
+ Py_INCREF (Py_None);
+ return Py_None;
+ }
+
+ /* since mini objects cannot notify us when they get destroyed, we
+ * can't use a global hash to map GMO to PyO, and have to create a new
+ * Python object every time we see it */
+ tp = pygstminiobject_lookup_class (G_OBJECT_TYPE (obj));
+ GST_DEBUG ("have to create wrapper for object %p", obj);
+ if (!tp)
+ g_warning ("Couldn't get class for type object : %p", obj);
+ if (tp->tp_flags & Py_TPFLAGS_HEAPTYPE) {
+ GST_INFO ("Increment refcount %p", tp);
+ Py_INCREF (tp);
+ }
+ state = pyg_gil_state_ensure();
+ self = PyObject_New (PyGstMiniObject, tp);
+ pyg_gil_state_release(state);
+
+ if (self == NULL)
+ return NULL;
+ self->obj = gst_mini_object_ref (obj);
+
+ self->inst_dict = NULL;
+ self->weakreflist = NULL;
+
+ GST_DEBUG ("created Python object %p for GstMiniObject %p [ref:%d]",
+ self, obj, GST_MINI_OBJECT_REFCOUNT_VALUE (obj));
+ return (PyObject *) self;
+}
+
+static void
+pygstminiobject_dealloc(PyGstMiniObject *self)
+{
+ PyGILState_STATE state;
+
+ g_return_if_fail (self != NULL);
+
+ GST_DEBUG ("At the beginning %p", self);
+ state = pyg_gil_state_ensure();
+
+ if (self->obj) {
+ GST_DEBUG ("PyO %p unreffing GstMiniObject %p [ref:%d]", self,
+ self->obj, GST_MINI_OBJECT_REFCOUNT_VALUE (self->obj));
+ gst_mini_object_unref(self->obj);
+ GST_DEBUG ("setting self %p -> obj to NULL", self);
+ self->obj = NULL;
+ }
+
+ if (self->inst_dict) {
+ Py_DECREF(self->inst_dict);
+ self->inst_dict = NULL;
+ }
+
+ self->ob_type->tp_free((PyObject *) self);
+ pyg_gil_state_release(state);
+ GST_DEBUG ("At the end %p", self);
+}
+
+static int
+pygstminiobject_compare(PyGstMiniObject *self, PyGstMiniObject *v)
+{
+ if (self->obj == v->obj) return 0;
+ if (self->obj > v->obj) return -1;
+ return 1;
+}
+
+static long
+pygstminiobject_hash(PyGstMiniObject *self)
+{
+ return (long)self->obj;
+}
+
+static PyObject *
+pygstminiobject_repr(PyGstMiniObject *self)
+{
+ gchar buf[256];
+
+ g_snprintf(buf, sizeof(buf),
+ "<%s mini-object (%s) at 0x%lx>",
+ self->ob_type->tp_name,
+ self->obj ? G_OBJECT_TYPE_NAME(self->obj) : "uninitialized",
+ (long)self);
+ return PyString_FromString(buf);
+}
+
+
+static void
+pygstminiobject_free(PyObject *op)
+{
+ PyObject_FREE(op);
+}
+
+
+/* ---------------- PyGstMiniObject methods ----------------- */
+
+static int
+pygstminiobject_init(PyGstMiniObject *self, PyObject *args, PyObject *kwargs)
+{
+ GType object_type;
+ GstMiniObjectClass *class;
+
+ if (!PyArg_ParseTuple(args, ":GstMiniObject.__init__", &object_type))
+ return -1;
+
+ object_type = pyg_type_from_object((PyObject *)self);
+ if (!object_type)
+ return -1;
+
+ if (G_TYPE_IS_ABSTRACT(object_type)) {
+ PyErr_Format(PyExc_TypeError, "cannot create instance of abstract "
+ "(non-instantiable) type `%s'", g_type_name(object_type));
+ return -1;
+ }
+
+ if ((class = g_type_class_ref (object_type)) == NULL) {
+ PyErr_SetString(PyExc_TypeError,
+ "could not get a reference to type class");
+ return -1;
+ }
+
+ self->obj = gst_mini_object_new(object_type);
+ if (self->obj == NULL)
+ PyErr_SetString (PyExc_RuntimeError, "could not create object");
+
+ g_type_class_unref(class);
+
+ return (self->obj) ? 0 : -1;
+}
+
+static PyObject *
+pygstminiobject__gstminiobject_init__(PyGstMiniObject *self, PyObject *args, PyObject *kwargs)
+{
+ if (pygstminiobject_init(self, args, kwargs) < 0)
+ return NULL;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+pygstminiobject_copy(PyGstMiniObject *self, PyObject *args)
+{
+ return pygstminiobject_new(gst_mini_object_copy(self->obj));
+}
+
+static PyMethodDef pygstminiobject_methods[] = {
+ { "__gstminiobject_init__", (PyCFunction)pygstminiobject__gstminiobject_init__,
+ METH_VARARGS|METH_KEYWORDS },
+ { "copy", (PyCFunction)pygstminiobject_copy, METH_VARARGS, "Copies the miniobject"},
+ { NULL, NULL, 0 }
+};
+
+static PyObject *
+pygstminiobject_get_dict(PyGstMiniObject *self, void *closure)
+{
+ if (self->inst_dict == NULL) {
+ self->inst_dict = PyDict_New();
+ if (self->inst_dict == NULL)
+ return NULL;
+ }
+ Py_INCREF(self->inst_dict);
+ return self->inst_dict;
+}
+
+static PyObject *
+pygstminiobject_get_refcount(PyGstMiniObject *self, void *closure)
+{
+ return PyInt_FromLong(GST_MINI_OBJECT_REFCOUNT_VALUE(self->obj));
+}
+
+static PyObject *
+pygstminiobject_get_flags(PyGstMiniObject *self, void *closure)
+{
+ return PyInt_FromLong(GST_MINI_OBJECT_FLAGS(self->obj));
+}
+
+static PyGetSetDef pygstminiobject_getsets[] = {
+ { "__dict__", (getter)pygstminiobject_get_dict, (setter)0 },
+ { "__grefcount__", (getter)pygstminiobject_get_refcount, (setter)0, },
+ { "flags", (getter)pygstminiobject_get_flags, (setter)0, },
+ { NULL, 0, 0 }
+};
+
+PyTypeObject PyGstMiniObject_Type = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /* ob_size */
+ "gst.MiniObject", /* tp_name */
+ sizeof(PyGstMiniObject), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ /* methods */
+ (destructor)pygstminiobject_dealloc, /* tp_dealloc */
+ (printfunc)0, /* tp_print */
+ (getattrfunc)0, /* tp_getattr */
+ (setattrfunc)0, /* tp_setattr */
+ (cmpfunc)pygstminiobject_compare, /* tp_compare */
+ (reprfunc)pygstminiobject_repr, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ (hashfunc)pygstminiobject_hash, /* tp_hash */
+ (ternaryfunc)0, /* tp_call */
+ (reprfunc)0, /* tp_str */
+ (getattrofunc)0, /* tp_getattro */
+ (setattrofunc)0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ NULL, /* Documentation string */
+ (traverseproc)0, /* tp_traverse */
+ (inquiry)0, /* tp_clear */
+ (richcmpfunc)0, /* tp_richcompare */
+ offsetof(PyGstMiniObject, weakreflist), /* tp_weaklistoffset */
+ (getiterfunc)0, /* tp_iter */
+ (iternextfunc)0, /* tp_iternext */
+ pygstminiobject_methods, /* tp_methods */
+ 0, /* tp_members */
+ pygstminiobject_getsets, /* tp_getset */
+ (PyTypeObject *)0, /* tp_base */
+ (PyObject *)0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ offsetof(PyGstMiniObject, inst_dict), /* tp_dictoffset */
+ (initproc)pygstminiobject_init, /* tp_init */
+ (allocfunc)0, /* tp_alloc */
+ (newfunc)0, /* tp_new */
+ (freefunc)pygstminiobject_free, /* tp_free */
+ (inquiry)0, /* tp_is_gc */
+ (PyObject *)0, /* tp_bases */
+};
+
diff --git a/farstream/python/pygstminiobject.h b/farstream/python/pygstminiobject.h
new file mode 100644
index 000000000..85516035f
--- /dev/null
+++ b/farstream/python/pygstminiobject.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; c-basic-offset: 4 -*- */
+
+#ifndef _PYGSTMINIOBJECT_H_
+#define _PYGSTMINIOBJECT_H_
+
+#include <Python.h>
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "common.h"
+
+G_BEGIN_DECLS
+
+/* Work around bugs in PyGILState api fixed in 2.4.0a4 */
+#undef PYGIL_API_IS_BUGGY
+#if PY_VERSION_HEX < 0x020400A4
+#define PYGIL_API_IS_BUGGY TRUE
+#else
+#define PYGIL_API_IS_BUGGY FALSE
+#endif
+
+typedef struct {
+ PyObject_HEAD
+ GstMiniObject *obj;
+ PyObject *inst_dict; /* the instance dictionary -- must be last */
+ PyObject *weakreflist; /* list of weak references */
+} PyGstMiniObject;
+
+PyObject *
+pygstminiobject_new(GstMiniObject *obj);
+
+#define pygstminiobject_get(v) (((PyGstMiniObject *)(v))->obj)
+#define pygstminiobject_check(v,base) (PyObject_TypeCheck(v,base))
+
+void
+pygstminiobject_register_class(PyObject *dict, const gchar *type_name,
+ GType gtype, PyTypeObject *type,
+ PyObject *bases);
+void
+pygstminiobject_register_wrapper(PyObject *self);
+
+void
+pygst_miniobject_init();
+
+#ifndef _INSIDE_PYGSTMINIOBJECT_
+
+
+extern PyTypeObject PyGstMiniObject_Type;
+
+#endif /* !_INSIDE_PYGSTMINIOBJECT_ */
+
+G_END_DECLS
+
+#endif /* !_PYGSTMINIOBJECT_H_ */
diff --git a/farstream/python/pytpfarstream-filter.defs b/farstream/python/pytpfarstream-filter.defs
new file mode 100644
index 000000000..7a8b527f4
--- /dev/null
+++ b/farstream/python/pytpfarstream-filter.defs
@@ -0,0 +1,24 @@
+(define-object Channel
+ (in-module "Tf")
+ (parent "GObject")
+ (c-name "TfChannel")
+ (gtype-id "TF_TYPE_CHANNEL")
+)
+
+(define-object Content
+ (in-module "Tf")
+ (parent "GObject")
+ (c-name "TfContent")
+ (gtype-id "TF_TYPE_CONTENT")
+)
+
+(define-function tf_channel_new_async
+ (c-name "tf_channel_new_async")
+ (return-type "none")
+ (parameters
+ '("const-gchar*" "connection_busname")
+ '("const-gchar*" "connection_path")
+ '("const-gchar*" "channel_path")
+ '("GAsyncReadyCallback" "callback")
+ )
+)
diff --git a/farstream/python/pytpfarstream.defs b/farstream/python/pytpfarstream.defs
new file mode 100644
index 000000000..59b678309
--- /dev/null
+++ b/farstream/python/pytpfarstream.defs
@@ -0,0 +1,134 @@
+(define-object Channel
+ (in-module "Tf")
+ (parent "GObject")
+ (c-name "TfChannel")
+ (gtype-id "TF_TYPE_CHANNEL")
+)
+
+(define-object Content
+ (in-module "Tf")
+ (parent "GObject")
+ (c-name "TfContent")
+ (gtype-id "TF_TYPE_CONTENT")
+)
+
+(define-function tf_channel_new_async
+ (c-name "tf_channel_new_async")
+ (return-type "none")
+ (parameters
+ '("const-gchar*" "connection_busname")
+ '("const-gchar*" "connection_path")
+ '("const-gchar*" "channel_path")
+ '("GAsyncReadyCallback" "callback")
+ )
+)
+;; -*- scheme -*-
+; object definitions ...
+;; Enumerations and flags ...
+
+
+;; From telepathy-farstream.h
+
+(define-function tf_init
+ (c-name "tf_init")
+ (return-type "none")
+)
+
+
+;; -*- scheme -*-
+; object definitions ...
+;; Enumerations and flags ...
+
+
+;; From channel.h
+
+(define-function tf_channel_get_type
+ (c-name "tf_channel_get_type")
+ (return-type "GType")
+)
+
+(define-method bus_message
+ (of-object "TfChannel")
+ (c-name "tf_channel_bus_message")
+ (return-type "gboolean")
+ (parameters
+ '("GstMessage*" "message")
+ )
+)
+
+
+;; -*- scheme -*-
+; object definitions ...
+;; Enumerations and flags ...
+
+
+;; From content.h
+
+(define-function tf_content_get_type
+ (c-name "tf_content_get_type")
+ (return-type "GType")
+)
+
+(define-method error_literal
+ (of-object "TfContent")
+ (c-name "tf_content_error_literal")
+ (return-type "none")
+ (parameters
+ '("guint" "reason")
+ '("const-gchar*" "detailed_reason")
+ '("const-gchar*" "message")
+ )
+)
+
+(define-method error
+ (of-object "TfContent")
+ (c-name "tf_content_error")
+ (return-type "none")
+ (parameters
+ '("guint" "reason")
+ '("const-gchar*" "detailed_reason")
+ '("const-gchar*" "message_format")
+ )
+ (varargs #t)
+)
+
+(define-method iterate_src_pads
+ (of-object "TfContent")
+ (c-name "tf_content_iterate_src_pads")
+ (return-type "GstIterator*")
+ (parameters
+ '("guint*" "handles")
+ '("guint" "handle_count")
+ )
+)
+
+
+;; -*- scheme -*-
+; object definitions ...
+;; Enumerations and flags ...
+
+
+;; From stream.h
+
+(define-function tf_stream_get_type
+ (c-name "tf_stream_get_type")
+ (return-type "GType")
+)
+
+(define-method get_id
+ (of-object "TfStream")
+ (c-name "tf_stream_get_id")
+ (return-type "guint")
+)
+
+(define-method error
+ (of-object "TfStream")
+ (c-name "tf_stream_error")
+ (return-type "none")
+ (parameters
+ '("TpMediaStreamError" "error")
+ '("const-gchar*" "message")
+ )
+)
+
+
diff --git a/farstream/python/pytpfarstream.override b/farstream/python/pytpfarstream.override
new file mode 100644
index 000000000..411831b00
--- /dev/null
+++ b/farstream/python/pytpfarstream.override
@@ -0,0 +1,107 @@
+%%
+headers
+#include <pygobject.h>
+
+#include <gst/gst.h>
+
+#include <telepathy-farstream/telepathy-farstream.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/channel.h>
+
+#include "pygstminiobject.h"
+
+static void
+async_result_callback_marshal(GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ PyObject *callback = user_data;
+ PyObject *ret;
+ PyGILState_STATE state;
+
+ state = pyg_gil_state_ensure();
+
+ ret = PyObject_CallFunction(callback, "NN",
+ pygobject_new(source_object),
+ pygobject_new((GObject *)result));
+
+ if (ret == NULL) {
+ PyErr_Print();
+ PyErr_Clear();
+ }
+
+ Py_XDECREF(callback);
+ Py_XDECREF(ret);
+
+ pyg_gil_state_release(state);
+}
+%%
+modulename tf
+%%
+import gobject.GObject as PyGObject_Type
+import gst.Message as PyGstMessage_Type
+%%
+ignore-glob
+ *_get_type
+%%
+headers
+%%
+override tf_channel_new_async kwargs
+static PyObject *
+_wrap_tf_channel_new_async(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = { "connection_busname", "connection_path",
+ "channel_path", "callback", NULL };
+
+ char *busname, *connection_path, *channel_path;
+ TpChannel *proxy = NULL;
+ TpConnection *connection = NULL;
+ TpDBusDaemon *dbus = NULL;
+ GError *error = NULL;
+ PyObject *callback;
+ PyObject *ret = NULL;
+
+ if (!PyArg_ParseTupleAndKeywords (args, kwargs, "sssO:tf_channel_new_async",
+ kwlist, &busname, &connection_path, &channel_path, &callback))
+ goto out;
+
+ dbus = tp_dbus_daemon_dup (&error);
+ if (dbus == NULL)
+ {
+ pyg_error_check (&error);
+ goto out;
+ }
+
+ connection = tp_connection_new (dbus, busname, connection_path, &error);
+ if (connection == NULL)
+ {
+ pyg_error_check (&error);
+ goto out;
+ }
+
+ proxy = tp_channel_new (connection, channel_path, NULL,
+ TP_UNKNOWN_HANDLE_TYPE, 0, &error);
+
+ if (proxy == NULL)
+ {
+ pyg_error_check (&error);
+ goto out;
+ }
+
+ ret = Py_None;
+ Py_XINCREF (callback);
+ tf_channel_new_async (proxy, async_result_callback_marshal, callback);
+
+out:
+ if (dbus != NULL)
+ g_object_unref (dbus);
+
+ if (connection != NULL)
+ g_object_unref (connection);
+
+ if (proxy != NULL)
+ g_object_unref (proxy);
+
+ return ret;
+}
diff --git a/farstream/python/pytpfarstreammodule.c b/farstream/python/pytpfarstreammodule.c
new file mode 100644
index 000000000..f6e6f8669
--- /dev/null
+++ b/farstream/python/pytpfarstreammodule.c
@@ -0,0 +1,30 @@
+#include <pygobject.h>
+
+#include <gst/gst.h>
+#include <telepathy-farstream/telepathy-farstream.h>
+
+void tf_register_classes (PyObject *d);
+
+DL_EXPORT(void) inittpfarstream(void);
+extern PyMethodDef tf_functions[];
+
+GST_DEBUG_CATEGORY (pygst_debug); /* for python code */
+
+DL_EXPORT(void)
+inittpfarstream(void)
+{
+ PyObject *m, *d;
+
+ tf_init ();
+ init_pygobject ();
+
+ m = Py_InitModule ("tpfarstream", tf_functions);
+ d = PyModule_GetDict (m);
+
+ tf_register_classes (d);
+
+ if (PyErr_Occurred ()) {
+ PyErr_Print();
+ Py_FatalError ("can't initialise module tpfarstream");
+ }
+}
diff --git a/farstream/python/rebuild-defs.sh b/farstream/python/rebuild-defs.sh
new file mode 100755
index 000000000..f74bdd356
--- /dev/null
+++ b/farstream/python/rebuild-defs.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+HEADERS=" \
+ telepathy-farstream.h \
+ channel.h \
+ content.h \
+ stream.h"
+
+srcdir=../telepathy-farstream/
+
+output=pytpfarstream.defs
+filter=pytpfarstream-filter.defs
+
+cat ${filter} > ${output}
+
+for h in $HEADERS; do
+ python codegen/h2def.py --defsfilter=${filter} ${srcdir}/$h >> $output
+done
diff --git a/farstream/telepathy-farstream/Makefile.am b/farstream/telepathy-farstream/Makefile.am
new file mode 100644
index 000000000..04f3c680e
--- /dev/null
+++ b/farstream/telepathy-farstream/Makefile.am
@@ -0,0 +1,88 @@
+BUILT_SOURCES = \
+ tf-signals-marshal.h \
+ tf-signals-marshal.c
+
+CLEANFILES = $(BUILT_SOURCES) tf-signals-marshal.list
+
+
+tfincludedir = $(includedir)/telepathy-1.0/telepathy-farstream
+tfinclude_HEADERS = \
+ telepathy-farstream.h \
+ channel.h \
+ content.h
+
+libtelepathy_farstream_la_SOURCES = \
+ stream.c \
+ stream.h \
+ content.c \
+ content.h \
+ content-priv.h \
+ session.c \
+ channel.c \
+ channel.h \
+ stream-priv.h \
+ session-priv.h \
+ channel-priv.h \
+ media-signalling-channel.c \
+ media-signalling-channel.h \
+ media-signalling-content.c \
+ media-signalling-content.h \
+ call-channel.c \
+ call-channel.h \
+ call-content.h \
+ call-content.c \
+ call-stream.h \
+ call-stream.c \
+ telepathy-farstream.h \
+ telepathy-farstream.c \
+ utils.h
+
+nodist_libtelepathy_farstream_la_SOURCES = $(BUILT_SOURCES)
+
+lib_LTLIBRARIES = libtelepathy-farstream.la
+
+pkgconfigdir = ${libdir}/pkgconfig
+pkgconfig_DATA = telepathy-farstream.pc
+
+AM_CFLAGS = \
+ -DG_LOG_DOMAIN=\"tp-fs\" \
+ $(ERROR_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(DBUS_CFLAGS) \
+ $(GST_CFLAGS) \
+ $(FARSTREAM_CFLAGS) \
+ $(TELEPATHY_CFLAGS) \
+ -I$(top_srcdir) \
+ -I$(top_builddir)
+
+libtelepathy_farstream_la_LIBADD = \
+ $(GLIB_LIBS) \
+ $(DBUS_LIBS) \
+ $(GST_LIBS) \
+ $(FARSTREAM_LIBS) \
+ $(TELEPATHY_LIBS) \
+ ../extensions/libfuture-extensions.la
+
+libtelepathy_farstream_la_LDFLAGS = -no-undefined \
+ -export-symbols-regex "^tf_(init|content_|channel_).*" \
+ -version-info "$(LT_CURRENT)":"$(LT_REVISION)":"$(LT_AGE)"
+
+
+# rules to generate signal marshallers
+tf-signals-marshal.list: $(libtelepathy_farstream_la_SOURCES) Makefile.am
+ ( cd $(srcdir) && \
+ sed -n -e 's/.*_tf_marshal_\([[:upper:][:digit:]]*__[[:upper:][:digit:]_]*\).*/\1/p' \
+ $(libtelepathy_farstream_la_SOURCES) ) \
+ | sed -e 's/__/:/' -e 'y/_/,/' | sort -u > $@.tmp
+ if cmp -s $@.tmp $@; then \
+ rm $@.tmp; \
+ touch $@; \
+ else \
+ mv $@.tmp $@; \
+ fi
+
+%-signals-marshal.h: %-signals-marshal.list Makefile.in
+ glib-genmarshal --header --prefix=_$(subst -,_,$*)_marshal $< > $*-signals-marshal.h
+
+%-signals-marshal.c: %-signals-marshal.list Makefile.in
+ glib-genmarshal --body --prefix=_$(subst -,_,$*)_marshal $< > $*-signals-marshal.c
diff --git a/farstream/telepathy-farstream/call-channel.c b/farstream/telepathy-farstream/call-channel.c
new file mode 100644
index 000000000..57c650f77
--- /dev/null
+++ b/farstream/telepathy-farstream/call-channel.c
@@ -0,0 +1,723 @@
+/*
+ * call-channel.c - Source for TfCallChannel
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * SECTION:call-channel
+
+ * @short_description: Handle the Call interface on a Channel
+ *
+ * This class handles the
+ * org.freedesktop.Telepathy.Channel.Interface.Call on a
+ * channel using Farstream.
+ */
+
+
+#include "call-channel.h"
+
+#include <telepathy-glib/util.h>
+#include <telepathy-glib/interfaces.h>
+#include <farstream/fs-conference.h>
+
+#include "extensions/extensions.h"
+
+#include "call-content.h"
+#include "tf-signals-marshal.h"
+
+
+static void call_channel_async_initable_init (GAsyncInitableIface *asynciface);
+
+G_DEFINE_TYPE_WITH_CODE (TfCallChannel, tf_call_channel, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
+ call_channel_async_initable_init))
+
+enum
+{
+ PROP_FS_CONFERENCES = 1
+};
+
+
+enum
+{
+ SIGNAL_FS_CONFERENCE_ADDED,
+ SIGNAL_FS_CONFERENCE_REMOVED,
+ SIGNAL_CONTENT_ADDED,
+ SIGNAL_CONTENT_REMOVED,
+ SIGNAL_COUNT
+};
+
+static guint signals[SIGNAL_COUNT] = {0};
+
+struct CallConference {
+ gint use_count;
+ gchar *conference_type;
+ FsConference *fsconference;
+};
+
+struct CallParticipant {
+ gint use_count;
+ guint handle;
+ FsConference *fsconference;
+ FsParticipant *fsparticipant;
+};
+
+static void
+tf_call_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void tf_call_channel_dispose (GObject *object);
+
+
+static void tf_call_channel_init_async (GAsyncInitable *initable,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+static gboolean tf_call_channel_init_finish (GAsyncInitable *initable,
+ GAsyncResult *res,
+ GError **error);
+
+static void got_hardware_streaming (TpProxy *proxy, const GValue *out_value,
+ const GError *error, gpointer user_data, GObject *weak_object);
+
+
+static void
+tf_call_channel_class_init (TfCallChannelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = tf_call_channel_dispose;
+ object_class->get_property = tf_call_channel_get_property;
+
+ g_object_class_install_property (object_class, PROP_FS_CONFERENCES,
+ g_param_spec_boxed ("fs-conferences",
+ "Farstream FsConference object",
+ "GPtrArray of Farstream FsConferences for this channel",
+ G_TYPE_PTR_ARRAY,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ signals[SIGNAL_FS_CONFERENCE_ADDED] = g_signal_new ("fs-conference-added",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ _tf_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, FS_TYPE_CONFERENCE);
+
+ signals[SIGNAL_FS_CONFERENCE_REMOVED] = g_signal_new ("fs-conference-removed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ _tf_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, FS_TYPE_CONFERENCE);
+
+ signals[SIGNAL_CONTENT_ADDED] = g_signal_new ("content-added",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ _tf_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, TF_TYPE_CALL_CONTENT);
+
+ signals[SIGNAL_CONTENT_REMOVED] = g_signal_new ("content-removed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ _tf_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, TF_TYPE_CALL_CONTENT);
+}
+
+
+static void
+call_channel_async_initable_init (GAsyncInitableIface *asynciface)
+{
+ asynciface->init_async = tf_call_channel_init_async;
+ asynciface->init_finish = tf_call_channel_init_finish;
+}
+
+static void
+free_call_conference (gpointer data)
+{
+ struct CallConference *cc = data;
+
+ gst_object_unref (cc->fsconference);
+ g_slice_free (struct CallConference, data);
+}
+
+static void
+free_participant (gpointer data)
+{
+ struct CallParticipant *cp = data;
+
+ g_object_unref (cp->fsparticipant);
+ gst_object_unref (cp->fsconference);
+ g_slice_free (struct CallParticipant, cp);
+}
+
+static void
+tf_call_channel_init (TfCallChannel *self)
+{
+ self->fsconferences = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+ free_call_conference);
+
+ self->participants = g_ptr_array_new_with_free_func (free_participant);
+}
+
+
+static void
+tf_call_channel_init_async (GAsyncInitable *initable,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TfCallChannel *self = TF_CALL_CHANNEL (initable);
+ GSimpleAsyncResult *res;
+
+ if (cancellable != NULL)
+ {
+ g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data,
+ G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED,
+ "TfCallChannel initialisation does not support cancellation");
+ return;
+ }
+
+ res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+ tf_call_channel_init_async);
+
+ tp_cli_dbus_properties_call_get (self->proxy, -1,
+ TF_FUTURE_IFACE_CHANNEL_TYPE_CALL,
+ "HardwareStreaming",
+ got_hardware_streaming, res, NULL, G_OBJECT (self));
+}
+
+static gboolean
+tf_call_channel_init_finish (GAsyncInitable *initable,
+ GAsyncResult *res,
+ GError **error)
+{
+ GSimpleAsyncResult *simple_res;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (res,
+ G_OBJECT (initable), tf_call_channel_init_async), FALSE);
+ simple_res = G_SIMPLE_ASYNC_RESULT (res);
+
+ if (g_simple_async_result_propagate_error (simple_res, error))
+ return FALSE;
+
+ return g_simple_async_result_get_op_res_gboolean (simple_res);
+}
+
+
+static void
+tf_call_channel_dispose (GObject *object)
+{
+ TfCallChannel *self = TF_CALL_CHANNEL (object);
+
+ g_debug (G_STRFUNC);
+
+ /* Some of the contents may have more than our ref - if they're in the
+ middle of an async op, they're reffed by the async result.
+ In this case, unreffing them (implicitely) through destruction of
+ the hash table they're in will not dispose them just yet.
+ However, they keep an unreffed pointer to the call channel, and will,
+ when eventually disposed of, call upon the call channel to put their
+ conference back. Since that call channel will then be disposed of,
+ I think we can all agree that this is a bit unfortunate.
+ So we force dispose the contents as other objects already do, and
+ add checks to the content routines to bail out when the object has
+ already been disposed of. */
+ if (self->contents)
+ {
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, self->contents);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ g_object_run_dispose (G_OBJECT (value));
+ }
+
+ g_hash_table_destroy (self->contents);
+ self->contents = NULL;
+ }
+
+ if (self->participants)
+ g_ptr_array_unref (self->participants);
+ self->participants = NULL;
+
+ if (self->fsconferences)
+ g_hash_table_unref (self->fsconferences);
+ self->fsconferences = NULL;
+
+ if (self->proxy)
+ g_object_unref (self->proxy);
+ self->proxy = NULL;
+
+ if (G_OBJECT_CLASS (tf_call_channel_parent_class)->dispose)
+ G_OBJECT_CLASS (tf_call_channel_parent_class)->dispose (object);
+}
+
+static void
+conf_into_ptr_array (gpointer key, gpointer value, gpointer data)
+{
+ struct CallConference *cc = value;
+ GPtrArray *array = data;
+
+ g_ptr_array_add (array, cc->fsconference);
+}
+
+static void
+tf_call_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TfCallChannel *self = TF_CALL_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_FS_CONFERENCES:
+ {
+ GPtrArray *array = g_ptr_array_sized_new (
+ g_hash_table_size (self->fsconferences));
+
+ g_ptr_array_set_free_func (array, gst_object_unref);
+ g_hash_table_foreach (self->fsconferences, conf_into_ptr_array, array);
+ g_value_take_boxed (value, array);
+ }
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+content_ready (GObject *object, GAsyncResult *res, gpointer user_data)
+{
+ TfCallChannel *self = TF_CALL_CHANNEL (user_data);
+ TfCallContent *content = TF_CALL_CONTENT (object);
+
+ if (g_async_initable_init_finish (G_ASYNC_INITABLE (object), res, NULL))
+ {
+ g_signal_emit (self, signals[SIGNAL_CONTENT_ADDED], 0, content);
+ g_object_unref (content);
+ }
+ else
+ {
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, self->contents);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ if (value == object)
+ {
+ g_hash_table_iter_remove (&iter);
+ break;
+ }
+ }
+ }
+
+ g_object_unref (self);
+}
+
+static gboolean
+add_content (TfCallChannel *self, const gchar *content_path)
+{
+ GError *error = NULL;
+ TfCallContent *content;
+
+ /* Check if content already added */
+ if (g_hash_table_lookup (self->contents, content_path))
+ return TRUE;
+
+ content = tf_call_content_new_async (self, content_path,
+ &error, content_ready, g_object_ref (self));
+
+ if (error)
+ {
+ /* Error was already transmitted to the CM by TfCallContent */
+ g_clear_error (&error);
+ g_object_unref (self);
+ return FALSE;
+ }
+
+ g_hash_table_insert (self->contents, g_strdup (content_path), content);
+
+ return TRUE;
+}
+
+static void
+got_contents (TpProxy *proxy, const GValue *out_value,
+ const GError *error, gpointer user_data, GObject *weak_object)
+{
+ TfCallChannel *self = TF_CALL_CHANNEL (weak_object);
+ GSimpleAsyncResult *res = user_data;
+ GPtrArray *contents;
+ guint i;
+
+ if (error)
+ {
+ g_warning ("Error getting the Contents property: %s",
+ error->message);
+ g_simple_async_result_set_from_error (res, error);
+ goto out;
+ }
+
+ contents = g_value_get_boxed (out_value);
+
+ self->contents = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+
+ for (i = 0; i < contents->len; i++)
+ if (!add_content (self, g_ptr_array_index (contents, i)))
+ break;
+
+ g_simple_async_result_set_op_res_gboolean (res, TRUE);
+
+out:
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+}
+
+static void
+content_added (TpChannel *proxy,
+ const gchar *arg_Content,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfCallChannel *self = TF_CALL_CHANNEL (weak_object);
+
+ /* Ignore signals before we got the "Contents" property to avoid races that
+ * could cause the same content to be added twice
+ */
+
+ if (!self->contents)
+ return;
+
+ add_content (self, arg_Content);
+}
+
+static void
+content_removed (TpChannel *proxy,
+ const gchar *arg_Content,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfCallChannel *self = TF_CALL_CHANNEL (weak_object);
+ TfCallContent *content;
+
+ if (!self->contents)
+ return;
+
+ content = g_hash_table_lookup (self->contents, arg_Content);
+
+ if (content)
+ {
+ g_object_ref (content);
+ g_hash_table_remove (self->contents, arg_Content);
+
+ g_signal_emit (self, signals[SIGNAL_CONTENT_REMOVED], 0, content);
+ g_object_unref (content);
+ }
+}
+
+
+static void
+got_hardware_streaming (TpProxy *proxy, const GValue *out_value,
+ const GError *error, gpointer user_data, GObject *weak_object)
+{
+ TfCallChannel *self = TF_CALL_CHANNEL (weak_object);
+ GSimpleAsyncResult *res = user_data;
+ GError *myerror = NULL;
+
+ if (error)
+ {
+ g_warning ("Error getting the hardware streaming property: %s",
+ error->message);
+ g_simple_async_result_set_from_error (res, error);
+ goto error;
+ }
+
+ if (g_value_get_boolean (out_value))
+ {
+ g_warning ("Hardware streaming property is TRUE, ignoring");
+
+ g_simple_async_result_set_error (res, TP_ERROR, TP_ERROR_NOT_CAPABLE,
+ "This channel does hardware streaming, not handled here");
+ goto error;
+ }
+
+ tf_future_cli_channel_type_call_connect_to_content_added (TP_CHANNEL (proxy),
+ content_added, NULL, NULL, G_OBJECT (self), &myerror);
+ if (myerror)
+ {
+ g_warning ("Error connectiong to ContentAdded signal: %s",
+ myerror->message);
+ g_simple_async_result_set_from_error (res, myerror);
+ g_clear_error (&myerror);
+ goto error;
+ }
+
+ tf_future_cli_channel_type_call_connect_to_content_removed (
+ TP_CHANNEL (proxy), content_removed, NULL, NULL, G_OBJECT (self),
+ &myerror);
+ if (myerror)
+ {
+ g_warning ("Error connectiong to ContentRemoved signal: %s",
+ myerror->message);
+ g_simple_async_result_set_from_error (res, myerror);
+ g_clear_error (&myerror);
+ goto error;
+ }
+
+ tp_cli_dbus_properties_call_get (proxy, -1,
+ TF_FUTURE_IFACE_CHANNEL_TYPE_CALL, "Contents",
+ got_contents, res, NULL, G_OBJECT (self));
+
+ return;
+
+error:
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+}
+
+void
+tf_call_channel_new_async (TpChannel *channel,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TfCallChannel *self = g_object_new (TF_TYPE_CALL_CHANNEL, NULL);
+
+ self->proxy = g_object_ref (channel);
+ g_async_initable_init_async (G_ASYNC_INITABLE (self), 0, NULL, callback,
+ user_data);
+
+ /* Ownership passed to async call */
+ g_object_unref (self);
+}
+
+
+static gboolean
+find_conf_func (gpointer key, gpointer value, gpointer data)
+{
+ FsConference *conf = data;
+ struct CallConference *cc = value;
+
+ if (cc->fsconference == conf)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static struct CallConference *
+find_call_conference_by_conference (TfCallChannel *channel,
+ GstObject *conference)
+{
+ return g_hash_table_find (channel->fsconferences, find_conf_func,
+ conference);
+}
+
+gboolean
+tf_call_channel_bus_message (TfCallChannel *channel,
+ GstMessage *message)
+{
+ GError *error = NULL;
+ gchar *debug;
+ GHashTableIter iter;
+ gpointer key, value;
+ struct CallConference *cc;
+
+ cc = find_call_conference_by_conference (channel, GST_MESSAGE_SRC (message));
+ if (!cc)
+ return FALSE;
+
+ switch (GST_MESSAGE_TYPE (message))
+ {
+ case GST_MESSAGE_WARNING:
+ gst_message_parse_warning (message, &error, &debug);
+
+ g_warning ("session: %s (%s)", error->message, debug);
+
+ g_error_free (error);
+ g_free (debug);
+ return TRUE;
+ case GST_MESSAGE_ERROR:
+ gst_message_parse_error (message, &error, &debug);
+
+ g_warning ("session ERROR: %s (%s)", error->message, debug);
+
+ tf_call_channel_error (channel);
+
+ g_error_free (error);
+ g_free (debug);
+ return TRUE;
+ default:
+ break;
+ }
+
+ g_hash_table_iter_init (&iter, channel->contents);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ if (tf_call_content_bus_message (value, message))
+ return TRUE;
+
+ return FALSE;
+}
+
+void
+tf_call_channel_error (TfCallChannel *channel)
+{
+ tf_future_cli_channel_type_call_call_hangup (channel->proxy,
+ -1, TF_FUTURE_CALL_STATE_CHANGE_REASON_UNKNOWN, "", "",
+ NULL, NULL, NULL, NULL);
+}
+
+
+/* This always returns a reference, one should use _put_conference to unref it
+ */
+FsConference *
+_tf_call_channel_get_conference (TfCallChannel *channel,
+ const gchar *conference_type)
+{
+ gchar *tmp;
+ struct CallConference *cc;
+
+ cc = g_hash_table_lookup (channel->fsconferences, conference_type);
+
+ if (cc)
+ {
+ cc->use_count++;
+ gst_object_ref (cc->fsconference);
+ return cc->fsconference;
+ }
+
+ cc = g_slice_new (struct CallConference);
+ cc->use_count = 1;
+ cc->conference_type = g_strdup (conference_type);
+
+ tmp = g_strdup_printf ("fs%sconference", conference_type);
+ cc->fsconference = FS_CONFERENCE (gst_element_factory_make (tmp, NULL));
+ g_free (tmp);
+
+ if (cc->fsconference == NULL)
+ {
+ g_slice_free (struct CallConference, cc);
+ return NULL;
+ }
+
+ /* Take ownership of the conference */
+ gst_object_ref_sink (cc->fsconference);
+ g_hash_table_insert (channel->fsconferences, cc->conference_type, cc);
+
+ g_signal_emit (channel, signals[SIGNAL_FS_CONFERENCE_ADDED], 0,
+ cc->fsconference);
+ g_object_notify (G_OBJECT (channel), "fs-conferences");
+
+ gst_object_ref (cc->fsconference);
+
+ return cc->fsconference;
+}
+
+void
+_tf_call_channel_put_conference (TfCallChannel *channel,
+ FsConference *conference)
+{
+ struct CallConference *cc;
+
+ cc = find_call_conference_by_conference (channel, GST_OBJECT (conference));
+ if (!cc)
+ {
+ g_warning ("Trying to put conference that does not exist");
+ return;
+ }
+
+ cc->use_count--;
+
+ if (cc->use_count <= 0)
+ {
+ g_signal_emit (channel, signals[SIGNAL_FS_CONFERENCE_REMOVED], 0,
+ cc->fsconference);
+ g_hash_table_remove (channel->fsconferences, cc->conference_type);
+ g_object_notify (G_OBJECT (channel), "fs-conferences");
+ }
+
+ gst_object_unref (conference);
+}
+
+
+FsParticipant *
+_tf_call_channel_get_participant (TfCallChannel *channel,
+ FsConference *fsconference,
+ guint contact_handle,
+ GError **error)
+{
+ guint i;
+ struct CallParticipant *cp;
+ FsParticipant *p;
+
+ for (i = 0; i < channel->participants->len; i++)
+ {
+ cp = g_ptr_array_index (channel->participants, i);
+
+ if (cp->fsconference == fsconference &&
+ cp->handle == contact_handle)
+ {
+ cp->use_count++;
+ return g_object_ref (cp->fsparticipant);
+ }
+ }
+
+ p = fs_conference_new_participant (fsconference, error);
+ if (!p)
+ return NULL;
+
+ cp = g_slice_new (struct CallParticipant);
+ cp->use_count = 1;
+ cp->handle = contact_handle;
+ cp->fsconference = gst_object_ref (fsconference);
+ cp->fsparticipant = p;
+ g_ptr_array_add (channel->participants, cp);
+
+ return p;
+}
+
+
+void
+_tf_call_channel_put_participant (TfCallChannel *channel,
+ FsParticipant *participant)
+{
+ guint i;
+
+ for (i = 0; i < channel->participants->len; i++)
+ {
+ struct CallParticipant *cp = g_ptr_array_index (channel->participants, i);
+
+ if (cp->fsparticipant == participant)
+ {
+ cp->use_count--;
+ if (cp->use_count <= 0)
+ g_ptr_array_remove_index_fast (channel->participants, i);
+ else
+ gst_object_unref (cp->fsparticipant);
+ return;
+ }
+ }
+
+}
diff --git a/farstream/telepathy-farstream/call-channel.h b/farstream/telepathy-farstream/call-channel.h
new file mode 100644
index 000000000..38488188f
--- /dev/null
+++ b/farstream/telepathy-farstream/call-channel.h
@@ -0,0 +1,118 @@
+/*
+ * call-channel.c - Source for TfCallChannel
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __TF_CALL_CHANNEL_H__
+#define __TF_CALL_CHANNEL_H__
+
+#include <glib-object.h>
+
+#include <gst/gst.h>
+#include <farstream/fs-conference.h>
+#include <telepathy-glib/channel.h>
+
+G_BEGIN_DECLS
+
+#define TF_TYPE_CALL_CHANNEL tf_call_channel_get_type()
+
+#define TF_CALL_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TF_TYPE_CALL_CHANNEL, TfCallChannel))
+
+#define TF_CALL_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ TF_TYPE_CALL_CHANNEL, TfCallChannelClass))
+
+#define TF_IS_CALL_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_CALL_CHANNEL))
+
+#define TF_IS_CALL_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_CALL_CHANNEL))
+
+#define TF_CALL_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TF_TYPE_CALL_CHANNEL, TfCallChannelClass))
+
+typedef struct _TfCallChannelPrivate TfCallChannelPrivate;
+
+/**
+ * TfCallChannel:
+ *
+ * All members of the object are private
+ */
+
+typedef struct _TfCallChannel TfCallChannel;
+
+/**
+ * TfCallChannelClass:
+ * @parent_class: the parent #GObjecClass
+ *
+ * There are no overridable functions
+ */
+
+typedef struct _TfCallChannelClass TfCallChannelClass;
+
+
+
+struct _TfCallChannel {
+ GObject parent;
+
+ TpChannel *proxy;
+
+ GHashTable *fsconferences;
+
+ GHashTable *contents; /* NULL before getting the first contents */
+
+ GPtrArray *participants;
+};
+
+struct _TfCallChannelClass{
+ GObjectClass parent_class;
+};
+
+
+GType tf_call_channel_get_type (void);
+
+void tf_call_channel_new_async (TpChannel *channel_proxy,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+void tf_call_channel_error (TfCallChannel *channel);
+
+gboolean tf_call_channel_bus_message (TfCallChannel *channel,
+ GstMessage *message);
+
+/* Private methods, only to be used inside TP-FS */
+
+FsConference *_tf_call_channel_get_conference (TfCallChannel *channel,
+ const gchar *conference_type);
+void _tf_call_channel_put_conference (TfCallChannel *channel,
+ FsConference *conference);
+
+
+FsParticipant *_tf_call_channel_get_participant (TfCallChannel *channel,
+ FsConference *fsconference,
+ guint contact_handle,
+ GError **error);
+void _tf_call_channel_put_participant (TfCallChannel *channel,
+ FsParticipant *participant);
+
+G_END_DECLS
+
+#endif /* __TF_CALL_CHANNEL_H__ */
+
diff --git a/farstream/telepathy-farstream/call-content.c b/farstream/telepathy-farstream/call-content.c
new file mode 100644
index 000000000..36c827820
--- /dev/null
+++ b/farstream/telepathy-farstream/call-content.c
@@ -0,0 +1,1722 @@
+/*
+ * call-content.c - Source for TfCallContent
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * SECTION:call-content
+
+ * @short_description: Handle the Call interface on a Channel
+ *
+ * This class handles the
+ * org.freedesktop.Telepathy.Channel.Interface.Call on a
+ * channel using Farstream.
+ */
+
+
+#include "call-content.h"
+
+#include <telepathy-glib/util.h>
+#include <telepathy-glib/interfaces.h>
+#include <farstream/fs-conference.h>
+#include <farstream/fs-utils.h>
+#include <farstream/fs-element-added-notifier.h>
+
+#include <stdarg.h>
+#include <string.h>
+
+#include <telepathy-glib/proxy-subclass.h>
+
+#include "call-stream.h"
+#include "tf-signals-marshal.h"
+#include "utils.h"
+
+#include "extensions/extensions.h"
+
+struct _TfCallContent {
+ TfContent parent;
+
+ TfCallChannel *call_channel;
+ FsConference *fsconference;
+
+ TfFutureCallContent *proxy;
+
+ FsSession *fssession;
+ TpMediaStreamType media_type;
+
+ GList *current_codecs;
+ TpProxy *current_offer;
+ guint current_offer_contact_handle;
+ GList *current_offer_fscodecs;
+
+ GHashTable *streams; /* NULL before getting the first streams */
+ /* Streams for which we don't have a session yet*/
+ GList *outstanding_streams;
+
+ GMutex *mutex;
+
+ /* Content protected by the Mutex */
+ GPtrArray *fsstreams;
+ guint fsstreams_cookie;
+
+ gboolean got_codec_offer_property;
+
+ /* VideoControl API */
+ FsElementAddedNotifier *notifier;
+
+ volatile gint bitrate;
+ volatile gint mtu;
+ gboolean manual_keyframes;
+
+ guint framerate;
+ guint width;
+ guint height;
+};
+
+struct _TfCallContentClass{
+ TfContentClass parent_class;
+};
+
+
+static void call_content_async_initable_init (GAsyncInitableIface *asynciface);
+
+G_DEFINE_TYPE_WITH_CODE (TfCallContent, tf_call_content, TF_TYPE_CONTENT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
+ call_content_async_initable_init))
+
+#define TF_CALL_CONTENT_LOCK(self) g_mutex_lock ((self)->mutex)
+#define TF_CALL_CONTENT_UNLOCK(self) g_mutex_unlock ((self)->mutex)
+
+
+enum
+{
+ PROP_TF_CHANNEL = 1,
+ PROP_FS_CONFERENCE,
+ PROP_FS_SESSION,
+ PROP_SINK_PAD,
+ PROP_MEDIA_TYPE,
+ PROP_OBJECT_PATH,
+ PROP_FRAMERATE,
+ PROP_WIDTH,
+ PROP_HEIGHT
+};
+
+enum
+{
+ RESOLUTION_CHANGED = 0,
+ SIGNAL_COUNT
+};
+
+static guint signals[SIGNAL_COUNT] = {0};
+
+
+struct CallFsStream {
+ TfCallChannel *parent_channel;
+ guint use_count;
+ guint contact_handle;
+ FsParticipant *fsparticipant;
+ FsStream *fsstream;
+};
+
+static void
+tf_call_content_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void tf_call_content_dispose (GObject *object);
+static void tf_call_content_finalize (GObject *object);
+
+static void tf_call_content_init_async (GAsyncInitable *initable,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+static gboolean tf_call_content_init_finish (GAsyncInitable *initable,
+ GAsyncResult *res,
+ GError **error);
+
+
+static void tf_call_content_try_sending_codecs (TfCallContent *self);
+static FsStream * tf_call_content_get_existing_fsstream_by_handle (
+ TfCallContent *content, guint contact_handle);
+
+static void src_pad_added (FsStream *fsstream, GstPad *pad, FsCodec *codec,
+ TfCallContent *content);
+static GstIterator * tf_call_content_iterate_src_pads (TfContent *content,
+ guint *handles, guint handle_count);
+
+static void tf_call_content_error (TfContent *content,
+ guint reason, /* TfFutureContentRemovalReason */
+ const gchar *detailed_reason,
+ const gchar *message);
+
+static void
+tf_call_content_class_init (TfCallContentClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ TfContentClass *content_class = TF_CONTENT_CLASS (klass);
+
+ content_class->iterate_src_pads = tf_call_content_iterate_src_pads;
+
+ content_class->content_error = tf_call_content_error;
+
+ object_class->dispose = tf_call_content_dispose;
+ object_class->finalize = tf_call_content_finalize;
+ object_class->get_property = tf_call_content_get_property;
+
+ g_object_class_override_property (object_class, PROP_TF_CHANNEL,
+ "tf-channel");
+ g_object_class_override_property (object_class, PROP_FS_CONFERENCE,
+ "fs-conference");
+ g_object_class_override_property (object_class, PROP_FS_SESSION,
+ "fs-session");
+ g_object_class_override_property (object_class, PROP_SINK_PAD,
+ "sink-pad");
+ g_object_class_override_property (object_class, PROP_MEDIA_TYPE,
+ "media-type");
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+
+ g_object_class_install_property (object_class, PROP_FRAMERATE,
+ g_param_spec_uint ("framerate",
+ "Framerate",
+ "The framerate as indicated by the VideoControl interface"
+ "or the media layer",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_WIDTH,
+ g_param_spec_uint ("width",
+ "Width",
+ "The video width indicated by the VideoControl interface"
+ "or the media layer",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_HEIGHT,
+ g_param_spec_uint ("height",
+ "Height",
+ "The video height as indicated by the VideoControl interface"
+ "or the media layer",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ signals[RESOLUTION_CHANGED] = g_signal_new ("resolution-changed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ _tf_marshal_VOID__UINT_UINT,
+ G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
+}
+
+static void
+call_content_async_initable_init (GAsyncInitableIface *asynciface)
+{
+ asynciface->init_async = tf_call_content_init_async;
+ asynciface->init_finish = tf_call_content_init_finish;
+}
+
+
+static void
+free_content_fsstream (gpointer data)
+{
+ struct CallFsStream *cfs = data;
+
+ g_object_run_dispose (G_OBJECT (cfs->fsstream));
+ g_object_unref (cfs->fsstream);
+ _tf_call_channel_put_participant (cfs->parent_channel, cfs->fsparticipant);
+ g_slice_free (struct CallFsStream, cfs);
+}
+
+static void
+tf_call_content_init (TfCallContent *self)
+{
+ self->fsstreams = g_ptr_array_new ();
+
+ self->mutex = g_mutex_new ();
+}
+
+static void
+tf_call_content_dispose (GObject *object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (object);
+
+ g_debug (G_STRFUNC);
+
+ if (self->streams)
+ g_hash_table_destroy (self->streams);
+ self->streams = NULL;
+
+ if (self->fssession)
+ {
+ g_object_run_dispose (G_OBJECT (self->fssession));
+ g_object_unref (self->fssession);
+ }
+ self->fssession = NULL;
+
+ if (self->fsstreams)
+ {
+ while (self->fsstreams->len)
+ free_content_fsstream (
+ g_ptr_array_remove_index_fast (self->fsstreams, 0));
+ g_ptr_array_unref (self->fsstreams);
+ }
+ self->fsstreams = NULL;
+
+ if (self->notifier)
+ g_object_unref (self->notifier);
+ self->notifier = NULL;
+
+ if (self->fsconference)
+ _tf_call_channel_put_conference (self->call_channel,
+ self->fsconference);
+ self->fsconference = NULL;
+
+ if (self->proxy)
+ g_object_unref (self->proxy);
+ self->proxy = NULL;
+
+ /* We do not hold a ref to the call channel, and use it as a flag to ensure
+ * we will bail out when disposed */
+ self->call_channel = NULL;
+
+ if (G_OBJECT_CLASS (tf_call_content_parent_class)->dispose)
+ G_OBJECT_CLASS (tf_call_content_parent_class)->dispose (object);
+}
+
+
+static void
+tf_call_content_finalize (GObject *object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (object);
+
+ g_mutex_free (self->mutex);
+
+ if (G_OBJECT_CLASS (tf_call_content_parent_class)->finalize)
+ G_OBJECT_CLASS (tf_call_content_parent_class)->finalize (object);
+}
+
+
+static void
+tf_call_content_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TfCallContent *self = TF_CALL_CONTENT (object);
+
+ switch (property_id)
+ {
+ case PROP_TF_CHANNEL:
+ if (self->call_channel)
+ g_value_set_object (value, self->call_channel);
+ break;
+ case PROP_FS_CONFERENCE:
+ if (self->fsconference)
+ g_value_set_object (value, self->fsconference);
+ break;
+ case PROP_FS_SESSION:
+ if (self->fssession)
+ g_value_set_object (value, self->fssession);
+ break;
+ case PROP_SINK_PAD:
+ if (self->fssession)
+ g_object_get_property (G_OBJECT (self->fssession), "sink-pad", value);
+ break;
+ case PROP_MEDIA_TYPE:
+ g_value_set_enum (value, tf_call_content_get_fs_media_type (self));
+ break;
+ case PROP_OBJECT_PATH:
+ g_object_get_property (G_OBJECT (self->proxy), "object-path", value);
+ break;
+ case PROP_FRAMERATE:
+ g_value_set_uint (value, self->framerate);
+ break;
+ case PROP_WIDTH:
+ g_value_set_uint (value, self->width);
+ break;
+ case PROP_HEIGHT:
+ g_value_set_uint (value, self->height);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+create_stream (TfCallContent *self, gchar *stream_path)
+{
+ GError *error = NULL;
+ TfCallStream *stream = tf_call_stream_new (self->call_channel, self,
+ stream_path, &error);
+
+ if (error)
+ {
+ /* TODO: Use per-stream errors */
+ tf_content_error (TF_CONTENT (self),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error creating the stream object: %s", error->message);
+ return;
+ }
+
+ g_hash_table_insert (self->streams, stream_path, stream);
+}
+
+static void
+add_stream (TfCallContent *self, const gchar *stream_path)
+{
+
+ if (!self->fsconference)
+ {
+ self->outstanding_streams = g_list_prepend (self->outstanding_streams,
+ g_strdup (stream_path));
+ } else {
+ create_stream (self, g_strdup (stream_path));
+ }
+}
+
+static void
+update_streams (TfCallContent *self)
+{
+ GList *l;
+
+ g_assert (self->fsconference);
+
+ for (l = self->outstanding_streams ; l != NULL; l = l->next)
+ create_stream (self, l->data);
+
+ g_list_free (self->outstanding_streams);
+ self->outstanding_streams = NULL;
+}
+
+static void
+tpparam_to_fsparam (gpointer key, gpointer value, gpointer user_data)
+{
+ gchar *name = key;
+ gchar *val = value;
+ FsCodec *fscodec = user_data;
+
+ fs_codec_add_optional_parameter (fscodec, name, val);
+}
+
+static GList *
+tpcodecs_to_fscodecs (FsMediaType fsmediatype, const GPtrArray *tpcodecs)
+{
+ GList *fscodecs = NULL;
+ guint i;
+
+ for (i = 0; i < tpcodecs->len; i++)
+ {
+ GValueArray *tpcodec = g_ptr_array_index (tpcodecs, i);
+ guint pt;
+ gchar *name;
+ guint clock_rate;
+ guint channels;
+ GHashTable *params;
+ FsCodec *fscodec;
+
+ tp_value_array_unpack (tpcodec, 5, &pt, &name, &clock_rate, &channels,
+ &params);
+
+ fscodec = fs_codec_new (pt, name, fsmediatype, clock_rate);
+ fscodec->channels = channels;
+
+ g_hash_table_foreach (params, tpparam_to_fsparam, fscodec);
+
+ fscodecs = g_list_prepend (fscodecs, fscodec);
+ }
+
+ fscodecs = g_list_reverse (fscodecs);
+
+ return fscodecs;
+}
+
+static void
+process_codec_offer_try_codecs (TfCallContent *self, FsStream *fsstream,
+ TpProxy *offer, GList *fscodecs)
+{
+ gboolean success = TRUE;
+ GError *error = NULL;
+
+ if (fscodecs != NULL)
+ success = fs_stream_set_remote_codecs (fsstream, fscodecs, &error);
+
+ fs_codec_list_destroy (fscodecs);
+
+ if (success)
+ {
+ self->current_offer = offer;
+ tf_call_content_try_sending_codecs (self);
+ }
+ else
+ {
+ tf_future_cli_call_content_codec_offer_call_reject (offer,
+ -1, NULL, NULL, NULL, NULL);
+ g_object_unref (offer);
+ }
+
+}
+
+static void
+process_codec_offer (TfCallContent *self, const gchar *offer_objpath,
+ guint contact_handle, const GPtrArray *codecs)
+{
+ TpProxy *proxy;
+ GError *error = NULL;
+ FsStream *fsstream;
+ GList *fscodecs;
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ return;
+
+ if (!tp_dbus_check_valid_object_path (offer_objpath, &error))
+ {
+ tf_content_error (TF_CONTENT (self),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Invalid offer path: %s", error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ proxy = g_object_new (TP_TYPE_PROXY,
+ "dbus-daemon", tp_proxy_get_dbus_daemon (self->proxy),
+ "bus-name", tp_proxy_get_bus_name (self->proxy),
+ "object-path", offer_objpath,
+ NULL);
+ tp_proxy_add_interface_by_id (TP_PROXY (proxy),
+ TF_FUTURE_IFACE_QUARK_CALL_CONTENT_CODEC_OFFER);
+
+ fscodecs = tpcodecs_to_fscodecs (tp_media_type_to_fs (self->media_type),
+ codecs);
+
+ fsstream = tf_call_content_get_existing_fsstream_by_handle (self,
+ contact_handle);
+
+ if (!fsstream)
+ {
+ g_debug ("Delaying codec offer processing");
+ self->current_offer = proxy;
+ self->current_offer_contact_handle = contact_handle;
+ self->current_offer_fscodecs = fscodecs;
+ return;
+ }
+
+ process_codec_offer_try_codecs (self, fsstream, proxy, fscodecs);
+}
+
+static void
+on_content_video_keyframe_requested (TfFutureCallContent *proxy,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+ GstPad *pad;
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ return;
+
+ /* In case there is no session, ignore the request a new session should start
+ * with sending a KeyFrame in any case */
+ if (self->fssession == NULL)
+ return;
+
+ g_object_get (self->fssession, "sink-pad", &pad, NULL);
+
+ if (pad == NULL)
+ {
+ g_warning ("Failed to get a pad for the keyframe request");
+ return;
+ }
+
+ g_message ("Sending out a keyframe request");
+ gst_pad_send_event (pad,
+ gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM,
+ gst_structure_new ("GstForceKeyUnit",
+ "all-headers", G_TYPE_BOOLEAN, TRUE,
+ NULL)));
+
+ g_object_unref (pad);
+}
+
+static void
+on_content_video_resolution_changed (TfFutureCallContent *proxy,
+ const GValueArray *resolution,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+ guint width, height;
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ return;
+
+ tp_value_array_unpack ((GValueArray *)resolution, 2,
+ &width, &height, NULL);
+
+ /* Can be 0 in the initial property dump, shouldn't be at any other time */
+ if (width == 0 || height == 0)
+ return;
+
+ self->width = width;
+ self->height = height;
+
+ g_signal_emit (self, signals[RESOLUTION_CHANGED], 0, width, height);
+ g_signal_emit_by_name (self, "restart-source");
+
+ g_message ("requested video resolution: %dx%d", width, height);
+}
+
+static void
+on_content_video_bitrate_changed (TfFutureCallContent *proxy,
+ guint bitrate,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ return;
+
+ g_message ("Setting bitrate to %d bits/s", bitrate);
+ self->bitrate = bitrate;
+
+ if (self->fssession != NULL && self->bitrate > 0)
+ g_object_set (self->fssession, "send-bitrate", self->bitrate, NULL);
+}
+
+static void
+on_content_video_framerate_changed (TfFutureCallContent *proxy,
+ guint framerate,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ return;
+
+ g_message ("updated framerate requested: %d", framerate);
+
+ self->framerate = framerate;
+ g_object_notify (G_OBJECT (self), "framerate");
+ g_signal_emit_by_name (self, "restart-source");
+}
+
+static void
+on_content_video_mtu_changed (TfFutureCallContent *proxy,
+ guint mtu,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ return;
+
+ g_atomic_int_set (&self->mtu, mtu);
+
+ if (self->fsconference != NULL)
+ {
+ fs_element_added_notifier_remove (self->notifier,
+ GST_BIN (self->fsconference));
+
+ if (mtu > 0 || self->manual_keyframes)
+ fs_element_added_notifier_add (self->notifier,
+ GST_BIN (self->fsconference));
+ }
+}
+
+
+static void
+got_content_media_properties (TpProxy *proxy, GHashTable *properties,
+ const GError *error, gpointer user_data, GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+ GSimpleAsyncResult *res = user_data;
+ GValueArray *gva;
+ const gchar *offer_objpath;
+ guint contact;
+ GError *myerror = NULL;
+ GPtrArray *codecs;
+ guint32 packetization;
+ const gchar *conference_type;
+ gboolean valid;
+ GList *codec_prefs;
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ {
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Call content has been disposed of");
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+ }
+
+ if (error != NULL)
+ {
+ tf_content_error (TF_CONTENT (self),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error getting the Content's media properties: %s", error->message);
+ g_simple_async_result_set_from_error (res, error);
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+ }
+
+ packetization = tp_asv_get_uint32 (properties, "Packetization", &valid);
+
+ if (!valid)
+ goto invalid_property;
+
+ g_assert (self->fssession == NULL);
+
+ switch (packetization)
+ {
+ case TF_FUTURE_CALL_CONTENT_PACKETIZATION_TYPE_RTP:
+ conference_type = "rtp";
+ break;
+ case TF_FUTURE_CALL_CONTENT_PACKETIZATION_TYPE_RAW:
+ conference_type = "raw";
+ break;
+ default:
+ tf_content_error (TF_CONTENT (self),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_UNSUPPORTED, "",
+ "Could not create FsConference for type %d", packetization);
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Could not create FsConference for type %d", packetization);
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+ }
+
+ self->fsconference = _tf_call_channel_get_conference (self->call_channel,
+ conference_type);
+ if (!self->fsconference)
+ {
+ tf_content_error (TF_CONTENT (self),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_UNSUPPORTED, "",
+ "Could not create FsConference for type %s", conference_type);
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Error getting the Content's properties: invalid type");
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+ }
+
+ self->fssession = fs_conference_new_session (self->fsconference,
+ tp_media_type_to_fs (self->media_type), &myerror);
+
+ if (!self->fssession)
+ {
+ tf_content_error (TF_CONTENT (self),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_UNSUPPORTED, "",
+ "Could not create FsSession: %s", myerror->message);
+ g_simple_async_result_set_from_error (res, myerror);
+ g_simple_async_result_complete (res);
+ g_clear_error (&myerror);
+ g_object_unref (res);
+ return;
+ }
+
+ if (self->notifier != NULL)
+ fs_element_added_notifier_add (self->notifier,
+ GST_BIN (self->fsconference));
+
+ /* Now process outstanding streams */
+ update_streams (self);
+
+ gva = tp_asv_get_boxed (properties, "CodecOffer",
+ TF_FUTURE_STRUCT_TYPE_CODEC_OFFERING);
+ if (gva == NULL)
+ {
+ goto invalid_property;
+ }
+
+ codec_prefs = fs_utils_get_default_codec_preferences (
+ GST_ELEMENT (self->fsconference));
+
+ if (codec_prefs)
+ {
+ if (!fs_session_set_codec_preferences (self->fssession, codec_prefs,
+ &myerror))
+ {
+ g_warning ("Could not set codec preference: %s", myerror->message);
+ g_clear_error (&myerror);
+ }
+ }
+
+ /* First complete so we get signalled and the preferences can be set, then
+ * start looking at the offer. We only unref the result later, to avoid
+ * self possibly being disposed early */
+ g_simple_async_result_set_op_res_gboolean (res, TRUE);
+ g_simple_async_result_complete (res);
+
+ tp_value_array_unpack (gva, 3, &offer_objpath, &contact, &codecs);
+
+ process_codec_offer (self, offer_objpath, contact, codecs);
+
+ self->got_codec_offer_property = TRUE;
+
+ /* The async result holds a ref to self which may be the last one, so this
+ * comes after we're done with self */
+ g_object_unref (res);
+ return;
+
+ invalid_property:
+ tf_content_error_literal (TF_CONTENT (self),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error getting the Content's properties: invalid type");
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Error getting the Content's properties: invalid type");
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+}
+
+static void
+setup_content_media_properties (TfCallContent *self,
+ TpProxy *proxy,
+ GSimpleAsyncResult *res)
+{
+ tp_cli_dbus_properties_call_get_all (proxy, -1,
+ TF_FUTURE_IFACE_CALL_CONTENT_INTERFACE_MEDIA,
+ got_content_media_properties, res, NULL, G_OBJECT (self));
+}
+
+static gboolean
+object_has_property (GObject *object, const gchar *property)
+{
+ return g_object_class_find_property (G_OBJECT_GET_CLASS (object),
+ property) != NULL;
+}
+
+static void
+content_video_element_added (FsElementAddedNotifier *notifier,
+ GstBin *conference,
+ GstElement *element,
+ TfCallContent *self)
+{
+ gint mtu = g_atomic_int_get (&self->mtu);
+
+ if (G_UNLIKELY (mtu == 0 && !self->manual_keyframes))
+ return;
+
+ if (G_UNLIKELY (mtu > 0 && object_has_property (G_OBJECT (element), "mtu")))
+ {
+ g_message ("Setting %d as mtu on payloader", mtu);
+ g_object_set (element, "mtu", mtu, NULL);
+ }
+
+ if (G_UNLIKELY (self->manual_keyframes))
+ {
+ if (object_has_property (G_OBJECT (element), "key-int-max"))
+ {
+ g_message ("Setting key-int-max to max uint");
+ g_object_set (element, "key-int-max", G_MAXINT, NULL);
+ }
+
+ if (object_has_property (G_OBJECT (element), "intra-period"))
+ {
+ g_message ("Setting intra-period to 0");
+ g_object_set (element, "intra-period", 0, NULL);
+ }
+ }
+}
+
+static void
+got_content_video_control_properties (TpProxy *proxy, GHashTable *properties,
+ const GError *error, gpointer user_data, GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+ GSimpleAsyncResult *res = user_data;
+ GValueArray *array;
+ guint32 bitrate, mtu;
+ gboolean valid;
+ gboolean manual_keyframes;
+
+ if (error)
+ {
+ tf_content_error (TF_CONTENT (self),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error getting the Content's VideoControl properties: %s",
+ error->message);
+ g_simple_async_result_set_from_error (res, error);
+ goto error;
+ }
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ {
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Call content has been disposed of");
+ goto error;
+ }
+
+ if (properties == NULL)
+ {
+ tf_content_error_literal (TF_CONTENT (self),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error getting the Content's VideoControl properties: "
+ "there are none");
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Error getting the VideoControl Content's properties: "
+ "there are none");
+ goto error;
+ }
+
+
+ /* Only get the various variables, we will not have an FsSession untill the
+ * media properties are retrieved so no need to act just yet */
+ bitrate = tp_asv_get_uint32 (properties, "Bitrate", &valid);
+ if (valid)
+ self->bitrate = bitrate;
+
+ mtu = tp_asv_get_uint32 (properties, "MTU", &valid);
+ if (valid)
+ self->mtu = mtu;
+
+ manual_keyframes = tp_asv_get_boolean (properties,
+ "ManualKeyFrames", &valid);
+ if (valid)
+ self->manual_keyframes = manual_keyframes;
+
+ array = tp_asv_get_boxed (properties, "VideoResolution",
+ TF_FUTURE_STRUCT_TYPE_VIDEO_RESOLUTION);
+ if (array)
+ on_content_video_resolution_changed (TF_FUTURE_CALL_CONTENT (proxy), array,
+ NULL, G_OBJECT (self));
+
+ self->notifier = fs_element_added_notifier_new ();
+ g_signal_connect (self->notifier, "element-added",
+ G_CALLBACK (content_video_element_added), self);
+
+ setup_content_media_properties (self, proxy, res);
+ return;
+
+error:
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+}
+
+
+static void
+setup_content_video_control (TfCallContent *self,
+ TpProxy *proxy,
+ GSimpleAsyncResult *res)
+{
+ GError *error = NULL;
+
+ tp_proxy_add_interface_by_id (proxy,
+ TF_FUTURE_IFACE_QUARK_CALL_CONTENT_INTERFACE_VIDEO_CONTROL);
+
+ if (tf_future_cli_call_content_interface_video_control_connect_to_key_frame_requested (
+ TF_FUTURE_CALL_CONTENT (proxy),
+ on_content_video_keyframe_requested,
+ NULL, NULL, G_OBJECT (self), &error) == NULL)
+ goto connect_failed;
+
+ if (tf_future_cli_call_content_interface_video_control_connect_to_video_resolution_changed (
+ TF_FUTURE_CALL_CONTENT (proxy),
+ on_content_video_resolution_changed,
+ NULL, NULL, G_OBJECT (self), &error) == NULL)
+ goto connect_failed;
+
+ if (tf_future_cli_call_content_interface_video_control_connect_to_bitrate_changed (
+ TF_FUTURE_CALL_CONTENT (proxy),
+ on_content_video_bitrate_changed,
+ NULL, NULL, G_OBJECT (self), NULL) == NULL)
+ goto connect_failed;
+
+ if (tf_future_cli_call_content_interface_video_control_connect_to_framerate_changed (
+ TF_FUTURE_CALL_CONTENT (proxy),
+ on_content_video_framerate_changed,
+ NULL, NULL, G_OBJECT (self), NULL) == NULL)
+ goto connect_failed;
+
+ if (tf_future_cli_call_content_interface_video_control_connect_to_mtu_changed (
+ TF_FUTURE_CALL_CONTENT (proxy),
+ on_content_video_mtu_changed,
+ NULL, NULL, G_OBJECT (self), NULL) == NULL)
+ goto connect_failed;
+
+ tp_cli_dbus_properties_call_get_all (proxy, -1,
+ TF_FUTURE_IFACE_CALL_CONTENT_INTERFACE_VIDEO_CONTROL,
+ got_content_video_control_properties, res, NULL, G_OBJECT (self));
+
+ return;
+
+connect_failed:
+ tf_content_error (TF_CONTENT (self),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error getting the Content's VideoControl properties: %s",
+ error->message);
+ g_simple_async_result_take_error (res, error);
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+}
+
+static void
+new_codec_offer (TfFutureCallContent *proxy,
+ guint arg_Contact,
+ const gchar *arg_Offer,
+ const GPtrArray *arg_Codecs,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ return;
+
+ /* Ignore signals before we get the first codec offer property */
+ if (!self->got_codec_offer_property)
+ return;
+
+ if (self->current_offer) {
+ g_object_unref (self->current_offer);
+ fs_codec_list_destroy (self->current_offer_fscodecs);
+ self->current_offer = NULL;
+ self->current_offer_fscodecs = NULL;
+ }
+
+ process_codec_offer (self, arg_Offer, arg_Contact, arg_Codecs);
+}
+
+
+static void
+got_content_properties (TpProxy *proxy, GHashTable *out_Properties,
+ const GError *error, gpointer user_data, GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+ GSimpleAsyncResult *res = user_data;
+ gboolean valid;
+ GPtrArray *streams;
+ GError *myerror = NULL;
+ guint i;
+ const gchar * const *interfaces;
+ gboolean got_media_interface = FALSE;
+ gboolean got_video_control_interface = FALSE;
+
+ if (error)
+ {
+ tf_content_error (TF_CONTENT (self),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error getting the Content's properties: %s", error->message);
+ g_simple_async_result_set_from_error (res, error);
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+ }
+
+ /* Guard against early disposal */
+ if (self->call_channel == NULL)
+ {
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Call content has been disposed of");
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+ }
+
+ if (!out_Properties)
+ {
+ tf_content_error_literal (TF_CONTENT (self),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error getting the Content's properties: there are none");
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Error getting the Content's properties: there are none");
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+ }
+
+ interfaces = tp_asv_get_strv (out_Properties, "Interfaces");
+
+ if (interfaces == NULL)
+ {
+ tf_content_error_literal (TF_CONTENT (self),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Content does not have the Interfaces property, "
+ "but HardwareStreaming was NOT true");
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Content does not have the Interfaces property, "
+ "but HardwareStreaming was NOT true");
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+ }
+
+ for (i = 0; interfaces[i]; i++)
+ {
+ if (!strcmp (interfaces[i],
+ TF_FUTURE_IFACE_CALL_CONTENT_INTERFACE_MEDIA))
+ got_media_interface = TRUE;
+
+ if (!strcmp (interfaces[i],
+ TF_FUTURE_IFACE_CALL_CONTENT_INTERFACE_VIDEO_CONTROL))
+ got_video_control_interface = TRUE;
+ }
+
+ if (!got_media_interface)
+ {
+ tf_content_error_literal (TF_CONTENT (self),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Content does not have the media interface,"
+ " but HardwareStreaming was NOT true");
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Content does not have the media interface,"
+ " but HardwareStreaming was NOT true");
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+ }
+
+ self->media_type = tp_asv_get_uint32 (out_Properties, "Type", &valid);
+ if (!valid)
+ goto invalid_property;
+
+
+ streams = tp_asv_get_boxed (out_Properties, "Streams",
+ TP_ARRAY_TYPE_OBJECT_PATH_LIST);
+ if (!streams)
+ goto invalid_property;
+
+ self->streams = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+
+ for (i = 0; i < streams->len; i++)
+ add_stream (self, g_ptr_array_index (streams, i));
+
+ tp_proxy_add_interface_by_id (TP_PROXY (self->proxy),
+ TF_FUTURE_IFACE_QUARK_CALL_CONTENT_INTERFACE_MEDIA);
+
+
+ tf_future_cli_call_content_interface_media_connect_to_new_codec_offer (
+ TF_FUTURE_CALL_CONTENT (proxy), new_codec_offer, NULL, NULL,
+ G_OBJECT (self), &myerror);
+
+ if (myerror)
+ {
+ tf_content_error (TF_CONTENT (self),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error connectiong to NewCodecOffer signal: %s",
+ myerror->message);
+ g_simple_async_result_set_from_error (res, myerror);
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ g_clear_error (&myerror);
+ return;
+ }
+
+ if (got_video_control_interface)
+ setup_content_video_control (self, proxy, res);
+ else
+ setup_content_media_properties (self, proxy, res);
+
+ return;
+
+ invalid_property:
+ tf_content_error_literal (TF_CONTENT (self), TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR,
+ "", "Error getting the Content's properties: invalid type");
+ g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Error getting the Content's properties: invalid type");
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ return;
+}
+
+static void
+streams_added (TfFutureCallContent *proxy,
+ const GPtrArray *arg_Streams,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+ guint i;
+
+ /* Ignore signals before we got the "Contents" property to avoid races that
+ * could cause the same content to be added twice
+ */
+
+ if (!self->streams)
+ return;
+
+ for (i = 0; i < arg_Streams->len; i++)
+ add_stream (self, g_ptr_array_index (arg_Streams, i));
+}
+
+static void
+streams_removed (TfFutureCallContent *proxy,
+ const GPtrArray *arg_Streams,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfCallContent *self = TF_CALL_CONTENT (weak_object);
+ guint i;
+
+ if (!self->streams)
+ return;
+
+ for (i = 0; i < arg_Streams->len; i++)
+ g_hash_table_remove (self->streams, g_ptr_array_index (arg_Streams, i));
+}
+
+
+static void
+tf_call_content_init_async (GAsyncInitable *initable,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TfCallContent *self = TF_CALL_CONTENT (initable);
+ GError *myerror = NULL;
+ GSimpleAsyncResult *res;
+
+ if (cancellable != NULL)
+ {
+ g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data,
+ G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED,
+ "TfCallChannel initialisation does not support cancellation");
+ return;
+ }
+
+ tf_future_cli_call_content_connect_to_streams_added (
+ TF_FUTURE_CALL_CONTENT (self->proxy), streams_added, NULL, NULL,
+ G_OBJECT (self), &myerror);
+ if (myerror)
+ {
+ tf_content_error (TF_CONTENT (self),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error connectiong to StreamAdded signal: %s", myerror->message);
+ g_simple_async_report_gerror_in_idle (G_OBJECT (self), callback,
+ user_data, myerror);
+ return;
+ }
+
+ tf_future_cli_call_content_connect_to_streams_removed (
+ TF_FUTURE_CALL_CONTENT (self->proxy), streams_removed, NULL, NULL,
+ G_OBJECT (self), &myerror);
+ if (myerror)
+ {
+ tf_content_error (TF_CONTENT (self),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error connectiong to StreamRemoved signal: %s", myerror->message);
+ g_simple_async_report_gerror_in_idle (G_OBJECT (self), callback,
+ user_data, myerror);
+ return;
+ }
+
+
+ res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+ tf_call_content_init_async);
+
+ tp_cli_dbus_properties_call_get_all (self->proxy, -1,
+ TF_FUTURE_IFACE_CALL_CONTENT, got_content_properties, res,
+ NULL, G_OBJECT (self));
+}
+
+static gboolean
+tf_call_content_init_finish (GAsyncInitable *initable,
+ GAsyncResult *res,
+ GError **error)
+{
+ GSimpleAsyncResult *simple_res;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (res,
+ G_OBJECT (initable), tf_call_content_init_async), FALSE);
+ simple_res = G_SIMPLE_ASYNC_RESULT (res);
+
+ if (g_simple_async_result_propagate_error (simple_res, error))
+ return FALSE;
+
+ return g_simple_async_result_get_op_res_gboolean (simple_res);
+}
+
+TfCallContent *
+tf_call_content_new_async (TfCallChannel *call_channel,
+ const gchar *object_path, GError **error,
+ GAsyncReadyCallback callback, gpointer user_data)
+{
+ TfCallContent *self;
+ TfFutureCallContent *proxy = tf_future_call_content_new (
+ call_channel->proxy, object_path, error);
+
+ if (!proxy)
+ return NULL;
+
+ self = g_object_new (TF_TYPE_CALL_CONTENT, NULL);
+
+ self->call_channel = call_channel;
+ self->proxy = proxy;
+
+ g_async_initable_init_async (G_ASYNC_INITABLE (self), 0, NULL,
+ callback, user_data);
+
+ return g_object_ref (self);
+}
+
+
+
+static GPtrArray *
+fscodecs_to_tpcodecs (GList *codecs)
+{
+ GPtrArray *tpcodecs = g_ptr_array_new ();
+ GList *item;
+
+ for (item = codecs; item; item = item->next)
+ {
+ FsCodec *fscodec = item->data;
+ GValue tpcodec = { 0, };
+ GHashTable *params;
+ GList *param_item;
+
+ params = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ for (param_item = fscodec->optional_params;
+ param_item;
+ param_item = param_item->next)
+ {
+ FsCodecParameter *param = (FsCodecParameter *) param_item->data;
+
+ g_hash_table_insert (params, g_strdup (param->name),
+ g_strdup (param->value));
+ }
+
+ g_value_init (&tpcodec, TF_FUTURE_STRUCT_TYPE_CODEC);
+ g_value_take_boxed (&tpcodec,
+ dbus_g_type_specialized_construct (TF_FUTURE_STRUCT_TYPE_CODEC));
+
+ dbus_g_type_struct_set (&tpcodec,
+ 0, fscodec->id,
+ 1, fscodec->encoding_name,
+ 2, fscodec->clock_rate,
+ 3, fscodec->channels,
+ 4, params,
+ G_MAXUINT);
+
+
+ g_hash_table_destroy (params);
+
+ g_ptr_array_add (tpcodecs, g_value_get_boxed (&tpcodec));
+ }
+
+ return tpcodecs;
+}
+
+static void
+tf_call_content_try_sending_codecs (TfCallContent *self)
+{
+ GList *codecs;
+ GPtrArray *tpcodecs;
+ const gchar *codecs_prop = NULL;
+
+ if (self->current_offer_fscodecs != NULL)
+ {
+ g_debug ("Ignoring updated codecs unprocessed offer outstanding");
+ return;
+ }
+
+ g_debug ("updating local codecs");
+
+ if (TF_CONTENT (self)->sending_count == 0)
+ codecs_prop = "codecs-without-config";
+ else
+ codecs_prop = "codecs";
+
+ g_object_get (self->fssession, codecs_prop, &codecs, NULL);
+
+ if (!codecs)
+ return;
+
+ if (fs_codec_list_are_equal (codecs, self->current_codecs))
+ {
+ fs_codec_list_destroy (codecs);
+ return;
+ }
+
+ tpcodecs = fscodecs_to_tpcodecs (codecs);
+
+ if (self->current_offer)
+ {
+ tf_future_cli_call_content_codec_offer_call_accept (self->current_offer,
+ -1, tpcodecs, NULL, NULL, NULL, NULL);
+
+ g_object_unref (self->current_offer);
+ self->current_offer = NULL;
+ }
+ else
+ {
+ tf_future_cli_call_content_interface_media_call_update_codecs (
+ self->proxy, -1, tpcodecs, NULL, NULL, NULL, NULL);
+ }
+
+ g_boxed_free (TF_FUTURE_ARRAY_TYPE_CODEC_LIST, tpcodecs);
+}
+
+gboolean
+tf_call_content_bus_message (TfCallContent *content,
+ GstMessage *message)
+{
+ const GstStructure *s;
+ gboolean ret = FALSE;
+ const gchar *debug;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ /* Guard against early disposal */
+ if (content->call_channel == NULL)
+ return FALSE;
+
+ if (!content->fssession)
+ return FALSE;
+
+ if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT)
+ return FALSE;
+
+ s = gst_message_get_structure (message);
+
+ if (gst_structure_has_name (s, "farstream-error"))
+ {
+ GObject *object;
+ const GValue *value = NULL;
+
+ value = gst_structure_get_value (s, "src-object");
+ object = g_value_get_object (value);
+
+ if (object == (GObject*) content->fssession)
+ {
+ const gchar *msg;
+ FsError errorno;
+ GEnumClass *enumclass;
+ GEnumValue *enumvalue;
+
+ value = gst_structure_get_value (s, "error-no");
+ errorno = g_value_get_enum (value);
+ msg = gst_structure_get_string (s, "error-msg");
+ debug = gst_structure_get_string (s, "debug-msg");
+
+ enumclass = g_type_class_ref (FS_TYPE_ERROR);
+ enumvalue = g_enum_get_value (enumclass, errorno);
+ g_warning ("error (%s (%d)): %s : %s",
+ enumvalue->value_nick, errorno, msg, debug);
+ g_type_class_unref (enumclass);
+
+ tf_content_error_literal (TF_CONTENT (content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", msg);
+
+ ret = TRUE;
+ }
+ }
+ else if (gst_structure_has_name (s, "farstream-codecs-changed"))
+ {
+ FsSession *fssession;
+ const GValue *value;
+
+ value = gst_structure_get_value (s, "session");
+ fssession = g_value_get_object (value);
+
+ if (fssession != content->fssession)
+ return FALSE;
+
+ g_debug ("Codecs changed");
+
+ tf_call_content_try_sending_codecs (content);
+
+ ret = TRUE;
+ }
+
+ g_hash_table_iter_init (&iter, content->streams);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ if (tf_call_stream_bus_message (value, message))
+ return TRUE;
+
+ return ret;
+}
+
+static void
+tf_call_content_error (TfContent *content,
+ guint reason, /* TfFutureContentRemovalReason */
+ const gchar *detailed_reason,
+ const gchar *message)
+{
+ TfCallContent *self = TF_CALL_CONTENT (content);
+
+ g_warning ("%s", message);
+ tf_future_cli_call_content_call_remove (
+ self->proxy, -1, reason, detailed_reason, message, NULL, NULL,
+ NULL, NULL);
+}
+
+
+
+static FsStream *
+tf_call_content_get_existing_fsstream_by_handle (TfCallContent *content,
+ guint contact_handle)
+{
+ guint i;
+
+ TF_CALL_CONTENT_LOCK (content);
+
+ for (i = 0; i < content->fsstreams->len; i++)
+ {
+ struct CallFsStream *cfs = g_ptr_array_index (content->fsstreams, i);
+
+ if (cfs->contact_handle == contact_handle)
+ {
+ cfs->use_count++;
+ TF_CALL_CONTENT_UNLOCK (content);
+ return cfs->fsstream;
+ }
+ }
+
+ TF_CALL_CONTENT_UNLOCK (content);
+
+ return NULL;
+}
+
+
+FsStream *
+_tf_call_content_get_fsstream_by_handle (TfCallContent *content,
+ guint contact_handle,
+ const gchar *transmitter,
+ guint stream_transmitter_n_parameters,
+ GParameter *stream_transmitter_parameters,
+ GError **error)
+{
+ struct CallFsStream *cfs;
+ FsParticipant *p;
+ FsStream *s;
+
+ s = tf_call_content_get_existing_fsstream_by_handle (content,
+ contact_handle);
+ if (s)
+ return s;
+
+ p = _tf_call_channel_get_participant (content->call_channel,
+ content->fsconference, contact_handle, error);
+ if (!p)
+ return NULL;
+
+ s = fs_session_new_stream (content->fssession, p, FS_DIRECTION_RECV, error);
+ if (!s)
+ {
+ _tf_call_channel_put_participant (content->call_channel, p);
+ return NULL;
+ }
+
+ if (!fs_stream_set_transmitter (s, transmitter,
+ stream_transmitter_parameters, stream_transmitter_n_parameters,
+ error))
+ {
+ g_object_unref (s);
+ _tf_call_channel_put_participant (content->call_channel, p);
+ return NULL;
+ }
+
+ cfs = g_slice_new (struct CallFsStream);
+ cfs->use_count = 1;
+ cfs->contact_handle = contact_handle;
+ cfs->parent_channel = content->call_channel;
+ cfs->fsparticipant = p;
+ cfs->fsstream = s;
+
+ tp_g_signal_connect_object (s, "src-pad-added",
+ G_CALLBACK (src_pad_added), content, 0);
+
+ g_ptr_array_add (content->fsstreams, cfs);
+ content->fsstreams_cookie ++;
+ if (content->current_offer != NULL
+ && content->current_offer_contact_handle == contact_handle)
+ {
+ GList *codecs = content->current_offer_fscodecs;
+ TpProxy *current_offer = content->current_offer;
+ content->current_offer_fscodecs = NULL;
+ content->current_offer = NULL;
+
+ /* ownership transfers to try_codecs */
+ process_codec_offer_try_codecs (content, s,
+ current_offer, codecs);
+ }
+
+ return s;
+}
+
+void
+_tf_call_content_put_fsstream (TfCallContent *content, FsStream *fsstream)
+{
+ guint i;
+ struct CallFsStream *fs_cfs = NULL;
+
+ TF_CALL_CONTENT_LOCK (content);
+ for (i = 0; i < content->fsstreams->len; i++)
+ {
+ struct CallFsStream *cfs = g_ptr_array_index (content->fsstreams, i);
+
+ if (cfs->fsstream == fsstream)
+ {
+ cfs->use_count--;
+ if (cfs->use_count <= 0)
+ {
+ fs_cfs = g_ptr_array_remove_index_fast (content->fsstreams, i);
+ content->fsstreams_cookie++;
+ }
+ break;
+ }
+ }
+ TF_CALL_CONTENT_UNLOCK (content);
+
+ if (fs_cfs)
+ free_content_fsstream (fs_cfs);
+}
+
+FsMediaType
+tf_call_content_get_fs_media_type (TfCallContent *content)
+{
+ return tp_media_type_to_fs (content->media_type);
+}
+
+static void
+src_pad_added (FsStream *fsstream, GstPad *pad, FsCodec *codec,
+ TfCallContent *content)
+{
+ guint handle = 0;
+ guint i;
+
+ TF_CALL_CONTENT_LOCK (content);
+
+ if (!content->fsstreams)
+ {
+ TF_CALL_CONTENT_UNLOCK (content);
+ return;
+ }
+
+ for (i = 0; i < content->fsstreams->len; i++)
+ {
+ struct CallFsStream *cfs = g_ptr_array_index (content->fsstreams, i);
+ if (cfs->fsstream == fsstream)
+ {
+ handle = cfs->contact_handle;
+ break;
+ }
+ }
+
+ TF_CALL_CONTENT_UNLOCK (content);
+
+ _tf_content_emit_src_pad_added (TF_CONTENT (content), handle,
+ fsstream, pad, codec);
+}
+
+struct StreamSrcPadIterator {
+ GstIterator iterator;
+
+ GArray *handles;
+ GArray *handles_backup;
+
+ TfCallContent *self;
+};
+
+static GstIteratorResult
+streams_src_pads_iter_next (GstIterator *it, gpointer *result)
+{
+ struct StreamSrcPadIterator *iter = (struct StreamSrcPadIterator *) it;
+ guint i;
+
+ if (iter->handles->len == 0)
+ return GST_ITERATOR_DONE;
+
+ for (i = 0; i < iter->self->fsstreams->len; i++)
+ {
+ struct CallFsStream *cfs = g_ptr_array_index (iter->self->fsstreams, i);
+
+ if (cfs->contact_handle == g_array_index (iter->handles, guint, 0))
+ {
+ g_array_remove_index_fast (iter->handles, 0);
+ *result = cfs;
+ return GST_ITERATOR_OK;
+ }
+ }
+
+ return GST_ITERATOR_ERROR;
+
+}
+
+static GstIteratorItem
+streams_src_pads_iter_item (GstIterator *it, gpointer item)
+{
+ struct CallFsStream *cfs = item;
+
+ gst_iterator_push (it, fs_stream_iterate_src_pads (cfs->fsstream));
+
+ return GST_ITERATOR_ITEM_SKIP;
+}
+
+static void
+streams_src_pads_iter_resync (GstIterator *it)
+{
+ struct StreamSrcPadIterator *iter = (struct StreamSrcPadIterator *) it;
+
+ g_array_set_size (iter->handles, iter->handles_backup->len);
+ memcpy (iter->handles->data, iter->handles_backup->data,
+ iter->handles_backup->len * sizeof(guint));
+}
+
+static void
+streams_src_pads_iter_free (GstIterator *it)
+{
+ struct StreamSrcPadIterator *iter = (struct StreamSrcPadIterator *) it;
+
+ g_array_unref (iter->handles);
+ g_array_unref (iter->handles_backup);
+ g_object_unref (iter->self);
+}
+
+static GstIterator *
+tf_call_content_iterate_src_pads (TfContent *content, guint *handles,
+ guint handle_count)
+{
+ TfCallContent *self = TF_CALL_CONTENT (content);
+ struct StreamSrcPadIterator *iter;
+
+ iter = (struct StreamSrcPadIterator *) gst_iterator_new (
+ sizeof (struct StreamSrcPadIterator), GST_TYPE_PAD,
+ self->mutex, &self->fsstreams_cookie,
+ streams_src_pads_iter_next,
+ streams_src_pads_iter_item,
+ streams_src_pads_iter_resync,
+ streams_src_pads_iter_free);
+
+ iter->handles = g_array_sized_new (TRUE, FALSE, sizeof(guint), handle_count);
+ iter->handles_backup = g_array_sized_new (TRUE, FALSE, sizeof(guint),
+ handle_count);
+ g_array_append_vals (iter->handles, handles, handle_count);
+ g_array_append_vals (iter->handles_backup, handles, handle_count);
+ iter->self = g_object_ref (self);
+
+ return (GstIterator *) iter;
+}
diff --git a/farstream/telepathy-farstream/call-content.h b/farstream/telepathy-farstream/call-content.h
new file mode 100644
index 000000000..697148e97
--- /dev/null
+++ b/farstream/telepathy-farstream/call-content.h
@@ -0,0 +1,106 @@
+/*
+ * call-content.h - Source for TfCallContent
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __TF_CALL_CONTENT_H__
+#define __TF_CALL_CONTENT_H__
+
+#include <glib-object.h>
+
+#include <gst/gst.h>
+#include <telepathy-glib/channel.h>
+
+#include "extensions/extensions.h"
+#include "call-channel.h"
+#include "content.h"
+#include "content-priv.h"
+
+G_BEGIN_DECLS
+
+#define TF_TYPE_CALL_CONTENT tf_call_content_get_type()
+
+#define TF_CALL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TF_TYPE_CALL_CONTENT, TfCallContent))
+
+#define TF_CALL_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ TF_TYPE_CALL_CONTENT, TfCallContentClass))
+
+#define TF_IS_CALL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_CALL_CONTENT))
+
+#define TF_IS_CALL_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_CALL_CONTENT))
+
+#define TF_CALL_CONTENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TF_TYPE_CALL_CONTENT, TfCallContentClass))
+
+typedef struct _TfCallContentPrivate TfCallContentPrivate;
+
+/**
+ * TfCallContent:
+ *
+ * All members of the object are private
+ */
+
+typedef struct _TfCallContent TfCallContent;
+
+/**
+ * TfCallContentClass:
+ * @parent_class: the parent #GObjecClass
+ *
+ * There are no overridable functions
+ */
+
+typedef struct _TfCallContentClass TfCallContentClass;
+
+GType tf_call_content_get_type (void);
+
+TfCallContent *tf_call_content_new_async (
+ TfCallChannel *call_channel,
+ const gchar *object_path,
+ GError **error,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean tf_call_content_bus_message (TfCallContent *content,
+ GstMessage *message);
+
+
+/* Private */
+FsStream *_tf_call_content_get_fsstream_by_handle (TfCallContent *content,
+ guint contact_handle,
+ const gchar *transmitter,
+ guint stream_transmitter_n_parameters,
+ GParameter *stream_transmitter_parameters,
+ GError **error);
+void _tf_call_content_put_fsstream (TfCallContent *content, FsStream *fsstream);
+
+FsMediaType
+tf_call_content_get_fs_media_type (TfCallContent *content);
+
+
+gboolean
+tf_call_content_bus_message (TfCallContent *content, GstMessage *message);
+
+G_END_DECLS
+
+#endif /* __TF_CALL_CONTENT_H__ */
+
diff --git a/farstream/telepathy-farstream/call-stream.c b/farstream/telepathy-farstream/call-stream.c
new file mode 100644
index 000000000..7658c9b51
--- /dev/null
+++ b/farstream/telepathy-farstream/call-stream.c
@@ -0,0 +1,1140 @@
+/*
+ * call-stream.c - Source for TfCallStream
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * SECTION:tfcallstream
+ *
+ * @short_description: Handle the Stream interface on a Channel
+ *
+ * This class handles the
+ * org.freedesktop.Telepathy.Channel.Interface.Stream on a
+ * channel using Farstream.
+ */
+
+#include "call-stream.h"
+
+#include <telepathy-glib/util.h>
+#include <telepathy-glib/interfaces.h>
+#include <farstream/fs-conference.h>
+
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <telepathy-glib/proxy-subclass.h>
+
+#include "extensions/extensions.h"
+
+#include "tf-signals-marshal.h"
+#include "utils.h"
+
+
+G_DEFINE_TYPE (TfCallStream, tf_call_stream, G_TYPE_OBJECT);
+
+static void tf_call_stream_dispose (GObject *object);
+
+static void
+tf_call_stream_class_init (TfCallStreamClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = tf_call_stream_dispose;
+}
+
+static void
+tf_call_stream_init (TfCallStream *self)
+{
+}
+
+static void
+tf_call_stream_dispose (GObject *object)
+{
+ TfCallStream *self = TF_CALL_STREAM (object);
+
+ g_debug (G_STRFUNC);
+
+ if (self->proxy)
+ g_object_unref (self->proxy);
+ self->proxy = NULL;
+
+ if (self->stun_servers)
+ g_boxed_free (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST, self->stun_servers);
+ self->stun_servers = NULL;
+
+ if (self->relay_info)
+ g_boxed_free (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, self->relay_info);
+ self->relay_info = NULL;
+
+ if (self->fsstream)
+ _tf_call_content_put_fsstream (self->call_content, self->fsstream);
+ self->fsstream = NULL;
+
+ if (self->endpoint)
+ g_object_unref (self->endpoint);
+ self->endpoint = NULL;
+
+ g_free (self->creds_username);
+ self->creds_username = NULL;
+ g_free (self->creds_password);
+ self->creds_password = NULL;
+
+ fs_candidate_list_destroy (self->stored_remote_candidates);
+ self->stored_remote_candidates = NULL;
+
+ if (G_OBJECT_CLASS (tf_call_stream_parent_class)->dispose)
+ G_OBJECT_CLASS (tf_call_stream_parent_class)->dispose (object);
+}
+
+static void
+local_sending_state_changed (TfFutureCallStream *proxy,
+ guint arg_State,
+ gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+
+ self->local_sending_state = arg_State;
+
+ if (!self->fsstream)
+ return;
+
+ if (arg_State == TF_FUTURE_SENDING_STATE_SENDING)
+ {
+ if (!self->has_send_resource)
+ {
+ if (_tf_content_start_sending (TF_CONTENT (self->call_content)))
+ {
+ self->has_send_resource = TRUE;
+ }
+ else
+ {
+ /* FIXME add a way to call to report media errors */
+ g_warning ("Sending failed");
+ return;
+ }
+ }
+ }
+
+ switch (arg_State)
+ {
+ case TF_FUTURE_SENDING_STATE_PENDING_STOP_SENDING:
+ case TF_FUTURE_SENDING_STATE_PENDING_SEND:
+ /* the UI should respond to these */
+ break;
+ case TF_FUTURE_SENDING_STATE_SENDING:
+ g_object_set (self->fsstream, "direction", FS_DIRECTION_BOTH, NULL);
+ break;
+ case TF_FUTURE_SENDING_STATE_NONE:
+ if (self->has_send_resource)
+ {
+ _tf_content_stop_sending (TF_CONTENT (self->call_content));
+
+ self->has_send_resource = FALSE;
+ }
+ g_object_set (self->fsstream, "direction", FS_DIRECTION_RECV, NULL);
+ break;
+ }
+}
+
+
+static void
+tf_call_stream_try_adding_fsstream (TfCallStream *self)
+{
+ gchar *transmitter;
+ GError *error = NULL;
+ guint n_params = 0;
+ GParameter params[6] = { {NULL,} };
+ GList *preferred_local_candidates = NULL;
+ guint i;
+
+ memset (params, 0, sizeof(params));
+
+ if (!self->server_info_retrieved ||
+ !self->has_contact ||
+ !self->has_media_properties)
+ return;
+
+ switch (self->transport_type)
+ {
+ case TF_FUTURE_STREAM_TRANSPORT_TYPE_RAW_UDP:
+ transmitter = "rawudp";
+
+ switch (tf_call_content_get_fs_media_type (self->call_content))
+ {
+ case TP_MEDIA_STREAM_TYPE_VIDEO:
+ preferred_local_candidates = g_list_prepend (NULL,
+ fs_candidate_new (NULL, FS_COMPONENT_RTP, FS_CANDIDATE_TYPE_HOST,
+ FS_NETWORK_PROTOCOL_UDP, NULL, 9078));
+ break;
+ case TP_MEDIA_STREAM_TYPE_AUDIO:
+ preferred_local_candidates = g_list_prepend (NULL,
+ fs_candidate_new (NULL, FS_COMPONENT_RTP, FS_CANDIDATE_TYPE_HOST,
+ FS_NETWORK_PROTOCOL_UDP, NULL, 7078));
+ default:
+ break;
+ }
+
+ if (preferred_local_candidates)
+ {
+ params[n_params].name = "preferred-local-candidates";
+ g_value_init (&params[n_params].value, FS_TYPE_CANDIDATE_LIST);
+ g_value_take_boxed (&params[n_params].value,
+ preferred_local_candidates);
+ n_params++;
+ }
+ break;
+ case TF_FUTURE_STREAM_TRANSPORT_TYPE_ICE:
+ case TF_FUTURE_STREAM_TRANSPORT_TYPE_GTALK_P2P:
+ case TF_FUTURE_STREAM_TRANSPORT_TYPE_WLM_2009:
+ transmitter = "nice";
+
+ /* MISSING: Set Controlling mode property here */
+
+ params[n_params].name = "compatibility-mode";
+ g_value_init (&params[n_params].value, G_TYPE_UINT);
+ switch (self->transport_type)
+ {
+ case TF_FUTURE_STREAM_TRANSPORT_TYPE_ICE:
+ g_value_set_uint (&params[n_params].value, 0);
+ break;
+ case TF_FUTURE_STREAM_TRANSPORT_TYPE_GTALK_P2P:
+ g_value_set_uint (&params[n_params].value, 1);
+ self->multiple_usernames = TRUE;
+ break;
+ case TF_FUTURE_STREAM_TRANSPORT_TYPE_WLM_2009:
+ g_value_set_uint (&params[n_params].value, 3);
+ break;
+ default:
+ break;
+ }
+ n_params++;
+ break;
+ case TF_FUTURE_STREAM_TRANSPORT_TYPE_SHM:
+ transmitter = "shm";
+ params[n_params].name = "create-local-candidates";
+ g_value_init (&params[n_params].value, G_TYPE_BOOLEAN);
+ g_value_set_boolean (&params[n_params].value, TRUE);
+ n_params++;
+ break;
+ default:
+ tf_content_error (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR,
+ "org.freedesktop.Telepathy.Error.NotImplemented",
+ "Unknown transport type %d", self->transport_type);
+ return;
+ }
+
+ if (self->stun_servers->len)
+ {
+ GValueArray *gva = g_ptr_array_index (self->stun_servers, 0);
+ gchar *ip;
+ guint port;
+ gchar *conn_timeout_str;
+
+ /* We only use the first STUN server if there are many */
+
+ tp_value_array_unpack (gva, 2, &ip, &port);
+
+ params[n_params].name = "stun-ip";
+ g_value_init (&params[n_params].value, G_TYPE_STRING);
+ g_value_set_string (&params[n_params].value, ip);
+ n_params++;
+
+ params[n_params].name = "stun-port";
+ g_value_init (&params[n_params].value, G_TYPE_UINT);
+ g_value_set_uint (&params[n_params].value, port);
+ n_params++;
+
+ conn_timeout_str = getenv ("FS_CONN_TIMEOUT");
+ if (conn_timeout_str)
+ {
+ gint conn_timeout = strtol (conn_timeout_str, NULL, 10);
+
+ params[n_params].name = "stun-timeout";
+ g_value_init (&params[n_params].value, G_TYPE_UINT);
+ g_value_set_uint (&params[n_params].value, conn_timeout);
+ n_params++;
+ }
+ }
+
+ if (self->relay_info->len)
+ {
+ GValueArray *fs_relay_info = g_value_array_new (0);
+ GValue val = {0};
+ g_value_init (&val, GST_TYPE_STRUCTURE);
+
+ for (i = 0; i < self->relay_info->len; i++)
+ {
+ GHashTable *one_relay = g_ptr_array_index(self->relay_info, i);
+ const gchar *type = NULL;
+ const gchar *ip;
+ guint32 port;
+ const gchar *username;
+ const gchar *password;
+ guint component;
+ GstStructure *s;
+
+ ip = tp_asv_get_string (one_relay, "ip");
+ port = tp_asv_get_uint32 (one_relay, "port", NULL);
+ type = tp_asv_get_string (one_relay, "type");
+ username = tp_asv_get_string (one_relay, "username");
+ password = tp_asv_get_string (one_relay, "password");
+ component = tp_asv_get_uint32 (one_relay, "component", NULL);
+
+ if (!ip || !port || !username || !password)
+ continue;
+
+ if (!type)
+ type = "udp";
+
+ s = gst_structure_new ("relay-info",
+ "ip", G_TYPE_STRING, ip,
+ "port", G_TYPE_UINT, port,
+ "username", G_TYPE_STRING, username,
+ "password", G_TYPE_STRING, password,
+ "type", G_TYPE_STRING, type,
+ NULL);
+
+ if (component)
+ gst_structure_set (s, "component", G_TYPE_UINT, component, NULL);
+
+
+ g_value_take_boxed (&val, s);
+
+ g_value_array_append (fs_relay_info, &val);
+ g_value_reset (&val);
+ }
+
+ if (fs_relay_info->n_values)
+ {
+ params[n_params].name = "relay-info";
+ g_value_init (&params[n_params].value, G_TYPE_VALUE_ARRAY);
+ g_value_set_boxed (&params[n_params].value, fs_relay_info);
+ n_params++;
+ }
+
+ g_value_array_free (fs_relay_info);
+ }
+
+ self->fsstream = _tf_call_content_get_fsstream_by_handle (self->call_content,
+ self->contact_handle,
+ transmitter,
+ n_params,
+ params,
+ &error);
+
+ for (i = 0; i < n_params; i++)
+ g_value_unset (&params[i].value);
+
+ if (!self->fsstream)
+ {
+ tf_content_error (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR,
+ "",
+ "Could not create FsStream: %s", error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ if (self->local_sending_state == TF_FUTURE_SENDING_STATE_PENDING_SEND ||
+ self->local_sending_state == TF_FUTURE_SENDING_STATE_SENDING)
+ local_sending_state_changed (self->proxy, self->local_sending_state,
+ NULL, (GObject *) self);
+}
+
+static void
+server_info_retrieved (TfFutureCallStream *proxy,
+ gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+
+ self->server_info_retrieved = TRUE;
+
+ tf_call_stream_try_adding_fsstream (self);
+}
+
+static void
+relay_info_changed (TfFutureCallStream *proxy,
+ const GPtrArray *arg_Relay_Info,
+ gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+
+ if (self->server_info_retrieved)
+ {
+ tf_content_error_literal (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR,
+ "org.freedesktop.Telepathy.Error.NotImplemented",
+ "Changing relay servers after ServerInfoRetrived is not implemented");
+ return;
+ }
+
+ /* Ignore signals that come before the basic info has been retrived */
+ if (!self->relay_info)
+ return;
+
+ g_boxed_free (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, self->relay_info);
+ self->relay_info = g_boxed_copy (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST,
+ arg_Relay_Info);
+}
+
+static void
+stun_servers_changed (TfFutureCallStream *proxy,
+ const GPtrArray *arg_Servers,
+ gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+
+ if (self->server_info_retrieved)
+ {
+ tf_content_error_literal (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR,
+ "org.freedesktop.Telepathy.Error.NotImplemented",
+ "Changing STUN servers after ServerInfoRetrived is not implemented");
+ return;
+ }
+
+ /* Ignore signals that come before the basic info has been retrived */
+ if (!self->stun_servers)
+ return;
+
+ g_boxed_free (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST, self->stun_servers);
+ self->stun_servers = g_boxed_copy (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST,
+ arg_Servers);
+}
+
+static void
+tf_call_stream_add_remote_candidates (TfCallStream *self,
+ const GPtrArray *candidates)
+{
+ GList *fscandidates = NULL;
+ guint i;
+ GError *error = NULL;
+
+ /* No candidates to add, ignore. This could either be caused by the CM
+ * accidentally emitting an empty RemoteCandidatesAdded or when there are no
+ * remote candidates on the endpoint yet when we query it */
+ if (candidates->len == 0)
+ return;
+
+ for (i = 0; i < candidates->len; i++)
+ {
+ GValueArray *tpcandidate = g_ptr_array_index (candidates, i);
+ guint component;
+ gchar *ip;
+ guint port;
+ GHashTable *extra_info;
+ const gchar *foundation;
+ guint priority;
+ const gchar *username;
+ const gchar *password;
+ gboolean valid;
+ FsCandidate *cand;
+
+ tp_value_array_unpack (tpcandidate, 4, &component, &ip, &port,
+ &extra_info);
+
+ foundation = tp_asv_get_string (extra_info, "Foundation");
+ if (!foundation)
+ foundation = "";
+ priority = tp_asv_get_uint32 (extra_info, "Priority", &valid);
+ if (!valid)
+ priority = 0;
+
+ username = tp_asv_get_string (extra_info, "Username");
+ if (!username)
+ username = self->creds_username;
+
+ password = tp_asv_get_string (extra_info, "Password");
+ if (!password)
+ password = self->creds_password;
+
+ cand = fs_candidate_new (foundation, component, FS_CANDIDATE_TYPE_HOST,
+ FS_NETWORK_PROTOCOL_UDP, ip, port);
+ cand->priority = priority;
+ cand->username = g_strdup (username);
+ cand->password = g_strdup (password);
+
+ fscandidates = g_list_append (fscandidates, cand);
+ }
+
+ if (self->fsstream)
+ {
+ if (!fs_stream_add_remote_candidates (self->fsstream, fscandidates,
+ &error))
+ {
+ tf_content_error (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error setting the remote candidates: %s", error->message);
+ g_clear_error (&error);
+ }
+ fs_candidate_list_destroy (fscandidates);
+ }
+ else
+ {
+ self->stored_remote_candidates =
+ g_list_concat (self->stored_remote_candidates, fscandidates);
+ }
+}
+
+static void
+remote_candidates_added (TpProxy *proxy,
+ const GPtrArray *arg_Candidates,
+ gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+
+ tf_call_stream_add_remote_candidates (self, arg_Candidates);
+}
+
+static void
+remote_credentials_set (TpProxy *proxy,
+ const gchar *arg_Username,
+ const gchar *arg_Password,
+ gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+
+ if ((self->creds_username && strcmp (self->creds_username, arg_Username)) ||
+ (self->creds_password && strcmp (self->creds_password, arg_Password)))
+ {
+ /* Remote credentials changed, this will perform a ICE restart, so
+ * clear old remote candidates */
+ fs_candidate_list_destroy (self->stored_remote_candidates);
+ self->stored_remote_candidates = NULL;
+ }
+
+ g_free (self->creds_username);
+ g_free (self->creds_password);
+ self->creds_username = g_strdup (arg_Username);
+ self->creds_password = g_strdup (arg_Password);
+}
+
+
+static void
+got_endpoint_properties (TpProxy *proxy, GHashTable *out_Properties,
+ const GError *error, gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+ GValueArray *credentials;
+ gchar *username, *password;
+ GPtrArray *candidates;
+
+ if (error)
+ {
+ tf_content_error (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error getting the Streams's media properties: %s", error->message);
+ return;
+ }
+
+ if (!out_Properties)
+ {
+ tf_content_error_literal (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error getting the Stream's media properties: there are none");
+ return;
+ }
+
+
+ credentials = tp_asv_get_boxed (out_Properties, "RemoteCredentials",
+ TF_FUTURE_STRUCT_TYPE_STREAM_CREDENTIALS);
+ if (!credentials)
+ goto invalid_property;
+ tp_value_array_unpack (credentials, 2, &username, &password);
+ self->creds_username = g_strdup (username);
+ self->creds_password = g_strdup (password);
+
+ candidates = tp_asv_get_boxed (out_Properties, "RemoteCandidates",
+ TF_FUTURE_ARRAY_TYPE_CANDIDATE_LIST);
+ if (!candidates)
+ goto invalid_property;
+
+ tf_call_stream_add_remote_candidates (self, candidates);
+
+
+ return;
+
+ invalid_property:
+ tf_content_error_literal (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error getting the Endpoint's properties: invalid type");
+}
+
+static void
+tf_call_stream_add_endpoint (TfCallStream *self)
+{
+ GError *error = NULL;
+
+ self->endpoint = g_object_new (TP_TYPE_PROXY,
+ "dbus-daemon", tp_proxy_get_dbus_daemon (self->proxy),
+ "bus-name", tp_proxy_get_bus_name (self->proxy),
+ "object-path", self->endpoint_objpath,
+ NULL);
+ tp_proxy_add_interface_by_id (TP_PROXY (self->endpoint),
+ TF_FUTURE_IFACE_QUARK_CALL_STREAM_ENDPOINT);
+
+ tf_future_cli_call_stream_endpoint_connect_to_remote_credentials_set (
+ TP_PROXY (self->endpoint), remote_credentials_set, NULL, NULL,
+ G_OBJECT (self), &error);
+ if (error)
+ {
+ tf_content_error (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error connectiong to RemoteCredentialsSet signal: %s",
+ error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ tf_future_cli_call_stream_endpoint_connect_to_remote_candidates_added (
+ TP_PROXY (self->endpoint), remote_candidates_added, NULL, NULL,
+ G_OBJECT (self), &error);
+ if (error)
+ {
+ tf_content_error (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error connectiong to RemoteCandidatesAdded signal: %s",
+ error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ tp_cli_dbus_properties_call_get_all (self->endpoint, -1,
+ TF_FUTURE_IFACE_CALL_STREAM_ENDPOINT,
+ got_endpoint_properties, NULL, NULL, G_OBJECT (self));
+}
+
+
+static void
+endpoints_changed (TfFutureCallStream *proxy,
+ const GPtrArray *arg_Endpoints_Added,
+ const GPtrArray *arg_Endpoints_Removed,
+ gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+
+ /* Ignore signals before getting the properties to avoid races */
+ if (!self->has_media_properties)
+ return;
+
+ if (arg_Endpoints_Removed->len != 0)
+ {
+ tf_content_error_literal (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR,
+ "org.freedesktop.Telepathy.Error.NotImplemented",
+ "Removing Endpoints is not implemented");
+ return;
+ }
+
+ if (arg_Endpoints_Added->len != 1)
+ {
+ tf_content_error_literal (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR,
+ "org.freedesktop.Telepathy.Error.NotImplemented",
+ "Having more than one endpoint is not implemented");
+ return;
+ }
+
+ if (self->endpoint_objpath)
+ {
+ if (strcmp (g_ptr_array_index (arg_Endpoints_Added, 0),
+ self->endpoint_objpath))
+ tf_content_error_literal (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Trying to give a different endpoint, CM bug");
+ return;
+ }
+
+ self->endpoint_objpath = g_strdup (
+ g_ptr_array_index (arg_Endpoints_Added, 0));
+ tf_call_stream_add_endpoint (self);
+}
+
+
+static void
+got_stream_media_properties (TpProxy *proxy, GHashTable *out_Properties,
+ const GError *error, gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+ GPtrArray *stun_servers;
+ GPtrArray *relay_info;
+ GPtrArray *endpoints;
+ gboolean valid;
+
+ if (error)
+ {
+ tf_content_error (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error getting the Streams's media properties: %s",
+ error->message);
+ return;
+ }
+
+ if (!out_Properties)
+ {
+ tf_content_error_literal (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error getting the Stream's media properties: there are none");
+ return;
+ }
+
+ self->transport_type =
+ tp_asv_get_uint32 (out_Properties, "Transport", &valid);
+ if (!valid)
+ {
+ g_warning ("No valid transport");
+ goto invalid_property;
+ }
+
+ stun_servers = tp_asv_get_boxed (out_Properties, "STUNServers",
+ TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST);
+ if (!stun_servers)
+ {
+ g_warning ("No valid STUN servers");
+ goto invalid_property;
+ }
+
+ relay_info = tp_asv_get_boxed (out_Properties, "RelayInfo",
+ TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST);
+ if (!relay_info)
+ {
+ g_warning ("No valid RelayInfo");
+ goto invalid_property;
+ }
+
+ self->server_info_retrieved = tp_asv_get_boolean (out_Properties,
+ "HasServerInfo", &valid);
+ if (!valid)
+ {
+ g_warning ("No valid server info");
+ goto invalid_property;
+ }
+
+
+ self->stun_servers = g_boxed_copy (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST,
+ stun_servers);
+ self->relay_info = g_boxed_copy (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST,
+ relay_info);
+
+ endpoints = tp_asv_get_boxed (out_Properties, "Endpoints",
+ TP_ARRAY_TYPE_OBJECT_PATH_LIST);
+
+ if (endpoints->len > 1)
+ {
+ tf_content_error_literal (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR,
+ "org.freedesktop.Telepathy.Error.NotImplemented",
+ "Having more than one endpoint is not implemented");
+ return;
+ }
+
+ if (endpoints->len == 1)
+ {
+ self->endpoint_objpath = g_strdup (g_ptr_array_index (endpoints, 0));
+ tf_call_stream_add_endpoint (self);
+ }
+
+ self->has_media_properties = TRUE;
+
+ tf_call_stream_try_adding_fsstream (self);
+
+ return;
+ invalid_property:
+ tf_content_error_literal (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error getting the Stream's properties: invalid type");
+ return;
+}
+
+
+static void
+got_stream_properties (TpProxy *proxy, GHashTable *out_Properties,
+ const GError *error, gpointer user_data, GObject *weak_object)
+{
+ TfCallStream *self = TF_CALL_STREAM (weak_object);
+ gboolean valid;
+ GError *myerror = NULL;
+ guint i;
+ const gchar * const * interfaces;
+ gboolean got_media_interface = FALSE;
+ guint32 local_sending_state;
+ GHashTable *members;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ if (error)
+ {
+ tf_content_error (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error getting the Streams's properties: %s", error->message);
+ return;
+ }
+
+ if (!out_Properties)
+ {
+ tf_content_error_literal (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error getting the Content's properties: there are none");
+ return;
+ }
+
+ interfaces = tp_asv_get_strv (out_Properties, "Interfaces");
+
+ for (i = 0; interfaces[i]; i++)
+ if (!strcmp (interfaces[i], TF_FUTURE_IFACE_CALL_STREAM_INTERFACE_MEDIA))
+ {
+ got_media_interface = TRUE;
+ break;
+ }
+
+ if (!got_media_interface)
+ {
+ tf_content_error_literal (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Stream does not have the media interface,"
+ " but HardwareStreaming was NOT true");
+ return;
+ }
+
+ members = tp_asv_get_boxed (out_Properties, "RemoteMembers",
+ TF_FUTURE_HASH_TYPE_CONTACT_SENDING_STATE_MAP);
+ if (!members)
+ goto invalid_property;
+
+ local_sending_state = tp_asv_get_uint32 (out_Properties, "LocalSendingState",
+ &valid);
+ if (!valid)
+ goto invalid_property;
+
+ self->local_sending_state = local_sending_state;
+
+ if (g_hash_table_size (members) != 1)
+ {
+ tf_content_error (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR,
+ "org.freedesktop.Telepathy.Error.NotImplemented",
+ "Only one Member per Stream is supported, there are %d",
+ g_hash_table_size (members));
+ return;
+ }
+
+ g_hash_table_iter_init (&iter, members);
+
+ if (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ self->has_contact = TRUE;
+ self->contact_handle = GPOINTER_TO_UINT (key);
+ }
+
+ tp_proxy_add_interface_by_id (TP_PROXY (self->proxy),
+ TF_FUTURE_IFACE_QUARK_CALL_STREAM_INTERFACE_MEDIA);
+
+ tf_future_cli_call_stream_interface_media_connect_to_server_info_retrieved (
+ TF_FUTURE_CALL_STREAM (proxy), server_info_retrieved, NULL, NULL,
+ G_OBJECT (self), &myerror);
+ if (myerror)
+ {
+ tf_content_error (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error connectiong to ServerInfoRetrived signal: %s",
+ myerror->message);
+ g_clear_error (&myerror);
+ return;
+ }
+
+ tf_future_cli_call_stream_interface_media_connect_to_stun_servers_changed (
+ TF_FUTURE_CALL_STREAM (proxy), stun_servers_changed, NULL, NULL,
+ G_OBJECT (self), &myerror);
+ if (myerror)
+ {
+ tf_content_error (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error connectiong to ServerInfoRetrived signal: %s",
+ myerror->message);
+ g_clear_error (&myerror);
+ return;
+ }
+
+
+ tf_future_cli_call_stream_interface_media_connect_to_relay_info_changed (
+ TF_FUTURE_CALL_STREAM (proxy), relay_info_changed, NULL, NULL,
+ G_OBJECT (self), &myerror);
+ if (myerror)
+ {
+ tf_content_error (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error connectiong to ServerInfoRetrived signal: %s",
+ myerror->message);
+ g_clear_error (&myerror);
+ return;
+ }
+
+
+ tf_future_cli_call_stream_interface_media_connect_to_endpoints_changed (
+ TF_FUTURE_CALL_STREAM (proxy), endpoints_changed, NULL, NULL,
+ G_OBJECT (self), &myerror);
+ if (myerror)
+ {
+ tf_content_error (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error connectiong to EndpointsChanged signal: %s",
+ myerror->message);
+ g_clear_error (&myerror);
+ return;
+ }
+
+ tp_cli_dbus_properties_call_get_all (proxy, -1,
+ TF_FUTURE_IFACE_CALL_STREAM_INTERFACE_MEDIA,
+ got_stream_media_properties, NULL, NULL, G_OBJECT (self));
+
+ return;
+
+ invalid_property:
+ tf_content_error_literal (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error getting the Stream's properties: invalid type");
+ return;
+}
+
+TfCallStream *
+tf_call_stream_new (TfCallChannel *call_channel,
+ TfCallContent *call_content,
+ const gchar *object_path,
+ GError **error)
+{
+ TfCallStream *self;
+ TfFutureCallStream *proxy = tf_future_call_stream_new (call_channel->proxy,
+ object_path, error);
+ GError *myerror = NULL;
+
+ if (!proxy)
+ return NULL;
+
+ self = g_object_new (TF_TYPE_STREAM, NULL);
+
+ self->call_content = call_content;
+ self->proxy = proxy;
+
+ tf_future_cli_call_stream_connect_to_local_sending_state_changed (
+ TF_FUTURE_CALL_STREAM (proxy), local_sending_state_changed, NULL, NULL,
+ G_OBJECT (self), &myerror);
+ if (myerror)
+ {
+ tf_content_error (TF_CONTENT (self->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "",
+ "Error connectiong to LocalSendingStateChanged signal: %s",
+ myerror->message);
+ g_object_unref (self);
+ g_propagate_error (error, myerror);
+ return NULL;
+ }
+
+ tp_cli_dbus_properties_call_get_all (proxy, -1, TF_FUTURE_IFACE_CALL_STREAM,
+ got_stream_properties, NULL, NULL, G_OBJECT (self));
+
+ return self;
+}
+
+static void
+cb_fs_new_local_candidate (TfCallStream *stream, FsCandidate *candidate)
+{
+ GPtrArray *candidate_list = g_ptr_array_sized_new (1);
+ GValueArray *gva;
+ GHashTable *extra_info;
+
+ extra_info = tp_asv_new (NULL, NULL);
+ if (candidate->priority)
+ tp_asv_set_uint32 (extra_info, "Priority", candidate->priority);
+
+ if (candidate->foundation)
+ tp_asv_set_string (extra_info, "Foundation", candidate->foundation);
+
+ if (stream->multiple_usernames)
+ {
+ if (candidate->username)
+ tp_asv_set_string (extra_info, "Username", candidate->username);
+ if (candidate->password)
+ tp_asv_set_string (extra_info, "Password", candidate->password);
+ }
+ else
+ {
+ if ((!stream->last_local_username && candidate->username) ||
+ (!stream->last_local_password && candidate->password) ||
+ (stream->last_local_username &&
+ strcmp (candidate->username, stream->last_local_username)) ||
+ (stream->last_local_password &&
+ strcmp (candidate->password, stream->last_local_password)))
+ {
+ g_free (stream->last_local_username);
+ g_free (stream->last_local_password);
+ stream->last_local_username = g_strdup (candidate->username);
+ stream->last_local_password = g_strdup (candidate->password);
+
+ if (!stream->last_local_username)
+ stream->last_local_username = g_strdup ("");
+ if (!stream->last_local_password)
+ stream->last_local_password = g_strdup ("");
+
+ /* Add a callback to kill Call on errors */
+ tf_future_cli_call_stream_interface_media_call_set_credentials (
+ stream->proxy, -1, stream->last_local_username,
+ stream->last_local_password, NULL, NULL, NULL, NULL);
+
+ }
+ }
+
+ gva = tp_value_array_build (4,
+ G_TYPE_UINT, candidate->component_id,
+ G_TYPE_STRING, candidate->ip,
+ G_TYPE_UINT, candidate->port,
+ TF_FUTURE_HASH_TYPE_CANDIDATE_INFO, extra_info,
+ G_TYPE_INVALID);
+
+ g_ptr_array_add (candidate_list, gva);
+
+ /* Should also check for errors */
+ tf_future_cli_call_stream_interface_media_call_add_candidates (stream->proxy,
+ -1, candidate_list, NULL, NULL, NULL, NULL);
+
+
+ g_boxed_free (TF_FUTURE_ARRAY_TYPE_CANDIDATE_LIST, candidate_list);
+}
+
+static void
+cb_fs_local_candidates_prepared (TfCallStream *stream)
+{
+ tf_future_cli_call_stream_interface_media_call_candidates_prepared (
+ stream->proxy, -1, NULL, NULL, NULL, NULL);
+
+}
+
+static void
+cb_fs_component_state_changed (TfCallStream *stream, guint component,
+ FsStreamState fsstate)
+{
+ TpMediaStreamState state;
+
+ if (!stream->endpoint)
+ return;
+
+ switch (fsstate)
+ {
+ case FS_STREAM_STATE_FAILED:
+ case FS_STREAM_STATE_DISCONNECTED:
+ state = TP_MEDIA_STREAM_STATE_DISCONNECTED;
+ break;
+ case FS_STREAM_STATE_GATHERING:
+ case FS_STREAM_STATE_CONNECTING:
+ case FS_STREAM_STATE_CONNECTED:
+ state = TP_MEDIA_STREAM_STATE_CONNECTING;
+ break;
+ case FS_STREAM_STATE_READY:
+ default:
+ state = TP_MEDIA_STREAM_STATE_CONNECTED;
+ break;
+ }
+
+ tf_future_cli_call_stream_endpoint_call_set_stream_state (stream->endpoint,
+ -1, state, NULL, NULL, NULL, NULL);
+}
+
+
+gboolean
+tf_call_stream_bus_message (TfCallStream *stream, GstMessage *message)
+{
+ const GstStructure *s;
+ const GValue *val;
+
+ if (!stream->fsstream)
+ return FALSE;
+
+ s = gst_message_get_structure (message);
+
+ if (gst_structure_has_name (s, "farstream-error"))
+ {
+ GObject *object;
+ const gchar *msg;
+ FsError errorno;
+ GEnumClass *enumclass;
+ GEnumValue *enumvalue;
+ const gchar *debug;
+
+ val = gst_structure_get_value (s, "src-object");
+ object = g_value_get_object (val);
+
+ if (object != (GObject*) stream->fsstream)
+ return FALSE;
+
+ val = gst_structure_get_value (s, "error-no");
+ errorno = g_value_get_enum (val);
+ msg = gst_structure_get_string (s, "error-msg");
+ debug = gst_structure_get_string (s, "debug-msg");
+
+ enumclass = g_type_class_ref (FS_TYPE_ERROR);
+ enumvalue = g_enum_get_value (enumclass, errorno);
+ g_warning ("error (%s (%d)): %s : %s",
+ enumvalue->value_nick, errorno, msg, debug);
+ g_type_class_unref (enumclass);
+
+ tf_content_error_literal (TF_CONTENT (stream->call_content),
+ TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", msg);
+ return TRUE;
+ }
+
+ val = gst_structure_get_value (s, "stream");
+ if (!val)
+ return FALSE;
+ if (!G_VALUE_HOLDS_OBJECT (val))
+ return FALSE;
+ if (g_value_get_object (val) != stream->fsstream)
+ return FALSE;
+
+ if (gst_structure_has_name (s, "farstream-new-local-candidate"))
+ {
+ FsCandidate *candidate;
+
+ val = gst_structure_get_value (s, "candidate");
+ candidate = g_value_get_boxed (val);
+
+ cb_fs_new_local_candidate (stream, candidate);
+ return TRUE;
+ }
+ else if (gst_structure_has_name (s, "farstream-local-candidates-prepared"))
+ {
+ cb_fs_local_candidates_prepared (stream);
+ }
+ else if (gst_structure_has_name (s, "farstream-component-state-changed"))
+ {
+ guint component;
+ FsStreamState fsstate;
+
+ if (!gst_structure_get_uint (s, "component", &component) ||
+ !gst_structure_get_enum (s, "state", FS_TYPE_STREAM_STATE,
+ (gint*) &fsstate))
+ return TRUE;
+
+ cb_fs_component_state_changed (stream, component, fsstate);
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/farstream/telepathy-farstream/call-stream.h b/farstream/telepathy-farstream/call-stream.h
new file mode 100644
index 000000000..dc6518410
--- /dev/null
+++ b/farstream/telepathy-farstream/call-stream.h
@@ -0,0 +1,122 @@
+/*
+ * call-stream.h - Source for TfCallStream
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __TF_CALL_STREAM_H__
+#define __TF_CALL_STREAM_H__
+
+#include <glib-object.h>
+
+#include <gst/gst.h>
+#include <telepathy-glib/channel.h>
+
+#include "call-channel.h"
+#include "call-content.h"
+#include "extensions/extensions.h"
+
+G_BEGIN_DECLS
+
+#define TF_TYPE_STREAM tf_call_stream_get_type()
+
+#define TF_CALL_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TF_TYPE_STREAM, TfCallStream))
+
+#define TF_CALL_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ TF_TYPE_STREAM, TfCallStreamClass))
+
+#define TF_IS_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_STREAM))
+
+#define TF_IS_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_STREAM))
+
+#define TF_CALL_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TF_TYPE_STREAM, TfCallStreamClass))
+
+typedef struct _TfCallStreamPrivate TfCallStreamPrivate;
+
+/**
+ * TfCallStream:
+ *
+ * All members of the object are private
+ */
+
+typedef struct _TfCallStream TfCallStream;
+
+/**
+ * TfCallStreamClass:
+ * @parent_class: the parent #GObjecClass
+ *
+ * There are no overridable functions
+ */
+
+typedef struct _TfCallStreamClass TfCallStreamClass;
+
+
+struct _TfCallStream {
+ GObject parent;
+
+ TfCallContent *call_content;
+
+ TfFutureCallStream *proxy;
+
+ gchar *endpoint_objpath;
+ TpProxy *endpoint;
+ gchar *creds_username;
+ gchar *creds_password;
+ GList *stored_remote_candidates;
+ gboolean multiple_usernames;
+
+ gchar *last_local_username;
+ gchar *last_local_password;
+
+ TfFutureSendingState local_sending_state;
+ gboolean has_send_resource;
+
+ gboolean has_contact;
+ guint contact_handle;
+ FsStream *fsstream;
+
+ gboolean has_media_properties;
+ TfFutureStreamTransportType transport_type;
+ gboolean server_info_retrieved;
+ GPtrArray *stun_servers;
+ GPtrArray *relay_info;
+};
+
+struct _TfCallStreamClass{
+ GObjectClass parent_class;
+};
+
+
+GType tf_call_stream_get_type (void);
+
+TfCallStream *tf_call_stream_new (
+ TfCallChannel *channel,
+ TfCallContent *content,
+ const gchar *object_path,
+ GError **error);
+
+gboolean tf_call_stream_bus_message (TfCallStream *stream, GstMessage *message);
+
+G_END_DECLS
+
+#endif /* __TF_CALL_STREAM_H__ */
diff --git a/farstream/telepathy-farstream/channel-priv.h b/farstream/telepathy-farstream/channel-priv.h
new file mode 100644
index 000000000..8fdee93a5
--- /dev/null
+++ b/farstream/telepathy-farstream/channel-priv.h
@@ -0,0 +1,28 @@
+#ifndef __TF_CHANNEL_PRIV_H__
+#define __TF_CHANNEL_PRIV_H__
+
+#include "channel.h"
+
+G_BEGIN_DECLS
+
+
+struct _TfChannel{
+ GObject parent;
+
+ /*< private >*/
+
+ TfChannelPrivate *priv;
+};
+
+struct _TfChannelClass{
+ GObjectClass parent_class;
+
+ /*< private >*/
+
+ gpointer unused[4];
+};
+
+
+G_END_DECLS
+
+#endif /* __TF_CHANNEL_PRIV_H__ */
diff --git a/farstream/telepathy-farstream/channel.c b/farstream/telepathy-farstream/channel.c
new file mode 100644
index 000000000..d2f77b064
--- /dev/null
+++ b/farstream/telepathy-farstream/channel.c
@@ -0,0 +1,668 @@
+/*
+ * channel.c - Source for TfChannel
+ * Copyright (C) 2006-2007 Collabora Ltd.
+ * Copyright (C) 2006-2007 Nokia Corporation
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * SECTION:channel
+ * @short_description: Handle the MediaSignalling or Call media interfaces on a
+ * Channel
+ *
+ * This class handles the
+ * org.freedesktop.Telepathy.Channel.Interface.MediaSignalling on a
+ * channel using Farstream or the media part of the
+ * org.freedesktop.Telepathy.Channel.Type.Call that has HardwareStreaming=FALSE
+ */
+
+#include <stdlib.h>
+
+#include <telepathy-glib/channel.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/util.h>
+
+#include <farstream/fs-conference.h>
+
+#include "extensions/extensions.h"
+
+#include "channel.h"
+#include "channel-priv.h"
+#include "tf-signals-marshal.h"
+#include "media-signalling-channel.h"
+#include "media-signalling-content.h"
+#include "call-channel.h"
+#include "content.h"
+
+
+static void channel_async_initable_init (GAsyncInitableIface *asynciface);
+
+G_DEFINE_TYPE_WITH_CODE (TfChannel, tf_channel, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, channel_async_initable_init));
+
+struct _TfChannelPrivate
+{
+ TpChannel *channel_proxy;
+
+ TfMediaSignallingChannel *media_signalling_channel;
+ TfCallChannel *call_channel;
+
+ GHashTable *media_signalling_contents;
+
+ gulong channel_invalidated_handler;
+
+ gboolean closed;
+};
+
+enum
+{
+ SIGNAL_CLOSED,
+ SIGNAL_FS_CONFERENCE_ADDED,
+ SIGNAL_FS_CONFERENCE_REMOVED,
+ SIGNAL_CONTENT_ADDED,
+ SIGNAL_CONTENT_REMOVED,
+ SIGNAL_COUNT
+};
+
+static guint signals[SIGNAL_COUNT] = {0};
+
+enum
+{
+ PROP_CHANNEL = 1,
+ PROP_OBJECT_PATH,
+ PROP_FS_CONFERENCES
+};
+
+static void shutdown_channel (TfChannel *self);
+
+static void channel_fs_conference_added (GObject *chan,
+ FsConference *conf, TfChannel *self);
+static void channel_fs_conference_removed (GObject *chan,
+ FsConference *conf, TfChannel *self);
+
+static void tf_channel_init_async (GAsyncInitable *initable,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+static gboolean tf_channel_init_finish (GAsyncInitable *initable,
+ GAsyncResult *res,
+ GError **error);
+
+static void content_added (GObject *proxy,
+ TfContent *content,
+ TfChannel *self);
+static void content_removed (GObject *proxy,
+ TfContent *content,
+ TfChannel *self);
+
+
+static void
+tf_channel_init (TfChannel *self)
+{
+ TfChannelPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TF_TYPE_CHANNEL, TfChannelPrivate);
+
+ self->priv = priv;
+}
+
+static void
+channel_async_initable_init (GAsyncInitableIface *asynciface)
+{
+ asynciface->init_async = tf_channel_init_async;
+ asynciface->init_finish = tf_channel_init_finish;
+}
+
+
+static void
+tf_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TfChannel *self = TF_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_CHANNEL:
+ g_value_set_object (value, self->priv->channel_proxy);
+ break;
+ case PROP_OBJECT_PATH:
+ {
+ TpProxy *as_proxy = (TpProxy *) self->priv->channel_proxy;
+
+ g_value_set_string (value, as_proxy->object_path);
+ }
+ break;
+ case PROP_FS_CONFERENCES:
+ if (self->priv->call_channel)
+ g_object_get_property (G_OBJECT (self->priv->call_channel),
+ "fs-conferences", value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tf_channel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TfChannel *self = TF_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_CHANNEL:
+ self->priv->channel_proxy = TP_CHANNEL (g_value_dup_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void channel_invalidated (TpChannel *channel_proxy,
+ guint domain, gint code, gchar *message, TfChannel *self);
+
+static void
+call_channel_ready (GObject *obj, GAsyncResult *call_res, gpointer user_data)
+{
+ GError *error = NULL;
+ GSimpleAsyncResult *res = user_data;
+ TfChannel *self = TF_CHANNEL (
+ g_async_result_get_source_object (G_ASYNC_RESULT (res)));
+
+ self->priv->call_channel = TF_CALL_CHANNEL (g_async_initable_new_finish (
+ G_ASYNC_INITABLE (obj), call_res, &error));
+
+ if (error)
+ {
+ shutdown_channel (self);
+ g_simple_async_result_set_op_res_gboolean (res, FALSE);
+ g_simple_async_result_set_from_error (res, error);
+ g_clear_error (&error);
+ }
+ else
+ {
+ g_simple_async_result_set_op_res_gboolean (res, TRUE);
+
+ tp_g_signal_connect_object (self->priv->call_channel,
+ "fs-conference-added", G_CALLBACK (channel_fs_conference_added),
+ self, 0);
+ tp_g_signal_connect_object (self->priv->call_channel,
+ "fs-conference-removed", G_CALLBACK (channel_fs_conference_removed),
+ self, 0);
+
+ tp_g_signal_connect_object (self->priv->call_channel,
+ "content_added", G_CALLBACK (content_added),
+ self, 0);
+ tp_g_signal_connect_object (self->priv->call_channel,
+ "content_removed", G_CALLBACK (content_removed),
+ self, 0);
+ }
+
+
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+ g_object_unref (self);
+}
+
+static gboolean
+content_remove_all (gpointer key, gpointer value, gpointer user_data)
+{
+ TfMediaSignallingContent *content = value;
+ TfChannel *self = user_data;
+
+ g_signal_emit (self, signals[SIGNAL_CONTENT_REMOVED], 0, content);
+
+ return TRUE;
+}
+
+static void
+channel_session_invalidated (TfMediaSignallingChannel *media_signalling_channel,
+ FsConference *fsconference, FsParticipant *part, TfChannel *self)
+{
+ g_object_notify (G_OBJECT (self), "fs-conferences");
+ g_signal_emit (self, signals[SIGNAL_FS_CONFERENCE_REMOVED], 0,
+ fsconference);
+
+ if (self->priv->media_signalling_contents)
+ g_hash_table_foreach_remove (self->priv->media_signalling_contents,
+ content_remove_all, self);
+}
+
+static void
+channel_stream_closed (TfStream *stream, TfChannel *self)
+{
+ TfMediaSignallingContent *content;
+
+ content = g_hash_table_lookup (self->priv->media_signalling_contents, stream);
+ g_signal_emit (self, signals[SIGNAL_CONTENT_REMOVED], 0, content);
+ g_hash_table_remove (self->priv->media_signalling_contents, stream);
+}
+
+static void
+channel_stream_created (TfMediaSignallingChannel *media_signalling_channel,
+ TfStream *stream, TfChannel *self)
+{
+ TfMediaSignallingContent *content;
+
+ g_assert (self->priv->media_signalling_contents);
+
+ content = tf_media_signalling_content_new (
+ self->priv->media_signalling_channel, stream, 0 /* HANDLE HERE */);
+
+ g_hash_table_insert (self->priv->media_signalling_contents,
+ g_object_ref (stream), content);
+
+ tp_g_signal_connect_object (stream, "closed",
+ G_CALLBACK (channel_stream_closed), self, 0);
+
+ g_signal_emit (self, signals[SIGNAL_CONTENT_ADDED], 0,
+ content);
+}
+
+static void
+channel_prepared (GObject *obj,
+ GAsyncResult *proxy_res,
+ gpointer user_data)
+{
+ TpChannel *channel_proxy = TP_CHANNEL (obj);
+ TpProxy *as_proxy = (TpProxy *) channel_proxy;
+ GSimpleAsyncResult *res = user_data;
+ GError *error = NULL;
+ TfChannel *self = TF_CHANNEL (
+ g_async_result_get_source_object (G_ASYNC_RESULT (res)));
+
+ if (!tp_proxy_prepare_finish (channel_proxy, proxy_res, &error))
+ {
+ g_simple_async_result_propagate_error (res, &error);
+ shutdown_channel (self);
+ goto error;
+ }
+
+ if (self->priv->closed)
+ {
+ g_simple_async_result_set_error (res, TP_ERROR, TP_ERROR_CANCELLED,
+ "Channel already closed");
+ goto error;
+ }
+
+ if (tp_proxy_has_interface_by_id (as_proxy,
+ TP_IFACE_QUARK_CHANNEL_INTERFACE_MEDIA_SIGNALLING))
+ {
+ self->priv->media_signalling_channel =
+ tf_media_signalling_channel_new (channel_proxy);
+
+ self->priv->media_signalling_contents = g_hash_table_new_full (
+ g_direct_hash, g_direct_equal, g_object_unref, g_object_unref);
+
+ tp_g_signal_connect_object (self->priv->media_signalling_channel,
+ "session-created", G_CALLBACK (channel_fs_conference_added),
+ self, 0);
+ tp_g_signal_connect_object (self->priv->media_signalling_channel,
+ "session-invalidated", G_CALLBACK (channel_session_invalidated),
+ self, 0);
+ tp_g_signal_connect_object (self->priv->media_signalling_channel,
+ "stream-created", G_CALLBACK (channel_stream_created),
+ self, 0);
+ g_simple_async_result_set_op_res_gboolean (res, TRUE);
+ }
+ else if (tp_proxy_has_interface_by_id (as_proxy,
+ TF_FUTURE_IFACE_QUARK_CHANNEL_TYPE_CALL))
+ {
+ tf_call_channel_new_async (channel_proxy, call_channel_ready, res);
+
+ self->priv->channel_invalidated_handler = g_signal_connect (
+ self->priv->channel_proxy,
+ "invalidated", G_CALLBACK (channel_invalidated), self);
+ }
+ else
+ {
+ g_simple_async_result_set_error (res, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "Channel does not implement "
+ TP_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING " or "
+ TF_FUTURE_IFACE_CHANNEL_TYPE_CALL);
+ goto error;
+ }
+
+ g_object_unref (self);
+ return;
+
+error:
+ g_simple_async_result_set_op_res_gboolean (res, FALSE);
+ g_simple_async_result_complete (res);
+
+ g_object_unref (res);
+ g_object_unref (self);
+}
+
+
+static void
+tf_channel_init_async (GAsyncInitable *initable,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TfChannel *self = TF_CHANNEL (initable);
+ GSimpleAsyncResult *res;
+
+ if (cancellable != NULL)
+ {
+ g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data,
+ G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED,
+ "TfChannel initialisation does not support cancellation");
+ return;
+ }
+
+ res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+ tf_channel_init_async);
+ tp_proxy_prepare_async (self->priv->channel_proxy, NULL,
+ channel_prepared, res);
+}
+
+static gboolean
+tf_channel_init_finish (GAsyncInitable *initable,
+ GAsyncResult *res,
+ GError **error)
+{
+ GSimpleAsyncResult *simple_res;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (res,
+ G_OBJECT (initable), tf_channel_init_async), FALSE);
+ simple_res = G_SIMPLE_ASYNC_RESULT (res);
+
+ g_simple_async_result_propagate_error (simple_res, error);
+
+ return g_simple_async_result_get_op_res_gboolean (simple_res);
+}
+
+
+static void
+tf_channel_dispose (GObject *object)
+{
+ TfChannel *self = TF_CHANNEL (object);
+
+ g_debug (G_STRFUNC);
+
+
+ if (self->priv->media_signalling_contents != NULL)
+ {
+ g_hash_table_unref (self->priv->media_signalling_contents);
+ self->priv->media_signalling_contents = NULL;
+ }
+
+ tp_clear_object (&self->priv->media_signalling_channel);
+ tp_clear_object (&self->priv->call_channel);
+
+ if (self->priv->channel_proxy)
+ {
+ TpChannel *tmp;
+
+ if (self->priv->channel_invalidated_handler != 0)
+ g_signal_handler_disconnect (self->priv->channel_proxy,
+ self->priv->channel_invalidated_handler);
+
+ tmp = self->priv->channel_proxy;
+ self->priv->channel_proxy = NULL;
+ g_object_unref (tmp);
+ }
+
+ if (G_OBJECT_CLASS (tf_channel_parent_class)->dispose)
+ G_OBJECT_CLASS (tf_channel_parent_class)->dispose (object);
+}
+
+static void
+tf_channel_class_init (TfChannelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (TfChannelPrivate));
+
+ object_class->set_property = tf_channel_set_property;
+ object_class->get_property = tf_channel_get_property;
+
+ object_class->dispose = tf_channel_dispose;
+
+ g_object_class_install_property (object_class, PROP_CHANNEL,
+ g_param_spec_object ("channel",
+ "TpChannel object",
+ "Telepathy channel object which this media channel should operate on",
+ TP_TYPE_CHANNEL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_OBJECT_PATH,
+ g_param_spec_string ("object-path",
+ "channel object path",
+ "D-Bus object path of the Telepathy channel which this channel"
+ " operates on",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+
+ g_object_class_install_property (object_class, PROP_FS_CONFERENCES,
+ g_param_spec_boxed ("fs-conferences",
+ "Farstream FsConferences objects",
+ "GPtrArray of Farstream FsConferences for this channel",
+ G_TYPE_PTR_ARRAY,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * TfChannel::closed:
+ *
+ * This function is called after a channel is closed, either because
+ * it has been closed by the connection manager or because we had a locally
+ * generated error.
+ */
+
+ signals[SIGNAL_CLOSED] =
+ g_signal_new ("closed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /**
+ * TfChannel::fs-conference-added
+ * @tfchannel: the #TfChannel
+ * @conf: a #FsConference
+ *
+ * When this signal is emitted, the conference should be added to the
+ * application's pipeline.
+ */
+
+ signals[SIGNAL_FS_CONFERENCE_ADDED] = g_signal_new ("fs-conference-added",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ _tf_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, FS_TYPE_CONFERENCE);
+
+ /**
+ * TfChannel::fs-conference-removed
+ * @tfchannel: the #TfChannel
+ * @conf: a #FsConference
+ *
+ * When this signal is emitted, the conference should be remove from the
+ * application's pipeline.
+ */
+
+ signals[SIGNAL_FS_CONFERENCE_REMOVED] = g_signal_new ("fs-conference-removed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ _tf_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, FS_TYPE_CONFERENCE);
+
+
+ /**
+ * TfChannel::content-added
+ * @tfchannel: the #TfChannel
+ * @content: a #TfContent
+ *
+ * Tells the application that a content has been added. In the callback for
+ * this signal, the application should set its preferred codecs, and hook
+ * up to any signal from #TfContent it cares about. Special care should be
+ * made to connect #TfContent::src-pad-added as well
+ * as the #TfContent::start-sending and #TfContent::stop-sending signals.
+ */
+
+ signals[SIGNAL_CONTENT_ADDED] = g_signal_new ("content-added",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ _tf_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, TF_TYPE_CONTENT);
+
+ /**
+ * TfChannel::content-removed
+ * @tfchannel: the #TfChannel
+ * @content: a #TfContent
+ *
+ * Tells the application that a content is being removed.
+ */
+
+ signals[SIGNAL_CONTENT_REMOVED] = g_signal_new ("content-removed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ _tf_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, TF_TYPE_CONTENT);
+}
+
+static void
+shutdown_channel (TfChannel *self)
+{
+ if (self->priv->media_signalling_channel)
+ {
+ g_object_unref (self->priv->media_signalling_channel);
+ self->priv->media_signalling_channel = NULL;
+ }
+
+ tp_clear_object (&self->priv->call_channel);
+
+ if (self->priv->channel_proxy != NULL)
+ {
+ if (self->priv->channel_invalidated_handler)
+ {
+ g_signal_handler_disconnect (
+ self->priv->channel_proxy, self->priv->channel_invalidated_handler);
+ self->priv->channel_invalidated_handler = 0;
+ }
+ }
+
+ g_signal_emit (self, signals[SIGNAL_CLOSED], 0);
+
+ self->priv->closed = TRUE;
+}
+
+static void
+channel_invalidated (TpChannel *channel_proxy,
+ guint domain,
+ gint code,
+ gchar *message,
+ TfChannel *self)
+{
+ shutdown_channel (self);
+}
+
+/**
+ * tf_channel_new_async:
+ * @channel_proxy: a #TpChannel proxy
+ * @callback: a #GAsyncReadyCallback to call when the channel is ready
+ * @user_data: the data to pass to callback function
+ *
+ * Creates a new #TfChannel from an existing channel proxy, the new
+ * TfChannel object will be return in the async callback.
+ *
+ * The user must call g_async_initable_new_finish() in the callback
+ * to get the finished object.
+ */
+
+void
+tf_channel_new_async (TpChannel *channel_proxy,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ return g_async_initable_new_async (TF_TYPE_CHANNEL,
+ 0, NULL, callback, user_data,
+ "channel", channel_proxy,
+ NULL);
+}
+
+/**
+ * tf_channel_bus_message:
+ * @channel: A #TfChannel
+ * @message: A #GstMessage received from the bus
+ *
+ * You must call this function on call messages received on the async bus.
+ * #GstMessages are not modified.
+ *
+ * Returns: %TRUE if the message has been handled, %FALSE otherwise
+ */
+
+gboolean
+tf_channel_bus_message (TfChannel *channel,
+ GstMessage *message)
+{
+ if (channel->priv->media_signalling_channel)
+ return tf_media_signalling_channel_bus_message (
+ channel->priv->media_signalling_channel, message);
+ else if (channel->priv->call_channel)
+ return tf_call_channel_bus_message (channel->priv->call_channel,
+ message);
+
+ return FALSE;
+}
+
+static void
+channel_fs_conference_added (GObject *proxy, FsConference *conf,
+ TfChannel *self)
+{
+ g_object_notify (G_OBJECT (self), "fs-conferences");
+ g_signal_emit (self, signals[SIGNAL_FS_CONFERENCE_ADDED], 0,
+ conf);
+}
+
+static void
+channel_fs_conference_removed (GObject *proxy, FsConference *conf,
+ TfChannel *self)
+{
+ g_object_notify (G_OBJECT (self), "fs-conferences");
+ g_signal_emit (self, signals[SIGNAL_FS_CONFERENCE_REMOVED], 0,
+ conf);
+}
+
+static void
+content_added (GObject *proxy, TfContent *content, TfChannel *self)
+{
+ g_signal_emit (self, signals[SIGNAL_CONTENT_ADDED], 0, content);
+}
+
+static void
+content_removed (GObject *proxy, TfContent *content, TfChannel *self)
+{
+ g_signal_emit (self, signals[SIGNAL_CONTENT_REMOVED], 0, content);
+}
diff --git a/farstream/telepathy-farstream/channel.h b/farstream/telepathy-farstream/channel.h
new file mode 100644
index 000000000..55533bd74
--- /dev/null
+++ b/farstream/telepathy-farstream/channel.h
@@ -0,0 +1,62 @@
+#ifndef __TF_CHANNEL_H__
+#define __TF_CHANNEL_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/channel.h>
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define TF_TYPE_CHANNEL tf_channel_get_type()
+
+#define TF_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TF_TYPE_CHANNEL, TfChannel))
+
+#define TF_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ TF_TYPE_CHANNEL, TfChannelClass))
+
+#define TF_IS_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_CHANNEL))
+
+#define TF_IS_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_CHANNEL))
+
+#define TF_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TF_TYPE_CHANNEL, TfChannelClass))
+
+typedef struct _TfChannelPrivate TfChannelPrivate;
+
+/**
+ * TfChannel:
+ *
+ * All members of the object are private
+ */
+
+typedef struct _TfChannel TfChannel;
+
+/**
+ * TfChannelClass:
+ * @parent_class: the parent #GObjectClass
+ *
+ * There are no overridable functions
+ */
+
+typedef struct _TfChannelClass TfChannelClass;
+
+GType tf_channel_get_type (void);
+
+void tf_channel_new_async (TpChannel *channel_proxy,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+
+gboolean tf_channel_bus_message (TfChannel *channel,
+ GstMessage *message);
+
+G_END_DECLS
+
+#endif /* __TF_CHANNEL_H__ */
diff --git a/farstream/telepathy-farstream/content-priv.h b/farstream/telepathy-farstream/content-priv.h
new file mode 100644
index 000000000..bee82e327
--- /dev/null
+++ b/farstream/telepathy-farstream/content-priv.h
@@ -0,0 +1,43 @@
+
+#ifndef __TF_CONTENT_PRIV_H__
+#define __TF_CONTENT_PRIV_H__
+
+#include <glib-object.h>
+
+#include <farstream/fs-conference.h>
+
+
+G_BEGIN_DECLS
+
+struct _TfContent {
+ GObject parent;
+
+ guint sending_count;
+};
+
+struct _TfContentClass{
+ GObjectClass parent_class;
+
+ void (*content_error) (TfContent *content,
+ guint reason, /* TfFutureContentRemovalReason */
+ const gchar *detailed_reason,
+ const gchar *message);
+
+ GstIterator * (*iterate_src_pads) (TfContent *content, guint *handle,
+ guint handle_count);
+};
+
+gboolean _tf_content_start_sending (TfContent *self);
+void _tf_content_stop_sending (TfContent *self);
+
+void _tf_content_emit_src_pad_added (TfContent *self, guint handle,
+ FsStream *stream, GstPad *pad, FsCodec *codec);
+
+gboolean _tf_content_start_receiving (TfContent *self, guint *handles,
+ guint handle_count);
+void _tf_content_stop_receiving (TfContent *self, guint *handles,
+ guint handle_count);
+
+G_END_DECLS
+
+#endif /* __TF_CONTENT_PRIV_H__ */
diff --git a/farstream/telepathy-farstream/content.c b/farstream/telepathy-farstream/content.c
new file mode 100644
index 000000000..7f2359642
--- /dev/null
+++ b/farstream/telepathy-farstream/content.c
@@ -0,0 +1,424 @@
+
+#include "content.h"
+#include "content-priv.h"
+
+#include <farstream/fs-conference.h>
+
+#include "channel.h"
+#include "tf-signals-marshal.h"
+
+
+/**
+ * SECTION:content
+ * @short_description: Represent the Content of a channel handled by #TfChannel
+ *
+ * Objects of this class allow the user to handle the media side of a Telepathy
+ * channel handled by #TfChannel.
+ *
+ * This object is created by the #TfChannel and the user is notified of its
+ * creation by the #TfChannel::content-added signal. In the callback for this
+ * signal, the user should call tf_content_set_codec_preferences() and connect
+ * to the #TfContent::src-pad-added signal.
+ *
+ */
+
+
+G_DEFINE_ABSTRACT_TYPE (TfContent, tf_content, G_TYPE_OBJECT);
+
+
+enum
+{
+ PROP_TF_CHANNEL = 1,
+ PROP_FS_CONFERENCE,
+ PROP_FS_SESSION,
+ PROP_MEDIA_TYPE,
+ PROP_SINK_PAD,
+ PROP_OBJECT_PATH
+};
+
+enum
+{
+ SIGNAL_START_SENDING,
+ SIGNAL_STOP_SENDING,
+ SIGNAL_SRC_PAD_ADDED,
+ SIGNAL_START_RECEIVING,
+ SIGNAL_STOP_RECEIVING,
+ SIGNAL_RESTART_SOURCE,
+ SIGNAL_COUNT
+};
+
+static guint signals[SIGNAL_COUNT] = {0};
+
+static void
+tf_content_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ /* Other properties need to be overwritten */
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tf_content_class_init (TfContentClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = tf_content_get_property;
+
+ g_object_class_install_property (object_class, PROP_TF_CHANNEL,
+ g_param_spec_object ("tf-channel",
+ "Parent TfChannel object ",
+ "The Telepathy-Farstream Channel for this object",
+ TF_TYPE_CHANNEL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_FS_CONFERENCE,
+ g_param_spec_object ("fs-conference",
+ "Farstream FsConference used by the Content ",
+ "The Farstream conference for this content "
+ "(could be the same as other contents)",
+ FS_TYPE_CONFERENCE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_FS_SESSION,
+ g_param_spec_object ("fs-session",
+ "Farstream FsSession ",
+ "The Farstream session for this content",
+ FS_TYPE_SESSION,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_SINK_PAD,
+ g_param_spec_object ("sink-pad",
+ "Sink Pad",
+ "Sink GstPad for this content",
+ GST_TYPE_PAD,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_MEDIA_TYPE,
+ g_param_spec_enum ("media-type",
+ "MediaType",
+ "The FsMediaType for this content",
+ FS_TYPE_MEDIA_TYPE,
+ 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_OBJECT_PATH,
+ g_param_spec_string ("object-path",
+ "content object path",
+ "D-Bus object path of the Telepathy content which this content"
+ " operates on",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+
+ /**
+ * TfContent::start-sending
+ * @content: the #TfContent
+ *
+ * This signal is emitted when the connection manager ask to send media.
+ * For example, this can be used to open a camera, start recording from a
+ * microphone or play back a file. The application should start
+ * sending data on the #TfContent:sink-pad
+ *
+ * Returns: %TRUE if the application can start providing data or %FALSE
+ * otherwise
+ */
+
+ signals[SIGNAL_START_SENDING] =
+ g_signal_new ("start-sending",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ g_signal_accumulator_true_handled, NULL,
+ _tf_marshal_BOOLEAN__VOID,
+ G_TYPE_BOOLEAN, 0);
+
+ /**
+ * TfContent::stop-sending
+ * @content: the #TfContent
+ *
+ * This signal is emitted when the connection manager ask to stop
+ * sending media
+ */
+
+ signals[SIGNAL_STOP_SENDING] =
+ g_signal_new ("stop-sending",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /**
+ * TfContent::src-pad-added
+ * @content: the #TfContent
+ * @handle: the handle of the remote party producing the content on this pad
+ * or 0 if unknown
+ * @stream: the #FsStream for this pad
+ * @pad: a #GstPad
+ * @codec: the #FsCodec for this pad
+ *
+ * This signal is emitted when a data is coming on a new pad. This signal
+ * is not emitted on the main thread, so special care must be made to lock
+ * the relevant data. When the callback returns from this signal, data will
+ * start flowing through the pad, so the application MUST connect a sink.
+ */
+
+ signals[SIGNAL_SRC_PAD_ADDED] =
+ g_signal_new ("src-pad-added",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ _tf_marshal_VOID__UINT_OBJECT_OBJECT_BOXED,
+ G_TYPE_NONE, 4,
+ G_TYPE_UINT, FS_TYPE_STREAM, GST_TYPE_PAD, FS_TYPE_CODEC);
+
+ /**
+ * TfContent::start-receiving
+ * @content: the #TfContent
+ * @handles: a 0-terminated array of #guint containing the handles
+ * @handle_count: The number of handles in the @handles array
+ *
+ * This signal is emitted when the connection managers requests that the
+ * application prepares itself to start receiving data again from certain
+ * handles.
+ *
+ * This signal will only be emitted after the #TfContent::stop-receiving
+ * signal has succeeded. It will not be emitted right after
+ * #TfContent::src-pad-added.
+ *
+ * Returns: %TRUE if the application can start receiving data or %FALSE
+ * otherwise
+ */
+
+ signals[SIGNAL_START_RECEIVING] =
+ g_signal_new ("start-receiving",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ g_signal_accumulator_true_handled, NULL,
+ _tf_marshal_BOOLEAN__POINTER_UINT,
+ G_TYPE_BOOLEAN, 2, G_TYPE_POINTER, G_TYPE_UINT);
+
+ /**
+ * TfContent::stop-receiving
+ * @content: the #TfContent
+ * @handles: a 0-terminated array of #guint containing the handles
+ * @handle_count: The number of handles in the @handles array
+ *
+ * This signal is emitted when the connection manager wants to tell the
+ * application that it is now allowed to stop receiving.
+ */
+
+ signals[SIGNAL_STOP_RECEIVING] =
+ g_signal_new ("stop-receiving",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ _tf_marshal_VOID__POINTER_UINT,
+ G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_UINT);
+
+ /**
+ * TfContent::restart-source:
+ * @content: the #TfContent
+ *
+ * This signal requests that the source be restarted so that the caps can
+ * be renegotiated with a new resolutions and framerate.
+ */
+
+ signals[SIGNAL_RESTART_SOURCE] =
+ g_signal_new ("restart-source",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ _tf_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+
+static void
+tf_content_init (TfContent *self)
+{
+}
+
+
+gboolean
+_tf_content_start_sending (TfContent *self)
+{
+ GValue instance = {0};
+ GValue sending_success_val = {0,};
+ gboolean sending_success;
+
+ if (self->sending_count)
+ {
+ self->sending_count ++;
+ return TRUE;
+ }
+
+ g_value_init (&sending_success_val, G_TYPE_BOOLEAN);
+ g_value_set_boolean (&sending_success_val, TRUE);
+
+ g_value_init (&instance, TF_TYPE_CONTENT);
+ g_value_set_object (&instance, self);
+
+ g_debug ("Requesting that the application start sending");
+
+ g_signal_emitv (&instance, signals[SIGNAL_START_SENDING], 0,
+ &sending_success_val);
+ sending_success = g_value_get_boolean (&sending_success_val);
+
+ g_value_unset (&instance);
+
+ g_debug ("Request to start sending %s",
+ sending_success ? "succeeded" : "failed");
+
+ self->sending_count = 1;
+
+ return sending_success;
+}
+
+void
+_tf_content_stop_sending (TfContent *self)
+{
+ self->sending_count --;
+
+ if (self->sending_count == 0)
+ g_signal_emit (self, signals[SIGNAL_STOP_SENDING], 0);
+}
+
+void
+_tf_content_emit_src_pad_added (TfContent *self, guint handle,
+ FsStream *stream, GstPad *pad, FsCodec *codec)
+{
+ g_signal_emit (self, signals[SIGNAL_SRC_PAD_ADDED], 0, handle,
+ stream, pad, codec);
+}
+
+/**
+ * tf_content_error_literal:
+ * @content: a #TfContent
+ * @reason: the reason (a #TfContentRemovalReason)
+ * @detailed_reason: The detailled error (as a DBus name)
+ * @message: error Message
+ *
+ * Send an error to the Content to the CM, the effect is most likely that the
+ * content will be removed.
+ */
+
+void
+tf_content_error_literal (TfContent *content,
+ guint reason, /* TfFutureContentRemovalReason */
+ const gchar *detailed_reason,
+ const gchar *message)
+{
+ TfContentClass *klass = TF_CONTENT_GET_CLASS (content);
+
+ if (klass->content_error)
+ klass->content_error (content, reason, detailed_reason, message);
+ else
+ GST_WARNING ("content_error not defined in class: %s", message);
+}
+
+/**
+ * tf_content_error:
+ * @content: a #TfContent
+ * @reason: the reason (a #TfContentRemovalReason)
+ * @detailed_reason: The detailled error (as a DBus name)
+ * @message_format: error Message with printf style formatting
+ * @...: Parameters to insert into the @message_format string
+ *
+ * Send an error to the Content to the CM, the effect is most likely that the
+ * content will be removed.
+ */
+
+void
+tf_content_error (TfContent *content,
+ guint reason, /* TfFutureContentRemovalReason */
+ const gchar *detailed_reason,
+ const gchar *message_format,
+ ...)
+{
+ gchar *message;
+ va_list valist;
+
+ va_start (valist, message_format);
+ message = g_strdup_vprintf (message_format, valist);
+ va_end (valist);
+
+ tf_content_error_literal (content, reason, detailed_reason, message);
+ g_free (message);
+}
+
+/**
+ * tf_content_iterate_src_pads:
+ * @content: a #TfContent
+ * @handles: a 0 terminated array of #guint representing Telepathy handles
+ * @handle_count: the numner of handles in @handles
+ *
+ * Provides a iterator that can be used to iterate through all of the src
+ * pads that are are used to receive from a group of Telepathy handles.
+ *
+ * Returns: a #GstIterator
+ */
+
+GstIterator *
+tf_content_iterate_src_pads (TfContent *content, guint *handles,
+ guint handle_count)
+{
+ TfContentClass *klass = TF_CONTENT_GET_CLASS (content);
+
+ if (klass->iterate_src_pads)
+ return klass->iterate_src_pads (content, handles, handle_count);
+ else
+ GST_WARNING ("iterate_src_pads not defined in class");
+
+ return NULL;
+}
+
+gboolean
+_tf_content_start_receiving (TfContent *self, guint *handles,
+ guint handle_count)
+{
+ GValue instance_and_params[3] = {{0} , {0}, {0}};
+ GValue receiving_success_val = {0,};
+ gboolean receiving_success;
+
+ g_value_init (&receiving_success_val, G_TYPE_BOOLEAN);
+ g_value_set_boolean (&receiving_success_val, TRUE);
+
+ g_value_init (&instance_and_params[0], TF_TYPE_CONTENT);
+ g_value_set_object (&instance_and_params[0], self);
+
+ g_value_init (&instance_and_params[1], G_TYPE_POINTER);
+ g_value_set_pointer (&instance_and_params[1], handles);
+
+ g_value_init (&instance_and_params[2], G_TYPE_UINT);
+ g_value_set_uint (&instance_and_params[2], handle_count);
+
+ g_debug ("Requesting that the application start receiving");
+
+ g_signal_emitv (instance_and_params, signals[SIGNAL_START_RECEIVING], 0,
+ &receiving_success_val);
+ receiving_success = g_value_get_boolean (&receiving_success_val);
+
+ g_value_unset (&instance_and_params[0]);
+
+ g_debug ("Request to start receiving %s",
+ receiving_success ? "succeeded" : "failed");
+
+ return receiving_success;
+}
+
+void
+_tf_content_stop_receiving (TfContent *self, guint *handles,
+ guint handle_count)
+{
+ g_signal_emit (self, signals[SIGNAL_STOP_SENDING], 0, handles,
+ handle_count);
+}
diff --git a/farstream/telepathy-farstream/content.h b/farstream/telepathy-farstream/content.h
new file mode 100644
index 000000000..e0716792a
--- /dev/null
+++ b/farstream/telepathy-farstream/content.h
@@ -0,0 +1,64 @@
+#ifndef __TF_CONTENT_H__
+#define __TF_CONTENT_H__
+
+#include <glib-object.h>
+#include <farstream/fs-conference.h>
+
+G_BEGIN_DECLS
+
+#define TF_TYPE_CONTENT tf_content_get_type()
+
+#define TF_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TF_TYPE_CONTENT, TfContent))
+
+#define TF_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ TF_TYPE_CONTENT, TfContentClass))
+
+#define TF_IS_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_CONTENT))
+
+#define TF_IS_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_CONTENT))
+
+#define TF_CONTENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TF_TYPE_CONTENT, TfContentClass))
+
+typedef struct _TfContentPrivate TfContentPrivate;
+
+/**
+ * TfContent:
+ *
+ * This structure is private, this class is not subclassable.
+ */
+
+typedef struct _TfContent TfContent;
+
+/**
+ * TfContentClass:
+ *
+ * This structure is private, this class is not subclassable.
+ */
+
+typedef struct _TfContentClass TfContentClass;
+
+GType tf_content_get_type (void);
+
+void tf_content_error_literal (TfContent *content,
+ guint reason, /* TfFutureContentRemovalReason */
+ const gchar *detailed_reason,
+ const gchar *message);
+
+void tf_content_error (TfContent *content,
+ guint reason, /* TfFutureContentRemovalReason */
+ const gchar *detailed_reason,
+ const gchar *message_format, ...) G_GNUC_PRINTF (4, 5);
+
+GstIterator *tf_content_iterate_src_pads (TfContent *content,
+ guint *handles, guint handle_count);
+
+G_END_DECLS
+
+#endif /* __TF_CONTENT_H__ */
diff --git a/farstream/telepathy-farstream/media-signalling-channel.c b/farstream/telepathy-farstream/media-signalling-channel.c
new file mode 100644
index 000000000..bfd369649
--- /dev/null
+++ b/farstream/telepathy-farstream/media-signalling-channel.c
@@ -0,0 +1,729 @@
+/*
+ * media-signalling-channel.c - Source for TfMediaSignallingChannel
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * SECTION:media-signalling-channel
+ * @short_description: Handle the MediaSignalling interface on a Channel
+ *
+ * This class handles the
+ * org.freedesktop.Telepathy.Channel.Interface.MediaSignalling on a
+ * channel using Farstream.
+ */
+
+
+#include "media-signalling-channel.h"
+
+#include <telepathy-glib/util.h>
+#include <telepathy-glib/interfaces.h>
+
+#include "stream.h"
+#include "session-priv.h"
+#include "stream-priv.h"
+#include "tf-signals-marshal.h"
+
+
+struct _TfMediaSignallingChannel {
+ GObject parent;
+
+ TpChannel *channel_proxy;
+
+ TfNatProperties nat_props;
+ guint prop_id_nat_traversal;
+ guint prop_id_stun_server;
+ guint prop_id_stun_port;
+ guint prop_id_gtalk_p2p_relay_token;
+
+ /* sessions is NULL until we've had a reply from GetSessionHandlers */
+ TfSession *session;
+ gboolean got_sessions;
+ GPtrArray *streams;
+};
+
+struct _TfMediaSignallingChannelClass{
+ GObjectClass parent_class;
+};
+
+
+G_DEFINE_TYPE (TfMediaSignallingChannel, tf_media_signalling_channel,
+ G_TYPE_OBJECT);
+
+enum
+{
+ STREAM_CREATED,
+ SESSION_CREATED,
+ SESSION_INVALIDATED,
+ GET_CODEC_CONFIG,
+ SIGNAL_COUNT
+};
+
+static guint signals[SIGNAL_COUNT] = {0};
+
+static void tf_media_signalling_channel_dispose (GObject *object);
+
+static void new_media_session_handler (TpChannel *channel_proxy,
+ const gchar *session_handler_path, const gchar *type,
+ gpointer user_data, GObject *weak_object);
+
+static void get_session_handlers_reply (TpChannel *channel_proxy,
+ const GPtrArray *session_handlers, const GError *error,
+ gpointer user_data, GObject *weak_object);
+
+
+static void new_stream_cb (TfSession *session, gchar *object_path,
+ guint stream_id, TpMediaStreamType media_type,
+ TpMediaStreamDirection direction, gpointer user_data);
+
+static void stream_closed_cb (TfStream *stream,
+ gpointer user_data);
+
+static void
+tf_media_signalling_channel_error (TfMediaSignallingChannel *chan,
+ TpMediaStreamError error,
+ const gchar *message);
+
+
+
+static void
+tf_media_signalling_channel_class_init (TfMediaSignallingChannelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = tf_media_signalling_channel_dispose;
+
+ /**
+ * TfMediaSignallingChannel::stream-created:
+ * @channel: the #TfMediaSignallingChannel which has a new stream
+ * @stream: The new #TfStream
+ *
+ * This signal is emitted when a new stream has been created in the connection
+ * manager and a local proxy has been generated.
+ */
+
+ signals[STREAM_CREATED] =
+ g_signal_new ("stream-created",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, TF_TYPE_STREAM);
+
+ /**
+ * TfMediaSignallingChannel::session-created:
+ * @channel: the #TfMediaSignallingChannel which has a new stream
+ * @conference: the #FsConference of the new session
+ * @participant: the #FsParticipant of the new session
+ *
+ * This signal is emitted when a new session has been created in the
+ * connection manager. The user should add the new #FsConference to a pipeline
+ * and set it to playing. The user should also set any property he wants to
+ * set.
+ */
+
+ signals[SESSION_CREATED] =
+ g_signal_new ("session-created",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ _tf_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, FS_TYPE_CONFERENCE);
+
+ /**
+ * TfMediaSignallingChannel::session-invalidated:
+ * @channel: the #TfMediaSignallingChannel which has a new stream
+ * @conference: the #FsConference of the new session
+ * @participant: the #FsParticipant of the new session
+ *
+ * This signal is emitted when a session has been invalidated.
+ * The #FsConference and #FsParticipant for this session are returned.
+ * The #FsConference should be removed from the pipeline.
+ */
+
+ signals[SESSION_INVALIDATED] =
+ g_signal_new ("session-invalidated",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ _tf_marshal_VOID__OBJECT_OBJECT,
+ G_TYPE_NONE, 2, FS_TYPE_CONFERENCE, FS_TYPE_PARTICIPANT);
+
+
+ signals[GET_CODEC_CONFIG] =
+ g_signal_new ("get-codec-config",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ _tf_marshal_BOXED__UINT,
+ FS_TYPE_CODEC_LIST, 1, G_TYPE_UINT);
+}
+
+static void
+tf_media_signalling_channel_init (TfMediaSignallingChannel *self)
+{
+ self->streams = g_ptr_array_new ();
+}
+
+static void
+cb_properties_changed (TpProxy *proxy G_GNUC_UNUSED,
+ const GPtrArray *structs,
+ gpointer user_data G_GNUC_UNUSED,
+ GObject *object)
+{
+ TfMediaSignallingChannel *self = TF_MEDIA_SIGNALLING_CHANNEL (object);
+ guint i;
+
+ for (i = 0; i < structs->len; i++)
+ {
+ GValueArray *pair = g_ptr_array_index (structs, i);
+ guint id;
+ GValue *value;
+
+ id = g_value_get_uint (g_value_array_get_nth (pair, 0));
+ value = g_value_get_boxed (g_value_array_get_nth (pair, 1));
+
+ if (id == self->prop_id_nat_traversal)
+ {
+ g_free (self->nat_props.nat_traversal);
+ self->nat_props.nat_traversal = NULL;
+
+ if (G_VALUE_HOLDS_STRING (value) && g_value_get_string (value)[0])
+ self->nat_props.nat_traversal = g_value_dup_string (value);
+ }
+ else if (id == self->prop_id_stun_server)
+ {
+ g_free (self->nat_props.stun_server);
+ self->nat_props.stun_server = NULL;
+
+ if (G_VALUE_HOLDS_STRING (value) && g_value_get_string (value)[0])
+ self->nat_props.stun_server = g_value_dup_string (value);
+ }
+ else if (id == self->prop_id_gtalk_p2p_relay_token)
+ {
+ g_free (self->nat_props.relay_token);
+ self->nat_props.relay_token = NULL;
+
+ if (G_VALUE_HOLDS_STRING (value) && g_value_get_string (value)[0])
+ self->nat_props.relay_token = g_value_dup_string (value);
+ }
+ else if (id == self->prop_id_stun_port)
+ {
+ self->nat_props.stun_port = 0;
+
+ if (G_VALUE_HOLDS_UINT (value))
+ self->nat_props.stun_port = g_value_get_uint (value);
+ }
+ }
+}
+
+static void
+cb_properties_got (TpProxy *proxy,
+ const GPtrArray *structs,
+ const GError *error,
+ gpointer user_data,
+ GObject *object)
+{
+ if (error != NULL)
+ {
+ g_warning ("GetProperties(): %s", error->message);
+ return;
+ }
+ else
+ {
+ cb_properties_changed (proxy, structs, user_data, object);
+ }
+}
+
+static void
+cb_properties_listed (TpProxy *proxy,
+ const GPtrArray *structs,
+ const GError *error,
+ gpointer user_data G_GNUC_UNUSED,
+ GObject *object)
+{
+ TfMediaSignallingChannel *self = TF_MEDIA_SIGNALLING_CHANNEL (object);
+ guint i;
+ GArray *get_properties;
+
+ if (error != NULL)
+ {
+ g_warning ("ListProperties(): %s", error->message);
+ return;
+ }
+
+ get_properties = g_array_sized_new (FALSE, FALSE, sizeof (guint), 4);
+
+ for (i = 0; i < structs->len; i++)
+ {
+ GValueArray *spec = g_ptr_array_index (structs, i);
+ guint id, flags;
+ const gchar *name, *type;
+ gboolean want = FALSE;
+
+ id = g_value_get_uint (g_value_array_get_nth (spec, 0));
+ name = g_value_get_string (g_value_array_get_nth (spec, 1));
+ type = g_value_get_string (g_value_array_get_nth (spec, 2));
+ flags = g_value_get_uint (g_value_array_get_nth (spec, 3));
+
+ if (!tp_strdiff (name, "nat-traversal") && !tp_strdiff (type, "s"))
+ {
+ self->prop_id_nat_traversal = id;
+ want = TRUE;
+ }
+ else if (!tp_strdiff (name, "stun-server") && !tp_strdiff (type, "s"))
+ {
+ self->prop_id_stun_server = id;
+ want = TRUE;
+ }
+ else if (!tp_strdiff (name, "gtalk-p2p-relay-token") &&
+ !tp_strdiff (type, "s"))
+ {
+ self->prop_id_gtalk_p2p_relay_token = id;
+ want = TRUE;
+ }
+ else if (!tp_strdiff (name, "stun-port") &&
+ (!tp_strdiff (type, "u") || !tp_strdiff (type, "q")))
+ {
+ self->prop_id_stun_port = id;
+ want = TRUE;
+ }
+ else
+ {
+ g_debug ("Ignoring unrecognised property %s of type %s", name, type);
+ }
+
+ if (want && (flags & TP_PROPERTY_FLAG_READ))
+ g_array_append_val (get_properties, id);
+ }
+
+ if (get_properties->len > 0)
+ tp_cli_properties_interface_call_get_properties (proxy, -1,
+ get_properties, cb_properties_got, NULL, NULL, object);
+
+ g_array_free (get_properties, TRUE);
+}
+
+
+TfMediaSignallingChannel *
+tf_media_signalling_channel_new (TpChannel *channel)
+{
+ TfMediaSignallingChannel *self = g_object_new (
+ TF_TYPE_MEDIA_SIGNALLING_CHANNEL, NULL);
+
+ self->channel_proxy = channel;
+
+ if (!tp_proxy_has_interface_by_id (TP_PROXY (channel),
+ TP_IFACE_QUARK_PROPERTIES_INTERFACE))
+ {
+ /* no point doing properties manipulation on a channel with none */
+ g_message ("Channel has no properties: %s",
+ tp_proxy_get_object_path (TP_PROXY (channel)));
+ }
+ else
+ {
+ /* FIXME: it'd be good to use the replacement for TpPropsIface, when it
+ * exists */
+ tp_cli_properties_interface_connect_to_properties_changed (channel,
+ cb_properties_changed, NULL, NULL, (GObject *) self, NULL);
+ tp_cli_properties_interface_call_list_properties (channel, -1,
+ cb_properties_listed, NULL, NULL, (GObject *) self);
+ }
+
+ tp_cli_channel_interface_media_signalling_connect_to_new_session_handler
+ (channel, new_media_session_handler, NULL, NULL, (GObject *) self,
+ NULL);
+ tp_cli_channel_interface_media_signalling_call_get_session_handlers
+ (channel, -1, get_session_handlers_reply, NULL, NULL,
+ (GObject *) self);
+
+ return self;
+}
+
+
+static void
+tf_media_signalling_channel_dispose (GObject *object)
+{
+ TfMediaSignallingChannel *self = TF_MEDIA_SIGNALLING_CHANNEL (object);
+
+ g_debug (G_STRFUNC);
+
+
+ if (self->streams)
+ {
+ guint i;
+
+ for (i = 0; i < self->streams->len; i++)
+ {
+ GObject *obj = g_ptr_array_index (self->streams, i);
+
+ if (obj != NULL)
+ {
+ tf_stream_error (TF_STREAM (obj),
+ TP_MEDIA_STREAM_ERROR_UNKNOWN,
+ "UI stopped channel");
+
+ /* this first one covers both error and closed */
+ g_signal_handlers_disconnect_by_func (obj,
+ stream_closed_cb, self);
+
+ g_object_unref (obj);
+ }
+ }
+
+ g_ptr_array_free (self->streams, TRUE);
+ self->streams = NULL;
+ }
+
+ if (self->session)
+ {
+ g_signal_handlers_disconnect_by_func (self->session, new_stream_cb, self);
+
+ g_object_unref (self->session);
+ self->session = NULL;
+ }
+
+ g_free (self->nat_props.nat_traversal);
+ self->nat_props.nat_traversal = NULL;
+
+ g_free (self->nat_props.stun_server);
+ self->nat_props.stun_server = NULL;
+
+ g_free (self->nat_props.relay_token);
+ self->nat_props.relay_token = NULL;
+
+ if (G_OBJECT_CLASS (tf_media_signalling_channel_parent_class)->dispose)
+ G_OBJECT_CLASS (tf_media_signalling_channel_parent_class)->dispose (object);
+}
+
+
+static void
+stream_closed_cb (TfStream *stream,
+ gpointer user_data)
+{
+ TfMediaSignallingChannel *self = TF_MEDIA_SIGNALLING_CHANNEL (user_data);
+ guint stream_id;
+
+ g_object_get (stream, "stream-id", &stream_id, NULL);
+
+ g_assert (stream == g_ptr_array_index (self->streams, stream_id));
+
+ g_object_unref (stream);
+ g_ptr_array_index (self->streams, stream_id) = NULL;
+}
+
+static void
+stream_created_cb (TfStream *stream, gpointer user_data)
+{
+ TfMediaSignallingChannel *self = user_data;
+
+ g_signal_emit (self, signals[STREAM_CREATED], 0, stream);
+
+ _tf_stream_try_sending_codecs (stream);
+}
+
+static void
+new_stream_cb (TfSession *session,
+ gchar *object_path,
+ guint stream_id,
+ TpMediaStreamType media_type,
+ TpMediaStreamDirection direction,
+ gpointer user_data)
+{
+ TfMediaSignallingChannel *self = TF_MEDIA_SIGNALLING_CHANNEL (user_data);
+ TfStream *stream;
+ FsConference *fs_conference;
+ FsParticipant *fs_participant;
+ TpProxy *channel_as_proxy = (TpProxy *) self->channel_proxy;
+ TpMediaStreamHandler *proxy;
+ GList *local_codec_config = NULL;
+
+ proxy = tp_media_stream_handler_new (channel_as_proxy->dbus_daemon,
+ channel_as_proxy->bus_name, object_path, NULL);
+
+ if (proxy == NULL)
+ {
+ gchar *str = g_strdup_printf ("failed to construct TpMediaStreamHandler:"
+ " bad object path '%s'?", object_path);
+ g_warning ("%s", str);
+ tf_media_signalling_channel_error (self, TP_MEDIA_STREAM_ERROR_UNKNOWN,
+ str);
+ g_free (str);
+ return;
+ }
+
+ g_signal_emit (self, signals[GET_CODEC_CONFIG], 0,
+ media_type,
+ &local_codec_config);
+
+ g_object_get (session,
+ "farstream-conference", &fs_conference,
+ "farstream-participant", &fs_participant,
+ NULL);
+
+ stream = _tf_stream_new ((gpointer) self, fs_conference,
+ fs_participant, proxy, stream_id, media_type, direction,
+ &self->nat_props, local_codec_config,
+ stream_created_cb);
+
+ fs_codec_list_destroy (local_codec_config);
+
+ g_object_unref (proxy);
+ g_object_unref (fs_conference);
+ g_object_unref (fs_participant);
+
+ if (self->streams->len <= stream_id)
+ g_ptr_array_set_size (self->streams, stream_id + 1);
+
+ if (g_ptr_array_index (self->streams, stream_id) != NULL)
+ {
+ g_warning ("connection manager gave us a new stream with existing id "
+ "%u, sending error!", stream_id);
+
+ tf_stream_error (stream, TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR,
+ "already have a stream with this ID");
+
+ g_object_unref (stream);
+
+ return;
+ }
+
+ g_ptr_array_index (self->streams, stream_id) = stream;
+ g_signal_connect (stream, "closed", G_CALLBACK (stream_closed_cb),
+ self);
+}
+
+static void
+session_invalidated_cb (TfSession *session, gpointer user_data)
+{
+ TfMediaSignallingChannel *self = TF_MEDIA_SIGNALLING_CHANNEL (user_data);
+
+ g_assert (session == self->session);
+
+ g_signal_handlers_disconnect_by_func (session, new_stream_cb, self);
+
+ g_object_unref (session);
+ self->session = NULL;
+}
+
+static void
+add_session (TfMediaSignallingChannel *self,
+ const gchar *object_path,
+ const gchar *session_type)
+{
+ GError *error = NULL;
+ TpProxy *channel_as_proxy = (TpProxy *) self->channel_proxy;
+ TpMediaSessionHandler *proxy;
+ FsConference *conf = NULL;
+
+ g_debug ("adding session handler %s, type %s", object_path, session_type);
+
+ g_assert (self->session == NULL);
+
+ proxy = tp_media_session_handler_new (channel_as_proxy->dbus_daemon,
+ channel_as_proxy->bus_name, object_path, &error);
+
+ if (proxy == NULL)
+ {
+ g_prefix_error (&error,"failed to construct TpMediaSessionHandler: ");
+ g_warning ("%s", error->message);
+ tf_media_signalling_channel_error (self, TP_MEDIA_STREAM_ERROR_UNKNOWN,
+ error->message);
+ g_error_free (error);
+ return;
+ }
+
+ self->session = _tf_session_new (proxy, session_type, &error);
+
+ if (self->session == NULL)
+ {
+ g_prefix_error (&error, "failed to create session: ");
+ g_warning ("%s", error->message);
+ tf_media_signalling_channel_error (self, fserror_to_tperror (error), error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_signal_connect (self->session, "new-stream", G_CALLBACK (new_stream_cb),
+ self);
+ g_signal_connect (self->session, "invalidated",
+ G_CALLBACK (session_invalidated_cb), self);
+
+ g_object_get (self->session,
+ "farstream-conference", &conf,
+ NULL);
+
+ g_signal_emit (self, signals[SESSION_CREATED], 0, conf);
+ g_object_unref (conf);
+}
+
+static void
+new_media_session_handler (TpChannel *channel_proxy G_GNUC_UNUSED,
+ const gchar *session_handler_path,
+ const gchar *type,
+ gpointer user_data G_GNUC_UNUSED,
+ GObject *weak_object)
+{
+ TfMediaSignallingChannel *self = TF_MEDIA_SIGNALLING_CHANNEL (weak_object);
+
+ /* Ignore NewMediaSessionHandler until we've had a reply to
+ * GetSessionHandlers; otherwise, if the two cross over in mid-flight,
+ * we think the CM is asking us to add the same session twice, and get
+ * very confused
+ */
+
+ if (!self->got_sessions)
+ return;
+
+ add_session (self, session_handler_path, type);
+}
+
+
+static void
+get_session_handlers_reply (TpChannel *channel_proxy G_GNUC_UNUSED,
+ const GPtrArray *session_handlers,
+ const GError *error,
+ gpointer user_data G_GNUC_UNUSED,
+ GObject *weak_object)
+{
+ TfMediaSignallingChannel *self = TF_MEDIA_SIGNALLING_CHANNEL (weak_object);
+
+ if (error)
+ {
+ g_critical ("Error calling GetSessionHandlers: %s", error->message);
+ return;
+ }
+
+ if (session_handlers->len == 0)
+ {
+ g_debug ("GetSessionHandlers returned 0 sessions");
+ }
+ else if (session_handlers->len == 1)
+ {
+ GValueArray *session;
+ GValue *obj;
+ GValue *type;
+
+ g_debug ("GetSessionHandlers replied: ");
+
+ session = g_ptr_array_index (session_handlers, 0);
+ obj = g_value_array_get_nth (session, 0);
+ type = g_value_array_get_nth (session, 1);
+
+ g_assert (G_VALUE_TYPE (obj) == DBUS_TYPE_G_OBJECT_PATH);
+ g_assert (G_VALUE_HOLDS_STRING (type));
+
+ g_debug (" - session %s", (char *)g_value_get_boxed (obj));
+ g_debug (" type %s", g_value_get_string (type));
+
+ add_session (self,
+ g_value_get_boxed (obj), g_value_get_string (type));
+ }
+ else
+ {
+ g_error ("Got more than one session");
+ }
+
+ self->got_sessions = TRUE;
+}
+
+
+/**
+ * tf_media_signalling_channel_bus_message:
+ * @channel: A #TfMediaSignallingChannel
+ * @message: A #GstMessage received from the bus
+ *
+ * You must call this function on call messages received on the async bus.
+ * #GstMessages are not modified.
+ *
+ * Returns: %TRUE if the message has been handled, %FALSE otherwise
+ */
+
+gboolean
+tf_media_signalling_channel_bus_message (TfMediaSignallingChannel *channel,
+ GstMessage *message)
+{
+ guint i;
+ gboolean ret = FALSE;
+
+ if (channel->session == NULL)
+ return FALSE;
+
+ if (_tf_session_bus_message (channel->session, message))
+ ret = TRUE;
+
+ for (i = 0; i < channel->streams->len; i++)
+ {
+ TfStream *stream = g_ptr_array_index (
+ channel->streams, i);
+
+ if (stream != NULL)
+ if (_tf_stream_bus_message (stream, message))
+ ret = TRUE;
+ }
+
+ return ret;
+}
+
+/**
+ * tf_media_signalling_channel_lookup_stream:
+ * @chan: a #TfMediaSignallingChannel
+ * @stream_id: the stream id to look for
+ *
+ * Finds the stream with the specified id if it exists.
+ *
+ * Returns: a #TfStream or %NULL
+ */
+
+TfStream *
+tf_media_signalling__channel_lookup_stream (TfMediaSignallingChannel *chan,
+ guint stream_id)
+{
+ if (stream_id >= chan->streams->len)
+ return NULL;
+
+ return g_ptr_array_index (chan->streams, stream_id);
+}
+
+
+
+/**
+ * tf_media_signalling_channel_error:
+ * @chan: a #TfMediaSignallingChannel
+ * @error: the error number of type #TpMediaStreamError
+ * @message: the error message
+ *
+ * Stops the channel and all stream related to it and sends an error to the
+ * connection manager.
+ */
+
+static void
+tf_media_signalling_channel_error (TfMediaSignallingChannel *chan,
+ TpMediaStreamError error,
+ const gchar *message)
+{
+ guint i;
+
+ for (i = 0; i < chan->streams->len; i++)
+ if (g_ptr_array_index (chan->streams, i) != NULL)
+ tf_stream_error (g_ptr_array_index (chan->streams, i),
+ error, message);
+}
diff --git a/farstream/telepathy-farstream/media-signalling-channel.h b/farstream/telepathy-farstream/media-signalling-channel.h
new file mode 100644
index 000000000..3e9f1a2a5
--- /dev/null
+++ b/farstream/telepathy-farstream/media-signalling-channel.h
@@ -0,0 +1,87 @@
+/*
+ * media-signalling-channel.h - Source for TfMediaSignallingChannel
+ * Copyright (C) 2006-2011 Collabora Ltd.
+ * Copyright (C) 2006-2011 Nokia Corporation
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#ifndef __TF_MEDIA_SIGNALLING_CHANNEL_H__
+#define __TF_MEDIA_SIGNALLING_CHANNEL_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/channel.h>
+#include "stream.h"
+
+G_BEGIN_DECLS
+
+#define TF_TYPE_MEDIA_SIGNALLING_CHANNEL tf_media_signalling_channel_get_type()
+
+#define TF_MEDIA_SIGNALLING_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TF_TYPE_MEDIA_SIGNALLING_CHANNEL, TfMediaSignallingChannel))
+
+#define TF_MEDIA_SIGNALLING_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ TF_TYPE_MEDIA_SIGNALLING_CHANNEL, TfMediaSignallingChannelClass))
+
+#define TF_IS_MEDIA_SIGNALLING_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_MEDIA_SIGNALLING_CHANNEL))
+
+#define TF_IS_MEDIA_SIGNALLING_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_MEDIA_SIGNALLING_CHANNEL))
+
+#define TF_MEDIA_SIGNALLING_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TF_TYPE_MEDIA_SIGNALLING_CHANNEL, TfMediaSignallingChannelClass))
+
+typedef struct _TfMediaSignallingChannelPrivate TfMediaSignallingChannelPrivate;
+
+/**
+ * TfMediaSignallingChannel:
+ *
+ * All members of the object are private
+ */
+
+typedef struct _TfMediaSignallingChannel TfMediaSignallingChannel;
+
+/**
+ * TfMediaSignallingChannelClass:
+ * @parent_class: the parent #GObjecClass
+ *
+ * There are no overridable functions
+ */
+
+typedef struct _TfMediaSignallingChannelClass TfMediaSignallingChannelClass;
+
+GType tf_media_signalling_channel_get_type (void);
+
+TfMediaSignallingChannel *tf_media_signalling_channel_new (
+ TpChannel *channel_proxy);
+
+TfStream *tf_media_signalling_channel_lookup_stream (
+ TfMediaSignallingChannel *chan,
+ guint stream_id);
+
+gboolean tf_media_signalling_channel_bus_message (
+ TfMediaSignallingChannel *channel,
+ GstMessage *message);
+
+G_END_DECLS
+
+#endif /* __TF_MEDIA_SIGNALLING_CHANNEL_H__ */
+
diff --git a/farstream/telepathy-farstream/media-signalling-content.c b/farstream/telepathy-farstream/media-signalling-content.c
new file mode 100644
index 000000000..34c3ebc04
--- /dev/null
+++ b/farstream/telepathy-farstream/media-signalling-content.c
@@ -0,0 +1,324 @@
+/*
+ * media-signalling-content.c - Source for TfMediaSignallingContent
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * SECTION:media-signalling-content
+
+ * @short_description: Handle the MediaSignalling interface on a Channel
+ *
+ * This class handles the
+ * org.freedesktop.Telepathy.Channel.Interface.MediaSignalling on a
+ * channel using Farstream.
+ */
+
+
+#include "media-signalling-content.h"
+
+#include <farstream/fs-conference.h>
+#include <farstream/fs-utils.h>
+
+#include <stdarg.h>
+#include <string.h>
+
+#include <telepathy-glib/proxy-subclass.h>
+
+#include "tf-signals-marshal.h"
+#include "utils.h"
+
+
+struct _TfMediaSignallingContent {
+ TfContent parent;
+
+ TfMediaSignallingChannel *channel;
+ TfStream *stream;
+ guint handle;
+
+ gboolean receiving;
+};
+
+struct _TfMediaSignallingContentClass{
+ TfContentClass parent_class;
+};
+
+G_DEFINE_TYPE (TfMediaSignallingContent, tf_media_signalling_content,
+ TF_TYPE_CONTENT)
+
+
+enum
+{
+ PROP_TF_CHANNEL = 1,
+ PROP_FS_CONFERENCE,
+ PROP_FS_SESSION,
+ PROP_SINK_PAD,
+ PROP_MEDIA_TYPE,
+ PROP_STREAM_ID
+};
+
+enum
+{
+ SIGNAL_COUNT
+};
+
+
+// static guint signals[SIGNAL_COUNT] = {0};
+
+static void
+tf_media_signalling_content_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void tf_media_signalling_content_error (TfContent *content,
+ guint reason, /* TfFutureContentRemovalReason */
+ const gchar *detailed_reason,
+ const gchar *message);
+
+static GstIterator * tf_media_signalling_content_iterate_src_pads (
+ TfContent *content,
+ guint *handles,
+ guint handle_count);
+
+static void src_pad_added (TfStream *stream, GstPad *pad, FsCodec *codec,
+ TfMediaSignallingContent *self);
+
+static gboolean request_resource (TfStream *stream, guint direction,
+ TfMediaSignallingContent *self);
+static void free_resource (TfStream *stream, guint direction,
+ TfMediaSignallingContent *self);
+static void restart_source (TfStream *stream, TfMediaSignallingContent *self);
+
+
+static void
+tf_media_signalling_content_class_init (TfMediaSignallingContentClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ TfContentClass *content_class = TF_CONTENT_CLASS (klass);
+
+ object_class->get_property = tf_media_signalling_content_get_property;
+
+ content_class->content_error = tf_media_signalling_content_error;
+ content_class->iterate_src_pads =
+ tf_media_signalling_content_iterate_src_pads;
+
+ g_object_class_override_property (object_class, PROP_TF_CHANNEL,
+ "tf-channel");
+ g_object_class_override_property (object_class, PROP_FS_CONFERENCE,
+ "fs-conference");
+ g_object_class_override_property (object_class, PROP_FS_SESSION,
+ "fs-session");
+ g_object_class_override_property (object_class, PROP_SINK_PAD,
+ "sink-pad");
+ g_object_class_override_property (object_class, PROP_MEDIA_TYPE,
+ "media-type");
+
+ g_object_class_install_property (object_class, PROP_STREAM_ID,
+ g_param_spec_uint ("stream-id",
+ "stream ID",
+ "A number identifying this stream within "
+ "its channel.",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+}
+
+
+
+static void
+tf_media_signalling_content_init (TfMediaSignallingContent *self)
+{
+ self->receiving = TRUE;
+}
+
+
+static void
+tf_media_signalling_content_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TfMediaSignallingContent *self = TF_MEDIA_SIGNALLING_CONTENT (object);
+
+ switch (property_id)
+ {
+ case PROP_TF_CHANNEL:
+ g_value_set_object (value, self->channel);
+ break;
+ case PROP_FS_CONFERENCE:
+ g_object_get_property (G_OBJECT (self->stream),
+ "farstream-conference", value);
+ break;
+ case PROP_FS_SESSION:
+ g_object_get_property (G_OBJECT (self->stream),
+ "farstream-session", value);
+ break;
+ case PROP_SINK_PAD:
+ g_object_get_property (G_OBJECT (self->stream),
+ "sink-pad", value);
+ break;
+ case PROP_MEDIA_TYPE:
+ g_object_get_property (G_OBJECT (self->stream),
+ "media-type", value);
+ break;
+ case PROP_STREAM_ID:
+ g_object_get_property (G_OBJECT (self->stream),
+ "stream-id", value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+TfMediaSignallingContent *
+tf_media_signalling_content_new (
+ TfMediaSignallingChannel *media_signalling_channel,
+ TfStream *stream,
+ guint handle)
+{
+ TfMediaSignallingContent *self =
+ g_object_new (TF_TYPE_MEDIA_SIGNALLING_CONTENT, NULL);
+ GstElement *conf;
+ FsSession *session;
+ GList *codec_prefs;
+
+ self->channel = media_signalling_channel;
+ self->stream = stream;
+ self->handle = handle;
+
+ tp_g_signal_connect_object (stream, "src-pad-added",
+ G_CALLBACK (src_pad_added), G_OBJECT (self), 0);
+ tp_g_signal_connect_object (stream, "request-resource",
+ G_CALLBACK (request_resource), G_OBJECT (self), 0);
+ tp_g_signal_connect_object (stream, "free-resource",
+ G_CALLBACK (free_resource), G_OBJECT (self), 0);
+ tp_g_signal_connect_object (stream, "restart-source",
+ G_CALLBACK (restart_source), G_OBJECT (self), 0);
+
+ g_object_get (stream,
+ "farstream-conference", &conf,
+ "farstream-session", &session,
+ NULL);
+
+ codec_prefs = fs_utils_get_default_codec_preferences (conf);
+ if (!fs_session_set_codec_preferences (session, codec_prefs, NULL))
+ tf_stream_error (stream, TP_MEDIA_STREAM_ERROR_MEDIA_ERROR,
+ "Default codec preferences disabled all codecs");
+
+ fs_codec_list_destroy (codec_prefs);
+ g_object_unref (session);
+ gst_object_unref (conf);
+
+ return self;
+}
+
+static void
+src_pad_added (TfStream *stream, GstPad *pad, FsCodec *codec,
+ TfMediaSignallingContent *self)
+{
+ FsStream *fs_stream;
+
+ g_object_get (stream, "farstream-stream", &fs_stream, NULL);
+ _tf_content_emit_src_pad_added (TF_CONTENT (self), self->handle, fs_stream,
+ pad, codec);
+ g_object_unref (fs_stream);
+}
+
+static gboolean
+request_resource (TfStream *stream, guint direction,
+ TfMediaSignallingContent *self)
+{
+ if (direction & TP_MEDIA_STREAM_DIRECTION_SEND)
+ return _tf_content_start_sending (TF_CONTENT (self));
+
+ if (!self->receiving && direction & TP_MEDIA_STREAM_DIRECTION_RECEIVE)
+ {
+ guint handles[2] = { self->handle, 0};
+
+ self->receiving = _tf_content_start_receiving (TF_CONTENT (self),
+ handles, 1);
+
+ return self->receiving;
+ }
+
+ return FALSE;
+}
+
+
+static void
+free_resource (TfStream *stream, guint direction,
+ TfMediaSignallingContent *self)
+{
+ guint handles[2] = { self->handle, 0};
+
+ if (direction & TP_MEDIA_STREAM_DIRECTION_SEND)
+ _tf_content_stop_sending (TF_CONTENT (self));
+
+ if (self->receiving && direction & TP_MEDIA_STREAM_DIRECTION_RECEIVE)
+ {
+ _tf_content_stop_receiving (TF_CONTENT (self), handles, 1);
+ self->receiving = FALSE;
+ }
+}
+
+static void
+restart_source (TfStream *stream, TfMediaSignallingContent *self)
+{
+ g_signal_emit_by_name (self, "restart-source");
+}
+
+static void
+tf_media_signalling_content_error (TfContent *content,
+ guint reason, /* TfFutureContentRemovalReason */
+ const gchar *detailed_reason,
+ const gchar *message)
+{
+ TfMediaSignallingContent *self = TF_MEDIA_SIGNALLING_CONTENT (content);
+ TpMediaStreamError stream_error;
+
+ switch (reason)
+ {
+ case TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR:
+ stream_error = TP_MEDIA_STREAM_ERROR_MEDIA_ERROR;
+ break;
+ default:
+ stream_error = TP_MEDIA_STREAM_ERROR_UNKNOWN;
+ }
+
+ tf_stream_error (self->stream, stream_error, message);
+}
+
+static GstIterator *
+tf_media_signalling_content_iterate_src_pads (TfContent *content,
+ guint *handles,
+ guint handle_count)
+{
+ TfMediaSignallingContent *self = TF_MEDIA_SIGNALLING_CONTENT (content);
+ GstIterator *iter = NULL;
+ FsStream *fs_stream;
+
+ g_return_val_if_fail (handle_count <= 1, NULL);
+
+ g_object_get (self->stream, "farstream-stream", &fs_stream, NULL);
+ iter = fs_stream_iterate_src_pads (fs_stream);
+ g_object_unref (fs_stream);
+
+ return iter;
+}
diff --git a/farstream/telepathy-farstream/media-signalling-content.h b/farstream/telepathy-farstream/media-signalling-content.h
new file mode 100644
index 000000000..2bb7fd4c4
--- /dev/null
+++ b/farstream/telepathy-farstream/media-signalling-content.h
@@ -0,0 +1,86 @@
+/*
+ * media-signalling-content.h - Source for TfMediaSignallingContent
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __TF_CALL_CONTENT_H__
+#define __TF_CALL_CONTENT_H__
+
+#include <glib-object.h>
+
+#include <gst/gst.h>
+#include <telepathy-glib/channel.h>
+
+#include "extensions/extensions.h"
+#include "media-signalling-channel.h"
+#include "content.h"
+#include "content-priv.h"
+
+G_BEGIN_DECLS
+
+#define TF_TYPE_MEDIA_SIGNALLING_CONTENT tf_media_signalling_content_get_type()
+
+#define TF_MEDIA_SIGNALLING_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TF_TYPE_MEDIA_SIGNALLING_CONTENT, TfMediaSignallingContent))
+
+#define TF_MEDIA_SIGNALLING_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ TF_TYPE_MEDIA_SIGNALLING_CONTENT, TfMediaSignallingContentClass))
+
+#define TF_IS_MEDIA_SIGNALLING_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_MEDIA_SIGNALLING_CONTENT))
+
+#define TF_IS_MEDIA_SIGNALLING_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_MEDIA_SIGNALLING_CONTENT))
+
+#define TF_MEDIA_SIGNALLING_CONTENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TF_TYPE_MEDIA_SIGNALLING_CONTENT, TfMediaSignallingContentClass))
+
+typedef struct _TfMediaSignallingContentPrivate TfMediaSignallingContentPrivate;
+
+/**
+ * TfMediaSignallingContent:
+ *
+ * All members of the object are private
+ */
+
+typedef struct _TfMediaSignallingContent TfMediaSignallingContent;
+
+/**
+ * TfMediaSignallingContentClass:
+ * @parent_class: the parent #GObjecClass
+ *
+ * There are no overridable functions
+ */
+
+typedef struct _TfMediaSignallingContentClass TfMediaSignallingContentClass;
+
+GType tf_media_signalling_content_get_type (void);
+
+TfMediaSignallingContent *
+tf_media_signalling_content_new (
+ TfMediaSignallingChannel *media_signalling_channel,
+ TfStream *stream,
+ guint handle);
+
+G_END_DECLS
+
+#endif /* __TF_MEDIA_SIGNALLING_CONTENT_H__ */
+
diff --git a/farstream/telepathy-farstream/session-priv.h b/farstream/telepathy-farstream/session-priv.h
new file mode 100644
index 000000000..af69c4de7
--- /dev/null
+++ b/farstream/telepathy-farstream/session-priv.h
@@ -0,0 +1,69 @@
+#ifndef __TF_SESSION_H__
+#define __TF_SESSION_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/media-interfaces.h>
+
+G_BEGIN_DECLS
+
+#define TF_TYPE_SESSION _tf_session_get_type()
+
+#define TF_SESSION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TF_TYPE_SESSION, TfSession))
+
+#define TF_SESSION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ TF_TYPE_SESSION, TfSessionClass))
+
+#define TF_IS_SESSION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ TF_TYPE_SESSION))
+
+#define TF_IS_SESSION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ TF_TYPE_SESSION))
+
+#define TF_SESSION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TF_TYPE_SESSION, TfSessionClass))
+
+typedef struct _TfSessionPrivate TfSessionPrivate;
+
+
+/**
+ * TfSession:
+ *
+ * All members of the object are private
+ */
+
+typedef struct {
+ GObject parent;
+
+ TfSessionPrivate *priv;
+} TfSession;
+
+/**
+ * TfSessionClass:
+ *
+ * There are no overridable functions
+ */
+
+typedef struct {
+ GObjectClass parent_class;
+} TfSessionClass;
+
+GType _tf_session_get_type (void);
+
+TfSession *
+_tf_session_new (TpMediaSessionHandler *proxy,
+ const gchar *conference_type,
+ GError **error);
+
+gboolean _tf_session_bus_message (TfSession *session,
+ GstMessage *message);
+
+G_END_DECLS
+
+#endif /* __TF_SESSION_H__ */
+
diff --git a/farstream/telepathy-farstream/session.c b/farstream/telepathy-farstream/session.c
new file mode 100644
index 000000000..42cb4dbd3
--- /dev/null
+++ b/farstream/telepathy-farstream/session.c
@@ -0,0 +1,456 @@
+/*
+ * session.c - Source for TfSession
+ * Copyright (C) 2006-2007 Collabora Ltd.
+ * Copyright (C) 2006-2007 Nokia Corporation
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/interfaces.h>
+
+#include <farstream/fs-conference.h>
+
+#include "session-priv.h"
+#include "tf-signals-marshal.h"
+
+G_DEFINE_TYPE (TfSession, _tf_session, G_TYPE_OBJECT);
+
+struct _TfSessionPrivate
+{
+ GError *construction_error;
+
+ gchar *session_type;
+ FsConference *fs_conference;
+ FsParticipant *fs_participant;
+
+ TpMediaSessionHandler *session_handler_proxy;
+};
+
+enum
+{
+ PROP_PROXY = 1,
+ PROP_SESSION_TYPE,
+ PROP_FARSTREAM_CONFERENCE,
+ PROP_FARSTREAM_PARTICIPANT,
+};
+
+enum
+{
+ NEW_STREAM,
+ INVALIDATED,
+ SIGNAL_COUNT
+};
+
+static guint signals[SIGNAL_COUNT] = { 0 };
+
+static void
+_tf_session_init (TfSession *self)
+{
+ TfSessionPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TF_TYPE_SESSION, TfSessionPrivate);
+
+ self->priv = priv;
+}
+
+static void
+_tf_session_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TfSession *self = TF_SESSION (object);
+
+ switch (property_id)
+ {
+ case PROP_SESSION_TYPE:
+ g_value_set_string (value, self->priv->session_type);
+ break;
+ case PROP_FARSTREAM_CONFERENCE:
+ g_value_set_object (value, self->priv->fs_conference);
+ break;
+ case PROP_FARSTREAM_PARTICIPANT:
+ g_value_set_object (value, self->priv->fs_participant);
+ break;
+ case PROP_PROXY:
+ g_value_set_object (value, self->priv->session_handler_proxy);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+_tf_session_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TfSession *self = TF_SESSION (object);
+
+ switch (property_id)
+ {
+ case PROP_SESSION_TYPE:
+ self->priv->session_type = g_value_dup_string (value);
+ break;
+ case PROP_PROXY:
+ self->priv->session_handler_proxy =
+ TP_MEDIA_SESSION_HANDLER (g_value_dup_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void new_media_stream_handler (TpMediaSessionHandler *proxy,
+ const gchar *stream_handler_path, guint id, guint media_type,
+ guint direction, gpointer user_data, GObject *object);
+
+static void dummy_callback (TpMediaSessionHandler *proxy, const GError *error,
+ gpointer user_data, GObject *object);
+
+static void invalidated_cb (TpMediaSessionHandler *proxy, guint domain,
+ gint code, gchar *message, gpointer user_data);
+
+static GObject *
+_tf_session_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ TfSession *self;
+ gchar *conftype;
+ GstElement *conf;
+
+ obj = G_OBJECT_CLASS (_tf_session_parent_class)->
+ constructor (type, n_props, props);
+ self = (TfSession *) obj;
+
+ conftype = g_strdup_printf ("fs%sconference", self->priv->session_type);
+ conf = gst_element_factory_make (conftype, NULL);
+ g_free (conftype);
+
+ if (!conf)
+ {
+ self->priv->construction_error = g_error_new (FS_ERROR,
+ FS_ERROR_CONSTRUCTION, "Invalid session type");
+ return obj;
+ }
+
+ self->priv->fs_conference = FS_CONFERENCE (gst_object_ref (conf));
+
+ self->priv->fs_participant =
+ fs_conference_new_participant (self->priv->fs_conference,
+ &self->priv->construction_error);
+
+ if (!self->priv->fs_participant)
+ return obj;
+
+ g_signal_connect (self->priv->session_handler_proxy, "invalidated",
+ G_CALLBACK (invalidated_cb), obj);
+
+
+ tp_cli_media_session_handler_connect_to_new_stream_handler
+ (self->priv->session_handler_proxy, new_media_stream_handler, NULL, NULL,
+ obj, NULL);
+
+ g_debug ("calling MediaSessionHandler::Ready");
+
+ tp_cli_media_session_handler_call_ready (self->priv->session_handler_proxy,
+ -1, dummy_callback, "Media.SessionHandler::Ready", NULL, NULL);
+
+ return obj;
+}
+
+static void
+_tf_session_dispose (GObject *object)
+{
+ TfSession *self = TF_SESSION (object);
+
+ g_debug (G_STRFUNC);
+
+ if (self->priv->session_handler_proxy)
+ {
+ TpMediaSessionHandler *tmp;
+
+ g_signal_handlers_disconnect_by_func (
+ self->priv->session_handler_proxy, invalidated_cb, self);
+
+ tmp = self->priv->session_handler_proxy;
+ self->priv->session_handler_proxy = NULL;
+ g_object_unref (tmp);
+ }
+
+ if (self->priv->fs_participant)
+ {
+ g_object_unref (self->priv->fs_participant);
+ self->priv->fs_participant = NULL;
+ }
+
+ if (self->priv->fs_conference)
+ {
+
+ gst_object_unref (self->priv->fs_conference);
+ self->priv->fs_conference = NULL;
+ }
+
+ g_free (self->priv->session_type);
+ self->priv->session_type = NULL;
+
+ if (G_OBJECT_CLASS (_tf_session_parent_class)->dispose)
+ G_OBJECT_CLASS (_tf_session_parent_class)->dispose (object);
+}
+
+static void
+_tf_session_class_init (TfSessionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (TfSessionPrivate));
+
+ object_class->set_property = _tf_session_set_property;
+ object_class->get_property = _tf_session_get_property;
+
+ object_class->constructor = _tf_session_constructor;
+
+ object_class->dispose = _tf_session_dispose;
+
+ g_object_class_install_property (object_class, PROP_SESSION_TYPE,
+ g_param_spec_string ("conference-type",
+ "Farstream conference type",
+ "Name of the Farstream conference type this "
+ "session will create (rtp, msn, etc).",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_FARSTREAM_CONFERENCE,
+ g_param_spec_object ("farstream-conference",
+ "Farstream conference",
+ "The Farstream conference to add to the pipeline",
+ FS_TYPE_CONFERENCE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_FARSTREAM_PARTICIPANT,
+ g_param_spec_object ("farstream-participant",
+ "Farstream participant",
+ "The Farstream participant for this session",
+ FS_TYPE_PARTICIPANT,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_PROXY,
+ g_param_spec_object ("proxy", "TpMediaSessionHandler proxy",
+ "The session handler proxy which this session interacts with.",
+ TP_TYPE_MEDIA_SESSION_HANDLER,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * TfSession::new-stream:
+ * @session: the #TfSession which has a new stream
+ * @object_path: The object-path of the new stream
+ * @stream_id: The stream id of the new strema
+ * @media_type: The media type of the new stream
+ * @direction: The direction of the new stream
+ *
+ * This is emitted when a new stream is created and should only be used
+ * by the #TfChannel.
+ * One should connect to the #TfChannel::channel-new-stream signal
+ * instead.
+ */
+
+ signals[NEW_STREAM] =
+ g_signal_new ("new-stream",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ _tf_marshal_VOID__BOXED_UINT_UINT_UINT,
+ G_TYPE_NONE, 4,
+ DBUS_TYPE_G_OBJECT_PATH, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT);
+ signals[INVALIDATED] =
+ g_signal_new ("invalidated",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+/* dummy callback handler for async calling calls with no return values */
+static void
+dummy_callback (TpMediaSessionHandler *proxy G_GNUC_UNUSED,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object G_GNUC_UNUSED)
+{
+ if (error != NULL)
+ {
+ g_warning ("Error calling %s: %s", (gchar *) user_data, error->message);
+ }
+}
+
+static void
+invalidated_cb (TpMediaSessionHandler *proxy G_GNUC_UNUSED,
+ guint domain G_GNUC_UNUSED,
+ gint code G_GNUC_UNUSED,
+ gchar *message G_GNUC_UNUSED,
+ gpointer user_data)
+{
+ TfSession *self = TF_SESSION (user_data);
+
+ if (self->priv->session_handler_proxy)
+ {
+ TpMediaSessionHandler *tmp;
+
+ tmp = self->priv->session_handler_proxy;
+ self->priv->session_handler_proxy = NULL;
+ g_object_unref (tmp);
+ }
+
+ g_signal_emit (self, signals[INVALIDATED], 0);
+}
+
+static void
+new_media_stream_handler (TpMediaSessionHandler *proxy G_GNUC_UNUSED,
+ const gchar *object_path,
+ guint stream_id,
+ guint media_type,
+ guint direction,
+ gpointer user_data G_GNUC_UNUSED,
+ GObject *object)
+{
+ TfSession *self = TF_SESSION (object);
+
+ g_debug ("New stream, stream_id=%d, media_type=%d, direction=%d",
+ stream_id, media_type, direction);
+
+ g_signal_emit (self, signals[NEW_STREAM], 0, object_path, stream_id,
+ media_type, direction);
+}
+
+TfSession *
+_tf_session_new (TpMediaSessionHandler *proxy,
+ const gchar *conference_type,
+ GError **error)
+{
+ TfSession *self;
+
+ g_return_val_if_fail (proxy != NULL, NULL);
+ g_return_val_if_fail (conference_type != NULL, NULL);
+
+ self = g_object_new (TF_TYPE_SESSION,
+ "proxy", proxy,
+ "conference-type", conference_type,
+ NULL);
+
+ if (self->priv->construction_error)
+ {
+ g_propagate_error (error, self->priv->construction_error);
+ g_object_unref (self);
+ return NULL;
+ }
+
+ return self;
+}
+
+/**
+ * _tf_session_bus_message:
+ * @session: A #TfSession
+ * @message: A #GstMessage received from the bus
+ *
+ * You must call this function on call messages received on the async bus.
+ * #GstMessages are not modified.
+ *
+ * Returns: %TRUE if the message has been handled, %FALSE otherwise
+ */
+
+gboolean
+_tf_session_bus_message (TfSession *session,
+ GstMessage *message)
+{
+ GError *error = NULL;
+ gchar *debug = NULL;
+
+ if (GST_MESSAGE_SRC (message) !=
+ GST_OBJECT_CAST (session->priv->fs_conference))
+ return FALSE;
+
+ switch (GST_MESSAGE_TYPE (message))
+ {
+ case GST_MESSAGE_WARNING:
+ gst_message_parse_warning (message, &error, &debug);
+
+ g_warning ("session: %s (%s)", error->message, debug);
+
+ g_error_free (error);
+ g_free (debug);
+ return TRUE;
+ case GST_MESSAGE_ERROR:
+ gst_message_parse_error (message, &error, &debug);
+
+ g_warning ("session ERROR: %s (%s)", error->message, debug);
+
+ tp_cli_media_session_handler_call_error (
+ session->priv->session_handler_proxy,
+ -1, 0, /* Not errors defined ??? */
+ error->message, NULL, /* Do I need a callback ? */
+ NULL, NULL, NULL);
+
+ g_error_free (error);
+ g_free (debug);
+ return TRUE;
+ case GST_MESSAGE_ELEMENT:
+ {
+ const GstStructure *s = gst_message_get_structure (message);
+
+ if (gst_structure_has_name (s, "farstream-error"))
+ {
+ GObject *object;
+ const GValue *value = NULL;
+
+ value = gst_structure_get_value (s, "src-object");
+ object = g_value_get_object (value);
+
+ if (object == G_OBJECT (session->priv->fs_participant))
+ {
+ const gchar *msg;
+ FsError errorno;
+ GEnumClass *enumclass;
+ GEnumValue *enumvalue;
+
+ value = gst_structure_get_value (s, "error-no");
+ errorno = g_value_get_enum (value);
+ msg = gst_structure_get_string (s, "error-msg");
+
+ enumclass = g_type_class_ref (FS_TYPE_ERROR);
+ enumvalue = g_enum_get_value (enumclass, errorno);
+ g_warning ("participant error (%s (%d)): %s",
+ enumvalue->value_nick, errorno, msg);
+ g_type_class_unref (enumclass);
+
+ tp_cli_media_session_handler_call_error (
+ session->priv->session_handler_proxy,
+ -1, 0, msg, NULL, NULL, NULL, NULL);
+ return TRUE;
+ }
+ }
+ }
+ default:
+ return FALSE;
+ }
+}
diff --git a/farstream/telepathy-farstream/stream-priv.h b/farstream/telepathy-farstream/stream-priv.h
new file mode 100644
index 000000000..3e82a180e
--- /dev/null
+++ b/farstream/telepathy-farstream/stream-priv.h
@@ -0,0 +1,75 @@
+#ifndef __TF_STREAM_PRIV_H__
+#define __TF_STREAM_PRIV_H__
+
+#include "stream.h"
+
+G_BEGIN_DECLS
+
+typedef struct _TfStreamPrivate TfStreamPrivate;
+
+/*
+ * TfStream:
+ * @parent: the parent #GObject
+ * @stream_id: the ID of the stream (READ-ONLY)
+ *
+ * All other members are privated
+ */
+
+struct _TfStream {
+ GObject parent;
+
+ /* Read-only */
+ guint stream_id;
+
+ /*< private >*/
+
+ TfStreamPrivate *priv;
+};
+
+/*
+ * TfStreamClass:
+ * @parent_class: the parent #GObjecClass
+ *
+ * There are no overridable functions
+ */
+
+struct _TfStreamClass {
+ GObjectClass parent_class;
+
+ /*< private >*/
+
+ gpointer unused[4];
+};
+
+
+typedef struct {
+ gchar *nat_traversal;
+ gchar *stun_server;
+ guint16 stun_port;
+ gchar *relay_token;
+} TfNatProperties;
+
+typedef void (NewStreamCreatedCb) (TfStream *stream, gpointer channel);
+
+TfStream *
+_tf_stream_new (gpointer channel,
+ FsConference *conference,
+ FsParticipant *participant,
+ TpMediaStreamHandler *proxy,
+ guint stream_id,
+ TpMediaStreamType media_type,
+ TpMediaStreamDirection direction,
+ TfNatProperties *nat_props,
+ GList *local_codecs_config,
+ NewStreamCreatedCb new_stream_created_cb);
+
+gboolean _tf_stream_bus_message (TfStream *stream,
+ GstMessage *message);
+
+void _tf_stream_try_sending_codecs (TfStream *stream);
+
+TpMediaStreamError fserror_to_tperror (GError *error);
+
+G_END_DECLS
+
+#endif /* __TF_STREAM_PRIV_H__ */
diff --git a/farstream/telepathy-farstream/stream.c b/farstream/telepathy-farstream/stream.c
new file mode 100644
index 000000000..2e39ff845
--- /dev/null
+++ b/farstream/telepathy-farstream/stream.c
@@ -0,0 +1,3097 @@
+/*
+ * stream.c - Source for TfStream
+ * Copyright (C) 2006-2008 Collabora Ltd.
+ * Copyright (C) 2006-2008 Nokia Corporation
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * SECTION:stream
+ * @short_description: Handles a media Stream
+ *
+ * These objects handle media streams and wrap the appropriate Farstream
+ * objects. It is used to interact on a stream level with the other parts
+ * of the media pipeline and the proper UI.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/util.h>
+
+#include <farstream/fs-conference.h>
+#include <farstream/fs-rtp.h>
+#include <farstream/fs-utils.h>
+
+#include "stream.h"
+#include "stream-priv.h"
+#include "channel.h"
+#include "tf-signals-marshal.h"
+#include "utils.h"
+
+G_DEFINE_TYPE (TfStream, tf_stream, G_TYPE_OBJECT);
+
+#define DEBUG(stream, format, ...) \
+ g_debug ("stream %d %p (%s) %s: " format, \
+ stream->stream_id, stream, \
+ (stream->priv->media_type == TP_MEDIA_STREAM_TYPE_AUDIO) ? "audio" \
+ : "video", \
+ G_STRFUNC, \
+ ##__VA_ARGS__)
+
+#define WARNING(stream, format, ...) \
+ g_warning ("stream %d %p (%s) %s: " format, \
+ stream->stream_id, stream, \
+ (stream->priv->media_type == TP_MEDIA_STREAM_TYPE_AUDIO) ? "audio" \
+ : "video", \
+ G_STRFUNC, \
+ ##__VA_ARGS__)
+
+#define STREAM_PRIVATE(o) ((o)->priv)
+
+#define TF_STREAM_LOCK(o) (g_static_mutex_lock (&(o)->priv->mutex))
+#define TF_STREAM_UNLOCK(o) (g_static_mutex_unlock (&(o)->priv->mutex))
+
+static TpMediaStreamError fserrorno_to_tperrorno (FsError fserror);
+
+struct DtmfEvent {
+ gint codec_id;
+ guint event_id;
+};
+
+struct _TfStreamPrivate
+{
+ TfChannel *channel;
+ FsConference *fs_conference;
+ FsParticipant *fs_participant;
+ FsSession *fs_session;
+ FsStream *fs_stream;
+ TpMediaStreamType media_type;
+ TpMediaStreamDirection direction;
+ const TfNatProperties *nat_props;
+ GList *local_preferences;
+
+ TpMediaStreamHandler *stream_handler_proxy;
+
+ FsStreamDirection desired_direction;
+ gboolean held;
+ TpMediaStreamDirection has_resource;
+
+ GList *local_candidates;
+
+ GList *last_sent_codecs;
+
+ gboolean send_local_codecs;
+ gboolean send_supported_codecs;
+
+ guint tos;
+
+ GHashTable *feedback_messages;
+ GPtrArray *header_extensions;
+
+ GStaticMutex mutex;
+ guint idle_connected_id; /* Protected by mutex */
+ gboolean disposed; /* Protected by mutex */
+
+ TpMediaStreamState current_state;
+
+ NewStreamCreatedCb *new_stream_created_cb;
+
+ GQueue events_to_send;
+
+ gint sending_telephony_event;
+};
+
+enum
+{
+ CLOSED,
+ ERROR_SIGNAL,
+ REQUEST_RESOURCE,
+ FREE_RESOURCE,
+ SRC_PAD_ADDED,
+ RESTART_SOURCE,
+ SIGNAL_COUNT
+};
+
+static guint signals[SIGNAL_COUNT] = {0};
+
+/* properties */
+enum
+{
+ PROP_CHANNEL = 1,
+ PROP_FARSTREAM_CONFERENCE,
+ PROP_FARSTREAM_SESSION,
+ PROP_FARSTREAM_STREAM,
+ PROP_FARSTREAM_PARTICIPANT,
+ PROP_PROXY,
+ PROP_STREAM_ID,
+ PROP_MEDIA_TYPE,
+ PROP_DIRECTION,
+ PROP_NAT_PROPERTIES,
+ PROP_SINK_PAD,
+ PROP_LOCAL_PREFERENCES,
+ PROP_TOS,
+ PROP_RESOURCES
+};
+
+static void get_all_properties_cb (TpProxy *proxy,
+ GHashTable *out_Properties,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object);
+static gboolean tf_stream_request_resource (
+ TfStream *self,
+ TpMediaStreamDirection dir);
+static void tf_stream_free_resource (TfStream *self,
+ TpMediaStreamDirection dir);
+
+static void add_remote_candidate (TpMediaStreamHandler *proxy,
+ const gchar *candidate, const GPtrArray *transports,
+ gpointer user_data, GObject *object);
+
+static void remove_remote_candidate (TpMediaStreamHandler *proxy,
+ const gchar *candidate,
+ gpointer user_data, GObject *object);
+
+static void set_active_candidate_pair (TpMediaStreamHandler *proxy,
+ const gchar *native_candidate, const gchar *remote_candidate,
+ gpointer user_data, GObject *object);
+
+static void set_remote_candidate_list (TpMediaStreamHandler *proxy,
+ const GPtrArray *candidates, gpointer user_data, GObject *object);
+
+static void set_remote_codecs (TpMediaStreamHandler *proxy,
+ const GPtrArray *codecs, gpointer user_data, GObject *object);
+
+static void set_stream_playing (TpMediaStreamHandler *proxy, gboolean play,
+ gpointer user_data, GObject *object);
+static void set_stream_held (TpMediaStreamHandler *proxy, gboolean held,
+ gpointer user_data, GObject *object);
+
+static void set_stream_sending (TpMediaStreamHandler *proxy, gboolean play,
+ gpointer user_data, GObject *object);
+
+static void start_telephony_event (TpMediaStreamHandler *proxy, guchar event,
+ gpointer user_data, GObject *object);
+
+static void start_named_telephony_event (TpMediaStreamHandler *proxy,
+ guchar event, guint codecid, gpointer user_data, GObject *object);
+
+static void start_sound_telephony_event (TpMediaStreamHandler *proxy,
+ guchar event, gpointer user_data, GObject *object);
+
+static void stop_telephony_event (TpMediaStreamHandler *proxy,
+ gpointer user_data, GObject *object);
+
+static void stream_close (TpMediaStreamHandler *proxy,
+ gpointer user_data, GObject *object);
+
+static void set_remote_feedback_messages (TpMediaStreamHandler *proxy,
+ GHashTable *messages, gpointer user_data, GObject *object);
+
+static void set_remote_header_extensions (TpMediaStreamHandler *proxy,
+ const GPtrArray *header_extensions, gpointer user_data, GObject *object);
+
+static void invalidated_cb (TpMediaStreamHandler *proxy,
+ guint domain, gint code, gchar *message, gpointer user_data);
+
+static TpMediaStreamBaseProto fs_network_proto_to_tp (FsNetworkProtocol proto,
+ gboolean *valid);
+static TpMediaStreamTransportType fs_candidate_type_to_tp (FsCandidateType type,
+ gboolean *valid);
+static GValueArray *fs_candidate_to_tp_array (const FsCandidate *candidate);
+
+static GPtrArray *fs_codecs_to_tp (TfStream *stream,
+ const GList *codecs);
+static void async_method_callback (TpMediaStreamHandler *proxy G_GNUC_UNUSED,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object);
+static void tf_stream_shutdown (TfStream *self);
+
+
+static void cb_fs_stream_src_pad_added (FsStream *fsstream G_GNUC_UNUSED,
+ GstPad *pad,
+ FsCodec *codec,
+ gpointer user_data);
+
+static void cb_fs_component_state_changed (TfStream *self,
+ guint component,
+ FsStreamState fsstate);
+
+
+static void
+tf_stream_init (TfStream *self)
+{
+ TfStreamPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TF_TYPE_STREAM, TfStreamPrivate);
+
+ self->priv = priv;
+ g_static_mutex_init (&priv->mutex);
+ priv->has_resource = TP_MEDIA_STREAM_DIRECTION_NONE;
+ priv->current_state = TP_MEDIA_STREAM_STATE_DISCONNECTED;
+ priv->sending_telephony_event = -1;
+
+ g_queue_init (&priv->events_to_send);
+}
+
+static void
+tf_stream_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TfStream *self = TF_STREAM (object);
+
+ switch (property_id)
+ {
+ case PROP_CHANNEL:
+ g_value_set_object (value, self->priv->channel);
+ break;
+ case PROP_FARSTREAM_CONFERENCE:
+ g_value_set_object (value, self->priv->fs_conference);
+ break;
+ case PROP_FARSTREAM_PARTICIPANT:
+ g_value_set_object (value, self->priv->fs_participant);
+ break;
+ case PROP_FARSTREAM_SESSION:
+ g_value_set_object (value, self->priv->fs_session);
+ break;
+ case PROP_FARSTREAM_STREAM:
+ g_value_set_object (value, self->priv->fs_stream);
+ break;
+ case PROP_PROXY:
+ g_value_set_object (value, self->priv->stream_handler_proxy);
+ break;
+ case PROP_STREAM_ID:
+ g_value_set_uint (value, self->stream_id);
+ break;
+ case PROP_MEDIA_TYPE:
+ g_value_set_uint (value, self->priv->media_type);
+ break;
+ case PROP_DIRECTION:
+ g_value_set_uint (value, self->priv->direction);
+ break;
+ case PROP_NAT_PROPERTIES:
+ g_value_set_pointer (value,
+ (TfNatProperties *) self->priv->nat_props);
+ break;
+ case PROP_SINK_PAD:
+ g_object_get_property (G_OBJECT (self->priv->fs_session),
+ "sink-pad", value);
+ break;
+ case PROP_LOCAL_PREFERENCES:
+ g_value_set_boxed (value, self->priv->local_preferences);
+ break;
+ case PROP_TOS:
+ if (self->priv->fs_session)
+ g_object_get_property (G_OBJECT (self->priv->fs_session), "tos", value);
+ else
+ g_value_set_uint (value, self->priv->tos);
+ break;
+ case PROP_RESOURCES:
+ g_value_set_uint (value, self->priv->has_resource);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tf_stream_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TfStream *self = TF_STREAM (object);
+
+ switch (property_id)
+ {
+ case PROP_CHANNEL:
+ self->priv->channel =
+ TF_CHANNEL (g_value_get_object (value));
+ break;
+ case PROP_FARSTREAM_CONFERENCE:
+ self->priv->fs_conference =
+ FS_CONFERENCE (g_value_dup_object (value));
+ break;
+ case PROP_FARSTREAM_PARTICIPANT:
+ self->priv->fs_participant =
+ FS_PARTICIPANT (g_value_dup_object (value));
+ break;
+ case PROP_PROXY:
+ self->priv->stream_handler_proxy =
+ TP_MEDIA_STREAM_HANDLER (g_value_dup_object (value));
+ break;
+ case PROP_STREAM_ID:
+ self->stream_id = g_value_get_uint (value);
+ break;
+ case PROP_MEDIA_TYPE:
+ self->priv->media_type = g_value_get_uint (value);
+ break;
+ case PROP_DIRECTION:
+ self->priv->direction = g_value_get_uint (value);
+ break;
+ case PROP_NAT_PROPERTIES:
+ self->priv->nat_props = g_value_get_pointer (value);
+ break;
+ case PROP_LOCAL_PREFERENCES:
+ self->priv->local_preferences = g_value_dup_boxed (value);
+ break;
+ case PROP_TOS:
+ self->priv->tos = g_value_get_uint (value);
+ if (self->priv->fs_session)
+ g_object_set_property (G_OBJECT (self->priv->fs_session), "tos", value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+#define MAX_STREAM_TRANS_PARAMS 7
+
+static GObject *
+tf_stream_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ TfStream *stream;
+
+
+ obj = G_OBJECT_CLASS (tf_stream_parent_class)->
+ constructor (type, n_props, props);
+
+ stream = TF_STREAM (obj);
+
+ g_signal_connect (stream->priv->stream_handler_proxy, "invalidated",
+ G_CALLBACK (invalidated_cb), obj);
+
+ tp_cli_dbus_properties_call_get_all (stream->priv->stream_handler_proxy,
+ -1, "org.freedesktop.Telepathy.Media.StreamHandler",
+ get_all_properties_cb, NULL, NULL, obj);
+
+ return obj;
+}
+
+static void
+tf_stream_dispose (GObject *object)
+{
+ TfStream *stream = TF_STREAM (object);
+ TfStreamPrivate *priv = stream->priv;
+ gpointer data;
+
+ TF_STREAM_LOCK (stream);
+ if (stream->priv->idle_connected_id)
+ g_source_remove (stream->priv->idle_connected_id);
+ stream->priv->idle_connected_id = 0;
+
+ stream->priv->disposed = TRUE;
+ TF_STREAM_UNLOCK (stream);
+
+
+ if (priv->stream_handler_proxy)
+ {
+ TpMediaStreamHandler *tmp = priv->stream_handler_proxy;
+
+ g_signal_handlers_disconnect_by_func (
+ priv->stream_handler_proxy, invalidated_cb, stream);
+
+ priv->stream_handler_proxy = NULL;
+ g_object_unref (tmp);
+ }
+
+ if (priv->fs_stream)
+ {
+ tf_stream_free_resource (stream,
+ TP_MEDIA_STREAM_DIRECTION_RECEIVE);
+
+ g_object_run_dispose (G_OBJECT (priv->fs_stream));
+ g_object_unref (priv->fs_stream);
+
+ tf_stream_free_resource (stream,
+ TP_MEDIA_STREAM_DIRECTION_SEND);
+
+ priv->fs_stream = NULL;
+ }
+
+ if (priv->fs_session)
+ {
+ g_object_run_dispose (G_OBJECT (priv->fs_session));
+ g_object_unref (priv->fs_session);
+ priv->fs_session = NULL;
+ }
+
+ if (priv->fs_participant)
+ {
+ g_object_unref (priv->fs_participant);
+ priv->fs_participant = NULL;
+ }
+
+ if (priv->fs_conference)
+ {
+ g_object_unref (priv->fs_conference);
+ priv->fs_conference = NULL;
+ }
+
+ if (priv->local_preferences)
+ {
+ fs_codec_list_destroy (priv->local_preferences);
+ priv->local_preferences = NULL;
+ }
+
+ if (priv->last_sent_codecs)
+ {
+ fs_codec_list_destroy (priv->last_sent_codecs);
+ priv->last_sent_codecs = NULL;
+ }
+
+ if (priv->feedback_messages)
+ g_boxed_free (TP_HASH_TYPE_RTCP_FEEDBACK_MESSAGE_MAP,
+ priv->feedback_messages);
+ priv->feedback_messages = NULL;
+
+ if (priv->header_extensions)
+ g_boxed_free (TP_ARRAY_TYPE_RTP_HEADER_EXTENSIONS_LIST,
+ priv->header_extensions);
+ priv->header_extensions = NULL;
+
+ while ((data = g_queue_pop_head (&priv->events_to_send)))
+ g_slice_free (struct DtmfEvent, data);
+
+ fs_candidate_list_destroy (priv->local_candidates);
+ priv->local_candidates = NULL;
+
+ if (G_OBJECT_CLASS (tf_stream_parent_class)->dispose)
+ G_OBJECT_CLASS (tf_stream_parent_class)->dispose (object);
+}
+
+static void
+tf_stream_class_init (TfStreamClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (TfStreamPrivate));
+
+ object_class->set_property = tf_stream_set_property;
+ object_class->get_property = tf_stream_get_property;
+ object_class->constructor = tf_stream_constructor;
+ object_class->dispose = tf_stream_dispose;
+
+ g_object_class_install_property (object_class, PROP_CHANNEL,
+ g_param_spec_object ("channel",
+ "Telepathy channel",
+ "The TfChannel this stream is in",
+ TF_TYPE_CHANNEL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_FARSTREAM_CONFERENCE,
+ g_param_spec_object ("farstream-conference",
+ "Farstream conference",
+ "The Farstream conference this stream will "
+ "create streams within.",
+ FS_TYPE_CONFERENCE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_FARSTREAM_PARTICIPANT,
+ g_param_spec_object ("farstream-participant",
+ "Farstream participant",
+ "The Farstream participant this stream will "
+ "create streams for.",
+ FS_TYPE_PARTICIPANT,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_FARSTREAM_SESSION,
+ g_param_spec_object ("farstream-session",
+ "Farstream session",
+ "The Farstream session",
+ FS_TYPE_SESSION,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_FARSTREAM_STREAM,
+ g_param_spec_object ("farstream-stream",
+ "Farstream stream",
+ "The Farstream stream",
+ FS_TYPE_STREAM,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_PROXY,
+ g_param_spec_object ("proxy",
+ "TpMediaStreamHandler proxy",
+ "The stream handler proxy which this stream interacts with.",
+ TP_TYPE_MEDIA_STREAM_HANDLER,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_STREAM_ID,
+ g_param_spec_uint ("stream-id",
+ "stream ID",
+ "A number identifying this stream within "
+ "its channel.",
+ 0, G_MAXUINT, 0,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_MEDIA_TYPE,
+ g_param_spec_uint ("media-type",
+ "stream media type",
+ "The Telepathy stream media type"
+ " (as a TpStreamMediaType)",
+ TP_MEDIA_STREAM_TYPE_AUDIO,
+ TP_MEDIA_STREAM_TYPE_VIDEO,
+ TP_MEDIA_STREAM_TYPE_AUDIO,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_DIRECTION,
+ g_param_spec_uint ("direction",
+ "stream direction",
+ "The Telepathy stream direction"
+ " (a TpMediaStreamDirection)",
+ TP_MEDIA_STREAM_DIRECTION_NONE,
+ TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL,
+ TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_NAT_PROPERTIES,
+ g_param_spec_pointer ("nat-properties",
+ "NAT properties",
+ "A pointer to a "
+ "TfNatProperties structure "
+ "detailing which NAT traversal method "
+ "and parameters to use for this stream",
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_SINK_PAD,
+ g_param_spec_object ("sink-pad",
+ "Sink pad for this stream",
+ "This sink pad that data has to be sent",
+ GST_TYPE_PAD,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_LOCAL_PREFERENCES,
+ g_param_spec_boxed ("codec-preferences",
+ "Local codec preferences",
+ "A GList of FsCodec representing preferences"
+ " to be passed to the"
+ " fs_session_set_local_preferences()"
+ " function",
+ FS_TYPE_CODEC_LIST,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_TOS,
+ g_param_spec_uint ("tos",
+ "IP Type of Service",
+ "The IP Type of Service to set on sent packets",
+ 0, 255, 0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_RESOURCES,
+ g_param_spec_uint ("resources",
+ "Resources held by the stream",
+ "The resources held by a TpMediaStreamDirection",
+ TP_MEDIA_STREAM_DIRECTION_NONE,
+ TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL,
+ TP_MEDIA_STREAM_DIRECTION_NONE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * TfStream::closed:
+ * @stream: the stream that has been closed
+ *
+ * This signal is emitted when the Close() signal is received from the
+ * connection manager.
+ */
+
+ signals[CLOSED] =
+ g_signal_new ("closed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /**
+ * TfStream::error:
+ * @stream: the stream that has been errored
+ *
+ * This signal is emitted when there is an error on this stream
+ */
+
+ signals[ERROR_SIGNAL] =
+ g_signal_new ("error",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /**
+ * TfStream::request-resource:
+ * @stream: the stream requesting the resources
+ * @direction: The direction for which this resource is requested
+ * (as a #TpMediaDirection
+ *
+ * This signal is emitted when the connection manager ask to send or receive
+ * media. For example, this can be used allocated an X window or open a
+ * camera. The resouces can later be freed on #TfStream::free-resource
+ *
+ * Returns: %TRUE if the resources requested could be allocated or %FALSE
+ * otherwise
+ */
+
+ signals[REQUEST_RESOURCE] =
+ g_signal_new ("request-resource",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ g_signal_accumulator_true_handled, NULL,
+ _tf_marshal_BOOLEAN__UINT,
+ G_TYPE_BOOLEAN, 1, G_TYPE_UINT);
+
+ /**
+ * TfStream::free-resource:
+ * @stream: the stream for which resources can be freed
+ * @direction: The direction for which this resource is freed
+ * (as a #TpMediaDirection
+ *
+ * Emitted when the stream no longer needs a resource allocated
+ * from #TfStream::request-resource and it can be freed.
+ */
+
+ signals[FREE_RESOURCE] =
+ g_signal_new ("free-resource",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1, G_TYPE_UINT);
+
+ /**
+ * TfStream::src-pad-added:
+ * @stream: the stream which has a new pad
+ * @pad: The new src pad
+ * @codec: the codec for which data is coming out
+ *
+ * This is emitted when a new src pad comes out. The user must connect
+ * this pad to his pipeline.
+ */
+
+ signals[SRC_PAD_ADDED] =
+ g_signal_new ("src-pad-added",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ _tf_marshal_VOID__OBJECT_BOXED,
+ G_TYPE_NONE, 2, GST_TYPE_PAD, FS_TYPE_CODEC);
+
+
+ /**
+ * TfStream::restart-source:
+ * @stream: the stream
+ *
+ * This is emitted when there is a caps change and the source should be
+ * restarted to take this into account.
+ */
+
+ signals[RESTART_SOURCE] =
+ g_signal_new ("restart-source",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ _tf_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+get_all_properties_cb (TpProxy *proxy,
+ GHashTable *out_Properties,
+ const GError *dbus_error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfStream *stream = TF_STREAM (weak_object);
+ GError *myerror = NULL;
+ gchar *transmitter;
+ guint n_args = 0;
+ GList *preferred_local_candidates = NULL;
+ GParameter params[MAX_STREAM_TRANS_PARAMS];
+ const gchar *nat_traversal = NULL;
+ GPtrArray *stun_servers = NULL;
+ gboolean got_stun = FALSE;
+ GPtrArray *dbus_relay_info = NULL;
+ gboolean created_locally = TRUE;
+ gboolean valid = FALSE;
+ guint i;
+ gboolean do_controlling = FALSE;
+ GList *rtp_header_extensions;
+ gboolean res = FALSE;
+
+ if (dbus_error &&
+ !(dbus_error->domain == DBUS_GERROR &&
+ dbus_error->code == DBUS_GERROR_UNKNOWN_METHOD))
+ {
+ tf_stream_error (stream, TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR,
+ dbus_error->message);
+ return;
+ }
+
+ tp_cli_media_stream_handler_connect_to_add_remote_candidate
+ (stream->priv->stream_handler_proxy, add_remote_candidate, NULL, NULL,
+ (GObject*) stream, NULL);
+ tp_cli_media_stream_handler_connect_to_remove_remote_candidate
+ (stream->priv->stream_handler_proxy, remove_remote_candidate, NULL, NULL,
+ (GObject*) stream, NULL);
+ tp_cli_media_stream_handler_connect_to_set_active_candidate_pair
+ (stream->priv->stream_handler_proxy, set_active_candidate_pair, NULL,
+ NULL, (GObject*) stream, NULL);
+ tp_cli_media_stream_handler_connect_to_set_remote_candidate_list
+ (stream->priv->stream_handler_proxy, set_remote_candidate_list, NULL,
+ NULL, (GObject*) stream, NULL);
+ tp_cli_media_stream_handler_connect_to_set_remote_codecs
+ (stream->priv->stream_handler_proxy, set_remote_codecs, NULL, NULL,
+ (GObject*) stream, NULL);
+ tp_cli_media_stream_handler_connect_to_set_stream_playing
+ (stream->priv->stream_handler_proxy, set_stream_playing, NULL, NULL,
+ (GObject*) stream, NULL);
+ tp_cli_media_stream_handler_connect_to_set_stream_sending
+ (stream->priv->stream_handler_proxy, set_stream_sending, NULL, NULL,
+ (GObject*) stream, NULL);
+ tp_cli_media_stream_handler_connect_to_set_stream_held
+ (stream->priv->stream_handler_proxy, set_stream_held, NULL, NULL,
+ (GObject*) stream, NULL);
+ tp_cli_media_stream_handler_connect_to_start_telephony_event
+ (stream->priv->stream_handler_proxy, start_telephony_event, NULL, NULL,
+ (GObject*) stream, NULL);
+ tp_cli_media_stream_handler_connect_to_start_named_telephony_event
+ (stream->priv->stream_handler_proxy, start_named_telephony_event, NULL,
+ NULL, (GObject*) stream, NULL);
+ tp_cli_media_stream_handler_connect_to_start_sound_telephony_event
+ (stream->priv->stream_handler_proxy, start_sound_telephony_event, NULL,
+ NULL, (GObject*) stream, NULL);
+ tp_cli_media_stream_handler_connect_to_stop_telephony_event
+ (stream->priv->stream_handler_proxy, stop_telephony_event, NULL, NULL,
+ (GObject*) stream, NULL);
+ tp_cli_media_stream_handler_connect_to_close
+ (stream->priv->stream_handler_proxy, stream_close, NULL, NULL,
+ (GObject*) stream, NULL);
+ tp_cli_media_stream_handler_connect_to_set_remote_feedback_messages
+ (stream->priv->stream_handler_proxy, set_remote_feedback_messages, NULL,
+ NULL, (GObject*) stream, NULL);
+ tp_cli_media_stream_handler_connect_to_set_remote_header_extensions
+ (stream->priv->stream_handler_proxy, set_remote_header_extensions, NULL,
+ NULL, (GObject*) stream, NULL);
+
+ memset (params, 0, sizeof(GParameter) * MAX_STREAM_TRANS_PARAMS);
+
+ if (out_Properties)
+ nat_traversal = tp_asv_get_string (out_Properties, "NATTraversal");
+ if (!nat_traversal && stream->priv->nat_props)
+ nat_traversal = stream->priv->nat_props->nat_traversal;
+
+ if (!nat_traversal || !strcmp (nat_traversal, "gtalk-p2p"))
+ {
+ transmitter = "nice";
+ do_controlling = TRUE;
+
+ params[n_args].name = "compatibility-mode";
+ g_value_init (&params[n_args].value, G_TYPE_UINT);
+ g_value_set_uint (&params[n_args].value, 1);
+ n_args++;
+ }
+ else if (!strcmp (nat_traversal, "ice-udp"))
+ {
+ transmitter = "nice";
+ do_controlling = TRUE;
+ }
+ else if (!strcmp (nat_traversal, "wlm-8.5"))
+ {
+ transmitter = "nice";
+ do_controlling = TRUE;
+
+ params[n_args].name = "compatibility-mode";
+ g_value_init (&params[n_args].value, G_TYPE_UINT);
+ g_value_set_uint (&params[n_args].value, 2);
+ n_args++;
+ }
+ else if (!strcmp (nat_traversal, "wlm-2009"))
+ {
+ transmitter = "nice";
+ do_controlling = TRUE;
+
+ params[n_args].name = "compatibility-mode";
+ g_value_init (&params[n_args].value, G_TYPE_UINT);
+ g_value_set_uint (&params[n_args].value, 3);
+ n_args++;
+ }
+ else if (!strcmp (nat_traversal, "shm"))
+ {
+ transmitter = "shm";
+ }
+ else
+ {
+ transmitter = "rawudp";
+
+ if (stream->priv->media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
+ preferred_local_candidates = g_list_prepend (NULL,
+ fs_candidate_new (NULL, FS_COMPONENT_RTP, FS_CANDIDATE_TYPE_HOST,
+ FS_NETWORK_PROTOCOL_UDP, NULL, 7078));
+ else if (stream->priv->media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
+ preferred_local_candidates = g_list_prepend (NULL,
+ fs_candidate_new (NULL, FS_COMPONENT_RTP, FS_CANDIDATE_TYPE_HOST,
+ FS_NETWORK_PROTOCOL_UDP, NULL, 9078));
+ }
+
+ /* FIXME: use correct macro when available */
+ if (out_Properties)
+ stun_servers = tp_asv_get_boxed (out_Properties, "STUNServers",
+ tp_type_dbus_array_su ());
+
+ if (stun_servers && stun_servers->len)
+ {
+ GValueArray *stun_server = g_ptr_array_index (stun_servers, 0);
+
+ if (stun_server && stun_server->n_values == 2)
+ {
+ GValue *stun_ip = g_value_array_get_nth (stun_server, 0);
+ GValue *stun_port = g_value_array_get_nth (stun_server, 1);
+
+ DEBUG (stream, "Adding STUN server %s:%u",
+ g_value_get_string (stun_ip),
+ g_value_get_uint (stun_port));
+
+ params[n_args].name = "stun-ip";
+ g_value_init (&params[n_args].value, G_TYPE_STRING);
+ g_value_copy (stun_ip, &params[n_args].value);
+ n_args++;
+
+ params[n_args].name = "stun-port";
+ g_value_init (&params[n_args].value, G_TYPE_UINT);
+ g_value_copy (stun_port, &params[n_args].value);
+ n_args++;
+
+ got_stun = TRUE;
+ }
+ }
+
+ if (!got_stun && stream->priv->nat_props &&
+ stream->priv->nat_props->stun_server &&
+ stream->priv->nat_props->stun_port)
+ {
+ DEBUG (stream, "Adding STUN server (old API) %s:%u",
+ stream->priv->nat_props->stun_server,
+ stream->priv->nat_props->stun_port);
+ params[n_args].name = "stun-ip";
+ g_value_init (&params[n_args].value, G_TYPE_STRING);
+ g_value_set_string (&params[n_args].value,
+ stream->priv->nat_props->stun_server);
+ n_args++;
+
+ params[n_args].name = "stun-port";
+ g_value_init (&params[n_args].value, G_TYPE_UINT);
+ g_value_set_uint (&params[n_args].value,
+ stream->priv->nat_props->stun_port);
+ n_args++;
+
+ got_stun = TRUE;
+ }
+
+ if (got_stun)
+ {
+ gchar *conn_timeout_str = NULL;
+
+ conn_timeout_str = getenv ("FS_CONN_TIMEOUT");
+ if (conn_timeout_str)
+ {
+ gint conn_timeout = strtol (conn_timeout_str, NULL, 10);
+
+ params[n_args].name = "stun-timeout";
+ g_value_init (&params[n_args].value, G_TYPE_UINT);
+ g_value_set_uint (&params[n_args].value, conn_timeout);
+ n_args++;
+ }
+ }
+
+ if (out_Properties)
+ dbus_relay_info = tp_asv_get_boxed (out_Properties, "RelayInfo",
+ TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST);
+
+ if (dbus_relay_info && dbus_relay_info->len)
+ {
+ GValueArray *fs_relay_info = g_value_array_new (0);
+ GValue val = {0};
+ g_value_init (&val, GST_TYPE_STRUCTURE);
+
+ for (i = 0; i < dbus_relay_info->len; i++)
+ {
+ GHashTable *one_relay = g_ptr_array_index(dbus_relay_info, i);
+ const gchar *type;
+ const gchar *ip;
+ guint32 port;
+ const gchar *username;
+ const gchar *password;
+ guint component;
+ GstStructure *s;
+
+ ip = tp_asv_get_string (one_relay, "ip");
+ port = tp_asv_get_uint32 (one_relay, "port", NULL);
+ type = tp_asv_get_string (one_relay, "type");
+ username = tp_asv_get_string (one_relay, "username");
+ password = tp_asv_get_string (one_relay, "password");
+ component = tp_asv_get_uint32 (one_relay, "component", NULL);
+
+ if (!ip || !port || !username || !password)
+ continue;
+
+ s = gst_structure_new ("relay-info",
+ "ip", G_TYPE_STRING, ip,
+ "port", G_TYPE_UINT, port,
+ "username", G_TYPE_STRING, username,
+ "password", G_TYPE_STRING, password,
+ NULL);
+
+ if (type)
+ gst_structure_set (s, "relay-type", G_TYPE_STRING, type, NULL);
+
+ if (component)
+ gst_structure_set (s, "component", G_TYPE_UINT, component, NULL);
+
+ if (!type)
+ type = "udp";
+
+ DEBUG (stream, "Adding relay (%s) %s:%u %s:%s %u",
+ type, ip, port, username, password, component);
+
+ g_value_take_boxed (&val, s);
+
+ g_value_array_append (fs_relay_info, &val);
+ g_value_reset (&val);
+ }
+
+ if (fs_relay_info->n_values)
+ {
+ params[n_args].name = "relay-info";
+ g_value_init (&params[n_args].value, G_TYPE_VALUE_ARRAY);
+ g_value_set_boxed (&params[n_args].value, fs_relay_info);
+ n_args++;
+ }
+
+ g_value_array_free (fs_relay_info);
+ }
+
+ if (out_Properties && do_controlling)
+ {
+ created_locally = tp_asv_get_boolean (out_Properties, "CreatedLocally",
+ &valid);
+ if (valid)
+ {
+ params[n_args].name = "controlling-mode";
+ g_value_init (&params[n_args].value, G_TYPE_BOOLEAN);
+ g_value_set_boolean (&params[n_args].value, created_locally);
+ n_args++;
+ }
+ }
+
+ if (preferred_local_candidates)
+ {
+ params[n_args].name = "preferred-local-candidates";
+ g_value_init (&params[n_args].value, FS_TYPE_CANDIDATE_LIST);
+ g_value_take_boxed (&params[n_args].value,
+ preferred_local_candidates);
+ n_args++;
+ }
+
+ stream->priv->fs_session = fs_conference_new_session (
+ stream->priv->fs_conference,
+ tp_media_type_to_fs (stream->priv->media_type),
+ &myerror);
+
+ if (!stream->priv->fs_session)
+ {
+ tf_stream_error (stream, fserror_to_tperror (myerror), myerror->message);
+ WARNING (stream, "Error creating session: %s", myerror->message);
+ g_clear_error (&myerror);
+ return;
+ }
+
+ if (stream->priv->tos)
+ g_object_set (stream->priv->fs_session, "tos", stream->priv->tos, NULL);
+
+ stream->priv->fs_stream = fs_session_new_stream (stream->priv->fs_session,
+ stream->priv->fs_participant,
+ FS_DIRECTION_NONE,
+ &myerror);
+
+ if (stream->priv->fs_stream)
+ res = fs_stream_set_transmitter (stream->priv->fs_stream,
+ transmitter, params, n_args, &myerror);
+
+ for (i = 0; i < n_args; i++)
+ g_value_unset (&params[i].value);
+
+ if (!stream->priv->fs_stream)
+ {
+ tf_stream_error (stream, fserror_to_tperror (myerror), myerror->message);
+ WARNING (stream, "Error creating stream: %s", myerror->message);
+ g_clear_error (&myerror);
+ return;
+ }
+
+ if (!res)
+ {
+ tf_stream_error (stream, fserror_to_tperror (myerror), myerror->message);
+ WARNING (stream, "Could not set transmitter for stream: %s",
+ myerror->message);
+ g_clear_error (&myerror);
+ return;
+ }
+
+ rtp_header_extensions =
+ fs_utils_get_default_rtp_header_extension_preferences (
+ GST_ELEMENT (stream->priv->fs_conference),
+ tp_media_type_to_fs (stream->priv->media_type));
+
+ if (rtp_header_extensions)
+ {
+ g_object_set (stream->priv->fs_session,
+ "rtp-header-extension-preferences", rtp_header_extensions, NULL);
+ fs_rtp_header_extension_list_destroy (rtp_header_extensions);
+ }
+
+ if (!stream->priv->local_preferences)
+ stream->priv->local_preferences = fs_utils_get_default_codec_preferences (
+ GST_ELEMENT (stream->priv->fs_conference));
+
+ if (stream->priv->local_preferences)
+ if (!fs_session_set_codec_preferences (stream->priv->fs_session,
+ stream->priv->local_preferences,
+ &myerror))
+ {
+ if (!(myerror->domain == FS_ERROR &&
+ myerror->code == FS_ERROR_NOT_IMPLEMENTED))
+ {
+ tf_stream_error (stream, fserror_to_tperror (myerror),
+ myerror->message);
+ WARNING (stream, "Error setting codec preferences: %s",
+ myerror->message);
+ g_clear_error (&myerror);
+ return;
+ }
+ g_clear_error (&myerror);
+ }
+
+ if (g_object_class_find_property (
+ G_OBJECT_GET_CLASS (stream->priv->fs_session),
+ "no-rtcp-timeout"))
+ g_object_set (stream->priv->fs_session, "no-rtcp-timeout", 0, NULL);
+
+ g_signal_connect_object (stream->priv->fs_stream, "src-pad-added",
+ G_CALLBACK (cb_fs_stream_src_pad_added), stream, 0);
+
+ stream->priv->send_local_codecs = TRUE;
+
+ stream->priv->new_stream_created_cb (stream, stream->priv->channel);
+}
+
+/* dummy callback handler for async calling calls with no return values */
+static void
+async_method_callback (TpMediaStreamHandler *proxy G_GNUC_UNUSED,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TfStream *self = TF_STREAM (weak_object);
+
+ if (error != NULL)
+ {
+ g_warning ("Error calling %s: %s", (gchar *) user_data, error->message);
+ g_signal_emit (self, signals[ERROR_SIGNAL], 0);
+ }
+}
+
+
+/* dummy callback handler for async calling calls with no return values
+ * and whose implementation is optional */
+static void
+async_method_callback_optional (TpMediaStreamHandler *proxy G_GNUC_UNUSED,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ if (error == NULL ||
+ g_error_matches (error, DBUS_GERROR, G_DBUS_ERROR_UNKNOWN_METHOD) ||
+ g_error_matches (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED))
+ return;
+
+ async_method_callback (proxy, error, user_data, weak_object);
+}
+
+static void
+cb_fs_new_local_candidate (TfStream *self,
+ FsCandidate *candidate)
+{
+ DEBUG (self, "called");
+
+ self->priv->local_candidates = g_list_append (self->priv->local_candidates,
+ fs_candidate_copy (candidate));
+}
+
+static void
+cb_fs_local_candidates_prepared (TfStream *self)
+{
+ DEBUG (self, "called");
+
+ while (self->priv->local_candidates)
+ {
+ GPtrArray *transports = g_ptr_array_new ();
+ FsCandidate *candidate =
+ g_list_first (self->priv->local_candidates)->data;
+ gchar *foundation = g_strdup (candidate->foundation);
+
+ while (candidate)
+ {
+ GValue transport = { 0, };
+ TpMediaStreamBaseProto proto;
+ TpMediaStreamTransportType type;
+ gboolean valid = TRUE;
+ GList *item = NULL;
+
+ g_value_init (&transport,
+ TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_TRANSPORT);
+ g_value_take_boxed (&transport,
+ dbus_g_type_specialized_construct (
+ TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_TRANSPORT));
+
+ proto = fs_network_proto_to_tp (candidate->proto, &valid);
+ if (valid == FALSE)
+ return;
+ type = fs_candidate_type_to_tp (candidate->type, &valid);
+ if (valid == FALSE)
+ return;
+
+ DEBUG (self, "ip = '%s port = %u component = %u'", candidate->ip,
+ candidate->port, candidate->component_id);
+
+ dbus_g_type_struct_set (&transport,
+ 0, candidate->component_id,
+ 1, candidate->ip,
+ 2, candidate->port,
+ 3, proto,
+ 4, "RTP",
+ 5, "AVP",
+ 6, (double) (candidate->priority / 65536.0),
+ 7, type,
+ 8, candidate->username,
+ 9, candidate->password,
+ G_MAXUINT);
+
+ g_ptr_array_add (transports, g_value_get_boxed (&transport));
+
+ self->priv->local_candidates = g_list_remove (
+ self->priv->local_candidates, candidate);
+
+ fs_candidate_destroy (candidate);
+
+ for (item = self->priv->local_candidates;
+ item;
+ item = g_list_next (item))
+ {
+ FsCandidate *tmpcand = item->data;
+ if (!strcmp (tmpcand->foundation, foundation))
+ break;
+ }
+ if (item)
+ candidate = item->data;
+ else
+ candidate = NULL;
+ }
+
+ tp_cli_media_stream_handler_call_new_native_candidate (
+ self->priv->stream_handler_proxy, -1, foundation, transports,
+ async_method_callback,
+ "Media.StreamHandler::NewNativeCandidate",
+ NULL, (GObject *) self);
+
+ g_boxed_free (TP_ARRAY_TYPE_MEDIA_STREAM_HANDLER_TRANSPORT_LIST,
+ transports);
+ g_free (foundation);
+ }
+
+ tp_cli_media_stream_handler_call_native_candidates_prepared (
+ self->priv->stream_handler_proxy, -1, async_method_callback,
+ "Media.StreamHandler::NativeCandidatesPrepared",
+ NULL, (GObject *) self);
+}
+
+
+
+/*
+ * small helper function to help converting a
+ * telepathy dbus candidate to a list of FsCandidate.
+ * nothing is copied, so always keep the usage of this within a function
+ * Free the result with fs_candidate_list_destroy()
+ */
+static GList *
+tp_transports_to_fs (const gchar* foundation, const GPtrArray *transports)
+{
+ GList *fs_trans_list = NULL;
+ GValueArray *transport;
+ FsCandidate *fs_candidate;
+ guint i;
+
+ for (i=0; i< transports->len; i++)
+ {
+ transport = g_ptr_array_index (transports, i);
+ FsNetworkProtocol proto;
+ FsCandidateType type;
+
+ g_assert(G_VALUE_HOLDS_UINT (g_value_array_get_nth (transport, 0)));
+ g_assert(G_VALUE_HOLDS_STRING (g_value_array_get_nth (transport, 1)));
+ g_assert(G_VALUE_HOLDS_UINT (g_value_array_get_nth (transport, 2)));
+ g_assert(G_VALUE_HOLDS_UINT (g_value_array_get_nth (transport, 3)));
+ g_assert(G_VALUE_HOLDS_STRING (g_value_array_get_nth (transport, 4)));
+ g_assert(G_VALUE_HOLDS_STRING (g_value_array_get_nth (transport, 5)));
+ g_assert(G_VALUE_HOLDS_DOUBLE (g_value_array_get_nth (transport, 6)));
+ g_assert(G_VALUE_HOLDS_UINT (g_value_array_get_nth (transport, 7)));
+ g_assert(G_VALUE_HOLDS_STRING (g_value_array_get_nth (transport, 8)));
+ g_assert(G_VALUE_HOLDS_STRING (g_value_array_get_nth (transport, 9)));
+
+ switch (g_value_get_uint (g_value_array_get_nth (transport, 7)))
+ {
+ case TP_MEDIA_STREAM_TRANSPORT_TYPE_LOCAL:
+ type = FS_CANDIDATE_TYPE_HOST;
+ break;
+ case TP_MEDIA_STREAM_TRANSPORT_TYPE_DERIVED:
+ type = FS_CANDIDATE_TYPE_SRFLX;
+ /* or FS_CANDIDATE_TYPE_PRFLX .. the TP spec doesn't differentiate */
+ break;
+ case TP_MEDIA_STREAM_TRANSPORT_TYPE_RELAY:
+ type = FS_CANDIDATE_TYPE_RELAY;
+ break;
+ default:
+ g_critical ("%s: FarstreamTransportInfo.proto has an invalid value",
+ G_STRFUNC);
+ type = FS_CANDIDATE_TYPE_HOST;
+ }
+
+ switch (g_value_get_uint (g_value_array_get_nth (transport, 3)))
+ {
+ case TP_MEDIA_STREAM_BASE_PROTO_UDP:
+ proto = FS_NETWORK_PROTOCOL_UDP;
+ break;
+ case TP_MEDIA_STREAM_BASE_PROTO_TCP:
+ proto = FS_NETWORK_PROTOCOL_TCP;
+ break;
+ default:
+ g_critical ("%s: FarstreamTransportInfo.proto has an invalid value",
+ G_STRFUNC);
+ proto = FS_NETWORK_PROTOCOL_UDP;
+ }
+
+ fs_candidate = fs_candidate_new (foundation,
+ g_value_get_uint (g_value_array_get_nth (transport, 0)), /*component*/
+ type, proto,
+
+ g_value_get_string (g_value_array_get_nth (transport, 1)), /* ip */
+ g_value_get_uint (g_value_array_get_nth (transport, 2))); /* port */
+
+ fs_candidate->priority = (gint)
+ (g_value_get_double (g_value_array_get_nth (transport, 6)) * 65536.0);
+ fs_candidate->username =
+ g_value_dup_string (g_value_array_get_nth (transport, 8));
+ fs_candidate->password =
+ g_value_dup_string (g_value_array_get_nth (transport, 9));
+
+ fs_trans_list = g_list_prepend (fs_trans_list, fs_candidate);
+ }
+ fs_trans_list = g_list_reverse (fs_trans_list);
+
+ return fs_trans_list;
+}
+
+static TpMediaStreamBaseProto
+fs_network_proto_to_tp (FsNetworkProtocol proto, gboolean *valid)
+{
+ if (valid != NULL)
+ *valid = TRUE;
+
+ switch (proto) {
+ case FS_NETWORK_PROTOCOL_UDP:
+ return TP_MEDIA_STREAM_BASE_PROTO_UDP;
+ case FS_NETWORK_PROTOCOL_TCP:
+ return TP_MEDIA_STREAM_BASE_PROTO_TCP;
+ default:
+ g_critical ("%s: FarstreamTransportInfo.proto has an invalid value",
+ G_STRFUNC);
+ if (valid != NULL)
+ *valid = FALSE;
+ g_return_val_if_reached(0);
+ }
+}
+
+static TpMediaStreamTransportType
+fs_candidate_type_to_tp (FsCandidateType type, gboolean *valid)
+{
+ if (valid != NULL)
+ *valid = TRUE;
+
+ switch (type) {
+ case FS_CANDIDATE_TYPE_HOST:
+ return TP_MEDIA_STREAM_TRANSPORT_TYPE_LOCAL;
+ case FS_CANDIDATE_TYPE_SRFLX:
+ case FS_CANDIDATE_TYPE_PRFLX:
+ return TP_MEDIA_STREAM_TRANSPORT_TYPE_DERIVED;
+ case FS_CANDIDATE_TYPE_RELAY:
+ return TP_MEDIA_STREAM_TRANSPORT_TYPE_RELAY;
+ default:
+ g_critical ("%s: FarstreamTransportInfo.proto has an invalid value",
+ G_STRFUNC);
+ if (valid != NULL)
+ *valid = FALSE;
+ g_return_val_if_reached(0);
+ }
+}
+
+static GValueArray *
+fs_candidate_to_tp_array (const FsCandidate *candidate)
+{
+ GValueArray *transport = NULL;
+ TpMediaStreamBaseProto proto;
+ TpMediaStreamTransportType type;
+ gboolean valid = TRUE;
+
+ proto = fs_network_proto_to_tp (candidate->proto, &valid);
+ if (valid == FALSE)
+ return NULL;
+ type = fs_candidate_type_to_tp (candidate->type, &valid);
+ if (valid == FALSE)
+ return NULL;
+
+ transport = tp_value_array_build (10,
+ G_TYPE_UINT, candidate->component_id,
+ G_TYPE_STRING, candidate->ip,
+ G_TYPE_UINT, candidate->port,
+ G_TYPE_UINT, proto,
+ G_TYPE_STRING, "RTP",
+ G_TYPE_STRING, "AVP",
+ G_TYPE_DOUBLE, (double) (candidate->priority / 65536.0),
+ G_TYPE_UINT, type,
+ G_TYPE_STRING, candidate->username,
+ G_TYPE_STRING, candidate->password,
+ G_TYPE_INVALID);
+
+ return transport;
+}
+
+/*
+ * Small helper function to help converting a list of FarstreamCodecs
+ * to a Telepathy codec list.
+ */
+static GPtrArray *
+fs_codecs_to_tp (TfStream *stream,
+ const GList *codecs)
+{
+ GPtrArray *tp_codecs;
+ const GList *el;
+
+ tp_codecs = g_ptr_array_new ();
+
+ for (el = codecs; el; el = g_list_next (el))
+ {
+ FsCodec *fsc = el->data;
+ GValue codec = { 0, };
+ TpMediaStreamType type;
+ GHashTable *params;
+ GList *cur;
+
+ switch (fsc->media_type) {
+ case FS_MEDIA_TYPE_AUDIO:
+ type = TP_MEDIA_STREAM_TYPE_AUDIO;
+ break;
+ case FS_MEDIA_TYPE_VIDEO:
+ type = TP_MEDIA_STREAM_TYPE_VIDEO;
+ break;
+ default:
+ g_critical ("%s: FarstreamCodec [%d, %s]'s media_type has an invalid value",
+ G_STRFUNC, fsc->id, fsc->encoding_name);
+ return NULL;
+ }
+
+ /* fill in optional parameters */
+ params = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ for (cur = fsc->optional_params; cur != NULL; cur = cur->next)
+ {
+ FsCodecParameter *param = (FsCodecParameter *) cur->data;
+
+ g_hash_table_insert (params, g_strdup (param->name),
+ g_strdup (param->value));
+ }
+
+ g_value_init (&codec, TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_CODEC);
+ g_value_take_boxed (&codec,
+ dbus_g_type_specialized_construct (TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_CODEC));
+
+ dbus_g_type_struct_set (&codec,
+ 0, fsc->id,
+ 1, fsc->encoding_name,
+ 2, type,
+ 3, fsc->clock_rate,
+ 4, fsc->channels,
+ 5, params,
+ G_MAXUINT);
+
+ g_hash_table_destroy (params);
+
+ DEBUG (stream, "adding codec " FS_CODEC_FORMAT, FS_CODEC_ARGS (fsc));
+
+ g_ptr_array_add (tp_codecs, g_value_get_boxed (&codec));
+ }
+
+ return tp_codecs;
+}
+
+static void
+add_remote_candidate (TpMediaStreamHandler *proxy G_GNUC_UNUSED,
+ const gchar *candidate,
+ const GPtrArray *transports,
+ gpointer user_data G_GNUC_UNUSED,
+ GObject *object)
+{
+ TfStream *self = TF_STREAM (object);
+ GError *error = NULL;
+ GList *fscandidates;
+
+ DEBUG (self, "adding remote candidate %s", candidate);
+
+ fscandidates = tp_transports_to_fs (candidate, transports);
+
+ if (!fs_stream_add_remote_candidates (self->priv->fs_stream,
+ fscandidates, &error))
+ tf_stream_error (self, fserror_to_tperror (error), error->message);
+
+ fs_candidate_list_destroy (fscandidates);
+ g_clear_error (&error);
+}
+
+static void
+remove_remote_candidate (TpMediaStreamHandler *proxy G_GNUC_UNUSED,
+ const gchar *candidate G_GNUC_UNUSED,
+ gpointer user_data G_GNUC_UNUSED,
+ GObject *object G_GNUC_UNUSED)
+{
+ TfStream *self = TF_STREAM (object);
+
+ tf_stream_error (self, TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR,
+ "RemoveRemoteCandidate is DEPRECATED");
+}
+
+static void
+set_active_candidate_pair (TpMediaStreamHandler *proxy G_GNUC_UNUSED,
+ const gchar *native_candidate,
+ const gchar *remote_candidate,
+ gpointer user_data G_GNUC_UNUSED,
+ GObject *object)
+{
+ /*
+ TfStream *self = TF_STREAM (object);
+ GError *error = NULL;
+
+ if (!fs_stream_select_candidate_pair (self->priv->fs_stream,
+ native_candidate,
+ remote_candidate,
+ &error))
+ {
+ if (error->domain == FS_ERROR && error->code == FS_ERROR_NOT_IMPLEMENTED)
+ DEBUG (self, "Called not implemented SetActiveCandidatePair");
+ else
+ tf_stream_error (self, 0, error->message);
+ }
+
+ g_clear_error (&error);
+ */
+}
+
+static void
+set_remote_candidate_list (TpMediaStreamHandler *proxy G_GNUC_UNUSED,
+ const GPtrArray *candidates,
+ gpointer user_data G_GNUC_UNUSED,
+ GObject *object)
+{
+ TfStream *self = TF_STREAM (object);
+ guint i;
+ GList *fs_candidates = NULL;
+ GError *error = NULL;
+ gboolean ret;
+
+ for (i = 0; i < candidates->len; i++)
+ {
+ GPtrArray *transports = NULL;
+ gchar *foundation;
+ GValueArray *candidate;
+
+ candidate = g_ptr_array_index (candidates, i);
+
+ g_assert(G_VALUE_HOLDS_STRING (g_value_array_get_nth (candidate,0)));
+ g_assert(G_VALUE_TYPE (g_value_array_get_nth (candidate, 1)) ==
+ TP_ARRAY_TYPE_MEDIA_STREAM_HANDLER_TRANSPORT_LIST);
+
+ foundation =
+ (gchar*) g_value_get_string (g_value_array_get_nth (candidate, 0));
+ transports =
+ g_value_get_boxed (g_value_array_get_nth (candidate, 1));
+
+ fs_candidates = g_list_concat (fs_candidates,
+ tp_transports_to_fs (foundation, transports));
+ }
+
+ ret = fs_stream_add_remote_candidates (self->priv->fs_stream,
+ fs_candidates, &error);
+ if (!ret && error &&
+ error->domain == FS_ERROR && error->code == FS_ERROR_NOT_IMPLEMENTED)
+ {
+ g_clear_error (&error);
+ ret = fs_stream_force_remote_candidates (self->priv->fs_stream,
+ fs_candidates, &error);
+ }
+ if (!ret)
+ tf_stream_error (self, fserror_to_tperror (error), error->message);
+
+ g_clear_error (&error);
+ fs_candidate_list_destroy (fs_candidates);
+}
+
+static void
+fill_fs_params (gpointer key, gpointer value, gpointer user_data)
+{
+ FsCodec *codec = user_data;
+
+ fs_codec_add_optional_parameter (codec, key, value);
+}
+
+static FsStreamDirection
+tpdirection_to_fsdirection (TpMediaStreamDirection dir)
+{
+ switch (dir) {
+ case TP_MEDIA_STREAM_DIRECTION_NONE:
+ return FS_DIRECTION_NONE;
+ case TP_MEDIA_STREAM_DIRECTION_SEND:
+ return FS_DIRECTION_SEND;
+ case TP_MEDIA_STREAM_DIRECTION_RECEIVE:
+ return FS_DIRECTION_RECV;
+ case TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL:
+ return FS_DIRECTION_BOTH;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+set_remote_codecs (TpMediaStreamHandler *proxy G_GNUC_UNUSED,
+ const GPtrArray *codecs,
+ gpointer user_data G_GNUC_UNUSED,
+ GObject *object)
+{
+ TfStream *self = TF_STREAM (object);
+ GValueArray *codec;
+ GHashTable *params = NULL;
+ GList *fs_remote_codecs = NULL;
+ guint i;
+ GError *error = NULL;
+
+ DEBUG (self, "called");
+
+ for (i = 0; i < codecs->len; i++)
+ {
+ FsCodec *fs_codec = NULL;
+
+
+ codec = g_ptr_array_index (codecs, i);
+
+ g_assert(G_VALUE_HOLDS_UINT (g_value_array_get_nth (codec,0)));
+ g_assert(G_VALUE_HOLDS_STRING (g_value_array_get_nth (codec,1)));
+ g_assert(G_VALUE_HOLDS_UINT (g_value_array_get_nth (codec,2)));
+ g_assert(G_VALUE_HOLDS_UINT (g_value_array_get_nth (codec,3)));
+ g_assert(G_VALUE_HOLDS_UINT (g_value_array_get_nth (codec,4)));
+ g_assert(G_VALUE_TYPE (g_value_array_get_nth (codec, 5)) ==
+ DBUS_TYPE_G_STRING_STRING_HASHTABLE);
+
+
+ fs_codec = fs_codec_new (
+ g_value_get_uint (g_value_array_get_nth (codec, 0)), /* id */
+ g_value_get_string (g_value_array_get_nth (codec, 1)), /* encoding_name */
+ g_value_get_uint (g_value_array_get_nth (codec, 2)), /* media_type */
+ g_value_get_uint (g_value_array_get_nth (codec, 3))); /* clock_rate */
+
+ fs_codec->channels =
+ g_value_get_uint (g_value_array_get_nth (codec, 4));
+
+ params = g_value_get_boxed (g_value_array_get_nth (codec, 5));
+ g_hash_table_foreach (params, fill_fs_params, fs_codec);
+
+ if (self->priv->feedback_messages)
+ {
+ GValueArray *message_props;
+
+ message_props = g_hash_table_lookup (self->priv->feedback_messages,
+ GUINT_TO_POINTER (fs_codec->id));
+
+ if (message_props)
+ {
+ GValue *val;
+ GPtrArray *messages;
+ guint j;
+
+ g_assert (G_VALUE_HOLDS_UINT (
+ g_value_array_get_nth (message_props, 0)));
+ g_assert (G_VALUE_TYPE (
+ g_value_array_get_nth (message_props, 1)) ==
+ TP_ARRAY_TYPE_RTCP_FEEDBACK_MESSAGE_LIST);
+
+ val = g_value_array_get_nth (message_props, 0);
+ fs_codec->minimum_reporting_interval =
+ g_value_get_uint (val);
+
+ val = g_value_array_get_nth (message_props, 1);
+ messages = g_value_get_boxed (val);
+
+ for (j = 0; j < messages->len; j++)
+ {
+ GValueArray *msg = g_ptr_array_index (messages, j);
+
+ g_assert (G_VALUE_HOLDS_STRING (
+ g_value_array_get_nth (msg, 0)));
+ g_assert (G_VALUE_HOLDS_STRING (
+ g_value_array_get_nth (msg, 1)));
+ g_assert (G_VALUE_HOLDS_STRING (
+ g_value_array_get_nth (msg, 2)));
+
+ fs_codec_add_feedback_parameter (fs_codec,
+ g_value_get_string (g_value_array_get_nth (msg, 0)),
+ g_value_get_string (g_value_array_get_nth (msg, 1)),
+ g_value_get_string (g_value_array_get_nth (msg, 2)));
+ }
+ }
+ }
+
+
+ DEBUG (self, "adding remote codec %s [%d]",
+ fs_codec->encoding_name, fs_codec->id);
+
+ fs_remote_codecs = g_list_prepend (fs_remote_codecs, fs_codec);
+ }
+ fs_remote_codecs = g_list_reverse (fs_remote_codecs);
+
+ if (self->priv->feedback_messages)
+ {
+ g_boxed_free (TP_HASH_TYPE_RTCP_FEEDBACK_MESSAGE_MAP,
+ self->priv->feedback_messages);
+ self->priv->feedback_messages = NULL;
+ }
+
+
+ if (self->priv->header_extensions)
+ {
+ if (g_object_class_find_property (
+ G_OBJECT_GET_CLASS (self->priv->fs_stream),
+ "rtp-header-extensions"))
+ {
+ GList *hdrexts = NULL;
+
+ for (i = 0; i < self->priv->header_extensions->len; i++)
+ {
+ GValueArray *extension =
+ g_ptr_array_index (self->priv->header_extensions, i);
+ FsRtpHeaderExtension *hdrext;
+
+ g_assert (extension->n_values >= 3);
+ g_assert (G_VALUE_HOLDS_UINT (
+ g_value_array_get_nth (extension, 0)));
+ g_assert (G_VALUE_HOLDS_UINT (
+ g_value_array_get_nth (extension, 1)));
+ g_assert (G_VALUE_HOLDS_STRING (
+ g_value_array_get_nth (extension, 2)));
+
+ hdrext = fs_rtp_header_extension_new (
+ g_value_get_uint (g_value_array_get_nth (extension, 0)),
+ tpdirection_to_fsdirection (
+ g_value_get_uint (g_value_array_get_nth (extension, 1))),
+ g_value_get_string (g_value_array_get_nth (extension, 2)));
+
+ hdrexts = g_list_append (hdrexts, hdrext);
+ }
+
+ g_object_set (self->priv->fs_stream, "rtp-header-extensions",
+ hdrexts, NULL);
+
+ fs_rtp_header_extension_list_destroy (hdrexts);
+ }
+ g_boxed_free (TP_ARRAY_TYPE_RTP_HEADER_EXTENSIONS_LIST,
+ self->priv->header_extensions);
+ self->priv->header_extensions = NULL;
+ }
+
+ if (!fs_stream_set_remote_codecs (self->priv->fs_stream, fs_remote_codecs,
+ &error)) {
+ /*
+ * Call the error method with the proper thing here
+ */
+ g_prefix_error (&error, "Codec negotiation failed: ");
+ tf_stream_error (self, fserror_to_tperror (error), error->message);
+ g_clear_error (&error);
+ fs_codec_list_destroy (fs_remote_codecs);
+ return;
+ }
+
+ fs_codec_list_destroy (fs_remote_codecs);
+
+ self->priv->send_supported_codecs = TRUE;
+ _tf_stream_try_sending_codecs (self);
+}
+
+static void
+set_stream_playing (TpMediaStreamHandler *proxy G_GNUC_UNUSED,
+ gboolean play,
+ gpointer user_data G_GNUC_UNUSED,
+ GObject *object)
+{
+ TfStream *self = TF_STREAM (object);
+ FsStreamDirection current_direction;
+ gboolean playing;
+
+ g_assert (self->priv->fs_stream != NULL);
+
+ DEBUG (self, "%d", play);
+
+ g_object_get (self->priv->fs_stream, "direction", &current_direction, NULL);
+
+ playing = (current_direction & FS_DIRECTION_RECV) != 0;
+
+ /* We're already in the right state */
+ if (play == playing)
+ return;
+
+ if (play)
+ {
+ if (!self->priv->held) {
+ if (tf_stream_request_resource (self,
+ TP_MEDIA_STREAM_DIRECTION_RECEIVE))
+ {
+ g_object_set (self->priv->fs_stream,
+ "direction", current_direction | FS_DIRECTION_RECV,
+ NULL);
+ }
+ else
+ {
+ tf_stream_error (self, TP_MEDIA_STREAM_ERROR_MEDIA_ERROR,
+ "Resource Unavailable");
+ }
+ }
+ self->priv->desired_direction |= FS_DIRECTION_RECV;
+ }
+ else
+ {
+ if (!self->priv->held) {
+ tf_stream_free_resource (self,
+ TP_MEDIA_STREAM_DIRECTION_RECEIVE);
+
+ g_object_set (self->priv->fs_stream,
+ "direction", current_direction & ~(FS_DIRECTION_RECV),
+ NULL);
+ }
+ self->priv->desired_direction &= ~(FS_DIRECTION_RECV);
+ }
+}
+
+static void
+set_stream_sending (TpMediaStreamHandler *proxy G_GNUC_UNUSED,
+ gboolean send,
+ gpointer user_data G_GNUC_UNUSED,
+ GObject *object)
+{
+ TfStream *self = TF_STREAM (object);
+ FsStreamDirection current_direction;
+ gboolean sending;
+
+ g_assert (self->priv->fs_stream != NULL);
+
+ DEBUG (self, "%d", send);
+
+ g_object_get (self->priv->fs_stream, "direction", &current_direction, NULL);
+
+
+ sending = (current_direction & FS_DIRECTION_SEND) != 0;
+
+ /* We're already in the right state */
+ if (send == sending)
+ return;
+
+ if (send)
+ {
+ if (!self->priv->held) {
+ if (tf_stream_request_resource (self,
+ TP_MEDIA_STREAM_DIRECTION_SEND))
+ {
+ g_object_set (self->priv->fs_stream,
+ "direction", current_direction | FS_DIRECTION_SEND,
+ NULL);
+ }
+ else
+ {
+ tf_stream_error (self, TP_MEDIA_STREAM_ERROR_MEDIA_ERROR,
+ "Resource Unavailable");
+ }
+ }
+ self->priv->desired_direction |= FS_DIRECTION_SEND;
+ }
+ else
+ {
+ g_object_set (self->priv->fs_stream,
+ "direction", current_direction & ~(FS_DIRECTION_SEND),
+ NULL);
+
+ tf_stream_free_resource (self, FS_DIRECTION_SEND);
+
+ self->priv->desired_direction &= ~(FS_DIRECTION_SEND);
+ }
+}
+
+static gboolean
+tf_stream_request_resource (TfStream *self,
+ TpMediaStreamDirection dir)
+{
+ gboolean resource_available;
+ GValue instance_and_arg[2];
+ GValue resource_avail_val = {0,};
+
+ if ((self->priv->has_resource & dir) == dir)
+ return TRUE;
+
+ memset (instance_and_arg, 0, sizeof(GValue) * 2);
+
+ g_value_init (&resource_avail_val, G_TYPE_BOOLEAN);
+ g_value_set_boolean (&resource_avail_val, TRUE);
+
+ g_value_init (&instance_and_arg[0], TF_TYPE_STREAM);
+ g_value_set_object (&instance_and_arg[0], self);
+ g_value_init (&instance_and_arg[1], G_TYPE_UINT);
+ g_value_set_uint (&instance_and_arg[1], dir & ~self->priv->has_resource);
+
+ DEBUG (self, "Requesting resource for direction %d", dir);
+
+ g_signal_emitv (instance_and_arg, signals[REQUEST_RESOURCE], 0,
+ &resource_avail_val);
+ resource_available = g_value_get_boolean (&resource_avail_val);
+
+ g_value_unset (&instance_and_arg[0]);
+ g_value_unset (&instance_and_arg[1]);
+ g_value_unset (&resource_avail_val);
+
+ DEBUG (self, "Requesting resource for direction %d returned %d", dir,
+ resource_available);
+
+ /* Make sure we have access to the resource */
+ if (resource_available)
+ {
+ self->priv->has_resource |= dir;
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+static void
+tf_stream_free_resource (TfStream *self,
+ TpMediaStreamDirection dir)
+{
+ if ((self->priv->has_resource & dir) == 0)
+ return;
+
+ g_signal_emit (self, signals[FREE_RESOURCE], 0,
+ self->priv->has_resource & dir);
+ self->priv->has_resource &= ~dir;
+}
+
+static void
+set_stream_held (TpMediaStreamHandler *proxy G_GNUC_UNUSED,
+ gboolean held,
+ gpointer user_data G_GNUC_UNUSED,
+ GObject *object)
+{
+ TfStream *self = TF_STREAM (object);
+
+ if (held == self->priv->held)
+ return;
+
+ DEBUG (self, "Holding : %d", held);
+
+ if (held)
+ {
+ g_object_set (self->priv->fs_stream,
+ "direction", FS_DIRECTION_NONE,
+ NULL);
+
+ tf_stream_free_resource (self,
+ TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL);
+ /* Send success message */
+ if (self->priv->stream_handler_proxy)
+ {
+ tp_cli_media_stream_handler_call_hold_state (
+ self->priv->stream_handler_proxy, -1, TRUE,
+ async_method_callback, "Media.StreamHandler::HoldState TRUE",
+ NULL, (GObject *) self);
+ }
+ self->priv->held = TRUE;
+ }
+ else
+ {
+ FsStreamDirection desired_direction = self->priv->desired_direction;
+
+ if (tf_stream_request_resource (self, desired_direction))
+ {
+ g_object_set (self->priv->fs_stream,
+ "direction", self->priv->desired_direction,
+ NULL);
+ tp_cli_media_stream_handler_call_hold_state (
+ self->priv->stream_handler_proxy, -1, FALSE,
+ async_method_callback, "Media.StreamHandler::HoldState FALSE",
+ NULL, (GObject *) self);
+
+ self->priv->held = FALSE;
+ }
+ else
+ {
+ tf_stream_error (self, TP_MEDIA_STREAM_ERROR_MEDIA_ERROR,
+ "Error unholding stream");
+ }
+ }
+}
+
+static void
+start_telephony_event (TpMediaStreamHandler *proxy G_GNUC_UNUSED,
+ guchar event,
+ gpointer user_data G_GNUC_UNUSED,
+ GObject *object)
+{
+ TfStream *self = TF_STREAM (object);
+
+ g_assert (self->priv->fs_session != NULL);
+
+ DEBUG (self, "called with event %u", event);
+
+ if (self->priv->sending_telephony_event != -1)
+ {
+ WARNING (self, "start new telephony event without stopping the"
+ " previous one first");
+ if (!fs_session_stop_telephony_event (self->priv->fs_session,
+ self->priv->sending_telephony_event))
+ WARNING (self, "stopping event failed");
+ }
+
+ /* this week, volume is 8, for the sake of argument... */
+
+ if (!fs_session_start_telephony_event (self->priv->fs_session, event, 8,
+ FS_DTMF_METHOD_AUTO))
+ WARNING (self, "sending event %u failed", event);
+ self->priv->sending_telephony_event = FS_DTMF_METHOD_AUTO;
+}
+
+static gboolean
+check_codecs_for_telephone_event (TfStream *self, GList **codecs,
+ FsCodec *send_codec, guint codecid)
+{
+ GList *item = NULL;
+ gboolean found = FALSE;
+ GError *error = NULL;
+
+ again:
+
+ for (item = *codecs; item; item = item->next)
+ {
+ FsCodec *codec = item->data;
+
+ if (!g_ascii_strcasecmp (codec->encoding_name, "telephone-event") &&
+ send_codec->clock_rate == codec->clock_rate)
+ {
+ if (found)
+ {
+ *codecs = g_list_delete_link (*codecs, item);
+ goto again;
+ }
+ else if (codecid == (guint) codec->id)
+ {
+ return TRUE; }
+ else
+ {
+ codec->id = codecid;
+ }
+ }
+ }
+
+ if (!found)
+ {
+ FsCodec *codec = fs_codec_new (codecid, "telephone-event",
+ FS_MEDIA_TYPE_AUDIO, send_codec->clock_rate);
+
+ *codecs = g_list_append (*codecs, codec);
+ }
+
+ if (!fs_stream_set_remote_codecs (self->priv->fs_stream, *codecs, &error))
+ {
+ /*
+ * Call the error method with the proper thing here
+ */
+ g_prefix_error (&error, "Codec negotiation failed for DTMF: ");
+ tf_stream_error (self, fserror_to_tperror (error), error->message);
+ g_clear_error (&error);
+ }
+
+ return FALSE;
+}
+
+static void
+start_named_telephony_event (TpMediaStreamHandler *proxy,
+ guchar event,
+ guint codecid,
+ gpointer user_data,
+ GObject *object)
+{
+ TfStream *self = TF_STREAM (object);
+ FsCodec *send_codec = NULL;
+ GList *codecs = NULL;
+ struct DtmfEvent *dtmfevent;
+
+ g_object_get (self->priv->fs_session,
+ "current-send-codec", &send_codec,
+ "codecs", &codecs,
+ NULL);
+
+ if (send_codec == NULL)
+ goto out;
+
+ if (check_codecs_for_telephone_event (self, &codecs, send_codec, codecid))
+ {
+ if (self->priv->sending_telephony_event != -1)
+ {
+ WARNING (self, "start new telephony event without stopping the"
+ " previous one first");
+ if (!fs_session_stop_telephony_event (self->priv->fs_session,
+ self->priv->sending_telephony_event))
+ WARNING (self, "stopping event failed");
+ }
+
+
+ DEBUG (self, "Sending named telephony event %d with pt %d",
+ event, codecid);
+ if (!fs_session_start_telephony_event (self->priv->fs_session,
+ event, 8, FS_DTMF_METHOD_RTP_RFC4733))
+ WARNING (self, "sending event %u failed", event);
+ self->priv->sending_telephony_event = FS_DTMF_METHOD_RTP_RFC4733;
+ }
+ else
+ {
+ DEBUG (self, "Queing named telephony event %d with pt %d",
+ event, codecid);
+ dtmfevent = g_slice_new (struct DtmfEvent);
+ dtmfevent->codec_id = codecid;
+ dtmfevent->event_id = event;
+ g_queue_push_tail (&self->priv->events_to_send, dtmfevent);
+ }
+
+out:
+
+ fs_codec_destroy (send_codec);
+ fs_codec_list_destroy (codecs);
+}
+
+static void
+start_sound_telephony_event (TpMediaStreamHandler *proxy, guchar event,
+ gpointer user_data, GObject *object)
+{
+ TfStream *self = TF_STREAM (object);
+
+ g_assert (self->priv->fs_session != NULL);
+
+ DEBUG (self, "called with event %u", event);
+
+ if (self->priv->sending_telephony_event != -1)
+ {
+ WARNING (self, "start new telephony event without stopping the"
+ " previous one first");
+ if (!fs_session_stop_telephony_event (self->priv->fs_session,
+ self->priv->sending_telephony_event))
+ WARNING (self, "stopping event failed");
+ }
+
+ /* this week, volume is 8, for the sake of argument... */
+
+ if (!fs_session_start_telephony_event (self->priv->fs_session, event, 8,
+ FS_DTMF_METHOD_SOUND))
+ WARNING (self, "sending sound event %u failed", event);
+ self->priv->sending_telephony_event = FS_DTMF_METHOD_SOUND;
+}
+
+
+static void
+stop_telephony_event (TpMediaStreamHandler *proxy G_GNUC_UNUSED,
+ gpointer user_data G_GNUC_UNUSED,
+ GObject *object)
+{
+ TfStream *self = TF_STREAM (object);
+
+ g_assert (self->priv->fs_session != NULL);
+
+ DEBUG (self, "called");
+
+ if (self->priv->sending_telephony_event == -1)
+ WARNING (self, "Trying to stop telephony event without having started"
+ " one");
+ self->priv->sending_telephony_event = -1;
+
+ if (!fs_session_stop_telephony_event (self->priv->fs_session,
+ FS_DTMF_METHOD_AUTO))
+ WARNING (self, "stopping event failed");
+}
+
+
+static void
+tf_stream_shutdown (TfStream *self)
+{
+ if (self->priv->fs_stream)
+ g_object_set (self->priv->fs_stream,
+ "direction", FS_DIRECTION_NONE,
+ NULL);
+ tf_stream_free_resource (self,
+ TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL);
+
+ g_signal_emit (self, signals[CLOSED], 0);
+}
+
+
+static void
+stream_close (TpMediaStreamHandler *proxy G_GNUC_UNUSED,
+ gpointer user_data G_GNUC_UNUSED,
+ GObject *object)
+{
+ TfStream *self = TF_STREAM (object);
+
+ DEBUG (self, "close requested by connection manager");
+
+ tf_stream_shutdown (self);
+}
+
+
+static void
+set_remote_feedback_messages (TpMediaStreamHandler *proxy,
+ GHashTable *messages, gpointer user_data, GObject *object)
+{
+ TfStream *self = TF_STREAM (object);
+
+ if (self->priv->feedback_messages)
+ g_boxed_free (TP_HASH_TYPE_RTCP_FEEDBACK_MESSAGE_MAP,
+ self->priv->feedback_messages);
+
+ self->priv->feedback_messages =
+ g_boxed_copy (TP_HASH_TYPE_RTCP_FEEDBACK_MESSAGE_MAP, messages);
+}
+
+
+static void
+set_remote_header_extensions (TpMediaStreamHandler *proxy,
+ const GPtrArray *header_extensions, gpointer user_data, GObject *object)
+{
+ TfStream *self = TF_STREAM (object);
+
+ if (self->priv->header_extensions)
+ g_boxed_free (TP_ARRAY_TYPE_RTP_HEADER_EXTENSIONS_LIST,
+ self->priv->header_extensions);
+
+ self->priv->header_extensions =
+ g_boxed_copy (TP_ARRAY_TYPE_RTP_HEADER_EXTENSIONS_LIST,
+ header_extensions);
+}
+
+
+static void
+cb_fs_recv_codecs_changed (TfStream *self,
+ GList *codecs)
+{
+ guint id;
+ GList *item;
+
+ for (item = codecs;
+ item;
+ item = g_list_next (item))
+ {
+ gchar *str = fs_codec_to_string (item->data);
+
+ DEBUG (self, "receiving codec: %s", str);
+ g_free (str);
+ }
+
+ id = ((FsCodec*)codecs->data)->id;
+
+ tp_cli_media_stream_handler_call_codec_choice
+ (self->priv->stream_handler_proxy, -1, id,
+ async_method_callback_optional,
+ "Media.StreamHandler::CodecChoice", NULL, (GObject *) self);
+}
+
+static void
+cb_fs_new_active_candidate_pair (TfStream *self,
+ FsCandidate *local_candidate,
+ FsCandidate *remote_candidate)
+{
+ GValueArray *local_transport = NULL;
+ GValueArray *remote_transport = NULL;
+
+ DEBUG (self, "called: c:%d local: %s %s:%u remote: %s %s:%u",
+ local_candidate->component_id,
+ local_candidate->foundation, local_candidate->ip, local_candidate->port,
+ remote_candidate->foundation, remote_candidate->ip,
+ remote_candidate->port);
+
+ local_transport = fs_candidate_to_tp_array (local_candidate);
+ if (!local_transport)
+ return;
+
+ remote_transport = fs_candidate_to_tp_array (remote_candidate);
+ if (!remote_transport)
+ {
+ g_value_array_free (local_transport);
+ return;
+ }
+
+ tp_cli_media_stream_handler_call_new_active_transport_pair (
+ self->priv->stream_handler_proxy, -1, local_candidate->foundation,
+ local_transport, remote_candidate->foundation, remote_transport,
+ async_method_callback_optional,
+ "Media.StreamHandler::NewActiveTransportPair",
+ NULL, (GObject *) self);
+
+ tp_cli_media_stream_handler_call_new_active_candidate_pair (
+ self->priv->stream_handler_proxy, -1, local_candidate->foundation,
+ remote_candidate->foundation,
+ async_method_callback_optional,
+ "Media.StreamHandler::NewActiveCandidatePair",
+ NULL, (GObject *) self);
+
+ if (self->priv->current_state == TP_MEDIA_STREAM_STATE_DISCONNECTED)
+ {
+ tp_cli_media_stream_handler_call_stream_state (
+ self->priv->stream_handler_proxy, -1, TP_MEDIA_STREAM_STATE_CONNECTED,
+ async_method_callback, "Media.StreamHandler::StreamState",
+ NULL, (GObject *) self);
+ self->priv->current_state = TP_MEDIA_STREAM_STATE_CONNECTED;
+ }
+
+ g_value_array_free (local_transport);
+ g_value_array_free (remote_transport);
+}
+
+static void
+invalidated_cb (TpMediaStreamHandler *proxy G_GNUC_UNUSED,
+ guint domain G_GNUC_UNUSED,
+ gint code G_GNUC_UNUSED,
+ gchar *message G_GNUC_UNUSED,
+ gpointer user_data)
+{
+ TfStream *stream = TF_STREAM (user_data);
+
+ DEBUG (stream, "proxy invalidated");
+
+ if (stream->priv->stream_handler_proxy)
+ {
+ TpMediaStreamHandler *tmp = stream->priv->stream_handler_proxy;
+
+ stream->priv->stream_handler_proxy = NULL;
+ g_object_unref (tmp);
+ }
+
+ tf_stream_shutdown (stream);
+}
+
+static void
+cb_fs_send_codec_changed (TfStream *self,
+ FsCodec *send_codec,
+ GList *secondary_codecs)
+{
+ GList *item;
+ gint last_event_id = -1;
+ struct DtmfEvent *dtmfevent;
+
+ while ((dtmfevent = g_queue_peek_head (&self->priv->events_to_send)))
+ {
+ if (dtmfevent->codec_id != last_event_id)
+ {
+ last_event_id = -1;
+ for (item = secondary_codecs; item; item = item->next)
+ {
+ FsCodec *codec = item->data;
+
+ if (!g_ascii_strcasecmp (codec->encoding_name, "telephone-event")
+ && codec->id == dtmfevent->codec_id)
+ {
+ last_event_id = codec->id;
+ goto have_id;
+ }
+ }
+ if (dtmfevent->codec_id != last_event_id)
+ {
+ GList *codecs = NULL;
+
+ g_object_get (self->priv->fs_session, "codecs", &codecs, NULL);
+
+ DEBUG (self, "Still do not have the right PT for telephony"
+ " events, trying to force it again");
+ if (check_codecs_for_telephone_event (self, &codecs, send_codec,
+ dtmfevent->codec_id))
+ WARNING (self, "Did not have the right pt in the secondary"
+ " codecs, but it was in the codec list. Ignoring for now");
+ fs_codec_list_destroy (codecs);
+ return;
+ }
+ }
+
+ have_id:
+ DEBUG (self, "Sending queued event %d with pt %d", dtmfevent->event_id,
+ dtmfevent->codec_id);
+ dtmfevent = g_queue_pop_head (&self->priv->events_to_send);
+ if (self->priv->sending_telephony_event != -1)
+ {
+ WARNING (self, "start new telephony event without stopping the"
+ " previous one first");
+ if (!fs_session_stop_telephony_event (self->priv->fs_session,
+ self->priv->sending_telephony_event))
+ WARNING (self, "stopping event failed");
+ }
+ self->priv->sending_telephony_event = -1;
+
+ if (!fs_session_start_telephony_event (self->priv->fs_session,
+ dtmfevent->event_id, 8, FS_DTMF_METHOD_RTP_RFC4733))
+ WARNING (self, "sending event %u failed", dtmfevent->event_id);
+ fs_session_stop_telephony_event (self->priv->fs_session,
+ FS_DTMF_METHOD_RTP_RFC4733);
+
+ g_slice_free (struct DtmfEvent, dtmfevent);
+ }
+}
+
+/**
+ * tf_stream_error:
+ * @self: a #TfStream
+ * @error: the error number as a #TpMediaStreamError
+ * @message: the message for this error
+ *
+ * This function can be used to tell the connection manager that an error
+ * has happened on a specific stream.
+ */
+
+void
+tf_stream_error (TfStream *self,
+ TpMediaStreamError error,
+ const gchar *message)
+{
+ g_message ("%s: stream error errorno=%d error=%s", G_STRFUNC, error, message);
+
+ tp_cli_media_stream_handler_call_error (self->priv->stream_handler_proxy,
+ -1, error, message, NULL, NULL, NULL, NULL);
+
+ g_signal_emit (self, signals[ERROR_SIGNAL], 0);
+}
+
+
+/**
+ * _tf_stream_bus_message:
+ * @stream: A #TfStream
+ * @message: A #GstMessage received from the bus
+ *
+ * You must call this function on call messages received on the async bus.
+ * #GstMessages are not modified.
+ *
+ * Returns: %TRUE if the message has been handled, %FALSE otherwise
+ */
+
+gboolean
+_tf_stream_bus_message (TfStream *stream,
+ GstMessage *message)
+{
+ const GstStructure *s = gst_message_get_structure (message);
+
+ if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT)
+ return FALSE;
+
+ if (!stream->priv->fs_stream || !stream->priv->fs_session)
+ return FALSE;
+
+ if (gst_structure_has_name (s, "farstream-error"))
+ {
+ GObject *object;
+ const GValue *value = NULL;
+
+ value = gst_structure_get_value (s, "src-object");
+ object = g_value_get_object (value);
+
+ if (object == (GObject*) stream->priv->fs_session ||
+ object == (GObject*) stream->priv->fs_stream)
+ {
+ const gchar *msg;
+ FsError errorno;
+ GEnumClass *enumclass;
+ GEnumValue *enumvalue;
+
+ value = gst_structure_get_value (s, "error-no");
+ errorno = g_value_get_enum (value);
+ msg = gst_structure_get_string (s, "error-msg");
+
+ enumclass = g_type_class_ref (FS_TYPE_ERROR);
+ enumvalue = g_enum_get_value (enumclass, errorno);
+ WARNING (stream, "error (%s (%d)): %s",
+ enumvalue->value_nick, errorno, msg);
+ g_type_class_unref (enumclass);
+
+ tf_stream_error (stream, fserrorno_to_tperrorno (errorno), msg);
+ return TRUE;
+ }
+ }
+ else if (gst_structure_has_name (s, "farstream-new-local-candidate"))
+ {
+ FsStream *fsstream;
+ FsCandidate *candidate;
+ const GValue *value;
+
+ value = gst_structure_get_value (s, "stream");
+ fsstream = g_value_get_object (value);
+
+ if (fsstream != stream->priv->fs_stream)
+ return FALSE;
+
+ value = gst_structure_get_value (s, "candidate");
+ candidate = g_value_get_boxed (value);
+
+ cb_fs_new_local_candidate (stream, candidate);
+ return TRUE;
+ }
+ else if (gst_structure_has_name (s, "farstream-local-candidates-prepared"))
+ {
+ FsStream *fsstream;
+ const GValue *value;
+
+ value = gst_structure_get_value (s, "stream");
+ fsstream = g_value_get_object (value);
+
+ if (fsstream != stream->priv->fs_stream)
+ return FALSE;
+
+ cb_fs_local_candidates_prepared (stream);
+
+ return TRUE;
+ }
+ else if (gst_structure_has_name (s, "farstream-new-active-candidate-pair"))
+ {
+ FsStream *fsstream;
+ FsCandidate *local_candidate;
+ FsCandidate *remote_candidate;
+ const GValue *value;
+
+ value = gst_structure_get_value (s, "stream");
+ fsstream = g_value_get_object (value);
+
+ if (fsstream != stream->priv->fs_stream)
+ return FALSE;
+
+ value = gst_structure_get_value (s, "local-candidate");
+ local_candidate = g_value_get_boxed (value);
+
+ value = gst_structure_get_value (s, "remote-candidate");
+ remote_candidate = g_value_get_boxed (value);
+
+ cb_fs_new_active_candidate_pair (stream, local_candidate, remote_candidate);
+ return TRUE;
+ }
+ else if (gst_structure_has_name (s, "farstream-current-recv-codecs-changed"))
+ {
+ FsStream *fsstream;
+ GList *codecs;
+ const GValue *value;
+
+ value = gst_structure_get_value (s, "stream");
+ fsstream = g_value_get_object (value);
+
+ if (fsstream != stream->priv->fs_stream)
+ return FALSE;
+
+ value = gst_structure_get_value (s, "codecs");
+ codecs = g_value_get_boxed (value);
+
+ cb_fs_recv_codecs_changed (stream, codecs);
+ return TRUE;
+ }
+ else if (gst_structure_has_name (s, "farstream-codecs-changed"))
+ {
+ FsSession *fssession;
+ const GValue *value;
+
+ value = gst_structure_get_value (s, "session");
+ fssession = g_value_get_object (value);
+
+ if (fssession != stream->priv->fs_session)
+ return FALSE;
+
+ DEBUG (stream, "Codecs changed");
+
+ _tf_stream_try_sending_codecs (stream);
+
+ return TRUE;
+ }
+ else if (gst_structure_has_name (s, "farstream-send-codec-changed"))
+ {
+ FsSession *fssession;
+ const GValue *value;
+ FsCodec *codec = NULL;
+ GList *secondary_codecs = NULL;
+ FsCodec *objcodec = NULL;
+
+ value = gst_structure_get_value (s, "session");
+ fssession = g_value_get_object (value);
+
+ if (fssession != stream->priv->fs_session)
+ return FALSE;
+
+ value = gst_structure_get_value (s, "codec");
+ codec = g_value_get_boxed (value);
+ g_object_get (fssession, "current-send-codec", &objcodec, NULL);
+
+ if (!fs_codec_are_equal (objcodec, codec))
+ {
+ fs_codec_destroy (objcodec);
+ return TRUE;
+ }
+
+ value = gst_structure_get_value (s, "secondary-codecs");
+ secondary_codecs = g_value_get_boxed (value);
+
+
+ if (codec)
+ DEBUG (stream, "Send codec changed: " FS_CODEC_FORMAT,
+ FS_CODEC_ARGS (codec));
+
+ cb_fs_send_codec_changed (stream, codec, secondary_codecs);
+
+ if (codec)
+ fs_codec_destroy (codec);
+ fs_codec_list_destroy (secondary_codecs);
+ return TRUE;
+ }
+ else if (gst_structure_has_name (s, "farstream-component-state-changed"))
+ {
+ FsStream *fsstream;
+ const GValue *value;
+ guint component;
+ FsStreamState fsstate;
+
+ value = gst_structure_get_value (s, "stream");
+ fsstream = g_value_get_object (value);
+
+ if (fsstream != stream->priv->fs_stream)
+ return FALSE;
+
+ if (!gst_structure_get_uint (s, "component", &component) ||
+ !gst_structure_get_enum (s, "state", FS_TYPE_STREAM_STATE,
+ (gint*) &fsstate))
+ return TRUE;
+
+ cb_fs_component_state_changed (stream, component, fsstate);
+ return TRUE;
+ }
+ else if (gst_structure_has_name (s, "farstream-renegotiate"))
+ {
+ FsSession *fssession;
+ const GValue *value;
+
+ value = gst_structure_get_value (s, "session");
+ fssession = g_value_get_object (value);
+
+ if (fssession != stream->priv->fs_session)
+ return FALSE;
+
+ g_signal_emit (stream, signals[RESTART_SOURCE], 0);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+emit_connected (gpointer data)
+{
+ TfStream *self = TF_STREAM (data);
+
+ TF_STREAM_LOCK (self);
+ self->priv->idle_connected_id = 0;
+ if (self->priv->disposed)
+ {
+ TF_STREAM_UNLOCK (self);
+ return FALSE;
+ }
+ TF_STREAM_UNLOCK (self);
+
+ tp_cli_media_stream_handler_call_stream_state (
+ self->priv->stream_handler_proxy, -1, TP_MEDIA_STREAM_STATE_CONNECTED,
+ async_method_callback, "Media.StreamHandler::StreamState",
+ NULL, (GObject *) self);
+
+ return FALSE;
+}
+
+static void
+cb_fs_stream_src_pad_added (FsStream *fsstream G_GNUC_UNUSED,
+ GstPad *pad,
+ FsCodec *codec,
+ gpointer user_data)
+{
+ TfStream *self = TF_STREAM (user_data);
+ gchar *padname = gst_pad_get_name (pad);
+
+ DEBUG (self, "New pad %s: " FS_CODEC_FORMAT, padname, FS_CODEC_ARGS (codec));
+ g_free (padname);
+
+ TF_STREAM_LOCK (self);
+ if (self->priv->disposed)
+ {
+ TF_STREAM_UNLOCK (self);
+ return;
+ }
+
+ if (!self->priv->idle_connected_id)
+ self->priv->idle_connected_id = g_idle_add (emit_connected, self);
+ TF_STREAM_UNLOCK (self);
+
+ g_signal_emit (self, signals[SRC_PAD_ADDED], 0, pad, codec);
+}
+
+TfStream *
+_tf_stream_new (gpointer channel,
+ FsConference *conference,
+ FsParticipant *participant,
+ TpMediaStreamHandler *proxy,
+ guint stream_id,
+ TpMediaStreamType media_type,
+ TpMediaStreamDirection direction,
+ TfNatProperties *nat_props,
+ GList *local_preferences,
+ NewStreamCreatedCb new_stream_created_cb)
+{
+ TfStream *self = NULL;
+
+ self = g_object_new (TF_TYPE_STREAM,
+ "channel", channel,
+ "farstream-conference", conference,
+ "farstream-participant", participant,
+ "proxy", proxy,
+ "stream-id", stream_id,
+ "media-type", media_type,
+ "direction", direction,
+ "nat-properties", nat_props,
+ "codec-preferences", local_preferences,
+ NULL);
+
+ self->priv->new_stream_created_cb = new_stream_created_cb;
+
+ return self;
+}
+
+static GHashTable *
+fs_codecs_to_feedback_messages (GList *fscodecs)
+{
+ GList *item;
+ GHashTable *feedback_messages = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, NULL, (GDestroyNotify) g_value_array_free);
+
+ for (item = fscodecs; item; item = item->next)
+ {
+ FsCodec *fs_codec = item->data;
+
+ if (fs_codec->minimum_reporting_interval != G_MAXUINT ||
+ fs_codec->feedback_params)
+ {
+ GValueArray *codec = g_value_array_new (2);
+ GPtrArray *messages = g_ptr_array_new ();
+ GValue *val;
+ GList *item2;
+
+ for (item2 = fs_codec->feedback_params;
+ item2;
+ item2 = item2->next)
+ {
+ FsFeedbackParameter *p = item2->data;
+ GValueArray *message = g_value_array_new (3);
+ GValue *val2;
+
+ g_value_array_insert (message, 0, NULL);
+ val2 = g_value_array_get_nth (message, 0);
+ g_value_init (val2, G_TYPE_STRING);
+ g_value_set_string (val2, p->type);
+
+ g_value_array_insert (message, 1, NULL);
+ val2 = g_value_array_get_nth (message, 1);
+ g_value_init (val2, G_TYPE_STRING);
+ g_value_set_string (val2, p->subtype);
+
+ g_value_array_insert (message, 2, NULL);
+ val2 = g_value_array_get_nth (message, 2);
+ g_value_init (val2, G_TYPE_STRING);
+ g_value_set_string (val2, p->extra_params);
+
+ g_ptr_array_add (messages, message);
+ }
+
+ g_value_array_insert (codec, 0, NULL);
+ val = g_value_array_get_nth (codec, 0);
+ g_value_init (val, G_TYPE_UINT);
+ g_value_set_uint (val, fs_codec->minimum_reporting_interval);
+
+ g_value_array_insert (codec, 1, NULL);
+ val = g_value_array_get_nth (codec, 1);
+ g_value_init (val, TP_ARRAY_TYPE_RTCP_FEEDBACK_MESSAGE_LIST);
+ g_value_take_boxed (val, messages);
+
+ g_hash_table_insert (feedback_messages,
+ GUINT_TO_POINTER (fs_codec->id), codec);
+ }
+ }
+
+ return feedback_messages;
+}
+
+
+static TpMediaStreamDirection
+fsdirection_to_tpdirection (FsStreamDirection dir)
+{
+ switch (dir) {
+ case FS_DIRECTION_NONE:
+ return TP_MEDIA_STREAM_DIRECTION_NONE;
+ case FS_DIRECTION_SEND:
+ return TP_MEDIA_STREAM_DIRECTION_SEND;
+ case FS_DIRECTION_RECV:
+ return TP_MEDIA_STREAM_DIRECTION_RECEIVE;
+ case FS_DIRECTION_BOTH:
+ return TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+
+static GPtrArray *
+_tf_stream_get_header_extensions (TfStream *stream)
+{
+ GPtrArray *extensions = g_ptr_array_new ();
+ GList *hdrexts;
+ GList *item;
+
+ if (!g_object_class_find_property (
+ G_OBJECT_GET_CLASS (stream->priv->fs_session),
+ "rtp-header-extensions"))
+ return extensions;
+
+ g_object_get (stream->priv->fs_session,
+ "rtp-header-extensions", &hdrexts, NULL);
+
+ for (item = hdrexts; item; item = item->next)
+ {
+ FsRtpHeaderExtension *hdrext = item->data;
+
+ g_ptr_array_add (extensions,
+ tp_value_array_build (4,
+ G_TYPE_UINT, hdrext->id,
+ G_TYPE_UINT, fsdirection_to_tpdirection (hdrext->direction),
+ G_TYPE_STRING, hdrext->uri,
+ G_TYPE_STRING, "",
+ G_TYPE_INVALID));
+ }
+
+ return extensions;
+}
+
+void
+_tf_stream_try_sending_codecs (TfStream *stream)
+{
+ GList *fscodecs = NULL;
+ GList *item = NULL;
+ GPtrArray *tpcodecs = NULL;
+ GHashTable *feedback_messages = NULL;
+ GPtrArray *header_extensions = NULL;
+ gboolean sent = FALSE;
+ GList *resend_codecs = NULL;
+ const gchar *codecs_prop = NULL;
+
+ DEBUG (stream, "called (send_local:%d send_supported:%d)",
+ stream->priv->send_local_codecs, stream->priv->send_supported_codecs);
+
+
+ if (stream->priv->has_resource & TP_MEDIA_STREAM_DIRECTION_SEND)
+ codecs_prop = "codecs";
+ else
+ codecs_prop = "codecs-without-config";
+
+ g_object_get (stream->priv->fs_session, codecs_prop, &fscodecs, NULL);
+
+ if (!fscodecs)
+ {
+ DEBUG (stream, "Ignoring new codecs because we're sending,"
+ " but we're not ready");
+ return;
+ }
+
+ for(item = fscodecs; item; item = g_list_next (item))
+ {
+ gchar *tmp = fs_codec_to_string (item->data);
+ DEBUG (stream, "%s", tmp);
+ g_free (tmp);
+ }
+
+ if (stream->priv->send_local_codecs)
+ {
+ tpcodecs = fs_codecs_to_tp (stream, fscodecs);
+ feedback_messages = fs_codecs_to_feedback_messages (fscodecs);
+ header_extensions = _tf_stream_get_header_extensions (stream);
+
+ DEBUG (stream, "calling MediaStreamHandler::Ready");
+ tp_cli_media_stream_handler_call_supported_feedback_messages (
+ stream->priv->stream_handler_proxy,
+ -1, feedback_messages, async_method_callback_optional,
+ "Media.StreamHandler::SupportedFeedbackMessages for Ready",
+ NULL, (GObject *) stream);
+ tp_cli_media_stream_handler_call_supported_header_extensions (
+ stream->priv->stream_handler_proxy,
+ -1, header_extensions, async_method_callback_optional,
+ "Media.StreamHandler::SupportedHeaderExtensions for Ready",
+ NULL, (GObject *) stream);
+ tp_cli_media_stream_handler_call_ready (
+ stream->priv->stream_handler_proxy,
+ -1, tpcodecs, async_method_callback, "Media.StreamHandler::Ready",
+ NULL, (GObject *) stream);
+ stream->priv->send_local_codecs = FALSE;
+ sent = TRUE;
+ goto out;
+ }
+
+ if (stream->priv->send_supported_codecs)
+ {
+ tpcodecs = fs_codecs_to_tp (stream, fscodecs);
+ feedback_messages = fs_codecs_to_feedback_messages (fscodecs);
+ header_extensions = _tf_stream_get_header_extensions (stream);
+
+ DEBUG (stream, "calling MediaStreamHandler::SupportedCodecs");
+ tp_cli_media_stream_handler_call_supported_feedback_messages (
+ stream->priv->stream_handler_proxy,
+ -1, feedback_messages, async_method_callback_optional,
+ "Media.StreamHandler::SupportedFeedbackMessages for SupportedCodecs",
+ NULL, (GObject *) stream);
+ tp_cli_media_stream_handler_call_supported_header_extensions (
+ stream->priv->stream_handler_proxy,
+ -1, header_extensions, async_method_callback_optional,
+ "Media.StreamHandler::SupportedHeaderExtensions for SupportedCodecs",
+ NULL, (GObject *) stream);
+ tp_cli_media_stream_handler_call_supported_codecs (
+ stream->priv->stream_handler_proxy,
+ -1, tpcodecs, async_method_callback,
+ "Media.StreamHandler::SupportedCodecs", NULL, (GObject *) stream);
+ stream->priv->send_supported_codecs = FALSE;
+ sent = TRUE;
+
+ /* Fallthrough to potentially call CodecsUpdated as CMs assume
+ * SupportedCodecs will only give the intersection of the already sent
+ * (if any) local codecs, not any updates */
+ }
+
+
+ /* Only send updates if there was something to update (iotw we sent codecs
+ * before) or our list changed */
+ if (stream->priv->last_sent_codecs != NULL
+ && (resend_codecs =
+ fs_session_codecs_need_resend (stream->priv->fs_session,
+ stream->priv->last_sent_codecs, fscodecs)) != NULL)
+ {
+ fs_codec_list_destroy (resend_codecs);
+
+ if (!tpcodecs)
+ tpcodecs = fs_codecs_to_tp (stream, fscodecs);
+ if (!feedback_messages)
+ feedback_messages = fs_codecs_to_feedback_messages (fscodecs);
+ if (!header_extensions)
+ header_extensions = _tf_stream_get_header_extensions (stream);
+
+
+ DEBUG (stream, "calling MediaStreamHandler::CodecsUpdated");
+ tp_cli_media_stream_handler_call_supported_feedback_messages (
+ stream->priv->stream_handler_proxy,
+ -1, feedback_messages, async_method_callback_optional,
+ "Media.StreamHandler::SupportedFeedbackMessages for CodecsUpdated",
+ NULL, (GObject *) stream);
+ tp_cli_media_stream_handler_call_supported_header_extensions (
+ stream->priv->stream_handler_proxy,
+ -1, header_extensions, async_method_callback_optional,
+ "Media.StreamHandler::SupportedHeaderExtensions for CodecsUpdated",
+ NULL, (GObject *) stream);
+ tp_cli_media_stream_handler_call_codecs_updated (
+ stream->priv->stream_handler_proxy,
+ -1, tpcodecs, async_method_callback,
+ "Media.StreamHandler::CodecsUpdated", NULL, (GObject *) stream);
+ sent = TRUE;
+ }
+
+out:
+ if (tpcodecs)
+ g_boxed_free (TP_ARRAY_TYPE_MEDIA_STREAM_HANDLER_CODEC_LIST, tpcodecs);
+ if (feedback_messages)
+ g_boxed_free (TP_HASH_TYPE_RTCP_FEEDBACK_MESSAGE_MAP, feedback_messages);
+ if (header_extensions)
+ g_boxed_free (TP_ARRAY_TYPE_RTP_HEADER_EXTENSIONS_LIST, header_extensions);
+ if (sent)
+ {
+ fs_codec_list_destroy (stream->priv->last_sent_codecs);
+ stream->priv->last_sent_codecs = fscodecs;
+ }
+}
+
+/**
+ * tf_stream_get_id
+ * @stream: A #TfStream
+ *
+ * Quick getter for the stream id
+ *
+ * Returns: the stream's id
+ */
+
+guint
+tf_stream_get_id (TfStream *stream)
+{
+ g_return_val_if_fail (TF_IS_STREAM (stream), 0);
+
+ return stream->stream_id;
+}
+
+static TpMediaStreamError
+fserrorno_to_tperrorno (FsError fserror)
+{
+ TpMediaStreamError tperror;
+
+ switch (fserror)
+ {
+ case FS_ERROR_NETWORK:
+ tperror = TP_MEDIA_STREAM_ERROR_NETWORK_ERROR;
+ break;
+ case FS_ERROR_CONNECTION_FAILED:
+ tperror = TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED;
+ break;
+ case FS_ERROR_NO_CODECS:
+ tperror = TP_MEDIA_STREAM_ERROR_NO_CODECS;
+ break;
+ case FS_ERROR_NEGOTIATION_FAILED:
+ tperror = TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED;
+ break;
+ case FS_ERROR_INVALID_ARGUMENTS:
+ tperror = TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR;
+ break;
+ case FS_ERROR_NO_CODECS_LEFT:
+ case FS_ERROR_CONSTRUCTION:
+ case FS_ERROR_INTERNAL:
+ case FS_ERROR_NOT_IMPLEMENTED: /* Not really a real error */
+ case FS_ERROR_DISPOSED: /* Not really a real error */
+ default:
+ tperror = TP_MEDIA_STREAM_ERROR_MEDIA_ERROR;
+ }
+
+ return tperror;
+}
+
+TpMediaStreamError
+fserror_to_tperror (GError *error)
+{
+ if (!error || error->domain != FS_ERROR)
+ return TP_MEDIA_STREAM_ERROR_UNKNOWN;
+
+ return fserrorno_to_tperrorno (error->code);
+}
+
+static void
+cb_fs_component_state_changed (TfStream *self,
+ guint component,
+ FsStreamState fsstate)
+{
+ TpMediaStreamState state;
+ const gchar *state_str;
+
+ if (component != 1)
+ return;
+
+ switch (fsstate)
+ {
+ case FS_STREAM_STATE_FAILED:
+ case FS_STREAM_STATE_DISCONNECTED:
+ state = TP_MEDIA_STREAM_STATE_DISCONNECTED;
+ state_str = "disconnected";
+ break;
+ case FS_STREAM_STATE_GATHERING:
+ case FS_STREAM_STATE_CONNECTING:
+ state = TP_MEDIA_STREAM_STATE_CONNECTING;
+ break;
+ case FS_STREAM_STATE_CONNECTED:
+ default:
+ state_str = "connected";
+ state = TP_MEDIA_STREAM_STATE_CONNECTED;
+ break;
+ }
+
+ DEBUG (self, "calling MediaStreamHandler::StreamState (%u: %s)", state,
+ state_str);
+
+ self->priv->current_state = state;
+
+ tp_cli_media_stream_handler_call_stream_state (
+ self->priv->stream_handler_proxy, -1, state,
+ async_method_callback, "Media.StreamHandler::StreamState",
+ NULL, (GObject *) self);
+}
diff --git a/farstream/telepathy-farstream/stream.h b/farstream/telepathy-farstream/stream.h
new file mode 100644
index 000000000..ddd4b6bbe
--- /dev/null
+++ b/farstream/telepathy-farstream/stream.h
@@ -0,0 +1,61 @@
+#ifndef __TF_STREAM_H__
+#define __TF_STREAM_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/media-interfaces.h>
+
+#include <farstream/fs-conference.h>
+
+G_BEGIN_DECLS
+
+#define TF_TYPE_STREAM tf_stream_get_type()
+
+#define TF_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TF_TYPE_STREAM, TfStream))
+
+#define TF_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ TF_TYPE_STREAM, TfStreamClass))
+
+#define TF_IS_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ TF_TYPE_STREAM))
+
+#define TF_IS_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ TF_TYPE_STREAM))
+
+#define TF_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TF_TYPE_STREAM, TfStreamClass))
+
+/**
+ * TfStream:
+ *
+ * All members are privated
+ */
+
+typedef struct _TfStream TfStream;
+
+/**
+ * TfStreamClass:
+ *
+ * This class is not subclassable
+ */
+
+typedef struct _TfStreamClass TfStreamClass;
+
+GType tf_stream_get_type (void);
+
+guint tf_stream_get_id (TfStream *stream);
+
+void tf_stream_error (TfStream *self,
+ TpMediaStreamError error,
+ const gchar *message);
+
+
+G_END_DECLS
+
+#endif /* __TF_STREAM_H__ */
diff --git a/farstream/telepathy-farstream/telepathy-farstream-uninstalled.pc.in b/farstream/telepathy-farstream/telepathy-farstream-uninstalled.pc.in
new file mode 100644
index 000000000..37fc7652d
--- /dev/null
+++ b/farstream/telepathy-farstream/telepathy-farstream-uninstalled.pc.in
@@ -0,0 +1,11 @@
+prefix=
+exec_prefix=
+abs_top_srcdir=@abs_top_srcdir@
+abs_top_builddir=@abs_top_builddir@
+
+Name: Telepathy-Farstream
+Description: Library implementing the Telepathy Call API using Farstream
+Version: @VERSION@
+Requires.private: dbus-glib-1 >= 0.73, glib-2.0 >= 2.10, gobject-2.0 >= 2.10, farstream-0.1
+Libs: ${abs_top_builddir}/telepathy-farstream/libtelepathy-farstream.la
+Cflags: -I${abs_top_srcdir} -I${abs_top_builddir}
diff --git a/farstream/telepathy-farstream/telepathy-farstream.c b/farstream/telepathy-farstream/telepathy-farstream.c
new file mode 100644
index 000000000..85d16a1f9
--- /dev/null
+++ b/farstream/telepathy-farstream/telepathy-farstream.c
@@ -0,0 +1,34 @@
+/*
+ * telepathy-farstream.c - Global functions for telepathy-farstream
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "extensions/extensions.h"
+#include <telepathy-farstream/telepathy-farstream.h>
+
+/**
+ * tf_init:
+ *
+ * Initializes telepathy-farstream. This must be called at the start of your
+ * application, specifically before any DBus proxies related to Call channels
+ * are created
+ */
+void
+tf_init (void)
+{
+ tf_future_cli_init ();
+}
diff --git a/farstream/telepathy-farstream/telepathy-farstream.h b/farstream/telepathy-farstream/telepathy-farstream.h
new file mode 100644
index 000000000..256389495
--- /dev/null
+++ b/farstream/telepathy-farstream/telepathy-farstream.h
@@ -0,0 +1,33 @@
+/*
+ * telepathy-farstream.h - Source for TfCallChannel
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __TF_FARSTREAM_H_
+#define __TF_FARSTREAM_H_
+
+#include <telepathy-farstream/channel.h>
+#include <telepathy-farstream/content.h>
+
+G_BEGIN_DECLS
+
+void tf_init (void);
+
+G_END_DECLS
+
+#endif /* __TF_FARSTREAM_H__ */
+
diff --git a/farstream/telepathy-farstream/telepathy-farstream.pc.in b/farstream/telepathy-farstream/telepathy-farstream.pc.in
new file mode 100644
index 000000000..3734eb1de
--- /dev/null
+++ b/farstream/telepathy-farstream/telepathy-farstream.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: Telepathy-Farstream
+Description: Library implementing the Telepathy Call API using Farstream
+Version: @VERSION@
+Requires.private: dbus-glib-1 >= 0.73, glib-2.0 >= 2.10, gobject-2.0 >= 2.10, farstream-0.1, telepathy-glib >= 0.7.23, gstreamer-0.10
+Libs: -L${libdir} -ltelepathy-farstream
+Cflags: -I${includedir}/telepathy-1.0
diff --git a/farstream/telepathy-farstream/utils.h b/farstream/telepathy-farstream/utils.h
new file mode 100644
index 000000000..934924cf5
--- /dev/null
+++ b/farstream/telepathy-farstream/utils.h
@@ -0,0 +1,28 @@
+#ifndef __UTILS_H__
+#define __UTILS_H__
+
+/**
+ * tp_media_type_to_fs:
+ * @type: A Telepathy Media Stream Type
+ *
+ * Converts a Telepathy Media Stream Type to the Farstream equivalent
+ *
+ * Return: A Farstream Stream Type
+ */
+
+static inline FsMediaType
+tp_media_type_to_fs (TpMediaStreamType type)
+{
+ switch (type)
+ {
+ case TP_MEDIA_STREAM_TYPE_AUDIO:
+ return FS_MEDIA_TYPE_AUDIO;
+ case TP_MEDIA_STREAM_TYPE_VIDEO:
+ return FS_MEDIA_TYPE_VIDEO;
+ default:
+ g_return_val_if_reached(0);
+ }
+}
+
+
+#endif /* __UTILS_H__ */
diff --git a/farstream/tools/Makefile.am b/farstream/tools/Makefile.am
new file mode 100644
index 000000000..4e776776f
--- /dev/null
+++ b/farstream/tools/Makefile.am
@@ -0,0 +1,21 @@
+EXTRA_DIST = \
+ telepathy.am \
+ c-constants-gen.py \
+ glib-client-gen.py \
+ glib-client-marshaller-gen.py \
+ glib-ginterface-gen.py \
+ glib-gtypes-generator.py \
+ glib-interfaces-gen.py \
+ glib-signals-marshal-gen.py \
+ libglibcodegen.py \
+ libtpcodegen.py \
+ xincludator.py
+
+TELEPATHY_GLIB_SRCDIR = $(top_srcdir)/../telepathy-glib
+maintainer-update-from-telepathy-glib:
+ set -e && cd $(srcdir) && \
+ for x in $(EXTRA_DIST); do \
+ if test -f $(TELEPATHY_GLIB_SRCDIR)/tools/$$x; then \
+ cp $(TELEPATHY_GLIB_SRCDIR)/tools/$$x $$x; \
+ fi; \
+ done
diff --git a/farstream/tools/c-constants-gen.py b/farstream/tools/c-constants-gen.py
new file mode 100644
index 000000000..09608c2bd
--- /dev/null
+++ b/farstream/tools/c-constants-gen.py
@@ -0,0 +1,169 @@
+#!/usr/bin/python
+
+from sys import argv, stdout, stderr
+import xml.dom.minidom
+
+from libglibcodegen import NS_TP, get_docstring, \
+ get_descendant_text, get_by_path
+
+class Generator(object):
+ def __init__(self, prefix, dom, output_base):
+ self.prefix = prefix + '_'
+ self.spec = get_by_path(dom, "spec")[0]
+
+ self.__header = open(output_base + '.h', 'w')
+ self.__docs = open(output_base + '-gtk-doc.h', 'w')
+
+ def __call__(self):
+ self.do_header()
+ self.do_body()
+ self.do_footer()
+
+ def write(self, code):
+ self.__header.write(code.encode('utf-8'))
+
+ def d(self, code):
+ self.__docs.write(code.encode('utf-8'))
+
+ # Header
+ def do_header(self):
+ self.write('/* Generated from ')
+ self.write(get_descendant_text(get_by_path(self.spec, 'title')))
+ version = get_by_path(self.spec, "version")
+ if version:
+ self.write(', version ' + get_descendant_text(version))
+ self.write('\n\n')
+ for copyright in get_by_path(self.spec, 'copyright'):
+ self.write(get_descendant_text(copyright))
+ self.write('\n')
+ self.write(get_descendant_text(get_by_path(self.spec, 'license')))
+ self.write('\n')
+ self.write(get_descendant_text(get_by_path(self.spec, 'docstring')))
+ self.write("""
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+\n""")
+
+ # Body
+ def do_body(self):
+ for elem in self.spec.getElementsByTagNameNS(NS_TP, '*'):
+ if elem.localName == 'flags':
+ self.do_flags(elem)
+ elif elem.localName == 'enum':
+ self.do_enum(elem)
+
+ def do_flags(self, flags):
+ name = flags.getAttribute('plural') or flags.getAttribute('name')
+ value_prefix = flags.getAttribute('singular') or \
+ flags.getAttribute('value-prefix') or \
+ flags.getAttribute('name')
+ self.d("""\
+/**
+ *
+%s:
+""" % (self.prefix + name).replace('_', ''))
+ for flag in get_by_path(flags, 'flag'):
+ self.do_gtkdoc(flag, value_prefix)
+ self.d(' *\n')
+ docstrings = get_by_path(flags, 'docstring')
+ if docstrings:
+ self.d("""\
+ * <![CDATA[%s]]>
+ *
+""" % get_descendant_text(docstrings).replace('\n', ' '))
+ self.d("""\
+ * Bitfield/set of flags generated from the Telepathy specification.
+ */
+""")
+
+ self.write("typedef enum {\n")
+
+ for flag in get_by_path(flags, 'flag'):
+ self.do_val(flag, value_prefix)
+ self.write("""\
+} %s;
+
+""" % (self.prefix + name).replace('_', ''))
+
+ def do_enum(self, enum):
+ name = enum.getAttribute('singular') or enum.getAttribute('name')
+ value_prefix = enum.getAttribute('singular') or \
+ enum.getAttribute('value-prefix') or \
+ enum.getAttribute('name')
+ name_plural = enum.getAttribute('plural') or \
+ enum.getAttribute('name') + 's'
+ self.d("""\
+/**
+ *
+%s:
+""" % (self.prefix + name).replace('_', ''))
+ vals = get_by_path(enum, 'enumvalue')
+ for val in vals:
+ self.do_gtkdoc(val, value_prefix)
+ self.d(' *\n')
+ docstrings = get_by_path(enum, 'docstring')
+ if docstrings:
+ self.d("""\
+ * <![CDATA[%s]]>
+ *
+""" % get_descendant_text(docstrings).replace('\n', ' '))
+ self.d("""\
+ * Bitfield/set of flags generated from the Telepathy specification.
+ */
+""")
+
+ self.write("typedef enum {\n")
+
+ for val in vals:
+ self.do_val(val, value_prefix)
+ self.write("} %s;\n" % (self.prefix + name).replace('_', ''))
+
+ self.d("""\
+/**
+ * NUM_%(upper-plural)s:
+ *
+ * 1 higher than the highest valid value of #%(mixed-name)s.
+ */
+""" % {'mixed-name' : (self.prefix + name).replace('_', ''),
+ 'upper-plural' : (self.prefix + name_plural).upper(),
+ 'last-val' : vals[-1].getAttribute('value')})
+
+ self.write("""\
+#define NUM_%(upper-plural)s (%(last-val)s+1)
+
+""" % {'mixed-name' : (self.prefix + name).replace('_', ''),
+ 'upper-plural' : (self.prefix + name_plural).upper(),
+ 'last-val' : vals[-1].getAttribute('value')})
+
+ def do_val(self, val, value_prefix):
+ name = val.getAttribute('name')
+ suffix = val.getAttribute('suffix')
+ use_name = (self.prefix + value_prefix + '_' + \
+ (suffix or name)).upper()
+ assert not (name and suffix) or name == suffix, \
+ 'Flag/enumvalue name %s != suffix %s' % (name, suffix)
+ self.write(' %s = %s,\n' % (use_name, val.getAttribute('value')))
+
+ def do_gtkdoc(self, node, value_prefix):
+ self.d(' * @')
+ self.d((self.prefix + value_prefix + '_' +
+ node.getAttribute('suffix')).upper())
+ self.d(': <![CDATA[')
+ docstring = get_by_path(node, 'docstring')
+ self.d(get_descendant_text(docstring).replace('\n', ' '))
+ self.d(']]>\n')
+
+ # Footer
+ def do_footer(self):
+ self.write("""
+#ifdef __cplusplus
+}
+#endif
+""")
+
+if __name__ == '__main__':
+ argv = argv[1:]
+ Generator(argv[0], xml.dom.minidom.parse(argv[1]), argv[2])()
diff --git a/farstream/tools/glib-client-gen.py b/farstream/tools/glib-client-gen.py
new file mode 100644
index 000000000..6b0bdeba1
--- /dev/null
+++ b/farstream/tools/glib-client-gen.py
@@ -0,0 +1,1224 @@
+#!/usr/bin/python
+
+# glib-client-gen.py: "I Can't Believe It's Not dbus-binding-tool"
+#
+# Generate GLib client wrappers from the Telepathy specification.
+# The master copy of this program is in the telepathy-glib repository -
+# please make any changes there.
+#
+# Copyright (C) 2006-2008 Collabora Ltd. <http://www.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 St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import sys
+import os.path
+import xml.dom.minidom
+from getopt import gnu_getopt
+
+from libglibcodegen import Signature, type_to_gtype, cmp_by_name, \
+ get_docstring, xml_escape, get_deprecated
+
+
+NS_TP = "http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+
+class Generator(object):
+
+ def __init__(self, dom, prefix, basename, opts):
+ self.dom = dom
+ self.__header = []
+ self.__body = []
+ self.__docs = []
+
+ self.prefix_lc = prefix.lower()
+ self.prefix_uc = prefix.upper()
+ self.prefix_mc = prefix.replace('_', '')
+ self.basename = basename
+ self.group = opts.get('--group', None)
+ self.iface_quark_prefix = opts.get('--iface-quark-prefix', None)
+ self.tp_proxy_api = tuple(map(int,
+ opts.get('--tp-proxy-api', '0').split('.')))
+ self.proxy_cls = opts.get('--subclass', 'TpProxy') + ' *'
+ self.proxy_arg = opts.get('--subclass', 'void') + ' *'
+ self.proxy_assert = opts.get('--subclass-assert', 'TP_IS_PROXY')
+ self.proxy_doc = ('A #%s or subclass'
+ % opts.get('--subclass', 'TpProxy'))
+ if self.proxy_arg == 'void *':
+ self.proxy_arg = 'gpointer '
+ self.generate_reentrant = ('--generate-reentrant' in opts or
+ '--deprecate-reentrant' in opts)
+ self.deprecate_reentrant = opts.get('--deprecate-reentrant', None)
+ self.deprecation_attribute = opts.get('--deprecation-attribute',
+ 'G_GNUC_DEPRECATED')
+
+ def h(self, s):
+ if isinstance(s, unicode):
+ s = s.encode('utf-8')
+ self.__header.append(s)
+
+ def b(self, s):
+ if isinstance(s, unicode):
+ s = s.encode('utf-8')
+ self.__body.append(s)
+
+ def d(self, s):
+ if isinstance(s, unicode):
+ s = s.encode('utf-8')
+ self.__docs.append(s)
+
+ def get_iface_quark(self):
+ assert self.iface_dbus is not None
+ assert self.iface_uc is not None
+ if self.iface_quark_prefix is None:
+ return 'g_quark_from_static_string (\"%s\")' % self.iface_dbus
+ else:
+ return '%s_%s' % (self.iface_quark_prefix, self.iface_uc)
+
+ def do_signal(self, iface, signal):
+ iface_lc = iface.lower()
+
+ member = signal.getAttribute('name')
+ member_lc = signal.getAttribute('tp:name-for-bindings')
+ if member != member_lc.replace('_', ''):
+ raise AssertionError('Signal %s tp:name-for-bindings (%s) does '
+ 'not match' % (member, member_lc))
+ member_lc = member_lc.lower()
+ member_uc = member_lc.upper()
+
+ arg_count = 0
+ args = []
+ out_args = []
+
+ for arg in signal.getElementsByTagName('arg'):
+ name = arg.getAttribute('name')
+ type = arg.getAttribute('type')
+ tp_type = arg.getAttribute('tp:type')
+
+ if not name:
+ name = 'arg%u' % arg_count
+ arg_count += 1
+ else:
+ name = 'arg_%s' % name
+
+ info = type_to_gtype(type)
+ args.append((name, info, tp_type, arg))
+
+ callback_name = ('%s_%s_signal_callback_%s'
+ % (self.prefix_lc, iface_lc, member_lc))
+ collect_name = ('_%s_%s_collect_args_of_%s'
+ % (self.prefix_lc, iface_lc, member_lc))
+ invoke_name = ('_%s_%s_invoke_callback_for_%s'
+ % (self.prefix_lc, iface_lc, member_lc))
+
+ # Example:
+ #
+ # typedef void (*tp_cli_connection_signal_callback_new_channel)
+ # (TpConnection *proxy, const gchar *arg_object_path,
+ # const gchar *arg_channel_type, guint arg_handle_type,
+ # guint arg_handle, gboolean arg_suppress_handler,
+ # gpointer user_data, GObject *weak_object);
+
+ self.d('/**')
+ self.d(' * %s:' % callback_name)
+ self.d(' * @proxy: The proxy on which %s_%s_connect_to_%s ()'
+ % (self.prefix_lc, iface_lc, member_lc))
+ self.d(' * was called')
+
+ for arg in args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ self.d(' * @%s: %s' % (name,
+ xml_escape(get_docstring(elt) or '(Undocumented)')))
+
+ self.d(' * @user_data: User-supplied data')
+ self.d(' * @weak_object: User-supplied weakly referenced object')
+ self.d(' *')
+ self.d(' * Represents the signature of a callback for the signal %s.'
+ % member)
+ self.d(' */')
+ self.d('')
+
+ self.h('typedef void (*%s) (%sproxy,'
+ % (callback_name, self.proxy_cls))
+
+ for arg in args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ const = pointer and 'const ' or ''
+
+ self.h(' %s%s%s,' % (const, ctype, name))
+
+ self.h(' gpointer user_data, GObject *weak_object);')
+
+ if args:
+ self.b('static void')
+ self.b('%s (DBusGProxy *proxy G_GNUC_UNUSED,' % collect_name)
+
+ for arg in args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ const = pointer and 'const ' or ''
+
+ self.b(' %s%s%s,' % (const, ctype, name))
+
+ self.b(' TpProxySignalConnection *sc)')
+ self.b('{')
+ self.b(' GValueArray *args = g_value_array_new (%d);' % len(args))
+ self.b(' GValue blank = { 0 };')
+ self.b(' guint i;')
+ self.b('')
+ self.b(' g_value_init (&blank, G_TYPE_INT);')
+ self.b('')
+ self.b(' for (i = 0; i < %d; i++)' % len(args))
+ self.b(' g_value_array_append (args, &blank);')
+ self.b('')
+
+ for i, arg in enumerate(args):
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ self.b(' g_value_unset (args->values + %d);' % i)
+ self.b(' g_value_init (args->values + %d, %s);' % (i, gtype))
+
+ if gtype == 'G_TYPE_STRING':
+ self.b(' g_value_set_string (args->values + %d, %s);'
+ % (i, name))
+ elif marshaller == 'BOXED':
+ self.b(' g_value_set_boxed (args->values + %d, %s);'
+ % (i, name))
+ elif gtype == 'G_TYPE_UCHAR':
+ self.b(' g_value_set_uchar (args->values + %d, %s);'
+ % (i, name))
+ elif gtype == 'G_TYPE_BOOLEAN':
+ self.b(' g_value_set_boolean (args->values + %d, %s);'
+ % (i, name))
+ elif gtype == 'G_TYPE_INT':
+ self.b(' g_value_set_int (args->values + %d, %s);'
+ % (i, name))
+ elif gtype == 'G_TYPE_UINT':
+ self.b(' g_value_set_uint (args->values + %d, %s);'
+ % (i, name))
+ elif gtype == 'G_TYPE_INT64':
+ self.b(' g_value_set_int (args->values + %d, %s);'
+ % (i, name))
+ elif gtype == 'G_TYPE_UINT64':
+ self.b(' g_value_set_uint64 (args->values + %d, %s);'
+ % (i, name))
+ elif gtype == 'G_TYPE_DOUBLE':
+ self.b(' g_value_set_double (args->values + %d, %s);'
+ % (i, name))
+ else:
+ assert False, ("Don't know how to put %s in a GValue"
+ % gtype)
+ self.b('')
+
+ self.b(' tp_proxy_signal_connection_v0_take_results (sc, args);')
+ self.b('}')
+
+ self.b('static void')
+ self.b('%s (TpProxy *tpproxy,' % invoke_name)
+ self.b(' GError *error G_GNUC_UNUSED,')
+ self.b(' GValueArray *args,')
+ self.b(' GCallback generic_callback,')
+ self.b(' gpointer user_data,')
+ self.b(' GObject *weak_object)')
+ self.b('{')
+ self.b(' %s callback =' % callback_name)
+ self.b(' (%s) generic_callback;' % callback_name)
+ self.b('')
+ self.b(' if (callback != NULL)')
+ self.b(' callback (g_object_ref (tpproxy),')
+
+ # FIXME: factor out into a function
+ for i, arg in enumerate(args):
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ if marshaller == 'BOXED':
+ self.b(' g_value_get_boxed (args->values + %d),' % i)
+ elif gtype == 'G_TYPE_STRING':
+ self.b(' g_value_get_string (args->values + %d),' % i)
+ elif gtype == 'G_TYPE_UCHAR':
+ self.b(' g_value_get_uchar (args->values + %d),' % i)
+ elif gtype == 'G_TYPE_BOOLEAN':
+ self.b(' g_value_get_boolean (args->values + %d),' % i)
+ elif gtype == 'G_TYPE_UINT':
+ self.b(' g_value_get_uint (args->values + %d),' % i)
+ elif gtype == 'G_TYPE_INT':
+ self.b(' g_value_get_int (args->values + %d),' % i)
+ elif gtype == 'G_TYPE_UINT64':
+ self.b(' g_value_get_uint64 (args->values + %d),' % i)
+ elif gtype == 'G_TYPE_INT64':
+ self.b(' g_value_get_int64 (args->values + %d),' % i)
+ elif gtype == 'G_TYPE_DOUBLE':
+ self.b(' g_value_get_double (args->values + %d),' % i)
+ else:
+ assert False, "Don't know how to get %s from a GValue" % gtype
+
+ self.b(' user_data,')
+ self.b(' weak_object);')
+ self.b('')
+
+ if len(args) > 0:
+ self.b(' g_value_array_free (args);')
+ else:
+ self.b(' if (args != NULL)')
+ self.b(' g_value_array_free (args);')
+ self.b('')
+
+ self.b(' g_object_unref (tpproxy);')
+ self.b('}')
+
+ # Example:
+ #
+ # TpProxySignalConnection *
+ # tp_cli_connection_connect_to_new_channel
+ # (TpConnection *proxy,
+ # tp_cli_connection_signal_callback_new_channel callback,
+ # gpointer user_data,
+ # GDestroyNotify destroy);
+ #
+ # destroy is invoked when the signal becomes disconnected. This
+ # is either because the signal has been disconnected explicitly
+ # by the user, because the TpProxy has become invalid and
+ # emitted the 'invalidated' signal, or because the weakly referenced
+ # object has gone away.
+
+ self.d('/**')
+ self.d(' * %s_%s_connect_to_%s:'
+ % (self.prefix_lc, iface_lc, member_lc))
+ self.d(' * @proxy: %s' % self.proxy_doc)
+ self.d(' * @callback: Callback to be called when the signal is')
+ self.d(' * received')
+ self.d(' * @user_data: User-supplied data for the callback')
+ self.d(' * @destroy: Destructor for the user-supplied data, which')
+ self.d(' * will be called when this signal is disconnected, or')
+ self.d(' * before this function returns %NULL')
+ self.d(' * @weak_object: A #GObject which will be weakly referenced; ')
+ self.d(' * if it is destroyed, this callback will automatically be')
+ self.d(' * disconnected')
+ self.d(' * @error: If not %NULL, used to raise an error if %NULL is')
+ self.d(' * returned')
+ self.d(' *')
+ self.d(' * Connect a handler to the signal %s.' % member)
+ self.d(' *')
+ self.d(' * %s' % xml_escape(get_docstring(signal) or '(Undocumented)'))
+ self.d(' *')
+ self.d(' * Returns: a #TpProxySignalConnection containing all of the')
+ self.d(' * above, which can be used to disconnect the signal; or')
+ self.d(' * %NULL if the proxy does not have the desired interface')
+ self.d(' * or has become invalid.')
+ self.d(' */')
+ self.d('')
+
+ self.h('TpProxySignalConnection *%s_%s_connect_to_%s (%sproxy,'
+ % (self.prefix_lc, iface_lc, member_lc, self.proxy_arg))
+ self.h(' %s callback,' % callback_name)
+ self.h(' gpointer user_data,')
+ self.h(' GDestroyNotify destroy,')
+ self.h(' GObject *weak_object,')
+ self.h(' GError **error);')
+ self.h('')
+
+ self.b('TpProxySignalConnection *')
+ self.b('%s_%s_connect_to_%s (%sproxy,'
+ % (self.prefix_lc, iface_lc, member_lc, self.proxy_arg))
+ self.b(' %s callback,' % callback_name)
+ self.b(' gpointer user_data,')
+ self.b(' GDestroyNotify destroy,')
+ self.b(' GObject *weak_object,')
+ self.b(' GError **error)')
+ self.b('{')
+ self.b(' GType expected_types[%d] = {' % (len(args) + 1))
+
+ for arg in args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ self.b(' %s,' % gtype)
+
+ self.b(' G_TYPE_INVALID };')
+ self.b('')
+ self.b(' g_return_val_if_fail (%s (proxy), NULL);'
+ % self.proxy_assert)
+ self.b(' g_return_val_if_fail (callback != NULL, NULL);')
+ self.b('')
+ self.b(' return tp_proxy_signal_connection_v0_new ((TpProxy *) proxy,')
+ self.b(' %s, \"%s\",' % (self.get_iface_quark(), member))
+ self.b(' expected_types,')
+
+ if args:
+ self.b(' G_CALLBACK (%s),' % collect_name)
+ else:
+ self.b(' NULL, /* no args => no collector function */')
+
+ self.b(' %s,' % invoke_name)
+ self.b(' G_CALLBACK (callback), user_data, destroy,')
+ self.b(' weak_object, error);')
+ self.b('}')
+ self.b('')
+
+ def do_method(self, iface, method):
+ iface_lc = iface.lower()
+
+ member = method.getAttribute('name')
+ member_lc = method.getAttribute('tp:name-for-bindings')
+ if member != member_lc.replace('_', ''):
+ raise AssertionError('Method %s tp:name-for-bindings (%s) does '
+ 'not match' % (member, member_lc))
+ member_lc = member_lc.lower()
+ member_uc = member_lc.upper()
+
+ in_count = 0
+ ret_count = 0
+ in_args = []
+ out_args = []
+
+ for arg in method.getElementsByTagName('arg'):
+ name = arg.getAttribute('name')
+ direction = arg.getAttribute('direction')
+ type = arg.getAttribute('type')
+ tp_type = arg.getAttribute('tp:type')
+
+ if direction != 'out':
+ if not name:
+ name = 'in%u' % in_count
+ in_count += 1
+ else:
+ name = 'in_%s' % name
+ else:
+ if not name:
+ name = 'out%u' % ret_count
+ ret_count += 1
+ else:
+ name = 'out_%s' % name
+
+ info = type_to_gtype(type)
+ if direction != 'out':
+ in_args.append((name, info, tp_type, arg))
+ else:
+ out_args.append((name, info, tp_type, arg))
+
+ # Async reply callback type
+
+ # Example:
+ # void (*tp_cli_properties_interface_callback_for_get_properties)
+ # (TpProxy *proxy,
+ # const GPtrArray *out0,
+ # const GError *error,
+ # gpointer user_data,
+ # GObject *weak_object);
+
+ self.d('/**')
+ self.d(' * %s_%s_callback_for_%s:'
+ % (self.prefix_lc, iface_lc, member_lc))
+ self.d(' * @proxy: the proxy on which the call was made')
+
+ for arg in out_args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ self.d(' * @%s: Used to return an \'out\' argument if @error is '
+ '%%NULL: %s'
+ % (name, xml_escape(get_docstring(elt) or '(Undocumented)')))
+
+ self.d(' * @error: %NULL on success, or an error on failure')
+ self.d(' * @user_data: user-supplied data')
+ self.d(' * @weak_object: user-supplied object')
+ self.d(' *')
+ self.d(' * Signature of the callback called when a %s method call'
+ % member)
+ self.d(' * succeeds or fails.')
+
+ deprecated = method.getElementsByTagName('tp:deprecated')
+ if deprecated:
+ d = deprecated[0]
+ self.d(' *')
+ self.d(' * Deprecated: %s' % xml_escape(get_deprecated(d)))
+
+ self.d(' */')
+ self.d('')
+
+ callback_name = '%s_%s_callback_for_%s' % (self.prefix_lc, iface_lc,
+ member_lc)
+
+ self.h('typedef void (*%s) (%sproxy,'
+ % (callback_name, self.proxy_cls))
+
+ for arg in out_args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+ const = pointer and 'const ' or ''
+
+ self.h(' %s%s%s,' % (const, ctype, name))
+
+ self.h(' const GError *error, gpointer user_data,')
+ self.h(' GObject *weak_object);')
+ self.h('')
+
+ # Async callback implementation
+
+ invoke_callback = '_%s_%s_invoke_callback_%s' % (self.prefix_lc,
+ iface_lc,
+ member_lc)
+
+ collect_callback = '_%s_%s_collect_callback_%s' % (self.prefix_lc,
+ iface_lc,
+ member_lc)
+
+ # The callback called by dbus-glib; this ends the call and collects
+ # the results into a GValueArray.
+ self.b('static void')
+ self.b('%s (DBusGProxy *proxy,' % collect_callback)
+ self.b(' DBusGProxyCall *call,')
+ self.b(' gpointer user_data)')
+ self.b('{')
+ self.b(' GError *error = NULL;')
+
+ if len(out_args) > 0:
+ self.b(' GValueArray *args;')
+ self.b(' GValue blank = { 0 };')
+ self.b(' guint i;')
+
+ for arg in out_args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ # "We handle variants specially; the caller is expected to
+ # have already allocated storage for them". Thanks,
+ # dbus-glib...
+ if gtype == 'G_TYPE_VALUE':
+ self.b(' GValue *%s = g_new0 (GValue, 1);' % name)
+ else:
+ self.b(' %s%s;' % (ctype, name))
+
+ self.b('')
+ self.b(' dbus_g_proxy_end_call (proxy, call, &error,')
+
+ for arg in out_args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ if gtype == 'G_TYPE_VALUE':
+ self.b(' %s, %s,' % (gtype, name))
+ else:
+ self.b(' %s, &%s,' % (gtype, name))
+
+ self.b(' G_TYPE_INVALID);')
+
+ if len(out_args) == 0:
+ self.b(' tp_proxy_pending_call_v0_take_results (user_data, error,'
+ 'NULL);')
+ else:
+ self.b('')
+ self.b(' if (error != NULL)')
+ self.b(' {')
+ self.b(' tp_proxy_pending_call_v0_take_results (user_data, error,')
+ self.b(' NULL);')
+
+ for arg in out_args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+ if gtype == 'G_TYPE_VALUE':
+ self.b(' g_free (%s);' % name)
+
+ self.b(' return;')
+ self.b(' }')
+ self.b('')
+ self.b(' args = g_value_array_new (%d);' % len(out_args))
+ self.b(' g_value_init (&blank, G_TYPE_INT);')
+ self.b('')
+ self.b(' for (i = 0; i < %d; i++)' % len(out_args))
+ self.b(' g_value_array_append (args, &blank);')
+
+ for i, arg in enumerate(out_args):
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ self.b('')
+ self.b(' g_value_unset (args->values + %d);' % i)
+ self.b(' g_value_init (args->values + %d, %s);' % (i, gtype))
+
+ if gtype == 'G_TYPE_STRING':
+ self.b(' g_value_take_string (args->values + %d, %s);'
+ % (i, name))
+ elif marshaller == 'BOXED':
+ self.b(' g_value_take_boxed (args->values + %d, %s);'
+ % (i, name))
+ elif gtype == 'G_TYPE_UCHAR':
+ self.b(' g_value_set_uchar (args->values + %d, %s);'
+ % (i, name))
+ elif gtype == 'G_TYPE_BOOLEAN':
+ self.b(' g_value_set_boolean (args->values + %d, %s);'
+ % (i, name))
+ elif gtype == 'G_TYPE_INT':
+ self.b(' g_value_set_int (args->values + %d, %s);'
+ % (i, name))
+ elif gtype == 'G_TYPE_UINT':
+ self.b(' g_value_set_uint (args->values + %d, %s);'
+ % (i, name))
+ elif gtype == 'G_TYPE_INT64':
+ self.b(' g_value_set_int (args->values + %d, %s);'
+ % (i, name))
+ elif gtype == 'G_TYPE_UINT64':
+ self.b(' g_value_set_uint (args->values + %d, %s);'
+ % (i, name))
+ elif gtype == 'G_TYPE_DOUBLE':
+ self.b(' g_value_set_double (args->values + %d, %s);'
+ % (i, name))
+ else:
+ assert False, ("Don't know how to put %s in a GValue"
+ % gtype)
+
+ self.b(' tp_proxy_pending_call_v0_take_results (user_data, '
+ 'NULL, args);')
+
+ self.b('}')
+
+ self.b('static void')
+ self.b('%s (TpProxy *self,' % invoke_callback)
+ self.b(' GError *error,')
+ self.b(' GValueArray *args,')
+ self.b(' GCallback generic_callback,')
+ self.b(' gpointer user_data,')
+ self.b(' GObject *weak_object)')
+ self.b('{')
+ self.b(' %s callback = (%s) generic_callback;'
+ % (callback_name, callback_name))
+ self.b('')
+ self.b(' if (error != NULL)')
+ self.b(' {')
+ self.b(' callback ((%s) self,' % self.proxy_cls)
+
+ for arg in out_args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ if marshaller == 'BOXED' or pointer:
+ self.b(' NULL,')
+ elif gtype == 'G_TYPE_DOUBLE':
+ self.b(' 0.0,')
+ else:
+ self.b(' 0,')
+
+ self.b(' error, user_data, weak_object);')
+ self.b(' g_error_free (error);')
+ self.b(' return;')
+ self.b(' }')
+
+ self.b(' callback ((%s) self,' % self.proxy_cls)
+
+ # FIXME: factor out into a function
+ for i, arg in enumerate(out_args):
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ if marshaller == 'BOXED':
+ self.b(' g_value_get_boxed (args->values + %d),' % i)
+ elif gtype == 'G_TYPE_STRING':
+ self.b(' g_value_get_string (args->values + %d),' % i)
+ elif gtype == 'G_TYPE_UCHAR':
+ self.b(' g_value_get_uchar (args->values + %d),' % i)
+ elif gtype == 'G_TYPE_BOOLEAN':
+ self.b(' g_value_get_boolean (args->values + %d),' % i)
+ elif gtype == 'G_TYPE_UINT':
+ self.b(' g_value_get_uint (args->values + %d),' % i)
+ elif gtype == 'G_TYPE_INT':
+ self.b(' g_value_get_int (args->values + %d),' % i)
+ elif gtype == 'G_TYPE_UINT64':
+ self.b(' g_value_get_uint64 (args->values + %d),' % i)
+ elif gtype == 'G_TYPE_INT64':
+ self.b(' g_value_get_int64 (args->values + %d),' % i)
+ elif gtype == 'G_TYPE_DOUBLE':
+ self.b(' g_value_get_double (args->values + %d),' % i)
+ else:
+ assert False, "Don't know how to get %s from a GValue" % gtype
+
+ self.b(' error, user_data, weak_object);')
+ self.b('')
+
+ if len(out_args) > 0:
+ self.b(' g_value_array_free (args);')
+ else:
+ self.b(' if (args != NULL)')
+ self.b(' g_value_array_free (args);')
+
+ self.b('}')
+ self.b('')
+
+ # Async stub
+
+ # Example:
+ # TpProxyPendingCall *
+ # tp_cli_properties_interface_call_get_properties
+ # (gpointer proxy,
+ # gint timeout_ms,
+ # const GArray *in_properties,
+ # tp_cli_properties_interface_callback_for_get_properties callback,
+ # gpointer user_data,
+ # GDestroyNotify *destructor);
+
+ self.h('TpProxyPendingCall *%s_%s_call_%s (%sproxy,'
+ % (self.prefix_lc, iface_lc, member_lc, self.proxy_arg))
+ self.h(' gint timeout_ms,')
+
+ self.d('/**')
+ self.d(' * %s_%s_call_%s:'
+ % (self.prefix_lc, iface_lc, member_lc))
+ self.d(' * @proxy: the #TpProxy')
+ self.d(' * @timeout_ms: the timeout in milliseconds, or -1 to use the')
+ self.d(' * default')
+
+ for arg in in_args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ self.d(' * @%s: Used to pass an \'in\' argument: %s'
+ % (name, xml_escape(get_docstring(elt) or '(Undocumented)')))
+
+ self.d(' * @callback: called when the method call succeeds or fails;')
+ self.d(' * may be %NULL to make a "fire and forget" call with no ')
+ self.d(' * reply tracking')
+ self.d(' * @user_data: user-supplied data passed to the callback;')
+ self.d(' * must be %NULL if @callback is %NULL')
+ self.d(' * @destroy: called with the user_data as argument, after the')
+ self.d(' * call has succeeded, failed or been cancelled;')
+ self.d(' * must be %NULL if @callback is %NULL')
+ self.d(' * @weak_object: If not %NULL, a #GObject which will be ')
+ self.d(' * weakly referenced; if it is destroyed, this call ')
+ self.d(' * will automatically be cancelled. Must be %NULL if ')
+ self.d(' * @callback is %NULL')
+ self.d(' *')
+ self.d(' * Start a %s method call.' % member)
+ self.d(' *')
+ self.d(' * %s' % xml_escape(get_docstring(method) or '(Undocumented)'))
+ self.d(' *')
+ self.d(' * Returns: a #TpProxyPendingCall representing the call in')
+ self.d(' * progress. It is borrowed from the object, and will become')
+ self.d(' * invalid when the callback is called, the call is')
+ self.d(' * cancelled or the #TpProxy becomes invalid.')
+
+ deprecated = method.getElementsByTagName('tp:deprecated')
+ if deprecated:
+ d = deprecated[0]
+ self.d(' *')
+ self.d(' * Deprecated: %s' % xml_escape(get_deprecated(d)))
+
+ self.d(' */')
+ self.d('')
+
+ self.b('TpProxyPendingCall *\n%s_%s_call_%s (%sproxy,'
+ % (self.prefix_lc, iface_lc, member_lc, self.proxy_arg))
+ self.b(' gint timeout_ms,')
+
+ for arg in in_args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ const = pointer and 'const ' or ''
+
+ self.h(' %s%s%s,' % (const, ctype, name))
+ self.b(' %s%s%s,' % (const, ctype, name))
+
+ self.h(' %s callback,' % callback_name)
+ self.h(' gpointer user_data,')
+ self.h(' GDestroyNotify destroy,')
+ self.h(' GObject *weak_object);')
+ self.h('')
+
+ self.b(' %s callback,' % callback_name)
+ self.b(' gpointer user_data,')
+ self.b(' GDestroyNotify destroy,')
+ self.b(' GObject *weak_object)')
+ self.b('{')
+ self.b(' GError *error = NULL;')
+ self.b(' GQuark interface = %s;' % self.get_iface_quark())
+ self.b(' DBusGProxy *iface;')
+ self.b('')
+ self.b(' g_return_val_if_fail (%s (proxy), NULL);'
+ % self.proxy_assert)
+ self.b(' g_return_val_if_fail (callback != NULL || '
+ 'user_data == NULL, NULL);')
+ self.b(' g_return_val_if_fail (callback != NULL || '
+ 'destroy == NULL, NULL);')
+ self.b(' g_return_val_if_fail (callback != NULL || '
+ 'weak_object == NULL, NULL);')
+ self.b('')
+ self.b(' iface = tp_proxy_borrow_interface_by_id (')
+ self.b(' (TpProxy *) proxy,')
+ self.b(' interface, &error);')
+ self.b('')
+ self.b(' if (iface == NULL)')
+ self.b(' {')
+ self.b(' if (callback != NULL)')
+ self.b(' callback (proxy,')
+
+ for arg in out_args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ if pointer:
+ self.b(' NULL,')
+ else:
+ self.b(' 0,')
+
+ self.b(' error, user_data, weak_object);')
+ self.b('')
+ self.b(' if (destroy != NULL)')
+ self.b(' destroy (user_data);')
+ self.b('')
+ self.b(' g_error_free (error);')
+ self.b(' return NULL;')
+ self.b(' }')
+ self.b('')
+ self.b(' if (callback == NULL)')
+ self.b(' {')
+ self.b(' dbus_g_proxy_call_no_reply (iface, "%s",' % member)
+
+ for arg in in_args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ const = pointer and 'const ' or ''
+
+ self.b(' %s, %s,' % (gtype, name))
+
+ self.b(' G_TYPE_INVALID);')
+ self.b(' return NULL;')
+ self.b(' }')
+ self.b(' else')
+ self.b(' {')
+ self.b(' TpProxyPendingCall *data;')
+ self.b('')
+ self.b(' data = tp_proxy_pending_call_v0_new ((TpProxy *) proxy,')
+ self.b(' interface, "%s", iface,' % member)
+ self.b(' %s,' % invoke_callback)
+ self.b(' G_CALLBACK (callback), user_data, destroy,')
+ self.b(' weak_object, FALSE);')
+ self.b(' tp_proxy_pending_call_v0_take_pending_call (data,')
+ self.b(' dbus_g_proxy_begin_call_with_timeout (iface,')
+ self.b(' "%s",' % member)
+ self.b(' %s,' % collect_callback)
+ self.b(' data,')
+ self.b(' tp_proxy_pending_call_v0_completed,')
+ self.b(' timeout_ms,')
+
+ for arg in in_args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ const = pointer and 'const ' or ''
+
+ self.b(' %s, %s,' % (gtype, name))
+
+ self.b(' G_TYPE_INVALID));')
+ self.b('')
+ self.b(' return data;')
+ self.b(' }')
+ self.b('}')
+ self.b('')
+
+ if self.generate_reentrant:
+ self.do_method_reentrant(method, iface_lc, member, member_lc,
+ in_args, out_args, collect_callback)
+
+ # leave a gap for the end of the method
+ self.d('')
+ self.b('')
+ self.h('')
+
+ def do_method_reentrant(self, method, iface_lc, member, member_lc, in_args,
+ out_args, collect_callback):
+ # Reentrant blocking calls
+ # Example:
+ # gboolean tp_cli_properties_interface_run_get_properties
+ # (gpointer proxy,
+ # gint timeout_ms,
+ # const GArray *in_properties,
+ # GPtrArray **out0,
+ # GError **error,
+ # GMainLoop **loop);
+
+ self.b('typedef struct {')
+ self.b(' GMainLoop *loop;')
+ self.b(' GError **error;')
+
+ for arg in out_args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ self.b(' %s*%s;' % (ctype, name))
+
+ self.b(' unsigned success:1;')
+ self.b(' unsigned completed:1;')
+ self.b('} _%s_%s_run_state_%s;'
+ % (self.prefix_lc, iface_lc, member_lc))
+
+ reentrant_invoke = '_%s_%s_finish_running_%s' % (self.prefix_lc,
+ iface_lc,
+ member_lc)
+
+ self.b('static void')
+ self.b('%s (TpProxy *self G_GNUC_UNUSED,' % reentrant_invoke)
+ self.b(' GError *error,')
+ self.b(' GValueArray *args,')
+ self.b(' GCallback unused G_GNUC_UNUSED,')
+ self.b(' gpointer user_data G_GNUC_UNUSED,')
+ self.b(' GObject *unused2 G_GNUC_UNUSED)')
+ self.b('{')
+ self.b(' _%s_%s_run_state_%s *state = user_data;'
+ % (self.prefix_lc, iface_lc, member_lc))
+ self.b('')
+ self.b(' state->success = (error == NULL);')
+ self.b(' state->completed = TRUE;')
+ self.b(' g_main_loop_quit (state->loop);')
+ self.b('')
+ self.b(' if (error != NULL)')
+ self.b(' {')
+ self.b(' if (state->error != NULL)')
+ self.b(' *state->error = error;')
+ self.b(' else')
+ self.b(' g_error_free (error);')
+ self.b('')
+ self.b(' return;')
+ self.b(' }')
+ self.b('')
+
+ for i, arg in enumerate(out_args):
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ self.b(' if (state->%s != NULL)' % name)
+ if marshaller == 'BOXED':
+ self.b(' *state->%s = g_value_dup_boxed ('
+ 'args->values + %d);' % (name, i))
+ elif marshaller == 'STRING':
+ self.b(' *state->%s = g_value_dup_string '
+ '(args->values + %d);' % (name, i))
+ elif marshaller in ('UCHAR', 'BOOLEAN', 'INT', 'UINT',
+ 'INT64', 'UINT64', 'DOUBLE'):
+ self.b(' *state->%s = g_value_get_%s (args->values + %d);'
+ % (name, marshaller.lower(), i))
+ else:
+ assert False, "Don't know how to copy %s" % gtype
+
+ self.b('')
+
+ if len(out_args) > 0:
+ self.b(' g_value_array_free (args);')
+ else:
+ self.b(' if (args != NULL)')
+ self.b(' g_value_array_free (args);')
+
+ self.b('}')
+ self.b('')
+
+ if self.deprecate_reentrant:
+ self.h('#ifndef %s' % self.deprecate_reentrant)
+
+ self.h('gboolean %s_%s_run_%s (%sproxy,'
+ % (self.prefix_lc, iface_lc, member_lc, self.proxy_arg))
+ self.h(' gint timeout_ms,')
+
+ self.d('/**')
+ self.d(' * %s_%s_run_%s:' % (self.prefix_lc, iface_lc, member_lc))
+ self.d(' * @proxy: %s' % self.proxy_doc)
+ self.d(' * @timeout_ms: Timeout in milliseconds, or -1 for default')
+
+ for arg in in_args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ self.d(' * @%s: Used to pass an \'in\' argument: %s'
+ % (name, xml_escape(get_docstring(elt) or '(Undocumented)')))
+
+ for arg in out_args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ self.d(' * @%s: Used to return an \'out\' argument if %%TRUE is '
+ 'returned: %s'
+ % (name, xml_escape(get_docstring(elt) or '(Undocumented)')))
+
+ self.d(' * @error: If not %NULL, used to return errors if %FALSE ')
+ self.d(' * is returned')
+ self.d(' * @loop: If not %NULL, set before re-entering ')
+ self.d(' * the main loop, to point to a #GMainLoop ')
+ self.d(' * which can be used to cancel this call with ')
+ self.d(' * g_main_loop_quit(), causing a return of ')
+ self.d(' * %FALSE with @error set to %TP_DBUS_ERROR_CANCELLED')
+ self.d(' *')
+ self.d(' * Call the method %s and run the main loop' % member)
+ self.d(' * until it returns. Before calling this method, you must')
+ self.d(' * add a reference to any borrowed objects you need to keep,')
+ self.d(' * and generally ensure that everything is in a consistent')
+ self.d(' * state.')
+ self.d(' *')
+ self.d(' * %s' % xml_escape(get_docstring(method) or '(Undocumented)'))
+ self.d(' *')
+ self.d(' * Returns: TRUE on success, FALSE and sets @error on error')
+
+ deprecated = method.getElementsByTagName('tp:deprecated')
+ if deprecated:
+ d = deprecated[0]
+ self.d(' *')
+ self.d(' * Deprecated: %s' % xml_escape(get_deprecated(d)))
+
+ self.d(' */')
+ self.d('')
+
+ self.b('gboolean\n%s_%s_run_%s (%sproxy,'
+ % (self.prefix_lc, iface_lc, member_lc, self.proxy_arg))
+ self.b(' gint timeout_ms,')
+
+ for arg in in_args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ const = pointer and 'const ' or ''
+
+ self.h(' %s%s%s,' % (const, ctype, name))
+ self.b(' %s%s%s,' % (const, ctype, name))
+
+ for arg in out_args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ self.h(' %s*%s,' % (ctype, name))
+ self.b(' %s*%s,' % (ctype, name))
+
+ self.h(' GError **error,')
+
+ if self.deprecate_reentrant:
+ self.h(' GMainLoop **loop) %s;' % self.deprecation_attribute)
+ self.h('#endif /* not %s */' % self.deprecate_reentrant)
+ else:
+ self.h(' GMainLoop **loop);')
+
+ self.h('')
+
+ self.b(' GError **error,')
+ self.b(' GMainLoop **loop)')
+ self.b('{')
+ self.b(' DBusGProxy *iface;')
+ self.b(' GQuark interface = %s;' % self.get_iface_quark())
+ self.b(' TpProxyPendingCall *pc;')
+ self.b(' _%s_%s_run_state_%s state = {'
+ % (self.prefix_lc, iface_lc, member_lc))
+ self.b(' NULL /* loop */, error,')
+
+ for arg in out_args:
+ name, info, tp_type, elt = arg
+
+ self.b(' %s,' % name)
+
+ self.b(' FALSE /* completed */, FALSE /* success */ };')
+ self.b('')
+ self.b(' g_return_val_if_fail (%s (proxy), FALSE);'
+ % self.proxy_assert)
+ self.b('')
+ self.b(' iface = tp_proxy_borrow_interface_by_id')
+ self.b(' ((TpProxy *) proxy, interface, error);')
+ self.b('')
+ self.b(' if (iface == NULL)')
+ self.b(' return FALSE;')
+ self.b('')
+ self.b(' state.loop = g_main_loop_new (NULL, FALSE);')
+ self.b('')
+ self.b(' pc = tp_proxy_pending_call_v0_new ((TpProxy *) proxy,')
+ self.b(' interface, "%s", iface,' % member)
+ self.b(' %s,' % reentrant_invoke)
+ self.b(' NULL, &state, NULL, NULL, TRUE);')
+ self.b('')
+ self.b(' if (loop != NULL)')
+ self.b(' *loop = state.loop;')
+ self.b('')
+ self.b(' tp_proxy_pending_call_v0_take_pending_call (pc,')
+ self.b(' dbus_g_proxy_begin_call_with_timeout (iface,')
+ self.b(' "%s",' % member)
+ self.b(' %s,' % collect_callback)
+ self.b(' pc,')
+ self.b(' tp_proxy_pending_call_v0_completed,')
+ self.b(' timeout_ms,')
+
+ for arg in in_args:
+ name, info, tp_type, elt = arg
+ ctype, gtype, marshaller, pointer = info
+
+ const = pointer and 'const ' or ''
+
+ self.b(' %s, %s,' % (gtype, name))
+
+ self.b(' G_TYPE_INVALID));')
+ self.b('')
+ self.b(' if (!state.completed)')
+ self.b(' g_main_loop_run (state.loop);')
+ self.b('')
+ self.b(' if (!state.completed)')
+ self.b(' tp_proxy_pending_call_cancel (pc);')
+ self.b('')
+ self.b(' if (loop != NULL)')
+ self.b(' *loop = NULL;')
+ self.b('')
+ self.b(' g_main_loop_unref (state.loop);')
+ self.b('')
+ self.b(' return state.success;')
+ self.b('}')
+ self.b('')
+
+ def do_signal_add(self, signal):
+ marshaller_items = []
+ gtypes = []
+
+ for i in signal.getElementsByTagName('arg'):
+ name = i.getAttribute('name')
+ type = i.getAttribute('type')
+ info = type_to_gtype(type)
+ # type, GType, STRING, is a pointer
+ gtypes.append(info[1])
+
+ self.b(' dbus_g_proxy_add_signal (proxy, "%s",'
+ % signal.getAttribute('name'))
+ for gtype in gtypes:
+ self.b(' %s,' % gtype)
+ self.b(' G_TYPE_INVALID);')
+
+ def do_interface(self, node):
+ ifaces = node.getElementsByTagName('interface')
+ assert len(ifaces) == 1
+ iface = ifaces[0]
+ name = node.getAttribute('name').replace('/', '')
+
+ self.iface = name
+ self.iface_lc = name.lower()
+ self.iface_uc = name.upper()
+ self.iface_mc = name.replace('_', '')
+ self.iface_dbus = iface.getAttribute('name')
+
+ signals = node.getElementsByTagName('signal')
+ methods = node.getElementsByTagName('method')
+
+ if signals:
+ self.b('static inline void')
+ self.b('%s_add_signals_for_%s (DBusGProxy *proxy)'
+ % (self.prefix_lc, name.lower()))
+ self.b('{')
+
+ if self.tp_proxy_api >= (0, 7, 6):
+ self.b(' if (!tp_proxy_dbus_g_proxy_claim_for_signal_adding '
+ '(proxy))')
+ self.b(' return;')
+
+ for signal in signals:
+ self.do_signal_add(signal)
+
+ self.b('}')
+ self.b('')
+ self.b('')
+
+ for signal in signals:
+ self.do_signal(name, signal)
+
+ for method in methods:
+ self.do_method(name, method)
+
+ self.iface_dbus = None
+
+ def __call__(self):
+
+ self.h('G_BEGIN_DECLS')
+ self.h('')
+
+ self.b('/* We don\'t want gtkdoc scanning this file, it\'ll get')
+ self.b(' * confused by seeing function definitions, so mark it as: */')
+ self.b('/*<private_header>*/')
+ self.b('')
+
+ nodes = self.dom.getElementsByTagName('node')
+ nodes.sort(cmp_by_name)
+
+ for node in nodes:
+ self.do_interface(node)
+
+ if self.group is not None:
+
+ self.b('/*')
+ self.b(' * %s_%s_add_signals:' % (self.prefix_lc, self.group))
+ self.b(' * @self: the #TpProxy')
+ self.b(' * @quark: a quark whose string value is the interface')
+ self.b(' * name whose signals should be added')
+ self.b(' * @proxy: the D-Bus proxy to which to add the signals')
+ self.b(' * @unused: not used for anything')
+ self.b(' *')
+ self.b(' * Tell dbus-glib that @proxy has the signatures of all')
+ self.b(' * signals on the given interface, if it\'s one we')
+ self.b(' * support.')
+ self.b(' *')
+ self.b(' * This function should be used as a signal handler for')
+ self.b(' * #TpProxy::interface-added.')
+ self.b(' */')
+ self.b('static void')
+ self.b('%s_%s_add_signals (TpProxy *self G_GNUC_UNUSED,'
+ % (self.prefix_lc, self.group))
+ self.b(' guint quark,')
+ self.b(' DBusGProxy *proxy,')
+ self.b(' gpointer unused G_GNUC_UNUSED)')
+
+ self.b('{')
+
+ for node in nodes:
+ iface = node.getElementsByTagName('interface')[0]
+ self.iface_dbus = iface.getAttribute('name')
+ signals = node.getElementsByTagName('signal')
+ if not signals:
+ continue
+ name = node.getAttribute('name').replace('/', '').lower()
+ self.iface_uc = name.upper()
+ self.b(' if (quark == %s)' % self.get_iface_quark())
+ self.b(' %s_add_signals_for_%s (proxy);'
+ % (self.prefix_lc, name))
+
+ self.b('}')
+ self.b('')
+
+ self.h('G_END_DECLS')
+ self.h('')
+
+ open(self.basename + '.h', 'w').write('\n'.join(self.__header))
+ open(self.basename + '-body.h', 'w').write('\n'.join(self.__body))
+ open(self.basename + '-gtk-doc.h', 'w').write('\n'.join(self.__docs))
+
+
+def types_to_gtypes(types):
+ return [type_to_gtype(t)[1] for t in types]
+
+
+if __name__ == '__main__':
+ options, argv = gnu_getopt(sys.argv[1:], '',
+ ['group=', 'subclass=', 'subclass-assert=',
+ 'iface-quark-prefix=', 'tp-proxy-api=',
+ 'generate-reentrant', 'deprecate-reentrant=',
+ 'deprecation-attribute='])
+
+ opts = {}
+
+ for option, value in options:
+ opts[option] = value
+
+ dom = xml.dom.minidom.parse(argv[0])
+
+ Generator(dom, argv[1], argv[2], opts)()
diff --git a/farstream/tools/glib-client-marshaller-gen.py b/farstream/tools/glib-client-marshaller-gen.py
new file mode 100644
index 000000000..54447255b
--- /dev/null
+++ b/farstream/tools/glib-client-marshaller-gen.py
@@ -0,0 +1,59 @@
+#!/usr/bin/python
+
+import sys
+import xml.dom.minidom
+from string import ascii_letters, digits
+
+
+from libglibcodegen import signal_to_marshal_name
+
+
+NS_TP = "http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+
+class Generator(object):
+
+ def __init__(self, dom, prefix):
+ self.dom = dom
+ self.marshallers = {}
+ self.prefix = prefix
+
+ def do_signal(self, signal):
+ marshaller = signal_to_marshal_name(signal, self.prefix)
+
+ assert '__' in marshaller
+ rhs = marshaller.split('__', 1)[1].split('_')
+
+ self.marshallers[marshaller] = rhs
+
+ def __call__(self):
+ signals = self.dom.getElementsByTagName('signal')
+
+ for signal in signals:
+ self.do_signal(signal)
+
+ print 'void'
+ print '%s_register_dbus_glib_marshallers (void)' % self.prefix
+ print '{'
+
+ all = self.marshallers.keys()
+ all.sort()
+ for marshaller in all:
+ rhs = self.marshallers[marshaller]
+
+ print ' dbus_g_object_register_marshaller (%s,' % marshaller
+ print ' G_TYPE_NONE, /* return */'
+ for type in rhs:
+ print ' G_TYPE_%s,' % type.replace('VOID', 'NONE')
+ print ' G_TYPE_INVALID);'
+
+ print '}'
+
+
+def types_to_gtypes(types):
+ return [type_to_gtype(t)[1] for t in types]
+
+if __name__ == '__main__':
+ argv = sys.argv[1:]
+ dom = xml.dom.minidom.parse(argv[0])
+
+ Generator(dom, argv[1])()
diff --git a/farstream/tools/glib-ginterface-gen.py b/farstream/tools/glib-ginterface-gen.py
new file mode 100644
index 000000000..04509fd79
--- /dev/null
+++ b/farstream/tools/glib-ginterface-gen.py
@@ -0,0 +1,820 @@
+#!/usr/bin/python
+
+# glib-ginterface-gen.py: service-side interface generator
+#
+# Generate dbus-glib 0.x service GInterfaces from the Telepathy specification.
+# The master copy of this program is in the telepathy-glib repository -
+# please make any changes there.
+#
+# Copyright (C) 2006, 2007 Collabora Limited
+#
+# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import sys
+import os.path
+import xml.dom.minidom
+
+from libglibcodegen import Signature, type_to_gtype, cmp_by_name, \
+ NS_TP, dbus_gutils_wincaps_to_uscore, \
+ signal_to_marshal_name, method_to_glue_marshal_name
+
+
+NS_TP = "http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+
+class Generator(object):
+
+ def __init__(self, dom, prefix, basename, signal_marshal_prefix,
+ headers, end_headers, not_implemented_func,
+ allow_havoc):
+ self.dom = dom
+ self.__header = []
+ self.__body = []
+ self.__docs = []
+
+ assert prefix.endswith('_')
+ assert not signal_marshal_prefix.endswith('_')
+
+ # The main_prefix, sub_prefix thing is to get:
+ # FOO_ -> (FOO_, _)
+ # FOO_SVC_ -> (FOO_, _SVC_)
+ # but
+ # FOO_BAR/ -> (FOO_BAR_, _)
+ # FOO_BAR/SVC_ -> (FOO_BAR_, _SVC_)
+
+ if '/' in prefix:
+ main_prefix, sub_prefix = prefix.upper().split('/', 1)
+ prefix = prefix.replace('/', '_')
+ else:
+ main_prefix, sub_prefix = prefix.upper().split('_', 1)
+
+ self.MAIN_PREFIX_ = main_prefix + '_'
+ self._SUB_PREFIX_ = '_' + sub_prefix
+
+ self.Prefix_ = prefix
+ self.Prefix = prefix.replace('_', '')
+ self.prefix_ = prefix.lower()
+ self.PREFIX_ = prefix.upper()
+
+ self.basename = basename
+ self.signal_marshal_prefix = signal_marshal_prefix
+ self.headers = headers
+ self.end_headers = end_headers
+ self.not_implemented_func = not_implemented_func
+ self.allow_havoc = allow_havoc
+
+ def h(self, s):
+ if isinstance(s, unicode):
+ s = s.encode('utf-8')
+ self.__header.append(s)
+
+ def b(self, s):
+ if isinstance(s, unicode):
+ s = s.encode('utf-8')
+ self.__body.append(s)
+
+ def d(self, s):
+ if isinstance(s, unicode):
+ s = s.encode('utf-8')
+ self.__docs.append(s)
+
+ def do_node(self, node):
+ node_name = node.getAttribute('name').replace('/', '')
+ node_name_mixed = self.node_name_mixed = node_name.replace('_', '')
+ node_name_lc = self.node_name_lc = node_name.lower()
+ node_name_uc = self.node_name_uc = node_name.upper()
+
+ interfaces = node.getElementsByTagName('interface')
+ assert len(interfaces) == 1, interfaces
+ interface = interfaces[0]
+ self.iface_name = interface.getAttribute('name')
+
+ tmp = interface.getAttribute('tp:implement-service')
+ if tmp == "no":
+ return
+
+ tmp = interface.getAttribute('tp:causes-havoc')
+ if tmp and not self.allow_havoc:
+ raise AssertionError('%s is %s' % (self.iface_name, tmp))
+
+ self.b('static const DBusGObjectInfo _%s%s_object_info;'
+ % (self.prefix_, node_name_lc))
+ self.b('')
+
+ methods = interface.getElementsByTagName('method')
+ signals = interface.getElementsByTagName('signal')
+ properties = interface.getElementsByTagName('property')
+ # Don't put properties in dbus-glib glue
+ glue_properties = []
+
+ self.b('struct _%s%sClass {' % (self.Prefix, node_name_mixed))
+ self.b(' GTypeInterface parent_class;')
+ for method in methods:
+ self.b(' %s %s;' % self.get_method_impl_names(method))
+ self.b('};')
+ self.b('')
+
+ if signals:
+ self.b('enum {')
+ for signal in signals:
+ self.b(' %s,' % self.get_signal_const_entry(signal))
+ self.b(' N_%s_SIGNALS' % node_name_uc)
+ self.b('};')
+ self.b('static guint %s_signals[N_%s_SIGNALS] = {0};'
+ % (node_name_lc, node_name_uc))
+ self.b('')
+
+ self.b('static void %s%s_base_init (gpointer klass);'
+ % (self.prefix_, node_name_lc))
+ self.b('')
+
+ self.b('GType')
+ self.b('%s%s_get_type (void)'
+ % (self.prefix_, node_name_lc))
+ self.b('{')
+ self.b(' static GType type = 0;')
+ self.b('')
+ self.b(' if (G_UNLIKELY (type == 0))')
+ self.b(' {')
+ self.b(' static const GTypeInfo info = {')
+ self.b(' sizeof (%s%sClass),' % (self.Prefix, node_name_mixed))
+ self.b(' %s%s_base_init, /* base_init */'
+ % (self.prefix_, node_name_lc))
+ self.b(' NULL, /* base_finalize */')
+ self.b(' NULL, /* class_init */')
+ self.b(' NULL, /* class_finalize */')
+ self.b(' NULL, /* class_data */')
+ self.b(' 0,')
+ self.b(' 0, /* n_preallocs */')
+ self.b(' NULL /* instance_init */')
+ self.b(' };')
+ self.b('')
+ self.b(' type = g_type_register_static (G_TYPE_INTERFACE,')
+ self.b(' "%s%s", &info, 0);' % (self.Prefix, node_name_mixed))
+ self.b(' }')
+ self.b('')
+ self.b(' return type;')
+ self.b('}')
+ self.b('')
+
+ self.d('/**')
+ self.d(' * %s%s:' % (self.Prefix, node_name_mixed))
+ self.d(' *')
+ self.d(' * Dummy typedef representing any implementation of this '
+ 'interface.')
+ self.d(' */')
+
+ self.h('typedef struct _%s%s %s%s;'
+ % (self.Prefix, node_name_mixed, self.Prefix, node_name_mixed))
+ self.h('')
+
+ self.d('/**')
+ self.d(' * %s%sClass:' % (self.Prefix, node_name_mixed))
+ self.d(' *')
+ self.d(' * The class of %s%s.' % (self.Prefix, node_name_mixed))
+
+ if methods:
+ self.d(' *')
+ self.d(' * In a full implementation of this interface (i.e. all')
+ self.d(' * methods implemented), the interface initialization')
+ self.d(' * function used in G_IMPLEMENT_INTERFACE() would')
+ self.d(' * typically look like this:')
+ self.d(' *')
+ self.d(' * <programlisting>')
+ self.d(' * static void')
+ self.d(' * implement_%s (gpointer klass,' % self.node_name_lc)
+ self.d(' * gpointer unused G_GNUC_UNUSED)')
+ self.d(' * {')
+ self.d(' * #define IMPLEMENT(x) %s%s_implement_&num;&num;x (\\'
+ % (self.prefix_, self.node_name_lc))
+ self.d(' * klass, my_object_&num;&num;x)')
+
+ for method in methods:
+ class_member_name = method.getAttribute('tp:name-for-bindings')
+ class_member_name = class_member_name.lower()
+ self.d(' * IMPLEMENT (%s);' % class_member_name)
+
+ self.d(' * #undef IMPLEMENT')
+ self.d(' * }')
+ self.d(' * </programlisting>')
+ else:
+ self.d(' * This interface has no D-Bus methods, so an')
+ self.d(' * implementation can typically pass %NULL to')
+ self.d(' * G_IMPLEMENT_INTERFACE() as the interface')
+ self.d(' * initialization function.')
+
+ self.d(' */')
+ self.d('')
+
+ self.h('typedef struct _%s%sClass %s%sClass;'
+ % (self.Prefix, node_name_mixed, self.Prefix, node_name_mixed))
+ self.h('')
+ self.h('GType %s%s_get_type (void);'
+ % (self.prefix_, node_name_lc))
+
+ gtype = self.current_gtype = \
+ self.MAIN_PREFIX_ + 'TYPE' + self._SUB_PREFIX_ + node_name_uc
+ classname = self.Prefix + node_name_mixed
+
+ self.h('#define %s \\\n (%s%s_get_type ())'
+ % (gtype, self.prefix_, node_name_lc))
+ self.h('#define %s%s(obj) \\\n'
+ ' (G_TYPE_CHECK_INSTANCE_CAST((obj), %s, %s))'
+ % (self.PREFIX_, node_name_uc, gtype, classname))
+ self.h('#define %sIS%s%s(obj) \\\n'
+ ' (G_TYPE_CHECK_INSTANCE_TYPE((obj), %s))'
+ % (self.MAIN_PREFIX_, self._SUB_PREFIX_, node_name_uc, gtype))
+ self.h('#define %s%s_GET_CLASS(obj) \\\n'
+ ' (G_TYPE_INSTANCE_GET_INTERFACE((obj), %s, %sClass))'
+ % (self.PREFIX_, node_name_uc, gtype, classname))
+ self.h('')
+ self.h('')
+
+ base_init_code = []
+
+ for method in methods:
+ self.do_method(method)
+
+ for signal in signals:
+ base_init_code.extend(self.do_signal(signal))
+
+ self.b('static inline void')
+ self.b('%s%s_base_init_once (gpointer klass G_GNUC_UNUSED)'
+ % (self.prefix_, node_name_lc))
+ self.b('{')
+
+ if properties:
+ self.b(' static TpDBusPropertiesMixinPropInfo properties[%d] = {'
+ % (len(properties) + 1))
+
+ for m in properties:
+ access = m.getAttribute('access')
+ assert access in ('read', 'write', 'readwrite')
+
+ if access == 'read':
+ flags = 'TP_DBUS_PROPERTIES_MIXIN_FLAG_READ'
+ elif access == 'write':
+ flags = 'TP_DBUS_PROPERTIES_MIXIN_FLAG_WRITE'
+ else:
+ flags = ('TP_DBUS_PROPERTIES_MIXIN_FLAG_READ | '
+ 'TP_DBUS_PROPERTIES_MIXIN_FLAG_WRITE')
+
+ self.b(' { 0, %s, "%s", 0, NULL, NULL }, /* %s */'
+ % (flags, m.getAttribute('type'), m.getAttribute('name')))
+
+ self.b(' { 0, 0, NULL, 0, NULL, NULL }')
+ self.b(' };')
+ self.b(' static TpDBusPropertiesMixinIfaceInfo interface =')
+ self.b(' { 0, properties, NULL, NULL };')
+ self.b('')
+
+
+ self.b(' dbus_g_object_type_install_info (%s%s_get_type (),'
+ % (self.prefix_, node_name_lc))
+ self.b(' &_%s%s_object_info);'
+ % (self.prefix_, node_name_lc))
+ self.b('')
+
+ if properties:
+ self.b(' interface.dbus_interface = g_quark_from_static_string '
+ '("%s");' % self.iface_name)
+
+ for i, m in enumerate(properties):
+ self.b(' properties[%d].name = g_quark_from_static_string ("%s");'
+ % (i, m.getAttribute('name')))
+ self.b(' properties[%d].type = %s;'
+ % (i, type_to_gtype(m.getAttribute('type'))[1]))
+
+ self.b(' tp_svc_interface_set_dbus_properties_info (%s, &interface);'
+ % self.current_gtype)
+
+ self.b('')
+
+ for s in base_init_code:
+ self.b(s)
+ self.b('}')
+
+ self.b('static void')
+ self.b('%s%s_base_init (gpointer klass)'
+ % (self.prefix_, node_name_lc))
+ self.b('{')
+ self.b(' static gboolean initialized = FALSE;')
+ self.b('')
+ self.b(' if (!initialized)')
+ self.b(' {')
+ self.b(' initialized = TRUE;')
+ self.b(' %s%s_base_init_once (klass);'
+ % (self.prefix_, node_name_lc))
+ self.b(' }')
+ # insert anything we need to do per implementation here
+ self.b('}')
+
+ self.h('')
+
+ self.b('static const DBusGMethodInfo _%s%s_methods[] = {'
+ % (self.prefix_, node_name_lc))
+
+ method_blob, offsets = self.get_method_glue(methods)
+
+ for method, offset in zip(methods, offsets):
+ self.do_method_glue(method, offset)
+
+ if len(methods) == 0:
+ # empty arrays are a gcc extension, so put in a dummy member
+ self.b(" { NULL, NULL, 0 }")
+
+ self.b('};')
+ self.b('')
+
+ self.b('static const DBusGObjectInfo _%s%s_object_info = {'
+ % (self.prefix_, node_name_lc))
+ self.b(' 0,') # version
+ self.b(' _%s%s_methods,' % (self.prefix_, node_name_lc))
+ self.b(' %d,' % len(methods))
+ self.b('"' + method_blob.replace('\0', '\\0') + '",')
+ self.b('"' + self.get_signal_glue(signals).replace('\0', '\\0') + '",')
+ self.b('"' +
+ self.get_property_glue(glue_properties).replace('\0', '\\0') +
+ '",')
+ self.b('};')
+ self.b('')
+
+ self.node_name_mixed = None
+ self.node_name_lc = None
+ self.node_name_uc = None
+
+ def get_method_glue(self, methods):
+ info = []
+ offsets = []
+
+ for method in methods:
+ offsets.append(len(''.join(info)))
+
+ info.append(self.iface_name + '\0')
+ info.append(method.getAttribute('name') + '\0')
+
+ info.append('A\0') # async
+
+ counter = 0
+ for arg in method.getElementsByTagName('arg'):
+ out = arg.getAttribute('direction') == 'out'
+
+ name = arg.getAttribute('name')
+ if not name:
+ assert out
+ name = 'arg%u' % counter
+ counter += 1
+
+ info.append(name + '\0')
+
+ if out:
+ info.append('O\0')
+ else:
+ info.append('I\0')
+
+ if out:
+ info.append('F\0') # not const
+ info.append('N\0') # not error or return
+ info.append(arg.getAttribute('type') + '\0')
+
+ info.append('\0')
+
+ return ''.join(info) + '\0', offsets
+
+ def do_method_glue(self, method, offset):
+ lc_name = method.getAttribute('tp:name-for-bindings')
+ if method.getAttribute('name') != lc_name.replace('_', ''):
+ raise AssertionError('Method %s tp:name-for-bindings (%s) does '
+ 'not match' % (method.getAttribute('name'), lc_name))
+ lc_name = lc_name.lower()
+
+ marshaller = method_to_glue_marshal_name(method,
+ self.signal_marshal_prefix)
+ wrapper = self.prefix_ + self.node_name_lc + '_' + lc_name
+
+ self.b(" { (GCallback) %s, %s, %d }," % (wrapper, marshaller, offset))
+
+ def get_signal_glue(self, signals):
+ info = []
+
+ for signal in signals:
+ info.append(self.iface_name)
+ info.append(signal.getAttribute('name'))
+
+ return '\0'.join(info) + '\0\0'
+
+ # the implementation can be the same
+ get_property_glue = get_signal_glue
+
+ def get_method_impl_names(self, method):
+ dbus_method_name = method.getAttribute('name')
+
+ class_member_name = method.getAttribute('tp:name-for-bindings')
+ if dbus_method_name != class_member_name.replace('_', ''):
+ raise AssertionError('Method %s tp:name-for-bindings (%s) does '
+ 'not match' % (dbus_method_name, class_member_name))
+ class_member_name = class_member_name.lower()
+
+ stub_name = (self.prefix_ + self.node_name_lc + '_' +
+ class_member_name)
+ return (stub_name + '_impl', class_member_name + '_cb')
+
+ def do_method(self, method):
+ assert self.node_name_mixed is not None
+
+ in_class = []
+
+ # Examples refer to Thing.DoStuff (su) -> ii
+
+ # DoStuff
+ dbus_method_name = method.getAttribute('name')
+ # do_stuff
+ class_member_name = method.getAttribute('tp:name-for-bindings')
+ if dbus_method_name != class_member_name.replace('_', ''):
+ raise AssertionError('Method %s tp:name-for-bindings (%s) does '
+ 'not match' % (dbus_method_name, class_member_name))
+ class_member_name = class_member_name.lower()
+
+ # void tp_svc_thing_do_stuff (TpSvcThing *, const char *, guint,
+ # DBusGMethodInvocation *);
+ stub_name = (self.prefix_ + self.node_name_lc + '_' +
+ class_member_name)
+ # typedef void (*tp_svc_thing_do_stuff_impl) (TpSvcThing *,
+ # const char *, guint, DBusGMethodInvocation);
+ impl_name = stub_name + '_impl'
+ # void tp_svc_thing_return_from_do_stuff (DBusGMethodInvocation *,
+ # gint, gint);
+ ret_name = (self.prefix_ + self.node_name_lc + '_return_from_' +
+ class_member_name)
+
+ # Gather arguments
+ in_args = []
+ out_args = []
+ for i in method.getElementsByTagName('arg'):
+ name = i.getAttribute('name')
+ direction = i.getAttribute('direction') or 'in'
+ dtype = i.getAttribute('type')
+
+ assert direction in ('in', 'out')
+
+ if name:
+ name = direction + '_' + name
+ elif direction == 'in':
+ name = direction + str(len(in_args))
+ else:
+ name = direction + str(len(out_args))
+
+ ctype, gtype, marshaller, pointer = type_to_gtype(dtype)
+
+ if pointer:
+ ctype = 'const ' + ctype
+
+ struct = (ctype, name)
+
+ if direction == 'in':
+ in_args.append(struct)
+ else:
+ out_args.append(struct)
+
+ # Implementation type declaration (in header, docs separated)
+ self.d('/**')
+ self.d(' * %s:' % impl_name)
+ self.d(' * @self: The object implementing this interface')
+ for (ctype, name) in in_args:
+ self.d(' * @%s: %s (FIXME, generate documentation)'
+ % (name, ctype))
+ self.d(' * @context: Used to return values or throw an error')
+ self.d(' *')
+ self.d(' * The signature of an implementation of the D-Bus method')
+ self.d(' * %s on interface %s.' % (dbus_method_name, self.iface_name))
+ self.d(' */')
+
+ self.h('typedef void (*%s) (%s%s *self,'
+ % (impl_name, self.Prefix, self.node_name_mixed))
+ for (ctype, name) in in_args:
+ self.h(' %s%s,' % (ctype, name))
+ self.h(' DBusGMethodInvocation *context);')
+
+ # Class member (in class definition)
+ in_class.append(' %s %s;' % (impl_name, class_member_name))
+
+ # Stub definition (in body only - it's static)
+ self.b('static void')
+ self.b('%s (%s%s *self,'
+ % (stub_name, self.Prefix, self.node_name_mixed))
+ for (ctype, name) in in_args:
+ self.b(' %s%s,' % (ctype, name))
+ self.b(' DBusGMethodInvocation *context)')
+ self.b('{')
+ self.b(' %s impl = (%s%s_GET_CLASS (self)->%s_cb);'
+ % (impl_name, self.PREFIX_, self.node_name_uc, class_member_name))
+ self.b('')
+ self.b(' if (impl != NULL)')
+ tmp = ['self'] + [name for (ctype, name) in in_args] + ['context']
+ self.b(' {')
+ self.b(' (impl) (%s);' % ',\n '.join(tmp))
+ self.b(' }')
+ self.b(' else')
+ self.b(' {')
+ if self.not_implemented_func:
+ self.b(' %s (context);' % self.not_implemented_func)
+ else:
+ self.b(' GError e = { DBUS_GERROR, ')
+ self.b(' DBUS_GERROR_UNKNOWN_METHOD,')
+ self.b(' "Method not implemented" };')
+ self.b('')
+ self.b(' dbus_g_method_return_error (context, &e);')
+ self.b(' }')
+ self.b('}')
+ self.b('')
+
+ # Implementation registration (in both header and body)
+ self.h('void %s%s_implement_%s (%s%sClass *klass, %s impl);'
+ % (self.prefix_, self.node_name_lc, class_member_name,
+ self.Prefix, self.node_name_mixed, impl_name))
+
+ self.d('/**')
+ self.d(' * %s%s_implement_%s:'
+ % (self.prefix_, self.node_name_lc, class_member_name))
+ self.d(' * @klass: A class whose instances implement this interface')
+ self.d(' * @impl: A callback used to implement the %s D-Bus method'
+ % dbus_method_name)
+ self.d(' *')
+ self.d(' * Register an implementation for the %s method in the vtable'
+ % dbus_method_name)
+ self.d(' * of an implementation of this interface. To be called from')
+ self.d(' * the interface init function.')
+ self.d(' */')
+
+ self.b('void')
+ self.b('%s%s_implement_%s (%s%sClass *klass, %s impl)'
+ % (self.prefix_, self.node_name_lc, class_member_name,
+ self.Prefix, self.node_name_mixed, impl_name))
+ self.b('{')
+ self.b(' klass->%s_cb = impl;' % class_member_name)
+ self.b('}')
+ self.b('')
+
+ # Return convenience function (static inline, in header)
+ self.d('/**')
+ self.d(' * %s:' % ret_name)
+ self.d(' * @context: The D-Bus method invocation context')
+ for (ctype, name) in out_args:
+ self.d(' * @%s: %s (FIXME, generate documentation)'
+ % (name, ctype))
+ self.d(' *')
+ self.d(' * Return successfully by calling dbus_g_method_return().')
+ self.d(' * This inline function exists only to provide type-safety.')
+ self.d(' */')
+ self.d('')
+
+ tmp = (['DBusGMethodInvocation *context'] +
+ [ctype + name for (ctype, name) in out_args])
+ self.h('static inline')
+ self.h('/* this comment is to stop gtkdoc realising this is static */')
+ self.h(('void %s (' % ret_name) + (',\n '.join(tmp)) + ');')
+ self.h('static inline void')
+ self.h(('%s (' % ret_name) + (',\n '.join(tmp)) + ')')
+ self.h('{')
+ tmp = ['context'] + [name for (ctype, name) in out_args]
+ self.h(' dbus_g_method_return (' + ',\n '.join(tmp) + ');')
+ self.h('}')
+ self.h('')
+
+ return in_class
+
+ def get_signal_const_entry(self, signal):
+ assert self.node_name_uc is not None
+ return ('SIGNAL_%s_%s'
+ % (self.node_name_uc, signal.getAttribute('name')))
+
+ def do_signal(self, signal):
+ assert self.node_name_mixed is not None
+
+ in_base_init = []
+
+ # for signal: Thing::StuffHappened (s, u)
+ # we want to emit:
+ # void tp_svc_thing_emit_stuff_happened (gpointer instance,
+ # const char *arg0, guint arg1);
+
+ dbus_name = signal.getAttribute('name')
+
+ ugly_name = signal.getAttribute('tp:name-for-bindings')
+ if dbus_name != ugly_name.replace('_', ''):
+ raise AssertionError('Signal %s tp:name-for-bindings (%s) does '
+ 'not match' % (dbus_name, ugly_name))
+
+ stub_name = (self.prefix_ + self.node_name_lc + '_emit_' +
+ ugly_name.lower())
+
+ const_name = self.get_signal_const_entry(signal)
+
+ # Gather arguments
+ args = []
+ for i in signal.getElementsByTagName('arg'):
+ name = i.getAttribute('name')
+ dtype = i.getAttribute('type')
+ tp_type = i.getAttribute('tp:type')
+
+ if name:
+ name = 'arg_' + name
+ else:
+ name = 'arg' + str(len(args))
+
+ ctype, gtype, marshaller, pointer = type_to_gtype(dtype)
+
+ if pointer:
+ ctype = 'const ' + ctype
+
+ struct = (ctype, name, gtype)
+ args.append(struct)
+
+ tmp = (['gpointer instance'] +
+ [ctype + name for (ctype, name, gtype) in args])
+
+ self.h(('void %s (' % stub_name) + (',\n '.join(tmp)) + ');')
+
+ # FIXME: emit docs
+
+ self.d('/**')
+ self.d(' * %s:' % stub_name)
+ self.d(' * @instance: The object implementing this interface')
+ for (ctype, name, gtype) in args:
+ self.d(' * @%s: %s (FIXME, generate documentation)'
+ % (name, ctype))
+ self.d(' *')
+ self.d(' * Type-safe wrapper around g_signal_emit to emit the')
+ self.d(' * %s signal on interface %s.'
+ % (dbus_name, self.iface_name))
+ self.d(' */')
+
+ self.b('void')
+ self.b(('%s (' % stub_name) + (',\n '.join(tmp)) + ')')
+ self.b('{')
+ self.b(' g_assert (instance != NULL);')
+ self.b(' g_assert (G_TYPE_CHECK_INSTANCE_TYPE (instance, %s));'
+ % (self.current_gtype))
+ tmp = (['instance', '%s_signals[%s]' % (self.node_name_lc, const_name),
+ '0'] + [name for (ctype, name, gtype) in args])
+ self.b(' g_signal_emit (' + ',\n '.join(tmp) + ');')
+ self.b('}')
+ self.b('')
+
+ signal_name = dbus_gutils_wincaps_to_uscore(dbus_name).replace('_',
+ '-')
+
+ self.d('/**')
+ self.d(' * %s%s::%s:'
+ % (self.Prefix, self.node_name_mixed, signal_name))
+ for (ctype, name, gtype) in args:
+ self.d(' * @%s: %s (FIXME, generate documentation)'
+ % (name, ctype))
+ self.d(' *')
+ self.d(' * The %s D-Bus signal is emitted whenever '
+ 'this GObject signal is.' % dbus_name)
+ self.d(' */')
+ self.d('')
+
+ in_base_init.append(' %s_signals[%s] ='
+ % (self.node_name_lc, const_name))
+ in_base_init.append(' g_signal_new ("%s",' % signal_name)
+ in_base_init.append(' G_OBJECT_CLASS_TYPE (klass),')
+ in_base_init.append(' G_SIGNAL_RUN_LAST|G_SIGNAL_DETAILED,')
+ in_base_init.append(' 0,')
+ in_base_init.append(' NULL, NULL,')
+ in_base_init.append(' %s,'
+ % signal_to_marshal_name(signal, self.signal_marshal_prefix))
+ in_base_init.append(' G_TYPE_NONE,')
+ tmp = ['%d' % len(args)] + [gtype for (ctype, name, gtype) in args]
+ in_base_init.append(' %s);' % ',\n '.join(tmp))
+ in_base_init.append('')
+
+ return in_base_init
+
+ def have_properties(self, nodes):
+ for node in nodes:
+ interface = node.getElementsByTagName('interface')[0]
+ if interface.getElementsByTagName('property'):
+ return True
+ return False
+
+ def __call__(self):
+ nodes = self.dom.getElementsByTagName('node')
+ nodes.sort(cmp_by_name)
+
+ self.h('#include <glib-object.h>')
+ self.h('#include <dbus/dbus-glib.h>')
+
+ if self.have_properties(nodes):
+ self.h('#include <telepathy-glib/dbus-properties-mixin.h>')
+
+ self.h('')
+ self.h('G_BEGIN_DECLS')
+ self.h('')
+
+ self.b('#include "%s.h"' % self.basename)
+ self.b('')
+ for header in self.headers:
+ self.b('#include %s' % header)
+ self.b('')
+
+ for node in nodes:
+ self.do_node(node)
+
+ self.h('')
+ self.h('G_END_DECLS')
+
+ self.b('')
+ for header in self.end_headers:
+ self.b('#include %s' % header)
+
+ self.h('')
+ self.b('')
+ open(self.basename + '.h', 'w').write('\n'.join(self.__header))
+ open(self.basename + '.c', 'w').write('\n'.join(self.__body))
+ open(self.basename + '-gtk-doc.h', 'w').write('\n'.join(self.__docs))
+
+
+def cmdline_error():
+ print """\
+usage:
+ gen-ginterface [OPTIONS] xmlfile Prefix_
+options:
+ --include='<header.h>' (may be repeated)
+ --include='"header.h"' (ditto)
+ --include-end='"header.h"' (ditto)
+ Include extra headers in the generated .c file
+ --signal-marshal-prefix='prefix'
+ Use the given prefix on generated signal marshallers (default is
+ prefix.lower()).
+ --filename='BASENAME'
+ Set the basename for the output files (default is prefix.lower()
+ + 'ginterfaces')
+ --not-implemented-func='symbol'
+ Set action when methods not implemented in the interface vtable are
+ called. symbol must have signature
+ void symbol (DBusGMethodInvocation *context)
+ and return some sort of "not implemented" error via
+ dbus_g_method_return_error (context, ...)
+"""
+ sys.exit(1)
+
+
+if __name__ == '__main__':
+ from getopt import gnu_getopt
+
+ options, argv = gnu_getopt(sys.argv[1:], '',
+ ['filename=', 'signal-marshal-prefix=',
+ 'include=', 'include-end=',
+ 'allow-unstable',
+ 'not-implemented-func='])
+
+ try:
+ prefix = argv[1]
+ except IndexError:
+ cmdline_error()
+
+ basename = prefix.lower() + 'ginterfaces'
+ signal_marshal_prefix = prefix.lower().rstrip('_')
+ headers = []
+ end_headers = []
+ not_implemented_func = ''
+ allow_havoc = False
+
+ for option, value in options:
+ if option == '--filename':
+ basename = value
+ elif option == '--signal-marshal-prefix':
+ signal_marshal_prefix = value
+ elif option == '--include':
+ if value[0] not in '<"':
+ value = '"%s"' % value
+ headers.append(value)
+ elif option == '--include-end':
+ if value[0] not in '<"':
+ value = '"%s"' % value
+ end_headers.append(value)
+ elif option == '--not-implemented-func':
+ not_implemented_func = value
+ elif option == '--allow-unstable':
+ allow_havoc = True
+
+ try:
+ dom = xml.dom.minidom.parse(argv[0])
+ except IndexError:
+ cmdline_error()
+
+ Generator(dom, prefix, basename, signal_marshal_prefix, headers,
+ end_headers, not_implemented_func, allow_havoc)()
diff --git a/farstream/tools/glib-gtypes-generator.py b/farstream/tools/glib-gtypes-generator.py
new file mode 100644
index 000000000..a49c36e7f
--- /dev/null
+++ b/farstream/tools/glib-gtypes-generator.py
@@ -0,0 +1,298 @@
+#!/usr/bin/python
+
+# Generate GLib GInterfaces from the Telepathy specification.
+# The master copy of this program is in the telepathy-glib repository -
+# please make any changes there.
+#
+# Copyright (C) 2006, 2007 Collabora Limited
+#
+# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import sys
+import xml.dom.minidom
+
+from libglibcodegen import escape_as_identifier, \
+ get_docstring, \
+ NS_TP, \
+ Signature, \
+ type_to_gtype, \
+ xml_escape
+
+
+def types_to_gtypes(types):
+ return [type_to_gtype(t)[1] for t in types]
+
+
+class GTypesGenerator(object):
+ def __init__(self, dom, output, mixed_case_prefix):
+ self.dom = dom
+ self.Prefix = mixed_case_prefix
+ self.PREFIX_ = self.Prefix.upper() + '_'
+ self.prefix_ = self.Prefix.lower() + '_'
+
+ self.header = open(output + '.h', 'w')
+ self.body = open(output + '-body.h', 'w')
+ self.docs = open(output + '-gtk-doc.h', 'w')
+
+ for f in (self.header, self.body, self.docs):
+ f.write('/* Auto-generated, do not edit.\n *\n'
+ ' * This file may be distributed under the same terms\n'
+ ' * as the specification from which it was generated.\n'
+ ' */\n\n')
+
+ # keys are e.g. 'sv', values are the key escaped
+ self.need_mappings = {}
+ # keys are the contents of the struct (e.g. 'sssu'), values are the
+ # key escaped
+ self.need_structs = {}
+ # keys are the contents of the struct (e.g. 'sssu'), values are the
+ # key escaped
+ self.need_struct_arrays = {}
+
+ # keys are the contents of the array (unlike need_struct_arrays!),
+ # values are the key escaped
+ self.need_other_arrays = {}
+
+ def h(self, code):
+ self.header.write(code.encode("utf-8"))
+
+ def c(self, code):
+ self.body.write(code.encode("utf-8"))
+
+ def d(self, code):
+ self.docs.write(code.encode('utf-8'))
+
+ def do_mapping_header(self, mapping):
+ members = mapping.getElementsByTagNameNS(NS_TP, 'member')
+ assert len(members) == 2
+
+ impl_sig = ''.join([elt.getAttribute('type')
+ for elt in members])
+
+ esc_impl_sig = escape_as_identifier(impl_sig)
+
+ name = (self.PREFIX_ + 'HASH_TYPE_' +
+ mapping.getAttribute('name').upper())
+ impl = self.prefix_ + 'type_dbus_hash_' + esc_impl_sig
+
+ docstring = get_docstring(mapping) or '(Undocumented)'
+
+ self.d('/**\n * %s:\n *\n' % name)
+ self.d(' * %s\n' % xml_escape(docstring))
+ self.d(' *\n')
+ self.d(' * This macro expands to a call to a function\n')
+ self.d(' * that returns the #GType of a #GHashTable\n')
+ self.d(' * appropriate for representing a D-Bus\n')
+ self.d(' * dictionary of signature\n')
+ self.d(' * <literal>a{%s}</literal>.\n' % impl_sig)
+ self.d(' *\n')
+
+ key, value = members
+
+ self.d(' * Keys (D-Bus type <literal>%s</literal>,\n'
+ % key.getAttribute('type'))
+ tp_type = key.getAttributeNS(NS_TP, 'type')
+ if tp_type:
+ self.d(' * type <literal>%s</literal>,\n' % tp_type)
+ self.d(' * named <literal>%s</literal>):\n'
+ % key.getAttribute('name'))
+ docstring = get_docstring(key) or '(Undocumented)'
+ self.d(' * %s\n' % xml_escape(docstring))
+ self.d(' *\n')
+
+ self.d(' * Values (D-Bus type <literal>%s</literal>,\n'
+ % value.getAttribute('type'))
+ tp_type = value.getAttributeNS(NS_TP, 'type')
+ if tp_type:
+ self.d(' * type <literal>%s</literal>,\n' % tp_type)
+ self.d(' * named <literal>%s</literal>):\n'
+ % value.getAttribute('name'))
+ docstring = get_docstring(value) or '(Undocumented)'
+ self.d(' * %s\n' % xml_escape(docstring))
+ self.d(' *\n')
+
+ self.d(' */\n')
+
+ self.h('#define %s (%s ())\n\n' % (name, impl))
+ self.need_mappings[impl_sig] = esc_impl_sig
+
+ array_name = mapping.getAttribute('array-name')
+ if array_name:
+ gtype_name = self.PREFIX_ + 'ARRAY_TYPE_' + array_name.upper()
+ contents_sig = 'a{' + impl_sig + '}'
+ esc_contents_sig = escape_as_identifier(contents_sig)
+ impl = self.prefix_ + 'type_dbus_array_of_' + esc_contents_sig
+ self.d('/**\n * %s:\n\n' % gtype_name)
+ self.d(' * Expands to a call to a function\n')
+ self.d(' * that returns the #GType of a #GPtrArray\n')
+ self.d(' * of #%s.\n' % name)
+ self.d(' */\n\n')
+
+ self.h('#define %s (%s ())\n\n' % (gtype_name, impl))
+ self.need_other_arrays[contents_sig] = esc_contents_sig
+
+ def do_struct_header(self, struct):
+ members = struct.getElementsByTagNameNS(NS_TP, 'member')
+ impl_sig = ''.join([elt.getAttribute('type') for elt in members])
+ esc_impl_sig = escape_as_identifier(impl_sig)
+
+ name = (self.PREFIX_ + 'STRUCT_TYPE_' +
+ struct.getAttribute('name').upper())
+ impl = self.prefix_ + 'type_dbus_struct_' + esc_impl_sig
+ docstring = struct.getElementsByTagNameNS(NS_TP, 'docstring')
+ if docstring:
+ docstring = docstring[0].toprettyxml()
+ if docstring.startswith('<tp:docstring>'):
+ docstring = docstring[14:]
+ if docstring.endswith('</tp:docstring>\n'):
+ docstring = docstring[:-16]
+ if docstring.strip() in ('<tp:docstring/>', ''):
+ docstring = '(Undocumented)'
+ else:
+ docstring = '(Undocumented)'
+ self.d('/**\n * %s:\n\n' % name)
+ self.d(' * %s\n' % xml_escape(docstring))
+ self.d(' *\n')
+ self.d(' * This macro expands to a call to a function\n')
+ self.d(' * that returns the #GType of a #GValueArray\n')
+ self.d(' * appropriate for representing a D-Bus struct\n')
+ self.d(' * with signature <literal>(%s)</literal>.\n'
+ % impl_sig)
+ self.d(' *\n')
+
+ for i, member in enumerate(members):
+ self.d(' * Member %d (D-Bus type '
+ '<literal>%s</literal>,\n'
+ % (i, member.getAttribute('type')))
+ tp_type = member.getAttributeNS(NS_TP, 'type')
+ if tp_type:
+ self.d(' * type <literal>%s</literal>,\n' % tp_type)
+ self.d(' * named <literal>%s</literal>):\n'
+ % member.getAttribute('name'))
+ docstring = get_docstring(member) or '(Undocumented)'
+ self.d(' * %s\n' % xml_escape(docstring))
+ self.d(' *\n')
+
+ self.d(' */\n\n')
+
+ self.h('#define %s (%s ())\n\n' % (name, impl))
+
+ array_name = struct.getAttribute('array-name')
+ if array_name != '':
+ array_name = (self.PREFIX_ + 'ARRAY_TYPE_' + array_name.upper())
+ impl = self.prefix_ + 'type_dbus_array_' + esc_impl_sig
+ self.d('/**\n * %s:\n\n' % array_name)
+ self.d(' * Expands to a call to a function\n')
+ self.d(' * that returns the #GType of a #GPtrArray\n')
+ self.d(' * of #%s.\n' % name)
+ self.d(' */\n\n')
+
+ self.h('#define %s (%s ())\n\n' % (array_name, impl))
+ self.need_struct_arrays[impl_sig] = esc_impl_sig
+
+ self.need_structs[impl_sig] = esc_impl_sig
+
+ def __call__(self):
+ mappings = self.dom.getElementsByTagNameNS(NS_TP, 'mapping')
+ structs = self.dom.getElementsByTagNameNS(NS_TP, 'struct')
+
+ for mapping in mappings:
+ self.do_mapping_header(mapping)
+
+ for sig in self.need_mappings:
+ self.h('GType %stype_dbus_hash_%s (void);\n\n' %
+ (self.prefix_, self.need_mappings[sig]))
+ self.c('GType\n%stype_dbus_hash_%s (void)\n{\n' %
+ (self.prefix_, self.need_mappings[sig]))
+ self.c(' static GType t = 0;\n\n')
+ self.c(' if (G_UNLIKELY (t == 0))\n')
+ # FIXME: translate sig into two GTypes
+ items = tuple(Signature(sig))
+ gtypes = types_to_gtypes(items)
+ self.c(' t = dbus_g_type_get_map ("GHashTable", '
+ '%s, %s);\n' % (gtypes[0], gtypes[1]))
+ self.c(' return t;\n')
+ self.c('}\n\n')
+
+ for struct in structs:
+ self.do_struct_header(struct)
+
+ for sig in self.need_structs:
+ self.h('GType %stype_dbus_struct_%s (void);\n\n' %
+ (self.prefix_, self.need_structs[sig]))
+ self.c('GType\n%stype_dbus_struct_%s (void)\n{\n' %
+ (self.prefix_, self.need_structs[sig]))
+ self.c(' static GType t = 0;\n\n')
+ self.c(' if (G_UNLIKELY (t == 0))\n')
+ self.c(' t = dbus_g_type_get_struct ("GValueArray",\n')
+ items = tuple(Signature(sig))
+ gtypes = types_to_gtypes(items)
+ for gtype in gtypes:
+ self.c(' %s,\n' % gtype)
+ self.c(' G_TYPE_INVALID);\n')
+ self.c(' return t;\n')
+ self.c('}\n\n')
+
+ for sig in self.need_struct_arrays:
+ self.h('GType %stype_dbus_array_%s (void);\n\n' %
+ (self.prefix_, self.need_struct_arrays[sig]))
+ self.c('GType\n%stype_dbus_array_%s (void)\n{\n' %
+ (self.prefix_, self.need_struct_arrays[sig]))
+ self.c(' static GType t = 0;\n\n')
+ self.c(' if (G_UNLIKELY (t == 0))\n')
+ self.c(' t = dbus_g_type_get_collection ("GPtrArray", '
+ '%stype_dbus_struct_%s ());\n' %
+ (self.prefix_, self.need_struct_arrays[sig]))
+ self.c(' return t;\n')
+ self.c('}\n\n')
+
+ for sig in self.need_other_arrays:
+ self.h('GType %stype_dbus_array_of_%s (void);\n\n' %
+ (self.prefix_, self.need_other_arrays[sig]))
+ self.c('GType\n%stype_dbus_array_of_%s (void)\n{\n' %
+ (self.prefix_, self.need_other_arrays[sig]))
+ self.c(' static GType t = 0;\n\n')
+ self.c(' if (G_UNLIKELY (t == 0))\n')
+
+ if sig[:2] == 'a{' and sig[-1:] == '}':
+ # array of mappings
+ self.c(' t = dbus_g_type_get_collection ('
+ '"GPtrArray", '
+ '%stype_dbus_hash_%s ());\n' %
+ (self.prefix_, escape_as_identifier(sig[2:-1])))
+ elif sig[:2] == 'a(' and sig[-1:] == ')':
+ # array of arrays of struct
+ self.c(' t = dbus_g_type_get_collection ('
+ '"GPtrArray", '
+ '%stype_dbus_array_%s ());\n' %
+ (self.prefix_, escape_as_identifier(sig[2:-1])))
+ elif sig[:1] == 'a':
+ # array of arrays of non-struct
+ self.c(' t = dbus_g_type_get_collection ('
+ '"GPtrArray", '
+ '%stype_dbus_array_of_%s ());\n' %
+ (self.prefix_, escape_as_identifier(sig[1:])))
+ else:
+ raise AssertionError("array of '%s' not supported" % sig)
+
+ self.c(' return t;\n')
+ self.c('}\n\n')
+
+if __name__ == '__main__':
+ argv = sys.argv[1:]
+
+ dom = xml.dom.minidom.parse(argv[0])
+
+ GTypesGenerator(dom, argv[1], argv[2])()
diff --git a/farstream/tools/glib-interfaces-gen.py b/farstream/tools/glib-interfaces-gen.py
new file mode 100644
index 000000000..69c721be3
--- /dev/null
+++ b/farstream/tools/glib-interfaces-gen.py
@@ -0,0 +1,197 @@
+#!/usr/bin/python
+
+from sys import argv, stdout, stderr
+import xml.dom.minidom
+
+from libglibcodegen import NS_TP, get_docstring, \
+ get_descendant_text, get_by_path
+
+class Generator(object):
+ def __init__(self, prefix, implfile, declfile, dom):
+ self.prefix = prefix + '_'
+
+ assert declfile.endswith('.h')
+ docfile = declfile[:-2] + '-gtk-doc.h'
+
+ self.impls = open(implfile, 'w')
+ self.decls = open(declfile, 'w')
+ self.docs = open(docfile, 'w')
+ self.spec = get_by_path(dom, "spec")[0]
+
+ def h(self, code):
+ self.decls.write(code.encode('utf-8'))
+
+ def c(self, code):
+ self.impls.write(code.encode('utf-8'))
+
+ def d(self, code):
+ self.docs.write(code.encode('utf-8'))
+
+ def __call__(self):
+ for f in self.h, self.c:
+ self.do_header(f)
+ self.do_body()
+
+ # Header
+ def do_header(self, f):
+ f('/* Generated from: ')
+ f(get_descendant_text(get_by_path(self.spec, 'title')))
+ version = get_by_path(self.spec, "version")
+ if version:
+ f(' version ' + get_descendant_text(version))
+ f('\n\n')
+ for copyright in get_by_path(self.spec, 'copyright'):
+ f(get_descendant_text(copyright))
+ f('\n')
+ f('\n')
+ f(get_descendant_text(get_by_path(self.spec, 'license')))
+ f(get_descendant_text(get_by_path(self.spec, 'docstring')))
+ f("""
+ */
+
+""")
+
+ # Body
+ def do_body(self):
+ for iface in self.spec.getElementsByTagName('interface'):
+ self.do_iface(iface)
+
+ def do_iface(self, iface):
+ parent_name = get_by_path(iface, '../@name')
+ self.d("""\
+/**
+ * %(IFACE_DEFINE)s:
+ *
+ * The interface name "%(name)s"
+ */
+""" % {'IFACE_DEFINE' : (self.prefix + 'IFACE_' + \
+ parent_name).upper().replace('/', ''),
+ 'name' : iface.getAttribute('name')})
+
+ self.h("""
+#define %(IFACE_DEFINE)s \\
+"%(name)s"
+""" % {'IFACE_DEFINE' : (self.prefix + 'IFACE_' + \
+ parent_name).upper().replace('/', ''),
+ 'name' : iface.getAttribute('name')})
+
+ self.d("""
+/**
+ * %(IFACE_QUARK_DEFINE)s:
+ *
+ * Expands to a call to a function that returns a quark for the interface \
+name "%(name)s"
+ */
+""" % {'IFACE_QUARK_DEFINE' : (self.prefix + 'IFACE_QUARK_' + \
+ parent_name).upper().replace('/', ''),
+ 'iface_quark_func' : (self.prefix + 'iface_quark_' + \
+ parent_name).lower().replace('/', ''),
+ 'name' : iface.getAttribute('name')})
+
+ self.h("""
+#define %(IFACE_QUARK_DEFINE)s \\
+ (%(iface_quark_func)s ())
+
+GQuark %(iface_quark_func)s (void);
+
+""" % {'IFACE_QUARK_DEFINE' : (self.prefix + 'IFACE_QUARK_' + \
+ parent_name).upper().replace('/', ''),
+ 'iface_quark_func' : (self.prefix + 'iface_quark_' + \
+ parent_name).lower().replace('/', ''),
+ 'name' : iface.getAttribute('name')})
+
+ self.c("""\
+GQuark
+%(iface_quark_func)s (void)
+{
+ static GQuark quark = 0;
+
+ if (G_UNLIKELY (quark == 0))
+ {
+ quark = g_quark_from_static_string ("%(name)s");
+ }
+
+ return quark;
+}
+
+""" % {'iface_quark_func' : (self.prefix + 'iface_quark_' + \
+ parent_name).lower().replace('/', ''),
+ 'name' : iface.getAttribute('name')})
+
+ for prop in iface.getElementsByTagNameNS(None, 'property'):
+ self.d("""
+/**
+ * %(IFACE_PREFIX)s_%(PROP_UC)s:
+ *
+ * The fully-qualified property name "%(name)s.%(prop)s"
+ */
+""" % {'IFACE_PREFIX' : (self.prefix + 'PROP_' + \
+ parent_name).upper().replace('/', ''),
+ 'PROP_UC': prop.getAttributeNS(NS_TP, "name-for-bindings").upper(),
+ 'name' : iface.getAttribute('name'),
+ 'prop' : prop.getAttribute('name'),
+ })
+
+ self.h("""
+#define %(IFACE_PREFIX)s_%(PROP_UC)s \\
+"%(name)s.%(prop)s"
+""" % {'IFACE_PREFIX' : (self.prefix + 'PROP_' + \
+ parent_name).upper().replace('/', ''),
+ 'PROP_UC': prop.getAttributeNS(NS_TP, "name-for-bindings").upper(),
+ 'name' : iface.getAttribute('name'),
+ 'prop' : prop.getAttribute('name'),
+ })
+
+
+ for prop in iface.getElementsByTagNameNS(NS_TP, 'contact-attribute'):
+ self.d("""
+/**
+ * %(TOKEN_PREFIX)s_%(TOKEN_UC)s:
+ *
+ * The fully-qualified contact attribute token name "%(name)s/%(prop)s"
+ */
+""" % {'TOKEN_PREFIX' : (self.prefix + 'TOKEN_' + \
+ parent_name).upper().replace('/', ''),
+ 'TOKEN_UC': prop.getAttributeNS(None, "name").upper().replace("-", "_").replace(".", "_"),
+ 'name' : iface.getAttribute('name'),
+ 'prop' : prop.getAttribute('name'),
+ })
+
+ self.h("""
+#define %(TOKEN_PREFIX)s_%(TOKEN_UC)s \\
+"%(name)s/%(prop)s"
+""" % {'TOKEN_PREFIX' : (self.prefix + 'TOKEN_' + \
+ parent_name).upper().replace('/', ''),
+ 'TOKEN_UC': prop.getAttributeNS(None, "name").upper().replace("-", "_").replace(".", "_"),
+ 'name' : iface.getAttribute('name'),
+ 'prop' : prop.getAttribute('name'),
+ })
+
+ for prop in iface.getElementsByTagNameNS(NS_TP, 'hct'):
+ if (prop.getAttribute('is-family') != "yes"):
+ self.d("""
+/**
+ * %(TOKEN_PREFIX)s_%(TOKEN_UC)s:
+ *
+ * The fully-qualified capability token name "%(name)s/%(prop)s"
+ */
+""" % {'TOKEN_PREFIX' : (self.prefix + 'TOKEN_' + \
+ parent_name).upper().replace('/', ''),
+ 'TOKEN_UC': prop.getAttributeNS(None, "name").upper().replace("-", "_").replace(".", "_"),
+ 'name' : iface.getAttribute('name'),
+ 'prop' : prop.getAttribute('name'),
+ })
+
+ self.h("""
+#define %(TOKEN_PREFIX)s_%(TOKEN_UC)s \\
+"%(name)s/%(prop)s"
+""" % {'TOKEN_PREFIX' : (self.prefix + 'TOKEN_' + \
+ parent_name).upper().replace('/', ''),
+ 'TOKEN_UC': prop.getAttributeNS(None, "name").upper().replace("-", "_").replace(".", "_"),
+ 'name' : iface.getAttribute('name'),
+ 'prop' : prop.getAttribute('name'),
+ })
+
+if __name__ == '__main__':
+ argv = argv[1:]
+ Generator(argv[0], argv[1], argv[2], xml.dom.minidom.parse(argv[3]))()
diff --git a/farstream/tools/glib-signals-marshal-gen.py b/farstream/tools/glib-signals-marshal-gen.py
new file mode 100644
index 000000000..0d02c1341
--- /dev/null
+++ b/farstream/tools/glib-signals-marshal-gen.py
@@ -0,0 +1,55 @@
+#!/usr/bin/python
+
+import sys
+import xml.dom.minidom
+from string import ascii_letters, digits
+
+
+from libglibcodegen import signal_to_marshal_name, method_to_glue_marshal_name
+
+
+class Generator(object):
+
+ def __init__(self, dom):
+ self.dom = dom
+ self.marshallers = {}
+
+ def do_method(self, method):
+ marshaller = method_to_glue_marshal_name(method, 'PREFIX')
+
+ assert '__' in marshaller
+ rhs = marshaller.split('__', 1)[1].split('_')
+
+ self.marshallers[marshaller] = rhs
+
+ def do_signal(self, signal):
+ marshaller = signal_to_marshal_name(signal, 'PREFIX')
+
+ assert '__' in marshaller
+ rhs = marshaller.split('__', 1)[1].split('_')
+
+ self.marshallers[marshaller] = rhs
+
+ def __call__(self):
+ methods = self.dom.getElementsByTagName('method')
+
+ for method in methods:
+ self.do_method(method)
+
+ signals = self.dom.getElementsByTagName('signal')
+
+ for signal in signals:
+ self.do_signal(signal)
+
+ all = self.marshallers.keys()
+ all.sort()
+ for marshaller in all:
+ rhs = self.marshallers[marshaller]
+ if not marshaller.startswith('g_cclosure'):
+ print 'VOID:' + ','.join(rhs)
+
+if __name__ == '__main__':
+ argv = sys.argv[1:]
+ dom = xml.dom.minidom.parse(argv[0])
+
+ Generator(dom)()
diff --git a/farstream/tools/libglibcodegen.py b/farstream/tools/libglibcodegen.py
new file mode 100644
index 000000000..6a9d21485
--- /dev/null
+++ b/farstream/tools/libglibcodegen.py
@@ -0,0 +1,172 @@
+"""Library code for GLib/D-Bus-related code generation.
+
+The master copy of this library is in the telepathy-glib repository -
+please make any changes there.
+"""
+
+# Copyright (C) 2006-2008 Collabora Limited
+#
+# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+from libtpcodegen import NS_TP, \
+ Signature, \
+ cmp_by_name, \
+ escape_as_identifier, \
+ get_by_path, \
+ get_descendant_text, \
+ get_docstring, \
+ xml_escape, \
+ get_deprecated
+
+def dbus_gutils_wincaps_to_uscore(s):
+ """Bug-for-bug compatible Python port of _dbus_gutils_wincaps_to_uscore
+ which gets sequences of capital letters wrong in the same way.
+ (e.g. in Telepathy, SendDTMF -> send_dt_mf)
+ """
+ ret = ''
+ for c in s:
+ if c >= 'A' and c <= 'Z':
+ length = len(ret)
+ if length > 0 and (length < 2 or ret[length-2] != '_'):
+ ret += '_'
+ ret += c.lower()
+ else:
+ ret += c
+ return ret
+
+
+def signal_to_marshal_type(signal):
+ """
+ return a list of strings indicating the marshalling type for this signal.
+ """
+
+ mtype=[]
+ for i in signal.getElementsByTagName("arg"):
+ name =i.getAttribute("name")
+ type = i.getAttribute("type")
+ mtype.append(type_to_gtype(type)[2])
+
+ return mtype
+
+
+_glib_marshallers = ['VOID', 'BOOLEAN', 'CHAR', 'UCHAR', 'INT',
+ 'STRING', 'UINT', 'LONG', 'ULONG', 'ENUM', 'FLAGS', 'FLOAT',
+ 'DOUBLE', 'STRING', 'PARAM', 'BOXED', 'POINTER', 'OBJECT',
+ 'UINT_POINTER']
+
+
+def signal_to_marshal_name(signal, prefix):
+
+ mtype = signal_to_marshal_type(signal)
+ if len(mtype):
+ name = '_'.join(mtype)
+ else:
+ name = 'VOID'
+
+ if name in _glib_marshallers:
+ return 'g_cclosure_marshal_VOID__' + name
+ else:
+ return prefix + '_marshal_VOID__' + name
+
+
+def method_to_glue_marshal_name(method, prefix):
+
+ mtype = []
+ for i in method.getElementsByTagName("arg"):
+ if i.getAttribute("direction") != "out":
+ type = i.getAttribute("type")
+ mtype.append(type_to_gtype(type)[2])
+
+ mtype.append('POINTER')
+
+ name = '_'.join(mtype)
+
+ if name in _glib_marshallers:
+ return 'g_cclosure_marshal_VOID__' + name
+ else:
+ return prefix + '_marshal_VOID__' + name
+
+
+def type_to_gtype(s):
+ if s == 'y': #byte
+ return ("guchar ", "G_TYPE_UCHAR","UCHAR", False)
+ elif s == 'b': #boolean
+ return ("gboolean ", "G_TYPE_BOOLEAN","BOOLEAN", False)
+ elif s == 'n': #int16
+ return ("gint ", "G_TYPE_INT","INT", False)
+ elif s == 'q': #uint16
+ return ("guint ", "G_TYPE_UINT","UINT", False)
+ elif s == 'i': #int32
+ return ("gint ", "G_TYPE_INT","INT", False)
+ elif s == 'u': #uint32
+ return ("guint ", "G_TYPE_UINT","UINT", False)
+ elif s == 'x': #int64
+ return ("gint64 ", "G_TYPE_INT64","INT64", False)
+ elif s == 't': #uint64
+ return ("guint64 ", "G_TYPE_UINT64","UINT64", False)
+ elif s == 'd': #double
+ return ("gdouble ", "G_TYPE_DOUBLE","DOUBLE", False)
+ elif s == 's': #string
+ return ("gchar *", "G_TYPE_STRING", "STRING", True)
+ elif s == 'g': #signature - FIXME
+ return ("gchar *", "DBUS_TYPE_G_SIGNATURE", "STRING", True)
+ elif s == 'o': #object path
+ return ("gchar *", "DBUS_TYPE_G_OBJECT_PATH", "BOXED", True)
+ elif s == 'v': #variant
+ return ("GValue *", "G_TYPE_VALUE", "BOXED", True)
+ elif s == 'as': #array of strings
+ return ("gchar **", "G_TYPE_STRV", "BOXED", True)
+ elif s == 'ay': #byte array
+ return ("GArray *",
+ "dbus_g_type_get_collection (\"GArray\", G_TYPE_UCHAR)", "BOXED",
+ True)
+ elif s == 'au': #uint array
+ return ("GArray *", "DBUS_TYPE_G_UINT_ARRAY", "BOXED", True)
+ elif s == 'ai': #int array
+ return ("GArray *", "DBUS_TYPE_G_INT_ARRAY", "BOXED", True)
+ elif s == 'ax': #int64 array
+ return ("GArray *", "DBUS_TYPE_G_INT64_ARRAY", "BOXED", True)
+ elif s == 'at': #uint64 array
+ return ("GArray *", "DBUS_TYPE_G_UINT64_ARRAY", "BOXED", True)
+ elif s == 'ad': #double array
+ return ("GArray *", "DBUS_TYPE_G_DOUBLE_ARRAY", "BOXED", True)
+ elif s == 'ab': #boolean array
+ return ("GArray *", "DBUS_TYPE_G_BOOLEAN_ARRAY", "BOXED", True)
+ elif s == 'ao': #object path array
+ return ("GPtrArray *",
+ 'dbus_g_type_get_collection ("GPtrArray",'
+ ' DBUS_TYPE_G_OBJECT_PATH)',
+ "BOXED", True)
+ elif s == 'a{ss}': #hash table of string to string
+ return ("GHashTable *", "DBUS_TYPE_G_STRING_STRING_HASHTABLE", "BOXED", False)
+ elif s[:2] == 'a{': #some arbitrary hash tables
+ if s[2] not in ('y', 'b', 'n', 'q', 'i', 'u', 's', 'o', 'g'):
+ raise Exception, "can't index a hashtable off non-basic type " + s
+ first = type_to_gtype(s[2])
+ second = type_to_gtype(s[3:-1])
+ return ("GHashTable *", "(dbus_g_type_get_map (\"GHashTable\", " + first[1] + ", " + second[1] + "))", "BOXED", False)
+ elif s[:2] in ('a(', 'aa'): # array of structs or arrays, recurse
+ gtype = type_to_gtype(s[1:])[1]
+ return ("GPtrArray *", "(dbus_g_type_get_collection (\"GPtrArray\", "+gtype+"))", "BOXED", True)
+ elif s[:1] == '(': #struct
+ gtype = "(dbus_g_type_get_struct (\"GValueArray\", "
+ for subsig in Signature(s[1:-1]):
+ gtype = gtype + type_to_gtype(subsig)[1] + ", "
+ gtype = gtype + "G_TYPE_INVALID))"
+ return ("GValueArray *", gtype, "BOXED", True)
+
+ # we just don't know ..
+ raise Exception, "don't know the GType for " + s
diff --git a/farstream/tools/libtpcodegen.py b/farstream/tools/libtpcodegen.py
new file mode 100644
index 000000000..837ff2f74
--- /dev/null
+++ b/farstream/tools/libtpcodegen.py
@@ -0,0 +1,215 @@
+"""Library code for language-independent D-Bus-related code generation.
+
+The master copy of this library is in the telepathy-glib repository -
+please make any changes there.
+"""
+
+# Copyright (C) 2006-2008 Collabora Limited
+#
+# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+from string import ascii_letters, digits
+
+
+NS_TP = "http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+
+_ASCII_ALNUM = ascii_letters + digits
+
+
+def cmp_by_name(node1, node2):
+ return cmp(node1.getAttributeNode("name").nodeValue,
+ node2.getAttributeNode("name").nodeValue)
+
+
+def escape_as_identifier(identifier):
+ """Escape the given string to be a valid D-Bus object path or service
+ name component, using a reversible encoding to ensure uniqueness.
+
+ The reversible encoding is as follows:
+
+ * The empty string becomes '_'
+ * Otherwise, each non-alphanumeric character is replaced by '_' plus
+ two lower-case hex digits; the same replacement is carried out on
+ the first character, if it's a digit
+ """
+ # '' -> '_'
+ if not identifier:
+ return '_'
+
+ # A bit of a fast path for strings which are already OK.
+ # We deliberately omit '_' because, for reversibility, that must also
+ # be escaped.
+ if (identifier.strip(_ASCII_ALNUM) == '' and
+ identifier[0] in ascii_letters):
+ return identifier
+
+ # The first character may not be a digit
+ if identifier[0] not in ascii_letters:
+ ret = ['_%02x' % ord(identifier[0])]
+ else:
+ ret = [identifier[0]]
+
+ # Subsequent characters may be digits or ASCII letters
+ for c in identifier[1:]:
+ if c in _ASCII_ALNUM:
+ ret.append(c)
+ else:
+ ret.append('_%02x' % ord(c))
+
+ return ''.join(ret)
+
+
+def get_by_path(element, path):
+ branches = path.split('/')
+ branch = branches[0]
+
+ # Is the current branch an attribute, if so, return the attribute value
+ if branch[0] == '@':
+ return element.getAttribute(branch[1:])
+
+ # Find matching children for the branch
+ children = []
+ if branch == '..':
+ children.append(element.parentNode)
+ else:
+ for x in element.childNodes:
+ if x.localName == branch:
+ children.append(x)
+
+ ret = []
+ # If this is not the last path element, recursively gather results from
+ # children
+ if len(branches) > 1:
+ for x in children:
+ add = get_by_path(x, '/'.join(branches[1:]))
+ if isinstance(add, list):
+ ret += add
+ else:
+ return add
+ else:
+ ret = children
+
+ return ret
+
+
+def get_docstring(element):
+ docstring = None
+ for x in element.childNodes:
+ if x.namespaceURI == NS_TP and x.localName == 'docstring':
+ docstring = x
+ if docstring is not None:
+ docstring = docstring.toxml().replace('\n', ' ').strip()
+ if docstring.startswith('<tp:docstring>'):
+ docstring = docstring[14:].lstrip()
+ if docstring.endswith('</tp:docstring>'):
+ docstring = docstring[:-15].rstrip()
+ if docstring in ('<tp:docstring/>', ''):
+ docstring = ''
+ return docstring
+
+def get_deprecated(element):
+ text = []
+ for x in element.childNodes:
+ if hasattr(x, 'data'):
+ text.append(x.data.replace('\n', ' ').strip())
+ else:
+ # This caters for tp:dbus-ref elements, but little else.
+ if x.childNodes and hasattr(x.childNodes[0], 'data'):
+ text.append(x.childNodes[0].data.replace('\n', ' ').strip())
+ return ' '.join(text)
+
+def get_descendant_text(element_or_elements):
+ if not element_or_elements:
+ return ''
+ if isinstance(element_or_elements, list):
+ return ''.join(map(get_descendant_text, element_or_elements))
+ parts = []
+ for x in element_or_elements.childNodes:
+ if x.nodeType == x.TEXT_NODE:
+ parts.append(x.nodeValue)
+ elif x.nodeType == x.ELEMENT_NODE:
+ parts.append(get_descendant_text(x))
+ else:
+ pass
+ return ''.join(parts)
+
+
+class _SignatureIter:
+ """Iterator over a D-Bus signature. Copied from dbus-python 0.71 so we
+ can run genginterface in a limited environment with only Python
+ (like Scratchbox).
+ """
+ def __init__(self, string):
+ self.remaining = string
+
+ def next(self):
+ if self.remaining == '':
+ raise StopIteration
+
+ signature = self.remaining
+ block_depth = 0
+ block_type = None
+ end = len(signature)
+
+ for marker in range(0, end):
+ cur_sig = signature[marker]
+
+ if cur_sig == 'a':
+ pass
+ elif cur_sig == '{' or cur_sig == '(':
+ if block_type == None:
+ block_type = cur_sig
+
+ if block_type == cur_sig:
+ block_depth = block_depth + 1
+
+ elif cur_sig == '}':
+ if block_type == '{':
+ block_depth = block_depth - 1
+
+ if block_depth == 0:
+ end = marker
+ break
+
+ elif cur_sig == ')':
+ if block_type == '(':
+ block_depth = block_depth - 1
+
+ if block_depth == 0:
+ end = marker
+ break
+
+ else:
+ if block_depth == 0:
+ end = marker
+ break
+
+ end = end + 1
+ self.remaining = signature[end:]
+ return Signature(signature[0:end])
+
+
+class Signature(str):
+ """A string, iteration over which is by D-Bus single complete types
+ rather than characters.
+ """
+ def __iter__(self):
+ return _SignatureIter(self)
+
+
+def xml_escape(s):
+ s = s.replace('&', '&amp;').replace("'", '&apos;').replace('"', '&quot;')
+ return s.replace('<', '&lt;').replace('>', '&gt;')
diff --git a/farstream/tools/telepathy.am b/farstream/tools/telepathy.am
new file mode 100644
index 000000000..d061b8918
--- /dev/null
+++ b/farstream/tools/telepathy.am
@@ -0,0 +1,27 @@
+## Useful top-level Makefile.am snippets for Telepathy projects.
+
+dist-hook:
+ chmod u+w ${distdir}/ChangeLog
+ if test -d ${top_srcdir}/.git; then \
+ git log --stat > ${distdir}/ChangeLog || \
+ git log > ${distdir}/ChangeLog; \
+ fi
+
+maintainer-upload-release: _maintainer-upload-release
+
+_maintainer-upload-release-check:
+ @case @VERSION@ in \
+ (*.*.*.*) \
+ echo "@VERSION@ is not a release" >&2; \
+ exit 2; \
+ ;; \
+ esac
+ test -f @PACKAGE@-@VERSION@.tar.gz
+ test -f @PACKAGE@-@VERSION@.tar.gz.asc
+ gpg --verify @PACKAGE@-@VERSION@.tar.gz.asc
+
+_maintainer-upload-release: _maintainer-upload-release-check
+ rsync -vzP @PACKAGE@-@VERSION@.tar.gz telepathy.freedesktop.org:/srv/telepathy.freedesktop.org/www/releases/@PACKAGE@/@PACKAGE@-@VERSION@.tar.gz
+ rsync -vzP @PACKAGE@-@VERSION@.tar.gz.asc telepathy.freedesktop.org:/srv/telepathy.freedesktop.org/www/releases/@PACKAGE@/@PACKAGE@-@VERSION@.tar.gz.asc
+
+## vim:set ft=automake:
diff --git a/farstream/tools/xincludator.py b/farstream/tools/xincludator.py
new file mode 100644
index 000000000..63e106ace
--- /dev/null
+++ b/farstream/tools/xincludator.py
@@ -0,0 +1,39 @@
+#!/usr/bin/python
+
+from sys import argv, stdout, stderr
+import codecs, locale
+import os
+import xml.dom.minidom
+
+stdout = codecs.getwriter('utf-8')(stdout)
+
+NS_XI = 'http://www.w3.org/2001/XInclude'
+
+def xincludate(dom, base, dropns = []):
+ remove_attrs = []
+ for i in xrange(dom.documentElement.attributes.length):
+ attr = dom.documentElement.attributes.item(i)
+ if attr.prefix == 'xmlns':
+ if attr.localName in dropns:
+ remove_attrs.append(attr)
+ else:
+ dropns.append(attr.localName)
+ for attr in remove_attrs:
+ dom.documentElement.removeAttributeNode(attr)
+ for include in dom.getElementsByTagNameNS(NS_XI, 'include'):
+ href = include.getAttribute('href')
+ # FIXME: assumes Unixy paths
+ filename = os.path.join(os.path.dirname(base), href)
+ subdom = xml.dom.minidom.parse(filename)
+ xincludate(subdom, filename, dropns)
+ if './' in href:
+ subdom.documentElement.setAttribute('xml:base', href)
+ include.parentNode.replaceChild(subdom.documentElement, include)
+
+if __name__ == '__main__':
+ argv = argv[1:]
+ dom = xml.dom.minidom.parse(argv[0])
+ xincludate(dom, argv[0])
+ xml = dom.toxml()
+ stdout.write(xml)
+ stdout.write('\n')