diff options
author | Jonny Lamb <jonny.lamb@collabora.co.uk> | 2011-11-30 16:29:53 +0000 |
---|---|---|
committer | Jonny Lamb <jonny.lamb@collabora.co.uk> | 2011-11-30 16:29:53 +0000 |
commit | e6463976a330fadb28c2595a1b8cddcbe4439bd5 (patch) | |
tree | 8e645212281ddc329638a9092c8f1685bcde314e | |
parent | f5a6ffcdabbb6fa1c62a923611ee637753650375 (diff) | |
parent | 07203ce78f7f2703054067cbc27893933770f6b4 (diff) |
Merge remote-tracking branch 'farstream/testing'
114 files changed, 27395 insertions, 0 deletions
diff --git a/farstream/.gitignore b/farstream/.gitignore new file mode 100644 index 000000000..e917fd778 --- /dev/null +++ b/farstream/.gitignore @@ -0,0 +1,66 @@ +*.lo +*.loT +*.o +*.la +*.py[co] +Makefile +Makefile.in +.deps +.libs +*~ +*# +.#* +*.stamp + +telepathy-farstream/tf-signals-marshal.[ch] +telepathy-farstream/tf-signals-marshal.list +telepathy-farstream/telepathy-farstream.pc +telepathy-farstream/telepathy-farstream-uninstalled.pc + +doc/lib/*.bak +doc/lib/html/ +doc/lib/telepathy-farstream.args +doc/lib/telepathy-farstream-decl-list.txt +doc/lib/telepathy-farstream-decl.txt +doc/lib/telepathy-farstream.hierarchy +doc/lib/telepathy-farstream.interfaces +doc/lib/telepathy-farstream-overrides.txt +doc/lib/telepathy-farstream.prerequisites +doc/lib/telepathy-farstream.signals +doc/lib/telepathy-farstream-undeclared.txt +doc/lib/telepathy-farstream-undocumented.txt +doc/lib/telepathy-farstream-unused.txt +doc/lib/tmpl/ +doc/lib/xml/ + +INSTALL +aclocal.m4 +autom4te.cache +autoregen.sh +config.guess +config.h +config.h.in +config.log +config.status +config.sub +configure +compile +depcomp +diff +install-sh +libtool +ltmain.sh +missing +stamp-h.in +stamp-h1 +gtk-doc.make +m4/gtk-doc.m4 +python/pytpfarstream.c +examples/call-handler +extensions/_gen/ + +m4/libtool.m4 +m4/ltoptions.m4 +m4/ltsugar.m4 +m4/ltversion.m4 +m4/lt~obsolete.m4 diff --git a/farstream/AUTHORS b/farstream/AUTHORS new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/farstream/AUTHORS diff --git a/farstream/COPYING b/farstream/COPYING new file mode 100644 index 000000000..68e307cb6 --- /dev/null +++ b/farstream/COPYING @@ -0,0 +1,463 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control +compilation and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + diff --git a/farstream/ChangeLog b/farstream/ChangeLog new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/farstream/ChangeLog diff --git a/farstream/Makefile.am b/farstream/Makefile.am new file mode 100644 index 000000000..18cffacf9 --- /dev/null +++ b/farstream/Makefile.am @@ -0,0 +1,34 @@ +ACLOCAL_AMFLAGS = -I m4 + + +if WANT_PYTHON + PYTHON_SUBDIR = python +endif + +SUBDIRS = m4 \ + tools \ + extensions \ + telepathy-farstream \ + doc \ + $(PYTHON_SUBDIR) \ + examples + +DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc + +EXTRA_DIST = \ + autogen.sh \ + gtk-doc.make + +maintainer-upload-release: _maintainer-upload-release-local +_maintainer-upload-release-local: _maintainer-upload-release-check + rsync -rtvzPp --chmod=Dg+s,ug+rwX,o=rX doc/lib/html/ \ + telepathy.freedesktop.org:/srv/telepathy.freedesktop.org/www/doc/telepathy-farstream/ + +BRANCH = misc +UPLOAD_BRANCH_TO = people.freedesktop.org:public_html/telepathy-farstream + +upload-branch-docs: all + rsync -rtzvPp --chmod=a+rX doc/lib/html/ \ + $(UPLOAD_BRANCH_TO)-$(BRANCH)/ + +include tools/telepathy.am diff --git a/farstream/NEWS b/farstream/NEWS new file mode 100644 index 000000000..1376a6daa --- /dev/null +++ b/farstream/NEWS @@ -0,0 +1,105 @@ +telepathy-farstream 0.1.1 (14 Jul 2011) +====================================== +- Fix the python bindings +- Make the VideoControl interface actually work + +telepathy-farstream 0.1.0 (27 Jun 2011) +====================================== +- Rename from telepathy-farsight to telepathy-farstream +- Implement Call API +- Also implement Streamed Media API under the same C api + +telepathy-farsight 0.0.16 (22 Dec 2010) +======================================= +- Emit the NewActiveTransportPair signal +- Emit CodecsUpdated more often +- Various bug fixes + +telepathy-farsight 0.0.15 (30 Sep 2010) +======================================= +- Release sending resource when SetStreamSending(False) is called + +telepathy-farsight 0.0.14 (26 May 2010) +======================================= +- Add properties to get the Farsight2 session and stream +- Recognize the shm transmitter +- Ignore invalidly empty strings in tp properties +- Fix -Wshadow warnings +- Use silent rules if automake >= 1.11 + +telepathy-farsight 0.0.13 (5 Jan 2010) +====================================== +- Export held resource in a property +- Transfer the ptime/maxptime + +telepathy-farsight 0.0.12 (15 Oct 2009) +======================================= +- Fix mixup between GSlice and malloc +- Fix potential race between src-pad-added and dispose +- The connected state in farsight is a lie, ignore it + +telepathy-farsight 0.0.11 (10 Sep 2009) +======================================= +- Fix double free +- Fix more leaks + +telepathy-farsight 0.0.10 (08 Sep 2009) +====================================== +- Fix some leaks +- Fix possible deadlocks +- Emit different error codes depending on the error +- Emit stream state changes when the stream state changes according to ICE + +telepathy-farsight 0.0.9 (03 Aug 2009) +====================================== +- Emit session-invalidated on channel dispose (prevents leak) +- Fix ICE priority mangling (so not all candidates get 0) +- Use new error numbers from the the 0.17.27 spec + +telepathy-farsight 0.0.8 (03 Aug 2009) +====================================== +- Set ToS property on streams +- Set ICE controlling according to the CreatedLocally property +- Work around bug in the dbus-glib 0.80 of dbus properties +- Fix bugs + +telepathy-farsight 0.0.7 (06 May 2009) +====================================== +- Remove pygtk requirement +- Print errors in its own domain +- Update tp-glib dependency to 0.7.26 and fs2 dependency to 0.0.7 +- Make it more resilient in case of errors from other layers + +telepathy-farsight 0.0.6 (17 March 2009) +======================================== +- Add support of the RelayInfo property + +telepathy-farsight 0.0.5 (16 March 2009) +======================================== +- Recognize ice-udp +- Improve error handling +- Support the new CodecsUpdated method + +telepathy-farsight 0.0.4 (14 January 2009) +========================================== +- Add python bindings for tpfarsight +- Fix hold +- Make the "request-resource" signal optional, assumes + the resource is always there if there is no handler. + +telepathy-farsight 0.0.3 (21 November 2008) +=========================================== +- Fix small brown-paper bug in last release +- Rename tf_channel_new_from_proxy to tf_channel_new, + and leave the proxy creation to the client + +telepathy-farsight 0.0.2 (21 November 2008) +=========================================== + +- Added various makefile niceties from telepathy-glib + +telepathy-farsight 0.0.1 (21 November 2008) +=========================================== + +- Initial version +- Split from stream-engine after 0.5.3 diff --git a/farstream/README b/farstream/README new file mode 100644 index 000000000..3a18752db --- /dev/null +++ b/farstream/README @@ -0,0 +1,70 @@ +======================= +telepathy-farstream +======================= + +Telepathy Farstream is a Telepathy client library that uses Farstream +to handle Call channels. + +Telepathy is a D-Bus framework for unifying real time communication, +including instant messaging, voice calls and video calls. It abstracts +differences between protocols to provide a unified interface for +applications. + +Requirements +============ + +telepathy-farstream requires: + telepathy-glib <http://telepathy.freedesktop.org/releases/telepathy-glib/> + Farstream <http://farsight.freedesktop.org/releases/farstream/> + GStreamer base plugins <http://gstreamer.freedesktop.org/> + GLib, GObject <http://ftp.gnome.org/pub/GNOME/sources/glib/> + libdbus <http://dbus.freedesktop.org/releases/dbus/> + The D-Bus GLib bindings <http://dbus.freedesktop.org/releases/dbus-glib/> + +At build time, it also requires: + GNU make <http://www.gnu.org/software/make/> + pkg-config <http://ftp.gnome.org/pub/GNOME/sources/pkg-config/> + libxslt, xsltproc <http://xmlsoft.org/XSLT/> + Python <http://www.python.org/> + +See configure.ac for full details, including versions required. + +Building from Darcs also requires the GNU build system (Autoconf, Automake, +libtool). + +Bugs, feature requests and to-do list +===================================== + +Report all bugs, feature requests and "to-do" items here: + <https://bugs.freedesktop.org/enter_bug.cgi?product=Telepathy&component=tp-farstream> + +Versioning policy +================= + +We use an "odd/even" versioning scheme where the minor version (the y in +x.y.z) determines stability - stable branches have y even, development +branches have y odd. + +Unreleased builds straight from Darcs identify themselves as version +"x.y.z.1". These are compiled with -Werror, so they might stop working +if your gcc version issues more warnings than ours. If this is a problem +for you, use a release tarball. + +Contact info +============ + +This package is maintained by the Telepathy project: + <http://telepathy.freedesktop.org/> + <mailto:telepathy@lists.freedesktop.org> + <irc://irc.freenode.net/telepathy> + +Telepathy development is supported by Collabora Ltd. + <http://www.collabora.co.uk/>. + +Hacking +======= + +The current version of telepathy-farstream is always available from: + <http://git.collabora.co.uk/?p=telepathy-farstream.git;a=summary> + +Please follow <http://telepathy.freedesktop.org/wiki/Style>. diff --git a/farstream/autogen.sh b/farstream/autogen.sh new file mode 100755 index 000000000..822ebf863 --- /dev/null +++ b/farstream/autogen.sh @@ -0,0 +1,33 @@ +#!/bin/sh +set -e + +gtkdocize --copy + +if test -n "$AUTOMAKE"; then + : # don't override an explicit user request +elif automake-1.9 --version >/dev/null 2>/dev/null && \ + aclocal-1.9 --version >/dev/null 2>/dev/null; then + # If we have automake-1.9, use it. This helps to ensure that our build + # system doesn't accidentally grow automake-1.10 dependencies. + AUTOMAKE=automake-1.9 + export AUTOMAKE + ACLOCAL=aclocal-1.9 + export ACLOCAL +fi + +autoreconf -i -f + +run_configure=true +for arg in $*; do + case $arg in + --no-configure) + run_configure=false + ;; + *) + ;; + esac +done + +if test $run_configure = true; then + ./configure "$@" +fi diff --git a/farstream/configure.ac b/farstream/configure.ac new file mode 100644 index 000000000..67cf5b47a --- /dev/null +++ b/farstream/configure.ac @@ -0,0 +1,154 @@ +AC_PREREQ([2.59]) + +# Making releases: +# set the new version number: +# odd minor -> development series +# even minor -> stable series +# increment micro for each release within a series +# set tp_farstream_release to 1 + +m4_define([tp_farstream_major_version], [0]) +m4_define([tp_farstream_minor_version], [1]) +m4_define([tp_farstream_micro_version], [1]) +m4_define([tp_farstream_nano_version], [1]) + +dnl CURRENT, REVISION, AGE +dnl - library source changed -> increment REVISION +dnl - interfaces added/removed/changed -> increment CURRENT, REVISION = 0 +dnl - interfaces added -> increment AGE +dnl - interfaces removed -> AGE = 0 +m4_define([tp_farstream_lt_current], [0]) +m4_define([tp_farstream_lt_revision], [1]) +m4_define([tp_farstream_lt_age], [0]) + + +# Some magic +m4_define([tp_farstream_base_version], + [tp_farstream_major_version.tp_farstream_minor_version.tp_farstream_micro_version]) +m4_define([tp_farstream_version], + [m4_if(tp_farstream_nano_version, 0, [tp_farstream_base_version], + [tp_farstream_base_version].[tp_farstream_nano_version])]) + +AC_INIT([Telepathy-Farstream], [tp_farstream_version], + [https://bugs.freedesktop.org/enter_bug.cgi?product=Telepathy&component=tp-farstream]) + +AC_CONFIG_MACRO_DIR([m4]) + +AM_INIT_AUTOMAKE([1.9 -Wno-portability]) +AM_CONFIG_HEADER(config.h) + +dnl use pretty build output with automake >= 1.11 +m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])], + [AM_DEFAULT_VERBOSITY=1 + AC_SUBST(AM_DEFAULT_VERBOSITY)]) + +dnl check for tools +AC_PROG_CC +AC_PROG_CC_STDC +AC_PROG_INSTALL +AC_PROG_LIBTOOL + +dnl decide error flags +AS_COMPILER_FLAG(-Wall, ERROR_CFLAGS="-Wall", ERROR_CFLAGS="") +AS_COMPILER_FLAG(-Werror, werror=yes, werror=no) + +AC_ARG_ENABLE(Werror, + AC_HELP_STRING([--disable-Werror],[compile without -Werror (normally enabled in development builds)]), + werror=$enableval, :) + +AS_COMPILER_FLAG(-Wextra, wextra=yes, wextra=no) +AS_COMPILER_FLAG(-Wno-missing-field-initializers, + wno_missing_field_initializers=yes, + wno_missing_field_initializers=no) +AS_COMPILER_FLAG(-Wno-unused-parameter, + wno_unused_parameter=yes, + wno_unused_parameter=no) + +ifelse(tp_farstream_nano_version, 0, [], + [ + if test x$werror = xyes; then + ERROR_CFLAGS="$ERROR_CFLAGS -Werror" + fi + if test x$wextra = xyes -a \ + x$wno_missing_field_initializers = xyes -a \ + x$wno_unused_parameter = xyes; then + ERROR_CFLAGS="$ERROR_CFLAGS -Wextra -Wno-missing-field-initializers -Wno-unused-parameter" + fi + ]) + +AC_SUBST(ERROR_CFLAGS) + +AC_ARG_ENABLE(coverage, [ --enable-coverage compile with coverage info],[enable_coverage=${enableval}],enable_coverage=no) +if test "x$enable_coverage" = "xyes"; then + CFLAGS="$CFLAGS -g -fprofile-arcs -ftest-coverage" +fi + +dnl Check for Glib +PKG_CHECK_MODULES(GLIB, gobject-2.0 >= 2.16 glib-2.0 >= 2.16 gio-2.0) + +GLIB_GENMARSHAL=`$PKG_CONFIG --variable=glib_genmarshal glib-2.0` +AC_SUBST(GLIB_CFLAGS) +AC_SUBST(GLIB_LIBS) +AC_SUBST(GLIB_GENMARSHAL) + +dnl Check for DBus +PKG_CHECK_MODULES(DBUS, [dbus-1 >= 0.60, dbus-glib-1 >= 0.60]) + +AC_SUBST(DBUS_CFLAGS) +AC_SUBST(DBUS_LIBS) + +dnl Check for Telepathy libraries +PKG_CHECK_MODULES([TELEPATHY], [telepathy-glib >= 0.13.4]) + +AC_SUBST(TELEPATHY_CFLAGS) +AC_SUBST(TELEPATHY_LIBS) + +dnl Check for farstream +PKG_CHECK_MODULES(FARSTREAM, [farstream-0.1 >= 0.1.0]) + +AC_SUBST(FARSTREAM_CFLAGS) +AC_SUBST(FARSTREAM_LIBS) + +dnl Always required to generate extensions +AM_PATH_PYTHON([2.5]) + +AC_ARG_ENABLE([python], + AC_HELP_STRING([--disable-python], [Disable Python bindings]), + [case "${enableval}" in + yes) WANT_PYTHON=yes ;; + no) WANT_PYTHON=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-python) ;; + esac], + WANT_PYTHON=yes) + +if test "x$WANT_PYTHON" = "xyes"; then + AM_CHECK_PYTHON_HEADERS(,[AC_MSG_ERROR(could not find Python headers)]) + PKG_CHECK_MODULES(PYTPFARSTREAM, [ pygobject-2.0 >= 2.12.0 + gst-python-0.10 >= 0.10.10 ]) +fi +AM_CONDITIONAL(WANT_PYTHON, test "x$WANT_PYTHON" = "xyes") + +GTK_DOC_CHECK([1.10]) + +LT_CURRENT=tp_farstream_lt_current +LT_REVISION=tp_farstream_lt_revision +LT_AGE=tp_farstream_lt_age +AC_SUBST([LT_CURRENT]) +AC_SUBST([LT_REVISION]) +AC_SUBST([LT_AGE]) + + +AC_OUTPUT( Makefile \ + doc/Makefile \ + doc/lib/Makefile \ + m4/Makefile \ + extensions/Makefile \ + examples/Makefile \ + telepathy-farstream/Makefile \ + telepathy-farstream/telepathy-farstream.pc \ + telepathy-farstream/telepathy-farstream-uninstalled.pc + tools/Makefile \ + python/Makefile \ + python/codegen/Makefile \ + python/examples/Makefile \ +) diff --git a/farstream/doc/Makefile.am b/farstream/doc/Makefile.am new file mode 100644 index 000000000..0262e4ddc --- /dev/null +++ b/farstream/doc/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = lib diff --git a/farstream/doc/lib/Makefile.am b/farstream/doc/lib/Makefile.am new file mode 100644 index 000000000..59bfce77c --- /dev/null +++ b/farstream/doc/lib/Makefile.am @@ -0,0 +1,106 @@ +## Process this file with automake to produce Makefile.in + +abs_top_builddir = @abs_top_builddir@ + +# We require automake 1.6 at least. +AUTOMAKE_OPTIONS = 1.6 + +# The name of the module, e.g. 'glib'. +DOC_MODULE=telepathy-farstream + +# The top-level SGML file. You can change this if you want to. +DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.sgml + +# The directory containing the source code. Relative to $(srcdir). +# gtk-doc will search all .c & .h files beneath here for inline comments +# documenting the functions and macros. +# e.g. DOC_SOURCE_DIR=../../../gtk +DOC_SOURCE_DIR=../../telepathy-farstream + +# Extra options to pass to gtkdoc-scangobj. Not normally needed. +SCANGOBJ_OPTIONS= + +# Extra options to supply to gtkdoc-scan. +# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED" +SCAN_OPTIONS= + +# Extra options to supply to gtkdoc-mkdb. +# e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml +MKDB_OPTIONS=--sgml-mode --output-format=xml + +# Extra options to supply to gtkdoc-mktmpl +# e.g. MKTMPL_OPTIONS=--only-section-tmpl +MKTMPL_OPTIONS= + +# Extra options to supply to gtkdoc-fixref. Not normally needed. +# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html +FIXXREF_OPTIONS= + +# Used for dependencies. The docs will be rebuilt if any of these change. +# e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h +# e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c +HFILE_GLOB=$(top_srcdir)/telepathy-farstream/channel.h $(top_srcdir)/telepathy-farstream/content.h +CFILE_GLOB=$(top_srcdir)/telepathy-farstream/channel.c $(top_srcdir)/telepathy-farstream/content.c + +# Header files to ignore when scanning. +# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h +IGNORE_HFILES=channel-priv.h content-priv.h session-priv.h stream-priv.h \ + stream.h call-channel.h call-content.h call-stream.h tf-signals-marshal.h \ + media-signalling-channel.h media-signalling-content.h + +# Images to copy into HTML directory. +# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png +HTML_IMAGES= + +# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE). +# e.g. content_files=running.sgml building.sgml changes-2.0.sgml +content_files= + +# SGML files where gtk-doc abbrevations (#GtkWidget) are expanded +# These files must be listed here *and* in content_files +# e.g. expand_content_files=running.sgml +expand_content_files= + +# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library. +# Only needed if you are using gtkdoc-scangobj to dynamically query widget +# signals and properties. +# e.g. INCLUDES=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS) +# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib) +INCLUDES= \ + $(GLIB_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(X11_CFLAGS) \ + $(GST_CFLAGS) \ + $(GST_INTERFACES_CFLAGS) \ + $(FARSTREAM_CFLAGS) \ + $(TELEPATHY_CFLAGS) \ + -I$(top_srcdir) -I$(top_builddir) +GTKDOC_LIBS= \ + $(GLIB_LIBS) \ + $(DBUS_LIBS) \ + $(X11_LIBS) \ + $(GST_LIBS) \ + $(GST_INTERFACES_LIBS) \ + $(TELEPATHY_LIBS) \ + $(FARSTREAM_LIBS) \ + $(top_builddir)/telepathy-farstream/libtelepathy-farstream.la + +# This includes the standard gtk-doc make rules, copied by gtkdocize. +include $(top_srcdir)/gtk-doc.make + +if ENABLE_GTK_DOC +check-local: + @if grep '^0 symbols incomplete' \ + $(srcdir)/telepathy-farstream-undocumented.txt; then\ + :; else \ + cat $(srcdir)/telepathy-farstream-undocumented.txt; exit 1; fi + @if grep '^0 not documented' \ + $(srcdir)/telepathy-farstream-undocumented.txt; then\ + :; else \ + cat $(srcdir)/telepathy-farstream-undocumented.txt; exit 1; fi + @if grep . $(srcdir)/telepathy-farstream-unused.txt; then\ + echo "^^^ Unused symbols" >&2; exit 1; fi + @if test -e $(srcdir)/telepathy-farstream-undeclared.txt &&\ + grep . $(srcdir)/telepathy-farstream-undeclared.txt; then\ + echo "^^^ Undeclared symbols" >&2; exit 1; fi +endif diff --git a/farstream/doc/lib/telepathy-farstream-docs.sgml b/farstream/doc/lib/telepathy-farstream-docs.sgml new file mode 100644 index 000000000..11bd7951e --- /dev/null +++ b/farstream/doc/lib/telepathy-farstream-docs.sgml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" + "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"> +<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude"> + <bookinfo> + <title>telepathy-farstream Reference Manual</title> + <releaseinfo> + The latest version of this documentation can be found on-line at + <ulink role="online-location" url="http://telepathy.freedesktop.org/doc/telepathy-farstream/">http://telepathy.freedesktop.org/telepathy-farstream/</ulink>. + </releaseinfo> + </bookinfo> + + <chapter> + <title>Telepathy Farstream Helper Classes</title> + <xi:include href="xml/channel.xml"/> + <xi:include href="xml/content.xml"/> + </chapter> +</book> diff --git a/farstream/doc/lib/telepathy-farstream-sections.txt b/farstream/doc/lib/telepathy-farstream-sections.txt new file mode 100644 index 000000000..4a1382da8 --- /dev/null +++ b/farstream/doc/lib/telepathy-farstream-sections.txt @@ -0,0 +1,43 @@ +<SECTION> +<TITLE>Core</TITLE> +tf_init +tp_media_type_to_fs +</SECTION> + +<SECTION> +<FILE>content</FILE> +<TITLE>TfContent</TITLE> +TfContent +TfContentClass +tf_content_iterate_src_pads +tf_content_error +tf_content_error_literal +<SUBSECTION Private> +TfContentPrivate +<SUBSECTION Standard> +TF_CONTENT +TF_TYPE_CONTENT +tf_content_get_type +TF_CONTENT_CLASS +TF_CONTENT_GET_CLASS +TF_IS_CONTENT +TF_IS_CONTENT_CLASS +</SECTION> + +<SECTION> +<FILE>channel</FILE> +<TITLE>TfChannel</TITLE> +TfChannel +tf_channel_new_async +tf_channel_bus_message +<SUBSECTION Private> +TfChannelPrivate +<SUBSECTION Standard> +TF_CHANNEL +TF_TYPE_CHANNEL +tf_channel_get_type +TF_CHANNEL_CLASS +TF_CHANNEL_GET_CLASS +TF_IS_CHANNEL +TF_IS_CHANNEL_CLASS +</SECTION> diff --git a/farstream/doc/lib/telepathy-farstream.types b/farstream/doc/lib/telepathy-farstream.types new file mode 100644 index 000000000..a2e28ba0d --- /dev/null +++ b/farstream/doc/lib/telepathy-farstream.types @@ -0,0 +1,2 @@ +tf_content_get_type +tf_channel_get_type diff --git a/farstream/examples/Makefile.am b/farstream/examples/Makefile.am new file mode 100644 index 000000000..ff2e7c34e --- /dev/null +++ b/farstream/examples/Makefile.am @@ -0,0 +1,20 @@ +noinst_PROGRAMS = call-handler + +LDADD = \ + ../telepathy-farstream/libtelepathy-farstream.la \ + ../extensions/libfuture-extensions.la \ + $(GLIB_LIBS) \ + $(DBUS_LIBS) \ + $(GST_LIBS) \ + $(FARSTREAM_LIBS) \ + $(TELEPATHY_LIBS) + +AM_CFLAGS = \ + $(ERROR_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(GST_CFLAGS) \ + $(FARSTREAM_CFLAGS) \ + $(TELEPATHY_CFLAGS) \ + -I$(top_srcdir) \ + -I$(top_builddir) diff --git a/farstream/examples/call-handler.c b/farstream/examples/call-handler.c new file mode 100644 index 000000000..5385a26a9 --- /dev/null +++ b/farstream/examples/call-handler.c @@ -0,0 +1,534 @@ +/* + * call-handler.c + * Copyright (C) 2011 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <gst/gst.h> +#include <telepathy-glib/telepathy-glib.h> +#include <extensions/extensions.h> +#include <farstream/fs-element-added-notifier.h> +#include <farstream/fs-utils.h> +#include <telepathy-farstream/telepathy-farstream.h> + +typedef struct { + GstElement *pipeline; + guint buswatch; + TpChannel *proxy; + TfChannel *channel; + GList *notifiers; + + GstElement *video_input; + GstElement *video_capsfilter; + + guint width; + guint height; + guint framerate; +} ChannelContext; + +GMainLoop *loop; + +static gboolean +bus_watch_cb (GstBus *bus, + GstMessage *message, + gpointer user_data) +{ + ChannelContext *context = user_data; + + if (context->channel != NULL) + tf_channel_bus_message (context->channel, message); + + if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) + { + GError *error = NULL; + gchar *debug = NULL; + gst_message_parse_error (message, &error, &debug); + g_printerr ("ERROR from element %s: %s\n", + GST_OBJECT_NAME (message->src), error->message); + g_printerr ("Debugging info: %s\n", (debug) ? debug : "none"); + g_error_free (error); + g_free (debug); + } + + return TRUE; +} + +static void +src_pad_added_cb (TfContent *content, + TpHandle handle, + FsStream *stream, + GstPad *pad, + FsCodec *codec, + gpointer user_data) +{ + ChannelContext *context = user_data; + gchar *cstr = fs_codec_to_string (codec); + FsMediaType mtype; + GstPad *sinkpad; + GstElement *element; + GstStateChangeReturn ret; + + g_debug ("New src pad: %s", cstr); + g_object_get (content, "media-type", &mtype, NULL); + + switch (mtype) + { + case FS_MEDIA_TYPE_AUDIO: + element = gst_parse_bin_from_description ( + "audioconvert ! audioresample ! audioconvert ! autoaudiosink", + TRUE, NULL); + break; + case FS_MEDIA_TYPE_VIDEO: + element = gst_parse_bin_from_description ( + "ffmpegcolorspace ! videoscale ! autovideosink", + TRUE, NULL); + break; + default: + g_warning ("Unknown media type"); + return; + } + + gst_bin_add (GST_BIN (context->pipeline), element); + sinkpad = gst_element_get_pad (element, "sink"); + ret = gst_element_set_state (element, GST_STATE_PLAYING); + if (ret == GST_STATE_CHANGE_FAILURE) + { + tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL); + g_warning ("Failed to start sink pipeline !?"); + return; + } + + if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sinkpad))) + { + tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL); + g_warning ("Couldn't link sink pipeline !?"); + return; + } + + g_object_unref (sinkpad); +} + +static void +update_video_parameters (ChannelContext *context, gboolean restart) +{ + GstCaps *caps; + GstClock *clock; + + if (restart) + { + /* Assuming the pipeline is in playing state */ + gst_element_set_locked_state (context->video_input, TRUE); + gst_element_set_state (context->video_input, GST_STATE_NULL); + } + + g_object_get (context->video_capsfilter, "caps", &caps, NULL); + caps = gst_caps_make_writable (caps); + + gst_caps_set_simple (caps, + "framerate", GST_TYPE_FRACTION, context->framerate, 1, + "width", G_TYPE_INT, context->width, + "height", G_TYPE_INT, context->height, + NULL); + + g_object_set (context->video_capsfilter, "caps", caps, NULL); + + if (restart) + { + clock = gst_pipeline_get_clock (GST_PIPELINE (context->pipeline)); + /* Need to reset the clock if we set the pipeline back to ready by hand */ + if (clock != NULL) + { + gst_element_set_clock (context->video_input, clock); + g_object_unref (clock); + } + + gst_element_set_locked_state (context->video_input, FALSE); + gst_element_sync_state_with_parent (context->video_input); + } +} + +static void +on_video_framerate_changed (TfContent *content, + GParamSpec *spec, + ChannelContext *context) +{ + guint framerate; + + g_object_get (content, "framerate", &framerate, NULL); + + if (framerate != 0) + context->framerate = framerate; + + update_video_parameters (context, FALSE); +} + +static void +on_video_resolution_changed (TfContent *content, + guint width, + guint height, + ChannelContext *context) +{ + g_assert (width > 0 && height > 0); + + context->width = width; + context->height = height; + + update_video_parameters (context, TRUE); +} + +static GstElement * +setup_video_source (ChannelContext *context, TfContent *content) +{ + GstElement *result, *input, *rate, *scaler, *colorspace, *capsfilter; + GstCaps *caps; + guint framerate = 0, width = 0, height = 0; + GstPad *pad, *ghost; + + result = gst_bin_new ("video_input"); + input = gst_element_factory_make ("autovideosrc", NULL); + rate = gst_element_factory_make ("videomaxrate", NULL); + scaler = gst_element_factory_make ("videoscale", NULL); + colorspace = gst_element_factory_make ("colorspace", NULL); + capsfilter = gst_element_factory_make ("capsfilter", NULL); + + g_assert (input && rate && scaler && colorspace && capsfilter); + g_object_get (content, + "framerate", &framerate, + "width", &width, + "height", &height, + NULL); + + if (framerate == 0) + framerate = 15; + + if (width == 0 || height == 0) + { + width = 320; + height = 240; + } + + context->framerate = framerate; + context->width = width; + context->height = height; + + caps = gst_caps_new_simple ("video/x-raw-yuv", + "width", G_TYPE_INT, width, + "height", G_TYPE_INT, height, + "framerate", GST_TYPE_FRACTION, framerate, 1, + NULL); + + g_object_set (G_OBJECT (capsfilter), "caps", caps, NULL); + + gst_bin_add_many (GST_BIN (result), input, rate, scaler, + colorspace, capsfilter, NULL); + g_assert (gst_element_link_many (input, rate, scaler, + colorspace, capsfilter, NULL)); + + pad = gst_element_get_static_pad (capsfilter, "src"); + g_assert (pad != NULL); + + ghost = gst_ghost_pad_new ("src", pad); + gst_element_add_pad (result, ghost); + + g_object_unref (pad); + + context->video_input = result; + context->video_capsfilter = capsfilter; + + g_signal_connect (content, "notify::framerate", + G_CALLBACK (on_video_framerate_changed), + context); + + g_signal_connect (content, "resolution-changed", + G_CALLBACK (on_video_resolution_changed), + context); + + return result; +} + + +static void +content_added_cb (TfChannel *channel, + TfContent *content, + gpointer user_data) +{ + GstPad *srcpad, *sinkpad; + FsMediaType mtype; + GstElement *element; + GstStateChangeReturn ret; + ChannelContext *context = user_data; + + g_debug ("Content added"); + + g_object_get (content, + "sink-pad", &sinkpad, + "media-type", &mtype, + NULL); + + switch (mtype) + { + case FS_MEDIA_TYPE_AUDIO: + element = gst_parse_bin_from_description ( + "audiotestsrc is-live=1 ! audio/x-raw-int,rate=8000 ! queue" + " ! audioconvert ! audioresample ! audioconvert ", + + TRUE, NULL); + break; + case FS_MEDIA_TYPE_VIDEO: + element = setup_video_source (context, content); + break; + default: + g_warning ("Unknown media type"); + goto out; + } + + g_signal_connect (content, "src-pad-added", + G_CALLBACK (src_pad_added_cb), context); + + gst_bin_add (GST_BIN (context->pipeline), element); + srcpad = gst_element_get_pad (element, "src"); + + if (GST_PAD_LINK_FAILED (gst_pad_link (srcpad, sinkpad))) + { + tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL); + g_warning ("Couldn't link source pipeline !?"); + return; + } + + ret = gst_element_set_state (element, GST_STATE_PLAYING); + if (ret == GST_STATE_CHANGE_FAILURE) + { + tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL); + g_warning ("source pipeline failed to start!?"); + return; + } + + g_object_unref (srcpad); +out: + g_object_unref (sinkpad); +} + +static void +conference_added_cb (TfChannel *channel, + GstElement *conference, + gpointer user_data) +{ + ChannelContext *context = user_data; + GKeyFile *keyfile; + + g_debug ("Conference added"); + + /* Add notifier to set the various element properties as needed */ + keyfile = fs_utils_get_default_element_properties (conference); + if (keyfile != NULL) + { + FsElementAddedNotifier *notifier; + g_debug ("Loaded default codecs for %s", GST_ELEMENT_NAME (conference)); + + notifier = fs_element_added_notifier_new (); + fs_element_added_notifier_set_properties_from_keyfile (notifier, keyfile); + fs_element_added_notifier_add (notifier, GST_BIN (context->pipeline)); + + context->notifiers = g_list_prepend (context->notifiers, notifier); + } + + + gst_bin_add (GST_BIN (context->pipeline), conference); + gst_element_set_state (conference, GST_STATE_PLAYING); +} + +static gboolean +dump_pipeline_cb (gpointer data) +{ + ChannelContext *context = data; + + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (context->pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, + "call-handler"); + + return TRUE; +} + +static void +new_tf_channel_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + ChannelContext *context = user_data; + + g_debug ("New TfChannel"); + + context->channel = TF_CHANNEL (g_async_initable_new_finish ( + G_ASYNC_INITABLE (source), result, NULL)); + + + if (context->channel == NULL) + { + g_warning ("Failed to create channel"); + return; + } + + g_debug ("Adding timeout"); + g_timeout_add_seconds (5, dump_pipeline_cb, context); + + g_signal_connect (context->channel, "fs-conference-added", + G_CALLBACK (conference_added_cb), context); + + g_signal_connect (context->channel, "content-added", + G_CALLBACK (content_added_cb), context); +} + +static void +proxy_invalidated_cb (TpProxy *proxy, + guint domain, + gint code, + gchar *message, + gpointer user_data) +{ + ChannelContext *context = user_data; + + g_debug ("Channel closed"); + if (context->pipeline != NULL) + { + gst_element_set_state (context->pipeline, GST_STATE_NULL); + g_object_unref (context->pipeline); + } + + if (context->channel != NULL) + g_object_unref (context->channel); + + g_list_foreach (context->notifiers, (GFunc) g_object_unref, NULL); + g_list_free (context->notifiers); + + g_object_unref (context->proxy); + + g_slice_free (ChannelContext, context); + + g_main_loop_quit (loop); +} + +static void +new_call_channel_cb (TpSimpleHandler *handler, + TpAccount *account, + TpConnection *connection, + GList *channels, + GList *requests_satisfied, + gint64 user_action_time, + TpHandleChannelsContext *handler_context, + gpointer user_data) +{ + ChannelContext *context; + TpChannel *proxy; + GstBus *bus; + GstElement *pipeline; + GstStateChangeReturn ret; + + g_debug ("New channel"); + + proxy = channels->data; + + pipeline = gst_pipeline_new (NULL); + + ret = gst_element_set_state (pipeline, GST_STATE_PLAYING); + + if (ret == GST_STATE_CHANGE_FAILURE) + { + tp_channel_close_async (TP_CHANNEL (proxy), NULL, NULL); + g_object_unref (pipeline); + g_warning ("Failed to start an empty pipeline !?"); + return; + } + + context = g_slice_new0 (ChannelContext); + context->pipeline = pipeline; + + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + context->buswatch = gst_bus_add_watch (bus, bus_watch_cb, context); + g_object_unref (bus); + + tf_channel_new_async (proxy, new_tf_channel_cb, context); + + tp_handle_channels_context_accept (handler_context); + + tf_future_cli_channel_type_call_call_accept (proxy, -1, + NULL, NULL, NULL, NULL); + + context->proxy = g_object_ref (proxy); + g_signal_connect (proxy, "invalidated", + G_CALLBACK (proxy_invalidated_cb), + context); +} + +int +main (int argc, char **argv) +{ + TpBaseClient *client; + TpDBusDaemon *bus; + + g_type_init (); + tf_init (); + gst_init (&argc, &argv); + + loop = g_main_loop_new (NULL, FALSE); + + bus = tp_dbus_daemon_dup (NULL); + + client = tp_simple_handler_new (bus, + FALSE, + FALSE, + "TpFsCallHandlerDemo", + TRUE, + new_call_channel_cb, + NULL, + NULL); + + tp_base_client_take_handler_filter (client, + tp_asv_new ( + TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, + TF_FUTURE_IFACE_CHANNEL_TYPE_CALL, + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, + TP_HANDLE_TYPE_CONTACT, + TF_FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, G_TYPE_BOOLEAN, + TRUE, + NULL)); + + tp_base_client_take_handler_filter (client, + tp_asv_new ( + TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, + TF_FUTURE_IFACE_CHANNEL_TYPE_CALL, + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, + TP_HANDLE_TYPE_CONTACT, + TF_FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, G_TYPE_BOOLEAN, + TRUE, + NULL)); + + tp_base_client_add_handler_capabilities_varargs (client, + TF_FUTURE_IFACE_CHANNEL_TYPE_CALL "/video/h264", + TF_FUTURE_IFACE_CHANNEL_TYPE_CALL "/shm", + TF_FUTURE_IFACE_CHANNEL_TYPE_CALL "/ice", + TF_FUTURE_IFACE_CHANNEL_TYPE_CALL "/gtalk-p2p", + NULL); + + tp_base_client_register (client, NULL); + + g_main_loop_run (loop); + + g_object_unref (bus); + g_object_unref (client); + g_main_loop_unref (loop); + + return 0; +} diff --git a/farstream/extensions/Call_Content.xml b/farstream/extensions/Call_Content.xml new file mode 100644 index 000000000..270d99b08 --- /dev/null +++ b/farstream/extensions/Call_Content.xml @@ -0,0 +1,255 @@ +<?xml version="1.0" ?> +<node name="/Call_Content" + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright> + <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright> + <tp:license xmlns="http://www.w3.org/1999/xhtml"> + <p>This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version.</p> + + <p>This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details.</p> + + <p>You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA.</p> + </tp:license> + + <interface name="org.freedesktop.Telepathy.Call.Content.DRAFT" + tp:causes-havoc="experimental"> + <tp:added version="0.19.0">(draft 1)</tp:added> + + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + This object represents one Content inside a <tp:dbus-ref + namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref>. For + example, in an audio/video call there would be one audio content + and one video content. Each content has one or more <tp:dbus-ref + namespace="ofdT.Call">Stream.DRAFT</tp:dbus-ref> objects which + represent the actual transport to one or more remote contacts. + </tp:docstring> + + <tp:enum name="Content_Removal_Reason" type="u"> + <tp:added version="0.21.2"/> + <tp:docstring> + A representation of the reason for a content to be removed, + which may be used by simple clients, or used as a fallback + when the DBus_Reason is not understood. This enum will be + extended with future reasons as and when appropriate, so + clients SHOULD keep up to date with its values, but also be + happy to fallback to the Unknown value when an unknown value + is encountered. + </tp:docstring> + + <tp:enumvalue suffix="Unknown" value="0"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + We just don't know. Unknown values of this enum SHOULD also be + treated like this. + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="User_Requested" value="1"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The local user requests that this content is removed + from the call.</p> + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="Error" value="2"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>There is an error with the content which means that it + has to be removed from the call.</p> + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="Unsupported" value="3"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Some aspect of the content is unsupported so has to be + removed from the call.</p> + </tp:docstring> + </tp:enumvalue> + </tp:enum> + + <method name="Remove" tp:name-for-bindings="Remove"> + <tp:changed version="0.21.2">previously there were no + arguments</tp:changed> + <tp:docstring> + Remove the content from the call. + </tp:docstring> + + <arg direction="in" name="Reason" type="u" + tp:type="Content_Removal_Reason"> + <tp:docstring> + A generic hangup reason. + </tp:docstring> + </arg> + + <arg direction="in" name="Detailed_Removal_Reason" type="s" + tp:type="DBus_Error_Name"> + <tp:docstring> + A more specific reason for the content removal, if one is + available, or an empty string. + </tp:docstring> + </arg> + + <arg direction="in" name="Message" type="s"> + <tp:docstring> + A human-readable message for the reason of removing the + content, such as "Fatal streaming failure" or "no codec + intersection". This property can be left empty if no reason + is to be given. + </tp:docstring> + </arg> + + <tp:possible-errors> + <tp:error name="org.freedesktop.Telepathy.Error.NetworkError" /> + <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented"> + <tp:docstring> + Raised when a Call doesn't support removing contents + (e.g. a Google Talk video call). + </tp:docstring> + </tp:error> + </tp:possible-errors> + </method> + + <signal name="Removed" tp:name-for-bindings="Removed"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Emitted when the content is removed from the call. This + is the same as the <tp:dbus-ref + namespace="ofdT.Channel.Type">Call.DRAFT.ContentRemoved</tp:dbus-ref> + signal.</p> + </tp:docstring> + </signal> + + <property name="Interfaces" tp:name-for-bindings="Interfaces" + type="as" tp:type="DBus_Interface[]" access="read" tp:immutable="yes"> + <tp:added version="0.19.11"/> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Extra interfaces provided by this content, such as <tp:dbus-ref + namespace="ofdT.Call">Content.Interface.Media.DRAFT</tp:dbus-ref> or + <tp:dbus-ref namespace="ofdT.Call">Content.Interface.Mute.DRAFT</tp:dbus-ref>. + This SHOULD NOT include the Content interface itself, and cannot + change once the content has been created.</p> + </tp:docstring> + </property> + + <property name="Name" tp:name-for-bindings="Name" type="s" access="read" + tp:immutable="yes"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The name of the content.</p> + + <tp:rationale> + The content name property should be meaningful, so should be + given a name which is significant to the user. The name + could be the "audio" or "video" string localized, or perhaps + include some string identifying the source, such as a webcam + identifier. + </tp:rationale> + </tp:docstring> + </property> + + <property name="Type" tp:name-for-bindings="Type" + type="u" tp:type="Media_Stream_Type" access="read" tp:immutable="yes"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The media type of this content.</p> + </tp:docstring> + </property> + + <tp:enum name="Call_Content_Disposition" type="u"> + <tp:docstring> + The disposition of this content, which defines whether to + automatically start sending data on the streams when + <tp:dbus-ref + namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref> is + called on the channel. + </tp:docstring> + + <tp:enumvalue suffix="None" value="0"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + The content has no specific disposition + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="Initial" value="1"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The content was initially part of the call. When + <tp:dbus-ref + namespace="ofdT.Channel.Type.Call.DRAFT">Accept</tp:dbus-ref> + is called on the channel, all streams of this content with + <tp:dbus-ref + namespace="ofdT.Call.Stream.DRAFT">LocalSendingState</tp:dbus-ref> + set to <tp:type>Sending_State</tp:type>_Pending_Send will be + moved to <tp:type>Sending_State</tp:type>_Sending as if + <tp:dbus-ref + namespace="ofdT.Call.Stream.DRAFT">SetSending</tp:dbus-ref> + (True) had been called.</p> + </tp:docstring> + </tp:enumvalue> + </tp:enum> + + <property name="Disposition" tp:name-for-bindings="Disposition" + type="u" tp:type="Call_Content_Disposition" access="read" + tp:immutable="yes"> + <tp:docstring> + The disposition of this content. + </tp:docstring> + </property> + + <signal name="StreamsAdded" tp:name-for-bindings="Streams_Added"> + <tp:changed version="0.21.2">plural version, renamed from + StreamAdded</tp:changed> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Emitted when streams are added to a call.</p> + </tp:docstring> + <arg name="Streams" type="ao"> + <tp:docstring> + The <tp:dbus-ref + namespace="ofdT.Call">Stream.DRAFT</tp:dbus-ref>s which were + added. + </tp:docstring> + </arg> + </signal> + + <signal name="StreamsRemoved" tp:name-for-bindings="Streams_Removed"> + <tp:changed version="0.21.2">plural version, renamed from + StreamRemoved</tp:changed> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Emitted when streams are removed from a call</p> + </tp:docstring> + <arg name="Streams" type="ao"> + <tp:docstring> + The <tp:dbus-ref + namespace="ofdT.Call">Stream.DRAFT</tp:dbus-ref>s which were + removed. + </tp:docstring> + </arg> + </signal> + + <property name="Streams" tp:name-for-bindings="Streams" + type="ao" access="read"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The list of <tp:dbus-ref namespace="ofdT.Call" + >Stream.DRAFT</tp:dbus-ref> objects that exist in this + content.</p> + + <tp:rationale> + In a conference call multiple parties can share one media + content (say, audio), but the streaming of that media can + either be shared or separate. For example, in a multicast + conference all contacts would share one stream, while in a + Muji conference there would be a stream for each + participant. + </tp:rationale> + + <p>Change notification is through the + <tp:member-ref>StreamsAdded</tp:member-ref> and + <tp:member-ref>StreamsRemoved</tp:member-ref> signals.</p> + </tp:docstring> + </property> + </interface> +</node> +<!-- vim:set sw=2 sts=2 et ft=xml: --> diff --git a/farstream/extensions/Call_Content_Codec_Offer.xml b/farstream/extensions/Call_Content_Codec_Offer.xml new file mode 100644 index 000000000..f88143f69 --- /dev/null +++ b/farstream/extensions/Call_Content_Codec_Offer.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" ?> +<node name="/Call_Content_Codec_Offer" + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright> + <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright> + <tp:license xmlns="http://www.w3.org/1999/xhtml"> + <p>This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version.</p> + + <p>This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details.</p> + + <p>You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA.</p> + </tp:license> + + <interface name="org.freedesktop.Telepathy.Call.Content.CodecOffer.DRAFT" + tp:causes-havoc="experimental"> + <tp:added version="0.19.0">(draft 1)</tp:added> + + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + This object represents an offer of a Codec payload mapping. + </tp:docstring> + + <method name="Accept" tp:name-for-bindings="Accept"> + <arg name="Codecs" direction="in" + type="a(usuua{ss})" tp:type="Codec[]"> + <tp:docstring> + The local codec mapping to send to the remote contacts and + to use in the <tp:dbus-ref + namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref>. + </tp:docstring> + </arg> + <tp:docstring> + Accept the updated Codec mapping and update the local mapping. + </tp:docstring> + <tp:possible-errors> + <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"> + <tp:docstring> + The codecs given as the argument are invalid in some way. + </tp:docstring> + </tp:error> + </tp:possible-errors> + </method> + + <method name="Reject" tp:name-for-bindings="Reject"> + <tp:docstring> + Reject the proposed update to the codecs + FIXME add error codes and strings here + </tp:docstring> + </method> + + <property name="Interfaces" tp:name-for-bindings="Interfaces" + type="as" tp:type="DBus_Interface[]" access="read" tp:immutable="yes"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Extra interfaces provided by this codec offer. This SHOULD + NOT include the CodecOffer interface itself, and cannot change + once the content has been created.</p> + </tp:docstring> + </property> + + <property name="RemoteContactCodecs" + tp:name-for-bindings="Remote_Contact_Codecs" + type="a(usuua{ss})" tp:type="Codec[]" access="read" + tp:immutable="yes"> + <tp:docstring> + A list of codecs the remote contact supports. + </tp:docstring> + </property> + + <property name="RemoteContact" tp:name-for-bindings="Remote_Contact" + type="u" tp:type="Contact_Handle" access="read" tp:immutable="yes"> + <tp:docstring> + The contact handle that this codec offer applies to. + </tp:docstring> + </property> + + + </interface> +</node> +<!-- vim:set sw=2 sts=2 et ft=xml: --> diff --git a/farstream/extensions/Call_Content_Interface_Media.xml b/farstream/extensions/Call_Content_Interface_Media.xml new file mode 100644 index 000000000..038ce8c7a --- /dev/null +++ b/farstream/extensions/Call_Content_Interface_Media.xml @@ -0,0 +1,367 @@ +<?xml version="1.0" ?> +<node name="/Call_Content_Interface_Media" + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright> + <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright> + <tp:license xmlns="http://www.w3.org/1999/xhtml"> + <p>This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version.</p> + + <p>This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details.</p> + + <p>You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA.</p> + </tp:license> + + <interface name="org.freedesktop.Telepathy.Call.Content.Interface.Media.DRAFT" + tp:causes-havoc="experimental"> + <tp:added version="0.19.0">(draft 1)</tp:added> + <tp:requires interface="org.freedesktop.Telepathy.Call.Content.DRAFT"/> + + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Interface to use by a software implementation of media + streaming. The reason behind splitting the members of this + interface out from the main <tp:dbus-ref + namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref> interface is + that the software is not necessarily what controls the + media. An example of this is in GSM phones, where the CM just + tells the phone to dial a number and it does the audio routing + in a device specific hardware way and the CM does not need + to concern itself with codecs.</p> + + <h4>Codec negotiation</h4> + + <p>When a new <tp:dbus-ref + namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref> channel + appears, whether it was requested or not, a <tp:dbus-ref + namespace="ofdT.Call.Content">CodecOffer.DRAFT</tp:dbus-ref> + will either be waiting in the + <tp:member-ref>CodecOffer</tp:member-ref> property, or will + appear at some point via the + <tp:member-ref>NewCodecOffer</tp:member-ref> signal.</p> + + <p>The <tp:dbus-ref + namespace="ofdT.Call.Content.CodecOffer.DRAFT">RemoteContactCodecs</tp:dbus-ref> + property on the codec offer lists the codecs which are + supported by the remote contact, and so will determine the + codecs that should be proposed by the local user's streaming + implementation. An empty list means all codecs can be proposed.</p> + + <p>For incoming calls on protocols where codecs are proposed when + starting the call (for example, <a + href="http://xmpp.org/extensions/xep-0166.html">Jingle</a>), + the <tp:dbus-ref + namespace="ofdT.Call.Content.CodecOffer.DRAFT">RemoteContactCodecs</tp:dbus-ref> + will contain information on the codecs that have already been + proposed by the remote contact, otherwise the codec map will + be the empty list.</p> + + <p>The streaming implementation should look at the remote codec + map and the codecs known by the local user and call + <tp:dbus-ref + namespace="ofdT.Call.Content">CodecOffer.DRAFT.Accept</tp:dbus-ref> + on the intersection of these two codec lists.</p> + + <p>This means that in practice, outgoing calls will have a codec + offer pop up with no information in the <tp:dbus-ref + namespace="ofdT.Call.Content.CodecOffer.DRAFT">RemoteContactCodecs</tp:dbus-ref>, + so the local user will call <tp:dbus-ref + namespace="ofdT.Call.Content.CodecOffer.DRAFT">Accept</tp:dbus-ref> + with the list of all codecs supported. If this codec offer is + accepted, then <tp:member-ref>CodecsChanged</tp:member-ref> + will fire with the details of the codecs passed into + <tp:dbus-ref + namespace="ofdT.Call.Content.CodecOffer.DRAFT">Accept</tp:dbus-ref>. If + the call is incoming, then the <tp:dbus-ref + namespace="ofdT.Call.Content.CodecOffer.DRAFT">RemoteContactCodecs</tp:dbus-ref> + will contain details of the remote contact's codecs and the + local user will call <tp:dbus-ref + namespace="ofdT.Call.Content.CodecOffer.DRAFT">Accept</tp:dbus-ref> + with the codecs that both sides understand. After the codec + set is accepted, <tp:member-ref>CodecsChanged</tp:member-ref> + will fire to signal this change.</p> + + <h4>Protocols without codec negotiation</h4> + + <p>For protocols where the codecs are not negotiable, instead of + popping up the initial content's <tp:dbus-ref + namespace="ofdT.Call.Content">CodecOffer.DRAFT</tp:dbus-ref> + object with an empty <tp:dbus-ref + namespace="ofdT.Call.Content.CodecOffer.DRAFT">RemoteContactCodecs</tp:dbus-ref>, + the CM should set the supported codec values to known codec + values in the said object's codec map.</p> + + <h4>Changing codecs mid-call</h4> + + <p>To update the codec list used mid-call, the + <tp:member-ref>UpdateCodecs</tp:member-ref> method should be + called with details of the new codec list. If this is + accepted, then <tp:member-ref>CodecsChanged</tp:member-ref> + will be emitted with the new codec set.</p> + + <p>If the other side decides to update his or her codec list + during a call, a new <tp:dbus-ref + namespace="ofdT.Call.Content">CodecOffer.DRAFT</tp:dbus-ref> + object will appear through + <tp:member-ref>NewCodecOffer</tp:member-ref> which should be + acted on as documented above.</p> + + </tp:docstring> + + <tp:struct name="Codec" array-name="Codec_List"> + <tp:docstring> + A description of a codec. + </tp:docstring> + <tp:member name="Identifier" type="u"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + Numeric identifier for the codec. This will be used as the PT in the + SDP or content description. + </tp:docstring> + </tp:member> + <tp:member name="Name" type="s"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + The name of the codec. + </tp:docstring> + </tp:member> + <tp:member name="Clockrate" type="u"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + The clockrate of the codec. + </tp:docstring> + </tp:member> + <tp:member name="Channels" type="u"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + Number of channels of the codec if applicable, otherwise 0. + </tp:docstring> + </tp:member> + <tp:member name="Parameters" type="a{ss}" tp:type="String_String_Map"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + Extra parameters for this codec. + </tp:docstring> + </tp:member> + </tp:struct> + + <tp:mapping name="Contact_Codec_Map"> + <tp:docstring> + A map from contact to the list of codecs he or she supports. + </tp:docstring> + <tp:member name="Handle" type="u" tp:type="Contact_Handle"> + <tp:docstring> + A contact handle. + </tp:docstring> + </tp:member> + <tp:member name="Codecs" type="a(usuua{ss})" tp:type="Codec[]"> + <tp:docstring> + The codecs that the contact supports. + </tp:docstring> + </tp:member> + </tp:mapping> + + <tp:struct name="Codec_Offering"> + <tp:docstring> + A codec offer and its corresponding remote contact codec map. + </tp:docstring> + <tp:member name="Codec_Offer" type="o"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + The object path to the <tp:dbus-ref namespace="ofdT.Call.Content" + >CodecOffer.DRAFT</tp:dbus-ref> + </tp:docstring> + </tp:member> + <tp:member name="Remote_Contact" type="u" tp:type="Contact_Handle"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + The contact handle that this codec offer applies to. + </tp:docstring> + </tp:member> + <tp:member name="Remote_Contact_Codecs" type="a(usuua{ss})" + tp:type="Codec[]"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + The <tp:dbus-ref namespace="ofdT.Call.Content" + >CodecOffer.DRAFT.RemoteContactCodecs</tp:dbus-ref> property + of the codec offer. + </tp:docstring> + </tp:member> + </tp:struct> + + <signal name="CodecsChanged" tp:name-for-bindings="Codecs_Changed"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Emitted when the codecs in use change.</p> + + <p>As well as acting as change notification for the + <tp:member-ref>ContactCodecMap</tp:member-ref>, emission of this + signal implies that the <tp:member-ref>CodecOffer</tp:member-ref> + property has changed to <code>('/', {})</code>.</p> + </tp:docstring> + <arg name="Updated_Codecs" type="a{ua(usuua{ss})}" + tp:type="Contact_Codec_Map"> + <tp:docstring> + A map from contact to his or her codecs. Each pair in this + map is added to the + <tp:member-ref>ContactCodecMap</tp:member-ref> property, + replacing any previous pair with that key. + </tp:docstring> + </arg> + <arg name="Removed_Contacts" type="au" tp:type="Contact_Handle[]"> + <tp:docstring> + A list of keys which were removed from the + <tp:member-ref>ContactCodecMap</tp:member-ref>, probably because + those contacts left the call. + </tp:docstring> + </arg> + </signal> + + <method name="UpdateCodecs" tp:name-for-bindings="Update_Codecs"> + <tp:docstring> + Update the local codec mapping. This method should only be + used during an existing call to update the codec mapping. + </tp:docstring> + <arg name="Codecs" direction="in" + type="a(usuua{ss})" tp:type="Codec[]"> + <tp:docstring> + The codecs now supported by the local user. + </tp:docstring> + </arg> + <tp:possible-errors> + <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"> + <tp:docstring> + Raised when a <tp:dbus-ref + namespace="ofdT.Call.Content">CodecOffer.DRAFT</tp:dbus-ref> + object exists and is referred to in the + <tp:member-ref>CodecOffer</tp:member-ref> property which + should be used instead of calling this method, or before + the content's initial <tp:dbus-ref + namespace="ofdT.Call.Content">CodecOffer.DRAFT</tp:dbus-ref> + object has appeared. + </tp:docstring> + </tp:error> + </tp:possible-errors> + </method> + + <property name="ContactCodecMap" tp:name-for-bindings="Contact_Codec_Map" + type="a{ua(usuua{ss})}" tp:type="Contact_Codec_Map" access="read"> + <tp:docstring> + <p>A map from contact handles (including the local user's own handle) + to the codecs supported by that contact.</p> + + <p>Change notification is via the + <tp:member-ref>CodecsChanged</tp:member-ref> signal.</p> + </tp:docstring> + </property> + + <signal name="NewCodecOffer" tp:name-for-bindings="New_Codec_Offer"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Emitted when a new <tp:dbus-ref namespace="ofdT.Call.Content" + >CodecOffer.DRAFT</tp:dbus-ref> appears. The streaming + implementation MUST respond by calling the <tp:dbus-ref + namespace="ofdT.Call.Content.CodecOffer.DRAFT" + >Accept</tp:dbus-ref> or <tp:dbus-ref + namespace="ofdT.Call.Content.CodecOffer.DRAFT" + >Reject</tp:dbus-ref> method on the codec offer object.</p> + + <p>Emission of this signal indicates that the + <tp:member-ref>CodecOffer</tp:member-ref> property has changed to + <code>(Contact, Offer, Codecs)</code>.</p> + </tp:docstring> + <arg name="Contact" type="u"> + <tp:docstring> + The contact the codec offer belongs to. + </tp:docstring> + </arg> + <arg name="Offer" type="o"> + <tp:docstring> + The object path of the new codec offer. This replaces any previous + codec offer. + </tp:docstring> + </arg> + <arg name="Codecs" type="a(usuua{ss})" tp:type="Codec[]"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The <tp:dbus-ref namespace="ofdT.Call.Content" + >CodecOffer.DRAFT.RemoteContactCodecs</tp:dbus-ref> property + of the codec offer.</p> + + <tp:rationale> + Having the <tp:dbus-ref + namespace="ofdT.Call.Content.CodecOffer.DRAFT">RemoteContactCodecs</tp:dbus-ref> + property here saves a D-Bus round-trip - it shouldn't be + necessary to get the property from the CodecOffer object, in + practice. + </tp:rationale> + </tp:docstring> + </arg> + </signal> + + <property name="CodecOffer" tp:name-for-bindings="Codec_Offer" + type="(oua(usuua{ss}))" tp:type="Codec_Offering" access="read"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The object path to the current + <tp:dbus-ref namespace="ofdT.Call.Content" + >CodecOffer.DRAFT</tp:dbus-ref> object, its + <tp:dbus-ref namespace="ofdT.Call.Content" + >CodecOffer.DRAFT.RemoteContact</tp:dbus-ref> and + <tp:dbus-ref namespace="ofdT.Call.Content" + >CodecOffer.DRAFT.RemoteContactCodecs</tp:dbus-ref> properties. + If the object path is "/" then there isn't an outstanding + codec offer, and the mapping MUST be empty.</p> + + <tp:rationale> + Having the <tp:dbus-ref + namespace="ofdT.Call.Content.CodecOffer.DRAFT" + >RemoteContact</tp:dbus-ref> and + <tp:dbus-ref namespace="ofdT.Call.Content.CodecOffer.DRAFT" + >RemoteContactCodecs</tp:dbus-ref> + properties here saves a D-Bus round-trip - it shouldn't be + necessary to get these properties from the CodecOffer object, in + practice. + </tp:rationale> + + <p>Change notification is via the + <tp:member-ref>NewCodecOffer</tp:member-ref> (which replaces the + value of this property with a new codec offer), and + <tp:member-ref>CodecsChanged</tp:member-ref> (which implies that + there is no longer any active codec offer) signals.</p> + </tp:docstring> + </property> + + <tp:enum name="Call_Content_Packetization_Type" type="u"> + <tp:added version="0.21.2"/> + <tp:docstring> + A packetization method that can be used for a content. + </tp:docstring> + + <tp:enumvalue suffix="RTP" value="0"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + Real-time Transport Protocol, as documented by RFC 3550. + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="Raw" value="1"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + Raw media. + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="MSN_Webcam" value="2"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + MSN webcam. This is the video-only one-way type which was + used in earlier versions of WLM. Although no longer used, + modern WLM clients still support the MSN webcam protocol. + </tp:docstring> + </tp:enumvalue> + </tp:enum> + + <property name="Packetization" tp:name-for-bindings="Packetization" + type="u" tp:type="Call_Content_Packetization_Type" access="read" + tp:immutable="yes"> + <tp:added version="0.21.2"/> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The packetization method in use for this content.</p> + </tp:docstring> + </property> + </interface> +</node> +<!-- vim:set sw=2 sts=2 et ft=xml: --> diff --git a/farstream/extensions/Call_Content_Interface_Mute.xml b/farstream/extensions/Call_Content_Interface_Mute.xml new file mode 100644 index 000000000..f926e03cd --- /dev/null +++ b/farstream/extensions/Call_Content_Interface_Mute.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" ?> +<node name="/Call_Content_Interface_Mute" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <tp:copyright> Copyright © 2005-2010 Nokia Corporation </tp:copyright> + <tp:copyright> Copyright © 2005-2010 Collabora Ltd </tp:copyright> + <tp:license xmlns="http://www.w3.org/1999/xhtml"> +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + </tp:license> + + <interface name="org.freedesktop.Telepathy.Call.Content.Interface.Mute.DRAFT" tp:causes-havoc="experimental"> + <tp:added version="0.19.6">(draft version, not API-stable)</tp:added> + <tp:requires interface="org.freedesktop.Telepathy.Call.Content.DRAFT"/> + + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Interface for calls which may be muted. This only makes sense + for channels where audio or video is streaming between members.</p> + + <p>Muting a call content indicates that the user does not wish to send + outgoing audio or video.</p> + + <p>Although it's client's responsibility to actually mute the microphone + or turn off the camera, using this interface the client can also + inform the CM and other clients of that fact.</p> + + <tp:rationale> + For some protocols, the fact that the content is muted needs + to be transmitted to the peer; for others, the notification + to the peer is only informational (eg. XMPP), and some + protocols may have no notion of muting at all. + </tp:rationale> + </tp:docstring> + + <signal name="MuteStateChanged" tp:name-for-bindings="Mute_State_Changed"> + <tp:docstring> + Emitted to indicate that the mute state has changed for this call content. + This may occur as a consequence of the client calling + <tp:member-ref>SetMuted</tp:member-ref>, or as an indication that another + client has (un)muted the content. + </tp:docstring> + <arg name="MuteState" type="b"> + <tp:docstring> + True if the content is now muted. + </tp:docstring> + </arg> + </signal> + + <property name="MuteState" type="b" + access="read" tp:name-for-bindings="Mute_State"> + <tp:docstring> + True if the content is muted. + </tp:docstring> + </property> + + <method name="SetMuted" tp:name-for-bindings="Set_Muted"> + <tp:changed version="0.21.2">renamed from SetMuted to Mute</tp:changed> + <tp:changed version="0.21.3">renamed back from Mute to SetMuted</tp:changed> + <arg direction="in" name="Muted" type="b"> + <tp:docstring> + True if the client has muted the content. + </tp:docstring> + </arg> + <tp:docstring> + <p>Inform the CM that the call content has been muted or unmuted by + the client.</p> + + <p>It is the client's responsibility to actually mute or unmute the + microphone or camera used for the content. However, the client + MUST call this whenever it mutes or unmutes the content.</p> + </tp:docstring> + </method> + + </interface> +</node> +<!-- vim:set sw=2 sts=2 et ft=xml: --> diff --git a/farstream/extensions/Call_Content_Interface_Video_Control.xml b/farstream/extensions/Call_Content_Interface_Video_Control.xml new file mode 100644 index 000000000..b066de42b --- /dev/null +++ b/farstream/extensions/Call_Content_Interface_Video_Control.xml @@ -0,0 +1,137 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node name="/Call_Content_Interface_Video_Control" + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright> + <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright> + <tp:license xmlns="http://www.w3.org/1999/xhtml"> + <p>This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version.</p> + + <p>This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details.</p> + + <p>You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA.</p> + </tp:license> + + <interface name="org.freedesktop.Telepathy.Call.Content.Interface.VideoControl.DRAFT" + tp:causes-havoc="experimental"> + <tp:added version="0.21.10">(draft 1)</tp:added> + <tp:requires interface="org.freedesktop.Telepathy.Call.Content.Interface.Media.DRAFT"/> + + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>An interface that allows the connection manager to control the video + stream.</p> + <p>This interface is generally not needed. In cases where the connection + manager handles the network communication and the media is transferred + from the client to the connection manager via shared memory, it can + sometimes be beneficial for the connection manager to be able to + control certain aspects of the video stream.</p> + </tp:docstring> + + <signal name="KeyFrameRequested" tp:name-for-bindings="Key_Frame_Requested"> + <tp:docstring> + Request that the video encoder produce a new key frame as soon as + possible. + </tp:docstring> + </signal> + + <tp:struct name="Video_Resolution" + array-name="Video_Resolution_Struct"> + <tp:member type="u" name="Width"> + <tp:docstring> + With of the video stream. + </tp:docstring> + </tp:member> + <tp:member type="u" name="Height"> + <tp:docstring> + Height of the video stream. + </tp:docstring> + </tp:member> + </tp:struct> + + <property name="VideoResolution" type="(uu)" tp:type="Video_Resolution" + access="read" tp:name-for-bindings="Video_Resolution"> + <tp:docstring> + The resolution at which the streaming engine should be sending. + + <p>Change notification is via the + <tp:member-ref>VideoResolutionChanged</tp:member-ref> signal.</p> + </tp:docstring> + </property> + + <signal name="VideoResolutionChanged" + tp:name-for-bindings="Video_Resolution_Changed"> + <tp:docstring> + The desired video resolution has changed. + </tp:docstring> + <arg type="(uu)" tp:type="Video_Resolution" name="NewResolution" /> + </signal> + + <property name="Bitrate" type="u" access="read" + tp:name-for-bindings="Bitrate"> + <tp:docstring> + The bitrate the streaming engine should be sending at. + + <p>Change notification is via the + <tp:member-ref>BitrateChanged</tp:member-ref> signal.</p> + </tp:docstring> + </property> + + <signal name="BitrateChanged" + tp:name-for-bindings="Bitrate_Changed"> + <tp:docstring> + The desired bitrate has changed + </tp:docstring> + <arg type="u" name="NewBitrate" /> + </signal> + + <property name="Framerate" type="u" access="read" + tp:name-for-bindings="Framerate"> + <tp:docstring> + The framerate the streaming engine should be sending at. + + <p>Change notification is via the + <tp:member-ref>FramerateChanged</tp:member-ref> signal.</p> + </tp:docstring> + </property> + + <signal name="FramerateChanged" + tp:name-for-bindings="Framerate_Changed"> + <tp:docstring> + The desired framerate has changed + </tp:docstring> + <arg type="u" name="NewFramerate" /> + </signal> + + <property name="MTU" type="u" access="read" + tp:name-for-bindings="MTU"> + <tp:docstring> + The Maximum Transmission Unit + + <p>Change notification is via the + <tp:member-ref>MTUChanged</tp:member-ref> signal.</p> + </tp:docstring> + </property> + + <signal name="MTUChanged" tp:name-for-bindings="MTU_Changed"> + <tp:docstring> + The Maximum Transmission Unit has changed + </tp:docstring> + <arg type="u" name="NewMTU" /> + </signal> + + <property name="ManualKeyFrames" type="b" access="read" + tp:name-for-bindings="Manual_Key_Frames"> + <tp:docstring> + Only send key frames when manually requested + </tp:docstring> + </property> + </interface> +</node> diff --git a/farstream/extensions/Call_Stream.xml b/farstream/extensions/Call_Stream.xml new file mode 100644 index 000000000..1d7b28147 --- /dev/null +++ b/farstream/extensions/Call_Stream.xml @@ -0,0 +1,261 @@ +<?xml version="1.0" ?> +<node name="/Call_Stream" + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright> + <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright> + <tp:license xmlns="http://www.w3.org/1999/xhtml"> + <p>This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version.</p> + + <p>This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details.</p> + + <p>You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA.</p> + </tp:license> + + <interface name="org.freedesktop.Telepathy.Call.Stream.DRAFT" + tp:causes-havoc="experimental"> + <tp:added version="0.19.0">(draft 1)</tp:added> + + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + One stream inside a <tp:dbus-ref + namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref>. + </tp:docstring> + + <method name="SetSending" tp:name-for-bindings="Set_Sending"> + <tp:docstring> + Set the stream to start or stop sending media from the local + user to other contacts. + </tp:docstring> + + <arg name="Send" type="b" direction="in"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>If True, the + <tp:member-ref>LocalSendingState</tp:member-ref> should + change to <tp:type>Sending_State</tp:type>_Sending, if it isn't + already.</p> + + <p>If False, the + <tp:member-ref>LocalSendingState</tp:member-ref> should + change to <tp:type>Sending_State</tp:type>_None, if it isn't + already.</p> + </tp:docstring> + </arg> + + <tp:possible-errors> + <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented" /> + </tp:possible-errors> + </method> + + <method name="RequestReceiving" tp:name-for-bindings="Request_Receiving"> + <tp:docstring> + <p>Request that a remote contact stops or starts sending on + this stream.</p> + + <p>The <tp:member-ref>CanRequestReceiving</tp:member-ref> + property defines whether the protocol allows the local user to + request the other side start sending on this stream.</p> + </tp:docstring> + + <arg name="Contact" type="u" tp:type="Contact_Handle" direction="in"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Contact from which sending is requested</p> + </tp:docstring> + </arg> + + <arg name="Receive" type="b" direction="in"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>If true, request that the given contact starts to send media. + If false, request that the given contact stops sending media.</p> + </tp:docstring> + </arg> + + <tp:possible-errors> + <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/> + <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"> + <tp:docstring> + The request contact is valid but is not involved in this + stream. + </tp:docstring> + </tp:error> + <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented"> + <tp:docstring> + The protocol does not allow the local user to request the + other side starts sending on this stream. + </tp:docstring> + </tp:error> + </tp:possible-errors> + </method> + + <signal name="RemoteMembersChanged" + tp:name-for-bindings="Remote_Members_Changed"> + <tp:changed version="0.21.2">renamed from SendersChanged to MembersChanged</tp:changed> + <tp:changed version="0.21.3">renamed from MembersChanged to RemoteMembersChanged</tp:changed> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + Emitted when <tp:member-ref>RemoteMembers</tp:member-ref> changes. + </tp:docstring> + + <arg name="Updates" type="a{uu}" tp:type="Contact_Sending_State_Map"> + <tp:docstring> + A mapping from channel-specific handles to their updated sending + state, whose keys include at least the members who were added, + and the members whose states changed. + </tp:docstring> + </arg> + <arg name="Removed" type="au" tp:type="Contact_Handle[]"> + <tp:docstring> + The channel-specific handles that were removed from the keys + of the <tp:member-ref>RemoteMembers</tp:member-ref> + property, as a result of the contact leaving this stream + </tp:docstring> + </arg> + </signal> + + <signal name="LocalSendingStateChanged" + tp:name-for-bindings="Local_Sending_State_Changed"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + Emitted when <tp:member-ref>LocalSendingState</tp:member-ref> changes. + </tp:docstring> + + <arg name="State" type="u" tp:type="Sending_State"> + <tp:docstring> + The new value of + <tp:member-ref>LocalSendingState</tp:member-ref>. + </tp:docstring> + </arg> + </signal> + + <tp:enum name="Sending_State" type="u"> + <tp:docstring> + Enum indicating whether a contact is sending media. + </tp:docstring> + + <tp:enumvalue suffix="None" value="0"> + <tp:docstring> + The contact is not sending media and has not been asked to + do so. + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="Pending_Send" value="1"> + <tp:docstring> + The contact has been asked to start sending media. + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="Sending" value="2"> + <tp:docstring> + The contact is sending media. + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="Pending_Stop_Sending" value="3"> + <tp:docstring> + The contact has been asked to stop sending media. + </tp:docstring> + </tp:enumvalue> + </tp:enum> + + <tp:mapping name="Contact_Sending_State_Map"> + <tp:docstring> + A map from a contact to his or her sending state. + </tp:docstring> + <tp:member name="Contact" type="u" tp:type="Contact_Handle"> + <tp:docstring> + The contact handle. + </tp:docstring> + </tp:member> + <tp:member name="Sending" type="u" tp:type="Sending_State"> + <tp:docstring> + The sending state of the contact. + </tp:docstring> + </tp:member> + </tp:mapping> + + <property name="Interfaces" tp:name-for-bindings="Interfaces" + type="as" tp:type="DBus_Interface[]" access="read" tp:immutable="yes"> + <tp:added version="0.19.11"/> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Extra interfaces provided by this stream, such as <tp:dbus-ref + namespace="ofdT.Call">Stream.Interface.Media.DRAFT</tp:dbus-ref>. + This SHOULD NOT include the Stream interface itself, and cannot + change once the stream has been created.</p> + </tp:docstring> + </property> + + <property name="RemoteMembers" tp:name-for-bindings="Remote_Members" + type="a{uu}" access="read" tp:type="Contact_Sending_State_Map"> + <tp:changed version="0.21.2">renamed from Senders</tp:changed> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>A map from remote contacts to their sending state. The + local user's sending state is shown in + <tp:member-ref>LocalSendingState</tp:member-ref>.</p> + + <p><tp:type>Sending_State</tp:type>_Pending_Send indicates + that another contact has asked the local user to send + media.</p> + + <p>Other contacts' handles in this map indicate whether they are + sending media to the contacts in this stream. + Sending_State_Pending_Send indicates contacts who are not sending but + have been asked to do so.</p> + </tp:docstring> + </property> + + <property name="LocalSendingState" tp:name-for-bindings="Local_Sending_State" + type="u" access="read" tp:type="Sending_State"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The local user's sending state. Media sent on this stream + should be assumed to be received, directly or indirectly, by + every other contact in the + <tp:member-ref>RemoteMembers</tp:member-ref> mapping. Change + notification is given via the + <tp:member-ref>LocalSendingStateChanged</tp:member-ref> + signal.</p> + + <tp:rationale> + Implementations of the first Call draft had the self handle + in the <tp:member-ref>RemoteMembers</tp:member-ref> (then + called Members) map and this showed that it's annoying + having to keep track of the self handle so that it can be + special-cased. + </tp:rationale> + + <p>A value of <tp:type>Sending_State</tp:type>_Pending_Send for + this property indicates that the other side requested the + local user start sending media, which can be done by calling + <tp:member-ref>SetSending</tp:member-ref>. When a call is + accepted, all initial contents with streams that have a + local sending state of + <tp:type>Sending_State</tp:type>_Pending_Send are + automatically set to sending. For example, on an incoming + call it means you need to <tp:dbus-ref + namespace="ofdT.Channel.Type.Call.DRAFT">Accept</tp:dbus-ref> + to start the actual call, on an outgoing call it might mean + you need to call <tp:dbus-ref + namespace="ofdT.Channel.Type.Call.DRAFT">Accept</tp:dbus-ref> + before actually starting the call.</p> + </tp:docstring> + </property> + + <property name="CanRequestReceiving" tp:name-for-bindings="Can_Request_Receiving" + type="b" access="read" tp:immutable="yes"> + <tp:added version="0.21.2"/> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>If true, the user can request that a remote contact starts + sending on this stream.</p> + + <tp:rationale>Not all protocols allow the user to ask the + other side to start sending media.</tp:rationale> + </tp:docstring> + </property> + </interface> +</node> +<!-- vim:set sw=2 sts=2 et ft=xml: --> diff --git a/farstream/extensions/Call_Stream_Endpoint.xml b/farstream/extensions/Call_Stream_Endpoint.xml new file mode 100644 index 000000000..4818168dc --- /dev/null +++ b/farstream/extensions/Call_Stream_Endpoint.xml @@ -0,0 +1,182 @@ +<?xml version="1.0" ?> +<node name="/Call_Stream_Endpoint" + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright> + <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright> + <tp:license xmlns="http://www.w3.org/1999/xhtml"> + <p>This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version.</p> + + <p>This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details.</p> + + <p>You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA.</p> + </tp:license> + + <interface name="org.freedesktop.Telepathy.Call.Stream.Endpoint.DRAFT" + tp:causes-havoc="experimental"> + <tp:added version="0.19.0">(draft 1)</tp:added> + + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>This object represents an endpoint for a stream. In a one-to-one + call, there will be one (bidirectional) stream per content and + one endpoint per stream (as there is only one remote + contact). In a multi-user call there is a stream for each remote + contact and each stream has one endpoint as it refers to the one + physical machine on the other end of the stream.</p> + + <p>The multiple endpoint use case appears when SIP call forking + is used. Unlike jingle call forking (which is just making + multiple jingle calls to different resources appear as one + call), SIP call forking is actually done at the server so you + have one stream to the remote contact and then and endpoint for + each SIP client to be called.</p> + </tp:docstring> + + <property name="RemoteCredentials" + tp:name-for-bindings="Remote_Credentials" + type="(ss)" tp:type="Stream_Credentials" access="read"> + <tp:docstring> + The ICE credentials used for all candidates. If each candidate + has different credentials, then this property SHOULD be ("", + ""). Per-candidate credentials are set in the + <tp:type>Candidate</tp:type>'s + <tp:type>Candidate_Info</tp:type> a{sv}. + </tp:docstring> + </property> + + <signal name="RemoteCredentialsSet" + tp:name-for-bindings="Remote_Credentials_Set"> + <arg name="Username" type="s"> + <tp:docstring> + The username set. + </tp:docstring> + </arg> + <arg name="Password" type="s"> + <tp:docstring> + The password set. + </tp:docstring> + </arg> + <tp:docstring> + Emitted when the remote ICE credentials for the endpoint are + set. If each candidate has different credentials, then this + signal will never be fired. + </tp:docstring> + </signal> + + <property name="RemoteCandidates" tp:name-for-bindings="Remote_Candidates" + type="a(usqa{sv})" tp:type="Candidate[]" access="read"> + <tp:docstring> + A list of candidates for this endpoint. + </tp:docstring> + </property> + + <signal name="RemoteCandidatesAdded" + tp:name-for-bindings="Remote_Candidates_Added"> + <tp:docstring> + Emitted when remote candidates are added to the + <tp:member-ref>RemoteCandidates</tp:member-ref> property. + </tp:docstring> + <arg name="Candidates" + type="a(usqa{sv})" tp:type="Candidate[]"> + <tp:docstring> + The candidates that were added. + </tp:docstring> + </arg> + </signal> + + <signal name="CandidateSelected" + tp:name-for-bindings="Candidate_Selected"> + <tp:docstring> + Emitted when a candidate is selected for use in the stream. + </tp:docstring> + <arg name="Candidate" + type="(usqa{sv})" tp:type="Candidate"> + <tp:docstring> + The candidate that has been selected. + </tp:docstring> + </arg> + </signal> + + <property name="SelectedCandidate" + tp:name-for-bindings="Selected_Candidate" + type="(usqa{sv})" tp:type="Candidate" access="read"> + <tp:docstring> + The candidate that has been selected for use to stream packets + to the remote contact. Change notification is given via the + the <tp:member-ref>CandidateSelected</tp:member-ref> signal. + </tp:docstring> + </property> + + <method name="SetSelectedCandidate" + tp:name-for-bindings="Set_Selected_Candidate"> + <tp:docstring> + Set the value of + <tp:member-ref>CandidateSelected</tp:member-ref>. + </tp:docstring> + <arg name="Candidate" + type="(usqa{sv})" tp:type="Candidate" direction="in"> + <tp:docstring> + The candidate that has been selected. + </tp:docstring> + </arg> + <tp:possible-errors> + <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/> + </tp:possible-errors> + </method> + + <property name="StreamState" tp:name-for-bindings="Stream_State" + type="u" tp:type="Media_Stream_State" + access="read"> + <tp:docstring> + The stream state of the endpoint. + </tp:docstring> + </property> + + <signal name="StreamStateChanged" + tp:name-for-bindings="Stream_State_Changed"> + <tp:docstring> + Emitted when the <tp:member-ref>StreamState</tp:member-ref> + property changes. + </tp:docstring> + <arg name="state" type="u" tp:type="Media_Stream_State"> + <tp:docstring> + The new <tp:member-ref>StreamState</tp:member-ref> value. + </tp:docstring> + </arg> + </signal> + + <method name="SetStreamState" + tp:name-for-bindings="Set_Stream_State"> + <tp:docstring> + Change the <tp:member-ref>StreamState</tp:member-ref> of the + endpoint. + </tp:docstring> + <arg direction="in" name="State" type="u" tp:type="Media_Stream_State"> + <tp:docstring> + The requested stream state. + </tp:docstring> + </arg> + <tp:possible-errors> + <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/> + <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/> + </tp:possible-errors> + </method> + + <property name="Transport" tp:name-for-bindings="Transport" + type="u" tp:type="Stream_Transport_Type" access="read"> + <tp:docstring> + The transport type for the stream endpoint. + </tp:docstring> + </property> + + </interface> +</node> +<!-- vim:set sw=2 sts=2 et ft=xml: --> diff --git a/farstream/extensions/Call_Stream_Interface_Media.xml b/farstream/extensions/Call_Stream_Interface_Media.xml new file mode 100644 index 000000000..9e62c8744 --- /dev/null +++ b/farstream/extensions/Call_Stream_Interface_Media.xml @@ -0,0 +1,447 @@ +<?xml version="1.0" ?> +<node name="/Call_Stream_Interface_Media" + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright> + <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright> + <tp:license xmlns="http://www.w3.org/1999/xhtml"> + <p>This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version.</p> + + <p>This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details.</p> + + <p>You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA.</p> + </tp:license> + + <interface name="org.freedesktop.Telepathy.Call.Stream.Interface.Media.DRAFT" + tp:causes-havoc="experimental"> + <tp:added version="0.19.0">(draft 1)</tp:added> + <tp:requires interface="org.freedesktop.Telepathy.Call.Stream.DRAFT"/> + + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + [FIXME] + + <h4>ICE restarts</h4> + + <p>If the <tp:dbus-ref + namespace="ofdT.Call.Stream.Endpoint.DRAFT">RemoteCredentialsSet</tp:dbus-ref> + signal is fired during a call once it has been called before + whilst setting up the call for initial use, and the + credentials have changed, then there has been an ICE + restart. In such a case, the CM SHOULD also empty the remote + candidate list in the <tp:dbus-ref + namespace="ofdT.Call.Stream">Endpoint.DRAFT</tp:dbus-ref>.</p> + + <p>If the CM does an ICE restart, then the + <tp:member-ref>PleaseRestartICE</tp:member-ref> signal is + emitted and the streaming implementation should then call + <tp:member-ref>SetCredentials</tp:member-ref> again.</p> + + <p>For more information on ICE restarts see + <a href="http://tools.ietf.org/html/rfc5245#section-9.1.1.1">RFC 5245 + section 9.1.1.1</a></p> + </tp:docstring> + + <method name="SetCredentials" tp:name-for-bindings="Set_Credentials"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Used to set the username fragment and password for streams that have + global credentials.</p> + </tp:docstring> + <arg name="Username" type="s" direction="in"> + <tp:docstring> + The username to use when authenticating on the stream. + </tp:docstring> + </arg> + <arg name="Password" type="s" direction="in"> + <tp:docstring> + The password to use when authenticating on the stream. + </tp:docstring> + </arg> + </method> + + <tp:mapping name="Candidate_Info"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Extra information about the candidate. Allowed and mandatory keys + depend on the transport protocol used. The following keys are commenly + used:</p> + + <dl> + <dt>Type (u)</dt> + <dd>type of candidate (host, srflx, prflx, relay)</dd> + + <dt>Foundation (s)</dt> + <dd>the foundation of this candiate</dd> + + <dt>Protocol (u) </dt> + <dd>Underlying protocol of the candidate (udp, tcp) </dd> + + <dt>Priority (u) </dt> + <dd>Priority of the candidate </dd> + + <dt>BaseIP (u) </dt> + <dd>Base IP of this candidate </dd> + + <dt>Username (s) </dt> + <dd>Username of this candidate + (only if credentials are per candidate)</dd> + + <dt>Password (s) </dt> + <dd>Password of this candidate + (only if credentials are per candidate)</dd> + + <dt>RawUDPFallback (b) </dt> + <dd>Indicate whether this candidate may be used to provide a UDP + fallback</dd> + </dl> + </tp:docstring> + <tp:member name="Key" type="s"> + <tp:docstring>One of the well-known keys documented here, or an + implementation-specific key.</tp:docstring> + </tp:member> + <tp:member name="Value" type="v"> + <tp:docstring>The value corresponding to that key.</tp:docstring> + </tp:member> + </tp:mapping> + + <tp:struct name="Candidate" array-name="Candidate_List"> + <tp:docstring>A Stream Candidate.</tp:docstring> + <tp:member name="Component" type="u"> + <tp:docstring>The component number.</tp:docstring> + </tp:member> + <tp:member name="IP" type="s"> + <tp:docstring>The IP address to use.</tp:docstring> + </tp:member> + <tp:member name="Port" type="q"> + <tp:docstring>The port number to use.</tp:docstring> + </tp:member> + <tp:member name="Info" type="a{sv}" tp:type="Candidate_Info"> + <tp:docstring>Additional information about the candidate.</tp:docstring> + </tp:member> + </tp:struct> + + <method name="AddCandidates" tp:name-for-bindings="Add_Candidates"> + <tp:docstring> + Add candidates to the + <tp:member-ref>LocalCandidates</tp:member-ref> property and + signal them to the remote contact(s). + </tp:docstring> + <arg name="Candidates" direction="in" + type="a(usqa{sv})" tp:type="Candidate[]"> + <tp:docstring> + The candidates to be added. + </tp:docstring> + </arg> + </method> + + <method name="CandidatesPrepared" + tp:name-for-bindings="Candidates_Prepared"> + <tp:docstring> + This indicates to the CM that the initial batch of candidates + has been added. + </tp:docstring> + </method> + + <tp:enum type="u" name="Stream_Transport_Type"> + <tp:changed version="0.21.2">WLM_8_5 was removed</tp:changed> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + A transport that can be used for streaming. + </tp:docstring> + <tp:enumvalue suffix="Unknown" value="0"> + <tp:docstring> + The stream transport type is unknown or not applicable + (for streams that do not have a configurable transport). + </tp:docstring> + </tp:enumvalue> + <tp:enumvalue suffix="Raw_UDP" value="1"> + <tp:docstring> + Raw UDP, with or without STUN. All streaming clients are assumed to + support this transport, so there is no handler capability token for + it in the <tp:dbus-ref namespace="ofdT.Channel.Type" + >Call.DRAFT</tp:dbus-ref> interface. + [This corresponds to "none" or "stun" in the old Media.StreamHandler + interface.] + </tp:docstring> + </tp:enumvalue> + <tp:enumvalue suffix="ICE" value="2"> + <tp:docstring> + Interactive Connectivity Establishment, as defined by RFC + 5245. Note that this value covers ICE-UDP only. + [This corresponds to "ice-udp" in the old + Media.StreamHandler interface.] + </tp:docstring> + </tp:enumvalue> + <tp:enumvalue suffix="GTalk_P2P" value="3"> + <tp:docstring> + Google Talk peer-to-peer connectivity establishment, as implemented + by libjingle 0.3. + [This corresponds to "gtalk-p2p" in the old Media.StreamHandler + interface.] + </tp:docstring> + </tp:enumvalue> + <tp:enumvalue suffix="WLM_2009" value="4"> + <tp:docstring> + The transport used by Windows Live Messenger 2009 or later, which + resembles ICE draft 19. + [This corresponds to "wlm-2009" in the old Media.StreamHandler + interface.] + </tp:docstring> + </tp:enumvalue> + <tp:enumvalue suffix="SHM" value="5"> + <tp:added version="0.21.2"/> + <tp:docstring> + Shared memory transport, as implemented by the GStreamer + shmsrc and shmsink plugins. + </tp:docstring> + </tp:enumvalue> + <tp:enumvalue suffix="Multicast" value="6"> + <tp:added version="0.21.5"/> + <tp:docstring> + Multicast transport. + </tp:docstring> + </tp:enumvalue> + </tp:enum> + + <property name="Transport" tp:name-for-bindings="Transport" + type="u" tp:type="Stream_Transport_Type" access="read" tp:immutable="yes"> + <tp:docstring> + The transport for this stream. + </tp:docstring> + </property> + + <property name="LocalCandidates" tp:name-for-bindings="Local_Candidates" + type="a(usqa{sv})" tp:type="Candidate[]" access="read"> + <tp:docstring> + [FIXME]. Change notification is via the + <tp:member-ref>LocalCandidatesAdded</tp:member-ref> signal. + </tp:docstring> + </property> + + <signal name="LocalCandidatesAdded" + tp:name-for-bindings="Local_Candidates_Added"> + <tp:docstring> + Emitted when local candidates are added to the + <tp:member-ref>LocalCandidates</tp:member-ref> property. + </tp:docstring> + <arg name="Candidates" type="a(usqa{sv})" tp:type="Candidate[]"> + <tp:docstring> + Candidates that have been added. + </tp:docstring> + </arg> + </signal> + + <tp:struct name="Stream_Credentials"> + <tp:docstring>A username and password pair.</tp:docstring> + + <tp:member name="Username" type="s"> + <tp:docstring>The username.</tp:docstring> + </tp:member> + + <tp:member name="Password" type="s"> + <tp:docstring>The password.</tp:docstring> + </tp:member> + </tp:struct> + + <property name="LocalCredentials" tp:name-for-bindings="Local_Credentials" + type="(ss)" tp:type="Stream_Credentials" access="read"> + <tp:docstring> + [FIXME]. Change notification is via the + <tp:member-ref>LocalCredentialsChanged</tp:member-ref> signal. + </tp:docstring> + </property> + + <signal name="LocalCredentialsChanged" + tp:name-for-bindings="Local_Credentials_Changed"> + <tp:changed version="0.21.2">renamed from LocalCredentailsSet</tp:changed> + <tp:docstring> + Emitted when the value of + <tp:member-ref>LocalCredentials</tp:member-ref> changes. + </tp:docstring> + <arg name="Username" type="s" /> + <arg name="Password" type="s" /> + </signal> + + <signal name="RelayInfoChanged" + tp:name-for-bindings="Relay_Info_Changed"> + <tp:docstring> + Emitted when the value of + <tp:member-ref>RelayInfo</tp:member-ref> changes. + </tp:docstring> + <arg name="Relay_Info" type="aa{sv}" tp:type="String_Variant_Map[]" /> + </signal> + + <signal name="STUNServersChanged" + tp:name-for-bindings="STUN_Servers_Changed"> + <tp:docstring> + Emitted when the value of + <tp:member-ref>STUNServers</tp:member-ref> changes. + </tp:docstring> + <arg name="Servers" type="a(sq)" tp:type="Socket_Address_IP[]" /> + </signal> + + <property name="STUNServers" tp:name-for-bindings="STUN_Servers" + type="a(sq)" tp:type="Socket_Address_IP[]" access="read"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The IP addresses of possible STUN servers to use for NAT + traversal, as dotted-quad IPv4 address literals or RFC2373 + IPv6 address literals. Change notification is via the + <tp:member-ref>STUNServersChanged</tp:member-ref> + signal. The IP addresses MUST NOT be given as DNS hostnames.</p> + + <tp:rationale> + High-quality connection managers already need an asynchronous + DNS resolver, so they might as well resolve this name to an IP + to make life easier for streaming implementations. + </tp:rationale> + </tp:docstring> + </property> + + <property name="RelayInfo" type="aa{sv}" access="read" + tp:type="String_Variant_Map[]" tp:name-for-bindings="Relay_Info"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>A list of mappings describing TURN or Google relay servers + available for the client to use in its candidate gathering, as + determined from the protocol. Map keys are:</p> + + <dl> + <dt><code>ip</code> - s</dt> + <dd>The IP address of the relay server as a dotted-quad IPv4 + address literal or an RFC2373 IPv6 address literal. This MUST NOT + be a DNS hostname. + + <tp:rationale> + High-quality connection managers already need an asynchronous + DNS resolver, so they might as well resolve this name to an IP + and make life easier for streaming implementations. + </tp:rationale> + </dd> + + <dt><code>type</code> - s</dt> + <dd> + <p>Either <code>udp</code> for UDP (UDP MUST be assumed if this + key is omitted), <code>tcp</code> for TCP, or + <code>tls</code>.</p> + + <p>The precise meaning of this key depends on the + <tp:member-ref>Transport</tp:member-ref> property: if + Transport is ICE, <code>tls</code> means + TLS over TCP as referenced by ICE draft 19, and if + Transport is GTalk_P2P, <code>tls</code> means + a fake SSL session over TCP as implemented by libjingle.</p> + </dd> + + <dt><code>port</code> - q</dt> + <dd>The UDP or TCP port of the relay server as an ASCII unsigned + integer</dd> + + <dt><code>username</code> - s</dt> + <dd>The username to use</dd> + + <dt><code>password</code> - s</dt> + <dd>The password to use</dd> + + <dt><code>component</code> - u</dt> + <dd>The component number to use this relay server for, as an + ASCII unsigned integer; if not included, this relay server + may be used for any or all components. + + <tp:rationale> + In ICE draft 6, as used by Google Talk, credentials are only + valid once, so each component needs relaying separately. + </tp:rationale> + </dd> + </dl> + + <tp:rationale> + <p>An equivalent of the gtalk-p2p-relay-token property on + MediaSignalling channels is not included here. The connection + manager should be responsible for making the necessary HTTP + requests to turn the token into a username and password.</p> + </tp:rationale> + + <p>The type of relay server that this represents depends on + the value of the <tp:member-ref>Transport</tp:member-ref> + property. If Transport is ICE, this is a TURN server; + if Transport is GTalk_P2P, this is a Google relay server; + otherwise, the meaning of RelayInfo is undefined.</p> + + <p>If relaying is not possible for this stream, the list is + empty.</p> + + <p>Change notification is given via the + <tp:member-ref>RelayInfoChanged</tp:member-ref> signal.</p> + </tp:docstring> + </property> + + <signal name="ServerInfoRetrieved" + tp:name-for-bindings="Server_Info_Retrieved"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Signals that the initial information about STUN and Relay servers + has been retrieved, i.e. the + <tp:member-ref>HasServerInfo</tp:member-ref> property is + now true.</p> + </tp:docstring> + </signal> + + <property name="HasServerInfo" type="b" + tp:name-for-bindings="Has_Server_Info" access="read"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>True if all the initial information about STUN servers and Relay + servers has been retrieved. Change notification is via the + <tp:member-ref>ServerInfoRetrieved</tp:member-ref> signal.</p> + + <tp:rationale> + Streaming implementations that can't cope with STUN and + relay servers being added later SHOULD wait for this + property to become true before proceeding. + </tp:rationale> + </tp:docstring> + </property> + + <signal name="EndpointsChanged" + tp:name-for-bindings="Endpoints_Changed"> + <tp:docstring> + Emitted when the <tp:member-ref>Endpoints</tp:member-ref> property + changes. + </tp:docstring> + <arg name="Endpoints_Added" type="ao"> + <tp:docstring> + Endpoints that were added. + </tp:docstring> + </arg> + <arg name="Endpoints_Removed" type="ao"> + <tp:docstring> + Endpoints that no longer exist. + </tp:docstring> + </arg> + </signal> + + <property name="Endpoints" tp:name-for-bindings="Endpoints" + type="ao" access="read"> + <tp:docstring> + <p>The list of <tp:dbus-ref namespace="ofdT.Call.Stream" + >Endpoint.DRAFT</tp:dbus-ref> objects that exist for this + stream.</p> + + <p>Change notification is via the + <tp:member-ref>EndpointsChanged</tp:member-ref> signal.</p> + </tp:docstring> + </property> + + <signal name="PleaseRestartICE" + tp:name-for-bindings="Please_Restart_ICE"> + <tp:docstring> + Emitted when the CM does an ICE restart to notify the + streaming implementation that it should call + <tp:member-ref>SetCredentials</tp:member-ref> again. + </tp:docstring> + </signal> + </interface> +</node> +<!-- vim:set sw=2 sts=2 et ft=xml: --> diff --git a/farstream/extensions/Channel_Type_Call.xml b/farstream/extensions/Channel_Type_Call.xml new file mode 100644 index 000000000..045d41693 --- /dev/null +++ b/farstream/extensions/Channel_Type_Call.xml @@ -0,0 +1,1429 @@ +<?xml version="1.0" ?> +<node name="/Channel_Type_Call" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <tp:copyright>Copyright © 2009-2010 Collabora Limited</tp:copyright> + <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright> + <tp:license> + This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + </tp:license> + <interface name="org.freedesktop.Telepathy.Channel.Type.Call.DRAFT" + tp:causes-havoc="experimental"> + <tp:added version="0.19.0">(draft 1)</tp:added> + + <tp:requires interface="org.freedesktop.Telepathy.Channel"/> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>A channel type for making audio and video calls. Call + channels supersede the old <tp:dbus-ref + namespace="ofdT.Channel.Type">StreamedMedia</tp:dbus-ref> + channel type. Call channels are much more flexible than its + predecessor and allow more than two participants.</p> + + <p>Handlers are advised against executing all the media + signalling, codec and candidate negotiation themselves but + instead use a helper library such as <a + href="http://telepathy.freedesktop.org/doc/telepathy-farstream/">telepathy-farstream</a> + which when given a new Call channel will set up the + transports and codecs and create GStreamer pads which + can be added to the handler UI. This is useful as it means + the handler does not have to worry how exactly the + connection between the call participants is being made.</p> + + <p>The <tp:dbus-ref + namespace="ofdT.Channel">TargetHandle</tp:dbus-ref> and + <tp:dbus-ref namespace="ofdT.Channel">TargetID</tp:dbus-ref> + properties in a Call channel refer to the contact that the + user initially called, or which contact initially called the + user. Even in a conference call, where there are multiple + contacts in the call, these properties refer to the + initial contact, who might have left the conference since + then. As a result, handlers should not rely on these + properties.</p> + + <h4>Contents</h4> + + <p><tp:dbus-ref namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref> + objects represent the actual media that forms the Call (for + example an audio content and a video content). Calls always + have one or more Content objects associated with them. As a + result, a new Call channel request MUST have either + <tp:member-ref>InitialAudio</tp:member-ref>=True, or + <tp:member-ref>InitialVideo</tp:member-ref>=True, or both, + as the Requestable Channel Classes will document.</p> + + <p><tp:dbus-ref + namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref> objects have + one or more stream associated with them. More information on + these streams and how to maniuplate them can be found on the + <tp:dbus-ref namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref> + interface page.</p> + + <h4>Outgoing calls</h4> + + <p>To make an audio-only call to a contact <tt>foo@example.com</tt> + handlers should call:</p> + + <blockquote> + <pre> +<tp:dbus-ref namespace="ofdT.Connection.Interface.Requests">CreateChannel</tp:dbus-ref>({ + ...<tp:dbus-ref namespace="ofdT.Channel">ChannelType</tp:dbus-ref>: ...<tp:dbus-ref + namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref>, + ...<tp:dbus-ref namespace="ofdT.Channel">TargetHandleType</tp:dbus-ref>: Contact, + ...<tp:dbus-ref namespace="ofdT.Channel">TargetID</tp:dbus-ref>: 'foo@example.com', + ...<tp:member-ref>InitialAudio</tp:member-ref>: True, +})</pre></blockquote> + + <p>As always, <tp:dbus-ref + namespace="ofdT.Channel">TargetHandle</tp:dbus-ref> may be used + in place of + <tp:dbus-ref namespace="ofdT.Channel">TargetID</tp:dbus-ref> + if the contact's handle is already known. To make an audio + and video call, the handler should also specify + <tp:member-ref>InitialVideo</tp:member-ref> The + connection manager SHOULD return a channel whose immutable + properties contain the local user as the <tp:dbus-ref + namespace="ofdT.Channel">InitiatorHandle</tp:dbus-ref>, the + remote contact as the <tp:dbus-ref + namespace="ofdT.Channel">TargetHandle</tp:dbus-ref>, + <tp:dbus-ref namespace="ofdT.Channel">Requested</tp:dbus-ref> = + <code>True</code> (indicating the call is outgoing).</p> + + <p>After a new Call channel is requested, the + <tp:member-ref>CallState</tp:member-ref> property will be + <tp:type>Call_State</tp:type>_Pending_Initiator. As the local + user is the initiator, the call must be accepted by the handler + by calling the <tp:member-ref>Accept</tp:member-ref> method. + At this point, <tp:member-ref>CallState</tp:member-ref> changes + to <tp:type>Call_State</tp:type>_Pending_Receiver which signifies + that the call is ringing, waiting for the remote contact to + accept the call. All changes to + <tp:member-ref>CallState</tp:member-ref> property are signalled + using the <tp:member-ref>CallStateChanged</tp:member-ref> + signal.</p> + + <p>When the call is accepted by the remote contact, the + <tp:member-ref>CallStateChanged</tp:member-ref> signal fires + again to show that <tp:member-ref>CallState</tp:member-ref> = + <tp:type>Call_State</tp:type>_Accepted.</p> + + <p>At this point <a + href="http://telepathy.freedesktop.org/doc/telepathy-farstream/">telepathy-farstream</a> + will signal that a pad is available for the handler to show + in the user interface.</p> + + <h5>Missed calls</h5> + + <p>If the remote contact does not accept the call in time, then + the call can be terminated by the server. Note that this only + happens in some protocols. Most XMPP clients, for example, do + not do this and rely on the call initiator terminating the call. + A missed call is shown in a Call channel by the + <tp:member-ref>CallState</tp:member-ref> property changing to + <tp:type>Call_State</tp:type>_Ended, and the + <tp:member-ref>CallStateReason</tp:member-ref> property changing + to (remote contact, + <tp:type>Call_State_Change_Reason</tp:type>_No_Answer, "").</p> + + <h5>Rejected calls</h5> + + <p>If the remote contact decides he or she does not feel like + talking to the local user, he or she can reject his or her + incoming call. This will be shown in the Call channel by + <tp:member-ref>CallState</tp:member-ref> changing to + <tp:type>Call_State</tp:type>_Ended and the + <tp:member-ref>CallStateReason</tp:member-ref> property + changing to (remote contact, + <tp:type>Call_State</tp:type>_Change_Reason_User_Requested, + "org.freedesktop.Telepathy.Error.Rejected").</p> + + <h4>Incoming calls</h4> + + <p>When an incoming call occurs, something like the following + <tp:dbus-ref + namespace="ofdT.Connection.Interface.Requests">NewChannels</tp:dbus-ref> + signal will occur:</p> + + <blockquote> + <pre> +<tp:dbus-ref namespace="ofdT.Connection.Interface.Requests">NewChannels</tp:dbus-ref>([ + /org/freedesktop/Telepathy/Connection/foo/bar/foo_40bar_2ecom/CallChannel, + { + ...<tp:dbus-ref namespace="ofdT.Channel">ChannelType</tp:dbus-ref>: ...<tp:dbus-ref + namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref>, + ...<tp:dbus-ref namespace="ofdT.Channel">TargetHandleType</tp:dbus-ref>: Contact, + ...<tp:dbus-ref namespace="ofdT.Channel">TargetID</tp:dbus-ref>: 'foo@example.com', + ...<tp:dbus-ref namespace="ofdT.Channel">TargetHandle</tp:dbus-ref>: 42, + ...<tp:dbus-ref namespace="ofdT.Channel">Requested</tp:dbus-ref>: False, + ...<tp:member-ref>InitialAudio</tp:member-ref>: True, + ...<tp:member-ref>InitialVideo</tp:member-ref>: True, + ...<tp:member-ref>InitialAudioName</tp:member-ref>: "audio", + ...<tp:member-ref>InitialVideoName</tp:member-ref>: "video", + ...<tp:member-ref>MutableContents</tp:member-ref>: True, + }])</pre></blockquote> + + <p>The <tp:member-ref>InitialAudio</tp:member-ref> and + <tp:member-ref>InitialVideo</tp:member-ref> properties show that + the call has been started with two contents: one for audio + streaming and one for video streaming. The + <tp:member-ref>InitialAudioName</tp:member-ref> and + <tp:member-ref>InitialVideoName</tp:member-ref> properties also + show that the aforementioned audio and video contents have names + "audio" and "video".</p> + + <p>Once the handler has notified the local user that there is an + incoming call waiting for acceptance, the handler should call + <tp:member-ref>SetRinging</tp:member-ref> to let the CM know. + The new channel should also be given to telepathy-farstream to + work out how the two participants will connect together. + telepathy-farstream will call the appropriate methods on the call's + <tp:dbus-ref namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref>s + to negotiate codecs and transports.</p> + + <p>To pick up the call, the handler should call + <tp:member-ref>Accept</tp:member-ref>. The + <tp:member-ref>CallState</tp:member-ref> property changes to + <tp:type>Call_State</tp:type>_Accepted and once media is + being transferred, telepathy-farstream will notify the + handler of a new pad to be shown to the local user in the + UI</p> + + <p>To reject the call, the handler should call the + <tp:member-ref>Hangup</tp:member-ref> method. The + <tp:member-ref>CallState</tp:member-ref> property will change to + <tp:type>Call_State</tp:type>_Ended and the + <tp:member-ref>CallStateReason</tp:member-ref> property will + change to (self handle, + <tp:type>Call_State_Change_Reason</tp:type>_User_Requested, + "org.freedesktop.Telepathy.Error.Rejected").</p> + + <h4>Ongoing calls</h4> + + <h5>Adding and removing contents</h5> + + <p>When a call is open, new contents can be added as long as the + CM supports it. The + <tp:member-ref>MutableContents</tp:member-ref> property will let + the handler know whether further contents can be added or + existing contents removed. An example of this is starting a + voice call between a contact and then adding a video content. + To do this, the should call + <tp:member-ref>AddContent</tp:member-ref> like this:</p> + + <blockquote> + <pre><tp:member-ref>AddContent</tp:member-ref>("video", + <tp:type>Media_Stream_Type</tp:type>_Video)</pre> + </blockquote> + + <p>Assuming no errors, the new video content will be added to + the call. telepathy-farstream will pick up the new content and + perform the transport and codec negotiation automatically. + telpathy-farstream will signal when the video is ready to + show in the handler's user interface.</p> + + <p>A similar method is used for removing contents from a call, + except that the <tp:dbus-ref + namespace="ofdT.Call.Content.DRAFT">Remove</tp:dbus-ref> method + is on the <tp:dbus-ref + namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref> object.</p> + + <h5>Ending the call</h5> + + <p>To end the call, the handler should call the + <tp:member-ref>Hangup</tp:member-ref> method. The + <tp:member-ref>CallState</tp:member-ref> property will change to + <tp:type>Call_State</tp:type>_Ended and + <tp:member-ref>CallStateReason</tp:member-ref> will change + to (self handle, + <tp:type>Call_State_Change_Reason</tp:type>_User_Requested, + "org.freedesktop.Telepathy.Error.Cancelled").</p> + + <p>If the other participant hangs up first then the + <tp:member-ref>CallState</tp:member-ref> property will change to + <tp:type>Call_State</tp:type>_Ended and + <tp:member-ref>CallStateReason</tp:member-ref> will change + to (remote contact, + <tp:type>Call_State_Change_Reason</tp:type>_User_Requested, + "org.freedesktop.Telepathy.Error.Terminated").</p> + + <h4>Multi-party calls</h4> + + [TODO] + + <h4>Call states</h4> + + <p>There are many combinations of the + <tp:member-ref>CallState</tp:member-ref> and + <tp:member-ref>CallStateReason</tp:member-ref> properties which + mean different things. Here is a table to try to make these + meanings clearer:</p> + + <table> + <tr> + <th rowspan="2"><tp:dbus-ref namespace="ofdT.Channel">Requested</tp:dbus-ref></th> + <th rowspan="2"><tp:member-ref>CallState</tp:member-ref></th> + <th colspan="3"><tp:member-ref>CallStateReason</tp:member-ref></th> + <th rowspan="2">Meaning</th> + </tr> + <tr> + <th>Actor</th> + <th>Reason</th> + <th>DBus_Reason</th> + </tr> + <!-- Pending_Initiator --> + <tr> + <td>True</td> + <td><tp:type>Call_State</tp:type>_Pending_Initiator</td> + <td>Self handle</td> + <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td> + <td>""</td> + <td>The outgoing call channel is waiting for the local user to call <tp:member-ref>Accept</tp:member-ref>.</td> + </tr> + <!-- Pending_Receiver --> + <tr> + <td>True</td> + <td><tp:type>Call_State</tp:type>_Pending_Receiver</td> + <td>Self handle</td> + <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td> + <td>""</td> + <td>The outgoing call is waiting for the remote contact to pick up the call.</td> + </tr> + <tr> + <td>False</td> + <td><tp:type>Call_State</tp:type>_Pending_Receiver</td> + <td>0</td> + <td><tp:type>Call_State_Change_Reason</tp:type>_Unknown</td> + <td>""</td> + <td>The incoming call is waiting for the local user to call <tp:member-ref>Accept</tp:member-ref> on the call.</td> + </tr> + <!-- Accepted --> + <tr> + <td>True</td> + <td><tp:type>Call_State</tp:type>_Accepted</td> + <td>Remote contact handle</td> + <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td> + <td>""</td> + <td>The remote contact accepted the outgoing call.</td> + </tr> + <tr> + <td>False</td> + <td><tp:type>Call_State</tp:type>_Accepted</td> + <td>Self handle</td> + <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td> + <td>""</td> + <td>The local user accepted the incoming call.</td> + </tr> + <!-- Ended --> + <tr> + <td>True or False</td> + <td><tp:type>Call_State</tp:type>_Ended</td> + <td>Self handle</td> + <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td> + <td><tp:error-ref>Cancelled</tp:error-ref></td> + <td>The local user hung up the incoming or outgoing call.</td> + </tr> + <tr> + <td>True or False</td> + <td><tp:type>Call_State</tp:type>_Ended</td> + <td>Remote contact handle</td> + <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td> + <td><tp:error-ref>Terminated</tp:error-ref></td> + <td>The remote contact hung up the incoming or outgoing call.</td> + </tr> + <tr> + <td>True</td> + <td><tp:type>Call_State</tp:type>_Ended</td> + <td>Remote contact handle</td> + <td><tp:type>Call_State_Change_Reason</tp:type>_No_Answer</td> + <td>""</td> + <td>The outgoing call was not picked up and the call ended.</td> + </tr> + <tr> + <td>False</td> + <td><tp:type>Call_State</tp:type>_Ended</td> + <td>Remote contact handle</td> + <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td> + <td><tp:error-ref>PickedUpElsewhere</tp:error-ref></td> + <td>The incoming call was ended because it was picked up elsewhere.</td> + </tr> + </table> + + <h4>Requestable channel classes</h4> + + <p>The <tp:dbus-ref + namespace="ofdT.Connection.Interface.Requests">RequestableChannelClasses</tp:dbus-ref> + for <tp:dbus-ref + namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref> channels + can be:</p> + + <blockquote> + <pre> +[( Fixed = { ...<tp:dbus-ref namespace="ofdT.Channel">ChannelType</tp:dbus-ref>: ...<tp:dbus-ref namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref>, + ...<tp:dbus-ref namespace="ofdT.Channel">TargetHandleType</tp:dbus-ref>: Contact, + ...<tp:member-ref>InitialVideo</tp:member-ref>: True + }, + Allowed = [ ...<tp:member-ref>InitialVideoName</tp:member-ref>, + ...<tp:member-ref>InitialAudio</tp:member-ref>, + ...<tp:member-ref>InitialAudioName</tp:member-ref> + ] +), +( Fixed = { ...<tp:dbus-ref namespace="ofdT.Channel">ChannelType</tp:dbus-ref>: ...<tp:dbus-ref namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref>, + ...<tp:dbus-ref namespace="ofdT.Channel">TargetHandleType</tp:dbus-ref>: Contact, + ...<tp:member-ref>InitialAudio</tp:member-ref>: True + }, + Allowed = [ ...<tp:member-ref>InitialAudioName</tp:member-ref>, + ...<tp:member-ref>InitialVideo</tp:member-ref>, + ...<tp:member-ref>InitialVideoName</tp:member-ref> + ] +)]</pre></blockquote> + + <p>Clients aren't allowed to make outgoing calls that have + neither initial audio nor initial video. Clearly, CMs + which don't support video should leave out the first class and + omit <tp:member-ref>InitialVideo</tp:member-ref> from the second + class, and vice versa for CMs without audio support.</p> + + <p>Handlers should not close <tp:dbus-ref + namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref> channels + without first calling <tp:member-ref>Hangup</tp:member-ref> on + the channel. If a Call handler crashes, the <tp:dbus-ref + namespace="ofdT">ChannelDispatcher</tp:dbus-ref> will call + <tp:dbus-ref namespace="ofdT.Channel">Close</tp:dbus-ref> on the + channel which SHOULD also imply a call to + <tp:member-ref>Hangup</tp:member-ref>(<tp:type>Call_State_Change_Reason</tp:type>_User_Requested, + "org.freedesktop.Telepathy.Error.Terminated", "") before + actually closing the channel.</p> + + </tp:docstring> + + <method name="SetRinging" tp:name-for-bindings="Set_Ringing"> + <tp:changed version="0.21.2">renamed from Ringing</tp:changed> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Indicate that the local user has been alerted about the incoming + call.</p> + + <p>This method is only useful if the + channel's <tp:dbus-ref namespace="ofdT.Channel">Requested</tp:dbus-ref> + property is False, and + the <tp:member-ref>CallState</tp:member-ref> is + <tp:type>Call_State</tp:type>_Pending_Receiver (an incoming + call waiting on the local user to pick up). While this is + the case, this method SHOULD change the + <tp:member-ref>CallFlags</tp:member-ref> to include + <tp:type>Call_Flags</tp:type>_Locally_Ringing, and notify the + remote contact that the local user has been alerted (if the + protocol implements this); repeated calls to this method + SHOULD succeed, but have no further effect.</p> + + <p>In all other states, this method SHOULD fail with the error + NotAvailable.</p> + </tp:docstring> + + <tp:possible-errors> + <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"> + <tp:docstring> + The call was <tp:dbus-ref namespace="ofdT.Channel" + >Requested</tp:dbus-ref>, so ringing does not make sense. + </tp:docstring> + </tp:error> + <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"> + <tp:docstring> + The call is no longer in state + <tp:type>Call_State</tp:type>_Pending_Receiver. + </tp:docstring> + </tp:error> + </tp:possible-errors> + </method> + + <method name="Accept" tp:name-for-bindings="Accept"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>For incoming calls in state + <tp:type>Call_State</tp:type>_Pending_Receiver, accept the + incoming call; this changes the + <tp:member-ref>CallState</tp:member-ref> to + <tp:type>Call_State</tp:type>_Accepted.</p> + + <p>For outgoing calls in state + <tp:type>Call_State</tp:type>_Pending_Initiator, actually + call the remote contact; this changes the + <tp:member-ref>CallState</tp:member-ref> to + <tp:type>Call_State</tp:type>_Pending_Receiver.</p> + + <p>Otherwise, this method SHOULD fail with the error NotAvailable.</p> + + <p>This method should be called exactly once per Call, by whatever + client (user interface) is handling the channel.</p> + + <p>When this method is called, for each <tp:dbus-ref + namespace="ofdT.Call" >Content.DRAFT</tp:dbus-ref> whose + <tp:dbus-ref namespace="ofdT.Call.Content.DRAFT" + >Disposition</tp:dbus-ref> is + <tp:type>Call_Content_Disposition</tp:type>_Initial, any + streams where the <tp:dbus-ref + namespace="ofdT.Call.Stream.DRAFT">LocalSendingState</tp:dbus-ref> + is <tp:type>Sending_State</tp:type>_Pending_Send will be + moved to <tp:type>Sending_State</tp:type>_Sending as if + <tp:dbus-ref namespace="ofdT.Call.Stream.DRAFT" + >SetSending</tp:dbus-ref>(True) had been called.</p> + </tp:docstring> + + <tp:possible-errors> + <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"> + <tp:docstring> + The call is not in one of the states where this method makes sense. + </tp:docstring> + </tp:error> + </tp:possible-errors> + </method> + + <method name="Hangup" tp:name-for-bindings="Hangup"> + <tp:docstring> + Request that the call is ended. All contents will be removed + from the Call so that the + <tp:member-ref>Contents</tp:member-ref> property will be the + empty list. + </tp:docstring> + + <arg direction="in" name="Reason" + type="u" tp:type="Call_State_Change_Reason"> + <tp:docstring> + A generic hangup reason. + </tp:docstring> + </arg> + + <arg direction="in" name="Detailed_Hangup_Reason" + type="s" tp:type="DBus_Error_Name"> + <tp:docstring> + A more specific reason for the call hangup, if one is available, or + an empty string otherwise. + </tp:docstring> + </arg> + + <arg direction="in" name="Message" type="s"> + <tp:docstring> + A human-readable message to be sent to the remote contact(s). + + <tp:rationale> + XMPP Jingle allows calls to be terminated with a human-readable + message. + </tp:rationale> + </tp:docstring> + </arg> + + <tp:possible-errors> + <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"> + <tp:docstring> + The call has already been ended. + </tp:docstring> + </tp:error> + </tp:possible-errors> + </method> + + <method name="AddContent" tp:name-for-bindings="Add_Content"> + <tp:docstring> + Request that a new <tp:dbus-ref + namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref> of type + Content_Type is added to the Call. Handlers should check the + value of the <tp:member-ref>MutableContents</tp:member-ref> + property before trying to add another content as it might not + be allowed. + </tp:docstring> + <arg direction="in" name="Content_Name" type="s"> + <tp:docstring> + <p>The suggested name of the content to add.</p> + + <tp:rationale> + The content name property should be meaningful, so should + be given a name which is significant to the user. The name + could be a localized "audio", "video" or perhaps include + some string identifying the source, such as a webcam + identifier. + </tp:rationale> + + <p>If there is already a content with the same name as this + property then a sensible suffix should be added. For example, + if this argument is "audio" but a content of the same name + already exists, a sensible suffix such as " (1)" is appended + to name the new content "audio (1)". A further content with the + name "audio" would then be named "audio (2)".</p> + + </tp:docstring> + </arg> + <arg direction="in" name="Content_Type" type="u" + tp:type="Media_Stream_Type"> + <tp:docstring> + The media stream type of the content to be added to the + call. + </tp:docstring> + </arg> + <arg direction="out" name="Content" type="o"> + <tp:docstring> + Path to the newly-created <tp:dbus-ref + namespace="org.freedesktop.Telepathy" + >Call.Content.DRAFT</tp:dbus-ref> object. + </tp:docstring> + </arg> + + <tp:possible-errors> + <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"> + <tp:docstring> + The media stream type given is invalid. + </tp:docstring> + </tp:error> + <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented"> + <tp:docstring> + The media stream type requested is not implemented by the + CM. + </tp:docstring> + </tp:error> + <tp:error name="org.freedesktop.Telepathy.Error.NotCapable"> + <tp:docstring> + The content type requested cannot be added to this + call. Examples of why this might be the case include + because a second video stream cannot be added, or a + content cannot be added when the content set isn't + mutable. + </tp:docstring> + </tp:error> + </tp:possible-errors> + </method> + + <signal name="ContentAdded" + tp:name-for-bindings="Content_Added"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Emitted when a new <tp:dbus-ref namespace="ofdT.Call" + >Content.DRAFT</tp:dbus-ref> is added to the call.</p> + </tp:docstring> + <arg name="Content" type="o"> + <tp:docstring> + Path to the newly-created <tp:dbus-ref namespace="ofdT.Call" + >Content.DRAFT</tp:dbus-ref> object. + </tp:docstring> + </arg> + </signal> + + <signal name="ContentRemoved" tp:name-for-bindings="Content_Removed"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Emitted when a <tp:dbus-ref namespace="ofdT.Call" + >Content.DRAFT</tp:dbus-ref> is removed from the call.</p> + </tp:docstring> + <arg name="Content" type="o"> + <tp:docstring> + The <tp:dbus-ref namespace="ofdT.Call" + >Content.DRAFT</tp:dbus-ref> which was removed. + </tp:docstring> + </arg> + </signal> + + <property name="Contents" type="ao" access="read" + tp:name-for-bindings="Contents"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The list of <tp:dbus-ref + namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref> objects that + are part of this call. Change notification is via the + <tp:member-ref>ContentAdded</tp:member-ref> and + <tp:member-ref>ContentRemoved</tp:member-ref> signals. + </p> + </tp:docstring> + </property> + + <tp:enum type="u" name="Call_State"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The state of a call, as a whole.</p> + + <p>The allowed transitions are:</p> + + <ul> + <li>Pending_Initiator → Pending_Receiver (for outgoing calls, + when <tp:member-ref>Accept</tp:member-ref> is called)</li> + <li>Pending_Receiver → Accepted (for incoming calls, when + <tp:member-ref>Accept</tp:member-ref> is called; for outgoing + calls to a contact, when the remote contact accepts the call; + for joining a conference call, when the local user successfully + joins the conference)</li> + <li>Accepted → Pending_Receiver (when transferred to another + contact)</li> + <li>any state → Ended (when the call is terminated normally, or + when an error occurs)</li> + </ul> + + <p>Clients MAY consider unknown values from this enum to be an + error - additional values will not be defined after the Call + specification is declared to be stable.</p> + </tp:docstring> + + <tp:enumvalue suffix="Unknown" value = "0"> + <tp:docstring> + The call state is not known. This call state MUST NOT appear as a + value of the <tp:member-ref>CallState</tp:member-ref> property, but + MAY be used by client code to represent calls whose state is as yet + unknown. + </tp:docstring> + </tp:enumvalue> + <tp:enumvalue suffix="Pending_Initiator" value = "1"> + <tp:docstring> + The initiator of the call hasn't accepted the call yet. This state + only makes sense for outgoing calls, where it means that the local + user has not yet sent any signalling messages to the remote user(s), + and will not do so until <tp:member-ref>Accept</tp:member-ref> is + called. + </tp:docstring> + </tp:enumvalue> + <tp:enumvalue suffix="Pending_Receiver" value = "2"> + <tp:docstring> + The receiver (the contact being called) hasn't accepted the call yet. + </tp:docstring> + </tp:enumvalue> + <tp:enumvalue suffix="Accepted" value = "3"> + <tp:docstring> + The contact being called has accepted the call. + </tp:docstring> + </tp:enumvalue> + <tp:enumvalue suffix="Ended" value = "4"> + <tp:docstring> + The call has ended, either via normal termination or an error. + </tp:docstring> + </tp:enumvalue> + </tp:enum> + + <tp:flags name="Call_Flags" value-prefix="Call_Flag" type="u"> + <tp:docstring> + A set of flags representing the status of the call as a whole, + providing more specific information than the + <tp:member-ref>CallState</tp:member-ref>. Many of these flags only make + sense in a particular state. + </tp:docstring> + + <tp:flag suffix="Locally_Ringing" value="1"> + <tp:docstring> + The local contact has been alerted about the call but has not + responded; if possible, the remote contact(s) have been informed of + this fact. This flag only makes sense on incoming calls in + state <tp:type>Call_State</tp:type>_Pending_Receiver. It SHOULD + be set when <tp:member-ref>SetRinging</tp:member-ref> is + called successfully, and unset when the state changes. + </tp:docstring> + </tp:flag> + + <tp:flag suffix="Queued" value="2"> + <tp:docstring> + The contact is temporarily unavailable, and the call has been placed + in a queue (e.g. 182 Queued in SIP, or call-waiting in telephony). + This flag only makes sense on outgoing 1-1 calls in + state <tp:type>Call_State</tp:type>_Pending_Receiver. It SHOULD be + set or unset according to informational messages from other + contacts. + </tp:docstring> + </tp:flag> + + <tp:flag suffix="Locally_Held" value="4"> + <tp:docstring> + The call has been put on hold by the local user, e.g. using + the <tp:dbus-ref namespace="ofdT.Channel.Interface" + >Hold</tp:dbus-ref> interface. This flag SHOULD only be set + if there is at least one Content, and all Contents are + locally held; it makes sense on calls in state + <tp:type>Call_State</tp:type>_Pending_Receiver + or <tp:type>Call_State</tp:type>_Accepted. + + <tp:rationale> + Otherwise, in transient situations where some but not all contents + are on hold, UIs would falsely indicate that the call as a whole + is on hold, which could lead to the user saying something they'll + regret, while under the impression that the other contacts can't + hear them! + </tp:rationale> + </tp:docstring> + </tp:flag> + + <tp:flag suffix="Forwarded" value="8"> + <tp:docstring> + The initiator of the call originally called a contact other than the + current recipient of the call, but the call was then forwarded or + diverted. This flag only makes sense on outgoing calls, in state + <tp:type>Call_State</tp:type>_Pending_Receiver or + <tp:type>Call_State</tp:type>_Accepted. It SHOULD be set or unset + according to informational messages from other contacts. + </tp:docstring> + </tp:flag> + + <tp:flag suffix="In_Progress" value="16"> + <tp:docstring> + Progress has been made in placing the outgoing call, but the + contact may not have been made aware of the call yet + (so the Ringing state is not appropriate). This corresponds to SIP's + status code 183 Session Progress, and could be used when the + outgoing call has reached a gateway, for instance. + This flag only makes sense on outgoing calls in state + <tp:type>Call_State</tp:type>_Pending_Receiver, and SHOULD be set + or unset according to informational messages from servers, gateways + and other infrastructure. + </tp:docstring> + </tp:flag> + + <tp:flag suffix="Clearing" value="32"> + <tp:docstring> + This flag only occurs when the CallState is Ended. The call with + this flag set has ended, but not all resources corresponding to the + call have been freed yet. + + Depending on the protocol there might be some audible feedback while + the clearing flag is set. + + <tp:rationale> + In calls following the ITU-T Q.931 standard there is a period of + time between the call ending and the underlying channel being + completely free for re-use. + </tp:rationale> + </tp:docstring> + </tp:flag> + + <tp:flag suffix="Muted" value="64"> + <tp:docstring> + The call has been muted by the local user, e.g. using the + <tp:dbus-ref namespace="ofdT.Call.Content.Interface" + >Mute.DRAFT</tp:dbus-ref> interface. This flag SHOULD only + be set if there is at least one Content, and all Contents + are locally muted; it makes sense on calls in state + <tp:type>Call_State</tp:type>_Pending_Receiver or + <tp:type>Call_State</tp:type>_Accepted. + </tp:docstring> + </tp:flag> + </tp:flags> + + <property name="CallStateDetails" + tp:name-for-bindings="Call_State_Details" type="a{sv}" access="read"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>A map used to provide optional extensible details for the + <tp:member-ref>CallState</tp:member-ref>, + <tp:member-ref>CallFlags</tp:member-ref> and/or + <tp:member-ref>CallStateReason</tp:member-ref>.</p> + + <p>Well-known keys and their corresponding value types include:</p> + + <dl> + <dt>hangup-message - s</dt> + <dd>An optional human-readable message sent when the call was ended, + corresponding to the Message argument to the + <tp:member-ref>Hangup</tp:member-ref> method. This is only + applicable when the call state is <tp:type>Call_State</tp:type>_Ended. + <tp:rationale> + XMPP Jingle can send such messages. + </tp:rationale> + </dd> + + <dt>queue-message - s</dt> + <dd>An optional human-readable message sent when the local contact + is being held in a queue. This is only applicable when + <tp:type>Call_Flags</tp:type>_Queued is in the call flags. + <tp:rationale> + SIP 182 notifications can have human-readable messages attached. + </tp:rationale> + </dd> + + <dt>debug-message - s</dt> + <dd>A message giving further details of any error indicated by the + <tp:member-ref>CallStateReason</tp:member-ref>. This will not + normally be localized or suitable for display to users, and is only + applicable when the call state is + <tp:type>Call_State</tp:type>_Ended.</dd> + </dl> + </tp:docstring> + </property> + + <property name="CallState" type="u" access="read" + tp:name-for-bindings="Call_State" tp:type="Call_State"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The current high-level state of this call. The + <tp:member-ref>CallFlags</tp:member-ref> provide additional + information, and the <tp:member-ref>CallStateReason</tp:member-ref> + and <tp:member-ref>CallStateDetails</tp:member-ref> explain the + reason for the current values for those properties.</p> + + <p>Note that when in a conference call, this property is + purely to show your state in joining the call. The receiver + (or remote contact) in this context is the conference server + itself. The property does not change when other call members' + states change.</p> + + <p>Clients MAY consider unknown values in this property to be an + error.</p> + </tp:docstring> + </property> + + <property name="CallFlags" type="u" access="read" + tp:name-for-bindings="Call_Flags" tp:type="Call_Flags"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Flags representing the status of the call as a whole, + providing more specific information than the + <tp:member-ref>CallState</tp:member-ref>.</p> + + <p>Clients are expected to ignore unknown flags in this property, + without error.</p> + + <p>When an ongoing call is active and not on hold or has any + other problems, this property will be 0.</p> + </tp:docstring> + </property> + + <tp:enum name="Call_State_Change_Reason" type="u"> + <tp:docstring> + A simple representation of the reason for a change in the call's + state, which may be used by simple clients, or used as a fallback + when the DBus_Reason member of a <tp:type>Call_State_Reason</tp:type> + struct is not understood. + </tp:docstring> + + <tp:enumvalue suffix="Unknown" value="0"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + We just don't know. Unknown values of this enum SHOULD also be + treated like this. + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="User_Requested" value="1"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The change was requested by the contact indicated by the Actor + member of a <tp:type>Call_State_Reason</tp:type> struct.</p> + + <p>If the Actor is the local user, the DBus_Reason SHOULD be the + empty string.</p> + + <p>If the Actor is a remote user, the DBus_Reason SHOULD be the empty + string if the call was terminated normally, but MAY be a non-empty + error name to indicate error-like call termination reasons (call + rejected as busy, kicked from a conference by a moderator, etc.).</p> + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="Forwarded" value="2"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The call was forwarded. If known, the handle of the contact + the call was forwarded to will be indicated by the Actor member + of a <tp:type>Call_State_Reason</tp:type> struct.</p> + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="No_Answer" value="3"> + <tp:added version="0.21.2"/> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The <tp:member-ref>CallState</tp:member-ref> changed from + <tp:type>Call_State</tp:type>_Pending_Receiver to + <tp:type>Call_State</tp:type>_Ended because the initiator + ended the call before the receiver accepted it. With an + incoming call this state change reason signifies a missed + call.</p> + </tp:docstring> + </tp:enumvalue> + </tp:enum> + + <tp:struct name="Call_State_Reason"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>A description of the reason for a change to the + <tp:member-ref>CallState</tp:member-ref> and/or + <tp:member-ref>CallFlags</tp:member-ref>.</p> + </tp:docstring> + + <tp:member type="u" tp:type="Contact_Handle" name="Actor"> + <tp:docstring> + The contact responsible for the change, or 0 if no contact was + responsible. + </tp:docstring> + </tp:member> + + <tp:member type="u" tp:type="Call_State_Change_Reason" name="Reason"> + <tp:docstring> + The reason, chosen from a limited set of possibilities defined by + the Telepathy specification. If + <tp:type>Call_State_Change_Reason</tp:type>_User_Requested then + the Actor member will dictate whether it was the local user or + a remote contact responsible. + </tp:docstring> + </tp:member> + + <tp:member type="s" tp:type="DBus_Error_Name" name="DBus_Reason"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>A specific reason for the change, which may be a D-Bus error in + the Telepathy namespace, a D-Bus error in any other namespace + (for implementation-specific errors), or the empty string to + indicate that the state change was not an error.</p> + + <p>This SHOULD be an empty string for changes to any state other + than Ended.</p> + + <p>The errors Cancelled and Terminated SHOULD NOT be used here; + an empty string SHOULD be used instead.</p> + + <tp:rationale> + <p>Those error names are used to indicate normal call + termination by the local user or another user, respectively, + in contexts where a D-Bus error name must appear.</p> + </tp:rationale> + </tp:docstring> + </tp:member> + </tp:struct> + + <property name="CallStateReason" tp:name-for-bindings="Call_State_Reason" + type="(uus)" access="read" tp:type="Call_State_Reason"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The reason for the last change to the + <tp:member-ref>CallState</tp:member-ref> and/or + <tp:member-ref>CallFlags</tp:member-ref>. The + <tp:member-ref>CallStateDetails</tp:member-ref> MAY provide additional + information.</p> + </tp:docstring> + </property> + + <signal name="CallStateChanged" + tp:name-for-bindings="Call_State_Changed"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Emitted when the state of the call as a whole changes.</p> + + <p>This signal is emitted for any change in the properties + corresponding to its arguments, even if the other properties + referenced remain unchanged.</p> + </tp:docstring> + + <arg name="Call_State" type="u" tp:type="Call_State"> + <tp:docstring> + The new value of the <tp:member-ref>CallState</tp:member-ref> + property. + </tp:docstring> + </arg> + + <arg name="Call_Flags" type="u" tp:type="Call_Flags"> + <tp:docstring> + The new value of the <tp:member-ref>CallFlags</tp:member-ref> + property. + </tp:docstring> + </arg> + + <arg name="Call_State_Reason" type="(uus)" tp:type="Call_State_Reason"> + <tp:docstring> + The new value of the <tp:member-ref>CallStateReason</tp:member-ref> + property. + </tp:docstring> + </arg> + + <arg name="Call_State_Details" type="a{sv}"> + <tp:docstring> + The new value of the <tp:member-ref>CallStateDetails</tp:member-ref> + property. + </tp:docstring> + </arg> + </signal> + + <property name="HardwareStreaming" tp:name-for-bindings="Hardware_Streaming" + type="b" access="read" tp:immutable="yes"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>If this property is True, all of the media streaming is done by some + mechanism outside the scope of Telepathy.</p> + + <tp:rationale> + <p>A connection manager might be intended for a specialized hardware + device, which will take care of the audio streaming (e.g. + telepathy-yafono, which uses GSM hardware which does the actual + audio streaming for the call).</p> + </tp:rationale> + + <p>If this is False, the handler is responsible for doing the actual + media streaming for at least some contents itself. Those contents + will have the <tp:dbus-ref namespace="ofdT.Call.Content.Interface" + >Media.DRAFT</tp:dbus-ref> interface, to communicate the necessary + information to a streaming implementation. Connection managers SHOULD + operate like this, if possible.</p> + + <tp:rationale> + <p>Many connection managers (such as telepathy-gabble) only do the + call signalling, and expect the client to do the actual streaming + using something like + <a href="http://farsight.freedesktop.org/">Farsight</a>, to improve + latency and allow better UI integration.</p> + </tp:rationale> + </tp:docstring> + </property> + + <tp:flags type="u" name="Call_Member_Flags" value-prefix="Call_Member_Flag"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>A set of flags representing the status of a remote contact in a + call.</p> + + <p>It is protocol- and client-specific whether a particular contact + will ever have a particular flag set on them, and Telepathy clients + SHOULD NOT assume that a flag will ever be set.</p> + + <tp:rationale> + <p>180 Ringing in SIP, and its equivalent in XMPP, are optional + informational messages, and implementations are not required + to send them. The same applies to the messages used to indicate + hold state.</p> + </tp:rationale> + </tp:docstring> + + <tp:flag suffix="Ringing" value = "1"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The remote contact's client has told us that the contact has been + alerted about the call but has not responded.</p> + + <tp:rationale> + <p>This is a flag per member, not a flag for the call as a whole, + because in Muji conference calls, you could invite someone and + have their state be "ringing" for a while.</p> + </tp:rationale> + </tp:docstring> + </tp:flag> + + <tp:flag suffix="Held" value = "2"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The call member has put this call on hold.</p> + + <tp:rationale> + <p>This is a flag per member, not a flag for the call as a whole, + because in conference calls, any member could put the conference + on hold.</p> + </tp:rationale> + </tp:docstring> + </tp:flag> + + <tp:flag suffix="Conference_Host" value="4"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + This contact has merged this call into a conference. Note that GSM + provides a notification when the remote party merges a call into a + conference, but not when it is split out again; thus, this flag can + only indicate that the call has been part of a conference at some + point. If a GSM connection manager receives a notification that a + call has been merged into a conference a second time, it SHOULD + represent this by clearing and immediately re-setting this flag on + the remote contact. + </tp:docstring> + </tp:flag> + </tp:flags> + + <tp:mapping name="Call_Member_Map" array-name="Call_Member_Map_List"> + <tp:docstring>A mapping from handles to their current state in the call. + </tp:docstring> + <tp:member type="u" tp:type="Handle" name="key"/> + <tp:member type="u" tp:type="Call_Member_Flags" name="Flag"/> + </tp:mapping> + + <signal name="CallMembersChanged" + tp:name-for-bindings="Call_Members_Changed"> + <tp:docstring> + Emitted when the <tp:member-ref>CallMembers</tp:member-ref> property + changes in any way, either because contacts have been added to the + call, contacts have been removed from the call, or contacts' flags + have changed. + </tp:docstring> + + <arg name="Flags_Changed" type="a{uu}" tp:type="Call_Member_Map"> + <tp:docstring> + A map from members of the call to their new call member flags, + including at least the members who have been added to + <tp:member-ref>CallMembers</tp:member-ref>, and the members whose + flags have changed. + </tp:docstring> + </arg> + <arg name="Removed" type="au" tp:type="Contact_Handle[]"> + <tp:docstring> + A list of members who have left the call, i.e. keys to be removed + from <tp:member-ref>CallMembers</tp:member-ref>. + </tp:docstring> + </arg> + </signal> + + <property name="CallMembers" tp:name-for-bindings="Call_Members" + type="a{uu}" access="read" tp:type="Call_Member_Map"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>A mapping from the remote contacts that are part of this call to flags + describing their status. This mapping never has the local user's handle + as a key.</p> + + <p>When the call ends, this property should be an empty list, + and notified with + <tp:member-ref>CallMembersChanged</tp:member-ref></p> + + <p>If the Call implements + <tp:dbus-ref namespace="ofdT.Channel.Interface" + >Group</tp:dbus-ref> and the Group members are + channel-specific handles, then this call SHOULD also use + channel-specific handles.</p> + + <p>Anonymous members are exposed as channel-specific handles + with no owner.</p> + </tp:docstring> + </property> + + <property name="InitialTransport" tp:name-for-bindings="Initial_Transport" + type="u" tp:type="Stream_Transport_Type" access="read" + tp:requestable="yes" tp:immutable="yes"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>If set on a requested channel, this indicates the transport that + should be used for this call. Where not applicable, this property + is defined to be <tp:type>Stream_Transport_Type</tp:type>_Unknown, + in particular, on CMs with hardware streaming.</p> + + <tp:rationale> + When implementing a voip gateway one wants the outgoing leg of the + gatewayed to have the same transport as the incoming leg. This + property allows the gateway to request a Call with the right + transport from the CM. + </tp:rationale> + </tp:docstring> + </property> + + <property name="InitialAudio" tp:name-for-bindings="Initial_Audio" + type="b" access="read" tp:immutable="yes" tp:requestable="yes"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>If set to True in a channel request that will create a new channel, + the connection manager should immediately attempt to establish an + audio stream to the remote contact, making it unnecessary for the + client to call <tp:dbus-ref + namespace="ofdT.Channel.Type.Call.DRAFT">AddContent</tp:dbus-ref>.</p> + + <p>If this property, or InitialVideo, is passed to EnsureChannel + (as opposed to CreateChannel), the connection manager SHOULD ignore + these properties when checking whether it can return an existing + channel as suitable; these properties only become significant when + the connection manager has decided to create a new channel.</p> + + <p>If True on a requested channel, this indicates that the audio + stream has already been requested and the client does not need to + call RequestStreams, although it MAY still do so.</p> + + <p>If True on an unrequested (incoming) channel, this indicates that + the remote contact initially requested an audio stream; this does + not imply that that audio stream is still active (as indicated by + <tp:dbus-ref namespace="ofdT.Channel.Type.Call.DRAFT" + >Contents</tp:dbus-ref>).</p> + + <p>The name of this new content can be decided by using the + <tp:member-ref>InitialAudioName</tp:member-ref> property.</p> + + <p>Connection managers that support the <tp:dbus-ref + namespace="ofdT.Connection.Interface">ContactCapabilities</tp:dbus-ref> + interface SHOULD represent the capabilities of receiving audio + and/or video calls by including a channel class in + a contact's capabilities with ChannelType = Call + in the fixed properties dictionary, and InitialAudio and/or + InitialVideo in the allowed properties list. Clients wishing to + discover whether a particular contact is likely to be able to + receive audio and/or video calls SHOULD use this information.</p> + + <tp:rationale> + <p>Not all clients support video calls, and it would also be + possible (although unlikely) to have a client which could only + stream video, not audio.</p> + </tp:rationale> + + <p>Clients that are willing to receive audio and/or video calls + SHOULD include the following among their channel classes if + calling <tp:dbus-ref + namespace="ofdT.Connection.Interface.ContactCapabilities">UpdateCapabilities</tp:dbus-ref> + (clients of a <tp:dbus-ref + namespace="org.freedesktop.Telepathy">ChannelDispatcher</tp:dbus-ref> + SHOULD instead arrange for the ChannelDispatcher to do this, + by including the filters in their <tp:dbus-ref + namespace="ofdT.Client.Handler">HandlerChannelFilter</tp:dbus-ref> + properties):</p> + + <ul> + <li>{ ChannelType = Call }</li> + <li>{ ChannelType = Call, InitialAudio = True } + if receiving calls with audio is supported</li> + <li>{ ChannelType = Call, InitialVideo = True } + if receiving calls with video is supported</li> + </ul> + + <tp:rationale> + <p>Connection managers for protocols with capability discovery, + like XMPP, need this information to advertise the appropriate + capabilities for their protocol.</p> + </tp:rationale> + </tp:docstring> + </property> + + <property name="InitialVideo" tp:name-for-bindings="Initial_Video" + type="b" access="read" tp:immutable="yes" tp:requestable="yes"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The same as <tp:member-ref>InitialAudio</tp:member-ref>, but for + a video stream. This property is immutable (cannot change).</p> + + <p>In particular, note that if this property is False, this does not + imply that an active video stream has not been added, only that no + video stream was active at the time the channel appeared.</p> + + <p>This property is the correct way to discover whether connection + managers, contacts etc. support video calls; it appears in + capabilities structures in the same way as InitialAudio.</p> + </tp:docstring> + </property> + + <property name="InitialAudioName" tp:name-for-bindings="Initial_Audio_Name" + type="s" access="read" tp:immutable="yes" tp:requestable="yes"> + <tp:added version="0.21.2"/> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>If <tp:member-ref>InitialAudio</tp:member-ref> is set to + True, then this property will name the intial audio content + with the value of this property.</p> + + <tp:rationale> + <p>Content names are meant to be significant, but if no name + can be given to initial audio content, then its name cannot + be meaningful or even localized.</p> + </tp:rationale> + + <p>If this property is empty or missing from the channel + request and InitialAudio is True, then the CM must come up + with a sensible for the content, such as "audio".</p> + + <p>If the protocol has no concept of stream names then this + property will not show up in the allowed properties list of + the Requestable Channel Classes for call channels.</p> + </tp:docstring> + </property> + + <property name="InitialVideoName" tp:name-for-bindings="Initial_Video_Name" + type="s" access="read" tp:immutable="yes" tp:requestable="yes"> + <tp:added version="0.21.2"/> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The same as + <tp:member-ref>InitialAudioName</tp:member-ref>, but for a + video stream created by setting + <tp:member-ref>InitialVideo</tp:member-ref> to True. This + property is immutable and so cannot change.</p> + </tp:docstring> + </property> + + <property name="MutableContents" tp:name-for-bindings="Mutable_Contents" + type="b" access="read" tp:immutable="yes"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>If True, a stream of a different content type can be added + after the Channel has been requested </p> + + <p>If this property is missing, clients SHOULD assume that it is False, + and thus that the channel's streams cannot be changed once the call + has started.</p> + + <p>If this property isn't present in the "allowed" set in any of the + Call entries contact capabilities, then user interfaces MAY choose to + show a separate "call" option for each class of call.</p> + + <tp:rationale> + <p>For example, once an audio-only Google Talk call has started, + it is not possible to add a video stream; both audio and video + must be requested at the start of the call if video is desired. + User interfaces may use this pseudo-capability as a hint to + display separate "Audio call" and "Video call" buttons, rather + than a single "Call" button with the option to add and remove + video once the call has started for contacts without this flag. + </p> + </tp:rationale> + </tp:docstring> + </property> + + <tp:hct name="audio"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>This client supports audio calls.</p> + </tp:docstring> + </tp:hct> + + <tp:hct name="video"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>This client supports video calls.</p> + </tp:docstring> + </tp:hct> + + <tp:hct name="gtalk-p2p"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The client can implement streaming for streams whose <tp:dbus-ref + namespace="ofdT.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref> + property is <tp:type>Stream_Transport_Type</tp:type>_GTalk_P2P.</p> + </tp:docstring> + </tp:hct> + + <tp:hct name="ice"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The client can implement streaming for streams whose <tp:dbus-ref + namespace="ofdT.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref> + property is <tp:type>Stream_Transport_Type</tp:type>_ICE.</p> + </tp:docstring> + </tp:hct> + + <tp:hct name="wlm-2009"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The client can implement streaming for streams whose <tp:dbus-ref + namespace="ofdT.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref> + property is <tp:type>Stream_Transport_Type</tp:type>_WLM_2009.</p> + </tp:docstring> + </tp:hct> + + <tp:hct name="shm"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The client can implement streaming for streams whose <tp:dbus-ref + namespace="ofdT.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref> + property is <tp:type>Stream_Transport_Type</tp:type>_SHM.</p> + </tp:docstring> + </tp:hct> + + <tp:hct name="video/h264" is-family="yes"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The client supports media streaming with H264 (etc.).</p> + + <p>This handler capability token is a one of a family + of similar tokens: for any other audio or video codec whose MIME + type is audio/<em>subtype</em> or video/<em>subtype</em>, a handler + capability token of this form may exist (the subtype MUST appear + in lower case in this context). Clients MAY support more + codecs than they explicitly advertise support for; clients SHOULD + explicitly advertise support for their preferred codec(s), and + for codecs like H264 that are, in practice, significant in codec + negotiation.</p> + + <tp:rationale> + <p>For instance, the XMPP capability used by the Google Video + Chat web client to determine whether a client is compatible + with it requires support for H264 video, so an XMPP + connection manager that supports this version of Jingle should + not advertise the Google Video Chat capability unless there + is at least one installed client that declares that it supports + <code>video/h264</code> on Call channels.</p> + </tp:rationale> + + <p>For example, a client could advertise support for audio and video + calls using Speex, Theora and H264 by having five handler capability + tokens in its <tp:dbus-ref + namespace="ofdT.Client.Handler">Capabilities</tp:dbus-ref> + property:</p> + + <ul> + <li><code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/audio</code></li> + <li><code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/audio/speex</code></li> + <li><code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/video</code></li> + <li><code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/video/theora</code></li> + <li><code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/video/h264</code></li> + </ul> + + <p>Clients MAY have media signalling abilities without explicitly + supporting any particular codec, and connection managers SHOULD + support this usage.</p> + + <tp:rationale> + <p>This is necessary to support gatewaying between two Telepathy + connections, in which case the available codecs might not be + known to the gatewaying process.</p> + </tp:rationale> + </tp:docstring> + </tp:hct> + + </interface> +</node> +<!-- vim:set sw=2 sts=2 et ft=xml: --> diff --git a/farstream/extensions/Makefile.am b/farstream/extensions/Makefile.am new file mode 100644 index 000000000..24f71c8ef --- /dev/null +++ b/farstream/extensions/Makefile.am @@ -0,0 +1,166 @@ +# This directory re-uses telepathy-glib's code generation mechanisms to +# generate code for interfaces that aren't stable enough for telepathy-glib +# yet, so we can start to adapt example code to use them. + +tools_dir = $(top_srcdir)/tools + +AM_CFLAGS = \ + $(ERROR_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(TELEPATHY_CFLAGS) \ + $(DBUS_CFLAGS) \ + -I${top_builddir} -I${top_srcdir} + +EXTRA_DIST = \ + all.xml \ + Call_Content_Codec_Offer.xml \ + Call_Content_Interface_Media.xml \ + Call_Content_Interface_Mute.xml \ + Call_Content_Interface_Video_Control.xml \ + call-content.xml \ + Call_Content.xml \ + Call_Stream_Endpoint.xml \ + Call_Stream_Interface_Media.xml \ + call-stream.xml \ + Call_Stream.xml \ + Channel_Type_Call.xml \ + channel.xml \ + misc.xml + +noinst_LTLIBRARIES = libfuture-extensions.la + +# In an external project you'd use $(TP_GLIB_LIBS) (obtained from +# pkg-config via autoconf) instead of the .la path +libfuture_extensions_la_LIBADD = \ + $(GLIB_LIBS) \ + $(TELEPATHY_LIBS) + +libfuture_extensions_la_SOURCES = \ + call-content.c \ + call-content.h \ + call-stream.c \ + call-stream.h \ + extensions.c \ + extensions-cli.c \ + extensions.h + +nodist_libfuture_extensions_la_SOURCES = \ + _gen/signals-marshal.c \ + _gen/signals-marshal.h \ + _gen/signals-marshal.list \ + _gen/register-dbus-glib-marshallers-body.h \ + _gen/enums.h \ + _gen/gtypes.h \ + _gen/gtypes-body.h \ + _gen/interfaces.h \ + _gen/interfaces-body.h \ + _gen/cli-call-content.h \ + _gen/cli-call-content-body.h \ + _gen/cli-call-stream.h \ + _gen/cli-call-stream-body.h \ + _gen/cli-channel.h \ + _gen/cli-channel-body.h \ + _gen/cli-misc.h \ + _gen/cli-misc-body.h + +BUILT_SOURCES = \ + _gen/all.xml \ + _gen/call-content.xml \ + _gen/call-stream.xml \ + _gen/channel.xml \ + _gen/misc.xml \ + $(nodist_libfuture_extensions_la_SOURCES) + +CLEANFILES = $(BUILT_SOURCES) + +clean-local: + rm -rf _gen + +XSLTPROCFLAGS = --nonet --novalid + +# Generated files which can be generated for all categories simultaneously + +_gen/all.xml: all.xml $(wildcard $(srcdir)/*.xml) $(wildcard $(top_srcdir)/spec/*.xml) $(tools_dir)/xincludator.py + $(mkdir_p) _gen + $(AM_V_GEN)$(PYTHON) $(tools_dir)/xincludator.py $< > $@ + +_gen/gtypes.h _gen/gtypes-body.h: _gen/all.xml \ + $(top_srcdir)/tools/glib-gtypes-generator.py + $(AM_V_GEN)$(PYTHON) $(top_srcdir)/tools/glib-gtypes-generator.py \ + $< _gen/gtypes Tf_Future + +_gen/signals-marshal.list: _gen/all.xml \ + $(tools_dir)/glib-signals-marshal-gen.py + $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-signals-marshal-gen.py $< > $@ + +_gen/signals-marshal.h: _gen/signals-marshal.list Makefile.am + $(AM_V_GEN)$(GLIB_GENMARSHAL) --header --prefix=_tf_future_ext_marshal $< > $@ + +_gen/signals-marshal.c: _gen/signals-marshal.list Makefile.am + $(AM_V_GEN){ echo '#include "_gen/signals-marshal.h"' && \ + $(GLIB_GENMARSHAL) --body --prefix=_tf_future_ext_marshal $< ; } > $@ + +_gen/register-dbus-glib-marshallers-body.h: _gen/all.xml \ + $(tools_dir)/glib-client-marshaller-gen.py + $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-marshaller-gen.py $< \ + _tf_future_ext > $@ + +_gen/enums.h: _gen/all.xml \ + $(tools_dir)/c-constants-gen.py + $(AM_V_GEN)$(PYTHON) $(tools_dir)/c-constants-gen.py \ + Tf_Future \ + $< _gen/enums + +_gen/interfaces-body.h _gen/interfaces.h: _gen/all.xml \ + $(tools_dir)/glib-interfaces-gen.py + $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-interfaces-gen.py \ + Tf_Future _gen/interfaces-body.h _gen/interfaces.h $< + +# Generated files which must be generated per "category". Each TpProxy +# subclass you want to use with --subclass will need to have its own category, +# although you can subdivide further if you want. + +_gen/%.xml: %.xml $(wildcard $(srcdir)/*.xml) $(wildcard $(top_srcdir)/spec/*.xml) $(tools_dir)/xincludator.py + $(mkdir_p) _gen + $(AM_V_GEN)$(PYTHON) $(tools_dir)/xincludator.py $< > $@ + +_gen/cli-channel-body.h _gen/cli-channel.h: _gen/channel.xml \ + $(tools_dir)/glib-client-gen.py Makefile.am + $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-gen.py \ + --group=channel \ + --subclass=TpChannel \ + --subclass-assert=TP_IS_CHANNEL \ + --iface-quark-prefix=TF_FUTURE_IFACE_QUARK \ + --tp-proxy-api=0.7.6 \ + $< Tf_Future_Cli _gen/cli-channel + +_gen/cli-call-content-body.h _gen/cli-call-content.h: _gen/call-content.xml \ + $(tools_dir)/glib-client-gen.py Makefile.am + $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-gen.py \ + --group=call_content \ + --subclass=TfFutureCallContent \ + --subclass-assert=TF_FUTURE_IS_CALL_CONTENT \ + --iface-quark-prefix=TF_FUTURE_IFACE_QUARK \ + --tp-proxy-api=0.7.6 \ + $< Tf_Future_Cli _gen/cli-call-content + +_gen/cli-call-stream-body.h _gen/cli-call-stream.h: _gen/call-stream.xml \ + $(tools_dir)/glib-client-gen.py Makefile.am + $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-gen.py \ + --group=call_stream \ + --subclass=TfFutureCallStream \ + --subclass-assert=TF_FUTURE_IS_CALL_STREAM \ + --iface-quark-prefix=TF_FUTURE_IFACE_QUARK \ + --tp-proxy-api=0.7.6 \ + $< Tf_Future_Cli _gen/cli-call-stream + +# for now the Endpoint etc. interfaces are on every TpProxy - when we +# have a TpCallEndpoint etc., they should appear on that + +_gen/cli-misc-body.h _gen/cli-misc.h: _gen/misc.xml \ + $(tools_dir)/glib-client-gen.py Makefile.am + $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-gen.py \ + --group=misc \ + --iface-quark-prefix=TF_FUTURE_IFACE_QUARK \ + --tp-proxy-api=0.7.6 \ + $< Tf_Future_Cli _gen/cli-misc diff --git a/farstream/extensions/all.xml b/farstream/extensions/all.xml new file mode 100644 index 000000000..618cef7b0 --- /dev/null +++ b/farstream/extensions/all.xml @@ -0,0 +1,12 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Extensions from the future</tp:title> + +<xi:include href="call-content.xml"/> +<xi:include href="call-stream.xml"/> +<xi:include href="channel.xml"/> +<xi:include href="misc.xml"/> + +</tp:spec> diff --git a/farstream/extensions/call-content.c b/farstream/extensions/call-content.c new file mode 100644 index 000000000..deb36ecc3 --- /dev/null +++ b/farstream/extensions/call-content.c @@ -0,0 +1,161 @@ +/* + * call-content.c - proxy for a Content in a Call channel + * + * Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * Copyright (C) 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "extensions/call-content.h" + +#include <telepathy-glib/proxy-subclass.h> +#include <telepathy-glib/telepathy-glib.h> + +#include "extensions/extensions.h" + +/* Generated code */ +#include "_gen/cli-call-content-body.h" + +/** + * SECTION:call-content + * @title: TfFutureCallContent + * @short_description: proxy for a Content in a Call channel + * @see_also: #TpChannel + * + * FIXME + * + * Since: FIXME + */ + +/** + * TfFutureCallContentClass: + * + * The class of a #TfFutureCallContent. + * + * Since: FIXME + */ +struct _TfFutureCallContentClass { + TpProxyClass parent_class; + /*<private>*/ + gpointer priv; +}; + +/** + * TfFutureCallContent: + * + * A proxy object for a Telepathy connection manager. + * + * Since: FIXME + */ +struct _TfFutureCallContent { + TpProxy parent; + /*<private>*/ + TfFutureCallContentPrivate *priv; +}; + +struct _TfFutureCallContentPrivate { + int dummy; +}; + +G_DEFINE_TYPE (TfFutureCallContent, + tf_future_call_content, + TP_TYPE_PROXY); + +static void +tf_future_call_content_init (TfFutureCallContent *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TF_FUTURE_TYPE_CALL_CONTENT, + TfFutureCallContentPrivate); +} + +static void +tf_future_call_content_class_init (TfFutureCallContentClass *klass) +{ + TpProxyClass *proxy_class = (TpProxyClass *) klass; + + g_type_class_add_private (klass, sizeof (TfFutureCallContentPrivate)); + + proxy_class->must_have_unique_name = TRUE; + proxy_class->interface = TF_FUTURE_IFACE_QUARK_CALL_CONTENT; + tf_future_call_content_init_known_interfaces (); +} + +/** + * tf_future_call_content_new: + * @channel: the Call channel + * @object_path: the object path of the content; may not be %NULL + * @error: used to indicate the error if %NULL is returned + * + * <!-- --> + * + * Returns: a new content proxy, or %NULL on invalid arguments + * + * Since: FIXME + */ +TfFutureCallContent * +tf_future_call_content_new (TpChannel *channel, + const gchar *object_path, + GError **error) +{ + TfFutureCallContent *ret = NULL; + + g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL); + g_return_val_if_fail (object_path != NULL, NULL); + + if (!tp_dbus_check_valid_object_path (object_path, error)) + goto finally; + + ret = TF_FUTURE_CALL_CONTENT (g_object_new (TF_FUTURE_TYPE_CALL_CONTENT, + /* FIXME: pass in the Channel as a property? */ + "dbus-daemon", tp_proxy_get_dbus_daemon (channel), + "bus-name", tp_proxy_get_bus_name (channel), + "object-path", object_path, + NULL)); + +finally: + return ret; +} + +/** + * tf_future_call_content_init_known_interfaces: + * + * Ensure that the known interfaces for TfFutureCallContent have been set up. + * This is done automatically when necessary, but for correct + * overriding of library interfaces by local extensions, you should + * call this function before calling + * tp_proxy_or_subclass_hook_on_interface_add() with first argument + * %TF_FUTURE_TYPE_CALL_CONTENT. + * + * Since: 0.7.32 + */ +void +tf_future_call_content_init_known_interfaces (void) +{ + static gsize once = 0; + + if (g_once_init_enter (&once)) + { + GType tp_type = TF_FUTURE_TYPE_CALL_CONTENT; + + tp_proxy_init_known_interfaces (); + tp_proxy_or_subclass_hook_on_interface_add (tp_type, + tf_future_cli_call_content_add_signals); + tp_proxy_subclass_add_error_mapping (tp_type, + TP_ERROR_PREFIX, TP_ERRORS, TP_TYPE_ERROR); + + g_once_init_leave (&once, 1); + } +} diff --git a/farstream/extensions/call-content.h b/farstream/extensions/call-content.h new file mode 100644 index 000000000..68873d5ca --- /dev/null +++ b/farstream/extensions/call-content.h @@ -0,0 +1,62 @@ +/* + * call-content.h - proxy for a Content in a Call channel + * + * Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * Copyright (C) 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef TF_FUTURE_CALL_CONTENT_H +#define TF_FUTURE_CALL_CONTENT_H + +#include <telepathy-glib/channel.h> +#include <telepathy-glib/proxy.h> + +G_BEGIN_DECLS + +typedef struct _TfFutureCallContent TfFutureCallContent; +typedef struct _TfFutureCallContentPrivate TfFutureCallContentPrivate; +typedef struct _TfFutureCallContentClass TfFutureCallContentClass; + +GType tf_future_call_content_get_type (void); + +/* TYPE MACROS */ +#define TF_FUTURE_TYPE_CALL_CONTENT \ + (tf_future_call_content_get_type ()) +#define TF_FUTURE_CALL_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), TF_FUTURE_TYPE_CALL_CONTENT, \ + TfFutureCallContent)) +#define TF_FUTURE_CALL_CONTENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), TF_FUTURE_TYPE_CALL_CONTENT, \ + TfFutureCallContentClass)) +#define TF_FUTURE_IS_CALL_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), TF_FUTURE_TYPE_CALL_CONTENT)) +#define TF_FUTURE_IS_CALL_CONTENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), TF_FUTURE_TYPE_CALL_CONTENT)) +#define TF_FUTURE_CALL_CONTENT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), TF_FUTURE_TYPE_CALL_CONTENT, \ + TfFutureCallContentClass)) + +TfFutureCallContent *tf_future_call_content_new (TpChannel *channel, + const gchar *object_path, GError **error); + +void tf_future_call_content_init_known_interfaces (void); + +G_END_DECLS + +#include "extensions/_gen/cli-call-content.h" + +#endif diff --git a/farstream/extensions/call-content.xml b/farstream/extensions/call-content.xml new file mode 100644 index 000000000..a543becb6 --- /dev/null +++ b/farstream/extensions/call-content.xml @@ -0,0 +1,11 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Call Content</tp:title> + +<xi:include href="Call_Content.xml"/> +<xi:include href="Call_Content_Interface_Media.xml"/> +<xi:include href="Call_Content_Interface_Video_Control.xml"/> + +</tp:spec> diff --git a/farstream/extensions/call-stream.c b/farstream/extensions/call-stream.c new file mode 100644 index 000000000..a9a0d1ccb --- /dev/null +++ b/farstream/extensions/call-stream.c @@ -0,0 +1,161 @@ +/* + * call-stream.c - proxy for a Stream in a Call channel + * + * Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * Copyright (C) 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "extensions/call-stream.h" + +#include <telepathy-glib/proxy-subclass.h> +#include <telepathy-glib/telepathy-glib.h> + +#include "extensions/extensions.h" + +/* Generated code */ +#include "_gen/cli-call-stream-body.h" + +/** + * SECTION:call-stream + * @title: TfFutureCallStream + * @short_description: proxy for a Stream in a Call channel + * @see_also: #TpChannel + * + * FIXME + * + * Since: FIXME + */ + +/** + * TfFutureCallStreamClass: + * + * The class of a #TfFutureCallStream. + * + * Since: FIXME + */ +struct _TfFutureCallStreamClass { + TpProxyClass parent_class; + /*<private>*/ + gpointer priv; +}; + +/** + * TfFutureCallStream: + * + * A proxy object for a Telepathy connection manager. + * + * Since: FIXME + */ +struct _TfFutureCallStream { + TpProxy parent; + /*<private>*/ + TfFutureCallStreamPrivate *priv; +}; + +struct _TfFutureCallStreamPrivate { + int dummy; +}; + +G_DEFINE_TYPE (TfFutureCallStream, + tf_future_call_stream, + TP_TYPE_PROXY); + +static void +tf_future_call_stream_init (TfFutureCallStream *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TF_FUTURE_TYPE_CALL_STREAM, + TfFutureCallStreamPrivate); +} + +static void +tf_future_call_stream_class_init (TfFutureCallStreamClass *klass) +{ + TpProxyClass *proxy_class = (TpProxyClass *) klass; + + g_type_class_add_private (klass, sizeof (TfFutureCallStreamPrivate)); + + proxy_class->must_have_unique_name = TRUE; + proxy_class->interface = TF_FUTURE_IFACE_QUARK_CALL_STREAM; + tf_future_call_stream_init_known_interfaces (); +} + +/** + * tf_future_call_stream_new: + * @channel: the Call channel + * @object_path: the object path of the stream; may not be %NULL + * @error: used to indicate the error if %NULL is returned + * + * <!-- --> + * + * Returns: a new stream proxy, or %NULL on invalid arguments + * + * Since: FIXME + */ +TfFutureCallStream * +tf_future_call_stream_new (TpChannel *channel, + const gchar *object_path, + GError **error) +{ + TfFutureCallStream *ret = NULL; + + g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL); + g_return_val_if_fail (object_path != NULL, NULL); + + if (!tp_dbus_check_valid_object_path (object_path, error)) + goto finally; + + ret = TF_FUTURE_CALL_STREAM (g_object_new (TF_FUTURE_TYPE_CALL_STREAM, + /* FIXME: pass in the Channel as a property? */ + "dbus-daemon", tp_proxy_get_dbus_daemon (channel), + "bus-name", tp_proxy_get_bus_name (channel), + "object-path", object_path, + NULL)); + +finally: + return ret; +} + +/** + * tf_future_call_stream_init_known_interfaces: + * + * Ensure that the known interfaces for TfFutureCallStream have been set up. + * This is done automatically when necessary, but for correct + * overriding of library interfaces by local extensions, you should + * call this function before calling + * tp_proxy_or_subclass_hook_on_interface_add() with first argument + * %TF_FUTURE_TYPE_CALL_STREAM. + * + * Since: 0.7.32 + */ +void +tf_future_call_stream_init_known_interfaces (void) +{ + static gsize once = 0; + + if (g_once_init_enter (&once)) + { + GType tp_type = TF_FUTURE_TYPE_CALL_STREAM; + + tp_proxy_init_known_interfaces (); + tp_proxy_or_subclass_hook_on_interface_add (tp_type, + tf_future_cli_call_stream_add_signals); + tp_proxy_subclass_add_error_mapping (tp_type, + TP_ERROR_PREFIX, TP_ERRORS, TP_TYPE_ERROR); + + g_once_init_leave (&once, 1); + } +} diff --git a/farstream/extensions/call-stream.h b/farstream/extensions/call-stream.h new file mode 100644 index 000000000..2cd82b554 --- /dev/null +++ b/farstream/extensions/call-stream.h @@ -0,0 +1,62 @@ +/* + * call-stream.h - proxy for a Stream in a Call channel + * + * Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * Copyright (C) 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef TF_FUTURE_CALL_STREAM_H +#define TF_FUTURE_CALL_STREAM_H + +#include <telepathy-glib/channel.h> +#include <telepathy-glib/proxy.h> + +G_BEGIN_DECLS + +typedef struct _TfFutureCallStream TfFutureCallStream; +typedef struct _TfFutureCallStreamPrivate TfFutureCallStreamPrivate; +typedef struct _TfFutureCallStreamClass TfFutureCallStreamClass; + +GType tf_future_call_stream_get_type (void); + +/* TYPE MACROS */ +#define TF_FUTURE_TYPE_CALL_STREAM \ + (tf_future_call_stream_get_type ()) +#define TF_FUTURE_CALL_STREAM(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), TF_FUTURE_TYPE_CALL_STREAM, \ + TfFutureCallStream)) +#define TF_FUTURE_CALL_STREAM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), TF_FUTURE_TYPE_CALL_STREAM, \ + TfFutureCallStreamClass)) +#define TF_FUTURE_IS_CALL_STREAM(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), TF_FUTURE_TYPE_CALL_STREAM)) +#define TF_FUTURE_IS_CALL_STREAM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), TF_FUTURE_TYPE_CALL_STREAM)) +#define TF_FUTURE_CALL_STREAM_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), TF_FUTURE_TYPE_CALL_STREAM, \ + TfFutureCallStreamClass)) + +TfFutureCallStream *tf_future_call_stream_new (TpChannel *channel, + const gchar *object_path, GError **error); + +void tf_future_call_stream_init_known_interfaces (void); + +G_END_DECLS + +#include "extensions/_gen/cli-call-stream.h" + +#endif diff --git a/farstream/extensions/call-stream.xml b/farstream/extensions/call-stream.xml new file mode 100644 index 000000000..3e85b3791 --- /dev/null +++ b/farstream/extensions/call-stream.xml @@ -0,0 +1,10 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Call Stream</tp:title> + +<xi:include href="Call_Stream.xml"/> +<xi:include href="Call_Stream_Interface_Media.xml"/> + +</tp:spec> diff --git a/farstream/extensions/channel.xml b/farstream/extensions/channel.xml new file mode 100644 index 000000000..b4e0a469c --- /dev/null +++ b/farstream/extensions/channel.xml @@ -0,0 +1,9 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Channel extensions from the future</tp:title> + +<xi:include href="Channel_Type_Call.xml"/> + +</tp:spec> diff --git a/farstream/extensions/extensions-cli.c b/farstream/extensions/extensions-cli.c new file mode 100644 index 000000000..971669df0 --- /dev/null +++ b/farstream/extensions/extensions-cli.c @@ -0,0 +1,35 @@ +#include "extensions.h" + +#include <telepathy-glib/telepathy-glib.h> +#include <telepathy-glib/proxy-subclass.h> + +static void _tf_future_ext_register_dbus_glib_marshallers (void); + +/* include auto-generated stubs for client-specific code */ +#include "_gen/signals-marshal.h" +#include "_gen/cli-channel-body.h" +#include "_gen/cli-misc-body.h" +#include "_gen/register-dbus-glib-marshallers-body.h" + +static gpointer +tf_future_cli_once (gpointer data) +{ + _tf_future_ext_register_dbus_glib_marshallers (); + + tp_channel_init_known_interfaces (); + + tp_proxy_or_subclass_hook_on_interface_add (TP_TYPE_PROXY, + tf_future_cli_misc_add_signals); + tp_proxy_or_subclass_hook_on_interface_add (TP_TYPE_CHANNEL, + tf_future_cli_channel_add_signals); + + return NULL; +} + +void +tf_future_cli_init (void) +{ + static GOnce once = G_ONCE_INIT; + + g_once (&once, tf_future_cli_once, NULL); +} diff --git a/farstream/extensions/extensions.c b/farstream/extensions/extensions.c new file mode 100644 index 000000000..eeda4623c --- /dev/null +++ b/farstream/extensions/extensions.c @@ -0,0 +1,6 @@ +#include "extensions.h" + +/* include auto-generated stubs for things common to service and client */ +#include "_gen/gtypes-body.h" +#include "_gen/interfaces-body.h" +#include "_gen/signals-marshal.h" diff --git a/farstream/extensions/extensions.h b/farstream/extensions/extensions.h new file mode 100644 index 000000000..f6e080d5e --- /dev/null +++ b/farstream/extensions/extensions.h @@ -0,0 +1,23 @@ +#ifndef FUTURE_EXTENSIONS_H +#define FUTURE_EXTENSIONS_H + +#include <glib-object.h> +#include <telepathy-glib/telepathy-glib.h> + +#include "extensions/_gen/enums.h" +#include "extensions/_gen/cli-channel.h" +#include "extensions/_gen/cli-misc.h" + +#include "extensions/call-content.h" +#include "extensions/call-stream.h" + +G_BEGIN_DECLS + +#include "extensions/_gen/gtypes.h" +#include "extensions/_gen/interfaces.h" + +void tf_future_cli_init (void); + +G_END_DECLS + +#endif diff --git a/farstream/extensions/misc.xml b/farstream/extensions/misc.xml new file mode 100644 index 000000000..0384410f6 --- /dev/null +++ b/farstream/extensions/misc.xml @@ -0,0 +1,10 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Miscellaneous extensions from the future</tp:title> + +<xi:include href="Call_Content_Codec_Offer.xml"/> +<xi:include href="Call_Stream_Endpoint.xml"/> + +</tp:spec> diff --git a/farstream/m4/Makefile.am b/farstream/m4/Makefile.am new file mode 100644 index 000000000..893cef7b4 --- /dev/null +++ b/farstream/m4/Makefile.am @@ -0,0 +1,5 @@ +EXTRA_DIST = \ +as-compiler-flag.m4 \ +as-version.m4 \ +as-ac-expand.m4 \ +ac-python-headers.m4 diff --git a/farstream/m4/ac-python-headers.m4 b/farstream/m4/ac-python-headers.m4 new file mode 100644 index 000000000..585455fd4 --- /dev/null +++ b/farstream/m4/ac-python-headers.m4 @@ -0,0 +1,30 @@ +dnl Copy pasted from gst-python's acinclude.m4 file + +dnl a macro to check for ability to create python extensions +dnl AM_CHECK_PYTHON_HEADERS([ACTION-IF-POSSIBLE], [ACTION-IF-NOT-POSSIBLE]) +dnl function also defines PYTHON_INCLUDES +AC_DEFUN([AM_CHECK_PYTHON_HEADERS], +[AC_REQUIRE([AM_PATH_PYTHON]) +AC_MSG_CHECKING(for headers required to compile python extensions) +dnl deduce PYTHON_INCLUDES +py_prefix=`$PYTHON -c "import sys; print sys.prefix"` +py_exec_prefix=`$PYTHON -c "import sys; print sys.exec_prefix"` +if $PYTHON-config --help 2>/dev/null; then + PYTHON_INCLUDES=`$PYTHON-config --includes 2>/dev/null` +else + PYTHON_INCLUDES="-I${py_prefix}/include/python${PYTHON_VERSION}" + if test "$py_prefix" != "$py_exec_prefix"; then + PYTHON_INCLUDES="$PYTHON_INCLUDES -I${py_exec_prefix}/include/python${PYTHON_VERSION}" + fi +fi +AC_SUBST(PYTHON_INCLUDES) +dnl check if the headers exist: +save_CPPFLAGS="$CPPFLAGS" +CPPFLAGS="$CPPFLAGS $PYTHON_INCLUDES" +AC_TRY_CPP([#include <Python.h>],dnl +[AC_MSG_RESULT(found) +$1],dnl +[AC_MSG_RESULT(not found) +$2]) +CPPFLAGS="$save_CPPFLAGS" +]) diff --git a/farstream/m4/as-ac-expand.m4 b/farstream/m4/as-ac-expand.m4 new file mode 100644 index 000000000..0c7117393 --- /dev/null +++ b/farstream/m4/as-ac-expand.m4 @@ -0,0 +1,40 @@ +dnl AS_AC_EXPAND(VAR, CONFIGURE_VAR) +dnl +dnl example +dnl AS_AC_EXPAND(SYSCONFDIR, $sysconfdir) +dnl will set SYSCONFDIR to /usr/local/etc if prefix=/usr/local + +AC_DEFUN([AS_AC_EXPAND], +[ + EXP_VAR=[$1] + FROM_VAR=[$2] + + dnl first expand prefix and exec_prefix if necessary + prefix_save=$prefix + exec_prefix_save=$exec_prefix + + dnl if no prefix given, then use /usr/local, the default prefix + if test "x$prefix" = "xNONE"; then + prefix=$ac_default_prefix + fi + dnl if no exec_prefix given, then use prefix + if test "x$exec_prefix" = "xNONE"; then + exec_prefix=$prefix + fi + + full_var="$FROM_VAR" + dnl loop until it doesn't change anymore + while true; do + new_full_var="`eval echo $full_var`" + if test "x$new_full_var"="x$full_var"; then break; fi + full_var=$new_full_var + done + + dnl clean up + full_var=$new_full_var + AC_SUBST([$1], "$full_var") + + dnl restore prefix and exec_prefix + prefix=$prefix_save + exec_prefix=$exec_prefix_save +]) diff --git a/farstream/m4/as-compiler-flag.m4 b/farstream/m4/as-compiler-flag.m4 new file mode 100644 index 000000000..605708a5a --- /dev/null +++ b/farstream/m4/as-compiler-flag.m4 @@ -0,0 +1,33 @@ +dnl as-compiler-flag.m4 0.1.0 + +dnl autostars m4 macro for detection of compiler flags + +dnl David Schleef <ds@schleef.org> + +dnl $Id: as-compiler-flag.m4,v 1.1 2005/06/18 18:02:46 burgerman Exp $ + +dnl AS_COMPILER_FLAG(CFLAGS, ACTION-IF-ACCEPTED, [ACTION-IF-NOT-ACCEPTED]) +dnl Tries to compile with the given CFLAGS. +dnl Runs ACTION-IF-ACCEPTED if the compiler can compile with the flags, +dnl and ACTION-IF-NOT-ACCEPTED otherwise. + +AC_DEFUN([AS_COMPILER_FLAG], +[ + AC_MSG_CHECKING([to see if compiler understands $1]) + + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $1" + + AC_TRY_COMPILE([ ], [], [flag_ok=yes], [flag_ok=no]) + CFLAGS="$save_CFLAGS" + + if test "X$flag_ok" = Xyes ; then + $2 + true + else + $3 + true + fi + AC_MSG_RESULT([$flag_ok]) +]) + diff --git a/farstream/m4/as-version.m4 b/farstream/m4/as-version.m4 new file mode 100644 index 000000000..defc887e0 --- /dev/null +++ b/farstream/m4/as-version.m4 @@ -0,0 +1,66 @@ +dnl as-version.m4 0.1.0 + +dnl autostars m4 macro for versioning + +dnl Thomas Vander Stichele <thomas at apestaart dot org> + +dnl $Id: as-version.m4,v 1.1 2005/06/18 18:02:46 burgerman Exp $ + +dnl AS_VERSION(PACKAGE, PREFIX, MAJOR, MINOR, MICRO, NANO, +dnl ACTION-IF-NO-NANO, [ACTION-IF-NANO]) + +dnl example +dnl AS_VERSION(gstreamer, GST_VERSION, 0, 3, 2,) +dnl for a 0.3.2 release version + +dnl this macro +dnl - defines [$PREFIX]_MAJOR, MINOR and MICRO +dnl - if NANO is empty, then we're in release mode, else in cvs/dev mode +dnl - defines [$PREFIX], VERSION, and [$PREFIX]_RELEASE +dnl - executes the relevant action +dnl - AC_SUBST's PACKAGE, VERSION, [$PREFIX] and [$PREFIX]_RELEASE +dnl as well as the little ones +dnl - doesn't call AM_INIT_AUTOMAKE anymore because it prevents +dnl maintainer mode from running ok +dnl +dnl don't forget to put #undef [$2] and [$2]_RELEASE in acconfig.h +dnl if you use acconfig.h + +AC_DEFUN([AS_VERSION], +[ + PACKAGE=[$1] + [$2]_MAJOR=[$3] + [$2]_MINOR=[$4] + [$2]_MICRO=[$5] + NANO=[$6] + [$2]_NANO=$NANO + if test "x$NANO" = "x" || test "x$NANO" = "x0"; + then + AC_MSG_NOTICE(configuring [$1] for release) + VERSION=[$3].[$4].[$5] + [$2]_RELEASE=1 + dnl execute action + ifelse([$7], , :, [$7]) + else + AC_MSG_NOTICE(configuring [$1] for development with nano $NANO) + VERSION=[$3].[$4].[$5].$NANO + [$2]_RELEASE=0.`date +%Y%m%d.%H%M%S` + dnl execute action + ifelse([$8], , :, [$8]) + fi + + [$2]=$VERSION + AC_DEFINE_UNQUOTED([$2], "$[$2]", [Define the version]) + AC_SUBST([$2]) + AC_DEFINE_UNQUOTED([$2]_RELEASE, "$[$2]_RELEASE", [Define the release version]) + AC_SUBST([$2]_RELEASE) + + AC_SUBST([$2]_MAJOR) + AC_SUBST([$2]_MINOR) + AC_SUBST([$2]_MICRO) + AC_SUBST([$2]_NANO) + AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Define the package name]) + AC_SUBST(PACKAGE) + AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Define the version]) + AC_SUBST(VERSION) +]) diff --git a/farstream/python/Makefile.am b/farstream/python/Makefile.am new file mode 100644 index 000000000..d1f3686c4 --- /dev/null +++ b/farstream/python/Makefile.am @@ -0,0 +1,48 @@ +SUBDIRS = codegen examples + +PYDEFS=`pkg-config --variable=defsdir pygtk-2.0` +GSTPYDEFS=`pkg-config --variable=defsdir gst-python-0.10` + +noinst_HEADERS = pygstminiobject.h common.h + +AM_CPPFLAGS = \ + -I. \ + -I$(top_srcdir) \ + -DDATADIR=\""$(datadir)"\" \ + $(TELEPATHY_CFLAGS) \ + $(PYTHON_INCLUDES) \ + $(PYTPFARSTREAM_CFLAGS) \ + $(FARSTREAM_CFLAGS) \ + $(DBUS_CFLAGS) + +BUILT_SOURCES = \ + pytpfarstream.c + +pytpfarstreamdir = $(pyexecdir) +pytpfarstream_LTLIBRARIES = tpfarstream.la + +tpfarstream_la_SOURCES = \ + pytpfarstreammodule.c pygstminiobject.c + +nodist_tpfarstream_la_SOURCES = pytpfarstream.c + +tpfarstream_la_LIBADD = \ + $(PYTPFARSTREAM_LIBS) \ + $(top_builddir)/telepathy-farstream/libtelepathy-farstream.la + +tpfarstream_la_LDFLAGS = -module -avoid-version + +pytpfarstream.c: pytpfarstream.override pytpfarstream.defs + $(PYTHON) $(top_srcdir)/python/codegen/codegen.py \ + --prefix tf \ + --register $(GSTPYDEFS)/gst-types.defs \ + --override $(srcdir)/pytpfarstream.override \ + $(srcdir)/pytpfarstream.defs > $@ + +EXTRA_DIST = \ + pytpfarstream.override \ + pytpfarstream.defs \ + pytpfarstream-filter.defs \ + rebuild-defs.sh + +CLEANFILES = $(BUILT_SOURCES) diff --git a/farstream/python/codegen/Makefile.am b/farstream/python/codegen/Makefile.am new file mode 100644 index 000000000..dd3eea064 --- /dev/null +++ b/farstream/python/codegen/Makefile.am @@ -0,0 +1,15 @@ +EXTRA_DIST = \ + argtypes.py \ + code-coverage.py \ + codegen.py \ + definitions.py \ + defsparser.py \ + docextract.py \ + docgen.py \ + h2def.py \ + __init__.py \ + mergedefs.py \ + mkskel.py \ + override.py \ + reversewrapper.py \ + scmexpr.py diff --git a/farstream/python/codegen/__init__.py b/farstream/python/codegen/__init__.py new file mode 100644 index 000000000..cfa896ee6 --- /dev/null +++ b/farstream/python/codegen/__init__.py @@ -0,0 +1,15 @@ +# -*- Mode: Python; py-indent-offset: 4 -*- + +__all__ = [ + 'argtypes', + 'codegen', + 'definitions', + 'defsparser', + 'docextract', + 'docgen', + 'h2def', + 'mergedefs', + 'mkskel', + 'override', + 'scmexpr' +] diff --git a/farstream/python/codegen/argtypes.py b/farstream/python/codegen/argtypes.py new file mode 100644 index 000000000..948bae106 --- /dev/null +++ b/farstream/python/codegen/argtypes.py @@ -0,0 +1,1075 @@ +# -*- Mode: Python; py-indent-offset: 4 -*- +import string +import keyword +import struct + +class VarList: + """Nicely format a C variable list""" + def __init__(self): + self.vars = {} + def add(self, ctype, name): + if self.vars.has_key(ctype): + self.vars[ctype] = self.vars[ctype] + (name,) + else: + self.vars[ctype] = (name,) + def __str__(self): + ret = [] + for type in self.vars.keys(): + ret.append(' ') + ret.append(type) + ret.append(' ') + ret.append(string.join(self.vars[type], ', ')) + ret.append(';\n') + if ret: + ret.append('\n') + return string.join(ret, '') + return '' + +class WrapperInfo: + """A class that holds information about variable defs, code + snippets, etcd for use in writing out the function/method + wrapper.""" + def __init__(self): + self.varlist = VarList() + self.parsestr = '' + self.parselist = ['', 'kwlist'] + self.codebefore = [] + self.codeafter = [] + self.arglist = [] + self.kwlist = [] + def get_parselist(self): + return string.join(self.parselist, ', ') + def get_codebefore(self): + return string.join(self.codebefore, '') + def get_codeafter(self): + return string.join(self.codeafter, '') + def get_arglist(self): + return string.join(self.arglist, ', ') + def get_varlist(self): + return str(self.varlist) + def get_kwlist(self): + ret = ' static char *kwlist[] = { %s };\n' % \ + string.join(self.kwlist + [ 'NULL' ], ', ') + if not self.get_varlist(): + ret = ret + '\n' + return ret + + def add_parselist(self, codes, parseargs, keywords): + self.parsestr = self.parsestr + codes + for arg in parseargs: + self.parselist.append(arg) + for kw in keywords: + if keyword.iskeyword(kw): + kw = kw + '_' + self.kwlist.append('"%s"' % kw) + +class ArgType: + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + """Add code to the WrapperInfo instance to handle + parameter.""" + raise RuntimeError, "write_param not implemented for %s" % \ + self.__class__.__name__ + def write_return(self, ptype, ownsreturn, info): + """Adds a variable named ret of the return type to + info.varlist, and add any required code to info.codeafter to + convert the return value to a python object.""" + raise RuntimeError, "write_return not implemented for %s" % \ + self.__class__.__name__ + +class NoneArg(ArgType): + def write_return(self, ptype, ownsreturn, info): + info.codeafter.append(' Py_INCREF(Py_None);\n' + + ' return Py_None;') + +class StringArg(ArgType): + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pdflt: + if pdflt != 'NULL': pdflt = '"' + pdflt + '"' + info.varlist.add('char', '*' + pname + ' = ' + pdflt) + else: + info.varlist.add('char', '*' + pname) + info.arglist.append(pname) + if pnull: + info.add_parselist('z', ['&' + pname], [pname]) + else: + info.add_parselist('s', ['&' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + if ownsreturn: + # have to free result ... + info.varlist.add('gchar', '*ret') + info.codeafter.append(' if (ret) {\n' + + ' PyObject *py_ret = PyString_FromString(ret);\n' + + ' g_free(ret);\n' + + ' return py_ret;\n' + + ' }\n' + + ' Py_INCREF(Py_None);\n' + + ' return Py_None;') + else: + info.varlist.add('const gchar', '*ret') + info.codeafter.append(' if (ret)\n' + + ' return PyString_FromString(ret);\n'+ + ' Py_INCREF(Py_None);\n' + + ' return Py_None;') + +class UCharArg(ArgType): + # allows strings with embedded NULLs. + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pdflt: + info.varlist.add('guchar', '*' + pname + ' = "' + pdflt + '"') + else: + info.varlist.add('guchar', '*' + pname) + info.varlist.add('int', pname + '_len') + info.arglist.append(pname) + if pnull: + info.add_parselist('z#', ['&' + pname, '&' + pname + '_len'], + [pname]) + else: + info.add_parselist('s#', ['&' + pname, '&' + pname + '_len'], + [pname]) + +class CharArg(ArgType): + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pdflt: + info.varlist.add('char', pname + " = '" + pdflt + "'") + else: + info.varlist.add('char', pname) + info.arglist.append(pname) + info.add_parselist('c', ['&' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + info.varlist.add('gchar', 'ret') + info.codeafter.append(' return PyString_FromStringAndSize(&ret, 1);') +class GUniCharArg(ArgType): + ret_tmpl = ('#if !defined(Py_UNICODE_SIZE) || Py_UNICODE_SIZE == 2\n' + ' if (ret > 0xffff) {\n' + ' PyErr_SetString(PyExc_RuntimeError, "returned character can not be represented in 16-bit unicode");\n' + ' return NULL;\n' + ' }\n' + '#endif\n' + ' py_ret = (Py_UNICODE)ret;\n' + ' return PyUnicode_FromUnicode(&py_ret, 1);\n') + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pdflt: + info.varlist.add('gunichar', pname + " = '" + pdflt + "'") + else: + info.varlist.add('gunichar', pname) + info.arglist.append(pname) + info.add_parselist('O&', ['pyg_pyobj_to_unichar_conv', '&' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + info.varlist.add('gunichar', 'ret') + info.varlist.add('Py_UNICODE', 'py_ret') + info.codeafter.append(self.ret_tmpl) + + +class IntArg(ArgType): + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pdflt: + info.varlist.add('int', pname + ' = ' + pdflt) + else: + info.varlist.add('int', pname) + info.arglist.append(pname) + info.add_parselist('i', ['&' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + info.varlist.add('int', 'ret') + info.codeafter.append(' return PyInt_FromLong(ret);') + +class UIntArg(ArgType): + dflt = (' if (py_%(name)s) {\n' + ' if (PyLong_Check(py_%(name)s))\n' + ' %(name)s = PyLong_AsUnsignedLong(py_%(name)s);\n' + ' else if (PyInt_Check(py_%(name)s))\n' + ' %(name)s = PyInt_AsLong(py_%(name)s);\n' + ' else\n' + ' PyErr_SetString(PyExc_TypeError, "Parameter \'%(name)s\' must be an int or a long");\n' + ' if (PyErr_Occurred())\n' + ' return NULL;\n' + ' }\n') + before = (' if (PyLong_Check(py_%(name)s))\n' + ' %(name)s = PyLong_AsUnsignedLong(py_%(name)s);\n' + ' else if (PyInt_Check(py_%(name)s))\n' + ' %(name)s = PyInt_AsLong(py_%(name)s);\n' + ' else\n' + ' PyErr_SetString(PyExc_TypeError, "Parameter \'%(name)s\' must be an int or a long");\n' + ' if (PyErr_Occurred())\n' + ' return NULL;\n') + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if not pdflt: + pdflt = '0'; + + info.varlist.add(ptype, pname + ' = ' + pdflt) + info.codebefore.append(self.dflt % {'name':pname}) + info.varlist.add('PyObject', "*py_" + pname + ' = NULL') + info.arglist.append(pname) + info.add_parselist('O', ['&py_' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + info.varlist.add(ptype, 'ret') + info.codeafter.append(' return PyLong_FromUnsignedLong(ret);') + +class SizeArg(ArgType): + + if struct.calcsize('P') <= struct.calcsize('l'): + llp64 = True + else: + llp64 = False + + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pdflt: + info.varlist.add(ptype, pname + ' = ' + pdflt) + else: + info.varlist.add(ptype, pname) + info.arglist.append(pname) + if self.llp64: + info.add_parselist('k', ['&' + pname], [pname]) + else: + info.add_parselist('K', ['&' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + info.varlist.add(ptype, 'ret') + if self.llp64: + info.codeafter.append(' return PyLong_FromUnsignedLongLong(ret);\n') + else: + info.codeafter.append(' return PyLong_FromUnsignedLong(ret);\n') + +class SSizeArg(ArgType): + + if struct.calcsize('P') <= struct.calcsize('l'): + llp64 = True + else: + llp64 = False + + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pdflt: + info.varlist.add(ptype, pname + ' = ' + pdflt) + else: + info.varlist.add(ptype, pname) + info.arglist.append(pname) + if self.llp64: + info.add_parselist('l', ['&' + pname], [pname]) + else: + info.add_parselist('L', ['&' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + info.varlist.add(ptype, 'ret') + if self.llp64: + info.codeafter.append(' return PyLong_FromLongLong(ret);\n') + else: + info.codeafter.append(' return PyLong_FromLong(ret);\n') + +class LongArg(ArgType): + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pdflt: + info.varlist.add(ptype, pname + ' = ' + pdflt) + else: + info.varlist.add(ptype, pname) + info.arglist.append(pname) + info.add_parselist('l', ['&' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + info.varlist.add(ptype, 'ret') + info.codeafter.append(' return PyInt_FromLong(ret);\n') + +class BoolArg(IntArg): + def write_return(self, ptype, ownsreturn, info): + info.varlist.add('int', 'ret') + info.codeafter.append(' return PyBool_FromLong(ret);\n') + +class TimeTArg(ArgType): + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pdflt: + info.varlist.add('time_t', pname + ' = ' + pdflt) + else: + info.varlist.add('time_t', pname) + info.arglist.append(pname) + info.add_parselist('i', ['&' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + info.varlist.add('time_t', 'ret') + info.codeafter.append(' return PyInt_FromLong(ret);') + +class ULongArg(ArgType): + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pdflt: + info.varlist.add('unsigned long', pname + ' = ' + pdflt) + else: + info.varlist.add('unsigned long', pname) + info.arglist.append(pname) + info.add_parselist('k', ['&' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + info.varlist.add(ptype, 'ret') + info.codeafter.append(' return PyLong_FromUnsignedLong(ret);\n') + +class UInt32Arg(ULongArg): + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + ULongArg.write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info) + ## if sizeof(unsigned long) > sizeof(unsigned int), we need to + ## check the value is within guint32 range + if struct.calcsize('L') > struct.calcsize('I'): + info.codebefore.append(( + ' if (%(pname)s > G_MAXUINT32) {\n' + ' PyErr_SetString(PyExc_ValueError,\n' + ' "Value out of range in conversion of"\n' + ' " %(pname)s parameter to unsigned 32 bit integer");\n' + ' return NULL;\n' + ' }\n') % vars()) + +class Int64Arg(ArgType): + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pdflt: + info.varlist.add('gint64', pname + ' = ' + pdflt) + else: + info.varlist.add('gint64', pname) + info.arglist.append(pname) + info.add_parselist('L', ['&' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + info.varlist.add('gint64', 'ret') + info.codeafter.append(' return PyLong_FromLongLong(ret);') + +class UInt64Arg(ArgType): + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pdflt: + info.varlist.add('guint64', pname + ' = ' + pdflt) + else: + info.varlist.add('guint64', pname) + info.arglist.append(pname) + info.add_parselist('K', ['&' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + info.varlist.add('guint64', 'ret') + info.codeafter.append(' return PyLong_FromUnsignedLongLong(ret);') + + +class DoubleArg(ArgType): + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pdflt: + info.varlist.add('double', pname + ' = ' + pdflt) + else: + info.varlist.add('double', pname) + info.arglist.append(pname) + info.add_parselist('d', ['&' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + info.varlist.add('double', 'ret') + info.codeafter.append(' return PyFloat_FromDouble(ret);') + +class FileArg(ArgType): + nulldflt = (' if (py_%(name)s == Py_None)\n' + ' %(name)s = NULL;\n' + ' else if (py_%(name)s && PyFile_Check(py_%(name)s)\n' + ' %s = PyFile_AsFile(py_%(name)s);\n' + ' else if (py_%(name)s) {\n' + ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a file object or None");\n' + ' return NULL;\n' + ' }') + null = (' if (py_%(name)s && PyFile_Check(py_%(name)s)\n' + ' %(name)s = PyFile_AsFile(py_%(name)s);\n' + ' else if (py_%(name)s != Py_None) {\n' + ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a file object or None");\n' + ' return NULL;\n' + ' }\n') + dflt = (' if (py_%(name)s)\n' + ' %(name)s = PyFile_AsFile(py_%(name)s);\n') + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pnull: + if pdflt: + info.varlist.add('FILE', '*' + pname + ' = ' + pdflt) + info.varlist.add('PyObject', '*py_' + pname + ' = NULL') + info.codebefore.append(self.nulldflt % {'name':pname}) + else: + info.varlist.add('FILE', '*' + pname + ' = NULL') + info.varlist.add('PyObject', '*py_' + pname) + info.codebefore.append(self.null & {'name':pname}) + info.arglist.appned(pname) + info.add_parselist('O', ['&py_' + pname], [pname]) + else: + if pdflt: + info.varlist.add('FILE', '*' + pname + ' = ' + pdflt) + info.varlist.add('PyObject', '*py_' + pname + ' = NULL') + info.codebefore.append(self.dflt % {'name':pname}) + info.arglist.append(pname) + else: + info.varlist.add('PyObject', '*' + pname) + info.arglist.append('PyFile_AsFile(' + pname + ')') + info.add_parselist('O!', ['&PyFile_Type', '&' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + info.varlist.add('FILE', '*ret') + info.codeafter.append(' if (ret)\n' + + ' return PyFile_FromFile(ret, "", "", fclose);\n' + + ' Py_INCREF(Py_None);\n' + + ' return Py_None;') + +class EnumArg(ArgType): + enum = (' if (pyg_enum_get_value(%(typecode)s, py_%(name)s, (gint *)&%(name)s))\n' + ' return NULL;\n') + def __init__(self, enumname, typecode): + self.enumname = enumname + self.typecode = typecode + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pdflt: + info.varlist.add(self.enumname, pname + ' = ' + pdflt) + else: + info.varlist.add(self.enumname, pname) + info.varlist.add('PyObject', '*py_' + pname + ' = NULL') + info.codebefore.append(self.enum % { 'typecode': self.typecode, + 'name': pname}) + info.arglist.append(pname) + info.add_parselist('O', ['&py_' + pname], [pname]); + def write_return(self, ptype, ownsreturn, info): + info.varlist.add('gint', 'ret') + info.codeafter.append(' return pyg_enum_from_gtype(%s, ret);' % self.typecode) + +class FlagsArg(ArgType): + flag = (' if (%(default)spyg_flags_get_value(%(typecode)s, py_%(name)s, (gint *)&%(name)s))\n' + ' return NULL;\n') + def __init__(self, flagname, typecode): + self.flagname = flagname + self.typecode = typecode + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pdflt: + info.varlist.add(self.flagname, pname + ' = ' + pdflt) + default = "py_%s && " % (pname,) + else: + info.varlist.add(self.flagname, pname) + default = "" + info.varlist.add('PyObject', '*py_' + pname + ' = NULL') + info.codebefore.append(self.flag % {'default':default, + 'typecode':self.typecode, + 'name':pname}) + info.arglist.append(pname) + info.add_parselist('O', ['&py_' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + info.varlist.add('guint', 'ret') + info.codeafter.append(' return pyg_flags_from_gtype(%s, ret);' % self.typecode) + +class ObjectArg(ArgType): + # should change these checks to more typesafe versions that check + # a little further down in the class heirachy. + nulldflt = (' if ((PyObject *)py_%(name)s == Py_None)\n' + ' %(name)s = NULL;\n' + ' else if (py_%(name)s && pygobject_check(py_%(name)s, &Py%(type)s_Type))\n' + ' %(name)s = %(cast)s(py_%(name)s->obj);\n' + ' else if (py_%(name)s) {\n' + ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(type)s or None");\n' + ' return NULL;\n' + ' }\n') + null = (' if (py_%(name)s && pygobject_check(py_%(name)s, &Py%(type)s_Type))\n' + ' %(name)s = %(cast)s(py_%(name)s->obj);\n' + ' else if ((PyObject *)py_%(name)s != Py_None) {\n' + ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(type)s or None");\n' + ' return NULL;\n' + ' }\n') + dflt = ' if (py_%(name)s)\n' \ + ' %(name)s = %(cast)s(py_%(name)s->obj);\n' + def __init__(self, objname, parent, typecode): + self.objname = objname + self.cast = string.replace(typecode, '_TYPE_', '_', 1) + self.parent = parent + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pnull: + if pdflt: + info.varlist.add(self.objname, '*' + pname + ' = ' + pdflt) + info.varlist.add('PyGObject', '*py_' + pname + ' = NULL') + info.codebefore.append(self.nulldflt % {'name':pname, + 'cast':self.cast, + 'type':self.objname}) + else: + info.varlist.add(self.objname, '*' + pname + ' = NULL') + info.varlist.add('PyGObject', '*py_' + pname) + info.codebefore.append(self.null % {'name':pname, + 'cast':self.cast, + 'type':self.objname}) + if ptype.endswith('*'): + typename = ptype[:-1] + try: + const, typename = typename.split('const-') + except ValueError: + const = '' + if typename != ptype: + info.arglist.append('(%s *) %s' % (ptype[:-1], pname)) + else: + info.arglist.append(pname) + + info.add_parselist('O', ['&py_' + pname], [pname]) + else: + if pdflt: + info.varlist.add(self.objname, '*' + pname + ' = ' + pdflt) + info.varlist.add('PyGObject', '*py_' + pname + ' = NULL') + info.codebefore.append(self.dflt % {'name':pname, + 'cast':self.cast}) + info.arglist.append(pname) + info.add_parselist('O!', ['&Py%s_Type' % self.objname, + '&py_' + pname], [pname]) + else: + info.varlist.add('PyGObject', '*' + pname) + info.arglist.append('%s(%s->obj)' % (self.cast, pname)) + info.add_parselist('O!', ['&Py%s_Type' % self.objname, + '&' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + if ptype.endswith('*'): + typename = ptype[:-1] + try: + const, typename = typename.split('const-') + except ValueError: + const = '' + info.varlist.add(typename, '*ret') + if ownsreturn: + info.varlist.add('PyObject', '*py_ret') + # < GLib 2.8: using our custom _new and _unref functions + # makes sure we update the proper GstObject refcount + info.codeafter.append(' py_ret = pygobject_new((GObject *)ret);\n' + ' if (ret != NULL)\n' + ' g_object_unref((GObject *)ret);\n' + ' return py_ret;') + else: + info.codeafter.append(' /* pygobject_new handles NULL checking */\n' + + ' return pygobject_new((GObject *)ret);') + +class MiniObjectArg(ArgType): + # should change these checks to more typesafe versions that check + # a little further down in the class heirachy. + nulldflt = (' if ((PyObject *)py_%(name)s == Py_None)\n' + ' %(name)s = NULL;\n' + ' else if (py_%(name)s) && pygstminiobject_check(py_%(name)s, &Py%(type)s_Type))\n' + ' %(name)s = %(cast)s(py_%(name)s->obj);\n' + ' else if (py_%(name)s) {\n' + ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(type)s or None");\n' + ' return NULL;\n' + ' }\n') + null = (' if (py_%(name)s && pygstminiobject_check(py_%(name)s, &Py%(type)s_Type))\n' + ' %(name)s = %(cast)s(py_%(name)s->obj);\n' + ' else if ((PyObject *)py_%(name)s != Py_None) {\n' + ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(type)s or None");\n' + ' return NULL;\n' + ' }\n') + dflt = ' if (py_%(name)s)\n' \ + ' %(name)s = %(cast)s(py_%(name)s->obj);\n' + def __init__(self, objname, parent, typecode): + self.objname = objname + self.cast = string.replace(typecode, '_TYPE_', '_', 1) + self.parent = parent + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pnull: + if pdflt: + info.varlist.add(self.objname, '*' + pname + ' = ' + pdflt) + info.varlist.add('PyGstMiniObject', '*py_' + pname + ' = NULL') + info.codebefore.append(self.nulldflt % {'name':pname, + 'cast':self.cast, + 'type':self.objname}) + else: + info.varlist.add(self.objname, '*' + pname + ' = NULL') + info.varlist.add('PyGstMiniObject', '*py_' + pname) + info.codebefore.append(self.null % {'name':pname, + 'cast':self.cast, + 'type':self.objname}) + if ptype.endswith('*'): + typename = ptype[:-1] + try: + const, typename = typename.split('const-') + except ValueError: + const = '' + if typename != ptype: + info.arglist.append('(%s *) %s' % (ptype[:-1], pname)) + else: + info.arglist.append(pname) + + info.add_parselist('O', ['&py_' + pname], [pname]) + else: + if pdflt: + info.varlist.add(self.objname, '*' + pname + ' = ' + pdflt) + info.varlist.add('PyGstMiniObject', '*py_' + pname + ' = NULL') + info.codebefore.append(self.dflt % {'name':pname, + 'cast':self.cast}) + info.arglist.append(pname) + info.add_parselist('O', ['&Py%s_Type' % self.objname, + '&py_' + pname], [pname]) + else: + info.varlist.add('PyGstMiniObject', '*' + pname) + info.arglist.append('%s(%s->obj)' % (self.cast, pname)) + info.add_parselist('O!', ['&Py%s_Type' % self.objname, + '&' + pname], [pname]) + if keeprefcount: + info.codebefore.append(' gst_mini_object_ref(GST_MINI_OBJECT(%s->obj));\n' % pname) + def write_return(self, ptype, ownsreturn, info): + if ptype.endswith('*'): + typename = ptype[:-1] + try: + const, typename = typename.split('const-') + except ValueError: + const = '' + info.varlist.add(typename, '*ret') + if ownsreturn: + info.varlist.add('PyObject', '*py_ret') + info.codeafter.append(' py_ret = pygstminiobject_new((GstMiniObject *)ret);\n' + ' if (ret != NULL)\n' + ' gst_mini_object_unref((GstMiniObject *)ret);\n' + ' return py_ret;') + else: + info.codeafter.append(' /* pygobject_new handles NULL checking */\n' + + ' return pygstminiobject_new((GstMiniObject *)ret);') + +class BoxedArg(ArgType): + # haven't done support for default args. Is it needed? + check = (' if (pyg_boxed_check(py_%(name)s, %(typecode)s))\n' + ' %(name)s = pyg_boxed_get(py_%(name)s, %(typename)s);\n' + ' else {\n' + ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(typename)s");\n' + ' return NULL;\n' + ' }\n') + null = (' if (pyg_boxed_check(py_%(name)s, %(typecode)s))\n' + ' %(name)s = pyg_boxed_get(py_%(name)s, %(typename)s);\n' + ' else if (py_%(name)s != Py_None) {\n' + ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(typename)s or None");\n' + ' return NULL;\n' + ' }\n') + def __init__(self, ptype, typecode): + self.typename = ptype + self.typecode = typecode + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pnull: + info.varlist.add(self.typename, '*' + pname + ' = NULL') + info.varlist.add('PyObject', '*py_' + pname + ' = Py_None') + info.codebefore.append(self.null % {'name': pname, + 'typename': self.typename, + 'typecode': self.typecode}) + else: + info.varlist.add(self.typename, '*' + pname + ' = NULL') + info.varlist.add('PyObject', '*py_' + pname) + info.codebefore.append(self.check % {'name': pname, + 'typename': self.typename, + 'typecode': self.typecode}) + if ptype[-1] == '*': + typename = ptype[:-1] + if typename[:6] == 'const-': typename = typename[6:] + if typename != self.typename: + info.arglist.append('(%s *)%s' % (ptype[:-1], pname)) + else: + info.arglist.append(pname) + else: + info.arglist.append(pname) + info.add_parselist('O', ['&py_' + pname], [pname]) + ret_tmpl = ' /* pyg_boxed_new handles NULL checking */\n' \ + ' return pyg_boxed_new(%(typecode)s, %(ret)s, %(copy)s, TRUE);' + def write_return(self, ptype, ownsreturn, info): + if ptype[-1] == '*': + info.varlist.add(self.typename, '*ret') + ret = 'ret' + else: + info.varlist.add(self.typename, 'ret') + ret = '&ret' + ownsreturn = 0 # of course it can't own a ref to a local var ... + info.codeafter.append(self.ret_tmpl % + { 'typecode': self.typecode, + 'ret': ret, + 'copy': ownsreturn and 'FALSE' or 'TRUE'}) + +class CustomBoxedArg(ArgType): + # haven't done support for default args. Is it needed? + null = (' if (%(check)s(py_%(name)s))\n' + ' %(name)s = %(get)s(py_%(name)s);\n' + ' else if (py_%(name)s != Py_None) {\n' + ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(type)s or None");\n' + ' return NULL;\n' + ' }\n') + def __init__(self, ptype, pytype, getter, new): + self.pytype = pytype + self.getter = getter + self.checker = 'Py' + ptype + '_Check' + self.new = new + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pnull: + info.varlist.add(ptype[:-1], '*' + pname + ' = NULL') + info.varlist.add('PyObject', '*py_' + pname + ' = Py_None') + info.codebefore.append(self.null % {'name': pname, + 'get': self.getter, + 'check': self.checker, + 'type': ptype[:-1]}) + info.arglist.append(pname) + info.add_parselist('O', ['&py_' + pname], [pname]) + else: + info.varlist.add('PyObject', '*' + pname) + info.arglist.append(self.getter + '(' + pname + ')') + info.add_parselist('O!', ['&' + self.pytype, '&' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + info.varlist.add(ptype[:-1], '*ret') + info.codeafter.append(' if (ret)\n' + + ' return ' + self.new + '(ret);\n' + + ' Py_INCREF(Py_None);\n' + + ' return Py_None;') + +class PointerArg(ArgType): + # haven't done support for default args. Is it needed? + check = (' if (pyg_pointer_check(py_%(name)s, %(typecode)s))\n' + ' %(name)s = pyg_pointer_get(py_%(name)s, %(typename)s);\n' + ' else {\n' + ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(typename)s");\n' + ' return NULL;\n' + ' }\n') + null = (' if (pyg_pointer_check(py_%(name)s, %(typecode)s))\n' + ' %(name)s = pyg_pointer_get(py_%(name)s, %(typename)s);\n' + ' else if (py_%(name)s != Py_None) {\n' + ' PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(typename)s or None");\n' + ' return NULL;\n' + ' }\n') + def __init__(self, ptype, typecode): + self.typename = ptype + self.typecode = typecode + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pnull: + info.varlist.add(self.typename, '*' + pname + ' = NULL') + info.varlist.add('PyObject', '*py_' + pname + ' = Py_None') + info.codebefore.append(self.null % {'name': pname, + 'typename': self.typename, + 'typecode': self.typecode}) + else: + info.varlist.add(self.typename, '*' + pname + ' = NULL') + info.varlist.add('PyObject', '*py_' + pname) + info.codebefore.append(self.check % {'name': pname, + 'typename': self.typename, + 'typecode': self.typecode}) + info.arglist.append(pname) + info.add_parselist('O', ['&py_' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + if ptype[-1] == '*': + info.varlist.add(self.typename, '*ret') + info.codeafter.append(' /* pyg_pointer_new handles NULL checking */\n' + + ' return pyg_pointer_new(' + self.typecode + ', ret);') + else: + info.varlist.add(self.typename, 'ret') + info.codeafter.append(' /* pyg_pointer_new handles NULL checking */\n' + + ' return pyg_pointer_new(' + self.typecode + ', &ret);') + +class AtomArg(IntArg): + dflt = ' if (py_%(name)s) {\n' \ + ' %(name)s = pygdk_atom_from_pyobject(py_%(name)s);\n' \ + ' if (PyErr_Occurred())\n' \ + ' return NULL;\n' \ + ' }\n' + atom = (' %(name)s = pygdk_atom_from_pyobject(py_%(name)s);\n' + ' if (PyErr_Occurred())\n' + ' return NULL;\n') + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pdflt: + info.varlist.add('GdkAtom', pname + ' = ' + pdflt) + info.varlist.add('PyObject', '*py_' + pname + ' = NULL') + info.codebefore.append(self.dflt % {'name': pname}) + else: + info.varlist.add('GdkAtom', pname) + info.varlist.add('PyObject', '*py_' + pname + ' = NULL') + info.codebefore.append(self.atom % {'name': pname}) + info.arglist.append(pname) + info.add_parselist('O', ['&py_' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + info.varlist.add('GdkAtom', 'ret') + info.varlist.add('PyObject *', 'py_ret') + info.varlist.add('gchar *', 'name') + info.codeafter.append(' name = gdk_atom_name(ret);\n' + ' py_ret = PyString_FromString(name);\n' + ' g_free(name);\n' + ' return py_ret;') + +class GTypeArg(ArgType): + gtype = (' if ((%(name)s = pyg_type_from_object(py_%(name)s)) == 0)\n' + ' return NULL;\n') + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + info.varlist.add('GType', pname) + info.varlist.add('PyObject', '*py_' + pname + ' = NULL') + info.codebefore.append(self.gtype % {'name': pname}) + info.arglist.append(pname) + info.add_parselist('O', ['&py_' + pname], [pname]) + def write_return(self, ptype, ownsreturn, info): + info.varlist.add('GType', 'ret') + info.codeafter.append(' return pyg_type_wrapper_new(ret);') + +# simple GError handler. +class GErrorArg(ArgType): + handleerror = (' if (pyg_error_check(&%(name)s))\n' + ' return NULL;\n') + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + info.varlist.add('GError', '*' + pname + ' = NULL') + info.arglist.append('&' + pname) + info.codeafter.append(self.handleerror % { 'name': pname }) + +class GtkTreePathArg(ArgType): + # haven't done support for default args. Is it needed? + normal = (' %(name)s = pygtk_tree_path_from_pyobject(py_%(name)s);\n' + ' if (!%(name)s) {\n' + ' PyErr_SetString(PyExc_TypeError, "could not convert %(name)s to a GtkTreePath");\n' + ' return NULL;\n' + ' }\n') + null = (' if (py_%(name)s != Py_None) {\n' + ' %(name)s = pygtk_tree_path_from_pyobject(py_%(name)s);\n' + ' if (!%(name)s) {\n' + ' PyErr_SetString(PyExc_TypeError, "could not convert %(name)s to a GtkTreePath");\n' + ' return NULL;\n' + ' }\n' + ' }\n') + freepath = (' if (%(name)s)\n' + ' gtk_tree_path_free(%(name)s);\n') + def __init__(self): + pass + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pnull: + info.varlist.add('GtkTreePath', '*' + pname + ' = NULL') + info.varlist.add('PyObject', '*py_' + pname + ' = Py_None') + info.codebefore.append(self.null % {'name': pname}) + info.arglist.append(pname) + info.add_parselist('O', ['&py_' + pname], [pname]) + else: + info.varlist.add('GtkTreePath', '*' + pname) + info.varlist.add('PyObject', '*py_' + pname) + info.codebefore.append(self.normal % {'name': pname}) + info.arglist.append(pname) + info.add_parselist('O', ['&py_' + pname], [pname]) + info.codeafter.append(self.freepath % {'name': pname}) + def write_return(self, ptype, ownsreturn, info): + info.varlist.add('GtkTreePath', '*ret') + if ownsreturn: + info.codeafter.append(' if (ret) {\n' + ' PyObject *py_ret = pygtk_tree_path_to_pyobject(ret);\n' + ' gtk_tree_path_free(ret);\n' + ' return py_ret;\n' + ' }\n' + ' Py_INCREF(Py_None);\n' + ' return Py_None;') + else: + info.codeafter.append(' if (ret) {\n' + ' PyObject *py_ret = pygtk_tree_path_to_pyobject(ret);\n' + ' return py_ret;\n' + ' }\n' + ' Py_INCREF(Py_None);\n' + ' return Py_None;') + +class GdkRectanglePtrArg(ArgType): + normal = (' if (!pygdk_rectangle_from_pyobject(py_%(name)s, &%(name)s))\n' + ' return NULL;\n') + null = (' if (py_%(name)s == Py_None)\n' + ' %(name)s = NULL;\n' + ' else if (pygdk_rectangle_from_pyobject(py_%(name)s, &%(name)s_rect))\n' + ' %(name)s = &%(name)s_rect;\n' + ' else\n' + ' return NULL;\n') + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + if pnull: + info.varlist.add('GdkRectangle', pname + '_rect = { 0, 0, 0, 0 }') + info.varlist.add('GdkRectangle', '*' + pname) + info.varlist.add('PyObject', '*py_' + pname + ' = Py_None') + info.add_parselist('O', ['&py_' + pname], [pname]) + info.arglist.append(pname) + info.codebefore.append(self.null % {'name': pname}) + else: + info.varlist.add('GdkRectangle', pname + ' = { 0, 0, 0, 0 }') + info.varlist.add('PyObject', '*py_' + pname) + info.add_parselist('O', ['&py_' + pname], [pname]) + info.arglist.append('&' + pname) + info.codebefore.append(self.normal % {'name': pname}) + +class GdkRectangleArg(ArgType): + def write_return(self, ptype, ownsreturn, info): + info.varlist.add('GdkRectangle', 'ret') + info.codeafter.append(' return pyg_boxed_new(GDK_TYPE_RECTANGLE, &ret, TRUE, TRUE);') + +class PyObjectArg(ArgType): + def write_param(self, ptype, pname, pdflt, pnull, keeprefcount, info): + info.varlist.add('PyObject', '*' + pname) + info.add_parselist('O', ['&' + pname], [pname]) + info.arglist.append(pname) + def write_return(self, ptype, ownsreturn, info): + info.varlist.add("PyObject", "*ret") + if ownsreturn: + info.codeafter.append(' if (ret) {\n' + ' return ret;\n' + ' }\n' + ' Py_INCREF(Py_None);\n' + ' return Py_None;') + else: + info.codeafter.append(' if (!ret) ret = Py_None;\n' + ' Py_INCREF(ret);\n' + ' return ret;') + +class ArgMatcher: + def __init__(self): + self.argtypes = {} + self.reverse_argtypes = {} + self.reverse_rettypes = {} + + def register(self, ptype, handler): + self.argtypes[ptype] = handler + def register_reverse(self, ptype, handler): + self.reverse_argtypes[ptype] = handler + def register_reverse_ret(self, ptype, handler): + self.reverse_rettypes[ptype] = handler + + def register_enum(self, ptype, typecode): + if typecode is None: + typecode = "G_TYPE_NONE" + self.register(ptype, EnumArg(ptype, typecode)) + def register_flag(self, ptype, typecode): + if typecode is None: + typecode = "G_TYPE_NONE" + self.register(ptype, FlagsArg(ptype, typecode)) + def register_object(self, ptype, parent, typecode): + oa = ObjectArg(ptype, parent, typecode) + self.register(ptype, oa) # in case I forget the * in the .defs + self.register(ptype+'*', oa) + self.register('const-'+ptype+'*', oa) + if ptype == 'GdkPixmap': + # hack to handle GdkBitmap synonym. + self.register('GdkBitmap', oa) + self.register('GdkBitmap*', oa) + def register_miniobject(self, ptype, parent, typecode): + oa = MiniObjectArg(ptype, parent, typecode) + self.register(ptype, oa) # in case I forget the * in the .defs + self.register(ptype+'*', oa) + def register_boxed(self, ptype, typecode): + if self.argtypes.has_key(ptype): return + arg = BoxedArg(ptype, typecode) + self.register(ptype, arg) + self.register(ptype+'*', arg) + self.register('const-'+ptype+'*', arg) + def register_custom_boxed(self, ptype, pytype, getter, new): + arg = CustomBoxedArg(ptype, pytype, getter, new) + self.register(ptype+'*', arg) + self.register('const-'+ptype+'*', arg) + def register_pointer(self, ptype, typecode): + arg = PointerArg(ptype, typecode) + self.register(ptype, arg) + self.register(ptype+'*', arg) + self.register('const-'+ptype+'*', arg) + + def get(self, ptype): + try: + return self.argtypes[ptype] + except KeyError: + if ptype[:8] == 'GdkEvent' and ptype[-1] == '*': + return self.argtypes['GdkEvent*'] + raise + def _get_reverse_common(self, ptype, registry): + props = dict(c_type=ptype) + try: + return registry[ptype], props + except KeyError: + try: + handler = self.argtypes[ptype] + except KeyError: + if ptype.startswith('GdkEvent') and ptype.endswith('*'): + handler = self.argtypes['GdkEvent*'] + else: + raise + if isinstance(handler, ObjectArg): + return registry['GObject*'], props + elif isinstance(handler, EnumArg): + props['typecode'] = handler.typecode + props['enumname'] = handler.enumname + return registry['GEnum'], props + elif isinstance(handler, FlagsArg): + props['typecode'] = handler.typecode + props['flagname'] = handler.flagname + return registry['GFlags'], props + elif isinstance(handler, BoxedArg): + props['typecode'] = handler.typecode + props['typename'] = handler.typename + return registry['GBoxed'], props + else: + raise + def get_reverse(self, ptype): + return self._get_reverse_common(ptype, self.reverse_argtypes) + def get_reverse_ret(self, ptype): + return self._get_reverse_common(ptype, self.reverse_rettypes) + + def object_is_a(self, otype, parent): + if otype == None: return 0 + if otype == parent: return 1 + if not self.argtypes.has_key(otype): return 0 + return self.object_is_a(self.get(otype).parent, parent) + +matcher = ArgMatcher() + +arg = NoneArg() +matcher.register(None, arg) +matcher.register('none', arg) + +arg = StringArg() +matcher.register('char*', arg) +matcher.register('gchar*', arg) +matcher.register('const-char*', arg) +matcher.register('char-const*', arg) +matcher.register('const-gchar*', arg) +matcher.register('gchar-const*', arg) +matcher.register('string', arg) +matcher.register('static_string', arg) + +arg = UCharArg() +matcher.register('unsigned-char*', arg) +matcher.register('const-guchar*', arg) +matcher.register('guchar*', arg) + +arg = CharArg() +matcher.register('char', arg) +matcher.register('gchar', arg) +matcher.register('guchar', arg) + +arg = GUniCharArg() +matcher.register('gunichar', arg) + +arg = IntArg() +matcher.register('int', arg) +matcher.register('gint', arg) +matcher.register('short', arg) +matcher.register('gshort', arg) +matcher.register('gushort', arg) +matcher.register('gsize', SizeArg()) +matcher.register('gssize', SSizeArg()) +matcher.register('guint8', arg) +matcher.register('gint8', arg) +matcher.register('guint16', arg) +matcher.register('gint16', arg) +matcher.register('gint32', arg) +matcher.register('GTime', arg) + +arg = LongArg() +matcher.register('long', arg) +matcher.register('glong', arg) + +arg = UIntArg() +matcher.register('guint', arg) + +arg = BoolArg() +matcher.register('gboolean', arg) + +arg = TimeTArg() +matcher.register('time_t', arg) + +matcher.register('guint32', UInt32Arg()) + +arg = ULongArg() +matcher.register('gulong', arg) + +arg = Int64Arg() +matcher.register('gint64', arg) +matcher.register('long-long', arg) + +arg = UInt64Arg() +matcher.register('guint64', arg) +matcher.register('unsigned-long-long', arg) + +arg = DoubleArg() +matcher.register('double', arg) +matcher.register('gdouble', arg) +matcher.register('float', arg) +matcher.register('gfloat', arg) + +arg = FileArg() +matcher.register('FILE*', arg) + +# enums, flags, objects + +matcher.register('GdkAtom', AtomArg()) + +matcher.register('GType', GTypeArg()) +matcher.register('GtkType', GTypeArg()) + +matcher.register('GError**', GErrorArg()) +matcher.register('GtkTreePath*', GtkTreePathArg()) +matcher.register('GdkRectangle*', GdkRectanglePtrArg()) +matcher.register('GtkAllocation*', GdkRectanglePtrArg()) +matcher.register('GdkRectangle', GdkRectangleArg()) +matcher.register('PyObject*', PyObjectArg()) + +matcher.register('GdkNativeWindow', ULongArg()) + +matcher.register_object('GObject', None, 'G_TYPE_OBJECT') +matcher.register_miniobject('GstMiniObject', None, 'GST_TYPE_MINI_OBJECT') + +del arg diff --git a/farstream/python/codegen/code-coverage.py b/farstream/python/codegen/code-coverage.py new file mode 100755 index 000000000..fd1503473 --- /dev/null +++ b/farstream/python/codegen/code-coverage.py @@ -0,0 +1,42 @@ +from __future__ import generators +import sys, os + +def read_symbols(file, type=None, dynamic=0): + if dynamic: + cmd = 'nm -D %s' % file + else: + cmd = 'nm %s' % file + for line in os.popen(cmd, 'r'): + if line[0] != ' ': # has an address as first bit of line + while line[0] != ' ': + line = line[1:] + while line[0] == ' ': + line = line[1:] + # we should be up to "type symbolname" now + sym_type = line[0] + symbol = line[1:].strip() + + if not type or type == sym_type: + yield symbol + +def main(): + if len(sys.argv) != 3: + sys.stderr.write('usage: coverage-check library.so wrapper.so\n') + sys.exit(1) + library = sys.argv[1] + wrapper = sys.argv[2] + + # first create a dict with all referenced symbols in the wrapper + # should really be a set, but a dict will do ... + wrapper_symbols = {} + for symbol in read_symbols(wrapper, type='U', dynamic=1): + wrapper_symbols[symbol] = 1 + + # now go through the library looking for matches on the defined symbols: + for symbol in read_symbols(library, type='T', dynamic=1): + if symbol[0] == '_': continue + if symbol not in wrapper_symbols: + print symbol + +if __name__ == '__main__': + main() diff --git a/farstream/python/codegen/codegen.py b/farstream/python/codegen/codegen.py new file mode 100644 index 000000000..8f20bf708 --- /dev/null +++ b/farstream/python/codegen/codegen.py @@ -0,0 +1,1572 @@ +import getopt +import keyword +import os +import string +import sys + +import argtypes +import definitions +import defsparser +import override +import reversewrapper + +class Coverage(object): + def __init__(self, name): + self.name = name + self.wrapped = 0 + self.not_wrapped = 0 + + def declare_wrapped(self): + self.wrapped += 1 + + def declare_not_wrapped(self): + self.not_wrapped += 1 + + def printstats(self): + total = self.wrapped + self.not_wrapped + fd = sys.stderr + if total: + fd.write("***INFO*** The coverage of %s is %.2f%% (%i/%i)\n" % + (self.name, + float(self.wrapped*100)/total, + self.wrapped, + total)) + else: + fd.write("***INFO*** There are no declared %s." % self.name) + +functions_coverage = Coverage("global functions") +methods_coverage = Coverage("methods") +vproxies_coverage = Coverage("virtual proxies") +vaccessors_coverage = Coverage("virtual accessors") +iproxies_coverage = Coverage("interface proxies") + +def exc_info(): + #traceback.print_exc() + etype, value, tb = sys.exc_info() + ret = "" + try: + sval = str(value) + if etype == KeyError: + ret = "No ArgType for %s" % (sval,) + else: + ret = sval + finally: + del etype, value, tb + return ret + +def fixname(name): + if keyword.iskeyword(name): + return name + '_' + return name + +class FileOutput: + '''Simple wrapper for file object, that makes writing #line + statements easier.''' # " + def __init__(self, fp, filename=None): + self.fp = fp + self.lineno = 1 + if filename: + self.filename = filename + else: + self.filename = self.fp.name + # handle writing to the file, and keep track of the line number ... + def write(self, str): + self.fp.write(str) + self.lineno = self.lineno + string.count(str, '\n') + def writelines(self, sequence): + for line in sequence: + self.write(line) + def close(self): + self.fp.close() + def flush(self): + self.fp.flush() + + def setline(self, linenum, filename): + '''writes out a #line statement, for use by the C + preprocessor.''' # " + self.write('#line %d "%s"\n' % (linenum, filename)) + def resetline(self): + '''resets line numbering to the original file''' + self.setline(self.lineno + 1, self.filename) + +class Wrapper: + type_tmpl = ( + 'PyTypeObject Py%(typename)s_Type = {\n' + ' PyObject_HEAD_INIT(NULL)\n' + ' 0, /* ob_size */\n' + ' "%(classname)s", /* tp_name */\n' + ' sizeof(%(tp_basicsize)s), /* tp_basicsize */\n' + ' 0, /* tp_itemsize */\n' + ' /* methods */\n' + ' (destructor)%(tp_dealloc)s, /* tp_dealloc */\n' + ' (printfunc)0, /* tp_print */\n' + ' (getattrfunc)%(tp_getattr)s, /* tp_getattr */\n' + ' (setattrfunc)%(tp_setattr)s, /* tp_setattr */\n' + ' (cmpfunc)%(tp_compare)s, /* tp_compare */\n' + ' (reprfunc)%(tp_repr)s, /* tp_repr */\n' + ' (PyNumberMethods*)%(tp_as_number)s, /* tp_as_number */\n' + ' (PySequenceMethods*)%(tp_as_sequence)s, /* tp_as_sequence */\n' + ' (PyMappingMethods*)%(tp_as_mapping)s, /* tp_as_mapping */\n' + ' (hashfunc)%(tp_hash)s, /* tp_hash */\n' + ' (ternaryfunc)%(tp_call)s, /* tp_call */\n' + ' (reprfunc)%(tp_str)s, /* tp_str */\n' + ' (getattrofunc)%(tp_getattro)s, /* tp_getattro */\n' + ' (setattrofunc)%(tp_setattro)s, /* tp_setattro */\n' + ' (PyBufferProcs*)%(tp_as_buffer)s, /* tp_as_buffer */\n' + ' %(tp_flags)s, /* tp_flags */\n' + ' %(tp_doc)s, /* Documentation string */\n' + ' (traverseproc)%(tp_traverse)s, /* tp_traverse */\n' + ' (inquiry)%(tp_clear)s, /* tp_clear */\n' + ' (richcmpfunc)%(tp_richcompare)s, /* tp_richcompare */\n' + ' %(tp_weaklistoffset)s, /* tp_weaklistoffset */\n' + ' (getiterfunc)%(tp_iter)s, /* tp_iter */\n' + ' (iternextfunc)%(tp_iternext)s, /* tp_iternext */\n' + ' (struct PyMethodDef*)%(tp_methods)s, /* tp_methods */\n' + ' (struct PyMemberDef*)0, /* tp_members */\n' + ' (struct PyGetSetDef*)%(tp_getset)s, /* tp_getset */\n' + ' NULL, /* tp_base */\n' + ' NULL, /* tp_dict */\n' + ' (descrgetfunc)%(tp_descr_get)s, /* tp_descr_get */\n' + ' (descrsetfunc)%(tp_descr_set)s, /* tp_descr_set */\n' + ' %(tp_dictoffset)s, /* tp_dictoffset */\n' + ' (initproc)%(tp_init)s, /* tp_init */\n' + ' (allocfunc)%(tp_alloc)s, /* tp_alloc */\n' + ' (newfunc)%(tp_new)s, /* tp_new */\n' + ' (freefunc)%(tp_free)s, /* tp_free */\n' + ' (inquiry)%(tp_is_gc)s /* tp_is_gc */\n' + '};\n\n' + ) + + slots_list = [ + 'tp_getattr', 'tp_setattr', 'tp_getattro', 'tp_setattro', + 'tp_compare', 'tp_repr', + 'tp_as_number', 'tp_as_sequence', 'tp_as_mapping', 'tp_hash', + 'tp_call', 'tp_str', 'tp_as_buffer', 'tp_richcompare', 'tp_iter', + 'tp_iternext', 'tp_descr_get', 'tp_descr_set', 'tp_init', + 'tp_alloc', 'tp_new', 'tp_free', 'tp_is_gc', + 'tp_traverse', 'tp_clear', 'tp_dealloc', 'tp_flags', 'tp_doc' + ] + + getter_tmpl = ( + 'static PyObject *\n' + '%(funcname)s(PyObject *self, void *closure)\n' + '{\n' + '%(varlist)s' + ' ret = %(field)s;\n' + '%(codeafter)s\n' + '}\n\n' + ) + + parse_tmpl = ( + ' if (!PyArg_ParseTupleAndKeywords(args, kwargs,' + '"%(typecodes)s:%(name)s"%(parselist)s))\n' + ' return %(errorreturn)s;\n' + ) + + deprecated_tmpl = ( + ' if (PyErr_Warn(PyExc_DeprecationWarning, ' + '"%(deprecationmsg)s") < 0)\n' + ' return %(errorreturn)s;\n' + ) + + methdef_tmpl = ( + ' { "%(name)s", (PyCFunction)%(cname)s, %(flags)s,\n' + ' %(docstring)s },\n' + ) + + noconstructor = ( + 'static int\n' + 'pygobject_no_constructor(PyObject *self, PyObject *args, ' + 'PyObject *kwargs)\n' + '{\n' + ' gchar buf[512];\n' + '\n' + ' g_snprintf(buf, sizeof(buf), "%s is an abstract widget", ' + 'self->ob_type->tp_name);\n' + ' PyErr_SetString(PyExc_NotImplementedError, buf);\n' + ' return -1;\n' + '}\n\n' + ) + + function_tmpl = ( + 'static PyObject *\n' + '_wrap_%(cname)s(PyObject *self%(extraparams)s)\n' + '{\n' + '%(varlist)s' + '%(parseargs)s' + '%(codebefore)s' + ' %(begin_allow_threads)s\n' + ' %(setreturn)s%(cname)s(%(arglist)s);\n' + ' %(end_allow_threads)s\n' + '%(codeafter)s\n' + '}\n\n' + ) + + virtual_accessor_tmpl = ( + 'static PyObject *\n' + '_wrap_%(cname)s(PyObject *cls%(extraparams)s)\n' + '{\n' + ' gpointer klass;\n' + '%(varlist)s' + '%(parseargs)s' + '%(codebefore)s' + ' klass = g_type_class_ref(pyg_type_from_object(cls));\n' + ' if (%(class_cast_macro)s(klass)->%(virtual)s) {\n' + ' pyg_begin_allow_threads;\n' + ' %(setreturn)s%(class_cast_macro)s(klass)->' + '%(virtual)s(%(arglist)s);\n' + ' pyg_end_allow_threads;\n' + ' } else {\n' + ' PyErr_SetString(PyExc_NotImplementedError, ' + '"virtual method %(name)s not implemented");\n' + ' g_type_class_unref(klass);\n' + ' return NULL;\n' + ' }\n' + ' g_type_class_unref(klass);\n' + '%(codeafter)s\n' + '}\n\n' + ) + + # template for method calls + constructor_tmpl = None + method_tmpl = None + + def __init__(self, parser, objinfo, overrides, fp=FileOutput(sys.stdout)): + self.parser = parser + self.objinfo = objinfo + self.overrides = overrides + self.fp = fp + + def get_lower_name(self): + return string.lower(string.replace(self.objinfo.typecode, + '_TYPE_', '_', 1)) + + def get_field_accessor(self, fieldname): + raise NotImplementedError + + def get_initial_class_substdict(self): return {} + + def get_initial_constructor_substdict(self, constructor): + return { 'name': '%s.__init__' % self.objinfo.c_name, + 'errorreturn': '-1' } + def get_initial_method_substdict(self, method): + substdict = { 'name': '%s.%s' % (self.objinfo.c_name, method.name) } + substdict['begin_allow_threads'] = 'pyg_begin_allow_threads;' + substdict['end_allow_threads'] = 'pyg_end_allow_threads;' + return substdict + + def write_class(self): + if self.overrides.is_type_ignored(self.objinfo.c_name): + return + self.fp.write('\n/* ----------- %s ----------- */\n\n' % + self.objinfo.c_name) + substdict = self.get_initial_class_substdict() + if not substdict.has_key('tp_flags'): + substdict['tp_flags'] = 'Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE' + substdict['typename'] = self.objinfo.c_name + if self.overrides.modulename: + substdict['classname'] = '%s.%s' % (self.overrides.modulename, + self.objinfo.name) + else: + substdict['classname'] = self.objinfo.name + substdict['tp_doc'] = self.objinfo.docstring + + # Maybe this could be done in a nicer way, but I'll leave it as it is + # for now: -- Johan + if not self.overrides.slot_is_overriden('%s.tp_init' % + self.objinfo.c_name): + substdict['tp_init'] = self.write_constructor() + substdict['tp_methods'] = self.write_methods() + substdict['tp_getset'] = self.write_getsets() + + # handle slots ... + for slot in self.slots_list: + + slotname = '%s.%s' % (self.objinfo.c_name, slot) + slotfunc = '_wrap_%s_%s' % (self.get_lower_name(), slot) + if slot[:6] == 'tp_as_': + slotfunc = '&' + slotfunc + if self.overrides.slot_is_overriden(slotname): + data = self.overrides.slot_override(slotname) + self.write_function(slotname, data) + substdict[slot] = slotfunc + else: + if not substdict.has_key(slot): + substdict[slot] = '0' + + self.fp.write(self.type_tmpl % substdict) + + self.write_virtuals() + + def write_function_wrapper(self, function_obj, template, + handle_return=0, is_method=0, kwargs_needed=0, + substdict=None): + '''This function is the guts of all functions that generate + wrappers for functions, methods and constructors.''' + if not substdict: substdict = {} + + info = argtypes.WrapperInfo() + + substdict.setdefault('errorreturn', 'NULL') + + # for methods, we want the leading comma + if is_method: + info.arglist.append('') + + if function_obj.varargs: + raise ValueError, "varargs functions not supported" + + for param in function_obj.params: + if param.pdflt and '|' not in info.parsestr: + info.add_parselist('|', [], []) + handler = argtypes.matcher.get(param.ptype) + handler.write_param(param.ptype, param.pname, param.pdflt, + param.pnull, param.keeprefcount, info) + + substdict['setreturn'] = '' + if handle_return: + if function_obj.ret not in ('none', None): + substdict['setreturn'] = 'ret = ' + handler = argtypes.matcher.get(function_obj.ret) + handler.write_return(function_obj.ret, + function_obj.caller_owns_return, info) + + if function_obj.deprecated != None: + deprecated = self.deprecated_tmpl % { + 'deprecationmsg': function_obj.deprecated, + 'errorreturn': substdict['errorreturn'] } + else: + deprecated = '' + + # if name isn't set, set it to function_obj.name + substdict.setdefault('name', function_obj.name) + + substdict['begin_allow_threads'] = 'pyg_begin_allow_threads;' + substdict['end_allow_threads'] = 'pyg_end_allow_threads;' + + if self.objinfo: + substdict['typename'] = self.objinfo.c_name + substdict.setdefault('cname', function_obj.c_name) + substdict['varlist'] = info.get_varlist() + substdict['typecodes'] = info.parsestr + substdict['parselist'] = info.get_parselist() + substdict['arglist'] = info.get_arglist() + substdict['codebefore'] = deprecated + ( + string.replace(info.get_codebefore(), + 'return NULL', 'return ' + substdict['errorreturn']) + ) + substdict['codeafter'] = ( + string.replace(info.get_codeafter(), + 'return NULL', + 'return ' + substdict['errorreturn'])) + + if info.parsestr or kwargs_needed: + substdict['parseargs'] = self.parse_tmpl % substdict + substdict['extraparams'] = ', PyObject *args, PyObject *kwargs' + flags = 'METH_VARARGS|METH_KEYWORDS' + + # prepend the keyword list to the variable list + substdict['varlist'] = info.get_kwlist() + substdict['varlist'] + else: + substdict['parseargs'] = '' + substdict['extraparams'] = '' + flags = 'METH_NOARGS' + + return template % substdict, flags + + def write_constructor(self): + initfunc = '0' + constructor = self.parser.find_constructor(self.objinfo,self.overrides) + if not constructor: + return self.write_default_constructor() + + funcname = constructor.c_name + try: + if self.overrides.is_overriden(funcname): + data = self.overrides.override(funcname) + self.write_function(funcname, data) + self.objinfo.has_new_constructor_api = ( + self.objinfo.typecode in + self.overrides.newstyle_constructors) + else: + # ok, a hack to determine if we should use + # new-style constructores :P + property_based = getattr(self, + 'write_property_based_constructor', + None) + if property_based: + if (len(constructor.params) == 0 or + isinstance(constructor.params[0], + definitions.Property)): + # write_property_based_constructor is only + # implemented in GObjectWrapper + return self.write_property_based_constructor( + constructor) + else: + sys.stderr.write( + "Warning: generating old-style constructor for:" + + constructor.c_name + '\n') + + # write constructor from template ... + code = self.write_function_wrapper(constructor, + self.constructor_tmpl, + handle_return=0, is_method=0, kwargs_needed=1, + substdict=self.get_initial_constructor_substdict( + constructor))[0] + self.fp.write(code) + initfunc = '_wrap_' + funcname + except: + sys.stderr.write('Could not write constructor for %s: %s\n' + % (self.objinfo.c_name, exc_info())) + + initfunc = self.write_noconstructor() + return initfunc + + def write_noconstructor(self): + # this is a hack ... + if not hasattr(self.overrides, 'no_constructor_written'): + self.fp.write(self.noconstructor) + self.overrides.no_constructor_written = 1 + initfunc = 'pygobject_no_constructor' + return initfunc + + def write_default_constructor(self): + return self.write_noconstructor() + + def get_methflags(self, funcname): + if self.overrides.wants_kwargs(funcname): + flags = 'METH_VARARGS|METH_KEYWORDS' + elif self.overrides.wants_noargs(funcname): + flags = 'METH_NOARGS' + elif self.overrides.wants_onearg(funcname): + flags = 'METH_O' + else: + flags = 'METH_VARARGS' + if self.overrides.is_staticmethod(funcname): + flags += '|METH_STATIC' + elif self.overrides.is_classmethod(funcname): + flags += '|METH_CLASS' + return flags + + def write_function(self, funcname, data): + lineno, filename = self.overrides.getstartline(funcname) + self.fp.setline(lineno, filename) + self.fp.write(data) + self.fp.resetline() + self.fp.write('\n\n') + + def _get_class_virtual_substdict(self, meth, cname, parent): + substdict = self.get_initial_method_substdict(meth) + substdict['virtual'] = substdict['name'].split('.')[1] + substdict['cname'] = cname + substdict['class_cast_macro'] = parent.typecode.replace( + '_TYPE_', '_', 1) + "_CLASS" + substdict['typecode'] = self.objinfo.typecode + substdict['cast'] = string.replace(parent.typecode, '_TYPE_', '_', 1) + return substdict + + def write_methods(self): + methods = [] + klass = self.objinfo.c_name + # First, get methods from the defs files + for meth in self.parser.find_methods(self.objinfo): + method_name = meth.c_name + if self.overrides.is_ignored(method_name): + continue + try: + if self.overrides.is_overriden(method_name): + if not self.overrides.is_already_included(method_name): + data = self.overrides.override(method_name) + self.write_function(method_name, data) + + methflags = self.get_methflags(method_name) + else: + # write constructor from template ... + code, methflags = self.write_function_wrapper(meth, + self.method_tmpl, handle_return=1, is_method=1, + substdict=self.get_initial_method_substdict(meth)) + self.fp.write(code) + methods.append(self.methdef_tmpl % + { 'name': fixname(meth.name), + 'cname': '_wrap_' + method_name, + 'flags': methflags, + 'docstring': meth.docstring }) + methods_coverage.declare_wrapped() + except: + methods_coverage.declare_not_wrapped() + sys.stderr.write('Could not write method %s.%s: %s\n' + % (klass, meth.name, exc_info())) + + # Now try to see if there are any defined in the override + for method_name in self.overrides.get_defines_for(klass): + c_name = override.class2cname(klass, method_name) + if self.overrides.is_already_included(method_name): + continue + + try: + data = self.overrides.define(klass, method_name) + self.write_function(method_name, data) + methflags = self.get_methflags(method_name) + + methods.append(self.methdef_tmpl % + { 'name': method_name, + 'cname': '_wrap_' + c_name, + 'flags': methflags, + 'docstring': meth.docstring }) + methods_coverage.declare_wrapped() + except: + methods_coverage.declare_not_wrapped() + sys.stderr.write('Could not write method %s.%s: %s\n' + % (klass, meth.name, exc_info())) + + # Add GObject virtual method accessors, for chaining to parent + # virtuals from subclasses + methods += self.write_virtual_accessors() + + if methods: + methoddefs = '_Py%s_methods' % self.objinfo.c_name + # write the PyMethodDef structure + methods.append(' { NULL, NULL, 0, NULL }\n') + self.fp.write('static const PyMethodDef %s[] = {\n' % methoddefs) + self.fp.write(string.join(methods, '')) + self.fp.write('};\n\n') + else: + methoddefs = 'NULL' + return methoddefs + + def write_virtual_accessors(self): + klass = self.objinfo.c_name + methods = [] + for meth in self.parser.find_virtuals(self.objinfo): + method_name = self.objinfo.c_name + "__do_" + meth.name + if self.overrides.is_ignored(method_name): + continue + try: + if self.overrides.is_overriden(method_name): + if not self.overrides.is_already_included(method_name): + data = self.overrides.override(method_name) + self.write_function(method_name, data) + methflags = self.get_methflags(method_name) + else: + # temporarily add a 'self' parameter as first argument + meth.params.insert(0, definitions.Parameter( + ptype=(self.objinfo.c_name + '*'), + pname='self', pdflt=None, pnull=None)) + try: + # write method from template ... + code, methflags = self.write_function_wrapper( + meth, self.virtual_accessor_tmpl, + handle_return=True, is_method=False, + substdict=self._get_class_virtual_substdict( + meth, method_name, self.objinfo)) + self.fp.write(code) + finally: + del meth.params[0] + methods.append(self.methdef_tmpl % + { 'name': "do_" + fixname(meth.name), + 'cname': '_wrap_' + method_name, + 'flags': methflags + '|METH_CLASS', + 'docstring': 'NULL'}) + vaccessors_coverage.declare_wrapped() + except: + vaccessors_coverage.declare_not_wrapped() + sys.stderr.write( + 'Could not write virtual accessor method %s.%s: %s\n' + % (klass, meth.name, exc_info())) + return methods + + def write_virtuals(self): + ''' + Write _wrap_FooBar__proxy_do_zbr() reverse wrapers for + GObject virtuals + ''' + klass = self.objinfo.c_name + virtuals = [] + for meth in self.parser.find_virtuals(self.objinfo): + method_name = self.objinfo.c_name + "__proxy_do_" + meth.name + if self.overrides.is_ignored(method_name): + continue + try: + if self.overrides.is_overriden(method_name): + if not self.overrides.is_already_included(method_name): + data = self.overrides.override(method_name) + self.write_function(method_name, data) + else: + # write virtual proxy ... + ret, props = argtypes.matcher.get_reverse_ret(meth.ret) + wrapper = reversewrapper.ReverseWrapper( + '_wrap_' + method_name, is_static=True) + wrapper.set_return_type(ret(wrapper, **props)) + wrapper.add_parameter(reversewrapper.PyGObjectMethodParam( + wrapper, "self", method_name="do_" + meth.name, + c_type=(klass + ' *'))) + for param in meth.params: + handler, props = argtypes.matcher.get_reverse( + param.ptype) + props["direction"] = param.pdir + wrapper.add_parameter(handler(wrapper, + param.pname, **props)) + buf = reversewrapper.MemoryCodeSink() + wrapper.generate(buf) + self.fp.write(buf.flush()) + virtuals.append((fixname(meth.name), '_wrap_' + method_name)) + vproxies_coverage.declare_wrapped() + except (KeyError, ValueError): + vproxies_coverage.declare_not_wrapped() + virtuals.append((fixname(meth.name), None)) + sys.stderr.write('Could not write virtual proxy %s.%s: %s\n' + % (klass, meth.name, exc_info())) + if virtuals: + # Write a 'pygtk class init' function for this object, + # except when the object type is explicitly ignored (like + # GtkPlug and GtkSocket on win32). + if self.overrides.is_ignored(self.objinfo.typecode): + return + class_cast_macro = self.objinfo.typecode.replace( + '_TYPE_', '_', 1) + "_CLASS" + cast_macro = self.objinfo.typecode.replace('_TYPE_', '_', 1) + funcname = "__%s_class_init" % klass + self.objinfo.class_init_func = funcname + have_implemented_virtuals = not not [True + for name, cname in virtuals + if cname is not None] + self.fp.write( + ('\nstatic int\n' + '%(funcname)s(gpointer gclass, PyTypeObject *pyclass)\n' + '{\n') % vars()) + + if have_implemented_virtuals: + self.fp.write(' PyObject *o;\n') + self.fp.write( + ' %(klass)sClass *klass = ' + '%(class_cast_macro)s(gclass);\n' + ' PyObject *gsignals = ' + 'PyDict_GetItemString(pyclass->tp_dict, "__gsignals__");\n' + % vars()) + + for name, cname in virtuals: + do_name = 'do_' + name + if cname is None: + self.fp.write('\n /* overriding %(do_name)s ' + 'is currently not supported */\n' % vars()) + else: + self.fp.write(''' + o = PyObject_GetAttrString((PyObject *) pyclass, "%(do_name)s"); + if (o == NULL) + PyErr_Clear(); + else { + if (!PyObject_TypeCheck(o, &PyCFunction_Type) + && !(gsignals && PyDict_GetItemString(gsignals, "%(name)s"))) + klass->%(name)s = %(cname)s; + Py_DECREF(o); + } +''' % vars()) + self.fp.write(' return 0;\n}\n') + + def write_getsets(self): + lower_name = self.get_lower_name() + getsets_name = lower_name + '_getsets' + getterprefix = '_wrap_' + lower_name + '__get_' + setterprefix = '_wrap_' + lower_name + '__set_' + + # no overrides for the whole function. If no fields, + # don't write a func + if not self.objinfo.fields: + return '0' + getsets = [] + for ftype, cfname in self.objinfo.fields: + fname = cfname.replace('.', '_') + gettername = '0' + settername = '0' + attrname = self.objinfo.c_name + '.' + fname + if self.overrides.attr_is_overriden(attrname): + code = self.overrides.attr_override(attrname) + self.write_function(attrname, code) + if string.find(code, getterprefix + fname) >= 0: + gettername = getterprefix + fname + if string.find(code, setterprefix + fname) >= 0: + settername = setterprefix + fname + if gettername == '0': + try: + funcname = getterprefix + fname + info = argtypes.WrapperInfo() + handler = argtypes.matcher.get(ftype) + # for attributes, we don't own the "return value" + handler.write_return(ftype, 0, info) + self.fp.write(self.getter_tmpl % + { 'funcname': funcname, + 'varlist': info.varlist, + 'field': self.get_field_accessor(cfname), + 'codeafter': info.get_codeafter() }) + gettername = funcname + except: + sys.stderr.write( + "Could not write getter for %s.%s: %s\n" + % (self.objinfo.c_name, fname, exc_info())) + if gettername != '0' or settername != '0': + getsets.append(' { "%s", (getter)%s, (setter)%s },\n' % + (fixname(fname), gettername, settername)) + + if not getsets: + return '0' + self.fp.write('static const PyGetSetDef %s[] = {\n' % getsets_name) + for getset in getsets: + self.fp.write(getset) + self.fp.write(' { NULL, (getter)0, (setter)0 },\n') + self.fp.write('};\n\n') + + return getsets_name + + def write_functions(self, prefix): + self.fp.write('\n/* ----------- functions ----------- */\n\n') + functions = [] + + # First, get methods from the defs files + for func in self.parser.find_functions(): + funcname = func.c_name + if self.overrides.is_ignored(funcname): + continue + try: + if self.overrides.is_overriden(funcname): + data = self.overrides.override(funcname) + self.write_function(funcname, data) + + methflags = self.get_methflags(funcname) + else: + # write constructor from template ... + code, methflags = self.write_function_wrapper(func, + self.function_tmpl, handle_return=1, is_method=0) + self.fp.write(code) + functions.append(self.methdef_tmpl % + { 'name': func.name, + 'cname': '_wrap_' + funcname, + 'flags': methflags, + 'docstring': func.docstring }) + functions_coverage.declare_wrapped() + except: + functions_coverage.declare_not_wrapped() + sys.stderr.write('Could not write function %s: %s\n' + % (func.name, exc_info())) + + # Now try to see if there are any defined in the override + for funcname in self.overrides.get_functions(): + try: + data = self.overrides.function(funcname) + self.write_function(funcname, data) + methflags = self.get_methflags(funcname) + functions.append(self.methdef_tmpl % + { 'name': funcname, + 'cname': '_wrap_' + funcname, + 'flags': methflags, + 'docstring': 'NULL'}) + functions_coverage.declare_wrapped() + except: + functions_coverage.declare_not_wrapped() + sys.stderr.write('Could not write function %s: %s\n' + % (funcname, exc_info())) + + # write the PyMethodDef structure + functions.append(' { NULL, NULL, 0, NULL }\n') + + self.fp.write('const PyMethodDef ' + prefix + '_functions[] = {\n') + self.fp.write(string.join(functions, '')) + self.fp.write('};\n\n') + +class GObjectWrapper(Wrapper): + constructor_tmpl = ( + 'static int\n' + '_wrap_%(cname)s(PyGObject *self%(extraparams)s)\n' + '{\n' + '%(varlist)s' + '%(parseargs)s' + '%(codebefore)s' + ' self->obj = (GObject *)%(cname)s(%(arglist)s);\n' + '%(codeafter)s\n' + ' if (!self->obj) {\n' + ' PyErr_SetString(PyExc_RuntimeError, ' + '"could not create %(typename)s object");\n' + ' return -1;\n' + ' }\n' + '%(aftercreate)s' + ' pygobject_register_wrapper((PyObject *)self);\n' + ' return 0;\n' + '}\n\n' + ) + + method_tmpl = ( + 'static PyObject *\n' + '_wrap_%(cname)s(PyGObject *self%(extraparams)s)\n' + '{\n' + '%(varlist)s' + '%(parseargs)s' + '%(codebefore)s' + ' %(begin_allow_threads)s\n' + ' %(setreturn)s%(cname)s(%(cast)s(self->obj)%(arglist)s);\n' + ' %(end_allow_threads)s\n' + '%(codeafter)s\n' + '}\n\n' + ) + def __init__(self, parser, objinfo, overrides, fp=FileOutput(sys.stdout)): + Wrapper.__init__(self, parser, objinfo, overrides, fp) + if self.objinfo: + self.castmacro = string.replace(self.objinfo.typecode, + '_TYPE_', '_', 1) + + def get_initial_class_substdict(self): + return { 'tp_basicsize' : 'PyGObject', + 'tp_weaklistoffset' : 'offsetof(PyGObject, weakreflist)', + 'tp_dictoffset' : 'offsetof(PyGObject, inst_dict)' } + + def get_field_accessor(self, fieldname): + castmacro = string.replace(self.objinfo.typecode, '_TYPE_', '_', 1) + return '%s(pygobject_get(self))->%s' % (castmacro, fieldname) + + def get_initial_constructor_substdict(self, constructor): + substdict = Wrapper.get_initial_constructor_substdict(self, + constructor) + if not constructor.caller_owns_return: + substdict['aftercreate'] = " g_object_ref(self->obj);\n" + else: + substdict['aftercreate'] = '' + return substdict + + def get_initial_method_substdict(self, method): + substdict = Wrapper.get_initial_method_substdict(self, method) + substdict['cast'] = string.replace(self.objinfo.typecode, + '_TYPE_', '_', 1) + return substdict + + def write_default_constructor(self): + try: + parent = self.parser.find_object(self.objinfo.parent) + except ValueError: + parent = None + if parent is not None: + ## just like the constructor is inheritted, we should + # inherit the new API compatibility flag + self.objinfo.has_new_constructor_api = ( + parent.has_new_constructor_api) + elif self.objinfo.parent == 'GObject': + self.objinfo.has_new_constructor_api = True + return '0' + + def write_property_based_constructor(self, constructor): + self.objinfo.has_new_constructor_api = True + out = self.fp + print >> out, "static int" + print >> out, '_wrap_%s(PyGObject *self, PyObject *args,' \ + ' PyObject *kwargs)\n{' % constructor.c_name + if constructor.params: + s = " GType obj_type = pyg_type_from_object((PyObject *) self);" + print >> out, s + + def py_str_list_to_c(arg): + if arg: + return "{" + ", ".join( + map(lambda s: '"' + s + '"', arg)) + ", NULL }" + else: + return "{ NULL }" + + classname = '%s.%s' % (self.overrides.modulename, + self.objinfo.name) + + if constructor.params: + mandatory_arguments = [param for param in constructor.params + if not param.optional] + optional_arguments = [param for param in constructor.params + if param.optional] + arg_names = py_str_list_to_c( + [param.argname + for param in mandatory_arguments + optional_arguments]) + + prop_names = py_str_list_to_c( + [param.pname + for param in mandatory_arguments + optional_arguments]) + + print >> out, " GParameter params[%i];" % \ + len(constructor.params) + print >> out, " PyObject *parsed_args[%i] = {NULL, };" % \ + len(constructor.params) + print >> out, " char *arg_names[] = %s;" % arg_names + print >> out, " char *prop_names[] = %s;" % prop_names + print >> out, " guint nparams, i;" + print >> out + if constructor.deprecated is not None: + out.write( + ' if (PyErr_Warn(PyExc_DeprecationWarning, ' + '"%s") < 0)\n' % + constructor.deprecated) + print >> out, ' return -1;' + print >> out + out.write(" if (!PyArg_ParseTupleAndKeywords(args, kwargs, ") + template = '"' + if mandatory_arguments: + template += "O"*len(mandatory_arguments) + if optional_arguments: + template += "|" + "O"*len(optional_arguments) + template += ':%s.__init__"' % classname + print >> out, template, ", arg_names", + for i in range(len(constructor.params)): + print >> out, ", &parsed_args[%i]" % i, + + out.write( + "))\n" + " return -1;\n" + "\n" + " memset(params, 0, sizeof(GParameter)*%i);\n" + " if (!pyg_parse_constructor_args(obj_type, arg_names,\n" + " prop_names, params, \n" + " &nparams, parsed_args))\n" + " return -1;\n" + " pygobject_constructv(self, nparams, params);\n" + " for (i = 0; i < nparams; ++i)\n" + " g_value_unset(¶ms[i].value);\n" + % len(constructor.params)) + else: + out.write( + " static char* kwlist[] = { NULL };\n" + "\n") + + if constructor.deprecated is not None: + out.write( + ' if (PyErr_Warn(PyExc_DeprecationWarning, "%s") < 0)\n' + ' return -1;\n' + '\n' % constructor.deprecated) + + out.write( + ' if (!PyArg_ParseTupleAndKeywords(args, kwargs,\n' + ' ":%s.__init__",\n' + ' kwlist))\n' + ' return -1;\n' + '\n' + ' pygobject_constructv(self, 0, NULL);\n' % classname) + out.write( + ' if (!self->obj) {\n' + ' PyErr_SetString(\n' + ' PyExc_RuntimeError, \n' + ' "could not create %s object");\n' + ' return -1;\n' + ' }\n' % classname) + + if not constructor.caller_owns_return: + print >> out, " g_object_ref(self->obj);\n" + + out.write( + ' return 0;\n' + '}\n\n') + + return "_wrap_%s" % constructor.c_name + + +## TODO : Add GstMiniObjectWrapper(Wrapper) +class GstMiniObjectWrapper(Wrapper): + constructor_tmpl = ( + 'static int\n' + '_wrap_%(cname)s(PyGstMiniObject *self%(extraparams)s)\n' + '{\n' + '%(varlist)s' + '%(parseargs)s' + '%(codebefore)s' + ' self->obj = (GstMiniObject *)%(cname)s(%(arglist)s);\n' + '%(codeafter)s\n' + ' if (!self->obj) {\n' + ' PyErr_SetString(PyExc_RuntimeError, ' + '"could not create %(typename)s miniobject");\n' + ' return -1;\n' + ' }\n' + '%(aftercreate)s' + ' pygstminiobject_register_wrapper((PyObject *)self);\n' + ' return 0;\n' + '}\n\n' + ) + method_tmpl = ( + 'static PyObject *\n' + '_wrap_%(cname)s(PyGstMiniObject *self%(extraparams)s)\n' + '{\n' + '%(varlist)s' + '%(parseargs)s' + '%(codebefore)s' + ' %(begin_allow_threads)s\n' + ' %(setreturn)s%(cname)s(%(cast)s(self->obj)%(arglist)s);\n' + ' %(end_allow_threads)s\n' + '%(codeafter)s\n' + '}\n\n' + ) + def __init__(self, parser, objinfo, overrides, fp=FileOutput(sys.stdout)): + Wrapper.__init__(self, parser, objinfo, overrides, fp) + if self.objinfo: + self.castmacro = string.replace(self.objinfo.typecode, + '_TYPE_', '_', 1) + + def get_initial_class_substdict(self): + return { 'tp_basicsize' : 'PyGstMiniObject', + 'tp_weaklistoffset' : 'offsetof(PyGstMiniObject, weakreflist)', + 'tp_dictoffset' : 'offsetof(PyGstMiniObject, inst_dict)' } + + def get_field_accessor(self, fieldname): + castmacro = string.replace(self.objinfo.typecode, '_TYPE_', '_', 1) + return '%s(pygstminiobject_get(self))->%s' % (castmacro, fieldname) + + def get_initial_constructor_substdict(self, constructor): + substdict = Wrapper.get_initial_constructor_substdict(self, + constructor) + if not constructor.caller_owns_return: + substdict['aftercreate'] = " gst_mini_object_ref(self->obj);\n" + else: + substdict['aftercreate'] = '' + return substdict + + def get_initial_method_substdict(self, method): + substdict = Wrapper.get_initial_method_substdict(self, method) + substdict['cast'] = string.replace(self.objinfo.typecode, + '_TYPE_', '_', 1) + return substdict + + + +class GInterfaceWrapper(GObjectWrapper): + virtual_accessor_tmpl = ( + 'static PyObject *\n' + '_wrap_%(cname)s(PyObject *cls%(extraparams)s)\n' + '{\n' + ' %(vtable)s *iface;\n' + '%(varlist)s' + '%(parseargs)s' + '%(codebefore)s' + ' iface = g_type_interface_peek(' + 'g_type_class_peek(pyg_type_from_object(cls)), %(typecode)s);\n' + ' if (iface->%(virtual)s)\n' + ' %(setreturn)siface->%(virtual)s(%(arglist)s);\n' + ' else {\n' + ' PyErr_SetString(PyExc_NotImplementedError, ' + '"interface method %(name)s not implemented");\n' + ' return NULL;\n' + ' }\n' + '%(codeafter)s\n' + '}\n\n' + ) + + def get_initial_class_substdict(self): + return { 'tp_basicsize' : 'PyObject', + 'tp_weaklistoffset' : '0', + 'tp_dictoffset' : '0'} + + def write_constructor(self): + # interfaces have no constructors ... + return '0' + def write_getsets(self): + # interfaces have no fields ... + return '0' + + def _get_class_virtual_substdict(self, meth, cname, parent): + substdict = self.get_initial_method_substdict(meth) + substdict['virtual'] = substdict['name'].split('.')[1] + substdict['cname'] = cname + substdict['typecode'] = self.objinfo.typecode + substdict['vtable'] = self.objinfo.vtable + return substdict + + def write_virtuals(self): + ## Now write reverse method wrappers, which let python code + ## implement interface methods. + # First, get methods from the defs files + klass = self.objinfo.c_name + proxies = [] + for meth in self.parser.find_virtuals(self.objinfo): + method_name = self.objinfo.c_name + "__proxy_do_" + meth.name + if self.overrides.is_ignored(method_name): + continue + try: + if self.overrides.is_overriden(method_name): + if not self.overrides.is_already_included(method_name): + data = self.overrides.override(method_name) + self.write_function(method_name, data) + else: + # write proxy ... + ret, props = argtypes.matcher.get_reverse_ret(meth.ret) + wrapper = reversewrapper.ReverseWrapper( + '_wrap_' + method_name, is_static=True) + wrapper.set_return_type(ret(wrapper, **props)) + wrapper.add_parameter(reversewrapper.PyGObjectMethodParam( + wrapper, "self", method_name="do_" + meth.name, + c_type=(klass + ' *'))) + for param in meth.params: + handler, props = argtypes.matcher.get_reverse( + param.ptype) + props["direction"] = param.pdir + wrapper.add_parameter( + handler(wrapper, param.pname, **props)) + buf = reversewrapper.MemoryCodeSink() + wrapper.generate(buf) + self.fp.write(buf.flush()) + proxies.append((fixname(meth.name), '_wrap_' + method_name)) + iproxies_coverage.declare_wrapped() + except (KeyError, ValueError): + iproxies_coverage.declare_not_wrapped() + proxies.append((fixname(meth.name), None)) + sys.stderr.write('Could not write interface proxy %s.%s: %s\n' + % (klass, meth.name, exc_info())) + + if not proxies: + return + + # Make sure we have at least one proxy function + if not [cname for name,cname in proxies if not cname is None]: + return + + ## Write an interface init function for this object + funcname = "__%s__interface_init" % klass + vtable = self.objinfo.vtable + self.fp.write( + '\nstatic void\n' + '%(funcname)s(%(vtable)s *iface, PyTypeObject *pytype)\n' + '{\n' + ' %(vtable)s *parent_iface = ' + 'g_type_interface_peek_parent(iface);\n' + ' PyObject *py_method;\n' + '\n' + % vars()) + + for name, cname in proxies: + do_name = 'do_' + name + if cname is None: + continue + + self.fp.write(( + ' py_method = pytype? PyObject_GetAttrString(' + '(PyObject *) pytype, "%(do_name)s") : NULL;\n' + ' if (py_method && !PyObject_TypeCheck(py_method, ' + '&PyCFunction_Type)) {\n' + ' iface->%(name)s = %(cname)s;\n' + ' } else {\n' + ' PyErr_Clear();\n' + ' if (parent_iface) {\n' + ' iface->%(name)s = parent_iface->%(name)s;\n' + ' }\n' + ' Py_XDECREF(py_method);\n' + ' }\n' + ) % vars()) + self.fp.write('}\n\n') + interface_info = "__%s__iinfo" % klass + self.fp.write(''' +static const GInterfaceInfo %s = { + (GInterfaceInitFunc) %s, + NULL, + NULL +}; +''' % (interface_info, funcname)) + self.objinfo.interface_info = interface_info + +class GBoxedWrapper(Wrapper): + constructor_tmpl = ( + 'static int\n' + '_wrap_%(cname)s(PyGBoxed *self%(extraparams)s)\n' + '{\n' \ + '%(varlist)s' + '%(parseargs)s' + '%(codebefore)s' + ' self->gtype = %(typecode)s;\n' + ' self->free_on_dealloc = FALSE;\n' + ' self->boxed = %(cname)s(%(arglist)s);\n' + '%(codeafter)s\n' + ' if (!self->boxed) {\n' + ' PyErr_SetString(PyExc_RuntimeError, ' + '"could not create %(typename)s object");\n' + ' return -1;\n' + ' }\n' + ' self->free_on_dealloc = TRUE;\n' + ' return 0;\n' + '}\n\n' + ) + + method_tmpl = ( + 'static PyObject *\n' + '_wrap_%(cname)s(PyObject *self%(extraparams)s)\n' + '{\n' + '%(varlist)s' + '%(parseargs)s' + '%(codebefore)s' + ' %(begin_allow_threads)s\n' + ' %(setreturn)s%(cname)s(pyg_boxed_get(self, ' + '%(typename)s)%(arglist)s);\n' + ' %(end_allow_threads)s\n' + '%(codeafter)s\n' + '}\n\n' + ) + + def get_initial_class_substdict(self): + return { 'tp_basicsize' : 'PyGBoxed', + 'tp_weaklistoffset' : '0', + 'tp_dictoffset' : '0' } + + def get_field_accessor(self, fieldname): + return 'pyg_boxed_get(self, %s)->%s' % (self.objinfo.c_name, fieldname) + + def get_initial_constructor_substdict(self, constructor): + substdict = Wrapper.get_initial_constructor_substdict( + self, constructor) + substdict['typecode'] = self.objinfo.typecode + return substdict + +class GPointerWrapper(GBoxedWrapper): + constructor_tmpl = ( + 'static int\n' + '_wrap_%(cname)s(PyGPointer *self%(extraparams)s)\n' + '{\n' + '%(varlist)s' + '%(parseargs)s' + '%(codebefore)s' + ' self->gtype = %(typecode)s;\n' + ' self->pointer = %(cname)s(%(arglist)s);\n' + '%(codeafter)s\n' + ' if (!self->pointer) {\n' + ' PyErr_SetString(PyExc_RuntimeError, ' + '"could not create %(typename)s object");\n' + ' return -1;\n' + ' }\n' + ' return 0;\n' + '}\n\n' + ) + + method_tmpl = ( + 'static PyObject *\n' + '_wrap_%(cname)s(PyObject *self%(extraparams)s)\n' + '{\n' + '%(varlist)s' + '%(parseargs)s' + '%(codebefore)s' + ' %(setreturn)s%(cname)s(pyg_pointer_get(self, ' + '%(typename)s)%(arglist)s);\n' + '%(codeafter)s\n' + '}\n\n' + ) + + def get_initial_class_substdict(self): + return { 'tp_basicsize' : 'PyGPointer', + 'tp_weaklistoffset' : '0', + 'tp_dictoffset' : '0' } + + def get_field_accessor(self, fieldname): + return 'pyg_pointer_get(self, %s)->%s' % (self.objinfo.c_name, + fieldname) + + def get_initial_constructor_substdict(self, constructor): + substdict = Wrapper.get_initial_constructor_substdict( + self, constructor) + substdict['typecode'] = self.objinfo.typecode + return substdict + +def write_headers(data, fp): + fp.write('/* -- THIS FILE IS GENERATED - DO NOT EDIT */') + fp.write('/* -*- Mode: C; c-basic-offset: 4 -*- */\n\n') + fp.write('#include <Python.h>\n\n\n') + fp.write(data) + fp.resetline() + fp.write('\n\n') + +def write_body(data, fp): + fp.write(data) + fp.resetline() + fp.write('\n\n') + +def write_imports(overrides, fp): + fp.write('/* ---------- types from other modules ---------- */\n') + for module, pyname, cname in overrides.get_imports(): + fp.write('static PyTypeObject *_%s;\n' % cname) + fp.write('#define %s (*_%s)\n' % (cname, cname)) + fp.write('\n\n') + +def write_type_declarations(parser, fp): + fp.write('/* ---------- forward type declarations ---------- */\n') + for obj in parser.boxes: + fp.write('PyTypeObject Py' + obj.c_name + '_Type;\n') + for obj in parser.objects: + fp.write('PyTypeObject Py' + obj.c_name + '_Type;\n') + for obj in parser.miniobjects: + fp.write('PyTypeObject Py' + obj.c_name + '_Type;\n') + for interface in parser.interfaces: + fp.write('PyTypeObject Py' + interface.c_name + '_Type;\n') + fp.write('\n') + + +def sort_parent_children(objects): + objects = list(objects) + modified = True + while modified: + modified = False + parent_index = None + child_index = None + for i, obj in enumerate(objects): + if obj.parent == 'GObject': + continue + if obj.parent not in [info.c_name for info in objects[:i]]: + for j, info in enumerate(objects[i+1:]): + if info.c_name == obj.parent: + parent_index = i + 1 + j + child_index = i + break + else: + continue + break + if child_index is not None and parent_index is not None: + if child_index != parent_index: + objects.insert(child_index, objects.pop(parent_index)) + modified = True + return objects + +def write_classes(parser, overrides, fp): + ## Sort the objects, so that we generate code for the parent types + ## before their children. + objects = sort_parent_children(parser.objects) + for klass, items in ((GBoxedWrapper, parser.boxes), + (GPointerWrapper, parser.pointers), + (GObjectWrapper, objects), + (GstMiniObjectWrapper, parser.miniobjects), + (GInterfaceWrapper, parser.interfaces)): + for item in items: + instance = klass(parser, item, overrides, fp) + instance.write_class() + fp.write('\n') + +def write_enums(parser, overrides, prefix, fp=sys.stdout): + if not parser.enums: + return + fp.write('\n/* ----------- enums and flags ----------- */\n\n') + fp.write( + 'void\n' + prefix + + '_add_constants(PyObject *module, const gchar *strip_prefix)\n{\n') + + for enum in parser.enums: + if overrides.is_type_ignored(enum.c_name): + continue + if enum.typecode is None: + for nick, value in enum.values: + fp.write( + ' PyModule_AddIntConstant(module, ' + '(char *) pyg_constant_strip_prefix("%s", strip_prefix), %s);\n' + % (value, value)) + else: + if enum.deftype == 'enum': + fp.write(' pyg_enum_add(module, "%s", strip_prefix, %s);\n' + % (enum.name, enum.typecode)) + else: + fp.write(' pyg_flags_add(module, "%s", strip_prefix, %s);\n' + % (enum.name, enum.typecode)) + + fp.write('\n') + fp.write(' if (PyErr_Occurred())\n') + fp.write(' PyErr_Print();\n') + fp.write('}\n\n') + +def write_extension_init(overrides, prefix, fp): + fp.write('/* initialise stuff extension classes */\n') + fp.write('void\n' + prefix + '_register_classes(PyObject *d)\n{\n') + imports = overrides.get_imports()[:] + if imports: + bymod = {} + for module, pyname, cname in imports: + bymod.setdefault(module, []).append((pyname, cname)) + fp.write(' PyObject *module;\n\n') + for module in bymod: + fp.write( + ' if ((module = PyImport_ImportModule("%s")) != NULL) {\n' + % module) + fp.write( + ' PyObject *moddict = PyModule_GetDict(module);\n\n') + for pyname, cname in bymod[module]: + fp.write( + ' _%s = (PyTypeObject *)PyDict_GetItemString(' + 'moddict, "%s");\n' % (cname, pyname)) + fp.write(' if (_%s == NULL) {\n' % cname) + fp.write(' PyErr_SetString(PyExc_ImportError,\n') + fp.write(' "cannot import name %s from %s");\n' + % (pyname, module)) + fp.write(' return;\n') + fp.write(' }\n') + fp.write(' } else {\n') + fp.write(' PyErr_SetString(PyExc_ImportError,\n') + fp.write(' "could not import %s");\n' % module) + fp.write(' return;\n') + fp.write(' }\n') + fp.write('\n') + fp.write(overrides.get_init() + '\n') + fp.resetline() + +def write_registers(parser, overrides, fp): + for boxed in parser.boxes: + if overrides.is_type_ignored(boxed.c_name): + continue + fp.write(' pyg_register_boxed(d, "' + boxed.name + + '", ' + boxed.typecode + + ', &Py' + boxed.c_name + + '_Type);\n') + for pointer in parser.pointers: + if overrides.is_type_ignored(pointer.c_name): + continue + fp.write(' pyg_register_pointer(d, "' + pointer.name + + '", ' + pointer.typecode + + ', &Py' + pointer.c_name + '_Type);\n') + for interface in parser.interfaces: + if overrides.is_type_ignored(interface.c_name): + continue + fp.write(' pyg_register_interface(d, "' + interface.name + + '", '+ interface.typecode + ', &Py' + interface.c_name + + '_Type);\n') + if interface.interface_info is not None: + fp.write(' pyg_register_interface_info(%s, &%s);\n' % + (interface.typecode, interface.interface_info)) + + objects = parser.objects[:] + pos = 0 + while pos < len(objects): + parent = objects[pos].parent + for i in range(pos+1, len(objects)): + if objects[i].c_name == parent: + objects.insert(i+1, objects[pos]) + del objects[pos] + break + else: + pos = pos + 1 + for obj in objects: + if overrides.is_type_ignored(obj.c_name): + continue + bases = [] + if obj.parent != None: + bases.append(obj.parent) + bases = bases + obj.implements + if bases: + fp.write(' pygobject_register_class(d, "' + obj.c_name + + '", ' + obj.typecode + ', &Py' + obj.c_name + + '_Type, Py_BuildValue("(' + 'O' * len(bases) + ')", ' + + string.join(map(lambda s: '&Py'+s+'_Type', bases), ', ') + + '));\n') + else: + fp.write(' pygobject_register_class(d, "' + obj.c_name + + '", ' + obj.typecode + ', &Py' + obj.c_name + + '_Type, NULL);\n') + if obj.has_new_constructor_api: + fp.write(' pyg_set_object_has_new_constructor(%s);\n' % + obj.typecode) + else: + print >> sys.stderr, ( + "Warning: Constructor for %s needs to be updated to new API\n" + " See http://live.gnome.org/PyGTK_2fWhatsNew28" + "#update-constructors") % obj.c_name + if obj.class_init_func is not None: + fp.write(' pyg_register_class_init(%s, %s);\n' % + (obj.typecode, obj.class_init_func)) + #TODO: register mini-objects + miniobjects = parser.miniobjects[:] + for obj in miniobjects: + bases = [] + if obj.parent != None: + bases.append(obj.parent) + bases = bases + obj.implements + if bases: + fp.write(' pygstminiobject_register_class(d, "' + obj.c_name + + '", ' + obj.typecode + ', &Py' + obj.c_name + + '_Type, Py_BuildValue("(' + 'O' * len(bases) + ')", ' + + string.join(map(lambda s: '&Py'+s+'_Type', bases), ', ') + + '));\n') + else: + fp.write(' pygstminiobject_register_class(d, "' + obj.c_name + + '", ' + obj.typecode + ', &Py' + obj.c_name + + '_Type, NULL);\n') + + fp.write('}\n') + +def write_source(parser, overrides, prefix, fp=FileOutput(sys.stdout)): + write_headers(overrides.get_headers(), fp) + write_imports(overrides, fp) + write_type_declarations(parser, fp) + write_body(overrides.get_body(), fp) + write_classes(parser, overrides, fp) + + wrapper = Wrapper(parser, None, overrides, fp) + wrapper.write_functions(prefix) + + write_enums(parser, overrides, prefix, fp) + write_extension_init(overrides, prefix, fp) + write_registers(parser, overrides, fp) + +def register_types(parser): + for boxed in parser.boxes: + argtypes.matcher.register_boxed(boxed.c_name, boxed.typecode) + for pointer in parser.pointers: + argtypes.matcher.register_pointer(pointer.c_name, pointer.typecode) + for obj in parser.objects: + argtypes.matcher.register_object(obj.c_name, obj.parent, obj.typecode) + for obj in parser.miniobjects: + argtypes.matcher.register_miniobject(obj.c_name, obj.parent, obj.typecode) + for obj in parser.interfaces: + argtypes.matcher.register_object(obj.c_name, None, obj.typecode) + for enum in parser.enums: + if enum.deftype == 'flags': + argtypes.matcher.register_flag(enum.c_name, enum.typecode) + else: + argtypes.matcher.register_enum(enum.c_name, enum.typecode) + +usage = 'usage: codegen.py [-o overridesfile] [-p prefix] defsfile' +def main(argv): + o = override.Overrides() + prefix = 'pygtk' + outfilename = None + errorfilename = None + extendpath = [] + opts, args = getopt.getopt(argv[1:], "o:p:r:t:D:x", + ["override=", "prefix=", "register=", "outfilename=", + "load-types=", "errorfilename=", "extendpath="]) + defines = {} # -Dkey[=val] options + + for opt, arg in opts: + if opt in ('-x', '--extendpath'): + extendpath.append(arg) + extendpath.insert(0, os.getcwd()) + o = override.Overrides(path=extendpath) + + for opt, arg in opts: + if opt in ('-o', '--override'): + o = override.Overrides(arg, path=extendpath) + elif opt in ('-p', '--prefix'): + prefix = arg + elif opt in ('-r', '--register'): + # Warning: user has to make sure all -D options appear before -r + p = defsparser.DefsParser(arg, defines) + p.startParsing() + register_types(p) + del p + elif opt == '--outfilename': + outfilename = arg + elif opt == '--errorfilename': + errorfilename = arg + elif opt in ('-t', '--load-types'): + globals = {} + execfile(arg, globals) + elif opt == '-D': + nameval = arg.split('=') + try: + defines[nameval[0]] = nameval[1] + except IndexError: + defines[nameval[0]] = None + if len(args) < 1: + print >> sys.stderr, usage + return 1 + if errorfilename: + sys.stderr = open(errorfilename, "w") + p = defsparser.DefsParser(args[0], defines) + if not outfilename: + outfilename = os.path.splitext(args[0])[0] + '.c' + + p.startParsing() + + register_types(p) + write_source(p, o, prefix, FileOutput(sys.stdout, outfilename)) + + functions_coverage.printstats() + methods_coverage.printstats() + vproxies_coverage.printstats() + vaccessors_coverage.printstats() + iproxies_coverage.printstats() + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/farstream/python/codegen/definitions.py b/farstream/python/codegen/definitions.py new file mode 100644 index 000000000..911c8ddb4 --- /dev/null +++ b/farstream/python/codegen/definitions.py @@ -0,0 +1,607 @@ +# -*- Mode: Python; py-indent-offset: 4 -*- +import copy +import sys + +def get_valid_scheme_definitions(defs): + return [x for x in defs if isinstance(x, tuple) and len(x) >= 2] + +def unescape(s): + s = s.replace('\r\n', '\\r\\n').replace('\t', '\\t') + return s.replace('\r', '\\r').replace('\n', '\\n') + +def make_docstring(lines): + return "(char *) " + '\n'.join(['"%s"' % unescape(s) for s in lines]) + +# New Parameter class, wich emulates a tuple for compatibility reasons +class Parameter(object): + def __init__(self, ptype, pname, pdflt, pnull, pdir=None, keeprefcount = False): + self.ptype = ptype + self.pname = pname + self.pdflt = pdflt + self.pnull = pnull + self.pdir = pdir + self.keeprefcount = keeprefcount + + def __len__(self): return 4 + def __getitem__(self, i): + return (self.ptype, self.pname, self.pdflt, self.pnull)[i] + + def merge(self, old): + if old.pdflt is not None: + self.pdflt = old.pdflt + if old.pnull is not None: + self.pnull = old.pnull + +# Parameter for property based constructors +class Property(object): + def __init__(self, pname, optional, argname): + self.pname = pname + self.optional = optional + self.argname = argname + + def merge(self, old): + if old.optional is not None: + self.optional = old.optional + if old.argname is not None: + self.argname = old.argname + + +class Definition: + docstring = "NULL" + def __init__(self, *args): + """Create a new defs object of this type. The arguments are the + components of the definition""" + raise RuntimeError, "this is an abstract class" + def merge(self, old): + """Merge in customisations from older version of definition""" + raise RuntimeError, "this is an abstract class" + def write_defs(self, fp=sys.stdout): + """write out this definition in defs file format""" + raise RuntimeError, "this is an abstract class" + + def guess_return_value_ownership(self): + "return 1 if caller owns return value" + if getattr(self, 'is_constructor_of', False): + self.caller_owns_return = True + elif self.ret in ('char*', 'gchar*', 'string'): + self.caller_owns_return = True + else: + self.caller_owns_return = False + + +class ObjectDef(Definition): + def __init__(self, name, *args): + self.name = name + self.module = None + self.parent = None + self.c_name = None + self.typecode = None + self.fields = [] + self.implements = [] + self.class_init_func = None + self.has_new_constructor_api = False + for arg in get_valid_scheme_definitions(args): + if arg[0] == 'in-module': + self.module = arg[1] + elif arg[0] == 'docstring': + self.docstring = make_docstring(arg[1:]) + elif arg[0] == 'parent': + self.parent = arg[1] + elif arg[0] == 'c-name': + self.c_name = arg[1] + elif arg[0] == 'gtype-id': + self.typecode = arg[1] + elif arg[0] == 'fields': + for parg in arg[1:]: + self.fields.append((parg[0], parg[1])) + elif arg[0] == 'implements': + self.implements.append(arg[1]) + def merge(self, old): + # currently the .h parser doesn't try to work out what fields of + # an object structure should be public, so we just copy the list + # from the old version ... + self.fields = old.fields + self.implements = old.implements + def write_defs(self, fp=sys.stdout): + fp.write('(define-object ' + self.name + '\n') + if self.module: + fp.write(' (in-module "' + self.module + '")\n') + if self.parent != (None, None): + fp.write(' (parent "' + self.parent + '")\n') + for interface in self.implements: + fp.write(' (implements "' + interface + '")\n') + if self.c_name: + fp.write(' (c-name "' + self.c_name + '")\n') + if self.typecode: + fp.write(' (gtype-id "' + self.typecode + '")\n') + if self.fields: + fp.write(' (fields\n') + for (ftype, fname) in self.fields: + fp.write(' \'("' + ftype + '" "' + fname + '")\n') + fp.write(' )\n') + fp.write(')\n\n') + +class MiniObjectDef(Definition): + def __init__(self, name, *args): + self.name = name + self.module = None + self.parent = None + self.c_name = None + self.typecode = None + self.fields = [] + self.implements = [] + for arg in args: + if type(arg) != type(()) or len(arg) < 2: + continue + if arg[0] == 'in-module': + self.module = arg[1] + elif arg[0] == 'parent': + self.parent = arg[1] + elif arg[0] == 'c-name': + self.c_name = arg[1] + elif arg[0] == 'gtype-id': + self.typecode = arg[1] + elif arg[0] == 'fields': + for parg in arg[1:]: + self.fields.append((parg[0], parg[1])) + elif arg[0] == 'implements': + self.implements.append(arg[1]) + def merge(self, old): + # currently the .h parser doesn't try to work out what fields of + # an object structure should be public, so we just copy the list + # from the old version ... + self.fields = old.fields + self.implements = old.implements + def write_defs(self, fp=sys.stdout): + fp.write('(define-object ' + self.name + '\n') + if self.module: + fp.write(' (in-module "' + self.module + '")\n') + if self.parent != (None, None): + fp.write(' (parent "' + self.parent + '")\n') + for interface in self.implements: + fp.write(' (implements "' + interface + '")\n') + if self.c_name: + fp.write(' (c-name "' + self.c_name + '")\n') + if self.typecode: + fp.write(' (gtype-id "' + self.typecode + '")\n') + if self.fields: + fp.write(' (fields\n') + for (ftype, fname) in self.fields: + fp.write(' \'("' + ftype + '" "' + fname + '")\n') + fp.write(' )\n') + fp.write(')\n\n') + + +class InterfaceDef(Definition): + def __init__(self, name, *args): + self.name = name + self.module = None + self.c_name = None + self.typecode = None + self.vtable = None + self.fields = [] + self.interface_info = None + for arg in get_valid_scheme_definitions(args): + if arg[0] == 'in-module': + self.module = arg[1] + elif arg[0] == 'docstring': + self.docstring = make_docstring(arg[1:]) + elif arg[0] == 'c-name': + self.c_name = arg[1] + elif arg[0] == 'gtype-id': + self.typecode = arg[1] + elif arg[0] == 'vtable': + self.vtable = arg[1] + if self.vtable is None: + self.vtable = self.c_name + "Iface" + def write_defs(self, fp=sys.stdout): + fp.write('(define-interface ' + self.name + '\n') + if self.module: + fp.write(' (in-module "' + self.module + '")\n') + if self.c_name: + fp.write(' (c-name "' + self.c_name + '")\n') + if self.typecode: + fp.write(' (gtype-id "' + self.typecode + '")\n') + fp.write(')\n\n') + +class EnumDef(Definition): + def __init__(self, name, *args): + self.deftype = 'enum' + self.name = name + self.in_module = None + self.c_name = None + self.typecode = None + self.values = [] + for arg in get_valid_scheme_definitions(args): + if arg[0] == 'in-module': + self.in_module = arg[1] + elif arg[0] == 'c-name': + self.c_name = arg[1] + elif arg[0] == 'gtype-id': + self.typecode = arg[1] + elif arg[0] == 'values': + for varg in arg[1:]: + self.values.append((varg[0], varg[1])) + def merge(self, old): + pass + def write_defs(self, fp=sys.stdout): + fp.write('(define-' + self.deftype + ' ' + self.name + '\n') + if self.in_module: + fp.write(' (in-module "' + self.in_module + '")\n') + fp.write(' (c-name "' + self.c_name + '")\n') + fp.write(' (gtype-id "' + self.typecode + '")\n') + if self.values: + fp.write(' (values\n') + for name, val in self.values: + fp.write(' \'("' + name + '" "' + val + '")\n') + fp.write(' )\n') + fp.write(')\n\n') + +class FlagsDef(EnumDef): + def __init__(self, *args): + apply(EnumDef.__init__, (self,) + args) + self.deftype = 'flags' + +class BoxedDef(Definition): + def __init__(self, name, *args): + self.name = name + self.module = None + self.c_name = None + self.typecode = None + self.copy = None + self.release = None + self.fields = [] + for arg in get_valid_scheme_definitions(args): + if arg[0] == 'in-module': + self.module = arg[1] + elif arg[0] == 'c-name': + self.c_name = arg[1] + elif arg[0] == 'gtype-id': + self.typecode = arg[1] + elif arg[0] == 'copy-func': + self.copy = arg[1] + elif arg[0] == 'release-func': + self.release = arg[1] + elif arg[0] == 'fields': + for parg in arg[1:]: + self.fields.append((parg[0], parg[1])) + def merge(self, old): + # currently the .h parser doesn't try to work out what fields of + # an object structure should be public, so we just copy the list + # from the old version ... + self.fields = old.fields + def write_defs(self, fp=sys.stdout): + fp.write('(define-boxed ' + self.name + '\n') + if self.module: + fp.write(' (in-module "' + self.module + '")\n') + if self.c_name: + fp.write(' (c-name "' + self.c_name + '")\n') + if self.typecode: + fp.write(' (gtype-id "' + self.typecode + '")\n') + if self.copy: + fp.write(' (copy-func "' + self.copy + '")\n') + if self.release: + fp.write(' (release-func "' + self.release + '")\n') + if self.fields: + fp.write(' (fields\n') + for (ftype, fname) in self.fields: + fp.write(' \'("' + ftype + '" "' + fname + '")\n') + fp.write(' )\n') + fp.write(')\n\n') + +class PointerDef(Definition): + def __init__(self, name, *args): + self.name = name + self.module = None + self.c_name = None + self.typecode = None + self.fields = [] + for arg in get_valid_scheme_definitions(args): + if arg[0] == 'in-module': + self.module = arg[1] + elif arg[0] == 'c-name': + self.c_name = arg[1] + elif arg[0] == 'gtype-id': + self.typecode = arg[1] + elif arg[0] == 'fields': + for parg in arg[1:]: + self.fields.append((parg[0], parg[1])) + def merge(self, old): + # currently the .h parser doesn't try to work out what fields of + # an object structure should be public, so we just copy the list + # from the old version ... + self.fields = old.fields + def write_defs(self, fp=sys.stdout): + fp.write('(define-pointer ' + self.name + '\n') + if self.module: + fp.write(' (in-module "' + self.module + '")\n') + if self.c_name: + fp.write(' (c-name "' + self.c_name + '")\n') + if self.typecode: + fp.write(' (gtype-id "' + self.typecode + '")\n') + if self.fields: + fp.write(' (fields\n') + for (ftype, fname) in self.fields: + fp.write(' \'("' + ftype + '" "' + fname + '")\n') + fp.write(' )\n') + fp.write(')\n\n') + +class MethodDefBase(Definition): + def __init__(self, name, *args): + dump = 0 + self.name = name + self.ret = None + self.caller_owns_return = None + self.unblock_threads = None + self.c_name = None + self.typecode = None + self.of_object = None + self.params = [] # of form (type, name, default, nullok) + self.varargs = 0 + self.deprecated = None + for arg in get_valid_scheme_definitions(args): + if arg[0] == 'of-object': + self.of_object = arg[1] + elif arg[0] == 'docstring': + self.docstring = make_docstring(arg[1:]) + elif arg[0] == 'c-name': + self.c_name = arg[1] + elif arg[0] == 'gtype-id': + self.typecode = arg[1] + elif arg[0] == 'return-type': + self.ret = arg[1] + elif arg[0] == 'caller-owns-return': + self.caller_owns_return = arg[1] in ('t', '#t') + elif arg[0] == 'unblock-threads': + self.unblock_threads = arg[1] in ('t', '#t') + elif arg[0] == 'parameters': + for parg in arg[1:]: + ptype = parg[0] + pname = parg[1] + pdflt = None + pnull = 0 + pdir = None + keeprefcount = False + for farg in parg[2:]: + assert isinstance(farg, tuple) + if farg[0] == 'default': + pdflt = farg[1] + elif farg[0] == 'null-ok': + pnull = 1 + elif farg[0] == 'direction': + pdir = farg[1] + elif farg[0] == 'keep-refcount': + keeprefcount = True + self.params.append(Parameter(ptype, pname, pdflt, pnull, pdir, + keeprefcount=keeprefcount)) + elif arg[0] == 'varargs': + self.varargs = arg[1] in ('t', '#t') + elif arg[0] == 'deprecated': + self.deprecated = arg[1] + else: + sys.stderr.write("Warning: %s argument unsupported.\n" + % (arg[0])) + dump = 1 + if dump: + self.write_defs(sys.stderr) + + if self.caller_owns_return is None and self.ret is not None: + self.guess_return_value_ownership() + + def merge(self, old, parmerge): + self.caller_owns_return = old.caller_owns_return + self.varargs = old.varargs + # here we merge extra parameter flags accross to the new object. + if not parmerge: + self.params = copy.deepcopy(old.params) + return + for i in range(len(self.params)): + ptype, pname, pdflt, pnull = self.params[i] + for p2 in old.params: + if p2[1] == pname: + self.params[i] = (ptype, pname, p2[2], p2[3]) + break + def _write_defs(self, fp=sys.stdout): + if self.of_object != (None, None): + fp.write(' (of-object "' + self.of_object + '")\n') + if self.c_name: + fp.write(' (c-name "' + self.c_name + '")\n') + if self.typecode: + fp.write(' (gtype-id "' + self.typecode + '")\n') + if self.caller_owns_return: + fp.write(' (caller-owns-return #t)\n') + if self.unblock_threads: + fp.write(' (unblock_threads #t)\n') + if self.ret: + fp.write(' (return-type "' + self.ret + '")\n') + if self.deprecated: + fp.write(' (deprecated "' + self.deprecated + '")\n') + if self.params: + fp.write(' (parameters\n') + for ptype, pname, pdflt, pnull in self.params: + fp.write(' \'("' + ptype + '" "' + pname +'"') + if pdflt: fp.write(' (default "' + pdflt + '")') + if pnull: fp.write(' (null-ok)') + fp.write(')\n') + fp.write(' )\n') + if self.varargs: + fp.write(' (varargs #t)\n') + fp.write(')\n\n') + + +class MethodDef(MethodDefBase): + def __init__(self, name, *args): + MethodDefBase.__init__(self, name, *args) + for item in ('c_name', 'of_object'): + if self.__dict__[item] == None: + self.write_defs(sys.stderr) + raise RuntimeError, "definition missing required %s" % (item,) + + def write_defs(self, fp=sys.stdout): + fp.write('(define-method ' + self.name + '\n') + self._write_defs(fp) + +class VirtualDef(MethodDefBase): + def write_defs(self, fp=sys.stdout): + fp.write('(define-virtual ' + self.name + '\n') + self._write_defs(fp) + +class FunctionDef(Definition): + def __init__(self, name, *args): + dump = 0 + self.name = name + self.in_module = None + self.is_constructor_of = None + self.ret = None + self.caller_owns_return = None + self.unblock_threads = None + self.c_name = None + self.typecode = None + self.params = [] # of form (type, name, default, nullok) + self.varargs = 0 + self.deprecated = None + for arg in get_valid_scheme_definitions(args): + if arg[0] == 'in-module': + self.in_module = arg[1] + elif arg[0] == 'docstring': + self.docstring = make_docstring(arg[1:]) + elif arg[0] == 'is-constructor-of': + self.is_constructor_of = arg[1] + elif arg[0] == 'c-name': + self.c_name = arg[1] + elif arg[0] == 'gtype-id': + self.typecode = arg[1] + elif arg[0] == 'return-type': + self.ret = arg[1] + elif arg[0] == 'caller-owns-return': + self.caller_owns_return = arg[1] in ('t', '#t') + elif arg[0] == 'unblock-threads': + self.unblock_threads = arg[1] in ('t', '#t') + elif arg[0] == 'parameters': + for parg in arg[1:]: + ptype = parg[0] + pname = parg[1] + pdflt = None + pnull = 0 + keeprefcount = False + for farg in parg[2:]: + if farg[0] == 'default': + pdflt = farg[1] + elif farg[0] == 'null-ok': + pnull = 1 + elif farg[0] == 'keep-refcount': + keeprefcount = True + self.params.append(Parameter(ptype, pname, pdflt, pnull, + keeprefcount = keeprefcount)) + elif arg[0] == 'properties': + if self.is_constructor_of is None: + print >> sys.stderr, "Warning: (properties ...) "\ + "is only valid for constructors" + for prop in arg[1:]: + pname = prop[0] + optional = False + argname = pname + for farg in prop[1:]: + if farg[0] == 'optional': + optional = True + elif farg[0] == 'argname': + argname = farg[1] + self.params.append(Property(pname, optional, argname)) + elif arg[0] == 'varargs': + self.varargs = arg[1] in ('t', '#t') + elif arg[0] == 'deprecated': + self.deprecated = arg[1] + else: + sys.stderr.write("Warning: %s argument unsupported\n" + % (arg[0],)) + dump = 1 + if dump: + self.write_defs(sys.stderr) + + if self.caller_owns_return is None and self.ret is not None: + self.guess_return_value_ownership() + for item in ('c_name',): + if self.__dict__[item] == None: + self.write_defs(sys.stderr) + raise RuntimeError, "definition missing required %s" % (item,) + + _method_write_defs = MethodDef.__dict__['write_defs'] + + def merge(self, old, parmerge): + self.caller_owns_return = old.caller_owns_return + self.varargs = old.varargs + if not parmerge: + self.params = copy.deepcopy(old.params) + return + # here we merge extra parameter flags accross to the new object. + def merge_param(param): + for old_param in old.params: + if old_param.pname == param.pname: + if isinstance(old_param, Property): + # h2def never scans Property's, therefore if + # we have one it was manually written, so we + # keep it. + return copy.deepcopy(old_param) + else: + param.merge(old_param) + return param + raise RuntimeError, "could not find %s in old_parameters %r" % ( + param.pname, [p.pname for p in old.params]) + try: + self.params = map(merge_param, self.params) + except RuntimeError: + # parameter names changed and we can't find a match; it's + # safer to keep the old parameter list untouched. + self.params = copy.deepcopy(old.params) + + if not self.is_constructor_of: + try: + self.is_constructor_of = old.is_constructor_of + except AttributeError: + pass + if isinstance(old, MethodDef): + self.name = old.name + # transmogrify from function into method ... + self.write_defs = self._method_write_defs + self.of_object = old.of_object + del self.params[0] + def write_defs(self, fp=sys.stdout): + fp.write('(define-function ' + self.name + '\n') + if self.in_module: + fp.write(' (in-module "' + self.in_module + '")\n') + if self.is_constructor_of: + fp.write(' (is-constructor-of "' + self.is_constructor_of +'")\n') + if self.c_name: + fp.write(' (c-name "' + self.c_name + '")\n') + if self.typecode: + fp.write(' (gtype-id "' + self.typecode + '")\n') + if self.caller_owns_return: + fp.write(' (caller-owns-return #t)\n') + if self.unblock_threads: + fp.write(' (unblock-threads #t)\n') + if self.ret: + fp.write(' (return-type "' + self.ret + '")\n') + if self.deprecated: + fp.write(' (deprecated "' + self.deprecated + '")\n') + if self.params: + if isinstance(self.params[0], Parameter): + fp.write(' (parameters\n') + for ptype, pname, pdflt, pnull in self.params: + fp.write(' \'("' + ptype + '" "' + pname +'"') + if pdflt: fp.write(' (default "' + pdflt + '")') + if pnull: fp.write(' (null-ok)') + fp.write(')\n') + fp.write(' )\n') + elif isinstance(self.params[0], Property): + fp.write(' (properties\n') + for prop in self.params: + fp.write(' \'("' + prop.pname +'"') + if prop.optional: fp.write(' (optional)') + fp.write(')\n') + fp.write(' )\n') + else: + assert False, "strange parameter list %r" % self.params[0] + if self.varargs: + fp.write(' (varargs #t)\n') + + fp.write(')\n\n') diff --git a/farstream/python/codegen/defsparser.py b/farstream/python/codegen/defsparser.py new file mode 100644 index 000000000..e24a96967 --- /dev/null +++ b/farstream/python/codegen/defsparser.py @@ -0,0 +1,143 @@ +# -*- Mode: Python; py-indent-offset: 4 -*- +import os, sys +import scmexpr +from definitions import BoxedDef, EnumDef, FlagsDef, FunctionDef, \ + InterfaceDef, MethodDef, ObjectDef, MiniObjectDef, PointerDef, \ + VirtualDef + +class IncludeParser(scmexpr.Parser): + """A simple parser that follows include statements automatically""" + def include(self, filename): + if not os.path.isabs(filename): + filename = os.path.join(os.path.dirname(self.filename), filename) + + # set self.filename to the include name, to handle recursive includes + oldfile = self.filename + self.filename = filename + self.startParsing() + self.filename = oldfile + +class DefsParser(IncludeParser): + def __init__(self, arg, defines={}): + IncludeParser.__init__(self, arg) + self.objects = [] + self.miniobjects = [] + self.interfaces = [] + self.enums = [] # enums and flags + self.boxes = [] # boxed types + self.pointers = [] # pointer types + self.functions = [] # functions and methods + self.virtuals = [] # virtual methods + self.c_name = {} # hash of c names of functions + self.methods = {} # hash of methods of particular objects + self.defines = defines # -Dfoo=bar options, as dictionary + + def define_object(self, *args): + odef = apply(ObjectDef, args) + self.objects.append(odef) + self.c_name[odef.c_name] = odef + # TODO: define_mini_object + def define_miniobject(self, *args): + odef = apply(MiniObjectDef, args) + self.miniobjects.append(odef) + self.c_name[odef.c_name] = odef + def define_interface(self, *args): + idef = apply(InterfaceDef, args) + self.interfaces.append(idef) + self.c_name[idef.c_name] = idef + def define_enum(self, *args): + edef = apply(EnumDef, args) + self.enums.append(edef) + self.c_name[edef.c_name] = edef + def define_flags(self, *args): + fdef = apply(FlagsDef, args) + self.enums.append(fdef) + self.c_name[fdef.c_name] = fdef + def define_boxed(self, *args): + bdef = apply(BoxedDef, args) + self.boxes.append(bdef) + self.c_name[bdef.c_name] = bdef + def define_pointer(self, *args): + pdef = apply(PointerDef, args) + self.pointers.append(pdef) + self.c_name[pdef.c_name] = pdef + def define_function(self, *args): + fdef = apply(FunctionDef, args) + self.functions.append(fdef) + self.c_name[fdef.c_name] = fdef + def define_method(self, *args): + mdef = apply(MethodDef, args) + self.functions.append(mdef) + self.c_name[mdef.c_name] = mdef + def define_virtual(self, *args): + vdef = apply(VirtualDef, args) + self.virtuals.append(vdef) + def merge(self, old, parmerge): + for obj in self.objects: + if old.c_name.has_key(obj.c_name): + obj.merge(old.c_name[obj.c_name]) + for f in self.functions: + if old.c_name.has_key(f.c_name): + f.merge(old.c_name[f.c_name], parmerge) + + def printMissing(self, old): + for obj in self.objects: + if not old.c_name.has_key(obj.c_name): + obj.write_defs() + for f in self.functions: + if not old.c_name.has_key(f.c_name): + f.write_defs() + + def write_defs(self, fp=sys.stdout): + for obj in self.objects: + obj.write_defs(fp) + # TODO: Add miniobject + for obj in self.miniobjects: + obj.write_defs(fp) + for enum in self.enums: + enum.write_defs(fp) + for boxed in self.boxes: + boxed.write_defs(fp) + for pointer in self.pointers: + pointer.write_defs(fp) + for func in self.functions: + func.write_defs(fp) + + def find_object(self, c_name): + for obj in self.objects: + if obj.c_name == c_name: + return obj + else: + raise ValueError, 'object not found' + + def find_constructor(self, obj, overrides): + for func in self.functions: + if isinstance(func, FunctionDef) and \ + func.is_constructor_of == obj.c_name and \ + not overrides.is_ignored(func.c_name): + return func + + def find_methods(self, obj): + objname = obj.c_name + return filter(lambda func, on=objname: isinstance(func, MethodDef) and + func.of_object == on, self.functions) + + def find_virtuals(self, obj): + objname = obj.c_name + retval = filter(lambda func, on=objname: isinstance(func, VirtualDef) and + func.of_object == on, self.virtuals) + return retval + + def find_functions(self): + return filter(lambda func: isinstance(func, FunctionDef) and + not func.is_constructor_of, self.functions) + + def ifdef(self, *args): + if args[0] in self.defines: + for arg in args[1:]: + self.handle(arg) + + def ifndef(self, *args): + if args[0] not in self.defines: + for arg in args[1:]: + self.handle(arg) diff --git a/farstream/python/codegen/docextract.py b/farstream/python/codegen/docextract.py new file mode 100644 index 000000000..e6c65053d --- /dev/null +++ b/farstream/python/codegen/docextract.py @@ -0,0 +1,185 @@ +# -*- Mode: Python; py-indent-offset: 4 -*- +'''Simple module for extracting GNOME style doc comments from C +sources, so I can use them for other purposes.''' + +import sys, os, string, re + +__all__ = ['extract'] + +class FunctionDoc: + def __init__(self): + self.name = None + self.params = [] + self.description = '' + self.ret = '' + def set_name(self, name): + self.name = name + def add_param(self, name, description): + if name == '...': + name = 'Varargs' + self.params.append((name, description)) + def append_to_last_param(self, extra): + self.params[-1] = (self.params[-1][0], self.params[-1][1] + extra) + def append_to_named_param(self, name, extra): + for i in range(len(self.params)): + if self.params[i][0] == name: + self.params[i] = (name, self.params[i][1] + extra) + return + # fall through to adding extra parameter ... + self.add_param(name, extra) + def append_description(self, extra): + self.description = self.description + extra + def append_return(self, extra): + self.ret = self.ret + extra + + def get_param_description(self, name): + for param, description in self.params: + if param == name: + return description + else: + return '' + +comment_start_pat = re.compile(r'^\s*/\*\*\s') +comment_end_pat = re.compile(r'^\s*\*+/') +comment_line_lead = re.compile(r'^\s*\*\s*') +funcname_pat = re.compile(r'^(\w+)\s*:?') +return_pat = re.compile(r'^(returns:|return\s+value:|returns\s*)(.*\n?)$', + re.IGNORECASE) +param_pat = re.compile(r'^@(\S+)\s*:(.*\n?)$') + +def parse_file(fp, doc_dict): + line = fp.readline() + in_comment_block = 0 + while line: + if not in_comment_block: + if comment_start_pat.match(line): + in_comment_block = 1 + cur_doc = FunctionDoc() + in_description = 0 + in_return = 0 + line = fp.readline() + continue + + # we are inside a comment block ... + if comment_end_pat.match(line): + if not cur_doc.name: + sys.stderr.write("no function name found in doc comment\n") + else: + doc_dict[cur_doc.name] = cur_doc + in_comment_block = 0 + line = fp.readline() + continue + + # inside a comment block, and not the end of the block ... + line = comment_line_lead.sub('', line) + if not line: line = '\n' + + if not cur_doc.name: + match = funcname_pat.match(line) + if match: + cur_doc.set_name(match.group(1)) + elif in_return: + match = return_pat.match(line) + if match: + # assume the last return statement was really part of the + # description + return_start = match.group(1) + cur_doc.ret = match.group(2) + cur_doc.description = cur_doc.description + return_start + \ + cur_doc.ret + else: + cur_doc.append_return(line) + elif in_description: + if line[:12] == 'Description:': + line = line[12:] + match = return_pat.match(line) + if match: + in_return = 1 + return_start = match.group(1) + cur_doc.append_return(match.group(2)) + else: + cur_doc.append_description(line) + elif line == '\n': + # end of parameters + in_description = 1 + else: + match = param_pat.match(line) + if match: + param = match.group(1) + desc = match.group(2) + if param == 'returns': + cur_doc.ret = desc + else: + cur_doc.add_param(param, desc) + else: + # must be continuation + try: + if param == 'returns': + cur_doc.append_return(line) + else: + cur_doc.append_to_last_param(line) + except: + sys.stderr.write('something weird while reading param\n') + line = fp.readline() + +def parse_dir(dir, doc_dict): + for file in os.listdir(dir): + if file in ('.', '..'): continue + path = os.path.join(dir, file) + if os.path.isdir(path): + parse_dir(path, doc_dict) + if len(file) > 2 and file[-2:] == '.c': + parse_file(open(path, 'r'), doc_dict) + +def extract(dirs, doc_dict=None): + if not doc_dict: doc_dict = {} + for dir in dirs: + parse_dir(dir, doc_dict) + return doc_dict + +tmpl_section_pat = re.compile(r'^<!-- ##### (\w+) (\w+) ##### -->$') +def parse_tmpl(fp, doc_dict): + cur_doc = None + + line = fp.readline() + while line: + match = tmpl_section_pat.match(line) + if match: + cur_doc = None # new input shouldn't affect the old doc dict + sect_type = match.group(1) + sect_name = match.group(2) + + if sect_type == 'FUNCTION': + cur_doc = doc_dict.get(sect_name) + if not cur_doc: + cur_doc = FunctionDoc() + cur_doc.set_name(sect_name) + doc_dict[sect_name] = cur_doc + elif line == '<!-- # Unused Parameters # -->\n': + cur_doc = None # don't worry about unused params. + elif cur_doc: + if line[:10] == '@Returns: ': + if string.strip(line[10:]): + cur_doc.append_return(line[10:]) + elif line[0] == '@': + pos = string.find(line, ':') + if pos >= 0: + cur_doc.append_to_named_param(line[1:pos], line[pos+1:]) + else: + cur_doc.append_description(line) + else: + cur_doc.append_description(line) + + line = fp.readline() + +def extract_tmpl(dirs, doc_dict=None): + if not doc_dict: doc_dict = {} + for dir in dirs: + for file in os.listdir(dir): + if file in ('.', '..'): continue + path = os.path.join(dir, file) + if os.path.isdir(path): + continue + if len(file) > 2 and file[-2:] == '.sgml': + parse_tmpl(open(path, 'r'), doc_dict) + return doc_dict diff --git a/farstream/python/codegen/docgen.py b/farstream/python/codegen/docgen.py new file mode 100644 index 000000000..6905a14bf --- /dev/null +++ b/farstream/python/codegen/docgen.py @@ -0,0 +1,752 @@ +#!/usr/bin/env python +# -*- Mode: Python; py-indent-offset: 4 -*- +import sys, os, string, re, getopt + +import defsparser +import definitions +import override +import docextract + +class Node: + def __init__(self, name, interfaces=[]): + self.name = name + self.interfaces = interfaces + self.subclasses = [] + def add_child(self, node): + self.subclasses.append(node) + +def build_object_tree(parser): + # reorder objects so that parent classes come first ... + objects = parser.objects[:] + pos = 0 + while pos < len(objects): + parent = objects[pos].parent + for i in range(pos+1, len(objects)): + if objects[i].c_name == parent: + objects.insert(i+1, objects[pos]) + del objects[pos] + break + else: + pos = pos + 1 + + root = Node(None) + nodes = { None: root } + for obj_def in objects: + print obj_def.name + parent_node = nodes[obj_def.parent] + node = Node(obj_def.c_name, obj_def.implements) + parent_node.add_child(node) + nodes[node.name] = node + + if parser.interfaces: + interfaces = Node('gobject.GInterface') + root.add_child(interfaces) + nodes[interfaces.name] = interfaces + for obj_def in parser.interfaces: + node = Node(obj_def.c_name) + interfaces.add_child(node) + nodes[node.name] = node + + if parser.boxes: + boxed = Node('gobject.GBoxed') + root.add_child(boxed) + nodes[boxed.name] = boxed + for obj_def in parser.boxes: + node = Node(obj_def.c_name) + boxed.add_child(node) + nodes[node.name] = node + + if parser.pointers: + pointers = Node('gobject.GPointer') + root.add_child(pointers) + nodes[pointers.name] = pointers + for obj_def in parser.pointers: + node = Node(obj_def.c_name) + pointers.add_child(node) + nodes[node.name] = node + + return root + +class DocWriter: + def __init__(self): + # parse the defs file + self.parser = defsparser.DefsParser(()) + self.overrides = override.Overrides() + self.classmap = {} + self.docs = {} + + def add_sourcedirs(self, source_dirs): + self.docs = docextract.extract(source_dirs, self.docs) + def add_tmpldirs(self, tmpl_dirs): + self.docs = docextract.extract_tmpl(tmpl_dirs, self.docs) + + def add_docs(self, defs_file, overrides_file, module_name): + '''parse information about a given defs file''' + self.parser.filename = defs_file + self.parser.startParsing(defs_file) + if overrides_file: + self.overrides.handle_file(overrides_file) + + for obj in self.parser.objects: + if not self.classmap.has_key(obj.c_name): + self.classmap[obj.c_name] = '%s.%s' % (module_name, obj.name) + for obj in self.parser.interfaces: + if not self.classmap.has_key(obj.c_name): + self.classmap[obj.c_name] = '%s.%s' % (module_name, obj.name) + for obj in self.parser.boxes: + if not self.classmap.has_key(obj.c_name): + self.classmap[obj.c_name] = '%s.%s' % (module_name, obj.name) + for obj in self.parser.pointers: + if not self.classmap.has_key(obj.c_name): + self.classmap[obj.c_name] = '%s.%s' % (module_name, obj.name) + + def pyname(self, name): + return self.classmap.get(name, name) + + def __compare(self, obja, objb): + return cmp(self.pyname(obja.c_name), self.pyname(objb.c_name)) + def output_docs(self, output_prefix): + files = [] + + # class hierarchy + hierarchy = build_object_tree(self.parser) + filename = self.create_filename('hierarchy', output_prefix) + fp = open(filename, 'w') + self.write_full_hierarchy(hierarchy, fp) + fp.close() + + obj_defs = self.parser.objects + self.parser.interfaces + \ + self.parser.boxes + self.parser.pointers + obj_defs.sort(self.__compare) + for obj_def in obj_defs: + filename = self.create_filename(obj_def.c_name, output_prefix) + fp = open(filename, 'w') + if isinstance(obj_def, definitions.ObjectDef): + self.output_object_docs(obj_def, fp) + elif isinstance(obj_def, definitions.InterfaceDef): + self.output_interface_docs(obj_def, fp) + elif isinstance(obj_def, definitions.BoxedDef): + self.output_boxed_docs(obj_def, fp) + elif isinstance(obj_def, definitions.PointerDef): + self.output_boxed_docs(obj_def, fp) + fp.close() + files.append((os.path.basename(filename), obj_def)) + + if files: + filename = self.create_toc_filename(output_prefix) + fp = open(filename, 'w') + self.output_toc(files, fp) + fp.close() + + def output_object_docs(self, obj_def, fp=sys.stdout): + self.write_class_header(obj_def.c_name, fp) + + self.write_heading('Synopsis', fp) + self.write_synopsis(obj_def, fp) + self.close_section(fp) + + # construct the inheritence hierarchy ... + ancestry = [ (obj_def.c_name, obj_def.implements) ] + try: + parent = obj_def.parent + while parent != None: + if parent == 'GObject': + ancestry.append(('GObject', [])) + parent = None + else: + parent_def = self.parser.find_object(parent) + ancestry.append((parent_def.c_name, parent_def.implements)) + parent = parent_def.parent + except ValueError: + pass + ancestry.reverse() + self.write_heading('Ancestry', fp) + self.write_hierarchy(obj_def.c_name, ancestry, fp) + self.close_section(fp) + + constructor = self.parser.find_constructor(obj_def, self.overrides) + if constructor: + self.write_heading('Constructor', fp) + self.write_constructor(constructor, + self.docs.get(constructor.c_name, None), + fp) + self.close_section(fp) + + methods = self.parser.find_methods(obj_def) + methods = filter(lambda meth, self=self: + not self.overrides.is_ignored(meth.c_name), methods) + if methods: + self.write_heading('Methods', fp) + for method in methods: + self.write_method(method, self.docs.get(method.c_name, None), fp) + self.close_section(fp) + + self.write_class_footer(obj_def.c_name, fp) + + def output_interface_docs(self, int_def, fp=sys.stdout): + self.write_class_header(int_def.c_name, fp) + + self.write_heading('Synopsis', fp) + self.write_synopsis(int_def, fp) + self.close_section(fp) + + methods = self.parser.find_methods(int_def) + methods = filter(lambda meth, self=self: + not self.overrides.is_ignored(meth.c_name), methods) + if methods: + self.write_heading('Methods', fp) + for method in methods: + self.write_method(method, self.docs.get(method.c_name, None), fp) + self.close_section(fp) + + self.write_class_footer(int_def.c_name, fp) + + def output_boxed_docs(self, box_def, fp=sys.stdout): + self.write_class_header(box_def.c_name, fp) + + self.write_heading('Synopsis', fp) + self.write_synopsis(box_def, fp) + self.close_section(fp) + + constructor = self.parser.find_constructor(box_def, self.overrides) + if constructor: + self.write_heading('Constructor', fp) + self.write_constructor(constructor, + self.docs.get(constructor.c_name, None), + fp) + self.close_section(fp) + + methods = self.parser.find_methods(box_def) + methods = filter(lambda meth, self=self: + not self.overrides.is_ignored(meth.c_name), methods) + if methods: + self.write_heading('Methods', fp) + for method in methods: + self.write_method(method, self.docs.get(method.c_name, None), fp) + self.close_section(fp) + + self.write_class_footer(box_def.c_name, fp) + + def output_toc(self, files, fp=sys.stdout): + fp.write('TOC\n\n') + for filename, obj_def in files: + fp.write(obj_def.c_name + ' - ' + filename + '\n') + + # override the following to create a more complex output format + def create_filename(self, obj_name, output_prefix): + '''Create output filename for this particular object''' + return output_prefix + '-' + string.lower(obj_name) + '.txt' + def create_toc_filename(self, output_prefix): + return self.create_filename(self, 'docs', output_prefix) + + def write_full_hierarchy(self, hierarchy, fp): + def handle_node(node, fp, indent=''): + for child in node.subclasses: + fp.write(indent + node.name) + if node.interfaces: + fp.write(' (implements ') + fp.write(string.join(node.interfaces, ', ')) + fp.write(')\n') + else: + fp.write('\n') + handle_node(child, fp, indent + ' ') + handle_node(hierarchy, fp) + + # these need to handle default args ... + def create_constructor_prototype(self, func_def): + return func_def.is_constructor_of + '(' + \ + string.join(map(lambda x: x[1], func_def.params), ', ') + \ + ')' + def create_function_prototype(self, func_def): + return func_def.name + '(' + \ + string.join(map(lambda x: x[1], func_def.params), ', ') + \ + ')' + def create_method_prototype(self, meth_def): + return meth_def.of_object + '.' + \ + meth_def.name + '(' + \ + string.join(map(lambda x: x[1], meth_def.params), ', ') + \ + ')' + + def write_class_header(self, obj_name, fp): + fp.write('Class %s\n' % obj_name) + fp.write('======%s\n\n' % ('=' * len(obj_name))) + def write_class_footer(self, obj_name, fp): + pass + def write_heading(self, text, fp): + fp.write('\n' + text + '\n' + ('-' * len(text)) + '\n') + def close_section(self, fp): + pass + def write_synopsis(self, obj_def, fp): + fp.write('class %s' % obj_def.c_name) + if isinstance(obj_def, definitions.ObjectDef): + bases = [] + if obj_def.parent: bases.append(obj_def.parent) + bases = bases = obj_def.implements + if bases: + fp.write('(%s)' % string.join(bases, ', ')) + fp.write(':\n') + + constructor = self.parser.find_constructor(obj_def, self.overrides) + if constructor: + prototype = self.create_constructor_prototype(constructor) + fp.write(' def %s\n' % prototype) + methods = self.parser.find_methods(obj_def) + methods = filter(lambda meth, self=self: + not self.overrides.is_ignored(meth.c_name), methods) + for meth in methods: + prototype = self.create_method_prototype(meth) + fp.write(' def %s\n' % prototype) + + def write_hierarchy(self, obj_name, ancestry, fp): + indent = '' + for name, interfaces in ancestry: + fp.write(indent + '+-- ' + name) + if interfaces: + fp.write(' (implements ') + fp.write(string.join(interfaces, ', ')) + fp.write(')\n') + else: + fp.write('\n') + indent = indent + ' ' + fp.write('\n') + def write_constructor(self, func_def, func_doc, fp): + prototype = self.create_constructor_prototype(func_def) + fp.write(prototype + '\n\n') + for type, name, dflt, null in func_def.params: + if func_doc: + descr = func_doc.get_param_description(name) + else: + descr = 'a ' + type + fp.write(' ' + name + ': ' + descr + '\n') + if func_def.ret and func_def.ret != 'none': + if func_doc and func_doc.ret: + descr = func_doc.ret + else: + descr = 'a ' + func_def.ret + fp.write(' Returns: ' + descr + '\n') + if func_doc and func_doc.description: + fp.write(func_doc.description) + fp.write('\n\n\n') + def write_method(self, meth_def, func_doc, fp): + prototype = self.create_method_prototype(meth_def) + fp.write(prototype + '\n\n') + for type, name, dflt, null in meth_def.params: + if func_doc: + descr = func_doc.get_param_description(name) + else: + descr = 'a ' + type + fp.write(' ' + name + ': ' + descr + '\n') + if meth_def.ret and meth_def.ret != 'none': + if func_doc and func_doc.ret: + descr = func_doc.ret + else: + descr = 'a ' + meth_def.ret + fp.write(' Returns: ' + descr + '\n') + if func_doc and func_doc.description: + fp.write('\n') + fp.write(func_doc.description) + fp.write('\n\n') + +class DocbookDocWriter(DocWriter): + def __init__(self, use_xml=0): + DocWriter.__init__(self) + self.use_xml = use_xml + + def create_filename(self, obj_name, output_prefix): + '''Create output filename for this particular object''' + stem = output_prefix + '-' + string.lower(obj_name) + if self.use_xml: + return stem + '.xml' + else: + return stem + '.sgml' + def create_toc_filename(self, output_prefix): + if self.use_xml: + return self.create_filename('classes', output_prefix) + else: + return self.create_filename('docs', output_prefix) + + # make string -> reference translation func + __transtable = [ '-' ] * 256 + for digit in '0123456789': + __transtable[ord(digit)] = digit + for letter in 'abcdefghijklmnopqrstuvwxyz': + __transtable[ord(letter)] = letter + __transtable[ord(string.upper(letter))] = letter + __transtable = string.join(__transtable, '') + + def make_class_ref(self, obj_name): + return 'class-' + string.translate(obj_name, self.__transtable) + def make_method_ref(self, meth_def): + return 'method-' + string.translate(meth_def.of_object, + self.__transtable) + \ + '--' + string.translate(meth_def.name, self.__transtable) + + __function_pat = re.compile(r'(\w+)\s*\(\)') + def __format_function(self, match): + info = self.parser.c_name.get(match.group(1), None) + if info: + if isinstance(info, defsparser.FunctionDef): + if info.is_constructor_of is not None: + # should have a link here + return '<function>%s()</function>' % \ + self.pyname(info.is_constructor_of) + else: + return '<function>' + info.name + '()</function>' + if isinstance(info, defsparser.MethodDef): + return '<link linkend="' + self.make_method_ref(info) + \ + '"><function>' + self.pyname(info.of_object) + '.' + \ + info.name + '()</function></link>' + # fall through through + return '<function>' + match.group(1) + '()</function>' + __parameter_pat = re.compile(r'\@(\w+)') + def __format_param(self, match): + return '<parameter>' + match.group(1) + '</parameter>' + __constant_pat = re.compile(r'\%(-?\w+)') + def __format_const(self, match): + return '<literal>' + match.group(1) + '</literal>' + __symbol_pat = re.compile(r'#([\w-]+)') + def __format_symbol(self, match): + info = self.parser.c_name.get(match.group(1), None) + if info: + if isinstance(info, defsparser.FunctionDef): + if info.is_constructor_of is not None: + # should have a link here + return '<methodname>' + self.pyname(info.is_constructor_of) + \ + '</methodname>' + else: + return '<function>' + info.name + '</function>' + if isinstance(info, defsparser.MethodDef): + return '<link linkend="' + self.make_method_ref(info) + \ + '"><methodname>' + self.pyname(info.of_object) + '.' + \ + info.name + '</methodname></link>' + if isinstance(info, defsparser.ObjectDef) or \ + isinstance(info, defsparser.InterfaceDef) or \ + isinstance(info, defsparser.BoxedDef) or \ + isinstance(info, defsparser.PointerDef): + return '<link linkend="' + self.make_class_ref(info.c_name) + \ + '"><classname>' + self.pyname(info.c_name) + \ + '</classname></link>' + # fall through through + return '<literal>' + match.group(1) + '</literal>' + + def reformat_text(self, text, singleline=0): + # replace special strings ... + text = self.__function_pat.sub(self.__format_function, text) + text = self.__parameter_pat.sub(self.__format_param, text) + text = self.__constant_pat.sub(self.__format_const, text) + text = self.__symbol_pat.sub(self.__format_symbol, text) + + # don't bother with <para> expansion for single line text. + if singleline: return text + + lines = string.split(string.strip(text), '\n') + for index in range(len(lines)): + if string.strip(lines[index]) == '': + lines[index] = '</para>\n<para>' + continue + lines.insert(0, '<para>') + lines.append('</para>') + return string.join(lines, '\n') + + # write out hierarchy + def write_full_hierarchy(self, hierarchy, fp): + def handle_node(node, fp, indent=''): + if node.name: + fp.write('%s<link linkend="%s">%s</link>' % + (indent, self.make_class_ref(node.name), + self.pyname(node.name))) + if node.interfaces: + fp.write(' (implements ') + for i in range(len(node.interfaces)): + fp.write('<link linkend="%s">%s</link>' % + (self.make_class_ref(node.interfaces[i]), + self.pyname(node.interfaces[i]))) + if i != len(node.interfaces) - 1: + fp.write(', ') + fp.write(')\n') + else: + fp.write('\n') + + indent = indent + ' ' + node.subclasses.sort(lambda a,b: + cmp(self.pyname(a.name), self.pyname(b.name))) + for child in node.subclasses: + handle_node(child, fp, indent) + if self.use_xml: + fp.write('<?xml version="1.0" standalone="no"?>\n') + fp.write('<!DOCTYPE synopsis PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"\n') + fp.write(' "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd">\n') + fp.write('<synopsis>') + handle_node(hierarchy, fp) + fp.write('</synopsis>\n') + + # these need to handle default args ... + def create_constructor_prototype(self, func_def): + sgml = [ '<constructorsynopsis language="python">\n'] + sgml.append(' <methodname>__init__</methodname>\n') + for type, name, dflt, null in func_def.params: + sgml.append(' <methodparam><parameter>') + sgml.append(name) + sgml.append('</parameter>') + if dflt: + sgml.append('<initializer>') + sgml.append(dflt) + sgml.append('</initializer>') + sgml.append('</methodparam>\n') + if not func_def.params: + sgml.append(' <methodparam></methodparam>') + sgml.append(' </constructorsynopsis>') + return string.join(sgml, '') + def create_function_prototype(self, func_def): + sgml = [ '<funcsynopsis language="python">\n <funcprototype>\n'] + sgml.append(' <funcdef><function>') + sgml.append(func_def.name) + sgml.append('</function></funcdef>\n') + for type, name, dflt, null in func_def.params: + sgml.append(' <paramdef><parameter>') + sgml.append(name) + sgml.append('</parameter>') + if dflt: + sgml.append('<initializer>') + sgml.append(dflt) + sgml.append('</initializer>') + sgml.append('</paramdef>\n') + if not func_def.params: + sgml.append(' <paramdef></paramdef') + sgml.append(' </funcprototype>\n </funcsynopsis>') + return string.join(sgml, '') + def create_method_prototype(self, meth_def, addlink=0): + sgml = [ '<methodsynopsis language="python">\n'] + sgml.append(' <methodname>') + if addlink: + sgml.append('<link linkend="%s">' % self.make_method_ref(meth_def)) + sgml.append(self.pyname(meth_def.name)) + if addlink: + sgml.append('</link>') + sgml.append('</methodname>\n') + for type, name, dflt, null in meth_def.params: + sgml.append(' <methodparam><parameter>') + sgml.append(name) + sgml.append('</parameter>') + if dflt: + sgml.append('<initializer>') + sgml.append(dflt) + sgml.append('</initializer>') + sgml.append('</methodparam>\n') + if not meth_def.params: + sgml.append(' <methodparam></methodparam>') + sgml.append(' </methodsynopsis>') + return string.join(sgml, '') + + def write_class_header(self, obj_name, fp): + if self.use_xml: + fp.write('<?xml version="1.0" standalone="no"?>\n') + fp.write('<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"\n') + fp.write(' "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd">\n') + fp.write('<refentry id="' + self.make_class_ref(obj_name) + '">\n') + fp.write(' <refmeta>\n') + fp.write(' <refentrytitle>%s</refentrytitle>\n' + % self.pyname(obj_name)) + fp.write(' <manvolnum>3</manvolnum>\n') + fp.write(' <refmiscinfo>PyGTK Docs</refmiscinfo>\n') + fp.write(' </refmeta>\n\n') + fp.write(' <refnamediv>\n') + fp.write(' <refname>%s</refname><refpurpose></refpurpose>\n' + % self.pyname(obj_name)) + fp.write(' </refnamediv>\n\n') + def write_class_footer(self, obj_name, fp): + fp.write('</refentry>\n') + def write_heading(self, text, fp): + fp.write(' <refsect1>\n') + fp.write(' <title>' + text + '</title>\n\n') + def close_section(self, fp): + fp.write(' </refsect1>\n') + + def write_synopsis(self, obj_def, fp): + fp.write('<classsynopsis language="python">\n') + fp.write(' <ooclass><classname>%s</classname></ooclass>\n' + % self.pyname(obj_def.c_name)) + if isinstance(obj_def, definitions.ObjectDef): + if obj_def.parent: + fp.write(' <ooclass><classname><link linkend="%s">%s' + '</link></classname></ooclass>\n' + % (self.make_class_ref(obj_def.parent), + self.pyname(obj_def.parent))) + for base in obj_def.implements: + fp.write(' <ooclass><classname><link linkend="%s">%s' + '</link></classname></ooclass>\n' + % (self.make_class_ref(base), self.pyname(base))) + elif isinstance(obj_def, definitions.InterfaceDef): + fp.write(' <ooclass><classname>gobject.GInterface' + '</classname></ooclass>\n') + elif isinstance(obj_def, definitions.BoxedDef): + fp.write(' <ooclass><classname>gobject.GBoxed' + '</classname></ooclass>\n') + elif isinstance(obj_def, definitions.PointerDef): + fp.write(' <ooclass><classname>gobject.GPointer' + '</classname></ooclass>\n') + + constructor = self.parser.find_constructor(obj_def, self.overrides) + if constructor: + fp.write('%s\n' % self.create_constructor_prototype(constructor)) + methods = self.parser.find_methods(obj_def) + methods = filter(lambda meth, self=self: + not self.overrides.is_ignored(meth.c_name), methods) + for meth in methods: + fp.write('%s\n' % self.create_method_prototype(meth, addlink=1)) + fp.write('</classsynopsis>\n\n') + + def write_hierarchy(self, obj_name, ancestry, fp): + fp.write('<synopsis>') + indent = '' + for name, interfaces in ancestry: + fp.write(indent + '+-- <link linkend="' + + self.make_class_ref(name) + '">'+ self.pyname(name) + '</link>') + if interfaces: + fp.write(' (implements ') + for i in range(len(interfaces)): + fp.write('<link linkend="%s">%s</link>' % + (self.make_class_ref(interfaces[i]), + self.pyname(interfaces[i]))) + if i != len(interfaces) - 1: + fp.write(', ') + fp.write(')\n') + else: + fp.write('\n') + indent = indent + ' ' + fp.write('</synopsis>\n\n') + + def write_params(self, params, ret, func_doc, fp): + if not params and (not ret or ret == 'none'): + return + fp.write(' <variablelist>\n') + for type, name, dflt, null in params: + if func_doc: + descr = string.strip(func_doc.get_param_description(name)) + else: + descr = 'a ' + type + fp.write(' <varlistentry>\n') + fp.write(' <term><parameter>%s</parameter> :</term>\n' % name) + fp.write(' <listitem><simpara>%s</simpara></listitem>\n' % + self.reformat_text(descr, singleline=1)) + fp.write(' </varlistentry>\n') + if ret and ret != 'none': + if func_doc and func_doc.ret: + descr = string.strip(func_doc.ret) + else: + descr = 'a ' + ret + fp.write(' <varlistentry>\n') + fp.write(' <term><emphasis>Returns</emphasis> :</term>\n') + fp.write(' <listitem><simpara>%s</simpara></listitem>\n' % + self.reformat_text(descr, singleline=1)) + fp.write(' </varlistentry>\n') + fp.write(' </variablelist>\n') + + def write_constructor(self, func_def, func_doc, fp): + prototype = self.create_constructor_prototype(func_def) + fp.write('<programlisting>%s</programlisting>\n' % prototype) + self.write_params(func_def.params, func_def.ret, func_doc, fp) + + if func_doc and func_doc.description: + fp.write(self.reformat_text(func_doc.description)) + fp.write('\n\n\n') + + def write_method(self, meth_def, func_doc, fp): + fp.write(' <refsect2 id="' + self.make_method_ref(meth_def) + '">\n') + fp.write(' <title>' + self.pyname(meth_def.of_object) + '.' + + meth_def.name + '</title>\n\n') + prototype = self.create_method_prototype(meth_def) + fp.write('<programlisting>%s</programlisting>\n' % prototype) + self.write_params(meth_def.params, meth_def.ret, func_doc, fp) + if func_doc and func_doc.description: + fp.write(self.reformat_text(func_doc.description)) + fp.write(' </refsect2>\n\n\n') + + def output_toc(self, files, fp=sys.stdout): + if self.use_xml: + fp.write('<?xml version="1.0" standalone="no"?>\n') + fp.write('<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"\n') + fp.write(' "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd">\n') + #for filename, obj_def in files: + # fp.write(' <!ENTITY ' + string.translate(obj_def.c_name, + # self.__transtable) + + # ' SYSTEM "' + filename + '" >\n') + #fp.write(']>\n\n') + + #fp.write('<reference id="class-reference">\n') + #fp.write(' <title>Class Documentation</title>\n') + #for filename, obj_def in files: + # fp.write('&' + string.translate(obj_def.c_name, + # self.__transtable) + ';\n') + #fp.write('</reference>\n') + + fp.write('<reference id="class-reference" xmlns:xi="http://www.w3.org/2001/XInclude">\n') + fp.write(' <title>Class Reference</title>\n') + for filename, obj_def in files: + fp.write(' <xi:include href="%s"/>\n' % filename) + fp.write('</reference>\n') + else: + fp.write('<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook V4.1.2//EN" [\n') + for filename, obj_def in files: + fp.write(' <!ENTITY ' + string.translate(obj_def.c_name, + self.__transtable) + + ' SYSTEM "' + filename + '" >\n') + fp.write(']>\n\n') + + fp.write('<book id="index">\n\n') + fp.write(' <bookinfo>\n') + fp.write(' <title>PyGTK Docs</title>\n') + fp.write(' <authorgroup>\n') + fp.write(' <author>\n') + fp.write(' <firstname>James</firstname>\n') + fp.write(' <surname>Henstridge</surname>\n') + fp.write(' </author>\n') + fp.write(' </authorgroup>\n') + fp.write(' </bookinfo>\n\n') + + fp.write(' <chapter id="class-hierarchy">\n') + fp.write(' <title>Class Hierarchy</title>\n') + fp.write(' <para>Not done yet</para>\n') + fp.write(' </chapter>\n\n') + + fp.write(' <reference id="class-reference">\n') + fp.write(' <title>Class Documentation</title>\n') + for filename, obj_def in files: + fp.write('&' + string.translate(obj_def.c_name, + self.__transtable) + ';\n') + + fp.write(' </reference>\n') + fp.write('</book>\n') + +if __name__ == '__main__': + try: + opts, args = getopt.getopt(sys.argv[1:], "d:s:o:", + ["defs-file=", "override=", "source-dir=", + "output-prefix="]) + except getopt.error, e: + sys.stderr.write('docgen.py: %s\n' % e) + sys.stderr.write( + 'usage: docgen.py -d file.defs [-s /src/dir] [-o output-prefix]\n') + sys.exit(1) + defs_file = None + overrides_file = None + source_dirs = [] + output_prefix = 'docs' + for opt, arg in opts: + if opt in ('-d', '--defs-file'): + defs_file = arg + if opt in ('--override',): + overrides_file = arg + elif opt in ('-s', '--source-dir'): + source_dirs.append(arg) + elif opt in ('-o', '--output-prefix'): + output_prefix = arg + if len(args) != 0 or not defs_file: + sys.stderr.write( + 'usage: docgen.py -d file.defs [-s /src/dir] [-o output-prefix]\n') + sys.exit(1) + + d = DocbookDocWriter() + d.add_sourcedirs(source_dirs) + d.add_docs(defs_file, overrides_file, 'gtk') + d.output_docs(output_prefix) diff --git a/farstream/python/codegen/h2def.py b/farstream/python/codegen/h2def.py new file mode 100755 index 000000000..d4b21356c --- /dev/null +++ b/farstream/python/codegen/h2def.py @@ -0,0 +1,536 @@ +#!/usr/bin/env python +# -*- Mode: Python; py-indent-offset: 4 -*- +# Search through a header file looking for function prototypes. +# For each prototype, generate a scheme style definition. +# GPL'ed +# Toby D. Reeves <toby@max.rl.plh.af.mil> +# +# Modified by James Henstridge <james@daa.com.au> to output stuff in +# Havoc's new defs format. Info on this format can be seen at: +# http://www.gnome.org/mailing-lists/archives/gtk-devel-list/2000-January/0085.shtml +# Updated to be PEP-8 compatible and refactored to use OOP + +import getopt +import os +import re +import string +import sys + +import defsparser + +# ------------------ Create typecodes from typenames --------- + +_upperstr_pat1 = re.compile(r'([^A-Z])([A-Z])') +_upperstr_pat2 = re.compile(r'([A-Z][A-Z])([A-Z][0-9a-z])') +_upperstr_pat3 = re.compile(r'^([A-Z])([A-Z])') + +def to_upper_str(name): + """Converts a typename to the equivalent upercase and underscores + name. This is used to form the type conversion macros and enum/flag + name variables""" + name = _upperstr_pat1.sub(r'\1_\2', name) + name = _upperstr_pat2.sub(r'\1_\2', name) + name = _upperstr_pat3.sub(r'\1_\2', name, count=1) + return string.upper(name) + +def typecode(typename): + """create a typecode (eg. GTK_TYPE_WIDGET) from a typename""" + return string.replace(to_upper_str(typename), '_', '_TYPE_', 1) + + +# ------------------ Find object definitions ----------------- + +def strip_comments(buf): + parts = [] + lastpos = 0 + while 1: + pos = string.find(buf, '/*', lastpos) + if pos >= 0: + parts.append(buf[lastpos:pos]) + pos = string.find(buf, '*/', pos) + if pos >= 0: + lastpos = pos + 2 + else: + break + else: + parts.append(buf[lastpos:]) + break + return string.join(parts, '') + +obj_name_pat = "[A-Z][a-z]*[A-Z][A-Za-z0-9]*" + +split_prefix_pat = re.compile('([A-Z]+[a-z]*)([A-Za-z0-9]+)') + +def find_obj_defs(buf, objdefs=[]): + """ + Try to find object definitions in header files. + """ + + # filter out comments from buffer. + buf = strip_comments(buf) + + maybeobjdefs = [] # contains all possible objects from file + + # first find all structures that look like they may represent a GtkObject + pat = re.compile("struct _(" + obj_name_pat + ")\s*{\s*" + + "(" + obj_name_pat + ")\s+", re.MULTILINE) + pos = 0 + while pos < len(buf): + m = pat.search(buf, pos) + if not m: break + maybeobjdefs.append((m.group(1), m.group(2))) + pos = m.end() + + # handle typedef struct { ... } style struct defs. + pat = re.compile("typedef struct\s+[_\w]*\s*{\s*" + + "(" + obj_name_pat + ")\s+[^}]*}\s*" + + "(" + obj_name_pat + ")\s*;", re.MULTILINE) + pos = 0 + while pos < len(buf): + m = pat.search(buf, pos) + if not m: break + maybeobjdefs.append((m.group(2), m.group(2))) + pos = m.end() + + # now find all structures that look like they might represent a class: + pat = re.compile("struct _(" + obj_name_pat + ")Class\s*{\s*" + + "(" + obj_name_pat + ")Class\s+", re.MULTILINE) + pos = 0 + while pos < len(buf): + m = pat.search(buf, pos) + if not m: break + t = (m.group(1), m.group(2)) + # if we find an object structure together with a corresponding + # class structure, then we have probably found a GtkObject subclass. + if t in maybeobjdefs: + objdefs.append(t) + pos = m.end() + + pat = re.compile("typedef struct\s+[_\w]*\s*{\s*" + + "(" + obj_name_pat + ")Class\s+[^}]*}\s*" + + "(" + obj_name_pat + ")Class\s*;", re.MULTILINE) + pos = 0 + while pos < len(buf): + m = pat.search(buf, pos) + if not m: break + t = (m.group(2), m.group(1)) + # if we find an object structure together with a corresponding + # class structure, then we have probably found a GtkObject subclass. + if t in maybeobjdefs: + objdefs.append(t) + pos = m.end() + + # now find all structures that look like they might represent + # a class inherited from GTypeInterface: + pat = re.compile("struct _(" + obj_name_pat + ")Class\s*{\s*" + + "GTypeInterface\s+", re.MULTILINE) + pos = 0 + while pos < len(buf): + m = pat.search(buf, pos) + if not m: break + t = (m.group(1), '') + t2 = (m.group(1)+'Class', 'GTypeInterface') + # if we find an object structure together with a corresponding + # class structure, then we have probably found a GtkObject subclass. + if t2 in maybeobjdefs: + objdefs.append(t) + pos = m.end() + + # now find all structures that look like they might represent + # an Iface inherited from GTypeInterface: + pat = re.compile("struct _(" + obj_name_pat + ")Iface\s*{\s*" + + "GTypeInterface\s+", re.MULTILINE) + pos = 0 + while pos < len(buf): + m = pat.search(buf, pos) + if not m: break + t = (m.group(1), '') + t2 = (m.group(1)+'Iface', 'GTypeInterface') + # if we find an object structure together with a corresponding + # class structure, then we have probably found a GtkObject subclass. + if t2 in maybeobjdefs: + objdefs.append(t) + pos = m.end() + +def sort_obj_defs(objdefs): + objdefs.sort() # not strictly needed, but looks nice + pos = 0 + while pos < len(objdefs): + klass,parent = objdefs[pos] + for i in range(pos+1, len(objdefs)): + # parent below subclass ... reorder + if objdefs[i][0] == parent: + objdefs.insert(i+1, objdefs[pos]) + del objdefs[pos] + break + else: + pos = pos + 1 + return objdefs + +# ------------------ Find enum definitions ----------------- + +def find_enum_defs(buf, enums=[]): + # strip comments + # bulk comments + buf = strip_comments(buf) + + buf = re.sub('\n', ' ', buf) + + enum_pat = re.compile(r'enum\s*{([^}]*)}\s*([A-Z][A-Za-z]*)(\s|;)') + splitter = re.compile(r'\s*,\s', re.MULTILINE) + pos = 0 + while pos < len(buf): + m = enum_pat.search(buf, pos) + if not m: break + + name = m.group(2) + vals = m.group(1) + isflags = string.find(vals, '<<') >= 0 + entries = [] + for val in splitter.split(vals): + if not string.strip(val): continue + entries.append(string.split(val)[0]) + if name != 'GdkCursorType': + enums.append((name, isflags, entries)) + + pos = m.end() + +# ------------------ Find function definitions ----------------- + +def clean_func(buf): + """ + Ideally would make buf have a single prototype on each line. + Actually just cuts out a good deal of junk, but leaves lines + where a regex can figure prototypes out. + """ + # bulk comments + buf = strip_comments(buf) + + # compact continued lines + pat = re.compile(r"""\\\n""", re.MULTILINE) + buf = pat.sub('', buf) + + # Preprocess directives + pat = re.compile(r"""^[#].*?$""", re.MULTILINE) + buf = pat.sub('', buf) + + #typedefs, stucts, and enums + pat = re.compile(r"""^(typedef|struct|enum)(\s|.|\n)*?;\s*""", + re.MULTILINE) + buf = pat.sub('', buf) + + #strip DECLS macros + pat = re.compile(r"""G_BEGIN_DECLS|BEGIN_LIBGTOP_DECLS""", re.MULTILINE) + buf = pat.sub('', buf) + + #extern "C" + pat = re.compile(r"""^\s*(extern)\s+\"C\"\s+{""", re.MULTILINE) + buf = pat.sub('', buf) + + #multiple whitespace + pat = re.compile(r"""\s+""", re.MULTILINE) + buf = pat.sub(' ', buf) + + #clean up line ends + pat = re.compile(r""";\s*""", re.MULTILINE) + buf = pat.sub('\n', buf) + buf = buf.lstrip() + + #associate *, &, and [] with type instead of variable + #pat = re.compile(r'\s+([*|&]+)\s*(\w+)') + pat = re.compile(r' \s* ([*|&]+) \s* (\w+)', re.VERBOSE) + buf = pat.sub(r'\1 \2', buf) + pat = re.compile(r'\s+ (\w+) \[ \s* \]', re.VERBOSE) + buf = pat.sub(r'[] \1', buf) + + # make return types that are const work. + buf = string.replace(buf, 'G_CONST_RETURN ', 'const-') + buf = string.replace(buf, 'const ', 'const-') + + return buf + +proto_pat=re.compile(r""" +(?P<ret>(-|\w|\&|\*)+\s*) # return type +\s+ # skip whitespace +(?P<func>\w+)\s*[(] # match the function name until the opening ( +\s*(?P<args>.*?)\s*[)] # group the function arguments +""", re.IGNORECASE|re.VERBOSE) +#""" +arg_split_pat = re.compile("\s*,\s*") + +get_type_pat = re.compile(r'(const-)?([A-Za-z0-9]+)\*?\s+') +pointer_pat = re.compile('.*\*$') +func_new_pat = re.compile('(\w+)_new$') + +class DefsWriter: + def __init__(self, fp=None, prefix=None, verbose=False, + defsfilter=None): + if not fp: + fp = sys.stdout + + self.fp = fp + self.prefix = prefix + self.verbose = verbose + + self._enums = {} + self._objects = {} + self._functions = {} + if defsfilter: + filter = defsparser.DefsParser(defsfilter) + filter.startParsing() + for func in filter.functions + filter.methods.values(): + self._functions[func.c_name] = func + for obj in filter.objects + filter.boxes + filter.interfaces: + self._objects[obj.c_name] = func + for obj in filter.enums: + self._enums[obj.c_name] = func + + def write_def(self, deffile): + buf = open(deffile).read() + + self.fp.write('\n;; From %s\n\n' % os.path.basename(deffile)) + self._define_func(buf) + self.fp.write('\n') + + def write_enum_defs(self, enums, fp=None): + if not fp: + fp = self.fp + + fp.write(';; Enumerations and flags ...\n\n') + trans = string.maketrans(string.uppercase + '_', + string.lowercase + '-') + filter = self._enums + for cname, isflags, entries in enums: + if filter: + if cname in filter: + continue + name = cname + module = None + m = split_prefix_pat.match(cname) + if m: + module = m.group(1) + name = m.group(2) + if isflags: + fp.write('(define-flags ' + name + '\n') + else: + fp.write('(define-enum ' + name + '\n') + if module: + fp.write(' (in-module "' + module + '")\n') + fp.write(' (c-name "' + cname + '")\n') + fp.write(' (gtype-id "' + typecode(cname) + '")\n') + prefix = entries[0] + for ent in entries: + # shorten prefix til we get a match ... + # and handle GDK_FONT_FONT, GDK_FONT_FONTSET case + while ent[:len(prefix)] != prefix or len(prefix) >= len(ent): + prefix = prefix[:-1] + prefix_len = len(prefix) + fp.write(' (values\n') + for ent in entries: + fp.write(' \'("%s" "%s")\n' % + (string.translate(ent[prefix_len:], trans), ent)) + fp.write(' )\n') + fp.write(')\n\n') + + def write_obj_defs(self, objdefs, fp=None): + if not fp: + fp = self.fp + + fp.write(';; -*- scheme -*-\n') + fp.write('; object definitions ...\n') + + filter = self._objects + for klass, parent in objdefs: + if filter: + if klass in filter: + continue + m = split_prefix_pat.match(klass) + cmodule = None + cname = klass + if m: + cmodule = m.group(1) + cname = m.group(2) + fp.write('(define-object ' + cname + '\n') + if cmodule: + fp.write(' (in-module "' + cmodule + '")\n') + if parent: + fp.write(' (parent "' + parent + '")\n') + fp.write(' (c-name "' + klass + '")\n') + fp.write(' (gtype-id "' + typecode(klass) + '")\n') + # should do something about accessible fields + fp.write(')\n\n') + + def _define_func(self, buf): + buf = clean_func(buf) + buf = string.split(buf,'\n') + filter = self._functions + for p in buf: + if not p: + continue + m = proto_pat.match(p) + if m == None: + if self.verbose: + sys.stderr.write('No match:|%s|\n' % p) + continue + func = m.group('func') + if func[0] == '_': + continue + if filter: + if func in filter: + continue + ret = m.group('ret') + args = m.group('args') + args = arg_split_pat.split(args) + for i in range(len(args)): + spaces = string.count(args[i], ' ') + if spaces > 1: + args[i] = string.replace(args[i], ' ', '-', spaces - 1) + + self._write_func(func, ret, args) + + def _write_func(self, name, ret, args): + if len(args) >= 1: + # methods must have at least one argument + munged_name = name.replace('_', '') + m = get_type_pat.match(args[0]) + if m: + obj = m.group(2) + if munged_name[:len(obj)] == obj.lower(): + self._write_method(obj, name, ret, args) + return + + if self.prefix: + l = len(self.prefix) + if name[:l] == self.prefix and name[l] == '_': + fname = name[l+1:] + else: + fname = name + else: + fname = name + + # it is either a constructor or normal function + self.fp.write('(define-function ' + fname + '\n') + self.fp.write(' (c-name "' + name + '")\n') + + # Hmmm... Let's asume that a constructor function name + # ends with '_new' and it returns a pointer. + m = func_new_pat.match(name) + if pointer_pat.match(ret) and m: + cname = '' + for s in m.group(1).split ('_'): + cname += s.title() + if cname != '': + self.fp.write(' (is-constructor-of "' + cname + '")\n') + + self._write_return(ret) + self._write_arguments(args) + + def _write_method(self, obj, name, ret, args): + regex = string.join(map(lambda x: x+'_?', string.lower(obj)),'') + mname = re.sub(regex, '', name, 1) + if self.prefix: + l = len(self.prefix) + 1 + if mname[:l] == self.prefix and mname[l+1] == '_': + mname = mname[l+1:] + self.fp.write('(define-method ' + mname + '\n') + self.fp.write(' (of-object "' + obj + '")\n') + self.fp.write(' (c-name "' + name + '")\n') + self._write_return(ret) + self._write_arguments(args[1:]) + + def _write_return(self, ret): + if ret != 'void': + self.fp.write(' (return-type "' + ret + '")\n') + else: + self.fp.write(' (return-type "none")\n') + + def _write_arguments(self, args): + is_varargs = 0 + has_args = len(args) > 0 + for arg in args: + if arg == '...': + is_varargs = 1 + elif arg in ('void', 'void '): + has_args = 0 + if has_args: + self.fp.write(' (parameters\n') + for arg in args: + if arg != '...': + tupleArg = tuple(string.split(arg)) + if len(tupleArg) == 2: + self.fp.write(' \'("%s" "%s")\n' % tupleArg) + self.fp.write(' )\n') + if is_varargs: + self.fp.write(' (varargs #t)\n') + self.fp.write(')\n\n') + +# ------------------ Main function ----------------- + +def main(args): + verbose = False + onlyenums = False + onlyobjdefs = False + separate = False + modulename = None + defsfilter = None + opts, args = getopt.getopt(args[1:], 'vs:m:f:', + ['onlyenums', 'onlyobjdefs', + 'modulename=', 'separate=', + 'defsfilter=']) + for o, v in opts: + if o == '-v': + verbose = True + if o == '--onlyenums': + onlyenums = True + if o == '--onlyobjdefs': + onlyobjdefs = True + if o in ('-s', '--separate'): + separate = v + if o in ('-m', '--modulename'): + modulename = v + if o in ('-f', '--defsfilter'): + defsfilter = v + + if not args[0:1]: + print 'Must specify at least one input file name' + return -1 + + # read all the object definitions in + objdefs = [] + enums = [] + for filename in args: + buf = open(filename).read() + find_obj_defs(buf, objdefs) + find_enum_defs(buf, enums) + objdefs = sort_obj_defs(objdefs) + + if separate: + methods = file(separate + '.defs', 'w') + types = file(separate + '-types.defs', 'w') + + dw = DefsWriter(methods, prefix=modulename, verbose=verbose, + defsfilter=defsfilter) + dw.write_obj_defs(objdefs, types) + dw.write_enum_defs(enums, types) + print "Wrote %s-types.defs" % separate + + for filename in args: + dw.write_def(filename) + print "Wrote %s.defs" % separate + else: + dw = DefsWriter(prefix=modulename, verbose=verbose, + defsfilter=defsfilter) + + if onlyenums: + dw.write_enum_defs(enums) + elif onlyobjdefs: + dw.write_obj_defs(objdefs) + else: + dw.write_obj_defs(objdefs) + dw.write_enum_defs(enums) + + for filename in args: + dw.write_def(filename) + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/farstream/python/codegen/mergedefs.py b/farstream/python/codegen/mergedefs.py new file mode 100755 index 000000000..773e499bb --- /dev/null +++ b/farstream/python/codegen/mergedefs.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# -*- Mode: Python; py-indent-offset: 4 -*- + +import optparse + +import defsparser + +parser = optparse.OptionParser( + usage="usage: %prog [options] generated-defs old-defs") +parser.add_option("-p", "--merge-parameters", + help="Merge changes in function/methods parameter lists", + action="store_true", dest="parmerge", default=False) +(options, args) = parser.parse_args() + +if len(args) != 2: + parser.error("wrong number of arguments") + +newp = defsparser.DefsParser(args[0]) +oldp = defsparser.DefsParser(args[1]) + +newp.startParsing() +oldp.startParsing() + +newp.merge(oldp, options.parmerge) + +newp.write_defs() diff --git a/farstream/python/codegen/mkskel.py b/farstream/python/codegen/mkskel.py new file mode 100755 index 000000000..61f520bf7 --- /dev/null +++ b/farstream/python/codegen/mkskel.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# -*- Mode: Python; py-indent-offset: 4 -*- + +import sys, os, getopt + +module_init_template = \ +'/* -*- Mode: C; c-basic-offset: 4 -*- */\n' + \ +'#ifdef HAVE_CONFIG_H\n' + \ +'# include "config.h"\n' + \ +'#endif\n' + \ +'#include <Python.h>\n' + \ +'#include <pygtk.h>\n' + \ +'\n' + \ +'/* include any extra headers needed here */\n' + \ +'\n' + \ +'void %(prefix)s_register_classes(PyObject *d);\n' + \ +'extern PyMethodDef %(prefix)s_functions[];\n' + \ +'\n' + \ +'DL_EXPORT(void)\n' + \ +'init%(module)s(void)\n' + \ +'{\n' + \ +' PyObject *m, *d;\n' + \ +'\n' + \ +' /* perform any initialisation required by the library here */\n' + \ +'\n' + \ +' m = Py_InitModule("%(module)s", %(prefix)s_functions);\n' + \ +' d = PyModule_GetDict(m);\n' + \ +'\n' + \ +' init_pygtk();\n' + \ +'\n' + \ +' %(prefix)s_register_classes(d);\n' + \ +'\n' + \ +' /* add anything else to the module dictionary (such as constants) */\n' +\ +'\n' + \ +' if (PyErr_Occurred())\n' + \ +' Py_FatalError("could not initialise module %(module)s");\n' + \ +'}\n' + +override_template = \ +'/* -*- Mode: C; c-basic-offset: 4 -*- */\n' + \ +'%%%%\n' + \ +'headers\n' + \ +'/* include any required headers here */\n' + \ +'%%%%\n' + \ +'init\n' + \ +' /* include any code here that needs to be executed before the\n' + \ +' * extension classes get initialised */\n' + \ +'%%%%\n' + \ +'\n' + \ +'/* you should add appropriate ignore, ignore-glob and\n' + \ +' * override sections here */\n' + +def open_with_backup(file): + if os.path.exists(file): + try: + os.rename(file, file+'~') + except OSError: + # fail silently if we can't make a backup + pass + return open(file, 'w') + +def write_skels(fileprefix, prefix, module): + fp = open_with_backup(fileprefix+'module.c') + fp.write(module_init_template % { 'prefix': prefix, 'module': module }) + fp.close() + fp = open_with_backup(fileprefix+'.override') + fp.write(override_template % { 'prefix': prefix, 'module': module }) + fp.close() + +if __name__ == '__main__': + opts, args = getopt.getopt(sys.argv[1:], 'f:p:m:h', + ['file-prefix=', 'prefix=', 'module=', 'help']) + fileprefix = None + prefix = None + module = None + for opt, arg in opts: + if opt in ('-f', '--file-prefix'): + fileprefix = arg + elif opt in ('-p', '--prefix'): + prefix = arg + elif opt in ('-m', '--module'): + module = arg + elif opt in ('-h', '--help'): + print 'usage: mkskel.py -f fileprefix -p prefix -m module' + sys.exit(0) + if not fileprefix or not prefix or not module: + print 'usage: mkskel.py -f fileprefix -p prefix -m module' + sys.exit(1) + write_skels(fileprefix, prefix, module) diff --git a/farstream/python/codegen/override.py b/farstream/python/codegen/override.py new file mode 100644 index 000000000..2e8c6a4c3 --- /dev/null +++ b/farstream/python/codegen/override.py @@ -0,0 +1,288 @@ +# -*- Mode: Python; py-indent-offset: 4 -*- + +# this file contains code for loading up an override file. The override file +# provides implementations of functions where the code generator could not +# do its job correctly. + +import fnmatch +import os +import re +import string +import sys + +def class2cname(klass, method): + c_name = '' + for c in klass: + if c.isupper(): + c_name += '_' + c.lower() + else: + c_name += c + return c_name[1:] + '_' + method + +import_pat = re.compile(r'\s*import\s+(\S+)\.([^\s.]+)\s+as\s+(\S+)') + +class Overrides: + def __init__(self, filename=None, path=[]): + self.modulename = None + self.ignores = {} + self.glob_ignores = [] + self.type_ignores = {} + self.overrides = {} + self.overridden = {} + self.kwargs = {} + self.noargs = {} + self.onearg = {} + self.staticmethod = {} + self.classmethod = {} + self.startlines = {} + self.override_attrs = {} + self.override_slots = {} + self.headers = '' + self.body = '' + self.init = '' + self.imports = [] + self.defines = {} + self.functions = {} + self.newstyle_constructors = {} + self.path = [os.path.abspath(x) for x in path] + if filename: + self.handle_file(filename) + + def handle_file(self, filename): + oldpath = os.getcwd() + + fp = None + for path in self.path: + os.chdir(oldpath) + os.chdir(path) + try: + fp = open(filename, 'r') + break + except: + os.chdir(oldpath) + if not fp: + raise Exception, "Couldn't find file %s" % filename + + dirname = path + + if dirname != oldpath: + os.chdir(dirname) + + # read all the components of the file ... + bufs = [] + startline = 1 + lines = [] + line = fp.readline() + linenum = 1 + while line: + if line == '%%\n' or line == '%%': + if lines: + bufs.append((string.join(lines, ''), startline)) + startline = linenum + 1 + lines = [] + else: + lines.append(line) + line = fp.readline() + linenum = linenum + 1 + if lines: + bufs.append((string.join(lines, ''), startline)) + if not bufs: return + + for buf, startline in bufs: + self.__parse_override(buf, startline, filename) + + os.chdir(oldpath) + + def __parse_override(self, buffer, startline, filename): + pos = string.find(buffer, '\n') + if pos >= 0: + line = buffer[:pos] + rest = buffer[pos+1:] + else: + line = buffer ; rest = '' + words = string.split(line) + command = words[0] + if (command == 'ignore' or + command == 'ignore-' + sys.platform): + "ignore/ignore-platform [functions..]" + for func in words[1:]: + self.ignores[func] = 1 + for func in string.split(rest): + self.ignores[func] = 1 + elif (command == 'ignore-glob' or + command == 'ignore-glob-' + sys.platform): + "ignore-glob/ignore-glob-platform [globs..]" + for func in words[1:]: + self.glob_ignores.append(func) + for func in string.split(rest): + self.glob_ignores.append(func) + elif (command == 'ignore-type' or + command == 'ignore-type-' + sys.platform): + "ignore-type/ignore-type-platform [typenames..]" + for typename in words[1:]: + self.type_ignores[typename] = 1 + for typename in string.split(rest): + self.type_ignores[typename] = 1 + elif command == 'override': + "override function/method [kwargs|noargs|onearg] [staticmethod|classmethod]" + func = words[1] + if 'kwargs' in words[1:]: + self.kwargs[func] = 1 + elif 'noargs' in words[1:]: + self.noargs[func] = 1 + elif 'onearg' in words[1:]: + self.onearg[func] = True + + if 'staticmethod' in words[1:]: + self.staticmethod[func] = True + elif 'classmethod' in words[1:]: + self.classmethod[func] = True + if func in self.overrides: + raise RuntimeError("Function %s is being overridden more than once" % (func,)) + self.overrides[func] = rest + self.startlines[func] = (startline + 1, filename) + elif command == 'override-attr': + "override-slot Class.attr" + attr = words[1] + self.override_attrs[attr] = rest + self.startlines[attr] = (startline + 1, filename) + elif command == 'override-slot': + "override-slot Class.slot" + slot = words[1] + self.override_slots[slot] = rest + self.startlines[slot] = (startline + 1, filename) + elif command == 'headers': + "headers" + self.headers = '%s\n#line %d "%s"\n%s' % \ + (self.headers, startline + 1, filename, rest) + elif command == 'body': + "body" + self.body = '%s\n#line %d "%s"\n%s' % \ + (self.body, startline + 1, filename, rest) + elif command == 'init': + "init" + self.init = '%s\n#line %d "%s"\n%s' % \ + (self.init, startline + 1, filename, rest) + elif command == 'modulename': + "modulename name" + self.modulename = words[1] + elif command == 'include': + "include filename" + for filename in words[1:]: + self.handle_file(filename) + for filename in string.split(rest): + self.handle_file(filename) + elif command == 'import': + "import module1 [\n module2, \n module3 ...]" + for line in string.split(buffer, '\n'): + match = import_pat.match(line) + if match: + self.imports.append(match.groups()) + elif command == 'define': + "define funcname [kwargs|noargs|onearg] [classmethod|staticmethod]" + "define Class.method [kwargs|noargs|onearg] [classmethod|staticmethod]" + func = words[1] + klass = None + if func.find('.') != -1: + klass, func = func.split('.', 1) + + if not self.defines.has_key(klass): + self.defines[klass] = {} + self.defines[klass][func] = rest + else: + self.functions[func] = rest + + if 'kwargs' in words[1:]: + self.kwargs[func] = 1 + elif 'noargs' in words[1:]: + self.noargs[func] = 1 + elif 'onearg' in words[1:]: + self.onearg[func] = 1 + + if 'staticmethod' in words[1:]: + self.staticmethod[func] = True + elif 'classmethod' in words[1:]: + self.classmethod[func] = True + + self.startlines[func] = (startline + 1, filename) + + elif command == 'new-constructor': + "new-constructor GType" + gtype, = words[1:] + self.newstyle_constructors[gtype] = True + + def is_ignored(self, name): + if self.ignores.has_key(name): + return 1 + for glob in self.glob_ignores: + if fnmatch.fnmatchcase(name, glob): + return 1 + return 0 + + def is_type_ignored(self, name): + return name in self.type_ignores + + def is_overriden(self, name): + return self.overrides.has_key(name) + + def is_already_included(self, name): + return self.overridden.has_key(name) + + def override(self, name): + self.overridden[name] = 1 + return self.overrides[name] + + def define(self, klass, name): + self.overridden[class2cname(klass, name)] = 1 + return self.defines[klass][name] + + def function(self, name): + return self.functions[name] + + def getstartline(self, name): + return self.startlines[name] + + def wants_kwargs(self, name): + return self.kwargs.has_key(name) + + def wants_noargs(self, name): + return self.noargs.has_key(name) + + def wants_onearg(self, name): + return self.onearg.has_key(name) + + def is_staticmethod(self, name): + return self.staticmethod.has_key(name) + + def is_classmethod(self, name): + return self.classmethod.has_key(name) + + def attr_is_overriden(self, attr): + return self.override_attrs.has_key(attr) + + def attr_override(self, attr): + return self.override_attrs[attr] + + def slot_is_overriden(self, slot): + return self.override_slots.has_key(slot) + + def slot_override(self, slot): + return self.override_slots[slot] + + def get_headers(self): + return self.headers + + def get_body(self): + return self.body + + def get_init(self): + return self.init + + def get_imports(self): + return self.imports + + def get_defines_for(self, klass): + return self.defines.get(klass, {}) + + def get_functions(self): + return self.functions diff --git a/farstream/python/codegen/reversewrapper.py b/farstream/python/codegen/reversewrapper.py new file mode 100644 index 000000000..f528828ef --- /dev/null +++ b/farstream/python/codegen/reversewrapper.py @@ -0,0 +1,771 @@ +### -*- python -*- +### Code to generate "Reverse Wrappers", i.e. C->Python wrappers +### (C) 2004 Gustavo Carneiro <gjc@gnome.org> +import argtypes +import os + +DEBUG_MODE = ('PYGTK_CODEGEN_DEBUG' in os.environ) + +def join_ctype_name(ctype, name): + '''Joins a C type and a variable name into a single string''' + if ctype[-1] != '*': + return " ".join((ctype, name)) + else: + return "".join((ctype, name)) + + +class CodeSink(object): + def __init__(self): + self.indent_level = 0 # current indent level + self.indent_stack = [] # previous indent levels + + def _format_code(self, code): + assert isinstance(code, str) + l = [] + for line in code.split('\n'): + l.append(' '*self.indent_level + line) + if l[-1]: + l.append('') + return '\n'.join(l) + + def writeln(self, line=''): + raise NotImplementedError + + def indent(self, level=4): + '''Add a certain ammount of indentation to all lines written + from now on and until unindent() is called''' + self.indent_stack.append(self.indent_level) + self.indent_level += level + + def unindent(self): + '''Revert indentation level to the value before last indent() call''' + self.indent_level = self.indent_stack.pop() + + +class FileCodeSink(CodeSink): + def __init__(self, fp): + CodeSink.__init__(self) + assert isinstance(fp, file) + self.fp = fp + + def writeln(self, line=''): + self.fp.write(self._format_code(line)) + +class MemoryCodeSink(CodeSink): + def __init__(self): + CodeSink.__init__(self) + self.lines = [] + + def writeln(self, line=''): + self.lines.append(self._format_code(line)) + + def flush_to(self, sink): + assert isinstance(sink, CodeSink) + for line in self.lines: + sink.writeln(line.rstrip()) + self.lines = [] + + def flush(self): + l = [] + for line in self.lines: + l.append(self._format_code(line)) + self.lines = [] + return "".join(l) + +class ReverseWrapper(object): + '''Object that generates a C->Python wrapper''' + def __init__(self, cname, is_static=True): + assert isinstance(cname, str) + + self.cname = cname + ## function object we will call, or object whose method we will call + self.called_pyobj = None + ## name of method of self.called_pyobj we will call + self.method_name = None + self.is_static = is_static + + self.parameters = [] + self.declarations = MemoryCodeSink() + self.post_return_code = MemoryCodeSink() + self.body = MemoryCodeSink() + self.cleanup_actions = [] + self.pyargv_items = [] + self.pyargv_optional_items = [] + self.pyret_parse_items = [] # list of (format_spec, parameter) + + def set_call_target(self, called_pyobj, method_name=None): + assert called_pyobj is not None + assert self.called_pyobj is None + self.called_pyobj = called_pyobj + self.method_name = method_name + + def set_return_type(self, return_type): + assert isinstance(return_type, ReturnType) + self.return_type = return_type + + def add_parameter(self, param): + assert isinstance(param, Parameter) + self.parameters.append(param) + + def add_declaration(self, decl_code): + self.declarations.writeln(decl_code) + + def add_pyargv_item(self, variable, optional=False): + if optional: + self.pyargv_optional_items.append(variable) + else: + self.pyargv_items.append(variable) + + def add_pyret_parse_item(self, format_specifier, parameter, prepend=False): + if prepend: + self.pyret_parse_items.insert(0, (format_specifier, parameter)) + else: + self.pyret_parse_items.append((format_specifier, parameter)) + + def write_code(self, code, + cleanup=None, + failure_expression=None, + failure_cleanup=None, + failure_exception=None, + code_sink=None): + '''Add a chunk of code with cleanup and error handling + + This method is to be used by TypeHandlers when generating code + + Keywork arguments: + code -- code to add + cleanup -- code to cleanup any dynamic resources created by @code + (except in case of failure) (default None) + failure_expression -- C boolean expression to indicate + if anything failed (default None) + failure_cleanup -- code to cleanup any dynamic resources + created by @code in case of failure (default None) + failure_exception -- code to raise an exception in case of + failure (which will be immediately + printed and cleared), (default None) + code_sink -- "code sink" to use; by default, + ReverseWrapper.body is used, which writes the + main body of the wrapper, before calling the + python method. Alternatively, + ReverseWrapper.after_pyret_parse can be used, to + write code after the PyArg_ParseTuple that + parses the python method return value. + ''' + if code_sink is None: + code_sink = self.body + if code is not None: + code_sink.writeln(code) + if failure_expression is not None: + code_sink.writeln("if (%s) {" % (failure_expression,)) + code_sink.indent() + if failure_exception is None: + code_sink.writeln("if (PyErr_Occurred())") + code_sink.indent() + code_sink.writeln("PyErr_Print();") + code_sink.unindent() + else: + code_sink.writeln(failure_exception) + code_sink.writeln("PyErr_Print();") + if failure_cleanup is not None: + code_sink.writeln(failure_cleanup) + for cleanup_action in self.cleanup_actions: + code_sink.writeln(cleanup_action) + self.return_type.write_error_return() + code_sink.unindent() + code_sink.writeln("}") + if cleanup is not None: + self.cleanup_actions.insert(0, cleanup) + + def generate(self, sink): + '''Generate the code into a CodeSink object''' + assert isinstance(sink, CodeSink) + + if DEBUG_MODE: + self.declarations.writeln("/* begin declarations */") + self.body.writeln("/* begin main body */") + self.post_return_code.writeln("/* begin post-return code */") + + self.add_declaration("PyGILState_STATE __py_state;") + self.write_code(code="__py_state = pyg_gil_state_ensure();", + cleanup="pyg_gil_state_release(__py_state);") + + for param in self.parameters: + param.convert_c2py() + + assert self.called_pyobj is not None,\ + "Parameters failed to provide a target function or method." + + if self.is_static: + sink.writeln('static %s' % self.return_type.get_c_type()) + else: + sink.writeln(self.return_type.get_c_type()) + c_proto_params = map(Parameter.format_for_c_proto, self.parameters) + sink.writeln("%s(%s)\n{" % (self.cname, ", ".join(c_proto_params))) + + self.return_type.write_decl() + self.add_declaration("PyObject *py_retval;") + + ## Handle number of arguments + if self.pyargv_items: + self.add_declaration("PyObject *py_args;") + py_args = "py_args" + if self.pyargv_optional_items: + self.add_declaration("int argc = %i;" % len(self.pyargv_items)) + argc = "argc" + for arg in self.pyargv_optional_items: + self.body.writeln("if (%s)" % arg) + self.body.indent() + self.body.writeln("++argc;") + self.body.unindent() + else: + argc = str(len(self.pyargv_items)) + else: + if self.pyargv_optional_items: + self.add_declaration("PyObject *py_args;") + py_args = "py_args" + self.add_declaration("int argc = 0;") + argc = "argc" + for arg in self.pyargv_optional_items: + self.body.writeln("if (%s)" % arg) + self.body.indent() + self.body.writeln("++argc;") + self.body.unindent() + else: + py_args = "NULL" + argc = None + + self.body.writeln() + + if py_args != "NULL": + self.write_code("py_args = PyTuple_New(%s);" % argc, + cleanup="Py_DECREF(py_args);") + pos = 0 + for arg in self.pyargv_items: + try: # try to remove the Py_DECREF cleanup action, if we can + self.cleanup_actions.remove("Py_DECREF(%s);" % arg) + except ValueError: # otherwise we have to Py_INCREF.. + self.body.writeln("Py_INCREF(%s);" % arg) + self.body.writeln("PyTuple_SET_ITEM(%s, %i, %s);" % (py_args, pos, arg)) + pos += 1 + for arg in self.pyargv_optional_items: + self.body.writeln("if (%s) {" % arg) + self.body.indent() + try: # try to remove the Py_DECREF cleanup action, if we can + self.cleanup_actions.remove("Py_XDECREF(%s);" % arg) + except ValueError: # otherwise we have to Py_INCREF.. + self.body.writeln("Py_INCREF(%s);" % arg) + self.body.writeln("PyTuple_SET_ITEM(%s, %i, %s);" % (py_args, pos, arg)) + self.body.unindent() + self.body.writeln("}") + pos += 1 + + self.body.writeln() + + ## Call the python method + if self.method_name is None: + self.write_code("py_retval = PyObject_Call(%s, %s);" + % (self.called_pyobj, py_args), + cleanup="Py_DECREF(py_retval);", + failure_expression="!py_retval") + else: + self.add_declaration("PyObject *py_method;") + self.write_code("py_method = PyObject_GetAttrString(%s, \"%s\");" + % (self.called_pyobj, self.method_name), + cleanup="Py_DECREF(py_method);", + failure_expression="!py_method") + self.write_code("py_retval = PyObject_CallObject(py_method, %s);" + % (py_args,), + cleanup="Py_DECREF(py_retval);", + failure_expression="!py_retval") + + ## -- Handle the return value -- + + ## we need to check if the return_type object is prepared to cooperate with multiple return values + len_before = len(self.pyret_parse_items) + self.return_type.write_conversion() + len_after = len(self.pyret_parse_items) + assert (self.return_type.get_c_type() == 'void' + or not (len_before == len_after and len_after > 0)),\ + ("Bug in reverse wrappers: return type handler %s" + " is not prepared to cooperate multiple return values") % (type(self.return_type),) + + sink.indent() + + if len(self.pyret_parse_items) == 1: + ## if retval is one item only, pack it in a tuple so we + ## can use PyArg_ParseTuple as usual.. + self.write_code('py_retval = Py_BuildValue("(N)", py_retval);') + if len(self.pyret_parse_items) > 0: + ## Parse return values using PyArg_ParseTuple + self.write_code(code=None, failure_expression=( + '!PyArg_ParseTuple(py_retval, "%s", %s)' % ( + "".join([format for format, param in self.pyret_parse_items]), + ", ".join([param for format, param in self.pyret_parse_items])))) + + if DEBUG_MODE: + self.declarations.writeln("/* end declarations */") + self.declarations.flush_to(sink) + sink.writeln() + if DEBUG_MODE: + self.body.writeln("/* end main body */") + self.body.flush_to(sink) + sink.writeln() + if DEBUG_MODE: + self.post_return_code.writeln("/* end post-return code */") + self.post_return_code.flush_to(sink) + sink.writeln() + + for cleanup_action in self.cleanup_actions: + sink.writeln(cleanup_action) + if self.return_type.get_c_type() != 'void': + sink.writeln() + sink.writeln("return retval;") + sink.unindent() + sink.writeln("}") + +class TypeHandler(object): + def __init__(self, wrapper, **props): + assert isinstance(wrapper, ReverseWrapper) + self.wrapper = wrapper + self.props = props + +class ReturnType(TypeHandler): + + def get_c_type(self): + raise NotImplementedError + + def write_decl(self): + raise NotImplementedError + + def write_error_return(self): + '''Write "return <value>" code in case of error''' + raise NotImplementedError + + def write_conversion(self): + '''Writes code to convert Python return value in 'py_retval' + into C 'retval'. Returns a string with C boolean expression + that determines if anything went wrong. ''' + raise NotImplementedError + +class Parameter(TypeHandler): + + def __init__(self, wrapper, name, **props): + TypeHandler.__init__(self, wrapper, **props) + self.name = name + + def get_c_type(self): + raise NotImplementedError + + def convert_c2py(self): + '''Write some code before calling the Python method.''' + pass + + def format_for_c_proto(self): + return join_ctype_name(self.get_c_type(), self.name) + + +###--- +class StringParam(Parameter): + + def get_c_type(self): + return self.props.get('c_type', 'char *').replace('const-', 'const ') + + def convert_c2py(self): + if self.props.get('optional', False): + self.wrapper.add_declaration("PyObject *py_%s = NULL;" % self.name) + self.wrapper.write_code(code=("if (%s)\n" + " py_%s = PyString_FromString(%s);\n" + % (self.name, self.name, self.name)), + cleanup=("Py_XDECREF(py_%s);" % self.name)) + self.wrapper.add_pyargv_item("py_%s" % self.name, optional=True) + else: + self.wrapper.add_declaration("PyObject *py_%s;" % self.name) + self.wrapper.write_code(code=("py_%s = PyString_FromString(%s);" % + (self.name, self.name)), + cleanup=("Py_DECREF(py_%s);" % self.name), + failure_expression=("!py_%s" % self.name)) + self.wrapper.add_pyargv_item("py_%s" % self.name) + +for ctype in ('char*', 'gchar*', 'const-char*', 'char-const*', 'const-gchar*', + 'gchar-const*', 'string', 'static_string'): + argtypes.matcher.register_reverse(ctype, StringParam) +del ctype + +class StringReturn(ReturnType): + + def get_c_type(self): + return "char *" + + def write_decl(self): + self.wrapper.add_declaration("char *retval;") + + def write_error_return(self): + self.wrapper.write_code("return NULL;") + + def write_conversion(self): + self.wrapper.add_pyret_parse_item("s", "&retval", prepend=True) + self.wrapper.write_code("retval = g_strdup(retval);", code_sink=self.wrapper.post_return_code) + +for ctype in ('char*', 'gchar*'): + argtypes.matcher.register_reverse_ret(ctype, StringReturn) +del ctype + + +class VoidReturn(ReturnType): + + def get_c_type(self): + return "void" + + def write_decl(self): + pass + + def write_error_return(self): + self.wrapper.write_code("return;") + + def write_conversion(self): + self.wrapper.write_code( + code=None, + failure_expression="py_retval != Py_None", + failure_cleanup='PyErr_SetString(PyExc_TypeError, "retval should be None");') + +argtypes.matcher.register_reverse_ret('void', VoidReturn) +argtypes.matcher.register_reverse_ret('none', VoidReturn) + +class GObjectParam(Parameter): + + def get_c_type(self): + return self.props.get('c_type', 'GObject *') + + def convert_c2py(self): + self.wrapper.add_declaration("PyObject *py_%s = NULL;" % self.name) + self.wrapper.write_code(code=("if (%s)\n" + " py_%s = pygobject_new((GObject *) %s);\n" + "else {\n" + " Py_INCREF(Py_None);\n" + " py_%s = Py_None;\n" + "}" + % (self.name, self.name, self.name, self.name)), + cleanup=("Py_DECREF(py_%s);" % self.name)) + self.wrapper.add_pyargv_item("py_%s" % self.name) + +argtypes.matcher.register_reverse('GObject*', GObjectParam) + +class GObjectReturn(ReturnType): + + def get_c_type(self): + return self.props.get('c_type', 'GObject *') + + def write_decl(self): + self.wrapper.add_declaration("%s retval;" % self.get_c_type()) + + def write_error_return(self): + self.wrapper.write_code("return NULL;") + + def write_conversion(self): + self.wrapper.write_code( + code=None, + failure_expression="!PyObject_TypeCheck(py_retval, &PyGObject_Type)", + failure_exception='PyErr_SetString(PyExc_TypeError, "retval should be a GObject");') + self.wrapper.write_code("retval = (%s) pygobject_get(py_retval);" + % self.get_c_type()) + self.wrapper.write_code("g_object_ref((GObject *) retval);") + +argtypes.matcher.register_reverse_ret('GObject*', GObjectReturn) + + + +class IntParam(Parameter): + + def get_c_type(self): + return self.props.get('c_type', 'int') + + def convert_c2py(self): + self.wrapper.add_declaration("PyObject *py_%s;" % self.name) + self.wrapper.write_code(code=("py_%s = PyInt_FromLong(%s);" % + (self.name, self.name)), + cleanup=("Py_DECREF(py_%s);" % self.name)) + self.wrapper.add_pyargv_item("py_%s" % self.name) + +class IntReturn(ReturnType): + def get_c_type(self): + return self.props.get('c_type', 'int') + def write_decl(self): + self.wrapper.add_declaration("%s retval;" % self.get_c_type()) + def write_error_return(self): + self.wrapper.write_code("return -G_MAXINT;") + def write_conversion(self): + self.wrapper.add_pyret_parse_item("i", "&retval", prepend=True) + +for argtype in ('int', 'gint', 'guint', 'short', 'gshort', 'gushort', 'long', + 'glong', 'gsize', 'gssize', 'guint8', 'gint8', 'guint16', + 'gint16', 'gint32', 'GTime'): + argtypes.matcher.register_reverse(argtype, IntParam) + argtypes.matcher.register_reverse_ret(argtype, IntReturn) +del argtype + +class IntPtrParam(Parameter): + def __init__(self, wrapper, name, **props): + if "direction" not in props: + raise ValueError("cannot use int* parameter without direction") + if props["direction"] not in ("out", "inout"): + raise ValueError("cannot use int* parameter with direction '%s'" % (props["direction"],)) + Parameter.__init__(self, wrapper, name, **props) + def get_c_type(self): + return self.props.get('c_type', 'int*') + def convert_c2py(self): + if self.props["direction"] == "inout": + self.wrapper.add_declaration("PyObject *py_%s;" % self.name) + self.wrapper.write_code(code=("py_%s = PyInt_FromLong(*%s);" % + (self.name, self.name)), + cleanup=("Py_DECREF(py_%s);" % self.name)) + self.wrapper.add_pyargv_item("py_%s" % self.name) + self.wrapper.add_pyret_parse_item("i", self.name) +for argtype in ('int*', 'gint*'): + argtypes.matcher.register_reverse(argtype, IntPtrParam) +del argtype + + +class GEnumReturn(IntReturn): + def write_conversion(self): + self.wrapper.write_code( + code=None, + failure_expression=("pyg_enum_get_value(%s, py_retval, (gint *)&retval)" % + self.props['typecode'])) + +argtypes.matcher.register_reverse_ret("GEnum", GEnumReturn) + +class GEnumParam(IntParam): + def convert_c2py(self): + self.wrapper.add_declaration("PyObject *py_%s;" % self.name) + self.wrapper.write_code(code=("py_%s = pyg_enum_from_gtype(%s, %s);" % + (self.name, self.props['typecode'], self.name)), + cleanup=("Py_DECREF(py_%s);" % self.name), + failure_expression=("!py_%s" % self.name)) + self.wrapper.add_pyargv_item("py_%s" % self.name) + +argtypes.matcher.register_reverse("GEnum", GEnumParam) + +class GFlagsReturn(IntReturn): + def write_conversion(self): + self.wrapper.write_code( + code=None, + failure_expression=("pyg_flags_get_value(%s, py_retval, (gint *)&retval)" % + self.props['typecode'])) + +argtypes.matcher.register_reverse_ret("GFlags", GFlagsReturn) + +class GFlagsParam(IntParam): + def convert_c2py(self): + self.wrapper.add_declaration("PyObject *py_%s;" % self.name) + self.wrapper.write_code(code=("py_%s = pyg_flags_from_gtype(%s, %s);" % + (self.name, self.props['typecode'], self.name)), + cleanup=("Py_DECREF(py_%s);" % self.name), + failure_expression=("!py_%s" % self.name)) + self.wrapper.add_pyargv_item("py_%s" % self.name) + +argtypes.matcher.register_reverse("GFlags", GFlagsParam) + + +class GtkTreePathParam(IntParam): + def convert_c2py(self): + self.wrapper.add_declaration("PyObject *py_%s;" % self.name) + self.wrapper.write_code(code=("py_%s = pygtk_tree_path_to_pyobject(%s);" % + (self.name, self.name)), + cleanup=("Py_DECREF(py_%s);" % self.name), + failure_expression=("!py_%s" % self.name)) + self.wrapper.add_pyargv_item("py_%s" % self.name) + +argtypes.matcher.register_reverse("GtkTreePath*", GtkTreePathParam) + + +class BooleanReturn(ReturnType): + def get_c_type(self): + return "gboolean" + def write_decl(self): + self.wrapper.add_declaration("gboolean retval;") + self.wrapper.add_declaration("PyObject *py_main_retval;") + def write_error_return(self): + self.wrapper.write_code("return FALSE;") + def write_conversion(self): + self.wrapper.add_pyret_parse_item("O", "&py_main_retval", prepend=True) + self.wrapper.write_code("retval = PyObject_IsTrue(py_main_retval)? TRUE : FALSE;", + code_sink=self.wrapper.post_return_code) +argtypes.matcher.register_reverse_ret("gboolean", BooleanReturn) + +class BooleanParam(Parameter): + def get_c_type(self): + return "gboolean" + def convert_c2py(self): + self.wrapper.add_declaration("PyObject *py_%s;" % self.name) + self.wrapper.write_code("py_%s = %s? Py_True : Py_False;" + % (self.name, self.name)) + self.wrapper.add_pyargv_item("py_%s" % self.name) + +argtypes.matcher.register_reverse("gboolean", BooleanParam) + + +class DoubleParam(Parameter): + def get_c_type(self): + return self.props.get('c_type', 'gdouble') + def convert_c2py(self): + self.wrapper.add_declaration("PyObject *py_%s;" % self.name) + self.wrapper.write_code(code=("py_%s = PyFloat_FromDouble(%s);" % + (self.name, self.name)), + cleanup=("Py_DECREF(py_%s);" % self.name)) + self.wrapper.add_pyargv_item("py_%s" % self.name) + +class DoublePtrParam(Parameter): + def __init__(self, wrapper, name, **props): + if "direction" not in props: + raise ValueError("cannot use double* parameter without direction") + if props["direction"] not in ("out", ): # inout not yet implemented + raise ValueError("cannot use double* parameter with direction '%s'" % (props["direction"],)) + Parameter.__init__(self, wrapper, name, **props) + def get_c_type(self): + return self.props.get('c_type', 'double*') + def convert_c2py(self): + self.wrapper.add_pyret_parse_item("d", self.name) +for argtype in ('double*', 'gdouble*'): + argtypes.matcher.register_reverse(argtype, DoublePtrParam) +del argtype + +class DoubleReturn(ReturnType): + def get_c_type(self): + return self.props.get('c_type', 'gdouble') + def write_decl(self): + self.wrapper.add_declaration("%s retval;" % self.get_c_type()) + def write_error_return(self): + self.wrapper.write_code("return -G_MAXFLOAT;") + def write_conversion(self): + self.wrapper.write_code( + code=None, + failure_expression="!PyFloat_AsDouble(py_retval)", + failure_cleanup='PyErr_SetString(PyExc_TypeError, "retval should be a float");') + self.wrapper.write_code("retval = PyFloat_AsDouble(py_retval);") + +for argtype in ('float', 'double', 'gfloat', 'gdouble'): + argtypes.matcher.register_reverse(argtype, DoubleParam) + argtypes.matcher.register_reverse_ret(argtype, DoubleReturn) + + +class GBoxedParam(Parameter): + def get_c_type(self): + return self.props.get('c_type').replace('const-', 'const ') + def convert_c2py(self): + self.wrapper.add_declaration("PyObject *py_%s;" % self.name) + ctype = self.get_c_type() + if ctype.startswith('const '): + ctype_no_const = ctype[len('const '):] + self.wrapper.write_code( + code=('py_%s = pyg_boxed_new(%s, (%s) %s, TRUE, TRUE);' % + (self.name, self.props['typecode'], + ctype_no_const, self.name)), + cleanup=("Py_DECREF(py_%s);" % self.name)) + else: + self.wrapper.write_code( + code=('py_%s = pyg_boxed_new(%s, %s, FALSE, FALSE);' % + (self.name, self.props['typecode'], self.name)), + cleanup=("Py_DECREF(py_%s);" % self.name)) + self.wrapper.add_pyargv_item("py_%s" % self.name) + +argtypes.matcher.register_reverse("GBoxed", GBoxedParam) + +class GBoxedReturn(ReturnType): + def get_c_type(self): + return self.props.get('c_type') + def write_decl(self): + self.wrapper.add_declaration("%s retval;" % self.get_c_type()) + def write_error_return(self): + self.wrapper.write_code("return retval;") + def write_conversion(self): + self.wrapper.write_code( + failure_expression=("!pyg_boxed_check(py_retval, %s)" % + (self.props['typecode'],)), + failure_cleanup=('PyErr_SetString(PyExc_TypeError, "retval should be a %s");' + % (self.props['typename'],))) + self.wrapper.write_code('retval = pyg_boxed_get(py_retval, %s);' % + self.props['typename']) + +argtypes.matcher.register_reverse_ret("GBoxed", GBoxedReturn) + + +class GdkRectanglePtrParam(Parameter): + def get_c_type(self): + return self.props.get('c_type').replace('const-', 'const ') + def convert_c2py(self): + self.wrapper.add_declaration("PyObject *py_%s;" % self.name) + self.wrapper.write_code( + code=('py_%s = pyg_boxed_new(GDK_TYPE_RECTANGLE, %s, TRUE, TRUE);' % + (self.name, self.name)), + cleanup=("Py_DECREF(py_%s);" % self.name)) + self.wrapper.add_pyargv_item("py_%s" % self.name) + +argtypes.matcher.register_reverse("GdkRectangle*", GdkRectanglePtrParam) +argtypes.matcher.register_reverse('GtkAllocation*', GdkRectanglePtrParam) + + +class PyGObjectMethodParam(Parameter): + def __init__(self, wrapper, name, method_name, **props): + Parameter.__init__(self, wrapper, name, **props) + self.method_name = method_name + + def get_c_type(self): + return self.props.get('c_type', 'GObject *') + + def convert_c2py(self): + self.wrapper.add_declaration("PyObject *py_%s;" % self.name) + self.wrapper.write_code(code=("py_%s = pygobject_new((GObject *) %s);" % + (self.name, self.name)), + cleanup=("Py_DECREF(py_%s);" % self.name), + failure_expression=("!py_%s" % self.name)) + self.wrapper.set_call_target("py_%s" % self.name, self.method_name) + +class CallbackInUserDataParam(Parameter): + def __init__(self, wrapper, name, free_it, **props): + Parameter.__init__(self, wrapper, name, **props) + self.free_it = free_it + + def get_c_type(self): + return "gpointer" + + def convert_c2py(self): + self.wrapper.add_declaration("PyObject **_user_data;") + cleanup = self.free_it and ("g_free(%s);" % self.name) or None + self.wrapper.write_code(code=("_real_user_data = (PyObject **) %s;" + % self.name), + cleanup=cleanup) + + self.wrapper.add_declaration("PyObject *py_func;") + cleanup = self.free_it and "Py_DECREF(py_func);" or None + self.wrapper.write_code(code="py_func = _user_data[0];", + cleanup=cleanup) + self.wrapper.set_call_target("py_func") + + self.wrapper.add_declaration("PyObject *py_user_data;") + cleanup = self.free_it and "Py_XDECREF(py_user_data);" or None + self.wrapper.write_code(code="py_user_data = _user_data[1];", + cleanup=cleanup) + self.wrapper.add_pyargv_item("py_user_data", optional=True) + +def _test(): + import sys + + if 1: + wrapper = ReverseWrapper("this_is_the_c_function_name", is_static=True) + wrapper.set_return_type(StringReturn(wrapper)) + wrapper.add_parameter(PyGObjectMethodParam(wrapper, "self", method_name="do_xxx")) + wrapper.add_parameter(StringParam(wrapper, "param2", optional=True)) + wrapper.add_parameter(GObjectParam(wrapper, "param3")) + #wrapper.add_parameter(InoutIntParam(wrapper, "param4")) + wrapper.generate(FileCodeSink(sys.stderr)) + + if 0: + wrapper = ReverseWrapper("this_a_callback_wrapper") + wrapper.set_return_type(VoidReturn(wrapper)) + wrapper.add_parameter(StringParam(wrapper, "param1", optional=False)) + wrapper.add_parameter(GObjectParam(wrapper, "param2")) + wrapper.add_parameter(CallbackInUserDataParam(wrapper, "data", free_it=True)) + wrapper.generate(FileCodeSink(sys.stderr)) + +if __name__ == '__main__': + _test() diff --git a/farstream/python/codegen/scmexpr.py b/farstream/python/codegen/scmexpr.py new file mode 100644 index 000000000..d08c517ad --- /dev/null +++ b/farstream/python/codegen/scmexpr.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python +# -*- Mode: Python; py-indent-offset: 4 -*- +from __future__ import generators + +import string +import types +from cStringIO import StringIO + +class error(Exception): + def __init__(self, filename, lineno, msg): + Exception.__init__(self, msg) + self.filename = filename + self.lineno = lineno + self.msg = msg + def __str__(self): + return '%s:%d: error: %s' % (self.filename, self.lineno, self.msg) + +trans = [' '] * 256 +for i in range(256): + if chr(i) in string.letters + string.digits + '_': + trans[i] = chr(i) + else: + trans[i] = '_' +trans = string.join(trans, '') + +def parse(filename): + if isinstance(filename, str): + fp = open(filename, 'r') + else: # if not string, assume it is some kind of iterator + fp = filename + filename = getattr(fp, 'name', '<unknown>') + whitespace = ' \t\n\r\x0b\x0c' + nonsymbol = whitespace + '();\'"' + stack = [] + openlines = [] + lineno = 0 + for line in fp: + pos = 0 + lineno += 1 + while pos < len(line): + if line[pos] in whitespace: # ignore whitespace + pass + elif line[pos] == ';': # comment + break + elif line[pos:pos+2] == "'(": + pass # the open parenthesis will be handled next iteration + elif line[pos] == '(': + stack.append(()) + openlines.append(lineno) + elif line[pos] == ')': + if len(stack) == 0: + raise error(filename, lineno, 'close parenthesis found when none open') + closed = stack[-1] + del stack[-1] + del openlines[-1] + if stack: + stack[-1] += (closed,) + else: + yield closed + elif line[pos] == '"': # quoted string + if not stack: + raise error(filename, lineno, + 'string found outside of s-expression') + endpos = pos + 1 + chars = [] + while endpos < len(line): + if endpos+1 < len(line) and line[endpos] == '\\': + endpos += 1 + if line[endpos] == 'n': + chars.append('\n') + elif line[endpos] == 'r': + chars.append('\r') + elif line[endpos] == 't': + chars.append('\t') + else: + chars.append('\\') + chars.append(line[endpos]) + elif line[endpos] == '"': + break + else: + chars.append(line[endpos]) + endpos += 1 + if endpos >= len(line): + raise error(filename, lineno, "unclosed quoted string") + pos = endpos + stack[-1] += (''.join(chars),) + else: # symbol/number + if not stack: + raise error(filename, lineno, + 'identifier found outside of s-expression') + endpos = pos + while endpos < len(line) and line[endpos] not in nonsymbol: + endpos += 1 + symbol = line[pos:endpos] + pos = max(pos, endpos-1) + try: symbol = int(symbol) + except ValueError: + try: symbol = float(symbol) + except ValueError: pass + stack[-1] += (symbol,) + pos += 1 + if len(stack) != 0: + msg = '%d unclosed parentheses found at end of ' \ + 'file (opened on line(s) %s)' % (len(stack), + ', '.join(map(str, openlines))) + raise error(filename, lineno, msg) + +class Parser: + def __init__(self, filename): + """Argument is either a string, a parse tree, or file object""" + self.filename = filename + def startParsing(self, filename=None): + statements = parse(filename or self.filename) + for statement in statements: + self.handle(statement) + def handle(self, tup): + cmd = string.translate(tup[0], trans) + if hasattr(self, cmd): + getattr(self, cmd)(*tup[1:]) + else: + self.unknown(tup) + def unknown(self, tup): + pass + +_testString = """; a scheme file +(define-func gdk_font_load ; a comment at end of line + GdkFont + ((string name))) + +(define-boxed GdkEvent + gdk_event_copy + gdk_event_free + "sizeof(GdkEvent)") +""" + +if __name__ == '__main__': + import sys + if sys.argv[1:]: + fp = open(sys.argv[1]) + else: + fp = StringIO(_testString) + statements = parse(fp) + for s in statements: + print `s` diff --git a/farstream/python/common.h b/farstream/python/common.h new file mode 100644 index 000000000..c2ffeb480 --- /dev/null +++ b/farstream/python/common.h @@ -0,0 +1,79 @@ +/* -*- Mode: C; ; c-file-style: "python" -*- */ +/* gst-python + * Copyright (C) 2004 Johan Dahlin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Johan Dahlin <johan@gnome.org> + */ +#ifndef __COMMON_H__ +#define __COMMON_H__ + +#include <Python.h> +#include <pygobject.h> +#include <glib.h> +#include <glib-object.h> +#include <gst/gst.h> + +#include <pygobject.h> +#include "pygstminiobject.h" + +#if (defined HAVE_OLD_PYGTK && (PY_VERSION_HEX < 0x02030000)) + typedef destructor freefunc; +#endif + +#if PY_VERSION_HEX < 0x02050000 +#define lenfunc inquiry +#define ssizeargfunc intargfunc +#define ssizessizeargfunc intintargfunc +#define ssizeobjargproc intobjargproc +#define ssizessizeobjargproc intintobjargproc +#endif + +typedef struct { + PyGObject *pad; + GClosure *link_function; + GClosure *event_function; + GClosure *chain_function; + GClosure *get_function; + GClosure *getcaps_function; + GClosure *setcaps_function; + GClosure *activate_function; + GClosure *activatepull_function; + GClosure *activatepush_function; + /* Query is not implemented as a closure to avoid refcounting + * making the query immutable and therefore useless */ + PyObject *query_function; +} PyGstPadPrivate; + +typedef struct { + PyObject *func, *data; +} PyGstCustomNotify; + +typedef struct { + PyObject_HEAD + GstIterator *iter; +} PyGstIterator; + +extern PyTypeObject PyGstIterator_Type; + + +/* from gst-types.c */ +GstCaps *pygst_caps_from_pyobject (PyObject *object, gboolean *copy); +PyObject* pygst_iterator_new(GstIterator *iter); + + +#endif /* __COMMON_H__ */ diff --git a/farstream/python/examples/Makefile.am b/farstream/python/examples/Makefile.am new file mode 100644 index 000000000..7b89027c2 --- /dev/null +++ b/farstream/python/examples/Makefile.am @@ -0,0 +1,7 @@ +EXTRA_DIST = \ + README \ + callchannel.py \ + callhandler.py \ + callui.py \ + constants.py \ + util.py diff --git a/farstream/python/examples/README b/farstream/python/examples/README new file mode 100644 index 000000000..8007df6b5 --- /dev/null +++ b/farstream/python/examples/README @@ -0,0 +1,5 @@ +Simple python example using telepathy-farstream in most minimal way possible. +Two programs are included: + +callui.py: Doesn't do anything with tp-fs, but allows the start of a Call call +callhandler.py: Simple handler that handles calls and handles the media diff --git a/farstream/python/examples/callchannel.py b/farstream/python/examples/callchannel.py new file mode 100644 index 000000000..f37c7243b --- /dev/null +++ b/farstream/python/examples/callchannel.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python +# +# callchannel.py +# Copyright (C) 2008-2010 Collabora Ltd. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import dbus +import dbus.glib +import gobject +import sys +from glib import GError + +import pygst +pygst.require("0.10") +import gst + +import tpfarstream +import farstream +from util import * +import gc + +from telepathy.client.channel import Channel +from telepathy.constants import ( + CONNECTION_HANDLE_TYPE_NONE, CONNECTION_HANDLE_TYPE_CONTACT, + CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_DISCONNECTED, + MEDIA_STREAM_STATE_CONNECTED + ) +from telepathy.interfaces import ( + CHANNEL_INTERFACE, CONN_INTERFACE, + CONNECTION_INTERFACE_REQUESTS, + CONNECTION_INTERFACE_CONTACT_CAPABILITIES, + CLIENT) + +from constants import * + +class CallChannel: + def __init__ (self, bus, connection, object_path, properties): + self.bus = bus + self.conn = connection + self.tfchannel = None + + self.obj = self.bus.get_object (self.conn.service_name, object_path) + self.obj.connect_to_signal ("CallStateChanged", + self.state_changed_cb, dbus_interface=CHANNEL_TYPE_CALL) + + self.pipeline = gst.Pipeline() + self.pipeline.get_bus().add_watch(self.async_handler) + + self.notifier = notifier = farstream.ElementAddedNotifier() + notifier.set_properties_from_file("element-properties") + notifier.add(self.pipeline) + + tpfarstream.tf_channel_new_async (connection.service_name, + connection.object_path, object_path, self.tpfs_created) + + def state_changed_cb(self, state, flags, reason, details): + print "* StateChanged:\n State: %s (%d)\n Flags: %s" % ( + call_state_to_s (state), state, call_flags_to_s (flags)) + + print "\tReason: actor: %d reason: %d dbus_reason: '%s'" % ( + reason[0], reason[1], reason[2]) + + print '\tDetails:' + for key, value in details.iteritems(): + print "\t %s: %s" % (key, value) + else: + print '\t None' + + if state == CALL_STATE_ENDED: + self.close() + + def accept (self): + self.obj.Accept(dbus_interface=CHANNEL_TYPE_CALL) + + def close (self): + print "Closing the channel" + # close and cleanup + self.obj.Close(dbus_interface=CHANNEL_INTERFACE) + + self.pipeline.set_state (gst.STATE_NULL) + self.pipeline = None + + self.tfchannel = None + self.notifier = None + + def async_handler (self, bus, message): + if self.tfchannel != None: + self.tfchannel.bus_message(message) + return True + + self.pipeline = gst.Pipeline() + + def tpfs_created (self, source, result): + tfchannel = self.tfchannel = source.new_finish(result) + tfchannel.connect ("fs-conference-added", self.conference_added) + tfchannel.connect ("content-added", self.content_added) + + + def src_pad_added (self, content, handle, stream, pad, codec): + type = content.get_property ("media-type") + if type == farstream.MEDIA_TYPE_AUDIO: + sink = gst.parse_bin_from_description("audioconvert ! audioresample ! audioconvert ! autoaudiosink", True) + elif type == farstream.MEDIA_TYPE_VIDEO: + sink = gst.parse_bin_from_description("ffmpegcolorspace ! videoscale ! autovideosink", True) + + self.pipeline.add(sink) + pad.link(sink.get_pad("sink")) + sink.set_state(gst.STATE_PLAYING) + + def get_codec_config (self, media_type): + if media_type == farstream.MEDIA_TYPE_VIDEO: + codecs = [ farstream.Codec(farstream.CODEC_ID_ANY, "H264", + farstream.MEDIA_TYPE_VIDEO, 0) ] + if self.conn.GetProtocol() == "sip" : + codecs += [ farstream.Codec(farstream.CODEC_ID_DISABLE, "THEORA", + farstream.MEDIA_TYPE_VIDEO, 0) ] + else: + codecs += [ farstream.Codec(farstream.CODEC_ID_ANY, "THEORA", + farstream.MEDIA_TYPE_VIDEO, 0) ] + codecs += [ + farstream.Codec(farstream.CODEC_ID_ANY, "H263", + farstream.MEDIA_TYPE_VIDEO, 0), + farstream.Codec(farstream.CODEC_ID_DISABLE, "DV", + farstream.MEDIA_TYPE_VIDEO, 0), + farstream.Codec(farstream.CODEC_ID_ANY, "JPEG", + farstream.MEDIA_TYPE_VIDEO, 0), + farstream.Codec(farstream.CODEC_ID_ANY, "MPV", + farstream.MEDIA_TYPE_VIDEO, 0), + ] + + else: + codecs = [ + farstream.Codec(farstream.CODEC_ID_ANY, "SPEEX", + farstream.MEDIA_TYPE_AUDIO, 16000 ), + farstream.Codec(farstream.CODEC_ID_ANY, "SPEEX", + farstream.MEDIA_TYPE_AUDIO, 8000 ) + ] + return codecs + + def content_added(self, channel, content): + sinkpad = content.get_property ("sink-pad") + + mtype = content.get_property ("media-type") + prefs = self.get_codec_config (mtype) + if prefs != None: + try: + content.set_codec_preferences(prefs) + except GError, e: + print e.message + + content.connect ("src-pad-added", self.src_pad_added) + + if mtype == farstream.MEDIA_TYPE_AUDIO: + src = gst.parse_bin_from_description("audiotestsrc is-live=1 ! " \ + "queue", True) + elif mtype == farstream.MEDIA_TYPE_VIDEO: + src = gst.parse_bin_from_description("videotestsrc is-live=1 ! " \ + "capsfilter caps=video/x-raw-yuv,width=320,height=240", True) + + self.pipeline.add(src) + src.get_pad("src").link(sinkpad) + src.set_state(gst.STATE_PLAYING) + + def conference_added (self, channel, conference): + self.pipeline.add(conference) + self.pipeline.set_state(gst.STATE_PLAYING) + diff --git a/farstream/python/examples/callhandler.py b/farstream/python/examples/callhandler.py new file mode 100644 index 000000000..71af24cd9 --- /dev/null +++ b/farstream/python/examples/callhandler.py @@ -0,0 +1,116 @@ +# callhandler.py +# Copyright (C) 2008-2010 Collabora Ltd. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gobject +# Need gio so GAsyncInitialbe is known +import gio + +import dbus +from dbus.mainloop.glib import DBusGMainLoop +DBusGMainLoop(set_as_default=True) + +from constants import * +from telepathy.interfaces import CHANNEL_INTERFACE, CLIENT, CLIENT_HANDLER +from telepathy.constants import CONNECTION_HANDLE_TYPE_CONTACT, CONNECTION_HANDLE_TYPE_ROOM +import telepathy + +from callchannel import CallChannel + +class CallHandler(dbus.service.Object, telepathy.server.DBusProperties): + def __init__(self, bus, bus_name = None): + self.bus = bus + if bus_name == None: + self.bus_name = "org.freedesktop.Telepathy.Client.CallDemo" \ + + bus.get_unique_name().replace(":", "_").replace(".","_") + else: + self.bus_name = bus_name + self.path = "/" + self.bus_name.replace(".", "/") + self._interfaces = set([CLIENT, CLIENT_HANDLER]) + self._prop_getters = {} + self._prop_setters = {} + + dbus.service.Object.__init__(self, bus, self.path) + telepathy.server.DBusProperties.__init__(self) + + self._name = dbus.service.BusName (self.bus_name, bus) + + self._implement_property_get (CLIENT, + { "Interfaces": self._get_interfaces } ) + self._implement_property_get (CLIENT_HANDLER, + { "HandlerChannelFilter": self._get_filters } ) + self._implement_property_get (CLIENT_HANDLER, + { "Capabilities": self._get_capabilities } ) + + def _get_interfaces(self): + return dbus.Array(self._interfaces, signature='s') + + def _get_filters(self): + return dbus.Array ([ + { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL, + CHANNEL_INTERFACE + ".TargetHandleType": + CONNECTION_HANDLE_TYPE_CONTACT, + CALL_INITIAL_AUDIO: True, + }, + { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL, + CHANNEL_INTERFACE + ".TargetHandleType": + CONNECTION_HANDLE_TYPE_CONTACT, + CALL_INITIAL_VIDEO: True, + }, + { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL, + CHANNEL_INTERFACE + ".TargetHandleType": + CONNECTION_HANDLE_TYPE_ROOM, + CALL_INITIAL_AUDIO: True, + }, + { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL, + CHANNEL_INTERFACE + ".TargetHandleType": + CONNECTION_HANDLE_TYPE_ROOM, + CALL_INITIAL_VIDEO: True, + } + ], + signature='a{sv}') + + def _get_capabilities(self): + return dbus.Array ([ + CHANNEL_TYPE_CALL + '/gtalk-p2p', + CHANNEL_TYPE_CALL + '/ice-udp', + CHANNEL_TYPE_CALL + '/video/h264', + ], signature='s') + + def do_handle_call_channel (self, requests, bus, conn, channel, properties): + cchannel = CallChannel(self.bus, conn, channel, properties) + cchannel.accept() + + @dbus.service.method(dbus_interface=CLIENT_HANDLER, + in_signature='ooa(oa{sv})aota{sv}', + async_callbacks= ('_success', '_error')) + def HandleChannels(self, account, connection, channels, + requests, time, info, _success, _error): + + conn = telepathy.client.Connection (connection[1:].replace('/','.'), + connection) + # Assume there can be only one + (channel, properties) = channels[0] + + _success() + self.do_handle_call_channel (requests, + self.bus, conn, channel, properties); + +if __name__ == '__main__': + gobject.threads_init() + loop = gobject.MainLoop() + CallHandler(dbus.SessionBus()) + loop.run() diff --git a/farstream/python/examples/callui.py b/farstream/python/examples/callui.py new file mode 100644 index 000000000..9e7558f6c --- /dev/null +++ b/farstream/python/examples/callui.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python +# +# callui.py +# Copyright (C) 2008-2010 Collabora Ltd. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gobject +gobject.threads_init() + +import pygtk +import gtk + +gtk.gdk.threads_init() + +import dbus +from dbus.mainloop.glib import DBusGMainLoop +DBusGMainLoop(set_as_default=True) + +import sys +import time + +from telepathy.interfaces import * +from telepathy.constants import * + +from constants import * + +class CallChannelRequest: + def __init__ (self, bus, account_path, contact, + preferred_handler = "", audio = True, video = False, + calltype = HANDLE_TYPE_CONTACT): + self.bus = bus + self.cd = bus.get_object (CHANNEL_DISPATCHER, + '/' + CHANNEL_DISPATCHER.replace('.', '/')) + + props = { + CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL, + CHANNEL_INTERFACE + ".TargetHandleType": calltype, + CHANNEL_INTERFACE + ".TargetID": contact, + } + + if audio: + props[CHANNEL_TYPE_CALL + ".InitialAudio"] = True + if video: + props[CHANNEL_TYPE_CALL + ".InitialVideo"] = True + + self.request_path = req_path = self.cd.CreateChannel(account_path, + props, + 0, + preferred_handler, + dbus_interface = CHANNEL_DISPATCHER) + + self.req = self.bus.get_object (CHANNEL_DISPATCHER, req_path) + self.req.connect_to_signal("Failed", self.req_failed) + self.req.connect_to_signal("Succeeded", self.req_succeeded) + self.req.Proceed(dbus_interface = CHANNEL_REQUEST) + + def req_failed(self, error, message): + print "FAILURE: %s (%s)"% (error, message) + + def req_succeeded(self): + pass + +class Account: + CALL_CLASS = { + CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_CALL, + CHANNEL_INTERFACE + '.TargetHandleType': HANDLE_TYPE_CONTACT + } + + def __init__(self, bus, path): + self.bus = bus + self.path = path + self.obj = bus.get_object (ACCOUNT_MANAGER, path) + self.properties = self.obj.GetAll (ACCOUNT, + dbus_interface=dbus.PROPERTIES_IFACE) + + def get_path(self): + return self.path + + def name(self): + return self.properties["DisplayName"] + + def has_connection(self): + return self.properties["Connection"] != "/" + + def get_contacts(self): + path = self.properties["Connection"] + if path == "/": + return [] + + conn = self.bus.get_object (path[1:].replace("/","."), path) + yours, channel, properties = conn.EnsureChannel ( + { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CONTACT_LIST, + CHANNEL_INTERFACE + ".TargetHandleType": HANDLE_TYPE_LIST, + CHANNEL_INTERFACE + ".TargetID": "subscribe" + }, + dbus_interface = CONNECTION_INTERFACE_REQUESTS + ) + + subscribe = self.bus.get_object (conn.bus_name, channel) + members = subscribe.Get(CHANNEL_INTERFACE_GROUP, "Members", + dbus_interface = dbus.PROPERTIES_IFACE) + + caps = conn.GetContactCapabilities (members, + dbus_interface = CONNECTION_INTERFACE_CONTACT_CAPABILITIES) + members = caps.keys() + + for k, v in caps.iteritems(): + for c in v: + if c[0][CHANNEL_TYPE] == CHANNEL_TYPE_CALL: + break + else: + members.remove (k) + + attributes = conn.GetContactAttributes ( + dbus.Array(members, signature="u"), + dbus.Array([], signature="s"), + True) + + return map (lambda v: v[CONNECTION + "/contact-id"], + attributes.itervalues()) + + def supports_calls(self): + path = self.properties["Connection"] + if path == "/": + return False + + conn = self.bus.get_object (path[1:].replace("/","."), path) + classes = conn.Get (CONNECTION_INTERFACE_REQUESTS, + 'RequestableChannelClasses', dbus_interface=dbus.PROPERTIES_IFACE) + + return len ([c for c in classes if c[0] == self.CALL_CLASS]) > 0 + +class UI(gtk.Window): + WIDTH=240 + HEIGHT=-1 + def __init__ (self, bus): + gtk.Window.__init__(self) + self.connect('destroy', lambda x: gtk.main_quit()) + self.set_resizable(False) + self.set_size_request(self.WIDTH, self.HEIGHT) + + vbox = gtk.VBox(False, 3) + self.add(vbox) + + # call type combo box + self.type_store = gtk.ListStore ( + gobject.TYPE_STRING, + gobject.TYPE_UINT) + + self.type_store.append (("1-to-1", CONNECTION_HANDLE_TYPE_CONTACT)) + self.type_store.append (("Conference", + CONNECTION_HANDLE_TYPE_ROOM)) + + self.type_combo = combobox = gtk.ComboBox (self.type_store) + vbox.pack_start(combobox, False) + + renderer = gtk.CellRendererText() + combobox.pack_start(renderer, True) + combobox.set_attributes(renderer, text=0) + combobox.set_active (0) + + # account combo box + self.store = gtk.ListStore (gobject.TYPE_STRING, + gobject.TYPE_BOOLEAN, + gobject.TYPE_PYOBJECT) + self.store.set_sort_func(0, + (lambda m, i0, i1: + { True: -1, False: 1}[m.get(i0, 0) < m.get(i1, 0)] )) + self.store.set_sort_column_id(0, gtk.SORT_ASCENDING) + + f = self.store.filter_new() + f.set_visible_func(self.filter_visible) + self.account_combo = combobox = gtk.ComboBox(f) + vbox.pack_start(combobox, False) + + renderer = gtk.CellRendererText() + combobox.pack_start(renderer, True) + combobox.set_attributes(renderer, text=0) + combobox.connect('changed', self.account_selected) + + # contact entry box + self.contact_store = gtk.ListStore(gobject.TYPE_STRING) + + completion = gtk.EntryCompletion () + completion.set_model(self.contact_store) + completion.set_text_column(0) + + self.contact_store.set_sort_func(0, self.contact_sort) + self.contact_store.set_sort_column_id(0, gtk.SORT_ASCENDING) + + self.contact_combo = combobox = gtk.ComboBoxEntry(self.contact_store) + combobox.get_child().set_completion(completion) + + vbox.pack_start(combobox, False) + + bbox = gtk.HButtonBox() + bbox.set_layout(gtk.BUTTONBOX_END) + vbox.pack_start(bbox, True, False, 3) + + call = gtk.Button("Audio call") + call.connect("clicked", self.start_call) + bbox.add(call) + + call = gtk.Button("Video call") + call.connect("clicked", + lambda button: self.start_call(button, video=True)) + bbox.add(call) + + self.show_all() + + self.bus = bus + self.account_mgr = bus.get_object (ACCOUNT_MANAGER, + '/' + ACCOUNT_MANAGER.replace('.', '/')) + self.get_accounts() + + def start_call(self, button, audio=True, video=False): + i = self.type_combo.get_active_iter() + (calltype, ) = self.type_combo.get_model().get(i, 1) + + i = self.account_combo.get_active_iter() + (account, ) = self.account_combo.get_model().get(i, 2) + + contact = self.contact_combo.get_active_text().strip() + + print "* starting %s call" % ('video' if video else 'audio') + CallChannelRequest (self.bus, account.path, contact, + audio=audio, video=video, calltype=calltype) + + def contact_sort (self, model, i0, i1): + if model.get(i0, 0)[0] < model.get(i1, 0)[0]: + return -1 + else: + return 0 + + def filter_visible(self, model, titer): + return model.get(titer, 1)[0] + + def account_selected (self, combobox): + iter = combobox.get_active_iter() + if iter == None: + return None + + (account,) = combobox.get_model().get(iter, 2) + + self.contact_store.clear() + + map(lambda x: self.contact_store.insert (0, (x,)), + account.get_contacts()) + + def bail (self, *args): + print "BAILING" + print args + gtk.main_quit() + + def got_accounts(self, accounts): + for x in accounts: + a = Account(self.bus, x) + if a.supports_calls(): + self.store.insert(0, (a.name(), a.has_connection(), a)) + self.account_combo.set_active(0) + + def get_accounts (self): + self.account_mgr.Get(ACCOUNT_MANAGER, "ValidAccounts", + dbus_interface = dbus.PROPERTIES_IFACE, + reply_handler = self.got_accounts, + error_handler = self.bail) + +if __name__ == '__main__': + bus = dbus.SessionBus() + + UI(bus) + gtk.main() diff --git a/farstream/python/examples/constants.py b/farstream/python/examples/constants.py new file mode 100644 index 000000000..0def268d8 --- /dev/null +++ b/farstream/python/examples/constants.py @@ -0,0 +1,66 @@ +# constants.py +# Copyright (C) 2008-2010 Collabora Ltd. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +from telepathy.interfaces import CHANNEL_INTERFACE + +CHANNEL = CHANNEL_INTERFACE + +CHANNEL_TYPE = CHANNEL + ".ChannelType" +CHANNEL_TYPE_CALL = CHANNEL + ".Type.Call.DRAFT" +CALL_INITIAL_AUDIO = CHANNEL_TYPE_CALL + '.InitialAudio' +CALL_INITIAL_VIDEO = CHANNEL_TYPE_CALL + '.InitialVideo' +CALL_MUTABLE_CONTENTS = CHANNEL_TYPE_CALL + '.MutableContents' + +CALL_CONTENT = 'org.freedesktop.Telepathy.Call.Content.DRAFT' +CALL_CONTENT_IFACE_MEDIA = \ + 'org.freedesktop.Telepathy.Call.Content.Interface.Media.DRAFT' + +CALL_CONTENT_CODECOFFER = \ + 'org.freedesktop.Telepathy.Call.Content.CodecOffer.DRAFT' + +CALL_STREAM = 'org.freedesktop.Telepathy.Call.Stream.DRAFT' +CALL_STREAM_IFACE_MEDIA = \ + 'org.freedesktop.Telepathy.Call.Stream.Interface.Media.DRAFT' + +CALL_STREAM_ENDPOINT = 'org.freedesktop.Telepathy.Call.Stream.Endpoint.DRAFT' + +STREAM_TRANSPORT_RAW_UDP = 1 +STREAM_TRANSPORT_ICE_UDP = 2 +STREAM_TRANSPORT_GTALK_P2P = 3 +STREAM_TRANSPORT_WLM_2009 = 4 +STREAM_TRANSPORT_SHM = 5 +STREAM_TRANSPORT_MULTICAST = 6 +STREAM_TRANSPOR_DUMMY = 0xff + +CALL_STATE_UNKNOWN = 0 +CALL_STATE_PENDING_INITIATOR = 1 +CALL_STATE_PENDING_RECEIVER = 2 +CALL_STATE_ACCEPTED = 3 +CALL_STATE_ENDED = 4 + +CALL_FLAG_LOCALLY_RINGING = 1 +CALL_FLAG_QUEUED = 2 +CALL_FLAG_LOCALLY_HELD = 4 +CALL_FLAG_FORWARDED = 8 +CALL_FLAG_IN_PROGRESS = 16 +CALL_FLAG_CLEARING = 32 + +CALL_STATE_CHANGE_REASON_UNKNOWN = 0 +CALL_STATE_CHANGE_REASON_REQUESTED = 1 + +CONTENT_PACKETIZATION_RTP = 0 +CONTENT_PACKETIZATION_RAW = 1 diff --git a/farstream/python/examples/element-properties b/farstream/python/examples/element-properties new file mode 100644 index 000000000..40f706d6e --- /dev/null +++ b/farstream/python/examples/element-properties @@ -0,0 +1,62 @@ +# Put the desired properties in the style of +# +# [element name] +# prop1=val1 + +[gstrtpbin] +latency=100 + +[x264enc] +byte-stream=1 +bframes=0 +b-adapt=0 +cabac=0 +dct8x8=0 +bitrate=256 +# tuned for zero latency +tune=0x4 +profile=1 +speed-preset=3 +sliced-threads=false + +[ffenc_h263] +rtp-payload-size=1 + +[theoraenc] +bitrate=256 + +[vp8enc] +bitrate=256000 +max-latency=1 +speed=2 +error-resilient=true + +# Work around bug in the re-timestamp slaving method in +# GStreamer (2 is skew) +[alsasrc] +slave-method=2 + +[osssrc] +slave-method=2 + +[oss4src] +slave-method=2 + +[sunaudiosrc] +slave-method=2 + +[rtph264pay] +config-interval=5 + +[rtppcmupay] +ptime-multiple=20000000 + +[rtppcmapay] +ptime-multiple=20000000 + +[gstrtpjitterbuffer] +do-lost=1 + +[ewh264enc] +profile=baseline +quality=5 diff --git a/farstream/python/examples/util.py b/farstream/python/examples/util.py new file mode 100644 index 000000000..bbad9c852 --- /dev/null +++ b/farstream/python/examples/util.py @@ -0,0 +1,40 @@ +# util.py +# Copyright (C) 2008-2010 Collabora Ltd. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +from constants import * + +def call_state_to_s(state): + return { + CALL_STATE_UNKNOWN: 'Unknown', + CALL_STATE_PENDING_INITIATOR: 'Pending Initiator', + CALL_STATE_PENDING_RECEIVER: 'Pending Receiver', + CALL_STATE_ACCEPTED: 'Accepted', + CALL_STATE_ENDED: 'Ended' + }[state] + +def call_flags_to_s(flags): + flag_strs = { + CALL_FLAG_LOCALLY_RINGING: 'Locally Ringing', + CALL_FLAG_QUEUED: 'Queued', + CALL_FLAG_LOCALLY_HELD: 'Locally Held', + CALL_FLAG_FORWARDED: 'Forwarded', + CALL_FLAG_IN_PROGRESS: 'In Progress', + CALL_FLAG_CLEARING: 'Clearing' + } + + return ' | '.join([ '%s (%d)' % (flag_strs[i], i) + for i in flag_strs.keys() if flags & i ]) or 'None' diff --git a/farstream/python/pygstminiobject.c b/farstream/python/pygstminiobject.c new file mode 100644 index 000000000..dcd4ded44 --- /dev/null +++ b/farstream/python/pygstminiobject.c @@ -0,0 +1,373 @@ +/* -*- Mode: C; c-basic-offset: 4 -*- + * pygtk- Python bindings for the GTK toolkit. + * Copyright (C) 1998-2003 James Henstridge + * + * pygobject.c: wrapper for the GObject type. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#define NO_IMPORT_PYGOBJECT +#include "pygstminiobject.h" +#include <gst/gst.h> + +static const gchar pygstminiobject_class_id[] = "PyGstMiniObject::class"; +static GQuark pygstminiobject_class_key = 0; +/* static const gchar pygstminiobject_wrapper_id[] = "PyGstMiniObject::wrapper"; */ +/* static GQuark pygstminiobject_wrapper_key = 0; */ + +static void pygstminiobject_dealloc(PyGstMiniObject *self); +/* static int pygstminiobject_traverse(PyGstMiniObject *self, visitproc visit, void *arg); */ +/* static int pygstminiobject_clear(PyGstMiniObject *self); */ + +GST_DEBUG_CATEGORY_EXTERN (pygst_debug); +#define GST_CAT_DEFAULT pygst_debug + +/** + * pygstminiobject_lookup_class: + * @gtype: the GType of the GstMiniObject subclass. + * + * This function looks up the wrapper class used to represent + * instances of a GstMiniObject represented by @gtype. If no wrapper class + * or interface has been registered for the given GType, then a new + * type will be created. + * + * Returns: The wrapper class for the GstMiniObject or NULL if the + * GType has no registered type and a new type couldn't be created + */ +PyTypeObject * +pygstminiobject_lookup_class(GType gtype) +{ + PyTypeObject *py_type = NULL; + GType ctype = gtype; + + while (!py_type && ctype) { + py_type = g_type_get_qdata(ctype, pygstminiobject_class_key); + ctype = g_type_parent(ctype); + } + if (!ctype) + g_error ("Couldn't find a good base type!!"); + + return py_type; +} + +/** + * pygstminiobject_register_class: + * @dict: the module dictionary. A reference to the type will be stored here. + * @type_name: not used ? + * @gtype: the GType of the Gstminiobject subclass. + * @type: the Python type object for this wrapper. + * @bases: a tuple of Python type objects that are the bases of this type. + * + * This function is used to register a Python type as the wrapper for + * a particular Gstminiobject subclass. It will also insert a reference to + * the wrapper class into the module dictionary passed as a reference, + * which simplifies initialisation. + */ +void +pygstminiobject_register_class(PyObject *dict, const gchar *type_name, + GType gtype, PyTypeObject *type, + PyObject *bases) +{ + PyObject *o; + const char *class_name, *s; + + if (!pygstminiobject_class_key) + pygstminiobject_class_key = g_quark_from_static_string(pygstminiobject_class_id); + + class_name = type->tp_name; + s = strrchr(class_name, '.'); + if (s != NULL) + class_name = s + 1; + + type->ob_type = &PyType_Type; + type->tp_alloc = PyType_GenericAlloc; + type->tp_new = PyType_GenericNew; + if (bases) { + type->tp_bases = bases; + type->tp_base = (PyTypeObject *)PyTuple_GetItem(bases, 0); + } + + if (PyType_Ready(type) < 0) { + g_warning ("couldn't make the type `%s' ready", type->tp_name); + return; + } + + if (gtype) { + o = pyg_type_wrapper_new(gtype); + PyDict_SetItemString(type->tp_dict, "__gtype__", o); + Py_DECREF(o); + + /* stash a pointer to the python class with the GType */ + Py_INCREF(type); + g_type_set_qdata(gtype, pygstminiobject_class_key, type); + } + + PyDict_SetItemString(dict, (char *)class_name, (PyObject *)type); +} + +void +pygstminiobject_register_wrapper (PyObject *self) +{ +} + + +/** + * pygstminiobject_new: + * @obj: a GstMiniObject instance. + * + * This function gets a reference to a wrapper for the given GstMiniObject + * instance. A new wrapper will always be created. + * + * Returns: a reference to the wrapper for the GstMiniObject. + */ +PyObject * +pygstminiobject_new (GstMiniObject *obj) +{ + PyGstMiniObject *self = NULL; + PyGILState_STATE state; + PyTypeObject *tp = NULL; + + if (obj == NULL) { + Py_INCREF (Py_None); + return Py_None; + } + + /* since mini objects cannot notify us when they get destroyed, we + * can't use a global hash to map GMO to PyO, and have to create a new + * Python object every time we see it */ + tp = pygstminiobject_lookup_class (G_OBJECT_TYPE (obj)); + GST_DEBUG ("have to create wrapper for object %p", obj); + if (!tp) + g_warning ("Couldn't get class for type object : %p", obj); + if (tp->tp_flags & Py_TPFLAGS_HEAPTYPE) { + GST_INFO ("Increment refcount %p", tp); + Py_INCREF (tp); + } + state = pyg_gil_state_ensure(); + self = PyObject_New (PyGstMiniObject, tp); + pyg_gil_state_release(state); + + if (self == NULL) + return NULL; + self->obj = gst_mini_object_ref (obj); + + self->inst_dict = NULL; + self->weakreflist = NULL; + + GST_DEBUG ("created Python object %p for GstMiniObject %p [ref:%d]", + self, obj, GST_MINI_OBJECT_REFCOUNT_VALUE (obj)); + return (PyObject *) self; +} + +static void +pygstminiobject_dealloc(PyGstMiniObject *self) +{ + PyGILState_STATE state; + + g_return_if_fail (self != NULL); + + GST_DEBUG ("At the beginning %p", self); + state = pyg_gil_state_ensure(); + + if (self->obj) { + GST_DEBUG ("PyO %p unreffing GstMiniObject %p [ref:%d]", self, + self->obj, GST_MINI_OBJECT_REFCOUNT_VALUE (self->obj)); + gst_mini_object_unref(self->obj); + GST_DEBUG ("setting self %p -> obj to NULL", self); + self->obj = NULL; + } + + if (self->inst_dict) { + Py_DECREF(self->inst_dict); + self->inst_dict = NULL; + } + + self->ob_type->tp_free((PyObject *) self); + pyg_gil_state_release(state); + GST_DEBUG ("At the end %p", self); +} + +static int +pygstminiobject_compare(PyGstMiniObject *self, PyGstMiniObject *v) +{ + if (self->obj == v->obj) return 0; + if (self->obj > v->obj) return -1; + return 1; +} + +static long +pygstminiobject_hash(PyGstMiniObject *self) +{ + return (long)self->obj; +} + +static PyObject * +pygstminiobject_repr(PyGstMiniObject *self) +{ + gchar buf[256]; + + g_snprintf(buf, sizeof(buf), + "<%s mini-object (%s) at 0x%lx>", + self->ob_type->tp_name, + self->obj ? G_OBJECT_TYPE_NAME(self->obj) : "uninitialized", + (long)self); + return PyString_FromString(buf); +} + + +static void +pygstminiobject_free(PyObject *op) +{ + PyObject_FREE(op); +} + + +/* ---------------- PyGstMiniObject methods ----------------- */ + +static int +pygstminiobject_init(PyGstMiniObject *self, PyObject *args, PyObject *kwargs) +{ + GType object_type; + GstMiniObjectClass *class; + + if (!PyArg_ParseTuple(args, ":GstMiniObject.__init__", &object_type)) + return -1; + + object_type = pyg_type_from_object((PyObject *)self); + if (!object_type) + return -1; + + if (G_TYPE_IS_ABSTRACT(object_type)) { + PyErr_Format(PyExc_TypeError, "cannot create instance of abstract " + "(non-instantiable) type `%s'", g_type_name(object_type)); + return -1; + } + + if ((class = g_type_class_ref (object_type)) == NULL) { + PyErr_SetString(PyExc_TypeError, + "could not get a reference to type class"); + return -1; + } + + self->obj = gst_mini_object_new(object_type); + if (self->obj == NULL) + PyErr_SetString (PyExc_RuntimeError, "could not create object"); + + g_type_class_unref(class); + + return (self->obj) ? 0 : -1; +} + +static PyObject * +pygstminiobject__gstminiobject_init__(PyGstMiniObject *self, PyObject *args, PyObject *kwargs) +{ + if (pygstminiobject_init(self, args, kwargs) < 0) + return NULL; + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject * +pygstminiobject_copy(PyGstMiniObject *self, PyObject *args) +{ + return pygstminiobject_new(gst_mini_object_copy(self->obj)); +} + +static PyMethodDef pygstminiobject_methods[] = { + { "__gstminiobject_init__", (PyCFunction)pygstminiobject__gstminiobject_init__, + METH_VARARGS|METH_KEYWORDS }, + { "copy", (PyCFunction)pygstminiobject_copy, METH_VARARGS, "Copies the miniobject"}, + { NULL, NULL, 0 } +}; + +static PyObject * +pygstminiobject_get_dict(PyGstMiniObject *self, void *closure) +{ + if (self->inst_dict == NULL) { + self->inst_dict = PyDict_New(); + if (self->inst_dict == NULL) + return NULL; + } + Py_INCREF(self->inst_dict); + return self->inst_dict; +} + +static PyObject * +pygstminiobject_get_refcount(PyGstMiniObject *self, void *closure) +{ + return PyInt_FromLong(GST_MINI_OBJECT_REFCOUNT_VALUE(self->obj)); +} + +static PyObject * +pygstminiobject_get_flags(PyGstMiniObject *self, void *closure) +{ + return PyInt_FromLong(GST_MINI_OBJECT_FLAGS(self->obj)); +} + +static PyGetSetDef pygstminiobject_getsets[] = { + { "__dict__", (getter)pygstminiobject_get_dict, (setter)0 }, + { "__grefcount__", (getter)pygstminiobject_get_refcount, (setter)0, }, + { "flags", (getter)pygstminiobject_get_flags, (setter)0, }, + { NULL, 0, 0 } +}; + +PyTypeObject PyGstMiniObject_Type = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "gst.MiniObject", /* tp_name */ + sizeof(PyGstMiniObject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)pygstminiobject_dealloc, /* tp_dealloc */ + (printfunc)0, /* tp_print */ + (getattrfunc)0, /* tp_getattr */ + (setattrfunc)0, /* tp_setattr */ + (cmpfunc)pygstminiobject_compare, /* tp_compare */ + (reprfunc)pygstminiobject_repr, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + (hashfunc)pygstminiobject_hash, /* tp_hash */ + (ternaryfunc)0, /* tp_call */ + (reprfunc)0, /* tp_str */ + (getattrofunc)0, /* tp_getattro */ + (setattrofunc)0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + NULL, /* Documentation string */ + (traverseproc)0, /* tp_traverse */ + (inquiry)0, /* tp_clear */ + (richcmpfunc)0, /* tp_richcompare */ + offsetof(PyGstMiniObject, weakreflist), /* tp_weaklistoffset */ + (getiterfunc)0, /* tp_iter */ + (iternextfunc)0, /* tp_iternext */ + pygstminiobject_methods, /* tp_methods */ + 0, /* tp_members */ + pygstminiobject_getsets, /* tp_getset */ + (PyTypeObject *)0, /* tp_base */ + (PyObject *)0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + offsetof(PyGstMiniObject, inst_dict), /* tp_dictoffset */ + (initproc)pygstminiobject_init, /* tp_init */ + (allocfunc)0, /* tp_alloc */ + (newfunc)0, /* tp_new */ + (freefunc)pygstminiobject_free, /* tp_free */ + (inquiry)0, /* tp_is_gc */ + (PyObject *)0, /* tp_bases */ +}; + diff --git a/farstream/python/pygstminiobject.h b/farstream/python/pygstminiobject.h new file mode 100644 index 000000000..85516035f --- /dev/null +++ b/farstream/python/pygstminiobject.h @@ -0,0 +1,55 @@ +/* -*- Mode: C; c-basic-offset: 4 -*- */ + +#ifndef _PYGSTMINIOBJECT_H_ +#define _PYGSTMINIOBJECT_H_ + +#include <Python.h> + +#include <glib.h> +#include <glib-object.h> + +#include "common.h" + +G_BEGIN_DECLS + +/* Work around bugs in PyGILState api fixed in 2.4.0a4 */ +#undef PYGIL_API_IS_BUGGY +#if PY_VERSION_HEX < 0x020400A4 +#define PYGIL_API_IS_BUGGY TRUE +#else +#define PYGIL_API_IS_BUGGY FALSE +#endif + +typedef struct { + PyObject_HEAD + GstMiniObject *obj; + PyObject *inst_dict; /* the instance dictionary -- must be last */ + PyObject *weakreflist; /* list of weak references */ +} PyGstMiniObject; + +PyObject * +pygstminiobject_new(GstMiniObject *obj); + +#define pygstminiobject_get(v) (((PyGstMiniObject *)(v))->obj) +#define pygstminiobject_check(v,base) (PyObject_TypeCheck(v,base)) + +void +pygstminiobject_register_class(PyObject *dict, const gchar *type_name, + GType gtype, PyTypeObject *type, + PyObject *bases); +void +pygstminiobject_register_wrapper(PyObject *self); + +void +pygst_miniobject_init(); + +#ifndef _INSIDE_PYGSTMINIOBJECT_ + + +extern PyTypeObject PyGstMiniObject_Type; + +#endif /* !_INSIDE_PYGSTMINIOBJECT_ */ + +G_END_DECLS + +#endif /* !_PYGSTMINIOBJECT_H_ */ diff --git a/farstream/python/pytpfarstream-filter.defs b/farstream/python/pytpfarstream-filter.defs new file mode 100644 index 000000000..7a8b527f4 --- /dev/null +++ b/farstream/python/pytpfarstream-filter.defs @@ -0,0 +1,24 @@ +(define-object Channel + (in-module "Tf") + (parent "GObject") + (c-name "TfChannel") + (gtype-id "TF_TYPE_CHANNEL") +) + +(define-object Content + (in-module "Tf") + (parent "GObject") + (c-name "TfContent") + (gtype-id "TF_TYPE_CONTENT") +) + +(define-function tf_channel_new_async + (c-name "tf_channel_new_async") + (return-type "none") + (parameters + '("const-gchar*" "connection_busname") + '("const-gchar*" "connection_path") + '("const-gchar*" "channel_path") + '("GAsyncReadyCallback" "callback") + ) +) diff --git a/farstream/python/pytpfarstream.defs b/farstream/python/pytpfarstream.defs new file mode 100644 index 000000000..59b678309 --- /dev/null +++ b/farstream/python/pytpfarstream.defs @@ -0,0 +1,134 @@ +(define-object Channel + (in-module "Tf") + (parent "GObject") + (c-name "TfChannel") + (gtype-id "TF_TYPE_CHANNEL") +) + +(define-object Content + (in-module "Tf") + (parent "GObject") + (c-name "TfContent") + (gtype-id "TF_TYPE_CONTENT") +) + +(define-function tf_channel_new_async + (c-name "tf_channel_new_async") + (return-type "none") + (parameters + '("const-gchar*" "connection_busname") + '("const-gchar*" "connection_path") + '("const-gchar*" "channel_path") + '("GAsyncReadyCallback" "callback") + ) +) +;; -*- scheme -*- +; object definitions ... +;; Enumerations and flags ... + + +;; From telepathy-farstream.h + +(define-function tf_init + (c-name "tf_init") + (return-type "none") +) + + +;; -*- scheme -*- +; object definitions ... +;; Enumerations and flags ... + + +;; From channel.h + +(define-function tf_channel_get_type + (c-name "tf_channel_get_type") + (return-type "GType") +) + +(define-method bus_message + (of-object "TfChannel") + (c-name "tf_channel_bus_message") + (return-type "gboolean") + (parameters + '("GstMessage*" "message") + ) +) + + +;; -*- scheme -*- +; object definitions ... +;; Enumerations and flags ... + + +;; From content.h + +(define-function tf_content_get_type + (c-name "tf_content_get_type") + (return-type "GType") +) + +(define-method error_literal + (of-object "TfContent") + (c-name "tf_content_error_literal") + (return-type "none") + (parameters + '("guint" "reason") + '("const-gchar*" "detailed_reason") + '("const-gchar*" "message") + ) +) + +(define-method error + (of-object "TfContent") + (c-name "tf_content_error") + (return-type "none") + (parameters + '("guint" "reason") + '("const-gchar*" "detailed_reason") + '("const-gchar*" "message_format") + ) + (varargs #t) +) + +(define-method iterate_src_pads + (of-object "TfContent") + (c-name "tf_content_iterate_src_pads") + (return-type "GstIterator*") + (parameters + '("guint*" "handles") + '("guint" "handle_count") + ) +) + + +;; -*- scheme -*- +; object definitions ... +;; Enumerations and flags ... + + +;; From stream.h + +(define-function tf_stream_get_type + (c-name "tf_stream_get_type") + (return-type "GType") +) + +(define-method get_id + (of-object "TfStream") + (c-name "tf_stream_get_id") + (return-type "guint") +) + +(define-method error + (of-object "TfStream") + (c-name "tf_stream_error") + (return-type "none") + (parameters + '("TpMediaStreamError" "error") + '("const-gchar*" "message") + ) +) + + diff --git a/farstream/python/pytpfarstream.override b/farstream/python/pytpfarstream.override new file mode 100644 index 000000000..411831b00 --- /dev/null +++ b/farstream/python/pytpfarstream.override @@ -0,0 +1,107 @@ +%% +headers +#include <pygobject.h> + +#include <gst/gst.h> + +#include <telepathy-farstream/telepathy-farstream.h> + +#include <telepathy-glib/dbus.h> +#include <telepathy-glib/channel.h> + +#include "pygstminiobject.h" + +static void +async_result_callback_marshal(GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + PyObject *callback = user_data; + PyObject *ret; + PyGILState_STATE state; + + state = pyg_gil_state_ensure(); + + ret = PyObject_CallFunction(callback, "NN", + pygobject_new(source_object), + pygobject_new((GObject *)result)); + + if (ret == NULL) { + PyErr_Print(); + PyErr_Clear(); + } + + Py_XDECREF(callback); + Py_XDECREF(ret); + + pyg_gil_state_release(state); +} +%% +modulename tf +%% +import gobject.GObject as PyGObject_Type +import gst.Message as PyGstMessage_Type +%% +ignore-glob + *_get_type +%% +headers +%% +override tf_channel_new_async kwargs +static PyObject * +_wrap_tf_channel_new_async(PyGObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = { "connection_busname", "connection_path", + "channel_path", "callback", NULL }; + + char *busname, *connection_path, *channel_path; + TpChannel *proxy = NULL; + TpConnection *connection = NULL; + TpDBusDaemon *dbus = NULL; + GError *error = NULL; + PyObject *callback; + PyObject *ret = NULL; + + if (!PyArg_ParseTupleAndKeywords (args, kwargs, "sssO:tf_channel_new_async", + kwlist, &busname, &connection_path, &channel_path, &callback)) + goto out; + + dbus = tp_dbus_daemon_dup (&error); + if (dbus == NULL) + { + pyg_error_check (&error); + goto out; + } + + connection = tp_connection_new (dbus, busname, connection_path, &error); + if (connection == NULL) + { + pyg_error_check (&error); + goto out; + } + + proxy = tp_channel_new (connection, channel_path, NULL, + TP_UNKNOWN_HANDLE_TYPE, 0, &error); + + if (proxy == NULL) + { + pyg_error_check (&error); + goto out; + } + + ret = Py_None; + Py_XINCREF (callback); + tf_channel_new_async (proxy, async_result_callback_marshal, callback); + +out: + if (dbus != NULL) + g_object_unref (dbus); + + if (connection != NULL) + g_object_unref (connection); + + if (proxy != NULL) + g_object_unref (proxy); + + return ret; +} diff --git a/farstream/python/pytpfarstreammodule.c b/farstream/python/pytpfarstreammodule.c new file mode 100644 index 000000000..f6e6f8669 --- /dev/null +++ b/farstream/python/pytpfarstreammodule.c @@ -0,0 +1,30 @@ +#include <pygobject.h> + +#include <gst/gst.h> +#include <telepathy-farstream/telepathy-farstream.h> + +void tf_register_classes (PyObject *d); + +DL_EXPORT(void) inittpfarstream(void); +extern PyMethodDef tf_functions[]; + +GST_DEBUG_CATEGORY (pygst_debug); /* for python code */ + +DL_EXPORT(void) +inittpfarstream(void) +{ + PyObject *m, *d; + + tf_init (); + init_pygobject (); + + m = Py_InitModule ("tpfarstream", tf_functions); + d = PyModule_GetDict (m); + + tf_register_classes (d); + + if (PyErr_Occurred ()) { + PyErr_Print(); + Py_FatalError ("can't initialise module tpfarstream"); + } +} diff --git a/farstream/python/rebuild-defs.sh b/farstream/python/rebuild-defs.sh new file mode 100755 index 000000000..f74bdd356 --- /dev/null +++ b/farstream/python/rebuild-defs.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +HEADERS=" \ + telepathy-farstream.h \ + channel.h \ + content.h \ + stream.h" + +srcdir=../telepathy-farstream/ + +output=pytpfarstream.defs +filter=pytpfarstream-filter.defs + +cat ${filter} > ${output} + +for h in $HEADERS; do + python codegen/h2def.py --defsfilter=${filter} ${srcdir}/$h >> $output +done diff --git a/farstream/telepathy-farstream/Makefile.am b/farstream/telepathy-farstream/Makefile.am new file mode 100644 index 000000000..04f3c680e --- /dev/null +++ b/farstream/telepathy-farstream/Makefile.am @@ -0,0 +1,88 @@ +BUILT_SOURCES = \ + tf-signals-marshal.h \ + tf-signals-marshal.c + +CLEANFILES = $(BUILT_SOURCES) tf-signals-marshal.list + + +tfincludedir = $(includedir)/telepathy-1.0/telepathy-farstream +tfinclude_HEADERS = \ + telepathy-farstream.h \ + channel.h \ + content.h + +libtelepathy_farstream_la_SOURCES = \ + stream.c \ + stream.h \ + content.c \ + content.h \ + content-priv.h \ + session.c \ + channel.c \ + channel.h \ + stream-priv.h \ + session-priv.h \ + channel-priv.h \ + media-signalling-channel.c \ + media-signalling-channel.h \ + media-signalling-content.c \ + media-signalling-content.h \ + call-channel.c \ + call-channel.h \ + call-content.h \ + call-content.c \ + call-stream.h \ + call-stream.c \ + telepathy-farstream.h \ + telepathy-farstream.c \ + utils.h + +nodist_libtelepathy_farstream_la_SOURCES = $(BUILT_SOURCES) + +lib_LTLIBRARIES = libtelepathy-farstream.la + +pkgconfigdir = ${libdir}/pkgconfig +pkgconfig_DATA = telepathy-farstream.pc + +AM_CFLAGS = \ + -DG_LOG_DOMAIN=\"tp-fs\" \ + $(ERROR_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(GST_CFLAGS) \ + $(FARSTREAM_CFLAGS) \ + $(TELEPATHY_CFLAGS) \ + -I$(top_srcdir) \ + -I$(top_builddir) + +libtelepathy_farstream_la_LIBADD = \ + $(GLIB_LIBS) \ + $(DBUS_LIBS) \ + $(GST_LIBS) \ + $(FARSTREAM_LIBS) \ + $(TELEPATHY_LIBS) \ + ../extensions/libfuture-extensions.la + +libtelepathy_farstream_la_LDFLAGS = -no-undefined \ + -export-symbols-regex "^tf_(init|content_|channel_).*" \ + -version-info "$(LT_CURRENT)":"$(LT_REVISION)":"$(LT_AGE)" + + +# rules to generate signal marshallers +tf-signals-marshal.list: $(libtelepathy_farstream_la_SOURCES) Makefile.am + ( cd $(srcdir) && \ + sed -n -e 's/.*_tf_marshal_\([[:upper:][:digit:]]*__[[:upper:][:digit:]_]*\).*/\1/p' \ + $(libtelepathy_farstream_la_SOURCES) ) \ + | sed -e 's/__/:/' -e 'y/_/,/' | sort -u > $@.tmp + if cmp -s $@.tmp $@; then \ + rm $@.tmp; \ + touch $@; \ + else \ + mv $@.tmp $@; \ + fi + +%-signals-marshal.h: %-signals-marshal.list Makefile.in + glib-genmarshal --header --prefix=_$(subst -,_,$*)_marshal $< > $*-signals-marshal.h + +%-signals-marshal.c: %-signals-marshal.list Makefile.in + glib-genmarshal --body --prefix=_$(subst -,_,$*)_marshal $< > $*-signals-marshal.c diff --git a/farstream/telepathy-farstream/call-channel.c b/farstream/telepathy-farstream/call-channel.c new file mode 100644 index 000000000..57c650f77 --- /dev/null +++ b/farstream/telepathy-farstream/call-channel.c @@ -0,0 +1,723 @@ +/* + * call-channel.c - Source for TfCallChannel + * Copyright (C) 2010 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * SECTION:call-channel + + * @short_description: Handle the Call interface on a Channel + * + * This class handles the + * org.freedesktop.Telepathy.Channel.Interface.Call on a + * channel using Farstream. + */ + + +#include "call-channel.h" + +#include <telepathy-glib/util.h> +#include <telepathy-glib/interfaces.h> +#include <farstream/fs-conference.h> + +#include "extensions/extensions.h" + +#include "call-content.h" +#include "tf-signals-marshal.h" + + +static void call_channel_async_initable_init (GAsyncInitableIface *asynciface); + +G_DEFINE_TYPE_WITH_CODE (TfCallChannel, tf_call_channel, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, + call_channel_async_initable_init)) + +enum +{ + PROP_FS_CONFERENCES = 1 +}; + + +enum +{ + SIGNAL_FS_CONFERENCE_ADDED, + SIGNAL_FS_CONFERENCE_REMOVED, + SIGNAL_CONTENT_ADDED, + SIGNAL_CONTENT_REMOVED, + SIGNAL_COUNT +}; + +static guint signals[SIGNAL_COUNT] = {0}; + +struct CallConference { + gint use_count; + gchar *conference_type; + FsConference *fsconference; +}; + +struct CallParticipant { + gint use_count; + guint handle; + FsConference *fsconference; + FsParticipant *fsparticipant; +}; + +static void +tf_call_channel_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void tf_call_channel_dispose (GObject *object); + + +static void tf_call_channel_init_async (GAsyncInitable *initable, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static gboolean tf_call_channel_init_finish (GAsyncInitable *initable, + GAsyncResult *res, + GError **error); + +static void got_hardware_streaming (TpProxy *proxy, const GValue *out_value, + const GError *error, gpointer user_data, GObject *weak_object); + + +static void +tf_call_channel_class_init (TfCallChannelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = tf_call_channel_dispose; + object_class->get_property = tf_call_channel_get_property; + + g_object_class_install_property (object_class, PROP_FS_CONFERENCES, + g_param_spec_boxed ("fs-conferences", + "Farstream FsConference object", + "GPtrArray of Farstream FsConferences for this channel", + G_TYPE_PTR_ARRAY, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + signals[SIGNAL_FS_CONFERENCE_ADDED] = g_signal_new ("fs-conference-added", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + _tf_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, FS_TYPE_CONFERENCE); + + signals[SIGNAL_FS_CONFERENCE_REMOVED] = g_signal_new ("fs-conference-removed", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + _tf_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, FS_TYPE_CONFERENCE); + + signals[SIGNAL_CONTENT_ADDED] = g_signal_new ("content-added", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + _tf_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, TF_TYPE_CALL_CONTENT); + + signals[SIGNAL_CONTENT_REMOVED] = g_signal_new ("content-removed", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + _tf_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, TF_TYPE_CALL_CONTENT); +} + + +static void +call_channel_async_initable_init (GAsyncInitableIface *asynciface) +{ + asynciface->init_async = tf_call_channel_init_async; + asynciface->init_finish = tf_call_channel_init_finish; +} + +static void +free_call_conference (gpointer data) +{ + struct CallConference *cc = data; + + gst_object_unref (cc->fsconference); + g_slice_free (struct CallConference, data); +} + +static void +free_participant (gpointer data) +{ + struct CallParticipant *cp = data; + + g_object_unref (cp->fsparticipant); + gst_object_unref (cp->fsconference); + g_slice_free (struct CallParticipant, cp); +} + +static void +tf_call_channel_init (TfCallChannel *self) +{ + self->fsconferences = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + free_call_conference); + + self->participants = g_ptr_array_new_with_free_func (free_participant); +} + + +static void +tf_call_channel_init_async (GAsyncInitable *initable, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + TfCallChannel *self = TF_CALL_CHANNEL (initable); + GSimpleAsyncResult *res; + + if (cancellable != NULL) + { + g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data, + G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, + "TfCallChannel initialisation does not support cancellation"); + return; + } + + res = g_simple_async_result_new (G_OBJECT (self), callback, user_data, + tf_call_channel_init_async); + + tp_cli_dbus_properties_call_get (self->proxy, -1, + TF_FUTURE_IFACE_CHANNEL_TYPE_CALL, + "HardwareStreaming", + got_hardware_streaming, res, NULL, G_OBJECT (self)); +} + +static gboolean +tf_call_channel_init_finish (GAsyncInitable *initable, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple_res; + + g_return_val_if_fail (g_simple_async_result_is_valid (res, + G_OBJECT (initable), tf_call_channel_init_async), FALSE); + simple_res = G_SIMPLE_ASYNC_RESULT (res); + + if (g_simple_async_result_propagate_error (simple_res, error)) + return FALSE; + + return g_simple_async_result_get_op_res_gboolean (simple_res); +} + + +static void +tf_call_channel_dispose (GObject *object) +{ + TfCallChannel *self = TF_CALL_CHANNEL (object); + + g_debug (G_STRFUNC); + + /* Some of the contents may have more than our ref - if they're in the + middle of an async op, they're reffed by the async result. + In this case, unreffing them (implicitely) through destruction of + the hash table they're in will not dispose them just yet. + However, they keep an unreffed pointer to the call channel, and will, + when eventually disposed of, call upon the call channel to put their + conference back. Since that call channel will then be disposed of, + I think we can all agree that this is a bit unfortunate. + So we force dispose the contents as other objects already do, and + add checks to the content routines to bail out when the object has + already been disposed of. */ + if (self->contents) + { + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, self->contents); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + g_object_run_dispose (G_OBJECT (value)); + } + + g_hash_table_destroy (self->contents); + self->contents = NULL; + } + + if (self->participants) + g_ptr_array_unref (self->participants); + self->participants = NULL; + + if (self->fsconferences) + g_hash_table_unref (self->fsconferences); + self->fsconferences = NULL; + + if (self->proxy) + g_object_unref (self->proxy); + self->proxy = NULL; + + if (G_OBJECT_CLASS (tf_call_channel_parent_class)->dispose) + G_OBJECT_CLASS (tf_call_channel_parent_class)->dispose (object); +} + +static void +conf_into_ptr_array (gpointer key, gpointer value, gpointer data) +{ + struct CallConference *cc = value; + GPtrArray *array = data; + + g_ptr_array_add (array, cc->fsconference); +} + +static void +tf_call_channel_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + TfCallChannel *self = TF_CALL_CHANNEL (object); + + switch (property_id) + { + case PROP_FS_CONFERENCES: + { + GPtrArray *array = g_ptr_array_sized_new ( + g_hash_table_size (self->fsconferences)); + + g_ptr_array_set_free_func (array, gst_object_unref); + g_hash_table_foreach (self->fsconferences, conf_into_ptr_array, array); + g_value_take_boxed (value, array); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +content_ready (GObject *object, GAsyncResult *res, gpointer user_data) +{ + TfCallChannel *self = TF_CALL_CHANNEL (user_data); + TfCallContent *content = TF_CALL_CONTENT (object); + + if (g_async_initable_init_finish (G_ASYNC_INITABLE (object), res, NULL)) + { + g_signal_emit (self, signals[SIGNAL_CONTENT_ADDED], 0, content); + g_object_unref (content); + } + else + { + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, self->contents); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + if (value == object) + { + g_hash_table_iter_remove (&iter); + break; + } + } + } + + g_object_unref (self); +} + +static gboolean +add_content (TfCallChannel *self, const gchar *content_path) +{ + GError *error = NULL; + TfCallContent *content; + + /* Check if content already added */ + if (g_hash_table_lookup (self->contents, content_path)) + return TRUE; + + content = tf_call_content_new_async (self, content_path, + &error, content_ready, g_object_ref (self)); + + if (error) + { + /* Error was already transmitted to the CM by TfCallContent */ + g_clear_error (&error); + g_object_unref (self); + return FALSE; + } + + g_hash_table_insert (self->contents, g_strdup (content_path), content); + + return TRUE; +} + +static void +got_contents (TpProxy *proxy, const GValue *out_value, + const GError *error, gpointer user_data, GObject *weak_object) +{ + TfCallChannel *self = TF_CALL_CHANNEL (weak_object); + GSimpleAsyncResult *res = user_data; + GPtrArray *contents; + guint i; + + if (error) + { + g_warning ("Error getting the Contents property: %s", + error->message); + g_simple_async_result_set_from_error (res, error); + goto out; + } + + contents = g_value_get_boxed (out_value); + + self->contents = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_object_unref); + + for (i = 0; i < contents->len; i++) + if (!add_content (self, g_ptr_array_index (contents, i))) + break; + + g_simple_async_result_set_op_res_gboolean (res, TRUE); + +out: + g_simple_async_result_complete (res); + g_object_unref (res); +} + +static void +content_added (TpChannel *proxy, + const gchar *arg_Content, + gpointer user_data, + GObject *weak_object) +{ + TfCallChannel *self = TF_CALL_CHANNEL (weak_object); + + /* Ignore signals before we got the "Contents" property to avoid races that + * could cause the same content to be added twice + */ + + if (!self->contents) + return; + + add_content (self, arg_Content); +} + +static void +content_removed (TpChannel *proxy, + const gchar *arg_Content, + gpointer user_data, + GObject *weak_object) +{ + TfCallChannel *self = TF_CALL_CHANNEL (weak_object); + TfCallContent *content; + + if (!self->contents) + return; + + content = g_hash_table_lookup (self->contents, arg_Content); + + if (content) + { + g_object_ref (content); + g_hash_table_remove (self->contents, arg_Content); + + g_signal_emit (self, signals[SIGNAL_CONTENT_REMOVED], 0, content); + g_object_unref (content); + } +} + + +static void +got_hardware_streaming (TpProxy *proxy, const GValue *out_value, + const GError *error, gpointer user_data, GObject *weak_object) +{ + TfCallChannel *self = TF_CALL_CHANNEL (weak_object); + GSimpleAsyncResult *res = user_data; + GError *myerror = NULL; + + if (error) + { + g_warning ("Error getting the hardware streaming property: %s", + error->message); + g_simple_async_result_set_from_error (res, error); + goto error; + } + + if (g_value_get_boolean (out_value)) + { + g_warning ("Hardware streaming property is TRUE, ignoring"); + + g_simple_async_result_set_error (res, TP_ERROR, TP_ERROR_NOT_CAPABLE, + "This channel does hardware streaming, not handled here"); + goto error; + } + + tf_future_cli_channel_type_call_connect_to_content_added (TP_CHANNEL (proxy), + content_added, NULL, NULL, G_OBJECT (self), &myerror); + if (myerror) + { + g_warning ("Error connectiong to ContentAdded signal: %s", + myerror->message); + g_simple_async_result_set_from_error (res, myerror); + g_clear_error (&myerror); + goto error; + } + + tf_future_cli_channel_type_call_connect_to_content_removed ( + TP_CHANNEL (proxy), content_removed, NULL, NULL, G_OBJECT (self), + &myerror); + if (myerror) + { + g_warning ("Error connectiong to ContentRemoved signal: %s", + myerror->message); + g_simple_async_result_set_from_error (res, myerror); + g_clear_error (&myerror); + goto error; + } + + tp_cli_dbus_properties_call_get (proxy, -1, + TF_FUTURE_IFACE_CHANNEL_TYPE_CALL, "Contents", + got_contents, res, NULL, G_OBJECT (self)); + + return; + +error: + g_simple_async_result_complete (res); + g_object_unref (res); +} + +void +tf_call_channel_new_async (TpChannel *channel, + GAsyncReadyCallback callback, + gpointer user_data) +{ + TfCallChannel *self = g_object_new (TF_TYPE_CALL_CHANNEL, NULL); + + self->proxy = g_object_ref (channel); + g_async_initable_init_async (G_ASYNC_INITABLE (self), 0, NULL, callback, + user_data); + + /* Ownership passed to async call */ + g_object_unref (self); +} + + +static gboolean +find_conf_func (gpointer key, gpointer value, gpointer data) +{ + FsConference *conf = data; + struct CallConference *cc = value; + + if (cc->fsconference == conf) + return TRUE; + else + return FALSE; +} + +static struct CallConference * +find_call_conference_by_conference (TfCallChannel *channel, + GstObject *conference) +{ + return g_hash_table_find (channel->fsconferences, find_conf_func, + conference); +} + +gboolean +tf_call_channel_bus_message (TfCallChannel *channel, + GstMessage *message) +{ + GError *error = NULL; + gchar *debug; + GHashTableIter iter; + gpointer key, value; + struct CallConference *cc; + + cc = find_call_conference_by_conference (channel, GST_MESSAGE_SRC (message)); + if (!cc) + return FALSE; + + switch (GST_MESSAGE_TYPE (message)) + { + case GST_MESSAGE_WARNING: + gst_message_parse_warning (message, &error, &debug); + + g_warning ("session: %s (%s)", error->message, debug); + + g_error_free (error); + g_free (debug); + return TRUE; + case GST_MESSAGE_ERROR: + gst_message_parse_error (message, &error, &debug); + + g_warning ("session ERROR: %s (%s)", error->message, debug); + + tf_call_channel_error (channel); + + g_error_free (error); + g_free (debug); + return TRUE; + default: + break; + } + + g_hash_table_iter_init (&iter, channel->contents); + while (g_hash_table_iter_next (&iter, &key, &value)) + if (tf_call_content_bus_message (value, message)) + return TRUE; + + return FALSE; +} + +void +tf_call_channel_error (TfCallChannel *channel) +{ + tf_future_cli_channel_type_call_call_hangup (channel->proxy, + -1, TF_FUTURE_CALL_STATE_CHANGE_REASON_UNKNOWN, "", "", + NULL, NULL, NULL, NULL); +} + + +/* This always returns a reference, one should use _put_conference to unref it + */ +FsConference * +_tf_call_channel_get_conference (TfCallChannel *channel, + const gchar *conference_type) +{ + gchar *tmp; + struct CallConference *cc; + + cc = g_hash_table_lookup (channel->fsconferences, conference_type); + + if (cc) + { + cc->use_count++; + gst_object_ref (cc->fsconference); + return cc->fsconference; + } + + cc = g_slice_new (struct CallConference); + cc->use_count = 1; + cc->conference_type = g_strdup (conference_type); + + tmp = g_strdup_printf ("fs%sconference", conference_type); + cc->fsconference = FS_CONFERENCE (gst_element_factory_make (tmp, NULL)); + g_free (tmp); + + if (cc->fsconference == NULL) + { + g_slice_free (struct CallConference, cc); + return NULL; + } + + /* Take ownership of the conference */ + gst_object_ref_sink (cc->fsconference); + g_hash_table_insert (channel->fsconferences, cc->conference_type, cc); + + g_signal_emit (channel, signals[SIGNAL_FS_CONFERENCE_ADDED], 0, + cc->fsconference); + g_object_notify (G_OBJECT (channel), "fs-conferences"); + + gst_object_ref (cc->fsconference); + + return cc->fsconference; +} + +void +_tf_call_channel_put_conference (TfCallChannel *channel, + FsConference *conference) +{ + struct CallConference *cc; + + cc = find_call_conference_by_conference (channel, GST_OBJECT (conference)); + if (!cc) + { + g_warning ("Trying to put conference that does not exist"); + return; + } + + cc->use_count--; + + if (cc->use_count <= 0) + { + g_signal_emit (channel, signals[SIGNAL_FS_CONFERENCE_REMOVED], 0, + cc->fsconference); + g_hash_table_remove (channel->fsconferences, cc->conference_type); + g_object_notify (G_OBJECT (channel), "fs-conferences"); + } + + gst_object_unref (conference); +} + + +FsParticipant * +_tf_call_channel_get_participant (TfCallChannel *channel, + FsConference *fsconference, + guint contact_handle, + GError **error) +{ + guint i; + struct CallParticipant *cp; + FsParticipant *p; + + for (i = 0; i < channel->participants->len; i++) + { + cp = g_ptr_array_index (channel->participants, i); + + if (cp->fsconference == fsconference && + cp->handle == contact_handle) + { + cp->use_count++; + return g_object_ref (cp->fsparticipant); + } + } + + p = fs_conference_new_participant (fsconference, error); + if (!p) + return NULL; + + cp = g_slice_new (struct CallParticipant); + cp->use_count = 1; + cp->handle = contact_handle; + cp->fsconference = gst_object_ref (fsconference); + cp->fsparticipant = p; + g_ptr_array_add (channel->participants, cp); + + return p; +} + + +void +_tf_call_channel_put_participant (TfCallChannel *channel, + FsParticipant *participant) +{ + guint i; + + for (i = 0; i < channel->participants->len; i++) + { + struct CallParticipant *cp = g_ptr_array_index (channel->participants, i); + + if (cp->fsparticipant == participant) + { + cp->use_count--; + if (cp->use_count <= 0) + g_ptr_array_remove_index_fast (channel->participants, i); + else + gst_object_unref (cp->fsparticipant); + return; + } + } + +} diff --git a/farstream/telepathy-farstream/call-channel.h b/farstream/telepathy-farstream/call-channel.h new file mode 100644 index 000000000..38488188f --- /dev/null +++ b/farstream/telepathy-farstream/call-channel.h @@ -0,0 +1,118 @@ +/* + * call-channel.c - Source for TfCallChannel + * Copyright (C) 2010 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __TF_CALL_CHANNEL_H__ +#define __TF_CALL_CHANNEL_H__ + +#include <glib-object.h> + +#include <gst/gst.h> +#include <farstream/fs-conference.h> +#include <telepathy-glib/channel.h> + +G_BEGIN_DECLS + +#define TF_TYPE_CALL_CHANNEL tf_call_channel_get_type() + +#define TF_CALL_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + TF_TYPE_CALL_CHANNEL, TfCallChannel)) + +#define TF_CALL_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + TF_TYPE_CALL_CHANNEL, TfCallChannelClass)) + +#define TF_IS_CALL_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_CALL_CHANNEL)) + +#define TF_IS_CALL_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_CALL_CHANNEL)) + +#define TF_CALL_CHANNEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + TF_TYPE_CALL_CHANNEL, TfCallChannelClass)) + +typedef struct _TfCallChannelPrivate TfCallChannelPrivate; + +/** + * TfCallChannel: + * + * All members of the object are private + */ + +typedef struct _TfCallChannel TfCallChannel; + +/** + * TfCallChannelClass: + * @parent_class: the parent #GObjecClass + * + * There are no overridable functions + */ + +typedef struct _TfCallChannelClass TfCallChannelClass; + + + +struct _TfCallChannel { + GObject parent; + + TpChannel *proxy; + + GHashTable *fsconferences; + + GHashTable *contents; /* NULL before getting the first contents */ + + GPtrArray *participants; +}; + +struct _TfCallChannelClass{ + GObjectClass parent_class; +}; + + +GType tf_call_channel_get_type (void); + +void tf_call_channel_new_async (TpChannel *channel_proxy, + GAsyncReadyCallback callback, + gpointer user_data); + +void tf_call_channel_error (TfCallChannel *channel); + +gboolean tf_call_channel_bus_message (TfCallChannel *channel, + GstMessage *message); + +/* Private methods, only to be used inside TP-FS */ + +FsConference *_tf_call_channel_get_conference (TfCallChannel *channel, + const gchar *conference_type); +void _tf_call_channel_put_conference (TfCallChannel *channel, + FsConference *conference); + + +FsParticipant *_tf_call_channel_get_participant (TfCallChannel *channel, + FsConference *fsconference, + guint contact_handle, + GError **error); +void _tf_call_channel_put_participant (TfCallChannel *channel, + FsParticipant *participant); + +G_END_DECLS + +#endif /* __TF_CALL_CHANNEL_H__ */ + diff --git a/farstream/telepathy-farstream/call-content.c b/farstream/telepathy-farstream/call-content.c new file mode 100644 index 000000000..36c827820 --- /dev/null +++ b/farstream/telepathy-farstream/call-content.c @@ -0,0 +1,1722 @@ +/* + * call-content.c - Source for TfCallContent + * Copyright (C) 2010 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * SECTION:call-content + + * @short_description: Handle the Call interface on a Channel + * + * This class handles the + * org.freedesktop.Telepathy.Channel.Interface.Call on a + * channel using Farstream. + */ + + +#include "call-content.h" + +#include <telepathy-glib/util.h> +#include <telepathy-glib/interfaces.h> +#include <farstream/fs-conference.h> +#include <farstream/fs-utils.h> +#include <farstream/fs-element-added-notifier.h> + +#include <stdarg.h> +#include <string.h> + +#include <telepathy-glib/proxy-subclass.h> + +#include "call-stream.h" +#include "tf-signals-marshal.h" +#include "utils.h" + +#include "extensions/extensions.h" + +struct _TfCallContent { + TfContent parent; + + TfCallChannel *call_channel; + FsConference *fsconference; + + TfFutureCallContent *proxy; + + FsSession *fssession; + TpMediaStreamType media_type; + + GList *current_codecs; + TpProxy *current_offer; + guint current_offer_contact_handle; + GList *current_offer_fscodecs; + + GHashTable *streams; /* NULL before getting the first streams */ + /* Streams for which we don't have a session yet*/ + GList *outstanding_streams; + + GMutex *mutex; + + /* Content protected by the Mutex */ + GPtrArray *fsstreams; + guint fsstreams_cookie; + + gboolean got_codec_offer_property; + + /* VideoControl API */ + FsElementAddedNotifier *notifier; + + volatile gint bitrate; + volatile gint mtu; + gboolean manual_keyframes; + + guint framerate; + guint width; + guint height; +}; + +struct _TfCallContentClass{ + TfContentClass parent_class; +}; + + +static void call_content_async_initable_init (GAsyncInitableIface *asynciface); + +G_DEFINE_TYPE_WITH_CODE (TfCallContent, tf_call_content, TF_TYPE_CONTENT, + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, + call_content_async_initable_init)) + +#define TF_CALL_CONTENT_LOCK(self) g_mutex_lock ((self)->mutex) +#define TF_CALL_CONTENT_UNLOCK(self) g_mutex_unlock ((self)->mutex) + + +enum +{ + PROP_TF_CHANNEL = 1, + PROP_FS_CONFERENCE, + PROP_FS_SESSION, + PROP_SINK_PAD, + PROP_MEDIA_TYPE, + PROP_OBJECT_PATH, + PROP_FRAMERATE, + PROP_WIDTH, + PROP_HEIGHT +}; + +enum +{ + RESOLUTION_CHANGED = 0, + SIGNAL_COUNT +}; + +static guint signals[SIGNAL_COUNT] = {0}; + + +struct CallFsStream { + TfCallChannel *parent_channel; + guint use_count; + guint contact_handle; + FsParticipant *fsparticipant; + FsStream *fsstream; +}; + +static void +tf_call_content_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void tf_call_content_dispose (GObject *object); +static void tf_call_content_finalize (GObject *object); + +static void tf_call_content_init_async (GAsyncInitable *initable, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static gboolean tf_call_content_init_finish (GAsyncInitable *initable, + GAsyncResult *res, + GError **error); + + +static void tf_call_content_try_sending_codecs (TfCallContent *self); +static FsStream * tf_call_content_get_existing_fsstream_by_handle ( + TfCallContent *content, guint contact_handle); + +static void src_pad_added (FsStream *fsstream, GstPad *pad, FsCodec *codec, + TfCallContent *content); +static GstIterator * tf_call_content_iterate_src_pads (TfContent *content, + guint *handles, guint handle_count); + +static void tf_call_content_error (TfContent *content, + guint reason, /* TfFutureContentRemovalReason */ + const gchar *detailed_reason, + const gchar *message); + +static void +tf_call_content_class_init (TfCallContentClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + TfContentClass *content_class = TF_CONTENT_CLASS (klass); + + content_class->iterate_src_pads = tf_call_content_iterate_src_pads; + + content_class->content_error = tf_call_content_error; + + object_class->dispose = tf_call_content_dispose; + object_class->finalize = tf_call_content_finalize; + object_class->get_property = tf_call_content_get_property; + + g_object_class_override_property (object_class, PROP_TF_CHANNEL, + "tf-channel"); + g_object_class_override_property (object_class, PROP_FS_CONFERENCE, + "fs-conference"); + g_object_class_override_property (object_class, PROP_FS_SESSION, + "fs-session"); + g_object_class_override_property (object_class, PROP_SINK_PAD, + "sink-pad"); + g_object_class_override_property (object_class, PROP_MEDIA_TYPE, + "media-type"); + g_object_class_override_property (object_class, PROP_OBJECT_PATH, + "object-path"); + + g_object_class_install_property (object_class, PROP_FRAMERATE, + g_param_spec_uint ("framerate", + "Framerate", + "The framerate as indicated by the VideoControl interface" + "or the media layer", + 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_WIDTH, + g_param_spec_uint ("width", + "Width", + "The video width indicated by the VideoControl interface" + "or the media layer", + 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_HEIGHT, + g_param_spec_uint ("height", + "Height", + "The video height as indicated by the VideoControl interface" + "or the media layer", + 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + signals[RESOLUTION_CHANGED] = g_signal_new ("resolution-changed", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + _tf_marshal_VOID__UINT_UINT, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT); +} + +static void +call_content_async_initable_init (GAsyncInitableIface *asynciface) +{ + asynciface->init_async = tf_call_content_init_async; + asynciface->init_finish = tf_call_content_init_finish; +} + + +static void +free_content_fsstream (gpointer data) +{ + struct CallFsStream *cfs = data; + + g_object_run_dispose (G_OBJECT (cfs->fsstream)); + g_object_unref (cfs->fsstream); + _tf_call_channel_put_participant (cfs->parent_channel, cfs->fsparticipant); + g_slice_free (struct CallFsStream, cfs); +} + +static void +tf_call_content_init (TfCallContent *self) +{ + self->fsstreams = g_ptr_array_new (); + + self->mutex = g_mutex_new (); +} + +static void +tf_call_content_dispose (GObject *object) +{ + TfCallContent *self = TF_CALL_CONTENT (object); + + g_debug (G_STRFUNC); + + if (self->streams) + g_hash_table_destroy (self->streams); + self->streams = NULL; + + if (self->fssession) + { + g_object_run_dispose (G_OBJECT (self->fssession)); + g_object_unref (self->fssession); + } + self->fssession = NULL; + + if (self->fsstreams) + { + while (self->fsstreams->len) + free_content_fsstream ( + g_ptr_array_remove_index_fast (self->fsstreams, 0)); + g_ptr_array_unref (self->fsstreams); + } + self->fsstreams = NULL; + + if (self->notifier) + g_object_unref (self->notifier); + self->notifier = NULL; + + if (self->fsconference) + _tf_call_channel_put_conference (self->call_channel, + self->fsconference); + self->fsconference = NULL; + + if (self->proxy) + g_object_unref (self->proxy); + self->proxy = NULL; + + /* We do not hold a ref to the call channel, and use it as a flag to ensure + * we will bail out when disposed */ + self->call_channel = NULL; + + if (G_OBJECT_CLASS (tf_call_content_parent_class)->dispose) + G_OBJECT_CLASS (tf_call_content_parent_class)->dispose (object); +} + + +static void +tf_call_content_finalize (GObject *object) +{ + TfCallContent *self = TF_CALL_CONTENT (object); + + g_mutex_free (self->mutex); + + if (G_OBJECT_CLASS (tf_call_content_parent_class)->finalize) + G_OBJECT_CLASS (tf_call_content_parent_class)->finalize (object); +} + + +static void +tf_call_content_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + TfCallContent *self = TF_CALL_CONTENT (object); + + switch (property_id) + { + case PROP_TF_CHANNEL: + if (self->call_channel) + g_value_set_object (value, self->call_channel); + break; + case PROP_FS_CONFERENCE: + if (self->fsconference) + g_value_set_object (value, self->fsconference); + break; + case PROP_FS_SESSION: + if (self->fssession) + g_value_set_object (value, self->fssession); + break; + case PROP_SINK_PAD: + if (self->fssession) + g_object_get_property (G_OBJECT (self->fssession), "sink-pad", value); + break; + case PROP_MEDIA_TYPE: + g_value_set_enum (value, tf_call_content_get_fs_media_type (self)); + break; + case PROP_OBJECT_PATH: + g_object_get_property (G_OBJECT (self->proxy), "object-path", value); + break; + case PROP_FRAMERATE: + g_value_set_uint (value, self->framerate); + break; + case PROP_WIDTH: + g_value_set_uint (value, self->width); + break; + case PROP_HEIGHT: + g_value_set_uint (value, self->height); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +create_stream (TfCallContent *self, gchar *stream_path) +{ + GError *error = NULL; + TfCallStream *stream = tf_call_stream_new (self->call_channel, self, + stream_path, &error); + + if (error) + { + /* TODO: Use per-stream errors */ + tf_content_error (TF_CONTENT (self), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error creating the stream object: %s", error->message); + return; + } + + g_hash_table_insert (self->streams, stream_path, stream); +} + +static void +add_stream (TfCallContent *self, const gchar *stream_path) +{ + + if (!self->fsconference) + { + self->outstanding_streams = g_list_prepend (self->outstanding_streams, + g_strdup (stream_path)); + } else { + create_stream (self, g_strdup (stream_path)); + } +} + +static void +update_streams (TfCallContent *self) +{ + GList *l; + + g_assert (self->fsconference); + + for (l = self->outstanding_streams ; l != NULL; l = l->next) + create_stream (self, l->data); + + g_list_free (self->outstanding_streams); + self->outstanding_streams = NULL; +} + +static void +tpparam_to_fsparam (gpointer key, gpointer value, gpointer user_data) +{ + gchar *name = key; + gchar *val = value; + FsCodec *fscodec = user_data; + + fs_codec_add_optional_parameter (fscodec, name, val); +} + +static GList * +tpcodecs_to_fscodecs (FsMediaType fsmediatype, const GPtrArray *tpcodecs) +{ + GList *fscodecs = NULL; + guint i; + + for (i = 0; i < tpcodecs->len; i++) + { + GValueArray *tpcodec = g_ptr_array_index (tpcodecs, i); + guint pt; + gchar *name; + guint clock_rate; + guint channels; + GHashTable *params; + FsCodec *fscodec; + + tp_value_array_unpack (tpcodec, 5, &pt, &name, &clock_rate, &channels, + ¶ms); + + fscodec = fs_codec_new (pt, name, fsmediatype, clock_rate); + fscodec->channels = channels; + + g_hash_table_foreach (params, tpparam_to_fsparam, fscodec); + + fscodecs = g_list_prepend (fscodecs, fscodec); + } + + fscodecs = g_list_reverse (fscodecs); + + return fscodecs; +} + +static void +process_codec_offer_try_codecs (TfCallContent *self, FsStream *fsstream, + TpProxy *offer, GList *fscodecs) +{ + gboolean success = TRUE; + GError *error = NULL; + + if (fscodecs != NULL) + success = fs_stream_set_remote_codecs (fsstream, fscodecs, &error); + + fs_codec_list_destroy (fscodecs); + + if (success) + { + self->current_offer = offer; + tf_call_content_try_sending_codecs (self); + } + else + { + tf_future_cli_call_content_codec_offer_call_reject (offer, + -1, NULL, NULL, NULL, NULL); + g_object_unref (offer); + } + +} + +static void +process_codec_offer (TfCallContent *self, const gchar *offer_objpath, + guint contact_handle, const GPtrArray *codecs) +{ + TpProxy *proxy; + GError *error = NULL; + FsStream *fsstream; + GList *fscodecs; + + /* Guard against early disposal */ + if (self->call_channel == NULL) + return; + + if (!tp_dbus_check_valid_object_path (offer_objpath, &error)) + { + tf_content_error (TF_CONTENT (self), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Invalid offer path: %s", error->message); + g_clear_error (&error); + return; + } + + proxy = g_object_new (TP_TYPE_PROXY, + "dbus-daemon", tp_proxy_get_dbus_daemon (self->proxy), + "bus-name", tp_proxy_get_bus_name (self->proxy), + "object-path", offer_objpath, + NULL); + tp_proxy_add_interface_by_id (TP_PROXY (proxy), + TF_FUTURE_IFACE_QUARK_CALL_CONTENT_CODEC_OFFER); + + fscodecs = tpcodecs_to_fscodecs (tp_media_type_to_fs (self->media_type), + codecs); + + fsstream = tf_call_content_get_existing_fsstream_by_handle (self, + contact_handle); + + if (!fsstream) + { + g_debug ("Delaying codec offer processing"); + self->current_offer = proxy; + self->current_offer_contact_handle = contact_handle; + self->current_offer_fscodecs = fscodecs; + return; + } + + process_codec_offer_try_codecs (self, fsstream, proxy, fscodecs); +} + +static void +on_content_video_keyframe_requested (TfFutureCallContent *proxy, + gpointer user_data, + GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + GstPad *pad; + + /* Guard against early disposal */ + if (self->call_channel == NULL) + return; + + /* In case there is no session, ignore the request a new session should start + * with sending a KeyFrame in any case */ + if (self->fssession == NULL) + return; + + g_object_get (self->fssession, "sink-pad", &pad, NULL); + + if (pad == NULL) + { + g_warning ("Failed to get a pad for the keyframe request"); + return; + } + + g_message ("Sending out a keyframe request"); + gst_pad_send_event (pad, + gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, + gst_structure_new ("GstForceKeyUnit", + "all-headers", G_TYPE_BOOLEAN, TRUE, + NULL))); + + g_object_unref (pad); +} + +static void +on_content_video_resolution_changed (TfFutureCallContent *proxy, + const GValueArray *resolution, + gpointer user_data, + GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + guint width, height; + + /* Guard against early disposal */ + if (self->call_channel == NULL) + return; + + tp_value_array_unpack ((GValueArray *)resolution, 2, + &width, &height, NULL); + + /* Can be 0 in the initial property dump, shouldn't be at any other time */ + if (width == 0 || height == 0) + return; + + self->width = width; + self->height = height; + + g_signal_emit (self, signals[RESOLUTION_CHANGED], 0, width, height); + g_signal_emit_by_name (self, "restart-source"); + + g_message ("requested video resolution: %dx%d", width, height); +} + +static void +on_content_video_bitrate_changed (TfFutureCallContent *proxy, + guint bitrate, + gpointer user_data, + GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + + /* Guard against early disposal */ + if (self->call_channel == NULL) + return; + + g_message ("Setting bitrate to %d bits/s", bitrate); + self->bitrate = bitrate; + + if (self->fssession != NULL && self->bitrate > 0) + g_object_set (self->fssession, "send-bitrate", self->bitrate, NULL); +} + +static void +on_content_video_framerate_changed (TfFutureCallContent *proxy, + guint framerate, + gpointer user_data, + GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + + /* Guard against early disposal */ + if (self->call_channel == NULL) + return; + + g_message ("updated framerate requested: %d", framerate); + + self->framerate = framerate; + g_object_notify (G_OBJECT (self), "framerate"); + g_signal_emit_by_name (self, "restart-source"); +} + +static void +on_content_video_mtu_changed (TfFutureCallContent *proxy, + guint mtu, + gpointer user_data, + GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + + /* Guard against early disposal */ + if (self->call_channel == NULL) + return; + + g_atomic_int_set (&self->mtu, mtu); + + if (self->fsconference != NULL) + { + fs_element_added_notifier_remove (self->notifier, + GST_BIN (self->fsconference)); + + if (mtu > 0 || self->manual_keyframes) + fs_element_added_notifier_add (self->notifier, + GST_BIN (self->fsconference)); + } +} + + +static void +got_content_media_properties (TpProxy *proxy, GHashTable *properties, + const GError *error, gpointer user_data, GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + GSimpleAsyncResult *res = user_data; + GValueArray *gva; + const gchar *offer_objpath; + guint contact; + GError *myerror = NULL; + GPtrArray *codecs; + guint32 packetization; + const gchar *conference_type; + gboolean valid; + GList *codec_prefs; + + /* Guard against early disposal */ + if (self->call_channel == NULL) + { + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Call content has been disposed of"); + g_simple_async_result_complete (res); + g_object_unref (res); + return; + } + + if (error != NULL) + { + tf_content_error (TF_CONTENT (self), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error getting the Content's media properties: %s", error->message); + g_simple_async_result_set_from_error (res, error); + g_simple_async_result_complete (res); + g_object_unref (res); + return; + } + + packetization = tp_asv_get_uint32 (properties, "Packetization", &valid); + + if (!valid) + goto invalid_property; + + g_assert (self->fssession == NULL); + + switch (packetization) + { + case TF_FUTURE_CALL_CONTENT_PACKETIZATION_TYPE_RTP: + conference_type = "rtp"; + break; + case TF_FUTURE_CALL_CONTENT_PACKETIZATION_TYPE_RAW: + conference_type = "raw"; + break; + default: + tf_content_error (TF_CONTENT (self), + TF_FUTURE_CONTENT_REMOVAL_REASON_UNSUPPORTED, "", + "Could not create FsConference for type %d", packetization); + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Could not create FsConference for type %d", packetization); + g_simple_async_result_complete (res); + g_object_unref (res); + return; + } + + self->fsconference = _tf_call_channel_get_conference (self->call_channel, + conference_type); + if (!self->fsconference) + { + tf_content_error (TF_CONTENT (self), + TF_FUTURE_CONTENT_REMOVAL_REASON_UNSUPPORTED, "", + "Could not create FsConference for type %s", conference_type); + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Error getting the Content's properties: invalid type"); + g_simple_async_result_complete (res); + g_object_unref (res); + return; + } + + self->fssession = fs_conference_new_session (self->fsconference, + tp_media_type_to_fs (self->media_type), &myerror); + + if (!self->fssession) + { + tf_content_error (TF_CONTENT (self), + TF_FUTURE_CONTENT_REMOVAL_REASON_UNSUPPORTED, "", + "Could not create FsSession: %s", myerror->message); + g_simple_async_result_set_from_error (res, myerror); + g_simple_async_result_complete (res); + g_clear_error (&myerror); + g_object_unref (res); + return; + } + + if (self->notifier != NULL) + fs_element_added_notifier_add (self->notifier, + GST_BIN (self->fsconference)); + + /* Now process outstanding streams */ + update_streams (self); + + gva = tp_asv_get_boxed (properties, "CodecOffer", + TF_FUTURE_STRUCT_TYPE_CODEC_OFFERING); + if (gva == NULL) + { + goto invalid_property; + } + + codec_prefs = fs_utils_get_default_codec_preferences ( + GST_ELEMENT (self->fsconference)); + + if (codec_prefs) + { + if (!fs_session_set_codec_preferences (self->fssession, codec_prefs, + &myerror)) + { + g_warning ("Could not set codec preference: %s", myerror->message); + g_clear_error (&myerror); + } + } + + /* First complete so we get signalled and the preferences can be set, then + * start looking at the offer. We only unref the result later, to avoid + * self possibly being disposed early */ + g_simple_async_result_set_op_res_gboolean (res, TRUE); + g_simple_async_result_complete (res); + + tp_value_array_unpack (gva, 3, &offer_objpath, &contact, &codecs); + + process_codec_offer (self, offer_objpath, contact, codecs); + + self->got_codec_offer_property = TRUE; + + /* The async result holds a ref to self which may be the last one, so this + * comes after we're done with self */ + g_object_unref (res); + return; + + invalid_property: + tf_content_error_literal (TF_CONTENT (self), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error getting the Content's properties: invalid type"); + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Error getting the Content's properties: invalid type"); + g_simple_async_result_complete (res); + g_object_unref (res); + return; +} + +static void +setup_content_media_properties (TfCallContent *self, + TpProxy *proxy, + GSimpleAsyncResult *res) +{ + tp_cli_dbus_properties_call_get_all (proxy, -1, + TF_FUTURE_IFACE_CALL_CONTENT_INTERFACE_MEDIA, + got_content_media_properties, res, NULL, G_OBJECT (self)); +} + +static gboolean +object_has_property (GObject *object, const gchar *property) +{ + return g_object_class_find_property (G_OBJECT_GET_CLASS (object), + property) != NULL; +} + +static void +content_video_element_added (FsElementAddedNotifier *notifier, + GstBin *conference, + GstElement *element, + TfCallContent *self) +{ + gint mtu = g_atomic_int_get (&self->mtu); + + if (G_UNLIKELY (mtu == 0 && !self->manual_keyframes)) + return; + + if (G_UNLIKELY (mtu > 0 && object_has_property (G_OBJECT (element), "mtu"))) + { + g_message ("Setting %d as mtu on payloader", mtu); + g_object_set (element, "mtu", mtu, NULL); + } + + if (G_UNLIKELY (self->manual_keyframes)) + { + if (object_has_property (G_OBJECT (element), "key-int-max")) + { + g_message ("Setting key-int-max to max uint"); + g_object_set (element, "key-int-max", G_MAXINT, NULL); + } + + if (object_has_property (G_OBJECT (element), "intra-period")) + { + g_message ("Setting intra-period to 0"); + g_object_set (element, "intra-period", 0, NULL); + } + } +} + +static void +got_content_video_control_properties (TpProxy *proxy, GHashTable *properties, + const GError *error, gpointer user_data, GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + GSimpleAsyncResult *res = user_data; + GValueArray *array; + guint32 bitrate, mtu; + gboolean valid; + gboolean manual_keyframes; + + if (error) + { + tf_content_error (TF_CONTENT (self), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error getting the Content's VideoControl properties: %s", + error->message); + g_simple_async_result_set_from_error (res, error); + goto error; + } + + /* Guard against early disposal */ + if (self->call_channel == NULL) + { + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Call content has been disposed of"); + goto error; + } + + if (properties == NULL) + { + tf_content_error_literal (TF_CONTENT (self), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error getting the Content's VideoControl properties: " + "there are none"); + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Error getting the VideoControl Content's properties: " + "there are none"); + goto error; + } + + + /* Only get the various variables, we will not have an FsSession untill the + * media properties are retrieved so no need to act just yet */ + bitrate = tp_asv_get_uint32 (properties, "Bitrate", &valid); + if (valid) + self->bitrate = bitrate; + + mtu = tp_asv_get_uint32 (properties, "MTU", &valid); + if (valid) + self->mtu = mtu; + + manual_keyframes = tp_asv_get_boolean (properties, + "ManualKeyFrames", &valid); + if (valid) + self->manual_keyframes = manual_keyframes; + + array = tp_asv_get_boxed (properties, "VideoResolution", + TF_FUTURE_STRUCT_TYPE_VIDEO_RESOLUTION); + if (array) + on_content_video_resolution_changed (TF_FUTURE_CALL_CONTENT (proxy), array, + NULL, G_OBJECT (self)); + + self->notifier = fs_element_added_notifier_new (); + g_signal_connect (self->notifier, "element-added", + G_CALLBACK (content_video_element_added), self); + + setup_content_media_properties (self, proxy, res); + return; + +error: + g_simple_async_result_complete (res); + g_object_unref (res); + return; +} + + +static void +setup_content_video_control (TfCallContent *self, + TpProxy *proxy, + GSimpleAsyncResult *res) +{ + GError *error = NULL; + + tp_proxy_add_interface_by_id (proxy, + TF_FUTURE_IFACE_QUARK_CALL_CONTENT_INTERFACE_VIDEO_CONTROL); + + if (tf_future_cli_call_content_interface_video_control_connect_to_key_frame_requested ( + TF_FUTURE_CALL_CONTENT (proxy), + on_content_video_keyframe_requested, + NULL, NULL, G_OBJECT (self), &error) == NULL) + goto connect_failed; + + if (tf_future_cli_call_content_interface_video_control_connect_to_video_resolution_changed ( + TF_FUTURE_CALL_CONTENT (proxy), + on_content_video_resolution_changed, + NULL, NULL, G_OBJECT (self), &error) == NULL) + goto connect_failed; + + if (tf_future_cli_call_content_interface_video_control_connect_to_bitrate_changed ( + TF_FUTURE_CALL_CONTENT (proxy), + on_content_video_bitrate_changed, + NULL, NULL, G_OBJECT (self), NULL) == NULL) + goto connect_failed; + + if (tf_future_cli_call_content_interface_video_control_connect_to_framerate_changed ( + TF_FUTURE_CALL_CONTENT (proxy), + on_content_video_framerate_changed, + NULL, NULL, G_OBJECT (self), NULL) == NULL) + goto connect_failed; + + if (tf_future_cli_call_content_interface_video_control_connect_to_mtu_changed ( + TF_FUTURE_CALL_CONTENT (proxy), + on_content_video_mtu_changed, + NULL, NULL, G_OBJECT (self), NULL) == NULL) + goto connect_failed; + + tp_cli_dbus_properties_call_get_all (proxy, -1, + TF_FUTURE_IFACE_CALL_CONTENT_INTERFACE_VIDEO_CONTROL, + got_content_video_control_properties, res, NULL, G_OBJECT (self)); + + return; + +connect_failed: + tf_content_error (TF_CONTENT (self), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error getting the Content's VideoControl properties: %s", + error->message); + g_simple_async_result_take_error (res, error); + g_simple_async_result_complete (res); + g_object_unref (res); +} + +static void +new_codec_offer (TfFutureCallContent *proxy, + guint arg_Contact, + const gchar *arg_Offer, + const GPtrArray *arg_Codecs, + gpointer user_data, + GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + + /* Guard against early disposal */ + if (self->call_channel == NULL) + return; + + /* Ignore signals before we get the first codec offer property */ + if (!self->got_codec_offer_property) + return; + + if (self->current_offer) { + g_object_unref (self->current_offer); + fs_codec_list_destroy (self->current_offer_fscodecs); + self->current_offer = NULL; + self->current_offer_fscodecs = NULL; + } + + process_codec_offer (self, arg_Offer, arg_Contact, arg_Codecs); +} + + +static void +got_content_properties (TpProxy *proxy, GHashTable *out_Properties, + const GError *error, gpointer user_data, GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + GSimpleAsyncResult *res = user_data; + gboolean valid; + GPtrArray *streams; + GError *myerror = NULL; + guint i; + const gchar * const *interfaces; + gboolean got_media_interface = FALSE; + gboolean got_video_control_interface = FALSE; + + if (error) + { + tf_content_error (TF_CONTENT (self), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error getting the Content's properties: %s", error->message); + g_simple_async_result_set_from_error (res, error); + g_simple_async_result_complete (res); + g_object_unref (res); + return; + } + + /* Guard against early disposal */ + if (self->call_channel == NULL) + { + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Call content has been disposed of"); + g_simple_async_result_complete (res); + g_object_unref (res); + return; + } + + if (!out_Properties) + { + tf_content_error_literal (TF_CONTENT (self), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error getting the Content's properties: there are none"); + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Error getting the Content's properties: there are none"); + g_simple_async_result_complete (res); + g_object_unref (res); + return; + } + + interfaces = tp_asv_get_strv (out_Properties, "Interfaces"); + + if (interfaces == NULL) + { + tf_content_error_literal (TF_CONTENT (self), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Content does not have the Interfaces property, " + "but HardwareStreaming was NOT true"); + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Content does not have the Interfaces property, " + "but HardwareStreaming was NOT true"); + g_simple_async_result_complete (res); + g_object_unref (res); + return; + } + + for (i = 0; interfaces[i]; i++) + { + if (!strcmp (interfaces[i], + TF_FUTURE_IFACE_CALL_CONTENT_INTERFACE_MEDIA)) + got_media_interface = TRUE; + + if (!strcmp (interfaces[i], + TF_FUTURE_IFACE_CALL_CONTENT_INTERFACE_VIDEO_CONTROL)) + got_video_control_interface = TRUE; + } + + if (!got_media_interface) + { + tf_content_error_literal (TF_CONTENT (self), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Content does not have the media interface," + " but HardwareStreaming was NOT true"); + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Content does not have the media interface," + " but HardwareStreaming was NOT true"); + g_simple_async_result_complete (res); + g_object_unref (res); + return; + } + + self->media_type = tp_asv_get_uint32 (out_Properties, "Type", &valid); + if (!valid) + goto invalid_property; + + + streams = tp_asv_get_boxed (out_Properties, "Streams", + TP_ARRAY_TYPE_OBJECT_PATH_LIST); + if (!streams) + goto invalid_property; + + self->streams = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_object_unref); + + for (i = 0; i < streams->len; i++) + add_stream (self, g_ptr_array_index (streams, i)); + + tp_proxy_add_interface_by_id (TP_PROXY (self->proxy), + TF_FUTURE_IFACE_QUARK_CALL_CONTENT_INTERFACE_MEDIA); + + + tf_future_cli_call_content_interface_media_connect_to_new_codec_offer ( + TF_FUTURE_CALL_CONTENT (proxy), new_codec_offer, NULL, NULL, + G_OBJECT (self), &myerror); + + if (myerror) + { + tf_content_error (TF_CONTENT (self), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error connectiong to NewCodecOffer signal: %s", + myerror->message); + g_simple_async_result_set_from_error (res, myerror); + g_simple_async_result_complete (res); + g_object_unref (res); + g_clear_error (&myerror); + return; + } + + if (got_video_control_interface) + setup_content_video_control (self, proxy, res); + else + setup_content_media_properties (self, proxy, res); + + return; + + invalid_property: + tf_content_error_literal (TF_CONTENT (self), TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, + "", "Error getting the Content's properties: invalid type"); + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Error getting the Content's properties: invalid type"); + g_simple_async_result_complete (res); + g_object_unref (res); + return; +} + +static void +streams_added (TfFutureCallContent *proxy, + const GPtrArray *arg_Streams, + gpointer user_data, + GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + guint i; + + /* Ignore signals before we got the "Contents" property to avoid races that + * could cause the same content to be added twice + */ + + if (!self->streams) + return; + + for (i = 0; i < arg_Streams->len; i++) + add_stream (self, g_ptr_array_index (arg_Streams, i)); +} + +static void +streams_removed (TfFutureCallContent *proxy, + const GPtrArray *arg_Streams, + gpointer user_data, + GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + guint i; + + if (!self->streams) + return; + + for (i = 0; i < arg_Streams->len; i++) + g_hash_table_remove (self->streams, g_ptr_array_index (arg_Streams, i)); +} + + +static void +tf_call_content_init_async (GAsyncInitable *initable, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + TfCallContent *self = TF_CALL_CONTENT (initable); + GError *myerror = NULL; + GSimpleAsyncResult *res; + + if (cancellable != NULL) + { + g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data, + G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, + "TfCallChannel initialisation does not support cancellation"); + return; + } + + tf_future_cli_call_content_connect_to_streams_added ( + TF_FUTURE_CALL_CONTENT (self->proxy), streams_added, NULL, NULL, + G_OBJECT (self), &myerror); + if (myerror) + { + tf_content_error (TF_CONTENT (self), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error connectiong to StreamAdded signal: %s", myerror->message); + g_simple_async_report_gerror_in_idle (G_OBJECT (self), callback, + user_data, myerror); + return; + } + + tf_future_cli_call_content_connect_to_streams_removed ( + TF_FUTURE_CALL_CONTENT (self->proxy), streams_removed, NULL, NULL, + G_OBJECT (self), &myerror); + if (myerror) + { + tf_content_error (TF_CONTENT (self), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error connectiong to StreamRemoved signal: %s", myerror->message); + g_simple_async_report_gerror_in_idle (G_OBJECT (self), callback, + user_data, myerror); + return; + } + + + res = g_simple_async_result_new (G_OBJECT (self), callback, user_data, + tf_call_content_init_async); + + tp_cli_dbus_properties_call_get_all (self->proxy, -1, + TF_FUTURE_IFACE_CALL_CONTENT, got_content_properties, res, + NULL, G_OBJECT (self)); +} + +static gboolean +tf_call_content_init_finish (GAsyncInitable *initable, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple_res; + + g_return_val_if_fail (g_simple_async_result_is_valid (res, + G_OBJECT (initable), tf_call_content_init_async), FALSE); + simple_res = G_SIMPLE_ASYNC_RESULT (res); + + if (g_simple_async_result_propagate_error (simple_res, error)) + return FALSE; + + return g_simple_async_result_get_op_res_gboolean (simple_res); +} + +TfCallContent * +tf_call_content_new_async (TfCallChannel *call_channel, + const gchar *object_path, GError **error, + GAsyncReadyCallback callback, gpointer user_data) +{ + TfCallContent *self; + TfFutureCallContent *proxy = tf_future_call_content_new ( + call_channel->proxy, object_path, error); + + if (!proxy) + return NULL; + + self = g_object_new (TF_TYPE_CALL_CONTENT, NULL); + + self->call_channel = call_channel; + self->proxy = proxy; + + g_async_initable_init_async (G_ASYNC_INITABLE (self), 0, NULL, + callback, user_data); + + return g_object_ref (self); +} + + + +static GPtrArray * +fscodecs_to_tpcodecs (GList *codecs) +{ + GPtrArray *tpcodecs = g_ptr_array_new (); + GList *item; + + for (item = codecs; item; item = item->next) + { + FsCodec *fscodec = item->data; + GValue tpcodec = { 0, }; + GHashTable *params; + GList *param_item; + + params = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + for (param_item = fscodec->optional_params; + param_item; + param_item = param_item->next) + { + FsCodecParameter *param = (FsCodecParameter *) param_item->data; + + g_hash_table_insert (params, g_strdup (param->name), + g_strdup (param->value)); + } + + g_value_init (&tpcodec, TF_FUTURE_STRUCT_TYPE_CODEC); + g_value_take_boxed (&tpcodec, + dbus_g_type_specialized_construct (TF_FUTURE_STRUCT_TYPE_CODEC)); + + dbus_g_type_struct_set (&tpcodec, + 0, fscodec->id, + 1, fscodec->encoding_name, + 2, fscodec->clock_rate, + 3, fscodec->channels, + 4, params, + G_MAXUINT); + + + g_hash_table_destroy (params); + + g_ptr_array_add (tpcodecs, g_value_get_boxed (&tpcodec)); + } + + return tpcodecs; +} + +static void +tf_call_content_try_sending_codecs (TfCallContent *self) +{ + GList *codecs; + GPtrArray *tpcodecs; + const gchar *codecs_prop = NULL; + + if (self->current_offer_fscodecs != NULL) + { + g_debug ("Ignoring updated codecs unprocessed offer outstanding"); + return; + } + + g_debug ("updating local codecs"); + + if (TF_CONTENT (self)->sending_count == 0) + codecs_prop = "codecs-without-config"; + else + codecs_prop = "codecs"; + + g_object_get (self->fssession, codecs_prop, &codecs, NULL); + + if (!codecs) + return; + + if (fs_codec_list_are_equal (codecs, self->current_codecs)) + { + fs_codec_list_destroy (codecs); + return; + } + + tpcodecs = fscodecs_to_tpcodecs (codecs); + + if (self->current_offer) + { + tf_future_cli_call_content_codec_offer_call_accept (self->current_offer, + -1, tpcodecs, NULL, NULL, NULL, NULL); + + g_object_unref (self->current_offer); + self->current_offer = NULL; + } + else + { + tf_future_cli_call_content_interface_media_call_update_codecs ( + self->proxy, -1, tpcodecs, NULL, NULL, NULL, NULL); + } + + g_boxed_free (TF_FUTURE_ARRAY_TYPE_CODEC_LIST, tpcodecs); +} + +gboolean +tf_call_content_bus_message (TfCallContent *content, + GstMessage *message) +{ + const GstStructure *s; + gboolean ret = FALSE; + const gchar *debug; + GHashTableIter iter; + gpointer key, value; + + /* Guard against early disposal */ + if (content->call_channel == NULL) + return FALSE; + + if (!content->fssession) + return FALSE; + + if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT) + return FALSE; + + s = gst_message_get_structure (message); + + if (gst_structure_has_name (s, "farstream-error")) + { + GObject *object; + const GValue *value = NULL; + + value = gst_structure_get_value (s, "src-object"); + object = g_value_get_object (value); + + if (object == (GObject*) content->fssession) + { + const gchar *msg; + FsError errorno; + GEnumClass *enumclass; + GEnumValue *enumvalue; + + value = gst_structure_get_value (s, "error-no"); + errorno = g_value_get_enum (value); + msg = gst_structure_get_string (s, "error-msg"); + debug = gst_structure_get_string (s, "debug-msg"); + + enumclass = g_type_class_ref (FS_TYPE_ERROR); + enumvalue = g_enum_get_value (enumclass, errorno); + g_warning ("error (%s (%d)): %s : %s", + enumvalue->value_nick, errorno, msg, debug); + g_type_class_unref (enumclass); + + tf_content_error_literal (TF_CONTENT (content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", msg); + + ret = TRUE; + } + } + else if (gst_structure_has_name (s, "farstream-codecs-changed")) + { + FsSession *fssession; + const GValue *value; + + value = gst_structure_get_value (s, "session"); + fssession = g_value_get_object (value); + + if (fssession != content->fssession) + return FALSE; + + g_debug ("Codecs changed"); + + tf_call_content_try_sending_codecs (content); + + ret = TRUE; + } + + g_hash_table_iter_init (&iter, content->streams); + while (g_hash_table_iter_next (&iter, &key, &value)) + if (tf_call_stream_bus_message (value, message)) + return TRUE; + + return ret; +} + +static void +tf_call_content_error (TfContent *content, + guint reason, /* TfFutureContentRemovalReason */ + const gchar *detailed_reason, + const gchar *message) +{ + TfCallContent *self = TF_CALL_CONTENT (content); + + g_warning ("%s", message); + tf_future_cli_call_content_call_remove ( + self->proxy, -1, reason, detailed_reason, message, NULL, NULL, + NULL, NULL); +} + + + +static FsStream * +tf_call_content_get_existing_fsstream_by_handle (TfCallContent *content, + guint contact_handle) +{ + guint i; + + TF_CALL_CONTENT_LOCK (content); + + for (i = 0; i < content->fsstreams->len; i++) + { + struct CallFsStream *cfs = g_ptr_array_index (content->fsstreams, i); + + if (cfs->contact_handle == contact_handle) + { + cfs->use_count++; + TF_CALL_CONTENT_UNLOCK (content); + return cfs->fsstream; + } + } + + TF_CALL_CONTENT_UNLOCK (content); + + return NULL; +} + + +FsStream * +_tf_call_content_get_fsstream_by_handle (TfCallContent *content, + guint contact_handle, + const gchar *transmitter, + guint stream_transmitter_n_parameters, + GParameter *stream_transmitter_parameters, + GError **error) +{ + struct CallFsStream *cfs; + FsParticipant *p; + FsStream *s; + + s = tf_call_content_get_existing_fsstream_by_handle (content, + contact_handle); + if (s) + return s; + + p = _tf_call_channel_get_participant (content->call_channel, + content->fsconference, contact_handle, error); + if (!p) + return NULL; + + s = fs_session_new_stream (content->fssession, p, FS_DIRECTION_RECV, error); + if (!s) + { + _tf_call_channel_put_participant (content->call_channel, p); + return NULL; + } + + if (!fs_stream_set_transmitter (s, transmitter, + stream_transmitter_parameters, stream_transmitter_n_parameters, + error)) + { + g_object_unref (s); + _tf_call_channel_put_participant (content->call_channel, p); + return NULL; + } + + cfs = g_slice_new (struct CallFsStream); + cfs->use_count = 1; + cfs->contact_handle = contact_handle; + cfs->parent_channel = content->call_channel; + cfs->fsparticipant = p; + cfs->fsstream = s; + + tp_g_signal_connect_object (s, "src-pad-added", + G_CALLBACK (src_pad_added), content, 0); + + g_ptr_array_add (content->fsstreams, cfs); + content->fsstreams_cookie ++; + if (content->current_offer != NULL + && content->current_offer_contact_handle == contact_handle) + { + GList *codecs = content->current_offer_fscodecs; + TpProxy *current_offer = content->current_offer; + content->current_offer_fscodecs = NULL; + content->current_offer = NULL; + + /* ownership transfers to try_codecs */ + process_codec_offer_try_codecs (content, s, + current_offer, codecs); + } + + return s; +} + +void +_tf_call_content_put_fsstream (TfCallContent *content, FsStream *fsstream) +{ + guint i; + struct CallFsStream *fs_cfs = NULL; + + TF_CALL_CONTENT_LOCK (content); + for (i = 0; i < content->fsstreams->len; i++) + { + struct CallFsStream *cfs = g_ptr_array_index (content->fsstreams, i); + + if (cfs->fsstream == fsstream) + { + cfs->use_count--; + if (cfs->use_count <= 0) + { + fs_cfs = g_ptr_array_remove_index_fast (content->fsstreams, i); + content->fsstreams_cookie++; + } + break; + } + } + TF_CALL_CONTENT_UNLOCK (content); + + if (fs_cfs) + free_content_fsstream (fs_cfs); +} + +FsMediaType +tf_call_content_get_fs_media_type (TfCallContent *content) +{ + return tp_media_type_to_fs (content->media_type); +} + +static void +src_pad_added (FsStream *fsstream, GstPad *pad, FsCodec *codec, + TfCallContent *content) +{ + guint handle = 0; + guint i; + + TF_CALL_CONTENT_LOCK (content); + + if (!content->fsstreams) + { + TF_CALL_CONTENT_UNLOCK (content); + return; + } + + for (i = 0; i < content->fsstreams->len; i++) + { + struct CallFsStream *cfs = g_ptr_array_index (content->fsstreams, i); + if (cfs->fsstream == fsstream) + { + handle = cfs->contact_handle; + break; + } + } + + TF_CALL_CONTENT_UNLOCK (content); + + _tf_content_emit_src_pad_added (TF_CONTENT (content), handle, + fsstream, pad, codec); +} + +struct StreamSrcPadIterator { + GstIterator iterator; + + GArray *handles; + GArray *handles_backup; + + TfCallContent *self; +}; + +static GstIteratorResult +streams_src_pads_iter_next (GstIterator *it, gpointer *result) +{ + struct StreamSrcPadIterator *iter = (struct StreamSrcPadIterator *) it; + guint i; + + if (iter->handles->len == 0) + return GST_ITERATOR_DONE; + + for (i = 0; i < iter->self->fsstreams->len; i++) + { + struct CallFsStream *cfs = g_ptr_array_index (iter->self->fsstreams, i); + + if (cfs->contact_handle == g_array_index (iter->handles, guint, 0)) + { + g_array_remove_index_fast (iter->handles, 0); + *result = cfs; + return GST_ITERATOR_OK; + } + } + + return GST_ITERATOR_ERROR; + +} + +static GstIteratorItem +streams_src_pads_iter_item (GstIterator *it, gpointer item) +{ + struct CallFsStream *cfs = item; + + gst_iterator_push (it, fs_stream_iterate_src_pads (cfs->fsstream)); + + return GST_ITERATOR_ITEM_SKIP; +} + +static void +streams_src_pads_iter_resync (GstIterator *it) +{ + struct StreamSrcPadIterator *iter = (struct StreamSrcPadIterator *) it; + + g_array_set_size (iter->handles, iter->handles_backup->len); + memcpy (iter->handles->data, iter->handles_backup->data, + iter->handles_backup->len * sizeof(guint)); +} + +static void +streams_src_pads_iter_free (GstIterator *it) +{ + struct StreamSrcPadIterator *iter = (struct StreamSrcPadIterator *) it; + + g_array_unref (iter->handles); + g_array_unref (iter->handles_backup); + g_object_unref (iter->self); +} + +static GstIterator * +tf_call_content_iterate_src_pads (TfContent *content, guint *handles, + guint handle_count) +{ + TfCallContent *self = TF_CALL_CONTENT (content); + struct StreamSrcPadIterator *iter; + + iter = (struct StreamSrcPadIterator *) gst_iterator_new ( + sizeof (struct StreamSrcPadIterator), GST_TYPE_PAD, + self->mutex, &self->fsstreams_cookie, + streams_src_pads_iter_next, + streams_src_pads_iter_item, + streams_src_pads_iter_resync, + streams_src_pads_iter_free); + + iter->handles = g_array_sized_new (TRUE, FALSE, sizeof(guint), handle_count); + iter->handles_backup = g_array_sized_new (TRUE, FALSE, sizeof(guint), + handle_count); + g_array_append_vals (iter->handles, handles, handle_count); + g_array_append_vals (iter->handles_backup, handles, handle_count); + iter->self = g_object_ref (self); + + return (GstIterator *) iter; +} diff --git a/farstream/telepathy-farstream/call-content.h b/farstream/telepathy-farstream/call-content.h new file mode 100644 index 000000000..697148e97 --- /dev/null +++ b/farstream/telepathy-farstream/call-content.h @@ -0,0 +1,106 @@ +/* + * call-content.h - Source for TfCallContent + * Copyright (C) 2010 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __TF_CALL_CONTENT_H__ +#define __TF_CALL_CONTENT_H__ + +#include <glib-object.h> + +#include <gst/gst.h> +#include <telepathy-glib/channel.h> + +#include "extensions/extensions.h" +#include "call-channel.h" +#include "content.h" +#include "content-priv.h" + +G_BEGIN_DECLS + +#define TF_TYPE_CALL_CONTENT tf_call_content_get_type() + +#define TF_CALL_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + TF_TYPE_CALL_CONTENT, TfCallContent)) + +#define TF_CALL_CONTENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + TF_TYPE_CALL_CONTENT, TfCallContentClass)) + +#define TF_IS_CALL_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_CALL_CONTENT)) + +#define TF_IS_CALL_CONTENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_CALL_CONTENT)) + +#define TF_CALL_CONTENT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + TF_TYPE_CALL_CONTENT, TfCallContentClass)) + +typedef struct _TfCallContentPrivate TfCallContentPrivate; + +/** + * TfCallContent: + * + * All members of the object are private + */ + +typedef struct _TfCallContent TfCallContent; + +/** + * TfCallContentClass: + * @parent_class: the parent #GObjecClass + * + * There are no overridable functions + */ + +typedef struct _TfCallContentClass TfCallContentClass; + +GType tf_call_content_get_type (void); + +TfCallContent *tf_call_content_new_async ( + TfCallChannel *call_channel, + const gchar *object_path, + GError **error, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean tf_call_content_bus_message (TfCallContent *content, + GstMessage *message); + + +/* Private */ +FsStream *_tf_call_content_get_fsstream_by_handle (TfCallContent *content, + guint contact_handle, + const gchar *transmitter, + guint stream_transmitter_n_parameters, + GParameter *stream_transmitter_parameters, + GError **error); +void _tf_call_content_put_fsstream (TfCallContent *content, FsStream *fsstream); + +FsMediaType +tf_call_content_get_fs_media_type (TfCallContent *content); + + +gboolean +tf_call_content_bus_message (TfCallContent *content, GstMessage *message); + +G_END_DECLS + +#endif /* __TF_CALL_CONTENT_H__ */ + diff --git a/farstream/telepathy-farstream/call-stream.c b/farstream/telepathy-farstream/call-stream.c new file mode 100644 index 000000000..7658c9b51 --- /dev/null +++ b/farstream/telepathy-farstream/call-stream.c @@ -0,0 +1,1140 @@ +/* + * call-stream.c - Source for TfCallStream + * Copyright (C) 2010 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * SECTION:tfcallstream + * + * @short_description: Handle the Stream interface on a Channel + * + * This class handles the + * org.freedesktop.Telepathy.Channel.Interface.Stream on a + * channel using Farstream. + */ + +#include "call-stream.h" + +#include <telepathy-glib/util.h> +#include <telepathy-glib/interfaces.h> +#include <farstream/fs-conference.h> + +#include <stdarg.h> +#include <string.h> +#include <stdlib.h> + +#include <telepathy-glib/proxy-subclass.h> + +#include "extensions/extensions.h" + +#include "tf-signals-marshal.h" +#include "utils.h" + + +G_DEFINE_TYPE (TfCallStream, tf_call_stream, G_TYPE_OBJECT); + +static void tf_call_stream_dispose (GObject *object); + +static void +tf_call_stream_class_init (TfCallStreamClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = tf_call_stream_dispose; +} + +static void +tf_call_stream_init (TfCallStream *self) +{ +} + +static void +tf_call_stream_dispose (GObject *object) +{ + TfCallStream *self = TF_CALL_STREAM (object); + + g_debug (G_STRFUNC); + + if (self->proxy) + g_object_unref (self->proxy); + self->proxy = NULL; + + if (self->stun_servers) + g_boxed_free (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST, self->stun_servers); + self->stun_servers = NULL; + + if (self->relay_info) + g_boxed_free (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, self->relay_info); + self->relay_info = NULL; + + if (self->fsstream) + _tf_call_content_put_fsstream (self->call_content, self->fsstream); + self->fsstream = NULL; + + if (self->endpoint) + g_object_unref (self->endpoint); + self->endpoint = NULL; + + g_free (self->creds_username); + self->creds_username = NULL; + g_free (self->creds_password); + self->creds_password = NULL; + + fs_candidate_list_destroy (self->stored_remote_candidates); + self->stored_remote_candidates = NULL; + + if (G_OBJECT_CLASS (tf_call_stream_parent_class)->dispose) + G_OBJECT_CLASS (tf_call_stream_parent_class)->dispose (object); +} + +static void +local_sending_state_changed (TfFutureCallStream *proxy, + guint arg_State, + gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + + self->local_sending_state = arg_State; + + if (!self->fsstream) + return; + + if (arg_State == TF_FUTURE_SENDING_STATE_SENDING) + { + if (!self->has_send_resource) + { + if (_tf_content_start_sending (TF_CONTENT (self->call_content))) + { + self->has_send_resource = TRUE; + } + else + { + /* FIXME add a way to call to report media errors */ + g_warning ("Sending failed"); + return; + } + } + } + + switch (arg_State) + { + case TF_FUTURE_SENDING_STATE_PENDING_STOP_SENDING: + case TF_FUTURE_SENDING_STATE_PENDING_SEND: + /* the UI should respond to these */ + break; + case TF_FUTURE_SENDING_STATE_SENDING: + g_object_set (self->fsstream, "direction", FS_DIRECTION_BOTH, NULL); + break; + case TF_FUTURE_SENDING_STATE_NONE: + if (self->has_send_resource) + { + _tf_content_stop_sending (TF_CONTENT (self->call_content)); + + self->has_send_resource = FALSE; + } + g_object_set (self->fsstream, "direction", FS_DIRECTION_RECV, NULL); + break; + } +} + + +static void +tf_call_stream_try_adding_fsstream (TfCallStream *self) +{ + gchar *transmitter; + GError *error = NULL; + guint n_params = 0; + GParameter params[6] = { {NULL,} }; + GList *preferred_local_candidates = NULL; + guint i; + + memset (params, 0, sizeof(params)); + + if (!self->server_info_retrieved || + !self->has_contact || + !self->has_media_properties) + return; + + switch (self->transport_type) + { + case TF_FUTURE_STREAM_TRANSPORT_TYPE_RAW_UDP: + transmitter = "rawudp"; + + switch (tf_call_content_get_fs_media_type (self->call_content)) + { + case TP_MEDIA_STREAM_TYPE_VIDEO: + preferred_local_candidates = g_list_prepend (NULL, + fs_candidate_new (NULL, FS_COMPONENT_RTP, FS_CANDIDATE_TYPE_HOST, + FS_NETWORK_PROTOCOL_UDP, NULL, 9078)); + break; + case TP_MEDIA_STREAM_TYPE_AUDIO: + preferred_local_candidates = g_list_prepend (NULL, + fs_candidate_new (NULL, FS_COMPONENT_RTP, FS_CANDIDATE_TYPE_HOST, + FS_NETWORK_PROTOCOL_UDP, NULL, 7078)); + default: + break; + } + + if (preferred_local_candidates) + { + params[n_params].name = "preferred-local-candidates"; + g_value_init (¶ms[n_params].value, FS_TYPE_CANDIDATE_LIST); + g_value_take_boxed (¶ms[n_params].value, + preferred_local_candidates); + n_params++; + } + break; + case TF_FUTURE_STREAM_TRANSPORT_TYPE_ICE: + case TF_FUTURE_STREAM_TRANSPORT_TYPE_GTALK_P2P: + case TF_FUTURE_STREAM_TRANSPORT_TYPE_WLM_2009: + transmitter = "nice"; + + /* MISSING: Set Controlling mode property here */ + + params[n_params].name = "compatibility-mode"; + g_value_init (¶ms[n_params].value, G_TYPE_UINT); + switch (self->transport_type) + { + case TF_FUTURE_STREAM_TRANSPORT_TYPE_ICE: + g_value_set_uint (¶ms[n_params].value, 0); + break; + case TF_FUTURE_STREAM_TRANSPORT_TYPE_GTALK_P2P: + g_value_set_uint (¶ms[n_params].value, 1); + self->multiple_usernames = TRUE; + break; + case TF_FUTURE_STREAM_TRANSPORT_TYPE_WLM_2009: + g_value_set_uint (¶ms[n_params].value, 3); + break; + default: + break; + } + n_params++; + break; + case TF_FUTURE_STREAM_TRANSPORT_TYPE_SHM: + transmitter = "shm"; + params[n_params].name = "create-local-candidates"; + g_value_init (¶ms[n_params].value, G_TYPE_BOOLEAN); + g_value_set_boolean (¶ms[n_params].value, TRUE); + n_params++; + break; + default: + tf_content_error (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, + "org.freedesktop.Telepathy.Error.NotImplemented", + "Unknown transport type %d", self->transport_type); + return; + } + + if (self->stun_servers->len) + { + GValueArray *gva = g_ptr_array_index (self->stun_servers, 0); + gchar *ip; + guint port; + gchar *conn_timeout_str; + + /* We only use the first STUN server if there are many */ + + tp_value_array_unpack (gva, 2, &ip, &port); + + params[n_params].name = "stun-ip"; + g_value_init (¶ms[n_params].value, G_TYPE_STRING); + g_value_set_string (¶ms[n_params].value, ip); + n_params++; + + params[n_params].name = "stun-port"; + g_value_init (¶ms[n_params].value, G_TYPE_UINT); + g_value_set_uint (¶ms[n_params].value, port); + n_params++; + + conn_timeout_str = getenv ("FS_CONN_TIMEOUT"); + if (conn_timeout_str) + { + gint conn_timeout = strtol (conn_timeout_str, NULL, 10); + + params[n_params].name = "stun-timeout"; + g_value_init (¶ms[n_params].value, G_TYPE_UINT); + g_value_set_uint (¶ms[n_params].value, conn_timeout); + n_params++; + } + } + + if (self->relay_info->len) + { + GValueArray *fs_relay_info = g_value_array_new (0); + GValue val = {0}; + g_value_init (&val, GST_TYPE_STRUCTURE); + + for (i = 0; i < self->relay_info->len; i++) + { + GHashTable *one_relay = g_ptr_array_index(self->relay_info, i); + const gchar *type = NULL; + const gchar *ip; + guint32 port; + const gchar *username; + const gchar *password; + guint component; + GstStructure *s; + + ip = tp_asv_get_string (one_relay, "ip"); + port = tp_asv_get_uint32 (one_relay, "port", NULL); + type = tp_asv_get_string (one_relay, "type"); + username = tp_asv_get_string (one_relay, "username"); + password = tp_asv_get_string (one_relay, "password"); + component = tp_asv_get_uint32 (one_relay, "component", NULL); + + if (!ip || !port || !username || !password) + continue; + + if (!type) + type = "udp"; + + s = gst_structure_new ("relay-info", + "ip", G_TYPE_STRING, ip, + "port", G_TYPE_UINT, port, + "username", G_TYPE_STRING, username, + "password", G_TYPE_STRING, password, + "type", G_TYPE_STRING, type, + NULL); + + if (component) + gst_structure_set (s, "component", G_TYPE_UINT, component, NULL); + + + g_value_take_boxed (&val, s); + + g_value_array_append (fs_relay_info, &val); + g_value_reset (&val); + } + + if (fs_relay_info->n_values) + { + params[n_params].name = "relay-info"; + g_value_init (¶ms[n_params].value, G_TYPE_VALUE_ARRAY); + g_value_set_boxed (¶ms[n_params].value, fs_relay_info); + n_params++; + } + + g_value_array_free (fs_relay_info); + } + + self->fsstream = _tf_call_content_get_fsstream_by_handle (self->call_content, + self->contact_handle, + transmitter, + n_params, + params, + &error); + + for (i = 0; i < n_params; i++) + g_value_unset (¶ms[i].value); + + if (!self->fsstream) + { + tf_content_error (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, + "", + "Could not create FsStream: %s", error->message); + g_clear_error (&error); + return; + } + + if (self->local_sending_state == TF_FUTURE_SENDING_STATE_PENDING_SEND || + self->local_sending_state == TF_FUTURE_SENDING_STATE_SENDING) + local_sending_state_changed (self->proxy, self->local_sending_state, + NULL, (GObject *) self); +} + +static void +server_info_retrieved (TfFutureCallStream *proxy, + gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + + self->server_info_retrieved = TRUE; + + tf_call_stream_try_adding_fsstream (self); +} + +static void +relay_info_changed (TfFutureCallStream *proxy, + const GPtrArray *arg_Relay_Info, + gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + + if (self->server_info_retrieved) + { + tf_content_error_literal (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, + "org.freedesktop.Telepathy.Error.NotImplemented", + "Changing relay servers after ServerInfoRetrived is not implemented"); + return; + } + + /* Ignore signals that come before the basic info has been retrived */ + if (!self->relay_info) + return; + + g_boxed_free (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, self->relay_info); + self->relay_info = g_boxed_copy (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, + arg_Relay_Info); +} + +static void +stun_servers_changed (TfFutureCallStream *proxy, + const GPtrArray *arg_Servers, + gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + + if (self->server_info_retrieved) + { + tf_content_error_literal (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, + "org.freedesktop.Telepathy.Error.NotImplemented", + "Changing STUN servers after ServerInfoRetrived is not implemented"); + return; + } + + /* Ignore signals that come before the basic info has been retrived */ + if (!self->stun_servers) + return; + + g_boxed_free (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST, self->stun_servers); + self->stun_servers = g_boxed_copy (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST, + arg_Servers); +} + +static void +tf_call_stream_add_remote_candidates (TfCallStream *self, + const GPtrArray *candidates) +{ + GList *fscandidates = NULL; + guint i; + GError *error = NULL; + + /* No candidates to add, ignore. This could either be caused by the CM + * accidentally emitting an empty RemoteCandidatesAdded or when there are no + * remote candidates on the endpoint yet when we query it */ + if (candidates->len == 0) + return; + + for (i = 0; i < candidates->len; i++) + { + GValueArray *tpcandidate = g_ptr_array_index (candidates, i); + guint component; + gchar *ip; + guint port; + GHashTable *extra_info; + const gchar *foundation; + guint priority; + const gchar *username; + const gchar *password; + gboolean valid; + FsCandidate *cand; + + tp_value_array_unpack (tpcandidate, 4, &component, &ip, &port, + &extra_info); + + foundation = tp_asv_get_string (extra_info, "Foundation"); + if (!foundation) + foundation = ""; + priority = tp_asv_get_uint32 (extra_info, "Priority", &valid); + if (!valid) + priority = 0; + + username = tp_asv_get_string (extra_info, "Username"); + if (!username) + username = self->creds_username; + + password = tp_asv_get_string (extra_info, "Password"); + if (!password) + password = self->creds_password; + + cand = fs_candidate_new (foundation, component, FS_CANDIDATE_TYPE_HOST, + FS_NETWORK_PROTOCOL_UDP, ip, port); + cand->priority = priority; + cand->username = g_strdup (username); + cand->password = g_strdup (password); + + fscandidates = g_list_append (fscandidates, cand); + } + + if (self->fsstream) + { + if (!fs_stream_add_remote_candidates (self->fsstream, fscandidates, + &error)) + { + tf_content_error (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error setting the remote candidates: %s", error->message); + g_clear_error (&error); + } + fs_candidate_list_destroy (fscandidates); + } + else + { + self->stored_remote_candidates = + g_list_concat (self->stored_remote_candidates, fscandidates); + } +} + +static void +remote_candidates_added (TpProxy *proxy, + const GPtrArray *arg_Candidates, + gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + + tf_call_stream_add_remote_candidates (self, arg_Candidates); +} + +static void +remote_credentials_set (TpProxy *proxy, + const gchar *arg_Username, + const gchar *arg_Password, + gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + + if ((self->creds_username && strcmp (self->creds_username, arg_Username)) || + (self->creds_password && strcmp (self->creds_password, arg_Password))) + { + /* Remote credentials changed, this will perform a ICE restart, so + * clear old remote candidates */ + fs_candidate_list_destroy (self->stored_remote_candidates); + self->stored_remote_candidates = NULL; + } + + g_free (self->creds_username); + g_free (self->creds_password); + self->creds_username = g_strdup (arg_Username); + self->creds_password = g_strdup (arg_Password); +} + + +static void +got_endpoint_properties (TpProxy *proxy, GHashTable *out_Properties, + const GError *error, gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + GValueArray *credentials; + gchar *username, *password; + GPtrArray *candidates; + + if (error) + { + tf_content_error (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error getting the Streams's media properties: %s", error->message); + return; + } + + if (!out_Properties) + { + tf_content_error_literal (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error getting the Stream's media properties: there are none"); + return; + } + + + credentials = tp_asv_get_boxed (out_Properties, "RemoteCredentials", + TF_FUTURE_STRUCT_TYPE_STREAM_CREDENTIALS); + if (!credentials) + goto invalid_property; + tp_value_array_unpack (credentials, 2, &username, &password); + self->creds_username = g_strdup (username); + self->creds_password = g_strdup (password); + + candidates = tp_asv_get_boxed (out_Properties, "RemoteCandidates", + TF_FUTURE_ARRAY_TYPE_CANDIDATE_LIST); + if (!candidates) + goto invalid_property; + + tf_call_stream_add_remote_candidates (self, candidates); + + + return; + + invalid_property: + tf_content_error_literal (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error getting the Endpoint's properties: invalid type"); +} + +static void +tf_call_stream_add_endpoint (TfCallStream *self) +{ + GError *error = NULL; + + self->endpoint = g_object_new (TP_TYPE_PROXY, + "dbus-daemon", tp_proxy_get_dbus_daemon (self->proxy), + "bus-name", tp_proxy_get_bus_name (self->proxy), + "object-path", self->endpoint_objpath, + NULL); + tp_proxy_add_interface_by_id (TP_PROXY (self->endpoint), + TF_FUTURE_IFACE_QUARK_CALL_STREAM_ENDPOINT); + + tf_future_cli_call_stream_endpoint_connect_to_remote_credentials_set ( + TP_PROXY (self->endpoint), remote_credentials_set, NULL, NULL, + G_OBJECT (self), &error); + if (error) + { + tf_content_error (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error connectiong to RemoteCredentialsSet signal: %s", + error->message); + g_clear_error (&error); + return; + } + + tf_future_cli_call_stream_endpoint_connect_to_remote_candidates_added ( + TP_PROXY (self->endpoint), remote_candidates_added, NULL, NULL, + G_OBJECT (self), &error); + if (error) + { + tf_content_error (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error connectiong to RemoteCandidatesAdded signal: %s", + error->message); + g_clear_error (&error); + return; + } + + tp_cli_dbus_properties_call_get_all (self->endpoint, -1, + TF_FUTURE_IFACE_CALL_STREAM_ENDPOINT, + got_endpoint_properties, NULL, NULL, G_OBJECT (self)); +} + + +static void +endpoints_changed (TfFutureCallStream *proxy, + const GPtrArray *arg_Endpoints_Added, + const GPtrArray *arg_Endpoints_Removed, + gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + + /* Ignore signals before getting the properties to avoid races */ + if (!self->has_media_properties) + return; + + if (arg_Endpoints_Removed->len != 0) + { + tf_content_error_literal (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, + "org.freedesktop.Telepathy.Error.NotImplemented", + "Removing Endpoints is not implemented"); + return; + } + + if (arg_Endpoints_Added->len != 1) + { + tf_content_error_literal (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, + "org.freedesktop.Telepathy.Error.NotImplemented", + "Having more than one endpoint is not implemented"); + return; + } + + if (self->endpoint_objpath) + { + if (strcmp (g_ptr_array_index (arg_Endpoints_Added, 0), + self->endpoint_objpath)) + tf_content_error_literal (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Trying to give a different endpoint, CM bug"); + return; + } + + self->endpoint_objpath = g_strdup ( + g_ptr_array_index (arg_Endpoints_Added, 0)); + tf_call_stream_add_endpoint (self); +} + + +static void +got_stream_media_properties (TpProxy *proxy, GHashTable *out_Properties, + const GError *error, gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + GPtrArray *stun_servers; + GPtrArray *relay_info; + GPtrArray *endpoints; + gboolean valid; + + if (error) + { + tf_content_error (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error getting the Streams's media properties: %s", + error->message); + return; + } + + if (!out_Properties) + { + tf_content_error_literal (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error getting the Stream's media properties: there are none"); + return; + } + + self->transport_type = + tp_asv_get_uint32 (out_Properties, "Transport", &valid); + if (!valid) + { + g_warning ("No valid transport"); + goto invalid_property; + } + + stun_servers = tp_asv_get_boxed (out_Properties, "STUNServers", + TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST); + if (!stun_servers) + { + g_warning ("No valid STUN servers"); + goto invalid_property; + } + + relay_info = tp_asv_get_boxed (out_Properties, "RelayInfo", + TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST); + if (!relay_info) + { + g_warning ("No valid RelayInfo"); + goto invalid_property; + } + + self->server_info_retrieved = tp_asv_get_boolean (out_Properties, + "HasServerInfo", &valid); + if (!valid) + { + g_warning ("No valid server info"); + goto invalid_property; + } + + + self->stun_servers = g_boxed_copy (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST, + stun_servers); + self->relay_info = g_boxed_copy (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, + relay_info); + + endpoints = tp_asv_get_boxed (out_Properties, "Endpoints", + TP_ARRAY_TYPE_OBJECT_PATH_LIST); + + if (endpoints->len > 1) + { + tf_content_error_literal (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, + "org.freedesktop.Telepathy.Error.NotImplemented", + "Having more than one endpoint is not implemented"); + return; + } + + if (endpoints->len == 1) + { + self->endpoint_objpath = g_strdup (g_ptr_array_index (endpoints, 0)); + tf_call_stream_add_endpoint (self); + } + + self->has_media_properties = TRUE; + + tf_call_stream_try_adding_fsstream (self); + + return; + invalid_property: + tf_content_error_literal (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error getting the Stream's properties: invalid type"); + return; +} + + +static void +got_stream_properties (TpProxy *proxy, GHashTable *out_Properties, + const GError *error, gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + gboolean valid; + GError *myerror = NULL; + guint i; + const gchar * const * interfaces; + gboolean got_media_interface = FALSE; + guint32 local_sending_state; + GHashTable *members; + GHashTableIter iter; + gpointer key, value; + + if (error) + { + tf_content_error (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error getting the Streams's properties: %s", error->message); + return; + } + + if (!out_Properties) + { + tf_content_error_literal (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error getting the Content's properties: there are none"); + return; + } + + interfaces = tp_asv_get_strv (out_Properties, "Interfaces"); + + for (i = 0; interfaces[i]; i++) + if (!strcmp (interfaces[i], TF_FUTURE_IFACE_CALL_STREAM_INTERFACE_MEDIA)) + { + got_media_interface = TRUE; + break; + } + + if (!got_media_interface) + { + tf_content_error_literal (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Stream does not have the media interface," + " but HardwareStreaming was NOT true"); + return; + } + + members = tp_asv_get_boxed (out_Properties, "RemoteMembers", + TF_FUTURE_HASH_TYPE_CONTACT_SENDING_STATE_MAP); + if (!members) + goto invalid_property; + + local_sending_state = tp_asv_get_uint32 (out_Properties, "LocalSendingState", + &valid); + if (!valid) + goto invalid_property; + + self->local_sending_state = local_sending_state; + + if (g_hash_table_size (members) != 1) + { + tf_content_error (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, + "org.freedesktop.Telepathy.Error.NotImplemented", + "Only one Member per Stream is supported, there are %d", + g_hash_table_size (members)); + return; + } + + g_hash_table_iter_init (&iter, members); + + if (g_hash_table_iter_next (&iter, &key, &value)) + { + self->has_contact = TRUE; + self->contact_handle = GPOINTER_TO_UINT (key); + } + + tp_proxy_add_interface_by_id (TP_PROXY (self->proxy), + TF_FUTURE_IFACE_QUARK_CALL_STREAM_INTERFACE_MEDIA); + + tf_future_cli_call_stream_interface_media_connect_to_server_info_retrieved ( + TF_FUTURE_CALL_STREAM (proxy), server_info_retrieved, NULL, NULL, + G_OBJECT (self), &myerror); + if (myerror) + { + tf_content_error (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error connectiong to ServerInfoRetrived signal: %s", + myerror->message); + g_clear_error (&myerror); + return; + } + + tf_future_cli_call_stream_interface_media_connect_to_stun_servers_changed ( + TF_FUTURE_CALL_STREAM (proxy), stun_servers_changed, NULL, NULL, + G_OBJECT (self), &myerror); + if (myerror) + { + tf_content_error (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error connectiong to ServerInfoRetrived signal: %s", + myerror->message); + g_clear_error (&myerror); + return; + } + + + tf_future_cli_call_stream_interface_media_connect_to_relay_info_changed ( + TF_FUTURE_CALL_STREAM (proxy), relay_info_changed, NULL, NULL, + G_OBJECT (self), &myerror); + if (myerror) + { + tf_content_error (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error connectiong to ServerInfoRetrived signal: %s", + myerror->message); + g_clear_error (&myerror); + return; + } + + + tf_future_cli_call_stream_interface_media_connect_to_endpoints_changed ( + TF_FUTURE_CALL_STREAM (proxy), endpoints_changed, NULL, NULL, + G_OBJECT (self), &myerror); + if (myerror) + { + tf_content_error (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error connectiong to EndpointsChanged signal: %s", + myerror->message); + g_clear_error (&myerror); + return; + } + + tp_cli_dbus_properties_call_get_all (proxy, -1, + TF_FUTURE_IFACE_CALL_STREAM_INTERFACE_MEDIA, + got_stream_media_properties, NULL, NULL, G_OBJECT (self)); + + return; + + invalid_property: + tf_content_error_literal (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error getting the Stream's properties: invalid type"); + return; +} + +TfCallStream * +tf_call_stream_new (TfCallChannel *call_channel, + TfCallContent *call_content, + const gchar *object_path, + GError **error) +{ + TfCallStream *self; + TfFutureCallStream *proxy = tf_future_call_stream_new (call_channel->proxy, + object_path, error); + GError *myerror = NULL; + + if (!proxy) + return NULL; + + self = g_object_new (TF_TYPE_STREAM, NULL); + + self->call_content = call_content; + self->proxy = proxy; + + tf_future_cli_call_stream_connect_to_local_sending_state_changed ( + TF_FUTURE_CALL_STREAM (proxy), local_sending_state_changed, NULL, NULL, + G_OBJECT (self), &myerror); + if (myerror) + { + tf_content_error (TF_CONTENT (self->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", + "Error connectiong to LocalSendingStateChanged signal: %s", + myerror->message); + g_object_unref (self); + g_propagate_error (error, myerror); + return NULL; + } + + tp_cli_dbus_properties_call_get_all (proxy, -1, TF_FUTURE_IFACE_CALL_STREAM, + got_stream_properties, NULL, NULL, G_OBJECT (self)); + + return self; +} + +static void +cb_fs_new_local_candidate (TfCallStream *stream, FsCandidate *candidate) +{ + GPtrArray *candidate_list = g_ptr_array_sized_new (1); + GValueArray *gva; + GHashTable *extra_info; + + extra_info = tp_asv_new (NULL, NULL); + if (candidate->priority) + tp_asv_set_uint32 (extra_info, "Priority", candidate->priority); + + if (candidate->foundation) + tp_asv_set_string (extra_info, "Foundation", candidate->foundation); + + if (stream->multiple_usernames) + { + if (candidate->username) + tp_asv_set_string (extra_info, "Username", candidate->username); + if (candidate->password) + tp_asv_set_string (extra_info, "Password", candidate->password); + } + else + { + if ((!stream->last_local_username && candidate->username) || + (!stream->last_local_password && candidate->password) || + (stream->last_local_username && + strcmp (candidate->username, stream->last_local_username)) || + (stream->last_local_password && + strcmp (candidate->password, stream->last_local_password))) + { + g_free (stream->last_local_username); + g_free (stream->last_local_password); + stream->last_local_username = g_strdup (candidate->username); + stream->last_local_password = g_strdup (candidate->password); + + if (!stream->last_local_username) + stream->last_local_username = g_strdup (""); + if (!stream->last_local_password) + stream->last_local_password = g_strdup (""); + + /* Add a callback to kill Call on errors */ + tf_future_cli_call_stream_interface_media_call_set_credentials ( + stream->proxy, -1, stream->last_local_username, + stream->last_local_password, NULL, NULL, NULL, NULL); + + } + } + + gva = tp_value_array_build (4, + G_TYPE_UINT, candidate->component_id, + G_TYPE_STRING, candidate->ip, + G_TYPE_UINT, candidate->port, + TF_FUTURE_HASH_TYPE_CANDIDATE_INFO, extra_info, + G_TYPE_INVALID); + + g_ptr_array_add (candidate_list, gva); + + /* Should also check for errors */ + tf_future_cli_call_stream_interface_media_call_add_candidates (stream->proxy, + -1, candidate_list, NULL, NULL, NULL, NULL); + + + g_boxed_free (TF_FUTURE_ARRAY_TYPE_CANDIDATE_LIST, candidate_list); +} + +static void +cb_fs_local_candidates_prepared (TfCallStream *stream) +{ + tf_future_cli_call_stream_interface_media_call_candidates_prepared ( + stream->proxy, -1, NULL, NULL, NULL, NULL); + +} + +static void +cb_fs_component_state_changed (TfCallStream *stream, guint component, + FsStreamState fsstate) +{ + TpMediaStreamState state; + + if (!stream->endpoint) + return; + + switch (fsstate) + { + case FS_STREAM_STATE_FAILED: + case FS_STREAM_STATE_DISCONNECTED: + state = TP_MEDIA_STREAM_STATE_DISCONNECTED; + break; + case FS_STREAM_STATE_GATHERING: + case FS_STREAM_STATE_CONNECTING: + case FS_STREAM_STATE_CONNECTED: + state = TP_MEDIA_STREAM_STATE_CONNECTING; + break; + case FS_STREAM_STATE_READY: + default: + state = TP_MEDIA_STREAM_STATE_CONNECTED; + break; + } + + tf_future_cli_call_stream_endpoint_call_set_stream_state (stream->endpoint, + -1, state, NULL, NULL, NULL, NULL); +} + + +gboolean +tf_call_stream_bus_message (TfCallStream *stream, GstMessage *message) +{ + const GstStructure *s; + const GValue *val; + + if (!stream->fsstream) + return FALSE; + + s = gst_message_get_structure (message); + + if (gst_structure_has_name (s, "farstream-error")) + { + GObject *object; + const gchar *msg; + FsError errorno; + GEnumClass *enumclass; + GEnumValue *enumvalue; + const gchar *debug; + + val = gst_structure_get_value (s, "src-object"); + object = g_value_get_object (val); + + if (object != (GObject*) stream->fsstream) + return FALSE; + + val = gst_structure_get_value (s, "error-no"); + errorno = g_value_get_enum (val); + msg = gst_structure_get_string (s, "error-msg"); + debug = gst_structure_get_string (s, "debug-msg"); + + enumclass = g_type_class_ref (FS_TYPE_ERROR); + enumvalue = g_enum_get_value (enumclass, errorno); + g_warning ("error (%s (%d)): %s : %s", + enumvalue->value_nick, errorno, msg, debug); + g_type_class_unref (enumclass); + + tf_content_error_literal (TF_CONTENT (stream->call_content), + TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR, "", msg); + return TRUE; + } + + val = gst_structure_get_value (s, "stream"); + if (!val) + return FALSE; + if (!G_VALUE_HOLDS_OBJECT (val)) + return FALSE; + if (g_value_get_object (val) != stream->fsstream) + return FALSE; + + if (gst_structure_has_name (s, "farstream-new-local-candidate")) + { + FsCandidate *candidate; + + val = gst_structure_get_value (s, "candidate"); + candidate = g_value_get_boxed (val); + + cb_fs_new_local_candidate (stream, candidate); + return TRUE; + } + else if (gst_structure_has_name (s, "farstream-local-candidates-prepared")) + { + cb_fs_local_candidates_prepared (stream); + } + else if (gst_structure_has_name (s, "farstream-component-state-changed")) + { + guint component; + FsStreamState fsstate; + + if (!gst_structure_get_uint (s, "component", &component) || + !gst_structure_get_enum (s, "state", FS_TYPE_STREAM_STATE, + (gint*) &fsstate)) + return TRUE; + + cb_fs_component_state_changed (stream, component, fsstate); + } + else + { + return FALSE; + } + + return TRUE; +} diff --git a/farstream/telepathy-farstream/call-stream.h b/farstream/telepathy-farstream/call-stream.h new file mode 100644 index 000000000..dc6518410 --- /dev/null +++ b/farstream/telepathy-farstream/call-stream.h @@ -0,0 +1,122 @@ +/* + * call-stream.h - Source for TfCallStream + * Copyright (C) 2010 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __TF_CALL_STREAM_H__ +#define __TF_CALL_STREAM_H__ + +#include <glib-object.h> + +#include <gst/gst.h> +#include <telepathy-glib/channel.h> + +#include "call-channel.h" +#include "call-content.h" +#include "extensions/extensions.h" + +G_BEGIN_DECLS + +#define TF_TYPE_STREAM tf_call_stream_get_type() + +#define TF_CALL_STREAM(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + TF_TYPE_STREAM, TfCallStream)) + +#define TF_CALL_STREAM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + TF_TYPE_STREAM, TfCallStreamClass)) + +#define TF_IS_STREAM(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_STREAM)) + +#define TF_IS_STREAM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_STREAM)) + +#define TF_CALL_STREAM_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + TF_TYPE_STREAM, TfCallStreamClass)) + +typedef struct _TfCallStreamPrivate TfCallStreamPrivate; + +/** + * TfCallStream: + * + * All members of the object are private + */ + +typedef struct _TfCallStream TfCallStream; + +/** + * TfCallStreamClass: + * @parent_class: the parent #GObjecClass + * + * There are no overridable functions + */ + +typedef struct _TfCallStreamClass TfCallStreamClass; + + +struct _TfCallStream { + GObject parent; + + TfCallContent *call_content; + + TfFutureCallStream *proxy; + + gchar *endpoint_objpath; + TpProxy *endpoint; + gchar *creds_username; + gchar *creds_password; + GList *stored_remote_candidates; + gboolean multiple_usernames; + + gchar *last_local_username; + gchar *last_local_password; + + TfFutureSendingState local_sending_state; + gboolean has_send_resource; + + gboolean has_contact; + guint contact_handle; + FsStream *fsstream; + + gboolean has_media_properties; + TfFutureStreamTransportType transport_type; + gboolean server_info_retrieved; + GPtrArray *stun_servers; + GPtrArray *relay_info; +}; + +struct _TfCallStreamClass{ + GObjectClass parent_class; +}; + + +GType tf_call_stream_get_type (void); + +TfCallStream *tf_call_stream_new ( + TfCallChannel *channel, + TfCallContent *content, + const gchar *object_path, + GError **error); + +gboolean tf_call_stream_bus_message (TfCallStream *stream, GstMessage *message); + +G_END_DECLS + +#endif /* __TF_CALL_STREAM_H__ */ diff --git a/farstream/telepathy-farstream/channel-priv.h b/farstream/telepathy-farstream/channel-priv.h new file mode 100644 index 000000000..8fdee93a5 --- /dev/null +++ b/farstream/telepathy-farstream/channel-priv.h @@ -0,0 +1,28 @@ +#ifndef __TF_CHANNEL_PRIV_H__ +#define __TF_CHANNEL_PRIV_H__ + +#include "channel.h" + +G_BEGIN_DECLS + + +struct _TfChannel{ + GObject parent; + + /*< private >*/ + + TfChannelPrivate *priv; +}; + +struct _TfChannelClass{ + GObjectClass parent_class; + + /*< private >*/ + + gpointer unused[4]; +}; + + +G_END_DECLS + +#endif /* __TF_CHANNEL_PRIV_H__ */ diff --git a/farstream/telepathy-farstream/channel.c b/farstream/telepathy-farstream/channel.c new file mode 100644 index 000000000..d2f77b064 --- /dev/null +++ b/farstream/telepathy-farstream/channel.c @@ -0,0 +1,668 @@ +/* + * channel.c - Source for TfChannel + * Copyright (C) 2006-2007 Collabora Ltd. + * Copyright (C) 2006-2007 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * SECTION:channel + * @short_description: Handle the MediaSignalling or Call media interfaces on a + * Channel + * + * This class handles the + * org.freedesktop.Telepathy.Channel.Interface.MediaSignalling on a + * channel using Farstream or the media part of the + * org.freedesktop.Telepathy.Channel.Type.Call that has HardwareStreaming=FALSE + */ + +#include <stdlib.h> + +#include <telepathy-glib/channel.h> +#include <telepathy-glib/dbus.h> +#include <telepathy-glib/enums.h> +#include <telepathy-glib/errors.h> +#include <telepathy-glib/interfaces.h> +#include <telepathy-glib/util.h> + +#include <farstream/fs-conference.h> + +#include "extensions/extensions.h" + +#include "channel.h" +#include "channel-priv.h" +#include "tf-signals-marshal.h" +#include "media-signalling-channel.h" +#include "media-signalling-content.h" +#include "call-channel.h" +#include "content.h" + + +static void channel_async_initable_init (GAsyncInitableIface *asynciface); + +G_DEFINE_TYPE_WITH_CODE (TfChannel, tf_channel, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, channel_async_initable_init)); + +struct _TfChannelPrivate +{ + TpChannel *channel_proxy; + + TfMediaSignallingChannel *media_signalling_channel; + TfCallChannel *call_channel; + + GHashTable *media_signalling_contents; + + gulong channel_invalidated_handler; + + gboolean closed; +}; + +enum +{ + SIGNAL_CLOSED, + SIGNAL_FS_CONFERENCE_ADDED, + SIGNAL_FS_CONFERENCE_REMOVED, + SIGNAL_CONTENT_ADDED, + SIGNAL_CONTENT_REMOVED, + SIGNAL_COUNT +}; + +static guint signals[SIGNAL_COUNT] = {0}; + +enum +{ + PROP_CHANNEL = 1, + PROP_OBJECT_PATH, + PROP_FS_CONFERENCES +}; + +static void shutdown_channel (TfChannel *self); + +static void channel_fs_conference_added (GObject *chan, + FsConference *conf, TfChannel *self); +static void channel_fs_conference_removed (GObject *chan, + FsConference *conf, TfChannel *self); + +static void tf_channel_init_async (GAsyncInitable *initable, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static gboolean tf_channel_init_finish (GAsyncInitable *initable, + GAsyncResult *res, + GError **error); + +static void content_added (GObject *proxy, + TfContent *content, + TfChannel *self); +static void content_removed (GObject *proxy, + TfContent *content, + TfChannel *self); + + +static void +tf_channel_init (TfChannel *self) +{ + TfChannelPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + TF_TYPE_CHANNEL, TfChannelPrivate); + + self->priv = priv; +} + +static void +channel_async_initable_init (GAsyncInitableIface *asynciface) +{ + asynciface->init_async = tf_channel_init_async; + asynciface->init_finish = tf_channel_init_finish; +} + + +static void +tf_channel_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + TfChannel *self = TF_CHANNEL (object); + + switch (property_id) + { + case PROP_CHANNEL: + g_value_set_object (value, self->priv->channel_proxy); + break; + case PROP_OBJECT_PATH: + { + TpProxy *as_proxy = (TpProxy *) self->priv->channel_proxy; + + g_value_set_string (value, as_proxy->object_path); + } + break; + case PROP_FS_CONFERENCES: + if (self->priv->call_channel) + g_object_get_property (G_OBJECT (self->priv->call_channel), + "fs-conferences", value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +tf_channel_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + TfChannel *self = TF_CHANNEL (object); + + switch (property_id) + { + case PROP_CHANNEL: + self->priv->channel_proxy = TP_CHANNEL (g_value_dup_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void channel_invalidated (TpChannel *channel_proxy, + guint domain, gint code, gchar *message, TfChannel *self); + +static void +call_channel_ready (GObject *obj, GAsyncResult *call_res, gpointer user_data) +{ + GError *error = NULL; + GSimpleAsyncResult *res = user_data; + TfChannel *self = TF_CHANNEL ( + g_async_result_get_source_object (G_ASYNC_RESULT (res))); + + self->priv->call_channel = TF_CALL_CHANNEL (g_async_initable_new_finish ( + G_ASYNC_INITABLE (obj), call_res, &error)); + + if (error) + { + shutdown_channel (self); + g_simple_async_result_set_op_res_gboolean (res, FALSE); + g_simple_async_result_set_from_error (res, error); + g_clear_error (&error); + } + else + { + g_simple_async_result_set_op_res_gboolean (res, TRUE); + + tp_g_signal_connect_object (self->priv->call_channel, + "fs-conference-added", G_CALLBACK (channel_fs_conference_added), + self, 0); + tp_g_signal_connect_object (self->priv->call_channel, + "fs-conference-removed", G_CALLBACK (channel_fs_conference_removed), + self, 0); + + tp_g_signal_connect_object (self->priv->call_channel, + "content_added", G_CALLBACK (content_added), + self, 0); + tp_g_signal_connect_object (self->priv->call_channel, + "content_removed", G_CALLBACK (content_removed), + self, 0); + } + + + g_simple_async_result_complete (res); + g_object_unref (res); + g_object_unref (self); +} + +static gboolean +content_remove_all (gpointer key, gpointer value, gpointer user_data) +{ + TfMediaSignallingContent *content = value; + TfChannel *self = user_data; + + g_signal_emit (self, signals[SIGNAL_CONTENT_REMOVED], 0, content); + + return TRUE; +} + +static void +channel_session_invalidated (TfMediaSignallingChannel *media_signalling_channel, + FsConference *fsconference, FsParticipant *part, TfChannel *self) +{ + g_object_notify (G_OBJECT (self), "fs-conferences"); + g_signal_emit (self, signals[SIGNAL_FS_CONFERENCE_REMOVED], 0, + fsconference); + + if (self->priv->media_signalling_contents) + g_hash_table_foreach_remove (self->priv->media_signalling_contents, + content_remove_all, self); +} + +static void +channel_stream_closed (TfStream *stream, TfChannel *self) +{ + TfMediaSignallingContent *content; + + content = g_hash_table_lookup (self->priv->media_signalling_contents, stream); + g_signal_emit (self, signals[SIGNAL_CONTENT_REMOVED], 0, content); + g_hash_table_remove (self->priv->media_signalling_contents, stream); +} + +static void +channel_stream_created (TfMediaSignallingChannel *media_signalling_channel, + TfStream *stream, TfChannel *self) +{ + TfMediaSignallingContent *content; + + g_assert (self->priv->media_signalling_contents); + + content = tf_media_signalling_content_new ( + self->priv->media_signalling_channel, stream, 0 /* HANDLE HERE */); + + g_hash_table_insert (self->priv->media_signalling_contents, + g_object_ref (stream), content); + + tp_g_signal_connect_object (stream, "closed", + G_CALLBACK (channel_stream_closed), self, 0); + + g_signal_emit (self, signals[SIGNAL_CONTENT_ADDED], 0, + content); +} + +static void +channel_prepared (GObject *obj, + GAsyncResult *proxy_res, + gpointer user_data) +{ + TpChannel *channel_proxy = TP_CHANNEL (obj); + TpProxy *as_proxy = (TpProxy *) channel_proxy; + GSimpleAsyncResult *res = user_data; + GError *error = NULL; + TfChannel *self = TF_CHANNEL ( + g_async_result_get_source_object (G_ASYNC_RESULT (res))); + + if (!tp_proxy_prepare_finish (channel_proxy, proxy_res, &error)) + { + g_simple_async_result_propagate_error (res, &error); + shutdown_channel (self); + goto error; + } + + if (self->priv->closed) + { + g_simple_async_result_set_error (res, TP_ERROR, TP_ERROR_CANCELLED, + "Channel already closed"); + goto error; + } + + if (tp_proxy_has_interface_by_id (as_proxy, + TP_IFACE_QUARK_CHANNEL_INTERFACE_MEDIA_SIGNALLING)) + { + self->priv->media_signalling_channel = + tf_media_signalling_channel_new (channel_proxy); + + self->priv->media_signalling_contents = g_hash_table_new_full ( + g_direct_hash, g_direct_equal, g_object_unref, g_object_unref); + + tp_g_signal_connect_object (self->priv->media_signalling_channel, + "session-created", G_CALLBACK (channel_fs_conference_added), + self, 0); + tp_g_signal_connect_object (self->priv->media_signalling_channel, + "session-invalidated", G_CALLBACK (channel_session_invalidated), + self, 0); + tp_g_signal_connect_object (self->priv->media_signalling_channel, + "stream-created", G_CALLBACK (channel_stream_created), + self, 0); + g_simple_async_result_set_op_res_gboolean (res, TRUE); + } + else if (tp_proxy_has_interface_by_id (as_proxy, + TF_FUTURE_IFACE_QUARK_CHANNEL_TYPE_CALL)) + { + tf_call_channel_new_async (channel_proxy, call_channel_ready, res); + + self->priv->channel_invalidated_handler = g_signal_connect ( + self->priv->channel_proxy, + "invalidated", G_CALLBACK (channel_invalidated), self); + } + else + { + g_simple_async_result_set_error (res, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, + "Channel does not implement " + TP_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING " or " + TF_FUTURE_IFACE_CHANNEL_TYPE_CALL); + goto error; + } + + g_object_unref (self); + return; + +error: + g_simple_async_result_set_op_res_gboolean (res, FALSE); + g_simple_async_result_complete (res); + + g_object_unref (res); + g_object_unref (self); +} + + +static void +tf_channel_init_async (GAsyncInitable *initable, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + TfChannel *self = TF_CHANNEL (initable); + GSimpleAsyncResult *res; + + if (cancellable != NULL) + { + g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data, + G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, + "TfChannel initialisation does not support cancellation"); + return; + } + + res = g_simple_async_result_new (G_OBJECT (self), callback, user_data, + tf_channel_init_async); + tp_proxy_prepare_async (self->priv->channel_proxy, NULL, + channel_prepared, res); +} + +static gboolean +tf_channel_init_finish (GAsyncInitable *initable, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple_res; + + g_return_val_if_fail (g_simple_async_result_is_valid (res, + G_OBJECT (initable), tf_channel_init_async), FALSE); + simple_res = G_SIMPLE_ASYNC_RESULT (res); + + g_simple_async_result_propagate_error (simple_res, error); + + return g_simple_async_result_get_op_res_gboolean (simple_res); +} + + +static void +tf_channel_dispose (GObject *object) +{ + TfChannel *self = TF_CHANNEL (object); + + g_debug (G_STRFUNC); + + + if (self->priv->media_signalling_contents != NULL) + { + g_hash_table_unref (self->priv->media_signalling_contents); + self->priv->media_signalling_contents = NULL; + } + + tp_clear_object (&self->priv->media_signalling_channel); + tp_clear_object (&self->priv->call_channel); + + if (self->priv->channel_proxy) + { + TpChannel *tmp; + + if (self->priv->channel_invalidated_handler != 0) + g_signal_handler_disconnect (self->priv->channel_proxy, + self->priv->channel_invalidated_handler); + + tmp = self->priv->channel_proxy; + self->priv->channel_proxy = NULL; + g_object_unref (tmp); + } + + if (G_OBJECT_CLASS (tf_channel_parent_class)->dispose) + G_OBJECT_CLASS (tf_channel_parent_class)->dispose (object); +} + +static void +tf_channel_class_init (TfChannelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (TfChannelPrivate)); + + object_class->set_property = tf_channel_set_property; + object_class->get_property = tf_channel_get_property; + + object_class->dispose = tf_channel_dispose; + + g_object_class_install_property (object_class, PROP_CHANNEL, + g_param_spec_object ("channel", + "TpChannel object", + "Telepathy channel object which this media channel should operate on", + TP_TYPE_CHANNEL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_OBJECT_PATH, + g_param_spec_string ("object-path", + "channel object path", + "D-Bus object path of the Telepathy channel which this channel" + " operates on", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + + g_object_class_install_property (object_class, PROP_FS_CONFERENCES, + g_param_spec_boxed ("fs-conferences", + "Farstream FsConferences objects", + "GPtrArray of Farstream FsConferences for this channel", + G_TYPE_PTR_ARRAY, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * TfChannel::closed: + * + * This function is called after a channel is closed, either because + * it has been closed by the connection manager or because we had a locally + * generated error. + */ + + signals[SIGNAL_CLOSED] = + g_signal_new ("closed", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * TfChannel::fs-conference-added + * @tfchannel: the #TfChannel + * @conf: a #FsConference + * + * When this signal is emitted, the conference should be added to the + * application's pipeline. + */ + + signals[SIGNAL_FS_CONFERENCE_ADDED] = g_signal_new ("fs-conference-added", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + _tf_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, FS_TYPE_CONFERENCE); + + /** + * TfChannel::fs-conference-removed + * @tfchannel: the #TfChannel + * @conf: a #FsConference + * + * When this signal is emitted, the conference should be remove from the + * application's pipeline. + */ + + signals[SIGNAL_FS_CONFERENCE_REMOVED] = g_signal_new ("fs-conference-removed", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + _tf_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, FS_TYPE_CONFERENCE); + + + /** + * TfChannel::content-added + * @tfchannel: the #TfChannel + * @content: a #TfContent + * + * Tells the application that a content has been added. In the callback for + * this signal, the application should set its preferred codecs, and hook + * up to any signal from #TfContent it cares about. Special care should be + * made to connect #TfContent::src-pad-added as well + * as the #TfContent::start-sending and #TfContent::stop-sending signals. + */ + + signals[SIGNAL_CONTENT_ADDED] = g_signal_new ("content-added", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + _tf_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, TF_TYPE_CONTENT); + + /** + * TfChannel::content-removed + * @tfchannel: the #TfChannel + * @content: a #TfContent + * + * Tells the application that a content is being removed. + */ + + signals[SIGNAL_CONTENT_REMOVED] = g_signal_new ("content-removed", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + _tf_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, TF_TYPE_CONTENT); +} + +static void +shutdown_channel (TfChannel *self) +{ + if (self->priv->media_signalling_channel) + { + g_object_unref (self->priv->media_signalling_channel); + self->priv->media_signalling_channel = NULL; + } + + tp_clear_object (&self->priv->call_channel); + + if (self->priv->channel_proxy != NULL) + { + if (self->priv->channel_invalidated_handler) + { + g_signal_handler_disconnect ( + self->priv->channel_proxy, self->priv->channel_invalidated_handler); + self->priv->channel_invalidated_handler = 0; + } + } + + g_signal_emit (self, signals[SIGNAL_CLOSED], 0); + + self->priv->closed = TRUE; +} + +static void +channel_invalidated (TpChannel *channel_proxy, + guint domain, + gint code, + gchar *message, + TfChannel *self) +{ + shutdown_channel (self); +} + +/** + * tf_channel_new_async: + * @channel_proxy: a #TpChannel proxy + * @callback: a #GAsyncReadyCallback to call when the channel is ready + * @user_data: the data to pass to callback function + * + * Creates a new #TfChannel from an existing channel proxy, the new + * TfChannel object will be return in the async callback. + * + * The user must call g_async_initable_new_finish() in the callback + * to get the finished object. + */ + +void +tf_channel_new_async (TpChannel *channel_proxy, + GAsyncReadyCallback callback, + gpointer user_data) +{ + return g_async_initable_new_async (TF_TYPE_CHANNEL, + 0, NULL, callback, user_data, + "channel", channel_proxy, + NULL); +} + +/** + * tf_channel_bus_message: + * @channel: A #TfChannel + * @message: A #GstMessage received from the bus + * + * You must call this function on call messages received on the async bus. + * #GstMessages are not modified. + * + * Returns: %TRUE if the message has been handled, %FALSE otherwise + */ + +gboolean +tf_channel_bus_message (TfChannel *channel, + GstMessage *message) +{ + if (channel->priv->media_signalling_channel) + return tf_media_signalling_channel_bus_message ( + channel->priv->media_signalling_channel, message); + else if (channel->priv->call_channel) + return tf_call_channel_bus_message (channel->priv->call_channel, + message); + + return FALSE; +} + +static void +channel_fs_conference_added (GObject *proxy, FsConference *conf, + TfChannel *self) +{ + g_object_notify (G_OBJECT (self), "fs-conferences"); + g_signal_emit (self, signals[SIGNAL_FS_CONFERENCE_ADDED], 0, + conf); +} + +static void +channel_fs_conference_removed (GObject *proxy, FsConference *conf, + TfChannel *self) +{ + g_object_notify (G_OBJECT (self), "fs-conferences"); + g_signal_emit (self, signals[SIGNAL_FS_CONFERENCE_REMOVED], 0, + conf); +} + +static void +content_added (GObject *proxy, TfContent *content, TfChannel *self) +{ + g_signal_emit (self, signals[SIGNAL_CONTENT_ADDED], 0, content); +} + +static void +content_removed (GObject *proxy, TfContent *content, TfChannel *self) +{ + g_signal_emit (self, signals[SIGNAL_CONTENT_REMOVED], 0, content); +} diff --git a/farstream/telepathy-farstream/channel.h b/farstream/telepathy-farstream/channel.h new file mode 100644 index 000000000..55533bd74 --- /dev/null +++ b/farstream/telepathy-farstream/channel.h @@ -0,0 +1,62 @@ +#ifndef __TF_CHANNEL_H__ +#define __TF_CHANNEL_H__ + +#include <glib-object.h> + +#include <telepathy-glib/channel.h> +#include <gst/gst.h> + +G_BEGIN_DECLS + +#define TF_TYPE_CHANNEL tf_channel_get_type() + +#define TF_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + TF_TYPE_CHANNEL, TfChannel)) + +#define TF_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + TF_TYPE_CHANNEL, TfChannelClass)) + +#define TF_IS_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_CHANNEL)) + +#define TF_IS_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_CHANNEL)) + +#define TF_CHANNEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + TF_TYPE_CHANNEL, TfChannelClass)) + +typedef struct _TfChannelPrivate TfChannelPrivate; + +/** + * TfChannel: + * + * All members of the object are private + */ + +typedef struct _TfChannel TfChannel; + +/** + * TfChannelClass: + * @parent_class: the parent #GObjectClass + * + * There are no overridable functions + */ + +typedef struct _TfChannelClass TfChannelClass; + +GType tf_channel_get_type (void); + +void tf_channel_new_async (TpChannel *channel_proxy, + GAsyncReadyCallback callback, + gpointer user_data); + + +gboolean tf_channel_bus_message (TfChannel *channel, + GstMessage *message); + +G_END_DECLS + +#endif /* __TF_CHANNEL_H__ */ diff --git a/farstream/telepathy-farstream/content-priv.h b/farstream/telepathy-farstream/content-priv.h new file mode 100644 index 000000000..bee82e327 --- /dev/null +++ b/farstream/telepathy-farstream/content-priv.h @@ -0,0 +1,43 @@ + +#ifndef __TF_CONTENT_PRIV_H__ +#define __TF_CONTENT_PRIV_H__ + +#include <glib-object.h> + +#include <farstream/fs-conference.h> + + +G_BEGIN_DECLS + +struct _TfContent { + GObject parent; + + guint sending_count; +}; + +struct _TfContentClass{ + GObjectClass parent_class; + + void (*content_error) (TfContent *content, + guint reason, /* TfFutureContentRemovalReason */ + const gchar *detailed_reason, + const gchar *message); + + GstIterator * (*iterate_src_pads) (TfContent *content, guint *handle, + guint handle_count); +}; + +gboolean _tf_content_start_sending (TfContent *self); +void _tf_content_stop_sending (TfContent *self); + +void _tf_content_emit_src_pad_added (TfContent *self, guint handle, + FsStream *stream, GstPad *pad, FsCodec *codec); + +gboolean _tf_content_start_receiving (TfContent *self, guint *handles, + guint handle_count); +void _tf_content_stop_receiving (TfContent *self, guint *handles, + guint handle_count); + +G_END_DECLS + +#endif /* __TF_CONTENT_PRIV_H__ */ diff --git a/farstream/telepathy-farstream/content.c b/farstream/telepathy-farstream/content.c new file mode 100644 index 000000000..7f2359642 --- /dev/null +++ b/farstream/telepathy-farstream/content.c @@ -0,0 +1,424 @@ + +#include "content.h" +#include "content-priv.h" + +#include <farstream/fs-conference.h> + +#include "channel.h" +#include "tf-signals-marshal.h" + + +/** + * SECTION:content + * @short_description: Represent the Content of a channel handled by #TfChannel + * + * Objects of this class allow the user to handle the media side of a Telepathy + * channel handled by #TfChannel. + * + * This object is created by the #TfChannel and the user is notified of its + * creation by the #TfChannel::content-added signal. In the callback for this + * signal, the user should call tf_content_set_codec_preferences() and connect + * to the #TfContent::src-pad-added signal. + * + */ + + +G_DEFINE_ABSTRACT_TYPE (TfContent, tf_content, G_TYPE_OBJECT); + + +enum +{ + PROP_TF_CHANNEL = 1, + PROP_FS_CONFERENCE, + PROP_FS_SESSION, + PROP_MEDIA_TYPE, + PROP_SINK_PAD, + PROP_OBJECT_PATH +}; + +enum +{ + SIGNAL_START_SENDING, + SIGNAL_STOP_SENDING, + SIGNAL_SRC_PAD_ADDED, + SIGNAL_START_RECEIVING, + SIGNAL_STOP_RECEIVING, + SIGNAL_RESTART_SOURCE, + SIGNAL_COUNT +}; + +static guint signals[SIGNAL_COUNT] = {0}; + +static void +tf_content_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + /* Other properties need to be overwritten */ + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +tf_content_class_init (TfContentClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = tf_content_get_property; + + g_object_class_install_property (object_class, PROP_TF_CHANNEL, + g_param_spec_object ("tf-channel", + "Parent TfChannel object ", + "The Telepathy-Farstream Channel for this object", + TF_TYPE_CHANNEL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_FS_CONFERENCE, + g_param_spec_object ("fs-conference", + "Farstream FsConference used by the Content ", + "The Farstream conference for this content " + "(could be the same as other contents)", + FS_TYPE_CONFERENCE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_FS_SESSION, + g_param_spec_object ("fs-session", + "Farstream FsSession ", + "The Farstream session for this content", + FS_TYPE_SESSION, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_SINK_PAD, + g_param_spec_object ("sink-pad", + "Sink Pad", + "Sink GstPad for this content", + GST_TYPE_PAD, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_MEDIA_TYPE, + g_param_spec_enum ("media-type", + "MediaType", + "The FsMediaType for this content", + FS_TYPE_MEDIA_TYPE, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_OBJECT_PATH, + g_param_spec_string ("object-path", + "content object path", + "D-Bus object path of the Telepathy content which this content" + " operates on", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + + /** + * TfContent::start-sending + * @content: the #TfContent + * + * This signal is emitted when the connection manager ask to send media. + * For example, this can be used to open a camera, start recording from a + * microphone or play back a file. The application should start + * sending data on the #TfContent:sink-pad + * + * Returns: %TRUE if the application can start providing data or %FALSE + * otherwise + */ + + signals[SIGNAL_START_SENDING] = + g_signal_new ("start-sending", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + g_signal_accumulator_true_handled, NULL, + _tf_marshal_BOOLEAN__VOID, + G_TYPE_BOOLEAN, 0); + + /** + * TfContent::stop-sending + * @content: the #TfContent + * + * This signal is emitted when the connection manager ask to stop + * sending media + */ + + signals[SIGNAL_STOP_SENDING] = + g_signal_new ("stop-sending", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * TfContent::src-pad-added + * @content: the #TfContent + * @handle: the handle of the remote party producing the content on this pad + * or 0 if unknown + * @stream: the #FsStream for this pad + * @pad: a #GstPad + * @codec: the #FsCodec for this pad + * + * This signal is emitted when a data is coming on a new pad. This signal + * is not emitted on the main thread, so special care must be made to lock + * the relevant data. When the callback returns from this signal, data will + * start flowing through the pad, so the application MUST connect a sink. + */ + + signals[SIGNAL_SRC_PAD_ADDED] = + g_signal_new ("src-pad-added", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + _tf_marshal_VOID__UINT_OBJECT_OBJECT_BOXED, + G_TYPE_NONE, 4, + G_TYPE_UINT, FS_TYPE_STREAM, GST_TYPE_PAD, FS_TYPE_CODEC); + + /** + * TfContent::start-receiving + * @content: the #TfContent + * @handles: a 0-terminated array of #guint containing the handles + * @handle_count: The number of handles in the @handles array + * + * This signal is emitted when the connection managers requests that the + * application prepares itself to start receiving data again from certain + * handles. + * + * This signal will only be emitted after the #TfContent::stop-receiving + * signal has succeeded. It will not be emitted right after + * #TfContent::src-pad-added. + * + * Returns: %TRUE if the application can start receiving data or %FALSE + * otherwise + */ + + signals[SIGNAL_START_RECEIVING] = + g_signal_new ("start-receiving", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + g_signal_accumulator_true_handled, NULL, + _tf_marshal_BOOLEAN__POINTER_UINT, + G_TYPE_BOOLEAN, 2, G_TYPE_POINTER, G_TYPE_UINT); + + /** + * TfContent::stop-receiving + * @content: the #TfContent + * @handles: a 0-terminated array of #guint containing the handles + * @handle_count: The number of handles in the @handles array + * + * This signal is emitted when the connection manager wants to tell the + * application that it is now allowed to stop receiving. + */ + + signals[SIGNAL_STOP_RECEIVING] = + g_signal_new ("stop-receiving", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + _tf_marshal_VOID__POINTER_UINT, + G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_UINT); + + /** + * TfContent::restart-source: + * @content: the #TfContent + * + * This signal requests that the source be restarted so that the caps can + * be renegotiated with a new resolutions and framerate. + */ + + signals[SIGNAL_RESTART_SOURCE] = + g_signal_new ("restart-source", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + _tf_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + + +static void +tf_content_init (TfContent *self) +{ +} + + +gboolean +_tf_content_start_sending (TfContent *self) +{ + GValue instance = {0}; + GValue sending_success_val = {0,}; + gboolean sending_success; + + if (self->sending_count) + { + self->sending_count ++; + return TRUE; + } + + g_value_init (&sending_success_val, G_TYPE_BOOLEAN); + g_value_set_boolean (&sending_success_val, TRUE); + + g_value_init (&instance, TF_TYPE_CONTENT); + g_value_set_object (&instance, self); + + g_debug ("Requesting that the application start sending"); + + g_signal_emitv (&instance, signals[SIGNAL_START_SENDING], 0, + &sending_success_val); + sending_success = g_value_get_boolean (&sending_success_val); + + g_value_unset (&instance); + + g_debug ("Request to start sending %s", + sending_success ? "succeeded" : "failed"); + + self->sending_count = 1; + + return sending_success; +} + +void +_tf_content_stop_sending (TfContent *self) +{ + self->sending_count --; + + if (self->sending_count == 0) + g_signal_emit (self, signals[SIGNAL_STOP_SENDING], 0); +} + +void +_tf_content_emit_src_pad_added (TfContent *self, guint handle, + FsStream *stream, GstPad *pad, FsCodec *codec) +{ + g_signal_emit (self, signals[SIGNAL_SRC_PAD_ADDED], 0, handle, + stream, pad, codec); +} + +/** + * tf_content_error_literal: + * @content: a #TfContent + * @reason: the reason (a #TfContentRemovalReason) + * @detailed_reason: The detailled error (as a DBus name) + * @message: error Message + * + * Send an error to the Content to the CM, the effect is most likely that the + * content will be removed. + */ + +void +tf_content_error_literal (TfContent *content, + guint reason, /* TfFutureContentRemovalReason */ + const gchar *detailed_reason, + const gchar *message) +{ + TfContentClass *klass = TF_CONTENT_GET_CLASS (content); + + if (klass->content_error) + klass->content_error (content, reason, detailed_reason, message); + else + GST_WARNING ("content_error not defined in class: %s", message); +} + +/** + * tf_content_error: + * @content: a #TfContent + * @reason: the reason (a #TfContentRemovalReason) + * @detailed_reason: The detailled error (as a DBus name) + * @message_format: error Message with printf style formatting + * @...: Parameters to insert into the @message_format string + * + * Send an error to the Content to the CM, the effect is most likely that the + * content will be removed. + */ + +void +tf_content_error (TfContent *content, + guint reason, /* TfFutureContentRemovalReason */ + const gchar *detailed_reason, + const gchar *message_format, + ...) +{ + gchar *message; + va_list valist; + + va_start (valist, message_format); + message = g_strdup_vprintf (message_format, valist); + va_end (valist); + + tf_content_error_literal (content, reason, detailed_reason, message); + g_free (message); +} + +/** + * tf_content_iterate_src_pads: + * @content: a #TfContent + * @handles: a 0 terminated array of #guint representing Telepathy handles + * @handle_count: the numner of handles in @handles + * + * Provides a iterator that can be used to iterate through all of the src + * pads that are are used to receive from a group of Telepathy handles. + * + * Returns: a #GstIterator + */ + +GstIterator * +tf_content_iterate_src_pads (TfContent *content, guint *handles, + guint handle_count) +{ + TfContentClass *klass = TF_CONTENT_GET_CLASS (content); + + if (klass->iterate_src_pads) + return klass->iterate_src_pads (content, handles, handle_count); + else + GST_WARNING ("iterate_src_pads not defined in class"); + + return NULL; +} + +gboolean +_tf_content_start_receiving (TfContent *self, guint *handles, + guint handle_count) +{ + GValue instance_and_params[3] = {{0} , {0}, {0}}; + GValue receiving_success_val = {0,}; + gboolean receiving_success; + + g_value_init (&receiving_success_val, G_TYPE_BOOLEAN); + g_value_set_boolean (&receiving_success_val, TRUE); + + g_value_init (&instance_and_params[0], TF_TYPE_CONTENT); + g_value_set_object (&instance_and_params[0], self); + + g_value_init (&instance_and_params[1], G_TYPE_POINTER); + g_value_set_pointer (&instance_and_params[1], handles); + + g_value_init (&instance_and_params[2], G_TYPE_UINT); + g_value_set_uint (&instance_and_params[2], handle_count); + + g_debug ("Requesting that the application start receiving"); + + g_signal_emitv (instance_and_params, signals[SIGNAL_START_RECEIVING], 0, + &receiving_success_val); + receiving_success = g_value_get_boolean (&receiving_success_val); + + g_value_unset (&instance_and_params[0]); + + g_debug ("Request to start receiving %s", + receiving_success ? "succeeded" : "failed"); + + return receiving_success; +} + +void +_tf_content_stop_receiving (TfContent *self, guint *handles, + guint handle_count) +{ + g_signal_emit (self, signals[SIGNAL_STOP_SENDING], 0, handles, + handle_count); +} diff --git a/farstream/telepathy-farstream/content.h b/farstream/telepathy-farstream/content.h new file mode 100644 index 000000000..e0716792a --- /dev/null +++ b/farstream/telepathy-farstream/content.h @@ -0,0 +1,64 @@ +#ifndef __TF_CONTENT_H__ +#define __TF_CONTENT_H__ + +#include <glib-object.h> +#include <farstream/fs-conference.h> + +G_BEGIN_DECLS + +#define TF_TYPE_CONTENT tf_content_get_type() + +#define TF_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + TF_TYPE_CONTENT, TfContent)) + +#define TF_CONTENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + TF_TYPE_CONTENT, TfContentClass)) + +#define TF_IS_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_CONTENT)) + +#define TF_IS_CONTENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_CONTENT)) + +#define TF_CONTENT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + TF_TYPE_CONTENT, TfContentClass)) + +typedef struct _TfContentPrivate TfContentPrivate; + +/** + * TfContent: + * + * This structure is private, this class is not subclassable. + */ + +typedef struct _TfContent TfContent; + +/** + * TfContentClass: + * + * This structure is private, this class is not subclassable. + */ + +typedef struct _TfContentClass TfContentClass; + +GType tf_content_get_type (void); + +void tf_content_error_literal (TfContent *content, + guint reason, /* TfFutureContentRemovalReason */ + const gchar *detailed_reason, + const gchar *message); + +void tf_content_error (TfContent *content, + guint reason, /* TfFutureContentRemovalReason */ + const gchar *detailed_reason, + const gchar *message_format, ...) G_GNUC_PRINTF (4, 5); + +GstIterator *tf_content_iterate_src_pads (TfContent *content, + guint *handles, guint handle_count); + +G_END_DECLS + +#endif /* __TF_CONTENT_H__ */ diff --git a/farstream/telepathy-farstream/media-signalling-channel.c b/farstream/telepathy-farstream/media-signalling-channel.c new file mode 100644 index 000000000..bfd369649 --- /dev/null +++ b/farstream/telepathy-farstream/media-signalling-channel.c @@ -0,0 +1,729 @@ +/* + * media-signalling-channel.c - Source for TfMediaSignallingChannel + * Copyright (C) 2011 Collabora Ltd. + * Copyright (C) 2011 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * SECTION:media-signalling-channel + * @short_description: Handle the MediaSignalling interface on a Channel + * + * This class handles the + * org.freedesktop.Telepathy.Channel.Interface.MediaSignalling on a + * channel using Farstream. + */ + + +#include "media-signalling-channel.h" + +#include <telepathy-glib/util.h> +#include <telepathy-glib/interfaces.h> + +#include "stream.h" +#include "session-priv.h" +#include "stream-priv.h" +#include "tf-signals-marshal.h" + + +struct _TfMediaSignallingChannel { + GObject parent; + + TpChannel *channel_proxy; + + TfNatProperties nat_props; + guint prop_id_nat_traversal; + guint prop_id_stun_server; + guint prop_id_stun_port; + guint prop_id_gtalk_p2p_relay_token; + + /* sessions is NULL until we've had a reply from GetSessionHandlers */ + TfSession *session; + gboolean got_sessions; + GPtrArray *streams; +}; + +struct _TfMediaSignallingChannelClass{ + GObjectClass parent_class; +}; + + +G_DEFINE_TYPE (TfMediaSignallingChannel, tf_media_signalling_channel, + G_TYPE_OBJECT); + +enum +{ + STREAM_CREATED, + SESSION_CREATED, + SESSION_INVALIDATED, + GET_CODEC_CONFIG, + SIGNAL_COUNT +}; + +static guint signals[SIGNAL_COUNT] = {0}; + +static void tf_media_signalling_channel_dispose (GObject *object); + +static void new_media_session_handler (TpChannel *channel_proxy, + const gchar *session_handler_path, const gchar *type, + gpointer user_data, GObject *weak_object); + +static void get_session_handlers_reply (TpChannel *channel_proxy, + const GPtrArray *session_handlers, const GError *error, + gpointer user_data, GObject *weak_object); + + +static void new_stream_cb (TfSession *session, gchar *object_path, + guint stream_id, TpMediaStreamType media_type, + TpMediaStreamDirection direction, gpointer user_data); + +static void stream_closed_cb (TfStream *stream, + gpointer user_data); + +static void +tf_media_signalling_channel_error (TfMediaSignallingChannel *chan, + TpMediaStreamError error, + const gchar *message); + + + +static void +tf_media_signalling_channel_class_init (TfMediaSignallingChannelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = tf_media_signalling_channel_dispose; + + /** + * TfMediaSignallingChannel::stream-created: + * @channel: the #TfMediaSignallingChannel which has a new stream + * @stream: The new #TfStream + * + * This signal is emitted when a new stream has been created in the connection + * manager and a local proxy has been generated. + */ + + signals[STREAM_CREATED] = + g_signal_new ("stream-created", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, TF_TYPE_STREAM); + + /** + * TfMediaSignallingChannel::session-created: + * @channel: the #TfMediaSignallingChannel which has a new stream + * @conference: the #FsConference of the new session + * @participant: the #FsParticipant of the new session + * + * This signal is emitted when a new session has been created in the + * connection manager. The user should add the new #FsConference to a pipeline + * and set it to playing. The user should also set any property he wants to + * set. + */ + + signals[SESSION_CREATED] = + g_signal_new ("session-created", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _tf_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, FS_TYPE_CONFERENCE); + + /** + * TfMediaSignallingChannel::session-invalidated: + * @channel: the #TfMediaSignallingChannel which has a new stream + * @conference: the #FsConference of the new session + * @participant: the #FsParticipant of the new session + * + * This signal is emitted when a session has been invalidated. + * The #FsConference and #FsParticipant for this session are returned. + * The #FsConference should be removed from the pipeline. + */ + + signals[SESSION_INVALIDATED] = + g_signal_new ("session-invalidated", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _tf_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, 2, FS_TYPE_CONFERENCE, FS_TYPE_PARTICIPANT); + + + signals[GET_CODEC_CONFIG] = + g_signal_new ("get-codec-config", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _tf_marshal_BOXED__UINT, + FS_TYPE_CODEC_LIST, 1, G_TYPE_UINT); +} + +static void +tf_media_signalling_channel_init (TfMediaSignallingChannel *self) +{ + self->streams = g_ptr_array_new (); +} + +static void +cb_properties_changed (TpProxy *proxy G_GNUC_UNUSED, + const GPtrArray *structs, + gpointer user_data G_GNUC_UNUSED, + GObject *object) +{ + TfMediaSignallingChannel *self = TF_MEDIA_SIGNALLING_CHANNEL (object); + guint i; + + for (i = 0; i < structs->len; i++) + { + GValueArray *pair = g_ptr_array_index (structs, i); + guint id; + GValue *value; + + id = g_value_get_uint (g_value_array_get_nth (pair, 0)); + value = g_value_get_boxed (g_value_array_get_nth (pair, 1)); + + if (id == self->prop_id_nat_traversal) + { + g_free (self->nat_props.nat_traversal); + self->nat_props.nat_traversal = NULL; + + if (G_VALUE_HOLDS_STRING (value) && g_value_get_string (value)[0]) + self->nat_props.nat_traversal = g_value_dup_string (value); + } + else if (id == self->prop_id_stun_server) + { + g_free (self->nat_props.stun_server); + self->nat_props.stun_server = NULL; + + if (G_VALUE_HOLDS_STRING (value) && g_value_get_string (value)[0]) + self->nat_props.stun_server = g_value_dup_string (value); + } + else if (id == self->prop_id_gtalk_p2p_relay_token) + { + g_free (self->nat_props.relay_token); + self->nat_props.relay_token = NULL; + + if (G_VALUE_HOLDS_STRING (value) && g_value_get_string (value)[0]) + self->nat_props.relay_token = g_value_dup_string (value); + } + else if (id == self->prop_id_stun_port) + { + self->nat_props.stun_port = 0; + + if (G_VALUE_HOLDS_UINT (value)) + self->nat_props.stun_port = g_value_get_uint (value); + } + } +} + +static void +cb_properties_got (TpProxy *proxy, + const GPtrArray *structs, + const GError *error, + gpointer user_data, + GObject *object) +{ + if (error != NULL) + { + g_warning ("GetProperties(): %s", error->message); + return; + } + else + { + cb_properties_changed (proxy, structs, user_data, object); + } +} + +static void +cb_properties_listed (TpProxy *proxy, + const GPtrArray *structs, + const GError *error, + gpointer user_data G_GNUC_UNUSED, + GObject *object) +{ + TfMediaSignallingChannel *self = TF_MEDIA_SIGNALLING_CHANNEL (object); + guint i; + GArray *get_properties; + + if (error != NULL) + { + g_warning ("ListProperties(): %s", error->message); + return; + } + + get_properties = g_array_sized_new (FALSE, FALSE, sizeof (guint), 4); + + for (i = 0; i < structs->len; i++) + { + GValueArray *spec = g_ptr_array_index (structs, i); + guint id, flags; + const gchar *name, *type; + gboolean want = FALSE; + + id = g_value_get_uint (g_value_array_get_nth (spec, 0)); + name = g_value_get_string (g_value_array_get_nth (spec, 1)); + type = g_value_get_string (g_value_array_get_nth (spec, 2)); + flags = g_value_get_uint (g_value_array_get_nth (spec, 3)); + + if (!tp_strdiff (name, "nat-traversal") && !tp_strdiff (type, "s")) + { + self->prop_id_nat_traversal = id; + want = TRUE; + } + else if (!tp_strdiff (name, "stun-server") && !tp_strdiff (type, "s")) + { + self->prop_id_stun_server = id; + want = TRUE; + } + else if (!tp_strdiff (name, "gtalk-p2p-relay-token") && + !tp_strdiff (type, "s")) + { + self->prop_id_gtalk_p2p_relay_token = id; + want = TRUE; + } + else if (!tp_strdiff (name, "stun-port") && + (!tp_strdiff (type, "u") || !tp_strdiff (type, "q"))) + { + self->prop_id_stun_port = id; + want = TRUE; + } + else + { + g_debug ("Ignoring unrecognised property %s of type %s", name, type); + } + + if (want && (flags & TP_PROPERTY_FLAG_READ)) + g_array_append_val (get_properties, id); + } + + if (get_properties->len > 0) + tp_cli_properties_interface_call_get_properties (proxy, -1, + get_properties, cb_properties_got, NULL, NULL, object); + + g_array_free (get_properties, TRUE); +} + + +TfMediaSignallingChannel * +tf_media_signalling_channel_new (TpChannel *channel) +{ + TfMediaSignallingChannel *self = g_object_new ( + TF_TYPE_MEDIA_SIGNALLING_CHANNEL, NULL); + + self->channel_proxy = channel; + + if (!tp_proxy_has_interface_by_id (TP_PROXY (channel), + TP_IFACE_QUARK_PROPERTIES_INTERFACE)) + { + /* no point doing properties manipulation on a channel with none */ + g_message ("Channel has no properties: %s", + tp_proxy_get_object_path (TP_PROXY (channel))); + } + else + { + /* FIXME: it'd be good to use the replacement for TpPropsIface, when it + * exists */ + tp_cli_properties_interface_connect_to_properties_changed (channel, + cb_properties_changed, NULL, NULL, (GObject *) self, NULL); + tp_cli_properties_interface_call_list_properties (channel, -1, + cb_properties_listed, NULL, NULL, (GObject *) self); + } + + tp_cli_channel_interface_media_signalling_connect_to_new_session_handler + (channel, new_media_session_handler, NULL, NULL, (GObject *) self, + NULL); + tp_cli_channel_interface_media_signalling_call_get_session_handlers + (channel, -1, get_session_handlers_reply, NULL, NULL, + (GObject *) self); + + return self; +} + + +static void +tf_media_signalling_channel_dispose (GObject *object) +{ + TfMediaSignallingChannel *self = TF_MEDIA_SIGNALLING_CHANNEL (object); + + g_debug (G_STRFUNC); + + + if (self->streams) + { + guint i; + + for (i = 0; i < self->streams->len; i++) + { + GObject *obj = g_ptr_array_index (self->streams, i); + + if (obj != NULL) + { + tf_stream_error (TF_STREAM (obj), + TP_MEDIA_STREAM_ERROR_UNKNOWN, + "UI stopped channel"); + + /* this first one covers both error and closed */ + g_signal_handlers_disconnect_by_func (obj, + stream_closed_cb, self); + + g_object_unref (obj); + } + } + + g_ptr_array_free (self->streams, TRUE); + self->streams = NULL; + } + + if (self->session) + { + g_signal_handlers_disconnect_by_func (self->session, new_stream_cb, self); + + g_object_unref (self->session); + self->session = NULL; + } + + g_free (self->nat_props.nat_traversal); + self->nat_props.nat_traversal = NULL; + + g_free (self->nat_props.stun_server); + self->nat_props.stun_server = NULL; + + g_free (self->nat_props.relay_token); + self->nat_props.relay_token = NULL; + + if (G_OBJECT_CLASS (tf_media_signalling_channel_parent_class)->dispose) + G_OBJECT_CLASS (tf_media_signalling_channel_parent_class)->dispose (object); +} + + +static void +stream_closed_cb (TfStream *stream, + gpointer user_data) +{ + TfMediaSignallingChannel *self = TF_MEDIA_SIGNALLING_CHANNEL (user_data); + guint stream_id; + + g_object_get (stream, "stream-id", &stream_id, NULL); + + g_assert (stream == g_ptr_array_index (self->streams, stream_id)); + + g_object_unref (stream); + g_ptr_array_index (self->streams, stream_id) = NULL; +} + +static void +stream_created_cb (TfStream *stream, gpointer user_data) +{ + TfMediaSignallingChannel *self = user_data; + + g_signal_emit (self, signals[STREAM_CREATED], 0, stream); + + _tf_stream_try_sending_codecs (stream); +} + +static void +new_stream_cb (TfSession *session, + gchar *object_path, + guint stream_id, + TpMediaStreamType media_type, + TpMediaStreamDirection direction, + gpointer user_data) +{ + TfMediaSignallingChannel *self = TF_MEDIA_SIGNALLING_CHANNEL (user_data); + TfStream *stream; + FsConference *fs_conference; + FsParticipant *fs_participant; + TpProxy *channel_as_proxy = (TpProxy *) self->channel_proxy; + TpMediaStreamHandler *proxy; + GList *local_codec_config = NULL; + + proxy = tp_media_stream_handler_new (channel_as_proxy->dbus_daemon, + channel_as_proxy->bus_name, object_path, NULL); + + if (proxy == NULL) + { + gchar *str = g_strdup_printf ("failed to construct TpMediaStreamHandler:" + " bad object path '%s'?", object_path); + g_warning ("%s", str); + tf_media_signalling_channel_error (self, TP_MEDIA_STREAM_ERROR_UNKNOWN, + str); + g_free (str); + return; + } + + g_signal_emit (self, signals[GET_CODEC_CONFIG], 0, + media_type, + &local_codec_config); + + g_object_get (session, + "farstream-conference", &fs_conference, + "farstream-participant", &fs_participant, + NULL); + + stream = _tf_stream_new ((gpointer) self, fs_conference, + fs_participant, proxy, stream_id, media_type, direction, + &self->nat_props, local_codec_config, + stream_created_cb); + + fs_codec_list_destroy (local_codec_config); + + g_object_unref (proxy); + g_object_unref (fs_conference); + g_object_unref (fs_participant); + + if (self->streams->len <= stream_id) + g_ptr_array_set_size (self->streams, stream_id + 1); + + if (g_ptr_array_index (self->streams, stream_id) != NULL) + { + g_warning ("connection manager gave us a new stream with existing id " + "%u, sending error!", stream_id); + + tf_stream_error (stream, TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR, + "already have a stream with this ID"); + + g_object_unref (stream); + + return; + } + + g_ptr_array_index (self->streams, stream_id) = stream; + g_signal_connect (stream, "closed", G_CALLBACK (stream_closed_cb), + self); +} + +static void +session_invalidated_cb (TfSession *session, gpointer user_data) +{ + TfMediaSignallingChannel *self = TF_MEDIA_SIGNALLING_CHANNEL (user_data); + + g_assert (session == self->session); + + g_signal_handlers_disconnect_by_func (session, new_stream_cb, self); + + g_object_unref (session); + self->session = NULL; +} + +static void +add_session (TfMediaSignallingChannel *self, + const gchar *object_path, + const gchar *session_type) +{ + GError *error = NULL; + TpProxy *channel_as_proxy = (TpProxy *) self->channel_proxy; + TpMediaSessionHandler *proxy; + FsConference *conf = NULL; + + g_debug ("adding session handler %s, type %s", object_path, session_type); + + g_assert (self->session == NULL); + + proxy = tp_media_session_handler_new (channel_as_proxy->dbus_daemon, + channel_as_proxy->bus_name, object_path, &error); + + if (proxy == NULL) + { + g_prefix_error (&error,"failed to construct TpMediaSessionHandler: "); + g_warning ("%s", error->message); + tf_media_signalling_channel_error (self, TP_MEDIA_STREAM_ERROR_UNKNOWN, + error->message); + g_error_free (error); + return; + } + + self->session = _tf_session_new (proxy, session_type, &error); + + if (self->session == NULL) + { + g_prefix_error (&error, "failed to create session: "); + g_warning ("%s", error->message); + tf_media_signalling_channel_error (self, fserror_to_tperror (error), error->message); + g_error_free (error); + return; + } + + g_signal_connect (self->session, "new-stream", G_CALLBACK (new_stream_cb), + self); + g_signal_connect (self->session, "invalidated", + G_CALLBACK (session_invalidated_cb), self); + + g_object_get (self->session, + "farstream-conference", &conf, + NULL); + + g_signal_emit (self, signals[SESSION_CREATED], 0, conf); + g_object_unref (conf); +} + +static void +new_media_session_handler (TpChannel *channel_proxy G_GNUC_UNUSED, + const gchar *session_handler_path, + const gchar *type, + gpointer user_data G_GNUC_UNUSED, + GObject *weak_object) +{ + TfMediaSignallingChannel *self = TF_MEDIA_SIGNALLING_CHANNEL (weak_object); + + /* Ignore NewMediaSessionHandler until we've had a reply to + * GetSessionHandlers; otherwise, if the two cross over in mid-flight, + * we think the CM is asking us to add the same session twice, and get + * very confused + */ + + if (!self->got_sessions) + return; + + add_session (self, session_handler_path, type); +} + + +static void +get_session_handlers_reply (TpChannel *channel_proxy G_GNUC_UNUSED, + const GPtrArray *session_handlers, + const GError *error, + gpointer user_data G_GNUC_UNUSED, + GObject *weak_object) +{ + TfMediaSignallingChannel *self = TF_MEDIA_SIGNALLING_CHANNEL (weak_object); + + if (error) + { + g_critical ("Error calling GetSessionHandlers: %s", error->message); + return; + } + + if (session_handlers->len == 0) + { + g_debug ("GetSessionHandlers returned 0 sessions"); + } + else if (session_handlers->len == 1) + { + GValueArray *session; + GValue *obj; + GValue *type; + + g_debug ("GetSessionHandlers replied: "); + + session = g_ptr_array_index (session_handlers, 0); + obj = g_value_array_get_nth (session, 0); + type = g_value_array_get_nth (session, 1); + + g_assert (G_VALUE_TYPE (obj) == DBUS_TYPE_G_OBJECT_PATH); + g_assert (G_VALUE_HOLDS_STRING (type)); + + g_debug (" - session %s", (char *)g_value_get_boxed (obj)); + g_debug (" type %s", g_value_get_string (type)); + + add_session (self, + g_value_get_boxed (obj), g_value_get_string (type)); + } + else + { + g_error ("Got more than one session"); + } + + self->got_sessions = TRUE; +} + + +/** + * tf_media_signalling_channel_bus_message: + * @channel: A #TfMediaSignallingChannel + * @message: A #GstMessage received from the bus + * + * You must call this function on call messages received on the async bus. + * #GstMessages are not modified. + * + * Returns: %TRUE if the message has been handled, %FALSE otherwise + */ + +gboolean +tf_media_signalling_channel_bus_message (TfMediaSignallingChannel *channel, + GstMessage *message) +{ + guint i; + gboolean ret = FALSE; + + if (channel->session == NULL) + return FALSE; + + if (_tf_session_bus_message (channel->session, message)) + ret = TRUE; + + for (i = 0; i < channel->streams->len; i++) + { + TfStream *stream = g_ptr_array_index ( + channel->streams, i); + + if (stream != NULL) + if (_tf_stream_bus_message (stream, message)) + ret = TRUE; + } + + return ret; +} + +/** + * tf_media_signalling_channel_lookup_stream: + * @chan: a #TfMediaSignallingChannel + * @stream_id: the stream id to look for + * + * Finds the stream with the specified id if it exists. + * + * Returns: a #TfStream or %NULL + */ + +TfStream * +tf_media_signalling__channel_lookup_stream (TfMediaSignallingChannel *chan, + guint stream_id) +{ + if (stream_id >= chan->streams->len) + return NULL; + + return g_ptr_array_index (chan->streams, stream_id); +} + + + +/** + * tf_media_signalling_channel_error: + * @chan: a #TfMediaSignallingChannel + * @error: the error number of type #TpMediaStreamError + * @message: the error message + * + * Stops the channel and all stream related to it and sends an error to the + * connection manager. + */ + +static void +tf_media_signalling_channel_error (TfMediaSignallingChannel *chan, + TpMediaStreamError error, + const gchar *message) +{ + guint i; + + for (i = 0; i < chan->streams->len; i++) + if (g_ptr_array_index (chan->streams, i) != NULL) + tf_stream_error (g_ptr_array_index (chan->streams, i), + error, message); +} diff --git a/farstream/telepathy-farstream/media-signalling-channel.h b/farstream/telepathy-farstream/media-signalling-channel.h new file mode 100644 index 000000000..3e9f1a2a5 --- /dev/null +++ b/farstream/telepathy-farstream/media-signalling-channel.h @@ -0,0 +1,87 @@ +/* + * media-signalling-channel.h - Source for TfMediaSignallingChannel + * Copyright (C) 2006-2011 Collabora Ltd. + * Copyright (C) 2006-2011 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef __TF_MEDIA_SIGNALLING_CHANNEL_H__ +#define __TF_MEDIA_SIGNALLING_CHANNEL_H__ + +#include <glib-object.h> + +#include <telepathy-glib/channel.h> +#include "stream.h" + +G_BEGIN_DECLS + +#define TF_TYPE_MEDIA_SIGNALLING_CHANNEL tf_media_signalling_channel_get_type() + +#define TF_MEDIA_SIGNALLING_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + TF_TYPE_MEDIA_SIGNALLING_CHANNEL, TfMediaSignallingChannel)) + +#define TF_MEDIA_SIGNALLING_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + TF_TYPE_MEDIA_SIGNALLING_CHANNEL, TfMediaSignallingChannelClass)) + +#define TF_IS_MEDIA_SIGNALLING_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_MEDIA_SIGNALLING_CHANNEL)) + +#define TF_IS_MEDIA_SIGNALLING_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_MEDIA_SIGNALLING_CHANNEL)) + +#define TF_MEDIA_SIGNALLING_CHANNEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + TF_TYPE_MEDIA_SIGNALLING_CHANNEL, TfMediaSignallingChannelClass)) + +typedef struct _TfMediaSignallingChannelPrivate TfMediaSignallingChannelPrivate; + +/** + * TfMediaSignallingChannel: + * + * All members of the object are private + */ + +typedef struct _TfMediaSignallingChannel TfMediaSignallingChannel; + +/** + * TfMediaSignallingChannelClass: + * @parent_class: the parent #GObjecClass + * + * There are no overridable functions + */ + +typedef struct _TfMediaSignallingChannelClass TfMediaSignallingChannelClass; + +GType tf_media_signalling_channel_get_type (void); + +TfMediaSignallingChannel *tf_media_signalling_channel_new ( + TpChannel *channel_proxy); + +TfStream *tf_media_signalling_channel_lookup_stream ( + TfMediaSignallingChannel *chan, + guint stream_id); + +gboolean tf_media_signalling_channel_bus_message ( + TfMediaSignallingChannel *channel, + GstMessage *message); + +G_END_DECLS + +#endif /* __TF_MEDIA_SIGNALLING_CHANNEL_H__ */ + diff --git a/farstream/telepathy-farstream/media-signalling-content.c b/farstream/telepathy-farstream/media-signalling-content.c new file mode 100644 index 000000000..34c3ebc04 --- /dev/null +++ b/farstream/telepathy-farstream/media-signalling-content.c @@ -0,0 +1,324 @@ +/* + * media-signalling-content.c - Source for TfMediaSignallingContent + * Copyright (C) 2011 Collabora Ltd. + * Copyright (C) 2011 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * SECTION:media-signalling-content + + * @short_description: Handle the MediaSignalling interface on a Channel + * + * This class handles the + * org.freedesktop.Telepathy.Channel.Interface.MediaSignalling on a + * channel using Farstream. + */ + + +#include "media-signalling-content.h" + +#include <farstream/fs-conference.h> +#include <farstream/fs-utils.h> + +#include <stdarg.h> +#include <string.h> + +#include <telepathy-glib/proxy-subclass.h> + +#include "tf-signals-marshal.h" +#include "utils.h" + + +struct _TfMediaSignallingContent { + TfContent parent; + + TfMediaSignallingChannel *channel; + TfStream *stream; + guint handle; + + gboolean receiving; +}; + +struct _TfMediaSignallingContentClass{ + TfContentClass parent_class; +}; + +G_DEFINE_TYPE (TfMediaSignallingContent, tf_media_signalling_content, + TF_TYPE_CONTENT) + + +enum +{ + PROP_TF_CHANNEL = 1, + PROP_FS_CONFERENCE, + PROP_FS_SESSION, + PROP_SINK_PAD, + PROP_MEDIA_TYPE, + PROP_STREAM_ID +}; + +enum +{ + SIGNAL_COUNT +}; + + +// static guint signals[SIGNAL_COUNT] = {0}; + +static void +tf_media_signalling_content_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void tf_media_signalling_content_error (TfContent *content, + guint reason, /* TfFutureContentRemovalReason */ + const gchar *detailed_reason, + const gchar *message); + +static GstIterator * tf_media_signalling_content_iterate_src_pads ( + TfContent *content, + guint *handles, + guint handle_count); + +static void src_pad_added (TfStream *stream, GstPad *pad, FsCodec *codec, + TfMediaSignallingContent *self); + +static gboolean request_resource (TfStream *stream, guint direction, + TfMediaSignallingContent *self); +static void free_resource (TfStream *stream, guint direction, + TfMediaSignallingContent *self); +static void restart_source (TfStream *stream, TfMediaSignallingContent *self); + + +static void +tf_media_signalling_content_class_init (TfMediaSignallingContentClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + TfContentClass *content_class = TF_CONTENT_CLASS (klass); + + object_class->get_property = tf_media_signalling_content_get_property; + + content_class->content_error = tf_media_signalling_content_error; + content_class->iterate_src_pads = + tf_media_signalling_content_iterate_src_pads; + + g_object_class_override_property (object_class, PROP_TF_CHANNEL, + "tf-channel"); + g_object_class_override_property (object_class, PROP_FS_CONFERENCE, + "fs-conference"); + g_object_class_override_property (object_class, PROP_FS_SESSION, + "fs-session"); + g_object_class_override_property (object_class, PROP_SINK_PAD, + "sink-pad"); + g_object_class_override_property (object_class, PROP_MEDIA_TYPE, + "media-type"); + + g_object_class_install_property (object_class, PROP_STREAM_ID, + g_param_spec_uint ("stream-id", + "stream ID", + "A number identifying this stream within " + "its channel.", + 0, G_MAXUINT, 0, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); +} + + + +static void +tf_media_signalling_content_init (TfMediaSignallingContent *self) +{ + self->receiving = TRUE; +} + + +static void +tf_media_signalling_content_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + TfMediaSignallingContent *self = TF_MEDIA_SIGNALLING_CONTENT (object); + + switch (property_id) + { + case PROP_TF_CHANNEL: + g_value_set_object (value, self->channel); + break; + case PROP_FS_CONFERENCE: + g_object_get_property (G_OBJECT (self->stream), + "farstream-conference", value); + break; + case PROP_FS_SESSION: + g_object_get_property (G_OBJECT (self->stream), + "farstream-session", value); + break; + case PROP_SINK_PAD: + g_object_get_property (G_OBJECT (self->stream), + "sink-pad", value); + break; + case PROP_MEDIA_TYPE: + g_object_get_property (G_OBJECT (self->stream), + "media-type", value); + break; + case PROP_STREAM_ID: + g_object_get_property (G_OBJECT (self->stream), + "stream-id", value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +TfMediaSignallingContent * +tf_media_signalling_content_new ( + TfMediaSignallingChannel *media_signalling_channel, + TfStream *stream, + guint handle) +{ + TfMediaSignallingContent *self = + g_object_new (TF_TYPE_MEDIA_SIGNALLING_CONTENT, NULL); + GstElement *conf; + FsSession *session; + GList *codec_prefs; + + self->channel = media_signalling_channel; + self->stream = stream; + self->handle = handle; + + tp_g_signal_connect_object (stream, "src-pad-added", + G_CALLBACK (src_pad_added), G_OBJECT (self), 0); + tp_g_signal_connect_object (stream, "request-resource", + G_CALLBACK (request_resource), G_OBJECT (self), 0); + tp_g_signal_connect_object (stream, "free-resource", + G_CALLBACK (free_resource), G_OBJECT (self), 0); + tp_g_signal_connect_object (stream, "restart-source", + G_CALLBACK (restart_source), G_OBJECT (self), 0); + + g_object_get (stream, + "farstream-conference", &conf, + "farstream-session", &session, + NULL); + + codec_prefs = fs_utils_get_default_codec_preferences (conf); + if (!fs_session_set_codec_preferences (session, codec_prefs, NULL)) + tf_stream_error (stream, TP_MEDIA_STREAM_ERROR_MEDIA_ERROR, + "Default codec preferences disabled all codecs"); + + fs_codec_list_destroy (codec_prefs); + g_object_unref (session); + gst_object_unref (conf); + + return self; +} + +static void +src_pad_added (TfStream *stream, GstPad *pad, FsCodec *codec, + TfMediaSignallingContent *self) +{ + FsStream *fs_stream; + + g_object_get (stream, "farstream-stream", &fs_stream, NULL); + _tf_content_emit_src_pad_added (TF_CONTENT (self), self->handle, fs_stream, + pad, codec); + g_object_unref (fs_stream); +} + +static gboolean +request_resource (TfStream *stream, guint direction, + TfMediaSignallingContent *self) +{ + if (direction & TP_MEDIA_STREAM_DIRECTION_SEND) + return _tf_content_start_sending (TF_CONTENT (self)); + + if (!self->receiving && direction & TP_MEDIA_STREAM_DIRECTION_RECEIVE) + { + guint handles[2] = { self->handle, 0}; + + self->receiving = _tf_content_start_receiving (TF_CONTENT (self), + handles, 1); + + return self->receiving; + } + + return FALSE; +} + + +static void +free_resource (TfStream *stream, guint direction, + TfMediaSignallingContent *self) +{ + guint handles[2] = { self->handle, 0}; + + if (direction & TP_MEDIA_STREAM_DIRECTION_SEND) + _tf_content_stop_sending (TF_CONTENT (self)); + + if (self->receiving && direction & TP_MEDIA_STREAM_DIRECTION_RECEIVE) + { + _tf_content_stop_receiving (TF_CONTENT (self), handles, 1); + self->receiving = FALSE; + } +} + +static void +restart_source (TfStream *stream, TfMediaSignallingContent *self) +{ + g_signal_emit_by_name (self, "restart-source"); +} + +static void +tf_media_signalling_content_error (TfContent *content, + guint reason, /* TfFutureContentRemovalReason */ + const gchar *detailed_reason, + const gchar *message) +{ + TfMediaSignallingContent *self = TF_MEDIA_SIGNALLING_CONTENT (content); + TpMediaStreamError stream_error; + + switch (reason) + { + case TF_FUTURE_CONTENT_REMOVAL_REASON_ERROR: + stream_error = TP_MEDIA_STREAM_ERROR_MEDIA_ERROR; + break; + default: + stream_error = TP_MEDIA_STREAM_ERROR_UNKNOWN; + } + + tf_stream_error (self->stream, stream_error, message); +} + +static GstIterator * +tf_media_signalling_content_iterate_src_pads (TfContent *content, + guint *handles, + guint handle_count) +{ + TfMediaSignallingContent *self = TF_MEDIA_SIGNALLING_CONTENT (content); + GstIterator *iter = NULL; + FsStream *fs_stream; + + g_return_val_if_fail (handle_count <= 1, NULL); + + g_object_get (self->stream, "farstream-stream", &fs_stream, NULL); + iter = fs_stream_iterate_src_pads (fs_stream); + g_object_unref (fs_stream); + + return iter; +} diff --git a/farstream/telepathy-farstream/media-signalling-content.h b/farstream/telepathy-farstream/media-signalling-content.h new file mode 100644 index 000000000..2bb7fd4c4 --- /dev/null +++ b/farstream/telepathy-farstream/media-signalling-content.h @@ -0,0 +1,86 @@ +/* + * media-signalling-content.h - Source for TfMediaSignallingContent + * Copyright (C) 2011 Collabora Ltd. + * Copyright (C) 2011 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __TF_CALL_CONTENT_H__ +#define __TF_CALL_CONTENT_H__ + +#include <glib-object.h> + +#include <gst/gst.h> +#include <telepathy-glib/channel.h> + +#include "extensions/extensions.h" +#include "media-signalling-channel.h" +#include "content.h" +#include "content-priv.h" + +G_BEGIN_DECLS + +#define TF_TYPE_MEDIA_SIGNALLING_CONTENT tf_media_signalling_content_get_type() + +#define TF_MEDIA_SIGNALLING_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + TF_TYPE_MEDIA_SIGNALLING_CONTENT, TfMediaSignallingContent)) + +#define TF_MEDIA_SIGNALLING_CONTENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + TF_TYPE_MEDIA_SIGNALLING_CONTENT, TfMediaSignallingContentClass)) + +#define TF_IS_MEDIA_SIGNALLING_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_MEDIA_SIGNALLING_CONTENT)) + +#define TF_IS_MEDIA_SIGNALLING_CONTENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_MEDIA_SIGNALLING_CONTENT)) + +#define TF_MEDIA_SIGNALLING_CONTENT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + TF_TYPE_MEDIA_SIGNALLING_CONTENT, TfMediaSignallingContentClass)) + +typedef struct _TfMediaSignallingContentPrivate TfMediaSignallingContentPrivate; + +/** + * TfMediaSignallingContent: + * + * All members of the object are private + */ + +typedef struct _TfMediaSignallingContent TfMediaSignallingContent; + +/** + * TfMediaSignallingContentClass: + * @parent_class: the parent #GObjecClass + * + * There are no overridable functions + */ + +typedef struct _TfMediaSignallingContentClass TfMediaSignallingContentClass; + +GType tf_media_signalling_content_get_type (void); + +TfMediaSignallingContent * +tf_media_signalling_content_new ( + TfMediaSignallingChannel *media_signalling_channel, + TfStream *stream, + guint handle); + +G_END_DECLS + +#endif /* __TF_MEDIA_SIGNALLING_CONTENT_H__ */ + diff --git a/farstream/telepathy-farstream/session-priv.h b/farstream/telepathy-farstream/session-priv.h new file mode 100644 index 000000000..af69c4de7 --- /dev/null +++ b/farstream/telepathy-farstream/session-priv.h @@ -0,0 +1,69 @@ +#ifndef __TF_SESSION_H__ +#define __TF_SESSION_H__ + +#include <glib-object.h> +#include <telepathy-glib/media-interfaces.h> + +G_BEGIN_DECLS + +#define TF_TYPE_SESSION _tf_session_get_type() + +#define TF_SESSION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + TF_TYPE_SESSION, TfSession)) + +#define TF_SESSION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + TF_TYPE_SESSION, TfSessionClass)) + +#define TF_IS_SESSION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + TF_TYPE_SESSION)) + +#define TF_IS_SESSION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + TF_TYPE_SESSION)) + +#define TF_SESSION_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + TF_TYPE_SESSION, TfSessionClass)) + +typedef struct _TfSessionPrivate TfSessionPrivate; + + +/** + * TfSession: + * + * All members of the object are private + */ + +typedef struct { + GObject parent; + + TfSessionPrivate *priv; +} TfSession; + +/** + * TfSessionClass: + * + * There are no overridable functions + */ + +typedef struct { + GObjectClass parent_class; +} TfSessionClass; + +GType _tf_session_get_type (void); + +TfSession * +_tf_session_new (TpMediaSessionHandler *proxy, + const gchar *conference_type, + GError **error); + +gboolean _tf_session_bus_message (TfSession *session, + GstMessage *message); + +G_END_DECLS + +#endif /* __TF_SESSION_H__ */ + diff --git a/farstream/telepathy-farstream/session.c b/farstream/telepathy-farstream/session.c new file mode 100644 index 000000000..42cb4dbd3 --- /dev/null +++ b/farstream/telepathy-farstream/session.c @@ -0,0 +1,456 @@ +/* + * session.c - Source for TfSession + * Copyright (C) 2006-2007 Collabora Ltd. + * Copyright (C) 2006-2007 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <telepathy-glib/dbus.h> +#include <telepathy-glib/errors.h> +#include <telepathy-glib/interfaces.h> + +#include <farstream/fs-conference.h> + +#include "session-priv.h" +#include "tf-signals-marshal.h" + +G_DEFINE_TYPE (TfSession, _tf_session, G_TYPE_OBJECT); + +struct _TfSessionPrivate +{ + GError *construction_error; + + gchar *session_type; + FsConference *fs_conference; + FsParticipant *fs_participant; + + TpMediaSessionHandler *session_handler_proxy; +}; + +enum +{ + PROP_PROXY = 1, + PROP_SESSION_TYPE, + PROP_FARSTREAM_CONFERENCE, + PROP_FARSTREAM_PARTICIPANT, +}; + +enum +{ + NEW_STREAM, + INVALIDATED, + SIGNAL_COUNT +}; + +static guint signals[SIGNAL_COUNT] = { 0 }; + +static void +_tf_session_init (TfSession *self) +{ + TfSessionPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + TF_TYPE_SESSION, TfSessionPrivate); + + self->priv = priv; +} + +static void +_tf_session_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + TfSession *self = TF_SESSION (object); + + switch (property_id) + { + case PROP_SESSION_TYPE: + g_value_set_string (value, self->priv->session_type); + break; + case PROP_FARSTREAM_CONFERENCE: + g_value_set_object (value, self->priv->fs_conference); + break; + case PROP_FARSTREAM_PARTICIPANT: + g_value_set_object (value, self->priv->fs_participant); + break; + case PROP_PROXY: + g_value_set_object (value, self->priv->session_handler_proxy); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +_tf_session_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + TfSession *self = TF_SESSION (object); + + switch (property_id) + { + case PROP_SESSION_TYPE: + self->priv->session_type = g_value_dup_string (value); + break; + case PROP_PROXY: + self->priv->session_handler_proxy = + TP_MEDIA_SESSION_HANDLER (g_value_dup_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void new_media_stream_handler (TpMediaSessionHandler *proxy, + const gchar *stream_handler_path, guint id, guint media_type, + guint direction, gpointer user_data, GObject *object); + +static void dummy_callback (TpMediaSessionHandler *proxy, const GError *error, + gpointer user_data, GObject *object); + +static void invalidated_cb (TpMediaSessionHandler *proxy, guint domain, + gint code, gchar *message, gpointer user_data); + +static GObject * +_tf_session_constructor (GType type, + guint n_props, + GObjectConstructParam *props) +{ + GObject *obj; + TfSession *self; + gchar *conftype; + GstElement *conf; + + obj = G_OBJECT_CLASS (_tf_session_parent_class)-> + constructor (type, n_props, props); + self = (TfSession *) obj; + + conftype = g_strdup_printf ("fs%sconference", self->priv->session_type); + conf = gst_element_factory_make (conftype, NULL); + g_free (conftype); + + if (!conf) + { + self->priv->construction_error = g_error_new (FS_ERROR, + FS_ERROR_CONSTRUCTION, "Invalid session type"); + return obj; + } + + self->priv->fs_conference = FS_CONFERENCE (gst_object_ref (conf)); + + self->priv->fs_participant = + fs_conference_new_participant (self->priv->fs_conference, + &self->priv->construction_error); + + if (!self->priv->fs_participant) + return obj; + + g_signal_connect (self->priv->session_handler_proxy, "invalidated", + G_CALLBACK (invalidated_cb), obj); + + + tp_cli_media_session_handler_connect_to_new_stream_handler + (self->priv->session_handler_proxy, new_media_stream_handler, NULL, NULL, + obj, NULL); + + g_debug ("calling MediaSessionHandler::Ready"); + + tp_cli_media_session_handler_call_ready (self->priv->session_handler_proxy, + -1, dummy_callback, "Media.SessionHandler::Ready", NULL, NULL); + + return obj; +} + +static void +_tf_session_dispose (GObject *object) +{ + TfSession *self = TF_SESSION (object); + + g_debug (G_STRFUNC); + + if (self->priv->session_handler_proxy) + { + TpMediaSessionHandler *tmp; + + g_signal_handlers_disconnect_by_func ( + self->priv->session_handler_proxy, invalidated_cb, self); + + tmp = self->priv->session_handler_proxy; + self->priv->session_handler_proxy = NULL; + g_object_unref (tmp); + } + + if (self->priv->fs_participant) + { + g_object_unref (self->priv->fs_participant); + self->priv->fs_participant = NULL; + } + + if (self->priv->fs_conference) + { + + gst_object_unref (self->priv->fs_conference); + self->priv->fs_conference = NULL; + } + + g_free (self->priv->session_type); + self->priv->session_type = NULL; + + if (G_OBJECT_CLASS (_tf_session_parent_class)->dispose) + G_OBJECT_CLASS (_tf_session_parent_class)->dispose (object); +} + +static void +_tf_session_class_init (TfSessionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (TfSessionPrivate)); + + object_class->set_property = _tf_session_set_property; + object_class->get_property = _tf_session_get_property; + + object_class->constructor = _tf_session_constructor; + + object_class->dispose = _tf_session_dispose; + + g_object_class_install_property (object_class, PROP_SESSION_TYPE, + g_param_spec_string ("conference-type", + "Farstream conference type", + "Name of the Farstream conference type this " + "session will create (rtp, msn, etc).", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_FARSTREAM_CONFERENCE, + g_param_spec_object ("farstream-conference", + "Farstream conference", + "The Farstream conference to add to the pipeline", + FS_TYPE_CONFERENCE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_FARSTREAM_PARTICIPANT, + g_param_spec_object ("farstream-participant", + "Farstream participant", + "The Farstream participant for this session", + FS_TYPE_PARTICIPANT, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_PROXY, + g_param_spec_object ("proxy", "TpMediaSessionHandler proxy", + "The session handler proxy which this session interacts with.", + TP_TYPE_MEDIA_SESSION_HANDLER, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * TfSession::new-stream: + * @session: the #TfSession which has a new stream + * @object_path: The object-path of the new stream + * @stream_id: The stream id of the new strema + * @media_type: The media type of the new stream + * @direction: The direction of the new stream + * + * This is emitted when a new stream is created and should only be used + * by the #TfChannel. + * One should connect to the #TfChannel::channel-new-stream signal + * instead. + */ + + signals[NEW_STREAM] = + g_signal_new ("new-stream", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _tf_marshal_VOID__BOXED_UINT_UINT_UINT, + G_TYPE_NONE, 4, + DBUS_TYPE_G_OBJECT_PATH, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT); + signals[INVALIDATED] = + g_signal_new ("invalidated", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +/* dummy callback handler for async calling calls with no return values */ +static void +dummy_callback (TpMediaSessionHandler *proxy G_GNUC_UNUSED, + const GError *error, + gpointer user_data, + GObject *weak_object G_GNUC_UNUSED) +{ + if (error != NULL) + { + g_warning ("Error calling %s: %s", (gchar *) user_data, error->message); + } +} + +static void +invalidated_cb (TpMediaSessionHandler *proxy G_GNUC_UNUSED, + guint domain G_GNUC_UNUSED, + gint code G_GNUC_UNUSED, + gchar *message G_GNUC_UNUSED, + gpointer user_data) +{ + TfSession *self = TF_SESSION (user_data); + + if (self->priv->session_handler_proxy) + { + TpMediaSessionHandler *tmp; + + tmp = self->priv->session_handler_proxy; + self->priv->session_handler_proxy = NULL; + g_object_unref (tmp); + } + + g_signal_emit (self, signals[INVALIDATED], 0); +} + +static void +new_media_stream_handler (TpMediaSessionHandler *proxy G_GNUC_UNUSED, + const gchar *object_path, + guint stream_id, + guint media_type, + guint direction, + gpointer user_data G_GNUC_UNUSED, + GObject *object) +{ + TfSession *self = TF_SESSION (object); + + g_debug ("New stream, stream_id=%d, media_type=%d, direction=%d", + stream_id, media_type, direction); + + g_signal_emit (self, signals[NEW_STREAM], 0, object_path, stream_id, + media_type, direction); +} + +TfSession * +_tf_session_new (TpMediaSessionHandler *proxy, + const gchar *conference_type, + GError **error) +{ + TfSession *self; + + g_return_val_if_fail (proxy != NULL, NULL); + g_return_val_if_fail (conference_type != NULL, NULL); + + self = g_object_new (TF_TYPE_SESSION, + "proxy", proxy, + "conference-type", conference_type, + NULL); + + if (self->priv->construction_error) + { + g_propagate_error (error, self->priv->construction_error); + g_object_unref (self); + return NULL; + } + + return self; +} + +/** + * _tf_session_bus_message: + * @session: A #TfSession + * @message: A #GstMessage received from the bus + * + * You must call this function on call messages received on the async bus. + * #GstMessages are not modified. + * + * Returns: %TRUE if the message has been handled, %FALSE otherwise + */ + +gboolean +_tf_session_bus_message (TfSession *session, + GstMessage *message) +{ + GError *error = NULL; + gchar *debug = NULL; + + if (GST_MESSAGE_SRC (message) != + GST_OBJECT_CAST (session->priv->fs_conference)) + return FALSE; + + switch (GST_MESSAGE_TYPE (message)) + { + case GST_MESSAGE_WARNING: + gst_message_parse_warning (message, &error, &debug); + + g_warning ("session: %s (%s)", error->message, debug); + + g_error_free (error); + g_free (debug); + return TRUE; + case GST_MESSAGE_ERROR: + gst_message_parse_error (message, &error, &debug); + + g_warning ("session ERROR: %s (%s)", error->message, debug); + + tp_cli_media_session_handler_call_error ( + session->priv->session_handler_proxy, + -1, 0, /* Not errors defined ??? */ + error->message, NULL, /* Do I need a callback ? */ + NULL, NULL, NULL); + + g_error_free (error); + g_free (debug); + return TRUE; + case GST_MESSAGE_ELEMENT: + { + const GstStructure *s = gst_message_get_structure (message); + + if (gst_structure_has_name (s, "farstream-error")) + { + GObject *object; + const GValue *value = NULL; + + value = gst_structure_get_value (s, "src-object"); + object = g_value_get_object (value); + + if (object == G_OBJECT (session->priv->fs_participant)) + { + const gchar *msg; + FsError errorno; + GEnumClass *enumclass; + GEnumValue *enumvalue; + + value = gst_structure_get_value (s, "error-no"); + errorno = g_value_get_enum (value); + msg = gst_structure_get_string (s, "error-msg"); + + enumclass = g_type_class_ref (FS_TYPE_ERROR); + enumvalue = g_enum_get_value (enumclass, errorno); + g_warning ("participant error (%s (%d)): %s", + enumvalue->value_nick, errorno, msg); + g_type_class_unref (enumclass); + + tp_cli_media_session_handler_call_error ( + session->priv->session_handler_proxy, + -1, 0, msg, NULL, NULL, NULL, NULL); + return TRUE; + } + } + } + default: + return FALSE; + } +} diff --git a/farstream/telepathy-farstream/stream-priv.h b/farstream/telepathy-farstream/stream-priv.h new file mode 100644 index 000000000..3e82a180e --- /dev/null +++ b/farstream/telepathy-farstream/stream-priv.h @@ -0,0 +1,75 @@ +#ifndef __TF_STREAM_PRIV_H__ +#define __TF_STREAM_PRIV_H__ + +#include "stream.h" + +G_BEGIN_DECLS + +typedef struct _TfStreamPrivate TfStreamPrivate; + +/* + * TfStream: + * @parent: the parent #GObject + * @stream_id: the ID of the stream (READ-ONLY) + * + * All other members are privated + */ + +struct _TfStream { + GObject parent; + + /* Read-only */ + guint stream_id; + + /*< private >*/ + + TfStreamPrivate *priv; +}; + +/* + * TfStreamClass: + * @parent_class: the parent #GObjecClass + * + * There are no overridable functions + */ + +struct _TfStreamClass { + GObjectClass parent_class; + + /*< private >*/ + + gpointer unused[4]; +}; + + +typedef struct { + gchar *nat_traversal; + gchar *stun_server; + guint16 stun_port; + gchar *relay_token; +} TfNatProperties; + +typedef void (NewStreamCreatedCb) (TfStream *stream, gpointer channel); + +TfStream * +_tf_stream_new (gpointer channel, + FsConference *conference, + FsParticipant *participant, + TpMediaStreamHandler *proxy, + guint stream_id, + TpMediaStreamType media_type, + TpMediaStreamDirection direction, + TfNatProperties *nat_props, + GList *local_codecs_config, + NewStreamCreatedCb new_stream_created_cb); + +gboolean _tf_stream_bus_message (TfStream *stream, + GstMessage *message); + +void _tf_stream_try_sending_codecs (TfStream *stream); + +TpMediaStreamError fserror_to_tperror (GError *error); + +G_END_DECLS + +#endif /* __TF_STREAM_PRIV_H__ */ diff --git a/farstream/telepathy-farstream/stream.c b/farstream/telepathy-farstream/stream.c new file mode 100644 index 000000000..2e39ff845 --- /dev/null +++ b/farstream/telepathy-farstream/stream.c @@ -0,0 +1,3097 @@ +/* + * stream.c - Source for TfStream + * Copyright (C) 2006-2008 Collabora Ltd. + * Copyright (C) 2006-2008 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * SECTION:stream + * @short_description: Handles a media Stream + * + * These objects handle media streams and wrap the appropriate Farstream + * objects. It is used to interact on a stream level with the other parts + * of the media pipeline and the proper UI. + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> +#include <stdlib.h> + +#include <telepathy-glib/dbus.h> +#include <telepathy-glib/errors.h> +#include <telepathy-glib/gtypes.h> +#include <telepathy-glib/interfaces.h> +#include <telepathy-glib/util.h> + +#include <farstream/fs-conference.h> +#include <farstream/fs-rtp.h> +#include <farstream/fs-utils.h> + +#include "stream.h" +#include "stream-priv.h" +#include "channel.h" +#include "tf-signals-marshal.h" +#include "utils.h" + +G_DEFINE_TYPE (TfStream, tf_stream, G_TYPE_OBJECT); + +#define DEBUG(stream, format, ...) \ + g_debug ("stream %d %p (%s) %s: " format, \ + stream->stream_id, stream, \ + (stream->priv->media_type == TP_MEDIA_STREAM_TYPE_AUDIO) ? "audio" \ + : "video", \ + G_STRFUNC, \ + ##__VA_ARGS__) + +#define WARNING(stream, format, ...) \ + g_warning ("stream %d %p (%s) %s: " format, \ + stream->stream_id, stream, \ + (stream->priv->media_type == TP_MEDIA_STREAM_TYPE_AUDIO) ? "audio" \ + : "video", \ + G_STRFUNC, \ + ##__VA_ARGS__) + +#define STREAM_PRIVATE(o) ((o)->priv) + +#define TF_STREAM_LOCK(o) (g_static_mutex_lock (&(o)->priv->mutex)) +#define TF_STREAM_UNLOCK(o) (g_static_mutex_unlock (&(o)->priv->mutex)) + +static TpMediaStreamError fserrorno_to_tperrorno (FsError fserror); + +struct DtmfEvent { + gint codec_id; + guint event_id; +}; + +struct _TfStreamPrivate +{ + TfChannel *channel; + FsConference *fs_conference; + FsParticipant *fs_participant; + FsSession *fs_session; + FsStream *fs_stream; + TpMediaStreamType media_type; + TpMediaStreamDirection direction; + const TfNatProperties *nat_props; + GList *local_preferences; + + TpMediaStreamHandler *stream_handler_proxy; + + FsStreamDirection desired_direction; + gboolean held; + TpMediaStreamDirection has_resource; + + GList *local_candidates; + + GList *last_sent_codecs; + + gboolean send_local_codecs; + gboolean send_supported_codecs; + + guint tos; + + GHashTable *feedback_messages; + GPtrArray *header_extensions; + + GStaticMutex mutex; + guint idle_connected_id; /* Protected by mutex */ + gboolean disposed; /* Protected by mutex */ + + TpMediaStreamState current_state; + + NewStreamCreatedCb *new_stream_created_cb; + + GQueue events_to_send; + + gint sending_telephony_event; +}; + +enum +{ + CLOSED, + ERROR_SIGNAL, + REQUEST_RESOURCE, + FREE_RESOURCE, + SRC_PAD_ADDED, + RESTART_SOURCE, + SIGNAL_COUNT +}; + +static guint signals[SIGNAL_COUNT] = {0}; + +/* properties */ +enum +{ + PROP_CHANNEL = 1, + PROP_FARSTREAM_CONFERENCE, + PROP_FARSTREAM_SESSION, + PROP_FARSTREAM_STREAM, + PROP_FARSTREAM_PARTICIPANT, + PROP_PROXY, + PROP_STREAM_ID, + PROP_MEDIA_TYPE, + PROP_DIRECTION, + PROP_NAT_PROPERTIES, + PROP_SINK_PAD, + PROP_LOCAL_PREFERENCES, + PROP_TOS, + PROP_RESOURCES +}; + +static void get_all_properties_cb (TpProxy *proxy, + GHashTable *out_Properties, + const GError *error, + gpointer user_data, + GObject *weak_object); +static gboolean tf_stream_request_resource ( + TfStream *self, + TpMediaStreamDirection dir); +static void tf_stream_free_resource (TfStream *self, + TpMediaStreamDirection dir); + +static void add_remote_candidate (TpMediaStreamHandler *proxy, + const gchar *candidate, const GPtrArray *transports, + gpointer user_data, GObject *object); + +static void remove_remote_candidate (TpMediaStreamHandler *proxy, + const gchar *candidate, + gpointer user_data, GObject *object); + +static void set_active_candidate_pair (TpMediaStreamHandler *proxy, + const gchar *native_candidate, const gchar *remote_candidate, + gpointer user_data, GObject *object); + +static void set_remote_candidate_list (TpMediaStreamHandler *proxy, + const GPtrArray *candidates, gpointer user_data, GObject *object); + +static void set_remote_codecs (TpMediaStreamHandler *proxy, + const GPtrArray *codecs, gpointer user_data, GObject *object); + +static void set_stream_playing (TpMediaStreamHandler *proxy, gboolean play, + gpointer user_data, GObject *object); +static void set_stream_held (TpMediaStreamHandler *proxy, gboolean held, + gpointer user_data, GObject *object); + +static void set_stream_sending (TpMediaStreamHandler *proxy, gboolean play, + gpointer user_data, GObject *object); + +static void start_telephony_event (TpMediaStreamHandler *proxy, guchar event, + gpointer user_data, GObject *object); + +static void start_named_telephony_event (TpMediaStreamHandler *proxy, + guchar event, guint codecid, gpointer user_data, GObject *object); + +static void start_sound_telephony_event (TpMediaStreamHandler *proxy, + guchar event, gpointer user_data, GObject *object); + +static void stop_telephony_event (TpMediaStreamHandler *proxy, + gpointer user_data, GObject *object); + +static void stream_close (TpMediaStreamHandler *proxy, + gpointer user_data, GObject *object); + +static void set_remote_feedback_messages (TpMediaStreamHandler *proxy, + GHashTable *messages, gpointer user_data, GObject *object); + +static void set_remote_header_extensions (TpMediaStreamHandler *proxy, + const GPtrArray *header_extensions, gpointer user_data, GObject *object); + +static void invalidated_cb (TpMediaStreamHandler *proxy, + guint domain, gint code, gchar *message, gpointer user_data); + +static TpMediaStreamBaseProto fs_network_proto_to_tp (FsNetworkProtocol proto, + gboolean *valid); +static TpMediaStreamTransportType fs_candidate_type_to_tp (FsCandidateType type, + gboolean *valid); +static GValueArray *fs_candidate_to_tp_array (const FsCandidate *candidate); + +static GPtrArray *fs_codecs_to_tp (TfStream *stream, + const GList *codecs); +static void async_method_callback (TpMediaStreamHandler *proxy G_GNUC_UNUSED, + const GError *error, + gpointer user_data, + GObject *weak_object); +static void tf_stream_shutdown (TfStream *self); + + +static void cb_fs_stream_src_pad_added (FsStream *fsstream G_GNUC_UNUSED, + GstPad *pad, + FsCodec *codec, + gpointer user_data); + +static void cb_fs_component_state_changed (TfStream *self, + guint component, + FsStreamState fsstate); + + +static void +tf_stream_init (TfStream *self) +{ + TfStreamPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + TF_TYPE_STREAM, TfStreamPrivate); + + self->priv = priv; + g_static_mutex_init (&priv->mutex); + priv->has_resource = TP_MEDIA_STREAM_DIRECTION_NONE; + priv->current_state = TP_MEDIA_STREAM_STATE_DISCONNECTED; + priv->sending_telephony_event = -1; + + g_queue_init (&priv->events_to_send); +} + +static void +tf_stream_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + TfStream *self = TF_STREAM (object); + + switch (property_id) + { + case PROP_CHANNEL: + g_value_set_object (value, self->priv->channel); + break; + case PROP_FARSTREAM_CONFERENCE: + g_value_set_object (value, self->priv->fs_conference); + break; + case PROP_FARSTREAM_PARTICIPANT: + g_value_set_object (value, self->priv->fs_participant); + break; + case PROP_FARSTREAM_SESSION: + g_value_set_object (value, self->priv->fs_session); + break; + case PROP_FARSTREAM_STREAM: + g_value_set_object (value, self->priv->fs_stream); + break; + case PROP_PROXY: + g_value_set_object (value, self->priv->stream_handler_proxy); + break; + case PROP_STREAM_ID: + g_value_set_uint (value, self->stream_id); + break; + case PROP_MEDIA_TYPE: + g_value_set_uint (value, self->priv->media_type); + break; + case PROP_DIRECTION: + g_value_set_uint (value, self->priv->direction); + break; + case PROP_NAT_PROPERTIES: + g_value_set_pointer (value, + (TfNatProperties *) self->priv->nat_props); + break; + case PROP_SINK_PAD: + g_object_get_property (G_OBJECT (self->priv->fs_session), + "sink-pad", value); + break; + case PROP_LOCAL_PREFERENCES: + g_value_set_boxed (value, self->priv->local_preferences); + break; + case PROP_TOS: + if (self->priv->fs_session) + g_object_get_property (G_OBJECT (self->priv->fs_session), "tos", value); + else + g_value_set_uint (value, self->priv->tos); + break; + case PROP_RESOURCES: + g_value_set_uint (value, self->priv->has_resource); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +tf_stream_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + TfStream *self = TF_STREAM (object); + + switch (property_id) + { + case PROP_CHANNEL: + self->priv->channel = + TF_CHANNEL (g_value_get_object (value)); + break; + case PROP_FARSTREAM_CONFERENCE: + self->priv->fs_conference = + FS_CONFERENCE (g_value_dup_object (value)); + break; + case PROP_FARSTREAM_PARTICIPANT: + self->priv->fs_participant = + FS_PARTICIPANT (g_value_dup_object (value)); + break; + case PROP_PROXY: + self->priv->stream_handler_proxy = + TP_MEDIA_STREAM_HANDLER (g_value_dup_object (value)); + break; + case PROP_STREAM_ID: + self->stream_id = g_value_get_uint (value); + break; + case PROP_MEDIA_TYPE: + self->priv->media_type = g_value_get_uint (value); + break; + case PROP_DIRECTION: + self->priv->direction = g_value_get_uint (value); + break; + case PROP_NAT_PROPERTIES: + self->priv->nat_props = g_value_get_pointer (value); + break; + case PROP_LOCAL_PREFERENCES: + self->priv->local_preferences = g_value_dup_boxed (value); + break; + case PROP_TOS: + self->priv->tos = g_value_get_uint (value); + if (self->priv->fs_session) + g_object_set_property (G_OBJECT (self->priv->fs_session), "tos", value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +#define MAX_STREAM_TRANS_PARAMS 7 + +static GObject * +tf_stream_constructor (GType type, + guint n_props, + GObjectConstructParam *props) +{ + GObject *obj; + TfStream *stream; + + + obj = G_OBJECT_CLASS (tf_stream_parent_class)-> + constructor (type, n_props, props); + + stream = TF_STREAM (obj); + + g_signal_connect (stream->priv->stream_handler_proxy, "invalidated", + G_CALLBACK (invalidated_cb), obj); + + tp_cli_dbus_properties_call_get_all (stream->priv->stream_handler_proxy, + -1, "org.freedesktop.Telepathy.Media.StreamHandler", + get_all_properties_cb, NULL, NULL, obj); + + return obj; +} + +static void +tf_stream_dispose (GObject *object) +{ + TfStream *stream = TF_STREAM (object); + TfStreamPrivate *priv = stream->priv; + gpointer data; + + TF_STREAM_LOCK (stream); + if (stream->priv->idle_connected_id) + g_source_remove (stream->priv->idle_connected_id); + stream->priv->idle_connected_id = 0; + + stream->priv->disposed = TRUE; + TF_STREAM_UNLOCK (stream); + + + if (priv->stream_handler_proxy) + { + TpMediaStreamHandler *tmp = priv->stream_handler_proxy; + + g_signal_handlers_disconnect_by_func ( + priv->stream_handler_proxy, invalidated_cb, stream); + + priv->stream_handler_proxy = NULL; + g_object_unref (tmp); + } + + if (priv->fs_stream) + { + tf_stream_free_resource (stream, + TP_MEDIA_STREAM_DIRECTION_RECEIVE); + + g_object_run_dispose (G_OBJECT (priv->fs_stream)); + g_object_unref (priv->fs_stream); + + tf_stream_free_resource (stream, + TP_MEDIA_STREAM_DIRECTION_SEND); + + priv->fs_stream = NULL; + } + + if (priv->fs_session) + { + g_object_run_dispose (G_OBJECT (priv->fs_session)); + g_object_unref (priv->fs_session); + priv->fs_session = NULL; + } + + if (priv->fs_participant) + { + g_object_unref (priv->fs_participant); + priv->fs_participant = NULL; + } + + if (priv->fs_conference) + { + g_object_unref (priv->fs_conference); + priv->fs_conference = NULL; + } + + if (priv->local_preferences) + { + fs_codec_list_destroy (priv->local_preferences); + priv->local_preferences = NULL; + } + + if (priv->last_sent_codecs) + { + fs_codec_list_destroy (priv->last_sent_codecs); + priv->last_sent_codecs = NULL; + } + + if (priv->feedback_messages) + g_boxed_free (TP_HASH_TYPE_RTCP_FEEDBACK_MESSAGE_MAP, + priv->feedback_messages); + priv->feedback_messages = NULL; + + if (priv->header_extensions) + g_boxed_free (TP_ARRAY_TYPE_RTP_HEADER_EXTENSIONS_LIST, + priv->header_extensions); + priv->header_extensions = NULL; + + while ((data = g_queue_pop_head (&priv->events_to_send))) + g_slice_free (struct DtmfEvent, data); + + fs_candidate_list_destroy (priv->local_candidates); + priv->local_candidates = NULL; + + if (G_OBJECT_CLASS (tf_stream_parent_class)->dispose) + G_OBJECT_CLASS (tf_stream_parent_class)->dispose (object); +} + +static void +tf_stream_class_init (TfStreamClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (TfStreamPrivate)); + + object_class->set_property = tf_stream_set_property; + object_class->get_property = tf_stream_get_property; + object_class->constructor = tf_stream_constructor; + object_class->dispose = tf_stream_dispose; + + g_object_class_install_property (object_class, PROP_CHANNEL, + g_param_spec_object ("channel", + "Telepathy channel", + "The TfChannel this stream is in", + TF_TYPE_CHANNEL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_FARSTREAM_CONFERENCE, + g_param_spec_object ("farstream-conference", + "Farstream conference", + "The Farstream conference this stream will " + "create streams within.", + FS_TYPE_CONFERENCE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_FARSTREAM_PARTICIPANT, + g_param_spec_object ("farstream-participant", + "Farstream participant", + "The Farstream participant this stream will " + "create streams for.", + FS_TYPE_PARTICIPANT, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_FARSTREAM_SESSION, + g_param_spec_object ("farstream-session", + "Farstream session", + "The Farstream session", + FS_TYPE_SESSION, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_FARSTREAM_STREAM, + g_param_spec_object ("farstream-stream", + "Farstream stream", + "The Farstream stream", + FS_TYPE_STREAM, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_PROXY, + g_param_spec_object ("proxy", + "TpMediaStreamHandler proxy", + "The stream handler proxy which this stream interacts with.", + TP_TYPE_MEDIA_STREAM_HANDLER, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_STREAM_ID, + g_param_spec_uint ("stream-id", + "stream ID", + "A number identifying this stream within " + "its channel.", + 0, G_MAXUINT, 0, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_MEDIA_TYPE, + g_param_spec_uint ("media-type", + "stream media type", + "The Telepathy stream media type" + " (as a TpStreamMediaType)", + TP_MEDIA_STREAM_TYPE_AUDIO, + TP_MEDIA_STREAM_TYPE_VIDEO, + TP_MEDIA_STREAM_TYPE_AUDIO, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_DIRECTION, + g_param_spec_uint ("direction", + "stream direction", + "The Telepathy stream direction" + " (a TpMediaStreamDirection)", + TP_MEDIA_STREAM_DIRECTION_NONE, + TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL, + TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_NAT_PROPERTIES, + g_param_spec_pointer ("nat-properties", + "NAT properties", + "A pointer to a " + "TfNatProperties structure " + "detailing which NAT traversal method " + "and parameters to use for this stream", + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_WRITABLE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_SINK_PAD, + g_param_spec_object ("sink-pad", + "Sink pad for this stream", + "This sink pad that data has to be sent", + GST_TYPE_PAD, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_LOCAL_PREFERENCES, + g_param_spec_boxed ("codec-preferences", + "Local codec preferences", + "A GList of FsCodec representing preferences" + " to be passed to the" + " fs_session_set_local_preferences()" + " function", + FS_TYPE_CODEC_LIST, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_TOS, + g_param_spec_uint ("tos", + "IP Type of Service", + "The IP Type of Service to set on sent packets", + 0, 255, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_RESOURCES, + g_param_spec_uint ("resources", + "Resources held by the stream", + "The resources held by a TpMediaStreamDirection", + TP_MEDIA_STREAM_DIRECTION_NONE, + TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL, + TP_MEDIA_STREAM_DIRECTION_NONE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * TfStream::closed: + * @stream: the stream that has been closed + * + * This signal is emitted when the Close() signal is received from the + * connection manager. + */ + + signals[CLOSED] = + g_signal_new ("closed", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * TfStream::error: + * @stream: the stream that has been errored + * + * This signal is emitted when there is an error on this stream + */ + + signals[ERROR_SIGNAL] = + g_signal_new ("error", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * TfStream::request-resource: + * @stream: the stream requesting the resources + * @direction: The direction for which this resource is requested + * (as a #TpMediaDirection + * + * This signal is emitted when the connection manager ask to send or receive + * media. For example, this can be used allocated an X window or open a + * camera. The resouces can later be freed on #TfStream::free-resource + * + * Returns: %TRUE if the resources requested could be allocated or %FALSE + * otherwise + */ + + signals[REQUEST_RESOURCE] = + g_signal_new ("request-resource", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + g_signal_accumulator_true_handled, NULL, + _tf_marshal_BOOLEAN__UINT, + G_TYPE_BOOLEAN, 1, G_TYPE_UINT); + + /** + * TfStream::free-resource: + * @stream: the stream for which resources can be freed + * @direction: The direction for which this resource is freed + * (as a #TpMediaDirection + * + * Emitted when the stream no longer needs a resource allocated + * from #TfStream::request-resource and it can be freed. + */ + + signals[FREE_RESOURCE] = + g_signal_new ("free-resource", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + + /** + * TfStream::src-pad-added: + * @stream: the stream which has a new pad + * @pad: The new src pad + * @codec: the codec for which data is coming out + * + * This is emitted when a new src pad comes out. The user must connect + * this pad to his pipeline. + */ + + signals[SRC_PAD_ADDED] = + g_signal_new ("src-pad-added", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _tf_marshal_VOID__OBJECT_BOXED, + G_TYPE_NONE, 2, GST_TYPE_PAD, FS_TYPE_CODEC); + + + /** + * TfStream::restart-source: + * @stream: the stream + * + * This is emitted when there is a caps change and the source should be + * restarted to take this into account. + */ + + signals[RESTART_SOURCE] = + g_signal_new ("restart-source", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _tf_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +get_all_properties_cb (TpProxy *proxy, + GHashTable *out_Properties, + const GError *dbus_error, + gpointer user_data, + GObject *weak_object) +{ + TfStream *stream = TF_STREAM (weak_object); + GError *myerror = NULL; + gchar *transmitter; + guint n_args = 0; + GList *preferred_local_candidates = NULL; + GParameter params[MAX_STREAM_TRANS_PARAMS]; + const gchar *nat_traversal = NULL; + GPtrArray *stun_servers = NULL; + gboolean got_stun = FALSE; + GPtrArray *dbus_relay_info = NULL; + gboolean created_locally = TRUE; + gboolean valid = FALSE; + guint i; + gboolean do_controlling = FALSE; + GList *rtp_header_extensions; + gboolean res = FALSE; + + if (dbus_error && + !(dbus_error->domain == DBUS_GERROR && + dbus_error->code == DBUS_GERROR_UNKNOWN_METHOD)) + { + tf_stream_error (stream, TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR, + dbus_error->message); + return; + } + + tp_cli_media_stream_handler_connect_to_add_remote_candidate + (stream->priv->stream_handler_proxy, add_remote_candidate, NULL, NULL, + (GObject*) stream, NULL); + tp_cli_media_stream_handler_connect_to_remove_remote_candidate + (stream->priv->stream_handler_proxy, remove_remote_candidate, NULL, NULL, + (GObject*) stream, NULL); + tp_cli_media_stream_handler_connect_to_set_active_candidate_pair + (stream->priv->stream_handler_proxy, set_active_candidate_pair, NULL, + NULL, (GObject*) stream, NULL); + tp_cli_media_stream_handler_connect_to_set_remote_candidate_list + (stream->priv->stream_handler_proxy, set_remote_candidate_list, NULL, + NULL, (GObject*) stream, NULL); + tp_cli_media_stream_handler_connect_to_set_remote_codecs + (stream->priv->stream_handler_proxy, set_remote_codecs, NULL, NULL, + (GObject*) stream, NULL); + tp_cli_media_stream_handler_connect_to_set_stream_playing + (stream->priv->stream_handler_proxy, set_stream_playing, NULL, NULL, + (GObject*) stream, NULL); + tp_cli_media_stream_handler_connect_to_set_stream_sending + (stream->priv->stream_handler_proxy, set_stream_sending, NULL, NULL, + (GObject*) stream, NULL); + tp_cli_media_stream_handler_connect_to_set_stream_held + (stream->priv->stream_handler_proxy, set_stream_held, NULL, NULL, + (GObject*) stream, NULL); + tp_cli_media_stream_handler_connect_to_start_telephony_event + (stream->priv->stream_handler_proxy, start_telephony_event, NULL, NULL, + (GObject*) stream, NULL); + tp_cli_media_stream_handler_connect_to_start_named_telephony_event + (stream->priv->stream_handler_proxy, start_named_telephony_event, NULL, + NULL, (GObject*) stream, NULL); + tp_cli_media_stream_handler_connect_to_start_sound_telephony_event + (stream->priv->stream_handler_proxy, start_sound_telephony_event, NULL, + NULL, (GObject*) stream, NULL); + tp_cli_media_stream_handler_connect_to_stop_telephony_event + (stream->priv->stream_handler_proxy, stop_telephony_event, NULL, NULL, + (GObject*) stream, NULL); + tp_cli_media_stream_handler_connect_to_close + (stream->priv->stream_handler_proxy, stream_close, NULL, NULL, + (GObject*) stream, NULL); + tp_cli_media_stream_handler_connect_to_set_remote_feedback_messages + (stream->priv->stream_handler_proxy, set_remote_feedback_messages, NULL, + NULL, (GObject*) stream, NULL); + tp_cli_media_stream_handler_connect_to_set_remote_header_extensions + (stream->priv->stream_handler_proxy, set_remote_header_extensions, NULL, + NULL, (GObject*) stream, NULL); + + memset (params, 0, sizeof(GParameter) * MAX_STREAM_TRANS_PARAMS); + + if (out_Properties) + nat_traversal = tp_asv_get_string (out_Properties, "NATTraversal"); + if (!nat_traversal && stream->priv->nat_props) + nat_traversal = stream->priv->nat_props->nat_traversal; + + if (!nat_traversal || !strcmp (nat_traversal, "gtalk-p2p")) + { + transmitter = "nice"; + do_controlling = TRUE; + + params[n_args].name = "compatibility-mode"; + g_value_init (¶ms[n_args].value, G_TYPE_UINT); + g_value_set_uint (¶ms[n_args].value, 1); + n_args++; + } + else if (!strcmp (nat_traversal, "ice-udp")) + { + transmitter = "nice"; + do_controlling = TRUE; + } + else if (!strcmp (nat_traversal, "wlm-8.5")) + { + transmitter = "nice"; + do_controlling = TRUE; + + params[n_args].name = "compatibility-mode"; + g_value_init (¶ms[n_args].value, G_TYPE_UINT); + g_value_set_uint (¶ms[n_args].value, 2); + n_args++; + } + else if (!strcmp (nat_traversal, "wlm-2009")) + { + transmitter = "nice"; + do_controlling = TRUE; + + params[n_args].name = "compatibility-mode"; + g_value_init (¶ms[n_args].value, G_TYPE_UINT); + g_value_set_uint (¶ms[n_args].value, 3); + n_args++; + } + else if (!strcmp (nat_traversal, "shm")) + { + transmitter = "shm"; + } + else + { + transmitter = "rawudp"; + + if (stream->priv->media_type == TP_MEDIA_STREAM_TYPE_AUDIO) + preferred_local_candidates = g_list_prepend (NULL, + fs_candidate_new (NULL, FS_COMPONENT_RTP, FS_CANDIDATE_TYPE_HOST, + FS_NETWORK_PROTOCOL_UDP, NULL, 7078)); + else if (stream->priv->media_type == TP_MEDIA_STREAM_TYPE_VIDEO) + preferred_local_candidates = g_list_prepend (NULL, + fs_candidate_new (NULL, FS_COMPONENT_RTP, FS_CANDIDATE_TYPE_HOST, + FS_NETWORK_PROTOCOL_UDP, NULL, 9078)); + } + + /* FIXME: use correct macro when available */ + if (out_Properties) + stun_servers = tp_asv_get_boxed (out_Properties, "STUNServers", + tp_type_dbus_array_su ()); + + if (stun_servers && stun_servers->len) + { + GValueArray *stun_server = g_ptr_array_index (stun_servers, 0); + + if (stun_server && stun_server->n_values == 2) + { + GValue *stun_ip = g_value_array_get_nth (stun_server, 0); + GValue *stun_port = g_value_array_get_nth (stun_server, 1); + + DEBUG (stream, "Adding STUN server %s:%u", + g_value_get_string (stun_ip), + g_value_get_uint (stun_port)); + + params[n_args].name = "stun-ip"; + g_value_init (¶ms[n_args].value, G_TYPE_STRING); + g_value_copy (stun_ip, ¶ms[n_args].value); + n_args++; + + params[n_args].name = "stun-port"; + g_value_init (¶ms[n_args].value, G_TYPE_UINT); + g_value_copy (stun_port, ¶ms[n_args].value); + n_args++; + + got_stun = TRUE; + } + } + + if (!got_stun && stream->priv->nat_props && + stream->priv->nat_props->stun_server && + stream->priv->nat_props->stun_port) + { + DEBUG (stream, "Adding STUN server (old API) %s:%u", + stream->priv->nat_props->stun_server, + stream->priv->nat_props->stun_port); + params[n_args].name = "stun-ip"; + g_value_init (¶ms[n_args].value, G_TYPE_STRING); + g_value_set_string (¶ms[n_args].value, + stream->priv->nat_props->stun_server); + n_args++; + + params[n_args].name = "stun-port"; + g_value_init (¶ms[n_args].value, G_TYPE_UINT); + g_value_set_uint (¶ms[n_args].value, + stream->priv->nat_props->stun_port); + n_args++; + + got_stun = TRUE; + } + + if (got_stun) + { + gchar *conn_timeout_str = NULL; + + conn_timeout_str = getenv ("FS_CONN_TIMEOUT"); + if (conn_timeout_str) + { + gint conn_timeout = strtol (conn_timeout_str, NULL, 10); + + params[n_args].name = "stun-timeout"; + g_value_init (¶ms[n_args].value, G_TYPE_UINT); + g_value_set_uint (¶ms[n_args].value, conn_timeout); + n_args++; + } + } + + if (out_Properties) + dbus_relay_info = tp_asv_get_boxed (out_Properties, "RelayInfo", + TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST); + + if (dbus_relay_info && dbus_relay_info->len) + { + GValueArray *fs_relay_info = g_value_array_new (0); + GValue val = {0}; + g_value_init (&val, GST_TYPE_STRUCTURE); + + for (i = 0; i < dbus_relay_info->len; i++) + { + GHashTable *one_relay = g_ptr_array_index(dbus_relay_info, i); + const gchar *type; + const gchar *ip; + guint32 port; + const gchar *username; + const gchar *password; + guint component; + GstStructure *s; + + ip = tp_asv_get_string (one_relay, "ip"); + port = tp_asv_get_uint32 (one_relay, "port", NULL); + type = tp_asv_get_string (one_relay, "type"); + username = tp_asv_get_string (one_relay, "username"); + password = tp_asv_get_string (one_relay, "password"); + component = tp_asv_get_uint32 (one_relay, "component", NULL); + + if (!ip || !port || !username || !password) + continue; + + s = gst_structure_new ("relay-info", + "ip", G_TYPE_STRING, ip, + "port", G_TYPE_UINT, port, + "username", G_TYPE_STRING, username, + "password", G_TYPE_STRING, password, + NULL); + + if (type) + gst_structure_set (s, "relay-type", G_TYPE_STRING, type, NULL); + + if (component) + gst_structure_set (s, "component", G_TYPE_UINT, component, NULL); + + if (!type) + type = "udp"; + + DEBUG (stream, "Adding relay (%s) %s:%u %s:%s %u", + type, ip, port, username, password, component); + + g_value_take_boxed (&val, s); + + g_value_array_append (fs_relay_info, &val); + g_value_reset (&val); + } + + if (fs_relay_info->n_values) + { + params[n_args].name = "relay-info"; + g_value_init (¶ms[n_args].value, G_TYPE_VALUE_ARRAY); + g_value_set_boxed (¶ms[n_args].value, fs_relay_info); + n_args++; + } + + g_value_array_free (fs_relay_info); + } + + if (out_Properties && do_controlling) + { + created_locally = tp_asv_get_boolean (out_Properties, "CreatedLocally", + &valid); + if (valid) + { + params[n_args].name = "controlling-mode"; + g_value_init (¶ms[n_args].value, G_TYPE_BOOLEAN); + g_value_set_boolean (¶ms[n_args].value, created_locally); + n_args++; + } + } + + if (preferred_local_candidates) + { + params[n_args].name = "preferred-local-candidates"; + g_value_init (¶ms[n_args].value, FS_TYPE_CANDIDATE_LIST); + g_value_take_boxed (¶ms[n_args].value, + preferred_local_candidates); + n_args++; + } + + stream->priv->fs_session = fs_conference_new_session ( + stream->priv->fs_conference, + tp_media_type_to_fs (stream->priv->media_type), + &myerror); + + if (!stream->priv->fs_session) + { + tf_stream_error (stream, fserror_to_tperror (myerror), myerror->message); + WARNING (stream, "Error creating session: %s", myerror->message); + g_clear_error (&myerror); + return; + } + + if (stream->priv->tos) + g_object_set (stream->priv->fs_session, "tos", stream->priv->tos, NULL); + + stream->priv->fs_stream = fs_session_new_stream (stream->priv->fs_session, + stream->priv->fs_participant, + FS_DIRECTION_NONE, + &myerror); + + if (stream->priv->fs_stream) + res = fs_stream_set_transmitter (stream->priv->fs_stream, + transmitter, params, n_args, &myerror); + + for (i = 0; i < n_args; i++) + g_value_unset (¶ms[i].value); + + if (!stream->priv->fs_stream) + { + tf_stream_error (stream, fserror_to_tperror (myerror), myerror->message); + WARNING (stream, "Error creating stream: %s", myerror->message); + g_clear_error (&myerror); + return; + } + + if (!res) + { + tf_stream_error (stream, fserror_to_tperror (myerror), myerror->message); + WARNING (stream, "Could not set transmitter for stream: %s", + myerror->message); + g_clear_error (&myerror); + return; + } + + rtp_header_extensions = + fs_utils_get_default_rtp_header_extension_preferences ( + GST_ELEMENT (stream->priv->fs_conference), + tp_media_type_to_fs (stream->priv->media_type)); + + if (rtp_header_extensions) + { + g_object_set (stream->priv->fs_session, + "rtp-header-extension-preferences", rtp_header_extensions, NULL); + fs_rtp_header_extension_list_destroy (rtp_header_extensions); + } + + if (!stream->priv->local_preferences) + stream->priv->local_preferences = fs_utils_get_default_codec_preferences ( + GST_ELEMENT (stream->priv->fs_conference)); + + if (stream->priv->local_preferences) + if (!fs_session_set_codec_preferences (stream->priv->fs_session, + stream->priv->local_preferences, + &myerror)) + { + if (!(myerror->domain == FS_ERROR && + myerror->code == FS_ERROR_NOT_IMPLEMENTED)) + { + tf_stream_error (stream, fserror_to_tperror (myerror), + myerror->message); + WARNING (stream, "Error setting codec preferences: %s", + myerror->message); + g_clear_error (&myerror); + return; + } + g_clear_error (&myerror); + } + + if (g_object_class_find_property ( + G_OBJECT_GET_CLASS (stream->priv->fs_session), + "no-rtcp-timeout")) + g_object_set (stream->priv->fs_session, "no-rtcp-timeout", 0, NULL); + + g_signal_connect_object (stream->priv->fs_stream, "src-pad-added", + G_CALLBACK (cb_fs_stream_src_pad_added), stream, 0); + + stream->priv->send_local_codecs = TRUE; + + stream->priv->new_stream_created_cb (stream, stream->priv->channel); +} + +/* dummy callback handler for async calling calls with no return values */ +static void +async_method_callback (TpMediaStreamHandler *proxy G_GNUC_UNUSED, + const GError *error, + gpointer user_data, + GObject *weak_object) +{ + TfStream *self = TF_STREAM (weak_object); + + if (error != NULL) + { + g_warning ("Error calling %s: %s", (gchar *) user_data, error->message); + g_signal_emit (self, signals[ERROR_SIGNAL], 0); + } +} + + +/* dummy callback handler for async calling calls with no return values + * and whose implementation is optional */ +static void +async_method_callback_optional (TpMediaStreamHandler *proxy G_GNUC_UNUSED, + const GError *error, + gpointer user_data, + GObject *weak_object) +{ + if (error == NULL || + g_error_matches (error, DBUS_GERROR, G_DBUS_ERROR_UNKNOWN_METHOD) || + g_error_matches (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED)) + return; + + async_method_callback (proxy, error, user_data, weak_object); +} + +static void +cb_fs_new_local_candidate (TfStream *self, + FsCandidate *candidate) +{ + DEBUG (self, "called"); + + self->priv->local_candidates = g_list_append (self->priv->local_candidates, + fs_candidate_copy (candidate)); +} + +static void +cb_fs_local_candidates_prepared (TfStream *self) +{ + DEBUG (self, "called"); + + while (self->priv->local_candidates) + { + GPtrArray *transports = g_ptr_array_new (); + FsCandidate *candidate = + g_list_first (self->priv->local_candidates)->data; + gchar *foundation = g_strdup (candidate->foundation); + + while (candidate) + { + GValue transport = { 0, }; + TpMediaStreamBaseProto proto; + TpMediaStreamTransportType type; + gboolean valid = TRUE; + GList *item = NULL; + + g_value_init (&transport, + TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_TRANSPORT); + g_value_take_boxed (&transport, + dbus_g_type_specialized_construct ( + TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_TRANSPORT)); + + proto = fs_network_proto_to_tp (candidate->proto, &valid); + if (valid == FALSE) + return; + type = fs_candidate_type_to_tp (candidate->type, &valid); + if (valid == FALSE) + return; + + DEBUG (self, "ip = '%s port = %u component = %u'", candidate->ip, + candidate->port, candidate->component_id); + + dbus_g_type_struct_set (&transport, + 0, candidate->component_id, + 1, candidate->ip, + 2, candidate->port, + 3, proto, + 4, "RTP", + 5, "AVP", + 6, (double) (candidate->priority / 65536.0), + 7, type, + 8, candidate->username, + 9, candidate->password, + G_MAXUINT); + + g_ptr_array_add (transports, g_value_get_boxed (&transport)); + + self->priv->local_candidates = g_list_remove ( + self->priv->local_candidates, candidate); + + fs_candidate_destroy (candidate); + + for (item = self->priv->local_candidates; + item; + item = g_list_next (item)) + { + FsCandidate *tmpcand = item->data; + if (!strcmp (tmpcand->foundation, foundation)) + break; + } + if (item) + candidate = item->data; + else + candidate = NULL; + } + + tp_cli_media_stream_handler_call_new_native_candidate ( + self->priv->stream_handler_proxy, -1, foundation, transports, + async_method_callback, + "Media.StreamHandler::NewNativeCandidate", + NULL, (GObject *) self); + + g_boxed_free (TP_ARRAY_TYPE_MEDIA_STREAM_HANDLER_TRANSPORT_LIST, + transports); + g_free (foundation); + } + + tp_cli_media_stream_handler_call_native_candidates_prepared ( + self->priv->stream_handler_proxy, -1, async_method_callback, + "Media.StreamHandler::NativeCandidatesPrepared", + NULL, (GObject *) self); +} + + + +/* + * small helper function to help converting a + * telepathy dbus candidate to a list of FsCandidate. + * nothing is copied, so always keep the usage of this within a function + * Free the result with fs_candidate_list_destroy() + */ +static GList * +tp_transports_to_fs (const gchar* foundation, const GPtrArray *transports) +{ + GList *fs_trans_list = NULL; + GValueArray *transport; + FsCandidate *fs_candidate; + guint i; + + for (i=0; i< transports->len; i++) + { + transport = g_ptr_array_index (transports, i); + FsNetworkProtocol proto; + FsCandidateType type; + + g_assert(G_VALUE_HOLDS_UINT (g_value_array_get_nth (transport, 0))); + g_assert(G_VALUE_HOLDS_STRING (g_value_array_get_nth (transport, 1))); + g_assert(G_VALUE_HOLDS_UINT (g_value_array_get_nth (transport, 2))); + g_assert(G_VALUE_HOLDS_UINT (g_value_array_get_nth (transport, 3))); + g_assert(G_VALUE_HOLDS_STRING (g_value_array_get_nth (transport, 4))); + g_assert(G_VALUE_HOLDS_STRING (g_value_array_get_nth (transport, 5))); + g_assert(G_VALUE_HOLDS_DOUBLE (g_value_array_get_nth (transport, 6))); + g_assert(G_VALUE_HOLDS_UINT (g_value_array_get_nth (transport, 7))); + g_assert(G_VALUE_HOLDS_STRING (g_value_array_get_nth (transport, 8))); + g_assert(G_VALUE_HOLDS_STRING (g_value_array_get_nth (transport, 9))); + + switch (g_value_get_uint (g_value_array_get_nth (transport, 7))) + { + case TP_MEDIA_STREAM_TRANSPORT_TYPE_LOCAL: + type = FS_CANDIDATE_TYPE_HOST; + break; + case TP_MEDIA_STREAM_TRANSPORT_TYPE_DERIVED: + type = FS_CANDIDATE_TYPE_SRFLX; + /* or FS_CANDIDATE_TYPE_PRFLX .. the TP spec doesn't differentiate */ + break; + case TP_MEDIA_STREAM_TRANSPORT_TYPE_RELAY: + type = FS_CANDIDATE_TYPE_RELAY; + break; + default: + g_critical ("%s: FarstreamTransportInfo.proto has an invalid value", + G_STRFUNC); + type = FS_CANDIDATE_TYPE_HOST; + } + + switch (g_value_get_uint (g_value_array_get_nth (transport, 3))) + { + case TP_MEDIA_STREAM_BASE_PROTO_UDP: + proto = FS_NETWORK_PROTOCOL_UDP; + break; + case TP_MEDIA_STREAM_BASE_PROTO_TCP: + proto = FS_NETWORK_PROTOCOL_TCP; + break; + default: + g_critical ("%s: FarstreamTransportInfo.proto has an invalid value", + G_STRFUNC); + proto = FS_NETWORK_PROTOCOL_UDP; + } + + fs_candidate = fs_candidate_new (foundation, + g_value_get_uint (g_value_array_get_nth (transport, 0)), /*component*/ + type, proto, + + g_value_get_string (g_value_array_get_nth (transport, 1)), /* ip */ + g_value_get_uint (g_value_array_get_nth (transport, 2))); /* port */ + + fs_candidate->priority = (gint) + (g_value_get_double (g_value_array_get_nth (transport, 6)) * 65536.0); + fs_candidate->username = + g_value_dup_string (g_value_array_get_nth (transport, 8)); + fs_candidate->password = + g_value_dup_string (g_value_array_get_nth (transport, 9)); + + fs_trans_list = g_list_prepend (fs_trans_list, fs_candidate); + } + fs_trans_list = g_list_reverse (fs_trans_list); + + return fs_trans_list; +} + +static TpMediaStreamBaseProto +fs_network_proto_to_tp (FsNetworkProtocol proto, gboolean *valid) +{ + if (valid != NULL) + *valid = TRUE; + + switch (proto) { + case FS_NETWORK_PROTOCOL_UDP: + return TP_MEDIA_STREAM_BASE_PROTO_UDP; + case FS_NETWORK_PROTOCOL_TCP: + return TP_MEDIA_STREAM_BASE_PROTO_TCP; + default: + g_critical ("%s: FarstreamTransportInfo.proto has an invalid value", + G_STRFUNC); + if (valid != NULL) + *valid = FALSE; + g_return_val_if_reached(0); + } +} + +static TpMediaStreamTransportType +fs_candidate_type_to_tp (FsCandidateType type, gboolean *valid) +{ + if (valid != NULL) + *valid = TRUE; + + switch (type) { + case FS_CANDIDATE_TYPE_HOST: + return TP_MEDIA_STREAM_TRANSPORT_TYPE_LOCAL; + case FS_CANDIDATE_TYPE_SRFLX: + case FS_CANDIDATE_TYPE_PRFLX: + return TP_MEDIA_STREAM_TRANSPORT_TYPE_DERIVED; + case FS_CANDIDATE_TYPE_RELAY: + return TP_MEDIA_STREAM_TRANSPORT_TYPE_RELAY; + default: + g_critical ("%s: FarstreamTransportInfo.proto has an invalid value", + G_STRFUNC); + if (valid != NULL) + *valid = FALSE; + g_return_val_if_reached(0); + } +} + +static GValueArray * +fs_candidate_to_tp_array (const FsCandidate *candidate) +{ + GValueArray *transport = NULL; + TpMediaStreamBaseProto proto; + TpMediaStreamTransportType type; + gboolean valid = TRUE; + + proto = fs_network_proto_to_tp (candidate->proto, &valid); + if (valid == FALSE) + return NULL; + type = fs_candidate_type_to_tp (candidate->type, &valid); + if (valid == FALSE) + return NULL; + + transport = tp_value_array_build (10, + G_TYPE_UINT, candidate->component_id, + G_TYPE_STRING, candidate->ip, + G_TYPE_UINT, candidate->port, + G_TYPE_UINT, proto, + G_TYPE_STRING, "RTP", + G_TYPE_STRING, "AVP", + G_TYPE_DOUBLE, (double) (candidate->priority / 65536.0), + G_TYPE_UINT, type, + G_TYPE_STRING, candidate->username, + G_TYPE_STRING, candidate->password, + G_TYPE_INVALID); + + return transport; +} + +/* + * Small helper function to help converting a list of FarstreamCodecs + * to a Telepathy codec list. + */ +static GPtrArray * +fs_codecs_to_tp (TfStream *stream, + const GList *codecs) +{ + GPtrArray *tp_codecs; + const GList *el; + + tp_codecs = g_ptr_array_new (); + + for (el = codecs; el; el = g_list_next (el)) + { + FsCodec *fsc = el->data; + GValue codec = { 0, }; + TpMediaStreamType type; + GHashTable *params; + GList *cur; + + switch (fsc->media_type) { + case FS_MEDIA_TYPE_AUDIO: + type = TP_MEDIA_STREAM_TYPE_AUDIO; + break; + case FS_MEDIA_TYPE_VIDEO: + type = TP_MEDIA_STREAM_TYPE_VIDEO; + break; + default: + g_critical ("%s: FarstreamCodec [%d, %s]'s media_type has an invalid value", + G_STRFUNC, fsc->id, fsc->encoding_name); + return NULL; + } + + /* fill in optional parameters */ + params = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + for (cur = fsc->optional_params; cur != NULL; cur = cur->next) + { + FsCodecParameter *param = (FsCodecParameter *) cur->data; + + g_hash_table_insert (params, g_strdup (param->name), + g_strdup (param->value)); + } + + g_value_init (&codec, TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_CODEC); + g_value_take_boxed (&codec, + dbus_g_type_specialized_construct (TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_CODEC)); + + dbus_g_type_struct_set (&codec, + 0, fsc->id, + 1, fsc->encoding_name, + 2, type, + 3, fsc->clock_rate, + 4, fsc->channels, + 5, params, + G_MAXUINT); + + g_hash_table_destroy (params); + + DEBUG (stream, "adding codec " FS_CODEC_FORMAT, FS_CODEC_ARGS (fsc)); + + g_ptr_array_add (tp_codecs, g_value_get_boxed (&codec)); + } + + return tp_codecs; +} + +static void +add_remote_candidate (TpMediaStreamHandler *proxy G_GNUC_UNUSED, + const gchar *candidate, + const GPtrArray *transports, + gpointer user_data G_GNUC_UNUSED, + GObject *object) +{ + TfStream *self = TF_STREAM (object); + GError *error = NULL; + GList *fscandidates; + + DEBUG (self, "adding remote candidate %s", candidate); + + fscandidates = tp_transports_to_fs (candidate, transports); + + if (!fs_stream_add_remote_candidates (self->priv->fs_stream, + fscandidates, &error)) + tf_stream_error (self, fserror_to_tperror (error), error->message); + + fs_candidate_list_destroy (fscandidates); + g_clear_error (&error); +} + +static void +remove_remote_candidate (TpMediaStreamHandler *proxy G_GNUC_UNUSED, + const gchar *candidate G_GNUC_UNUSED, + gpointer user_data G_GNUC_UNUSED, + GObject *object G_GNUC_UNUSED) +{ + TfStream *self = TF_STREAM (object); + + tf_stream_error (self, TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR, + "RemoveRemoteCandidate is DEPRECATED"); +} + +static void +set_active_candidate_pair (TpMediaStreamHandler *proxy G_GNUC_UNUSED, + const gchar *native_candidate, + const gchar *remote_candidate, + gpointer user_data G_GNUC_UNUSED, + GObject *object) +{ + /* + TfStream *self = TF_STREAM (object); + GError *error = NULL; + + if (!fs_stream_select_candidate_pair (self->priv->fs_stream, + native_candidate, + remote_candidate, + &error)) + { + if (error->domain == FS_ERROR && error->code == FS_ERROR_NOT_IMPLEMENTED) + DEBUG (self, "Called not implemented SetActiveCandidatePair"); + else + tf_stream_error (self, 0, error->message); + } + + g_clear_error (&error); + */ +} + +static void +set_remote_candidate_list (TpMediaStreamHandler *proxy G_GNUC_UNUSED, + const GPtrArray *candidates, + gpointer user_data G_GNUC_UNUSED, + GObject *object) +{ + TfStream *self = TF_STREAM (object); + guint i; + GList *fs_candidates = NULL; + GError *error = NULL; + gboolean ret; + + for (i = 0; i < candidates->len; i++) + { + GPtrArray *transports = NULL; + gchar *foundation; + GValueArray *candidate; + + candidate = g_ptr_array_index (candidates, i); + + g_assert(G_VALUE_HOLDS_STRING (g_value_array_get_nth (candidate,0))); + g_assert(G_VALUE_TYPE (g_value_array_get_nth (candidate, 1)) == + TP_ARRAY_TYPE_MEDIA_STREAM_HANDLER_TRANSPORT_LIST); + + foundation = + (gchar*) g_value_get_string (g_value_array_get_nth (candidate, 0)); + transports = + g_value_get_boxed (g_value_array_get_nth (candidate, 1)); + + fs_candidates = g_list_concat (fs_candidates, + tp_transports_to_fs (foundation, transports)); + } + + ret = fs_stream_add_remote_candidates (self->priv->fs_stream, + fs_candidates, &error); + if (!ret && error && + error->domain == FS_ERROR && error->code == FS_ERROR_NOT_IMPLEMENTED) + { + g_clear_error (&error); + ret = fs_stream_force_remote_candidates (self->priv->fs_stream, + fs_candidates, &error); + } + if (!ret) + tf_stream_error (self, fserror_to_tperror (error), error->message); + + g_clear_error (&error); + fs_candidate_list_destroy (fs_candidates); +} + +static void +fill_fs_params (gpointer key, gpointer value, gpointer user_data) +{ + FsCodec *codec = user_data; + + fs_codec_add_optional_parameter (codec, key, value); +} + +static FsStreamDirection +tpdirection_to_fsdirection (TpMediaStreamDirection dir) +{ + switch (dir) { + case TP_MEDIA_STREAM_DIRECTION_NONE: + return FS_DIRECTION_NONE; + case TP_MEDIA_STREAM_DIRECTION_SEND: + return FS_DIRECTION_SEND; + case TP_MEDIA_STREAM_DIRECTION_RECEIVE: + return FS_DIRECTION_RECV; + case TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL: + return FS_DIRECTION_BOTH; + default: + g_assert_not_reached (); + } +} + +static void +set_remote_codecs (TpMediaStreamHandler *proxy G_GNUC_UNUSED, + const GPtrArray *codecs, + gpointer user_data G_GNUC_UNUSED, + GObject *object) +{ + TfStream *self = TF_STREAM (object); + GValueArray *codec; + GHashTable *params = NULL; + GList *fs_remote_codecs = NULL; + guint i; + GError *error = NULL; + + DEBUG (self, "called"); + + for (i = 0; i < codecs->len; i++) + { + FsCodec *fs_codec = NULL; + + + codec = g_ptr_array_index (codecs, i); + + g_assert(G_VALUE_HOLDS_UINT (g_value_array_get_nth (codec,0))); + g_assert(G_VALUE_HOLDS_STRING (g_value_array_get_nth (codec,1))); + g_assert(G_VALUE_HOLDS_UINT (g_value_array_get_nth (codec,2))); + g_assert(G_VALUE_HOLDS_UINT (g_value_array_get_nth (codec,3))); + g_assert(G_VALUE_HOLDS_UINT (g_value_array_get_nth (codec,4))); + g_assert(G_VALUE_TYPE (g_value_array_get_nth (codec, 5)) == + DBUS_TYPE_G_STRING_STRING_HASHTABLE); + + + fs_codec = fs_codec_new ( + g_value_get_uint (g_value_array_get_nth (codec, 0)), /* id */ + g_value_get_string (g_value_array_get_nth (codec, 1)), /* encoding_name */ + g_value_get_uint (g_value_array_get_nth (codec, 2)), /* media_type */ + g_value_get_uint (g_value_array_get_nth (codec, 3))); /* clock_rate */ + + fs_codec->channels = + g_value_get_uint (g_value_array_get_nth (codec, 4)); + + params = g_value_get_boxed (g_value_array_get_nth (codec, 5)); + g_hash_table_foreach (params, fill_fs_params, fs_codec); + + if (self->priv->feedback_messages) + { + GValueArray *message_props; + + message_props = g_hash_table_lookup (self->priv->feedback_messages, + GUINT_TO_POINTER (fs_codec->id)); + + if (message_props) + { + GValue *val; + GPtrArray *messages; + guint j; + + g_assert (G_VALUE_HOLDS_UINT ( + g_value_array_get_nth (message_props, 0))); + g_assert (G_VALUE_TYPE ( + g_value_array_get_nth (message_props, 1)) == + TP_ARRAY_TYPE_RTCP_FEEDBACK_MESSAGE_LIST); + + val = g_value_array_get_nth (message_props, 0); + fs_codec->minimum_reporting_interval = + g_value_get_uint (val); + + val = g_value_array_get_nth (message_props, 1); + messages = g_value_get_boxed (val); + + for (j = 0; j < messages->len; j++) + { + GValueArray *msg = g_ptr_array_index (messages, j); + + g_assert (G_VALUE_HOLDS_STRING ( + g_value_array_get_nth (msg, 0))); + g_assert (G_VALUE_HOLDS_STRING ( + g_value_array_get_nth (msg, 1))); + g_assert (G_VALUE_HOLDS_STRING ( + g_value_array_get_nth (msg, 2))); + + fs_codec_add_feedback_parameter (fs_codec, + g_value_get_string (g_value_array_get_nth (msg, 0)), + g_value_get_string (g_value_array_get_nth (msg, 1)), + g_value_get_string (g_value_array_get_nth (msg, 2))); + } + } + } + + + DEBUG (self, "adding remote codec %s [%d]", + fs_codec->encoding_name, fs_codec->id); + + fs_remote_codecs = g_list_prepend (fs_remote_codecs, fs_codec); + } + fs_remote_codecs = g_list_reverse (fs_remote_codecs); + + if (self->priv->feedback_messages) + { + g_boxed_free (TP_HASH_TYPE_RTCP_FEEDBACK_MESSAGE_MAP, + self->priv->feedback_messages); + self->priv->feedback_messages = NULL; + } + + + if (self->priv->header_extensions) + { + if (g_object_class_find_property ( + G_OBJECT_GET_CLASS (self->priv->fs_stream), + "rtp-header-extensions")) + { + GList *hdrexts = NULL; + + for (i = 0; i < self->priv->header_extensions->len; i++) + { + GValueArray *extension = + g_ptr_array_index (self->priv->header_extensions, i); + FsRtpHeaderExtension *hdrext; + + g_assert (extension->n_values >= 3); + g_assert (G_VALUE_HOLDS_UINT ( + g_value_array_get_nth (extension, 0))); + g_assert (G_VALUE_HOLDS_UINT ( + g_value_array_get_nth (extension, 1))); + g_assert (G_VALUE_HOLDS_STRING ( + g_value_array_get_nth (extension, 2))); + + hdrext = fs_rtp_header_extension_new ( + g_value_get_uint (g_value_array_get_nth (extension, 0)), + tpdirection_to_fsdirection ( + g_value_get_uint (g_value_array_get_nth (extension, 1))), + g_value_get_string (g_value_array_get_nth (extension, 2))); + + hdrexts = g_list_append (hdrexts, hdrext); + } + + g_object_set (self->priv->fs_stream, "rtp-header-extensions", + hdrexts, NULL); + + fs_rtp_header_extension_list_destroy (hdrexts); + } + g_boxed_free (TP_ARRAY_TYPE_RTP_HEADER_EXTENSIONS_LIST, + self->priv->header_extensions); + self->priv->header_extensions = NULL; + } + + if (!fs_stream_set_remote_codecs (self->priv->fs_stream, fs_remote_codecs, + &error)) { + /* + * Call the error method with the proper thing here + */ + g_prefix_error (&error, "Codec negotiation failed: "); + tf_stream_error (self, fserror_to_tperror (error), error->message); + g_clear_error (&error); + fs_codec_list_destroy (fs_remote_codecs); + return; + } + + fs_codec_list_destroy (fs_remote_codecs); + + self->priv->send_supported_codecs = TRUE; + _tf_stream_try_sending_codecs (self); +} + +static void +set_stream_playing (TpMediaStreamHandler *proxy G_GNUC_UNUSED, + gboolean play, + gpointer user_data G_GNUC_UNUSED, + GObject *object) +{ + TfStream *self = TF_STREAM (object); + FsStreamDirection current_direction; + gboolean playing; + + g_assert (self->priv->fs_stream != NULL); + + DEBUG (self, "%d", play); + + g_object_get (self->priv->fs_stream, "direction", ¤t_direction, NULL); + + playing = (current_direction & FS_DIRECTION_RECV) != 0; + + /* We're already in the right state */ + if (play == playing) + return; + + if (play) + { + if (!self->priv->held) { + if (tf_stream_request_resource (self, + TP_MEDIA_STREAM_DIRECTION_RECEIVE)) + { + g_object_set (self->priv->fs_stream, + "direction", current_direction | FS_DIRECTION_RECV, + NULL); + } + else + { + tf_stream_error (self, TP_MEDIA_STREAM_ERROR_MEDIA_ERROR, + "Resource Unavailable"); + } + } + self->priv->desired_direction |= FS_DIRECTION_RECV; + } + else + { + if (!self->priv->held) { + tf_stream_free_resource (self, + TP_MEDIA_STREAM_DIRECTION_RECEIVE); + + g_object_set (self->priv->fs_stream, + "direction", current_direction & ~(FS_DIRECTION_RECV), + NULL); + } + self->priv->desired_direction &= ~(FS_DIRECTION_RECV); + } +} + +static void +set_stream_sending (TpMediaStreamHandler *proxy G_GNUC_UNUSED, + gboolean send, + gpointer user_data G_GNUC_UNUSED, + GObject *object) +{ + TfStream *self = TF_STREAM (object); + FsStreamDirection current_direction; + gboolean sending; + + g_assert (self->priv->fs_stream != NULL); + + DEBUG (self, "%d", send); + + g_object_get (self->priv->fs_stream, "direction", ¤t_direction, NULL); + + + sending = (current_direction & FS_DIRECTION_SEND) != 0; + + /* We're already in the right state */ + if (send == sending) + return; + + if (send) + { + if (!self->priv->held) { + if (tf_stream_request_resource (self, + TP_MEDIA_STREAM_DIRECTION_SEND)) + { + g_object_set (self->priv->fs_stream, + "direction", current_direction | FS_DIRECTION_SEND, + NULL); + } + else + { + tf_stream_error (self, TP_MEDIA_STREAM_ERROR_MEDIA_ERROR, + "Resource Unavailable"); + } + } + self->priv->desired_direction |= FS_DIRECTION_SEND; + } + else + { + g_object_set (self->priv->fs_stream, + "direction", current_direction & ~(FS_DIRECTION_SEND), + NULL); + + tf_stream_free_resource (self, FS_DIRECTION_SEND); + + self->priv->desired_direction &= ~(FS_DIRECTION_SEND); + } +} + +static gboolean +tf_stream_request_resource (TfStream *self, + TpMediaStreamDirection dir) +{ + gboolean resource_available; + GValue instance_and_arg[2]; + GValue resource_avail_val = {0,}; + + if ((self->priv->has_resource & dir) == dir) + return TRUE; + + memset (instance_and_arg, 0, sizeof(GValue) * 2); + + g_value_init (&resource_avail_val, G_TYPE_BOOLEAN); + g_value_set_boolean (&resource_avail_val, TRUE); + + g_value_init (&instance_and_arg[0], TF_TYPE_STREAM); + g_value_set_object (&instance_and_arg[0], self); + g_value_init (&instance_and_arg[1], G_TYPE_UINT); + g_value_set_uint (&instance_and_arg[1], dir & ~self->priv->has_resource); + + DEBUG (self, "Requesting resource for direction %d", dir); + + g_signal_emitv (instance_and_arg, signals[REQUEST_RESOURCE], 0, + &resource_avail_val); + resource_available = g_value_get_boolean (&resource_avail_val); + + g_value_unset (&instance_and_arg[0]); + g_value_unset (&instance_and_arg[1]); + g_value_unset (&resource_avail_val); + + DEBUG (self, "Requesting resource for direction %d returned %d", dir, + resource_available); + + /* Make sure we have access to the resource */ + if (resource_available) + { + self->priv->has_resource |= dir; + return TRUE; + } + else + { + return FALSE; + } +} + +static void +tf_stream_free_resource (TfStream *self, + TpMediaStreamDirection dir) +{ + if ((self->priv->has_resource & dir) == 0) + return; + + g_signal_emit (self, signals[FREE_RESOURCE], 0, + self->priv->has_resource & dir); + self->priv->has_resource &= ~dir; +} + +static void +set_stream_held (TpMediaStreamHandler *proxy G_GNUC_UNUSED, + gboolean held, + gpointer user_data G_GNUC_UNUSED, + GObject *object) +{ + TfStream *self = TF_STREAM (object); + + if (held == self->priv->held) + return; + + DEBUG (self, "Holding : %d", held); + + if (held) + { + g_object_set (self->priv->fs_stream, + "direction", FS_DIRECTION_NONE, + NULL); + + tf_stream_free_resource (self, + TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL); + /* Send success message */ + if (self->priv->stream_handler_proxy) + { + tp_cli_media_stream_handler_call_hold_state ( + self->priv->stream_handler_proxy, -1, TRUE, + async_method_callback, "Media.StreamHandler::HoldState TRUE", + NULL, (GObject *) self); + } + self->priv->held = TRUE; + } + else + { + FsStreamDirection desired_direction = self->priv->desired_direction; + + if (tf_stream_request_resource (self, desired_direction)) + { + g_object_set (self->priv->fs_stream, + "direction", self->priv->desired_direction, + NULL); + tp_cli_media_stream_handler_call_hold_state ( + self->priv->stream_handler_proxy, -1, FALSE, + async_method_callback, "Media.StreamHandler::HoldState FALSE", + NULL, (GObject *) self); + + self->priv->held = FALSE; + } + else + { + tf_stream_error (self, TP_MEDIA_STREAM_ERROR_MEDIA_ERROR, + "Error unholding stream"); + } + } +} + +static void +start_telephony_event (TpMediaStreamHandler *proxy G_GNUC_UNUSED, + guchar event, + gpointer user_data G_GNUC_UNUSED, + GObject *object) +{ + TfStream *self = TF_STREAM (object); + + g_assert (self->priv->fs_session != NULL); + + DEBUG (self, "called with event %u", event); + + if (self->priv->sending_telephony_event != -1) + { + WARNING (self, "start new telephony event without stopping the" + " previous one first"); + if (!fs_session_stop_telephony_event (self->priv->fs_session, + self->priv->sending_telephony_event)) + WARNING (self, "stopping event failed"); + } + + /* this week, volume is 8, for the sake of argument... */ + + if (!fs_session_start_telephony_event (self->priv->fs_session, event, 8, + FS_DTMF_METHOD_AUTO)) + WARNING (self, "sending event %u failed", event); + self->priv->sending_telephony_event = FS_DTMF_METHOD_AUTO; +} + +static gboolean +check_codecs_for_telephone_event (TfStream *self, GList **codecs, + FsCodec *send_codec, guint codecid) +{ + GList *item = NULL; + gboolean found = FALSE; + GError *error = NULL; + + again: + + for (item = *codecs; item; item = item->next) + { + FsCodec *codec = item->data; + + if (!g_ascii_strcasecmp (codec->encoding_name, "telephone-event") && + send_codec->clock_rate == codec->clock_rate) + { + if (found) + { + *codecs = g_list_delete_link (*codecs, item); + goto again; + } + else if (codecid == (guint) codec->id) + { + return TRUE; } + else + { + codec->id = codecid; + } + } + } + + if (!found) + { + FsCodec *codec = fs_codec_new (codecid, "telephone-event", + FS_MEDIA_TYPE_AUDIO, send_codec->clock_rate); + + *codecs = g_list_append (*codecs, codec); + } + + if (!fs_stream_set_remote_codecs (self->priv->fs_stream, *codecs, &error)) + { + /* + * Call the error method with the proper thing here + */ + g_prefix_error (&error, "Codec negotiation failed for DTMF: "); + tf_stream_error (self, fserror_to_tperror (error), error->message); + g_clear_error (&error); + } + + return FALSE; +} + +static void +start_named_telephony_event (TpMediaStreamHandler *proxy, + guchar event, + guint codecid, + gpointer user_data, + GObject *object) +{ + TfStream *self = TF_STREAM (object); + FsCodec *send_codec = NULL; + GList *codecs = NULL; + struct DtmfEvent *dtmfevent; + + g_object_get (self->priv->fs_session, + "current-send-codec", &send_codec, + "codecs", &codecs, + NULL); + + if (send_codec == NULL) + goto out; + + if (check_codecs_for_telephone_event (self, &codecs, send_codec, codecid)) + { + if (self->priv->sending_telephony_event != -1) + { + WARNING (self, "start new telephony event without stopping the" + " previous one first"); + if (!fs_session_stop_telephony_event (self->priv->fs_session, + self->priv->sending_telephony_event)) + WARNING (self, "stopping event failed"); + } + + + DEBUG (self, "Sending named telephony event %d with pt %d", + event, codecid); + if (!fs_session_start_telephony_event (self->priv->fs_session, + event, 8, FS_DTMF_METHOD_RTP_RFC4733)) + WARNING (self, "sending event %u failed", event); + self->priv->sending_telephony_event = FS_DTMF_METHOD_RTP_RFC4733; + } + else + { + DEBUG (self, "Queing named telephony event %d with pt %d", + event, codecid); + dtmfevent = g_slice_new (struct DtmfEvent); + dtmfevent->codec_id = codecid; + dtmfevent->event_id = event; + g_queue_push_tail (&self->priv->events_to_send, dtmfevent); + } + +out: + + fs_codec_destroy (send_codec); + fs_codec_list_destroy (codecs); +} + +static void +start_sound_telephony_event (TpMediaStreamHandler *proxy, guchar event, + gpointer user_data, GObject *object) +{ + TfStream *self = TF_STREAM (object); + + g_assert (self->priv->fs_session != NULL); + + DEBUG (self, "called with event %u", event); + + if (self->priv->sending_telephony_event != -1) + { + WARNING (self, "start new telephony event without stopping the" + " previous one first"); + if (!fs_session_stop_telephony_event (self->priv->fs_session, + self->priv->sending_telephony_event)) + WARNING (self, "stopping event failed"); + } + + /* this week, volume is 8, for the sake of argument... */ + + if (!fs_session_start_telephony_event (self->priv->fs_session, event, 8, + FS_DTMF_METHOD_SOUND)) + WARNING (self, "sending sound event %u failed", event); + self->priv->sending_telephony_event = FS_DTMF_METHOD_SOUND; +} + + +static void +stop_telephony_event (TpMediaStreamHandler *proxy G_GNUC_UNUSED, + gpointer user_data G_GNUC_UNUSED, + GObject *object) +{ + TfStream *self = TF_STREAM (object); + + g_assert (self->priv->fs_session != NULL); + + DEBUG (self, "called"); + + if (self->priv->sending_telephony_event == -1) + WARNING (self, "Trying to stop telephony event without having started" + " one"); + self->priv->sending_telephony_event = -1; + + if (!fs_session_stop_telephony_event (self->priv->fs_session, + FS_DTMF_METHOD_AUTO)) + WARNING (self, "stopping event failed"); +} + + +static void +tf_stream_shutdown (TfStream *self) +{ + if (self->priv->fs_stream) + g_object_set (self->priv->fs_stream, + "direction", FS_DIRECTION_NONE, + NULL); + tf_stream_free_resource (self, + TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL); + + g_signal_emit (self, signals[CLOSED], 0); +} + + +static void +stream_close (TpMediaStreamHandler *proxy G_GNUC_UNUSED, + gpointer user_data G_GNUC_UNUSED, + GObject *object) +{ + TfStream *self = TF_STREAM (object); + + DEBUG (self, "close requested by connection manager"); + + tf_stream_shutdown (self); +} + + +static void +set_remote_feedback_messages (TpMediaStreamHandler *proxy, + GHashTable *messages, gpointer user_data, GObject *object) +{ + TfStream *self = TF_STREAM (object); + + if (self->priv->feedback_messages) + g_boxed_free (TP_HASH_TYPE_RTCP_FEEDBACK_MESSAGE_MAP, + self->priv->feedback_messages); + + self->priv->feedback_messages = + g_boxed_copy (TP_HASH_TYPE_RTCP_FEEDBACK_MESSAGE_MAP, messages); +} + + +static void +set_remote_header_extensions (TpMediaStreamHandler *proxy, + const GPtrArray *header_extensions, gpointer user_data, GObject *object) +{ + TfStream *self = TF_STREAM (object); + + if (self->priv->header_extensions) + g_boxed_free (TP_ARRAY_TYPE_RTP_HEADER_EXTENSIONS_LIST, + self->priv->header_extensions); + + self->priv->header_extensions = + g_boxed_copy (TP_ARRAY_TYPE_RTP_HEADER_EXTENSIONS_LIST, + header_extensions); +} + + +static void +cb_fs_recv_codecs_changed (TfStream *self, + GList *codecs) +{ + guint id; + GList *item; + + for (item = codecs; + item; + item = g_list_next (item)) + { + gchar *str = fs_codec_to_string (item->data); + + DEBUG (self, "receiving codec: %s", str); + g_free (str); + } + + id = ((FsCodec*)codecs->data)->id; + + tp_cli_media_stream_handler_call_codec_choice + (self->priv->stream_handler_proxy, -1, id, + async_method_callback_optional, + "Media.StreamHandler::CodecChoice", NULL, (GObject *) self); +} + +static void +cb_fs_new_active_candidate_pair (TfStream *self, + FsCandidate *local_candidate, + FsCandidate *remote_candidate) +{ + GValueArray *local_transport = NULL; + GValueArray *remote_transport = NULL; + + DEBUG (self, "called: c:%d local: %s %s:%u remote: %s %s:%u", + local_candidate->component_id, + local_candidate->foundation, local_candidate->ip, local_candidate->port, + remote_candidate->foundation, remote_candidate->ip, + remote_candidate->port); + + local_transport = fs_candidate_to_tp_array (local_candidate); + if (!local_transport) + return; + + remote_transport = fs_candidate_to_tp_array (remote_candidate); + if (!remote_transport) + { + g_value_array_free (local_transport); + return; + } + + tp_cli_media_stream_handler_call_new_active_transport_pair ( + self->priv->stream_handler_proxy, -1, local_candidate->foundation, + local_transport, remote_candidate->foundation, remote_transport, + async_method_callback_optional, + "Media.StreamHandler::NewActiveTransportPair", + NULL, (GObject *) self); + + tp_cli_media_stream_handler_call_new_active_candidate_pair ( + self->priv->stream_handler_proxy, -1, local_candidate->foundation, + remote_candidate->foundation, + async_method_callback_optional, + "Media.StreamHandler::NewActiveCandidatePair", + NULL, (GObject *) self); + + if (self->priv->current_state == TP_MEDIA_STREAM_STATE_DISCONNECTED) + { + tp_cli_media_stream_handler_call_stream_state ( + self->priv->stream_handler_proxy, -1, TP_MEDIA_STREAM_STATE_CONNECTED, + async_method_callback, "Media.StreamHandler::StreamState", + NULL, (GObject *) self); + self->priv->current_state = TP_MEDIA_STREAM_STATE_CONNECTED; + } + + g_value_array_free (local_transport); + g_value_array_free (remote_transport); +} + +static void +invalidated_cb (TpMediaStreamHandler *proxy G_GNUC_UNUSED, + guint domain G_GNUC_UNUSED, + gint code G_GNUC_UNUSED, + gchar *message G_GNUC_UNUSED, + gpointer user_data) +{ + TfStream *stream = TF_STREAM (user_data); + + DEBUG (stream, "proxy invalidated"); + + if (stream->priv->stream_handler_proxy) + { + TpMediaStreamHandler *tmp = stream->priv->stream_handler_proxy; + + stream->priv->stream_handler_proxy = NULL; + g_object_unref (tmp); + } + + tf_stream_shutdown (stream); +} + +static void +cb_fs_send_codec_changed (TfStream *self, + FsCodec *send_codec, + GList *secondary_codecs) +{ + GList *item; + gint last_event_id = -1; + struct DtmfEvent *dtmfevent; + + while ((dtmfevent = g_queue_peek_head (&self->priv->events_to_send))) + { + if (dtmfevent->codec_id != last_event_id) + { + last_event_id = -1; + for (item = secondary_codecs; item; item = item->next) + { + FsCodec *codec = item->data; + + if (!g_ascii_strcasecmp (codec->encoding_name, "telephone-event") + && codec->id == dtmfevent->codec_id) + { + last_event_id = codec->id; + goto have_id; + } + } + if (dtmfevent->codec_id != last_event_id) + { + GList *codecs = NULL; + + g_object_get (self->priv->fs_session, "codecs", &codecs, NULL); + + DEBUG (self, "Still do not have the right PT for telephony" + " events, trying to force it again"); + if (check_codecs_for_telephone_event (self, &codecs, send_codec, + dtmfevent->codec_id)) + WARNING (self, "Did not have the right pt in the secondary" + " codecs, but it was in the codec list. Ignoring for now"); + fs_codec_list_destroy (codecs); + return; + } + } + + have_id: + DEBUG (self, "Sending queued event %d with pt %d", dtmfevent->event_id, + dtmfevent->codec_id); + dtmfevent = g_queue_pop_head (&self->priv->events_to_send); + if (self->priv->sending_telephony_event != -1) + { + WARNING (self, "start new telephony event without stopping the" + " previous one first"); + if (!fs_session_stop_telephony_event (self->priv->fs_session, + self->priv->sending_telephony_event)) + WARNING (self, "stopping event failed"); + } + self->priv->sending_telephony_event = -1; + + if (!fs_session_start_telephony_event (self->priv->fs_session, + dtmfevent->event_id, 8, FS_DTMF_METHOD_RTP_RFC4733)) + WARNING (self, "sending event %u failed", dtmfevent->event_id); + fs_session_stop_telephony_event (self->priv->fs_session, + FS_DTMF_METHOD_RTP_RFC4733); + + g_slice_free (struct DtmfEvent, dtmfevent); + } +} + +/** + * tf_stream_error: + * @self: a #TfStream + * @error: the error number as a #TpMediaStreamError + * @message: the message for this error + * + * This function can be used to tell the connection manager that an error + * has happened on a specific stream. + */ + +void +tf_stream_error (TfStream *self, + TpMediaStreamError error, + const gchar *message) +{ + g_message ("%s: stream error errorno=%d error=%s", G_STRFUNC, error, message); + + tp_cli_media_stream_handler_call_error (self->priv->stream_handler_proxy, + -1, error, message, NULL, NULL, NULL, NULL); + + g_signal_emit (self, signals[ERROR_SIGNAL], 0); +} + + +/** + * _tf_stream_bus_message: + * @stream: A #TfStream + * @message: A #GstMessage received from the bus + * + * You must call this function on call messages received on the async bus. + * #GstMessages are not modified. + * + * Returns: %TRUE if the message has been handled, %FALSE otherwise + */ + +gboolean +_tf_stream_bus_message (TfStream *stream, + GstMessage *message) +{ + const GstStructure *s = gst_message_get_structure (message); + + if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT) + return FALSE; + + if (!stream->priv->fs_stream || !stream->priv->fs_session) + return FALSE; + + if (gst_structure_has_name (s, "farstream-error")) + { + GObject *object; + const GValue *value = NULL; + + value = gst_structure_get_value (s, "src-object"); + object = g_value_get_object (value); + + if (object == (GObject*) stream->priv->fs_session || + object == (GObject*) stream->priv->fs_stream) + { + const gchar *msg; + FsError errorno; + GEnumClass *enumclass; + GEnumValue *enumvalue; + + value = gst_structure_get_value (s, "error-no"); + errorno = g_value_get_enum (value); + msg = gst_structure_get_string (s, "error-msg"); + + enumclass = g_type_class_ref (FS_TYPE_ERROR); + enumvalue = g_enum_get_value (enumclass, errorno); + WARNING (stream, "error (%s (%d)): %s", + enumvalue->value_nick, errorno, msg); + g_type_class_unref (enumclass); + + tf_stream_error (stream, fserrorno_to_tperrorno (errorno), msg); + return TRUE; + } + } + else if (gst_structure_has_name (s, "farstream-new-local-candidate")) + { + FsStream *fsstream; + FsCandidate *candidate; + const GValue *value; + + value = gst_structure_get_value (s, "stream"); + fsstream = g_value_get_object (value); + + if (fsstream != stream->priv->fs_stream) + return FALSE; + + value = gst_structure_get_value (s, "candidate"); + candidate = g_value_get_boxed (value); + + cb_fs_new_local_candidate (stream, candidate); + return TRUE; + } + else if (gst_structure_has_name (s, "farstream-local-candidates-prepared")) + { + FsStream *fsstream; + const GValue *value; + + value = gst_structure_get_value (s, "stream"); + fsstream = g_value_get_object (value); + + if (fsstream != stream->priv->fs_stream) + return FALSE; + + cb_fs_local_candidates_prepared (stream); + + return TRUE; + } + else if (gst_structure_has_name (s, "farstream-new-active-candidate-pair")) + { + FsStream *fsstream; + FsCandidate *local_candidate; + FsCandidate *remote_candidate; + const GValue *value; + + value = gst_structure_get_value (s, "stream"); + fsstream = g_value_get_object (value); + + if (fsstream != stream->priv->fs_stream) + return FALSE; + + value = gst_structure_get_value (s, "local-candidate"); + local_candidate = g_value_get_boxed (value); + + value = gst_structure_get_value (s, "remote-candidate"); + remote_candidate = g_value_get_boxed (value); + + cb_fs_new_active_candidate_pair (stream, local_candidate, remote_candidate); + return TRUE; + } + else if (gst_structure_has_name (s, "farstream-current-recv-codecs-changed")) + { + FsStream *fsstream; + GList *codecs; + const GValue *value; + + value = gst_structure_get_value (s, "stream"); + fsstream = g_value_get_object (value); + + if (fsstream != stream->priv->fs_stream) + return FALSE; + + value = gst_structure_get_value (s, "codecs"); + codecs = g_value_get_boxed (value); + + cb_fs_recv_codecs_changed (stream, codecs); + return TRUE; + } + else if (gst_structure_has_name (s, "farstream-codecs-changed")) + { + FsSession *fssession; + const GValue *value; + + value = gst_structure_get_value (s, "session"); + fssession = g_value_get_object (value); + + if (fssession != stream->priv->fs_session) + return FALSE; + + DEBUG (stream, "Codecs changed"); + + _tf_stream_try_sending_codecs (stream); + + return TRUE; + } + else if (gst_structure_has_name (s, "farstream-send-codec-changed")) + { + FsSession *fssession; + const GValue *value; + FsCodec *codec = NULL; + GList *secondary_codecs = NULL; + FsCodec *objcodec = NULL; + + value = gst_structure_get_value (s, "session"); + fssession = g_value_get_object (value); + + if (fssession != stream->priv->fs_session) + return FALSE; + + value = gst_structure_get_value (s, "codec"); + codec = g_value_get_boxed (value); + g_object_get (fssession, "current-send-codec", &objcodec, NULL); + + if (!fs_codec_are_equal (objcodec, codec)) + { + fs_codec_destroy (objcodec); + return TRUE; + } + + value = gst_structure_get_value (s, "secondary-codecs"); + secondary_codecs = g_value_get_boxed (value); + + + if (codec) + DEBUG (stream, "Send codec changed: " FS_CODEC_FORMAT, + FS_CODEC_ARGS (codec)); + + cb_fs_send_codec_changed (stream, codec, secondary_codecs); + + if (codec) + fs_codec_destroy (codec); + fs_codec_list_destroy (secondary_codecs); + return TRUE; + } + else if (gst_structure_has_name (s, "farstream-component-state-changed")) + { + FsStream *fsstream; + const GValue *value; + guint component; + FsStreamState fsstate; + + value = gst_structure_get_value (s, "stream"); + fsstream = g_value_get_object (value); + + if (fsstream != stream->priv->fs_stream) + return FALSE; + + if (!gst_structure_get_uint (s, "component", &component) || + !gst_structure_get_enum (s, "state", FS_TYPE_STREAM_STATE, + (gint*) &fsstate)) + return TRUE; + + cb_fs_component_state_changed (stream, component, fsstate); + return TRUE; + } + else if (gst_structure_has_name (s, "farstream-renegotiate")) + { + FsSession *fssession; + const GValue *value; + + value = gst_structure_get_value (s, "session"); + fssession = g_value_get_object (value); + + if (fssession != stream->priv->fs_session) + return FALSE; + + g_signal_emit (stream, signals[RESTART_SOURCE], 0); + + return TRUE; + } + + return FALSE; +} + +static gboolean +emit_connected (gpointer data) +{ + TfStream *self = TF_STREAM (data); + + TF_STREAM_LOCK (self); + self->priv->idle_connected_id = 0; + if (self->priv->disposed) + { + TF_STREAM_UNLOCK (self); + return FALSE; + } + TF_STREAM_UNLOCK (self); + + tp_cli_media_stream_handler_call_stream_state ( + self->priv->stream_handler_proxy, -1, TP_MEDIA_STREAM_STATE_CONNECTED, + async_method_callback, "Media.StreamHandler::StreamState", + NULL, (GObject *) self); + + return FALSE; +} + +static void +cb_fs_stream_src_pad_added (FsStream *fsstream G_GNUC_UNUSED, + GstPad *pad, + FsCodec *codec, + gpointer user_data) +{ + TfStream *self = TF_STREAM (user_data); + gchar *padname = gst_pad_get_name (pad); + + DEBUG (self, "New pad %s: " FS_CODEC_FORMAT, padname, FS_CODEC_ARGS (codec)); + g_free (padname); + + TF_STREAM_LOCK (self); + if (self->priv->disposed) + { + TF_STREAM_UNLOCK (self); + return; + } + + if (!self->priv->idle_connected_id) + self->priv->idle_connected_id = g_idle_add (emit_connected, self); + TF_STREAM_UNLOCK (self); + + g_signal_emit (self, signals[SRC_PAD_ADDED], 0, pad, codec); +} + +TfStream * +_tf_stream_new (gpointer channel, + FsConference *conference, + FsParticipant *participant, + TpMediaStreamHandler *proxy, + guint stream_id, + TpMediaStreamType media_type, + TpMediaStreamDirection direction, + TfNatProperties *nat_props, + GList *local_preferences, + NewStreamCreatedCb new_stream_created_cb) +{ + TfStream *self = NULL; + + self = g_object_new (TF_TYPE_STREAM, + "channel", channel, + "farstream-conference", conference, + "farstream-participant", participant, + "proxy", proxy, + "stream-id", stream_id, + "media-type", media_type, + "direction", direction, + "nat-properties", nat_props, + "codec-preferences", local_preferences, + NULL); + + self->priv->new_stream_created_cb = new_stream_created_cb; + + return self; +} + +static GHashTable * +fs_codecs_to_feedback_messages (GList *fscodecs) +{ + GList *item; + GHashTable *feedback_messages = g_hash_table_new_full (g_direct_hash, + g_direct_equal, NULL, (GDestroyNotify) g_value_array_free); + + for (item = fscodecs; item; item = item->next) + { + FsCodec *fs_codec = item->data; + + if (fs_codec->minimum_reporting_interval != G_MAXUINT || + fs_codec->feedback_params) + { + GValueArray *codec = g_value_array_new (2); + GPtrArray *messages = g_ptr_array_new (); + GValue *val; + GList *item2; + + for (item2 = fs_codec->feedback_params; + item2; + item2 = item2->next) + { + FsFeedbackParameter *p = item2->data; + GValueArray *message = g_value_array_new (3); + GValue *val2; + + g_value_array_insert (message, 0, NULL); + val2 = g_value_array_get_nth (message, 0); + g_value_init (val2, G_TYPE_STRING); + g_value_set_string (val2, p->type); + + g_value_array_insert (message, 1, NULL); + val2 = g_value_array_get_nth (message, 1); + g_value_init (val2, G_TYPE_STRING); + g_value_set_string (val2, p->subtype); + + g_value_array_insert (message, 2, NULL); + val2 = g_value_array_get_nth (message, 2); + g_value_init (val2, G_TYPE_STRING); + g_value_set_string (val2, p->extra_params); + + g_ptr_array_add (messages, message); + } + + g_value_array_insert (codec, 0, NULL); + val = g_value_array_get_nth (codec, 0); + g_value_init (val, G_TYPE_UINT); + g_value_set_uint (val, fs_codec->minimum_reporting_interval); + + g_value_array_insert (codec, 1, NULL); + val = g_value_array_get_nth (codec, 1); + g_value_init (val, TP_ARRAY_TYPE_RTCP_FEEDBACK_MESSAGE_LIST); + g_value_take_boxed (val, messages); + + g_hash_table_insert (feedback_messages, + GUINT_TO_POINTER (fs_codec->id), codec); + } + } + + return feedback_messages; +} + + +static TpMediaStreamDirection +fsdirection_to_tpdirection (FsStreamDirection dir) +{ + switch (dir) { + case FS_DIRECTION_NONE: + return TP_MEDIA_STREAM_DIRECTION_NONE; + case FS_DIRECTION_SEND: + return TP_MEDIA_STREAM_DIRECTION_SEND; + case FS_DIRECTION_RECV: + return TP_MEDIA_STREAM_DIRECTION_RECEIVE; + case FS_DIRECTION_BOTH: + return TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL; + default: + g_assert_not_reached (); + } +} + + +static GPtrArray * +_tf_stream_get_header_extensions (TfStream *stream) +{ + GPtrArray *extensions = g_ptr_array_new (); + GList *hdrexts; + GList *item; + + if (!g_object_class_find_property ( + G_OBJECT_GET_CLASS (stream->priv->fs_session), + "rtp-header-extensions")) + return extensions; + + g_object_get (stream->priv->fs_session, + "rtp-header-extensions", &hdrexts, NULL); + + for (item = hdrexts; item; item = item->next) + { + FsRtpHeaderExtension *hdrext = item->data; + + g_ptr_array_add (extensions, + tp_value_array_build (4, + G_TYPE_UINT, hdrext->id, + G_TYPE_UINT, fsdirection_to_tpdirection (hdrext->direction), + G_TYPE_STRING, hdrext->uri, + G_TYPE_STRING, "", + G_TYPE_INVALID)); + } + + return extensions; +} + +void +_tf_stream_try_sending_codecs (TfStream *stream) +{ + GList *fscodecs = NULL; + GList *item = NULL; + GPtrArray *tpcodecs = NULL; + GHashTable *feedback_messages = NULL; + GPtrArray *header_extensions = NULL; + gboolean sent = FALSE; + GList *resend_codecs = NULL; + const gchar *codecs_prop = NULL; + + DEBUG (stream, "called (send_local:%d send_supported:%d)", + stream->priv->send_local_codecs, stream->priv->send_supported_codecs); + + + if (stream->priv->has_resource & TP_MEDIA_STREAM_DIRECTION_SEND) + codecs_prop = "codecs"; + else + codecs_prop = "codecs-without-config"; + + g_object_get (stream->priv->fs_session, codecs_prop, &fscodecs, NULL); + + if (!fscodecs) + { + DEBUG (stream, "Ignoring new codecs because we're sending," + " but we're not ready"); + return; + } + + for(item = fscodecs; item; item = g_list_next (item)) + { + gchar *tmp = fs_codec_to_string (item->data); + DEBUG (stream, "%s", tmp); + g_free (tmp); + } + + if (stream->priv->send_local_codecs) + { + tpcodecs = fs_codecs_to_tp (stream, fscodecs); + feedback_messages = fs_codecs_to_feedback_messages (fscodecs); + header_extensions = _tf_stream_get_header_extensions (stream); + + DEBUG (stream, "calling MediaStreamHandler::Ready"); + tp_cli_media_stream_handler_call_supported_feedback_messages ( + stream->priv->stream_handler_proxy, + -1, feedback_messages, async_method_callback_optional, + "Media.StreamHandler::SupportedFeedbackMessages for Ready", + NULL, (GObject *) stream); + tp_cli_media_stream_handler_call_supported_header_extensions ( + stream->priv->stream_handler_proxy, + -1, header_extensions, async_method_callback_optional, + "Media.StreamHandler::SupportedHeaderExtensions for Ready", + NULL, (GObject *) stream); + tp_cli_media_stream_handler_call_ready ( + stream->priv->stream_handler_proxy, + -1, tpcodecs, async_method_callback, "Media.StreamHandler::Ready", + NULL, (GObject *) stream); + stream->priv->send_local_codecs = FALSE; + sent = TRUE; + goto out; + } + + if (stream->priv->send_supported_codecs) + { + tpcodecs = fs_codecs_to_tp (stream, fscodecs); + feedback_messages = fs_codecs_to_feedback_messages (fscodecs); + header_extensions = _tf_stream_get_header_extensions (stream); + + DEBUG (stream, "calling MediaStreamHandler::SupportedCodecs"); + tp_cli_media_stream_handler_call_supported_feedback_messages ( + stream->priv->stream_handler_proxy, + -1, feedback_messages, async_method_callback_optional, + "Media.StreamHandler::SupportedFeedbackMessages for SupportedCodecs", + NULL, (GObject *) stream); + tp_cli_media_stream_handler_call_supported_header_extensions ( + stream->priv->stream_handler_proxy, + -1, header_extensions, async_method_callback_optional, + "Media.StreamHandler::SupportedHeaderExtensions for SupportedCodecs", + NULL, (GObject *) stream); + tp_cli_media_stream_handler_call_supported_codecs ( + stream->priv->stream_handler_proxy, + -1, tpcodecs, async_method_callback, + "Media.StreamHandler::SupportedCodecs", NULL, (GObject *) stream); + stream->priv->send_supported_codecs = FALSE; + sent = TRUE; + + /* Fallthrough to potentially call CodecsUpdated as CMs assume + * SupportedCodecs will only give the intersection of the already sent + * (if any) local codecs, not any updates */ + } + + + /* Only send updates if there was something to update (iotw we sent codecs + * before) or our list changed */ + if (stream->priv->last_sent_codecs != NULL + && (resend_codecs = + fs_session_codecs_need_resend (stream->priv->fs_session, + stream->priv->last_sent_codecs, fscodecs)) != NULL) + { + fs_codec_list_destroy (resend_codecs); + + if (!tpcodecs) + tpcodecs = fs_codecs_to_tp (stream, fscodecs); + if (!feedback_messages) + feedback_messages = fs_codecs_to_feedback_messages (fscodecs); + if (!header_extensions) + header_extensions = _tf_stream_get_header_extensions (stream); + + + DEBUG (stream, "calling MediaStreamHandler::CodecsUpdated"); + tp_cli_media_stream_handler_call_supported_feedback_messages ( + stream->priv->stream_handler_proxy, + -1, feedback_messages, async_method_callback_optional, + "Media.StreamHandler::SupportedFeedbackMessages for CodecsUpdated", + NULL, (GObject *) stream); + tp_cli_media_stream_handler_call_supported_header_extensions ( + stream->priv->stream_handler_proxy, + -1, header_extensions, async_method_callback_optional, + "Media.StreamHandler::SupportedHeaderExtensions for CodecsUpdated", + NULL, (GObject *) stream); + tp_cli_media_stream_handler_call_codecs_updated ( + stream->priv->stream_handler_proxy, + -1, tpcodecs, async_method_callback, + "Media.StreamHandler::CodecsUpdated", NULL, (GObject *) stream); + sent = TRUE; + } + +out: + if (tpcodecs) + g_boxed_free (TP_ARRAY_TYPE_MEDIA_STREAM_HANDLER_CODEC_LIST, tpcodecs); + if (feedback_messages) + g_boxed_free (TP_HASH_TYPE_RTCP_FEEDBACK_MESSAGE_MAP, feedback_messages); + if (header_extensions) + g_boxed_free (TP_ARRAY_TYPE_RTP_HEADER_EXTENSIONS_LIST, header_extensions); + if (sent) + { + fs_codec_list_destroy (stream->priv->last_sent_codecs); + stream->priv->last_sent_codecs = fscodecs; + } +} + +/** + * tf_stream_get_id + * @stream: A #TfStream + * + * Quick getter for the stream id + * + * Returns: the stream's id + */ + +guint +tf_stream_get_id (TfStream *stream) +{ + g_return_val_if_fail (TF_IS_STREAM (stream), 0); + + return stream->stream_id; +} + +static TpMediaStreamError +fserrorno_to_tperrorno (FsError fserror) +{ + TpMediaStreamError tperror; + + switch (fserror) + { + case FS_ERROR_NETWORK: + tperror = TP_MEDIA_STREAM_ERROR_NETWORK_ERROR; + break; + case FS_ERROR_CONNECTION_FAILED: + tperror = TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED; + break; + case FS_ERROR_NO_CODECS: + tperror = TP_MEDIA_STREAM_ERROR_NO_CODECS; + break; + case FS_ERROR_NEGOTIATION_FAILED: + tperror = TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED; + break; + case FS_ERROR_INVALID_ARGUMENTS: + tperror = TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR; + break; + case FS_ERROR_NO_CODECS_LEFT: + case FS_ERROR_CONSTRUCTION: + case FS_ERROR_INTERNAL: + case FS_ERROR_NOT_IMPLEMENTED: /* Not really a real error */ + case FS_ERROR_DISPOSED: /* Not really a real error */ + default: + tperror = TP_MEDIA_STREAM_ERROR_MEDIA_ERROR; + } + + return tperror; +} + +TpMediaStreamError +fserror_to_tperror (GError *error) +{ + if (!error || error->domain != FS_ERROR) + return TP_MEDIA_STREAM_ERROR_UNKNOWN; + + return fserrorno_to_tperrorno (error->code); +} + +static void +cb_fs_component_state_changed (TfStream *self, + guint component, + FsStreamState fsstate) +{ + TpMediaStreamState state; + const gchar *state_str; + + if (component != 1) + return; + + switch (fsstate) + { + case FS_STREAM_STATE_FAILED: + case FS_STREAM_STATE_DISCONNECTED: + state = TP_MEDIA_STREAM_STATE_DISCONNECTED; + state_str = "disconnected"; + break; + case FS_STREAM_STATE_GATHERING: + case FS_STREAM_STATE_CONNECTING: + state = TP_MEDIA_STREAM_STATE_CONNECTING; + break; + case FS_STREAM_STATE_CONNECTED: + default: + state_str = "connected"; + state = TP_MEDIA_STREAM_STATE_CONNECTED; + break; + } + + DEBUG (self, "calling MediaStreamHandler::StreamState (%u: %s)", state, + state_str); + + self->priv->current_state = state; + + tp_cli_media_stream_handler_call_stream_state ( + self->priv->stream_handler_proxy, -1, state, + async_method_callback, "Media.StreamHandler::StreamState", + NULL, (GObject *) self); +} diff --git a/farstream/telepathy-farstream/stream.h b/farstream/telepathy-farstream/stream.h new file mode 100644 index 000000000..ddd4b6bbe --- /dev/null +++ b/farstream/telepathy-farstream/stream.h @@ -0,0 +1,61 @@ +#ifndef __TF_STREAM_H__ +#define __TF_STREAM_H__ + +#include <glib-object.h> +#include <telepathy-glib/enums.h> +#include <telepathy-glib/media-interfaces.h> + +#include <farstream/fs-conference.h> + +G_BEGIN_DECLS + +#define TF_TYPE_STREAM tf_stream_get_type() + +#define TF_STREAM(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + TF_TYPE_STREAM, TfStream)) + +#define TF_STREAM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + TF_TYPE_STREAM, TfStreamClass)) + +#define TF_IS_STREAM(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + TF_TYPE_STREAM)) + +#define TF_IS_STREAM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + TF_TYPE_STREAM)) + +#define TF_STREAM_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + TF_TYPE_STREAM, TfStreamClass)) + +/** + * TfStream: + * + * All members are privated + */ + +typedef struct _TfStream TfStream; + +/** + * TfStreamClass: + * + * This class is not subclassable + */ + +typedef struct _TfStreamClass TfStreamClass; + +GType tf_stream_get_type (void); + +guint tf_stream_get_id (TfStream *stream); + +void tf_stream_error (TfStream *self, + TpMediaStreamError error, + const gchar *message); + + +G_END_DECLS + +#endif /* __TF_STREAM_H__ */ diff --git a/farstream/telepathy-farstream/telepathy-farstream-uninstalled.pc.in b/farstream/telepathy-farstream/telepathy-farstream-uninstalled.pc.in new file mode 100644 index 000000000..37fc7652d --- /dev/null +++ b/farstream/telepathy-farstream/telepathy-farstream-uninstalled.pc.in @@ -0,0 +1,11 @@ +prefix= +exec_prefix= +abs_top_srcdir=@abs_top_srcdir@ +abs_top_builddir=@abs_top_builddir@ + +Name: Telepathy-Farstream +Description: Library implementing the Telepathy Call API using Farstream +Version: @VERSION@ +Requires.private: dbus-glib-1 >= 0.73, glib-2.0 >= 2.10, gobject-2.0 >= 2.10, farstream-0.1 +Libs: ${abs_top_builddir}/telepathy-farstream/libtelepathy-farstream.la +Cflags: -I${abs_top_srcdir} -I${abs_top_builddir} diff --git a/farstream/telepathy-farstream/telepathy-farstream.c b/farstream/telepathy-farstream/telepathy-farstream.c new file mode 100644 index 000000000..85d16a1f9 --- /dev/null +++ b/farstream/telepathy-farstream/telepathy-farstream.c @@ -0,0 +1,34 @@ +/* + * telepathy-farstream.c - Global functions for telepathy-farstream + * Copyright (C) 2011 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "extensions/extensions.h" +#include <telepathy-farstream/telepathy-farstream.h> + +/** + * tf_init: + * + * Initializes telepathy-farstream. This must be called at the start of your + * application, specifically before any DBus proxies related to Call channels + * are created + */ +void +tf_init (void) +{ + tf_future_cli_init (); +} diff --git a/farstream/telepathy-farstream/telepathy-farstream.h b/farstream/telepathy-farstream/telepathy-farstream.h new file mode 100644 index 000000000..256389495 --- /dev/null +++ b/farstream/telepathy-farstream/telepathy-farstream.h @@ -0,0 +1,33 @@ +/* + * telepathy-farstream.h - Source for TfCallChannel + * Copyright (C) 2011 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __TF_FARSTREAM_H_ +#define __TF_FARSTREAM_H_ + +#include <telepathy-farstream/channel.h> +#include <telepathy-farstream/content.h> + +G_BEGIN_DECLS + +void tf_init (void); + +G_END_DECLS + +#endif /* __TF_FARSTREAM_H__ */ + diff --git a/farstream/telepathy-farstream/telepathy-farstream.pc.in b/farstream/telepathy-farstream/telepathy-farstream.pc.in new file mode 100644 index 000000000..3734eb1de --- /dev/null +++ b/farstream/telepathy-farstream/telepathy-farstream.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Telepathy-Farstream +Description: Library implementing the Telepathy Call API using Farstream +Version: @VERSION@ +Requires.private: dbus-glib-1 >= 0.73, glib-2.0 >= 2.10, gobject-2.0 >= 2.10, farstream-0.1, telepathy-glib >= 0.7.23, gstreamer-0.10 +Libs: -L${libdir} -ltelepathy-farstream +Cflags: -I${includedir}/telepathy-1.0 diff --git a/farstream/telepathy-farstream/utils.h b/farstream/telepathy-farstream/utils.h new file mode 100644 index 000000000..934924cf5 --- /dev/null +++ b/farstream/telepathy-farstream/utils.h @@ -0,0 +1,28 @@ +#ifndef __UTILS_H__ +#define __UTILS_H__ + +/** + * tp_media_type_to_fs: + * @type: A Telepathy Media Stream Type + * + * Converts a Telepathy Media Stream Type to the Farstream equivalent + * + * Return: A Farstream Stream Type + */ + +static inline FsMediaType +tp_media_type_to_fs (TpMediaStreamType type) +{ + switch (type) + { + case TP_MEDIA_STREAM_TYPE_AUDIO: + return FS_MEDIA_TYPE_AUDIO; + case TP_MEDIA_STREAM_TYPE_VIDEO: + return FS_MEDIA_TYPE_VIDEO; + default: + g_return_val_if_reached(0); + } +} + + +#endif /* __UTILS_H__ */ diff --git a/farstream/tools/Makefile.am b/farstream/tools/Makefile.am new file mode 100644 index 000000000..4e776776f --- /dev/null +++ b/farstream/tools/Makefile.am @@ -0,0 +1,21 @@ +EXTRA_DIST = \ + telepathy.am \ + c-constants-gen.py \ + glib-client-gen.py \ + glib-client-marshaller-gen.py \ + glib-ginterface-gen.py \ + glib-gtypes-generator.py \ + glib-interfaces-gen.py \ + glib-signals-marshal-gen.py \ + libglibcodegen.py \ + libtpcodegen.py \ + xincludator.py + +TELEPATHY_GLIB_SRCDIR = $(top_srcdir)/../telepathy-glib +maintainer-update-from-telepathy-glib: + set -e && cd $(srcdir) && \ + for x in $(EXTRA_DIST); do \ + if test -f $(TELEPATHY_GLIB_SRCDIR)/tools/$$x; then \ + cp $(TELEPATHY_GLIB_SRCDIR)/tools/$$x $$x; \ + fi; \ + done diff --git a/farstream/tools/c-constants-gen.py b/farstream/tools/c-constants-gen.py new file mode 100644 index 000000000..09608c2bd --- /dev/null +++ b/farstream/tools/c-constants-gen.py @@ -0,0 +1,169 @@ +#!/usr/bin/python + +from sys import argv, stdout, stderr +import xml.dom.minidom + +from libglibcodegen import NS_TP, get_docstring, \ + get_descendant_text, get_by_path + +class Generator(object): + def __init__(self, prefix, dom, output_base): + self.prefix = prefix + '_' + self.spec = get_by_path(dom, "spec")[0] + + self.__header = open(output_base + '.h', 'w') + self.__docs = open(output_base + '-gtk-doc.h', 'w') + + def __call__(self): + self.do_header() + self.do_body() + self.do_footer() + + def write(self, code): + self.__header.write(code.encode('utf-8')) + + def d(self, code): + self.__docs.write(code.encode('utf-8')) + + # Header + def do_header(self): + self.write('/* Generated from ') + self.write(get_descendant_text(get_by_path(self.spec, 'title'))) + version = get_by_path(self.spec, "version") + if version: + self.write(', version ' + get_descendant_text(version)) + self.write('\n\n') + for copyright in get_by_path(self.spec, 'copyright'): + self.write(get_descendant_text(copyright)) + self.write('\n') + self.write(get_descendant_text(get_by_path(self.spec, 'license'))) + self.write('\n') + self.write(get_descendant_text(get_by_path(self.spec, 'docstring'))) + self.write(""" + */ + +#ifdef __cplusplus +extern "C" { +#endif +\n""") + + # Body + def do_body(self): + for elem in self.spec.getElementsByTagNameNS(NS_TP, '*'): + if elem.localName == 'flags': + self.do_flags(elem) + elif elem.localName == 'enum': + self.do_enum(elem) + + def do_flags(self, flags): + name = flags.getAttribute('plural') or flags.getAttribute('name') + value_prefix = flags.getAttribute('singular') or \ + flags.getAttribute('value-prefix') or \ + flags.getAttribute('name') + self.d("""\ +/** + * +%s: +""" % (self.prefix + name).replace('_', '')) + for flag in get_by_path(flags, 'flag'): + self.do_gtkdoc(flag, value_prefix) + self.d(' *\n') + docstrings = get_by_path(flags, 'docstring') + if docstrings: + self.d("""\ + * <![CDATA[%s]]> + * +""" % get_descendant_text(docstrings).replace('\n', ' ')) + self.d("""\ + * Bitfield/set of flags generated from the Telepathy specification. + */ +""") + + self.write("typedef enum {\n") + + for flag in get_by_path(flags, 'flag'): + self.do_val(flag, value_prefix) + self.write("""\ +} %s; + +""" % (self.prefix + name).replace('_', '')) + + def do_enum(self, enum): + name = enum.getAttribute('singular') or enum.getAttribute('name') + value_prefix = enum.getAttribute('singular') or \ + enum.getAttribute('value-prefix') or \ + enum.getAttribute('name') + name_plural = enum.getAttribute('plural') or \ + enum.getAttribute('name') + 's' + self.d("""\ +/** + * +%s: +""" % (self.prefix + name).replace('_', '')) + vals = get_by_path(enum, 'enumvalue') + for val in vals: + self.do_gtkdoc(val, value_prefix) + self.d(' *\n') + docstrings = get_by_path(enum, 'docstring') + if docstrings: + self.d("""\ + * <![CDATA[%s]]> + * +""" % get_descendant_text(docstrings).replace('\n', ' ')) + self.d("""\ + * Bitfield/set of flags generated from the Telepathy specification. + */ +""") + + self.write("typedef enum {\n") + + for val in vals: + self.do_val(val, value_prefix) + self.write("} %s;\n" % (self.prefix + name).replace('_', '')) + + self.d("""\ +/** + * NUM_%(upper-plural)s: + * + * 1 higher than the highest valid value of #%(mixed-name)s. + */ +""" % {'mixed-name' : (self.prefix + name).replace('_', ''), + 'upper-plural' : (self.prefix + name_plural).upper(), + 'last-val' : vals[-1].getAttribute('value')}) + + self.write("""\ +#define NUM_%(upper-plural)s (%(last-val)s+1) + +""" % {'mixed-name' : (self.prefix + name).replace('_', ''), + 'upper-plural' : (self.prefix + name_plural).upper(), + 'last-val' : vals[-1].getAttribute('value')}) + + def do_val(self, val, value_prefix): + name = val.getAttribute('name') + suffix = val.getAttribute('suffix') + use_name = (self.prefix + value_prefix + '_' + \ + (suffix or name)).upper() + assert not (name and suffix) or name == suffix, \ + 'Flag/enumvalue name %s != suffix %s' % (name, suffix) + self.write(' %s = %s,\n' % (use_name, val.getAttribute('value'))) + + def do_gtkdoc(self, node, value_prefix): + self.d(' * @') + self.d((self.prefix + value_prefix + '_' + + node.getAttribute('suffix')).upper()) + self.d(': <![CDATA[') + docstring = get_by_path(node, 'docstring') + self.d(get_descendant_text(docstring).replace('\n', ' ')) + self.d(']]>\n') + + # Footer + def do_footer(self): + self.write(""" +#ifdef __cplusplus +} +#endif +""") + +if __name__ == '__main__': + argv = argv[1:] + Generator(argv[0], xml.dom.minidom.parse(argv[1]), argv[2])() diff --git a/farstream/tools/glib-client-gen.py b/farstream/tools/glib-client-gen.py new file mode 100644 index 000000000..6b0bdeba1 --- /dev/null +++ b/farstream/tools/glib-client-gen.py @@ -0,0 +1,1224 @@ +#!/usr/bin/python + +# glib-client-gen.py: "I Can't Believe It's Not dbus-binding-tool" +# +# Generate GLib client wrappers from the Telepathy specification. +# The master copy of this program is in the telepathy-glib repository - +# please make any changes there. +# +# Copyright (C) 2006-2008 Collabora Ltd. <http://www.collabora.co.uk/> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import sys +import os.path +import xml.dom.minidom +from getopt import gnu_getopt + +from libglibcodegen import Signature, type_to_gtype, cmp_by_name, \ + get_docstring, xml_escape, get_deprecated + + +NS_TP = "http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + +class Generator(object): + + def __init__(self, dom, prefix, basename, opts): + self.dom = dom + self.__header = [] + self.__body = [] + self.__docs = [] + + self.prefix_lc = prefix.lower() + self.prefix_uc = prefix.upper() + self.prefix_mc = prefix.replace('_', '') + self.basename = basename + self.group = opts.get('--group', None) + self.iface_quark_prefix = opts.get('--iface-quark-prefix', None) + self.tp_proxy_api = tuple(map(int, + opts.get('--tp-proxy-api', '0').split('.'))) + self.proxy_cls = opts.get('--subclass', 'TpProxy') + ' *' + self.proxy_arg = opts.get('--subclass', 'void') + ' *' + self.proxy_assert = opts.get('--subclass-assert', 'TP_IS_PROXY') + self.proxy_doc = ('A #%s or subclass' + % opts.get('--subclass', 'TpProxy')) + if self.proxy_arg == 'void *': + self.proxy_arg = 'gpointer ' + self.generate_reentrant = ('--generate-reentrant' in opts or + '--deprecate-reentrant' in opts) + self.deprecate_reentrant = opts.get('--deprecate-reentrant', None) + self.deprecation_attribute = opts.get('--deprecation-attribute', + 'G_GNUC_DEPRECATED') + + def h(self, s): + if isinstance(s, unicode): + s = s.encode('utf-8') + self.__header.append(s) + + def b(self, s): + if isinstance(s, unicode): + s = s.encode('utf-8') + self.__body.append(s) + + def d(self, s): + if isinstance(s, unicode): + s = s.encode('utf-8') + self.__docs.append(s) + + def get_iface_quark(self): + assert self.iface_dbus is not None + assert self.iface_uc is not None + if self.iface_quark_prefix is None: + return 'g_quark_from_static_string (\"%s\")' % self.iface_dbus + else: + return '%s_%s' % (self.iface_quark_prefix, self.iface_uc) + + def do_signal(self, iface, signal): + iface_lc = iface.lower() + + member = signal.getAttribute('name') + member_lc = signal.getAttribute('tp:name-for-bindings') + if member != member_lc.replace('_', ''): + raise AssertionError('Signal %s tp:name-for-bindings (%s) does ' + 'not match' % (member, member_lc)) + member_lc = member_lc.lower() + member_uc = member_lc.upper() + + arg_count = 0 + args = [] + out_args = [] + + for arg in signal.getElementsByTagName('arg'): + name = arg.getAttribute('name') + type = arg.getAttribute('type') + tp_type = arg.getAttribute('tp:type') + + if not name: + name = 'arg%u' % arg_count + arg_count += 1 + else: + name = 'arg_%s' % name + + info = type_to_gtype(type) + args.append((name, info, tp_type, arg)) + + callback_name = ('%s_%s_signal_callback_%s' + % (self.prefix_lc, iface_lc, member_lc)) + collect_name = ('_%s_%s_collect_args_of_%s' + % (self.prefix_lc, iface_lc, member_lc)) + invoke_name = ('_%s_%s_invoke_callback_for_%s' + % (self.prefix_lc, iface_lc, member_lc)) + + # Example: + # + # typedef void (*tp_cli_connection_signal_callback_new_channel) + # (TpConnection *proxy, const gchar *arg_object_path, + # const gchar *arg_channel_type, guint arg_handle_type, + # guint arg_handle, gboolean arg_suppress_handler, + # gpointer user_data, GObject *weak_object); + + self.d('/**') + self.d(' * %s:' % callback_name) + self.d(' * @proxy: The proxy on which %s_%s_connect_to_%s ()' + % (self.prefix_lc, iface_lc, member_lc)) + self.d(' * was called') + + for arg in args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + self.d(' * @%s: %s' % (name, + xml_escape(get_docstring(elt) or '(Undocumented)'))) + + self.d(' * @user_data: User-supplied data') + self.d(' * @weak_object: User-supplied weakly referenced object') + self.d(' *') + self.d(' * Represents the signature of a callback for the signal %s.' + % member) + self.d(' */') + self.d('') + + self.h('typedef void (*%s) (%sproxy,' + % (callback_name, self.proxy_cls)) + + for arg in args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + const = pointer and 'const ' or '' + + self.h(' %s%s%s,' % (const, ctype, name)) + + self.h(' gpointer user_data, GObject *weak_object);') + + if args: + self.b('static void') + self.b('%s (DBusGProxy *proxy G_GNUC_UNUSED,' % collect_name) + + for arg in args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + const = pointer and 'const ' or '' + + self.b(' %s%s%s,' % (const, ctype, name)) + + self.b(' TpProxySignalConnection *sc)') + self.b('{') + self.b(' GValueArray *args = g_value_array_new (%d);' % len(args)) + self.b(' GValue blank = { 0 };') + self.b(' guint i;') + self.b('') + self.b(' g_value_init (&blank, G_TYPE_INT);') + self.b('') + self.b(' for (i = 0; i < %d; i++)' % len(args)) + self.b(' g_value_array_append (args, &blank);') + self.b('') + + for i, arg in enumerate(args): + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + self.b(' g_value_unset (args->values + %d);' % i) + self.b(' g_value_init (args->values + %d, %s);' % (i, gtype)) + + if gtype == 'G_TYPE_STRING': + self.b(' g_value_set_string (args->values + %d, %s);' + % (i, name)) + elif marshaller == 'BOXED': + self.b(' g_value_set_boxed (args->values + %d, %s);' + % (i, name)) + elif gtype == 'G_TYPE_UCHAR': + self.b(' g_value_set_uchar (args->values + %d, %s);' + % (i, name)) + elif gtype == 'G_TYPE_BOOLEAN': + self.b(' g_value_set_boolean (args->values + %d, %s);' + % (i, name)) + elif gtype == 'G_TYPE_INT': + self.b(' g_value_set_int (args->values + %d, %s);' + % (i, name)) + elif gtype == 'G_TYPE_UINT': + self.b(' g_value_set_uint (args->values + %d, %s);' + % (i, name)) + elif gtype == 'G_TYPE_INT64': + self.b(' g_value_set_int (args->values + %d, %s);' + % (i, name)) + elif gtype == 'G_TYPE_UINT64': + self.b(' g_value_set_uint64 (args->values + %d, %s);' + % (i, name)) + elif gtype == 'G_TYPE_DOUBLE': + self.b(' g_value_set_double (args->values + %d, %s);' + % (i, name)) + else: + assert False, ("Don't know how to put %s in a GValue" + % gtype) + self.b('') + + self.b(' tp_proxy_signal_connection_v0_take_results (sc, args);') + self.b('}') + + self.b('static void') + self.b('%s (TpProxy *tpproxy,' % invoke_name) + self.b(' GError *error G_GNUC_UNUSED,') + self.b(' GValueArray *args,') + self.b(' GCallback generic_callback,') + self.b(' gpointer user_data,') + self.b(' GObject *weak_object)') + self.b('{') + self.b(' %s callback =' % callback_name) + self.b(' (%s) generic_callback;' % callback_name) + self.b('') + self.b(' if (callback != NULL)') + self.b(' callback (g_object_ref (tpproxy),') + + # FIXME: factor out into a function + for i, arg in enumerate(args): + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + if marshaller == 'BOXED': + self.b(' g_value_get_boxed (args->values + %d),' % i) + elif gtype == 'G_TYPE_STRING': + self.b(' g_value_get_string (args->values + %d),' % i) + elif gtype == 'G_TYPE_UCHAR': + self.b(' g_value_get_uchar (args->values + %d),' % i) + elif gtype == 'G_TYPE_BOOLEAN': + self.b(' g_value_get_boolean (args->values + %d),' % i) + elif gtype == 'G_TYPE_UINT': + self.b(' g_value_get_uint (args->values + %d),' % i) + elif gtype == 'G_TYPE_INT': + self.b(' g_value_get_int (args->values + %d),' % i) + elif gtype == 'G_TYPE_UINT64': + self.b(' g_value_get_uint64 (args->values + %d),' % i) + elif gtype == 'G_TYPE_INT64': + self.b(' g_value_get_int64 (args->values + %d),' % i) + elif gtype == 'G_TYPE_DOUBLE': + self.b(' g_value_get_double (args->values + %d),' % i) + else: + assert False, "Don't know how to get %s from a GValue" % gtype + + self.b(' user_data,') + self.b(' weak_object);') + self.b('') + + if len(args) > 0: + self.b(' g_value_array_free (args);') + else: + self.b(' if (args != NULL)') + self.b(' g_value_array_free (args);') + self.b('') + + self.b(' g_object_unref (tpproxy);') + self.b('}') + + # Example: + # + # TpProxySignalConnection * + # tp_cli_connection_connect_to_new_channel + # (TpConnection *proxy, + # tp_cli_connection_signal_callback_new_channel callback, + # gpointer user_data, + # GDestroyNotify destroy); + # + # destroy is invoked when the signal becomes disconnected. This + # is either because the signal has been disconnected explicitly + # by the user, because the TpProxy has become invalid and + # emitted the 'invalidated' signal, or because the weakly referenced + # object has gone away. + + self.d('/**') + self.d(' * %s_%s_connect_to_%s:' + % (self.prefix_lc, iface_lc, member_lc)) + self.d(' * @proxy: %s' % self.proxy_doc) + self.d(' * @callback: Callback to be called when the signal is') + self.d(' * received') + self.d(' * @user_data: User-supplied data for the callback') + self.d(' * @destroy: Destructor for the user-supplied data, which') + self.d(' * will be called when this signal is disconnected, or') + self.d(' * before this function returns %NULL') + self.d(' * @weak_object: A #GObject which will be weakly referenced; ') + self.d(' * if it is destroyed, this callback will automatically be') + self.d(' * disconnected') + self.d(' * @error: If not %NULL, used to raise an error if %NULL is') + self.d(' * returned') + self.d(' *') + self.d(' * Connect a handler to the signal %s.' % member) + self.d(' *') + self.d(' * %s' % xml_escape(get_docstring(signal) or '(Undocumented)')) + self.d(' *') + self.d(' * Returns: a #TpProxySignalConnection containing all of the') + self.d(' * above, which can be used to disconnect the signal; or') + self.d(' * %NULL if the proxy does not have the desired interface') + self.d(' * or has become invalid.') + self.d(' */') + self.d('') + + self.h('TpProxySignalConnection *%s_%s_connect_to_%s (%sproxy,' + % (self.prefix_lc, iface_lc, member_lc, self.proxy_arg)) + self.h(' %s callback,' % callback_name) + self.h(' gpointer user_data,') + self.h(' GDestroyNotify destroy,') + self.h(' GObject *weak_object,') + self.h(' GError **error);') + self.h('') + + self.b('TpProxySignalConnection *') + self.b('%s_%s_connect_to_%s (%sproxy,' + % (self.prefix_lc, iface_lc, member_lc, self.proxy_arg)) + self.b(' %s callback,' % callback_name) + self.b(' gpointer user_data,') + self.b(' GDestroyNotify destroy,') + self.b(' GObject *weak_object,') + self.b(' GError **error)') + self.b('{') + self.b(' GType expected_types[%d] = {' % (len(args) + 1)) + + for arg in args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + self.b(' %s,' % gtype) + + self.b(' G_TYPE_INVALID };') + self.b('') + self.b(' g_return_val_if_fail (%s (proxy), NULL);' + % self.proxy_assert) + self.b(' g_return_val_if_fail (callback != NULL, NULL);') + self.b('') + self.b(' return tp_proxy_signal_connection_v0_new ((TpProxy *) proxy,') + self.b(' %s, \"%s\",' % (self.get_iface_quark(), member)) + self.b(' expected_types,') + + if args: + self.b(' G_CALLBACK (%s),' % collect_name) + else: + self.b(' NULL, /* no args => no collector function */') + + self.b(' %s,' % invoke_name) + self.b(' G_CALLBACK (callback), user_data, destroy,') + self.b(' weak_object, error);') + self.b('}') + self.b('') + + def do_method(self, iface, method): + iface_lc = iface.lower() + + member = method.getAttribute('name') + member_lc = method.getAttribute('tp:name-for-bindings') + if member != member_lc.replace('_', ''): + raise AssertionError('Method %s tp:name-for-bindings (%s) does ' + 'not match' % (member, member_lc)) + member_lc = member_lc.lower() + member_uc = member_lc.upper() + + in_count = 0 + ret_count = 0 + in_args = [] + out_args = [] + + for arg in method.getElementsByTagName('arg'): + name = arg.getAttribute('name') + direction = arg.getAttribute('direction') + type = arg.getAttribute('type') + tp_type = arg.getAttribute('tp:type') + + if direction != 'out': + if not name: + name = 'in%u' % in_count + in_count += 1 + else: + name = 'in_%s' % name + else: + if not name: + name = 'out%u' % ret_count + ret_count += 1 + else: + name = 'out_%s' % name + + info = type_to_gtype(type) + if direction != 'out': + in_args.append((name, info, tp_type, arg)) + else: + out_args.append((name, info, tp_type, arg)) + + # Async reply callback type + + # Example: + # void (*tp_cli_properties_interface_callback_for_get_properties) + # (TpProxy *proxy, + # const GPtrArray *out0, + # const GError *error, + # gpointer user_data, + # GObject *weak_object); + + self.d('/**') + self.d(' * %s_%s_callback_for_%s:' + % (self.prefix_lc, iface_lc, member_lc)) + self.d(' * @proxy: the proxy on which the call was made') + + for arg in out_args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + self.d(' * @%s: Used to return an \'out\' argument if @error is ' + '%%NULL: %s' + % (name, xml_escape(get_docstring(elt) or '(Undocumented)'))) + + self.d(' * @error: %NULL on success, or an error on failure') + self.d(' * @user_data: user-supplied data') + self.d(' * @weak_object: user-supplied object') + self.d(' *') + self.d(' * Signature of the callback called when a %s method call' + % member) + self.d(' * succeeds or fails.') + + deprecated = method.getElementsByTagName('tp:deprecated') + if deprecated: + d = deprecated[0] + self.d(' *') + self.d(' * Deprecated: %s' % xml_escape(get_deprecated(d))) + + self.d(' */') + self.d('') + + callback_name = '%s_%s_callback_for_%s' % (self.prefix_lc, iface_lc, + member_lc) + + self.h('typedef void (*%s) (%sproxy,' + % (callback_name, self.proxy_cls)) + + for arg in out_args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + const = pointer and 'const ' or '' + + self.h(' %s%s%s,' % (const, ctype, name)) + + self.h(' const GError *error, gpointer user_data,') + self.h(' GObject *weak_object);') + self.h('') + + # Async callback implementation + + invoke_callback = '_%s_%s_invoke_callback_%s' % (self.prefix_lc, + iface_lc, + member_lc) + + collect_callback = '_%s_%s_collect_callback_%s' % (self.prefix_lc, + iface_lc, + member_lc) + + # The callback called by dbus-glib; this ends the call and collects + # the results into a GValueArray. + self.b('static void') + self.b('%s (DBusGProxy *proxy,' % collect_callback) + self.b(' DBusGProxyCall *call,') + self.b(' gpointer user_data)') + self.b('{') + self.b(' GError *error = NULL;') + + if len(out_args) > 0: + self.b(' GValueArray *args;') + self.b(' GValue blank = { 0 };') + self.b(' guint i;') + + for arg in out_args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + # "We handle variants specially; the caller is expected to + # have already allocated storage for them". Thanks, + # dbus-glib... + if gtype == 'G_TYPE_VALUE': + self.b(' GValue *%s = g_new0 (GValue, 1);' % name) + else: + self.b(' %s%s;' % (ctype, name)) + + self.b('') + self.b(' dbus_g_proxy_end_call (proxy, call, &error,') + + for arg in out_args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + if gtype == 'G_TYPE_VALUE': + self.b(' %s, %s,' % (gtype, name)) + else: + self.b(' %s, &%s,' % (gtype, name)) + + self.b(' G_TYPE_INVALID);') + + if len(out_args) == 0: + self.b(' tp_proxy_pending_call_v0_take_results (user_data, error,' + 'NULL);') + else: + self.b('') + self.b(' if (error != NULL)') + self.b(' {') + self.b(' tp_proxy_pending_call_v0_take_results (user_data, error,') + self.b(' NULL);') + + for arg in out_args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + if gtype == 'G_TYPE_VALUE': + self.b(' g_free (%s);' % name) + + self.b(' return;') + self.b(' }') + self.b('') + self.b(' args = g_value_array_new (%d);' % len(out_args)) + self.b(' g_value_init (&blank, G_TYPE_INT);') + self.b('') + self.b(' for (i = 0; i < %d; i++)' % len(out_args)) + self.b(' g_value_array_append (args, &blank);') + + for i, arg in enumerate(out_args): + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + self.b('') + self.b(' g_value_unset (args->values + %d);' % i) + self.b(' g_value_init (args->values + %d, %s);' % (i, gtype)) + + if gtype == 'G_TYPE_STRING': + self.b(' g_value_take_string (args->values + %d, %s);' + % (i, name)) + elif marshaller == 'BOXED': + self.b(' g_value_take_boxed (args->values + %d, %s);' + % (i, name)) + elif gtype == 'G_TYPE_UCHAR': + self.b(' g_value_set_uchar (args->values + %d, %s);' + % (i, name)) + elif gtype == 'G_TYPE_BOOLEAN': + self.b(' g_value_set_boolean (args->values + %d, %s);' + % (i, name)) + elif gtype == 'G_TYPE_INT': + self.b(' g_value_set_int (args->values + %d, %s);' + % (i, name)) + elif gtype == 'G_TYPE_UINT': + self.b(' g_value_set_uint (args->values + %d, %s);' + % (i, name)) + elif gtype == 'G_TYPE_INT64': + self.b(' g_value_set_int (args->values + %d, %s);' + % (i, name)) + elif gtype == 'G_TYPE_UINT64': + self.b(' g_value_set_uint (args->values + %d, %s);' + % (i, name)) + elif gtype == 'G_TYPE_DOUBLE': + self.b(' g_value_set_double (args->values + %d, %s);' + % (i, name)) + else: + assert False, ("Don't know how to put %s in a GValue" + % gtype) + + self.b(' tp_proxy_pending_call_v0_take_results (user_data, ' + 'NULL, args);') + + self.b('}') + + self.b('static void') + self.b('%s (TpProxy *self,' % invoke_callback) + self.b(' GError *error,') + self.b(' GValueArray *args,') + self.b(' GCallback generic_callback,') + self.b(' gpointer user_data,') + self.b(' GObject *weak_object)') + self.b('{') + self.b(' %s callback = (%s) generic_callback;' + % (callback_name, callback_name)) + self.b('') + self.b(' if (error != NULL)') + self.b(' {') + self.b(' callback ((%s) self,' % self.proxy_cls) + + for arg in out_args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + if marshaller == 'BOXED' or pointer: + self.b(' NULL,') + elif gtype == 'G_TYPE_DOUBLE': + self.b(' 0.0,') + else: + self.b(' 0,') + + self.b(' error, user_data, weak_object);') + self.b(' g_error_free (error);') + self.b(' return;') + self.b(' }') + + self.b(' callback ((%s) self,' % self.proxy_cls) + + # FIXME: factor out into a function + for i, arg in enumerate(out_args): + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + if marshaller == 'BOXED': + self.b(' g_value_get_boxed (args->values + %d),' % i) + elif gtype == 'G_TYPE_STRING': + self.b(' g_value_get_string (args->values + %d),' % i) + elif gtype == 'G_TYPE_UCHAR': + self.b(' g_value_get_uchar (args->values + %d),' % i) + elif gtype == 'G_TYPE_BOOLEAN': + self.b(' g_value_get_boolean (args->values + %d),' % i) + elif gtype == 'G_TYPE_UINT': + self.b(' g_value_get_uint (args->values + %d),' % i) + elif gtype == 'G_TYPE_INT': + self.b(' g_value_get_int (args->values + %d),' % i) + elif gtype == 'G_TYPE_UINT64': + self.b(' g_value_get_uint64 (args->values + %d),' % i) + elif gtype == 'G_TYPE_INT64': + self.b(' g_value_get_int64 (args->values + %d),' % i) + elif gtype == 'G_TYPE_DOUBLE': + self.b(' g_value_get_double (args->values + %d),' % i) + else: + assert False, "Don't know how to get %s from a GValue" % gtype + + self.b(' error, user_data, weak_object);') + self.b('') + + if len(out_args) > 0: + self.b(' g_value_array_free (args);') + else: + self.b(' if (args != NULL)') + self.b(' g_value_array_free (args);') + + self.b('}') + self.b('') + + # Async stub + + # Example: + # TpProxyPendingCall * + # tp_cli_properties_interface_call_get_properties + # (gpointer proxy, + # gint timeout_ms, + # const GArray *in_properties, + # tp_cli_properties_interface_callback_for_get_properties callback, + # gpointer user_data, + # GDestroyNotify *destructor); + + self.h('TpProxyPendingCall *%s_%s_call_%s (%sproxy,' + % (self.prefix_lc, iface_lc, member_lc, self.proxy_arg)) + self.h(' gint timeout_ms,') + + self.d('/**') + self.d(' * %s_%s_call_%s:' + % (self.prefix_lc, iface_lc, member_lc)) + self.d(' * @proxy: the #TpProxy') + self.d(' * @timeout_ms: the timeout in milliseconds, or -1 to use the') + self.d(' * default') + + for arg in in_args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + self.d(' * @%s: Used to pass an \'in\' argument: %s' + % (name, xml_escape(get_docstring(elt) or '(Undocumented)'))) + + self.d(' * @callback: called when the method call succeeds or fails;') + self.d(' * may be %NULL to make a "fire and forget" call with no ') + self.d(' * reply tracking') + self.d(' * @user_data: user-supplied data passed to the callback;') + self.d(' * must be %NULL if @callback is %NULL') + self.d(' * @destroy: called with the user_data as argument, after the') + self.d(' * call has succeeded, failed or been cancelled;') + self.d(' * must be %NULL if @callback is %NULL') + self.d(' * @weak_object: If not %NULL, a #GObject which will be ') + self.d(' * weakly referenced; if it is destroyed, this call ') + self.d(' * will automatically be cancelled. Must be %NULL if ') + self.d(' * @callback is %NULL') + self.d(' *') + self.d(' * Start a %s method call.' % member) + self.d(' *') + self.d(' * %s' % xml_escape(get_docstring(method) or '(Undocumented)')) + self.d(' *') + self.d(' * Returns: a #TpProxyPendingCall representing the call in') + self.d(' * progress. It is borrowed from the object, and will become') + self.d(' * invalid when the callback is called, the call is') + self.d(' * cancelled or the #TpProxy becomes invalid.') + + deprecated = method.getElementsByTagName('tp:deprecated') + if deprecated: + d = deprecated[0] + self.d(' *') + self.d(' * Deprecated: %s' % xml_escape(get_deprecated(d))) + + self.d(' */') + self.d('') + + self.b('TpProxyPendingCall *\n%s_%s_call_%s (%sproxy,' + % (self.prefix_lc, iface_lc, member_lc, self.proxy_arg)) + self.b(' gint timeout_ms,') + + for arg in in_args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + const = pointer and 'const ' or '' + + self.h(' %s%s%s,' % (const, ctype, name)) + self.b(' %s%s%s,' % (const, ctype, name)) + + self.h(' %s callback,' % callback_name) + self.h(' gpointer user_data,') + self.h(' GDestroyNotify destroy,') + self.h(' GObject *weak_object);') + self.h('') + + self.b(' %s callback,' % callback_name) + self.b(' gpointer user_data,') + self.b(' GDestroyNotify destroy,') + self.b(' GObject *weak_object)') + self.b('{') + self.b(' GError *error = NULL;') + self.b(' GQuark interface = %s;' % self.get_iface_quark()) + self.b(' DBusGProxy *iface;') + self.b('') + self.b(' g_return_val_if_fail (%s (proxy), NULL);' + % self.proxy_assert) + self.b(' g_return_val_if_fail (callback != NULL || ' + 'user_data == NULL, NULL);') + self.b(' g_return_val_if_fail (callback != NULL || ' + 'destroy == NULL, NULL);') + self.b(' g_return_val_if_fail (callback != NULL || ' + 'weak_object == NULL, NULL);') + self.b('') + self.b(' iface = tp_proxy_borrow_interface_by_id (') + self.b(' (TpProxy *) proxy,') + self.b(' interface, &error);') + self.b('') + self.b(' if (iface == NULL)') + self.b(' {') + self.b(' if (callback != NULL)') + self.b(' callback (proxy,') + + for arg in out_args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + if pointer: + self.b(' NULL,') + else: + self.b(' 0,') + + self.b(' error, user_data, weak_object);') + self.b('') + self.b(' if (destroy != NULL)') + self.b(' destroy (user_data);') + self.b('') + self.b(' g_error_free (error);') + self.b(' return NULL;') + self.b(' }') + self.b('') + self.b(' if (callback == NULL)') + self.b(' {') + self.b(' dbus_g_proxy_call_no_reply (iface, "%s",' % member) + + for arg in in_args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + const = pointer and 'const ' or '' + + self.b(' %s, %s,' % (gtype, name)) + + self.b(' G_TYPE_INVALID);') + self.b(' return NULL;') + self.b(' }') + self.b(' else') + self.b(' {') + self.b(' TpProxyPendingCall *data;') + self.b('') + self.b(' data = tp_proxy_pending_call_v0_new ((TpProxy *) proxy,') + self.b(' interface, "%s", iface,' % member) + self.b(' %s,' % invoke_callback) + self.b(' G_CALLBACK (callback), user_data, destroy,') + self.b(' weak_object, FALSE);') + self.b(' tp_proxy_pending_call_v0_take_pending_call (data,') + self.b(' dbus_g_proxy_begin_call_with_timeout (iface,') + self.b(' "%s",' % member) + self.b(' %s,' % collect_callback) + self.b(' data,') + self.b(' tp_proxy_pending_call_v0_completed,') + self.b(' timeout_ms,') + + for arg in in_args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + const = pointer and 'const ' or '' + + self.b(' %s, %s,' % (gtype, name)) + + self.b(' G_TYPE_INVALID));') + self.b('') + self.b(' return data;') + self.b(' }') + self.b('}') + self.b('') + + if self.generate_reentrant: + self.do_method_reentrant(method, iface_lc, member, member_lc, + in_args, out_args, collect_callback) + + # leave a gap for the end of the method + self.d('') + self.b('') + self.h('') + + def do_method_reentrant(self, method, iface_lc, member, member_lc, in_args, + out_args, collect_callback): + # Reentrant blocking calls + # Example: + # gboolean tp_cli_properties_interface_run_get_properties + # (gpointer proxy, + # gint timeout_ms, + # const GArray *in_properties, + # GPtrArray **out0, + # GError **error, + # GMainLoop **loop); + + self.b('typedef struct {') + self.b(' GMainLoop *loop;') + self.b(' GError **error;') + + for arg in out_args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + self.b(' %s*%s;' % (ctype, name)) + + self.b(' unsigned success:1;') + self.b(' unsigned completed:1;') + self.b('} _%s_%s_run_state_%s;' + % (self.prefix_lc, iface_lc, member_lc)) + + reentrant_invoke = '_%s_%s_finish_running_%s' % (self.prefix_lc, + iface_lc, + member_lc) + + self.b('static void') + self.b('%s (TpProxy *self G_GNUC_UNUSED,' % reentrant_invoke) + self.b(' GError *error,') + self.b(' GValueArray *args,') + self.b(' GCallback unused G_GNUC_UNUSED,') + self.b(' gpointer user_data G_GNUC_UNUSED,') + self.b(' GObject *unused2 G_GNUC_UNUSED)') + self.b('{') + self.b(' _%s_%s_run_state_%s *state = user_data;' + % (self.prefix_lc, iface_lc, member_lc)) + self.b('') + self.b(' state->success = (error == NULL);') + self.b(' state->completed = TRUE;') + self.b(' g_main_loop_quit (state->loop);') + self.b('') + self.b(' if (error != NULL)') + self.b(' {') + self.b(' if (state->error != NULL)') + self.b(' *state->error = error;') + self.b(' else') + self.b(' g_error_free (error);') + self.b('') + self.b(' return;') + self.b(' }') + self.b('') + + for i, arg in enumerate(out_args): + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + self.b(' if (state->%s != NULL)' % name) + if marshaller == 'BOXED': + self.b(' *state->%s = g_value_dup_boxed (' + 'args->values + %d);' % (name, i)) + elif marshaller == 'STRING': + self.b(' *state->%s = g_value_dup_string ' + '(args->values + %d);' % (name, i)) + elif marshaller in ('UCHAR', 'BOOLEAN', 'INT', 'UINT', + 'INT64', 'UINT64', 'DOUBLE'): + self.b(' *state->%s = g_value_get_%s (args->values + %d);' + % (name, marshaller.lower(), i)) + else: + assert False, "Don't know how to copy %s" % gtype + + self.b('') + + if len(out_args) > 0: + self.b(' g_value_array_free (args);') + else: + self.b(' if (args != NULL)') + self.b(' g_value_array_free (args);') + + self.b('}') + self.b('') + + if self.deprecate_reentrant: + self.h('#ifndef %s' % self.deprecate_reentrant) + + self.h('gboolean %s_%s_run_%s (%sproxy,' + % (self.prefix_lc, iface_lc, member_lc, self.proxy_arg)) + self.h(' gint timeout_ms,') + + self.d('/**') + self.d(' * %s_%s_run_%s:' % (self.prefix_lc, iface_lc, member_lc)) + self.d(' * @proxy: %s' % self.proxy_doc) + self.d(' * @timeout_ms: Timeout in milliseconds, or -1 for default') + + for arg in in_args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + self.d(' * @%s: Used to pass an \'in\' argument: %s' + % (name, xml_escape(get_docstring(elt) or '(Undocumented)'))) + + for arg in out_args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + self.d(' * @%s: Used to return an \'out\' argument if %%TRUE is ' + 'returned: %s' + % (name, xml_escape(get_docstring(elt) or '(Undocumented)'))) + + self.d(' * @error: If not %NULL, used to return errors if %FALSE ') + self.d(' * is returned') + self.d(' * @loop: If not %NULL, set before re-entering ') + self.d(' * the main loop, to point to a #GMainLoop ') + self.d(' * which can be used to cancel this call with ') + self.d(' * g_main_loop_quit(), causing a return of ') + self.d(' * %FALSE with @error set to %TP_DBUS_ERROR_CANCELLED') + self.d(' *') + self.d(' * Call the method %s and run the main loop' % member) + self.d(' * until it returns. Before calling this method, you must') + self.d(' * add a reference to any borrowed objects you need to keep,') + self.d(' * and generally ensure that everything is in a consistent') + self.d(' * state.') + self.d(' *') + self.d(' * %s' % xml_escape(get_docstring(method) or '(Undocumented)')) + self.d(' *') + self.d(' * Returns: TRUE on success, FALSE and sets @error on error') + + deprecated = method.getElementsByTagName('tp:deprecated') + if deprecated: + d = deprecated[0] + self.d(' *') + self.d(' * Deprecated: %s' % xml_escape(get_deprecated(d))) + + self.d(' */') + self.d('') + + self.b('gboolean\n%s_%s_run_%s (%sproxy,' + % (self.prefix_lc, iface_lc, member_lc, self.proxy_arg)) + self.b(' gint timeout_ms,') + + for arg in in_args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + const = pointer and 'const ' or '' + + self.h(' %s%s%s,' % (const, ctype, name)) + self.b(' %s%s%s,' % (const, ctype, name)) + + for arg in out_args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + self.h(' %s*%s,' % (ctype, name)) + self.b(' %s*%s,' % (ctype, name)) + + self.h(' GError **error,') + + if self.deprecate_reentrant: + self.h(' GMainLoop **loop) %s;' % self.deprecation_attribute) + self.h('#endif /* not %s */' % self.deprecate_reentrant) + else: + self.h(' GMainLoop **loop);') + + self.h('') + + self.b(' GError **error,') + self.b(' GMainLoop **loop)') + self.b('{') + self.b(' DBusGProxy *iface;') + self.b(' GQuark interface = %s;' % self.get_iface_quark()) + self.b(' TpProxyPendingCall *pc;') + self.b(' _%s_%s_run_state_%s state = {' + % (self.prefix_lc, iface_lc, member_lc)) + self.b(' NULL /* loop */, error,') + + for arg in out_args: + name, info, tp_type, elt = arg + + self.b(' %s,' % name) + + self.b(' FALSE /* completed */, FALSE /* success */ };') + self.b('') + self.b(' g_return_val_if_fail (%s (proxy), FALSE);' + % self.proxy_assert) + self.b('') + self.b(' iface = tp_proxy_borrow_interface_by_id') + self.b(' ((TpProxy *) proxy, interface, error);') + self.b('') + self.b(' if (iface == NULL)') + self.b(' return FALSE;') + self.b('') + self.b(' state.loop = g_main_loop_new (NULL, FALSE);') + self.b('') + self.b(' pc = tp_proxy_pending_call_v0_new ((TpProxy *) proxy,') + self.b(' interface, "%s", iface,' % member) + self.b(' %s,' % reentrant_invoke) + self.b(' NULL, &state, NULL, NULL, TRUE);') + self.b('') + self.b(' if (loop != NULL)') + self.b(' *loop = state.loop;') + self.b('') + self.b(' tp_proxy_pending_call_v0_take_pending_call (pc,') + self.b(' dbus_g_proxy_begin_call_with_timeout (iface,') + self.b(' "%s",' % member) + self.b(' %s,' % collect_callback) + self.b(' pc,') + self.b(' tp_proxy_pending_call_v0_completed,') + self.b(' timeout_ms,') + + for arg in in_args: + name, info, tp_type, elt = arg + ctype, gtype, marshaller, pointer = info + + const = pointer and 'const ' or '' + + self.b(' %s, %s,' % (gtype, name)) + + self.b(' G_TYPE_INVALID));') + self.b('') + self.b(' if (!state.completed)') + self.b(' g_main_loop_run (state.loop);') + self.b('') + self.b(' if (!state.completed)') + self.b(' tp_proxy_pending_call_cancel (pc);') + self.b('') + self.b(' if (loop != NULL)') + self.b(' *loop = NULL;') + self.b('') + self.b(' g_main_loop_unref (state.loop);') + self.b('') + self.b(' return state.success;') + self.b('}') + self.b('') + + def do_signal_add(self, signal): + marshaller_items = [] + gtypes = [] + + for i in signal.getElementsByTagName('arg'): + name = i.getAttribute('name') + type = i.getAttribute('type') + info = type_to_gtype(type) + # type, GType, STRING, is a pointer + gtypes.append(info[1]) + + self.b(' dbus_g_proxy_add_signal (proxy, "%s",' + % signal.getAttribute('name')) + for gtype in gtypes: + self.b(' %s,' % gtype) + self.b(' G_TYPE_INVALID);') + + def do_interface(self, node): + ifaces = node.getElementsByTagName('interface') + assert len(ifaces) == 1 + iface = ifaces[0] + name = node.getAttribute('name').replace('/', '') + + self.iface = name + self.iface_lc = name.lower() + self.iface_uc = name.upper() + self.iface_mc = name.replace('_', '') + self.iface_dbus = iface.getAttribute('name') + + signals = node.getElementsByTagName('signal') + methods = node.getElementsByTagName('method') + + if signals: + self.b('static inline void') + self.b('%s_add_signals_for_%s (DBusGProxy *proxy)' + % (self.prefix_lc, name.lower())) + self.b('{') + + if self.tp_proxy_api >= (0, 7, 6): + self.b(' if (!tp_proxy_dbus_g_proxy_claim_for_signal_adding ' + '(proxy))') + self.b(' return;') + + for signal in signals: + self.do_signal_add(signal) + + self.b('}') + self.b('') + self.b('') + + for signal in signals: + self.do_signal(name, signal) + + for method in methods: + self.do_method(name, method) + + self.iface_dbus = None + + def __call__(self): + + self.h('G_BEGIN_DECLS') + self.h('') + + self.b('/* We don\'t want gtkdoc scanning this file, it\'ll get') + self.b(' * confused by seeing function definitions, so mark it as: */') + self.b('/*<private_header>*/') + self.b('') + + nodes = self.dom.getElementsByTagName('node') + nodes.sort(cmp_by_name) + + for node in nodes: + self.do_interface(node) + + if self.group is not None: + + self.b('/*') + self.b(' * %s_%s_add_signals:' % (self.prefix_lc, self.group)) + self.b(' * @self: the #TpProxy') + self.b(' * @quark: a quark whose string value is the interface') + self.b(' * name whose signals should be added') + self.b(' * @proxy: the D-Bus proxy to which to add the signals') + self.b(' * @unused: not used for anything') + self.b(' *') + self.b(' * Tell dbus-glib that @proxy has the signatures of all') + self.b(' * signals on the given interface, if it\'s one we') + self.b(' * support.') + self.b(' *') + self.b(' * This function should be used as a signal handler for') + self.b(' * #TpProxy::interface-added.') + self.b(' */') + self.b('static void') + self.b('%s_%s_add_signals (TpProxy *self G_GNUC_UNUSED,' + % (self.prefix_lc, self.group)) + self.b(' guint quark,') + self.b(' DBusGProxy *proxy,') + self.b(' gpointer unused G_GNUC_UNUSED)') + + self.b('{') + + for node in nodes: + iface = node.getElementsByTagName('interface')[0] + self.iface_dbus = iface.getAttribute('name') + signals = node.getElementsByTagName('signal') + if not signals: + continue + name = node.getAttribute('name').replace('/', '').lower() + self.iface_uc = name.upper() + self.b(' if (quark == %s)' % self.get_iface_quark()) + self.b(' %s_add_signals_for_%s (proxy);' + % (self.prefix_lc, name)) + + self.b('}') + self.b('') + + self.h('G_END_DECLS') + self.h('') + + open(self.basename + '.h', 'w').write('\n'.join(self.__header)) + open(self.basename + '-body.h', 'w').write('\n'.join(self.__body)) + open(self.basename + '-gtk-doc.h', 'w').write('\n'.join(self.__docs)) + + +def types_to_gtypes(types): + return [type_to_gtype(t)[1] for t in types] + + +if __name__ == '__main__': + options, argv = gnu_getopt(sys.argv[1:], '', + ['group=', 'subclass=', 'subclass-assert=', + 'iface-quark-prefix=', 'tp-proxy-api=', + 'generate-reentrant', 'deprecate-reentrant=', + 'deprecation-attribute=']) + + opts = {} + + for option, value in options: + opts[option] = value + + dom = xml.dom.minidom.parse(argv[0]) + + Generator(dom, argv[1], argv[2], opts)() diff --git a/farstream/tools/glib-client-marshaller-gen.py b/farstream/tools/glib-client-marshaller-gen.py new file mode 100644 index 000000000..54447255b --- /dev/null +++ b/farstream/tools/glib-client-marshaller-gen.py @@ -0,0 +1,59 @@ +#!/usr/bin/python + +import sys +import xml.dom.minidom +from string import ascii_letters, digits + + +from libglibcodegen import signal_to_marshal_name + + +NS_TP = "http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + +class Generator(object): + + def __init__(self, dom, prefix): + self.dom = dom + self.marshallers = {} + self.prefix = prefix + + def do_signal(self, signal): + marshaller = signal_to_marshal_name(signal, self.prefix) + + assert '__' in marshaller + rhs = marshaller.split('__', 1)[1].split('_') + + self.marshallers[marshaller] = rhs + + def __call__(self): + signals = self.dom.getElementsByTagName('signal') + + for signal in signals: + self.do_signal(signal) + + print 'void' + print '%s_register_dbus_glib_marshallers (void)' % self.prefix + print '{' + + all = self.marshallers.keys() + all.sort() + for marshaller in all: + rhs = self.marshallers[marshaller] + + print ' dbus_g_object_register_marshaller (%s,' % marshaller + print ' G_TYPE_NONE, /* return */' + for type in rhs: + print ' G_TYPE_%s,' % type.replace('VOID', 'NONE') + print ' G_TYPE_INVALID);' + + print '}' + + +def types_to_gtypes(types): + return [type_to_gtype(t)[1] for t in types] + +if __name__ == '__main__': + argv = sys.argv[1:] + dom = xml.dom.minidom.parse(argv[0]) + + Generator(dom, argv[1])() diff --git a/farstream/tools/glib-ginterface-gen.py b/farstream/tools/glib-ginterface-gen.py new file mode 100644 index 000000000..04509fd79 --- /dev/null +++ b/farstream/tools/glib-ginterface-gen.py @@ -0,0 +1,820 @@ +#!/usr/bin/python + +# glib-ginterface-gen.py: service-side interface generator +# +# Generate dbus-glib 0.x service GInterfaces from the Telepathy specification. +# The master copy of this program is in the telepathy-glib repository - +# please make any changes there. +# +# Copyright (C) 2006, 2007 Collabora Limited +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import sys +import os.path +import xml.dom.minidom + +from libglibcodegen import Signature, type_to_gtype, cmp_by_name, \ + NS_TP, dbus_gutils_wincaps_to_uscore, \ + signal_to_marshal_name, method_to_glue_marshal_name + + +NS_TP = "http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + +class Generator(object): + + def __init__(self, dom, prefix, basename, signal_marshal_prefix, + headers, end_headers, not_implemented_func, + allow_havoc): + self.dom = dom + self.__header = [] + self.__body = [] + self.__docs = [] + + assert prefix.endswith('_') + assert not signal_marshal_prefix.endswith('_') + + # The main_prefix, sub_prefix thing is to get: + # FOO_ -> (FOO_, _) + # FOO_SVC_ -> (FOO_, _SVC_) + # but + # FOO_BAR/ -> (FOO_BAR_, _) + # FOO_BAR/SVC_ -> (FOO_BAR_, _SVC_) + + if '/' in prefix: + main_prefix, sub_prefix = prefix.upper().split('/', 1) + prefix = prefix.replace('/', '_') + else: + main_prefix, sub_prefix = prefix.upper().split('_', 1) + + self.MAIN_PREFIX_ = main_prefix + '_' + self._SUB_PREFIX_ = '_' + sub_prefix + + self.Prefix_ = prefix + self.Prefix = prefix.replace('_', '') + self.prefix_ = prefix.lower() + self.PREFIX_ = prefix.upper() + + self.basename = basename + self.signal_marshal_prefix = signal_marshal_prefix + self.headers = headers + self.end_headers = end_headers + self.not_implemented_func = not_implemented_func + self.allow_havoc = allow_havoc + + def h(self, s): + if isinstance(s, unicode): + s = s.encode('utf-8') + self.__header.append(s) + + def b(self, s): + if isinstance(s, unicode): + s = s.encode('utf-8') + self.__body.append(s) + + def d(self, s): + if isinstance(s, unicode): + s = s.encode('utf-8') + self.__docs.append(s) + + def do_node(self, node): + node_name = node.getAttribute('name').replace('/', '') + node_name_mixed = self.node_name_mixed = node_name.replace('_', '') + node_name_lc = self.node_name_lc = node_name.lower() + node_name_uc = self.node_name_uc = node_name.upper() + + interfaces = node.getElementsByTagName('interface') + assert len(interfaces) == 1, interfaces + interface = interfaces[0] + self.iface_name = interface.getAttribute('name') + + tmp = interface.getAttribute('tp:implement-service') + if tmp == "no": + return + + tmp = interface.getAttribute('tp:causes-havoc') + if tmp and not self.allow_havoc: + raise AssertionError('%s is %s' % (self.iface_name, tmp)) + + self.b('static const DBusGObjectInfo _%s%s_object_info;' + % (self.prefix_, node_name_lc)) + self.b('') + + methods = interface.getElementsByTagName('method') + signals = interface.getElementsByTagName('signal') + properties = interface.getElementsByTagName('property') + # Don't put properties in dbus-glib glue + glue_properties = [] + + self.b('struct _%s%sClass {' % (self.Prefix, node_name_mixed)) + self.b(' GTypeInterface parent_class;') + for method in methods: + self.b(' %s %s;' % self.get_method_impl_names(method)) + self.b('};') + self.b('') + + if signals: + self.b('enum {') + for signal in signals: + self.b(' %s,' % self.get_signal_const_entry(signal)) + self.b(' N_%s_SIGNALS' % node_name_uc) + self.b('};') + self.b('static guint %s_signals[N_%s_SIGNALS] = {0};' + % (node_name_lc, node_name_uc)) + self.b('') + + self.b('static void %s%s_base_init (gpointer klass);' + % (self.prefix_, node_name_lc)) + self.b('') + + self.b('GType') + self.b('%s%s_get_type (void)' + % (self.prefix_, node_name_lc)) + self.b('{') + self.b(' static GType type = 0;') + self.b('') + self.b(' if (G_UNLIKELY (type == 0))') + self.b(' {') + self.b(' static const GTypeInfo info = {') + self.b(' sizeof (%s%sClass),' % (self.Prefix, node_name_mixed)) + self.b(' %s%s_base_init, /* base_init */' + % (self.prefix_, node_name_lc)) + self.b(' NULL, /* base_finalize */') + self.b(' NULL, /* class_init */') + self.b(' NULL, /* class_finalize */') + self.b(' NULL, /* class_data */') + self.b(' 0,') + self.b(' 0, /* n_preallocs */') + self.b(' NULL /* instance_init */') + self.b(' };') + self.b('') + self.b(' type = g_type_register_static (G_TYPE_INTERFACE,') + self.b(' "%s%s", &info, 0);' % (self.Prefix, node_name_mixed)) + self.b(' }') + self.b('') + self.b(' return type;') + self.b('}') + self.b('') + + self.d('/**') + self.d(' * %s%s:' % (self.Prefix, node_name_mixed)) + self.d(' *') + self.d(' * Dummy typedef representing any implementation of this ' + 'interface.') + self.d(' */') + + self.h('typedef struct _%s%s %s%s;' + % (self.Prefix, node_name_mixed, self.Prefix, node_name_mixed)) + self.h('') + + self.d('/**') + self.d(' * %s%sClass:' % (self.Prefix, node_name_mixed)) + self.d(' *') + self.d(' * The class of %s%s.' % (self.Prefix, node_name_mixed)) + + if methods: + self.d(' *') + self.d(' * In a full implementation of this interface (i.e. all') + self.d(' * methods implemented), the interface initialization') + self.d(' * function used in G_IMPLEMENT_INTERFACE() would') + self.d(' * typically look like this:') + self.d(' *') + self.d(' * <programlisting>') + self.d(' * static void') + self.d(' * implement_%s (gpointer klass,' % self.node_name_lc) + self.d(' * gpointer unused G_GNUC_UNUSED)') + self.d(' * {') + self.d(' * #define IMPLEMENT(x) %s%s_implement_##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') + + self.b(' { 0, %s, "%s", 0, NULL, NULL }, /* %s */' + % (flags, m.getAttribute('type'), m.getAttribute('name'))) + + self.b(' { 0, 0, NULL, 0, NULL, NULL }') + self.b(' };') + self.b(' static TpDBusPropertiesMixinIfaceInfo interface =') + self.b(' { 0, properties, NULL, NULL };') + self.b('') + + + self.b(' dbus_g_object_type_install_info (%s%s_get_type (),' + % (self.prefix_, node_name_lc)) + self.b(' &_%s%s_object_info);' + % (self.prefix_, node_name_lc)) + self.b('') + + if properties: + self.b(' interface.dbus_interface = g_quark_from_static_string ' + '("%s");' % self.iface_name) + + for i, m in enumerate(properties): + self.b(' properties[%d].name = g_quark_from_static_string ("%s");' + % (i, m.getAttribute('name'))) + self.b(' properties[%d].type = %s;' + % (i, type_to_gtype(m.getAttribute('type'))[1])) + + self.b(' tp_svc_interface_set_dbus_properties_info (%s, &interface);' + % self.current_gtype) + + self.b('') + + for s in base_init_code: + self.b(s) + self.b('}') + + self.b('static void') + self.b('%s%s_base_init (gpointer klass)' + % (self.prefix_, node_name_lc)) + self.b('{') + self.b(' static gboolean initialized = FALSE;') + self.b('') + self.b(' if (!initialized)') + self.b(' {') + self.b(' initialized = TRUE;') + self.b(' %s%s_base_init_once (klass);' + % (self.prefix_, node_name_lc)) + self.b(' }') + # insert anything we need to do per implementation here + self.b('}') + + self.h('') + + self.b('static const DBusGMethodInfo _%s%s_methods[] = {' + % (self.prefix_, node_name_lc)) + + method_blob, offsets = self.get_method_glue(methods) + + for method, offset in zip(methods, offsets): + self.do_method_glue(method, offset) + + if len(methods) == 0: + # empty arrays are a gcc extension, so put in a dummy member + self.b(" { NULL, NULL, 0 }") + + self.b('};') + self.b('') + + self.b('static const DBusGObjectInfo _%s%s_object_info = {' + % (self.prefix_, node_name_lc)) + self.b(' 0,') # version + self.b(' _%s%s_methods,' % (self.prefix_, node_name_lc)) + self.b(' %d,' % len(methods)) + self.b('"' + method_blob.replace('\0', '\\0') + '",') + self.b('"' + self.get_signal_glue(signals).replace('\0', '\\0') + '",') + self.b('"' + + self.get_property_glue(glue_properties).replace('\0', '\\0') + + '",') + self.b('};') + self.b('') + + self.node_name_mixed = None + self.node_name_lc = None + self.node_name_uc = None + + def get_method_glue(self, methods): + info = [] + offsets = [] + + for method in methods: + offsets.append(len(''.join(info))) + + info.append(self.iface_name + '\0') + info.append(method.getAttribute('name') + '\0') + + info.append('A\0') # async + + counter = 0 + for arg in method.getElementsByTagName('arg'): + out = arg.getAttribute('direction') == 'out' + + name = arg.getAttribute('name') + if not name: + assert out + name = 'arg%u' % counter + counter += 1 + + info.append(name + '\0') + + if out: + info.append('O\0') + else: + info.append('I\0') + + if out: + info.append('F\0') # not const + info.append('N\0') # not error or return + info.append(arg.getAttribute('type') + '\0') + + info.append('\0') + + return ''.join(info) + '\0', offsets + + def do_method_glue(self, method, offset): + lc_name = method.getAttribute('tp:name-for-bindings') + if method.getAttribute('name') != lc_name.replace('_', ''): + raise AssertionError('Method %s tp:name-for-bindings (%s) does ' + 'not match' % (method.getAttribute('name'), lc_name)) + lc_name = lc_name.lower() + + marshaller = method_to_glue_marshal_name(method, + self.signal_marshal_prefix) + wrapper = self.prefix_ + self.node_name_lc + '_' + lc_name + + self.b(" { (GCallback) %s, %s, %d }," % (wrapper, marshaller, offset)) + + def get_signal_glue(self, signals): + info = [] + + for signal in signals: + info.append(self.iface_name) + info.append(signal.getAttribute('name')) + + return '\0'.join(info) + '\0\0' + + # the implementation can be the same + get_property_glue = get_signal_glue + + def get_method_impl_names(self, method): + dbus_method_name = method.getAttribute('name') + + class_member_name = method.getAttribute('tp:name-for-bindings') + if dbus_method_name != class_member_name.replace('_', ''): + raise AssertionError('Method %s tp:name-for-bindings (%s) does ' + 'not match' % (dbus_method_name, class_member_name)) + class_member_name = class_member_name.lower() + + stub_name = (self.prefix_ + self.node_name_lc + '_' + + class_member_name) + return (stub_name + '_impl', class_member_name + '_cb') + + def do_method(self, method): + assert self.node_name_mixed is not None + + in_class = [] + + # Examples refer to Thing.DoStuff (su) -> ii + + # DoStuff + dbus_method_name = method.getAttribute('name') + # do_stuff + class_member_name = method.getAttribute('tp:name-for-bindings') + if dbus_method_name != class_member_name.replace('_', ''): + raise AssertionError('Method %s tp:name-for-bindings (%s) does ' + 'not match' % (dbus_method_name, class_member_name)) + class_member_name = class_member_name.lower() + + # void tp_svc_thing_do_stuff (TpSvcThing *, const char *, guint, + # DBusGMethodInvocation *); + stub_name = (self.prefix_ + self.node_name_lc + '_' + + class_member_name) + # typedef void (*tp_svc_thing_do_stuff_impl) (TpSvcThing *, + # const char *, guint, DBusGMethodInvocation); + impl_name = stub_name + '_impl' + # void tp_svc_thing_return_from_do_stuff (DBusGMethodInvocation *, + # gint, gint); + ret_name = (self.prefix_ + self.node_name_lc + '_return_from_' + + class_member_name) + + # Gather arguments + in_args = [] + out_args = [] + for i in method.getElementsByTagName('arg'): + name = i.getAttribute('name') + direction = i.getAttribute('direction') or 'in' + dtype = i.getAttribute('type') + + assert direction in ('in', 'out') + + if name: + name = direction + '_' + name + elif direction == 'in': + name = direction + str(len(in_args)) + else: + name = direction + str(len(out_args)) + + ctype, gtype, marshaller, pointer = type_to_gtype(dtype) + + if pointer: + ctype = 'const ' + ctype + + struct = (ctype, name) + + if direction == 'in': + in_args.append(struct) + else: + out_args.append(struct) + + # Implementation type declaration (in header, docs separated) + self.d('/**') + self.d(' * %s:' % impl_name) + self.d(' * @self: The object implementing this interface') + for (ctype, name) in in_args: + self.d(' * @%s: %s (FIXME, generate documentation)' + % (name, ctype)) + self.d(' * @context: Used to return values or throw an error') + self.d(' *') + self.d(' * The signature of an implementation of the D-Bus method') + self.d(' * %s on interface %s.' % (dbus_method_name, self.iface_name)) + self.d(' */') + + self.h('typedef void (*%s) (%s%s *self,' + % (impl_name, self.Prefix, self.node_name_mixed)) + for (ctype, name) in in_args: + self.h(' %s%s,' % (ctype, name)) + self.h(' DBusGMethodInvocation *context);') + + # Class member (in class definition) + in_class.append(' %s %s;' % (impl_name, class_member_name)) + + # Stub definition (in body only - it's static) + self.b('static void') + self.b('%s (%s%s *self,' + % (stub_name, self.Prefix, self.node_name_mixed)) + for (ctype, name) in in_args: + self.b(' %s%s,' % (ctype, name)) + self.b(' DBusGMethodInvocation *context)') + self.b('{') + self.b(' %s impl = (%s%s_GET_CLASS (self)->%s_cb);' + % (impl_name, self.PREFIX_, self.node_name_uc, class_member_name)) + self.b('') + self.b(' if (impl != NULL)') + tmp = ['self'] + [name for (ctype, name) in in_args] + ['context'] + self.b(' {') + self.b(' (impl) (%s);' % ',\n '.join(tmp)) + self.b(' }') + self.b(' else') + self.b(' {') + if self.not_implemented_func: + self.b(' %s (context);' % self.not_implemented_func) + else: + self.b(' GError e = { DBUS_GERROR, ') + self.b(' DBUS_GERROR_UNKNOWN_METHOD,') + self.b(' "Method not implemented" };') + self.b('') + self.b(' dbus_g_method_return_error (context, &e);') + self.b(' }') + self.b('}') + self.b('') + + # Implementation registration (in both header and body) + self.h('void %s%s_implement_%s (%s%sClass *klass, %s impl);' + % (self.prefix_, self.node_name_lc, class_member_name, + self.Prefix, self.node_name_mixed, impl_name)) + + self.d('/**') + self.d(' * %s%s_implement_%s:' + % (self.prefix_, self.node_name_lc, class_member_name)) + self.d(' * @klass: A class whose instances implement this interface') + self.d(' * @impl: A callback used to implement the %s D-Bus method' + % dbus_method_name) + self.d(' *') + self.d(' * Register an implementation for the %s method in the vtable' + % dbus_method_name) + self.d(' * of an implementation of this interface. To be called from') + self.d(' * the interface init function.') + self.d(' */') + + self.b('void') + self.b('%s%s_implement_%s (%s%sClass *klass, %s impl)' + % (self.prefix_, self.node_name_lc, class_member_name, + self.Prefix, self.node_name_mixed, impl_name)) + self.b('{') + self.b(' klass->%s_cb = impl;' % class_member_name) + self.b('}') + self.b('') + + # Return convenience function (static inline, in header) + self.d('/**') + self.d(' * %s:' % ret_name) + self.d(' * @context: The D-Bus method invocation context') + for (ctype, name) in out_args: + self.d(' * @%s: %s (FIXME, generate documentation)' + % (name, ctype)) + self.d(' *') + self.d(' * Return successfully by calling dbus_g_method_return().') + self.d(' * This inline function exists only to provide type-safety.') + self.d(' */') + self.d('') + + tmp = (['DBusGMethodInvocation *context'] + + [ctype + name for (ctype, name) in out_args]) + self.h('static inline') + self.h('/* this comment is to stop gtkdoc realising this is static */') + self.h(('void %s (' % ret_name) + (',\n '.join(tmp)) + ');') + self.h('static inline void') + self.h(('%s (' % ret_name) + (',\n '.join(tmp)) + ')') + self.h('{') + tmp = ['context'] + [name for (ctype, name) in out_args] + self.h(' dbus_g_method_return (' + ',\n '.join(tmp) + ');') + self.h('}') + self.h('') + + return in_class + + def get_signal_const_entry(self, signal): + assert self.node_name_uc is not None + return ('SIGNAL_%s_%s' + % (self.node_name_uc, signal.getAttribute('name'))) + + def do_signal(self, signal): + assert self.node_name_mixed is not None + + in_base_init = [] + + # for signal: Thing::StuffHappened (s, u) + # we want to emit: + # void tp_svc_thing_emit_stuff_happened (gpointer instance, + # const char *arg0, guint arg1); + + dbus_name = signal.getAttribute('name') + + ugly_name = signal.getAttribute('tp:name-for-bindings') + if dbus_name != ugly_name.replace('_', ''): + raise AssertionError('Signal %s tp:name-for-bindings (%s) does ' + 'not match' % (dbus_name, ugly_name)) + + stub_name = (self.prefix_ + self.node_name_lc + '_emit_' + + ugly_name.lower()) + + const_name = self.get_signal_const_entry(signal) + + # Gather arguments + args = [] + for i in signal.getElementsByTagName('arg'): + name = i.getAttribute('name') + dtype = i.getAttribute('type') + tp_type = i.getAttribute('tp:type') + + if name: + name = 'arg_' + name + else: + name = 'arg' + str(len(args)) + + ctype, gtype, marshaller, pointer = type_to_gtype(dtype) + + if pointer: + ctype = 'const ' + ctype + + struct = (ctype, name, gtype) + args.append(struct) + + tmp = (['gpointer instance'] + + [ctype + name for (ctype, name, gtype) in args]) + + self.h(('void %s (' % stub_name) + (',\n '.join(tmp)) + ');') + + # FIXME: emit docs + + self.d('/**') + self.d(' * %s:' % stub_name) + self.d(' * @instance: The object implementing this interface') + for (ctype, name, gtype) in args: + self.d(' * @%s: %s (FIXME, generate documentation)' + % (name, ctype)) + self.d(' *') + self.d(' * Type-safe wrapper around g_signal_emit to emit the') + self.d(' * %s signal on interface %s.' + % (dbus_name, self.iface_name)) + self.d(' */') + + self.b('void') + self.b(('%s (' % stub_name) + (',\n '.join(tmp)) + ')') + self.b('{') + self.b(' g_assert (instance != NULL);') + self.b(' g_assert (G_TYPE_CHECK_INSTANCE_TYPE (instance, %s));' + % (self.current_gtype)) + tmp = (['instance', '%s_signals[%s]' % (self.node_name_lc, const_name), + '0'] + [name for (ctype, name, gtype) in args]) + self.b(' g_signal_emit (' + ',\n '.join(tmp) + ');') + self.b('}') + self.b('') + + signal_name = dbus_gutils_wincaps_to_uscore(dbus_name).replace('_', + '-') + + self.d('/**') + self.d(' * %s%s::%s:' + % (self.Prefix, self.node_name_mixed, signal_name)) + for (ctype, name, gtype) in args: + self.d(' * @%s: %s (FIXME, generate documentation)' + % (name, ctype)) + self.d(' *') + self.d(' * The %s D-Bus signal is emitted whenever ' + 'this GObject signal is.' % dbus_name) + self.d(' */') + self.d('') + + in_base_init.append(' %s_signals[%s] =' + % (self.node_name_lc, const_name)) + in_base_init.append(' g_signal_new ("%s",' % signal_name) + in_base_init.append(' G_OBJECT_CLASS_TYPE (klass),') + in_base_init.append(' G_SIGNAL_RUN_LAST|G_SIGNAL_DETAILED,') + in_base_init.append(' 0,') + in_base_init.append(' NULL, NULL,') + in_base_init.append(' %s,' + % signal_to_marshal_name(signal, self.signal_marshal_prefix)) + in_base_init.append(' G_TYPE_NONE,') + tmp = ['%d' % len(args)] + [gtype for (ctype, name, gtype) in args] + in_base_init.append(' %s);' % ',\n '.join(tmp)) + in_base_init.append('') + + return in_base_init + + def have_properties(self, nodes): + for node in nodes: + interface = node.getElementsByTagName('interface')[0] + if interface.getElementsByTagName('property'): + return True + return False + + def __call__(self): + nodes = self.dom.getElementsByTagName('node') + nodes.sort(cmp_by_name) + + self.h('#include <glib-object.h>') + self.h('#include <dbus/dbus-glib.h>') + + if self.have_properties(nodes): + self.h('#include <telepathy-glib/dbus-properties-mixin.h>') + + self.h('') + self.h('G_BEGIN_DECLS') + self.h('') + + self.b('#include "%s.h"' % self.basename) + self.b('') + for header in self.headers: + self.b('#include %s' % header) + self.b('') + + for node in nodes: + self.do_node(node) + + self.h('') + self.h('G_END_DECLS') + + self.b('') + for header in self.end_headers: + self.b('#include %s' % header) + + self.h('') + self.b('') + open(self.basename + '.h', 'w').write('\n'.join(self.__header)) + open(self.basename + '.c', 'w').write('\n'.join(self.__body)) + open(self.basename + '-gtk-doc.h', 'w').write('\n'.join(self.__docs)) + + +def cmdline_error(): + print """\ +usage: + gen-ginterface [OPTIONS] xmlfile Prefix_ +options: + --include='<header.h>' (may be repeated) + --include='"header.h"' (ditto) + --include-end='"header.h"' (ditto) + Include extra headers in the generated .c file + --signal-marshal-prefix='prefix' + Use the given prefix on generated signal marshallers (default is + prefix.lower()). + --filename='BASENAME' + Set the basename for the output files (default is prefix.lower() + + 'ginterfaces') + --not-implemented-func='symbol' + Set action when methods not implemented in the interface vtable are + called. symbol must have signature + void symbol (DBusGMethodInvocation *context) + and return some sort of "not implemented" error via + dbus_g_method_return_error (context, ...) +""" + sys.exit(1) + + +if __name__ == '__main__': + from getopt import gnu_getopt + + options, argv = gnu_getopt(sys.argv[1:], '', + ['filename=', 'signal-marshal-prefix=', + 'include=', 'include-end=', + 'allow-unstable', + 'not-implemented-func=']) + + try: + prefix = argv[1] + except IndexError: + cmdline_error() + + basename = prefix.lower() + 'ginterfaces' + signal_marshal_prefix = prefix.lower().rstrip('_') + headers = [] + end_headers = [] + not_implemented_func = '' + allow_havoc = False + + for option, value in options: + if option == '--filename': + basename = value + elif option == '--signal-marshal-prefix': + signal_marshal_prefix = value + elif option == '--include': + if value[0] not in '<"': + value = '"%s"' % value + headers.append(value) + elif option == '--include-end': + if value[0] not in '<"': + value = '"%s"' % value + end_headers.append(value) + elif option == '--not-implemented-func': + not_implemented_func = value + elif option == '--allow-unstable': + allow_havoc = True + + try: + dom = xml.dom.minidom.parse(argv[0]) + except IndexError: + cmdline_error() + + Generator(dom, prefix, basename, signal_marshal_prefix, headers, + end_headers, not_implemented_func, allow_havoc)() diff --git a/farstream/tools/glib-gtypes-generator.py b/farstream/tools/glib-gtypes-generator.py new file mode 100644 index 000000000..a49c36e7f --- /dev/null +++ b/farstream/tools/glib-gtypes-generator.py @@ -0,0 +1,298 @@ +#!/usr/bin/python + +# Generate GLib GInterfaces from the Telepathy specification. +# The master copy of this program is in the telepathy-glib repository - +# please make any changes there. +# +# Copyright (C) 2006, 2007 Collabora Limited +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import sys +import xml.dom.minidom + +from libglibcodegen import escape_as_identifier, \ + get_docstring, \ + NS_TP, \ + Signature, \ + type_to_gtype, \ + xml_escape + + +def types_to_gtypes(types): + return [type_to_gtype(t)[1] for t in types] + + +class GTypesGenerator(object): + def __init__(self, dom, output, mixed_case_prefix): + self.dom = dom + self.Prefix = mixed_case_prefix + self.PREFIX_ = self.Prefix.upper() + '_' + self.prefix_ = self.Prefix.lower() + '_' + + self.header = open(output + '.h', 'w') + self.body = open(output + '-body.h', 'w') + self.docs = open(output + '-gtk-doc.h', 'w') + + for f in (self.header, self.body, self.docs): + f.write('/* Auto-generated, do not edit.\n *\n' + ' * This file may be distributed under the same terms\n' + ' * as the specification from which it was generated.\n' + ' */\n\n') + + # keys are e.g. 'sv', values are the key escaped + self.need_mappings = {} + # keys are the contents of the struct (e.g. 'sssu'), values are the + # key escaped + self.need_structs = {} + # keys are the contents of the struct (e.g. 'sssu'), values are the + # key escaped + self.need_struct_arrays = {} + + # keys are the contents of the array (unlike need_struct_arrays!), + # values are the key escaped + self.need_other_arrays = {} + + def h(self, code): + self.header.write(code.encode("utf-8")) + + def c(self, code): + self.body.write(code.encode("utf-8")) + + def d(self, code): + self.docs.write(code.encode('utf-8')) + + def do_mapping_header(self, mapping): + members = mapping.getElementsByTagNameNS(NS_TP, 'member') + assert len(members) == 2 + + impl_sig = ''.join([elt.getAttribute('type') + for elt in members]) + + esc_impl_sig = escape_as_identifier(impl_sig) + + name = (self.PREFIX_ + 'HASH_TYPE_' + + mapping.getAttribute('name').upper()) + impl = self.prefix_ + 'type_dbus_hash_' + esc_impl_sig + + docstring = get_docstring(mapping) or '(Undocumented)' + + self.d('/**\n * %s:\n *\n' % name) + self.d(' * %s\n' % xml_escape(docstring)) + self.d(' *\n') + self.d(' * This macro expands to a call to a function\n') + self.d(' * that returns the #GType of a #GHashTable\n') + self.d(' * appropriate for representing a D-Bus\n') + self.d(' * dictionary of signature\n') + self.d(' * <literal>a{%s}</literal>.\n' % impl_sig) + self.d(' *\n') + + key, value = members + + self.d(' * Keys (D-Bus type <literal>%s</literal>,\n' + % key.getAttribute('type')) + tp_type = key.getAttributeNS(NS_TP, 'type') + if tp_type: + self.d(' * type <literal>%s</literal>,\n' % tp_type) + self.d(' * named <literal>%s</literal>):\n' + % key.getAttribute('name')) + docstring = get_docstring(key) or '(Undocumented)' + self.d(' * %s\n' % xml_escape(docstring)) + self.d(' *\n') + + self.d(' * Values (D-Bus type <literal>%s</literal>,\n' + % value.getAttribute('type')) + tp_type = value.getAttributeNS(NS_TP, 'type') + if tp_type: + self.d(' * type <literal>%s</literal>,\n' % tp_type) + self.d(' * named <literal>%s</literal>):\n' + % value.getAttribute('name')) + docstring = get_docstring(value) or '(Undocumented)' + self.d(' * %s\n' % xml_escape(docstring)) + self.d(' *\n') + + self.d(' */\n') + + self.h('#define %s (%s ())\n\n' % (name, impl)) + self.need_mappings[impl_sig] = esc_impl_sig + + array_name = mapping.getAttribute('array-name') + if array_name: + gtype_name = self.PREFIX_ + 'ARRAY_TYPE_' + array_name.upper() + contents_sig = 'a{' + impl_sig + '}' + esc_contents_sig = escape_as_identifier(contents_sig) + impl = self.prefix_ + 'type_dbus_array_of_' + esc_contents_sig + self.d('/**\n * %s:\n\n' % gtype_name) + self.d(' * Expands to a call to a function\n') + self.d(' * that returns the #GType of a #GPtrArray\n') + self.d(' * of #%s.\n' % name) + self.d(' */\n\n') + + self.h('#define %s (%s ())\n\n' % (gtype_name, impl)) + self.need_other_arrays[contents_sig] = esc_contents_sig + + def do_struct_header(self, struct): + members = struct.getElementsByTagNameNS(NS_TP, 'member') + impl_sig = ''.join([elt.getAttribute('type') for elt in members]) + esc_impl_sig = escape_as_identifier(impl_sig) + + name = (self.PREFIX_ + 'STRUCT_TYPE_' + + struct.getAttribute('name').upper()) + impl = self.prefix_ + 'type_dbus_struct_' + esc_impl_sig + docstring = struct.getElementsByTagNameNS(NS_TP, 'docstring') + if docstring: + docstring = docstring[0].toprettyxml() + if docstring.startswith('<tp:docstring>'): + docstring = docstring[14:] + if docstring.endswith('</tp:docstring>\n'): + docstring = docstring[:-16] + if docstring.strip() in ('<tp:docstring/>', ''): + docstring = '(Undocumented)' + else: + docstring = '(Undocumented)' + self.d('/**\n * %s:\n\n' % name) + self.d(' * %s\n' % xml_escape(docstring)) + self.d(' *\n') + self.d(' * This macro expands to a call to a function\n') + self.d(' * that returns the #GType of a #GValueArray\n') + self.d(' * appropriate for representing a D-Bus struct\n') + self.d(' * with signature <literal>(%s)</literal>.\n' + % impl_sig) + self.d(' *\n') + + for i, member in enumerate(members): + self.d(' * Member %d (D-Bus type ' + '<literal>%s</literal>,\n' + % (i, member.getAttribute('type'))) + tp_type = member.getAttributeNS(NS_TP, 'type') + if tp_type: + self.d(' * type <literal>%s</literal>,\n' % tp_type) + self.d(' * named <literal>%s</literal>):\n' + % member.getAttribute('name')) + docstring = get_docstring(member) or '(Undocumented)' + self.d(' * %s\n' % xml_escape(docstring)) + self.d(' *\n') + + self.d(' */\n\n') + + self.h('#define %s (%s ())\n\n' % (name, impl)) + + array_name = struct.getAttribute('array-name') + if array_name != '': + array_name = (self.PREFIX_ + 'ARRAY_TYPE_' + array_name.upper()) + impl = self.prefix_ + 'type_dbus_array_' + esc_impl_sig + self.d('/**\n * %s:\n\n' % array_name) + self.d(' * Expands to a call to a function\n') + self.d(' * that returns the #GType of a #GPtrArray\n') + self.d(' * of #%s.\n' % name) + self.d(' */\n\n') + + self.h('#define %s (%s ())\n\n' % (array_name, impl)) + self.need_struct_arrays[impl_sig] = esc_impl_sig + + self.need_structs[impl_sig] = esc_impl_sig + + def __call__(self): + mappings = self.dom.getElementsByTagNameNS(NS_TP, 'mapping') + structs = self.dom.getElementsByTagNameNS(NS_TP, 'struct') + + for mapping in mappings: + self.do_mapping_header(mapping) + + for sig in self.need_mappings: + self.h('GType %stype_dbus_hash_%s (void);\n\n' % + (self.prefix_, self.need_mappings[sig])) + self.c('GType\n%stype_dbus_hash_%s (void)\n{\n' % + (self.prefix_, self.need_mappings[sig])) + self.c(' static GType t = 0;\n\n') + self.c(' if (G_UNLIKELY (t == 0))\n') + # FIXME: translate sig into two GTypes + items = tuple(Signature(sig)) + gtypes = types_to_gtypes(items) + self.c(' t = dbus_g_type_get_map ("GHashTable", ' + '%s, %s);\n' % (gtypes[0], gtypes[1])) + self.c(' return t;\n') + self.c('}\n\n') + + for struct in structs: + self.do_struct_header(struct) + + for sig in self.need_structs: + self.h('GType %stype_dbus_struct_%s (void);\n\n' % + (self.prefix_, self.need_structs[sig])) + self.c('GType\n%stype_dbus_struct_%s (void)\n{\n' % + (self.prefix_, self.need_structs[sig])) + self.c(' static GType t = 0;\n\n') + self.c(' if (G_UNLIKELY (t == 0))\n') + self.c(' t = dbus_g_type_get_struct ("GValueArray",\n') + items = tuple(Signature(sig)) + gtypes = types_to_gtypes(items) + for gtype in gtypes: + self.c(' %s,\n' % gtype) + self.c(' G_TYPE_INVALID);\n') + self.c(' return t;\n') + self.c('}\n\n') + + for sig in self.need_struct_arrays: + self.h('GType %stype_dbus_array_%s (void);\n\n' % + (self.prefix_, self.need_struct_arrays[sig])) + self.c('GType\n%stype_dbus_array_%s (void)\n{\n' % + (self.prefix_, self.need_struct_arrays[sig])) + self.c(' static GType t = 0;\n\n') + self.c(' if (G_UNLIKELY (t == 0))\n') + self.c(' t = dbus_g_type_get_collection ("GPtrArray", ' + '%stype_dbus_struct_%s ());\n' % + (self.prefix_, self.need_struct_arrays[sig])) + self.c(' return t;\n') + self.c('}\n\n') + + for sig in self.need_other_arrays: + self.h('GType %stype_dbus_array_of_%s (void);\n\n' % + (self.prefix_, self.need_other_arrays[sig])) + self.c('GType\n%stype_dbus_array_of_%s (void)\n{\n' % + (self.prefix_, self.need_other_arrays[sig])) + self.c(' static GType t = 0;\n\n') + self.c(' if (G_UNLIKELY (t == 0))\n') + + if sig[:2] == 'a{' and sig[-1:] == '}': + # array of mappings + self.c(' t = dbus_g_type_get_collection (' + '"GPtrArray", ' + '%stype_dbus_hash_%s ());\n' % + (self.prefix_, escape_as_identifier(sig[2:-1]))) + elif sig[:2] == 'a(' and sig[-1:] == ')': + # array of arrays of struct + self.c(' t = dbus_g_type_get_collection (' + '"GPtrArray", ' + '%stype_dbus_array_%s ());\n' % + (self.prefix_, escape_as_identifier(sig[2:-1]))) + elif sig[:1] == 'a': + # array of arrays of non-struct + self.c(' t = dbus_g_type_get_collection (' + '"GPtrArray", ' + '%stype_dbus_array_of_%s ());\n' % + (self.prefix_, escape_as_identifier(sig[1:]))) + else: + raise AssertionError("array of '%s' not supported" % sig) + + self.c(' return t;\n') + self.c('}\n\n') + +if __name__ == '__main__': + argv = sys.argv[1:] + + dom = xml.dom.minidom.parse(argv[0]) + + GTypesGenerator(dom, argv[1], argv[2])() diff --git a/farstream/tools/glib-interfaces-gen.py b/farstream/tools/glib-interfaces-gen.py new file mode 100644 index 000000000..69c721be3 --- /dev/null +++ b/farstream/tools/glib-interfaces-gen.py @@ -0,0 +1,197 @@ +#!/usr/bin/python + +from sys import argv, stdout, stderr +import xml.dom.minidom + +from libglibcodegen import NS_TP, get_docstring, \ + get_descendant_text, get_by_path + +class Generator(object): + def __init__(self, prefix, implfile, declfile, dom): + self.prefix = prefix + '_' + + assert declfile.endswith('.h') + docfile = declfile[:-2] + '-gtk-doc.h' + + self.impls = open(implfile, 'w') + self.decls = open(declfile, 'w') + self.docs = open(docfile, 'w') + self.spec = get_by_path(dom, "spec")[0] + + def h(self, code): + self.decls.write(code.encode('utf-8')) + + def c(self, code): + self.impls.write(code.encode('utf-8')) + + def d(self, code): + self.docs.write(code.encode('utf-8')) + + def __call__(self): + for f in self.h, self.c: + self.do_header(f) + self.do_body() + + # Header + def do_header(self, f): + f('/* Generated from: ') + f(get_descendant_text(get_by_path(self.spec, 'title'))) + version = get_by_path(self.spec, "version") + if version: + f(' version ' + get_descendant_text(version)) + f('\n\n') + for copyright in get_by_path(self.spec, 'copyright'): + f(get_descendant_text(copyright)) + f('\n') + f('\n') + f(get_descendant_text(get_by_path(self.spec, 'license'))) + f(get_descendant_text(get_by_path(self.spec, 'docstring'))) + f(""" + */ + +""") + + # Body + def do_body(self): + for iface in self.spec.getElementsByTagName('interface'): + self.do_iface(iface) + + def do_iface(self, iface): + parent_name = get_by_path(iface, '../@name') + self.d("""\ +/** + * %(IFACE_DEFINE)s: + * + * The interface name "%(name)s" + */ +""" % {'IFACE_DEFINE' : (self.prefix + 'IFACE_' + \ + parent_name).upper().replace('/', ''), + 'name' : iface.getAttribute('name')}) + + self.h(""" +#define %(IFACE_DEFINE)s \\ +"%(name)s" +""" % {'IFACE_DEFINE' : (self.prefix + 'IFACE_' + \ + parent_name).upper().replace('/', ''), + 'name' : iface.getAttribute('name')}) + + self.d(""" +/** + * %(IFACE_QUARK_DEFINE)s: + * + * Expands to a call to a function that returns a quark for the interface \ +name "%(name)s" + */ +""" % {'IFACE_QUARK_DEFINE' : (self.prefix + 'IFACE_QUARK_' + \ + parent_name).upper().replace('/', ''), + 'iface_quark_func' : (self.prefix + 'iface_quark_' + \ + parent_name).lower().replace('/', ''), + 'name' : iface.getAttribute('name')}) + + self.h(""" +#define %(IFACE_QUARK_DEFINE)s \\ + (%(iface_quark_func)s ()) + +GQuark %(iface_quark_func)s (void); + +""" % {'IFACE_QUARK_DEFINE' : (self.prefix + 'IFACE_QUARK_' + \ + parent_name).upper().replace('/', ''), + 'iface_quark_func' : (self.prefix + 'iface_quark_' + \ + parent_name).lower().replace('/', ''), + 'name' : iface.getAttribute('name')}) + + self.c("""\ +GQuark +%(iface_quark_func)s (void) +{ + static GQuark quark = 0; + + if (G_UNLIKELY (quark == 0)) + { + quark = g_quark_from_static_string ("%(name)s"); + } + + return quark; +} + +""" % {'iface_quark_func' : (self.prefix + 'iface_quark_' + \ + parent_name).lower().replace('/', ''), + 'name' : iface.getAttribute('name')}) + + for prop in iface.getElementsByTagNameNS(None, 'property'): + self.d(""" +/** + * %(IFACE_PREFIX)s_%(PROP_UC)s: + * + * The fully-qualified property name "%(name)s.%(prop)s" + */ +""" % {'IFACE_PREFIX' : (self.prefix + 'PROP_' + \ + parent_name).upper().replace('/', ''), + 'PROP_UC': prop.getAttributeNS(NS_TP, "name-for-bindings").upper(), + 'name' : iface.getAttribute('name'), + 'prop' : prop.getAttribute('name'), + }) + + self.h(""" +#define %(IFACE_PREFIX)s_%(PROP_UC)s \\ +"%(name)s.%(prop)s" +""" % {'IFACE_PREFIX' : (self.prefix + 'PROP_' + \ + parent_name).upper().replace('/', ''), + 'PROP_UC': prop.getAttributeNS(NS_TP, "name-for-bindings").upper(), + 'name' : iface.getAttribute('name'), + 'prop' : prop.getAttribute('name'), + }) + + + for prop in iface.getElementsByTagNameNS(NS_TP, 'contact-attribute'): + self.d(""" +/** + * %(TOKEN_PREFIX)s_%(TOKEN_UC)s: + * + * The fully-qualified contact attribute token name "%(name)s/%(prop)s" + */ +""" % {'TOKEN_PREFIX' : (self.prefix + 'TOKEN_' + \ + parent_name).upper().replace('/', ''), + 'TOKEN_UC': prop.getAttributeNS(None, "name").upper().replace("-", "_").replace(".", "_"), + 'name' : iface.getAttribute('name'), + 'prop' : prop.getAttribute('name'), + }) + + self.h(""" +#define %(TOKEN_PREFIX)s_%(TOKEN_UC)s \\ +"%(name)s/%(prop)s" +""" % {'TOKEN_PREFIX' : (self.prefix + 'TOKEN_' + \ + parent_name).upper().replace('/', ''), + 'TOKEN_UC': prop.getAttributeNS(None, "name").upper().replace("-", "_").replace(".", "_"), + 'name' : iface.getAttribute('name'), + 'prop' : prop.getAttribute('name'), + }) + + for prop in iface.getElementsByTagNameNS(NS_TP, 'hct'): + if (prop.getAttribute('is-family') != "yes"): + self.d(""" +/** + * %(TOKEN_PREFIX)s_%(TOKEN_UC)s: + * + * The fully-qualified capability token name "%(name)s/%(prop)s" + */ +""" % {'TOKEN_PREFIX' : (self.prefix + 'TOKEN_' + \ + parent_name).upper().replace('/', ''), + 'TOKEN_UC': prop.getAttributeNS(None, "name").upper().replace("-", "_").replace(".", "_"), + 'name' : iface.getAttribute('name'), + 'prop' : prop.getAttribute('name'), + }) + + self.h(""" +#define %(TOKEN_PREFIX)s_%(TOKEN_UC)s \\ +"%(name)s/%(prop)s" +""" % {'TOKEN_PREFIX' : (self.prefix + 'TOKEN_' + \ + parent_name).upper().replace('/', ''), + 'TOKEN_UC': prop.getAttributeNS(None, "name").upper().replace("-", "_").replace(".", "_"), + 'name' : iface.getAttribute('name'), + 'prop' : prop.getAttribute('name'), + }) + +if __name__ == '__main__': + argv = argv[1:] + Generator(argv[0], argv[1], argv[2], xml.dom.minidom.parse(argv[3]))() diff --git a/farstream/tools/glib-signals-marshal-gen.py b/farstream/tools/glib-signals-marshal-gen.py new file mode 100644 index 000000000..0d02c1341 --- /dev/null +++ b/farstream/tools/glib-signals-marshal-gen.py @@ -0,0 +1,55 @@ +#!/usr/bin/python + +import sys +import xml.dom.minidom +from string import ascii_letters, digits + + +from libglibcodegen import signal_to_marshal_name, method_to_glue_marshal_name + + +class Generator(object): + + def __init__(self, dom): + self.dom = dom + self.marshallers = {} + + def do_method(self, method): + marshaller = method_to_glue_marshal_name(method, 'PREFIX') + + assert '__' in marshaller + rhs = marshaller.split('__', 1)[1].split('_') + + self.marshallers[marshaller] = rhs + + def do_signal(self, signal): + marshaller = signal_to_marshal_name(signal, 'PREFIX') + + assert '__' in marshaller + rhs = marshaller.split('__', 1)[1].split('_') + + self.marshallers[marshaller] = rhs + + def __call__(self): + methods = self.dom.getElementsByTagName('method') + + for method in methods: + self.do_method(method) + + signals = self.dom.getElementsByTagName('signal') + + for signal in signals: + self.do_signal(signal) + + all = self.marshallers.keys() + all.sort() + for marshaller in all: + rhs = self.marshallers[marshaller] + if not marshaller.startswith('g_cclosure'): + print 'VOID:' + ','.join(rhs) + +if __name__ == '__main__': + argv = sys.argv[1:] + dom = xml.dom.minidom.parse(argv[0]) + + Generator(dom)() diff --git a/farstream/tools/libglibcodegen.py b/farstream/tools/libglibcodegen.py new file mode 100644 index 000000000..6a9d21485 --- /dev/null +++ b/farstream/tools/libglibcodegen.py @@ -0,0 +1,172 @@ +"""Library code for GLib/D-Bus-related code generation. + +The master copy of this library is in the telepathy-glib repository - +please make any changes there. +""" + +# Copyright (C) 2006-2008 Collabora Limited +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +from libtpcodegen import NS_TP, \ + Signature, \ + cmp_by_name, \ + escape_as_identifier, \ + get_by_path, \ + get_descendant_text, \ + get_docstring, \ + xml_escape, \ + get_deprecated + +def dbus_gutils_wincaps_to_uscore(s): + """Bug-for-bug compatible Python port of _dbus_gutils_wincaps_to_uscore + which gets sequences of capital letters wrong in the same way. + (e.g. in Telepathy, SendDTMF -> send_dt_mf) + """ + ret = '' + for c in s: + if c >= 'A' and c <= 'Z': + length = len(ret) + if length > 0 and (length < 2 or ret[length-2] != '_'): + ret += '_' + ret += c.lower() + else: + ret += c + return ret + + +def signal_to_marshal_type(signal): + """ + return a list of strings indicating the marshalling type for this signal. + """ + + mtype=[] + for i in signal.getElementsByTagName("arg"): + name =i.getAttribute("name") + type = i.getAttribute("type") + mtype.append(type_to_gtype(type)[2]) + + return mtype + + +_glib_marshallers = ['VOID', 'BOOLEAN', 'CHAR', 'UCHAR', 'INT', + 'STRING', 'UINT', 'LONG', 'ULONG', 'ENUM', 'FLAGS', 'FLOAT', + 'DOUBLE', 'STRING', 'PARAM', 'BOXED', 'POINTER', 'OBJECT', + 'UINT_POINTER'] + + +def signal_to_marshal_name(signal, prefix): + + mtype = signal_to_marshal_type(signal) + if len(mtype): + name = '_'.join(mtype) + else: + name = 'VOID' + + if name in _glib_marshallers: + return 'g_cclosure_marshal_VOID__' + name + else: + return prefix + '_marshal_VOID__' + name + + +def method_to_glue_marshal_name(method, prefix): + + mtype = [] + for i in method.getElementsByTagName("arg"): + if i.getAttribute("direction") != "out": + type = i.getAttribute("type") + mtype.append(type_to_gtype(type)[2]) + + mtype.append('POINTER') + + name = '_'.join(mtype) + + if name in _glib_marshallers: + return 'g_cclosure_marshal_VOID__' + name + else: + return prefix + '_marshal_VOID__' + name + + +def type_to_gtype(s): + if s == 'y': #byte + return ("guchar ", "G_TYPE_UCHAR","UCHAR", False) + elif s == 'b': #boolean + return ("gboolean ", "G_TYPE_BOOLEAN","BOOLEAN", False) + elif s == 'n': #int16 + return ("gint ", "G_TYPE_INT","INT", False) + elif s == 'q': #uint16 + return ("guint ", "G_TYPE_UINT","UINT", False) + elif s == 'i': #int32 + return ("gint ", "G_TYPE_INT","INT", False) + elif s == 'u': #uint32 + return ("guint ", "G_TYPE_UINT","UINT", False) + elif s == 'x': #int64 + return ("gint64 ", "G_TYPE_INT64","INT64", False) + elif s == 't': #uint64 + return ("guint64 ", "G_TYPE_UINT64","UINT64", False) + elif s == 'd': #double + return ("gdouble ", "G_TYPE_DOUBLE","DOUBLE", False) + elif s == 's': #string + return ("gchar *", "G_TYPE_STRING", "STRING", True) + elif s == 'g': #signature - FIXME + return ("gchar *", "DBUS_TYPE_G_SIGNATURE", "STRING", True) + elif s == 'o': #object path + return ("gchar *", "DBUS_TYPE_G_OBJECT_PATH", "BOXED", True) + elif s == 'v': #variant + return ("GValue *", "G_TYPE_VALUE", "BOXED", True) + elif s == 'as': #array of strings + return ("gchar **", "G_TYPE_STRV", "BOXED", True) + elif s == 'ay': #byte array + return ("GArray *", + "dbus_g_type_get_collection (\"GArray\", G_TYPE_UCHAR)", "BOXED", + True) + elif s == 'au': #uint array + return ("GArray *", "DBUS_TYPE_G_UINT_ARRAY", "BOXED", True) + elif s == 'ai': #int array + return ("GArray *", "DBUS_TYPE_G_INT_ARRAY", "BOXED", True) + elif s == 'ax': #int64 array + return ("GArray *", "DBUS_TYPE_G_INT64_ARRAY", "BOXED", True) + elif s == 'at': #uint64 array + return ("GArray *", "DBUS_TYPE_G_UINT64_ARRAY", "BOXED", True) + elif s == 'ad': #double array + return ("GArray *", "DBUS_TYPE_G_DOUBLE_ARRAY", "BOXED", True) + elif s == 'ab': #boolean array + return ("GArray *", "DBUS_TYPE_G_BOOLEAN_ARRAY", "BOXED", True) + elif s == 'ao': #object path array + return ("GPtrArray *", + 'dbus_g_type_get_collection ("GPtrArray",' + ' DBUS_TYPE_G_OBJECT_PATH)', + "BOXED", True) + elif s == 'a{ss}': #hash table of string to string + return ("GHashTable *", "DBUS_TYPE_G_STRING_STRING_HASHTABLE", "BOXED", False) + elif s[:2] == 'a{': #some arbitrary hash tables + if s[2] not in ('y', 'b', 'n', 'q', 'i', 'u', 's', 'o', 'g'): + raise Exception, "can't index a hashtable off non-basic type " + s + first = type_to_gtype(s[2]) + second = type_to_gtype(s[3:-1]) + return ("GHashTable *", "(dbus_g_type_get_map (\"GHashTable\", " + first[1] + ", " + second[1] + "))", "BOXED", False) + elif s[:2] in ('a(', 'aa'): # array of structs or arrays, recurse + gtype = type_to_gtype(s[1:])[1] + return ("GPtrArray *", "(dbus_g_type_get_collection (\"GPtrArray\", "+gtype+"))", "BOXED", True) + elif s[:1] == '(': #struct + gtype = "(dbus_g_type_get_struct (\"GValueArray\", " + for subsig in Signature(s[1:-1]): + gtype = gtype + type_to_gtype(subsig)[1] + ", " + gtype = gtype + "G_TYPE_INVALID))" + return ("GValueArray *", gtype, "BOXED", True) + + # we just don't know .. + raise Exception, "don't know the GType for " + s diff --git a/farstream/tools/libtpcodegen.py b/farstream/tools/libtpcodegen.py new file mode 100644 index 000000000..837ff2f74 --- /dev/null +++ b/farstream/tools/libtpcodegen.py @@ -0,0 +1,215 @@ +"""Library code for language-independent D-Bus-related code generation. + +The master copy of this library is in the telepathy-glib repository - +please make any changes there. +""" + +# Copyright (C) 2006-2008 Collabora Limited +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +from string import ascii_letters, digits + + +NS_TP = "http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + +_ASCII_ALNUM = ascii_letters + digits + + +def cmp_by_name(node1, node2): + return cmp(node1.getAttributeNode("name").nodeValue, + node2.getAttributeNode("name").nodeValue) + + +def escape_as_identifier(identifier): + """Escape the given string to be a valid D-Bus object path or service + name component, using a reversible encoding to ensure uniqueness. + + The reversible encoding is as follows: + + * The empty string becomes '_' + * Otherwise, each non-alphanumeric character is replaced by '_' plus + two lower-case hex digits; the same replacement is carried out on + the first character, if it's a digit + """ + # '' -> '_' + if not identifier: + return '_' + + # A bit of a fast path for strings which are already OK. + # We deliberately omit '_' because, for reversibility, that must also + # be escaped. + if (identifier.strip(_ASCII_ALNUM) == '' and + identifier[0] in ascii_letters): + return identifier + + # The first character may not be a digit + if identifier[0] not in ascii_letters: + ret = ['_%02x' % ord(identifier[0])] + else: + ret = [identifier[0]] + + # Subsequent characters may be digits or ASCII letters + for c in identifier[1:]: + if c in _ASCII_ALNUM: + ret.append(c) + else: + ret.append('_%02x' % ord(c)) + + return ''.join(ret) + + +def get_by_path(element, path): + branches = path.split('/') + branch = branches[0] + + # Is the current branch an attribute, if so, return the attribute value + if branch[0] == '@': + return element.getAttribute(branch[1:]) + + # Find matching children for the branch + children = [] + if branch == '..': + children.append(element.parentNode) + else: + for x in element.childNodes: + if x.localName == branch: + children.append(x) + + ret = [] + # If this is not the last path element, recursively gather results from + # children + if len(branches) > 1: + for x in children: + add = get_by_path(x, '/'.join(branches[1:])) + if isinstance(add, list): + ret += add + else: + return add + else: + ret = children + + return ret + + +def get_docstring(element): + docstring = None + for x in element.childNodes: + if x.namespaceURI == NS_TP and x.localName == 'docstring': + docstring = x + if docstring is not None: + docstring = docstring.toxml().replace('\n', ' ').strip() + if docstring.startswith('<tp:docstring>'): + docstring = docstring[14:].lstrip() + if docstring.endswith('</tp:docstring>'): + docstring = docstring[:-15].rstrip() + if docstring in ('<tp:docstring/>', ''): + docstring = '' + return docstring + +def get_deprecated(element): + text = [] + for x in element.childNodes: + if hasattr(x, 'data'): + text.append(x.data.replace('\n', ' ').strip()) + else: + # This caters for tp:dbus-ref elements, but little else. + if x.childNodes and hasattr(x.childNodes[0], 'data'): + text.append(x.childNodes[0].data.replace('\n', ' ').strip()) + return ' '.join(text) + +def get_descendant_text(element_or_elements): + if not element_or_elements: + return '' + if isinstance(element_or_elements, list): + return ''.join(map(get_descendant_text, element_or_elements)) + parts = [] + for x in element_or_elements.childNodes: + if x.nodeType == x.TEXT_NODE: + parts.append(x.nodeValue) + elif x.nodeType == x.ELEMENT_NODE: + parts.append(get_descendant_text(x)) + else: + pass + return ''.join(parts) + + +class _SignatureIter: + """Iterator over a D-Bus signature. Copied from dbus-python 0.71 so we + can run genginterface in a limited environment with only Python + (like Scratchbox). + """ + def __init__(self, string): + self.remaining = string + + def next(self): + if self.remaining == '': + raise StopIteration + + signature = self.remaining + block_depth = 0 + block_type = None + end = len(signature) + + for marker in range(0, end): + cur_sig = signature[marker] + + if cur_sig == 'a': + pass + elif cur_sig == '{' or cur_sig == '(': + if block_type == None: + block_type = cur_sig + + if block_type == cur_sig: + block_depth = block_depth + 1 + + elif cur_sig == '}': + if block_type == '{': + block_depth = block_depth - 1 + + if block_depth == 0: + end = marker + break + + elif cur_sig == ')': + if block_type == '(': + block_depth = block_depth - 1 + + if block_depth == 0: + end = marker + break + + else: + if block_depth == 0: + end = marker + break + + end = end + 1 + self.remaining = signature[end:] + return Signature(signature[0:end]) + + +class Signature(str): + """A string, iteration over which is by D-Bus single complete types + rather than characters. + """ + def __iter__(self): + return _SignatureIter(self) + + +def xml_escape(s): + s = s.replace('&', '&').replace("'", ''').replace('"', '"') + return s.replace('<', '<').replace('>', '>') diff --git a/farstream/tools/telepathy.am b/farstream/tools/telepathy.am new file mode 100644 index 000000000..d061b8918 --- /dev/null +++ b/farstream/tools/telepathy.am @@ -0,0 +1,27 @@ +## Useful top-level Makefile.am snippets for Telepathy projects. + +dist-hook: + chmod u+w ${distdir}/ChangeLog + if test -d ${top_srcdir}/.git; then \ + git log --stat > ${distdir}/ChangeLog || \ + git log > ${distdir}/ChangeLog; \ + fi + +maintainer-upload-release: _maintainer-upload-release + +_maintainer-upload-release-check: + @case @VERSION@ in \ + (*.*.*.*) \ + echo "@VERSION@ is not a release" >&2; \ + exit 2; \ + ;; \ + esac + test -f @PACKAGE@-@VERSION@.tar.gz + test -f @PACKAGE@-@VERSION@.tar.gz.asc + gpg --verify @PACKAGE@-@VERSION@.tar.gz.asc + +_maintainer-upload-release: _maintainer-upload-release-check + rsync -vzP @PACKAGE@-@VERSION@.tar.gz telepathy.freedesktop.org:/srv/telepathy.freedesktop.org/www/releases/@PACKAGE@/@PACKAGE@-@VERSION@.tar.gz + rsync -vzP @PACKAGE@-@VERSION@.tar.gz.asc telepathy.freedesktop.org:/srv/telepathy.freedesktop.org/www/releases/@PACKAGE@/@PACKAGE@-@VERSION@.tar.gz.asc + +## vim:set ft=automake: diff --git a/farstream/tools/xincludator.py b/farstream/tools/xincludator.py new file mode 100644 index 000000000..63e106ace --- /dev/null +++ b/farstream/tools/xincludator.py @@ -0,0 +1,39 @@ +#!/usr/bin/python + +from sys import argv, stdout, stderr +import codecs, locale +import os +import xml.dom.minidom + +stdout = codecs.getwriter('utf-8')(stdout) + +NS_XI = 'http://www.w3.org/2001/XInclude' + +def xincludate(dom, base, dropns = []): + remove_attrs = [] + for i in xrange(dom.documentElement.attributes.length): + attr = dom.documentElement.attributes.item(i) + if attr.prefix == 'xmlns': + if attr.localName in dropns: + remove_attrs.append(attr) + else: + dropns.append(attr.localName) + for attr in remove_attrs: + dom.documentElement.removeAttributeNode(attr) + for include in dom.getElementsByTagNameNS(NS_XI, 'include'): + href = include.getAttribute('href') + # FIXME: assumes Unixy paths + filename = os.path.join(os.path.dirname(base), href) + subdom = xml.dom.minidom.parse(filename) + xincludate(subdom, filename, dropns) + if './' in href: + subdom.documentElement.setAttribute('xml:base', href) + include.parentNode.replaceChild(subdom.documentElement, include) + +if __name__ == '__main__': + argv = argv[1:] + dom = xml.dom.minidom.parse(argv[0]) + xincludate(dom, argv[0]) + xml = dom.toxml() + stdout.write(xml) + stdout.write('\n') |