diff options
author | Jonny Lamb <jonny.lamb@collabora.co.uk> | 2011-11-30 16:29:57 +0000 |
---|---|---|
committer | Jonny Lamb <jonny.lamb@collabora.co.uk> | 2011-11-30 16:29:57 +0000 |
commit | 119d0f1b44482095ee699450dfffa925e5cb96e9 (patch) | |
tree | ceedffdf99f0bd48f10fd289d66c60915cc5d912 | |
parent | c27d23ab605c1e01a2fe5542f6140d71f47eccc4 (diff) | |
parent | 754dd092fd2a8c26e2c1ac30e629946d70318c61 (diff) |
Merge remote-tracking branch 'yell/testing'
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>' </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> <interface> </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> </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 </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> </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 </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> </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 </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> </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>' </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>' </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()"> → </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 </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> </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 </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> </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()"> → </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 </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> </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> + ) → + <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>' </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>' </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 </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> </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>}}} </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> </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_##x (\\' + % (self.prefix_, self.node_name_lc)) + self.d(' * klass, my_object_##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('&', '&').replace("'", ''').replace('"', '"') + return s.replace('<', '<').replace('>', '>') 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 |