summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonny Lamb <jonny.lamb@collabora.co.uk>2011-11-30 16:29:57 +0000
committerJonny Lamb <jonny.lamb@collabora.co.uk>2011-11-30 16:29:57 +0000
commit119d0f1b44482095ee699450dfffa925e5cb96e9 (patch)
treeceedffdf99f0bd48f10fd289d66c60915cc5d912
parentc27d23ab605c1e01a2fe5542f6140d71f47eccc4 (diff)
parent754dd092fd2a8c26e2c1ac30e629946d70318c61 (diff)
Merge remote-tracking branch 'yell/testing'
-rw-r--r--yell/.gitignore41
-rw-r--r--yell/Android.mk31
-rw-r--r--yell/COPYING509
-rw-r--r--yell/ChangeLog2
-rw-r--r--yell/Makefile.am8
-rw-r--r--yell/NEWS29
-rw-r--r--yell/README5
-rwxr-xr-xyell/autogen.sh31
-rw-r--r--yell/configure.ac150
-rw-r--r--yell/m4/Makefile.am6
-rw-r--r--yell/m4/as-compiler-flag.m433
-rw-r--r--yell/m4/compiler.m467
-rw-r--r--yell/m4/linker.m473
-rw-r--r--yell/m4/tp-compiler-flag.m436
-rw-r--r--yell/m4/tp-compiler-warnings.m440
-rw-r--r--yell/spec/Call_Content.xml255
-rw-r--r--yell/spec/Call_Content_Codec_Offer.xml87
-rw-r--r--yell/spec/Call_Content_Interface_Audio_Control.xml111
-rw-r--r--yell/spec/Call_Content_Interface_Media.xml367
-rw-r--r--yell/spec/Call_Content_Interface_Mute.xml85
-rw-r--r--yell/spec/Call_Content_Interface_Video_Control.xml137
-rw-r--r--yell/spec/Call_Stream.xml261
-rw-r--r--yell/spec/Call_Stream_Endpoint.xml182
-rw-r--r--yell/spec/Call_Stream_Interface_Media.xml473
-rw-r--r--yell/spec/Channel_Type_Call.xml1425
-rw-r--r--yell/spec/Makefile.am11
-rw-r--r--yell/telepathy-yell/Makefile.am206
-rw-r--r--yell/telepathy-yell/all.xml30
-rw-r--r--yell/telepathy-yell/base-call-channel.c953
-rw-r--r--yell/telepathy-yell/base-call-channel.h108
-rw-r--r--yell/telepathy-yell/base-call-content.c500
-rw-r--r--yell/telepathy-yell/base-call-content.h95
-rw-r--r--yell/telepathy-yell/base-call-stream.c509
-rw-r--r--yell/telepathy-yell/base-call-stream.h113
-rw-r--r--yell/telepathy-yell/base-media-call-content.c556
-rw-r--r--yell/telepathy-yell/base-media-call-content.h73
-rw-r--r--yell/telepathy-yell/base-media-call-stream.c517
-rw-r--r--yell/telepathy-yell/base-media-call-stream.h95
-rw-r--r--yell/telepathy-yell/call-channel.c1132
-rw-r--r--yell/telepathy-yell/call-channel.h134
-rw-r--r--yell/telepathy-yell/call-content-codec-offer.c424
-rw-r--r--yell/telepathy-yell/call-content-codec-offer.h85
-rw-r--r--yell/telepathy-yell/call-content.c561
-rw-r--r--yell/telepathy-yell/call-content.h87
-rw-r--r--yell/telepathy-yell/call-stream-endpoint.c482
-rw-r--r--yell/telepathy-yell/call-stream-endpoint.h89
-rw-r--r--yell/telepathy-yell/call-stream.c392
-rw-r--r--yell/telepathy-yell/call-stream.h88
-rw-r--r--yell/telepathy-yell/call.xml18
-rw-r--r--yell/telepathy-yell/cli-call.h6
-rw-r--r--yell/telepathy-yell/debug-internal.h3
-rw-r--r--yell/telepathy-yell/debug.c93
-rw-r--r--yell/telepathy-yell/debug.h64
-rw-r--r--yell/telepathy-yell/enums.h6
-rw-r--r--yell/telepathy-yell/extensions-cli.c33
-rw-r--r--yell/telepathy-yell/extensions.c6
-rw-r--r--yell/telepathy-yell/extensions.h21
-rw-r--r--yell/telepathy-yell/gtypes.h10
-rw-r--r--yell/telepathy-yell/interfaces.h12
-rw-r--r--yell/telepathy-yell/svc-call.h6
-rw-r--r--yell/telepathy-yell/telepathy-yell-uninstalled.pc.in12
-rw-r--r--yell/telepathy-yell/telepathy-yell.h34
-rw-r--r--yell/telepathy-yell/telepathy-yell.pc.in11
-rw-r--r--yell/tools/Makefile.am55
-rw-r--r--yell/tools/c-constants-gen.py169
-rw-r--r--yell/tools/check-c-style.sh69
-rw-r--r--yell/tools/check-coding-style.mk17
-rw-r--r--yell/tools/check-misc.sh13
-rw-r--r--yell/tools/check-whitespace.sh17
-rw-r--r--yell/tools/doc-generator.xsl1199
-rw-r--r--yell/tools/glib-client-gen.py1254
-rw-r--r--yell/tools/glib-client-marshaller-gen.py62
-rw-r--r--yell/tools/glib-errors-enum-body-gen.py62
-rw-r--r--yell/tools/glib-errors-enum-header-gen.py75
-rw-r--r--yell/tools/glib-ginterface-gen.py843
-rw-r--r--yell/tools/glib-gtypes-generator.py298
-rw-r--r--yell/tools/glib-interfaces-gen.py197
-rw-r--r--yell/tools/glib-signals-marshal-gen.py55
-rw-r--r--yell/tools/gobject-foo.py90
-rw-r--r--yell/tools/identity.xsl7
-rw-r--r--yell/tools/libglibcodegen.py172
-rw-r--r--yell/tools/libtpcodegen.py215
-rw-r--r--yell/tools/make-release-mail.py79
-rw-r--r--yell/tools/make-version-script.py208
-rw-r--r--yell/tools/telepathy.am69
-rw-r--r--yell/tools/with-session-bus.sh100
86 files changed, 17344 insertions, 0 deletions
diff --git a/yell/.gitignore b/yell/.gitignore
new file mode 100644
index 000000000..4f581edfd
--- /dev/null
+++ b/yell/.gitignore
@@ -0,0 +1,41 @@
+*.o
+*.lo
+*.la
+*.py[co]
+*.[oa]
+*.l[oa]
+.deps
+.libs
+/*.cdbs-config_list
+/*.cdbs-orig
+/*.make
+/aclocal.m4
+/autom4te.cache
+/config.guess
+/config.h
+/config.h.in
+/config.log
+/config.status
+/config.sub
+/configure
+/depcomp
+/install-sh
+/libtool
+/ltmain.sh
+/m4/libtool.m4
+/m4/ltoptions.m4
+/m4/ltsugar.m4
+/m4/ltversion.m4
+/m4/lt~obsolete.m4
+/missing
+/telepathy-yell/*.pc
+/telepathy-yell/extensions.html
+/stamp-h1
+Android.mk
+Makefile
+Makefile.in
+_gen
+*~
+*.sw?
+/.cproject
+/.project
diff --git a/yell/Android.mk b/yell/Android.mk
new file mode 100644
index 000000000..906cd553d
--- /dev/null
+++ b/yell/Android.mk
@@ -0,0 +1,31 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+TELEPATHY_YELL_BUILT_SOURCES := \
+ telepathy-yell/Android.mk
+
+telepathy-yell-configure-real:
+ cd $(TELEPATHY_YELL_TOP) ; \
+ CC="$(CONFIGURE_CC)" \
+ CFLAGS="$(CONFIGURE_CFLAGS)" \
+ LD=$(TARGET_LD) \
+ LDFLAGS="$(CONFIGURE_LDFLAGS)" \
+ CPP=$(CONFIGURE_CPP) \
+ CPPFLAGS="$(CONFIGURE_CPPFLAGS)" \
+ PKG_CONFIG_LIBDIR=$(CONFIGURE_PKG_CONFIG_LIBDIR) \
+ PKG_CONFIG_TOP_BUILD_DIR=$(PKG_CONFIG_TOP_BUILD_DIR) \
+ $(TELEPATHY_YELL_TOP)/$(CONFIGURE) --host=arm-linux-androideabi \
+ --disable-Werror && \
+ for file in $(TELEPATHY_YELL_BUILT_SOURCES); do \
+ rm -f $$file && \
+ make -C $$(dirname $$file) $$(basename $$file) ; \
+ done
+
+telepathy-yell-configure: telepathy-yell-configure-real
+
+.PHONY: telepathy-yell-configure
+
+CONFIGURE_TARGETS += telepathy-yell-configure
+
+-include $(TELEPATHY_YELL_TOP)/telepathy-yell/Android.mk
diff --git a/yell/COPYING b/yell/COPYING
new file mode 100644
index 000000000..c3bf576e5
--- /dev/null
+++ b/yell/COPYING
@@ -0,0 +1,509 @@
+Copyright © 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+
+ 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
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms
+of the ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library.
+It is safest to attach them to the start of each source file to most
+effectively convey the exclusion of warranty; and each file should
+have at least the "copyright" line and a pointer to where the full
+notice is found.
+
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or
+your school, if any, to sign a "copyright disclaimer" for the library,
+if necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James
+ Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/yell/ChangeLog b/yell/ChangeLog
new file mode 100644
index 000000000..8ae693cc8
--- /dev/null
+++ b/yell/ChangeLog
@@ -0,0 +1,2 @@
+This is a placeholder ChangeLog - use `git log` instead.
+In distributed tarballs this is replaced by the output of `git log --stat`.
diff --git a/yell/Makefile.am b/yell/Makefile.am
new file mode 100644
index 000000000..7781d617b
--- /dev/null
+++ b/yell/Makefile.am
@@ -0,0 +1,8 @@
+ACLOCAL_AMFLAGS = -I m4
+
+SUBDIRS = m4 tools spec telepathy-yell
+
+EXTRA_DIST = \
+ autogen.sh
+
+include tools/telepathy.am
diff --git a/yell/NEWS b/yell/NEWS
new file mode 100644
index 000000000..8419bdb3c
--- /dev/null
+++ b/yell/NEWS
@@ -0,0 +1,29 @@
+telepathy-yell 0.0.4 (18/11/2011)
+=================================
+
+Add Call_Content_Interface_Audio_Control (Sjoerd Simons)
+Use _unref instead of _free/_destroy when possible with
+ GPtrArrays and GHashTables (Xavier Claessens)
+Add TPY_CALL_CHANNEL_FEATURE_CORE (Guillaume Desmottes)
+Add Hold high-level API (Emilio Pozuelo Monfort)
+
+telepathy-yell 0.0.3 (26/05/2011)
+=================================
+
+Correctly track call members (Emilio Pozuelo Monfort)
+Fix tpy_call_content_codec_offer_offer_finish's return value (Marco Barisione)
+Don't leak the GError in codec_offer_finished_cb (Marco Barisione)
+Check the return value of functions instead of the GError (Marco Barisione)
+
+telepathy-yell 0.0.2 (20/05/2011)
+=================================
+
+Fix signal signature (Emilio Pozuelo Monfort)
+Make tpy_call_content_codec_offer_offer ref the cancellable (Mike Ruprecht)
+Don't Initialize member values in _constructed (Sjoerd Simons)
+
+telepathy-yell 0.0.1 (01/04/2011)
+=================================
+
+First release
+
diff --git a/yell/README b/yell/README
new file mode 100644
index 000000000..a148cf4d9
--- /dev/null
+++ b/yell/README
@@ -0,0 +1,5 @@
+Telepathy Yell is an experimental playground for CM base-classes and
+client-side high-level bindings for the new Call interface.
+
+Yell has no API or ABI guarantees, but will bump the soname when required. Use
+at your own risk.
diff --git a/yell/autogen.sh b/yell/autogen.sh
new file mode 100755
index 000000000..7bb241e9d
--- /dev/null
+++ b/yell/autogen.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+set -e
+
+if test -n "$AUTOMAKE"; then
+ : # don't override an explicit user request
+elif automake-1.11 --version >/dev/null 2>/dev/null && \
+ aclocal-1.11 --version >/dev/null 2>/dev/null; then
+ # If we have automake-1.11, use it. This helps to ensure that our build
+ # system doesn't accidentally grow other dependencies.
+ AUTOMAKE=automake-1.11
+ export AUTOMAKE
+ ACLOCAL=aclocal-1.11
+ 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/yell/configure.ac b/yell/configure.ac
new file mode 100644
index 000000000..e3177476f
--- /dev/null
+++ b/yell/configure.ac
@@ -0,0 +1,150 @@
+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 nano_version to 0
+# make the release, tag it
+# set nano_version to 1
+
+m4_define([tp_yell_major_version], [0])
+m4_define([tp_yell_minor_version], [0])
+m4_define([tp_yell_micro_version], [4])
+m4_define([tp_yell_nano_version], [1])
+
+# If library source has changed since last release, increment revision
+# If interfaces have been added, removed or changed since last release,
+# increment current and set revision to 0
+# If interfaces have been added since last release, increment age
+# If interfaces have been removed since last release, set age to 0
+
+m4_define([tp_yell_lt_current], [0])
+m4_define([tp_yell_lt_revision], [1])
+m4_define([tp_yell_lt_age], [0])
+
+# Some magic
+m4_define([tp_yell_base_version],
+ [tp_yell_major_version.tp_yell_minor_version.tp_yell_micro_version])
+m4_define([tp_yell_version],
+ [m4_if(tp_yell_nano_version, 0, [tp_yell_base_version],
+ [tp_yell_base_version].[tp_yell_nano_version])])
+
+AC_INIT([telepathy-yell], [tp_yell_version],
+ [mailto:telepathy@lists.freedesktop.org])
+
+AC_CONFIG_MACRO_DIR([m4])
+
+AM_INIT_AUTOMAKE([1.11 -Wall -Wno-portability foreign])
+AM_CONFIG_HEADER(config.h)
+
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES])
+
+dnl check for tools
+AC_PROG_CC
+AC_PROG_CC_STDC
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+AM_PROG_LIBTOOL
+AM_PROG_MKDIR_P
+
+LT_CURRENT=tp_yell_lt_current
+LT_REVISION=tp_yell_lt_revision
+LT_AGE=tp_yell_lt_age
+AC_SUBST([LT_CURRENT])
+AC_SUBST([LT_REVISION])
+AC_SUBST([LT_AGE])
+
+dnl optimizations, etc.
+COMPILER_OPTIMISATIONS
+COMPILER_COVERAGE
+LINKER_OPTIMISATIONS
+LINKER_VERSION_SCRIPT
+
+dnl decide error flags
+ifelse(tp_yell_nano_version, 0,
+ [ official_release=yes ],
+ [ official_release=no ])
+
+TP_COMPILER_WARNINGS([ERROR_CFLAGS], [test "x$official_release" = xno],
+ [all \
+ extra \
+ declaration-after-statement \
+ shadow \
+ strict-prototypes \
+ missing-prototypes \
+ sign-compare \
+ nested-externs \
+ pointer-arith \
+ format-security \
+ init-self],
+ [missing-field-initializers \
+ unused-parameter])
+AC_SUBST([ERROR_CFLAGS])
+
+ifelse(tp_yell_nano_version, 0,
+ [ # version x.y.z - disable coding style checks by default
+AC_ARG_ENABLE(coding-style-checks,
+ AC_HELP_STRING([--enable-coding-style-checks],
+ [check coding style using grep]),
+ [ENABLE_CODING_STYLE_CHECKS=$enableval], [ENABLE_CODING_STYLE_CHECKS=no] )
+ ],
+ [ # version x.y.z.1 - enable coding style checks by default
+AC_ARG_ENABLE(coding-style-checks,
+ AC_HELP_STRING([--disable-coding-style-checks],
+ [don't check coding style using grep]),
+ [ENABLE_CODING_STYLE_CHECKS=$enableval], [ENABLE_CODING_STYLE_CHECKS=yes])
+ ])
+
+AC_SUBST([ENABLE_CODING_STYLE_CHECKS])
+
+dnl shared library
+AC_ARG_ENABLE(shared-library,
+ AC_HELP_STRING([--disable-shared-library],
+ [disable producing a shared library to install]),
+ [ENABLE_SHARED_LIBRARY=$enableval], [ENABLE_SHARED_LIBRARY=yes])
+
+AM_CONDITIONAL(ENABLE_SHARED_LIBRARY, test "x$ENABLE_SHARED_LIBRARY" != "xno")
+
+dnl Check for Glib
+PKG_CHECK_MODULES(GLIB,
+ [glib-2.0 >= 2.28, gobject-2.0 >= 2.26, gio-2.0 >= 2.28, gio-2.0 >= 2.28])
+
+AC_SUBST(GLIB_CFLAGS)
+AC_SUBST(GLIB_LIBS)
+
+GLIB_GENMARSHAL=`$PKG_CONFIG --variable=glib_genmarshal glib-2.0`
+AC_SUBST(GLIB_GENMARSHAL)
+
+GLIB_MKENUMS=`$PKG_CONFIG --variable=glib_mkenums glib-2.0`
+AC_SUBST(GLIB_MKENUMS)
+
+dnl Check for D-Bus
+PKG_CHECK_MODULES(DBUS, [dbus-1 >= 0.95, dbus-glib-1 >= 0.82])
+
+AC_SUBST(DBUS_CFLAGS)
+AC_SUBST(DBUS_LIBS)
+
+dnl Check for D-Bus
+PKG_CHECK_MODULES(TP_GLIB, [telepathy-glib >= 0.15.6])
+
+AC_SUBST(TP_GLIB_CFLAGS)
+AC_SUBST(TP_GLIB_LIBS)
+
+dnl Check for code generation tools
+XSLTPROC=
+AC_CHECK_PROGS([XSLTPROC], [xsltproc])
+if test -z "$XSLTPROC"; then
+ AC_MSG_ERROR([xsltproc (from the libxslt source package) is required])
+fi
+AM_PATH_PYTHON([2.5])
+
+AC_OUTPUT( Makefile \
+ telepathy-yell/Makefile \
+ telepathy-yell/telepathy-yell.pc \
+ telepathy-yell/telepathy-yell-uninstalled.pc \
+ tools/Makefile \
+ spec/Makefile \
+ m4/Makefile
+)
diff --git a/yell/m4/Makefile.am b/yell/m4/Makefile.am
new file mode 100644
index 000000000..6879413c4
--- /dev/null
+++ b/yell/m4/Makefile.am
@@ -0,0 +1,6 @@
+EXTRA_DIST = \
+as-compiler-flag.m4 \
+compiler.m4 \
+linker.m4 \
+tp-compiler-flag.m4 \
+tp-compiler-warnings.m4
diff --git a/yell/m4/as-compiler-flag.m4 b/yell/m4/as-compiler-flag.m4
new file mode 100644
index 000000000..605708a5a
--- /dev/null
+++ b/yell/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/yell/m4/compiler.m4 b/yell/m4/compiler.m4
new file mode 100644
index 000000000..5aff5d819
--- /dev/null
+++ b/yell/m4/compiler.m4
@@ -0,0 +1,67 @@
+# compiler.m4 - autoconf macros for compiler settings
+#
+# Copyright © 2005 Scott James Remnant <scott@netsplit.com>.
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+# COMPILER_WARNINGS
+# ----------------------
+# Add configure option to enable additional compiler warnings and treat
+# them as errors.
+AC_DEFUN([COMPILER_WARNINGS],
+[AC_ARG_ENABLE(compiler-warnings,
+ AS_HELP_STRING([--enable-compiler-warnings],
+ [Enable additional compiler warnings]),
+[if test "x$enable_compiler_warnings" = "xyes"; then
+ if test "x$GCC" = "xyes"; then
+ CFLAGS="-Wall -Werror $CFLAGS"
+ fi
+ if test "x$GXX" = "xyes"; then
+ CXXFLAGS="-Wall -Werror $CXXFLAGS"
+ fi
+fi])dnl
+])# COMPILER_WARNINGS
+
+# COMPILER_OPTIMISATIONS
+# ---------------------------
+# Add configure option to disable optimisations.
+AC_DEFUN([COMPILER_OPTIMISATIONS],
+[AC_ARG_ENABLE(compiler-optimisations,
+ AS_HELP_STRING([--disable-compiler-optimisations],
+ [Disable compiler optimisations]),
+[if test "x$enable_compiler_optimisations" = "xno"; then
+ [CFLAGS=`echo "$CFLAGS" | sed -e "s/ -O[1-9]*\b/ -O0/g"`]
+fi])dnl
+])# COMPILER_OPTIMISATIONS
+
+# COMPILER_COVERAGE
+# ----------------------
+# Add configure option to enable coverage data.
+AC_DEFUN([COMPILER_COVERAGE],
+[AC_ARG_ENABLE(compiler-coverage,
+ AS_HELP_STRING([--enable-compiler-coverage],
+ [Enable generation of coverage data]),
+[if test "x$enable_compiler_coverage" = "xyes"; then
+ if test "x$GCC" = "xyes"; then
+ CFLAGS="$CFLAGS -fprofile-arcs -ftest-coverage"
+ fi
+fi])dnl
+])# COMPILER_COVERAGE
diff --git a/yell/m4/linker.m4 b/yell/m4/linker.m4
new file mode 100644
index 000000000..8093cb88e
--- /dev/null
+++ b/yell/m4/linker.m4
@@ -0,0 +1,73 @@
+# linker.m4 - autoconf macros for linker settings
+#
+# Copyright © 2005 Scott James Remnant <scott@netsplit.com>.
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+# LINKER_OPTIMISATIONS
+# --------------------
+# Add configure option to disable linker optimisations.
+AC_DEFUN([LINKER_OPTIMISATIONS],
+[AC_ARG_ENABLE(linker-optimisations,
+ AS_HELP_STRING([--disable-linker-optimisations],
+ [Disable linker optimisations]),
+[if test "x$enable_linker_optimisations" = "xno"; then
+ [LDFLAGS=`echo "$LDFLAGS" | sed -e "s/ -Wl,-O[0-9]*\b//g"`]
+else
+ [LDFLAGS="$LDFLAGS -Wl,-O1"]
+fi], [LDFLAGS="$LDFLAGS -Wl,-O1"])dnl
+])# LINKER_OPTIMISATIONS
+
+# LINKER_VERSION_SCRIPT
+# --------------------------
+# Detect whether the linker supports version scripts
+AC_DEFUN([LINKER_VERSION_SCRIPT],
+[AC_MSG_CHECKING([for linker version script argument])
+for aranha_try_arg in "-Wl,--version-script"; do
+ aranha_old_libs="$LIBS"
+ LIBS="$LIBS $aranha_try_arg=conftest.ver"
+
+ cat >conftest.ver <<EOF
+TEST {
+ global:
+ *;
+};
+EOF
+
+ AC_TRY_LINK([], [], [
+ rm -f conftest.ver
+ LIBS="$aranha_old_libs"
+
+ AC_MSG_RESULT([$aranha_try_arg])
+ AC_SUBST(VERSION_SCRIPT_ARG, [$aranha_try_arg])
+ break
+ ])
+
+ rm -f conftest.ver
+ LIBS="$aranha_old_libs"
+done
+
+AM_CONDITIONAL(HAVE_VERSION_SCRIPT_ARG, [test -n "$VERSION_SCRIPT_ARG"])
+if test -z "$VERSION_SCRIPT_ARG"; then
+ AC_MSG_RESULT([unknown])
+fi
+])dnl
+])# LINKER_VERSION_SCRIPT
diff --git a/yell/m4/tp-compiler-flag.m4 b/yell/m4/tp-compiler-flag.m4
new file mode 100644
index 000000000..fc05e9e17
--- /dev/null
+++ b/yell/m4/tp-compiler-flag.m4
@@ -0,0 +1,36 @@
+dnl A version of AS_COMPILER_FLAG that supports both C and C++.
+dnl Based on:
+
+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 TP_COMPILER_FLAG(CFLAGS, ACTION-IF-ACCEPTED, [ACTION-IF-NOT-ACCEPTED])
+dnl Tries to compile with the given CFLAGS and CXXFLAGS.
+dnl
+dnl Runs ACTION-IF-ACCEPTED if the compiler for the currently selected
+dnl AC_LANG can compile with the flags, and ACTION-IF-NOT-ACCEPTED otherwise.
+
+AC_DEFUN([TP_COMPILER_FLAG],
+[
+ AC_MSG_CHECKING([to see if compiler understands $1])
+
+ save_CFLAGS="$CFLAGS"
+ save_CXXFLAGS="$CXXFLAGS"
+ CFLAGS="$CFLAGS $1"
+ CXXFLAGS="$CXXFLAGS $1"
+
+ AC_TRY_COMPILE([ ], [], [flag_ok=yes], [flag_ok=no])
+ CFLAGS="$save_CFLAGS"
+ CXXFLAGS="$save_CXXFLAGS"
+
+ if test "X$flag_ok" = Xyes ; then
+ $2
+ true
+ else
+ $3
+ true
+ fi
+ AC_MSG_RESULT([$flag_ok])
+])
diff --git a/yell/m4/tp-compiler-warnings.m4 b/yell/m4/tp-compiler-warnings.m4
new file mode 100644
index 000000000..fab5dc898
--- /dev/null
+++ b/yell/m4/tp-compiler-warnings.m4
@@ -0,0 +1,40 @@
+dnl TP_COMPILER_WARNINGS(VARIABLE, WERROR_BY_DEFAULT, DESIRABLE, UNDESIRABLE)
+dnl $1 (VARIABLE): the variable to put flags into
+dnl $2 (WERROR_BY_DEFAULT): a command returning true if -Werror should be the
+dnl default
+dnl $3 (DESIRABLE): warning flags we want (e.g. all extra shadow)
+dnl $4 (UNDESIRABLE): warning flags we don't want (e.g.
+dnl missing-field-initializers unused-parameter)
+AC_DEFUN([TP_COMPILER_WARNINGS],
+[
+ AC_REQUIRE([AC_ARG_ENABLE])dnl
+ AC_REQUIRE([AC_HELP_STRING])dnl
+ AC_REQUIRE([TP_COMPILER_FLAG])dnl
+
+ tp_warnings=""
+ for tp_flag in $3; do
+ TP_COMPILER_FLAG([-W$tp_flag], [tp_warnings="$tp_warnings -W$tp_flag"])
+ done
+
+ tp_error_flags="-Werror"
+ TP_COMPILER_FLAG([-Werror], [tp_werror=yes], [tp_werror=no])
+
+ for tp_flag in $4; do
+ TP_COMPILER_FLAG([-Wno-$tp_flag],
+ [tp_warnings="$tp_warnings -Wno-$tp_flag"])
+ TP_COMPILER_FLAG([-Wno-error=$tp_flag],
+ [tp_error_flags="$tp_error_flags -Wno-error=$tp_flag"], [tp_werror=no])
+ done
+
+ AC_ARG_ENABLE([Werror],
+ AC_HELP_STRING([--disable-Werror],
+ [compile without -Werror (normally enabled in development builds)]),
+ tp_werror=$enableval, :)
+
+ if test "x$tp_werror" = xyes && $2; then
+ $1="$tp_warnings $tp_error_flags"
+ else
+ $1="$tp_warnings"
+ fi
+
+])
diff --git a/yell/spec/Call_Content.xml b/yell/spec/Call_Content.xml
new file mode 100644
index 000000000..270d99b08
--- /dev/null
+++ b/yell/spec/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/yell/spec/Call_Content_Codec_Offer.xml b/yell/spec/Call_Content_Codec_Offer.xml
new file mode 100644
index 000000000..f88143f69
--- /dev/null
+++ b/yell/spec/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/yell/spec/Call_Content_Interface_Audio_Control.xml b/yell/spec/Call_Content_Interface_Audio_Control.xml
new file mode 100644
index 000000000..1229fb51a
--- /dev/null
+++ b/yell/spec/Call_Content_Interface_Audio_Control.xml
@@ -0,0 +1,111 @@
+<!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_Audio_Control"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2011 Collabora Ltd.</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.Call1.Content.Interface.AudioControl"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.25.UNRELEASED">(draft 1)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Call1.Content.Interface.Media"/>
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface allows the connection manager to be kept informed of,
+ and control, the input and output volumes of an audio stream.
+ While generally not needed, if the connection manager needs to
+ handle stream volumes directly (typically when using
+ <tp:value-ref>Call_Content_Packetization_Type_Raw</tp:value-ref>),
+ this interface may be necessary.</p>
+
+ <p>If this interface is present, the handler should call
+ <tp:member-ref>ReportInputVolume</tp:member-ref>
+ and <tp:member-ref>ReportOutputVolume</tp:member-ref> whenever the
+ input and output volume change, both when the user manually modifies
+ the volume and when the volumes are adjusted in response to
+ <tp:member-ref>RequestedInputVolume</tp:member-ref> and
+ <tp:member-ref>RequestedOutputVolume</tp:member-ref> changing.</p>
+
+ <p>The maximum volume as used in this interface represent the unamplified
+ hardware volume (0 dB). No software amplification should be used to
+ boost the signal to a higher level when this Interface is in use</p>
+ </tp:docstring>
+
+ <property name="RequestedInputVolume" tp:type="Audio_Control_Volume"
+ type="i" access="read" tp:name-for-bindings="Requested_Input_Volume">
+ <tp:docstring>
+ The input volume as requested by the Connection Manager.
+ Initially and on any changes the client should change its input volume
+ to match the requested volume.
+ </tp:docstring>
+ </property>
+
+ <method name="ReportInputVolume" tp:name-for-bindings="Report_Input_Volume">
+ <arg direction="in" name="Volume" tp:type="Audio_Control_Volume" type="i">
+ <tp:docstring>
+ Report the input volume level as set by the client.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ <p>Report to the CM that the Content input volume has been
+ changed by the client.</p>
+
+ <p>It is the client's responsibility to change the input volume used for
+ the content. However, the client MUST call this whenever it changes
+ input volume for the content.</p>
+ </tp:docstring>
+ </method>
+
+ <property name="RequestedOutputVolume" tp:type="Audio_Control_Volume"
+ type="i" access="read" tp:name-for-bindings="Requested_Output_Volume">
+ <tp:docstring>
+ The input volume as requested by the Connection Manager.
+ Initially and on any changes the client should change its input volume
+ to match the requested volume.
+ </tp:docstring>
+ </property>
+
+ <method name="ReportOutputVolume"
+ tp:name-for-bindings="Report_Output_Volume">
+ <arg direction="in" name="Volume" tp:type="Audio_Control_Volume" type="i">
+ <tp:docstring>
+ Report the output volume level as set by the client.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ <p>Report to the CM that the content output volume has been
+ changed by the client.</p>
+
+ <p>It is the client's responsibility to change the output volume used
+ for the content. However, the client MUST call this whenever it
+ changes output volume for the content.</p>
+ </tp:docstring>
+ </method>
+
+ <tp:simple-type name="Audio_Control_Volume" type="i">
+ <tp:docstring>
+ <p>A volume value either reported to or requested by the Connection
+ Manager. This value should either be -1 for an unknown value or in the
+ range of 0-255, with 0 being the minimal volume and 255 being the
+ highest unamplified volume the input or output is capable of (known
+ as 0 dB)
+ </p>
+ </tp:docstring>
+ </tp:simple-type>
+ </interface>
+</node>
diff --git a/yell/spec/Call_Content_Interface_Media.xml b/yell/spec/Call_Content_Interface_Media.xml
new file mode 100644
index 000000000..038ce8c7a
--- /dev/null
+++ b/yell/spec/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/yell/spec/Call_Content_Interface_Mute.xml b/yell/spec/Call_Content_Interface_Mute.xml
new file mode 100644
index 000000000..f926e03cd
--- /dev/null
+++ b/yell/spec/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/yell/spec/Call_Content_Interface_Video_Control.xml b/yell/spec/Call_Content_Interface_Video_Control.xml
new file mode 100644
index 000000000..bac125b47
--- /dev/null
+++ b/yell/spec/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.UNRELEASED">(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/yell/spec/Call_Stream.xml b/yell/spec/Call_Stream.xml
new file mode 100644
index 000000000..1d7b28147
--- /dev/null
+++ b/yell/spec/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/yell/spec/Call_Stream_Endpoint.xml b/yell/spec/Call_Stream_Endpoint.xml
new file mode 100644
index 000000000..cf1783d1e
--- /dev/null
+++ b/yell/spec/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(usua{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(usua{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="(usua{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="(usua{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="(usua{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/yell/spec/Call_Stream_Interface_Media.xml b/yell/spec/Call_Stream_Interface_Media.xml
new file mode 100644
index 000000000..54476f0fa
--- /dev/null
+++ b/yell/spec/Call_Stream_Interface_Media.xml
@@ -0,0 +1,473 @@
+<?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:enum type="u" name="Stream_Component">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Media streams can use more than one UDP socket: one for RTP (data)
+ and one for RTCP (control). Most of the time, they are adjacent
+ to each other, but some protocols (xmpp) signal each port separately.
+ </tp:docstring>
+ <tp:enumvalue suffix="Unknown" value="0">
+ <tp:docstring>
+ The stream transport type is unknown or not applicable
+ (should not appear over dbus).
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Data" value="1">
+ <tp:docstring>
+ This is the high-traffic data socket, containing the audio/video
+ data for the stream.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Control" value="2">
+ <tp:docstring>
+ This is the low-traffic control socket, usually containing feedback
+ about packet loss etc.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:struct name="Candidate" array-name="Candidate_List">
+ <tp:docstring>A Stream Candidate.</tp:docstring>
+ <tp:member name="Component" type="u" tp:type="Stream_Component">
+ <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="u">
+ <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(usua{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(usua{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(usua{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/yell/spec/Channel_Type_Call.xml b/yell/spec/Channel_Type_Call.xml
new file mode 100644
index 000000000..eb1a66358
--- /dev/null
+++ b/yell/spec/Channel_Type_Call.xml
@@ -0,0 +1,1425 @@
+<?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-farsight/">telepathy-farsight</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.</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-farsight/">telepathy-farsight</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-farsight to
+ work out how the two participants will connect together.
+ telepathy-farsight 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-farsight 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-farsight will pick up the new content and
+ perform the transport and codec negotiation automatically.
+ telpathy-farsight 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="s" 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 the empty string, 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/yell/spec/Makefile.am b/yell/spec/Makefile.am
new file mode 100644
index 000000000..d23f4714f
--- /dev/null
+++ b/yell/spec/Makefile.am
@@ -0,0 +1,11 @@
+EXTRA_DIST = \
+ Call_Content_Codec_Offer.xml \
+ Call_Content_Interface_Audio_Control.xml \
+ Call_Content_Interface_Media.xml \
+ Call_Content_Interface_Mute.xml \
+ Call_Content_Interface_Video_Control.xml \
+ Call_Content.xml \
+ Call_Stream_Endpoint.xml \
+ Call_Stream_Interface_Media.xml \
+ Call_Stream.xml \
+ Channel_Type_Call.xml
diff --git a/yell/telepathy-yell/Makefile.am b/yell/telepathy-yell/Makefile.am
new file mode 100644
index 000000000..9de58847f
--- /dev/null
+++ b/yell/telepathy-yell/Makefile.am
@@ -0,0 +1,206 @@
+EXTRA_DIST = \
+ call.xml \
+ all.xml
+
+if ENABLE_SHARED_LIBRARY
+lib_LTLIBRARIES = libtelepathy-yell.la
+pkgconfigdir = ${libdir}/pkgconfig
+pkgconfig_DATA = telepathy-yell.pc
+tpyincludedir=$(includedir)/telepathy-1.0/telepathy-yell
+genincludedir=$(tpyincludedir)/_gen
+libtelepathy_yell_la_LDFLAGS = -no-undefined \
+ -export-symbols-regex "^tpy_.*" \
+ -version-info "$(LT_CURRENT)":"$(LT_REVISION)":"$(LT_AGE)"
+else
+noinst_LTLIBRARIES = libtelepathy-yell.la
+endif
+
+libtelepathy_yell_la_LIBADD = $(ALL_LIBS)
+
+libtelepathy_yell_la_SOURCES = \
+ base-call-channel.c \
+ base-call-stream.c \
+ base-call-content.c \
+ base-media-call-content.c \
+ base-media-call-stream.c \
+ call-channel.c \
+ call-content.c \
+ call-stream.c \
+ call-stream-endpoint.c \
+ call-content-codec-offer.c \
+ debug.c \
+ extensions.c \
+ extensions-cli.c \
+ $(headers)
+
+codegen_sources = \
+ _gen/signals-marshal.h \
+ _gen/register-dbus-glib-marshallers-body.h \
+ _gen/gtypes-body.h \
+ _gen/interfaces-body.h \
+ _gen/cli-call-body.h \
+ _gen/signals-marshal.c \
+ _gen/svc-call.c
+
+codegen_headers = \
+ _gen/enums.h \
+ _gen/gtypes.h \
+ _gen/interfaces.h \
+ _gen/cli-call.h \
+ _gen/svc-call.h
+
+headers = \
+ telepathy-yell.h \
+ base-call-channel.h \
+ base-call-stream.h \
+ base-call-content.h \
+ base-media-call-stream.h \
+ base-media-call-content.h \
+ call-channel.h \
+ call-content.h \
+ call-content-codec-offer.h \
+ call-stream.h \
+ call-stream-endpoint.h \
+ debug.h \
+ extensions.h \
+ gtypes.h \
+ enums.h \
+ interfaces.h \
+ svc-call.h \
+ cli-call.h
+
+if ENABLE_SHARED_LIBRARY
+tpyinclude_HEADERS = \
+ $(headers)
+geninclude_HEADERS = \
+ $(codegen_headers)
+endif
+
+nodist_libtelepathy_yell_la_SOURCES = \
+ $(codegen_sources) \
+ $(codegen_headers) \
+ $(headers)
+
+BUILT_SOURCES = \
+ _gen/all.xml \
+ _gen/call.xml \
+ _gen/signals-marshal.list \
+ $(codegen_sources) \
+ $(codegen_headers) \
+ extensions.html
+
+CLEANFILES = $(BUILT_SOURCES)
+
+AM_CFLAGS = \
+ -DG_LOG_DOMAIN=\"tp-yell\" \
+ -I$(top_srcdir) -I$(top_builddir) \
+ $(ERROR_CFLAGS) \
+ $(TP_GLIB_CFLAGS) \
+ $(DBUS_CFLAGS) \
+ $(GLIB_CFLAGS)
+
+ALL_LIBS = \
+ $(DBUS_LIBS) \
+ $(GLIB_LIBS) \
+ $(TP_GLIB_LIBS)
+
+distclean-local:
+ rm -rf _gen
+
+check_c_sources = \
+ $(pkginclude_HEADERS) \
+ $(libtelepathy_yell_la_SOURCES)
+include $(top_srcdir)/tools/check-coding-style.mk
+
+check-local: check-coding-style
+
+### Code generation from here on down
+
+tools_dir = $(top_srcdir)/tools
+
+XSLTPROCFLAGS = --nonet --novalid
+
+# Generated files which can be generated for all categories simultaneously
+
+extensions.html: _gen/all.xml $(tools_dir)/doc-generator.xsl Makefile.am
+ $(AM_V_GEN)$(XSLTPROC) $(XSLTPROCFLAGS) \
+ --param 'allow-undefined-interfaces' 'true()' \
+ $(tools_dir)/doc-generator.xsl \
+ $< > $@
+
+_gen/gtypes.h _gen/gtypes-body.h: _gen/all.xml \
+ $(top_srcdir)/tools/glib-gtypes-generator.py Makefile.am
+ $(AM_V_GEN)$(PYTHON) $(top_srcdir)/tools/glib-gtypes-generator.py \
+ $< _gen/gtypes Tpy
+
+_gen/signals-marshal.list: _gen/all.xml \
+ $(tools_dir)/glib-signals-marshal-gen.py Makefile.am
+ $(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=_tpy_marshal $< > $@
+
+_gen/signals-marshal.c: _gen/signals-marshal.list Makefile.am
+ $(AM_V_GEN){ echo '#include "_gen/signals-marshal.h"' && \
+ $(GLIB_GENMARSHAL) --body --prefix=_tpy_marshal $< ; } > $@
+
+_gen/register-dbus-glib-marshallers-body.h: _gen/all.xml \
+ $(tools_dir)/glib-client-marshaller-gen.py Makefile.am
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-marshaller-gen.py $< \
+ _tpy > $@
+
+_gen/enums.h: _gen/all.xml $(tools_dir)/c-constants-gen.py Makefile.am
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/c-constants-gen.py Tpy $< _gen/enums
+
+_gen/interfaces-body.h _gen/interfaces.h: _gen/all.xml \
+ $(tools_dir)/glib-interfaces-gen.py Makefile.am
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-interfaces-gen.py \
+ Tpy _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 ../spec/*.xml)
+ @$(mkdir_p) _gen
+ $(AM_V_GEN)$(XSLTPROC) $(XSLTPROCFLAGS) --xinclude $(tools_dir)/identity.xsl \
+ $< > $@
+
+_gen/cli-%-body.h _gen/cli-%.h: _gen/%.xml \
+ $(tools_dir)/glib-client-gen.py Makefile.am
+ $(AM_V_GEN)set -e; \
+ case "$*" in \
+ call) \
+ subclass="TpProxy"; \
+ assert="TP_IS_PROXY"; \
+ ;; \
+ esac; \
+ $(PYTHON) $(tools_dir)/glib-client-gen.py \
+ --group=`echo $* | tr x- x_` \
+ --subclass=$$subclass \
+ --subclass-assert=$$assert \
+ --tp-proxy-api=0.7.6 \
+ --iface-quark-prefix=TPY_IFACE_QUARK \
+ $< Tpy_Cli _gen/cli-$*
+
+_gen/svc-%.c _gen/svc-%.h: _gen/%.xml \
+ $(tools_dir)/glib-ginterface-gen.py Makefile.am
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-ginterface-gen.py \
+ --filename=_gen/svc-$* \
+ --signal-marshal-prefix=_tpy \
+ --include='<telepathy-glib/dbus.h>' \
+ --include='"_gen/signals-marshal.h"' \
+ --not-implemented-func='tp_dbus_g_method_return_not_implemented' \
+ --allow-unstable \
+ $< Tpy_Svc_
+
+Android.mk: Makefile.am $(BUILT_SOURCES)
+ androgenizer -:PROJECT telepathy-yell -:SHARED telepathy-yell -:TAGS eng debug \
+ -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \
+ -:SOURCES $(libtelepathy_yell_la_SOURCES) \
+ $(nodist_libtelepathy_yell_la_SOURCES) \
+ -:CFLAGS $(DEFS) $(CFLAGS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CFLAGS) \
+ -:CPPFLAGS $(CPPFLAGS) $(AM_CPPFLAGS) \
+ -:LDFLAGS $(libtelepathy_yell_la_LIBADD) \
+ > $@
diff --git a/yell/telepathy-yell/all.xml b/yell/telepathy-yell/all.xml
new file mode 100644
index 000000000..3ff9708bd
--- /dev/null
+++ b/yell/telepathy-yell/all.xml
@@ -0,0 +1,30 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Yell extensions to the Telepathy spec</tp:title>
+
+<xi:include href="call.xml"/>
+
+<tp:generic-types>
+ <tp:external-type name="Media_Stream_Type" type="u"
+ from="Telepathy specification (Channel.Type.StreamedMedia)"/>
+ <tp:external-type name="Media_Stream_State" type="u"
+ from="Telepathy specification (Channel.Type.StreamedMedia)"/>
+ <tp:external-type name="Contact_Handle" type="u"
+ from="Telepathy specification (Connection)"/>
+ <tp:external-type name="Socket_Address_IP" type="(sq)"
+ from="Telepathy specification (generic)"/>
+ <tp:external-type name="String_Variant_Map" type="a{sv}"
+ from="Telepathy specification (generic)"/>
+ <tp:external-type name="DBus_Error_Name" type="s"
+ from="Telepathy specification (generic)"/>
+ <tp:external-type name="Handle" type="u"
+ from="Telepathy specification (Connection)"/>
+ <tp:external-type name="DBus_Interface" type="s"
+ from="Telepathy specification (generic)"/>
+ <tp:external-type name="String_String_Map" type="a{ss}"
+ from="Telepathy specification (generic)"/>
+</tp:generic-types>
+
+</tp:spec>
diff --git a/yell/telepathy-yell/base-call-channel.c b/yell/telepathy-yell/base-call-channel.c
new file mode 100644
index 000000000..2c5e1df8e
--- /dev/null
+++ b/yell/telepathy-yell/base-call-channel.c
@@ -0,0 +1,953 @@
+/*
+ * base-call-channel.c - Source for TpyBaseCallChannel
+ * Copyright © 2009–2010 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@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
+ */
+
+#include "base-call-channel.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <gio/gio.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/dtmf.h>
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/exportable-channel.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/svc-channel.h>
+#include <telepathy-glib/svc-properties-interface.h>
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/gtypes.h>
+
+#include <telepathy-yell/enums.h>
+#include <telepathy-yell/gtypes.h>
+#include <telepathy-yell/interfaces.h>
+#include <telepathy-yell/svc-call.h>
+#include <telepathy-yell/base-call-content.h>
+
+#define DEBUG_FLAG TPY_DEBUG_CALL
+#include "debug.h"
+
+static void call_iface_init (gpointer, gpointer);
+static void tpy_base_call_channel_close (TpBaseChannel *base);
+static void dtmf_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE(TpyBaseCallChannel, tpy_base_call_channel,
+ TP_TYPE_BASE_CHANNEL,
+ G_IMPLEMENT_INTERFACE (TPY_TYPE_SVC_CHANNEL_TYPE_CALL,
+ call_iface_init)
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_DTMF,
+ dtmf_iface_init);
+);
+
+static const gchar *tpy_base_call_channel_interfaces[] = {
+ NULL
+};
+
+/* properties */
+enum
+{
+ PROP_INITIAL_AUDIO = 1,
+ PROP_INITIAL_VIDEO,
+ PROP_INITIAL_AUDIO_NAME,
+ PROP_INITIAL_VIDEO_NAME,
+ PROP_MUTABLE_CONTENTS,
+ PROP_HARDWARE_STREAMING,
+ PROP_CONTENTS,
+
+ PROP_CALL_STATE,
+ PROP_CALL_FLAGS,
+ PROP_CALL_STATE_DETAILS,
+ PROP_CALL_STATE_REASON,
+
+ PROP_CALL_MEMBERS,
+
+ PROP_CURRENTLY_SENDING_TONES,
+ PROP_INITIAL_TONES,
+ PROP_DEFERRED_TONES,
+
+ LAST_PROPERTY
+};
+
+enum
+{
+ ENDED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+
+/* private structure */
+struct _TpyBaseCallChannelPrivate
+{
+ gboolean dispose_has_run;
+
+ GList *contents;
+
+ gchar *initial_audio_name;
+ gchar *initial_video_name;
+
+ TpyCallState state;
+ TpyCallFlags flags;
+ GHashTable *details;
+ GValueArray *reason;
+
+ TpDTMFPlayer *dtmf_player;
+ gchar *deferred_tones;
+ gboolean have_some_audio;
+
+ /* CallMember handle => flag hash table */
+ GHashTable *call_members;
+};
+
+static void
+tpy_base_call_channel_tones_deferred_cb (TpyBaseCallChannel *self,
+ const gchar *tones,
+ TpDTMFPlayer *dtmf_player)
+{
+ DEBUG ("waiting for user to continue sending '%s'", tones);
+
+ g_free (self->priv->deferred_tones);
+ self->priv->deferred_tones = g_strdup (tones);
+ tp_svc_channel_interface_dtmf_emit_tones_deferred (self, tones);
+}
+
+static void
+tpy_base_call_channel_constructed (GObject *obj)
+{
+ TpyBaseCallChannel *self = TPY_BASE_CALL_CHANNEL (obj);
+ TpBaseChannel *base = TP_BASE_CHANNEL (self);
+
+ if (G_OBJECT_CLASS (tpy_base_call_channel_parent_class)->constructed
+ != NULL)
+ G_OBJECT_CLASS (tpy_base_call_channel_parent_class)->constructed (obj);
+
+ if (tp_base_channel_is_requested (base))
+ tpy_base_call_channel_set_state (self,
+ TPY_CALL_STATE_PENDING_INITIATOR);
+ else
+ tpy_base_call_channel_set_state (self,
+ TPY_CALL_STATE_PENDING_RECEIVER);
+}
+
+static void
+tpy_base_call_channel_init (TpyBaseCallChannel *self)
+{
+ TpyBaseCallChannelPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPY_TYPE_BASE_CALL_CHANNEL, TpyBaseCallChannelPrivate);
+
+ self->priv = priv;
+
+ priv->reason = tp_value_array_build (3,
+ G_TYPE_UINT, 0,
+ G_TYPE_UINT, 0,
+ G_TYPE_STRING, "",
+ G_TYPE_INVALID);
+
+ priv->details = tp_asv_new (NULL, NULL);
+
+ priv->call_members = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ priv->dtmf_player = tp_dtmf_player_new ();
+ priv->have_some_audio = FALSE;
+
+ tp_g_signal_connect_object (priv->dtmf_player, "finished",
+ G_CALLBACK (tp_svc_channel_interface_dtmf_emit_stopped_tones), self,
+ G_CONNECT_SWAPPED);
+
+ tp_g_signal_connect_object (priv->dtmf_player, "tones-deferred",
+ G_CALLBACK (tpy_base_call_channel_tones_deferred_cb), self,
+ G_CONNECT_SWAPPED);
+}
+
+static void tpy_base_call_channel_dispose (GObject *object);
+static void tpy_base_call_channel_finalize (GObject *object);
+
+static void
+tpy_base_call_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TpyBaseCallChannel *self = TPY_BASE_CALL_CHANNEL (object);
+ TpyBaseCallChannelPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_INITIAL_AUDIO:
+ g_value_set_boolean (value, self->initial_audio);
+ break;
+ case PROP_INITIAL_VIDEO:
+ g_value_set_boolean (value, self->initial_video);
+ break;
+ case PROP_INITIAL_AUDIO_NAME:
+ g_value_set_string (value, priv->initial_audio_name);
+ break;
+ case PROP_INITIAL_VIDEO_NAME:
+ g_value_set_string (value, priv->initial_video_name);
+ break;
+ case PROP_MUTABLE_CONTENTS:
+ /* FIXME: this should probably move to the implementation class */
+ g_value_set_boolean (value, TRUE);
+ break;
+ case PROP_CONTENTS:
+ {
+ GPtrArray *arr = g_ptr_array_sized_new (2);
+ GList *l;
+
+ for (l = priv->contents; l != NULL; l = g_list_next (l))
+ {
+ TpyBaseCallContent *c = TPY_BASE_CALL_CONTENT (l->data);
+ g_ptr_array_add (arr,
+ (gpointer) tpy_base_call_content_get_object_path (c));
+ }
+
+ g_value_set_boxed (value, arr);
+ g_ptr_array_unref (arr);
+ break;
+ }
+ case PROP_HARDWARE_STREAMING:
+ g_value_set_boolean (value, FALSE);
+ break;
+ case PROP_CALL_STATE:
+ g_value_set_uint (value, priv->state);
+ break;
+ case PROP_CALL_FLAGS:
+ g_value_set_uint (value, priv->flags);
+ break;
+ case PROP_CALL_STATE_DETAILS:
+ g_value_set_boxed (value, priv->details);
+ break;
+ case PROP_CALL_STATE_REASON:
+ g_value_set_boxed (value, priv->reason);
+ break;
+ case PROP_CALL_MEMBERS:
+ g_value_set_boxed (value, priv->call_members);
+ break;
+ case PROP_CURRENTLY_SENDING_TONES:
+ g_value_set_boolean (value,
+ tp_dtmf_player_is_active (priv->dtmf_player));
+ break;
+ case PROP_INITIAL_TONES:
+ /* FIXME: stub */
+ g_value_set_static_string (value, "");
+ break;
+ case PROP_DEFERRED_TONES:
+ if (priv->deferred_tones != NULL)
+ g_value_set_string (value, priv->deferred_tones);
+ else
+ g_value_set_static_string (value, "");
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tpy_base_call_channel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TpyBaseCallChannel *self = TPY_BASE_CALL_CHANNEL (object);
+ TpyBaseCallChannelPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_INITIAL_AUDIO:
+ self->initial_audio = g_value_get_boolean (value);
+ break;
+ case PROP_INITIAL_VIDEO:
+ self->initial_video = g_value_get_boolean (value);
+ break;
+ case PROP_INITIAL_AUDIO_NAME:
+ priv->initial_audio_name = g_value_dup_string (value);
+ break;
+ case PROP_INITIAL_VIDEO_NAME:
+ priv->initial_video_name = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tpy_base_call_channel_fill_immutable_properties (
+ TpBaseChannel *chan,
+ GHashTable *properties)
+{
+ TP_BASE_CHANNEL_CLASS (tpy_base_call_channel_parent_class)
+ ->fill_immutable_properties (chan, properties);
+
+ tp_dbus_properties_mixin_fill_properties_hash (
+ G_OBJECT (chan), properties,
+ TPY_IFACE_CHANNEL_TYPE_CALL, "InitialAudio",
+ TPY_IFACE_CHANNEL_TYPE_CALL, "InitialAudioName",
+ TPY_IFACE_CHANNEL_TYPE_CALL, "InitialVideo",
+ TPY_IFACE_CHANNEL_TYPE_CALL, "InitialVideoName",
+ TPY_IFACE_CHANNEL_TYPE_CALL, "MutableContents",
+ TPY_IFACE_CHANNEL_TYPE_CALL, "HardwareStreaming",
+ NULL);
+}
+
+static void
+tpy_base_call_channel_class_init (
+ TpyBaseCallChannelClass *tpy_base_call_channel_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (tpy_base_call_channel_class);
+ TpBaseChannelClass *base_channel_class =
+ TP_BASE_CHANNEL_CLASS (tpy_base_call_channel_class);
+ GParamSpec *param_spec;
+ static TpDBusPropertiesMixinPropImpl call_props[] = {
+ { "CallMembers", "call-members", NULL },
+ { "MutableContents", "mutable-contents", NULL },
+ { "InitialAudio", "initial-audio", NULL },
+ { "InitialVideo", "initial-video", NULL },
+ { "InitialAudioName", "initial-audio-name", NULL },
+ { "InitialVideoName", "initial-video-name", NULL },
+ { "Contents", "contents", NULL },
+ { "HardwareStreaming", "hardware-streaming", NULL },
+ { "CallState", "call-state", NULL },
+ { "CallFlags", "call-flags", NULL },
+ { "CallStateReason", "call-state-reason", NULL },
+ { "CallStateDetails", "call-state-details", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinPropImpl dtmf_props[] = {
+ { "CurrentlySendingTones", "currently-sending-tones", NULL },
+ { "InitialTones", "initial-tones", NULL },
+ { "DeferredTones", "deferred-tones", NULL },
+ { NULL }
+ };
+
+ g_type_class_add_private (tpy_base_call_channel_class,
+ sizeof (TpyBaseCallChannelPrivate));
+
+ object_class->constructed = tpy_base_call_channel_constructed;
+
+ object_class->get_property = tpy_base_call_channel_get_property;
+ object_class->set_property = tpy_base_call_channel_set_property;
+
+ object_class->dispose = tpy_base_call_channel_dispose;
+ object_class->finalize = tpy_base_call_channel_finalize;
+
+ base_channel_class->channel_type = TPY_IFACE_CHANNEL_TYPE_CALL;
+ base_channel_class->interfaces = tpy_base_call_channel_interfaces;
+ base_channel_class->fill_immutable_properties =
+ tpy_base_call_channel_fill_immutable_properties;
+ base_channel_class->close = tpy_base_call_channel_close;
+
+ signals[ENDED] = g_signal_new ("ended",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ param_spec = g_param_spec_boolean ("initial-audio", "InitialAudio",
+ "Whether the channel initially contained an audio stream",
+ FALSE,
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIAL_AUDIO,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("initial-video", "InitialVideo",
+ "Whether the channel initially contained an video stream",
+ FALSE,
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIAL_VIDEO,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initial-audio-name", "InitialAudioName",
+ "Name for the initial audio content",
+ "audio",
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIAL_AUDIO_NAME,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initial-video-name", "InitialVideoName",
+ "Name for the initial video content",
+ "video",
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIAL_VIDEO_NAME,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("mutable-contents", "MutableContents",
+ "Whether the set of streams on this channel are mutable once requested",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_MUTABLE_CONTENTS,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("contents", "Contents",
+ "The contents of the channel",
+ TP_ARRAY_TYPE_OBJECT_PATH_LIST,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONTENTS,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("hardware-streaming", "HardwareStreaming",
+ "True if all the streaming is done by hardware",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_HARDWARE_STREAMING,
+ param_spec);
+
+ param_spec = g_param_spec_uint ("call-state", "CallState",
+ "The status of the call",
+ TPY_CALL_STATE_UNKNOWN,
+ NUM_TPY_CALL_STATES,
+ TPY_CALL_STATE_UNKNOWN,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CALL_STATE, param_spec);
+
+ param_spec = g_param_spec_uint ("call-flags", "CallFlags",
+ "Flags representing the status of the call",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CALL_FLAGS,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("call-state-reason", "CallStateReason",
+ "The reason why the call is in the current state",
+ TPY_STRUCT_TYPE_CALL_STATE_REASON,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CALL_STATE_REASON,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("call-state-details", "CallStateDetails",
+ "The reason why the call is in the current state",
+ TP_HASH_TYPE_QUALIFIED_PROPERTY_VALUE_MAP,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CALL_STATE_DETAILS,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("call-members", "CallMembers",
+ "The members",
+ TPY_HASH_TYPE_CALL_MEMBER_MAP,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CALL_MEMBERS,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("currently-sending-tones",
+ "CurrentlySendingTones",
+ "True if a DTMF tone is being sent",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CURRENTLY_SENDING_TONES,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initial-tones", "InitialTones",
+ "Initial DTMF tones to be sent in the first audio stream",
+ "", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIAL_TONES,
+ param_spec);
+
+ param_spec = g_param_spec_string ("deferred-tones", "DeferredTones",
+ "DTMF tones that followed a 'w' or 'W', to be resumed on user request",
+ "", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DEFERRED_TONES,
+ param_spec);
+
+ tp_dbus_properties_mixin_implement_interface (object_class,
+ TPY_IFACE_QUARK_CHANNEL_TYPE_CALL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ call_props);
+
+ tp_dbus_properties_mixin_implement_interface (object_class,
+ TP_IFACE_QUARK_CHANNEL_INTERFACE_DTMF,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ dtmf_props);
+}
+
+void
+tpy_base_call_channel_dispose (GObject *object)
+{
+ TpyBaseCallChannel *self = TPY_BASE_CALL_CHANNEL (object);
+ TpyBaseCallChannelPrivate *priv = self->priv;
+
+ DEBUG ("hello thar");
+
+ if (priv->dispose_has_run)
+ return;
+
+ self->priv->dispose_has_run = TRUE;
+
+ g_list_foreach (priv->contents, (GFunc) tpy_base_call_content_deinit, NULL);
+ g_list_foreach (priv->contents, (GFunc) g_object_unref, NULL);
+ tp_clear_pointer (&priv->contents, g_list_free);
+
+ tp_clear_pointer (&priv->call_members, g_hash_table_unref);
+
+ if (G_OBJECT_CLASS (tpy_base_call_channel_parent_class)->dispose)
+ G_OBJECT_CLASS (tpy_base_call_channel_parent_class)->dispose (object);
+}
+
+void
+tpy_base_call_channel_finalize (GObject *object)
+{
+ TpyBaseCallChannel *self = TPY_BASE_CALL_CHANNEL (object);
+ TpyBaseCallChannelPrivate *priv = self->priv;
+
+ g_hash_table_unref (priv->details);
+ g_value_array_free (priv->reason);
+ g_free (self->priv->initial_audio_name);
+ g_free (self->priv->initial_video_name);
+ tp_clear_pointer (&self->priv->deferred_tones, g_free);
+
+ G_OBJECT_CLASS (tpy_base_call_channel_parent_class)->finalize (object);
+}
+
+void
+tpy_base_call_channel_set_state (TpyBaseCallChannel *self,
+ TpyCallState state)
+{
+ TpyBaseCallChannelPrivate *priv = self->priv;
+
+ /* signal when going to the ended state */
+ if (state != priv->state && state == TPY_CALL_STATE_ENDED)
+ g_signal_emit (self, signals[ENDED], 0);
+
+ priv->state = state;
+
+ if (priv->state != TPY_CALL_STATE_PENDING_RECEIVER)
+ priv->flags &= ~TPY_CALL_FLAG_LOCALLY_RINGING;
+
+ if (tp_base_channel_is_registered (TP_BASE_CHANNEL (self)))
+ tpy_svc_channel_type_call_emit_call_state_changed (self, priv->state,
+ priv->flags, priv->reason, priv->details);
+}
+
+TpyCallState
+tpy_base_call_channel_get_state (TpyBaseCallChannel *self)
+{
+ return self->priv->state;
+}
+
+void
+tpy_base_call_channel_remove_content (TpyBaseCallChannel *self,
+ TpyBaseCallContent *content)
+{
+ TpyBaseCallChannelPrivate *priv = self->priv;
+ const gchar *path;
+ GList *l;
+ gboolean still_have_audio = FALSE;
+
+ priv->contents = g_list_remove (priv->contents, content);
+
+ path = tpy_base_call_content_get_object_path (
+ TPY_BASE_CALL_CONTENT (content));
+ tpy_svc_channel_type_call_emit_content_removed (self, path);
+
+ tpy_base_call_content_deinit (TPY_BASE_CALL_CONTENT (content));
+ g_object_unref (content);
+
+ /* let's see if we still have any audio contents */
+ for (l = priv->contents; l != NULL; l = l->next)
+ {
+ if (tpy_base_call_content_get_media_type (
+ TPY_BASE_CALL_CONTENT (l->data)) ==
+ TP_MEDIA_STREAM_TYPE_AUDIO)
+ {
+ still_have_audio = TRUE;
+ break;
+ }
+ }
+
+ if (priv->have_some_audio && !still_have_audio)
+ {
+ /* the last audio stream just closed */
+ tp_dtmf_player_cancel (priv->dtmf_player);
+ }
+
+ priv->have_some_audio = still_have_audio;
+}
+
+void
+tpy_base_call_channel_add_content (TpyBaseCallChannel *self,
+ TpyBaseCallContent *content)
+{
+ TpyBaseCallChannelPrivate *priv = self->priv;
+
+ g_signal_connect_swapped (content, "removed",
+ G_CALLBACK (tpy_base_call_channel_remove_content), self);
+
+ priv->contents = g_list_prepend (priv->contents, content);
+
+ if (tpy_base_call_content_get_media_type (content)
+ == TP_MEDIA_STREAM_TYPE_AUDIO)
+ priv->have_some_audio = TRUE;
+
+ tpy_svc_channel_type_call_emit_content_added (self,
+ tpy_base_call_content_get_object_path (content));
+}
+
+static void
+tpy_base_call_channel_close (TpBaseChannel *base)
+{
+ TpyBaseCallChannel *self = TPY_BASE_CALL_CHANNEL (base);
+ TpyBaseCallChannelPrivate *priv = self->priv;
+
+ /* FIXME de-init members */
+ DEBUG ("Closing media channel %s", tp_base_channel_get_object_path (base));
+
+ /* shutdown all our contents */
+ g_list_foreach (priv->contents, (GFunc) tpy_base_call_content_deinit,
+ NULL);
+ g_list_foreach (priv->contents, (GFunc) g_object_unref, NULL);
+ tp_clear_pointer (&priv->contents, g_list_free);
+
+ tp_base_channel_destroyed (base);
+}
+
+static void
+tpy_base_call_channel_set_ringing (TpySvcChannelTypeCall *iface,
+ DBusGMethodInvocation *context)
+{
+ TpyBaseCallChannel *self = TPY_BASE_CALL_CHANNEL (iface);
+ TpyBaseCallChannelPrivate *priv = self->priv;
+ TpBaseChannel *tp_base = TP_BASE_CHANNEL (self);
+
+ if (tp_base_channel_is_requested (tp_base))
+ {
+ GError e = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Call was requested. Ringing doesn't make sense." };
+ dbus_g_method_return_error (context, &e);
+ }
+ else if (priv->state != TPY_CALL_STATE_PENDING_RECEIVER)
+ {
+ GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Call is not in the right state for Ringing." };
+ dbus_g_method_return_error (context, &e);
+ }
+ else
+ {
+ if ((priv->flags & TPY_CALL_FLAG_LOCALLY_RINGING) == 0)
+ {
+ DEBUG ("Client is ringing");
+ priv->flags |= TPY_CALL_FLAG_LOCALLY_RINGING;
+ tpy_base_call_channel_set_state (self, priv->state);
+ }
+
+ tpy_svc_channel_type_call_return_from_set_ringing (context);
+ }
+}
+
+static void
+tpy_base_call_channel_accept (TpySvcChannelTypeCall *iface,
+ DBusGMethodInvocation *context)
+{
+ TpyBaseCallChannel *self = TPY_BASE_CALL_CHANNEL (iface);
+ TpyBaseCallChannelPrivate *priv = self->priv;
+ TpyBaseCallChannelClass *base_class =
+ TPY_BASE_CALL_CHANNEL_GET_CLASS (self);
+ TpBaseChannel *tp_base = TP_BASE_CHANNEL (self);
+
+ DEBUG ("Client accepted the call");
+
+ if (tp_base_channel_is_requested (tp_base))
+ {
+ if (priv->state == TPY_CALL_STATE_PENDING_INITIATOR)
+ {
+ tpy_base_call_channel_set_state (self,
+ TPY_CALL_STATE_PENDING_RECEIVER);
+ }
+ else
+ {
+ DEBUG ("Invalid state for Accept: Channel requested and "
+ "state == %d", priv->state);
+ goto err;
+ }
+ }
+ else if (priv->state < TPY_CALL_STATE_ACCEPTED)
+ {
+ tpy_base_call_channel_set_state (self,
+ TPY_CALL_STATE_ACCEPTED);
+ }
+ else
+ {
+ DEBUG ("Invalid state for Accept: state == %d", priv->state);
+ goto err;
+ }
+
+ if (base_class->accept != NULL)
+ base_class->accept (self);
+
+ g_list_foreach (self->priv->contents,
+ (GFunc)tpy_base_call_content_accepted, NULL);
+
+ tpy_svc_channel_type_call_return_from_accept (context);
+ return;
+
+err:
+ {
+ GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Invalid state for Accept" };
+ dbus_g_method_return_error (context, &e);
+ }
+}
+
+static void
+tpy_base_call_channel_hangup (TpySvcChannelTypeCall *iface,
+ guint reason,
+ const gchar *detailed_reason,
+ const gchar *message,
+ DBusGMethodInvocation *context)
+{
+ TpyBaseCallChannel *self = TPY_BASE_CALL_CHANNEL (iface);
+ TpyBaseCallChannelClass *base_class =
+ TPY_BASE_CALL_CHANNEL_GET_CLASS (self);
+
+ if (base_class->hangup)
+ base_class->hangup (self, reason, detailed_reason, message);
+
+ tpy_base_call_channel_set_state ( TPY_BASE_CALL_CHANNEL (self),
+ TPY_CALL_STATE_ENDED);
+
+ tpy_svc_channel_type_call_return_from_hangup (context);
+}
+
+static void
+tpy_base_call_channel_add_content_dbus (TpySvcChannelTypeCall *iface,
+ const gchar *name,
+ TpMediaStreamType mtype,
+ DBusGMethodInvocation *context)
+{
+ TpyBaseCallChannel *self = TPY_BASE_CALL_CHANNEL (iface);
+ TpyBaseCallChannelPrivate *priv = self->priv;
+ TpyBaseCallChannelClass *base_class =
+ TPY_BASE_CALL_CHANNEL_GET_CLASS (self);
+ GError *error = NULL;
+ TpyBaseCallContent *content;
+
+ if (priv->state == TPY_CALL_STATE_ENDED)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "No contents can be added. The call has already ended.");
+ goto error;
+ }
+
+ if (mtype >= NUM_TP_MEDIA_STREAM_TYPES)
+ goto unicorns;
+
+ content = base_class->add_content (self, name, mtype, &error);
+
+ if (content == NULL)
+ goto error;
+
+ tpy_svc_channel_type_call_return_from_add_content (context,
+ tpy_base_call_content_get_object_path (
+ TPY_BASE_CALL_CONTENT (content)));
+ return;
+
+unicorns:
+ {
+ GError e = { TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, "Unknown content type" };
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+
+static void
+call_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpySvcChannelTypeCallClass *klass =
+ (TpySvcChannelTypeCallClass *) g_iface;
+
+#define IMPLEMENT(x, suffix) tpy_svc_channel_type_call_implement_##x (\
+ klass, tpy_base_call_channel_##x##suffix)
+ IMPLEMENT(set_ringing,);
+ IMPLEMENT(accept,);
+ IMPLEMENT(hangup,);
+ IMPLEMENT(add_content, _dbus);
+#undef IMPLEMENT
+}
+
+#define TONE_MS 200
+#define GAP_MS 100
+#define PAUSE_MS 3000
+/* arbitrary limit on the length of a tone started with StartTone */
+#define MAX_TONE_SECONDS 10
+
+static void
+tpy_base_call_channel_start_tone (TpSvcChannelInterfaceDTMF *iface,
+ guint stream_id G_GNUC_UNUSED,
+ guchar event,
+ DBusGMethodInvocation *context)
+{
+ TpyBaseCallChannel *self = TPY_BASE_CALL_CHANNEL (iface);
+ gchar tones[2] = { '\0', '\0' };
+ GError *error = NULL;
+
+ if (!self->priv->have_some_audio)
+ {
+ GError e = { TP_ERROR, TP_ERROR_NOT_AVAILABLE,
+ "There are no audio streams" };
+
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+
+ tones[0] = tp_dtmf_event_to_char (event);
+
+ if (tp_dtmf_player_play (self->priv->dtmf_player,
+ tones, MAX_TONE_SECONDS * 1000, GAP_MS, PAUSE_MS, &error))
+ {
+ tp_clear_pointer (&self->priv->deferred_tones, g_free);
+ tp_svc_channel_interface_dtmf_emit_sending_tones (self, tones);
+ tp_svc_channel_interface_dtmf_return_from_start_tone (context);
+ }
+ else
+ {
+ dbus_g_method_return_error (context, error);
+ g_clear_error (&error);
+ }
+}
+
+static void
+tpy_base_call_channel_stop_tone (TpSvcChannelInterfaceDTMF *iface,
+ guint stream_id,
+ DBusGMethodInvocation *context)
+{
+ TpyBaseCallChannel *self = TPY_BASE_CALL_CHANNEL (iface);
+
+ tp_dtmf_player_cancel (self->priv->dtmf_player);
+ tp_svc_channel_interface_dtmf_return_from_stop_tone (context);
+}
+
+static void
+tpy_base_call_channel_multiple_tones (
+ TpSvcChannelInterfaceDTMF *iface,
+ const gchar *dialstring,
+ DBusGMethodInvocation *context)
+{
+ TpyBaseCallChannel *self = TPY_BASE_CALL_CHANNEL (iface);
+ GError *error = NULL;
+
+ if (!self->priv->have_some_audio)
+ {
+ GError e = { TP_ERROR, TP_ERROR_NOT_AVAILABLE,
+ "There are no audio streams" };
+
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+
+ if (tp_dtmf_player_play (self->priv->dtmf_player,
+ dialstring, TONE_MS, GAP_MS, PAUSE_MS, &error))
+ {
+ tp_clear_pointer (&self->priv->deferred_tones, g_free);
+ tp_svc_channel_interface_dtmf_emit_sending_tones (self, dialstring);
+ tp_svc_channel_interface_dtmf_return_from_start_tone (context);
+ }
+ else
+ {
+ dbus_g_method_return_error (context, error);
+ g_clear_error (&error);
+ }
+}
+
+static void
+dtmf_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpSvcChannelInterfaceDTMFClass *klass = g_iface;
+
+#define IMPLEMENT(x) tp_svc_channel_interface_dtmf_implement_##x (\
+ klass, tpy_base_call_channel_##x)
+ IMPLEMENT(start_tone);
+ IMPLEMENT(stop_tone);
+ IMPLEMENT(multiple_tones);
+#undef IMPLEMENT
+}
+
+static void
+base_call_channel_signal_call_members (TpyBaseCallChannel *self,
+ TpHandle removed_handle)
+{
+ GArray *removals = g_array_new (TRUE, TRUE, sizeof (TpHandle));
+
+ if (removed_handle != 0)
+ g_array_append_val (removals, removed_handle);
+
+ tpy_svc_channel_type_call_emit_call_members_changed (self,
+ self->priv->call_members, removals);
+
+ g_array_unref (removals);
+}
+
+void
+tpy_base_call_channel_add_member (TpyBaseCallChannel *self,
+ TpHandle handle,
+ TpyCallMemberFlags initial_flags)
+{
+ TpyBaseCallChannelPrivate *priv = self->priv;
+
+ DEBUG ("Member %d (flags: %d) added", handle, initial_flags);
+
+ g_assert (!g_hash_table_lookup_extended (priv->call_members,
+ GUINT_TO_POINTER (handle), NULL, NULL));
+
+ g_hash_table_insert (priv->call_members,
+ GUINT_TO_POINTER (handle), GUINT_TO_POINTER (initial_flags));
+ base_call_channel_signal_call_members (self, 0);
+}
+
+void tpy_base_call_channel_update_member_flags (TpyBaseCallChannel *self,
+ TpHandle handle,
+ TpyCallMemberFlags flags)
+{
+ TpyBaseCallChannelPrivate *priv = self->priv;
+
+ DEBUG ("Member %d (flags: %d) updated", handle, flags);
+
+ g_assert (g_hash_table_lookup_extended (priv->call_members,
+ GUINT_TO_POINTER (handle), NULL, NULL));
+
+ g_hash_table_replace (priv->call_members,
+ GUINT_TO_POINTER (handle), GUINT_TO_POINTER (flags));
+ base_call_channel_signal_call_members (self, 0);
+}
+
+void
+tpy_base_call_channel_remove_member (TpyBaseCallChannel *self,
+ TpHandle handle)
+{
+ DEBUG ("Member %d removed", handle);
+
+ g_hash_table_remove (self->priv->call_members, GUINT_TO_POINTER (handle));
+ base_call_channel_signal_call_members (self, handle);
+}
+
+GList *
+tpy_base_call_channel_get_contents (TpyBaseCallChannel *self)
+{
+ return self->priv->contents;
+}
diff --git a/yell/telepathy-yell/base-call-channel.h b/yell/telepathy-yell/base-call-channel.h
new file mode 100644
index 000000000..0bca33b95
--- /dev/null
+++ b/yell/telepathy-yell/base-call-channel.h
@@ -0,0 +1,108 @@
+/*
+ * base-call-channel.h - Header for TpyBaseCallChannel
+ * Copyright © 2009–2010 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@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
+ */
+
+#ifndef __TPY_BASE_CALL_CHANNEL_H__
+#define __TPY_BASE_CALL_CHANNEL_H__
+
+#include <glib-object.h>
+
+#include <telepathy-yell/enums.h>
+#include <telepathy-yell/base-call-content.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpyBaseCallChannel TpyBaseCallChannel;
+typedef struct _TpyBaseCallChannelPrivate TpyBaseCallChannelPrivate;
+typedef struct _TpyBaseCallChannelClass TpyBaseCallChannelClass;
+
+struct _TpyBaseCallChannelClass {
+ TpBaseChannelClass parent_class;
+
+ void (*accept) (TpyBaseCallChannel *self);
+ TpyBaseCallContent * (*add_content) (TpyBaseCallChannel *self,
+ const gchar *name,
+ TpMediaStreamType media,
+ GError **error);
+
+ void (*hangup) (TpyBaseCallChannel *self,
+ guint reason,
+ const gchar *detailed_reason,
+ const gchar *message);
+};
+
+struct _TpyBaseCallChannel {
+ TpBaseChannel parent;
+
+ gboolean initial_audio;
+ gboolean initial_video;
+
+ TpyBaseCallChannelPrivate *priv;
+};
+
+GType tpy_base_call_channel_get_type (void);
+
+/* TYPE MACROS */
+#define TPY_TYPE_BASE_CALL_CHANNEL \
+ (tpy_base_call_channel_get_type ())
+#define TPY_BASE_CALL_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TPY_TYPE_BASE_CALL_CHANNEL, TpyBaseCallChannel))
+#define TPY_BASE_CALL_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ TPY_TYPE_BASE_CALL_CHANNEL, TpyBaseCallChannelClass))
+#define TPY_IS_BASE_CALL_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPY_TYPE_BASE_CALL_CHANNEL))
+#define TPY_IS_BASE_CALL_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TPY_TYPE_BASE_CALL_CHANNEL))
+#define TPY_BASE_CALL_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TPY_TYPE_BASE_CALL_CHANNEL, TpyBaseCallChannelClass))
+
+TpyCallState tpy_base_call_channel_get_state (
+ TpyBaseCallChannel *self);
+
+void tpy_base_call_channel_set_state (TpyBaseCallChannel *self,
+ TpyCallState state);
+
+GList * tpy_base_call_channel_get_contents (TpyBaseCallChannel *self);
+
+void tpy_base_call_channel_add_content (
+ TpyBaseCallChannel *self,
+ TpyBaseCallContent *content);
+
+void tpy_base_call_channel_remove_content (TpyBaseCallChannel *self,
+ TpyBaseCallContent *content);
+
+void tpy_base_call_channel_update_member_flags (TpyBaseCallChannel *self,
+ TpHandle handle,
+ TpyCallMemberFlags flags);
+
+void tpy_base_call_channel_add_member (TpyBaseCallChannel *self,
+ TpHandle handle,
+ TpyCallMemberFlags initial_flags);
+
+void tpy_base_call_channel_remove_member (TpyBaseCallChannel *self,
+ TpHandle handle);
+
+G_END_DECLS
+
+#endif /* #ifndef __TPY_BASE_CALL_CHANNEL_H__*/
diff --git a/yell/telepathy-yell/base-call-content.c b/yell/telepathy-yell/base-call-content.c
new file mode 100644
index 000000000..58e8460db
--- /dev/null
+++ b/yell/telepathy-yell/base-call-content.c
@@ -0,0 +1,500 @@
+/*
+ * base-call-content.c - Source for TpyBaseCallContent
+ * Copyright © 2009–2010 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ * @author Will Thompson <will.thompson@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
+ */
+
+#include "base-call-content.h"
+
+#include "base-call-stream.h"
+
+#define DEBUG_FLAG TPY_DEBUG_CALL
+#include "debug.h"
+
+#include <telepathy-yell/interfaces.h>
+#include <telepathy-yell/gtypes.h>
+#include <telepathy-yell/enums.h>
+#include <telepathy-yell/svc-call.h>
+
+static void call_content_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE(TpyBaseCallContent, tpy_base_call_content,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TPY_TYPE_SVC_CALL_CONTENT,
+ call_content_iface_init);
+ );
+
+struct _TpyBaseCallContentPrivate
+{
+ TpBaseConnection *conn;
+ TpDBusDaemon *dbus_daemon;
+
+ gchar *object_path;
+
+ gchar *name;
+ TpMediaStreamType media_type;
+ TpHandle creator;
+ TpyCallContentDisposition disposition;
+
+ GList *streams;
+
+ gboolean dispose_has_run;
+ gboolean deinit_has_run;
+};
+
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CONNECTION,
+
+ PROP_INTERFACES,
+ PROP_NAME,
+ PROP_MEDIA_TYPE,
+ PROP_CREATOR,
+ PROP_DISPOSITION,
+ PROP_STREAMS
+};
+
+static void base_call_content_deinit_real (TpyBaseCallContent *self);
+
+static void
+tpy_base_call_content_init (TpyBaseCallContent *self)
+{
+ TpyBaseCallContentPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPY_TYPE_BASE_CALL_CONTENT, TpyBaseCallContentPrivate);
+
+ self->priv = priv;
+}
+
+static void
+tpy_base_call_content_constructed (GObject *obj)
+{
+ TpyBaseCallContent *self = TPY_BASE_CALL_CONTENT (obj);
+ TpyBaseCallContentPrivate *priv = self->priv;
+
+ if (G_OBJECT_CLASS (tpy_base_call_content_parent_class)->constructed != NULL)
+ G_OBJECT_CLASS (tpy_base_call_content_parent_class)->constructed (obj);
+
+ DEBUG ("Registering %s", priv->object_path);
+ priv->dbus_daemon = g_object_ref (
+ tp_base_connection_get_dbus_daemon ((TpBaseConnection *) priv->conn));
+ tp_dbus_daemon_register_object (priv->dbus_daemon, priv->object_path, obj);
+}
+
+static void
+tpy_base_call_content_dispose (GObject *object)
+{
+ TpyBaseCallContent *self = TPY_BASE_CALL_CONTENT (object);
+ TpyBaseCallContentPrivate *priv = self->priv;
+ GList *l;
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ for (l = priv->streams; l != NULL; l = g_list_next (l))
+ g_object_unref (l->data);
+
+ tp_clear_pointer (&priv->streams, g_list_free);
+ tp_clear_object (&priv->conn);
+
+ if (G_OBJECT_CLASS (tpy_base_call_content_parent_class)->dispose != NULL)
+ G_OBJECT_CLASS (tpy_base_call_content_parent_class)->dispose (object);
+}
+
+static void
+tpy_base_call_content_finalize (GObject *object)
+{
+ TpyBaseCallContent *self = TPY_BASE_CALL_CONTENT (object);
+ TpyBaseCallContentPrivate *priv = self->priv;
+
+ /* free any data held directly by the object here */
+ g_free (priv->object_path);
+ g_free (priv->name);
+
+ G_OBJECT_CLASS (tpy_base_call_content_parent_class)->finalize (object);
+}
+
+static void
+tpy_base_call_content_get_property (
+ GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TpyBaseCallContent *content = TPY_BASE_CALL_CONTENT (object);
+ TpyBaseCallContentPrivate *priv = content->priv;
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, priv->object_path);
+ break;
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->conn);
+ break;
+ case PROP_INTERFACES:
+ {
+ TpyBaseCallContentClass *klass =
+ TPY_BASE_CALL_CONTENT_GET_CLASS (content);
+
+ if (klass->extra_interfaces != NULL)
+ {
+ g_value_set_boxed (value, klass->extra_interfaces);
+ }
+ else
+ {
+ static gchar *empty[] = { NULL };
+
+ g_value_set_boxed (value, empty);
+ }
+ break;
+ }
+ case PROP_NAME:
+ g_value_set_string (value, priv->name);
+ break;
+ case PROP_MEDIA_TYPE:
+ g_value_set_uint (value, priv->media_type);
+ break;
+ case PROP_CREATOR:
+ g_value_set_uint (value, priv->creator);
+ break;
+ case PROP_DISPOSITION:
+ g_value_set_uint (value, priv->disposition);
+ break;
+ case PROP_STREAMS:
+ {
+ GPtrArray *arr = g_ptr_array_sized_new (2);
+ GList *l;
+
+ for (l = priv->streams; l != NULL; l = g_list_next (l))
+ {
+ TpyBaseCallStream *s = TPY_BASE_CALL_STREAM (l->data);
+ g_ptr_array_add (arr,
+ g_strdup (tpy_base_call_stream_get_object_path (s)));
+ }
+
+ g_value_take_boxed (value, arr);
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tpy_base_call_content_set_property (
+ GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TpyBaseCallContent *content = TPY_BASE_CALL_CONTENT (object);
+ TpyBaseCallContentPrivate *priv = content->priv;
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ priv->object_path = g_value_dup_string (value);
+ g_assert (priv->object_path != NULL);
+ break;
+ case PROP_CONNECTION:
+ priv->conn = g_value_dup_object (value);
+ break;
+ case PROP_NAME:
+ priv->name = g_value_dup_string (value);
+ break;
+ case PROP_MEDIA_TYPE:
+ priv->media_type = g_value_get_uint (value);
+ break;
+ case PROP_CREATOR:
+ priv->creator = g_value_get_uint (value);
+ break;
+ case PROP_DISPOSITION:
+ priv->disposition = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tpy_base_call_content_class_init (
+ TpyBaseCallContentClass *bcc_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (bcc_class);
+ GParamSpec *param_spec;
+ static TpDBusPropertiesMixinPropImpl content_props[] = {
+ { "Interfaces", "interfaces", NULL },
+ { "Name", "name", NULL },
+ { "Type", "media-type", NULL },
+ { "Disposition", "disposition", NULL },
+ { "Streams", "streams", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TPY_IFACE_CALL_CONTENT,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ content_props,
+ },
+ { NULL }
+ };
+
+ g_type_class_add_private (bcc_class, sizeof (TpyBaseCallContentPrivate));
+
+ object_class->constructed = tpy_base_call_content_constructed;
+ object_class->dispose = tpy_base_call_content_dispose;
+ object_class->finalize = tpy_base_call_content_finalize;
+ object_class->get_property = tpy_base_call_content_get_property;
+ object_class->set_property = tpy_base_call_content_set_property;
+
+ param_spec = g_param_spec_string ("object-path", "D-Bus object path",
+ "The D-Bus object path used for this object on the bus.",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec);
+
+ param_spec = g_param_spec_object ("connection", "TpBaseConnection object",
+ "Tp base connection object that owns this call content",
+ TP_TYPE_BASE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Additional interfaces implemented by this content",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+ param_spec = g_param_spec_string ("name", "Name",
+ "The name of this content, if any",
+ "",
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_NAME, param_spec);
+
+ param_spec = g_param_spec_uint ("media-type", "Media Type",
+ "The media type of this content",
+ 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, param_spec);
+
+ param_spec = g_param_spec_uint ("creator", "Creator",
+ "The creator of this content",
+ 0, G_MAXUINT, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CREATOR, param_spec);
+
+ param_spec = g_param_spec_uint ("disposition", "Disposition",
+ "The disposition of this content",
+ 0, G_MAXUINT, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DISPOSITION, param_spec);
+
+ param_spec = g_param_spec_boxed ("streams", "Stream",
+ "The streams of this content",
+ TP_ARRAY_TYPE_OBJECT_PATH_LIST,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STREAMS,
+ param_spec);
+
+ bcc_class->dbus_props_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpyBaseCallContentClass, dbus_props_class));
+
+ bcc_class->deinit = base_call_content_deinit_real;
+}
+
+TpBaseConnection *
+tpy_base_call_content_get_connection (TpyBaseCallContent *self)
+{
+ g_return_val_if_fail (TPY_IS_BASE_CALL_CONTENT (self), NULL);
+
+ return self->priv->conn;
+}
+
+const gchar *
+tpy_base_call_content_get_object_path (TpyBaseCallContent *self)
+{
+ g_return_val_if_fail (TPY_IS_BASE_CALL_CONTENT (self), NULL);
+
+ return self->priv->object_path;
+}
+
+const gchar *
+tpy_base_call_content_get_name (TpyBaseCallContent *self)
+{
+ g_return_val_if_fail (TPY_IS_BASE_CALL_CONTENT (self), NULL);
+
+ return self->priv->name;
+}
+
+TpMediaStreamType
+tpy_base_call_content_get_media_type (TpyBaseCallContent *self)
+{
+ g_return_val_if_fail (TPY_IS_BASE_CALL_CONTENT (self),
+ TP_MEDIA_STREAM_TYPE_AUDIO);
+
+ return self->priv->media_type;
+}
+
+TpyCallContentDisposition
+tpy_base_call_content_get_disposition (TpyBaseCallContent *self)
+{
+ g_return_val_if_fail (TPY_IS_BASE_CALL_CONTENT (self),
+ TPY_CALL_CONTENT_DISPOSITION_NONE);
+
+ return self->priv->disposition;
+}
+
+GList *
+tpy_base_call_content_get_streams (TpyBaseCallContent *self)
+{
+ g_return_val_if_fail (TPY_IS_BASE_CALL_CONTENT (self), NULL);
+
+ return self->priv->streams;
+}
+
+void
+tpy_base_call_content_add_stream (TpyBaseCallContent *self,
+ TpyBaseCallStream *stream)
+{
+ GPtrArray *paths;
+
+ g_return_if_fail (TPY_IS_BASE_CALL_CONTENT (self));
+
+ self->priv->streams = g_list_prepend (self->priv->streams,
+ g_object_ref (stream));
+
+ paths = g_ptr_array_new_with_free_func ((GDestroyNotify) g_free);
+
+ g_ptr_array_add (paths, g_strdup (
+ tpy_base_call_stream_get_object_path (
+ TPY_BASE_CALL_STREAM (stream))));
+ tpy_svc_call_content_emit_streams_added (self, paths);
+ g_ptr_array_unref (paths);
+}
+
+void
+tpy_base_call_content_remove_stream (TpyBaseCallContent *self,
+ TpyBaseCallStream *stream)
+{
+ TpyBaseCallContentPrivate *priv;
+ GList *l;
+ GPtrArray *paths;
+
+ g_return_if_fail (TPY_IS_BASE_CALL_CONTENT (self));
+
+ priv = self->priv;
+
+ l = g_list_find (priv->streams, stream);
+ g_return_if_fail (l != NULL);
+
+ priv->streams = g_list_remove_link (priv->streams, l);
+ paths = g_ptr_array_new_with_free_func ((GDestroyNotify) g_free);
+ g_ptr_array_add (paths, g_strdup (
+ tpy_base_call_stream_get_object_path (
+ TPY_BASE_CALL_STREAM (stream))));
+ tpy_svc_call_content_emit_streams_removed (self, paths);
+ g_ptr_array_unref (paths);
+ g_object_unref (stream);
+}
+
+static void
+base_call_content_deinit_real (TpyBaseCallContent *self)
+{
+ TpyBaseCallContentPrivate *priv = self->priv;
+
+ if (priv->deinit_has_run)
+ return;
+
+ priv->deinit_has_run = TRUE;
+
+ tp_dbus_daemon_unregister_object (priv->dbus_daemon, G_OBJECT (self));
+ tp_clear_object (&priv->dbus_daemon);
+
+ g_list_foreach (priv->streams, (GFunc) g_object_unref, NULL);
+ tp_clear_pointer (&priv->streams, g_list_free);
+}
+
+void
+tpy_base_call_content_deinit (TpyBaseCallContent *self)
+{
+ TpyBaseCallContentClass *klass;
+
+ g_return_if_fail (TPY_IS_BASE_CALL_CONTENT (self));
+
+ klass = TPY_BASE_CALL_CONTENT_GET_CLASS (self);
+ g_return_if_fail (klass->deinit != NULL);
+ klass->deinit (self);
+}
+
+void
+tpy_base_call_content_accepted (TpyBaseCallContent *self)
+{
+ TpyBaseCallContentPrivate *priv = self->priv;
+ GList *l;
+
+ if (priv->disposition != TPY_CALL_CONTENT_DISPOSITION_INITIAL)
+ return;
+
+ for (l = priv->streams ; l != NULL; l = g_list_next (l))
+ {
+ TpyBaseCallStream *s = TPY_BASE_CALL_STREAM (l->data);
+
+ if (tpy_base_call_stream_get_local_sending_state (s) ==
+ TPY_SENDING_STATE_PENDING_SEND)
+ tpy_base_call_stream_set_sending (s, TRUE, NULL);
+ }
+}
+
+static void
+tpy_call_content_remove (TpySvcCallContent *content,
+ TpyContentRemovalReason reason,
+ const gchar *detailed_removal_reason,
+ const gchar *message,
+ DBusGMethodInvocation *context)
+{
+ /* TODO: actually do something with this reason and message. */
+ DEBUG ("removing content for reason %u, dbus error: %s, message: %s",
+ reason, detailed_removal_reason, message);
+
+ tpy_svc_call_content_emit_removed (content);
+ /* it doesn't matter if a ::removed signal handler calls deinit as
+ * there are guards around it being called again and breaking, so
+ * let's just call it be sure it's done. */
+ tpy_base_call_content_deinit (TPY_BASE_CALL_CONTENT (content));
+ tpy_svc_call_content_return_from_remove (context);
+}
+
+static void
+call_content_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpySvcCallContentClass *klass =
+ (TpySvcCallContentClass *) g_iface;
+
+#define IMPLEMENT(x) tpy_svc_call_content_implement_##x (\
+ klass, tpy_call_content_##x)
+ IMPLEMENT(remove);
+#undef IMPLEMENT
+}
diff --git a/yell/telepathy-yell/base-call-content.h b/yell/telepathy-yell/base-call-content.h
new file mode 100644
index 000000000..1f57b6f76
--- /dev/null
+++ b/yell/telepathy-yell/base-call-content.h
@@ -0,0 +1,95 @@
+/*
+ * base-call-content.h - Header for TpyBaseBaseCallContent
+ * Copyright © 2009–2010 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ * @author Will Thompson <will.thompson@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
+ */
+
+#ifndef TPY_BASE_CALL_CONTENT_H
+#define TPY_BASE_CALL_CONTENT_H
+
+#include <glib-object.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "base-call-stream.h"
+
+G_BEGIN_DECLS
+
+typedef struct _TpyBaseCallContent TpyBaseCallContent;
+typedef struct _TpyBaseCallContentPrivate TpyBaseCallContentPrivate;
+typedef struct _TpyBaseCallContentClass TpyBaseCallContentClass;
+
+typedef void (*TpyBaseCallContentFunc) (TpyBaseCallContent *);
+
+struct _TpyBaseCallContentClass {
+ GObjectClass parent_class;
+
+ TpDBusPropertiesMixinClass dbus_props_class;
+
+ const gchar * const *extra_interfaces;
+ TpyBaseCallContentFunc deinit;
+};
+
+struct _TpyBaseCallContent {
+ GObject parent;
+
+ TpyBaseCallContentPrivate *priv;
+};
+
+GType tpy_base_call_content_get_type (void);
+
+/* TYPE MACROS */
+#define TPY_TYPE_BASE_CALL_CONTENT \
+ (tpy_base_call_content_get_type ())
+#define TPY_BASE_CALL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ TPY_TYPE_BASE_CALL_CONTENT, TpyBaseCallContent))
+#define TPY_BASE_CALL_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ TPY_TYPE_BASE_CALL_CONTENT, TpyBaseCallContentClass))
+#define TPY_IS_BASE_CALL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPY_TYPE_BASE_CALL_CONTENT))
+#define TPY_IS_BASE_CALL_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TPY_TYPE_BASE_CALL_CONTENT))
+#define TPY_BASE_CALL_CONTENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TPY_TYPE_BASE_CALL_CONTENT, TpyBaseCallContentClass))
+
+TpBaseConnection *tpy_base_call_content_get_connection (
+ TpyBaseCallContent *self);
+const gchar *tpy_base_call_content_get_object_path (
+ TpyBaseCallContent *self);
+
+const gchar *tpy_base_call_content_get_name (TpyBaseCallContent *self);
+TpMediaStreamType tpy_base_call_content_get_media_type (
+ TpyBaseCallContent *self);
+TpyCallContentDisposition tpy_base_call_content_get_disposition (
+ TpyBaseCallContent *self);
+
+GList *tpy_base_call_content_get_streams (TpyBaseCallContent *self);
+void tpy_base_call_content_add_stream (TpyBaseCallContent *self,
+ TpyBaseCallStream *stream);
+void tpy_base_call_content_remove_stream (TpyBaseCallContent *self,
+ TpyBaseCallStream *stream);
+
+void tpy_base_call_content_accepted (TpyBaseCallContent *self);
+void tpy_base_call_content_deinit (TpyBaseCallContent *self);
+
+G_END_DECLS
+
+#endif /* #ifndef __TPY_BASE_CALL_CONTENT_H__*/
diff --git a/yell/telepathy-yell/base-call-stream.c b/yell/telepathy-yell/base-call-stream.c
new file mode 100644
index 000000000..9215e1bec
--- /dev/null
+++ b/yell/telepathy-yell/base-call-stream.c
@@ -0,0 +1,509 @@
+/*
+ * base-call-stream.c - Source for TpyBaseCallStream
+ * Copyright © 2009–2010 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ * @author Will Thompson <will.thompson@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
+ */
+
+#include "base-call-stream.h"
+
+#define DEBUG_FLAG TPY_DEBUG_CALL
+#include "debug.h"
+
+#include <telepathy-yell/interfaces.h>
+#include <telepathy-yell/gtypes.h>
+#include <telepathy-yell/svc-call.h>
+
+static void call_stream_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE(TpyBaseCallStream, tpy_base_call_stream,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TPY_TYPE_SVC_CALL_STREAM, call_stream_iface_init);
+ )
+
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CONNECTION,
+
+ /* Call interface properties */
+ PROP_INTERFACES,
+ PROP_REMOTE_MEMBERS,
+ PROP_LOCAL_SENDING_STATE,
+ PROP_CAN_REQUEST_RECEIVING,
+};
+
+struct _TpyBaseCallStreamPrivate
+{
+ gboolean dispose_has_run;
+
+ gchar *object_path;
+ TpBaseConnection *conn;
+
+ GHashTable *remote_members;
+
+ TpySendingState local_sending_state;
+};
+
+static void
+tpy_base_call_stream_init (TpyBaseCallStream *self)
+{
+ TpyBaseCallStreamPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPY_TYPE_BASE_CALL_STREAM, TpyBaseCallStreamPrivate);
+
+ self->priv = priv;
+ priv->remote_members = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+static void
+tpy_base_call_stream_constructed (GObject *obj)
+{
+ TpyBaseCallStream *self = TPY_BASE_CALL_STREAM (obj);
+ TpyBaseCallStreamPrivate *priv = self->priv;
+ TpDBusDaemon *bus = tp_base_connection_get_dbus_daemon (
+ (TpBaseConnection *) priv->conn);
+
+ if (G_OBJECT_CLASS (tpy_base_call_stream_parent_class)->constructed
+ != NULL)
+ G_OBJECT_CLASS (tpy_base_call_stream_parent_class)->constructed (obj);
+
+ /* register object on the bus */
+ DEBUG ("Registering %s", priv->object_path);
+ tp_dbus_daemon_register_object (bus, priv->object_path, obj);
+}
+
+static void
+tpy_base_call_stream_dispose (GObject *object)
+{
+ TpyBaseCallStream *self = TPY_BASE_CALL_STREAM (object);
+ TpyBaseCallStreamPrivate *priv = self->priv;
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ tp_clear_object (&priv->conn);
+
+ if (G_OBJECT_CLASS (tpy_base_call_stream_parent_class)->dispose != NULL)
+ G_OBJECT_CLASS (tpy_base_call_stream_parent_class)->dispose (object);
+}
+
+static void
+tpy_base_call_stream_finalize (GObject *object)
+{
+ TpyBaseCallStream *self = TPY_BASE_CALL_STREAM (object);
+ TpyBaseCallStreamPrivate *priv = self->priv;
+
+ /* free any data held directly by the object here */
+ g_free (priv->object_path);
+ g_hash_table_unref (priv->remote_members);
+
+ if (G_OBJECT_CLASS (tpy_base_call_stream_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (tpy_base_call_stream_parent_class)->finalize (object);
+}
+
+static void
+tpy_base_call_stream_get_property (
+ GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TpyBaseCallStream *self = TPY_BASE_CALL_STREAM (object);
+ TpyBaseCallStreamPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->conn);
+ break;
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, priv->object_path);
+ break;
+ case PROP_REMOTE_MEMBERS:
+ g_value_set_boxed (value, priv->remote_members);
+ break;
+ case PROP_LOCAL_SENDING_STATE:
+ g_value_set_uint (value, priv->local_sending_state);
+ break;
+ case PROP_CAN_REQUEST_RECEIVING:
+ {
+ TpyBaseCallStreamClass *klass =
+ TPY_BASE_CALL_STREAM_GET_CLASS (self);
+
+ g_value_set_boolean (value, klass->request_receiving != NULL);
+ break;
+ }
+ case PROP_INTERFACES:
+ {
+ TpyBaseCallStreamClass *klass =
+ TPY_BASE_CALL_STREAM_GET_CLASS (self);
+
+ if (klass->extra_interfaces != NULL)
+ {
+ g_value_set_boxed (value, klass->extra_interfaces);
+ }
+ else
+ {
+ gchar *empty[] = { NULL };
+
+ g_value_set_boxed (value, empty);
+ }
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tpy_base_call_stream_set_property (
+ GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TpyBaseCallStream *self = TPY_BASE_CALL_STREAM (object);
+ TpyBaseCallStreamPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ priv->conn = g_value_dup_object (value);
+ g_assert (priv->conn != NULL);
+ break;
+ case PROP_OBJECT_PATH:
+ g_free (priv->object_path);
+ priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_LOCAL_SENDING_STATE:
+ self->priv->local_sending_state = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tpy_base_call_stream_class_init (TpyBaseCallStreamClass *bsc_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (bsc_class);
+ GParamSpec *param_spec;
+ static TpDBusPropertiesMixinPropImpl stream_props[] = {
+ { "Interfaces", "interfaces", NULL },
+ { "RemoteMembers", "remote-members", NULL },
+ { "LocalSendingState", "local-sending-state", NULL },
+ { "CanRequestReceiving", "can-request-receiving", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TPY_IFACE_CALL_STREAM,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ stream_props,
+ },
+ { NULL }
+ };
+
+ g_type_class_add_private (bsc_class, sizeof (TpyBaseCallStreamPrivate));
+
+ object_class->constructed = tpy_base_call_stream_constructed;
+ object_class->dispose = tpy_base_call_stream_dispose;
+ object_class->finalize = tpy_base_call_stream_finalize;
+ object_class->set_property = tpy_base_call_stream_set_property;
+ object_class->get_property = tpy_base_call_stream_get_property;
+
+ param_spec = g_param_spec_object ("connection", "TpBaseConnection object",
+ "Tpy connection object that owns this call stream",
+ TP_TYPE_BASE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_string ("object-path", "D-Bus object path",
+ "The D-Bus object path used for this "
+ "object on the bus.",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec);
+
+ param_spec = g_param_spec_boxed ("interfaces", "Interfaces",
+ "Stream interfaces",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("remote-members", "Remote members",
+ "Remote member map",
+ TPY_HASH_TYPE_CONTACT_SENDING_STATE_MAP,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REMOTE_MEMBERS,
+ param_spec);
+
+ param_spec = g_param_spec_uint ("local-sending-state", "LocalSendingState",
+ "Local sending state",
+ TPY_SENDING_STATE_NONE, NUM_TPY_SENDING_STATES, TPY_SENDING_STATE_NONE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_LOCAL_SENDING_STATE,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("can-request-receiving",
+ "CanRequestReceiving",
+ "If true, the user can request that a remote contact starts sending on"
+ "this stream.",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CAN_REQUEST_RECEIVING,
+ param_spec);
+
+ bsc_class->dbus_props_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpyBaseCallStreamClass, dbus_props_class));
+}
+
+TpBaseConnection *
+tpy_base_call_stream_get_connection (TpyBaseCallStream *self)
+{
+ g_return_val_if_fail (TPY_IS_BASE_CALL_STREAM (self), NULL);
+
+ return self->priv->conn;
+}
+
+const gchar *
+tpy_base_call_stream_get_object_path (TpyBaseCallStream *self)
+{
+ g_return_val_if_fail (TPY_IS_BASE_CALL_STREAM (self), NULL);
+
+ return self->priv->object_path;
+}
+
+static gboolean
+_remote_member_update_state (TpyBaseCallStream *self,
+ TpHandle contact,
+ TpySendingState state)
+{
+ TpyBaseCallStreamPrivate *priv = self->priv;
+ gpointer state_p = 0;
+ gboolean exists;
+
+ exists = g_hash_table_lookup_extended (priv->remote_members,
+ GUINT_TO_POINTER (contact),
+ NULL,
+ &state_p);
+
+ if (exists && GPOINTER_TO_UINT (state_p) == state)
+ return FALSE;
+
+ DEBUG ("Updating remote member %d state: %d => %d", contact,
+ GPOINTER_TO_UINT (state_p), state);
+
+ g_hash_table_insert (priv->remote_members,
+ GUINT_TO_POINTER (contact),
+ GUINT_TO_POINTER (state));
+
+ return TRUE;
+}
+
+gboolean
+tpy_base_call_stream_update_remote_member_states (TpyBaseCallStream *self,
+ TpHandle peer, TpySendingState remote_state,
+ ...)
+{
+ GHashTable *updates = g_hash_table_new (g_direct_hash, g_direct_equal);
+ gboolean updated = FALSE;
+ va_list args;
+
+ va_start (args, remote_state);
+
+ do
+ {
+ if (_remote_member_update_state (self, peer, remote_state))
+ {
+ g_hash_table_insert (updates,
+ GUINT_TO_POINTER (peer),
+ GUINT_TO_POINTER (remote_state));
+ updated = TRUE;
+ }
+
+ peer = va_arg (args, TpHandle);
+ if (peer != 0)
+ remote_state = va_arg (args, TpySendingState);
+ }
+ while (peer != 0);
+
+ if (updated)
+ {
+ GArray *empty = g_array_new (FALSE, TRUE, sizeof (TpHandle));
+
+ tpy_svc_call_stream_emit_remote_members_changed (self, updates, empty);
+ g_array_unref (empty);
+ }
+
+ g_hash_table_unref (updates);
+ return updated;
+}
+
+gboolean
+tpy_base_call_stream_remove_member (TpyBaseCallStream *self,
+ TpHandle removed)
+{
+ GArray *removed_array;
+ GHashTable *empty;
+
+ if (!g_hash_table_remove (self->priv->remote_members,
+ GUINT_TO_POINTER(removed)))
+ return FALSE;
+
+ empty= g_hash_table_new (g_direct_hash, g_direct_equal);
+ removed_array = g_array_sized_new (FALSE, TRUE, sizeof (TpHandle), 1);
+ g_array_append_val (removed_array, removed);
+
+ tpy_svc_call_stream_emit_remote_members_changed (self, empty, removed_array);
+
+ g_hash_table_unref (empty);
+ g_array_unref (removed_array);
+ return TRUE;
+}
+
+TpySendingState
+tpy_base_call_stream_get_local_sending_state (
+ TpyBaseCallStream *self)
+{
+ return self->priv->local_sending_state;
+}
+
+gboolean
+tpy_base_call_stream_update_local_sending_state (TpyBaseCallStream *self,
+ TpySendingState state)
+{
+ TpyBaseCallStreamPrivate *priv = self->priv;
+
+ if (priv->local_sending_state == state)
+ return FALSE;
+
+ priv->local_sending_state = state;
+ g_object_notify (G_OBJECT (self), "local-sending-state");
+
+ tpy_svc_call_stream_emit_local_sending_state_changed (
+ TPY_SVC_CALL_STREAM (self), state);
+
+ return TRUE;
+}
+
+gboolean
+tpy_base_call_stream_set_sending (TpyBaseCallStream *self,
+ gboolean send,
+ GError **error)
+{
+ TpyBaseCallStreamPrivate *priv = self->priv;
+ TpyBaseCallStreamClass *klass = TPY_BASE_CALL_STREAM_GET_CLASS (self);
+
+ /* Determine if there is a state change for our sending side */
+ switch (priv->local_sending_state)
+ {
+ case TPY_SENDING_STATE_NONE:
+ case TPY_SENDING_STATE_PENDING_SEND:
+ if (!send)
+ goto out;
+ break;
+ case TPY_SENDING_STATE_SENDING:
+ case TPY_SENDING_STATE_PENDING_STOP_SENDING:
+ if (send)
+ goto out;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (klass->set_sending != NULL)
+ {
+ if (!klass->set_sending (self, send, error))
+ goto failed;
+ }
+ else
+ {
+ g_set_error_literal (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "This CM does not implement SetSending");
+ goto failed;
+ }
+
+out:
+ tpy_base_call_stream_update_local_sending_state (self,
+ send ? TPY_SENDING_STATE_SENDING : TPY_SENDING_STATE_NONE);
+ return TRUE;
+
+failed:
+ return FALSE;
+}
+
+static void
+tpy_base_call_stream_set_sending_dbus (TpySvcCallStream *iface,
+ gboolean sending,
+ DBusGMethodInvocation *context)
+{
+ GError *error = NULL;
+
+ if (tpy_base_call_stream_set_sending (TPY_BASE_CALL_STREAM (iface),
+ sending, &error))
+ tpy_svc_call_stream_return_from_set_sending (context);
+ else
+ dbus_g_method_return_error (context, error);
+
+ g_clear_error (&error);
+}
+
+static void
+tpy_base_call_stream_request_receiving (TpySvcCallStream *iface,
+ TpHandle handle,
+ gboolean receiving,
+ DBusGMethodInvocation *context)
+{
+ GError *error = NULL;
+ TpyBaseCallStream *self = TPY_BASE_CALL_STREAM (iface);
+ TpyBaseCallStreamClass *klass = TPY_BASE_CALL_STREAM_GET_CLASS (self);
+
+ if (klass->request_receiving != NULL)
+ klass->request_receiving (self, handle, receiving, &error);
+ else
+ g_set_error_literal (&error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "This CM does not implement request_receiving");
+
+ if (error != NULL)
+ dbus_g_method_return_error (context, error);
+ else
+ tpy_svc_call_stream_return_from_request_receiving (context);
+
+ g_clear_error (&error);
+}
+
+static void
+call_stream_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpySvcCallStreamClass *klass =
+ (TpySvcCallStreamClass *) g_iface;
+
+#define IMPLEMENT(x, suffix) tpy_svc_call_stream_implement_##x (\
+ klass, tpy_base_call_stream_##x##suffix)
+ IMPLEMENT(set_sending, _dbus);
+ IMPLEMENT(request_receiving,);
+#undef IMPLEMENT
+}
diff --git a/yell/telepathy-yell/base-call-stream.h b/yell/telepathy-yell/base-call-stream.h
new file mode 100644
index 000000000..2c99e2f87
--- /dev/null
+++ b/yell/telepathy-yell/base-call-stream.h
@@ -0,0 +1,113 @@
+/*
+ * base-call-stream.h - Header for TpyBaseCallStream
+ * Copyright © 2009–2010 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ * @author Will Thompson <will.thompson@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
+ */
+
+#ifndef TPY_BASE_CALL_STREAM_H
+#define TPY_BASE_CALL_STREAM_H
+
+#include <glib-object.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include <telepathy-yell/enums.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpyBaseCallStream TpyBaseCallStream;
+typedef struct _TpyBaseCallStreamPrivate TpyBaseCallStreamPrivate;
+typedef struct _TpyBaseCallStreamClass TpyBaseCallStreamClass;
+
+typedef gboolean (*TpyStreamSetSendingFunc) (TpyBaseCallStream *,
+ gboolean sending,
+ GError **error);
+typedef void (*TpyStreamRequestReceivingFunc) (TpyBaseCallStream *self,
+ TpHandle handle,
+ gboolean receive,
+ GError **error);
+
+struct _TpyBaseCallStreamClass {
+ GObjectClass parent_class;
+
+ TpDBusPropertiesMixinClass dbus_props_class;
+
+ TpyStreamRequestReceivingFunc request_receiving;
+ TpyStreamSetSendingFunc set_sending;
+
+ const gchar * const *extra_interfaces;
+};
+
+struct _TpyBaseCallStream {
+ GObject parent;
+
+ TpyBaseCallStreamPrivate *priv;
+};
+
+GType tpy_base_call_stream_get_type (void);
+
+/* TYPE MACROS */
+#define TPY_TYPE_BASE_CALL_STREAM \
+ (tpy_base_call_stream_get_type ())
+#define TPY_BASE_CALL_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TPY_TYPE_BASE_CALL_STREAM, TpyBaseCallStream))
+#define TPY_BASE_CALL_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TPY_TYPE_BASE_CALL_STREAM, \
+ TpyBaseCallStreamClass))
+#define TPY_IS_BASE_CALL_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPY_TYPE_BASE_CALL_STREAM))
+#define TPY_IS_BASE_CALL_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TPY_TYPE_BASE_CALL_STREAM))
+#define TPY_BASE_CALL_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TPY_TYPE_BASE_CALL_STREAM, \
+ TpyBaseCallStreamClass))
+
+TpBaseConnection *tpy_base_call_stream_get_connection (
+ TpyBaseCallStream *self);
+const gchar *tpy_base_call_stream_get_object_path (
+ TpyBaseCallStream *self);
+
+TpySendingState tpy_base_call_stream_get_sender_state (
+ TpyBaseCallStream *self,
+ TpHandle sender,
+ gboolean *existed);
+
+gboolean tpy_base_call_stream_update_local_sending_state (
+ TpyBaseCallStream *self,
+ TpySendingState state);
+
+TpySendingState
+tpy_base_call_stream_get_local_sending_state (
+ TpyBaseCallStream *self);
+
+gboolean tpy_base_call_stream_update_remote_member_states (
+ TpyBaseCallStream *self,
+ TpHandle contact,
+ TpySendingState state,
+ ...) G_GNUC_NULL_TERMINATED;
+
+gboolean tpy_base_call_stream_remove_member (
+ TpyBaseCallStream *self,
+ TpHandle removed);
+
+gboolean tpy_base_call_stream_set_sending (TpyBaseCallStream *self,
+ gboolean send, GError **error);
+
+G_END_DECLS
+
+#endif
diff --git a/yell/telepathy-yell/base-media-call-content.c b/yell/telepathy-yell/base-media-call-content.c
new file mode 100644
index 000000000..2ce54f7a6
--- /dev/null
+++ b/yell/telepathy-yell/base-media-call-content.c
@@ -0,0 +1,556 @@
+/*
+ * tpy-base-media-call-content.c - Source for TpyBaseMediaCallContent
+ * Copyright (C) 2009-2010 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@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
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/svc-properties-interface.h>
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/gtypes.h>
+
+#include <telepathy-yell/gtypes.h>
+#include <telepathy-yell/interfaces.h>
+#include <telepathy-yell/svc-call.h>
+
+#include "base-media-call-content.h"
+
+#define DEBUG_FLAG TPY_DEBUG_CALL
+
+#include "debug.h"
+
+static void call_content_media_iface_init (gpointer, gpointer);
+static void call_content_deinit (TpyBaseCallContent *base);
+static void tpy_base_media_call_content_next_offer (
+ TpyBaseMediaCallContent *self);
+
+G_DEFINE_TYPE_WITH_CODE(TpyBaseMediaCallContent, tpy_base_media_call_content,
+ TPY_TYPE_BASE_CALL_CONTENT,
+ G_IMPLEMENT_INTERFACE (TPY_TYPE_SVC_CALL_CONTENT_INTERFACE_MEDIA,
+ call_content_media_iface_init);
+ );
+
+/* properties */
+enum
+{
+ PROP_CONTACT_CODEC_MAP = 1,
+ PROP_PACKETIZATION,
+
+ PROP_CODEC_OFFER,
+};
+
+/* signal enum */
+enum
+{
+ LOCAL_CODECS_UPDATED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* private structure */
+struct _TpyBaseMediaCallContentPrivate
+{
+ gboolean initial_offer_appeared;
+ TpyCallContentCodecOffer *current_offer;
+ GQueue *outstanding_offers;
+ GCancellable *offer_cancellable;
+ guint offer_count;
+
+ gboolean dispose_has_run;
+ gboolean deinit_has_run;
+
+ GHashTable *codec_map;
+
+ GPtrArray *local_codecs;
+};
+
+static void
+_free_codec_array (gpointer codecs)
+{
+ g_boxed_free (TPY_ARRAY_TYPE_CODEC_LIST, codecs);
+}
+
+static void
+tpy_base_media_call_content_init (TpyBaseMediaCallContent *self)
+{
+ TpyBaseMediaCallContentPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPY_TYPE_BASE_MEDIA_CALL_CONTENT, TpyBaseMediaCallContentPrivate);
+
+ self->priv = priv;
+
+ priv->outstanding_offers = g_queue_new ();
+ priv->codec_map = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) _free_codec_array);
+}
+
+static void tpy_base_media_call_content_dispose (GObject *object);
+static void tpy_base_media_call_content_finalize (GObject *object);
+
+static void
+tpy_base_media_call_content_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TpyBaseMediaCallContent *content = TPY_BASE_MEDIA_CALL_CONTENT (object);
+ TpyBaseMediaCallContentPrivate *priv = content->priv;
+
+ switch (property_id)
+ {
+ case PROP_PACKETIZATION:
+ /* TODO: actually set this to its real value */
+ g_value_set_uint (value, TPY_CALL_CONTENT_PACKETIZATION_TYPE_RTP);
+ break;
+ case PROP_CONTACT_CODEC_MAP:
+ g_value_set_boxed (value, priv->codec_map);
+ break;
+ case PROP_CODEC_OFFER:
+ {
+ GValueArray *arr;
+ gchar *path;
+ GPtrArray *codecs;
+ TpHandle contact;
+
+ if (priv->current_offer == NULL)
+ {
+ path = g_strdup ("/");
+ contact = 0;
+ codecs = g_ptr_array_new ();
+ }
+ else
+ {
+ g_object_get (priv->current_offer,
+ "object-path", &path,
+ "remote-contact", &contact,
+ "remote-contact-codecs", &codecs,
+ NULL);
+ }
+
+ arr = tp_value_array_build (3,
+ DBUS_TYPE_G_OBJECT_PATH, path,
+ G_TYPE_UINT, contact,
+ TPY_ARRAY_TYPE_CODEC_LIST, codecs,
+ G_TYPE_INVALID);
+
+ g_value_take_boxed (value, arr);
+ g_free (path);
+ g_boxed_free (TPY_ARRAY_TYPE_CODEC_LIST, codecs);
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tpy_base_media_call_content_class_init (
+ TpyBaseMediaCallContentClass *tpy_base_media_call_content_class)
+{
+ GObjectClass *object_class =
+ G_OBJECT_CLASS (tpy_base_media_call_content_class);
+ TpyBaseCallContentClass *bcc_class =
+ TPY_BASE_CALL_CONTENT_CLASS (tpy_base_media_call_content_class);
+ GParamSpec *param_spec;
+ static TpDBusPropertiesMixinPropImpl content_media_props[] = {
+ { "ContactCodecMap", "contact-codec-map", NULL },
+ { "CodecOffer", "codec-offer", NULL },
+ { "Packetization", "packetization", NULL },
+ { NULL }
+ };
+ static const gchar *interfaces[] = {
+ TPY_IFACE_CALL_CONTENT_INTERFACE_MEDIA,
+ NULL
+ };
+
+ g_type_class_add_private (tpy_base_media_call_content_class,
+ sizeof (TpyBaseMediaCallContentPrivate));
+
+ object_class->get_property = tpy_base_media_call_content_get_property;
+ object_class->dispose = tpy_base_media_call_content_dispose;
+ object_class->finalize = tpy_base_media_call_content_finalize;
+
+ param_spec = g_param_spec_uint ("packetization", "Packetization",
+ "The Packetization of this content",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_PACKETIZATION,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("contact-codec-map", "ContactCodecMap",
+ "The map of contacts to codecs",
+ TPY_HASH_TYPE_CONTACT_CODEC_MAP,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONTACT_CODEC_MAP,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("codec-offer", "CodecOffer",
+ "The current codec offer if any",
+ TPY_STRUCT_TYPE_CODEC_OFFERING,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CODEC_OFFER,
+ param_spec);
+
+ signals[LOCAL_CODECS_UPDATED] = g_signal_new ("local-codecs-updated",
+ G_OBJECT_CLASS_TYPE (tpy_base_media_call_content_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+ tp_dbus_properties_mixin_implement_interface (object_class,
+ TPY_IFACE_QUARK_CALL_CONTENT_INTERFACE_MEDIA,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ content_media_props);
+
+ bcc_class->extra_interfaces = interfaces;
+ bcc_class->deinit = call_content_deinit;
+}
+
+static void
+tpy_base_media_call_content_dispose (GObject *object)
+{
+ TpyBaseMediaCallContent *self = TPY_BASE_MEDIA_CALL_CONTENT (object);
+ TpyBaseMediaCallContentPrivate *priv = self->priv;
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ g_assert (priv->current_offer == NULL);
+
+ g_hash_table_unref (priv->codec_map);
+ priv->local_codecs = NULL;
+ priv->codec_map = NULL;
+
+ if (G_OBJECT_CLASS (tpy_base_media_call_content_parent_class)->dispose)
+ G_OBJECT_CLASS (tpy_base_media_call_content_parent_class)->dispose (object);
+}
+
+static void
+tpy_base_media_call_content_finalize (GObject *object)
+{
+ TpyBaseMediaCallContent *self = TPY_BASE_MEDIA_CALL_CONTENT (object);
+ TpyBaseMediaCallContentPrivate *priv = self->priv;
+ GObjectClass *object_class =
+ G_OBJECT_CLASS (tpy_base_media_call_content_parent_class);
+
+ g_queue_free (priv->outstanding_offers);
+
+ if (object_class->finalize != NULL)
+ object_class->finalize (object);
+}
+
+static gboolean
+tpy_base_media_call_codec_array_equal (const GPtrArray *a, const GPtrArray *b)
+{
+ guint i;
+
+ if (a == b)
+ return TRUE;
+
+ if (a == NULL || b == NULL)
+ return FALSE;
+
+ if (a->len != b->len)
+ return FALSE;
+
+ for (i = 0 ; i < a->len; i++)
+ {
+ GValueArray *va, *vb;
+ GHashTable *ah, *bh;
+ GHashTableIter iter;
+ gpointer a_key, a_value, b_value;
+
+ va = g_ptr_array_index (a, i);
+ vb = g_ptr_array_index (b, i);
+
+ /* id */
+ if (g_value_get_uint (va->values + 0)
+ != g_value_get_uint (vb->values + 0))
+ return FALSE;
+
+ /* name */
+ if (tp_strdiff (g_value_get_string (va->values + 1),
+ g_value_get_string (vb->values + 1)))
+ return FALSE;
+ /* clock-rate */
+ if (g_value_get_uint (va->values + 2)
+ != g_value_get_uint (vb->values + 2))
+ return FALSE;
+
+ /* channels */
+ if (g_value_get_uint (va->values + 3)
+ != g_value_get_uint (vb->values + 3))
+ return FALSE;
+
+ ah = g_value_get_boxed (va->values + 4);
+ bh = g_value_get_boxed (vb->values + 4);
+
+ if (g_hash_table_size (ah) != g_hash_table_size (bh))
+ return FALSE;
+
+ g_hash_table_iter_init (&iter, ah);
+
+ while (g_hash_table_iter_next (&iter, &a_key, &a_value))
+ {
+ if (!g_hash_table_lookup_extended (bh, a_key, NULL, &b_value))
+ return FALSE;
+
+ if (tp_strdiff (a_value, b_value))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+tpy_base_media_call_content_set_local_codecs (TpyBaseMediaCallContent *self,
+ const GPtrArray *codecs)
+{
+ TpyBaseMediaCallContentPrivate *priv = self->priv;
+ TpBaseConnection *conn = tpy_base_call_content_get_connection (
+ TPY_BASE_CALL_CONTENT (self));
+ GPtrArray *c;
+
+ if (tpy_base_media_call_codec_array_equal (priv->local_codecs, codecs))
+ return;
+
+ c = g_boxed_copy (TPY_ARRAY_TYPE_CODEC_LIST, codecs);
+ priv->local_codecs = c;
+ g_hash_table_replace (priv->codec_map, GUINT_TO_POINTER (conn->self_handle),
+ c);
+
+ g_signal_emit (self, signals[LOCAL_CODECS_UPDATED], 0, priv->local_codecs);
+}
+
+static void
+tpy_base_media_call_content_update_codecs (
+ TpySvcCallContentInterfaceMedia *iface,
+ const GPtrArray *codecs,
+ DBusGMethodInvocation *context)
+{
+ TpyBaseMediaCallContent *self = TPY_BASE_MEDIA_CALL_CONTENT (iface);
+
+ if (self->priv->current_offer != NULL)
+ {
+ GError error = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "There is a codec offer around so "
+ "UpdateCodecs shouldn't be called." };
+ dbus_g_method_return_error (context, &error);
+ return;
+ }
+
+ if (!self->priv->initial_offer_appeared)
+ {
+ GError error = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "The initial CodecOffer object has not yet appeared; keep waiting." };
+ dbus_g_method_return_error (context, &error);
+ return;
+ }
+
+ tpy_base_media_call_content_set_local_codecs (self, codecs);
+ tpy_svc_call_content_interface_media_return_from_update_codecs (context);
+}
+
+static void
+call_content_media_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpySvcCallContentInterfaceMediaClass *klass =
+ (TpySvcCallContentInterfaceMediaClass *) g_iface;
+
+#define IMPLEMENT(x) tpy_svc_call_content_interface_media_implement_##x (\
+ klass, tpy_base_media_call_content_##x)
+ IMPLEMENT(update_codecs);
+#undef IMPLEMENT
+}
+
+static gboolean
+maybe_finish_deinit (TpyBaseMediaCallContent *self)
+{
+ TpyBaseMediaCallContentPrivate *priv = self->priv;
+
+ g_assert (priv->deinit_has_run);
+
+ if (priv->offer_count > 0)
+ return FALSE;
+
+ g_object_unref (self);
+ return TRUE;
+}
+
+static void
+call_content_deinit (TpyBaseCallContent *base)
+{
+ TpyBaseMediaCallContent *self = TPY_BASE_MEDIA_CALL_CONTENT (base);
+ TpyBaseMediaCallContentPrivate *priv = self->priv;
+
+ if (priv->deinit_has_run)
+ return;
+
+ priv->deinit_has_run = TRUE;
+
+
+ /* Keep ourself alive until we've finished deinitializing;
+ * maybe_finish_deinit() will drop this reference to ourself.
+ */
+ g_object_ref (base);
+
+ g_queue_foreach (priv->outstanding_offers, (GFunc) g_object_unref, NULL);
+ g_queue_clear (priv->outstanding_offers);
+
+ if (priv->offer_cancellable != NULL)
+ g_cancellable_cancel (priv->offer_cancellable);
+ else
+ maybe_finish_deinit (self);
+
+ TPY_BASE_CALL_CONTENT_CLASS (
+ tpy_base_media_call_content_parent_class)->deinit (base);
+}
+
+static void
+codec_offer_finished_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TpyBaseMediaCallContent *self = TPY_BASE_MEDIA_CALL_CONTENT (user_data);
+ TpyBaseMediaCallContentPrivate *priv = self->priv;
+ TpyCallContentCodecOffer *offer = TPY_CALL_CONTENT_CODEC_OFFER (source);
+ GError *error = NULL;
+ GPtrArray *local_codecs;
+ TpHandle contact;
+ GPtrArray *codecs;
+ GArray *empty;
+
+ local_codecs = tpy_call_content_codec_offer_offer_finish (
+ offer, result, &error);
+
+ if (local_codecs == NULL || priv->deinit_has_run ||
+ priv->current_offer != TPY_CALL_CONTENT_CODEC_OFFER (source))
+ goto out;
+
+ g_object_get (offer,
+ "remote-contact-codecs", &codecs,
+ "remote-contact", &contact,
+ NULL);
+
+ if (codecs->len > 0)
+ g_hash_table_replace (priv->codec_map, GUINT_TO_POINTER (contact), codecs);
+ else
+ _free_codec_array (codecs);
+
+ tpy_base_media_call_content_set_local_codecs (self, local_codecs);
+
+ empty = g_array_new (FALSE, FALSE, sizeof (TpHandle));
+ tpy_svc_call_content_interface_media_emit_codecs_changed (self,
+ priv->codec_map, empty);
+ g_array_unref (empty);
+
+out:
+ if (priv->current_offer == TPY_CALL_CONTENT_CODEC_OFFER (source))
+ {
+ priv->current_offer = NULL;
+ g_object_unref (priv->offer_cancellable);
+ priv->offer_cancellable = NULL;
+ }
+
+ --priv->offer_count;
+ g_object_unref (source);
+ g_clear_error (&error);
+
+ if (priv->deinit_has_run)
+ maybe_finish_deinit (self);
+ else
+ tpy_base_media_call_content_next_offer (self);
+}
+
+static void
+tpy_base_media_call_content_next_offer (TpyBaseMediaCallContent *self)
+{
+ TpyBaseMediaCallContentPrivate *priv = self->priv;
+ TpyCallContentCodecOffer *offer;
+ gchar *path;
+ GPtrArray *codecs;
+ TpHandle handle;
+
+ if (priv->current_offer != NULL)
+ {
+ DEBUG ("Waiting for the current offer to finish"
+ " before starting the next one");
+ return;
+ }
+
+ offer = g_queue_pop_head (priv->outstanding_offers);
+
+ if (offer == NULL)
+ {
+ DEBUG ("No more offers outstanding");
+ return;
+ }
+
+ priv->current_offer = offer;
+
+ g_assert (priv->offer_cancellable == NULL);
+ priv->offer_cancellable = g_cancellable_new ();
+
+ tpy_call_content_codec_offer_offer (priv->current_offer,
+ priv->offer_cancellable,
+ codec_offer_finished_cb, self);
+
+ g_object_get (offer,
+ "object-path", &path,
+ "remote-contact", &handle,
+ "remote-contact-codecs", &codecs,
+ NULL);
+
+ DEBUG ("emitting NewCodecOffer: %s", path);
+ tpy_svc_call_content_interface_media_emit_new_codec_offer (
+ self, handle, path, codecs);
+ g_free (path);
+ g_boxed_free (TPY_ARRAY_TYPE_CODEC_LIST, codecs);
+}
+
+
+void
+tpy_base_media_call_content_add_offer (TpyBaseMediaCallContent *self,
+ TpyCallContentCodecOffer *offer)
+{
+ TpyBaseMediaCallContentPrivate *priv = self->priv;
+
+ ++priv->offer_count;
+ /* set this to TRUE so that after the initial offer disappears,
+ * UpdateCodecs is allowed to be called. */
+ priv->initial_offer_appeared = TRUE;
+
+ g_queue_push_tail (priv->outstanding_offers, offer);
+ tpy_base_media_call_content_next_offer (self);
+}
+
+GPtrArray *
+tpy_base_media_call_content_get_local_codecs (TpyBaseMediaCallContent *self)
+{
+ return self->priv->local_codecs;
+}
diff --git a/yell/telepathy-yell/base-media-call-content.h b/yell/telepathy-yell/base-media-call-content.h
new file mode 100644
index 000000000..35965b53d
--- /dev/null
+++ b/yell/telepathy-yell/base-media-call-content.h
@@ -0,0 +1,73 @@
+/*
+ * gabble-call-content.h - Header for TpyBaseMediaCallContent
+ * Copyright (C) 2009 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@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
+ */
+
+#ifndef __TPY_BASE_MEDIA_CALL_CONTENT_H__
+#define __TPY_BASE_MEDIA_CALL_CONTENT_H__
+
+#include <glib-object.h>
+
+#include <telepathy-yell/call-content-codec-offer.h>
+#include <telepathy-yell/base-call-content.h>
+
+
+G_BEGIN_DECLS
+
+typedef struct _TpyBaseMediaCallContent TpyBaseMediaCallContent;
+typedef struct _TpyBaseMediaCallContentPrivate TpyBaseMediaCallContentPrivate;
+typedef struct _TpyBaseMediaCallContentClass TpyBaseMediaCallContentClass;
+
+struct _TpyBaseMediaCallContentClass {
+ TpyBaseCallContentClass parent_class;
+};
+
+struct _TpyBaseMediaCallContent {
+ TpyBaseCallContent parent;
+
+ TpyBaseMediaCallContentPrivate *priv;
+};
+
+GType tpy_base_media_call_content_get_type (void);
+
+/* TYPE MACROS */
+#define TPY_TYPE_BASE_MEDIA_CALL_CONTENT \
+ (tpy_base_media_call_content_get_type ())
+#define TPY_BASE_MEDIA_CALL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ TPY_TYPE_BASE_MEDIA_CALL_CONTENT, TpyBaseMediaCallContent))
+#define TPY_BASE_MEDIA_CALL_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ TPY_TYPE_BASE_MEDIA_CALL_CONTENT, TpyBaseMediaCallContentClass))
+#define TPY_IS_BASE_MEDIA_CALL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPY_TYPE_BASE_MEDIA_CALL_CONTENT))
+#define TPY_IS_BASE_MEDIA_CALL_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TPY_TYPE_BASE_MEDIA_CALL_CONTENT))
+#define TPY_BASE_MEDIA_CALL_CONTENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TPY_TYPE_BASE_MEDIA_CALL_CONTENT, TpyBaseMediaCallContentClass))
+
+GPtrArray *tpy_base_media_call_content_get_local_codecs (
+ TpyBaseMediaCallContent *self);
+
+void tpy_base_media_call_content_add_offer (TpyBaseMediaCallContent *self,
+ TpyCallContentCodecOffer *offer);
+
+G_END_DECLS
+
+#endif /* #ifndef __TPY_BASE_MEDIA_CALL_CONTENT_H__*/
diff --git a/yell/telepathy-yell/base-media-call-stream.c b/yell/telepathy-yell/base-media-call-stream.c
new file mode 100644
index 000000000..dffa21269
--- /dev/null
+++ b/yell/telepathy-yell/base-media-call-stream.c
@@ -0,0 +1,517 @@
+/*
+ * gabble-call-stream.c - Source for TpyBaseMediaCallStream
+ * Copyright (C) 2009-2011 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ * @author Jonny Lamb <jonny.lamb@collabora.co.uk>
+ * @author David Laban <david.laban@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
+ */
+
+#include "base-media-call-stream.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/svc-properties-interface.h>
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/util.h>
+
+#include <telepathy-yell/enums.h>
+#include <telepathy-yell/gtypes.h>
+#include <telepathy-yell/interfaces.h>
+#include <telepathy-yell/svc-call.h>
+#include <telepathy-yell/call-stream-endpoint.h>
+
+#define DEBUG_FLAG TPY_DEBUG_CALL
+#include "debug.h"
+
+static void call_stream_media_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE(TpyBaseMediaCallStream, tpy_base_media_call_stream,
+ TPY_TYPE_BASE_CALL_STREAM,
+ G_IMPLEMENT_INTERFACE (TPY_TYPE_SVC_CALL_STREAM_INTERFACE_MEDIA,
+ call_stream_media_iface_init);
+ );
+
+/* properties */
+enum
+{
+ PROP_LOCAL_CANDIDATES = 1,
+ PROP_LOCAL_CREDENTIALS,
+ PROP_ENDPOINTS,
+ PROP_TRANSPORT,
+ PROP_STUN_SERVERS,
+ PROP_RELAY_INFO,
+ PROP_HAS_SERVER_INFO,
+};
+
+/* private structure */
+struct _TpyBaseMediaCallStreamPrivate
+{
+ gboolean dispose_has_run;
+
+ GList *endpoints;
+ GPtrArray *local_candidates;
+ GPtrArray *relay_info;
+ GPtrArray *stun_servers;
+ TpyStreamTransportType transport;
+ gchar *username;
+ gchar *password;
+
+ gboolean got_relay_info;
+};
+
+static void
+tpy_base_media_call_stream_init (TpyBaseMediaCallStream *self)
+{
+ TpyBaseMediaCallStreamPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPY_TYPE_BASE_MEDIA_CALL_STREAM, TpyBaseMediaCallStreamPrivate);
+
+ self->priv = priv;
+
+ priv->local_candidates = g_ptr_array_new ();
+ priv->relay_info = g_ptr_array_new ();
+ priv->stun_servers = g_ptr_array_new ();
+
+ priv->username = g_strdup ("");
+ priv->password = g_strdup ("");
+
+}
+
+static void tpy_base_media_call_stream_dispose (GObject *object);
+static void tpy_base_media_call_stream_finalize (GObject *object);
+
+static gboolean
+has_server_info (TpyBaseMediaCallStream *self)
+{
+ /* extend this function when HasServerInfo gains more info to
+ * retrieve than just relay info */
+ return self->priv->got_relay_info;
+}
+
+static void
+tpy_base_media_call_stream_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TpyBaseMediaCallStream *stream = TPY_BASE_MEDIA_CALL_STREAM (object);
+ TpyBaseMediaCallStreamPrivate *priv = stream->priv;
+
+ switch (property_id)
+ {
+ case PROP_LOCAL_CANDIDATES:
+ {
+ g_value_set_boxed (value, stream->priv->local_candidates);
+ break;
+ }
+ case PROP_LOCAL_CREDENTIALS:
+ {
+ g_value_take_boxed (value, tp_value_array_build (2,
+ G_TYPE_STRING, stream->priv->username,
+ G_TYPE_STRING, stream->priv->password,
+ G_TYPE_INVALID));
+ break;
+ }
+ case PROP_ENDPOINTS:
+ {
+ GPtrArray *arr = g_ptr_array_sized_new (1);
+ GList *l;
+
+ for (l = priv->endpoints; l != NULL; l = g_list_next (l))
+ {
+ TpyCallStreamEndpoint *e =
+ TPY_CALL_STREAM_ENDPOINT (l->data);
+ g_ptr_array_add (arr,
+ g_strdup (tpy_call_stream_endpoint_get_object_path (e)));
+ }
+
+ g_value_take_boxed (value, arr);
+ break;
+ }
+ case PROP_TRANSPORT:
+ {
+ g_value_set_uint (value, priv->transport);
+
+ break;
+ }
+ case PROP_STUN_SERVERS:
+ {
+ g_value_set_boxed (value, priv->stun_servers);
+ break;
+ }
+ case PROP_RELAY_INFO:
+ {
+ g_value_set_boxed (value, priv->relay_info);
+ break;
+ }
+ case PROP_HAS_SERVER_INFO:
+ {
+ g_value_set_boolean (value, has_server_info (stream));
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tpy_base_media_call_stream_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TpyBaseMediaCallStream *stream = TPY_BASE_MEDIA_CALL_STREAM (object);
+ TpyBaseMediaCallStreamPrivate *priv = stream->priv;
+
+ switch (property_id)
+ {
+ case PROP_TRANSPORT:
+ priv->transport = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+maybe_emit_server_info_retrieved (TpyBaseMediaCallStream *self)
+{
+ if (has_server_info (self))
+ tpy_svc_call_stream_interface_media_emit_server_info_retrieved (self);
+}
+
+void
+tpy_base_media_call_stream_set_relay_info (
+ TpyBaseMediaCallStream *self,
+ const GPtrArray *relays)
+{
+ TpyBaseMediaCallStreamPrivate *priv = self->priv;
+
+ if (relays != NULL)
+ {
+ g_boxed_free (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, priv->relay_info);
+ priv->relay_info =
+ g_boxed_copy (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, relays);
+ }
+
+ tpy_svc_call_stream_interface_media_emit_relay_info_changed (
+ self, priv->relay_info);
+
+ if (!priv->got_relay_info)
+ {
+ priv->got_relay_info = TRUE;
+ maybe_emit_server_info_retrieved (self);
+ }
+}
+
+void tpy_base_media_call_stream_set_transport (
+ TpyBaseMediaCallStream *self,
+ TpyStreamTransportType transport)
+{
+ self->priv->transport = transport;
+}
+
+void
+tpy_base_media_call_stream_take_endpoint (
+ TpyBaseMediaCallStream *self,
+ TpyCallStreamEndpoint *endpoint)
+{
+ self->priv->endpoints = g_list_append (self->priv->endpoints, endpoint);
+}
+
+GList *
+tpy_base_media_call_stream_get_endpoints (
+ TpyBaseMediaCallStream *self)
+{
+ return self->priv->endpoints;
+}
+
+void
+tpy_base_media_call_stream_set_stun_servers (TpyBaseMediaCallStream *self,
+ const GPtrArray *stun_servers)
+{
+ TpyBaseMediaCallStreamPrivate *priv = self->priv;
+
+ g_return_if_fail (stun_servers != NULL);
+
+ g_boxed_free (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST, priv->stun_servers);
+ priv->stun_servers =
+ g_boxed_copy (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST, stun_servers);
+
+ tpy_svc_call_stream_interface_media_emit_stun_servers_changed (
+ self, stun_servers);
+}
+
+const gchar *tpy_base_media_call_stream_get_username (
+ TpyBaseMediaCallStream *self)
+{
+ return self->priv->username;
+}
+const gchar *tpy_base_media_call_stream_get_password (
+ TpyBaseMediaCallStream *self)
+{
+ return self->priv->password;
+}
+
+static void
+tpy_base_media_call_stream_constructed (GObject *obj)
+{
+ TpyBaseMediaCallStreamClass *klass =
+ TPY_BASE_MEDIA_CALL_STREAM_GET_CLASS (obj);
+ GObjectClass *g_klass = G_OBJECT_CLASS (
+ tpy_base_media_call_stream_parent_class);
+
+ if (g_klass->constructed != NULL)
+ g_klass->constructed (obj);
+
+ g_return_if_fail (klass->add_local_candidates != NULL);
+}
+
+static void
+tpy_base_media_call_stream_class_init (
+ TpyBaseMediaCallStreamClass *tpy_base_media_call_stream_class)
+{
+ GObjectClass *object_class =
+ G_OBJECT_CLASS (tpy_base_media_call_stream_class);
+ GParamSpec *param_spec;
+ TpyBaseCallStreamClass *bcs_class =
+ TPY_BASE_CALL_STREAM_CLASS (tpy_base_media_call_stream_class);
+
+ static TpDBusPropertiesMixinPropImpl stream_media_props[] = {
+ { "Transport", "transport", NULL },
+ { "LocalCandidates", "local-candidates", NULL },
+ { "LocalCredentials", "local-credentials", NULL },
+ { "STUNServers", "stun-servers", NULL },
+ { "RelayInfo", "relay-info", NULL },
+ { "HasServerInfo", "has-server-info", NULL },
+ { "Endpoints", "endpoints", NULL },
+ { NULL }
+ };
+
+ static const gchar *interfaces[] = {
+ TPY_IFACE_CALL_STREAM_INTERFACE_MEDIA,
+ NULL
+ };
+
+ g_type_class_add_private (tpy_base_media_call_stream_class,
+ sizeof (TpyBaseMediaCallStreamPrivate));
+
+ object_class->set_property = tpy_base_media_call_stream_set_property;
+ object_class->get_property = tpy_base_media_call_stream_get_property;
+
+ object_class->dispose = tpy_base_media_call_stream_dispose;
+ object_class->finalize = tpy_base_media_call_stream_finalize;
+ object_class->constructed = tpy_base_media_call_stream_constructed;
+
+ param_spec = g_param_spec_boxed ("local-candidates", "LocalCandidates",
+ "List of local candidates",
+ TPY_ARRAY_TYPE_CANDIDATE_LIST,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_LOCAL_CANDIDATES,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("local-credentials", "LocalCredentials",
+ "ufrag and pwd as defined by ICE",
+ TPY_STRUCT_TYPE_STREAM_CREDENTIALS,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_LOCAL_CREDENTIALS,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("endpoints", "Endpoints",
+ "The endpoints of this content",
+ TP_ARRAY_TYPE_OBJECT_PATH_LIST,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ENDPOINTS,
+ param_spec);
+
+ param_spec = g_param_spec_uint ("transport", "Transport",
+ "The transport type of this stream",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TRANSPORT,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("stun-servers", "STUNServers",
+ "List of STUN servers",
+ TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STUN_SERVERS,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("relay-info", "RelayInfo",
+ "List of relay information",
+ TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_RELAY_INFO,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("has-server-info",
+ "HasServerInfo",
+ "True if the server information about STUN and "
+ "relay servers has been retrieved",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_HAS_SERVER_INFO,
+ param_spec);
+
+ tp_dbus_properties_mixin_implement_interface (object_class,
+ TPY_IFACE_QUARK_CALL_STREAM_INTERFACE_MEDIA,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ stream_media_props);
+
+ bcs_class->extra_interfaces = interfaces;
+}
+
+static void
+tpy_base_media_call_stream_dispose (GObject *object)
+{
+ TpyBaseMediaCallStream *self = TPY_BASE_MEDIA_CALL_STREAM (object);
+ TpyBaseMediaCallStreamPrivate *priv = self->priv;
+ GList *l;
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ for (l = priv->endpoints; l != NULL; l = g_list_next (l))
+ {
+ g_object_unref (l->data);
+ }
+
+ tp_clear_pointer (&priv->endpoints, g_list_free);
+
+ if (G_OBJECT_CLASS (tpy_base_media_call_stream_parent_class)->dispose)
+ G_OBJECT_CLASS (tpy_base_media_call_stream_parent_class)->dispose (object);
+}
+
+static void
+tpy_base_media_call_stream_finalize (GObject *object)
+{
+ TpyBaseMediaCallStream *self = TPY_BASE_MEDIA_CALL_STREAM (object);
+ TpyBaseMediaCallStreamPrivate *priv = self->priv;
+
+ g_boxed_free (TPY_ARRAY_TYPE_CANDIDATE_LIST, priv->local_candidates);
+ g_boxed_free (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, priv->relay_info);
+ g_boxed_free (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST, priv->stun_servers);
+
+ G_OBJECT_CLASS (tpy_base_media_call_stream_parent_class)->finalize (object);
+}
+
+static void
+tpy_base_media_call_stream_add_candidates (
+ TpySvcCallStreamInterfaceMedia *iface,
+ const GPtrArray *candidates,
+ DBusGMethodInvocation *context)
+{
+ TpyBaseMediaCallStream *self = TPY_BASE_MEDIA_CALL_STREAM (iface);
+ TpyBaseMediaCallStreamClass *klass =
+ TPY_BASE_MEDIA_CALL_STREAM_GET_CLASS (self);
+ GPtrArray *accepted_candidates = NULL;
+ guint i;
+ GError *error = NULL;
+
+ if (klass->add_local_candidates != NULL)
+ accepted_candidates = klass->add_local_candidates (self, candidates,
+ &error);
+ else
+ g_set_error_literal (&error, TP_ERRORS, TP_ERROR_CONFUSED,
+ "CM failed to implement the compulsory function add_local_candidates");
+
+ if (accepted_candidates == NULL)
+ goto except;
+
+ for (i = 0; i < accepted_candidates->len; i++)
+ g_ptr_array_add (self->priv->local_candidates,
+ g_ptr_array_index (accepted_candidates, i));
+
+ tpy_svc_call_stream_interface_media_emit_local_candidates_added (self,
+ accepted_candidates);
+
+ tpy_svc_call_stream_interface_media_return_from_add_candidates (context);
+
+ goto finally;
+
+except:
+ dbus_g_method_return_error (context, error);
+ g_clear_error (&error);
+finally:
+ /* Note that we only do a shallow free because we've copied the contents into
+ * local_candidates. */
+ if (accepted_candidates != NULL)
+ g_ptr_array_unref (accepted_candidates);
+}
+
+static void
+tpy_base_media_call_stream_candidates_prepared (
+ TpySvcCallStreamInterfaceMedia *iface,
+ DBusGMethodInvocation *context)
+{
+ TpyBaseMediaCallStream *self = TPY_BASE_MEDIA_CALL_STREAM (iface);
+ TpyBaseMediaCallStreamClass *klass =
+ TPY_BASE_MEDIA_CALL_STREAM_GET_CLASS (self);
+
+ if (klass->local_candidates_prepared != NULL)
+ klass->local_candidates_prepared (self);
+
+ tpy_svc_call_stream_interface_media_return_from_candidates_prepared (
+ context);
+}
+
+static void
+tpy_base_media_call_stream_set_credentials (
+ TpySvcCallStreamInterfaceMedia *iface,
+ const gchar *username,
+ const gchar *password,
+ DBusGMethodInvocation *context)
+{
+ TpyBaseMediaCallStream *self = TPY_BASE_MEDIA_CALL_STREAM (iface);
+
+ g_free (self->priv->username);
+ self->priv->username = g_strdup (username);
+ g_free (self->priv->password);
+ self->priv->password = g_strdup (password);
+
+ g_boxed_free (TPY_ARRAY_TYPE_CANDIDATE_LIST, self->priv->local_candidates);
+ self->priv->local_candidates = g_ptr_array_new ();
+
+ g_object_notify (G_OBJECT (self), "local-candidates");
+ g_object_notify (G_OBJECT (self), "local-credentials");
+ tpy_svc_call_stream_interface_media_emit_local_credentials_changed (self,
+ username, password);
+
+ tpy_svc_call_stream_interface_media_return_from_set_credentials (context);
+}
+
+static void
+call_stream_media_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpySvcCallStreamInterfaceMediaClass *klass =
+ (TpySvcCallStreamInterfaceMediaClass *) g_iface;
+
+#define IMPLEMENT(x) tpy_svc_call_stream_interface_media_implement_##x (\
+ klass, tpy_base_media_call_stream_##x)
+ IMPLEMENT(add_candidates);
+ IMPLEMENT(candidates_prepared);
+ IMPLEMENT(set_credentials);
+#undef IMPLEMENT
+}
diff --git a/yell/telepathy-yell/base-media-call-stream.h b/yell/telepathy-yell/base-media-call-stream.h
new file mode 100644
index 000000000..de2dd2b9f
--- /dev/null
+++ b/yell/telepathy-yell/base-media-call-stream.h
@@ -0,0 +1,95 @@
+/*
+ * gabble-call-stream.h - Header for TpyBaseMediaCallStream
+ * Copyright (C) 2009 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@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
+ */
+
+#ifndef __TPY_BASE_MEDIA_CALL_STREAM_H__
+#define __TPY_BASE_MEDIA_CALL_STREAM_H__
+
+#include <glib-object.h>
+
+#include <telepathy-yell/base-call-stream.h>
+#include <telepathy-yell/call-stream-endpoint.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpyBaseMediaCallStream TpyBaseMediaCallStream;
+typedef struct _TpyBaseMediaCallStreamPrivate TpyBaseMediaCallStreamPrivate;
+typedef struct _TpyBaseMediaCallStreamClass TpyBaseMediaCallStreamClass;
+typedef void (*TpyBaseMediaStreamFunc) (TpyBaseMediaCallStream *self);
+typedef GPtrArray *(*TpyMediaStreamAddCandidatesFunc) (
+ TpyBaseMediaCallStream *self,
+ const GPtrArray *candidates,
+ GError **error);
+
+struct _TpyBaseMediaCallStreamClass {
+ TpyBaseCallStreamClass parent_class;
+
+ TpyMediaStreamAddCandidatesFunc add_local_candidates;
+ TpyBaseMediaStreamFunc local_candidates_prepared;
+};
+
+struct _TpyBaseMediaCallStream {
+ TpyBaseCallStream parent;
+
+ TpyBaseMediaCallStreamPrivate *priv;
+};
+
+GType tpy_base_media_call_stream_get_type (void);
+
+void tpy_base_media_call_stream_set_relay_info (
+ TpyBaseMediaCallStream *self,
+ const GPtrArray *relays);
+void tpy_base_media_call_stream_set_stun_servers (
+ TpyBaseMediaCallStream *self,
+ const GPtrArray *stun_servers);
+void tpy_base_media_call_stream_take_endpoint (
+ TpyBaseMediaCallStream *self,
+ TpyCallStreamEndpoint *endpoint);
+GList *tpy_base_media_call_stream_get_endpoints (
+ TpyBaseMediaCallStream *self);
+void tpy_base_media_call_stream_set_transport (
+ TpyBaseMediaCallStream *self,
+ TpyStreamTransportType transport);
+const gchar *tpy_base_media_call_stream_get_username (
+ TpyBaseMediaCallStream *self);
+const gchar *tpy_base_media_call_stream_get_password (
+ TpyBaseMediaCallStream *self);
+
+
+/* TYPE MACROS */
+#define TPY_TYPE_BASE_MEDIA_CALL_STREAM \
+ (tpy_base_media_call_stream_get_type ())
+#define TPY_BASE_MEDIA_CALL_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TPY_TYPE_BASE_MEDIA_CALL_STREAM, TpyBaseMediaCallStream))
+#define TPY_BASE_MEDIA_CALL_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TPY_TYPE_BASE_MEDIA_CALL_STREAM, \
+ TpyBaseMediaCallStreamClass))
+#define TPY_IS_BASE_MEDIA_CALL_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPY_TYPE_BASE_MEDIA_CALL_STREAM))
+#define TPY_IS_BASE_MEDIA_CALL_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TPY_TYPE_BASE_MEDIA_CALL_STREAM))
+#define TPY_BASE_MEDIA_CALL_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TPY_TYPE_BASE_MEDIA_CALL_STREAM, \
+ TpyBaseMediaCallStreamClass))
+
+
+
+G_END_DECLS
+
+#endif /* #ifndef __TPY_BASE_MEDIA_CALL_STREAM_H__*/
diff --git a/yell/telepathy-yell/call-channel.c b/yell/telepathy-yell/call-channel.c
new file mode 100644
index 000000000..2adb1b975
--- /dev/null
+++ b/yell/telepathy-yell/call-channel.c
@@ -0,0 +1,1132 @@
+/*
+ * call-channel.c - Source for TpyCallChannel
+ * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @author Emilio Pozuelo Monfort <emilio.pozuelo@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
+ */
+
+/**
+ * SECTION:call-channel
+ * @title: TpyCallChannel
+ * @short_description: help class for a call channel
+ *
+ * #TpyCallChannel is a sub-class of #TpChannel providing convenient API
+ * to make audio and video calls.
+ *
+ * Since:
+ */
+
+/**
+ * TpyCallChannel:
+ *
+ * Data structure representing a #TpyCallChannel.
+ *
+ * Since:
+ */
+
+/**
+ * TpyCallChannelClass:
+ *
+ * The class of a #TpyCallChannel.
+ *
+ * Since:
+ */
+
+#include <config.h>
+
+#include "telepathy-yell/call-channel.h"
+#include "telepathy-yell/call-stream.h"
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/proxy-subclass.h>
+#include <telepathy-glib/util.h>
+
+#include "extensions.h"
+#include "interfaces.h"
+#include "_gen/signals-marshal.h"
+
+#define DEBUG_FLAG TPY_DEBUG_CALL
+#include "debug.h"
+
+G_DEFINE_TYPE (TpyCallChannel, tpy_call_channel, TP_TYPE_CHANNEL)
+
+struct _TpyCallChannelPrivate
+{
+ TpyCallState state;
+ TpyCallFlags flags;
+ GHashTable *details;
+
+ gboolean initial_audio;
+ gboolean initial_video;
+
+ /* Hash of Handle => CallMemberFlags */
+ GHashTable *members;
+
+ /* Array of TpyCallContents */
+ GPtrArray *contents;
+
+ gboolean properties_retrieved;
+ gboolean ready;
+};
+
+enum /* props */
+{
+ PROP_CONTENTS = 1,
+ PROP_STATE,
+ PROP_STATE_DETAILS,
+ PROP_STATE_REASON,
+ PROP_FLAGS,
+ PROP_HARDWARE_STREAMING,
+ PROP_MEMBERS,
+ PROP_INITIAL_TRANSPORT,
+ PROP_INITIAL_AUDIO,
+ PROP_INITIAL_AUDIO_NAME,
+ PROP_INITIAL_VIDEO,
+ PROP_INITIAL_VIDEO_NAME,
+ PROP_MUTABLE_CONTENTS,
+ PROP_READY
+};
+
+enum /* signals */
+{
+ CONTENT_ADDED,
+ CONTENT_REMOVED,
+ STATE_CHANGED,
+ MEMBERS_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint _signals[LAST_SIGNAL] = { 0, };
+
+static void
+update_call_members (TpyCallChannel *self,
+ GHashTable *flags_changed,
+ const GArray *removed)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+ TpHandle handle;
+ guint i;
+
+ if (flags_changed != NULL)
+ {
+ g_hash_table_iter_init (&iter, flags_changed);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ g_hash_table_insert (self->priv->members, key, value);
+ }
+
+ if (removed != NULL)
+ {
+ for (i = 0; i < removed->len; i++)
+ {
+ handle = g_array_index (removed, TpHandle, i);
+ g_hash_table_remove (self->priv->members, GUINT_TO_POINTER (handle));
+ }
+ }
+}
+
+static void
+maybe_go_to_ready (TpyCallChannel *self)
+{
+ TpyCallChannelPrivate *priv = self->priv;
+ guint i;
+
+ if (priv->ready)
+ return;
+
+ if (!priv->properties_retrieved)
+ return;
+
+ for (i = 0 ; i < priv->contents->len; i++)
+ {
+ TpyCallContent *c = g_ptr_array_index (self->priv->contents, i);
+ gboolean ready;
+
+ g_object_get (c, "ready", &ready, NULL);
+
+ if (!ready)
+ return;
+ }
+
+ priv->ready = TRUE;
+ g_object_notify (G_OBJECT (self), "ready");
+}
+
+static void
+on_content_ready_cb (TpyCallContent *content,
+ GParamSpec *spec,
+ TpyCallChannel *self)
+{
+ maybe_go_to_ready (self);
+}
+
+static void
+on_content_added_cb (TpProxy *proxy,
+ const gchar *content_path,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpyCallChannel *self = TPY_CALL_CHANNEL (proxy);
+ TpyCallContent *content;
+
+ DEBUG ("Content added: %s", content_path);
+
+ content = g_object_new (TPY_TYPE_CALL_CONTENT,
+ "bus-name", tp_proxy_get_bus_name (self),
+ "dbus-daemon", tp_proxy_get_dbus_daemon (self),
+ "dbus-connection", tp_proxy_get_dbus_connection (self),
+ "object-path", content_path,
+ NULL);
+
+ if (content == NULL)
+ {
+ g_warning ("Could not create a CallContent for path %s", content_path);
+ return;
+ }
+
+ g_ptr_array_add (self->priv->contents, content);
+ tp_g_signal_connect_object (content, "notify::ready",
+ G_CALLBACK (on_content_ready_cb), self, 0);
+
+ g_signal_emit (self, _signals[CONTENT_ADDED], 0, content);
+}
+
+static void
+on_content_removed_cb (TpProxy *proxy,
+ const gchar *content_path,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpyCallChannel *self = TPY_CALL_CHANNEL (proxy);
+ TpyCallContent *content = NULL, *c;
+ guint i;
+
+ DEBUG ("Content removed: %s", content_path);
+
+ for (i = 0; i < self->priv->contents->len; i++)
+ {
+ c = g_ptr_array_index (self->priv->contents, i);
+ if (g_strcmp0 (tp_proxy_get_object_path (c), content_path) == 0)
+ {
+ content = c;
+ break;
+ }
+ }
+
+ if (content != NULL)
+ {
+ g_signal_emit (self, _signals[CONTENT_REMOVED], 0, content);
+ g_ptr_array_remove (self->priv->contents, content);
+ }
+ else
+ {
+ g_warning ("The removed content '%s' isn't in the call!", content_path);
+ }
+}
+
+static void
+on_call_members_changed_cb (TpProxy *proxy,
+ GHashTable *flags_changed,
+ const GArray *removed,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpyCallChannel *self = TPY_CALL_CHANNEL (proxy);
+
+ DEBUG ("Call members: %d changed, %d removed",
+ g_hash_table_size (flags_changed),
+ removed->len);
+
+ update_call_members (self, flags_changed, removed);
+
+ g_signal_emit (self, _signals[MEMBERS_CHANGED], 0, self->priv->members);
+}
+
+static void
+on_call_state_changed_cb (TpProxy *proxy,
+ guint call_state,
+ guint call_flags,
+ const GValueArray *call_state_reason,
+ GHashTable *call_state_details,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpyCallChannel *self = TPY_CALL_CHANNEL (proxy);
+
+ DEBUG ("Call state changed");
+
+ self->priv->state = call_state;
+ self->priv->flags = call_flags;
+
+ tp_clear_pointer (&self->priv->details, g_hash_table_unref);
+ self->priv->details = g_hash_table_ref (call_state_details);
+
+ g_signal_emit (self, _signals[STATE_CHANGED], 0,
+ call_state, call_flags, call_state_reason, call_state_details);
+}
+
+static void
+on_call_channel_get_all_properties_cb (TpProxy *proxy,
+ GHashTable *properties,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpyCallChannel *self = TPY_CALL_CHANNEL (proxy);
+ GSimpleAsyncResult *result = user_data;
+ GHashTable *hash_table;
+ GPtrArray *contents;
+ guint i;
+
+ if (error != NULL)
+ {
+ g_warning ("Could not get the channel properties: %s", error->message);
+ g_simple_async_result_set_from_error (result, error);
+ goto out;
+ }
+
+ self->priv->state = tp_asv_get_uint32 (properties,
+ "CallState", NULL);
+ self->priv->flags = tp_asv_get_uint32 (properties,
+ "CallFlags", NULL);
+ self->priv->initial_audio = tp_asv_get_boolean (properties,
+ "InitialAudio", NULL);
+ self->priv->initial_video = tp_asv_get_boolean (properties,
+ "InitialVideo", NULL);
+
+ hash_table = tp_asv_get_boxed (properties,
+ "CallStateDetails", TP_HASH_TYPE_STRING_VARIANT_MAP);
+ if (hash_table != NULL)
+ self->priv->details = g_boxed_copy (TP_HASH_TYPE_STRING_VARIANT_MAP,
+ hash_table);
+
+ hash_table = tp_asv_get_boxed (properties,
+ "CallMembers", TPY_HASH_TYPE_CALL_MEMBER_MAP);
+ update_call_members (self, hash_table, NULL);
+
+ contents = tp_asv_get_boxed (properties,
+ "Contents", TP_ARRAY_TYPE_OBJECT_PATH_LIST);
+
+ for (i = 0; i < contents->len; i++)
+ {
+ const gchar *content_path = g_ptr_array_index (contents, i);
+ TpyCallContent *content;
+
+ DEBUG ("Content added: %s", content_path);
+
+ content = g_object_new (TPY_TYPE_CALL_CONTENT,
+ "bus-name", tp_proxy_get_bus_name (self),
+ "dbus-daemon", tp_proxy_get_dbus_daemon (self),
+ "dbus-connection", tp_proxy_get_dbus_connection (self),
+ "object-path", content_path,
+ NULL);
+
+ if (content == NULL)
+ {
+ g_warning ("Could not create a CallContent for path %s", content_path);
+
+ g_simple_async_result_set_error (result, TP_ERRORS, TP_ERROR_CONFUSED,
+ "Could not create a CallContent for path %s", content_path);
+ goto out;
+ }
+
+ g_ptr_array_add (self->priv->contents, content);
+
+ tp_g_signal_connect_object (content, "notify::ready",
+ G_CALLBACK (on_content_ready_cb), self, 0);
+ }
+
+ g_signal_emit (self, _signals[MEMBERS_CHANGED], 0, self->priv->members);
+
+ self->priv->properties_retrieved = TRUE;
+
+ maybe_go_to_ready (self);
+
+out:
+ /* TODO; ideally we should get rid of the ready property and complete once
+ * all the contents have been prepared. Or maybe that should be another
+ * feature? */
+ g_simple_async_result_complete (result);
+}
+
+static void
+tpy_call_channel_dispose (GObject *obj)
+{
+ TpyCallChannel *self = (TpyCallChannel *) obj;
+
+ tp_clear_pointer (&self->priv->contents, g_ptr_array_unref);
+ tp_clear_pointer (&self->priv->details, g_hash_table_unref);
+ tp_clear_pointer (&self->priv->members, g_hash_table_unref);
+
+ G_OBJECT_CLASS (tpy_call_channel_parent_class)->dispose (obj);
+}
+
+static void
+tpy_call_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TpyCallChannel *self = (TpyCallChannel *) object;
+
+ switch (property_id)
+ {
+ case PROP_CONTENTS:
+ g_value_set_boxed (value, self->priv->contents);
+ break;
+
+ case PROP_STATE:
+ g_value_set_uint (value, self->priv->state);
+ break;
+
+ case PROP_FLAGS:
+ g_value_set_uint (value, self->priv->flags);
+ break;
+
+ case PROP_STATE_DETAILS:
+ g_value_set_boxed (value, self->priv->details);
+ break;
+
+ case PROP_MEMBERS:
+ g_value_set_boxed (value, self->priv->members);
+ break;
+
+ case PROP_INITIAL_AUDIO:
+ g_value_set_boolean (value, self->priv->initial_audio);
+ break;
+
+ case PROP_INITIAL_VIDEO:
+ g_value_set_boolean (value, self->priv->initial_video);
+ break;
+
+ case PROP_READY:
+ g_value_set_boolean (value, self->priv->ready);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tpy_call_channel_prepare_core_async (TpProxy *proxy,
+ const TpProxyFeature *feature,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TpyCallChannel *self = (TpyCallChannel *) proxy;
+ GSimpleAsyncResult *result;
+ GError *err = NULL;
+
+ result = g_simple_async_result_new ((GObject *) proxy, callback, user_data,
+ tpy_call_channel_prepare_core_async);
+
+ tpy_cli_channel_type_call_connect_to_content_added (TP_PROXY (self),
+ on_content_added_cb, NULL, NULL, NULL, &err);
+
+ if (err != NULL)
+ {
+ g_critical ("Failed to connect to ContentAdded signal: %s",
+ err->message);
+
+ goto failed;
+ }
+
+ tpy_cli_channel_type_call_connect_to_content_removed (TP_PROXY (self),
+ on_content_removed_cb, NULL, NULL, NULL, &err);
+
+ if (err != NULL)
+ {
+ g_critical ("Failed to connect to ContentRemoved signal: %s",
+ err->message);
+
+ goto failed;
+ }
+
+ tpy_cli_channel_type_call_connect_to_call_state_changed (TP_PROXY (self),
+ on_call_state_changed_cb, NULL, NULL, NULL, &err);
+
+ if (err != NULL)
+ {
+ g_critical ("Failed to connect to CallStateChanged signal: %s",
+ err->message);
+
+ goto failed;
+ }
+
+ tpy_cli_channel_type_call_connect_to_call_members_changed (TP_PROXY (self),
+ on_call_members_changed_cb, NULL, NULL, NULL, &err);
+
+ if (err != NULL)
+ {
+ g_critical ("Failed to connect to CallMembersChanged signal: %s",
+ err->message);
+
+ goto failed;
+ }
+
+ tp_cli_dbus_properties_call_get_all (self, -1,
+ TPY_IFACE_CHANNEL_TYPE_CALL,
+ on_call_channel_get_all_properties_cb, result, g_object_unref, NULL);
+
+ return;
+
+failed:
+ g_simple_async_result_take_error (result, err);
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+}
+
+enum {
+ FEAT_CORE,
+ N_FEAT
+};
+
+static const TpProxyFeature *
+tpy_call_channel_list_features (TpProxyClass *cls G_GNUC_UNUSED)
+{
+ static TpProxyFeature features[N_FEAT + 1] = { { 0 } };
+
+ if (G_LIKELY (features[0].name != 0))
+ return features;
+
+ features[FEAT_CORE].name = TPY_CALL_CHANNEL_FEATURE_CORE;
+ features[FEAT_CORE].prepare_async =
+ tpy_call_channel_prepare_core_async;
+ features[FEAT_CORE].core = TRUE;
+
+ /* assert that the terminator at the end is there */
+ g_assert (features[N_FEAT].name == 0);
+
+ return features;
+}
+
+static void
+tpy_call_channel_class_init (TpyCallChannelClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ TpProxyClass *proxy_class = (TpProxyClass *) klass;
+ GParamSpec *param_spec;
+
+ gobject_class->get_property = tpy_call_channel_get_property;
+ gobject_class->dispose = tpy_call_channel_dispose;
+
+ proxy_class->list_features = tpy_call_channel_list_features;
+
+ g_type_class_add_private (klass, sizeof (TpyCallChannelPrivate));
+
+ /**
+ * TpyCallChannel:contents:
+ *
+ * The list of content objects that are part of this call.
+ *
+ * Since:
+ */
+ param_spec = g_param_spec_boxed ("contents", "Contents",
+ "The content objects of this call",
+ G_TYPE_PTR_ARRAY,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (gobject_class, PROP_CONTENTS, param_spec);
+
+ /**
+ * TpyCallChannel:state:
+ *
+ * A #TpChannelCallState specifying the state of the call.
+ *
+ * Since:
+ */
+ param_spec = g_param_spec_uint ("state", "Call state",
+ "The state of the call",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (gobject_class, PROP_STATE, param_spec);
+
+ /**
+ * TpyCallChannel:flags:
+ *
+ * A #TpChannelCallFlags specifying the flags of the call.
+ *
+ * Since:
+ */
+ param_spec = g_param_spec_uint ("flags", "Call flags",
+ "The flags for the call",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (gobject_class, PROP_FLAGS, param_spec);
+
+ /**
+ * TpyCallChannel:state-details:
+ *
+ * The list of content objects that are part of this call.
+ *
+ * Since:
+ */
+ param_spec = g_param_spec_boxed ("state-details", "State details",
+ "The details of the call",
+ G_TYPE_HASH_TABLE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (gobject_class,
+ PROP_STATE_DETAILS, param_spec);
+
+ /**
+ * TpyCallChannel:members:
+ *
+ * The call participants, and their respective flags.
+ *
+ * Since:
+ */
+ param_spec = g_param_spec_boxed ("members", "Call members",
+ "The call participants",
+ G_TYPE_HASH_TABLE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (gobject_class,
+ PROP_MEMBERS, param_spec);
+
+ /**
+ * TpyCallChannel:initial-audio:
+ *
+ * Whether or not the Call was started with audio.
+ *
+ * Since:
+ */
+ param_spec = g_param_spec_boolean ("initial-audio", "Initial audio",
+ "Initial audio",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (gobject_class,
+ PROP_INITIAL_AUDIO, param_spec);
+
+ /**
+ * TpyCallChannel:initial-video:
+ *
+ * Whether or not the Call was started with video.
+ *
+ * Since:
+ */
+ param_spec = g_param_spec_boolean ("initial-video", "Initial video",
+ "Initial video",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (gobject_class,
+ PROP_INITIAL_VIDEO, param_spec);
+
+ /**
+ * TpyCallChannel:ready:
+ *
+ * Whether or call channel got all its async information
+ *
+ * Since:
+ */
+ param_spec = g_param_spec_boolean ("ready", "Ready",
+ "If true the call channel and all its contents have retrieved all "
+ "all async information from the CM",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (gobject_class, PROP_READY,
+ param_spec);
+
+ /**
+ * TpyCallChannel::content-added
+ * @self: the #TpyCallChannel
+ * @content: the newly added content
+ *
+ * The ::content-added signal is emitted whenever a
+ * #TpyCallContent is added to @self.
+ */
+ _signals[CONTENT_ADDED] = g_signal_new ("content-added",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, G_TYPE_OBJECT);
+
+ /**
+ * TpyCallChannel::content-removed
+ * @self: the #TpyCallChannel
+ * @content: the newly removed content
+ *
+ * The ::content-removed signal is emitted whenever a
+ * #TpyCallContent is removed from @self.
+ */
+ _signals[CONTENT_REMOVED] = g_signal_new ("content-removed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, G_TYPE_OBJECT);
+
+ /**
+ * TpyCallChannel::state-changed
+ * @self: the #TpyCallChannel
+ * @state: the new #TpyCallState
+ * @flags: the new #TpyCallFlags
+ * @state_reason: the reason for the change
+ * @state_details: additional details
+ *
+ * The ::state-changed signal is emitted whenever the
+ * call state changes.
+ */
+ _signals[STATE_CHANGED] = g_signal_new ("state-changed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ _tpy_marshal_VOID__UINT_UINT_BOXED_BOXED,
+ G_TYPE_NONE,
+ 4, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_VALUE_ARRAY, G_TYPE_HASH_TABLE);
+
+ /**
+ * TpyCallChannel::members-changed
+ * @self: the #TpyCallChannel
+ * @members: the call members
+ *
+ * The ::members-changed signal is emitted whenever participants
+ * are added, removed, or their flags change.
+ */
+ _signals[MEMBERS_CHANGED] = g_signal_new ("members-changed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE,
+ 1, G_TYPE_HASH_TABLE);
+
+}
+
+static void
+tpy_call_channel_init (TpyCallChannel *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPY_TYPE_CALL_CHANNEL, TpyCallChannelPrivate);
+
+ self->priv->contents = g_ptr_array_new_with_free_func (g_object_unref);
+
+ self->priv->members = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+/**
+ * tpy_call_channel_new:
+ * @conn: a #TpConnection; may not be %NULL
+ * @object_path: the object path of the channel; may not be %NULL
+ * @immutable_properties: (transfer none) (element-type utf8 GObject.Value):
+ * the immutable properties of the channel,
+ * as signalled by the NewChannel D-Bus signal or returned by the
+ * CreateChannel and EnsureChannel D-Bus methods: a mapping from
+ * strings (D-Bus interface name + "." + property name) to #GValue instances
+ * @error: used to indicate the error if %NULL is returned
+ *
+ * Convenient function to create a new #TpyCallChannel
+ *
+ * Returns: (transfer full): a newly created #TpyCallChannel
+ *
+ * Since:
+ */
+TpyCallChannel *
+tpy_call_channel_new (TpConnection *conn,
+ const gchar *object_path,
+ const GHashTable *immutable_properties,
+ GError **error)
+{
+ TpProxy *conn_proxy = (TpProxy *) conn;
+
+ g_return_val_if_fail (TP_IS_CONNECTION (conn), NULL);
+ g_return_val_if_fail (object_path != NULL, NULL);
+ g_return_val_if_fail (immutable_properties != NULL, NULL);
+
+ if (!tp_dbus_check_valid_object_path (object_path, error))
+ return NULL;
+
+ return g_object_new (TPY_TYPE_CALL_CHANNEL,
+ "connection", conn,
+ "dbus-daemon", conn_proxy->dbus_daemon,
+ "bus-name", conn_proxy->bus_name,
+ "object-path", object_path,
+ "handle-type", (guint) TP_UNKNOWN_HANDLE_TYPE,
+ "channel-properties", immutable_properties,
+ NULL);
+}
+
+static void
+channel_accept_cb (TpProxy *proxy,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ GSimpleAsyncResult *result = user_data;
+
+ if (error != NULL)
+ {
+ DEBUG ("Failed to accept call: %s", error->message);
+
+ g_simple_async_result_set_from_error (result, error);
+ }
+
+ g_simple_async_result_set_op_res_gboolean (result, error == NULL);
+ g_simple_async_result_complete (result);
+}
+
+/**
+ * tpy_call_channel_accept_async:
+ * @self: an incoming #TpyCallChannel
+ * @callback: a callback to call
+ * @user_data: data to pass to @callback
+ *
+ * Accept an incoming call. When the call has been accepted, @callback
+ * will be called. You can then call tpy_call_channel_accept_finish()
+ * to finish the operation.
+ *
+ * Since:
+ */
+void
+tpy_call_channel_accept_async (TpyCallChannel *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ g_return_if_fail (TPY_IS_CALL_CHANNEL (self));
+
+ result = g_simple_async_result_new (G_OBJECT (self), callback,
+ user_data, tpy_call_channel_accept_async);
+
+ tpy_cli_channel_type_call_call_accept (TP_PROXY (self), -1,
+ channel_accept_cb, result, NULL, G_OBJECT (self));
+}
+
+/**
+ * tpy_call_channel_accept_finish:
+ * @self: a #TpyCallChannel
+ * @result: a #GAsyncResult
+ * @error: a #GError to fill
+ *
+ * Finishes to accept a call.
+ *
+ * Since:
+ */
+gboolean
+tpy_call_channel_accept_finish (TpyCallChannel *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
+ error))
+ return FALSE;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (self), tpy_call_channel_accept_async),
+ FALSE);
+
+ return g_simple_async_result_get_op_res_gboolean (
+ G_SIMPLE_ASYNC_RESULT (result));
+}
+
+static void
+channel_hangup_cb (TpProxy *proxy,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ GSimpleAsyncResult *result = user_data;
+
+ if (error != NULL)
+ {
+ DEBUG ("Failed to hang up: %s", error->message);
+
+ g_simple_async_result_set_from_error (result, error);
+ }
+
+ g_simple_async_result_set_op_res_gboolean (result, error == NULL);
+ g_simple_async_result_complete (result);
+}
+
+void
+tpy_call_channel_hangup_async (TpyCallChannel *self,
+ TpyCallStateChangeReason reason,
+ gchar *detailed_reason,
+ gchar *message,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ g_return_if_fail (TPY_IS_CALL_CHANNEL (self));
+
+ result = g_simple_async_result_new (G_OBJECT (self), callback,
+ user_data, tpy_call_channel_accept_async);
+
+ tpy_cli_channel_type_call_call_hangup (TP_PROXY (self), -1,
+ reason, detailed_reason, message,
+ channel_hangup_cb, result, NULL, G_OBJECT (self));
+}
+
+gboolean
+tpy_call_channel_hangup_finish (TpyCallChannel *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
+ error))
+ return FALSE;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (self), tpy_call_channel_hangup_async),
+ FALSE);
+
+ return g_simple_async_result_get_op_res_gboolean (
+ G_SIMPLE_ASYNC_RESULT (result));
+}
+
+TpyCallState
+tpy_call_channel_get_state (TpyCallChannel *self,
+ TpyCallFlags *flags, GHashTable **details)
+{
+ g_return_val_if_fail (TPY_IS_CALL_CHANNEL (self), TPY_CALL_STATE_UNKNOWN);
+
+ if (flags != NULL)
+ *flags = self->priv->flags;
+
+ if (details != NULL)
+ {
+ if (self->priv->details != NULL)
+ g_hash_table_ref (self->priv->details);
+
+ *details = self->priv->details;
+ }
+ return self->priv->state;
+}
+
+gboolean
+tpy_call_channel_has_initial_video (TpyCallChannel *self)
+{
+ g_return_val_if_fail (TPY_IS_CALL_CHANNEL (self), FALSE);
+
+ return self->priv->initial_video;
+}
+
+gboolean
+tpy_call_channel_has_initial_audio (TpyCallChannel *self)
+{
+ g_return_val_if_fail (TPY_IS_CALL_CHANNEL (self), FALSE);
+
+ return self->priv->initial_audio;
+}
+
+void
+tpy_call_channel_send_video (TpyCallChannel *self,
+ gboolean send)
+{
+ gboolean found = FALSE;
+ guint i;
+
+ g_return_if_fail (TPY_IS_CALL_CHANNEL (self));
+
+ /* Loop over all the contents, if some of them a video set all their
+ * streams to sending, otherwise request a video channel in case we want to
+ * sent */
+ for (i = 0 ; i < self->priv->contents->len ; i++)
+ {
+ TpyCallContent *content = g_ptr_array_index (self->priv->contents, i);
+
+ if (tpy_call_content_get_media_type (content)
+ == TP_MEDIA_STREAM_TYPE_VIDEO)
+ {
+ GList *l;
+ found = TRUE;
+
+ for (l = tpy_call_content_get_streams (content);
+ l != NULL ; l = g_list_next (l))
+ {
+ TpyCallStream *stream = TPY_CALL_STREAM (l->data);
+ tpy_call_stream_set_sending_async (stream,
+ send, NULL, NULL);
+ }
+ }
+ }
+
+ if (send && !found)
+ tpy_cli_channel_type_call_call_add_content (TP_PROXY (self), -1,
+ "video", TP_MEDIA_STREAM_TYPE_VIDEO,
+ NULL, NULL, NULL, NULL);
+}
+
+TpySendingState
+tpy_call_channel_get_video_state (TpyCallChannel *self)
+{
+ TpySendingState result = TPY_SENDING_STATE_NONE;
+ guint i;
+
+ g_return_val_if_fail (TPY_IS_CALL_CHANNEL (self), TPY_SENDING_STATE_NONE);
+
+ for (i = 0 ; i < self->priv->contents->len ; i++)
+ {
+ TpyCallContent *content = g_ptr_array_index (self->priv->contents, i);
+
+ if (tpy_call_content_get_media_type (content)
+ == TP_MEDIA_STREAM_TYPE_VIDEO)
+ {
+ GList *l;
+
+ for (l = tpy_call_content_get_streams (content);
+ l != NULL ; l = g_list_next (l))
+ {
+ TpyCallStream *stream = TPY_CALL_STREAM (l->data);
+ TpySendingState state;
+
+ g_object_get (stream, "local-sending-state", &state, NULL);
+ if (state != TPY_SENDING_STATE_PENDING_STOP_SENDING &&
+ state > result)
+ result = state;
+ }
+ }
+ }
+
+ return result;
+}
+
+
+gboolean
+tpy_call_channel_has_dtmf (TpyCallChannel *self)
+{
+ g_return_val_if_fail (TPY_IS_CALL_CHANNEL (self), FALSE);
+
+ return tp_proxy_has_interface_by_id (self,
+ TP_IFACE_QUARK_CHANNEL_INTERFACE_DTMF);
+}
+
+static void
+on_dtmf_tone_cb (TpChannel *proxy,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ if (error)
+ DEBUG ("Error %s: %s", (gchar *) user_data, error->message);
+}
+
+void
+tpy_call_channel_dtmf_start_tone (TpyCallChannel *self,
+ TpDTMFEvent event)
+{
+ g_return_if_fail (TPY_IS_CALL_CHANNEL (self));
+
+ tp_cli_channel_interface_dtmf_call_start_tone (TP_CHANNEL (self), -1, 0,
+ event,
+ on_dtmf_tone_cb, "starting tone", NULL, G_OBJECT (self));
+}
+
+void
+tpy_call_channel_dtmf_stop_tone (TpyCallChannel *self)
+{
+ g_return_if_fail (TPY_IS_CALL_CHANNEL (self));
+
+ tp_cli_channel_interface_dtmf_call_stop_tone (TP_CHANNEL (self), -1, 0,
+ on_dtmf_tone_cb, "stoping tone", NULL, G_OBJECT (self));
+}
+
+gboolean
+tpy_call_channel_has_hold (TpyCallChannel *self)
+{
+ g_return_val_if_fail (TPY_IS_CALL_CHANNEL (self), FALSE);
+
+ return tp_proxy_has_interface_by_id (self,
+ TP_IFACE_QUARK_CHANNEL_INTERFACE_HOLD);
+}
+
+/**
+ * tpy_call_channel_held_finish:
+ * @self: a #TpyCallChannel
+ * @result: a #GAsyncResult
+ * @error: a #GError to fill
+ *
+ * Finishes to change the hold status of the call.
+ *
+ * Since:
+ */
+gboolean
+tpy_call_channel_held_finish (TpyCallChannel *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
+ error))
+ return FALSE;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (self), tpy_call_channel_held_async),
+ FALSE);
+
+ return g_simple_async_result_get_op_res_gboolean (
+ G_SIMPLE_ASYNC_RESULT (result));
+}
+
+static void
+on_request_hold_cb (TpChannel *proxy,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ GSimpleAsyncResult *result = user_data;
+
+ if (error != NULL)
+ {
+ DEBUG ("Failed to change hold status: %s", error->message);
+
+ g_simple_async_result_set_from_error (result, error);
+ }
+
+ g_simple_async_result_set_op_res_gboolean (result,
+ error == NULL);
+ g_simple_async_result_complete (result);
+ g_object_unref (result);
+}
+
+void
+tpy_call_channel_held_async (TpyCallChannel *self,
+ gboolean held,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ g_return_if_fail (TPY_IS_CALL_CHANNEL (self));
+
+ result = g_simple_async_result_new (G_OBJECT (self), callback,
+ user_data, tpy_call_channel_held_async);
+
+ tp_cli_channel_interface_hold_call_request_hold (TP_CHANNEL (self),
+ -1, held,
+ on_request_hold_cb, result, g_object_unref, G_OBJECT (self));
+}
+
+/**
+ * TPY_CALL_CHANNEL_FEATURE_CORE:
+ *
+ * Expands to a call to a function that returns a quark for the "core"
+ * feature on a #TpyCallChannel.
+ *
+ * One can ask for a feature to be prepared using the tp_proxy_prepare_async()
+ * function, and waiting for it to trigger the callback.
+ */
+GQuark
+tpy_call_channel_get_feature_core (void)
+{
+ return g_quark_from_static_string ("tpy-call-channel-feature-core");
+}
diff --git a/yell/telepathy-yell/call-channel.h b/yell/telepathy-yell/call-channel.h
new file mode 100644
index 000000000..0d738f899
--- /dev/null
+++ b/yell/telepathy-yell/call-channel.h
@@ -0,0 +1,134 @@
+/*
+ * call-channel.h - Header for TpyCallChannel
+ * Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @author Emilio Pozuelo Monfort <emilio.pozuelo@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
+ */
+
+#ifndef __TPY_CALL_CHANNEL_H__
+#define __TPY_CALL_CHANNEL_H__
+
+#include <telepathy-glib/channel.h>
+
+#include "enums.h"
+#include "call-content.h"
+
+G_BEGIN_DECLS
+
+#define TPY_TYPE_CALL_CHANNEL (tpy_call_channel_get_type ())
+#define TPY_CALL_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TPY_TYPE_CALL_CHANNEL, TpyCallChannel))
+#define TPY_CALL_CHANNEL_CLASS(obj) (G_TYPE_CHECK_CLASS_CAST ((obj), TPY_TYPE_CALL_CHANNEL, TpyCallChannelClass))
+#define TPY_IS_CALL_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TPY_TYPE_CALL_CHANNEL))
+#define TPY_IS_CALL_CHANNEL_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE ((obj), TPY_TYPE_CALL_CHANNEL))
+#define TPY_CALL_CHANNEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TPY_TYPE_CALL_CHANNEL, TpyCallChannelClass))
+
+typedef struct _TpyCallChannel TpyCallChannel;
+typedef struct _TpyCallChannelClass TpyCallChannelClass;
+typedef struct _TpyCallChannelPrivate TpyCallChannelPrivate;
+
+struct _TpyCallChannel
+{
+ /*<private>*/
+ TpChannel parent;
+ TpyCallChannelPrivate *priv;
+};
+
+struct _TpyCallChannelClass
+{
+ /*<private>*/
+ TpChannelClass parent_class;
+ GCallback _padding[7];
+};
+
+GType tpy_call_channel_get_type (void);
+
+#define TPY_CALL_CHANNEL_FEATURE_CORE \
+ tpy_call_channel_get_feature_core ()
+GQuark tpy_call_channel_get_feature_core (void) G_GNUC_CONST;
+
+TpyCallChannel *tpy_call_channel_new (TpConnection *conn,
+ const gchar *object_path,
+ const GHashTable *immutable_properties,
+ GError **error);
+
+void tpy_call_channel_accept_async (TpyCallChannel *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean tpy_call_channel_accept_finish (TpyCallChannel *self,
+ GAsyncResult *result,
+ GError **error);
+
+void tpy_call_channel_hangup_async (TpyCallChannel *self,
+ TpyCallStateChangeReason reason,
+ gchar *detailed_reason,
+ gchar *message,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean tpy_call_channel_hangup_finish (TpyCallChannel *self,
+ GAsyncResult *result,
+ GError **error);
+
+void tpy_call_channel_set_ringing_async (TpyCallChannel *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+void tpy_call_channel_set_ringing_finish (TpyCallChannel *self,
+ GAsyncResult *result,
+ GError **error);
+
+void tpy_call_channel_add_content_async (TpyCallChannel *self,
+ gchar *name,
+ TpMediaStreamType type,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+TpyCallContent *tpy_call_channel_add_content_finish (TpyCallChannel *self,
+ GAsyncResult *result,
+ GError **error);
+
+void tpy_call_channel_send_video (TpyCallChannel *self,
+ gboolean send);
+
+TpySendingState tpy_call_channel_get_video_state (TpyCallChannel *self);
+
+TpyCallState tpy_call_channel_get_state (TpyCallChannel *self,
+ TpyCallFlags *flags, GHashTable **details);
+
+gboolean tpy_call_channel_has_initial_audio (TpyCallChannel *self);
+gboolean tpy_call_channel_has_initial_video (TpyCallChannel *self);
+
+gboolean tpy_call_channel_has_dtmf (TpyCallChannel *self);
+
+void tpy_call_channel_dtmf_start_tone (TpyCallChannel *self,
+ TpDTMFEvent event);
+
+void tpy_call_channel_dtmf_stop_tone (TpyCallChannel *self);
+
+gboolean tpy_call_channel_has_hold (TpyCallChannel *self);
+
+void tpy_call_channel_held_async (TpyCallChannel *self,
+ gboolean held,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean tpy_call_channel_held_finish (TpyCallChannel *self,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif
diff --git a/yell/telepathy-yell/call-content-codec-offer.c b/yell/telepathy-yell/call-content-codec-offer.c
new file mode 100644
index 000000000..425aa10c6
--- /dev/null
+++ b/yell/telepathy-yell/call-content-codec-offer.c
@@ -0,0 +1,424 @@
+/*
+ * call-content-codec-offer.c - Source for TpyCallContentCodecOffer
+ * Copyright (C) 2009 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@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
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <glib.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/dbus-properties-mixin.h>
+#include <telepathy-glib/svc-properties-interface.h>
+
+#include "call-content-codec-offer.h"
+#include "extensions.h"
+
+#define DEBUG_FLAG TPY_DEBUG_CALL
+#include "debug.h"
+
+static void call_content_codec_offer_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE(TpyCallContentCodecOffer,
+ tpy_call_content_codec_offer,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TPY_TYPE_SVC_CALL_CONTENT_CODEC_OFFER,
+ call_content_codec_offer_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ );
+
+/* properties */
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_INTERFACES,
+ PROP_REMOTE_CONTACT_CODECS,
+ PROP_REMOTE_CONTACT
+};
+
+/* private structure */
+struct _TpyCallContentCodecOfferPrivate
+{
+ gboolean dispose_has_run;
+
+ TpDBusDaemon *bus;
+ gchar *object_path;
+
+ TpHandle contact;
+ GPtrArray *codecs;
+
+ GSimpleAsyncResult *result;
+ GCancellable *cancellable;
+ guint handler_id;
+};
+
+#define TPY_CALL_CONTENT_CODEC_OFFER_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
+ TPY_TYPE_CALL_CONTENT_CODEC_OFFER, TpyCallContentCodecOfferPrivate))
+
+static void
+tpy_call_content_codec_offer_init (TpyCallContentCodecOffer *self)
+{
+ TpyCallContentCodecOfferPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPY_TYPE_CALL_CONTENT_CODEC_OFFER,
+ TpyCallContentCodecOfferPrivate);
+
+ self->priv = priv;
+ priv->bus = tp_dbus_daemon_dup (NULL);
+}
+
+static void tpy_call_content_codec_offer_dispose (GObject *object);
+static void tpy_call_content_codec_offer_finalize (GObject *object);
+
+static const gchar *interfaces[] = {
+ NULL
+};
+
+static void
+tpy_call_content_codec_offer_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TpyCallContentCodecOffer *offer =
+ TPY_CALL_CONTENT_CODEC_OFFER (object);
+ TpyCallContentCodecOfferPrivate *priv = offer->priv;
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, priv->object_path);
+ break;
+ case PROP_INTERFACES:
+ g_value_set_boxed (value, interfaces);
+ break;
+ case PROP_REMOTE_CONTACT_CODECS:
+ g_value_set_boxed (value, priv->codecs);
+ break;
+ case PROP_REMOTE_CONTACT:
+ g_value_set_uint (value, priv->contact);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tpy_call_content_codec_offer_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TpyCallContentCodecOffer *content =
+ TPY_CALL_CONTENT_CODEC_OFFER (object);
+ TpyCallContentCodecOfferPrivate *priv = content->priv;
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ priv->object_path = g_value_dup_string (value);
+ g_assert (priv->object_path != NULL);
+ break;
+ case PROP_REMOTE_CONTACT_CODECS:
+ priv->codecs = g_value_dup_boxed (value);
+ break;
+ case PROP_REMOTE_CONTACT:
+ priv->contact = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tpy_call_content_codec_offer_class_init (
+ TpyCallContentCodecOfferClass *tpy_call_content_codec_offer_class)
+{
+ GObjectClass *object_class =
+ G_OBJECT_CLASS (tpy_call_content_codec_offer_class);
+ GParamSpec *spec;
+
+ static TpDBusPropertiesMixinPropImpl codec_offer_props[] = {
+ { "Interfaces", "interfaces", NULL },
+ { "RemoteContactCodecs", "remote-contact-codecs", NULL },
+ { "RemoteContact", "remote-contact", NULL },
+ { NULL }
+ };
+
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TPY_IFACE_CALL_CONTENT_CODEC_OFFER,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ codec_offer_props,
+ },
+ { NULL }
+ };
+
+
+ g_type_class_add_private (tpy_call_content_codec_offer_class,
+ sizeof (TpyCallContentCodecOfferPrivate));
+
+ object_class->get_property = tpy_call_content_codec_offer_get_property;
+ object_class->set_property = tpy_call_content_codec_offer_set_property;
+
+ object_class->dispose = tpy_call_content_codec_offer_dispose;
+ object_class->finalize = tpy_call_content_codec_offer_finalize;
+
+ spec = g_param_spec_string ("object-path", "D-Bus object path",
+ "The D-Bus object path used for this "
+ "object on the bus.",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_OBJECT_PATH, spec);
+
+ spec = g_param_spec_boxed ("interfaces",
+ "Interfaces",
+ "Extra interfaces provided by this codec offer",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES,
+ spec);
+
+ spec = g_param_spec_boxed ("remote-contact-codecs",
+ "RemoteContactCodecs",
+ "A list of codecs the remote contact supports",
+ TPY_ARRAY_TYPE_CODEC_LIST,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REMOTE_CONTACT_CODECS,
+ spec);
+
+ spec = g_param_spec_uint ("remote-contact",
+ "RemoteContact",
+ "The contact handle that this codec offer applies to",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REMOTE_CONTACT,
+ spec);
+
+ tpy_call_content_codec_offer_class->dbus_props_class.interfaces
+ = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpyCallContentCodecOfferClass, dbus_props_class));
+}
+
+void
+tpy_call_content_codec_offer_dispose (GObject *object)
+{
+ TpyCallContentCodecOffer *self = TPY_CALL_CONTENT_CODEC_OFFER (object);
+ TpyCallContentCodecOfferPrivate *priv = self->priv;
+
+ g_assert (priv->result == NULL);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (priv->codecs != NULL)
+ {
+ /* dbus-glib :( */
+ g_boxed_free (TPY_ARRAY_TYPE_CODEC_LIST, priv->codecs);
+ }
+ priv->codecs = NULL;
+
+ g_object_unref (priv->bus);
+ priv->bus = NULL;
+
+ /* release any references held by the object here */
+ if (G_OBJECT_CLASS (tpy_call_content_codec_offer_parent_class)->dispose)
+ G_OBJECT_CLASS (tpy_call_content_codec_offer_parent_class)->dispose (
+ object);
+}
+
+void
+tpy_call_content_codec_offer_finalize (GObject *object)
+{
+ TpyCallContentCodecOffer *self = TPY_CALL_CONTENT_CODEC_OFFER (object);
+ TpyCallContentCodecOfferPrivate *priv = self->priv;
+
+ g_free (priv->object_path);
+ /* free any data held directly by the object here */
+
+ G_OBJECT_CLASS (tpy_call_content_codec_offer_parent_class)->finalize (
+ object);
+}
+
+static void
+tpy_call_content_codec_offer_accept (TpySvcCallContentCodecOffer *iface,
+ const GPtrArray *codecs,
+ DBusGMethodInvocation *context)
+{
+ TpyCallContentCodecOffer *self = TPY_CALL_CONTENT_CODEC_OFFER (iface);
+ TpyCallContentCodecOfferPrivate *priv = self->priv;
+
+ g_return_if_fail (priv->bus != NULL);
+
+ DEBUG ("%s was accepted", priv->object_path);
+
+ if (priv->cancellable != NULL)
+ {
+ g_cancellable_disconnect (priv->cancellable, priv->handler_id);
+ g_object_unref (priv->cancellable);
+ priv->cancellable = NULL;
+ priv->handler_id = 0;
+ }
+
+ g_simple_async_result_set_op_res_gpointer (priv->result,
+ (gpointer) codecs, NULL);
+ g_simple_async_result_complete (priv->result);
+ g_object_unref (priv->result);
+ priv->result = NULL;
+
+ tpy_svc_call_content_codec_offer_return_from_accept (context);
+
+ tp_dbus_daemon_unregister_object (priv->bus, G_OBJECT (self));
+}
+
+static void
+tpy_call_content_codec_offer_reject (TpySvcCallContentCodecOffer *iface,
+ DBusGMethodInvocation *context)
+{
+ TpyCallContentCodecOffer *self = TPY_CALL_CONTENT_CODEC_OFFER (iface);
+ TpyCallContentCodecOfferPrivate *priv = self->priv;
+
+ g_return_if_fail (priv->bus != NULL);
+
+ DEBUG ("%s was rejected", priv->object_path);
+
+ if (priv->cancellable != NULL)
+ {
+ g_cancellable_disconnect (priv->cancellable, priv->handler_id);
+ g_object_unref (priv->cancellable);
+ priv->cancellable = NULL;
+ priv->handler_id = 0;
+ }
+
+ g_simple_async_result_set_error (priv->result,
+ G_IO_ERROR, G_IO_ERROR_FAILED, "Codec offer was rejected");
+ g_simple_async_result_complete (priv->result);
+ g_object_unref (priv->result);
+ priv->result = NULL;
+
+ tpy_svc_call_content_codec_offer_return_from_reject (context);
+
+ tp_dbus_daemon_unregister_object (priv->bus, G_OBJECT (self));
+}
+
+static void
+call_content_codec_offer_iface_init (gpointer iface, gpointer data)
+{
+ TpySvcCallContentCodecOfferClass *klass =
+ (TpySvcCallContentCodecOfferClass *) iface;
+
+#define IMPLEMENT(x) tpy_svc_call_content_codec_offer_implement_##x (\
+ klass, tpy_call_content_codec_offer_##x)
+ IMPLEMENT(accept);
+ IMPLEMENT(reject);
+#undef IMPLEMENT
+}
+
+TpyCallContentCodecOffer *
+tpy_call_content_codec_offer_new (const gchar *object_path,
+ TpHandle remote_contact,
+ GPtrArray *codecs)
+{
+ return g_object_new (TPY_TYPE_CALL_CONTENT_CODEC_OFFER,
+ "object-path", object_path,
+ "remote-contact", remote_contact,
+ "remote-contact-codecs", codecs,
+ NULL);
+}
+
+static void
+cancelled_cb (GCancellable *cancellable, gpointer user_data)
+{
+ TpyCallContentCodecOffer *offer = user_data;
+ TpyCallContentCodecOfferPrivate *priv = offer->priv;
+
+ g_return_if_fail (priv->bus != NULL);
+
+ tp_dbus_daemon_unregister_object (priv->bus, G_OBJECT (offer));
+
+ g_simple_async_result_set_error (priv->result,
+ G_IO_ERROR, G_IO_ERROR_CANCELLED, "Offer cancelled");
+ g_simple_async_result_complete_in_idle (priv->result);
+
+ g_object_unref (priv->cancellable);
+ g_object_unref (priv->result);
+ priv->result = NULL;
+ priv->cancellable = NULL;
+ priv->handler_id = 0;
+}
+
+void
+tpy_call_content_codec_offer_offer (TpyCallContentCodecOffer *offer,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TpyCallContentCodecOfferPrivate *priv = offer->priv;
+
+ g_return_if_fail (priv->bus != NULL);
+
+ /* FIXME implement cancellable support */
+ if (G_UNLIKELY (priv->result != NULL))
+ goto pending;
+
+ priv->result = g_simple_async_result_new (G_OBJECT (offer),
+ callback, user_data, tpy_call_content_codec_offer_offer_finish);
+
+ /* register object on the bus */
+ DEBUG ("Registering %s", priv->object_path);
+ tp_dbus_daemon_register_object (priv->bus, priv->object_path,
+ G_OBJECT (offer));
+
+ if (cancellable != NULL)
+ {
+ priv->cancellable = g_object_ref (cancellable);
+ priv->handler_id = g_cancellable_connect (
+ cancellable, G_CALLBACK (cancelled_cb), offer, NULL);
+ }
+
+ return;
+
+pending:
+ g_simple_async_report_error_in_idle (G_OBJECT (offer), callback, user_data,
+ G_IO_ERROR, G_IO_ERROR_PENDING, "Another offer operation is pending");
+}
+
+GPtrArray *
+tpy_call_content_codec_offer_offer_finish (
+ TpyCallContentCodecOffer *offer,
+ GAsyncResult *result,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
+ error))
+ return NULL;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (offer), tpy_call_content_codec_offer_offer_finish),
+ NULL);
+
+ return g_simple_async_result_get_op_res_gpointer (
+ G_SIMPLE_ASYNC_RESULT (result));
+}
diff --git a/yell/telepathy-yell/call-content-codec-offer.h b/yell/telepathy-yell/call-content-codec-offer.h
new file mode 100644
index 000000000..b37658688
--- /dev/null
+++ b/yell/telepathy-yell/call-content-codec-offer.h
@@ -0,0 +1,85 @@
+/*
+ * call-content-codec-offer.h - Header for TpyCallContentCodecOffer
+ * Copyright (C) 2009 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@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
+ */
+
+#ifndef __TPY_CALL_CONTENT_CODEC_OFFER_H__
+#define __TPY_CALL_CONTENT_CODEC_OFFER_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpyCallContentCodecOffer TpyCallContentCodecOffer;
+typedef struct _TpyCallContentCodecOfferPrivate
+ TpyCallContentCodecOfferPrivate;
+typedef struct _TpyCallContentCodecOfferClass
+ TpyCallContentCodecOfferClass;
+
+struct _TpyCallContentCodecOfferClass {
+ GObjectClass parent_class;
+
+ TpDBusPropertiesMixinClass dbus_props_class;
+};
+
+struct _TpyCallContentCodecOffer {
+ GObject parent;
+
+ TpyCallContentCodecOfferPrivate *priv;
+};
+
+GType tpy_call_content_codec_offer_get_type (void);
+
+/* TYPE MACROS */
+#define TPY_TYPE_CALL_CONTENT_CODEC_OFFER \
+ (tpy_call_content_codec_offer_get_type ())
+#define TPY_CALL_CONTENT_CODEC_OFFER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ TPY_TYPE_CALL_CONTENT_CODEC_OFFER, TpyCallContentCodecOffer))
+#define TPY_CALL_CONTENT_CODEC_OFFER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ TPY_TYPE_CALL_CONTENT_CODEC_OFFER, TpyCallContentCodecOfferClass))
+#define TPY_IS_CALL_CONTENT_CODEC_OFFER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPY_TYPE_CALL_CONTENT_CODEC_OFFER))
+#define TPY_IS_CALL_CONTENT_CODEC_OFFER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TPY_TYPE_CALL_CONTENT_CODEC_OFFER))
+#define TPY_CALL_CONTENT_CODEC_OFFER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TPY_TYPE_CALL_CONTENT_CODEC_OFFER, \
+ TpyCallContentCodecOfferClass))
+
+TpyCallContentCodecOffer *tpy_call_content_codec_offer_new (
+ const gchar *object_path,
+ TpHandle remote_contact,
+ GPtrArray *codecs);
+
+void tpy_call_content_codec_offer_offer (TpyCallContentCodecOffer *offer,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+GPtrArray *tpy_call_content_codec_offer_offer_finish (
+ TpyCallContentCodecOffer *offer,
+ GAsyncResult *result,
+ GError **error);
+
+
+G_END_DECLS
+
+#endif /* #ifndef __TPY_CALL_CONTENT_CODEC_OFFER_H__*/
diff --git a/yell/telepathy-yell/call-content.c b/yell/telepathy-yell/call-content.c
new file mode 100644
index 000000000..180b50ce5
--- /dev/null
+++ b/yell/telepathy-yell/call-content.c
@@ -0,0 +1,561 @@
+/*
+ * call-content.c - Source for TpyCallContent
+ * Copyright © 2009–2011 Collabora Ltd.
+ * @author Emilio Pozuelo Monfort <emilio.pozuelo@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
+ */
+
+#include <telepathy-glib/proxy-subclass.h>
+
+#include "call-content.h"
+
+#define DEBUG_FLAG TPY_DEBUG_CALL
+#include "debug.h"
+
+#include <telepathy-yell/call-stream.h>
+#include <telepathy-yell/interfaces.h>
+#include <telepathy-yell/gtypes.h>
+#include <telepathy-yell/enums.h>
+#include <telepathy-yell/cli-call.h>
+
+G_DEFINE_TYPE (TpyCallContent, tpy_call_content, TP_TYPE_PROXY)
+
+struct _TpyCallContentPrivate
+{
+ const gchar * const *extra_interfaces;
+ gchar *name;
+ TpMediaStreamType media_type;
+ TpyCallContentDisposition disposition;
+ GList *streams;
+ gboolean ready;
+ gboolean properties_retrieved;
+
+ GSimpleAsyncResult *result;
+};
+
+enum
+{
+ PROP_NAME = 1,
+ PROP_MEDIA_TYPE,
+ PROP_DISPOSITION,
+ PROP_STREAMS,
+ PROP_READY
+};
+
+enum
+{
+ REMOVED,
+ STREAMS_ADDED,
+ STREAMS_REMOVED,
+ LAST_SIGNAL
+};
+
+static guint _signals[LAST_SIGNAL] = { 0, };
+
+static void on_call_content_get_all_properties_cb (TpProxy *proxy,
+ GHashTable *properties,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object);
+
+static gint
+find_stream_for_object_path (gconstpointer a,
+ gconstpointer b)
+{
+ return g_strcmp0 (tp_proxy_get_object_path ((gpointer) a), b);
+}
+
+static void
+on_content_removed_cb (TpProxy *proxy,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpyCallContent *self = TPY_CALL_CONTENT (proxy);
+
+ g_signal_emit (self, _signals[REMOVED], 0);
+}
+
+static void
+maybe_go_to_ready (TpyCallContent *self)
+{
+ TpyCallContentPrivate *priv = self->priv;
+ GList *l;
+
+ if (priv->ready)
+ return;
+
+ if (!priv->properties_retrieved)
+ return;
+
+
+ for (l = priv->streams; l != NULL ; l = g_list_next (l))
+ {
+ TpyCallStream *s = l->data;
+ gboolean ready;
+
+ g_object_get (s, "ready", &ready, NULL);
+
+ if (!ready)
+ return;
+ }
+
+ priv->ready = TRUE;
+ g_object_notify (G_OBJECT (self), "ready");
+}
+
+static void
+on_stream_ready_cb (TpyCallStream *stream,
+ GParamSpec *spec,
+ TpyCallContent *self)
+{
+ maybe_go_to_ready (self);
+}
+
+static void
+add_streams (TpyCallContent *self, const GPtrArray *streams)
+{
+ GPtrArray *object_streams;
+ guint i;
+
+ object_streams = g_ptr_array_sized_new (streams->len);
+
+ for (i = 0; i < streams->len; i++)
+ {
+ TpyCallStream *stream;
+ const gchar *object_path;
+
+ object_path = g_ptr_array_index (streams, i);
+
+ stream = g_object_new (TPY_TYPE_CALL_STREAM,
+ "bus-name", tp_proxy_get_bus_name (self),
+ "dbus-daemon", tp_proxy_get_dbus_daemon (self),
+ "dbus-connection", tp_proxy_get_dbus_connection (self),
+ "object-path", object_path,
+ NULL);
+
+ if (stream == NULL)
+ {
+ g_warning ("Could not create a CallStream for path %s", object_path);
+ continue;
+ }
+
+ tp_g_signal_connect_object (stream, "notify::ready",
+ G_CALLBACK (on_stream_ready_cb), self, 0);
+
+ self->priv->streams = g_list_prepend (self->priv->streams, stream);
+ g_ptr_array_add (object_streams, stream);
+ }
+
+ g_signal_emit (self, _signals[STREAMS_ADDED], 0, object_streams);
+ g_ptr_array_unref (object_streams);
+}
+
+static void
+on_streams_added_cb (TpProxy *proxy,
+ const GPtrArray *streams,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpyCallContent *self = TPY_CALL_CONTENT (proxy);
+
+ add_streams (self, streams);
+}
+
+static void
+on_streams_removed_cb (TpProxy *proxy,
+ const GPtrArray *streams,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpyCallContent *self = TPY_CALL_CONTENT (proxy);
+ GPtrArray *object_streams;
+ guint i;
+
+ object_streams = g_ptr_array_sized_new (streams->len);
+ g_ptr_array_set_free_func (object_streams, g_object_unref);
+
+ for (i = 0; i < streams->len; i++)
+ {
+ GList *s;
+ const gchar *object_path;
+
+ object_path = g_ptr_array_index (streams, i);
+
+ s = g_list_find_custom (self->priv->streams,
+ object_path,
+ find_stream_for_object_path);
+
+ if (s == NULL)
+ {
+ g_warning ("Could not find a CallStream for path %s", object_path);
+ continue;
+ }
+
+ self->priv->streams = g_list_remove_link (self->priv->streams, s);
+ g_ptr_array_add (object_streams, s->data);
+ }
+
+ g_signal_emit (self, _signals[STREAMS_REMOVED], 0, object_streams);
+
+ g_ptr_array_unref (object_streams);
+}
+
+static void
+tpy_call_content_constructed (GObject *obj)
+{
+ TpyCallContent *self = (TpyCallContent *) obj;
+ GError *error = NULL;
+
+ ((GObjectClass *) tpy_call_content_parent_class)->constructed (obj);
+
+ tpy_cli_call_content_connect_to_removed (TP_PROXY (self),
+ on_content_removed_cb, NULL, NULL, G_OBJECT (self), &error);
+
+ if (error != NULL)
+ {
+ g_critical ("Failed to connect to Removed signal: %s",
+ error->message);
+
+ g_error_free (error);
+ return;
+ }
+
+ tpy_cli_call_content_connect_to_streams_added (TP_PROXY (self),
+ on_streams_added_cb, NULL, NULL, G_OBJECT (self), &error);
+
+ if (error != NULL)
+ {
+ g_critical ("Failed to connect to StreamsAdded signal: %s",
+ error->message);
+
+ g_error_free (error);
+ return;
+ }
+
+ tpy_cli_call_content_connect_to_streams_removed (TP_PROXY (self),
+ on_streams_removed_cb, NULL, NULL, G_OBJECT (self), &error);
+
+ if (error != NULL)
+ {
+ g_critical ("Failed to connect to StreamsRemoved signal: %s",
+ error->message);
+
+ g_error_free (error);
+ return;
+ }
+
+ tp_cli_dbus_properties_call_get_all (self, -1,
+ TPY_IFACE_CALL_CONTENT,
+ on_call_content_get_all_properties_cb, NULL, NULL, G_OBJECT (self));
+}
+
+static void
+tpy_call_content_init (TpyCallContent *self)
+{
+ TpyCallContentPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPY_TYPE_CALL_CONTENT, TpyCallContentPrivate);
+
+ self->priv = priv;
+}
+
+static void
+tpy_call_content_dispose (GObject *object)
+{
+ TpyCallContent *self = TPY_CALL_CONTENT (object);
+
+ tp_clear_pointer (&self->priv->name, g_free);
+ tp_clear_object (&self->priv->result);
+
+ g_list_free_full (self->priv->streams, g_object_unref);
+ self->priv->streams = NULL;
+
+ G_OBJECT_CLASS (tpy_call_content_parent_class)->dispose (object);
+}
+
+static void
+tpy_call_content_get_property (
+ GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TpyCallContent *self = TPY_CALL_CONTENT (object);
+
+ switch (property_id)
+ {
+ case PROP_NAME:
+ g_value_set_string (value, self->priv->name);
+ break;
+ case PROP_MEDIA_TYPE:
+ g_value_set_uint (value, self->priv->media_type);
+ break;
+ case PROP_DISPOSITION:
+ g_value_set_uint (value, self->priv->disposition);
+ break;
+ case PROP_STREAMS:
+ g_value_set_boxed (value, self->priv->streams);
+ break;
+ case PROP_READY:
+ g_value_set_boolean (value, self->priv->ready);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tpy_call_content_set_property (
+ GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TpyCallContent *self = TPY_CALL_CONTENT (object);
+
+ switch (property_id)
+ {
+ case PROP_NAME:
+ g_assert (self->priv->name == NULL);
+ self->priv->name = g_value_dup_string (value);
+ break;
+ case PROP_MEDIA_TYPE:
+ self->priv->media_type = g_value_get_uint (value);
+ break;
+ case PROP_DISPOSITION:
+ self->priv->disposition = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tpy_call_content_class_init (
+ TpyCallContentClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ TpProxyClass *proxy_class = TP_PROXY_CLASS (klass);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass, sizeof (TpyCallContentPrivate));
+
+ object_class->constructed = tpy_call_content_constructed;
+ object_class->dispose = tpy_call_content_dispose;
+ object_class->get_property = tpy_call_content_get_property;
+ object_class->set_property = tpy_call_content_set_property;
+
+ proxy_class->interface = TPY_IFACE_QUARK_CALL_CONTENT;
+
+ param_spec = g_param_spec_string ("name", "Name",
+ "The name of this content, if any",
+ "",
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_NAME, param_spec);
+
+ param_spec = g_param_spec_uint ("media-type", "Media Type",
+ "The media type of this content",
+ 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, param_spec);
+
+ param_spec = g_param_spec_uint ("disposition", "Disposition",
+ "The disposition of this content",
+ 0, G_MAXUINT, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DISPOSITION, param_spec);
+
+ param_spec = g_param_spec_boxed ("streams", "Stream",
+ "The streams of this content",
+ TP_ARRAY_TYPE_OBJECT_PATH_LIST,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STREAMS,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("ready",
+ "Ready",
+ "If true the content and all its streams have retrieved all"
+ "all async information from the CM",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_READY,
+ param_spec);
+
+ /**
+ * TpyCallContent::removed
+ * @self: the #TpyCallContent
+ *
+ * The ::removed signal is emitted when @self is removed from
+ * a #TpyCallChannel.
+ */
+ _signals[REMOVED] = g_signal_new ("removed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ /**
+ * TpyCallContent::streams-added
+ * @self: the #TpyCallContent
+ * @stream: the newly added streams
+ *
+ * The ::streams-added signal is emitted whenever
+ * #TpyCallStreams are added to @self.
+ */
+ _signals[STREAMS_ADDED] = g_signal_new ("streams-added",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE,
+ 1, G_TYPE_PTR_ARRAY);
+
+ /**
+ * TpyCallContent::streams-removed
+ * @self: the #TpyCallContent
+ * @stream: the newly removed streams
+ *
+ * The ::streams-removed signal is emitted whenever
+ * #TpyCallStreams are removed from @self.
+ */
+ _signals[STREAMS_REMOVED] = g_signal_new ("streams-removed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE,
+ 1, G_TYPE_PTR_ARRAY);
+}
+
+const gchar *
+tpy_call_content_get_name (TpyCallContent *self)
+{
+ g_return_val_if_fail (TPY_IS_CALL_CONTENT (self), NULL);
+
+ return self->priv->name;
+}
+
+TpMediaStreamType
+tpy_call_content_get_media_type (TpyCallContent *self)
+{
+ g_return_val_if_fail (TPY_IS_CALL_CONTENT (self),
+ TP_MEDIA_STREAM_TYPE_AUDIO);
+
+ return self->priv->media_type;
+}
+
+TpyCallContentDisposition
+tpy_call_content_get_disposition (TpyCallContent *self)
+{
+ g_return_val_if_fail (TPY_IS_CALL_CONTENT (self),
+ TPY_CALL_CONTENT_DISPOSITION_NONE);
+
+ return self->priv->disposition;
+}
+
+static void
+on_call_content_get_all_properties_cb (TpProxy *proxy,
+ GHashTable *properties,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpyCallContent *self = TPY_CALL_CONTENT (proxy);
+ GPtrArray *streams;
+
+ if (error != NULL)
+ {
+ g_warning ("Could not get the content properties: %s", error->message);
+ return;
+ }
+
+ self->priv->media_type = tp_asv_get_uint32 (properties, "Type", NULL);
+ g_free (self->priv->name);
+ self->priv->name = g_strdup (tp_asv_get_string (properties, "Name"));
+ self->priv->disposition = tp_asv_get_uint32 (properties, "Disposition",
+ NULL);
+
+ streams = tp_asv_get_boxed (properties, "Streams",
+ TP_ARRAY_TYPE_OBJECT_PATH_LIST);
+
+ if (streams != NULL)
+ add_streams (self, streams);
+
+ self->priv->properties_retrieved = TRUE;
+ maybe_go_to_ready (self);
+}
+
+static void
+on_content_remove_cb (TpProxy *proxy,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpyCallContent *self = TPY_CALL_CONTENT (proxy);
+
+ if (error != NULL)
+ {
+ DEBUG ("Failed to remove content: %s", error->message);
+
+ g_simple_async_result_set_from_error (self->priv->result, error);
+ }
+
+ g_simple_async_result_set_op_res_gboolean (self->priv->result, TRUE);
+ g_simple_async_result_complete (self->priv->result);
+ tp_clear_object (&self->priv->result);
+}
+
+void
+tpy_call_content_remove_async (TpyCallContent *self,
+ TpyContentRemovalReason reason,
+ const gchar *detailed_removal_reason,
+ const gchar *message,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ DEBUG ("removing content for reason %u, detailed reason: %s, message: %s",
+ reason, detailed_removal_reason, message);
+
+ tpy_cli_call_content_call_remove (TP_PROXY (self), -1,
+ reason, detailed_removal_reason, message,
+ on_content_remove_cb, NULL, NULL, G_OBJECT (self));
+}
+
+gboolean
+tpy_call_content_remove_finish (TpyCallContent *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
+ error))
+ return FALSE;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (self), tpy_call_content_remove_async),
+ FALSE);
+
+ return g_simple_async_result_get_op_res_gboolean (
+ G_SIMPLE_ASYNC_RESULT (result));
+}
+
+GList *
+tpy_call_content_get_streams (TpyCallContent *self)
+{
+ return self->priv->streams;
+}
diff --git a/yell/telepathy-yell/call-content.h b/yell/telepathy-yell/call-content.h
new file mode 100644
index 000000000..521e0f365
--- /dev/null
+++ b/yell/telepathy-yell/call-content.h
@@ -0,0 +1,87 @@
+/*
+ * call-content.h - Header for TpyCallContent
+ * Copyright © 2011 Collabora Ltd.
+ * @author Emilio Pozuelo Monfort <emilio.pozuelo@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
+ */
+
+#ifndef TPY_CALL_CONTENT_H
+#define TPY_CALL_CONTENT_H
+
+#include <glib-object.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "enums.h"
+
+G_BEGIN_DECLS
+
+typedef struct _TpyCallContent TpyCallContent;
+typedef struct _TpyCallContentPrivate TpyCallContentPrivate;
+typedef struct _TpyCallContentClass TpyCallContentClass;
+
+struct _TpyCallContentClass {
+ TpProxyClass parent_class;
+};
+
+struct _TpyCallContent {
+ TpProxy parent;
+
+ TpyCallContentPrivate *priv;
+};
+
+GType tpy_call_content_get_type (void);
+
+/* TYPE MACROS */
+#define TPY_TYPE_CALL_CONTENT \
+ (tpy_call_content_get_type ())
+#define TPY_CALL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ TPY_TYPE_CALL_CONTENT, TpyCallContent))
+#define TPY_CALL_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ TPY_TYPE_CALL_CONTENT, TpyCallContentClass))
+#define TPY_IS_CALL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPY_TYPE_CALL_CONTENT))
+#define TPY_IS_CALL_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TPY_TYPE_CALL_CONTENT))
+#define TPY_CALL_CONTENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TPY_TYPE_CALL_CONTENT, TpyCallContentClass))
+
+const gchar *tpy_call_content_get_name (TpyCallContent *self);
+
+TpMediaStreamType tpy_call_content_get_media_type (
+ TpyCallContent *self);
+
+TpyCallContentDisposition tpy_call_content_get_disposition (
+ TpyCallContent *self);
+
+GList *tpy_call_content_get_streams (TpyCallContent *self);
+
+void tpy_call_content_remove_async (TpyCallContent *self,
+ TpyContentRemovalReason reason,
+ const gchar *detailed_removal_reason,
+ const gchar *message,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean tpy_call_content_remove_finish (TpyCallContent *self,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* #ifndef __TPY_CALL_CONTENT_H__*/
diff --git a/yell/telepathy-yell/call-stream-endpoint.c b/yell/telepathy-yell/call-stream-endpoint.c
new file mode 100644
index 000000000..15738ba6c
--- /dev/null
+++ b/yell/telepathy-yell/call-stream-endpoint.c
@@ -0,0 +1,482 @@
+/*
+ * call-stream-endpoint.c - Source for TpyCallStreamEndpoint
+ * Copyright (C) 2009 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@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
+ */
+
+#include "call-stream-endpoint.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/dbus-properties-mixin.h>
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/svc-properties-interface.h>
+#include <telepathy-glib/util.h>
+
+#include <telepathy-yell/enums.h>
+#include <telepathy-yell/interfaces.h>
+#include <telepathy-yell/gtypes.h>
+#include <telepathy-yell/svc-call.h>
+
+
+#define DEBUG_FLAG TPY_DEBUG_CALL
+#include "debug.h"
+
+static void call_stream_endpoint_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE(TpyCallStreamEndpoint,
+ tpy_call_stream_endpoint,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TPY_TYPE_SVC_CALL_STREAM_ENDPOINT,
+ call_stream_endpoint_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+);
+
+/* properties */
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_DBUS_DAEMON,
+ PROP_REMOTE_CANDIDATES,
+ PROP_REMOTE_CREDENTIALS,
+ PROP_SELECTED_CANDIDATE,
+ PROP_STREAM_STATE,
+ PROP_TRANSPORT,
+};
+
+struct _TpyCallStreamEndpointPrivate
+{
+ gboolean dispose_has_run;
+
+ TpDBusDaemon *dbus_daemon;
+ gchar *object_path;
+ GValueArray *remote_credentials;
+ GPtrArray *remote_candidates;
+ GValueArray *selected_candidate;
+ TpMediaStreamState stream_state;
+ TpyStreamTransportType transport;
+};
+
+static void
+tpy_call_stream_endpoint_init (TpyCallStreamEndpoint *self)
+{
+ TpyCallStreamEndpointPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPY_TYPE_CALL_STREAM_ENDPOINT,
+ TpyCallStreamEndpointPrivate);
+
+ self->priv = priv;
+
+ priv->selected_candidate = tp_value_array_build (4,
+ G_TYPE_UINT, 0,
+ G_TYPE_STRING, "",
+ G_TYPE_UINT, 0,
+ TPY_HASH_TYPE_CANDIDATE_INFO,
+ g_hash_table_new (g_str_hash, g_str_equal),
+ G_TYPE_INVALID);
+
+ priv->remote_credentials = tp_value_array_build (2,
+ G_TYPE_STRING, "",
+ G_TYPE_STRING, "",
+ G_TYPE_INVALID);
+
+ priv->remote_candidates = g_ptr_array_new ();
+}
+
+static void tpy_call_stream_endpoint_dispose (GObject *object);
+static void tpy_call_stream_endpoint_finalize (GObject *object);
+
+static void
+tpy_call_stream_endpoint_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TpyCallStreamEndpoint *endpoint = TPY_CALL_STREAM_ENDPOINT (object);
+ TpyCallStreamEndpointPrivate *priv = endpoint->priv;
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, priv->object_path);
+ break;
+ case PROP_REMOTE_CANDIDATES:
+ g_value_set_boxed (value, priv->remote_candidates);
+ break;
+ case PROP_REMOTE_CREDENTIALS:
+ g_value_set_boxed (value, priv->remote_credentials);
+ break;
+ case PROP_SELECTED_CANDIDATE:
+ g_value_set_boxed (value, priv->selected_candidate);
+ break;
+ case PROP_STREAM_STATE:
+ g_value_set_uint (value, priv->stream_state);
+ break;
+ case PROP_TRANSPORT:
+ g_value_set_uint (value, priv->transport);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tpy_call_stream_endpoint_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TpyCallStreamEndpoint *endpoint = TPY_CALL_STREAM_ENDPOINT (object);
+ TpyCallStreamEndpointPrivate *priv = endpoint->priv;
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ priv->object_path = g_value_dup_string (value);
+ g_assert (priv->object_path != NULL);
+ break;
+ case PROP_DBUS_DAEMON:
+ g_assert (priv->dbus_daemon == NULL); /* construct-only */
+ priv->dbus_daemon = g_value_dup_object (value);
+ break;
+ case PROP_TRANSPORT:
+ priv->transport = g_value_get_uint (value);
+ break;
+ case PROP_STREAM_STATE:
+ priv->stream_state = g_value_get_uint (value);
+ break;
+ case PROP_SELECTED_CANDIDATE:
+ {
+ g_boxed_free (TPY_STRUCT_TYPE_CANDIDATE,
+ priv->selected_candidate);
+ priv->selected_candidate = g_value_get_boxed (value);
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tpy_call_stream_endpoint_constructed (GObject *obj)
+{
+ TpyCallStreamEndpointPrivate *priv;
+
+ priv = TPY_CALL_STREAM_ENDPOINT (obj)->priv;
+
+ /* register object on the bus */
+ DEBUG ("Registering %s", priv->object_path);
+ tp_dbus_daemon_register_object (priv->dbus_daemon, priv->object_path, obj);
+
+ if (G_OBJECT_CLASS (tpy_call_stream_endpoint_parent_class)->constructed
+ != NULL)
+ G_OBJECT_CLASS (tpy_call_stream_endpoint_parent_class)->constructed (
+ obj);
+}
+
+static void
+tpy_call_stream_endpoint_class_init (
+ TpyCallStreamEndpointClass *tpy_call_stream_endpoint_class)
+{
+ GObjectClass *object_class =
+ G_OBJECT_CLASS (tpy_call_stream_endpoint_class);
+ GParamSpec *param_spec;
+ static TpDBusPropertiesMixinPropImpl endpoint_props[] = {
+ { "RemoteCandidates", "remote-candidates", NULL },
+ { "RemoteCredentials", "remote-credentials", NULL },
+ { "SelectedCandidate", "selected-candidate", NULL },
+ { "StreamState", "stream-state", NULL },
+ { "Transport", "transport", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TPY_IFACE_CALL_STREAM_ENDPOINT,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ endpoint_props,
+ },
+ { NULL }
+ };
+
+ g_type_class_add_private (tpy_call_stream_endpoint_class,
+ sizeof (TpyCallStreamEndpointPrivate));
+
+ object_class->dispose = tpy_call_stream_endpoint_dispose;
+ object_class->finalize = tpy_call_stream_endpoint_finalize;
+ object_class->constructed = tpy_call_stream_endpoint_constructed;
+
+ object_class->set_property = tpy_call_stream_endpoint_set_property;
+ object_class->get_property = tpy_call_stream_endpoint_get_property;
+
+ param_spec = g_param_spec_string ("object-path", "D-Bus object path",
+ "The D-Bus object path used for this "
+ "object on the bus.",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec);
+
+ param_spec = g_param_spec_boxed ("remote-candidates",
+ "RemoteCandidates",
+ "The remote candidates of this endpoint",
+ TPY_ARRAY_TYPE_CANDIDATE_LIST,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REMOTE_CANDIDATES,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("remote-credentials",
+ "RemoteCredentials",
+ "The remote credentials of this endpoint",
+ TPY_STRUCT_TYPE_STREAM_CREDENTIALS,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REMOTE_CREDENTIALS,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("selected-candidate",
+ "SelectedCandidate",
+ "The candidate selected for this endpoint",
+ TPY_STRUCT_TYPE_CANDIDATE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SELECTED_CANDIDATE,
+ param_spec);
+
+ param_spec = g_param_spec_uint ("stream-state", "StreamState",
+ "The stream state of this endpoint.",
+ 0, G_MAXUINT32,
+ TP_MEDIA_STREAM_STATE_DISCONNECTED,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STREAM_STATE,
+ param_spec);
+
+ param_spec = g_param_spec_uint ("transport",
+ "Transport",
+ "The transport type for the content of this endpoint.",
+ 0, NUM_TPY_STREAM_TRANSPORT_TYPES, 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TRANSPORT, param_spec);
+
+ param_spec = g_param_spec_object ("dbus-daemon",
+ "The DBus daemon connection",
+ "The connection to the DBus daemon owning the CM",
+ TP_TYPE_DBUS_DAEMON,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DBUS_DAEMON, param_spec);
+
+ tpy_call_stream_endpoint_class->dbus_props_class.interfaces =
+ prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpyCallStreamEndpointClass, dbus_props_class));
+}
+
+void
+tpy_call_stream_endpoint_dispose (GObject *object)
+{
+ TpyCallStreamEndpoint *self = TPY_CALL_STREAM_ENDPOINT (object);
+ TpyCallStreamEndpointPrivate *priv = self->priv;
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ tp_clear_object (&priv->dbus_daemon);
+
+ if (G_OBJECT_CLASS (tpy_call_stream_endpoint_parent_class)->dispose)
+ G_OBJECT_CLASS (tpy_call_stream_endpoint_parent_class)->dispose (
+ object);
+}
+
+void
+tpy_call_stream_endpoint_finalize (GObject *object)
+{
+ TpyCallStreamEndpoint *self = TPY_CALL_STREAM_ENDPOINT (object);
+ TpyCallStreamEndpointPrivate *priv = self->priv;
+
+ /* free any data held directly by the object here */
+ g_free (priv->object_path);
+
+ g_boxed_free (TPY_STRUCT_TYPE_CANDIDATE, priv->selected_candidate);
+ g_boxed_free (TPY_STRUCT_TYPE_STREAM_CREDENTIALS,
+ priv->remote_credentials);
+ g_boxed_free (TPY_ARRAY_TYPE_CANDIDATE_LIST, priv->remote_candidates);
+
+ G_OBJECT_CLASS (tpy_call_stream_endpoint_parent_class)->finalize (object);
+}
+
+static void
+call_stream_endpoint_set_stream_state (TpySvcCallStreamEndpoint *iface,
+ TpMediaStreamState state,
+ DBusGMethodInvocation *context)
+{
+ TpyCallStreamEndpoint *self = TPY_CALL_STREAM_ENDPOINT (iface);
+
+ if (state >= NUM_TP_MEDIA_STREAM_STATES)
+ {
+ GError *error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Stream state %d is out of the valid range.", state);
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ self->priv->stream_state = state;
+ g_object_notify (G_OBJECT (self), "stream-state");
+
+ tpy_svc_call_stream_endpoint_emit_stream_state_changed (self, state);
+ tpy_svc_call_stream_endpoint_return_from_set_stream_state (context);
+}
+
+static void
+call_stream_endpoint_set_selected_candidate (
+ TpySvcCallStreamEndpoint *iface,
+ const GValueArray *candidate,
+ DBusGMethodInvocation *context)
+{
+ TpyCallStreamEndpoint *self = TPY_CALL_STREAM_ENDPOINT (iface);
+ GValueArray *va = (GValueArray *) candidate;
+ GValue *value;
+ GError *error = NULL;
+
+ if (candidate->n_values != 4)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "A candidate should have 4 values, got %d", candidate->n_values);
+ goto error;
+ }
+
+ value = g_value_array_get_nth (va, 0);
+ if (g_value_get_uint (value) > 2)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Invalid component id: %d", g_value_get_uint (value));
+ goto error;
+ }
+
+ value = g_value_array_get_nth (va, 1);
+ if (g_value_get_string (value) == NULL ||
+ g_value_get_string (value)[0] == 0)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Invalid address: %s", g_value_get_string (value));
+ goto error;
+ }
+
+ value = g_value_array_get_nth (va, 2);
+ if (g_value_get_uint (value) > 65535)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Invalid port: %d", g_value_get_uint (value));
+ goto error;
+ }
+
+ g_boxed_free (TPY_STRUCT_TYPE_CANDIDATE,
+ self->priv->selected_candidate);
+ self->priv->selected_candidate =
+ g_boxed_copy (TPY_STRUCT_TYPE_CANDIDATE, candidate);
+ g_object_notify (G_OBJECT (self), "selected-candidate");
+
+ tpy_svc_call_stream_endpoint_emit_candidate_selected (self, candidate);
+ tpy_svc_call_stream_endpoint_return_from_set_selected_candidate (context);
+ return;
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+static void
+call_stream_endpoint_iface_init (gpointer iface, gpointer data)
+{
+ TpySvcCallStreamEndpointClass *klass =
+ (TpySvcCallStreamEndpointClass *) iface;
+
+#define IMPLEMENT(x) tpy_svc_call_stream_endpoint_implement_##x (\
+ klass, call_stream_endpoint_##x)
+ IMPLEMENT(set_stream_state);
+ IMPLEMENT(set_selected_candidate);
+#undef IMPLEMENT
+}
+
+TpyCallStreamEndpoint *
+tpy_call_stream_endpoint_new (TpDBusDaemon *dbus_daemon,
+ const gchar *object_path,
+ TpyStreamTransportType type)
+{
+ return g_object_new (TPY_TYPE_CALL_STREAM_ENDPOINT,
+ "dbus-daemon", dbus_daemon,
+ "object-path", object_path,
+ "transport", type,
+ NULL);
+}
+
+const gchar *
+tpy_call_stream_endpoint_get_object_path (
+ TpyCallStreamEndpoint *endpoint)
+{
+ return endpoint->priv->object_path;
+}
+
+void
+tpy_call_stream_endpoint_add_new_candidates (
+ TpyCallStreamEndpoint *self,
+ GPtrArray *candidates)
+{
+ guint i;
+ GValueArray *c;
+
+ if (candidates == NULL)
+ return;
+
+ for (i = 0; i < candidates->len; i++)
+ {
+ c = g_boxed_copy (TPY_STRUCT_TYPE_CANDIDATE,
+ g_ptr_array_index (candidates, i));
+ g_ptr_array_add (self->priv->remote_candidates, c);
+ }
+
+ tpy_svc_call_stream_endpoint_emit_remote_candidates_added (self,
+ candidates);
+}
+
+void tpy_call_stream_endpoint_add_new_candidate (
+ TpyCallStreamEndpoint *self,
+ guint component,
+ const gchar *address,
+ guint port,
+ const GHashTable *info_hash)
+{
+ GPtrArray *candidates = g_ptr_array_sized_new (1);
+ GValueArray *a;
+
+ a = tp_value_array_build (4,
+ G_TYPE_UINT, component,
+ G_TYPE_STRING, address,
+ G_TYPE_UINT, port,
+ TPY_HASH_TYPE_CANDIDATE_INFO, info_hash,
+ G_TYPE_INVALID);
+
+ g_ptr_array_add (candidates, a);
+
+ tpy_call_stream_endpoint_add_new_candidates (self, candidates);
+
+ g_boxed_free (TPY_ARRAY_TYPE_CANDIDATE_LIST, candidates);
+}
diff --git a/yell/telepathy-yell/call-stream-endpoint.h b/yell/telepathy-yell/call-stream-endpoint.h
new file mode 100644
index 000000000..063924f65
--- /dev/null
+++ b/yell/telepathy-yell/call-stream-endpoint.h
@@ -0,0 +1,89 @@
+/*
+ * call-stream-endpoint.h - Header for TpyCallStreamEndpoint
+ * Copyright (C) 2009 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@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
+ */
+
+#ifndef __TPY_CALL_STREAM_ENDPOINT_H__
+#define __TPY_CALL_STREAM_ENDPOINT_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/dbus-properties-mixin.h>
+#include <telepathy-glib/dbus.h>
+
+#include <telepathy-yell/enums.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpyCallStreamEndpoint TpyCallStreamEndpoint;
+typedef struct _TpyCallStreamEndpointPrivate
+ TpyCallStreamEndpointPrivate;
+typedef struct _TpyCallStreamEndpointClass TpyCallStreamEndpointClass;
+
+struct _TpyCallStreamEndpointClass {
+ GObjectClass parent_class;
+
+ TpDBusPropertiesMixinClass dbus_props_class;
+};
+
+struct _TpyCallStreamEndpoint {
+ GObject parent;
+
+ TpyCallStreamEndpointPrivate *priv;
+};
+
+GType tpy_call_stream_endpoint_get_type (void);
+
+/* TYPE MACROS */
+#define TPY_TYPE_CALL_STREAM_ENDPOINT \
+ (tpy_call_stream_endpoint_get_type ())
+#define TPY_CALL_STREAM_ENDPOINT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ TPY_TYPE_CALL_STREAM_ENDPOINT, TpyCallStreamEndpoint))
+#define TPY_CALL_STREAM_ENDPOINT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ TPY_TYPE_CALL_STREAM_ENDPOINT, TpyCallStreamEndpointClass))
+#define TPY_IS_CALL_STREAM_ENDPOINT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPY_TYPE_CALL_STREAM_ENDPOINT))
+#define TPY_IS_CALL_STREAM_ENDPOINT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TPY_TYPE_CALL_STREAM_ENDPOINT))
+#define TPY_CALL_STREAM_ENDPOINT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TPY_TYPE_CALL_STREAM_ENDPOINT, TpyCallStreamEndpointClass))
+
+TpyCallStreamEndpoint *tpy_call_stream_endpoint_new (
+ TpDBusDaemon *dbus_daemon,
+ const gchar *object_path,
+ TpyStreamTransportType type);
+
+void tpy_call_stream_endpoint_add_new_candidates (
+ TpyCallStreamEndpoint *endpoint,
+ GPtrArray *candidates);
+void tpy_call_stream_endpoint_add_new_candidate (
+ TpyCallStreamEndpoint *endpoint,
+ TpyStreamComponent component,
+ const gchar *address,
+ guint port,
+ const GHashTable *info_hash);
+
+const gchar *tpy_call_stream_endpoint_get_object_path (
+ TpyCallStreamEndpoint *endpoint);
+
+G_END_DECLS
+
+#endif /* #ifndef __TPY_CALL_STREAM_ENDPOINT_H__*/
diff --git a/yell/telepathy-yell/call-stream.c b/yell/telepathy-yell/call-stream.c
new file mode 100644
index 000000000..9a045811a
--- /dev/null
+++ b/yell/telepathy-yell/call-stream.c
@@ -0,0 +1,392 @@
+/*
+ * call-stream.c - Source for TpyCallStream
+ * Copyright © 2009–2011 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ * @author Will Thompson <will.thompson@collabora.co.uk>
+ * @author Emilio Pozuelo Monfort <emilio.pozuelo@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
+ */
+
+#include "call-stream.h"
+
+#define DEBUG_FLAG TPY_DEBUG_CALL
+#include "debug.h"
+
+#include <telepathy-yell/interfaces.h>
+#include <telepathy-yell/gtypes.h>
+#include <telepathy-yell/cli-call.h>
+
+G_DEFINE_TYPE(TpyCallStream, tpy_call_stream, TP_TYPE_PROXY)
+
+enum
+{
+ PROP_REMOTE_MEMBERS = 1,
+ PROP_LOCAL_SENDING_STATE,
+ PROP_CAN_REQUEST_RECEIVING,
+ PROP_READY,
+};
+
+struct _TpyCallStreamPrivate
+{
+ GHashTable *remote_members;
+ TpySendingState local_sending_state;
+ gboolean can_request_receiving;
+ gboolean ready;
+
+ GSimpleAsyncResult *result;
+};
+
+static void
+on_call_stream_get_all_properties_cb (TpProxy *proxy,
+ GHashTable *properties,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpyCallStream *self = TPY_CALL_STREAM (proxy);
+ GHashTable *members;
+
+ if (error != NULL)
+ {
+ g_warning ("Could not get the stream properties: %s", error->message);
+ return;
+ }
+
+ self->priv->local_sending_state = tp_asv_get_uint32 (properties,
+ "LocalSendingState", NULL);
+ self->priv->can_request_receiving = tp_asv_get_boolean (properties,
+ "CanRequestReceiving", NULL);
+
+ tp_clear_pointer (&self->priv->remote_members, g_hash_table_unref);
+ members = tp_asv_get_boxed (properties,
+ "RemoteMembers", TPY_HASH_TYPE_CALL_MEMBER_MAP);
+ if (members != NULL)
+ self->priv->remote_members =
+ g_boxed_copy (TPY_HASH_TYPE_CALL_MEMBER_MAP, members);
+
+ self->priv->ready = TRUE;
+ g_object_notify (G_OBJECT (self), "ready");
+}
+
+static void
+on_remote_members_changed_cb (TpProxy *proxy,
+ GHashTable *updates,
+ const GArray *removed,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpyCallStream *self = TPY_CALL_STREAM (proxy);
+ GHashTableIter iter;
+ gpointer key, value;
+ guint i;
+
+ for (i = 0; i < removed->len; i++)
+ g_hash_table_remove (self->priv->remote_members,
+ GUINT_TO_POINTER (g_array_index (removed, TpHandle, i)));
+
+ g_hash_table_iter_init (&iter, updates);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ g_hash_table_insert (self->priv->remote_members, key, value);
+
+ g_object_notify (G_OBJECT (self), "remote-members");
+}
+
+static void
+on_local_sending_state_changed_cb (TpProxy *proxy,
+ guint state,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpyCallStream *self = TPY_CALL_STREAM (proxy);
+
+ if (self->priv->local_sending_state == state)
+ return;
+
+ self->priv->local_sending_state = state;
+ g_object_notify (G_OBJECT (self), "local-sending-state");
+}
+
+static void
+tpy_call_stream_constructed (GObject *obj)
+{
+ TpyCallStream *self = (TpyCallStream *) obj;
+ GError *err = NULL;
+
+ ((GObjectClass *) tpy_call_stream_parent_class)->constructed (obj);
+
+ tpy_cli_call_stream_connect_to_remote_members_changed (TP_PROXY (self),
+ on_remote_members_changed_cb, NULL, NULL, G_OBJECT (self), &err);
+
+ if (err != NULL)
+ {
+ g_critical ("Failed to connect to RemoteMembersChanged signal: %s",
+ err->message);
+
+ g_error_free (err);
+ return;
+ }
+
+ tpy_cli_call_stream_connect_to_local_sending_state_changed (TP_PROXY (self),
+ on_local_sending_state_changed_cb, NULL, NULL, G_OBJECT (self), &err);
+
+ if (err != NULL)
+ {
+ g_critical ("Failed to connect to LocalSendingStateChanged signal: %s",
+ err->message);
+
+ g_error_free (err);
+ return;
+ }
+
+ tp_cli_dbus_properties_call_get_all (self, -1,
+ TPY_IFACE_CALL_STREAM,
+ on_call_stream_get_all_properties_cb, NULL, NULL, G_OBJECT (self));
+}
+
+static void
+tpy_call_stream_init (TpyCallStream *self)
+{
+ TpyCallStreamPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPY_TYPE_CALL_STREAM, TpyCallStreamPrivate);
+
+ self->priv = priv;
+ priv->remote_members = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+static void
+tpy_call_stream_dispose (GObject *object)
+{
+ TpyCallStream *self = TPY_CALL_STREAM (object);
+
+ tp_clear_object (&self->priv->result);
+ tp_clear_pointer (&self->priv->remote_members, g_hash_table_unref);
+
+ G_OBJECT_CLASS (tpy_call_stream_parent_class)->dispose (object);
+}
+
+static void
+tpy_call_stream_get_property (
+ GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TpyCallStream *self = TPY_CALL_STREAM (object);
+ TpyCallStreamPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_REMOTE_MEMBERS:
+ g_value_set_boxed (value, priv->remote_members);
+ break;
+ case PROP_LOCAL_SENDING_STATE:
+ g_value_set_uint (value, priv->local_sending_state);
+ break;
+ case PROP_CAN_REQUEST_RECEIVING:
+ g_value_set_boolean (value, priv->can_request_receiving);
+ break;
+ case PROP_READY:
+ g_value_set_boolean (value, priv->ready);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tpy_call_stream_set_property (
+ GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TpyCallStream *self = TPY_CALL_STREAM (object);
+
+ switch (property_id)
+ {
+ case PROP_LOCAL_SENDING_STATE:
+ self->priv->local_sending_state = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tpy_call_stream_class_init (TpyCallStreamClass *bsc_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (bsc_class);
+ TpProxyClass *proxy_class = (TpProxyClass *) bsc_class;
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (bsc_class, sizeof (TpyCallStreamPrivate));
+
+ object_class->dispose = tpy_call_stream_dispose;
+ object_class->constructed = tpy_call_stream_constructed;
+ object_class->set_property = tpy_call_stream_set_property;
+ object_class->get_property = tpy_call_stream_get_property;
+
+ proxy_class->interface = TPY_IFACE_QUARK_CALL_STREAM;
+
+ param_spec = g_param_spec_boxed ("remote-members", "Remote members",
+ "Remote member map",
+ TPY_HASH_TYPE_CONTACT_SENDING_STATE_MAP,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REMOTE_MEMBERS,
+ param_spec);
+
+ param_spec = g_param_spec_uint ("local-sending-state", "LocalSendingState",
+ "Local sending state",
+ 0, NUM_TPY_SENDING_STATES, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_LOCAL_SENDING_STATE,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("can-request-receiving",
+ "CanRequestReceiving",
+ "If true, the user can request that a remote contact starts sending on"
+ "this stream.",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CAN_REQUEST_RECEIVING,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("ready",
+ "Ready",
+ "If true the stream has retrieved all async information from the CM",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_READY,
+ param_spec);
+}
+
+TpySendingState
+tpy_call_stream_get_local_sending_state (
+ TpyCallStream *self)
+{
+ return self->priv->local_sending_state;
+}
+
+static void
+on_set_sending_cb (TpProxy *proxy,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpyCallStream *self = TPY_CALL_STREAM (proxy);
+
+ if (error != NULL)
+ {
+ DEBUG ("Failed to set sending: %s", error->message);
+
+ g_simple_async_result_set_from_error (self->priv->result, error);
+ }
+
+ g_simple_async_result_set_op_res_gboolean (self->priv->result, TRUE);
+ g_simple_async_result_complete (self->priv->result);
+ tp_clear_object (&self->priv->result);
+}
+
+void
+tpy_call_stream_set_sending_async (TpyCallStream *self,
+ gboolean send,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (TPY_IS_CALL_STREAM (self));
+ g_return_if_fail (self->priv->result == NULL);
+
+ self->priv->result = g_simple_async_result_new (G_OBJECT (self), callback,
+ user_data, tpy_call_stream_set_sending_async);
+
+ tpy_cli_call_stream_call_set_sending (TP_PROXY (self), -1,
+ send,
+ on_set_sending_cb, NULL, NULL, G_OBJECT (self));
+}
+
+gboolean
+tpy_call_stream_set_sending_finish (TpyCallStream *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
+ error))
+ return FALSE;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (self), tpy_call_stream_set_sending_async),
+ FALSE);
+
+ return g_simple_async_result_get_op_res_gboolean (
+ G_SIMPLE_ASYNC_RESULT (result));
+}
+
+static void
+on_request_receiving_cb (TpProxy *proxy,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpyCallStream *self = TPY_CALL_STREAM (proxy);
+
+ if (error != NULL)
+ {
+ DEBUG ("Failed to request receiving: %s", error->message);
+
+ g_simple_async_result_set_from_error (self->priv->result, error);
+ }
+
+ g_simple_async_result_set_op_res_gboolean (self->priv->result, TRUE);
+ g_simple_async_result_complete (self->priv->result);
+ tp_clear_object (&self->priv->result);
+}
+
+void
+tpy_call_stream_request_receiving_async (TpyCallStream *self,
+ TpHandle handle,
+ gboolean receiving,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (TPY_IS_CALL_STREAM (self));
+ g_return_if_fail (self->priv->result == NULL);
+
+ self->priv->result = g_simple_async_result_new (G_OBJECT (self), callback,
+ user_data, tpy_call_stream_request_receiving_async);
+
+ tpy_cli_call_stream_call_request_receiving (TP_PROXY (self), -1,
+ handle, receiving,
+ on_request_receiving_cb, NULL, NULL, G_OBJECT (self));
+}
+
+gboolean
+tpy_call_stream_request_receiving_finish (TpyCallStream *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
+ error))
+ return FALSE;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (self), tpy_call_stream_request_receiving_async),
+ FALSE);
+
+ return g_simple_async_result_get_op_res_gboolean (
+ G_SIMPLE_ASYNC_RESULT (result));
+}
diff --git a/yell/telepathy-yell/call-stream.h b/yell/telepathy-yell/call-stream.h
new file mode 100644
index 000000000..369ed68bc
--- /dev/null
+++ b/yell/telepathy-yell/call-stream.h
@@ -0,0 +1,88 @@
+/*
+ * call-stream.h - Header for TpyCallStream
+ * Copyright © 2009–2011 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ * @author Will Thompson <will.thompson@collabora.co.uk>
+ * @author Emilio Pozuelo Monfort <emilio.pozuelo@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
+ */
+
+#ifndef TPY_CALL_STREAM_H
+#define TPY_CALL_STREAM_H
+
+#include <glib-object.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include <telepathy-yell/enums.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpyCallStream TpyCallStream;
+typedef struct _TpyCallStreamPrivate TpyCallStreamPrivate;
+typedef struct _TpyCallStreamClass TpyCallStreamClass;
+
+struct _TpyCallStreamClass {
+ TpProxyClass parent_class;
+};
+
+struct _TpyCallStream {
+ TpProxy parent;
+
+ TpyCallStreamPrivate *priv;
+};
+
+GType tpy_call_stream_get_type (void);
+
+/* TYPE MACROS */
+#define TPY_TYPE_CALL_STREAM \
+ (tpy_call_stream_get_type ())
+#define TPY_CALL_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TPY_TYPE_CALL_STREAM, TpyCallStream))
+#define TPY_CALL_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TPY_TYPE_CALL_STREAM, \
+ TpyCallStreamClass))
+#define TPY_IS_CALL_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPY_TYPE_CALL_STREAM))
+#define TPY_IS_CALL_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TPY_TYPE_CALL_STREAM))
+#define TPY_CALL_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TPY_TYPE_CALL_STREAM, \
+ TpyCallStreamClass))
+
+TpySendingState tpy_call_stream_get_local_sending_state (
+ TpyCallStream *self);
+
+void tpy_call_stream_set_sending_async (TpyCallStream *self,
+ gboolean send,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean tpy_call_stream_set_sending_finish (TpyCallStream *self,
+ GAsyncResult *result,
+ GError **error);
+
+void tpy_call_stream_request_receiving_async (TpyCallStream *self,
+ TpHandle handle,
+ gboolean receiving,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean tpy_call_stream_request_receiving_finish (TpyCallStream *self,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif
diff --git a/yell/telepathy-yell/call.xml b/yell/telepathy-yell/call.xml
new file mode 100644
index 000000000..283a2f46e
--- /dev/null
+++ b/yell/telepathy-yell/call.xml
@@ -0,0 +1,18 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Call interfaces</tp:title>
+
+<xi:include href="../spec/Call_Content.xml" />
+<xi:include href="../spec/Call_Content_Codec_Offer.xml" />
+<xi:include href="../spec/Call_Content_Interface_Audio_Control.xml" />
+<xi:include href="../spec/Call_Content_Interface_Media.xml" />
+<xi:include href="../spec/Call_Content_Interface_Mute.xml" />
+<xi:include href="../spec/Call_Content_Interface_Video_Control.xml" />
+<xi:include href="../spec/Call_Stream.xml" />
+<xi:include href="../spec/Call_Stream_Endpoint.xml" />
+<xi:include href="../spec/Call_Stream_Interface_Media.xml" />
+<xi:include href="../spec/Channel_Type_Call.xml" />
+
+</tp:spec>
diff --git a/yell/telepathy-yell/cli-call.h b/yell/telepathy-yell/cli-call.h
new file mode 100644
index 000000000..4b0c0bcec
--- /dev/null
+++ b/yell/telepathy-yell/cli-call.h
@@ -0,0 +1,6 @@
+#ifndef __TPY_META_CLI_CALL_H__
+#define __TPY_META_CLI_CALL_H__
+
+#include <telepathy-yell/_gen/cli-call.h>
+
+#endif
diff --git a/yell/telepathy-yell/debug-internal.h b/yell/telepathy-yell/debug-internal.h
new file mode 100644
index 000000000..cdcb093f3
--- /dev/null
+++ b/yell/telepathy-yell/debug-internal.h
@@ -0,0 +1,3 @@
+/* Minimal version of telepathy-glib's debug-internal.h */
+
+#define DEBUG(fmt, ...) g_debug ("%s: " fmt, G_STRFUNC , ##__VA_ARGS__)
diff --git a/yell/telepathy-yell/debug.c b/yell/telepathy-yell/debug.c
new file mode 100644
index 000000000..f5de1e6c4
--- /dev/null
+++ b/yell/telepathy-yell/debug.c
@@ -0,0 +1,93 @@
+/*
+ * debug.c - debugging utilities for telepathy-yell
+ * Copyright (C) 2010 Collabora Ltd.
+ * @author Jonathon Jongsma <jonathon.jongsma@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
+ */
+
+#include <config.h>
+
+#include <telepathy-glib/debug.h>
+#include <telepathy-glib/debug-sender.h>
+#include "debug.h"
+
+static TpyDebugFlags flags = 0;
+
+static GDebugKey debug_keys[] = {
+ {"call", TPY_DEBUG_CALL},
+ {NULL, 0}
+};
+
+void
+tpy_debug_set_flags (const char *flags_string)
+{
+ guint nkeys;
+
+ for (nkeys = 0; debug_keys[nkeys].value; nkeys++);
+
+ flags |= g_parse_debug_string (flags_string, debug_keys, nkeys);
+}
+
+static const char *
+debug_flag_to_domain (TpyDebugFlags flag)
+{
+ static GHashTable *flag_to_domains = NULL;
+
+ if (G_UNLIKELY (flag_to_domains == NULL))
+ {
+ guint i;
+
+ flag_to_domains = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_free);
+
+ for (i = 0; debug_keys[i].value; i++)
+ {
+ GDebugKey key = debug_keys[i];
+ char *val = g_strdup_printf ("%s/%s", G_LOG_DOMAIN, key.key);
+
+ g_hash_table_insert (flag_to_domains,
+ GUINT_TO_POINTER (key.value), val);
+ }
+ }
+
+ return g_hash_table_lookup (flag_to_domains, GUINT_TO_POINTER (flag));
+}
+
+void tpy_log (GLogLevelFlags level,
+ TpyDebugFlags flag,
+ const gchar *format,
+ ...)
+{
+ TpDebugSender *debug_sender = tp_debug_sender_dup ();
+ char *message;
+ va_list args;
+ GTimeVal now;
+
+ va_start (args, format);
+ message = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ if (flag & flags)
+ g_log (G_LOG_DOMAIN, level, "%s", message);
+
+ g_get_current_time (&now);
+
+ tp_debug_sender_add_message (debug_sender, &now, debug_flag_to_domain (flag),
+ level, message);
+
+ g_free (message);
+ g_object_unref (debug_sender);
+}
diff --git a/yell/telepathy-yell/debug.h b/yell/telepathy-yell/debug.h
new file mode 100644
index 000000000..990911534
--- /dev/null
+++ b/yell/telepathy-yell/debug.h
@@ -0,0 +1,64 @@
+/*
+ * debug.h - debugging utilities for telepathy-yell
+ * Copyright (C) 2010 Collabora Ltd.
+ * @author Jonathon Jongsma <jonathon.jongsma@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
+ */
+
+#ifndef __TPY_DEBUG_H__
+#define __TPY_DEBUG_H__
+
+#include "config.h"
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ TPY_DEBUG_CALL = 1 << 0
+} TpyDebugFlags;
+
+void tpy_debug_set_flags (const char *flags_string);
+
+void tpy_log (GLogLevelFlags level, TpyDebugFlags flag,
+ const gchar *format, ...) G_GNUC_PRINTF(3, 4);
+
+#ifdef DEBUG_FLAG
+
+#define ERROR(format, ...) \
+ tpy_log (G_LOG_LEVEL_ERROR, DEBUG_FLAG, "%s: " format, \
+ G_STRFUNC, ##__VA_ARGS__)
+#define CRITICAL(format, ...) \
+ tpy_log (G_LOG_LEVEL_CRITICAL, DEBUG_FLAG, "%s: " format, \
+ G_STRFUNC, ##__VA_ARGS__)
+#define WARNING(format, ...) \
+ tpy_log (G_LOG_LEVEL_WARNING, DEBUG_FLAG, "%s: " format, \
+ G_STRFUNC, ##__VA_ARGS__)
+#define MESSAGE(format, ...) \
+ tpy_log (G_LOG_LEVEL_MESSAGE, DEBUG_FLAG, "%s: " format, \
+ G_STRFUNC, ##__VA_ARGS__)
+#define INFO(format, ...) \
+ tpy_log (G_LOG_LEVEL_INFO, DEBUG_FLAG, "%s: " format, \
+ G_STRFUNC, ##__VA_ARGS__)
+#define DEBUG(format, ...) \
+ tpy_log (G_LOG_LEVEL_DEBUG, DEBUG_FLAG, "%s: " format, \
+ G_STRFUNC, ##__VA_ARGS__)
+
+#endif /* DEBUG_FLAG */
+
+G_END_DECLS
+
+#endif /* __TPY_DEBUG_H__ */
diff --git a/yell/telepathy-yell/enums.h b/yell/telepathy-yell/enums.h
new file mode 100644
index 000000000..b939ed82d
--- /dev/null
+++ b/yell/telepathy-yell/enums.h
@@ -0,0 +1,6 @@
+#ifndef __TPY_ENUMS_H__
+#define __TPY_ENUMS_H__
+
+#include <telepathy-yell/_gen/enums.h>
+
+#endif
diff --git a/yell/telepathy-yell/extensions-cli.c b/yell/telepathy-yell/extensions-cli.c
new file mode 100644
index 000000000..076ef2929
--- /dev/null
+++ b/yell/telepathy-yell/extensions-cli.c
@@ -0,0 +1,33 @@
+#include "extensions.h"
+
+#include <telepathy-glib/channel.h>
+#include <telepathy-glib/connection.h>
+#include <telepathy-glib/proxy-subclass.h>
+
+#include "_gen/signals-marshal.h"
+
+/* include auto-generated stubs for client-specific code */
+#include "_gen/cli-call-body.h"
+#include "_gen/register-dbus-glib-marshallers-body.h"
+
+static gpointer
+tpy_cli_once (gpointer unused)
+{
+ _tpy_register_dbus_glib_marshallers ();
+
+ tp_channel_init_known_interfaces ();
+ tp_connection_init_known_interfaces ();
+
+ tp_proxy_or_subclass_hook_on_interface_add (TP_TYPE_PROXY,
+ tpy_cli_call_add_signals);
+
+ return NULL;
+}
+
+void
+tpy_cli_init (void)
+{
+ static GOnce once = G_ONCE_INIT;
+
+ g_once (&once, tpy_cli_once, NULL);
+}
diff --git a/yell/telepathy-yell/extensions.c b/yell/telepathy-yell/extensions.c
new file mode 100644
index 000000000..eeda4623c
--- /dev/null
+++ b/yell/telepathy-yell/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/yell/telepathy-yell/extensions.h b/yell/telepathy-yell/extensions.h
new file mode 100644
index 000000000..d39db79fc
--- /dev/null
+++ b/yell/telepathy-yell/extensions.h
@@ -0,0 +1,21 @@
+#ifndef __TELEPATHY_YELL_EXTENSIONS_H__
+#define __TELEPATHY_YELL_EXTENSIONS_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/channel.h>
+#include <telepathy-glib/connection.h>
+
+#include <telepathy-yell/enums.h>
+#include <telepathy-yell/cli-call.h>
+#include <telepathy-yell/svc-call.h>
+
+G_BEGIN_DECLS
+
+#include <telepathy-yell/gtypes.h>
+#include <telepathy-yell/interfaces.h>
+
+void tpy_cli_init (void);
+
+G_END_DECLS
+
+#endif
diff --git a/yell/telepathy-yell/gtypes.h b/yell/telepathy-yell/gtypes.h
new file mode 100644
index 000000000..78a4964d2
--- /dev/null
+++ b/yell/telepathy-yell/gtypes.h
@@ -0,0 +1,10 @@
+#ifndef __TPY_GTYPES_H__
+#define __TPY_GTYPES_H__
+
+G_BEGIN_DECLS
+
+#include <telepathy-yell/_gen/gtypes.h>
+
+G_END_DECLS
+
+#endif
diff --git a/yell/telepathy-yell/interfaces.h b/yell/telepathy-yell/interfaces.h
new file mode 100644
index 000000000..1e6345b94
--- /dev/null
+++ b/yell/telepathy-yell/interfaces.h
@@ -0,0 +1,12 @@
+#ifndef __TPY_INTERFACES_H__
+#define __TPY_INTERFACES_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#include <telepathy-yell/_gen/interfaces.h>
+
+G_END_DECLS
+
+#endif
diff --git a/yell/telepathy-yell/svc-call.h b/yell/telepathy-yell/svc-call.h
new file mode 100644
index 000000000..2587a94d2
--- /dev/null
+++ b/yell/telepathy-yell/svc-call.h
@@ -0,0 +1,6 @@
+#ifndef __TPY_META_SVC_CALL_H__
+#define __TPY_META_SVC_CALL_H__
+
+#include <telepathy-yell/_gen/svc-call.h>
+
+#endif
diff --git a/yell/telepathy-yell/telepathy-yell-uninstalled.pc.in b/yell/telepathy-yell/telepathy-yell-uninstalled.pc.in
new file mode 100644
index 000000000..b77f3d49c
--- /dev/null
+++ b/yell/telepathy-yell/telepathy-yell-uninstalled.pc.in
@@ -0,0 +1,12 @@
+prefix=
+exec_prefix=
+abs_top_srcdir=@abs_top_srcdir@
+abs_top_builddir=@abs_top_builddir@
+
+Name: Telepathy Yell (uninstalled copy)
+Description: Extra Call telepathy interfaces
+Version: @VERSION@
+Requires: pkg-config >= 0.21
+Requires.private: dbus-glib-1 glib-2.0 gobject-2.0 telepathy-glib >= 0.7.3
+Libs: ${abs_top_builddir}/telepathy-yell/libtelepathy-yell.la
+Cflags: -I${abs_top_srcdir} -I${abs_top_builddir}
diff --git a/yell/telepathy-yell/telepathy-yell.h b/yell/telepathy-yell/telepathy-yell.h
new file mode 100644
index 000000000..3eff99ce6
--- /dev/null
+++ b/yell/telepathy-yell/telepathy-yell.h
@@ -0,0 +1,34 @@
+/*
+ * telepathy-yell.h - Headers for telepathy-yell
+ *
+ * Copyright (C) 2010 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
+ */
+
+#ifndef __TELEPATHY_YELL_H__
+#define __TELEPATHY_YELL_H__
+
+#include <telepathy-yell/enums.h>
+#include <telepathy-yell/extensions.h>
+#include <telepathy-yell/gtypes.h>
+#include <telepathy-yell/interfaces.h>
+#include <telepathy-yell/cli-call.h>
+#include <telepathy-yell/svc-call.h>
+#include <telepathy-yell/call-stream.h>
+#include <telepathy-yell/call-content.h>
+#include <telepathy-yell/call-channel.h>
+
+#endif
diff --git a/yell/telepathy-yell/telepathy-yell.pc.in b/yell/telepathy-yell/telepathy-yell.pc.in
new file mode 100644
index 000000000..4608de0f4
--- /dev/null
+++ b/yell/telepathy-yell/telepathy-yell.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: Telepathy Yell
+Description: Extra Call telepathy interfaces
+Version: @VERSION@
+Requires: dbus-glib-1 glib-2.0 gobject-2.0 telepathy-glib >= 0.7.3
+Libs: -L${libdir} -ltelepathy-yell
+Cflags: -I${includedir}/telepathy-1.0
diff --git a/yell/tools/Makefile.am b/yell/tools/Makefile.am
new file mode 100644
index 000000000..62298f1a6
--- /dev/null
+++ b/yell/tools/Makefile.am
@@ -0,0 +1,55 @@
+EXTRA_DIST = \
+ c-constants-gen.py \
+ check-coding-style.mk \
+ check-c-style.sh \
+ check-misc.sh \
+ check-whitespace.sh \
+ doc-generator.xsl \
+ glib-client-gen.py \
+ glib-client-marshaller-gen.py \
+ glib-errors-enum-body-gen.py \
+ glib-errors-enum-header-gen.py \
+ glib-ginterface-gen.py \
+ glib-gtypes-generator.py \
+ glib-interfaces-gen.py \
+ glib-signals-marshal-gen.py \
+ make-release-mail.py \
+ gobject-foo.py \
+ identity.xsl \
+ libtpcodegen.py \
+ libglibcodegen.py \
+ make-version-script.py \
+ telepathy.am \
+ with-session-bus.sh
+
+CLEANFILES = libtpcodegen.pyc libtpcodegen.pyo libglibcodegen.pyc libglibcodegen.pyo $(noinst_SCRIPTS)
+
+all: $(EXTRA_DIST)
+
+libglibcodegen.py: libtpcodegen.py
+ touch $@
+c-constants-gen.py: libglibcodegen.py
+ touch $@
+glib-client-marshaller-gen.py: libglibcodegen.py
+ touch $@
+glib-errors-enum-body-gen.py: libglibcodegen.py
+ touch $@
+glib-errors-enum-header-gen.py: libglibcodegen.py
+ touch $@
+glib-ginterface-gen.py: libglibcodegen.py
+ touch $@
+glib-gtypes-generator.py: libglibcodegen.py
+ touch $@
+glib-interfaces-gen.py: libglibcodegen.py
+ touch $@
+glib-signals-marshal-gen.py: libglibcodegen.py
+ touch $@
+
+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/yell/tools/c-constants-gen.py b/yell/tools/c-constants-gen.py
new file mode 100644
index 000000000..ff2a24d47
--- /dev/null
+++ b/yell/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 /*< flags >*/ {\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: (skip)
+ *
+ * 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/yell/tools/check-c-style.sh b/yell/tools/check-c-style.sh
new file mode 100644
index 000000000..4330b1479
--- /dev/null
+++ b/yell/tools/check-c-style.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+fail=0
+
+( . "${tools_dir}"/check-misc.sh ) || fail=$?
+
+if grep -n '^ *GError *\*[[:alpha:]_][[:alnum:]_]* *;' "$@"
+then
+ echo "^^^ The above files contain uninitialized GError*s - they should be"
+ echo " initialized to NULL"
+ fail=1
+fi
+
+# The first regex finds function calls like foo() (as opposed to foo ()).
+# It attempts to ignore string constants (may cause false negatives).
+# The second and third ignore block comments (gtkdoc uses foo() as markup).
+# The fourth ignores cpp so you can
+# #define foo(bar) (_real_foo (__FUNC__, bar)) (cpp insists on foo() style).
+if grep -n '^[^"]*[[:lower:]](' "$@" \
+ | grep -v '^[-[:alnum:]_./]*:[[:digit:]]*: *\*' \
+ | grep -v '^[-[:alnum:]_./]*:[[:digit:]]*: */\*' \
+ | grep -v '^[-[:alnum:]_./]*:[[:digit:]]*: *#'
+then
+ echo "^^^ Our coding style is to use function calls like foo (), not foo()"
+ fail=1
+fi
+
+if grep -En '[(][[:alnum:]_]+ ?\*[)][(]?[[:alpha:]_]' "$@"; then
+ echo "^^^ Our coding style is to have a space between a cast and the "
+ echo " thing being cast"
+ fail=1
+fi
+
+# this only spots casts
+if grep -En '[(][[:alnum:]_]+\*+[)]' "$@"; then
+ echo "^^^ Our coding style is to have a space before the * of pointer types"
+ echo " (regex 1)"
+ fail=1
+fi
+# ... and this only spots variable declarations and function return types
+if grep -En '^ *(static |const |)* *[[:alnum:]_]+\*+([[:alnum:]_]|;|$)' \
+ "$@"; then
+ echo "^^^ Our coding style is to have a space before the * of pointer types"
+ echo " (regex 2)"
+ fail=1
+fi
+
+if grep -n 'g_hash_table_destroy' "$@"; then
+ echo "^^^ Our coding style is to use g_hash_table_unref"
+ fail=1
+fi
+
+for p in "" "ptr_" "byte_"; do
+ if grep -En "g_${p}array_free \(([^ ,]+), TRUE\)" "$@"; then
+ echo "^^^ Our coding style is to use g_${p}array_unref in the case "
+ echo " the underlying C array is not used"
+ fail=1
+ fi
+done
+
+if test -n "$CHECK_FOR_LONG_LINES"
+then
+ if egrep -n '.{80,}' "$@"
+ then
+ echo "^^^ The above files contain long lines"
+ fail=1
+ fi
+fi
+
+exit $fail
diff --git a/yell/tools/check-coding-style.mk b/yell/tools/check-coding-style.mk
new file mode 100644
index 000000000..1c0a60f66
--- /dev/null
+++ b/yell/tools/check-coding-style.mk
@@ -0,0 +1,17 @@
+check-coding-style:
+ @fail=0; \
+ if test -n "$(check_misc_sources)"; then \
+ tools_dir=$(top_srcdir)/tools \
+ sh $(top_srcdir)/tools/check-misc.sh \
+ $(addprefix $(srcdir)/,$(check_misc_sources)) || fail=1; \
+ fi; \
+ if test -n "$(check_c_sources)"; then \
+ tools_dir=$(top_srcdir)/tools \
+ sh $(top_srcdir)/tools/check-c-style.sh \
+ $(addprefix $(srcdir)/,$(check_c_sources)) || fail=1; \
+ fi;\
+ if test yes = "$(ENABLE_CODING_STYLE_CHECKS)"; then \
+ exit "$$fail";\
+ else \
+ exit 0;\
+ fi
diff --git a/yell/tools/check-misc.sh b/yell/tools/check-misc.sh
new file mode 100644
index 000000000..89e8e871a
--- /dev/null
+++ b/yell/tools/check-misc.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+fail=0
+
+( . "${tools_dir}"/check-whitespace.sh ) || fail=$?
+
+if egrep '(Free\s*Software\s*Foundation.*02139|02111-1307)' "$@"
+then
+ echo "^^^ The above files contain the FSF's old address in GPL headers"
+ fail=1
+fi
+
+exit $fail
diff --git a/yell/tools/check-whitespace.sh b/yell/tools/check-whitespace.sh
new file mode 100644
index 000000000..534833126
--- /dev/null
+++ b/yell/tools/check-whitespace.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+fail=0
+
+if grep -n ' $' "$@"
+then
+ echo "^^^ The above files contain unwanted trailing spaces"
+ fail=1
+fi
+
+if grep -n ' ' "$@"
+then
+ echo "^^^ The above files contain tabs"
+ fail=1
+fi
+
+exit $fail
diff --git a/yell/tools/doc-generator.xsl b/yell/tools/doc-generator.xsl
new file mode 100644
index 000000000..76fc96964
--- /dev/null
+++ b/yell/tools/doc-generator.xsl
@@ -0,0 +1,1199 @@
+<!-- Generate HTML documentation from the Telepathy specification.
+The master copy of this stylesheet is in the Telepathy spec 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
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ exclude-result-prefixes="tp html">
+ <!--Don't move the declaration of the HTML namespace up here — XMLNSs
+ don't work ideally in the presence of two things that want to use the
+ absence of a prefix, sadly. -->
+
+ <xsl:param name="allow-undefined-interfaces" select="false()"/>
+
+ <xsl:template match="html:* | @*" mode="html">
+ <xsl:copy>
+ <xsl:apply-templates mode="html"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="tp:type" mode="html">
+ <xsl:call-template name="tp-type">
+ <xsl:with-param name="tp-type" select="string(.)"/>
+ </xsl:call-template>
+ </xsl:template>
+
+ <!-- tp:dbus-ref: reference a D-Bus interface, signal, method or property -->
+ <xsl:template match="tp:dbus-ref" mode="html">
+ <xsl:variable name="name">
+ <xsl:choose>
+ <xsl:when test="@namespace">
+ <xsl:value-of select="@namespace"/>
+ <xsl:text>.</xsl:text>
+ </xsl:when>
+ </xsl:choose>
+ <xsl:value-of select="string(.)"/>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="//interface[@name=$name]
+ or //interface/method[concat(../@name, '.', @name)=$name]
+ or //interface/signal[concat(../@name, '.', @name)=$name]
+ or //interface/property[concat(../@name, '.', @name)=$name]
+ or //interface[@name=concat($name, '.DRAFT')]
+ or //interface/method[
+ concat(../@name, '.', @name)=concat($name, '.DRAFT')]
+ or //interface/signal[
+ concat(../@name, '.', @name)=concat($name, '.DRAFT')]
+ or //interface/property[
+ concat(../@name, '.', @name)=concat($name, '.DRAFT')]
+ ">
+ <a xmlns="http://www.w3.org/1999/xhtml" href="#{$name}">
+ <xsl:value-of select="string(.)"/>
+ </a>
+ </xsl:when>
+
+ <xsl:when test="$allow-undefined-interfaces">
+ <span xmlns="http://www.w3.org/1999/xhtml" title="defined elsewhere">
+ <xsl:value-of select="string(.)"/>
+ </span>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: cannot find D-Bus interface, method, </xsl:text>
+ <xsl:text>signal or property called '</xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>'&#10;</xsl:text>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <!-- tp:member-ref: reference a property of the current interface -->
+ <xsl:template match="tp:member-ref" mode="html">
+ <xsl:variable name="prefix" select="concat(ancestor::interface/@name,
+ '.')"/>
+ <xsl:variable name="name" select="string(.)"/>
+
+ <xsl:if test="not(ancestor::interface)">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: Cannot use tp:member-ref when not in an</xsl:text>
+ <xsl:text> &lt;interface&gt;&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <xsl:choose>
+ <xsl:when test="ancestor::interface/signal[@name=$name]"/>
+ <xsl:when test="ancestor::interface/method[@name=$name]"/>
+ <xsl:when test="ancestor::interface/property[@name=$name]"/>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: interface </xsl:text>
+ <xsl:value-of select="ancestor::interface/@name"/>
+ <xsl:text> has no signal/method/property called </xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <a xmlns="http://www.w3.org/1999/xhtml" href="#{$prefix}{$name}">
+ <xsl:value-of select="$name"/>
+ </a>
+ </xsl:template>
+
+ <xsl:template match="*" mode="identity">
+ <xsl:copy>
+ <xsl:apply-templates mode="identity"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="tp:docstring">
+ <xsl:apply-templates mode="html"/>
+ </xsl:template>
+
+ <xsl:template match="tp:added">
+ <p class="added" xmlns="http://www.w3.org/1999/xhtml">Added in
+ version <xsl:value-of select="@version"/>.
+ <xsl:apply-templates select="node()" mode="html"/></p>
+ </xsl:template>
+
+ <xsl:template match="tp:changed">
+ <xsl:choose>
+ <xsl:when test="node()">
+ <p class="changed" xmlns="http://www.w3.org/1999/xhtml">Changed in
+ version <xsl:value-of select="@version"/>:
+ <xsl:apply-templates select="node()" mode="html"/></p>
+ </xsl:when>
+ <xsl:otherwise>
+ <p class="changed">Changed in version
+ <xsl:value-of select="@version"/></p>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <xsl:template match="tp:deprecated">
+ <p class="deprecated" xmlns="http://www.w3.org/1999/xhtml">Deprecated
+ since version <xsl:value-of select="@version"/>.
+ <xsl:apply-templates select="node()" mode="html"/></p>
+ </xsl:template>
+
+ <xsl:template match="tp:rationale" mode="html">
+ <div xmlns="http://www.w3.org/1999/xhtml" class="rationale">
+ <xsl:apply-templates select="node()" mode="html"/>
+ </div>
+ </xsl:template>
+
+ <xsl:template match="tp:errors">
+ <h1 xmlns="http://www.w3.org/1999/xhtml">Errors</h1>
+ <xsl:apply-templates/>
+ </xsl:template>
+
+ <xsl:template match="tp:generic-types">
+ <h1 xmlns="http://www.w3.org/1999/xhtml">Generic types</h1>
+ <xsl:call-template name="do-types"/>
+ </xsl:template>
+
+ <xsl:template name="do-types">
+ <xsl:if test="tp:simple-type">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">Simple types</h2>
+ <xsl:apply-templates select="tp:simple-type"/>
+ </xsl:if>
+
+ <xsl:if test="tp:enum">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">Enumerated types:</h2>
+ <xsl:apply-templates select="tp:enum"/>
+ </xsl:if>
+
+ <xsl:if test="tp:flags">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">Sets of flags:</h2>
+ <xsl:apply-templates select="tp:flags"/>
+ </xsl:if>
+
+ <xsl:if test="tp:struct">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">Structure types</h2>
+ <xsl:apply-templates select="tp:struct"/>
+ </xsl:if>
+
+ <xsl:if test="tp:mapping">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">Mapping types</h2>
+ <xsl:apply-templates select="tp:mapping"/>
+ </xsl:if>
+
+ <xsl:if test="tp:external-type">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">Types defined elsewhere</h2>
+ <dl><xsl:apply-templates select="tp:external-type"/></dl>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match="tp:error">
+ <h2 xmlns="http://www.w3.org/1999/xhtml"><a name="{concat(../@namespace, '.', translate(@name, ' ', ''))}"></a><xsl:value-of select="concat(../@namespace, '.', translate(@name, ' ', ''))"/></h2>
+ <xsl:apply-templates select="tp:docstring"/>
+ <xsl:apply-templates select="tp:added"/>
+ <xsl:apply-templates select="tp:changed"/>
+ <xsl:apply-templates select="tp:deprecated"/>
+ </xsl:template>
+
+ <xsl:template match="/tp:spec/tp:copyright">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates mode="text"/>
+ </div>
+ </xsl:template>
+ <xsl:template match="/tp:spec/tp:license">
+ <div xmlns="http://www.w3.org/1999/xhtml" class="license">
+ <xsl:apply-templates mode="html"/>
+ </div>
+ </xsl:template>
+
+ <xsl:template match="tp:copyright"/>
+ <xsl:template match="tp:license"/>
+
+ <xsl:template match="interface">
+ <h1 xmlns="http://www.w3.org/1999/xhtml"><a name="{@name}"></a><xsl:value-of select="@name"/></h1>
+
+ <xsl:if test="@tp:causes-havoc">
+ <p xmlns="http://www.w3.org/1999/xhtml" class="causes-havoc">
+ This interface is <xsl:value-of select="@tp:causes-havoc"/>
+ and is likely to cause havoc to your API/ABI if bindings are generated.
+ Don't include it in libraries that care about compatibility.
+ </p>
+ </xsl:if>
+
+ <xsl:if test="tp:requires">
+ <p>Implementations of this interface must also implement:</p>
+ <ul xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:for-each select="tp:requires">
+ <li><code><a href="#{@interface}"><xsl:value-of select="@interface"/></a></code></li>
+ </xsl:for-each>
+ </ul>
+ </xsl:if>
+
+ <xsl:apply-templates select="tp:docstring" />
+ <xsl:apply-templates select="tp:added"/>
+ <xsl:apply-templates select="tp:changed"/>
+ <xsl:apply-templates select="tp:deprecated"/>
+
+ <xsl:choose>
+ <xsl:when test="method">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">Methods:</h2>
+ <xsl:apply-templates select="method"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <p xmlns="http://www.w3.org/1999/xhtml">Interface has no methods.</p>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:choose>
+ <xsl:when test="signal">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">Signals:</h2>
+ <xsl:apply-templates select="signal"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <p xmlns="http://www.w3.org/1999/xhtml">Interface has no signals.</p>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:choose>
+ <xsl:when test="tp:property">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">Telepathy Properties:</h2>
+ <p xmlns="http://www.w3.org/1999/xhtml">Accessed using the
+ <a href="#org.freedesktop.Telepathy.Properties">Telepathy
+ Properties</a> interface.</p>
+ <dl xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="tp:property"/>
+ </dl>
+ </xsl:when>
+ <xsl:otherwise>
+ <p xmlns="http://www.w3.org/1999/xhtml">Interface has no Telepathy
+ properties.</p>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:choose>
+ <xsl:when test="property">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">D-Bus core Properties:</h2>
+ <p xmlns="http://www.w3.org/1999/xhtml">Accessed using the
+ org.freedesktop.DBus.Properties interface.</p>
+ <dl xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="property"/>
+ </dl>
+ </xsl:when>
+ <xsl:otherwise>
+ <p xmlns="http://www.w3.org/1999/xhtml">Interface has no D-Bus core
+ properties.</p>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:call-template name="do-types"/>
+
+ </xsl:template>
+
+ <xsl:template match="tp:flags">
+
+ <xsl:if test="not(@name) or @name = ''">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: missing @name on a tp:flags type&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <xsl:if test="not(@type) or @type = ''">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: missing @type on tp:flags type</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <h3>
+ <a name="type-{@name}">
+ <xsl:value-of select="@name"/>
+ </a>
+ </h3>
+ <xsl:apply-templates select="tp:docstring" />
+ <xsl:apply-templates select="tp:added"/>
+ <xsl:apply-templates select="tp:changed"/>
+ <xsl:apply-templates select="tp:deprecated"/>
+ <dl xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:variable name="value-prefix">
+ <xsl:choose>
+ <xsl:when test="@value-prefix">
+ <xsl:value-of select="@value-prefix"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="@name"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:for-each select="tp:flag">
+ <dt xmlns="http://www.w3.org/1999/xhtml"><code><xsl:value-of select="concat($value-prefix, '_', @suffix)"/> = <xsl:value-of select="@value"/></code></dt>
+ <xsl:choose>
+ <xsl:when test="tp:docstring">
+ <dd xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="tp:docstring" />
+ <xsl:apply-templates select="tp:added"/>
+ <xsl:apply-templates select="tp:changed"/>
+ <xsl:apply-templates select="tp:deprecated"/>
+ </dd>
+ </xsl:when>
+ <xsl:otherwise>
+ <dd xmlns="http://www.w3.org/1999/xhtml">(Undocumented)</dd>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ </dl>
+ </xsl:template>
+
+ <xsl:template match="tp:enum">
+
+ <xsl:if test="not(@name) or @name = ''">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: missing @name on a tp:enum type&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <xsl:if test="not(@type) or @type = ''">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: missing @type on tp:enum type</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <h3 xmlns="http://www.w3.org/1999/xhtml">
+ <a name="type-{@name}">
+ <xsl:value-of select="@name"/>
+ </a>
+ </h3>
+ <xsl:apply-templates select="tp:docstring" />
+ <xsl:apply-templates select="tp:added"/>
+ <xsl:apply-templates select="tp:changed"/>
+ <xsl:apply-templates select="tp:deprecated"/>
+ <dl xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:variable name="value-prefix">
+ <xsl:choose>
+ <xsl:when test="@value-prefix">
+ <xsl:value-of select="@value-prefix"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="@name"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:for-each select="tp:enumvalue">
+ <dt xmlns="http://www.w3.org/1999/xhtml"><code><xsl:value-of select="concat($value-prefix, '_', @suffix)"/> = <xsl:value-of select="@value"/></code></dt>
+ <xsl:choose>
+ <xsl:when test="tp:docstring">
+ <dd xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="tp:docstring" />
+ <xsl:apply-templates select="tp:added"/>
+ <xsl:apply-templates select="tp:changed"/>
+ <xsl:apply-templates select="tp:deprecated"/>
+ </dd>
+ </xsl:when>
+ <xsl:otherwise>
+ <dd xmlns="http://www.w3.org/1999/xhtml">(Undocumented)</dd>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ </dl>
+ </xsl:template>
+
+ <xsl:template match="property">
+
+ <xsl:if test="not(parent::interface)">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: property </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text> does not have an interface as parent&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <xsl:if test="not(@name) or @name = ''">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: missing @name on a property of </xsl:text>
+ <xsl:value-of select="../@name"/>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <xsl:if test="not(@type) or @type = ''">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: missing @type on property </xsl:text>
+ <xsl:value-of select="concat(../@name, '.', @name)"/>
+ <xsl:text>: '</xsl:text>
+ <xsl:value-of select="@access"/>
+ <xsl:text>'&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <dt xmlns="http://www.w3.org/1999/xhtml">
+ <a name="{concat(../@name, '.', @name)}">
+ <code><xsl:value-of select="@name"/></code>
+ </a>
+ <xsl:text> − </xsl:text>
+ <code><xsl:value-of select="@type"/></code>
+ <xsl:call-template name="parenthesized-tp-type"/>
+ <xsl:text>, </xsl:text>
+ <xsl:choose>
+ <xsl:when test="@access = 'read'">
+ <xsl:text>read-only</xsl:text>
+ </xsl:when>
+ <xsl:when test="@access = 'write'">
+ <xsl:text>write-only</xsl:text>
+ </xsl:when>
+ <xsl:when test="@access = 'readwrite'">
+ <xsl:text>read/write</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: unknown or missing value for </xsl:text>
+ <xsl:text>@access on property </xsl:text>
+ <xsl:value-of select="concat(../@name, '.', @name)"/>
+ <xsl:text>: '</xsl:text>
+ <xsl:value-of select="@access"/>
+ <xsl:text>'&#10;</xsl:text>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </dt>
+ <dd xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="tp:docstring"/>
+ <xsl:apply-templates select="tp:added"/>
+ <xsl:apply-templates select="tp:changed"/>
+ <xsl:apply-templates select="tp:deprecated"/>
+ </dd>
+ </xsl:template>
+
+ <xsl:template match="tp:property">
+ <dt xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:if test="@name">
+ <code><xsl:value-of select="@name"/></code> −
+ </xsl:if>
+ <code><xsl:value-of select="@type"/></code>
+ </dt>
+ <dd xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="tp:docstring"/>
+ <xsl:apply-templates select="tp:added"/>
+ <xsl:apply-templates select="tp:changed"/>
+ <xsl:apply-templates select="tp:deprecated"/>
+ </dd>
+ </xsl:template>
+
+ <xsl:template match="tp:mapping">
+ <div xmlns="http://www.w3.org/1999/xhtml" class="struct">
+ <h3>
+ <a name="type-{@name}">
+ <xsl:value-of select="@name"/>
+ </a> − a{
+ <xsl:for-each select="tp:member">
+ <xsl:value-of select="@type"/>
+ <xsl:text>: </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:if test="position() != last()"> &#x2192; </xsl:if>
+ </xsl:for-each>
+ }
+ </h3>
+ <div class="docstring">
+ <xsl:apply-templates select="tp:docstring"/>
+ <xsl:if test="string(@array-name) != ''">
+ <p>In bindings that need a separate name, arrays of
+ <xsl:value-of select="@name"/> should be called
+ <xsl:value-of select="@array-name"/>.</p>
+ </xsl:if>
+ </div>
+ <div>
+ <h4>Members</h4>
+ <dl>
+ <xsl:apply-templates select="tp:member" mode="members-in-docstring"/>
+ </dl>
+ </div>
+ </div>
+ </xsl:template>
+
+ <xsl:template match="tp:docstring" mode="in-index"/>
+
+ <xsl:template match="tp:simple-type | tp:enum | tp:flags | tp:external-type"
+ mode="in-index">
+ − <xsl:value-of select="@type"/>
+ </xsl:template>
+
+ <xsl:template match="tp:simple-type">
+
+ <xsl:if test="not(@name) or @name = ''">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: missing @name on a tp:simple-type&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <xsl:if test="not(@type) or @type = ''">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: missing @type on tp:simple-type</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <div xmlns="http://www.w3.org/1999/xhtml" class="simple-type">
+ <h3>
+ <a name="type-{@name}">
+ <xsl:value-of select="@name"/>
+ </a> − <xsl:value-of select="@type"/>
+ </h3>
+ <div class="docstring">
+ <xsl:apply-templates select="tp:docstring"/>
+ <xsl:apply-templates select="tp:added"/>
+ <xsl:apply-templates select="tp:changed"/>
+ <xsl:apply-templates select="tp:deprecated"/>
+ </div>
+ </div>
+ </xsl:template>
+
+ <xsl:template match="tp:external-type">
+
+ <xsl:if test="not(@name) or @name = ''">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: missing @name on a tp:external-type&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <xsl:if test="not(@type) or @type = ''">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: missing @type on tp:external-type</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <div xmlns="http://www.w3.org/1999/xhtml" class="external-type">
+ <dt>
+ <a name="type-{@name}">
+ <xsl:value-of select="@name"/>
+ </a> − <xsl:value-of select="@type"/>
+ </dt>
+ <dd>Defined by: <xsl:value-of select="@from"/></dd>
+ </div>
+ </xsl:template>
+
+ <xsl:template match="tp:struct" mode="in-index">
+ − ( <xsl:for-each select="tp:member">
+ <xsl:value-of select="@type"/>
+ <xsl:if test="position() != last()">, </xsl:if>
+ </xsl:for-each> )
+ </xsl:template>
+
+ <xsl:template match="tp:mapping" mode="in-index">
+ − a{ <xsl:for-each select="tp:member">
+ <xsl:value-of select="@type"/>
+ <xsl:if test="position() != last()"> &#x2192; </xsl:if>
+ </xsl:for-each> }
+ </xsl:template>
+
+ <xsl:template match="tp:struct">
+ <div xmlns="http://www.w3.org/1999/xhtml" class="struct">
+ <h3>
+ <a name="type-{@name}">
+ <xsl:value-of select="@name"/>
+ </a> − (
+ <xsl:for-each select="tp:member">
+ <xsl:value-of select="@type"/>
+ <xsl:text>: </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:if test="position() != last()">, </xsl:if>
+ </xsl:for-each>
+ )
+ </h3>
+ <div class="docstring">
+ <xsl:apply-templates select="tp:docstring"/>
+ <xsl:apply-templates select="tp:added"/>
+ <xsl:apply-templates select="tp:changed"/>
+ <xsl:apply-templates select="tp:deprecated"/>
+ </div>
+ <xsl:choose>
+ <xsl:when test="string(@array-name) != ''">
+ <p>In bindings that need a separate name, arrays of
+ <xsl:value-of select="@name"/> should be called
+ <xsl:value-of select="@array-name"/>.</p>
+ </xsl:when>
+ <xsl:otherwise>
+ <p>Arrays of <xsl:value-of select="@name"/> don't generally
+ make sense.</p>
+ </xsl:otherwise>
+ </xsl:choose>
+ <div>
+ <h4>Members</h4>
+ <dl>
+ <xsl:apply-templates select="tp:member" mode="members-in-docstring"/>
+ </dl>
+ </div>
+ </div>
+ </xsl:template>
+
+ <xsl:template match="method">
+
+ <xsl:if test="not(parent::interface)">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: method </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text> does not have an interface as parent&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <xsl:if test="not(@name) or @name = ''">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: missing @name on a method of </xsl:text>
+ <xsl:value-of select="../@name"/>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <xsl:for-each select="arg">
+ <xsl:if test="not(@type) or @type = ''">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: an arg of method </xsl:text>
+ <xsl:value-of select="concat(../../@name, '.', ../@name)"/>
+ <xsl:text> has no type</xsl:text>
+ </xsl:message>
+ </xsl:if>
+ <xsl:choose>
+ <xsl:when test="@direction='in'">
+ <xsl:if test="not(@name) or @name = ''">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: an 'in' arg of method </xsl:text>
+ <xsl:value-of select="concat(../../@name, '.', ../@name)"/>
+ <xsl:text> has no name</xsl:text>
+ </xsl:message>
+ </xsl:if>
+ </xsl:when>
+ <xsl:when test="@direction='out'">
+ <!-- FIXME: This is commented out until someone with a lot of time
+ on their hands goes through the spec adding names to all the "out"
+ arguments
+
+ <xsl:if test="not(@name) or @name = ''">
+ <xsl:message terminate="no">
+ <xsl:text>INFO: an 'out' arg of method </xsl:text>
+ <xsl:value-of select="concat(../../@name, '.', ../@name)"/>
+ <xsl:text> has no name</xsl:text>
+ </xsl:message>
+ </xsl:if>-->
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: an arg of method </xsl:text>
+ <xsl:value-of select="concat(../../@name, '.', ../@name)"/>
+ <xsl:text> has direction neither 'in' nor 'out'</xsl:text>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+
+ <div xmlns="http://www.w3.org/1999/xhtml" class="method">
+ <h3 xmlns="http://www.w3.org/1999/xhtml">
+ <a name="{concat(../@name, concat('.', @name))}">
+ <xsl:value-of select="@name"/>
+ </a> (
+ <xsl:for-each xmlns="" select="arg[@direction='in']">
+ <xsl:value-of select="@type"/>: <xsl:value-of select="@name"/>
+ <xsl:if test="position() != last()">, </xsl:if>
+ </xsl:for-each>
+ ) &#x2192;
+ <xsl:choose>
+ <xsl:when test="arg[@direction='out']">
+ <xsl:for-each xmlns="" select="arg[@direction='out']">
+ <xsl:value-of select="@type"/>
+ <xsl:if test="position() != last()">, </xsl:if>
+ </xsl:for-each>
+ </xsl:when>
+ <xsl:otherwise>nothing</xsl:otherwise>
+ </xsl:choose>
+ </h3>
+ <div xmlns="http://www.w3.org/1999/xhtml" class="docstring">
+ <xsl:apply-templates select="tp:docstring" />
+ <xsl:apply-templates select="tp:added"/>
+ <xsl:apply-templates select="tp:changed"/>
+ <xsl:apply-templates select="tp:deprecated"/>
+ </div>
+
+ <xsl:if test="arg[@direction='in']">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <h4>Parameters</h4>
+ <dl xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="arg[@direction='in']"
+ mode="parameters-in-docstring"/>
+ </dl>
+ </div>
+ </xsl:if>
+
+ <xsl:if test="arg[@direction='out']">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <h4>Returns</h4>
+ <dl xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="arg[@direction='out']"
+ mode="returns-in-docstring"/>
+ </dl>
+ </div>
+ </xsl:if>
+
+ <xsl:if test="tp:possible-errors">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <h4>Possible errors</h4>
+ <dl xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="tp:possible-errors/tp:error"/>
+ </dl>
+ </div>
+ </xsl:if>
+
+ </div>
+ </xsl:template>
+
+ <xsl:template name="tp-type">
+ <xsl:param name="tp-type"/>
+ <xsl:param name="type"/>
+
+ <xsl:variable name="single-type">
+ <xsl:choose>
+ <xsl:when test="contains($tp-type, '[]')">
+ <xsl:value-of select="substring-before($tp-type, '[]')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$tp-type"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:variable name="type-of-tp-type">
+ <xsl:if test="contains($tp-type, '[]')">
+ <!-- one 'a', plus one for each [ after the [], and delete all ] -->
+ <xsl:value-of select="concat('a',
+ translate(substring-after($tp-type, '[]'), '[]', 'a'))"/>
+ </xsl:if>
+
+ <xsl:choose>
+ <xsl:when test="//tp:simple-type[@name=$single-type]">
+ <xsl:value-of select="string(//tp:simple-type[@name=$single-type]/@type)"/>
+ </xsl:when>
+ <xsl:when test="//tp:struct[@name=$single-type]">
+ <xsl:text>(</xsl:text>
+ <xsl:for-each select="//tp:struct[@name=$single-type]/tp:member">
+ <xsl:value-of select="@type"/>
+ </xsl:for-each>
+ <xsl:text>)</xsl:text>
+ </xsl:when>
+ <xsl:when test="//tp:enum[@name=$single-type]">
+ <xsl:value-of select="string(//tp:enum[@name=$single-type]/@type)"/>
+ </xsl:when>
+ <xsl:when test="//tp:flags[@name=$single-type]">
+ <xsl:value-of select="string(//tp:flags[@name=$single-type]/@type)"/>
+ </xsl:when>
+ <xsl:when test="//tp:mapping[@name=$single-type]">
+ <xsl:text>a{</xsl:text>
+ <xsl:for-each select="//tp:mapping[@name=$single-type]/tp:member">
+ <xsl:value-of select="@type"/>
+ </xsl:for-each>
+ <xsl:text>}</xsl:text>
+ </xsl:when>
+ <xsl:when test="//tp:external-type[@name=$single-type]">
+ <xsl:value-of select="string(//tp:external-type[@name=$single-type]/@type)"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: Unable to find type '</xsl:text>
+ <xsl:value-of select="$tp-type"/>
+ <xsl:text>'&#10;</xsl:text>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:if test="string($type) != '' and
+ string($type-of-tp-type) != string($type)">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: tp:type '</xsl:text>
+ <xsl:value-of select="$tp-type"/>
+ <xsl:text>' has D-Bus type '</xsl:text>
+ <xsl:value-of select="$type-of-tp-type"/>
+ <xsl:text>' but has been used with type='</xsl:text>
+ <xsl:value-of select="$type"/>
+ <xsl:text>'&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <a href="#type-{$single-type}"><xsl:value-of select="$tp-type"/></a>
+
+ </xsl:template>
+
+ <xsl:template name="parenthesized-tp-type">
+ <xsl:if test="@tp:type">
+ <xsl:text> (</xsl:text>
+ <xsl:call-template name="tp-type">
+ <xsl:with-param name="tp-type" select="@tp:type"/>
+ <xsl:with-param name="type" select="@type"/>
+ </xsl:call-template>
+ <xsl:text>)</xsl:text>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match="tp:member" mode="members-in-docstring">
+ <dt xmlns="http://www.w3.org/1999/xhtml">
+ <code><xsl:value-of select="@name"/></code> −
+ <code><xsl:value-of select="@type"/></code>
+ <xsl:call-template name="parenthesized-tp-type"/>
+ </dt>
+ <dd xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:choose>
+ <xsl:when test="tp:docstring">
+ <xsl:apply-templates select="tp:docstring" />
+ </xsl:when>
+ <xsl:otherwise>
+ <em>(undocumented)</em>
+ </xsl:otherwise>
+ </xsl:choose>
+ </dd>
+ </xsl:template>
+
+ <xsl:template match="arg" mode="parameters-in-docstring">
+ <dt xmlns="http://www.w3.org/1999/xhtml">
+ <code><xsl:value-of select="@name"/></code> −
+ <code><xsl:value-of select="@type"/></code>
+ <xsl:call-template name="parenthesized-tp-type"/>
+ </dt>
+ <dd xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="tp:docstring" />
+ </dd>
+ </xsl:template>
+
+ <xsl:template match="arg" mode="returns-in-docstring">
+ <dt xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:if test="@name">
+ <code><xsl:value-of select="@name"/></code> −
+ </xsl:if>
+ <code><xsl:value-of select="@type"/></code>
+ <xsl:call-template name="parenthesized-tp-type"/>
+ </dt>
+ <dd xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="tp:docstring"/>
+ </dd>
+ </xsl:template>
+
+ <xsl:template match="tp:possible-errors/tp:error">
+ <dt xmlns="http://www.w3.org/1999/xhtml">
+ <code><xsl:value-of select="@name"/></code>
+ </dt>
+ <dd xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:variable name="name" select="@name"/>
+ <xsl:choose>
+ <xsl:when test="tp:docstring">
+ <xsl:apply-templates select="tp:docstring"/>
+ </xsl:when>
+ <xsl:when test="//tp:errors/tp:error[concat(../@namespace, '.', translate(@name, ' ', ''))=$name]/tp:docstring">
+ <xsl:apply-templates select="//tp:errors/tp:error[concat(../@namespace, '.', translate(@name, ' ', ''))=$name]/tp:docstring"/> <em xmlns="http://www.w3.org/1999/xhtml">(generic description)</em>
+ </xsl:when>
+ <xsl:otherwise>
+ (Undocumented.)
+ </xsl:otherwise>
+ </xsl:choose>
+ </dd>
+ </xsl:template>
+
+ <xsl:template match="signal">
+
+ <xsl:if test="not(parent::interface)">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: signal </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text> does not have an interface as parent&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <xsl:if test="not(@name) or @name = ''">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: missing @name on a signal of </xsl:text>
+ <xsl:value-of select="../@name"/>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <xsl:for-each select="arg">
+ <xsl:if test="not(@type) or @type = ''">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: an arg of signal </xsl:text>
+ <xsl:value-of select="concat(../../@name, '.', ../@name)"/>
+ <xsl:text> has no type</xsl:text>
+ </xsl:message>
+ </xsl:if>
+ <xsl:if test="not(@name) or @name = ''">
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: an arg of signal </xsl:text>
+ <xsl:value-of select="concat(../../@name, '.', ../@name)"/>
+ <xsl:text> has no name</xsl:text>
+ </xsl:message>
+ </xsl:if>
+ <xsl:choose>
+ <xsl:when test="not(@direction)"/>
+ <xsl:when test="@direction='in'">
+ <xsl:message terminate="no">
+ <xsl:text>INFO: an arg of signal </xsl:text>
+ <xsl:value-of select="concat(../../@name, '.', ../@name)"/>
+ <xsl:text> has unnecessary direction 'in'</xsl:text>
+ </xsl:message>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: an arg of signal </xsl:text>
+ <xsl:value-of select="concat(../../@name, '.', ../@name)"/>
+ <xsl:text> has direction other than 'in'</xsl:text>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+
+ <div xmlns="http://www.w3.org/1999/xhtml" class="signal">
+ <h3 xmlns="http://www.w3.org/1999/xhtml">
+ <a name="{concat(../@name, concat('.', @name))}">
+ <xsl:value-of select="@name"/>
+ </a> (
+ <xsl:for-each xmlns="" select="arg">
+ <xsl:value-of select="@type"/>: <xsl:value-of select="@name"/>
+ <xsl:if test="position() != last()">, </xsl:if>
+ </xsl:for-each>
+ )</h3>
+
+ <div xmlns="http://www.w3.org/1999/xhtml" class="docstring">
+ <xsl:apply-templates select="tp:docstring"/>
+ <xsl:apply-templates select="tp:added"/>
+ <xsl:apply-templates select="tp:changed"/>
+ <xsl:apply-templates select="tp:deprecated"/>
+ </div>
+
+ <xsl:if test="arg">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <h4>Parameters</h4>
+ <dl xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="arg" mode="parameters-in-docstring"/>
+ </dl>
+ </div>
+ </xsl:if>
+ </div>
+ </xsl:template>
+
+ <xsl:output method="xml" indent="no" encoding="ascii"
+ omit-xml-declaration="yes"
+ doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
+ doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" />
+
+ <xsl:template match="/tp:spec">
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>
+ <xsl:value-of select="tp:title"/>
+ <xsl:if test="tp:version">
+ <xsl:text> version </xsl:text>
+ <xsl:value-of select="tp:version"/>
+ </xsl:if>
+ </title>
+ <style type="text/css">
+
+ body {
+ font-family: sans-serif;
+ margin: 2em;
+ height: 100%;
+ font-size: 1.2em;
+ }
+ h1 {
+ padding-top: 5px;
+ padding-bottom: 5px;
+ font-size: 1.6em;
+ background: #dadae2;
+ }
+ h2 {
+ font-size: 1.3em;
+ }
+ h3 {
+ font-size: 1.2em;
+ }
+ a:link, a:visited, a:link:hover, a:visited:hover {
+ font-weight: bold;
+ }
+ .topbox {
+ padding-top: 10px;
+ padding-left: 10px;
+ border-bottom: black solid 1px;
+ padding-bottom: 10px;
+ background: #dadae2;
+ font-size: 2em;
+ font-weight: bold;
+ color: #5c5c5c;
+ }
+ .topnavbox {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ background: #abacba;
+ border-bottom: black solid 1px;
+ font-size: 1.2em;
+ }
+ .topnavbox a{
+ color: black;
+ font-weight: normal;
+ }
+ .sidebar {
+ float: left;
+ /* width:9em;
+ border-right:#abacba solid 1px;
+ border-left: #abacba solid 1px;
+ height:100%; */
+ border: #abacba solid 1px;
+ padding-left: 10px;
+ margin-left: 10px;
+ padding-right: 10px;
+ margin-right: 10px;
+ color: #5d5d5d;
+ background: #dadae2;
+ }
+ .sidebar a {
+ text-decoration: none;
+ border-bottom: #e29625 dotted 1px;
+ color: #e29625;
+ font-weight: normal;
+ }
+ .sidebar h1 {
+ font-size: 1.2em;
+ color: black;
+ }
+ .sidebar ul {
+ padding-left: 25px;
+ padding-bottom: 10px;
+ border-bottom: #abacba solid 1px;
+ }
+ .sidebar li {
+ padding-top: 2px;
+ padding-bottom: 2px;
+ }
+ .sidebar h2 {
+ font-style:italic;
+ font-size: 0.81em;
+ padding-left: 5px;
+ padding-right: 5px;
+ font-weight: normal;
+ }
+ .date {
+ font-size: 0.6em;
+ float: right;
+ font-style: italic;
+ }
+ .method, .signal, .property {
+ margin-left: 1em;
+ margin-right: 4em;
+ }
+ .rationale {
+ font-style: italic;
+ border-left: 0.25em solid #808080;
+ padding-left: 0.5em;
+ }
+
+ .added {
+ color: #006600;
+ background: #ffffff;
+ }
+ .deprecated {
+ color: #ff0000;
+ background: #ffffff;
+ }
+ table, tr, td, th {
+ border: 1px solid #666;
+ }
+
+ </style>
+ </head>
+ <body>
+ <h1 class="topbox">
+ <xsl:value-of select="tp:title" />
+ </h1>
+ <xsl:if test="tp:version">
+ <h2>Version <xsl:value-of select="string(tp:version)"/></h2>
+ </xsl:if>
+ <xsl:apply-templates select="tp:copyright"/>
+ <xsl:apply-templates select="tp:license"/>
+ <xsl:apply-templates select="tp:docstring"/>
+
+ <h2>Interfaces</h2>
+ <ul>
+ <xsl:for-each select="//node/interface">
+ <li><code><a href="#{@name}"><xsl:value-of select="@name"/></a></code></li>
+ </xsl:for-each>
+ </ul>
+
+ <xsl:apply-templates select="//node"/>
+ <xsl:apply-templates select="tp:generic-types"/>
+ <xsl:apply-templates select="tp:errors"/>
+
+ <h1>Index</h1>
+ <h2>Index of interfaces</h2>
+ <ul>
+ <xsl:for-each select="//node/interface">
+ <li><code><a href="#{@name}"><xsl:value-of select="@name"/></a></code></li>
+ </xsl:for-each>
+ </ul>
+ <h2>Index of types</h2>
+ <ul>
+ <xsl:for-each select="//tp:simple-type | //tp:enum | //tp:flags | //tp:mapping | //tp:struct | //tp:external-type">
+ <xsl:sort select="@name"/>
+ <li>
+ <code>
+ <a href="#type-{@name}">
+ <xsl:value-of select="@name"/>
+ </a>
+ </code>
+ <xsl:apply-templates mode="in-index" select="."/>
+ </li>
+ </xsl:for-each>
+ </ul>
+ </body>
+ </html>
+ </xsl:template>
+
+ <xsl:template match="node">
+ <xsl:apply-templates />
+ </xsl:template>
+
+ <xsl:template match="text()">
+ <xsl:if test="normalize-space(.) != ''">
+ <xsl:message terminate="yes">
+ <xsl:text>Stray text: {{{</xsl:text>
+ <xsl:value-of select="." />
+ <xsl:text>}}}&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match="*">
+ <xsl:message terminate="yes">
+ <xsl:text>Unrecognised element: {</xsl:text>
+ <xsl:value-of select="namespace-uri(.)" />
+ <xsl:text>}</xsl:text>
+ <xsl:value-of select="local-name(.)" />
+ <xsl:text>&#10;</xsl:text>
+ </xsl:message>
+ </xsl:template>
+</xsl:stylesheet>
+
+<!-- vim:set sw=2 sts=2 et: -->
diff --git a/yell/tools/glib-client-gen.py b/yell/tools/glib-client-gen.py
new file mode 100644
index 000000000..446d198f4
--- /dev/null
+++ b/yell/tools/glib-client-gen.py
@@ -0,0 +1,1254 @@
+#!/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.reentrant_symbols = set()
+ try:
+ filename = opts['--generate-reentrant']
+ with open(filename, 'r') as f:
+ for line in f.readlines():
+ self.reentrant_symbols.add(line.strip())
+ except KeyError:
+ pass
+
+ 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
+
+ docs = get_docstring(elt) or '(Undocumented)'
+
+ if ctype == 'guint ' and tp_type != '':
+ docs += ' (#%s)' % ('Tp' + tp_type.replace('_', ''))
+
+ self.d(' * @%s: %s' % (name, xml_escape(docs)))
+
+ 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
+
+ docs = xml_escape(get_docstring(elt) or '(Undocumented)')
+
+ if ctype == 'guint ' and tp_type != '':
+ docs += ' (#%s)' % ('Tp' + tp_type.replace('_', ''))
+
+ self.d(' * @%s: Used to return an \'out\' argument if @error is '
+ '%%NULL: %s'
+ % (name, docs))
+
+ 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
+
+ docs = xml_escape(get_docstring(elt) or '(Undocumented)')
+
+ if ctype == 'guint ' and tp_type != '':
+ docs += ' (#%s)' % ('Tp' + tp_type.replace('_', ''))
+
+ self.d(' * @%s: Used to pass an \'in\' argument: %s'
+ % (name, docs))
+
+ 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('')
+
+ 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);
+
+ run_method_name = '%s_%s_run_%s' % (self.prefix_lc, iface_lc, member_lc)
+ if run_method_name not in self.reentrant_symbols:
+ return
+
+ 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 (%sproxy,'
+ % (run_method_name, self.proxy_arg))
+ self.h(' gint timeout_ms,')
+
+ self.d('/**')
+ self.d(' * %s:' % run_method_name)
+ 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
+
+ docs = xml_escape(get_docstring(elt) or '(Undocumented)')
+
+ if ctype == 'guint ' and tp_type != '':
+ docs += ' (#%s)' % ('Tp' + tp_type.replace('_', ''))
+
+ self.d(' * @%s: Used to pass an \'in\' argument: %s'
+ % (name, docs))
+
+ 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 (%sproxy,'
+ % (run_method_name, 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/yell/tools/glib-client-marshaller-gen.py b/yell/tools/glib-client-marshaller-gen.py
new file mode 100644
index 000000000..801eec136
--- /dev/null
+++ b/yell/tools/glib-client-marshaller-gen.py
@@ -0,0 +1,62 @@
+#!/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 %s_register_dbus_glib_marshallers (void);' % self.prefix
+ print ''
+
+ 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/yell/tools/glib-errors-enum-body-gen.py b/yell/tools/glib-errors-enum-body-gen.py
new file mode 100644
index 000000000..44863ee4c
--- /dev/null
+++ b/yell/tools/glib-errors-enum-body-gen.py
@@ -0,0 +1,62 @@
+#!/usr/bin/python
+
+import sys
+import xml.dom.minidom
+
+from libglibcodegen import NS_TP, camelcase_to_upper, get_docstring, \
+ get_descendant_text
+
+class Generator(object):
+ def __init__(self, dom):
+ self.dom = dom
+ self.errors = self.dom.getElementsByTagNameNS(NS_TP, 'errors')[0]
+
+ def do_header(self):
+ print '/* Generated from the Telepathy spec\n'
+ copyrights = self.errors.getElementsByTagNameNS(NS_TP, 'copyright')
+ for copyright in copyrights:
+ print get_descendant_text(copyright)
+ license = self.errors.getElementsByTagNameNS(NS_TP, 'license')[0]
+ print '\n' + get_descendant_text(license) + '\n*/'
+
+ def do_enum_values(self):
+ for error in self.errors.getElementsByTagNameNS(NS_TP, 'error'):
+ print ''
+ nick = error.getAttribute('name').replace(' ', '')
+ name = camelcase_to_upper(nick.replace('.', ''))
+ ns = error.parentNode.getAttribute('namespace')
+ enum = 'TP_ERROR_' + name
+ print ' /* ' + ns + '.' + name
+ print ' ' + get_docstring(error)
+ print ' */'
+ print ' { %s, "%s", "%s" },' % (enum, enum, nick)
+
+
+ def do_get_type(self):
+ print """
+#include <_gen/telepathy-errors.h>
+
+GType
+tp_error_get_type (void)
+{
+ static GType etype = 0;
+ if (G_UNLIKELY (etype == 0))
+ {
+ static const GEnumValue values[] = {"""
+ self.do_enum_values()
+ print """\
+ };
+
+ etype = g_enum_register_static ("TpError", values);
+ }
+ return etype;
+}
+"""
+
+ def __call__(self):
+ self.do_header()
+ self.do_get_type()
+
+if __name__ == '__main__':
+ argv = sys.argv[1:]
+ Generator(xml.dom.minidom.parse(argv[0]))()
diff --git a/yell/tools/glib-errors-enum-header-gen.py b/yell/tools/glib-errors-enum-header-gen.py
new file mode 100644
index 000000000..64939b414
--- /dev/null
+++ b/yell/tools/glib-errors-enum-header-gen.py
@@ -0,0 +1,75 @@
+#!/usr/bin/python
+
+import sys
+import xml.dom.minidom
+
+from libglibcodegen import NS_TP, camelcase_to_upper, get_docstring, \
+ get_descendant_text
+
+class Generator(object):
+ def __init__(self, dom):
+ self.dom = dom
+ self.errors = self.dom.getElementsByTagNameNS(NS_TP, 'errors')[0]
+
+ def do_header(self):
+ print '/* Generated from the Telepathy spec\n'
+ copyrights = self.errors.getElementsByTagNameNS(NS_TP, 'copyright')
+ for copyright in copyrights:
+ print get_descendant_text(copyright)
+ license = self.errors.getElementsByTagNameNS(NS_TP, 'license')[0]
+ print '\n' + get_descendant_text(license) + '\n*/'
+
+ def do_gtkdoc(self):
+ for error in self.errors.getElementsByTagNameNS(NS_TP, 'error'):
+ ns = error.parentNode.getAttribute('namespace')
+ nick = error.getAttribute('name').replace(' ', '')
+ enum = 'TP_ERROR_' + camelcase_to_upper(nick.replace('.', ''))
+ print ' * @' + enum + ': ' + ns + '.' + nick + ':'
+ print ' * ' + get_docstring(error) + ' '
+
+ def do_enumnames(self):
+ for error in self.errors.getElementsByTagNameNS(NS_TP, 'error'):
+ nick = error.getAttribute('name').replace(' ', '')
+ enum = 'TP_ERROR_' + camelcase_to_upper(nick.replace('.', ''))
+ print ' ' + enum + ','
+
+ def do_get_type(self):
+ print """
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+GType tp_error_get_type (void);
+
+/**
+ * TP_TYPE_ERROR:
+ *
+ * The GType of the Telepathy error enumeration.
+ */
+#define TP_TYPE_ERROR (tp_error_get_type())
+"""
+
+ def do_enum(self):
+ print """\
+/**
+ * TpError:"""
+ self.do_gtkdoc()
+ print """\
+ *
+ * Enumerated type representing the Telepathy D-Bus errors.
+ */
+typedef enum {"""
+ self.do_enumnames()
+ print """\
+} TpError;
+
+G_END_DECLS"""
+
+ def __call__(self):
+ self.do_header()
+ self.do_get_type()
+ self.do_enum()
+
+if __name__ == '__main__':
+ argv = sys.argv[1:]
+ Generator(xml.dom.minidom.parse(argv[0]))()
diff --git a/yell/tools/glib-ginterface-gen.py b/yell/tools/glib-ginterface-gen.py
new file mode 100644
index 000000000..e277b91f4
--- /dev/null
+++ b/yell/tools/glib-ginterface-gen.py
@@ -0,0 +1,843 @@
+#!/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"
+
+def get_emits_changed(node):
+ try:
+ return [
+ annotation.getAttribute('value')
+ for annotation in node.getElementsByTagName('annotation')
+ if annotation.getAttribute('name') == 'org.freedesktop.DBus.Property.EmitsChangedSignal'
+ ][0]
+ except IndexError:
+ return None
+
+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))
+
+ iface_emits_changed = get_emits_changed(interface)
+
+ 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')
+
+ prop_emits_changed = get_emits_changed(m)
+
+ if prop_emits_changed is None:
+ prop_emits_changed = iface_emits_changed
+
+ if prop_emits_changed == 'true':
+ flags += ' | TP_DBUS_PROPERTIES_MIXIN_FLAG_EMITS_CHANGED'
+ elif prop_emits_changed == 'invalidates':
+ flags += ' | TP_DBUS_PROPERTIES_MIXIN_FLAG_EMITS_INVALIDATED'
+
+ 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))
+ self.d(' * @self: an object')
+ 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/yell/tools/glib-gtypes-generator.py b/yell/tools/glib-gtypes-generator.py
new file mode 100644
index 000000000..a49c36e7f
--- /dev/null
+++ b/yell/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/yell/tools/glib-interfaces-gen.py b/yell/tools/glib-interfaces-gen.py
new file mode 100644
index 000000000..69c721be3
--- /dev/null
+++ b/yell/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/yell/tools/glib-signals-marshal-gen.py b/yell/tools/glib-signals-marshal-gen.py
new file mode 100644
index 000000000..0d02c1341
--- /dev/null
+++ b/yell/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/yell/tools/gobject-foo.py b/yell/tools/gobject-foo.py
new file mode 100644
index 000000000..002a290ba
--- /dev/null
+++ b/yell/tools/gobject-foo.py
@@ -0,0 +1,90 @@
+#!/usr/bin/python
+
+# gobject-foo.py: generate standard GObject type macros etc.
+#
+# The master copy of this program is in the telepathy-glib repository -
+# please make any changes there.
+#
+# Copyright (C) 2007-2010 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
+
+def gobject_header(head, tail, as_interface=False):
+ out = []
+ o = out.append
+
+ name = head + '_' + tail
+ MixedCase = name.replace('_', '')
+ lower_case = name.lower()
+ UPPER_CASE = name.upper()
+
+ gtype = head.upper() + '_TYPE_' + tail.upper()
+
+ o("typedef struct _%s %s;" % (MixedCase, MixedCase))
+
+ if as_interface:
+ o("typedef struct _%sInterface %sInterface;" % (MixedCase, MixedCase))
+ else:
+ o("typedef struct _%sClass %sClass;" % (MixedCase, MixedCase))
+ o("typedef struct _%sPrivate %sPrivate;" % (MixedCase, MixedCase))
+
+ o("")
+ o("GType %s_get_type (void);" % lower_case)
+ o("")
+
+ o("#define %s \\" % gtype)
+ o(" (%s_get_type ())" % lower_case)
+
+ o("#define %s(obj) \\" % UPPER_CASE)
+ o(" (G_TYPE_CHECK_INSTANCE_CAST ((obj), %s, \\" % gtype)
+ o(" %s))" % MixedCase)
+
+ if not as_interface:
+ o("#define %s_CLASS(klass) \\" % UPPER_CASE)
+ o(" (G_TYPE_CHECK_CLASS_CAST ((klass), %s, \\" % gtype)
+ o(" %sClass))" % MixedCase)
+
+ o("#define %s_IS_%s(obj) \\" % (head.upper(), tail.upper()))
+ o(" (G_TYPE_CHECK_INSTANCE_TYPE ((obj), %s))" % gtype)
+
+ if as_interface:
+ o("#define %s_GET_IFACE(obj) \\" % UPPER_CASE)
+ o(" (G_TYPE_INSTANCE_GET_INTERFACE ((obj), %s, \\" % gtype)
+ o(" %sInterface))" % MixedCase)
+ else:
+ o("#define %s_IS_%s_CLASS(klass) \\" % (head.upper(), tail.upper()))
+ o(" (G_TYPE_CHECK_CLASS_TYPE ((klass), %s))" % gtype)
+
+ o("#define %s_GET_CLASS(obj) \\" % UPPER_CASE)
+ o(" (G_TYPE_INSTANCE_GET_CLASS ((obj), %s, \\" % gtype)
+ o(" %sClass))" % MixedCase)
+
+ return out
+
+if __name__ == '__main__':
+ import sys
+ from getopt import gnu_getopt
+
+ options, argv = gnu_getopt(sys.argv[1:], '', ['interface'])
+
+ as_interface = False
+
+ for opt, val in options:
+ if opt == '--interface':
+ as_interface = True
+
+ head, tail = argv
+
+ print '\n'.join(gobject_header(head, tail, as_interface=as_interface))
diff --git a/yell/tools/identity.xsl b/yell/tools/identity.xsl
new file mode 100644
index 000000000..6630f84de
--- /dev/null
+++ b/yell/tools/identity.xsl
@@ -0,0 +1,7 @@
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:template match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()"/>
+ </xsl:copy>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/yell/tools/libglibcodegen.py b/yell/tools/libglibcodegen.py
new file mode 100644
index 000000000..6a9d21485
--- /dev/null
+++ b/yell/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/yell/tools/libtpcodegen.py b/yell/tools/libtpcodegen.py
new file mode 100644
index 000000000..837ff2f74
--- /dev/null
+++ b/yell/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/yell/tools/make-release-mail.py b/yell/tools/make-release-mail.py
new file mode 100644
index 000000000..5c42b4798
--- /dev/null
+++ b/yell/tools/make-release-mail.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+# vim: set fileencoding=utf-8 :
+#
+# Hello. This is make-release-mail.py from the Telepathy project. It's
+# designed to turn an item from a NEWS file into a mail suitable for sending
+# to <telepathy@lists.freedesktop.org>. I hope that you enjoy your stay.
+
+import sys
+
+def extract_description(package, version, news_path):
+ release_name = []
+ details = []
+
+ with open(news_path) as f:
+ lines = (line for line in f.readlines())
+ for line in lines:
+ # Find the 'telepathy-foo 0.1.2' header
+ if line.startswith("%s %s" % (package, version)):
+ break
+
+ # Skip the ====== line, and the first blank line
+ lines.next()
+ lines.next()
+
+ got_release_name = False
+
+ for line in lines:
+ line = line.rstrip()
+ # If we hit the next version header, we're done
+ if line.startswith(package):
+ break
+ # Else, if we hit a blank line and we're still reading the release
+ # name, we're done with the release name.
+ elif not got_release_name and line == '':
+ got_release_name = True
+ # Otherwise, append this to the relevant list
+ elif not got_release_name:
+ release_name.append(line)
+ else:
+ details.append(line)
+
+ assert got_release_name, (release_name, details)
+
+ # We rstrip details because it picks up a trailing blank line
+ return ('\n'.join(release_name), '\n'.join(details).rstrip())
+
+BASE_URL = 'http://telepathy.freedesktop.org/releases'
+GIT_URL = 'http://cgit.freedesktop.org/telepathy'
+
+def main(package, version, news_path):
+ release_name, details = extract_description(package, version, news_path)
+
+ print """
+%(release_name)s
+
+tarball: %(base_url)s/%(package)s/%(package)s-%(version)s.tar.gz
+signature: %(base_url)s/%(package)s/%(package)s-%(version)s.tar.gz.asc
+git: %(git_url)s/%(package)s
+
+%(details)s""".strip().rstrip() % {
+ 'base_url': BASE_URL,
+ 'git_url': GIT_URL,
+ 'package': package,
+ 'version': version,
+ 'release_name': release_name,
+ 'details': details,
+ }
+
+if __name__ == '__main__':
+ try:
+ package, version, news_path = sys.argv[1:]
+
+ main(package, version, news_path)
+ except ValueError, e:
+ sys.stderr.write(
+ 'Usage: %s package-name package.version.number path/to/NEWS\n' %
+ sys.argv[0])
+ sys.stderr.flush()
+ sys.exit(1)
diff --git a/yell/tools/make-version-script.py b/yell/tools/make-version-script.py
new file mode 100644
index 000000000..0d30aa323
--- /dev/null
+++ b/yell/tools/make-version-script.py
@@ -0,0 +1,208 @@
+#!/usr/bin/python
+
+"""Construct a GNU ld or Debian dpkg version-script from a set of
+RFC822-style symbol lists.
+
+Usage:
+ make-version-script.py [--symbols SYMBOLS] [--unreleased-version VER]
+ [--dpkg "LIBRARY.so.0 LIBRARY0 #MINVER#"]
+ [--dpkg-build-depends-package LIBRARY-dev]
+ [FILES...]
+
+Each FILE starts with RFC822-style headers "Version:" (the name of the
+symbol version, e.g. FOO_1.2.3) and "Extends:" (either the previous
+version, or "-" if this is the first version). Next there is a blank
+line, then a list of C symbols one per line.
+
+Comments (lines starting with whitespace + "#") are allowed and ignored.
+
+If --symbols is given, SYMBOLS lists the symbols actually exported by
+the library (one per line). If --unreleased-version is given, any symbols
+in SYMBOLS but not in FILES are assigned to that version; otherwise, any
+such symbols cause an error.
+
+If --dpkg is given, produce a Debian dpkg-gensymbols file instead of a
+GNU ld version-script. The argument to --dpkg is the first line of the
+resulting symbols file, and --dpkg-build-depends-package can optionally
+be used to set the Build-Depends-Package field.
+
+This script originates in telepathy-glib <http://telepathy.freedesktop.org/> -
+please send us any changes that are needed.
+"""
+
+# Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+# Copyright (C) 2008 Nokia Corporation
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved.
+
+import sys
+from getopt import gnu_getopt
+
+
+def e(format, *args):
+ sys.stderr.write((format + '\n') % args)
+
+
+def main(abifiles, symbols=None, unreleased_version=None,
+ dpkg=False, dpkg_first_line=None, dpkg_build_depends_package=None):
+
+ gnuld = not dpkg
+ symbol_set = None
+
+ if symbols is not None:
+ symbol_set = open(symbols, 'r').readlines()
+ symbol_set = map(str.strip, symbol_set)
+ symbol_set = set(symbol_set)
+
+ versioned_symbols = set()
+
+ dpkg_symbols = []
+ dpkg_versions = []
+
+ if dpkg:
+ assert dpkg_first_line is not None
+ print dpkg_first_line
+ if dpkg_build_depends_package is not None:
+ print "* Build-Depends-Package: %s" % dpkg_build_depends_package
+
+ for filename in abifiles:
+ lines = open(filename, 'r').readlines()
+
+ version = None
+ extends = None
+ release = None
+
+ for i, line in enumerate(lines):
+ line = line.strip()
+
+ if line.startswith('#'):
+ continue
+ elif not line:
+ # the transition betwen headers and symbols
+ cut = i + 1
+ break
+ elif line.lower().startswith('version:'):
+ line = line[8:].strip()
+ version = line
+ continue
+ elif line.lower().startswith('extends:'):
+ line = line[8:].strip()
+ extends = line
+ continue
+ elif line.lower().startswith('release:'):
+ release = line[8:].strip()
+ continue
+ else:
+ e('Could not understand line in %s header: %s', filename, line)
+ raise SystemExit(1)
+
+ else:
+ e('No symbols in %s', filename)
+ raise SystemExit(1)
+
+ if version is None:
+ e('No Versions: header in %s', filename)
+ raise SystemExit(1)
+
+ if extends is None:
+ e('No Extends: header in %s', filename)
+ raise SystemExit(1)
+
+ if release is None and dpkg:
+ e('No Release: header in %s', filename)
+ raise SystemExit(1)
+
+ if dpkg:
+ dpkg_versions.append('%s@%s %s' % (version, version, release))
+
+ lines = lines[cut:]
+
+ if gnuld:
+ print "%s {" % version
+ print " global:"
+
+ for symbol in lines:
+ symbol = symbol.strip()
+
+ if symbol.startswith('#'):
+ continue
+
+ if gnuld:
+ print " %s;" % symbol
+ elif dpkg:
+ dpkg_symbols.append('%s@%s %s' % (symbol, version, release))
+
+ if symbol in versioned_symbols:
+ raise AssertionError('Symbol %s is in version %s and an '
+ 'earlier version' % (symbol, version))
+
+ versioned_symbols.add(symbol)
+
+ if gnuld:
+ if extends == '-':
+ print " local:"
+ print " *;"
+ print "};"
+ else:
+ print "} %s;" % extends
+ print
+
+ if dpkg:
+ dpkg_symbols.sort()
+ dpkg_versions.sort()
+
+ for x in dpkg_versions:
+ print " %s" % x
+
+ for x in dpkg_symbols:
+ print " %s" % x
+
+ if symbol_set is not None:
+ missing = versioned_symbols - symbol_set
+
+ if missing:
+ e('These symbols have disappeared:')
+
+ for symbol in missing:
+ e(' %s', symbol)
+
+ raise SystemExit(1)
+
+ unreleased = symbol_set - versioned_symbols
+
+ if unreleased:
+ if unreleased_version is None:
+ e('Unversioned symbols are not allowed in releases:')
+
+ for symbol in unreleased:
+ e(' %s', symbol)
+
+ raise SystemExit(1)
+
+ if gnuld:
+ print "%s {" % unreleased_version
+ print " global:"
+
+ for symbol in unreleased:
+ print " %s;" % symbol
+
+ print "} %s;" % version
+
+
+if __name__ == '__main__':
+ options, argv = gnu_getopt (sys.argv[1:], '',
+ ['symbols=', 'unreleased-version=',
+ 'dpkg=', 'dpkg-build-depends-package='])
+
+ opts = {'dpkg': False}
+
+ for option, value in options:
+ if option == '--dpkg':
+ opts['dpkg'] = True
+ opts['dpkg_first_line'] = value
+ else:
+ opts[option.lstrip('-').replace('-', '_')] = value
+
+ main(argv, **opts)
diff --git a/yell/tools/telepathy.am b/yell/tools/telepathy.am
new file mode 100644
index 000000000..a3c962b32
--- /dev/null
+++ b/yell/tools/telepathy.am
@@ -0,0 +1,69 @@
+## 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 --date=iso $(CHANGELOG_RANGE) > ${distdir}/ChangeLog; \
+ fi
+
+distcheck-hook:
+ @test "z$(CHECK_FOR_UNRELEASED)" = z || \
+ case @VERSION@ in \
+ *.*.*.*|*+) ;; \
+ *) \
+ if grep -r UNRELEASED $(CHECK_FOR_UNRELEASED); \
+ then \
+ echo "^^^ This is meant to be a release, but some files say UNRELEASED" >&2; \
+ exit 2; \
+ fi \
+ ;; \
+ esac
+
+_is-release-check:
+ @case @VERSION@ in \
+ (*.*.*.*|*+) \
+ echo "Hey! @VERSION@ is not a release!" >&2; \
+ exit 2; \
+ ;; \
+ esac
+ @if ! git diff --no-ext-diff --quiet --exit-code; then \
+ echo "Hey! Your tree is dirty! No release for you." >&2; \
+ exit 2; \
+ fi
+ @if ! git diff --cached --no-ext-diff --quiet --exit-code; then \
+ echo "Hey! You have changes staged! No release for you." >&2; \
+ exit 2; \
+ fi
+
+%.tar.gz.asc: %.tar.gz
+ $(AM_V_GEN)gpg --detach-sign --armor $@
+
+@PACKAGE@-@VERSION@.tar.gz: _is-release-check check distcheck
+
+maintainer-prepare-release: _is-release-check all distcheck release-mail
+ git tag -s @PACKAGE@-@VERSION@ -m @PACKAGE@' '@VERSION@
+ gpg --detach-sign --armor @PACKAGE@-@VERSION@.tar.gz
+
+release-mail: NEWS
+ $(AM_V_GEN)(python $(top_srcdir)/tools/make-release-mail.py \
+ @PACKAGE@ @VERSION@ $(top_srcdir)/NEWS > $@.tmp && \
+ mv $@.tmp $@)
+
+maintainer-upload-release: _maintainer-upload-release
+
+_maintainer-upload-release-check: _is-release-check
+ 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
+
+maintainer-make-release: maintainer-prepare-release maintainer-upload-release
+ @echo "Now:"
+ @echo " • bump the nano-version;"
+ @echo " • push the branch and tags upstream; and"
+ @echo " • send release-mail to <telepathy@lists.freedesktop.org>."
+
+## vim:set ft=automake:
diff --git a/yell/tools/with-session-bus.sh b/yell/tools/with-session-bus.sh
new file mode 100644
index 000000000..b3038cd9e
--- /dev/null
+++ b/yell/tools/with-session-bus.sh
@@ -0,0 +1,100 @@
+#!/bin/sh
+# with-session-bus.sh - run a program with a temporary D-Bus session daemon
+#
+# The canonical location of this program is the telepathy-glib tools/
+# directory, please synchronize any changes with that copy.
+#
+# Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved.
+
+set -e
+
+me=with-session-bus
+
+dbus_daemon_args="--print-address=5 --print-pid=6 --fork"
+sleep=0
+
+usage ()
+{
+ echo "usage: $me [options] -- program [program_options]" >&2
+ echo "Requires write access to the current directory." >&2
+ echo "" >&2
+ echo "If \$WITH_SESSION_BUS_FORK_DBUS_MONITOR is set, fork dbus-monitor" >&2
+ echo "with the arguments in \$WITH_SESSION_BUS_FORK_DBUS_MONITOR_OPT." >&2
+ echo "The output of dbus-monitor is saved in $me-<pid>.dbus-monitor-logs" >&2
+ exit 2
+}
+
+while test "z$1" != "z--"; do
+ case "$1" in
+ --sleep=*)
+ sleep="$1"
+ sleep="${sleep#--sleep=}"
+ shift
+ ;;
+ --session)
+ dbus_daemon_args="$dbus_daemon_args --session"
+ shift
+ ;;
+ --config-file=*)
+ # FIXME: assumes config file doesn't contain any special characters
+ dbus_daemon_args="$dbus_daemon_args $1"
+ shift
+ ;;
+ *)
+ usage
+ ;;
+ esac
+done
+shift
+if test "z$1" = "z"; then usage; fi
+
+exec 5> $me-$$.address
+exec 6> $me-$$.pid
+
+cleanup ()
+{
+ pid=`head -n1 $me-$$.pid`
+ if test -n "$pid" ; then
+ if [ -n "$VERBOSE_TESTS" ]; then
+ echo "Killing temporary bus daemon: $pid" >&2
+ fi
+ kill -INT "$pid"
+ fi
+ rm -f $me-$$.address
+ rm -f $me-$$.pid
+}
+
+trap cleanup INT HUP TERM
+dbus-daemon $dbus_daemon_args
+
+if [ -n "$VERBOSE_TESTS" ]; then
+ { echo -n "Temporary bus daemon is "; cat $me-$$.address; } >&2
+ { echo -n "Temporary bus daemon PID is "; head -n1 $me-$$.pid; } >&2
+fi
+
+e=0
+DBUS_SESSION_BUS_ADDRESS="`cat $me-$$.address`"
+export DBUS_SESSION_BUS_ADDRESS
+DBUS_SESSION_BUS_PID="`cat $me-$$.pid`"
+export DBUS_SESSION_BUS_PID
+
+if [ -n "$WITH_SESSION_BUS_FORK_DBUS_MONITOR" ] ; then
+ echo -n "Forking dbus-monitor $WITH_SESSION_BUS_FORK_DBUS_MONITOR_OPT" >&2
+ dbus-monitor $WITH_SESSION_BUS_FORK_DBUS_MONITOR_OPT \
+ > $me-$$.dbus-monitor-logs 2>&1 &
+fi
+
+"$@" || e=$?
+
+if test $sleep != 0; then
+ sleep $sleep
+fi
+
+trap - INT HUP TERM
+cleanup
+
+exit $e