pax_global_header00006660000000000000000000000064130566037410014517gustar00rootroot0000000000000052 comment=334baba53e253957637b86764347dcc27cf091ab dleyna-renderer-0.6.0/000077500000000000000000000000001305660374100146025ustar00rootroot00000000000000dleyna-renderer-0.6.0/.gitignore000066400000000000000000000010221305660374100165650ustar00rootroot00000000000000*.o *.lo *~ *.la *.pc *.tgz *.gz .dirstamp Makefile Makefile.in aclocal.m4 autom4te.cache/ compile config.guess config.h config.h.in config.log config.status config.sub configure depcomp install-sh libtool ltmain.sh missing .deps/ .libs/ stamp-h1 INSTALL libdleyna/renderer/dleyna-renderer-service.conf libdleyna/renderer/dleyna-renderer-1.0.pc m4/libtool.m4 m4/ltoptions.m4 m4/ltsugar.m4 m4/ltversion.m4 m4/lt~obsolete.m4 server/com.intel.dleyna-renderer.service server/dleyna-renderer-service test/dbus/rendererconsole.pyc dleyna-renderer-0.6.0/AUTHORS000066400000000000000000000003331305660374100156510ustar00rootroot00000000000000Mark Ryan (mark.d.ryan@intel.com) Ludovic Ferrandis (ludovic.ferrandis@intel.com) Sébastien Bianti (sebastien.bianti@intel.com) Regis Merlino (regis.merlino@intel.com) Christophe Guiraud (christophe.guiraud@intel.com) dleyna-renderer-0.6.0/COPYING000066400000000000000000000636421305660374100156500ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! dleyna-renderer-0.6.0/ChangeLog000077500000000000000000000103721305660374100163620ustar00rootroot00000000000000version 0.6.0 - Ensure that g_context.watchers has a valid value Merge pull request #150 - Merge pull request #132 - Used core functionality dleyna_core_prv_convert_udn_to_path for persistent path - [m4] Don't use bash arrays in m4 macros - [m4] Don't use bash "let" builtin - [m4] Use AS_VAR_APPEND macro instead of "+=" - Merge pull request #154 - Avoid any attempts to delete the same dlr_upnp_t twice Merge pull request #160 - Fix for issue #161 -- Uninitialized argument value in prv_open_uri_cb - Merge pull request #162 from 01org/ljsachs-patch-1 - Changed the Copyright to 2017 version 0.5.0 - [Deprecated API] Use new API instead of deprecated - [Device] Free dlna class strings after use https://github.com/01org/dleyna-renderer/issues/129 - host-service: use push-host-port setting for the web server - upnp: use port setting when creating GUPnPContextManager https://github.com/01org/dleyna-renderer/issues/141 - [Autoconf] Add Math lib to link https://github.com/01org/dleyna-renderer/issues/117 - Fix various configure & build issues [Autoconf] Sub Makefile: Remove ACLOCAL_AMFLAGS https://github.com/01org/dleyna-renderer/issues/124 - daemon: use GLib signal handling - [Device] Update playspeed when rate is set - [Configuration] Remove libdleyna-renderer .pc file version 0.4.0 - Logs: provide correct component version - Fix typos in the rendererconsole.py test application - Use GUPnP Mime Type if available https://github.com/01org/dleyna-renderer/issues/120 - Fix dlr_device_set_position implementation https://github.com/01org/dleyna-renderer/issues/115 - Fix dlr_device_seek implementation https://github.com/01org/dleyna-renderer/issues/115 - Fix a couple of memory leaks - New network filtering API - The 'never quit' option can be set via the connector version 0.2.2 - Various build optimizations - Fix a crash case when the server stops - Code cleanup - Documentation fixes - GetAll: do not fail if the DMR returns an error for GetPositionInf https://github.com/01org/dleyna-renderer/issues/92 - Fix a crash case for Rescan() - Fix #78: MPRIS OpenUri() does not start playing https://github.com/01org/dleyna-renderer/issues/78 - Add new SetUri API to Player https://github.com/01org/dleyna-renderer/issues/94 - Add OpenNextUri() method https://github.com/01org/dleyna-renderer/issues/35 - Handling of the publish_object() connector API change - Fix bug #97 - dLeyna renderer can crash if renderer dissapears https://github.com/01org/dleyna-renderer/issues/97 - Enable byte seek https://github.com/01org/dleyna-renderer/issues/59 - Change GetRenderers() API as well as FoundRenderer/LostRenderer signals: use object paths instead of strings https://github.com/01org/dleyna-renderer/issues/110 - Add Network Filtering support version 0.1.0 - Prepare for first stable branch version 0.0.2 - Add R/W Mute property support, Mute states are received via the LastChange event, Mute value is set with a SetMute action. - Add a new player interface method OpenUriEx, which is the same as the existing OpenUri one, with an additional metadata parameter to pass the URI description information in DIDL-Lite XML format. - Add the X_DLNA_PS transport action support to allow the playspeeds provided by the renderer once we have provided it a URI (SetAVTransportURI()) in addition to the renderer default playspeeds retrieved via the introspection. - Two new methods have been added to the Manager class, renderer_from_name and renderer_from_udn. These methods can be used to construct Renderer objects from UDNs or friendly names - Fix bug: dleyna-renderer can fail to discover a device if a network connection is lost during device construction https://github.com/01org/dleyna-renderer/issues/11 - Add a Rescan method to Manager interface - Add autogen.sh script to call `autoreconf -i` - Renamed a function and two signals in com.intel.dLeynaRenderer.Manager: GetServers() renamed GetRenderers() FoundServer renamed FoundRenderer LostServer renamed LostRenderer - A GetIcon() method is added to the com.intel.dLeynaRenderer.RendererDevice interface version 0.0.1 - Initial version of dleyna-renderer. - Enable support of deleyna-renderer as git submodules. dleyna-renderer-0.6.0/Makefile.am000066400000000000000000000005451305660374100166420ustar00rootroot00000000000000SUBDIRS = libdleyna/renderer if BUILD_SERVER SUBDIRS += server endif ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} MAINTAINERCLEANFILES = Makefile.in \ aclocal.m4 \ configure \ config.h.in \ config.h.in~ \ build-aux/depcomp \ build-aux/compile \ build-aux/missing \ build-aux/install-sh maintainer-clean-local: rm -rf build-aux dleyna-renderer-0.6.0/NEWS000066400000000000000000000000001305660374100152670ustar00rootroot00000000000000dleyna-renderer-0.6.0/README000066400000000000000000000047151305660374100154710ustar00rootroot00000000000000Introduction: ------------- TODO Compilation ------------ TODO Working with the source code repository --------------------------------------- dleyna-renderer can be downloaded, compiled and installed as follows: Clone repository # git clone git://github.com/01org/dleyna-renderer.git # cd dleyna-renderer Configure and build # ./autogen.sh # make Final installation # sudo make install These instructions are suitable for users who simply want to install and run dleyna-renderer. However, developers wishing to contribute to the project should follow a separate "Configure and build" step. Configure and build # ./bootstrap-configure # make The script "bootstrap-configure" cleans the repository, calls autreconf and then invokes configure with proper settings for development. These settings include the enabling of maintainer mode and debugging. Developers can remove autogenerated files with the following command # make maintainer-clean Configure Options: ------------------ --enable-werror This option is disabled by default. To enable use --enable-werror. When enabled, all warnings are treated as errors during compilation. Should be enabled during development to ensure that errors do not creep into the code base. This option is enabled by bootstrap-configure. --enable-debug This option is disabled by default. To enable use --enable-debug. When enabled, the make files produce debug builds. This option is enabled by bootstrap-configure. --enable-optimization This option is enabled by default. To disable use --disable-optimization. When enabled it turns on compiler optimizations. Disable = -O0, enable = -O2. --enable-never-quit This option is disabled by default. To enable use --enable-never-quit. When enabled, dleyna-renderer-service doesn't quit when the last client disconnects. --with-log-type See logging.txt for more information about logging. --with-log-level See logging.txt for more information about logging. --with-connector-name Set the IPC mechanism to be used. --enable-lib-only This option is disabled by default. To enable use --enable-lib-only. When enabled, only the libdleyna-renderer library is built. --with-ua-prefix This option allows a prefix to be added to the SOUP session user agent. For example, --with-ua-prefix=MyPrefix can be used to change a default user agent string from "dLeyna/0.0.1 GUPnP/0.19.4 DLNADOC/1.50" to "MyPrefix dLeyna/0.0.1 GUPnP/0.19.4 DLNADOC/1.50". dleyna-renderer-0.6.0/autogen.sh000077500000000000000000000010001305660374100165720ustar00rootroot00000000000000#!/bin/sh # Run this to generate all the initial makefiles, etc. # Derived from https://git.gnome.org/browse/glib/tree/autogen.sh test -n "$srcdir" || srcdir=`dirname "$0"` test -n "$srcdir" || srcdir=. olddir=`pwd` cd "$srcdir" AUTORECONF=`which autoreconf` if test -z $AUTORECONF; then echo "*** No autoreconf found, please install it ***" exit 1 fi autoreconf --force --install --verbose --warning=no-portability || exit $? cd "$olddir" test -n "$NOCONFIGURE" || "$srcdir/configure" "$@" dleyna-renderer-0.6.0/bootstrap-configure000077500000000000000000000003141305660374100205220ustar00rootroot00000000000000#!/bin/sh if [ -f config.status ]; then make maintainer-clean fi ./autogen.sh --enable-maintainer-mode \ --enable-silent-rules \ --disable-optimization \ --enable-debug \ --with-log-level=8 $* dleyna-renderer-0.6.0/configure.ac000066400000000000000000000170331305660374100170740ustar00rootroot00000000000000AC_PREREQ([2.66]) AC_INIT([dleyna-renderer], [0.6.0], [https://github.com/01org/dleyna-renderer/issues/new], , [https://01.org/dleyna/]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_SRCDIR([libdleyna/renderer/server.c]) AC_PREFIX_DEFAULT(/usr/local) AM_INIT_AUTOMAKE([subdir-objects]) AM_MAINTAINER_MODE AM_SILENT_RULES([yes]) DLEYNA_SERVER_COMPILER_FLAGS # Checks for languages. AC_LANG_C # Checks for programs. AC_PROG_CC AM_PROG_CC_C_O AC_PROG_MKDIR_P # Initialize libtool # Disable generation of static libraries LT_PREREQ([2.2.6]) LT_INIT([dlopen disable-static]) LT_LANG([C]) # Checks for libraries. PKG_PROG_PKG_CONFIG(0.16) PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.28]) PKG_CHECK_MODULES([GIO], [gio-2.0 >= 2.28]) PKG_CHECK_MODULES([GSSDP], [gssdp-1.0 >= 0.13.2]) PKG_CHECK_MODULES([GUPNP], [gupnp-1.0 >= 0.20.5]) PKG_CHECK_MODULES([GUPNPAV], [gupnp-av-1.0 >= 0.11.5]) PKG_CHECK_MODULES([GUPNPDLNA], [gupnp-dlna-2.0 >= 0.9.4]) PKG_CHECK_MODULES([SOUP], [libsoup-2.4 >= 2.28.2]) # Checks for header files. AC_CHECK_HEADERS([stdlib.h string.h syslog.h]) # Checks for typedefs, structures, and compiler characteristics. AC_TYPE_UINT8_T AC_HEADER_STDBOOL AC_TYPE_SIZE_T # Checks for library functions. AC_FUNC_MALLOC AC_FUNC_REALLOC AC_CHECK_FUNCS([memset strchr strrchr strstr]) # Define Log Level values LOG_LEVEL_0=0x00 LOG_LEVEL_1=0x01 LOG_LEVEL_2=0x02 LOG_LEVEL_3=0x04 LOG_LEVEL_4=0x08 LOG_LEVEL_5=0x10 LOG_LEVEL_6=0x20 LOG_LEVEL_7=0x13 LOG_LEVEL_8=0x3F AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL_DISABLED], [${LOG_LEVEL_0}], [Log level flag for disabled messages]) AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL_ERROR], [${LOG_LEVEL_1}], [Log level flag for errors]) AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL_CRITICAL], [${LOG_LEVEL_2}], [Log level flag for critical messages]) AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL_WARNING], [${LOG_LEVEL_3}], [Log level flag for warnings]) AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL_MESSAGE], [${LOG_LEVEL_4}], [Log level flag for messages]) AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL_INFO], [${LOG_LEVEL_5}], [Log level flag for informational messages]) AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL_DEBUG], [${LOG_LEVEL_6}], [Log level flag for debug messages]) AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL_DEFAULT], [${LOG_LEVEL_7}], [Log level flag to display default level messages]) AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL_ALL], [${LOG_LEVEL_8}], [Log level flag for all messages]) AC_ARG_ENABLE(master-build,, [], [master_build=no]) AS_IF([test "x$master_build" = "xno"], [PKG_CHECK_MODULES([DLEYNA_CORE], [dleyna-core-1.0 >= 0.6.0])], [this_abs_top_srcdir=`cd "$srcdir" && pwd`; DLEYNA_CORE_CFLAGS="-I$this_abs_top_srcdir/../dleyna-core"; DLEYNA_CORE_LIBS="-L$this_abs_top_srcdir/../dleyna-core/.libs -ldleyna-core-1.0" ]) AC_ARG_ENABLE(debug, AS_HELP_STRING( [--enable-debug], [enable compiling with debugging information]), [], [enable_debug=no]) AS_CASE("${enable_debug}", [yes], [CFLAGS="$CFLAGS -g"; AC_DEFINE_UNQUOTED([DLEYNA_DEBUG_ENABLED],[1], [Compiling with debugging information enabled]) ], [no], [], [AC_MSG_ERROR([bad value ${enable_debug} for --enable-debug])]) AC_ARG_ENABLE(werror, AS_HELP_STRING( [--enable-werror], [warnings are treated as errors]), [], [enable_werror=no]) AS_CASE("${enable_werror}", [yes], [CFLAGS="$CFLAGS -Werror"], [no], [], [AC_MSG_ERROR([bad value ${enable_werror} for --enable-werror])]) AC_ARG_ENABLE(optimization, AS_HELP_STRING( [--disable-optimization], [disable code optimization through compiler]), [], [enable_optimization=yes]) AS_CASE("${enable_optimization}", [yes], [disable_optimization=no], [no], [CFLAGS="$CFLAGS -O0"; disable_optimization=yes], [AC_MSG_ERROR([bad value ${enable_optimization} for --enable-werror])]) AC_ARG_ENABLE(never-quit, AS_HELP_STRING( [--enable-never-quit], [Service doesn't quit when last client disconnects]), [], [enable_never_quit=no]) AS_CASE("${enable_never_quit}", [yes], [never_quit=true], [no], [never_quit=false], [AC_MSG_ERROR([bad value ${enable_never_quit} for --enable-never-quit])]) AC_ARG_WITH(connector-name, AS_HELP_STRING( [--with-connector-name], [IPC connector name]), [], [with_connector_name=dbus]) AC_ARG_WITH(log-level, AS_HELP_STRING( [--with-log-level], [enable logging information (0,1..6,7,8)\ 0=disabled \ 7=default (=1,2,5) \ 8=all (=1,2,3,4,5,6) \ 1,..,6=a comma separated list of log level\ ]), [], [with_log_level=7]) DLEYNA_LOG_LEVEL_CHECK([${with_log_level}]) AC_ARG_WITH(log-type, AS_HELP_STRING( [--with-log-type], [Select log output technology \ 0=syslog 1=GLib \ ]), [], [with_log_type=0]) DLEYNA_LOG_TYPE_CHECK([${with_log_type}]) AC_ARG_WITH(ua-prefix, AS_HELP_STRING( [--with-ua-prefix], [Specify a user agent prefix]), [with_ua_prefix = "$withval"; AC_DEFINE_UNQUOTED([UA_PREFIX], "$with_ua_prefix", [User Agent prefix])], []) AC_ARG_WITH(dbus_service_dir, AS_HELP_STRING([--with-dbus-service-dir=PATH],[choose directory for dbus service files, [default=PREFIX/share/dbus-1/services]]), with_dbus_service_dir="$withval", with_dbus_service_dir=$datadir/dbus-1/services) DBUS_SERVICE_DIR=$with_dbus_service_dir AC_SUBST(DBUS_SERVICE_DIR) AC_ARG_ENABLE(lib-only, AS_HELP_STRING( [--enable-lib-only], [compile only the libdleyna-renderer library]), [], [enable_lib_only=no]) AM_CONDITIONAL([BUILD_SERVER], [test "x$enable_lib_only" = "xno"]) AC_DEFINE([DLEYNA_SERVER_OBJECT], "/com/intel/dLeynaRenderer", [Name of object exposed by dleyna-renderer]) AC_DEFINE([DLEYNA_SERVER_PATH], "/com/intel/dLeynaRenderer/server", [Path of server objects]) DLEYNA_SERVER_NAME=com.intel.dleyna-renderer AC_SUBST(DLEYNA_SERVER_NAME) AC_DEFINE([DLEYNA_SERVER_NAME], "com.intel.dleyna-renderer", [d-Bus Name of dleyna-renderer]) DLEYNA_SERVER_INTERFACE_MANAGER=com.intel.dLeynaRenderer.Manager AC_SUBST(DLEYNA_SERVER_INTERFACE_MANAGER) AC_DEFINE([DLEYNA_SERVER_INTERFACE_MANAGER], "com.intel.dLeynaRenderer.Manager", [d-Bus Name of dleyna-renderer main interface]) DLEYNA_SERVER_INTERFACE_RENDERER_DEVICE=com.intel.dLeynaRenderer.RendererDevice AC_SUBST(DLEYNA_SERVER_INTERFACE_RENDERER_DEVICE) AC_DEFINE([DLEYNA_SERVER_INTERFACE_RENDERER_DEVICE], "com.intel.dLeynaRenderer.RendererDevice", [d-Bus Name of dleyna-renderer device interface]) DLEYNA_INTERFACE_PUSH_HOST=com.intel.dLeynaRenderer.PushHost AC_SUBST(DLEYNA_INTERFACE_PUSH_HOST) AC_DEFINE([DLEYNA_INTERFACE_PUSH_HOST], "com.intel.dLeynaRenderer.PushHost", [d-Bus Name of dleyna-renderer push host interface]) AC_SUBST([never_quit]) AC_SUBST([with_connector_name]) AC_SUBST([with_log_level]) AC_SUBST([with_log_type]) AC_CONFIG_FILES([Makefile \ libdleyna/renderer/Makefile \ libdleyna/renderer/dleyna-renderer-service.conf \ server/dleyna-renderer-service-1.0.pc \ server/Makefile ]) AC_OUTPUT AS_ECHO(["------------------------------------------------- ${PACKAGE_NAME} Version ${PACKAGE_VERSION} Prefix : '${prefix}' Compiler : '${CC}' CFLAGS : '${CFLAGS}' -Package features: - enable-werror : ${enable_werror} - enable-debug : ${enable_debug} - enable-never-quit : ${enable_never_quit} - with-connector-name : ${with_connector_name} - disable-optimization: ${disable_optimization} - with-log-level : ${with_log_level} - with-log-type : ${with_log_type} - enable-lib-only : ${enable_lib_only} - with-ua-prefix : ${with_ua_prefix} --------------------------------------------------"]) dleyna-renderer-0.6.0/doc/000077500000000000000000000000001305660374100153475ustar00rootroot00000000000000dleyna-renderer-0.6.0/doc/TODO.txt000066400000000000000000000002771305660374100166630ustar00rootroot00000000000000TODO: ----- You can check the list of features we have planned or are currently working on in github: dleyna-renderer-0.6.0/doc/coding-style.txt000066400000000000000000000001761305660374100205150ustar00rootroot00000000000000dLeyna-renderer uses the dLeyna-core coding guidelines: https://github.com/01org/dleyna-core/blob/master/doc/coding-style.txtdleyna-renderer-0.6.0/doc/logging.txt000066400000000000000000000001721305660374100175360ustar00rootroot00000000000000dLeyna-renderer uses the dLeyna-core logging guidelines: https://github.com/01org/dleyna-core/blob/master/doc/logging.txtdleyna-renderer-0.6.0/doc/server/000077500000000000000000000000001305660374100166555ustar00rootroot00000000000000dleyna-renderer-0.6.0/doc/server/dbus/000077500000000000000000000000001305660374100176125ustar00rootroot00000000000000dleyna-renderer-0.6.0/doc/server/dbus/API.txt000066400000000000000000000453751305660374100210020ustar00rootroot00000000000000API --- Dleyna-renderer-service is a middleware component. It is designed to be launched by d-Bus activation when needed. It automatically shuts down when the last of its clients quits or releases its connection. Dleyna-renderer-service currently connects to the d-Bus session bus, although this may change in the future. It exposes two different types of objects: 1. A manager object. There is only ever a single instance of this object. It can be used to retrieve a list of the DMRs on the local area network. It is also used to perform certain server independent tasks. 2. Renderer objects. One separate object is exposed for each DMR available on the LAN. These objects expose interfaces that allow clients to retrieve information about the renderers, to manipulate them and to push content to them. The remainder of this document will describe the two d-Bus objects listed above and the interfaces they support. The Manager Object: ------------------- There is only ever a single instance of this object. The manager object exposes two d-Bus interfaces: 1 - com.intel.dLeynaServer.Manager. 2 - org.freedesktop.DBus.Properties. com.intel.dLeynaRenderer.Manager -------------------------------- Methods: ---------- The interface com.intel.dLeynaRenderer.Manager contains 4 methods. Descriptions of each of these methods along with their d-Bus signatures are given below. GetRenderers() -> ao GetRenderers takes no parameters and returns an array of d-Bus object paths. Each of these paths reference a d-Bus object that represents a single DMR. GetVersion() -> s Returns the version number of dleyna-renderer-service Release() -> void Indicates to dleyna-renderer-service that a client is no longer interested in its services. Internally, dleyna-renderer-service maintains a reference count. This reference count is increased when a new client connects. It is decreased when a client quits. When the reference count reaches 0, dleyna-renderer-service exits. A call to Release also decreases the reference count. Clients should call this method if they intend to keep running but they have no immediate plans to invoke any of dleyna-renderer-service's methods. This allows dleyna-renderer-service to quit, freeing up system resources. Rescan() -> void Forces a rescan for DMRs on the local area network. This is useful to detect DMRs which have shut down without sending BYE messages or to discover new DMRs which for some reason were not detected when either they, or the device on which dLeyna-renderer runs, was started or joined the network. New in version 0.0.2. Properties: --------- The com.intel.dLeynaRenderer.Manager interface exposes information via a number of d-Bus properties. These properties are described below: |------------------------------------------------------------------------------| | Name | Type |m/o*| Description | |------------------------------------------------------------------------------| | NeverQuit | b | m | True if the service always stay in | | | | | memory running. False if the service | | | | | quit when the last client disconnects. | |------------------------------------------------------------------------------| | WhiteListEntries | as | m | The list of entries that compose the | | | | | white list used to filter the networks. | | | | | An Entry could be an interface name | | | | | (eth0), an ip address (127.0.0.1) or | | | | | a SSID (MyWiFi) | |------------------------------------------------------------------------------| | WhiteListEnabled | b | m | True if the Network Filtering is active.| |------------------------------------------------------------------------------| A org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted when these properties change. These properties can be changed using the Set() method of org.freedesktop.DBus.Properties interface. Signals: --------- The com.intel.dLeynaRenderer.Manager interface also exposes two signals. FoundRenderer(o) Is generated whenever a new DMR is detected on the local area network. The signal contains the path of the newly discovered renderer. LostRenderer(o) Is generated whenever a DMR is shutdown. The signal contains the path of the renderer which has just been shutdown. The Renderer Objects: ------------------ Dleyna-renderer-service exposes a separate d-Bus object for each DMR it detects on the LAN. These objects serve three purposes. 1. They allow the client to retrieve information about the DMR, such as its name, the URL of its icon, its manufacturer, the currently playing content, etc. 2. They allow the client to manipulate the DMR, i.e., to play a given URL, to play, pause and stop, etc. 3. They can be used to implement two box push. Each renderer object exposes three separate interfaces: org.mpris.MediaPlayer2, org.mpris.MediaPlayer2.Player and com.intel.dLeynaRenderer.PushHost. com.intel.dLeynaRenderer.RendererDevice: ---------------------------------------- The com.intel.dLeynaRenderer.RendererDevice interface exposes information about the DMR via a number of d-Bus properties. These properties are described below: |------------------------------------------------------------------------------| | Name | Type | m/o* | Description | |------------------------------------------------------------------------------| | DeviceType | s | m | The UPnP type of the device, e.g., | | | | | urn:schemas-upnp-org:device:MediaServer:1 | |------------------------------------------------------------------------------| | UDN | s | m | The Unique Device Name of the server. | |------------------------------------------------------------------------------| | FriendlyName | s | m | The friendly name of the media server. | -------------------------------------------------------------------------------| | IconURL | s | o | A URL pointing to an icon that | | | | | graphically identifies the server | |------------------------------------------------------------------------------| | Manufacturer | s | m | A string identifying the manufacturer of | | | | | the server | |------------------------------------------------------------------------------| | ManufacturerUrl | s | o | A URL pointing to the manufacturer's web | | | | | site. | |------------------------------------------------------------------------------| | ModelDescription | s | o | A description of the server. | |------------------------------------------------------------------------------| | ModelName | s | m | The model name of the server. | |------------------------------------------------------------------------------| | ModelNumber | s | o | The server's version number | |------------------------------------------------------------------------------| | SerialNumber | s | o | The server's serial number | |------------------------------------------------------------------------------| | PresentationURL | s | o | The presentation URL of the server, i.e., | | | | | a link to it's HTML management interface. | |------------------------------------------------------------------------------| | ProtocolInfo | s | m | A string that identifies all of the file | | | | | formats and network protocol combinations | | | | | that the renderer supports. (1) | |------------------------------------------------------------------------------| | DeviceClasses | as | o | A list of supported device classes, such | | | | | as DMR-1.5, etc. | |------------------------------------------------------------------------------| (* where m/o indicates whether the property is optional or mandatory ) (1) The idea behind the ProtocolInfo property is a little complicated and requires further discussion. The Protocol info field is a comma separated list of protocol info values. Each protocol info value consists of 4 fields separated by colons. Unfortunately, the format is too complex to describe in this document. The reader is referred to the UPnP Connection Manager Service Template document (2) and the DLNA Guidelines (3) where it is described extensively. However, an example protocol info value is presented below, to give the reader an idea of what such a string might look like. "http-get:*:audio/mp4:DLNA.ORG_PN=AMR_WBplus, http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED" The ProtocolInfo property identifies the types of URLs the renderer is capable of playing. The example value given above indicates that the renderer is capable of retrieving (via HTTP) and playing/displaying audio MP4 and JPEG files. Now the astute among you might notice that the information provided by the ProtocolInfo property is already available via the standard MPRIS SupportedMimeTypes and SupportedUriSchemes properties. This is true, however ProtocolInfo is still useful as its value is specifically formatted to make it easy to write a certain type of application called a Digital Media Controller (DMC). When you write a DMC you instruct a DMR to play a media file exposed by a Digital Media Server (DMS). DMSs often publish the same piece of media content in a number of formats, exposing a separate URL for each format. The DMC can use the ProtocolInfo of his chosen renderer to determine which of the many formats is most suitable for the renderer and having done this to determine which URL to use. If the DMC uses dleyna-server-service to browse the contents of DMSs, it can achieve this simply by identifying the dleyna-server-service d-Bus object of the file it wishes to play, and then invoking this object's GetCompatibleResource method, passing the renderer's ProtocolInfo string as a parameter. This method will then return the most suitable URL for the renderer. Methods: --------- The com.intel.dLeynaRenderer.RendererDevice interface currently exposes two methods: Cancel() -> void Cancels all requests a client has outstanding on that server. GetIcon(s RequestedMimeType, s Resolution) -> (ay Bytes, s MimeType) Returns the device icon bytes and mime type according to the RequestedMimeType and Resolution parameters. Both RequestedMimeType and Resolution parameters are currently reserved for future use and should be set as an empty string. org.mpris.MediaPlayer2 ---------------------- This interface is part of the MPRIS standard and is documented in the MRPIS D-Bus Interface Specification document [1]. As the interface is well described in this document, this section will focus on the peculiarities of dleyna-renderer-service's implementation of this interface rather than the interface itself. The main points of interest are: - The methods Raise and Quit are implemented but they do nothing. The CanRaise and CanQuit properties necessarily return false. - The property HasTrackList is always set to false. This is because TrackLists are not yet implemented. Hopefully, this will change in the future. - The property CanSetFullscreen is always set to false and Fullscreen is not implemented. - The DesktopEntry is not implemented. org.mpris.MediaPlayer2.Player ----------------------------- Like org.mpris.MediaPlayer2, org.mpris.MediaPlayer2.Player is a standard MPRIS2 interface and is already documented in the MRPIS2 specification. Consequently, this section will only describe the peculiarities of dleyna-renderer-service's implementation of this interface. The main points of interest are noted below: - The LoopStatus property is not implemented. - The Shuffle property is not implemented. - The Seek signal is not implemented yet. - The first parameter to SetPosition is ignored, and any valid d-Bus path can be specified as its value. - PropertiesChanged signals are emitted via the org.freedesktop.DBus.Properties interface of a renderer object instance when org.mpris.MediaPlayer2.Player interface properties value change. - Some new properties have been added, they are described below: |------------------------------------------------------------------------------| | Name | Type |m/o*| Description | |------------------------------------------------------------------------------| | TransportPlaySpeeds | ad | m | Allowed play speed values supported by | | | | | the renderer. It allows clients to set | | | | | the Rate property with a value that will | | | | | be accepted by the DMR. | |------------------------------------------------------------------------------| | CurrentTrack | u | m | The sequence number of the currently | | | | | selected track. | |------------------------------------------------------------------------------| | NumberOfTracks | u | m | The number of tracks in the currently | | | | | selected media. | |------------------------------------------------------------------------------| | Mute | b | o | The mute setting of the master audio | | | | | channel. New in version 0.0.2. | |------------------------------------------------------------------------------| | CanByteSeek | b | m | Whether the client can control the | | | | | playback position using ByteSeek and | | | | | SetBytePosition. | |------------------------------------------------------------------------------| | BytePosition | x | o | The current track position in bytes. | |------------------------------------------------------------------------------| - new methods have been added, they are described below: GotoTrack(u TrackNumber) -> void Performs a seek operation to the specified track number. OpenUriEx(s Uri, s Metadata) -> void Same as the OpenUri method of the org.mpris.MediaPlayer2.Player MPRIS2 standard interface, with an additional parameter Metadata to specify the DIDL-Lite XML description of the item to be opened. New in version 0.0.2. OpenNextUri(s Uri, s Metadata) -> void Same as OpenUriEx method but for enabling an early download of the next object. New in version 0.2.0. SetUri(s Uri, s Metadata) -> void Same as OpenUriEx except that it doesn't automatically play the media. You need to explicitely call the Play method. New in version 0.2.0. ByteSeek(x Offset) -> void SetBytePosition(o TrackID, x Position) -> void Same as Seek and SetPosition but last parameter (Offset and Position) is not expressed in second but in byte, and allows to set the seekmode by using “X_DLNA_REL_BYTE” unit. New in version 0.2.1 org.mpris.MediaPlayer2.TrackList and org.mpris.MediaPlayer2.Playlists --------------------------------------------------------------------- Are not yet implemented. com.intel.dLeynaRenderer.PushHost --------------------------------- DMRs participate in two different DLNA use cases. 3 Box System and 2 Box Push. In the 3 Box System a DMC instructs a DMR to play a URL hosted by a DMS. In 2 Box Push, an application pushes content it has created or downloaded directly to a renderer. In 2 Box Push the DMS is not involved at all and this presents a problem as DMRs do not really support the concept of Push. Rather than pushing files to a DMR, the client passes it a URL and then instructs the DMR to pull the content from the URL. For this to work, a web server must be running somewhere to host the URL. In the 3 Box System the role of the web server is played by the DMS. Unfortunately, as we have seen, in 2 Box Push there is no DMS. So who is to host the content to be pushed? Conceptually, each application that wishes to push a file to a DMR could start its own web server on which it could temporarily host the file to be pushed. However, this is complicated for application developers and also wasteful of system resources. Potentially, every application that can create of download content can push this content to DMRs. As, these applications can legitimately run simultaneously having them run their own web servers would consume unnecessary system resources. Dleyna-renderer-service solves these problems by providing both a web server and simple APIs that can be used by clients to host files on this server. As only one instance of dleyna-renderer-service runs at any one time (well currently only one can run per user session) we avoid the proliferation of web servers. These push APIs are provided by the com.intel.dLeynaRenderer.PushHost interface which is implemented by all renderer objects. com.intel.dLeynaRenderer.PushHost contains two methods which are described in below. HostFile(s path) -> s Hosts a file on dleyna-renderer-service's web server. The parameter path should be a full path to the local file to be hosted, e.g., /home/user/Podcasts/pod.mp3. The value returned is the URL of the newly hosted file. RemoveFile(s path) -> void Stops hosting the file whose full path is passed as parameter to this function. Dleyna-renderer-service only runs a web server when files are being hosted. Once all clients have stopped hosting files, either by calling RemoveFile or by exiting, dleyna-renderer-service will shut down its web server. Actually, dleyna-renderer-service may run more than one web server if multiple DMRs are accessed over different interfaces. When a client chooses to host a file for a given renderer, dleyna-renderer-service checks to see if a web server is already running on the interface through which this renderer is accessible. If it is not it starts a new web server. It follows that if clients try to host multiple files for multiple renderers running on different interfaces, dleyna-renderer-service may actually run multiple servers. However, it will only run one server per interface, and the server will be shutdown as soon as it no longer has any files to host. References: ----------- 1) MRPIS D-Bus Interface Specification (http://specifications.freedesktop.org/mpris-spec/latest/) 2) ConnectionManager:2 Service Template (http://www.upnp.org/) 3) DLNA Guidelines December 2011, Part 1 Architectures and Protocols (http://www.dlna.org/) dleyna-renderer-0.6.0/libdleyna/000077500000000000000000000000001305660374100165455ustar00rootroot00000000000000dleyna-renderer-0.6.0/libdleyna/renderer/000077500000000000000000000000001305660374100203535ustar00rootroot00000000000000dleyna-renderer-0.6.0/libdleyna/renderer/Makefile.am000066400000000000000000000027301305660374100224110ustar00rootroot00000000000000DLEYNA_RENDERER_VERSION = 1:3:0 AM_CFLAGS = $(GLIB_CFLAGS) \ $(GIO_CFLAGS) \ $(DLEYNA_CORE_CFLAGS) \ $(GSSDP_CFLAGS) \ $(GUPNP_CFLAGS) \ $(GUPNPAV_CFLAGS) \ $(GUPNPDLNA_CFLAGS) \ $(SOUP_CFLAGS) \ -include config.h pkglib_LTLIBRARIES = libdleyna-renderer-1.0.la libdleyna_rendererincdir = $(includedir)/dleyna-1.0/libdleyna/renderer libdleyna_rendererinc_HEADERS = control-point-renderer.h libdleyna_renderer_1_0_la_LDFLAGS = -version-info $(DLEYNA_RENDERER_VERSION) \ -no-undefined libdleyna_renderer_1_0_la_SOURCES = $(libdleyna_rendererinc_HEADERS) \ async.c \ device.c \ host-service.c \ manager.c \ server.c \ task.c \ upnp.c libdleyna_renderer_1_0_la_LIBADD = $(GLIB_LIBS) \ $(GIO_LIBS) \ $(DLEYNA_CORE_LIBS) \ $(GSSDP_LIBS) \ $(GUPNP_LIBS) \ $(GUPNPAV_LIBS) \ $(GUPNPDLNA_LIBS) \ $(SOUP_LIBS) \ -lm MAINTAINERCLEANFILES = Makefile.in \ aclocal.m4 \ configure \ config.h.in \ config.h.in~ \ build-aux/depcomp \ build-aux/compile \ build-aux/missing \ build-aux/install-sh sysconf_DATA = dleyna-renderer-service.conf EXTRA_DIST = $(sysconf_DATA) \ async.h \ device.h \ host-service.h \ prop-defs.h \ manager.h \ server.h \ task.h \ upnp.h CLEANFILES = dleyna-renderer-service.conf DISTCLEANFILES = dleyna-renderer-service.conf maintainer-clean-local: rm -rf build-aux dleyna-renderer-0.6.0/libdleyna/renderer/async.c000066400000000000000000000040341305660374100216350ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #include #include #include "async.h" void dlr_async_task_delete(dlr_async_task_t *task) { if (task->free_private) task->free_private(task->private); if (task->cancellable) g_object_unref(task->cancellable); } gboolean dlr_async_task_complete(gpointer user_data) { dlr_async_task_t *cb_data = user_data; DLEYNA_LOG_DEBUG("Enter. %s %s", cb_data->error ? "Error:" : "Success.", cb_data->error ? cb_data->error->message : ""); DLEYNA_LOG_DEBUG_NL(); if (cb_data->proxy != NULL) g_object_remove_weak_pointer((G_OBJECT(cb_data->proxy)), (gpointer *)&cb_data->proxy); cb_data->cb(&cb_data->task, cb_data->error); return FALSE; } void dlr_async_task_cancelled(GCancellable *cancellable, gpointer user_data) { dlr_async_task_t *cb_data = user_data; if (cb_data->proxy != NULL) gupnp_service_proxy_cancel_action(cb_data->proxy, cb_data->action); if (!cb_data->error) cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_CANCELLED, "Operation cancelled."); (void) g_idle_add(dlr_async_task_complete, cb_data); } void dlr_async_task_cancel(dlr_async_task_t *task) { if (task->cancellable) g_cancellable_cancel(task->cancellable); } dleyna-renderer-0.6.0/libdleyna/renderer/async.h000066400000000000000000000031031305660374100216360ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #ifndef DLR_ASYNC_H__ #define DLR_ASYNC_H__ #include #include "device.h" #include "task.h" #include "upnp.h" typedef struct dlr_async_task_t_ dlr_async_task_t; struct dlr_async_task_t_ { dlr_task_t task; /* pseudo inheritance - MUST be first field */ dlr_upnp_task_complete_t cb; GError *error; GUPnPServiceProxyAction *action; GUPnPServiceProxy *proxy; GCancellable *cancellable; gulong cancel_id; gpointer private; GDestroyNotify free_private; dlr_device_t *device; }; gboolean dlr_async_task_complete(gpointer user_data); void dlr_async_task_cancelled(GCancellable *cancellable, gpointer user_data); void dlr_async_task_delete(dlr_async_task_t *task); void dlr_async_task_cancel(dlr_async_task_t *task); #endif /* DLR_ASYNC_H__ */ dleyna-renderer-0.6.0/libdleyna/renderer/control-point-renderer.h000066400000000000000000000020571305660374100251430ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Regis Merlino * */ #ifndef DLEYNA_CONTROL_POINT_RENDERER_H__ #define DLEYNA_CONTROL_POINT_RENDERER_H__ #include const dleyna_control_point_t *dleyna_control_point_get_renderer(void); #endif /* DLEYNA_CONTROL_POINT_RENDERER_H__ */ dleyna-renderer-0.6.0/libdleyna/renderer/device.c000066400000000000000000002573021305660374100217670ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #include #include #include #include #include #include #include #include #include #include "async.h" #include "device.h" #include "prop-defs.h" #include "server.h" typedef struct dlr_device_get_all_position_t_ dlr_device_get_all_position_t; struct dlr_device_get_all_position_t_ { gint expected_props; gchar *rel_time; gchar *rel_cnt; }; typedef struct dlr_device_data_t_ dlr_device_data_t; struct dlr_device_data_t_ { union { dlr_device_get_all_position_t get_all_position; } ut; }; typedef struct dlr_rc_event_t_ dlr_rc_event_t; struct dlr_rc_event_t_ { dlr_device_t *device; guint dev_volume; guint mute; guint source_id; }; /* Private structure used in chain task */ typedef struct prv_new_device_ct_t_ prv_new_device_ct_t; struct prv_new_device_ct_t_ { dlr_device_t *dev; const dleyna_connector_dispatch_cb_t *dispatch_table; }; typedef struct prv_download_info_t_ prv_download_info_t; struct prv_download_info_t_ { SoupSession *session; SoupMessage *msg; dlr_async_task_t *task; }; static void prv_last_change_cb(GUPnPServiceProxy *proxy, const char *variable, GValue *value, gpointer user_data); static void prv_sink_change_cb(GUPnPServiceProxy *proxy, const char *variable, GValue *value, gpointer user_data); static void prv_rc_last_change_cb(GUPnPServiceProxy *proxy, const char *variable, GValue *value, gpointer user_data); static gboolean prv_props_update(dlr_device_t *device, dlr_task_t *task); static void prv_get_rates_values(GList *allowed_tp_speeds, GVariant **mpris_tp_speeds, GPtrArray **upnp_tp_speeds, double *min_rate, double *max_rate); static void prv_add_player_speed_props(GHashTable *player_props, double min_rate, double max_rate, GVariant *mpris_transport_play_speeds, GVariantBuilder *changed_props_vb); static gint prv_compare_rationals(const gchar *a, const gchar *b); static void prv_get_position_info(dlr_async_task_t *cb_data, const gchar *action_name, GUPnPServiceProxyActionCallback callback); static void prv_unref_variant(gpointer variant) { GVariant *var = variant; if (var) g_variant_unref(var); } static void prv_props_init(dlr_props_t *props) { props->root_props = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, prv_unref_variant); props->player_props = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, prv_unref_variant); props->device_props = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, prv_unref_variant); props->synced = FALSE; } static void prv_props_free(dlr_props_t *props) { g_hash_table_unref(props->root_props); g_hash_table_unref(props->player_props); g_hash_table_unref(props->device_props); } static void prv_service_proxies_free(dlr_service_proxies_t *service_proxies) { if (service_proxies->av_proxy) g_object_unref(service_proxies->av_proxy); if (service_proxies->rc_proxy) g_object_unref(service_proxies->rc_proxy); if (service_proxies->cm_proxy) g_object_unref(service_proxies->cm_proxy); } static void prv_context_unsubscribe(dlr_device_context_t *ctx) { DLEYNA_LOG_DEBUG("Enter"); if (ctx->timeout_id_cm) { (void) g_source_remove(ctx->timeout_id_cm); ctx->timeout_id_cm = 0; } if (ctx->timeout_id_av) { (void) g_source_remove(ctx->timeout_id_av); ctx->timeout_id_av = 0; } if (ctx->timeout_id_rc) { (void) g_source_remove(ctx->timeout_id_rc); ctx->timeout_id_rc = 0; } if (ctx->subscribed_cm) { (void) gupnp_service_proxy_remove_notify( ctx->service_proxies.cm_proxy, "SinkProtocolInfo", prv_sink_change_cb, ctx->device); gupnp_service_proxy_set_subscribed( ctx->service_proxies.cm_proxy, FALSE); ctx->subscribed_cm = FALSE; } if (ctx->subscribed_av) { (void) gupnp_service_proxy_remove_notify( ctx->service_proxies.av_proxy, "LastChange", prv_last_change_cb, ctx->device); gupnp_service_proxy_set_subscribed( ctx->service_proxies.av_proxy, FALSE); ctx->subscribed_av = FALSE; } if (ctx->subscribed_rc) { (void) gupnp_service_proxy_remove_notify( ctx->service_proxies.rc_proxy, "LastChange", prv_rc_last_change_cb, ctx->device); gupnp_service_proxy_set_subscribed( ctx->service_proxies.rc_proxy, FALSE); ctx->subscribed_rc = FALSE; } DLEYNA_LOG_DEBUG("Exit"); } static void prv_dlr_context_delete(gpointer context) { dlr_device_context_t *ctx = context; if (ctx) { prv_context_unsubscribe(ctx); g_free(ctx->ip_address); if (ctx->device_proxy) g_object_unref(ctx->device_proxy); prv_service_proxies_free(&ctx->service_proxies); g_free(ctx); } } static void prv_change_props(GHashTable *props, const gchar *key, GVariant *value, GVariantBuilder *changed_props_vb) { g_hash_table_insert(props, (gpointer) key, value); if (changed_props_vb) g_variant_builder_add(changed_props_vb, "{sv}", key, value); } static void prv_emit_signal_properties_changed(dlr_device_t *device, const char *interface, GVariant *changed_props) { #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG gchar *params; #endif GVariant *val = g_variant_ref_sink(g_variant_new("(s@a{sv}as)", interface, changed_props, NULL)); DLEYNA_LOG_DEBUG("Emitted Signal: %s.%s - ObjectPath: %s", DLR_INTERFACE_PROPERTIES, DLR_INTERFACE_PROPERTIES_CHANGED, device->path); #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG params = g_variant_print(val, FALSE); DLEYNA_LOG_DEBUG("Params: %s", params); g_free(params); #endif dlr_renderer_get_connector()->notify(device->connection, device->path, DLR_INTERFACE_PROPERTIES, DLR_INTERFACE_PROPERTIES_CHANGED, val, NULL); g_variant_unref(val); } static void prv_merge_meta_data(dlr_device_t *device, const gchar *key, GVariant *value, GVariantBuilder *changed_props_vb) { GVariant *current_meta_data; GVariantIter viter; GVariantBuilder *vb; GVariant *val; gchar *vkey; gboolean replaced = FALSE; GVariant *new_val; vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); current_meta_data = g_hash_table_lookup(device->props.player_props, DLR_INTERFACE_PROP_METADATA); if (current_meta_data) { g_variant_iter_init(&viter, current_meta_data); while (g_variant_iter_next(&viter, "{&sv}", &vkey, &val)) { if (!strcmp(key, vkey)) { new_val = value; replaced = TRUE; } else { new_val = val; } g_variant_builder_add(vb, "{sv}", vkey, new_val); g_variant_unref(val); } } if (!replaced) g_variant_builder_add(vb, "{sv}", key, value); val = g_variant_ref_sink(g_variant_builder_end(vb)); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_METADATA, val, changed_props_vb); g_variant_builder_unref(vb); } static void prv_context_new(const gchar *ip_address, GUPnPDeviceProxy *proxy, dlr_device_t *device, dlr_device_context_t **context) { const gchar *cm_type = "urn:schemas-upnp-org:service:ConnectionManager"; const gchar *av_type = "urn:schemas-upnp-org:service:AVTransport"; const gchar *rc_type = "urn:schemas-upnp-org:service:RenderingControl"; dlr_device_context_t *ctx = g_new(dlr_device_context_t, 1); dlr_service_proxies_t *service_proxies = &ctx->service_proxies; ctx->ip_address = g_strdup(ip_address); ctx->device_proxy = proxy; ctx->device = device; ctx->subscribed_av = FALSE; ctx->subscribed_cm = FALSE; ctx->subscribed_rc = FALSE; ctx->timeout_id_av = 0; ctx->timeout_id_cm = 0; ctx->timeout_id_rc = 0; g_object_ref(proxy); service_proxies->cm_proxy = (GUPnPServiceProxy *) gupnp_device_info_get_service((GUPnPDeviceInfo *)proxy, cm_type); service_proxies->av_proxy = (GUPnPServiceProxy *) gupnp_device_info_get_service((GUPnPDeviceInfo *)proxy, av_type); service_proxies->rc_proxy = (GUPnPServiceProxy *) gupnp_device_info_get_service((GUPnPDeviceInfo *)proxy, rc_type); *context = ctx; } static dlr_device_context_t *prv_device_get_subscribed_context( const dlr_device_t *device) { dlr_device_context_t *context; unsigned int i; for (i = 0; i < device->contexts->len; ++i) { context = g_ptr_array_index(device->contexts, i); if (context->subscribed_av || context->subscribed_cm || context->subscribed_rc) goto on_found; } return NULL; on_found: return context; } static void prv_device_append_new_context(dlr_device_t *device, const gchar *ip_address, GUPnPDeviceProxy *proxy) { dlr_device_context_t *new_context; prv_context_new(ip_address, proxy, device, &new_context); g_ptr_array_add(device->contexts, new_context); } static void prv_device_subscribe_context(dlr_device_t *device) { dlr_device_context_t *subscribed_context; dlr_device_context_t *preferred_context; subscribed_context = prv_device_get_subscribed_context(device); preferred_context = dlr_device_get_context(device); if (subscribed_context != preferred_context) { if (subscribed_context) { DLEYNA_LOG_DEBUG( "Subscription switch from <%s> to <%s>", subscribed_context->ip_address, preferred_context->ip_address); prv_context_unsubscribe(subscribed_context); } dlr_device_subscribe_to_service_changes(device); } } void dlr_device_append_new_context(dlr_device_t *device, const gchar *ip_address, GUPnPDeviceProxy *proxy) { prv_device_append_new_context(device, ip_address, proxy); prv_device_subscribe_context(device); } void dlr_device_delete(void *device) { unsigned int i; dlr_device_t *dev = device; if (dev) { if (dev->timeout_id) (void) g_source_remove(dev->timeout_id); for (i = 0; i < DLR_INTERFACE_INFO_MAX && dev->ids[i]; ++i) (void) dlr_renderer_get_connector()->unpublish_object( dev->connection, dev->ids[i]); g_ptr_array_unref(dev->contexts); g_free(dev->path); prv_props_free(&dev->props); if (dev->transport_play_speeds != NULL) g_ptr_array_free(dev->transport_play_speeds, TRUE); if (dev->dlna_transport_play_speeds != NULL) g_ptr_array_free(dev->dlna_transport_play_speeds, TRUE); if (dev->mpris_transport_play_speeds) g_variant_unref(dev->mpris_transport_play_speeds); g_hash_table_unref(dev->rc_event_handlers); g_free(dev->rate); g_free(dev->icon.mime_type); g_free(dev->icon.bytes); g_free(dev); } } void dlr_device_unsubscribe(void *device) { unsigned int i; dlr_device_t *dev = device; dlr_device_context_t *context; if (dev) { for (i = 0; i < dev->contexts->len; ++i) { context = g_ptr_array_index(dev->contexts, i); prv_context_unsubscribe(context); } } } static gboolean prv_re_enable_cm_subscription(gpointer user_data) { dlr_device_context_t *context = user_data; context->timeout_id_cm = 0; return FALSE; } static void prv_cm_subscription_lost_cb(GUPnPServiceProxy *proxy, const GError *reason, gpointer user_data) { dlr_device_context_t *context = user_data; dlr_service_proxies_t *service_proxies = &context->service_proxies; if (!context->timeout_id_cm) { gupnp_service_proxy_set_subscribed(service_proxies->cm_proxy, TRUE); context->timeout_id_cm = g_timeout_add_seconds(10, prv_re_enable_cm_subscription, context); } else { g_source_remove(context->timeout_id_cm); (void) gupnp_service_proxy_remove_notify( service_proxies->cm_proxy, "SinkProtocolInfo", prv_sink_change_cb, context->device); context->timeout_id_cm = 0; context->subscribed_cm = FALSE; } } static gboolean prv_re_enable_av_subscription(gpointer user_data) { dlr_device_context_t *context = user_data; context->timeout_id_av = 0; return FALSE; } static void prv_av_subscription_lost_cb(GUPnPServiceProxy *proxy, const GError *reason, gpointer user_data) { dlr_device_context_t *context = user_data; dlr_service_proxies_t *service_proxies = &context->service_proxies; if (!context->timeout_id_av) { gupnp_service_proxy_set_subscribed(service_proxies->av_proxy, TRUE); context->timeout_id_av = g_timeout_add_seconds(10, prv_re_enable_av_subscription, context); } else { g_source_remove(context->timeout_id_av); (void) gupnp_service_proxy_remove_notify( service_proxies->av_proxy, "LastChange", prv_last_change_cb, context->device); context->timeout_id_av = 0; context->subscribed_av = FALSE; } } static gboolean prv_re_enable_rc_subscription(gpointer user_data) { dlr_device_context_t *context = user_data; context->timeout_id_rc = 0; return FALSE; } static void prv_rc_subscription_lost_cb(GUPnPServiceProxy *proxy, const GError *reason, gpointer user_data) { dlr_device_context_t *context = user_data; dlr_service_proxies_t *service_proxies = &context->service_proxies; if (!context->timeout_id_rc) { gupnp_service_proxy_set_subscribed(service_proxies->rc_proxy, TRUE); context->timeout_id_rc = g_timeout_add_seconds(10, prv_re_enable_rc_subscription, context); } else { g_source_remove(context->timeout_id_rc); (void) gupnp_service_proxy_remove_notify( service_proxies->rc_proxy, "LastChange", prv_rc_last_change_cb, context->device); context->timeout_id_rc = 0; context->subscribed_rc = FALSE; } } void dlr_device_subscribe_to_service_changes(dlr_device_t *device) { dlr_device_context_t *context; dlr_service_proxies_t *service_proxies; context = dlr_device_get_context(device); service_proxies = &context->service_proxies; DLEYNA_LOG_DEBUG("Subscribing through context <%s>", context->ip_address); if (service_proxies->cm_proxy) { gupnp_service_proxy_set_subscribed(service_proxies->cm_proxy, TRUE); (void) gupnp_service_proxy_add_notify(service_proxies->cm_proxy, "SinkProtocolInfo", G_TYPE_STRING, prv_sink_change_cb, device); context->subscribed_cm = TRUE; g_signal_connect(service_proxies->cm_proxy, "subscription-lost", G_CALLBACK(prv_cm_subscription_lost_cb), context); } if (service_proxies->av_proxy) { gupnp_service_proxy_set_subscribed(service_proxies->av_proxy, TRUE); (void) gupnp_service_proxy_add_notify(service_proxies->av_proxy, "LastChange", G_TYPE_STRING, prv_last_change_cb, device); context->subscribed_av = TRUE; g_signal_connect(service_proxies->av_proxy, "subscription-lost", G_CALLBACK(prv_av_subscription_lost_cb), context); } if (service_proxies->rc_proxy) { gupnp_service_proxy_set_subscribed(service_proxies->rc_proxy, TRUE); (void) gupnp_service_proxy_add_notify(service_proxies->rc_proxy, "LastChange", G_TYPE_STRING, prv_rc_last_change_cb, device); context->subscribed_rc = TRUE; g_signal_connect(service_proxies->av_proxy, "subscription-lost", G_CALLBACK(prv_rc_subscription_lost_cb), context); } } static void prv_as_prop_from_hash_table(const gchar *prop_name, GHashTable *values, GHashTable *props) { GVariantBuilder vb; GHashTableIter iter; gpointer key; GVariant *val; g_variant_builder_init(&vb, G_VARIANT_TYPE("as")); g_hash_table_iter_init(&iter, values); while (g_hash_table_iter_next(&iter, &key, NULL)) g_variant_builder_add(&vb, "s", key); val = g_variant_ref_sink(g_variant_builder_end(&vb)); g_hash_table_insert(props, (gchar *)prop_name, val); } static void prv_process_protocol_info(dlr_device_t *device, const gchar *protocol_info) { GVariant *val; gchar **entries; gchar **type_info; unsigned int i; GHashTable *protocols; GHashTable *types; const char http_prefix[] = "http-"; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("prv_process_protocol_info: %s", protocol_info); protocols = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); types = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); val = g_variant_ref_sink(g_variant_new_string(protocol_info)); g_hash_table_insert(device->props.device_props, DLR_INTERFACE_PROP_PROTOCOL_INFO, val); entries = g_strsplit(protocol_info, ",", 0); for (i = 0; entries[i]; ++i) { type_info = g_strsplit(entries[i], ":", 0); if (type_info[0] && type_info[1] && type_info[2]) { if (!g_ascii_strncasecmp(http_prefix, type_info[0], sizeof(http_prefix) - 1)) { type_info[0][sizeof(http_prefix) - 2] = 0; } g_hash_table_insert(protocols, g_ascii_strdown(type_info[0], -1), NULL); g_hash_table_insert(types, g_ascii_strdown(type_info[2], -1), NULL); } g_strfreev(type_info); } g_strfreev(entries); prv_as_prop_from_hash_table(DLR_INTERFACE_PROP_SUPPORTED_URIS, protocols, device->props.root_props); prv_as_prop_from_hash_table(DLR_INTERFACE_PROP_SUPPORTED_MIME, types, device->props.root_props); g_hash_table_unref(types); g_hash_table_unref(protocols); DLEYNA_LOG_DEBUG("Exit"); } static void prv_get_protocol_info_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { gchar *result = NULL; gboolean end; GError *error = NULL; prv_new_device_ct_t *priv_t = (prv_new_device_ct_t *)user_data; DLEYNA_LOG_DEBUG("Enter"); priv_t->dev->construct_step++; end = gupnp_service_proxy_end_action(proxy, action, &error, "Sink", G_TYPE_STRING, &result, NULL); if (!end || (result == NULL)) { DLEYNA_LOG_WARNING("GetProtocolInfo operation failed: %s", ((error != NULL) ? error->message : "Invalid result")); goto on_error; } prv_process_protocol_info(priv_t->dev, result); on_error: if (error) g_error_free(error); g_free(result); DLEYNA_LOG_DEBUG("Exit"); } static GUPnPServiceProxyAction *prv_get_protocol_info( dleyna_service_task_t *task, GUPnPServiceProxy *proxy, gboolean *failed) { *failed = FALSE; return gupnp_service_proxy_begin_action( proxy, "GetProtocolInfo", dleyna_service_task_begin_action_cb, task, NULL); } static GUPnPServiceProxyAction *prv_subscribe(dleyna_service_task_t *task, GUPnPServiceProxy *proxy, gboolean *failed) { dlr_device_t *device; DLEYNA_LOG_DEBUG("Enter"); device = (dlr_device_t *)dleyna_service_task_get_user_data(task); device->construct_step++; prv_device_subscribe_context(device); *failed = FALSE; DLEYNA_LOG_DEBUG("Exit"); return NULL; } static GUPnPServiceProxyAction *prv_declare(dleyna_service_task_t *task, GUPnPServiceProxy *proxy, gboolean *failed) { unsigned int i; dlr_device_t *device; prv_new_device_ct_t *priv_t; const dleyna_connector_dispatch_cb_t *table; DLEYNA_LOG_DEBUG("Enter"); *failed = FALSE; priv_t = (prv_new_device_ct_t *)dleyna_service_task_get_user_data(task); device = priv_t->dev; device->construct_step++; table = priv_t->dispatch_table; for (i = 0; i < DLR_INTERFACE_INFO_MAX; ++i) { device->ids[i] = dlr_renderer_get_connector()->publish_object( device->connection, device->path, FALSE, dlr_renderer_get_interface_name(i), table + i); if (!device->ids[i]) { *failed = TRUE; goto on_error; } } on_error: DLEYNA_LOG_DEBUG("Exit"); return NULL; } static void prv_free_rc_event(gpointer user_data) { dlr_rc_event_t *event = user_data; if (event->source_id) g_source_remove(event->source_id); g_free(event); } void dlr_device_construct( dlr_device_t *dev, dlr_device_context_t *context, dleyna_connector_id_t connection, const dleyna_connector_dispatch_cb_t *dispatch_table, const dleyna_task_queue_key_t *queue_id) { prv_new_device_ct_t *priv_t; GUPnPServiceProxy *s_proxy; DLEYNA_LOG_DEBUG("Current step: %d", dev->construct_step); priv_t = g_new0(prv_new_device_ct_t, 1); priv_t->dev = dev; priv_t->dispatch_table = dispatch_table; s_proxy = context->service_proxies.cm_proxy; if (dev->construct_step < 1) dleyna_service_task_add(queue_id, prv_get_protocol_info, s_proxy, prv_get_protocol_info_cb, NULL, priv_t); /* The following task should always be completed */ dleyna_service_task_add(queue_id, prv_subscribe, s_proxy, NULL, NULL, dev); if (dev->construct_step < 3) dleyna_service_task_add(queue_id, prv_declare, s_proxy, NULL, g_free, priv_t); dleyna_task_queue_start(queue_id); DLEYNA_LOG_DEBUG("Exit"); } dlr_device_t *dlr_device_new( dleyna_connector_id_t connection, GUPnPDeviceProxy *proxy, const gchar *ip_address, const char *udn, const dleyna_connector_dispatch_cb_t *dispatch_table, const dleyna_task_queue_key_t *queue_id) { dlr_device_t *dev; gchar *new_path; gchar *uuid; dlr_device_context_t *context; DLEYNA_LOG_DEBUG("New Device on %s", ip_address); uuid = dleyna_core_prv_convert_udn_to_path(udn); new_path = g_strdup_printf("%s/%s", DLEYNA_SERVER_PATH, uuid); g_free(uuid); DLEYNA_LOG_DEBUG("Server Path %s", new_path); dev = g_new0(dlr_device_t, 1); dev->connection = connection; dev->contexts = g_ptr_array_new_with_free_func(prv_dlr_context_delete); dev->path = new_path; dev->rate = g_strdup("1"); dev->rc_event_handlers = g_hash_table_new_full(g_int_hash, g_int_equal, g_free, prv_free_rc_event); prv_props_init(&dev->props); prv_device_append_new_context(dev, ip_address, proxy); context = dlr_device_get_context(dev); dlr_device_construct(dev, context, connection, dispatch_table, queue_id); DLEYNA_LOG_DEBUG("Exit"); return dev; } dlr_device_t *dlr_device_from_path(const gchar *path, GHashTable *device_list) { GHashTableIter iter; gpointer value; dlr_device_t *device; dlr_device_t *retval = NULL; g_hash_table_iter_init(&iter, device_list); while (g_hash_table_iter_next(&iter, NULL, &value)) { device = value; if (!strcmp(device->path, path)) { retval = device; break; } } return retval; } dlr_device_context_t *dlr_device_get_context(dlr_device_t *device) { dlr_device_context_t *context; unsigned int i; const char ip4_local_prefix[] = "127.0.0."; for (i = 0; i < device->contexts->len; ++i) { context = g_ptr_array_index(device->contexts, i); if (!strncmp(context->ip_address, ip4_local_prefix, sizeof(ip4_local_prefix) - 1) || !strcmp(context->ip_address, "::1") || !strcmp(context->ip_address, "0:0:0:0:0:0:0:1")) break; } if (i == device->contexts->len) context = g_ptr_array_index(device->contexts, 0); return context; } static void prv_get_prop(dlr_async_task_t *cb_data) { dlr_task_get_prop_t *get_prop = &cb_data->task.ut.get_prop; GVariant *res = NULL; DLEYNA_LOG_DEBUG("Enter"); if (!strcmp(get_prop->interface_name, DLEYNA_SERVER_INTERFACE_RENDERER_DEVICE)) { res = g_hash_table_lookup(cb_data->device->props.device_props, get_prop->prop_name); } else if (!strcmp(get_prop->interface_name, DLR_INTERFACE_SERVER)) { res = g_hash_table_lookup(cb_data->device->props.root_props, get_prop->prop_name); } else if (!strcmp(get_prop->interface_name, DLR_INTERFACE_PLAYER)) { res = g_hash_table_lookup(cb_data->device->props.player_props, get_prop->prop_name); } else if (!strcmp(get_prop->interface_name, "")) { res = g_hash_table_lookup(cb_data->device->props.root_props, get_prop->prop_name); if (!res) res = g_hash_table_lookup( cb_data->device->props.player_props, get_prop->prop_name); if (!res) res = g_hash_table_lookup( cb_data->device->props.device_props, get_prop->prop_name); } else { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_INTERFACE, "Unknown Interface"); } if (!res) { if (!cb_data->error) cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_PROPERTY, "Property not defined for object"); } else { cb_data->task.result = g_variant_ref(res); } DLEYNA_LOG_DEBUG("Exit"); } static void prv_add_props(GHashTable *props, GVariantBuilder *vb) { GHashTableIter iter; gpointer key; gpointer value; g_hash_table_iter_init(&iter, props); while (g_hash_table_iter_next(&iter, &key, &value)) g_variant_builder_add(vb, "{sv}", (gchar *)key, (GVariant *)value); } static void prv_get_props(dlr_async_task_t *cb_data) { dlr_task_get_props_t *get_props = &cb_data->task.ut.get_props; GVariantBuilder *vb; DLEYNA_LOG_DEBUG("Enter"); vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); if (!strcmp(get_props->interface_name, DLEYNA_SERVER_INTERFACE_RENDERER_DEVICE)) { prv_add_props(cb_data->device->props.device_props, vb); } else if (!strcmp(get_props->interface_name, DLR_INTERFACE_SERVER)) { prv_add_props(cb_data->device->props.root_props, vb); prv_add_props(cb_data->device->props.device_props, vb); } else if (!strcmp(get_props->interface_name, DLR_INTERFACE_PLAYER)) { prv_add_props(cb_data->device->props.player_props, vb); } else if (!strcmp(get_props->interface_name, "")) { prv_add_props(cb_data->device->props.root_props, vb); prv_add_props(cb_data->device->props.player_props, vb); prv_add_props(cb_data->device->props.device_props, vb); } else { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_INTERFACE, "Unknown Interface"); goto on_error; } cb_data->task.result = g_variant_ref_sink(g_variant_builder_end(vb)); on_error: g_variant_builder_unref(vb); DLEYNA_LOG_DEBUG("Exit"); } static const gchar *prv_map_transport_state(const gchar *upnp_state) { const gchar *retval; if (!strcmp(upnp_state, "PLAYING")) retval = "Playing"; else if (!strcmp(upnp_state, "PAUSED_PLAYBACK") || !strcmp(upnp_state, "PAUSED_RECORDING")) retval = "Paused"; else retval = "Stopped"; return retval; } static gdouble prv_map_transport_speed(const gchar *upnp_speed) { gdouble retval = 1; gchar **parts = NULL; gint num; gint dom; if (upnp_speed[0]) { parts = g_strsplit(upnp_speed, "/", 0); if (!parts[0]) goto on_error; g_strstrip(parts[0]); num = atoi(parts[0]); if (parts[1]) { if (parts[2]) goto on_error; g_strstrip(parts[1]); dom = atoi(parts[1]); if (dom == 0) goto on_error; retval = num / (gdouble) dom; } else { retval = num; } } on_error: if (parts) g_strfreev(parts); return retval; } static gint compare_speeds(gconstpointer a, gconstpointer b) { return prv_compare_rationals((const gchar *)a, (const gchar *)b); } static void prv_add_dlna_speeds(dlr_device_t *device, gchar **dlna_speeds, GVariantBuilder *changed_props_vb) { GList *allowed_tp_speeds = NULL; double min_rate = 0; double max_rate = 0; GVariant *mpris_tp_speeds = NULL; unsigned int i = 0; gchar *speed; if (dlna_speeds == NULL) goto exit; allowed_tp_speeds = g_list_prepend(allowed_tp_speeds, g_strdup("1")); while (dlna_speeds[i]) { speed = g_strstrip(g_strdup(dlna_speeds[i])); allowed_tp_speeds = g_list_prepend(allowed_tp_speeds, speed); ++i; } allowed_tp_speeds = g_list_sort(allowed_tp_speeds, compare_speeds); prv_get_rates_values(allowed_tp_speeds, &mpris_tp_speeds, &device->dlna_transport_play_speeds, &min_rate, &max_rate); prv_add_player_speed_props(device->props.player_props, min_rate, max_rate, mpris_tp_speeds, changed_props_vb); exit: if (allowed_tp_speeds != NULL) g_list_free_full(allowed_tp_speeds, g_free); return; } static GVariant *prv_as_prop_from_list(GList *list) { GVariantBuilder vb; g_variant_builder_init(&vb, G_VARIANT_TYPE("as")); do { g_variant_builder_add(&vb, "s", list->data); } while ((list = g_list_next(list))); return g_variant_ref_sink(g_variant_builder_end(&vb)); } static GVariant *prv_update_prop_dlna_device_classes(GUPnPDeviceInfo *proxy, GHashTable *props) { GVariant *retval; GList *dlna_classes; retval = g_hash_table_lookup(props, DLR_INTERFACE_PROP_DLNA_DEVICE_CLASSES); if (retval) goto on_exit; dlna_classes = gupnp_device_info_list_dlna_device_class_identifier(proxy); if (!dlna_classes) goto on_exit; retval = prv_as_prop_from_list(dlna_classes); g_hash_table_insert(props, DLR_INTERFACE_PROP_DLNA_DEVICE_CLASSES, retval); g_list_free_full(dlna_classes, g_free); on_exit: return retval; } static void prv_add_actions(dlr_device_t *device, const gchar *actions, GVariantBuilder *changed_props_vb) { gchar **parts; unsigned int i = 0; GVariant *true_val; GVariant *false_val; gboolean play = FALSE; gboolean ppause = FALSE; gboolean seek = FALSE; gboolean timeseek_missing = FALSE; gboolean byteseek = FALSE; gboolean next = FALSE; gboolean previous = FALSE; GVariant *val; GRegex *regex; gchar *tmp_str; gchar **speeds; GVariantIter iter; gchar *dlna_device_class; gchar *pos; GUPnPDeviceInfo *info; regex = g_regex_new("\\\\,", 0, 0, NULL); tmp_str = g_regex_replace_literal(regex, actions, -1, 0, "*", 0, NULL); parts = g_strsplit(tmp_str, ",", 0); g_free(tmp_str); g_regex_unref(regex); true_val = g_variant_ref_sink(g_variant_new_boolean(TRUE)); false_val = g_variant_ref_sink(g_variant_new_boolean(FALSE)); info = (GUPnPDeviceInfo *)dlr_device_get_context(device)->device_proxy; val = prv_update_prop_dlna_device_classes(info, device->props.device_props); /* If this device is not dlna compatible, there is no need */ /* to check for “X_DLNA_SeekTime” */ if (val) { g_variant_iter_init(&iter, val); while (g_variant_iter_next(&iter, "s", &dlna_device_class)) { /* Could be DMR-version or M-DMR-version*/ pos = g_strrstr(dlna_device_class, "DMR-"); if (!pos || pos + 4 == 0 || strtof(pos + 4, 0) < 1.5f) { g_free(dlna_device_class); continue; } DLEYNA_LOG_DEBUG("DLNA version ≥ 1.50 pour %s", device->path); timeseek_missing = TRUE; g_free(dlna_device_class); break; } } while (parts[i]) { g_strstrip(parts[i]); if (!strcmp(parts[i], "Play")) { play = TRUE; } else if (!strcmp(parts[i], "Pause")) { ppause = TRUE; } else if (!strcmp(parts[i], "Seek")) { seek = TRUE; } else if (!strcmp(parts[i], "Next")) { next = TRUE; } else if (!strcmp(parts[i], "Previous")) { previous = TRUE; } else if (!strncmp(parts[i], "X_DLNA_PS=", strlen("X_DLNA_PS="))) { speeds = g_strsplit(parts[i] + strlen("X_DLNA_PS="), "*", 0); prv_add_dlna_speeds(device, speeds, changed_props_vb); g_strfreev(speeds); } else if (!strcmp(parts[i], "X_DLNA_SeekTime")) { timeseek_missing = FALSE; } else if (!strcmp(parts[i], "X_DLNA_SeekByte")) { byteseek = TRUE; } ++i; } g_variant_ref(false_val); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_CAN_CONTROL, false_val, changed_props_vb); val = play ? true_val : false_val; g_variant_ref(val); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_CAN_PLAY, val, changed_props_vb); val = ppause ? true_val : false_val; g_variant_ref(val); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_CAN_PAUSE, val, changed_props_vb); val = seek && !timeseek_missing ? true_val : false_val; g_variant_ref(val); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_CAN_SEEK, val, changed_props_vb); val = seek && byteseek ? true_val : false_val; g_variant_ref(val); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_CAN_BYTE_SEEK, val, changed_props_vb); val = next ? true_val : false_val; g_variant_ref(val); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_CAN_NEXT, val, changed_props_vb); val = previous ? true_val : false_val; g_variant_ref(val); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_CAN_PREVIOUS, val, changed_props_vb); g_variant_unref(true_val); g_variant_unref(false_val); g_strfreev(parts); } static void prv_add_all_actions(dlr_device_t *device, GVariantBuilder *changed_props_vb) { GVariant *val; val = g_variant_ref_sink(g_variant_new_boolean(TRUE)); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_CAN_PLAY, val, changed_props_vb); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_CAN_PAUSE, g_variant_ref(val), changed_props_vb); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_CAN_SEEK, g_variant_ref(val), changed_props_vb); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_CAN_NEXT, g_variant_ref(val), changed_props_vb); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_CAN_PREVIOUS, g_variant_ref(val), changed_props_vb); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_CAN_CONTROL, g_variant_ref(val), changed_props_vb); } static gint64 prv_duration_to_int64(const gchar *duration) { gchar **parts; unsigned int i = 0; unsigned int count; gint64 pos = 0; parts = g_strsplit(duration, ":", 0); for (count = 0; parts[count]; ++count) ; if (count != 3) goto on_error; /* TODO: This does not handle fractional seconds */ i = 1; do { --count; g_strstrip(parts[count]); pos += atoi(parts[count]) * i; i *= 60; } while (count > 0); pos *= 1000000; on_error: g_strfreev(parts); return pos; } static gchar *prv_int64_to_duration(gint64 micro_seconds) { GString *retval; unsigned int seconds; if (micro_seconds < 0) { retval = g_string_new("-"); micro_seconds = -micro_seconds; } else { retval = g_string_new(""); } /* TODO: This does not handle fractional seconds */ seconds = micro_seconds / 1000000; g_string_append_printf(retval, "%02u:%02u:%02u", seconds / 3600, (seconds / 60) % 60, seconds % 60); return g_string_free(retval, FALSE); } static void prv_add_reltime(dlr_device_t *device, const gchar *reltime, GVariantBuilder *changed_props_vb) { GVariant *val; gint64 pos = prv_duration_to_int64(reltime); val = g_variant_ref_sink(g_variant_new_int64(pos)); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_POSITION, val, changed_props_vb); } static void prv_add_relcount(dlr_device_t *device, const gchar *relcount, GVariantBuilder *changed_props_vb) { GVariant *val; guint64 count = strtoll(relcount, NULL, 10); val = g_variant_ref_sink(g_variant_new_uint64(count)); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_BYTE_POSITION, val, changed_props_vb); } static void prv_found_item(GUPnPDIDLLiteParser *parser, GUPnPDIDLLiteObject *object, gpointer user_data) { GVariantBuilder *vb = user_data; gchar *track_id; int track_number = gupnp_didl_lite_object_get_track_number(object); GVariant *value; const gchar *str_value; GVariantBuilder *artists_vb; GVariantBuilder *album_artists_vb; GList *artists; GList *head; const gchar *artist_name; const gchar *artist_role; track_id = g_strdup_printf(DLEYNA_SERVER_OBJECT"/track/%u", track_number != -1 ? track_number : 0); value = g_variant_new_string(track_id); g_variant_builder_add(vb, "{sv}", "mpris:trackid", value); g_free(track_id); if (track_number != -1) { value = g_variant_new_int32(track_number); g_variant_builder_add(vb, "{sv}", "mpris:trackNumber", value); } str_value = gupnp_didl_lite_object_get_title(object); if (str_value) { value = g_variant_new_string(str_value); g_variant_builder_add(vb, "{sv}", "xesam:title", value); } str_value = gupnp_didl_lite_object_get_album_art(object); if (str_value) { value = g_variant_new_string(str_value); g_variant_builder_add(vb, "{sv}", "mpris:artUrl", value); } str_value = gupnp_didl_lite_object_get_album(object); if (str_value) { value = g_variant_new_string(str_value); g_variant_builder_add(vb, "{sv}", "xesam:album", value); } str_value = gupnp_didl_lite_object_get_genre(object); if (str_value) { value = g_variant_new_string(str_value); g_variant_builder_add(vb, "{sv}", "xesam:genre", value); } artists = gupnp_didl_lite_object_get_artists(object); head = artists; if (artists) { artists_vb = g_variant_builder_new(G_VARIANT_TYPE("as")); album_artists_vb = g_variant_builder_new(G_VARIANT_TYPE("as")); do { artist_name = gupnp_didl_lite_contributor_get_name( artists->data); artist_role = gupnp_didl_lite_contributor_get_role( artists->data); if (!artist_role) g_variant_builder_add(artists_vb, "s", artist_name); else if (!strcmp(artist_role, "AlbumArtist")) g_variant_builder_add(album_artists_vb, "s", artist_name); g_object_unref(artists->data); artists = g_list_next(artists); } while (artists); g_list_free(head); value = g_variant_builder_end(artists_vb); g_variant_builder_add(vb, "{sv}", "xesam:artist", value); value = g_variant_builder_end(album_artists_vb); g_variant_builder_add(vb, "{sv}", "xesam:albumArtist", value); g_variant_builder_unref(artists_vb); g_variant_builder_unref(album_artists_vb); } } static void prv_add_track_meta_data(dlr_device_t *device, const gchar *metadata, const gchar *duration, const gchar *uri, GVariantBuilder *changed_props_vb) { gchar *didl = g_strdup_printf("%s", metadata); GUPnPDIDLLiteParser *parser = NULL; GVariantBuilder *vb; GError *upnp_error = NULL; GVariant *val; gint error_code; parser = gupnp_didl_lite_parser_new(); vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); if (duration) { val = g_variant_new_int64(prv_duration_to_int64(duration)); g_variant_builder_add(vb, "{sv}", "mpris:length", val); } if (uri) { val = g_variant_new_string(uri); g_variant_builder_add(vb, "{sv}", "xesam:url", val); } g_signal_connect(parser, "object-available" , G_CALLBACK(prv_found_item), vb); if (!gupnp_didl_lite_parser_parse_didl(parser, didl, &upnp_error)) { error_code = upnp_error->code; g_error_free(upnp_error); if (error_code != GUPNP_XML_ERROR_EMPTY_NODE) goto on_error; } prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_METADATA, g_variant_ref_sink(g_variant_builder_end(vb)), changed_props_vb); on_error: if (parser) g_object_unref(parser); g_variant_builder_unref(vb); g_free(didl); } static void prv_last_change_cb(GUPnPServiceProxy *proxy, const char *variable, GValue *value, gpointer user_data) { GUPnPLastChangeParser *parser; dlr_device_t *device = user_data; GVariantBuilder *changed_props_vb; GVariant *changed_props; gchar *meta_data = NULL; gchar *actions = NULL; gchar *play_speed = NULL; gchar *state = NULL; gchar *duration = NULL; gchar *uri = NULL; guint tracks_number = G_MAXUINT; guint current_track = G_MAXUINT; GVariant *val; parser = gupnp_last_change_parser_new(); if (!gupnp_last_change_parser_parse_last_change( parser, 0, g_value_get_string(value), NULL, "CurrentTrackMetaData", G_TYPE_STRING, &meta_data, "CurrentTransportActions", G_TYPE_STRING, &actions, "TransportPlaySpeed", G_TYPE_STRING, &play_speed, "TransportState", G_TYPE_STRING, &state, "CurrentTrackDuration", G_TYPE_STRING, &duration, "CurrentTrackURI", G_TYPE_STRING, &uri, "NumberOfTracks", G_TYPE_UINT, &tracks_number, "CurrentTrack", G_TYPE_UINT, ¤t_track, NULL)) goto on_error; changed_props_vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); if (meta_data) { prv_add_track_meta_data(device, meta_data, duration, uri, changed_props_vb); g_free(meta_data); } else { if (duration) { val = g_variant_new_int64(prv_duration_to_int64( duration)); val = g_variant_ref_sink(val); prv_merge_meta_data(device, "mpris:length", val, changed_props_vb); g_variant_unref(val); } if (uri) { val = g_variant_ref_sink(g_variant_new_string(uri)); prv_merge_meta_data(device, "xesam:url", val, changed_props_vb); g_variant_unref(val); } } g_free(duration); g_free(uri); if (actions) { prv_add_actions(device, actions, changed_props_vb); g_free(actions); } if (play_speed) { val = g_variant_ref_sink( g_variant_new_double( prv_map_transport_speed(play_speed))); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_RATE, val, changed_props_vb); g_free(device->rate); device->rate = play_speed; } if (state) { val = g_variant_ref_sink( g_variant_new_string( prv_map_transport_state(state))); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_PLAYBACK_STATUS, val, changed_props_vb); g_free(state); } if (tracks_number != G_MAXUINT) { val = g_variant_ref_sink(g_variant_new_uint32(tracks_number)); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_NUMBER_OF_TRACKS, val, changed_props_vb); } if (current_track != G_MAXUINT) { val = g_variant_ref_sink(g_variant_new_uint32(current_track)); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_CURRENT_TRACK, val, changed_props_vb); } changed_props = g_variant_ref_sink( g_variant_builder_end(changed_props_vb)); prv_emit_signal_properties_changed(device, DLR_INTERFACE_PLAYER, changed_props); g_variant_unref(changed_props); g_variant_builder_unref(changed_props_vb); on_error: g_object_unref(parser); } static gboolean prv_process_rc_last_change(gpointer user_data) { dlr_rc_event_t *event = user_data; GVariantBuilder *changed_props_vb; GVariant *changed_props; GVariant *val; double mpris_volume; dlr_device_t *device = event->device; gint source_id; if (!device->props.synced && !prv_props_update(device, NULL)) goto on_lost_device; if (device->max_volume == 0) goto on_error; changed_props_vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); if (event->dev_volume != G_MAXUINT) { mpris_volume = (double) event->dev_volume / (double) device->max_volume; val = g_variant_ref_sink(g_variant_new_double(mpris_volume)); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_VOLUME, val, changed_props_vb); } if (event->mute != G_MAXUINT) { val = g_variant_ref_sink( g_variant_new_boolean(event->mute ? TRUE : FALSE)); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_MUTE, val, changed_props_vb); } changed_props = g_variant_ref_sink( g_variant_builder_end(changed_props_vb)); prv_emit_signal_properties_changed(device, DLR_INTERFACE_PLAYER, changed_props); g_variant_unref(changed_props); g_variant_builder_unref(changed_props_vb); on_error: source_id = (gint) event->source_id; event->source_id = 0; g_hash_table_remove(device->rc_event_handlers, &source_id); on_lost_device: return FALSE; } static void prv_rc_last_change_cb(GUPnPServiceProxy *proxy, const char *variable, GValue *value, gpointer user_data) { GUPnPLastChangeParser *parser; dlr_rc_event_t *event; guint dev_volume = G_MAXUINT; guint mute = G_MAXUINT; gint *key; parser = gupnp_last_change_parser_new(); if (!gupnp_last_change_parser_parse_last_change( parser, 0, g_value_get_string(value), NULL, "Volume", G_TYPE_UINT, &dev_volume, "Mute", G_TYPE_UINT, &mute, NULL)) goto on_error; event = g_new0(dlr_rc_event_t, 1); event->dev_volume = dev_volume; event->mute = mute; event->device = user_data; /* We cannot execute the code in prv_process_rc_last_change directly in this function as it can cause the main loop to be iterated, which may cause a crash when we return back to GUPnP. This code will be re-written once we can cancel calls to retrieve the service introspection data. */ event->source_id = g_idle_add(prv_process_rc_last_change, event); key = g_new(gint, 1); *key = (gint) event->source_id; g_hash_table_insert(event->device->rc_event_handlers, key, event); on_error: g_object_unref(parser); } static void prv_sink_change_cb(GUPnPServiceProxy *proxy, const char *variable, GValue *value, gpointer user_data) { dlr_device_t *device = user_data; const gchar *sink; sink = g_value_get_string(value); if (sink) prv_process_protocol_info(device, sink); } static void prv_get_position_info_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { gchar *result = NULL; const gchar *message; gboolean end; dlr_async_task_t *cb_data = user_data; GError *error = NULL; GVariantBuilder *changed_props_vb; GVariant *changed_props; end = gupnp_service_proxy_end_action( cb_data->proxy, cb_data->action, &error, "RelTime", G_TYPE_STRING, &result, NULL); if (!end || (result == NULL)) { message = (error != NULL) ? error->message : "Invalid result"; cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "GetPositionInfo operation failed: %s", message); if (error != NULL) g_error_free(error); goto on_error; } changed_props_vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); g_strstrip(result); prv_add_reltime(cb_data->device, result, changed_props_vb); changed_props = g_variant_ref_sink( g_variant_builder_end(changed_props_vb)); prv_emit_signal_properties_changed(cb_data->device, DLR_INTERFACE_PLAYER, changed_props); g_variant_unref(changed_props); g_variant_builder_unref(changed_props_vb); g_free(result); prv_get_prop(cb_data); on_error: (void) g_idle_add(dlr_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); } static void prv_get_byte_position_info_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { gchar *result = NULL; const gchar *message; gboolean end; dlr_async_task_t *cb_data = user_data; GError *error = NULL; GVariantBuilder *changed_props_vb; GVariant *changed_props; end = gupnp_service_proxy_end_action( cb_data->proxy, cb_data->action, &error, "RelByte", G_TYPE_STRING, &result, NULL); if (!end || (result == NULL)) { message = (error != NULL) ? error->message : "Invalid result"; cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "X_DLNA_GetBytePositionInfo operation failed: %s", message); if (error != NULL) g_error_free(error); goto on_error; } changed_props_vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); g_strstrip(result); prv_add_relcount(cb_data->device, result, changed_props_vb); changed_props = g_variant_ref_sink( g_variant_builder_end(changed_props_vb)); prv_emit_signal_properties_changed(cb_data->device, DLR_INTERFACE_PLAYER, changed_props); g_variant_unref(changed_props); g_variant_builder_unref(changed_props_vb); g_free(result); prv_get_prop(cb_data); on_error: (void) g_idle_add(dlr_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); } static void prv_get_all_position_info_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { gchar *result = NULL; dlr_async_task_t *cb_data = user_data; GError *error = NULL; dlr_device_data_t *device_data = cb_data->private; GVariantBuilder *changed_props_vb; GVariant *changed_props; if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, &error, "RelTime", G_TYPE_STRING, &result, NULL)) { if (error != NULL) { DLEYNA_LOG_WARNING( "GetPositionInfo operation failed: %s", error->message); g_error_free(error); } } if (result == NULL) { device_data->ut.get_all_position.expected_props--; /* Do not fail, just remove the property */ g_hash_table_remove(cb_data->device->props.player_props, DLR_INTERFACE_PROP_POSITION); } device_data->ut.get_all_position.rel_time = result; if (!device_data->ut.get_all_position.expected_props) goto on_complete; changed_props_vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); if (device_data->ut.get_all_position.rel_time != NULL) { g_strstrip(device_data->ut.get_all_position.rel_time); prv_add_reltime(cb_data->device, device_data->ut.get_all_position.rel_time, changed_props_vb); } if (device_data->ut.get_all_position.rel_cnt != NULL) { g_strstrip(device_data->ut.get_all_position.rel_cnt); prv_add_relcount(cb_data->device, device_data->ut.get_all_position.rel_cnt, changed_props_vb); } changed_props = g_variant_ref_sink( g_variant_builder_end(changed_props_vb)); prv_emit_signal_properties_changed(cb_data->device, DLR_INTERFACE_PLAYER, changed_props); g_variant_unref(changed_props); g_variant_builder_unref(changed_props_vb); on_complete: prv_get_props(cb_data); (void) g_idle_add(dlr_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); return; } static void prv_get_all_byte_position_info_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { gchar *result = NULL; dlr_async_task_t *cb_data = user_data; GError *error = NULL; dlr_device_data_t *device_data = cb_data->private; if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, &error, "RelByte", G_TYPE_STRING, &result, NULL)) { if (error != NULL) { DLEYNA_LOG_WARNING( "X_DLNA_GetBytePositionInfo operation failed: %s", error->message); g_error_free(error); } } if (result == NULL) { device_data->ut.get_all_position.expected_props--; /* Do not fail, just remove the property */ g_hash_table_remove(cb_data->device->props.player_props, DLR_INTERFACE_PROP_BYTE_POSITION); } device_data->ut.get_all_position.rel_cnt = result; cb_data->action = gupnp_service_proxy_begin_action( cb_data->proxy, "GetPositionInfo", prv_get_all_position_info_cb, cb_data, "InstanceID", G_TYPE_INT, 0, NULL); return; } static void prv_get_position_info(dlr_async_task_t *cb_data, const gchar *action_name, GUPnPServiceProxyActionCallback callback) { dlr_device_context_t *context; context = dlr_device_get_context(cb_data->device); cb_data->cancel_id = g_cancellable_connect(cb_data->cancellable, G_CALLBACK(dlr_async_task_cancelled), cb_data, NULL); cb_data->proxy = context->service_proxies.av_proxy; g_object_add_weak_pointer((G_OBJECT(context->service_proxies.av_proxy)), (gpointer *)&cb_data->proxy); cb_data->action = gupnp_service_proxy_begin_action( cb_data->proxy, action_name, callback, cb_data, "InstanceID", G_TYPE_INT, 0, NULL); } /***********************************************************************/ /* Rational numbers parameters of the following functions are formed */ /* like this : «2» «5/6». A decimal notation like «2.6» is not allowed */ /***********************************************************************/ static inline long prv_rational_get_numerator(const char *r) { return strtol(r, NULL, 10); } static long prv_rational_get_denominator(const char *r) { char *div_pos = strstr(r, "/"); if (div_pos == NULL) goto exit; return strtol(div_pos + 1, NULL, 10); exit: return 1; } static double prv_rational_to_double(const char *r, double precision) { long p; long q; double result; p = prv_rational_get_numerator(r); if (p == 0) goto error; q = prv_rational_get_denominator(r); if (q == 0) goto error; result = (double)p/(double)q; if (precision != 0) result = round(result/precision) * precision; return result; error: return 0.0; } static inline gboolean prv_rational_is_invalid(const char *val) { return (prv_rational_get_numerator(val) == 0) || (prv_rational_get_denominator(val) == 0); } static gint prv_compare_rationals(const gchar *a, const gchar *b) { long a_numerator = prv_rational_get_numerator(a); long b_numerator = prv_rational_get_numerator(b); long a_denominator = prv_rational_get_denominator(a); long b_denominator = prv_rational_get_denominator(b); return (a_numerator * b_denominator) - (b_numerator * a_denominator); } static void prv_get_rates_values(GList *allowed_tp_speeds, GVariant **mpris_tp_speeds, GPtrArray **upnp_tp_speeds, double *min_rate, double *max_rate) { char *rate; char *min_rate_str; char *max_rate_str; GList *list; GVariantBuilder vb; const double precision = 0.01; if (allowed_tp_speeds == NULL) goto exit; g_variant_builder_init(&vb, G_VARIANT_TYPE("ad")); list = allowed_tp_speeds; min_rate_str = list->data; max_rate_str = min_rate_str; if (*upnp_tp_speeds != NULL) g_ptr_array_free(*upnp_tp_speeds, TRUE); *upnp_tp_speeds = g_ptr_array_new_with_free_func(g_free); for (; list != NULL; list = list->next) { rate = (char *)list->data; if (prv_rational_is_invalid(rate)) continue; g_ptr_array_add(*upnp_tp_speeds, g_strdup(rate)); g_variant_builder_add(&vb, "d", prv_rational_to_double(rate, precision)); if (prv_compare_rationals(min_rate_str, rate) > 0) min_rate_str = rate; else if (prv_compare_rationals(max_rate_str, rate) < 0) max_rate_str = rate; } *mpris_tp_speeds = g_variant_builder_end(&vb); *min_rate = prv_rational_to_double(min_rate_str, precision); *max_rate = prv_rational_to_double(max_rate_str, precision); exit: return; } static gboolean prv_get_av_service_states_values(GUPnPServiceProxy *av_proxy, GVariant **mpris_tp_speeds, GPtrArray **upnp_tp_speeds, double *min_rate, double *max_rate, gboolean *can_get_byte_pos) { const GUPnPServiceStateVariableInfo *svi; const GUPnPServiceActionInfo *sai; GUPnPServiceIntrospection *introspection; GError *error = NULL; GVariant *speeds = NULL; GList *allowed_values; gpointer weak_ref = NULL; gboolean device_alive = TRUE; /* TODO: this weak_ref hack is needed as gupnp_service_info_get_introspection iterates the main loop. This can result in our device getting deleted before this function returns. Ultimately, this code needs to be re-written to use gupnp_service_info_get_introspection_async but this cannot really be done until GUPnP provides a way to cancel this function. */ weak_ref = av_proxy; g_object_add_weak_pointer(G_OBJECT(av_proxy), &weak_ref); introspection = gupnp_service_info_get_introspection( GUPNP_SERVICE_INFO(av_proxy), &error); if (!weak_ref) { DLEYNA_LOG_WARNING("Lost device during introspection call"); device_alive = FALSE; goto exit; } g_object_remove_weak_pointer(G_OBJECT(av_proxy), &weak_ref); if (error != NULL) { DLEYNA_LOG_DEBUG( "failed to fetch AV service introspection file"); g_error_free(error); goto exit; } svi = gupnp_service_introspection_get_state_variable( introspection, "TransportPlaySpeed"); if (svi && svi->allowed_values) { allowed_values = svi->allowed_values; allowed_values = g_list_sort(allowed_values, compare_speeds); prv_get_rates_values(allowed_values, &speeds, upnp_tp_speeds, min_rate, max_rate); *mpris_tp_speeds = g_variant_ref_sink(speeds); } sai = gupnp_service_introspection_get_action( introspection, "X_DLNA_GetBytePositionInfo"); *can_get_byte_pos = (sai != NULL); g_object_unref(introspection); exit: return device_alive; } static gboolean prv_get_rc_service_states_values(GUPnPServiceProxy *rc_proxy, guint *max_volume) { const GUPnPServiceStateVariableInfo *svi; GUPnPServiceIntrospection *introspection; GError *error = NULL; gpointer weak_ref = NULL; gboolean device_alive = TRUE; /* TODO: this weak_ref hack is needed as gupnp_service_info_get_introspection iterates the main loop. This can result in our device getting deleted before this function returns. Ultimately, this code needs to be re-written to use gupnp_service_info_get_introspection_async but this cannot really be done until GUPnP provides a way to cancel this function. */ weak_ref = rc_proxy; g_object_add_weak_pointer(G_OBJECT(rc_proxy), &weak_ref); introspection = gupnp_service_info_get_introspection( GUPNP_SERVICE_INFO(rc_proxy), &error); if (!weak_ref) { DLEYNA_LOG_WARNING("Lost device during introspection call"); device_alive = FALSE; goto exit; } g_object_remove_weak_pointer(G_OBJECT(rc_proxy), &weak_ref); if (error != NULL) { DLEYNA_LOG_DEBUG( "failed to fetch RC service introspection file"); g_error_free(error); goto exit; } svi = gupnp_service_introspection_get_state_variable(introspection, "Volume"); if (svi != NULL) *max_volume = g_value_get_uint(&svi->maximum); g_object_unref(introspection); exit: return device_alive; } static void prv_update_device_props(GUPnPDeviceInfo *proxy, GHashTable *props) { GVariant *val; gchar *str; (void) prv_update_prop_dlna_device_classes(proxy, props); val = g_variant_ref_sink(g_variant_new_string( gupnp_device_info_get_device_type(proxy))); g_hash_table_insert(props, DLR_INTERFACE_PROP_DEVICE_TYPE, val); val = g_variant_ref_sink(g_variant_new_string( gupnp_device_info_get_udn(proxy))); g_hash_table_insert(props, DLR_INTERFACE_PROP_UDN, val); str = gupnp_device_info_get_friendly_name(proxy); val = g_variant_ref_sink(g_variant_new_string(str)); g_hash_table_insert(props, DLR_INTERFACE_PROP_FRIENDLY_NAME, val); g_free(str); str = gupnp_device_info_get_icon_url(proxy, NULL, -1, -1, -1, FALSE, NULL, NULL, NULL, NULL); val = g_variant_ref_sink(g_variant_new_string(str)); g_hash_table_insert(props, DLR_INTERFACE_PROP_ICON_URL, val); g_free(str); str = gupnp_device_info_get_manufacturer(proxy); val = g_variant_ref_sink(g_variant_new_string(str)); g_hash_table_insert(props, DLR_INTERFACE_PROP_MANUFACTURER, val); g_free(str); str = gupnp_device_info_get_manufacturer_url(proxy); val = g_variant_ref_sink(g_variant_new_string(str)); g_hash_table_insert(props, DLR_INTERFACE_PROP_MANUFACTURER_URL, val); g_free(str); str = gupnp_device_info_get_model_description(proxy); val = g_variant_ref_sink(g_variant_new_string(str)); g_hash_table_insert(props, DLR_INTERFACE_PROP_MODEL_DESCRIPTION, val); g_free(str); str = gupnp_device_info_get_model_name(proxy); val = g_variant_ref_sink(g_variant_new_string(str)); g_hash_table_insert(props, DLR_INTERFACE_PROP_MODEL_NAME, val); g_free(str); str = gupnp_device_info_get_model_number(proxy); val = g_variant_ref_sink(g_variant_new_string(str)); g_hash_table_insert(props, DLR_INTERFACE_PROP_MODEL_NUMBER, val); g_free(str); str = gupnp_device_info_get_serial_number(proxy); val = g_variant_ref_sink(g_variant_new_string(str)); g_hash_table_insert(props, DLR_INTERFACE_PROP_SERIAL_NUMBER, val); g_free(str); str = gupnp_device_info_get_presentation_url(proxy); val = g_variant_ref_sink(g_variant_new_string(str)); g_hash_table_insert(props, DLR_INTERFACE_PROP_PRESENTATION_URL, val); g_free(str); } static void prv_add_player_speed_props(GHashTable *player_props, double min_rate, double max_rate, GVariant *mpris_transport_play_speeds, GVariantBuilder *changed_props_vb) { GVariant *val; if (min_rate != 0) { val = g_variant_ref_sink(g_variant_new_double(min_rate)); prv_change_props(player_props, DLR_INTERFACE_PROP_MINIMUM_RATE, val, changed_props_vb); } if (max_rate != 0) { val = g_variant_ref_sink(g_variant_new_double(max_rate)); prv_change_props(player_props, DLR_INTERFACE_PROP_MAXIMUM_RATE, val, changed_props_vb); } if (mpris_transport_play_speeds != NULL) { val = g_variant_ref_sink(mpris_transport_play_speeds); prv_change_props(player_props, DLR_INTERFACE_PROP_TRANSPORT_PLAY_SPEEDS, val, changed_props_vb); } } static gboolean prv_props_update(dlr_device_t *device, dlr_task_t *task) { GVariant *val; GUPnPDeviceInfo *info; dlr_device_context_t *context; dlr_service_proxies_t *service_proxies; dlr_props_t *props = &device->props; GVariantBuilder *changed_props_vb; GVariant *changed_props; gboolean device_alive = TRUE; context = dlr_device_get_context(device); val = g_variant_ref_sink(g_variant_new_boolean(FALSE)); g_hash_table_insert(props->root_props, DLR_INTERFACE_PROP_CAN_QUIT, val); g_hash_table_insert(props->root_props, DLR_INTERFACE_PROP_CAN_RAISE, g_variant_ref(val)); g_hash_table_insert(props->root_props, DLR_INTERFACE_PROP_CAN_SET_FULLSCREEN, g_variant_ref(val)); g_hash_table_insert(props->root_props, DLR_INTERFACE_PROP_HAS_TRACK_LIST, g_variant_ref(val)); info = (GUPnPDeviceInfo *)context->device_proxy; prv_update_device_props(info, props->device_props); val = g_hash_table_lookup(props->device_props, DLR_INTERFACE_PROP_FRIENDLY_NAME); g_hash_table_insert(props->root_props, DLR_INTERFACE_PROP_IDENTITY, g_variant_ref(val)); service_proxies = &context->service_proxies; /* TODO: We should not retrieve these values here. They should be retrieved during device construction. */ if (service_proxies->av_proxy) if (!prv_get_av_service_states_values( service_proxies->av_proxy, &device->mpris_transport_play_speeds, &device->transport_play_speeds, &device->min_rate, &device->max_rate, &device->can_get_byte_position)) { DLEYNA_LOG_DEBUG("Lost Device AV"); device_alive = FALSE; goto on_lost_device; } /* TODO: We should not retrieve these values here. They should be retrieved during device construction. */ if (service_proxies->rc_proxy) if (!prv_get_rc_service_states_values(service_proxies->rc_proxy, &device->max_volume)) { DLEYNA_LOG_DEBUG("Lost Device RC"); device_alive = FALSE; goto on_lost_device; } changed_props_vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); prv_add_player_speed_props(device->props.player_props, device->min_rate, device->max_rate, device->mpris_transport_play_speeds, changed_props_vb); prv_add_all_actions(device, changed_props_vb); device->props.synced = TRUE; changed_props = g_variant_ref_sink( g_variant_builder_end(changed_props_vb)); prv_emit_signal_properties_changed(device, DLR_INTERFACE_PLAYER, changed_props); g_variant_unref(changed_props); g_variant_builder_unref(changed_props_vb); on_lost_device: return device_alive; } static void prv_simple_call_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { dlr_async_task_t *cb_data = user_data; GError *upnp_error = NULL; if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, &upnp_error, NULL)) { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Operation failed: %s", upnp_error->message); g_error_free(upnp_error); } (void) g_idle_add(dlr_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); } static void prv_set_volume(dlr_async_task_t *cb_data, GVariant *params) { double volume; volume = g_variant_get_double(params) * cb_data->device->max_volume; DLEYNA_LOG_INFO("Set device volume to %d/%d", (guint)volume, cb_data->device->max_volume); cb_data->action = gupnp_service_proxy_begin_action(cb_data->proxy, "SetVolume", prv_simple_call_cb, cb_data, "InstanceID", G_TYPE_INT, 0, "Channel", G_TYPE_STRING, "Master", "DesiredVolume", G_TYPE_UINT, (guint) volume, NULL); } static void prv_set_mute(dlr_async_task_t *cb_data, GVariant *params) { gboolean mute; mute = g_variant_get_boolean(params); DLEYNA_LOG_INFO("Set device mute state to %s", mute ? "TRUE" : "FALSE"); cb_data->action = gupnp_service_proxy_begin_action(cb_data->proxy, "SetMute", prv_simple_call_cb, cb_data, "InstanceID", G_TYPE_INT, 0, "Channel", G_TYPE_STRING, "Master", "DesiredMute", G_TYPE_BOOLEAN, mute, NULL); } static GVariant *prv_get_rate_value_from_double(GVariant *params, gchar **upnp_rate, dlr_async_task_t *cb_data) { dlr_device_t *dev = cb_data->device; GVariant *val = NULL; GVariant *tps; GVariantIter iter; double tps_value; double mpris_rate; GPtrArray *tp_speeds; int i; if (dev->dlna_transport_play_speeds != NULL) { tps = g_hash_table_lookup(dev->props.player_props, DLR_INTERFACE_PROP_TRANSPORT_PLAY_SPEEDS); tp_speeds = dev->dlna_transport_play_speeds; } else { tps = dev->mpris_transport_play_speeds; tp_speeds = dev->transport_play_speeds; } if (tps == NULL) { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "TransportPlaySpeeds list is empty"); goto exit; } mpris_rate = g_variant_get_double(params); i = 0; g_variant_iter_init(&iter, tps); while (g_variant_iter_next(&iter, "d", &tps_value)) { if (fabs(mpris_rate - tps_value) <= 0.01) { val = g_variant_ref_sink( g_variant_new_double(tps_value)); *upnp_rate = g_ptr_array_index(tp_speeds, i); break; } i++; } if (val == NULL) cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_QUERY, "Value %.2f not in TransportPlaySpeeds", mpris_rate); exit: return val; } static void prv_set_rate(GVariant *params, dlr_async_task_t *cb_data) { GVariantBuilder *changed_props_vb; GVariant *changed_props; GVariant *val; gchar *rate; if (g_variant_is_of_type(params, G_VARIANT_TYPE_DOUBLE) == FALSE) { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_QUERY, "Parameter is not a double"); goto exit; } rate = NULL; val = prv_get_rate_value_from_double(params, &rate, cb_data); if (val == NULL) goto exit; DLEYNA_LOG_INFO("Set device rate to %s", rate); if (!strcmp(cb_data->device->rate, rate)) { g_variant_unref(val); goto exit; } g_free(cb_data->device->rate); cb_data->device->rate = g_strdup(rate); changed_props_vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); prv_change_props(cb_data->device->props.player_props, DLR_INTERFACE_PROP_RATE, val, changed_props_vb); changed_props = g_variant_ref_sink( g_variant_builder_end(changed_props_vb)); prv_emit_signal_properties_changed(cb_data->device, DLR_INTERFACE_PLAYER, changed_props); g_variant_unref(changed_props); g_variant_builder_unref(changed_props_vb); exit: return; } void dlr_device_set_prop(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_async_task_t *cb_data = (dlr_async_task_t *)task; dlr_device_context_t *context; dlr_task_set_prop_t *set_prop = &task->ut.set_prop; cb_data->cb = cb; cb_data->device = device; if (g_strcmp0(set_prop->interface_name, DLR_INTERFACE_PLAYER) != 0 && g_strcmp0(set_prop->interface_name, "") != 0) { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_INTERFACE, "Interface %s not managed for property setting", set_prop->interface_name); goto exit; } if (g_strcmp0(set_prop->prop_name, DLR_INTERFACE_PROP_RATE) == 0) { GVariant *state; prv_set_rate(set_prop->params, cb_data); state = g_hash_table_lookup(device->props.player_props, DLR_INTERFACE_PROP_PLAYBACK_STATUS); if (!state || strcmp(g_variant_get_string(state, NULL), "Playing")) { goto exit; } dlr_device_play(device, task, cb); /* dlr_device_play completes task: no need for goto exit */ return; } if ((g_strcmp0(set_prop->prop_name, DLR_INTERFACE_PROP_VOLUME) != 0) && (g_strcmp0(set_prop->prop_name, DLR_INTERFACE_PROP_MUTE) != 0)) { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_PROPERTY, "Property %s not managed for setting", set_prop->prop_name); goto exit; } context = dlr_device_get_context(device); cb_data->cancel_id = g_cancellable_connect(cb_data->cancellable, G_CALLBACK(dlr_async_task_cancelled), cb_data, NULL); cb_data->proxy = context->service_proxies.rc_proxy; g_object_add_weak_pointer((G_OBJECT(context->service_proxies.rc_proxy)), (gpointer *)&cb_data->proxy); if (g_strcmp0(set_prop->prop_name, DLR_INTERFACE_PROP_MUTE) == 0) prv_set_mute(cb_data, set_prop->params); else prv_set_volume(cb_data, set_prop->params); return; exit: g_idle_add(dlr_async_task_complete, cb_data); } static void prv_free_get_all_position_data(gpointer data) { dlr_device_data_t *device_cb_data = data; if (device_cb_data) { g_free(device_cb_data->ut.get_all_position.rel_cnt); g_free(device_cb_data->ut.get_all_position.rel_time); g_free(device_cb_data); } } void dlr_device_get_prop(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_async_task_t *cb_data = (dlr_async_task_t *)task; dlr_task_get_prop_t *get_prop = &task->ut.get_prop; const gchar *get_position_action; GUPnPServiceProxyActionCallback get_position_cb; cb_data->cb = cb; cb_data->device = device; /* Need to check to see if the property is DLR_INTERFACE_PROP_POSITION. If it is we need to call GetPositionInfo. This value is not evented. Otherwise we can just update the value straight away. */ if ((!strcmp(get_prop->interface_name, DLR_INTERFACE_PLAYER) || !strcmp(get_prop->interface_name, "")) && (!strcmp(task->ut.get_prop.prop_name, DLR_INTERFACE_PROP_POSITION) || !strcmp(task->ut.get_prop.prop_name, DLR_INTERFACE_PROP_BYTE_POSITION))) { /* Need to read the current position. This property is not evented */ if (!strcmp(task->ut.get_prop.prop_name, DLR_INTERFACE_PROP_POSITION)) { get_position_action = "GetPositionInfo"; get_position_cb = prv_get_position_info_cb; } else { get_position_action = "X_DLNA_GetBytePositionInfo"; get_position_cb = prv_get_byte_position_info_cb; } prv_get_position_info(cb_data, get_position_action, get_position_cb); } else { if (!device->props.synced && !prv_props_update(device, task)) { cb_data->error = g_error_new( DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Lost Device"); } else { prv_get_prop(cb_data); } (void) g_idle_add(dlr_async_task_complete, cb_data); } } void dlr_device_get_all_props(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_async_task_t *cb_data = (dlr_async_task_t *)task; dlr_task_get_props_t *get_props = &task->ut.get_props; dlr_device_data_t *device_cb_data; cb_data->cb = cb; cb_data->device = device; if (!device->props.synced && !prv_props_update(device, task)) { cb_data->error = g_error_new( DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Lost Device"); (void) g_idle_add(dlr_async_task_complete, cb_data); } else if ((!strcmp(get_props->interface_name, DLR_INTERFACE_PLAYER) || !strcmp(get_props->interface_name, ""))) { /* Need to read the current position. This property is not evented */ device_cb_data = g_new0(dlr_device_data_t, 1); cb_data->private = device_cb_data; cb_data->free_private = prv_free_get_all_position_data; if (device->can_get_byte_position) { device_cb_data->ut.get_all_position.expected_props = 2; prv_get_position_info( cb_data, "X_DLNA_GetBytePositionInfo", prv_get_all_byte_position_info_cb); } else { device_cb_data->ut.get_all_position.expected_props = 1; prv_get_position_info( cb_data, "GetPositionInfo", prv_get_all_position_info_cb); } } else { prv_get_props(cb_data); (void) g_idle_add(dlr_async_task_complete, cb_data); } } void dlr_device_play(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_device_context_t *context; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; DLEYNA_LOG_INFO("Play at speed %s", device->rate); context = dlr_device_get_context(device); cb_data->cb = cb; cb_data->device = device; cb_data->cancel_id = g_cancellable_connect(cb_data->cancellable, G_CALLBACK(dlr_async_task_cancelled), cb_data, NULL); cb_data->proxy = context->service_proxies.av_proxy; g_object_add_weak_pointer((G_OBJECT(context->service_proxies.av_proxy)), (gpointer *)&cb_data->proxy); cb_data->action = gupnp_service_proxy_begin_action(cb_data->proxy, "Play", prv_simple_call_cb, cb_data, "InstanceID", G_TYPE_INT, 0, "Speed", G_TYPE_STRING, device->rate, NULL); } void dlr_device_play_pause(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb) { GVariant *state; state = g_hash_table_lookup(device->props.player_props, DLR_INTERFACE_PROP_PLAYBACK_STATUS); if (state && !strcmp(g_variant_get_string(state, NULL), "Playing")) dlr_device_pause(device, task, cb); else dlr_device_play(device, task, cb); } static void prv_simple_command(dlr_device_t *device, dlr_task_t *task, const gchar *command_name, dlr_upnp_task_complete_t cb) { dlr_device_context_t *context; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; DLEYNA_LOG_INFO("%s", command_name); context = dlr_device_get_context(device); cb_data->cb = cb; cb_data->device = device; cb_data->cancel_id = g_cancellable_connect(cb_data->cancellable, G_CALLBACK(dlr_async_task_cancelled), cb_data, NULL); cb_data->proxy = context->service_proxies.av_proxy; g_object_add_weak_pointer((G_OBJECT(context->service_proxies.av_proxy)), (gpointer *)&cb_data->proxy); cb_data->action = gupnp_service_proxy_begin_action(cb_data->proxy, command_name, prv_simple_call_cb, cb_data, "InstanceID", G_TYPE_INT, 0, NULL); } void dlr_device_pause(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb) { prv_simple_command(device, task, "Pause", cb); } void dlr_device_stop(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb) { prv_simple_command(device, task, "Stop", cb); } void dlr_device_next(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb) { prv_simple_command(device, task, "Next", cb); } void dlr_device_previous(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb) { prv_simple_command(device, task, "Previous", cb); } static void prv_reset_transport_speed_props(dlr_device_t *device) { GVariantBuilder *changed_props_vb; GVariant *changed_props; GVariant *val; double min_rate; double max_rate; gboolean props_changed = FALSE; changed_props_vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); if (device->dlna_transport_play_speeds != NULL) { g_ptr_array_free(device->dlna_transport_play_speeds, TRUE); device->dlna_transport_play_speeds = NULL; } val = g_hash_table_lookup(device->props.player_props, DLR_INTERFACE_PROP_TRANSPORT_PLAY_SPEEDS); if (!val || !g_variant_equal(val, device->mpris_transport_play_speeds)) { min_rate = 0; val = g_hash_table_lookup(device->props.player_props, DLR_INTERFACE_PROP_MINIMUM_RATE); if (!val || (g_variant_get_double(val) != device->min_rate)) min_rate = device->min_rate; max_rate = 0; val = g_hash_table_lookup(device->props.player_props, DLR_INTERFACE_PROP_MAXIMUM_RATE); if (!val || (g_variant_get_double(val) != device->max_rate)) max_rate = device->max_rate; prv_add_player_speed_props(device->props.player_props, min_rate, max_rate, device->mpris_transport_play_speeds, changed_props_vb); props_changed = TRUE; } if (!device->rate || g_strcmp0(device->rate, "1") != 0) { g_free(device->rate); device->rate = g_strdup("1"); val = g_variant_ref_sink(g_variant_new_double( prv_map_transport_speed(device->rate))); prv_change_props(device->props.player_props, DLR_INTERFACE_PROP_RATE, val, changed_props_vb); props_changed = TRUE; } changed_props = g_variant_ref_sink( g_variant_builder_end(changed_props_vb)); if (props_changed) prv_emit_signal_properties_changed(device, DLR_INTERFACE_PLAYER, changed_props); g_variant_unref(changed_props); g_variant_builder_unref(changed_props_vb); } static void prv_open_uri_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { dlr_async_task_t *cb_data = user_data; GError *upnp_error = NULL; #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG gchar *type; #endif if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, &upnp_error, NULL)) { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Operation failed: %s", upnp_error->message); g_error_free(upnp_error); goto on_error; } prv_reset_transport_speed_props(cb_data->device); #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG if (cb_data->task.type == DLR_TASK_OPEN_URI) type = "OPEN URI"; else if (cb_data->task.type == DLR_TASK_OPEN_NEXT_URI) type = "OPEN NEXT URI"; else if (cb_data->task.type == DLR_TASK_SET_URI) type = "SET URI"; else type = "Unknown"; DLEYNA_LOG_DEBUG("Task: %s", type); #endif if (cb_data->task.type == DLR_TASK_OPEN_URI) { cb_data->action = gupnp_service_proxy_begin_action( cb_data->proxy, "Play", prv_simple_call_cb, cb_data, "InstanceID", G_TYPE_INT, 0, "Speed", G_TYPE_STRING, cb_data->device->rate, NULL); goto on_exit; } on_error: (void) g_idle_add(dlr_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); on_exit: return; } void dlr_device_open_uri(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_device_context_t *context; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; dlr_task_open_uri_t *open_uri_data = &task->ut.open_uri; gchar *metadata = open_uri_data->metadata; DLEYNA_LOG_INFO("URI: %s", open_uri_data->uri); DLEYNA_LOG_INFO("METADATA: %s", metadata ? metadata : "Not provided"); DLEYNA_LOG_INFO("ACTION: %s", open_uri_data->operation); context = dlr_device_get_context(device); cb_data->cb = cb; cb_data->device = device; cb_data->cancel_id = g_cancellable_connect(cb_data->cancellable, G_CALLBACK(dlr_async_task_cancelled), cb_data, NULL); cb_data->proxy = context->service_proxies.av_proxy; g_object_add_weak_pointer((G_OBJECT(context->service_proxies.av_proxy)), (gpointer *)&cb_data->proxy); cb_data->action = gupnp_service_proxy_begin_action(cb_data->proxy, open_uri_data->operation, prv_open_uri_cb, cb_data, "InstanceID", G_TYPE_INT, 0, open_uri_data->uri_type, G_TYPE_STRING, open_uri_data->uri, open_uri_data->metadata_type, G_TYPE_STRING, metadata ? metadata : "", NULL); } static void prv_device_set_position(dlr_device_t *device, dlr_task_t *task, const gchar *pos_type, dlr_upnp_task_complete_t cb) { dlr_device_context_t *context; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; dlr_task_seek_t *seek_data = &task->ut.seek; gchar *position; context = dlr_device_get_context(device); cb_data->cb = cb; cb_data->device = device; if (!strcmp(pos_type, "TRACK_NR")) position = g_strdup_printf("%u", seek_data->track_number); else if (g_strrstr(pos_type, "_TIME") != NULL) position = prv_int64_to_duration(seek_data->position); else position = g_strdup_printf("%llu", (long long unsigned int)seek_data->counter_position); DLEYNA_LOG_INFO("set %s position : %s", pos_type, position); cb_data->cancel_id = g_cancellable_connect(cb_data->cancellable, G_CALLBACK(dlr_async_task_cancelled), cb_data, NULL); cb_data->cancellable = cb_data->cancellable; cb_data->proxy = context->service_proxies.av_proxy; g_object_add_weak_pointer((G_OBJECT(context->service_proxies.av_proxy)), (gpointer *)&cb_data->proxy); cb_data->action = gupnp_service_proxy_begin_action(cb_data->proxy, "Seek", prv_simple_call_cb, cb_data, "InstanceID", G_TYPE_INT, 0, "Unit", G_TYPE_STRING, pos_type, "Target", G_TYPE_STRING, position, NULL); g_free(position); } static void prv_device_seek_set_position(dlr_device_t *device, dlr_task_t *task, const gchar *pos_type, dlr_upnp_task_complete_t cb) { dlr_async_task_t *cb_data = (dlr_async_task_t *)task; dlr_task_seek_t *seek_data = &task->ut.seek; gchar *position; cb_data->cb = cb; cb_data->device = device; if (g_strrstr(pos_type, "_TIME") != NULL) position = prv_int64_to_duration(seek_data->position); else position = g_strdup_printf("%llu", (long long unsigned int)seek_data->counter_position); DLEYNA_LOG_INFO("set %s position : %s", pos_type, position); cb_data->action = gupnp_service_proxy_begin_action(cb_data->proxy, "Seek", prv_simple_call_cb, cb_data, "InstanceID", G_TYPE_INT, 0, "Unit", G_TYPE_STRING, pos_type, "Target", G_TYPE_STRING, position, NULL); g_free(position); } static void prv_complete_seek_get_position(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { gchar *result = NULL; const gchar *message; gboolean end; GError *error = NULL; dlr_async_task_t *cb_data = user_data; dlr_task_t *task = &cb_data->task; dlr_task_seek_t *seek_data = &task->ut.seek; guint64 count; end = gupnp_service_proxy_end_action( proxy, action, &error, (task->type == DLR_TASK_SEEK) ? "RelTime" : "RelByte", G_TYPE_STRING, &result, NULL); if (!end || (result == NULL)) { message = (error != NULL) ? error->message : "Invalid result"; cb_data->error = g_error_new( DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "%s operation failed: %s", (task->type == DLR_TASK_SEEK) ? "GetPositionInfo" : "X_DLNA_GetBytePositionInfo", message); if (error != NULL) g_error_free(error); goto on_error; } g_strstrip(result); if (task->type == DLR_TASK_SEEK) { seek_data->position += prv_duration_to_int64(result); prv_device_seek_set_position(cb_data->device, task, "REL_TIME", cb_data->cb); } else { count = strtoull(result, NULL, 10); seek_data->counter_position += count; prv_device_seek_set_position(cb_data->device, task, "X_DLNA_REL_BYTE", cb_data->cb); } g_free(result); return; on_error: (void) g_idle_add(dlr_async_task_complete, task); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); } void dlr_device_seek(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_async_task_t *cb_data = (dlr_async_task_t *)task; cb_data->cb = cb; cb_data->device = device; prv_get_position_info(cb_data, (task->type == DLR_TASK_SEEK) ? "GetPositionInfo" : "X_DLNA_GetBytePositionInfo", prv_complete_seek_get_position); } void dlr_device_set_position(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb) { if (task->type == DLR_TASK_SET_POSITION) prv_device_set_position(device, task, "REL_TIME", cb); else prv_device_set_position(device, task, "X_DLNA_REL_BYTE", cb); } void dlr_device_goto_track(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb) { prv_device_set_position(device, task, "TRACK_NR", cb); } void dlr_device_host_uri(dlr_device_t *device, dlr_task_t *task, dlr_host_service_t *host_service, dlr_upnp_task_complete_t cb) { dlr_device_context_t *context; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; dlr_task_host_uri_t *host_uri = &task->ut.host_uri; gchar *url; GError *error = NULL; context = dlr_device_get_context(device); url = dlr_host_service_add(host_service, context->ip_address, host_uri->client, host_uri->uri, &error); cb_data->cb = cb; cb_data->device = device; if (url) { cb_data->task.result = g_variant_ref_sink( g_variant_new_string(url)); g_free(url); } else { cb_data->error = error; } (void) g_idle_add(dlr_async_task_complete, cb_data); } void dlr_device_remove_uri(dlr_device_t *device, dlr_task_t *task, dlr_host_service_t *host_service, dlr_upnp_task_complete_t cb) { dlr_device_context_t *context; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; dlr_task_host_uri_t *host_uri = &task->ut.host_uri; context = dlr_device_get_context(device); cb_data->cb = cb; cb_data->device = device; if (!dlr_host_service_remove(host_service, context->ip_address, host_uri->client, host_uri->uri)) { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "File not hosted for specified device"); } (void) g_idle_add(dlr_async_task_complete, cb_data); } static void prv_build_icon_result(dlr_device_t *device, dlr_task_t *task) { GVariant *out_p[2]; out_p[0] = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, device->icon.bytes, device->icon.size, 1); out_p[1] = g_variant_new_string(device->icon.mime_type); task->result = g_variant_ref_sink(g_variant_new_tuple(out_p, 2)); } static void prv_get_icon_cancelled(GCancellable *cancellable, gpointer user_data) { prv_download_info_t *download = (prv_download_info_t *)user_data; dlr_async_task_cancelled(cancellable, download->task); if (download->msg) { soup_session_cancel_message(download->session, download->msg, SOUP_STATUS_CANCELLED); DLEYNA_LOG_DEBUG("Cancelling device icon download"); } } static void prv_free_download_info(prv_download_info_t *download) { if (download->msg) g_object_unref(download->msg); g_object_unref(download->session); g_free(download); } static void prv_get_icon_session_cb(SoupSession *session, SoupMessage *msg, gpointer user_data) { prv_download_info_t *download = (prv_download_info_t *)user_data; dlr_async_task_t *cb_data = (dlr_async_task_t *)download->task; dlr_device_t *device = (dlr_device_t *)cb_data->device; if (msg->status_code == SOUP_STATUS_CANCELLED) goto out; if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) { device->icon.size = msg->response_body->length; device->icon.bytes = g_malloc(device->icon.size); memcpy(device->icon.bytes, msg->response_body->data, device->icon.size); prv_build_icon_result(device, &cb_data->task); } else { DLEYNA_LOG_DEBUG("Failed to GET device icon: %s", msg->reason_phrase); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Failed to GET device icon"); } (void) g_idle_add(dlr_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); out: prv_free_download_info(download); } void dlr_device_get_icon(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb) { GUPnPDeviceInfo *info; dlr_device_context_t *context; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; gchar *url; prv_download_info_t *download; cb_data->cb = cb; cb_data->device = device; if (device->icon.size != 0) { prv_build_icon_result(device, task); goto end; } context = dlr_device_get_context(device); info = (GUPnPDeviceInfo *)context->device_proxy; url = gupnp_device_info_get_icon_url(info, NULL, -1, -1, -1, FALSE, &device->icon.mime_type, NULL, NULL, NULL); if (url == NULL) { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_NOT_SUPPORTED, "No icon available"); goto end; } download = g_new0(prv_download_info_t, 1); download->session = soup_session_async_new(); download->msg = soup_message_new(SOUP_METHOD_GET, url); download->task = cb_data; if (!download->msg) { DLEYNA_LOG_WARNING("Invalid URL %s", url); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_RESULT, "Invalid URL %s", url); prv_free_download_info(download); g_free(url); goto end; } cb_data->cancel_id = g_cancellable_connect(cb_data->cancellable, G_CALLBACK(prv_get_icon_cancelled), download, NULL); g_object_ref(download->msg); soup_session_queue_message(download->session, download->msg, prv_get_icon_session_cb, download); g_free(url); return; end: (void) g_idle_add(dlr_async_task_complete, cb_data); } dleyna-renderer-0.6.0/libdleyna/renderer/device.h000066400000000000000000000116401305660374100217650ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #ifndef DLR_DEVICE_H__ #define DLR_DEVICE_H__ #include #include #include #include #include #include "host-service.h" #include "server.h" #include "upnp.h" typedef struct dlr_service_proxies_t_ dlr_service_proxies_t; struct dlr_service_proxies_t_ { GUPnPServiceProxy *cm_proxy; GUPnPServiceProxy *av_proxy; GUPnPServiceProxy *rc_proxy; }; typedef struct dlr_device_context_t_ dlr_device_context_t; struct dlr_device_context_t_ { gchar *ip_address; GUPnPDeviceProxy *device_proxy; dlr_service_proxies_t service_proxies; dlr_device_t *device; gboolean subscribed_av; gboolean subscribed_cm; gboolean subscribed_rc; guint timeout_id_av; guint timeout_id_cm; guint timeout_id_rc; }; typedef struct dlr_props_t_ dlr_props_t; struct dlr_props_t_ { GHashTable *root_props; GHashTable *player_props; GHashTable *device_props; gboolean synced; }; typedef struct dlr_device_icon_t_ dlr_device_icon_t; struct dlr_device_icon_t_ { gchar *mime_type; guchar *bytes; gsize size; }; struct dlr_device_t_ { dleyna_connector_id_t connection; guint ids[DLR_INTERFACE_INFO_MAX]; gchar *path; GPtrArray *contexts; dlr_props_t props; guint timeout_id; guint max_volume; GPtrArray *transport_play_speeds; GPtrArray *dlna_transport_play_speeds; GVariant *mpris_transport_play_speeds; gchar *rate; double min_rate; double max_rate; gboolean can_get_byte_position; guint construct_step; dlr_device_icon_t icon; GHashTable *rc_event_handlers; }; void dlr_device_construct( dlr_device_t *dev, dlr_device_context_t *context, dleyna_connector_id_t connection, const dleyna_connector_dispatch_cb_t *dispatch_table, const dleyna_task_queue_key_t *queue_id); dlr_device_t *dlr_device_new( dleyna_connector_id_t connection, GUPnPDeviceProxy *proxy, const gchar *ip_address, const char *udn, const dleyna_connector_dispatch_cb_t *dispatch_table, const dleyna_task_queue_key_t *queue_id); void dlr_device_delete(void *device); void dlr_device_unsubscribe(void *device); void dlr_device_append_new_context(dlr_device_t *device, const gchar *ip_address, GUPnPDeviceProxy *proxy); dlr_device_t *dlr_device_from_path(const gchar *path, GHashTable *device_list); dlr_device_context_t *dlr_device_get_context(dlr_device_t *device); void dlr_device_subscribe_to_service_changes(dlr_device_t *device); void dlr_device_set_prop(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_device_get_prop(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_device_get_all_props(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_device_play(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_device_pause(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_device_play_pause(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_device_stop(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_device_next(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_device_previous(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_device_open_uri(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_device_seek(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_device_set_position(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_device_goto_track(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_device_host_uri(dlr_device_t *device, dlr_task_t *task, dlr_host_service_t *host_service, dlr_upnp_task_complete_t cb); void dlr_device_remove_uri(dlr_device_t *device, dlr_task_t *task, dlr_host_service_t *host_service, dlr_upnp_task_complete_t cb); void dlr_device_get_icon(dlr_device_t *device, dlr_task_t *task, dlr_upnp_task_complete_t cb); #endif /* DLR_DEVICE_H__ */ dleyna-renderer-0.6.0/libdleyna/renderer/dleyna-renderer-service.conf.in000066400000000000000000000024651305660374100263540ustar00rootroot00000000000000# Configuration file for dleyna-renderer # # # # General configuration options [general] # true: Service always stay in memory running # false: Service quit when the last client disconnects. never-quit=@never_quit@ # IPC connector name connector-name=@with_connector_name@ # Source port for SSDP messages #port=4321 # Port for Push host fileserver # If unset, a random available port will be used. #push-host-port=5432 # Log configuration options [log] # Define the logging output method. 3 technologies are defined: # # 0=Syslog # 1=GLib # 2=File log-type=@with_log_type@ # Comma-separated list of logging level. # Log levels are: 1=critical, 2=error, 3=warning, 4=message, 5=info, 6=debug # # Allowed values for log-levels are # 0 = disabled # 7 = default (=1,2,5) # 8 = all (=1,2,3,4,5,6) # 1,..,6 = a comma separated list of log level # # IMPORTANT: This log level is a subset of the log level defined at compile time # You can't enable levels disabled at compile time # level=8 means all level flags defined at compile time. log-level=@with_log_level@ # Network filtering [netf] # true: Enable the network filtering. # false: Disable the network filtering. netf-enabled=false # Comma-separated list of interface name, SSID or IP address. # If netf is enabled but the list is empty, it behaves as disabled. netf-list= dleyna-renderer-0.6.0/libdleyna/renderer/host-service.c000066400000000000000000000330011305660374100231270ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #include #include #include #include #include #include #include #include #include #include #include "host-service.h" #define DLR_HOST_SERVICE_ROOT "/dleynarenderer" typedef struct dlr_host_file_t_ dlr_host_file_t; struct dlr_host_file_t_ { unsigned int id; GPtrArray *clients; gchar *mime_type; GMappedFile *mapped_file; unsigned int mapped_count; gchar *path; gchar *dlna_header; }; typedef struct dlr_host_server_t_ dlr_host_server_t; struct dlr_host_server_t_ { GHashTable *files; SoupServer *soup_server; unsigned int counter; }; struct dlr_host_service_t_ { GHashTable *servers; guint port; }; static void prv_compute_mime_and_dlna_header(const gchar *filename, gchar **mime_type, gchar **dlna_header, GError **error) { gchar *uri; GString *header; GUPnPDLNAProfile *profile; GUPnPDLNAProfileGuesser *guesser; gboolean relaxed_mode = TRUE; gboolean extended_mode = TRUE; const char *profile_name; const char *dlna_mime_type; GUPnPDLNAOperation operation; GUPnPDLNAFlags flags; GUPnPDLNAConversion conversion; gchar *content_type = NULL; *mime_type = NULL; *dlna_header = NULL; *error = NULL; header = g_string_new(""); guesser = gupnp_dlna_profile_guesser_new(relaxed_mode, extended_mode); uri = g_filename_to_uri(filename, NULL, error); if (uri == NULL) { DLEYNA_LOG_WARNING("Unable to convert filename: %s", filename); if (*error) { DLEYNA_LOG_WARNING("Error: %s", (*error)->message); g_error_free(*error); *error = NULL; } goto on_error; } profile = gupnp_dlna_profile_guesser_guess_profile_sync(guesser, uri, 5000, NULL, error); if (profile == NULL) { DLEYNA_LOG_WARNING("Unable to guess profile for URI: %s", uri); if (*error) { DLEYNA_LOG_WARNING("Error: %s", (*error)->message); g_error_free(*error); *error = NULL; } goto on_error; } profile_name = gupnp_dlna_profile_get_name(profile); if (profile_name != NULL) g_string_append_printf(header, "DLNA.ORG_PN=%s;", profile_name); operation = GUPNP_DLNA_OPERATION_RANGE; g_string_append_printf(header, "DLNA.ORG_OP=%.2x;", operation); conversion = GUPNP_DLNA_CONVERSION_NONE; g_string_append_printf(header, "DLNA.ORG_CI=%.2x;", conversion); dlna_mime_type = gupnp_dlna_profile_get_mime(profile); if (dlna_mime_type != NULL) { *mime_type = g_strdup(dlna_mime_type); flags = GUPNP_DLNA_FLAGS_BACKGROUND_TRANSFER_MODE; flags |= GUPNP_DLNA_FLAGS_CONNECTION_STALL; flags |= GUPNP_DLNA_FLAGS_DLNA_V15; if (g_content_type_is_a(dlna_mime_type, "image/*")) { flags |= GUPNP_DLNA_FLAGS_INTERACTIVE_TRANSFER_MODE; } else if (g_content_type_is_a(dlna_mime_type, "audio/*") || g_content_type_is_a(dlna_mime_type, "video/*")) { flags |= GUPNP_DLNA_FLAGS_STREAMING_TRANSFER_MODE; } else { DLEYNA_LOG_WARNING("Unsupported Mime Type: %s", dlna_mime_type); goto on_error; } g_string_append_printf(header, "DLNA.ORG_FLAGS=%.8x", flags); g_string_append_printf(header, "000000000000000000000000"); } else { DLEYNA_LOG_WARNING("Unable to discover mime_type"); } on_error: if (*mime_type == NULL) { content_type = g_content_type_guess(filename, NULL, 0, NULL); if (content_type != NULL) { *mime_type = g_content_type_get_mime_type(content_type); if (*mime_type == NULL) *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_MIME, "Unable to determine MIME Type for %s", filename); g_free(content_type); } else { *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_MIME, "Unable to determine Content Type for %s", filename); } } DLEYNA_LOG_DEBUG("contentFeatures.dlna.org: %s", header->str); g_object_unref(guesser); g_free(uri); if (*mime_type) *dlna_header = g_string_free(header, FALSE); else (void) g_string_free(header, TRUE); return; } static void prv_host_file_delete(gpointer host_file) { dlr_host_file_t *hf = host_file; unsigned int i; if (hf) { g_free(hf->path); for (i = 0; i < hf->mapped_count; ++i) g_mapped_file_unref(hf->mapped_file); g_ptr_array_unref(hf->clients); g_free(hf->mime_type); g_free(hf->dlna_header); g_free(hf); } } static dlr_host_file_t *prv_host_file_new(const gchar *file, unsigned int id, GError **error) { dlr_host_file_t *hf = NULL; gchar *extension; if (!g_file_test(file, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS)) { *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "File %s does not exist or is not a regular file", file); goto on_error; } hf = g_new0(dlr_host_file_t, 1); hf->id = id; hf->clients = g_ptr_array_new_with_free_func(g_free); extension = strrchr(file, '.'); hf->path = g_strdup_printf(DLR_HOST_SERVICE_ROOT"/%d%s", hf->id, extension ? extension : ""); prv_compute_mime_and_dlna_header(file, &hf->mime_type, &hf->dlna_header, error); if (*error != NULL) goto on_error; return hf; on_error: prv_host_file_delete(hf); return NULL; } static void prv_host_server_delete(gpointer host_server) { dlr_host_server_t *server = host_server; if (server) { soup_server_quit(server->soup_server); g_object_unref(server->soup_server); g_hash_table_unref(server->files); g_free(server); } } static dlr_host_file_t *prv_host_server_find_file(dlr_host_server_t *hs, const gchar *url, const gchar **file_name) { dlr_host_file_t *retval = NULL; GHashTableIter iter; gpointer key; gpointer value; g_hash_table_iter_init(&iter, hs->files); while (g_hash_table_iter_next(&iter, &key, &value)) { if (!strcmp(((dlr_host_file_t *)value)->path, url)) { retval = value; *file_name = key; break; } } return retval; } static void prv_soup_message_finished_cb(SoupMessage *msg, gpointer user_data) { dlr_host_file_t *hf = user_data; if (hf->mapped_count > 0) { g_mapped_file_unref(hf->mapped_file); --hf->mapped_count; if (hf->mapped_count == 0) hf->mapped_file = NULL; } } static void prv_soup_server_cb(SoupServer *server, SoupMessage *msg, const char *path, GHashTable *query, SoupClientContext *client, gpointer user_data) { dlr_host_file_t *hf; dlr_host_server_t *hs = user_data; const gchar *file_name; const char *hdr; if ((msg->method != SOUP_METHOD_GET) && (msg->method != SOUP_METHOD_HEAD)) { soup_message_set_status(msg, SOUP_STATUS_NOT_IMPLEMENTED); goto on_error; } hf = prv_host_server_find_file(hs, path, &file_name); if (!hf) { soup_message_set_status(msg, SOUP_STATUS_NOT_FOUND); goto on_error; } hdr = soup_message_headers_get_one(msg->request_headers, "getContentFeatures.dlna.org"); if (hdr) { if (strcmp(hdr, "1") != 0) { soup_message_set_status(msg, SOUP_STATUS_BAD_REQUEST); goto on_error; } if ((hf->dlna_header) && strlen(hf->dlna_header) > 0) soup_message_headers_append(msg->response_headers, "contentFeatures.dlna.org", hf->dlna_header); } if (hf->mapped_file) { g_mapped_file_ref(hf->mapped_file); ++hf->mapped_count; } else { hf->mapped_file = g_mapped_file_new(file_name, FALSE, NULL); if (!hf->mapped_file) { soup_message_set_status(msg, SOUP_STATUS_NOT_FOUND); goto on_error; } hf->mapped_count = 1; } if (msg->method == SOUP_METHOD_GET) { g_signal_connect(msg, "finished", G_CALLBACK(prv_soup_message_finished_cb), hf); soup_message_set_response( msg, hf->mime_type, SOUP_MEMORY_STATIC, g_mapped_file_get_contents(hf->mapped_file), g_mapped_file_get_length(hf->mapped_file)); } else { soup_message_headers_set_content_type(msg->response_headers, hf->mime_type, NULL); soup_message_headers_set_content_length( msg->response_headers, g_mapped_file_get_length(hf->mapped_file)); } soup_message_set_status(msg, SOUP_STATUS_OK); on_error: return; } static dlr_host_server_t *prv_host_server_new(const gchar *device_if, guint port, GError **error) { dlr_host_server_t *server = NULL; SoupAddress *addr; addr = soup_address_new(device_if, port); if (soup_address_resolve_sync(addr, NULL) != SOUP_STATUS_OK) { *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_HOST_FAILED, "Unable to create host server on %s", device_if); goto on_error; } server = g_new(dlr_host_server_t, 1); server->files = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, prv_host_file_delete); server->soup_server = soup_server_new(SOUP_SERVER_INTERFACE, addr, NULL); soup_server_add_handler(server->soup_server, DLR_HOST_SERVICE_ROOT, prv_soup_server_cb, server, NULL); soup_server_run_async(server->soup_server); server->counter = 0; on_error: g_object_unref(addr); return server; } void dlr_host_service_new(dlr_host_service_t **host_service, guint port) { dlr_host_service_t *hs; hs = g_new(dlr_host_service_t, 1); hs->servers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, prv_host_server_delete); hs->port = port; *host_service = hs; } static gchar *prv_add_new_file(dlr_host_server_t *server, const gchar *client, const gchar *device_if, const gchar *file, GError **error) { unsigned int i; dlr_host_file_t *hf; gchar *str; hf = g_hash_table_lookup(server->files, file); if (!hf) { hf = prv_host_file_new(file, server->counter++, error); if (!hf) goto on_error; g_ptr_array_add(hf->clients, g_strdup(client)); g_hash_table_insert(server->files, g_strdup(file), hf); } else { for (i = 0; i < hf->clients->len; ++i) if (!strcmp(g_ptr_array_index(hf->clients, i), client)) break; if (i == hf->clients->len) g_ptr_array_add(hf->clients, g_strdup(client)); } str = g_strdup_printf("http://%s:%d%s", device_if, soup_server_get_port(server->soup_server), hf->path); return str; on_error: return NULL; } gchar *dlr_host_service_add(dlr_host_service_t *host_service, const gchar *device_if, const gchar *client, const gchar *file, GError **error) { dlr_host_server_t *server; gchar *retval = NULL; server = g_hash_table_lookup(host_service->servers, device_if); if (!server) { server = prv_host_server_new(device_if, host_service->port, error); if (!server) goto on_error; g_hash_table_insert(host_service->servers, g_strdup(device_if), server); } retval = prv_add_new_file(server, client, device_if, file, error); on_error: return retval; } static gboolean prv_remove_client(dlr_host_service_t *host_service, const gchar *client, dlr_host_server_t *server, const gchar *device_if, const gchar *file, dlr_host_file_t *hf) { unsigned int i; gboolean retval = FALSE; for (i = 0; i < hf->clients->len; ++i) if (!strcmp(g_ptr_array_index(hf->clients, i), client)) break; if (i == hf->clients->len) goto on_error; g_ptr_array_remove_index(hf->clients, i); retval = TRUE; on_error: return retval; } gboolean dlr_host_service_remove(dlr_host_service_t *host_service, const gchar *device_if, const gchar *client, const gchar *file) { gboolean retval = FALSE; dlr_host_file_t *hf; dlr_host_server_t *server; server = g_hash_table_lookup(host_service->servers, device_if); if (!server) goto on_error; hf = g_hash_table_lookup(server->files, file); if (!hf) goto on_error; retval = prv_remove_client(host_service, client, server, device_if, file, hf); if (!retval) goto on_error; if (hf->clients->len == 0) g_hash_table_remove(server->files, file); if (g_hash_table_size(server->files) == 0) g_hash_table_remove(host_service->servers, device_if); on_error: return retval; } void dlr_host_service_lost_client(dlr_host_service_t *host_service, const gchar *client) { GHashTableIter iter; GHashTableIter iter2; gpointer value; gpointer key; gpointer value2; gpointer key2; dlr_host_server_t *server; dlr_host_file_t *hf; g_hash_table_iter_init(&iter, host_service->servers); while (g_hash_table_iter_next(&iter, &key, &value)) { server = value; g_hash_table_iter_init(&iter2, server->files); while (g_hash_table_iter_next(&iter2, &key2, &value2)) { hf = value2; if (!prv_remove_client(host_service, client, server, key, key2, hf)) continue; if (hf->clients->len > 0) continue; g_hash_table_iter_remove(&iter2); } if (g_hash_table_size(server->files) == 0) g_hash_table_iter_remove(&iter); } } void dlr_host_service_delete(dlr_host_service_t *host_service) { if (host_service) { g_hash_table_unref(host_service->servers); g_free(host_service); } } dleyna-renderer-0.6.0/libdleyna/renderer/host-service.h000066400000000000000000000027451305660374100231470ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #ifndef DLR_HOST_SERVICE_H__ #define DLR_HOST_SERVICE_H__ typedef struct dlr_host_service_t_ dlr_host_service_t; void dlr_host_service_new(dlr_host_service_t **host_service, guint port); gchar *dlr_host_service_add(dlr_host_service_t *host_service, const gchar *device_if, const gchar *client, const gchar *file, GError **error); gboolean dlr_host_service_remove(dlr_host_service_t *host_service, const gchar *device_if, const gchar *client, const gchar *file); void dlr_host_service_lost_client(dlr_host_service_t *host_service, const gchar *client); void dlr_host_service_delete(dlr_host_service_t *host_service); #endif /*DLR_HOST_SERVICE_H__ */ dleyna-renderer-0.6.0/libdleyna/renderer/manager.c000066400000000000000000000233621305660374100221370ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Ludovic Ferrandis * */ #include #include #include #include #include #include #include "async.h" #include "manager.h" #include "prop-defs.h" #include "server.h" struct dlr_manager_t_ { dleyna_connector_id_t connection; GUPnPContextManager *cm; dleyna_white_list_t *wl; }; static GVariant *prv_build_wl_entries(dleyna_settings_t *settings) { GVariant *result; result = dleyna_settings_white_list_entries(settings); if (result == NULL) result = g_variant_new("as", NULL); return result; } static void prv_add_all_props(dleyna_settings_t *settings, GVariantBuilder *vb) { g_variant_builder_add(vb, "{sv}", DLR_INTERFACE_PROP_NEVER_QUIT, g_variant_new_boolean( dleyna_settings_is_never_quit( settings))); g_variant_builder_add(vb, "{sv}", DLR_INTERFACE_PROP_WHITE_LIST_ENABLED, g_variant_new_boolean( dleyna_settings_is_white_list_enabled( settings))); g_variant_builder_add(vb, "{sv}", DLR_INTERFACE_PROP_WHITE_LIST_ENTRIES, prv_build_wl_entries(settings)); } static GVariant *prv_get_prop(dleyna_settings_t *settings, const gchar *prop) { GVariant *retval = NULL; #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG gchar *prop_str; #endif if (!strcmp(prop, DLR_INTERFACE_PROP_NEVER_QUIT)) retval = g_variant_ref_sink(g_variant_new_boolean( dleyna_settings_is_never_quit( settings))); else if (!strcmp(prop, DLR_INTERFACE_PROP_WHITE_LIST_ENABLED)) retval = g_variant_ref_sink(g_variant_new_boolean( dleyna_settings_is_white_list_enabled( settings))); else if (!strcmp(prop, DLR_INTERFACE_PROP_WHITE_LIST_ENTRIES)) retval = g_variant_ref_sink(prv_build_wl_entries(settings)); #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG if (retval) { prop_str = g_variant_print(retval, FALSE); DLEYNA_LOG_DEBUG("Prop %s = %s", prop, prop_str); g_free(prop_str); } #endif return retval; } static void prv_wl_notify_prop(dlr_manager_t *manager, const gchar *prop_name, GVariant *prop_val) { GVariant *val; GVariantBuilder array; g_variant_builder_init(&array, G_VARIANT_TYPE("a{sv}")); g_variant_builder_add(&array, "{sv}", prop_name, prop_val); val = g_variant_new("(s@a{sv}as)", DLEYNA_SERVER_INTERFACE_MANAGER, g_variant_builder_end(&array), NULL); (void) dlr_renderer_get_connector()->notify( manager->connection, DLEYNA_SERVER_OBJECT, DLR_INTERFACE_PROPERTIES, DLR_INTERFACE_PROPERTIES_CHANGED, val, NULL); } dlr_manager_t *dlr_manager_new(dleyna_connector_id_t connection, GUPnPContextManager *connection_manager) { dlr_manager_t *manager = g_new0(dlr_manager_t, 1); GUPnPWhiteList *gupnp_wl; gupnp_wl = gupnp_context_manager_get_white_list(connection_manager); manager->connection = connection; manager->cm = connection_manager; manager->wl = dleyna_white_list_new(gupnp_wl); return manager; } void dlr_manager_delete(dlr_manager_t *manager) { if (manager != NULL) { dleyna_white_list_delete(manager->wl); g_free(manager); } } dleyna_white_list_t *dlr_manager_get_white_list(dlr_manager_t *manager) { return manager->wl; } void dlr_manager_get_all_props(dlr_manager_t *manager, dleyna_settings_t *settings, dlr_task_t *task, dlr_manager_task_complete_t cb) { dlr_async_task_t *cb_data = (dlr_async_task_t *)task; dlr_task_get_props_t *task_data = &task->ut.get_props; gchar *i_name = task_data->interface_name; GVariantBuilder vb; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Path: %s", task->path); DLEYNA_LOG_DEBUG("Interface %s", i_name); cb_data->cb = cb; g_variant_builder_init(&vb, G_VARIANT_TYPE("a{sv}")); if (!strcmp(i_name, DLEYNA_SERVER_INTERFACE_MANAGER) || !strcmp(i_name, "")) { prv_add_all_props(settings, &vb); cb_data->task.result = g_variant_ref_sink( g_variant_builder_end(&vb)); } else { DLEYNA_LOG_WARNING("Interface is unknown."); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_INTERFACE, "Interface is unknown."); } (void) g_idle_add(dlr_async_task_complete, cb_data); DLEYNA_LOG_DEBUG("Exit"); } void dlr_manager_get_prop(dlr_manager_t *manager, dleyna_settings_t *settings, dlr_task_t *task, dlr_manager_task_complete_t cb) { dlr_async_task_t *cb_data = (dlr_async_task_t *)task; dlr_task_get_prop_t *task_data = &task->ut.get_prop; gchar *i_name = task_data->interface_name; gchar *name = task_data->prop_name; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Path: %s", task->path); DLEYNA_LOG_DEBUG("Interface %s", i_name); DLEYNA_LOG_DEBUG("Prop.%s", name); cb_data->cb = cb; if (!strcmp(i_name, DLEYNA_SERVER_INTERFACE_MANAGER) || !strcmp(i_name, "")) { cb_data->task.result = prv_get_prop(settings, name); if (!cb_data->task.result) cb_data->error = g_error_new( DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_PROPERTY, "Unknown property"); } else { DLEYNA_LOG_WARNING("Interface is unknown."); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_INTERFACE, "Interface is unknown."); } (void) g_idle_add(dlr_async_task_complete, cb_data); DLEYNA_LOG_DEBUG("Exit"); } static void prv_set_prop_never_quit(dlr_manager_t *manager, dleyna_settings_t *settings, gboolean never_quit, GError **error) { GVariant *prop_val; gboolean old_val; DLEYNA_LOG_DEBUG("Enter %d", never_quit); old_val = dleyna_settings_is_never_quit(settings); if (old_val == never_quit) goto exit; /* If no error, the white list will be updated in the reload callack */ dleyna_settings_set_never_quit(settings, never_quit, error); if (*error == NULL) { prop_val = g_variant_new_boolean(never_quit); prv_wl_notify_prop(manager, DLR_INTERFACE_PROP_NEVER_QUIT, prop_val); } exit: DLEYNA_LOG_DEBUG("Exit"); return; } static void prv_set_prop_wl_enabled(dlr_manager_t *manager, dleyna_settings_t *settings, gboolean enabled, GError **error) { GVariant *prop_val; gboolean old_val; DLEYNA_LOG_DEBUG("Enter %d", enabled); old_val = dleyna_settings_is_white_list_enabled(settings); if (old_val == enabled) goto exit; /* If no error, the white list will be updated in the reload callack */ dleyna_settings_set_white_list_enabled(settings, enabled, error); if (*error == NULL) { dleyna_white_list_enable(manager->wl, enabled); prop_val = g_variant_new_boolean(enabled); prv_wl_notify_prop(manager, DLR_INTERFACE_PROP_WHITE_LIST_ENABLED, prop_val); } exit: DLEYNA_LOG_DEBUG("Exit"); return; } static void prv_set_prop_wl_entries(dlr_manager_t *manager, dleyna_settings_t *settings, GVariant *entries, GError **error) { DLEYNA_LOG_DEBUG("Enter"); if (strcmp(g_variant_get_type_string(entries), "as")) { DLEYNA_LOG_WARNING("Invalid parameter type. 'as' expected."); *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_QUERY, "Invalid parameter type. 'as' expected."); goto exit; } /* If no error, the white list will be updated in the reload callack * callack */ dleyna_settings_set_white_list_entries(settings, entries, error); if (*error == NULL) { dleyna_white_list_clear(manager->wl); dleyna_white_list_add_entries(manager->wl, entries); prv_wl_notify_prop(manager, DLR_INTERFACE_PROP_WHITE_LIST_ENTRIES, entries); } exit: DLEYNA_LOG_DEBUG("Exit"); } void dlr_manager_set_prop(dlr_manager_t *manager, dleyna_settings_t *settings, dlr_task_t *task, dlr_manager_task_complete_t cb) { dlr_async_task_t *cb_data = (dlr_async_task_t *)task; dlr_task_set_prop_t *task_data = &task->ut.set_prop; GVariant *param = task_data->params; gchar *name = task_data->prop_name; gchar *i_name = task_data->interface_name; GError *error = NULL; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Path: %s", task->path); DLEYNA_LOG_DEBUG("Interface %s", i_name); DLEYNA_LOG_DEBUG("Prop.%s", name); cb_data->cb = cb; if (strcmp(i_name, DLEYNA_SERVER_INTERFACE_MANAGER) && strcmp(i_name, "")) { DLEYNA_LOG_WARNING("Interface is unknown."); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_INTERFACE, "Interface is unknown."); goto exit; } if (!strcmp(name, DLR_INTERFACE_PROP_NEVER_QUIT)) prv_set_prop_never_quit(manager, settings, g_variant_get_boolean(param), &error); else if (!strcmp(name, DLR_INTERFACE_PROP_WHITE_LIST_ENABLED)) prv_set_prop_wl_enabled(manager, settings, g_variant_get_boolean(param), &error); else if (!strcmp(name, DLR_INTERFACE_PROP_WHITE_LIST_ENTRIES)) prv_set_prop_wl_entries(manager, settings, param, &error); else cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_PROPERTY, "Unknown property"); if (error != NULL) cb_data->error = error; exit: (void) g_idle_add(dlr_async_task_complete, cb_data); DLEYNA_LOG_DEBUG("Exit"); } dleyna-renderer-0.6.0/libdleyna/renderer/manager.h000066400000000000000000000035211305660374100221370ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Ludovic Ferrandis * */ #ifndef DLR_MANAGER_H__ #define DLR_MANAGER_H__ #include #include #include #include "task.h" typedef struct dlr_manager_t_ dlr_manager_t; typedef void (*dlr_manager_task_complete_t)(dlr_task_t *task, GError *error); dlr_manager_t *dlr_manager_new(dleyna_connector_id_t connection, GUPnPContextManager *connection_manager); void dlr_manager_delete(dlr_manager_t *manager); dleyna_white_list_t *dlr_manager_get_white_list(dlr_manager_t *manager); void dlr_manager_get_all_props(dlr_manager_t *manager, dleyna_settings_t *settings, dlr_task_t *task, dlr_manager_task_complete_t cb); void dlr_manager_get_prop(dlr_manager_t *manager, dleyna_settings_t *settings, dlr_task_t *task, dlr_manager_task_complete_t cb); void dlr_manager_set_prop(dlr_manager_t *manager, dleyna_settings_t *settings, dlr_task_t *task, dlr_manager_task_complete_t cb); #endif /* DLR_MANAGER_H__ */ dleyna-renderer-0.6.0/libdleyna/renderer/prop-defs.h000066400000000000000000000065601305660374100224320ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #ifndef DLR_PROPS_DEFS_H__ #define DLR_PROPS_DEFS_H__ #define DLR_INTERFACE_PROPERTIES "org.freedesktop.DBus.Properties" #define DLR_INTERFACE_SERVER "org.mpris.MediaPlayer2" #define DLR_INTERFACE_PLAYER "org.mpris.MediaPlayer2.Player" #define DLR_INTERFACE_PROPERTIES_CHANGED "PropertiesChanged" /* Manager Properties */ #define DLR_INTERFACE_PROP_NEVER_QUIT "NeverQuit" #define DLR_INTERFACE_PROP_WHITE_LIST_ENTRIES "WhiteListEntries" #define DLR_INTERFACE_PROP_WHITE_LIST_ENABLED "WhiteListEnabled" #define DLR_INTERFACE_PROP_CAN_QUIT "CanQuit" #define DLR_INTERFACE_PROP_CAN_RAISE "CanRaise" #define DLR_INTERFACE_PROP_CAN_SET_FULLSCREEN "CanSetFullscreen" #define DLR_INTERFACE_PROP_HAS_TRACK_LIST "HasTrackList" #define DLR_INTERFACE_PROP_IDENTITY "Identity" #define DLR_INTERFACE_PROP_SUPPORTED_URIS "SupportedUriSchemes" #define DLR_INTERFACE_PROP_SUPPORTED_MIME "SupportedMimeTypes" #define DLR_INTERFACE_PROP_PLAYBACK_STATUS "PlaybackStatus" #define DLR_INTERFACE_PROP_RATE "Rate" #define DLR_INTERFACE_PROP_CAN_PLAY "CanPlay" #define DLR_INTERFACE_PROP_CAN_SEEK "CanSeek" #define DLR_INTERFACE_PROP_CAN_BYTE_SEEK "CanByteSeek" #define DLR_INTERFACE_PROP_CAN_CONTROL "CanControl" #define DLR_INTERFACE_PROP_CAN_PAUSE "CanPause" #define DLR_INTERFACE_PROP_CAN_NEXT "CanGoNext" #define DLR_INTERFACE_PROP_CAN_PREVIOUS "CanGoPrevious" #define DLR_INTERFACE_PROP_POSITION "Position" #define DLR_INTERFACE_PROP_BYTE_POSITION "BytePosition" #define DLR_INTERFACE_PROP_METADATA "Metadata" #define DLR_INTERFACE_PROP_TRANSPORT_PLAY_SPEEDS "TransportPlaySpeeds" #define DLR_INTERFACE_PROP_MINIMUM_RATE "MinimumRate" #define DLR_INTERFACE_PROP_MAXIMUM_RATE "MaximumRate" #define DLR_INTERFACE_PROP_VOLUME "Volume" #define DLR_INTERFACE_PROP_CURRENT_TRACK "CurrentTrack" #define DLR_INTERFACE_PROP_NUMBER_OF_TRACKS "NumberOfTracks" #define DLR_INTERFACE_PROP_MUTE "Mute" #define DLR_INTERFACE_PROP_DLNA_DEVICE_CLASSES "DeviceClasses" #define DLR_INTERFACE_PROP_DEVICE_TYPE "DeviceType" #define DLR_INTERFACE_PROP_UDN "UDN" #define DLR_INTERFACE_PROP_FRIENDLY_NAME "FriendlyName" #define DLR_INTERFACE_PROP_ICON_URL "IconURL" #define DLR_INTERFACE_PROP_MANUFACTURER "Manufacturer" #define DLR_INTERFACE_PROP_MANUFACTURER_URL "ManufacturerUrl" #define DLR_INTERFACE_PROP_MODEL_DESCRIPTION "ModelDescription" #define DLR_INTERFACE_PROP_MODEL_NAME "ModelName" #define DLR_INTERFACE_PROP_MODEL_NUMBER "ModelNumber" #define DLR_INTERFACE_PROP_SERIAL_NUMBER "SerialNumber" #define DLR_INTERFACE_PROP_PRESENTATION_URL "PresentationURL" #define DLR_INTERFACE_PROP_PROTOCOL_INFO "ProtocolInfo" #endif /* DLR_PROPS_DEFS_H__ */ dleyna-renderer-0.6.0/libdleyna/renderer/server.c000066400000000000000000001056031305660374100220320ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "async.h" #include "control-point-renderer.h" #include "device.h" #include "manager.h" #include "prop-defs.h" #include "server.h" #include "upnp.h" #ifdef UA_PREFIX #define DLR_PRG_NAME UA_PREFIX " dLeyna/" VERSION #else #define DLR_PRG_NAME "dLeyna/" VERSION #endif #define DLR_INTERFACE_GET_VERSION "GetVersion" #define DLR_INTERFACE_GET_RENDERERS "GetRenderers" #define DLR_INTERFACE_RESCAN "Rescan" #define DLR_INTERFACE_RELEASE "Release" #define DLR_INTERFACE_WHITE_LIST_ENABLE "WhiteListEnable" #define DLR_INTERFACE_WHITE_LIST_ADD_ENTRIES "WhiteListAddEntries" #define DLR_INTERFACE_WHITE_LIST_REMOVE_ENTRIES "WhiteListRemoveEntries" #define DLR_INTERFACE_WHITE_LIST_CLEAR "WhiteListClear" #define DLR_INTERFACE_ENTRY_LIST "EntryList" #define DLR_INTERFACE_IS_ENABLED "IsEnabled" #define DLR_INTERFACE_FOUND_RENDERER "FoundRenderer" #define DLR_INTERFACE_LOST_RENDERER "LostRenderer" #define DLR_INTERFACE_HOST_FILE "HostFile" #define DLR_INTERFACE_REMOVE_FILE "RemoveFile" #define DLR_INTERFACE_VERSION "Version" #define DLR_INTERFACE_RENDERERS "Renderers" #define DLR_INTERFACE_PATH "Path" #define DLR_INTERFACE_URI "Uri" #define DLR_INTERFACE_ID "Id" #define DLR_INTERFACE_METADATA "Metadata" #define DLR_INTERFACE_CHANGED_PROPERTIES "changed_properties" #define DLR_INTERFACE_INVALIDATED_PROPERTIES "invalidated_properties" #define DLR_INTERFACE_GET "Get" #define DLR_INTERFACE_GET_ALL "GetAll" #define DLR_INTERFACE_SET "Set" #define DLR_INTERFACE_INTERFACE_NAME "interface_name" #define DLR_INTERFACE_PROPERTY_NAME "property_name" #define DLR_INTERFACE_PROPERTIES_VALUE "properties" #define DLR_INTERFACE_VALUE "value" #define DLR_INTERFACE_OFFSET "offset" #define DLR_INTERFACE_POSITION "position" #define DLR_INTERFACE_BYTE_POSITION "byte_position" #define DLR_INTERFACE_TRACKID "trackid" #define DLR_INTERFACE_TRACK_NUMBER "TrackNumber" #define DLR_INTERFACE_RAISE "Raise" #define DLR_INTERFACE_QUIT "Quit" #define DLR_INTERFACE_PLAY "Play" #define DLR_INTERFACE_PLAY_PAUSE "PlayPause" #define DLR_INTERFACE_NEXT "Next" #define DLR_INTERFACE_PREVIOUS "Previous" #define DLR_INTERFACE_PAUSE "Pause" #define DLR_INTERFACE_STOP "Stop" #define DLR_INTERFACE_OPEN_URI "OpenUri" #define DLR_INTERFACE_OPEN_URI_EX "OpenUriEx" #define DLR_INTERFACE_OPEN_NEXT_URI "OpenNextUri" #define DLR_INTERFACE_SET_URI "SetUri" #define DLR_INTERFACE_SEEK "Seek" #define DLR_INTERFACE_BYTE_SEEK "ByteSeek" #define DLR_INTERFACE_SET_POSITION "SetPosition" #define DLR_INTERFACE_SET_BYTE_POSITION "SetBytePosition" #define DLR_INTERFACE_GOTO_TRACK "GotoTrack" #define DLR_INTERFACE_CANCEL "Cancel" #define DLR_INTERFACE_GET_ICON "GetIcon" #define DLR_INTERFACE_RESOLUTION "Resolution" #define DLR_INTERFACE_ICON_BYTES "Bytes" #define DLR_INTERFACE_MIME_TYPE "MimeType" #define DLR_INTERFACE_REQ_MIME_TYPE "RequestedMimeType" enum dlr_manager_interface_type_ { DLR_MANAGER_INTERFACE_MANAGER, DLR_MANAGER_INTERFACE_INFO_PROPERTIES, DLR_MANAGER_INTERFACE_INFO_MAX }; typedef struct dlr_context_t_ dlr_context_t; struct dlr_context_t_ { guint dlr_id[DLR_MANAGER_INTERFACE_INFO_MAX]; dleyna_connector_id_t connection; guint watchers; dleyna_task_processor_t *processor; const dleyna_connector_t *connector; dlr_upnp_t *upnp; dleyna_settings_t *settings; dlr_manager_t *manager; }; static dlr_context_t g_context; static const gchar g_root_introspection[] = "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " ""; static const gchar g_server_introspection[] = "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " ""; static const gchar *g_manager_interfaces[DLR_MANAGER_INTERFACE_INFO_MAX] = { /* MUST be in the exact same order as g_root_introspection */ DLEYNA_SERVER_INTERFACE_MANAGER, DLR_INTERFACE_PROPERTIES }; static void prv_process_task(dleyna_task_atom_t *task, gpointer user_data); static void prv_manager_root_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation); static void prv_manager_props_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation); static void prv_dlr_device_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation); static void prv_props_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation); static void prv_dlr_player_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation); static void prv_dlr_push_host_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation); static void prv_renderer_device_method_call( dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation); static const dleyna_connector_dispatch_cb_t g_root_vtables[DLR_MANAGER_INTERFACE_INFO_MAX] = { /* MUST be in the exact same order as g_root_introspection */ prv_manager_root_method_call, prv_manager_props_method_call }; static const dleyna_connector_dispatch_cb_t g_server_vtables[DLR_INTERFACE_INFO_MAX] = { /* MUST be in the exact same order as g_server_introspection */ prv_props_method_call, prv_dlr_device_method_call, prv_dlr_player_method_call, prv_dlr_push_host_method_call, prv_renderer_device_method_call }; static const gchar *g_server_interfaces[DLR_INTERFACE_INFO_MAX] = { /* MUST be in the exact same order as g_server_introspection */ DLR_INTERFACE_PROPERTIES, DLR_INTERFACE_SERVER, DLR_INTERFACE_PLAYER, DLEYNA_INTERFACE_PUSH_HOST, DLEYNA_SERVER_INTERFACE_RENDERER_DEVICE }; const gchar *dlr_renderer_get_interface_name(guint index) { return g_server_interfaces[index]; } const dleyna_connector_t *dlr_renderer_get_connector(void) { return g_context.connector; } dleyna_task_processor_t *dlr_renderer_service_get_task_processor(void) { return g_context.processor; } dlr_upnp_t *dlr_renderer_service_get_upnp(void) { return g_context.upnp; } static void prv_process_sync_task(dlr_task_t *task) { GError *error; switch (task->type) { case DLR_TASK_GET_VERSION: task->result = g_variant_ref_sink(g_variant_new_string( VERSION)); dlr_task_complete(task); break; case DLR_TASK_GET_SERVERS: task->result = dlr_upnp_get_server_ids(g_context.upnp); dlr_task_complete(task); break; case DLR_TASK_RESCAN: dlr_upnp_rescan(g_context.upnp); dlr_task_complete(task); break; case DLR_TASK_RAISE: case DLR_TASK_QUIT: error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_NOT_SUPPORTED, "Command not supported."); dlr_task_fail(task, error); g_error_free(error); break; default: goto finished; break; } dleyna_task_queue_task_completed(task->atom.queue_id); finished: return; } static void prv_async_task_complete(dlr_task_t *task, GError *error) { DLEYNA_LOG_DEBUG("Enter"); if (!error) { dlr_task_complete(task); } else { dlr_task_fail(task, error); g_error_free(error); } dleyna_task_queue_task_completed(task->atom.queue_id); DLEYNA_LOG_DEBUG("Exit"); } static void prv_process_async_task(dlr_task_t *task) { dlr_async_task_t *async_task = (dlr_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); async_task->cancellable = g_cancellable_new(); switch (task->type) { case DLR_TASK_GET_PROP: dlr_upnp_get_prop(g_context.upnp, task, prv_async_task_complete); break; case DLR_TASK_GET_ALL_PROPS: dlr_upnp_get_all_props(g_context.upnp, task, prv_async_task_complete); break; case DLR_TASK_SET_PROP: dlr_upnp_set_prop(g_context.upnp, task, prv_async_task_complete); break; case DLR_TASK_PLAY: dlr_upnp_play(g_context.upnp, task, prv_async_task_complete); break; case DLR_TASK_PAUSE: dlr_upnp_pause(g_context.upnp, task, prv_async_task_complete); break; case DLR_TASK_PLAY_PAUSE: dlr_upnp_play_pause(g_context.upnp, task, prv_async_task_complete); break; case DLR_TASK_STOP: dlr_upnp_stop(g_context.upnp, task, prv_async_task_complete); break; case DLR_TASK_NEXT: dlr_upnp_next(g_context.upnp, task, prv_async_task_complete); break; case DLR_TASK_PREVIOUS: dlr_upnp_previous(g_context.upnp, task, prv_async_task_complete); break; case DLR_TASK_OPEN_URI: case DLR_TASK_OPEN_NEXT_URI: case DLR_TASK_SET_URI: dlr_upnp_open_uri(g_context.upnp, task, prv_async_task_complete); break; case DLR_TASK_SEEK: case DLR_TASK_BYTE_SEEK: dlr_upnp_seek(g_context.upnp, task, prv_async_task_complete); break; case DLR_TASK_SET_POSITION: case DLR_TASK_SET_BYTE_POSITION: dlr_upnp_set_position(g_context.upnp, task, prv_async_task_complete); break; case DLR_TASK_GOTO_TRACK: dlr_upnp_goto_track(g_context.upnp, task, prv_async_task_complete); break; case DLR_TASK_HOST_URI: dlr_upnp_host_uri(g_context.upnp, task, prv_async_task_complete); break; case DLR_TASK_REMOVE_URI: dlr_upnp_remove_uri(g_context.upnp, task, prv_async_task_complete); break; case DLR_TASK_GET_ICON: dlr_upnp_get_icon(g_context.upnp, task, prv_async_task_complete); break; case DLR_TASK_MANAGER_GET_PROP: dlr_manager_get_prop(g_context.manager, g_context.settings, task, prv_async_task_complete); break; case DLR_TASK_MANAGER_GET_ALL_PROPS: dlr_manager_get_all_props(g_context.manager, g_context.settings, task, prv_async_task_complete); break; case DLR_TASK_MANAGER_SET_PROP: dlr_manager_set_prop(g_context.manager, g_context.settings, task, prv_async_task_complete); break; default: break; } DLEYNA_LOG_DEBUG("Exit"); } static void prv_process_task(dleyna_task_atom_t *task, gpointer user_data) { dlr_task_t *client_task = (dlr_task_t *)task; if (client_task->synchronous) prv_process_sync_task(client_task); else prv_process_async_task(client_task); } static void prv_cancel_task(dleyna_task_atom_t *task, gpointer user_data) { dlr_task_cancel((dlr_task_t *)task); } static void prv_delete_task(dleyna_task_atom_t *task, gpointer user_data) { dlr_task_delete((dlr_task_t *)task); } static void prv_remove_client(const gchar *name) { dleyna_task_processor_remove_queues_for_source(g_context.processor, name); dlr_upnp_lost_client(g_context.upnp, name); if (g_context.watchers > 0) g_context.watchers--; if (g_context.watchers == 0) if (!dleyna_settings_is_never_quit(g_context.settings)) dleyna_task_processor_set_quitting(g_context.processor); } static void prv_lost_client(const gchar *name) { DLEYNA_LOG_INFO("Client %s lost", name); prv_remove_client(name); } static void prv_control_point_initialize(const dleyna_connector_t *connector, dleyna_task_processor_t *processor, dleyna_settings_t *settings) { memset(&g_context, 0, sizeof(g_context)); g_context.processor = processor; g_context.settings = settings; g_context.connector = connector; g_context.connector->set_client_lost_cb(prv_lost_client); g_set_prgname(DLR_PRG_NAME); } static void prv_control_point_stop_service(void) { uint i; if (g_context.upnp) { dlr_upnp_unsubscribe(g_context.upnp); dlr_upnp_delete(g_context.upnp); g_context.upnp = NULL; } if (g_context.connection) { for (i = 0; i < DLR_MANAGER_INTERFACE_INFO_MAX; i++) if (g_context.dlr_id[i]) g_context.connector->unpublish_object( g_context.connection, g_context.dlr_id[i]); } } static void prv_control_point_free(void) { } static void prv_add_task(dlr_task_t *task, const gchar *source, const gchar *sink) { const dleyna_task_queue_key_t *queue_id; if (g_context.connector->watch_client(source)) g_context.watchers++; queue_id = dleyna_task_processor_lookup_queue(g_context.processor, source, sink); if (!queue_id) queue_id = dleyna_task_processor_add_queue( g_context.processor, source, sink, DLEYNA_TASK_QUEUE_FLAG_AUTO_START, prv_process_task, prv_cancel_task, prv_delete_task); dleyna_task_queue_add_task(queue_id, &task->atom); } static void prv_manager_root_method_call( dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation) { dlr_task_t *task; DLEYNA_LOG_INFO("Calling %s method", method); if (!strcmp(method, DLR_INTERFACE_RELEASE)) { g_context.connector->unwatch_client(sender); prv_remove_client(sender); g_context.connector->return_response(invocation, NULL); } else { if (!strcmp(method, DLR_INTERFACE_GET_VERSION)) task = dlr_task_get_version_new(invocation); else if (!strcmp(method, DLR_INTERFACE_GET_RENDERERS)) task = dlr_task_get_servers_new(invocation); else if (!strcmp(method, DLR_INTERFACE_RESCAN)) task = dlr_task_rescan_new(invocation); else goto finished; prv_add_task(task, sender, DLR_RENDERER_SINK); } finished: return; } static void prv_manager_props_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation) { dlr_task_t *task; GError *error = NULL; if (!strcmp(method, DLR_INTERFACE_GET_ALL)) task = dlr_task_manager_get_props_new(invocation, object, parameters, &error); else if (!strcmp(method, DLR_INTERFACE_GET)) task = dlr_task_manager_get_prop_new(invocation, object, parameters, &error); else if (!strcmp(method, DLR_INTERFACE_SET)) task = dlr_task_manager_set_prop_new(invocation, object, parameters, &error); else goto finished; if (!task) { g_context.connector->return_error(invocation, error); g_error_free(error); goto finished; } prv_add_task(task, sender, task->path); finished: return; } static const gchar *prv_get_device_id(const gchar *object, GError **error) { dlr_device_t *device; device = dlr_device_from_path(object, dlr_upnp_get_server_udn_map(g_context.upnp)); if (!device) { DLEYNA_LOG_WARNING("Cannot locate device for %s", object); *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Cannot locate device corresponding to the specified path"); goto on_error; } return device->path; on_error: return NULL; } static void prv_props_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation) { dlr_task_t *task; const gchar *device_id; GError *error = NULL; device_id = prv_get_device_id(object, &error); if (!device_id) { g_context.connector->return_error(invocation, error); g_error_free(error); goto finished; } if (!strcmp(method, DLR_INTERFACE_GET_ALL)) task = dlr_task_get_props_new(invocation, object, parameters); else if (!strcmp(method, DLR_INTERFACE_GET)) task = dlr_task_get_prop_new(invocation, object, parameters); else if (!strcmp(method, DLR_INTERFACE_SET)) task = dlr_task_set_prop_new(invocation, object, parameters); else goto finished; prv_add_task(task, sender, device_id); finished: return; } static void prv_dlr_device_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation) { dlr_task_t *task; const gchar *device_id; GError *error = NULL; device_id = prv_get_device_id(object, &error); if (!device_id) { g_context.connector->return_error(invocation, error); g_error_free(error); goto finished; } if (!strcmp(method, DLR_INTERFACE_RAISE)) task = dlr_task_raise_new(invocation); else if (!strcmp(method, DLR_INTERFACE_QUIT)) task = dlr_task_quit_new(invocation); else goto finished; prv_add_task(task, sender, device_id); finished: return; } static void prv_dlr_player_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation) { dlr_task_t *task; const gchar *device_id; GError *error = NULL; device_id = prv_get_device_id(object, &error); if (!device_id) { g_context.connector->return_error(invocation, error); g_error_free(error); goto finished; } if (!strcmp(method, DLR_INTERFACE_PLAY)) task = dlr_task_play_new(invocation, object); else if (!strcmp(method, DLR_INTERFACE_PAUSE)) task = dlr_task_pause_new(invocation, object); else if (!strcmp(method, DLR_INTERFACE_PLAY_PAUSE)) task = dlr_task_play_pause_new(invocation, object); else if (!strcmp(method, DLR_INTERFACE_STOP)) task = dlr_task_stop_new(invocation, object); else if (!strcmp(method, DLR_INTERFACE_NEXT)) task = dlr_task_next_new(invocation, object); else if (!strcmp(method, DLR_INTERFACE_PREVIOUS)) task = dlr_task_previous_new(invocation, object); else if (!strcmp(method, DLR_INTERFACE_OPEN_URI)) task = dlr_task_open_uri_new(invocation, object, parameters); else if (!strcmp(method, DLR_INTERFACE_OPEN_URI_EX)) task = dlr_task_open_uri_ex_new(invocation, object, parameters); else if (!strcmp(method, DLR_INTERFACE_OPEN_NEXT_URI)) task = dlr_task_open_next_uri_new(invocation, object, parameters); else if (!strcmp(method, DLR_INTERFACE_SET_URI)) task = dlr_task_set_uri_new(invocation, object, parameters); else if (!strcmp(method, DLR_INTERFACE_SEEK)) task = dlr_task_seek_new(invocation, object, parameters); else if (!strcmp(method, DLR_INTERFACE_BYTE_SEEK)) task = dlr_task_byte_seek_new(invocation, object, parameters); else if (!strcmp(method, DLR_INTERFACE_SET_POSITION)) task = dlr_task_set_position_new(invocation, object, parameters); else if (!strcmp(method, DLR_INTERFACE_SET_BYTE_POSITION)) task = dlr_task_set_byte_position_new(invocation, object, parameters); else if (!strcmp(method, DLR_INTERFACE_GOTO_TRACK)) task = dlr_task_goto_track_new(invocation, object, parameters); else goto finished; prv_add_task(task, sender, device_id); finished: return; } static void prv_dlr_push_host_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation) { dlr_task_t *task; const gchar *device_id; GError *error = NULL; device_id = prv_get_device_id(object, &error); if (!device_id) { g_context.connector->return_error(invocation, error); g_error_free(error); goto on_error; } if (!strcmp(method, DLR_INTERFACE_HOST_FILE)) task = dlr_task_host_uri_new(invocation, object, sender, parameters); else if (!strcmp(method, DLR_INTERFACE_REMOVE_FILE)) task = dlr_task_remove_uri_new(invocation, object, sender, parameters); else goto on_error; prv_add_task(task, sender, device_id); on_error: return; } static void prv_renderer_device_method_call( dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation) { dlr_task_t *task; const gchar *device_id = NULL; GError *error = NULL; const dleyna_task_queue_key_t *queue_id; device_id = prv_get_device_id(object, &error); if (!device_id) { g_context.connector->return_error(invocation, error); g_error_free(error); goto finished; } if (!strcmp(method, DLR_INTERFACE_CANCEL)) { queue_id = dleyna_task_processor_lookup_queue( g_context.processor, sender, device_id); if (queue_id) dleyna_task_processor_cancel_queue(queue_id); g_context.connector->return_response(invocation, NULL); } else if (!strcmp(method, DLR_INTERFACE_GET_ICON)) { task = dlr_task_get_icon_new(invocation, object, parameters); prv_add_task(task, sender, device_id); } finished: return; } static void prv_found_media_server(const gchar *path) { DLEYNA_LOG_INFO("New media server %s", path); (void) g_context.connector->notify(g_context.connection, DLEYNA_SERVER_OBJECT, DLEYNA_SERVER_INTERFACE_MANAGER, DLR_INTERFACE_FOUND_RENDERER, g_variant_new("(o)", path), NULL); } static void prv_lost_media_server(const gchar *path) { DLEYNA_LOG_INFO("Lost %s", path); (void) g_context.connector->notify(g_context.connection, DLEYNA_SERVER_OBJECT, DLEYNA_SERVER_INTERFACE_MANAGER, DLR_INTERFACE_LOST_RENDERER, g_variant_new("(o)", path), NULL); dleyna_task_processor_remove_queues_for_sink(g_context.processor, path); } static void prv_white_list_init(void) { gboolean enabled; GVariant *entries; dleyna_white_list_t *wl; DLEYNA_LOG_DEBUG("Enter"); enabled = dleyna_settings_is_white_list_enabled(g_context.settings); entries = dleyna_settings_white_list_entries(g_context.settings); wl = dlr_manager_get_white_list(g_context.manager); dleyna_white_list_enable(wl, enabled); dleyna_white_list_add_entries(wl, entries); DLEYNA_LOG_DEBUG("Exit"); } static gboolean prv_control_point_start_service( dleyna_connector_id_t connection) { gboolean retval = TRUE; uint i; g_context.connection = connection; for (i = 0; i < DLR_MANAGER_INTERFACE_INFO_MAX; i++) g_context.dlr_id[i] = g_context.connector->publish_object( connection, DLEYNA_SERVER_OBJECT, TRUE, g_manager_interfaces[i], g_root_vtables + i); if (g_context.dlr_id[DLR_MANAGER_INTERFACE_MANAGER]) { g_context.upnp = dlr_upnp_new(connection, dleyna_settings_port(g_context.settings), dleyna_settings_push_host_port(g_context.settings), g_server_vtables, prv_found_media_server, prv_lost_media_server); g_context.manager = dlr_manager_new(connection, dlr_upnp_get_context_manager( g_context.upnp)); prv_white_list_init(); } else { retval = FALSE; } return retval; } static const gchar *prv_control_point_server_name(void) { return DLEYNA_SERVER_NAME; } static const gchar *prv_control_point_server_introspection(void) { return g_server_introspection; } static const gchar *prv_control_point_root_introspection(void) { return g_root_introspection; } static const gchar *prv_control_point_get_version(void) { return VERSION; } static const dleyna_control_point_t g_control_point = { prv_control_point_initialize, prv_control_point_free, prv_control_point_server_name, prv_control_point_server_introspection, prv_control_point_root_introspection, prv_control_point_start_service, prv_control_point_stop_service, prv_control_point_get_version }; const dleyna_control_point_t *dleyna_control_point_get_renderer(void) { return &g_control_point; } dleyna-renderer-0.6.0/libdleyna/renderer/server.h000066400000000000000000000025111305660374100220310ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Regis Merlino * */ #ifndef DLR_SERVER_H__ #define DLR_SERVER_H__ #include #include #define DLR_RENDERER_SINK "dleyna-renderer" typedef struct dlr_device_t_ dlr_device_t; typedef struct dlr_upnp_t_ dlr_upnp_t; dlr_upnp_t *dlr_renderer_service_get_upnp(void); dleyna_task_processor_t *dlr_renderer_service_get_task_processor(void); const dleyna_connector_t *dlr_renderer_get_connector(void); const gchar *dlr_renderer_get_interface_name(guint index); #endif /* DLR_SERVER_H__ */ dleyna-renderer-0.6.0/libdleyna/renderer/task.c000066400000000000000000000342111305660374100214620ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #include #include #include "async.h" #include "server.h" #define DLR_TASK_SET_URI_OPERATION "SetAVTransportURI" #define DLR_TASK_SET_URI_TYPE "CurrentURI" #define DLR_TASK_SET_URI_META_DATA "CurrentURIMetaData" #define DLR_TASK_SET_NEXT_URI_OPERATION "SetNextAVTransportURI" #define DLR_TASK_SET_NEXT_URI_TYPE "NextURI" #define DLR_TASK_SET_NEXT_URI_META_DATA "NextURIMetaData" dlr_task_t *dlr_task_rescan_new(dleyna_connector_msg_id_t invocation) { dlr_task_t *task = g_new0(dlr_task_t, 1); task->type = DLR_TASK_RESCAN; task->invocation = invocation; task->synchronous = TRUE; return task; } dlr_task_t *dlr_task_get_version_new(dleyna_connector_msg_id_t invocation) { dlr_task_t *task = g_new0(dlr_task_t, 1); task->type = DLR_TASK_GET_VERSION; task->invocation = invocation; task->result_format = "(@s)"; task->synchronous = TRUE; return task; } dlr_task_t *dlr_task_get_servers_new(dleyna_connector_msg_id_t invocation) { dlr_task_t *task = g_new0(dlr_task_t, 1); task->type = DLR_TASK_GET_SERVERS; task->invocation = invocation; task->result_format = "(@ao)"; task->synchronous = TRUE; return task; } dlr_task_t *dlr_task_raise_new(dleyna_connector_msg_id_t invocation) { dlr_task_t *task = g_new0(dlr_task_t, 1); task->type = DLR_TASK_RAISE; task->invocation = invocation; task->synchronous = TRUE; return task; } dlr_task_t *dlr_task_quit_new(dleyna_connector_msg_id_t invocation) { dlr_task_t *task = g_new0(dlr_task_t, 1); task->type = DLR_TASK_QUIT; task->invocation = invocation; task->synchronous = TRUE; return task; } static void prv_dlr_task_delete(dlr_task_t *task) { if (!task->synchronous) dlr_async_task_delete((dlr_async_task_t *)task); switch (task->type) { case DLR_TASK_MANAGER_GET_ALL_PROPS: case DLR_TASK_GET_ALL_PROPS: g_free(task->ut.get_props.interface_name); break; case DLR_TASK_MANAGER_GET_PROP: case DLR_TASK_GET_PROP: g_free(task->ut.get_prop.interface_name); g_free(task->ut.get_prop.prop_name); break; case DLR_TASK_MANAGER_SET_PROP: case DLR_TASK_SET_PROP: g_free(task->ut.set_prop.interface_name); g_free(task->ut.set_prop.prop_name); g_variant_unref(task->ut.set_prop.params); break; case DLR_TASK_OPEN_URI: case DLR_TASK_OPEN_NEXT_URI: case DLR_TASK_SET_URI: g_free(task->ut.open_uri.uri); g_free(task->ut.open_uri.metadata); break; case DLR_TASK_HOST_URI: case DLR_TASK_REMOVE_URI: g_free(task->ut.host_uri.uri); g_free(task->ut.host_uri.client); break; case DLR_TASK_GET_ICON: g_free(task->ut.get_icon.mime_type); g_free(task->ut.get_icon.resolution); break; default: break; } g_free(task->path); if (task->result) g_variant_unref(task->result); g_free(task); } static dlr_task_t *prv_device_task_new(dlr_task_type_t type, dleyna_connector_msg_id_t invocation, const gchar *path, const gchar *result_format) { dlr_task_t *task = (dlr_task_t *)g_new0(dlr_async_task_t, 1); task->type = type; task->invocation = invocation; task->result_format = result_format; task->path = g_strdup(path); g_strstrip(task->path); return task; } dlr_task_t *dlr_task_get_prop_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters) { dlr_task_t *task; task = prv_device_task_new(DLR_TASK_GET_PROP, invocation, path, "(v)"); g_variant_get(parameters, "(ss)", &task->ut.get_prop.interface_name, &task->ut.get_prop.prop_name); g_strstrip(task->ut.get_prop.interface_name); g_strstrip(task->ut.get_prop.prop_name); return task; } dlr_task_t *dlr_task_get_props_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters) { dlr_task_t *task; task = prv_device_task_new(DLR_TASK_GET_ALL_PROPS, invocation, path, "(@a{sv})"); g_variant_get(parameters, "(s)", &task->ut.get_props.interface_name); g_strstrip(task->ut.get_props.interface_name); return task; } dlr_task_t *dlr_task_set_prop_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters) { dlr_task_t *task; task = prv_device_task_new(DLR_TASK_SET_PROP, invocation, path, NULL); g_variant_get(parameters, "(ssv)", &task->ut.set_prop.interface_name, &task->ut.set_prop.prop_name, &task->ut.set_prop.params); g_strstrip(task->ut.set_prop.interface_name); g_strstrip(task->ut.set_prop.prop_name); return task; } dlr_task_t *dlr_task_play_new(dleyna_connector_msg_id_t invocation, const gchar *path) { return prv_device_task_new(DLR_TASK_PLAY, invocation, path, NULL); } dlr_task_t *dlr_task_pause_new(dleyna_connector_msg_id_t invocation, const gchar *path) { return prv_device_task_new(DLR_TASK_PAUSE, invocation, path, NULL); } dlr_task_t *dlr_task_play_pause_new(dleyna_connector_msg_id_t invocation, const gchar *path) { return prv_device_task_new(DLR_TASK_PLAY_PAUSE, invocation, path, NULL); } dlr_task_t *dlr_task_stop_new(dleyna_connector_msg_id_t invocation, const gchar *path) { return prv_device_task_new(DLR_TASK_STOP, invocation, path, NULL); } dlr_task_t *dlr_task_next_new(dleyna_connector_msg_id_t invocation, const gchar *path) { return prv_device_task_new(DLR_TASK_NEXT, invocation, path, NULL); } dlr_task_t *dlr_task_previous_new(dleyna_connector_msg_id_t invocation, const gchar *path) { return prv_device_task_new(DLR_TASK_PREVIOUS, invocation, path, NULL); } dlr_task_t *dlr_task_seek_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters) { dlr_task_t *task = prv_device_task_new(DLR_TASK_SEEK, invocation, path, NULL); g_variant_get(parameters, "(x)", &task->ut.seek.position); return task; } dlr_task_t *dlr_task_byte_seek_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters) { dlr_task_t *task = prv_device_task_new(DLR_TASK_BYTE_SEEK, invocation, path, NULL); g_variant_get(parameters, "(x)", &task->ut.seek.counter_position); return task; } dlr_task_t *dlr_task_set_position_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters) { gchar *track_id; dlr_task_t *task = prv_device_task_new(DLR_TASK_SET_POSITION, invocation, path, NULL); g_variant_get(parameters, "(&ox)", &track_id, &task->ut.seek.position); return task; } dlr_task_t *dlr_task_set_byte_position_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters) { gchar *track_id; dlr_task_t *task = prv_device_task_new(DLR_TASK_SET_BYTE_POSITION, invocation, path, NULL); g_variant_get(parameters, "(&ox)", &track_id, &task->ut.seek.counter_position); return task; } dlr_task_t *dlr_task_goto_track_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters) { dlr_task_t *task = prv_device_task_new(DLR_TASK_GOTO_TRACK, invocation, path, NULL); g_variant_get(parameters, "(u)", &task->ut.seek.track_number); return task; } dlr_task_t *dlr_task_open_uri_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters) { dlr_task_t *task; task = prv_device_task_new(DLR_TASK_OPEN_URI, invocation, path, NULL); g_variant_get(parameters, "(s)", &task->ut.open_uri.uri); g_strstrip(task->ut.open_uri.uri); task->ut.open_uri.operation = DLR_TASK_SET_URI_OPERATION; task->ut.open_uri.uri_type = DLR_TASK_SET_URI_TYPE; task->ut.open_uri.metadata_type = DLR_TASK_SET_URI_META_DATA; return task; } static dlr_task_t *prv_open_uri_ex_generic(dlr_task_t *task, GVariant *parameters, const gchar *operation, const gchar *uri_type, const gchar *metadata_type) { g_variant_get(parameters, "(ss)", &task->ut.open_uri.uri, &task->ut.open_uri.metadata); g_strstrip(task->ut.open_uri.uri); g_strstrip(task->ut.open_uri.metadata); task->ut.open_uri.operation = operation; task->ut.open_uri.uri_type = uri_type; task->ut.open_uri.metadata_type = metadata_type; return task; } dlr_task_t *dlr_task_open_uri_ex_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters) { dlr_task_t *task; task = prv_device_task_new(DLR_TASK_OPEN_URI, invocation, path, NULL); return prv_open_uri_ex_generic(task, parameters, DLR_TASK_SET_URI_OPERATION, DLR_TASK_SET_URI_TYPE, DLR_TASK_SET_URI_META_DATA); } dlr_task_t *dlr_task_open_next_uri_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters) { dlr_task_t *task; task = prv_device_task_new(DLR_TASK_OPEN_NEXT_URI, invocation, path, NULL); return prv_open_uri_ex_generic(task, parameters, DLR_TASK_SET_NEXT_URI_OPERATION, DLR_TASK_SET_NEXT_URI_TYPE, DLR_TASK_SET_NEXT_URI_META_DATA); } dlr_task_t *dlr_task_set_uri_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters) { dlr_task_t *task; task = prv_device_task_new(DLR_TASK_SET_URI, invocation, path, NULL); return prv_open_uri_ex_generic(task, parameters, DLR_TASK_SET_URI_OPERATION, DLR_TASK_SET_URI_TYPE, DLR_TASK_SET_URI_META_DATA); } dlr_task_t *dlr_task_host_uri_new(dleyna_connector_msg_id_t invocation, const gchar *path, const gchar *sender, GVariant *parameters) { dlr_task_t *task; task = prv_device_task_new(DLR_TASK_HOST_URI, invocation, path, "(@s)"); g_variant_get(parameters, "(s)", &task->ut.host_uri.uri); g_strstrip(task->ut.host_uri.uri); task->ut.host_uri.client = g_strdup(sender); return task; } dlr_task_t *dlr_task_remove_uri_new(dleyna_connector_msg_id_t invocation, const gchar *path, const gchar *sender, GVariant *parameters) { dlr_task_t *task; task = prv_device_task_new(DLR_TASK_REMOVE_URI, invocation, path, NULL); g_variant_get(parameters, "(s)", &task->ut.host_uri.uri); g_strstrip(task->ut.host_uri.uri); task->ut.host_uri.client = g_strdup(sender); return task; } dlr_task_t *dlr_task_get_icon_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters) { dlr_task_t *task; task = prv_device_task_new(DLR_TASK_GET_ICON, invocation, path, "(@ays)"); task->multiple_retvals = TRUE; g_variant_get(parameters, "(ss)", &task->ut.get_icon.mime_type, &task->ut.get_icon.resolution); return task; } dlr_task_t *dlr_task_manager_get_prop_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error) { dlr_task_t *task; task = prv_device_task_new(DLR_TASK_MANAGER_GET_PROP, invocation, path, "(v)"); g_variant_get(parameters, "(ss)", &task->ut.get_prop.interface_name, &task->ut.get_prop.prop_name); g_strstrip(task->ut.get_prop.interface_name); g_strstrip(task->ut.get_prop.prop_name); return task; } dlr_task_t *dlr_task_manager_get_props_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error) { dlr_task_t *task; task = prv_device_task_new(DLR_TASK_MANAGER_GET_ALL_PROPS, invocation, path, "(@a{sv})"); g_variant_get(parameters, "(s)", &task->ut.get_props.interface_name); g_strstrip(task->ut.get_props.interface_name); return task; } dlr_task_t *dlr_task_manager_set_prop_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error) { dlr_task_t *task; task = prv_device_task_new(DLR_TASK_MANAGER_SET_PROP, invocation, path, NULL); g_variant_get(parameters, "(ssv)", &task->ut.set_prop.interface_name, &task->ut.set_prop.prop_name, &task->ut.set_prop.params); g_strstrip(task->ut.set_prop.interface_name); g_strstrip(task->ut.set_prop.prop_name); return task; } void dlr_task_complete(dlr_task_t *task) { GVariant *result; if (!task) goto finished; if (task->invocation) { if (task->result_format && task->result) { if (task->multiple_retvals) result = task->result; else result = g_variant_new(task->result_format, task->result); g_variant_ref_sink(result); dlr_renderer_get_connector()->return_response( task->invocation, result); g_variant_unref(result); } else { dlr_renderer_get_connector()->return_response( task->invocation, NULL); } task->invocation = NULL; } finished: return; } void dlr_task_fail(dlr_task_t *task, GError *error) { if (!task) goto finished; if (task->invocation) { dlr_renderer_get_connector()->return_error(task->invocation, error); task->invocation = NULL; } finished: return; } void dlr_task_cancel(dlr_task_t *task) { GError *error; if (!task) goto finished; if (task->invocation) { error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_CANCELLED, "Operation cancelled."); dlr_renderer_get_connector()->return_error(task->invocation, error); task->invocation = NULL; g_error_free(error); } if (!task->synchronous) dlr_async_task_cancel((dlr_async_task_t *)task); finished: return; } void dlr_task_delete(dlr_task_t *task) { GError *error; if (!task) goto finished; if (task->invocation) { error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_DIED, "Unable to complete command."); dlr_renderer_get_connector()->return_error(task->invocation, error); g_error_free(error); } prv_dlr_task_delete(task); finished: return; } dleyna-renderer-0.6.0/libdleyna/renderer/task.h000066400000000000000000000152211305660374100214670ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #ifndef DLR_TASK_H__ #define DLR_TASK_H__ #include #include #include #include enum dlr_task_type_t_ { DLR_TASK_GET_VERSION, DLR_TASK_GET_SERVERS, DLR_TASK_RESCAN, DLR_TASK_RAISE, DLR_TASK_QUIT, DLR_TASK_SET_PROP, DLR_TASK_GET_ALL_PROPS, DLR_TASK_GET_PROP, DLR_TASK_PAUSE, DLR_TASK_PLAY, DLR_TASK_PLAY_PAUSE, DLR_TASK_STOP, DLR_TASK_NEXT, DLR_TASK_PREVIOUS, DLR_TASK_OPEN_URI, DLR_TASK_OPEN_NEXT_URI, DLR_TASK_SET_URI, DLR_TASK_SEEK, DLR_TASK_BYTE_SEEK, DLR_TASK_SET_POSITION, DLR_TASK_SET_BYTE_POSITION, DLR_TASK_GOTO_TRACK, DLR_TASK_HOST_URI, DLR_TASK_REMOVE_URI, DLR_TASK_GET_ICON, DLR_TASK_MANAGER_GET_ALL_PROPS, DLR_TASK_MANAGER_GET_PROP, DLR_TASK_MANAGER_SET_PROP }; typedef enum dlr_task_type_t_ dlr_task_type_t; typedef void (*dlr_cancel_task_t)(void *handle); typedef struct dlr_task_get_props_t_ dlr_task_get_props_t; struct dlr_task_get_props_t_ { gchar *interface_name; }; typedef struct dlr_task_get_prop_t_ dlr_task_get_prop_t; struct dlr_task_get_prop_t_ { gchar *prop_name; gchar *interface_name; }; typedef struct dlr_task_set_prop_t_ dlr_task_set_prop_t; struct dlr_task_set_prop_t_ { gchar *prop_name; gchar *interface_name; GVariant *params; }; typedef struct dlr_task_open_uri_t_ dlr_task_open_uri_t; struct dlr_task_open_uri_t_ { gchar *uri; gchar *metadata; const gchar *operation; const gchar *uri_type; const gchar *metadata_type; }; typedef struct dlr_task_seek_t_ dlr_task_seek_t; struct dlr_task_seek_t_ { guint64 counter_position; gint64 position; guint32 track_number; }; typedef struct dlr_task_host_uri_t_ dlr_task_host_uri_t; struct dlr_task_host_uri_t_ { gchar *uri; gchar *client; }; typedef struct dlr_task_get_icon_t_ dlr_task_get_icon_t; struct dlr_task_get_icon_t_ { gchar *mime_type; gchar *resolution; }; typedef struct dlr_task_t_ dlr_task_t; struct dlr_task_t_ { dleyna_task_atom_t atom; /* pseudo inheritance - MUST be first field */ dlr_task_type_t type; gchar *path; const gchar *result_format; GVariant *result; dleyna_connector_msg_id_t invocation; gboolean synchronous; gboolean multiple_retvals; union { dlr_task_get_props_t get_props; dlr_task_get_prop_t get_prop; dlr_task_set_prop_t set_prop; dlr_task_open_uri_t open_uri; dlr_task_host_uri_t host_uri; dlr_task_seek_t seek; dlr_task_get_icon_t get_icon; } ut; }; dlr_task_t *dlr_task_rescan_new(dleyna_connector_msg_id_t invocation); dlr_task_t *dlr_task_get_version_new(dleyna_connector_msg_id_t invocation); dlr_task_t *dlr_task_get_servers_new(dleyna_connector_msg_id_t invocation); dlr_task_t *dlr_task_raise_new(dleyna_connector_msg_id_t invocation); dlr_task_t *dlr_task_quit_new(dleyna_connector_msg_id_t invocation); dlr_task_t *dlr_task_set_prop_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters); dlr_task_t *dlr_task_get_prop_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters); dlr_task_t *dlr_task_get_props_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters); dlr_task_t *dlr_task_play_new(dleyna_connector_msg_id_t invocation, const gchar *path); dlr_task_t *dlr_task_pause_new(dleyna_connector_msg_id_t invocation, const gchar *path); dlr_task_t *dlr_task_play_pause_new(dleyna_connector_msg_id_t invocation, const gchar *path); dlr_task_t *dlr_task_stop_new(dleyna_connector_msg_id_t invocation, const gchar *path); dlr_task_t *dlr_task_next_new(dleyna_connector_msg_id_t invocation, const gchar *path); dlr_task_t *dlr_task_previous_new(dleyna_connector_msg_id_t invocation, const gchar *path); dlr_task_t *dlr_task_seek_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters); dlr_task_t *dlr_task_byte_seek_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters); dlr_task_t *dlr_task_set_position_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters); dlr_task_t *dlr_task_set_byte_position_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters); dlr_task_t *dlr_task_goto_track_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters); dlr_task_t *dlr_task_open_uri_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters); dlr_task_t *dlr_task_open_uri_ex_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters); dlr_task_t *dlr_task_open_next_uri_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters); dlr_task_t *dlr_task_set_uri_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters); dlr_task_t *dlr_task_host_uri_new(dleyna_connector_msg_id_t invocation, const gchar *path, const gchar *sender, GVariant *parameters); dlr_task_t *dlr_task_remove_uri_new(dleyna_connector_msg_id_t invocation, const gchar *path, const gchar *sender, GVariant *parameters); dlr_task_t *dlr_task_get_icon_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters); dlr_task_t *dlr_task_manager_get_prop_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error); dlr_task_t *dlr_task_manager_get_props_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error); dlr_task_t *dlr_task_manager_set_prop_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error); void dlr_task_complete(dlr_task_t *task); void dlr_task_fail(dlr_task_t *task, GError *error); void dlr_task_delete(dlr_task_t *task); void dlr_task_cancel(dlr_task_t *task); #endif dleyna-renderer-0.6.0/libdleyna/renderer/upnp.c000066400000000000000000000522361305660374100215110ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #include #include #include #include #include #include #include #include "async.h" #include "device.h" #include "host-service.h" #include "prop-defs.h" #include "upnp.h" struct dlr_upnp_t_ { dleyna_connector_id_t connection; const dleyna_connector_dispatch_cb_t *interface_info; dlr_upnp_callback_t found_server; dlr_upnp_callback_t lost_server; GUPnPContextManager *context_manager; void *user_data; GHashTable *server_udn_map; GHashTable *server_uc_map; dlr_host_service_t *host_service; }; /* Private structure used in service task */ typedef struct prv_device_new_ct_t_ prv_device_new_ct_t; struct prv_device_new_ct_t_ { dlr_upnp_t *upnp; char *udn; gchar *ip_address; dlr_device_t *device; const dleyna_task_queue_key_t *queue_id; }; static void prv_device_new_free(prv_device_new_ct_t *priv_t) { if (priv_t) { g_free(priv_t->udn); g_free(priv_t->ip_address); g_free(priv_t); } } static void prv_device_chain_end(gboolean cancelled, gpointer data) { dlr_device_t *device; prv_device_new_ct_t *priv_t = (prv_device_new_ct_t *)data; DLEYNA_LOG_DEBUG("Enter"); device = priv_t->device; if (cancelled) goto on_clear; DLEYNA_LOG_DEBUG("Notify new server available: %s", device->path); g_hash_table_insert(priv_t->upnp->server_udn_map, g_strdup(priv_t->udn), device); priv_t->upnp->found_server(device->path); on_clear: g_hash_table_remove(priv_t->upnp->server_uc_map, priv_t->udn); prv_device_new_free(priv_t); if (cancelled) dlr_device_delete(device); DLEYNA_LOG_DEBUG("Exit"); DLEYNA_LOG_DEBUG_NL(); } static void prv_device_context_switch_end(gboolean cancelled, gpointer data) { prv_device_new_ct_t *priv_t = (prv_device_new_ct_t *)data; DLEYNA_LOG_DEBUG("Enter"); prv_device_new_free(priv_t); DLEYNA_LOG_DEBUG("Exit"); } static const dleyna_task_queue_key_t *prv_create_device_queue( prv_device_new_ct_t **priv_t) { const dleyna_task_queue_key_t *queue_id; *priv_t = g_new0(prv_device_new_ct_t, 1); queue_id = dleyna_task_processor_add_queue( dlr_renderer_service_get_task_processor(), dleyna_service_task_create_source(), DLR_RENDERER_SINK, DLEYNA_TASK_QUEUE_FLAG_AUTO_REMOVE, dleyna_service_task_process_cb, dleyna_service_task_cancel_cb, dleyna_service_task_delete_cb); dleyna_task_queue_set_finally(queue_id, prv_device_chain_end); dleyna_task_queue_set_user_data(queue_id, *priv_t); return queue_id; } static void prv_update_device_context(prv_device_new_ct_t *priv_t, dlr_upnp_t *upnp, const char *udn, dlr_device_t *device, const gchar *ip_address, const dleyna_task_queue_key_t *queue_id) { priv_t->upnp = upnp; priv_t->udn = g_strdup(udn); priv_t->ip_address = g_strdup(ip_address); priv_t->queue_id = queue_id; priv_t->device = device; g_hash_table_insert(upnp->server_uc_map, g_strdup(udn), priv_t); } static void prv_server_available_cb(GUPnPControlPoint *cp, GUPnPDeviceProxy *proxy, gpointer user_data) { dlr_upnp_t *upnp = user_data; const char *udn; dlr_device_t *device; const gchar *ip_address; dlr_device_context_t *context; const dleyna_task_queue_key_t *queue_id; unsigned int i; prv_device_new_ct_t *priv_t; DLEYNA_LOG_DEBUG("Enter"); udn = gupnp_device_info_get_udn((GUPnPDeviceInfo *)proxy); ip_address = gssdp_client_get_host_ip( GSSDP_CLIENT(gupnp_control_point_get_context(cp))); if (!udn || !ip_address) goto on_error; DLEYNA_LOG_DEBUG("UDN %s", udn); DLEYNA_LOG_DEBUG("IP Address %s", ip_address); device = g_hash_table_lookup(upnp->server_udn_map, udn); if (!device) { priv_t = g_hash_table_lookup(upnp->server_uc_map, udn); if (priv_t) device = priv_t->device; } if (!device) { DLEYNA_LOG_DEBUG("Device not found. Adding"); queue_id = prv_create_device_queue(&priv_t); device = dlr_device_new(upnp->connection, proxy, ip_address, udn, upnp->interface_info, queue_id); prv_update_device_context(priv_t, upnp, udn, device, ip_address, queue_id); } else { DLEYNA_LOG_DEBUG("Device Found"); for (i = 0; i < device->contexts->len; ++i) { context = g_ptr_array_index(device->contexts, i); if (!strcmp(context->ip_address, ip_address)) break; } if (i == device->contexts->len) { DLEYNA_LOG_DEBUG("Adding Context"); dlr_device_append_new_context(device, ip_address, proxy); } } on_error: DLEYNA_LOG_DEBUG("Exit"); DLEYNA_LOG_DEBUG_NL(); return; } static gboolean prv_subscribe_to_service_changes(gpointer user_data) { dlr_device_t *device = user_data; device->timeout_id = 0; dlr_device_subscribe_to_service_changes(device); return FALSE; } static void prv_server_unavailable_cb(GUPnPControlPoint *cp, GUPnPDeviceProxy *proxy, gpointer user_data) { dlr_upnp_t *upnp = user_data; const char *udn; dlr_device_t *device; const gchar *ip_address; unsigned int i; dlr_device_context_t *context; gboolean subscribed; gboolean under_construction = FALSE; prv_device_new_ct_t *priv_t; gboolean construction_ctx = FALSE; const dleyna_task_queue_key_t *queue_id; DLEYNA_LOG_DEBUG("Enter"); udn = gupnp_device_info_get_udn((GUPnPDeviceInfo *)proxy); ip_address = gupnp_context_get_host_ip( gupnp_control_point_get_context(cp)); if (!udn || !ip_address) goto on_error; DLEYNA_LOG_DEBUG("UDN %s", udn); DLEYNA_LOG_DEBUG("IP Address %s", ip_address); device = g_hash_table_lookup(upnp->server_udn_map, udn); if (!device) { priv_t = g_hash_table_lookup(upnp->server_uc_map, udn); if (priv_t) { device = priv_t->device; under_construction = TRUE; } } if (!device) { DLEYNA_LOG_WARNING("Device not found. Ignoring"); goto on_error; } for (i = 0; i < device->contexts->len; ++i) { context = g_ptr_array_index(device->contexts, i); if (!strcmp(context->ip_address, ip_address)) break; } if (i < device->contexts->len) { subscribed = (context->subscribed_av || context->subscribed_cm); if (under_construction) construction_ctx = !strcmp(context->ip_address, priv_t->ip_address); (void) g_ptr_array_remove_index(device->contexts, i); if (device->contexts->len == 0) { if (!under_construction) { DLEYNA_LOG_DEBUG( "Last Context lost. Delete device"); upnp->lost_server(device->path); g_hash_table_remove(upnp->server_udn_map, udn); } else { DLEYNA_LOG_WARNING( "Device under construction. Cancelling"); dleyna_task_processor_cancel_queue( priv_t->queue_id); } } else if (under_construction && construction_ctx) { DLEYNA_LOG_WARNING( "Device under construction. Switching context"); /* Cancel previous contruction task chain */ g_hash_table_remove(priv_t->upnp->server_uc_map, priv_t->udn); dleyna_task_queue_set_finally( priv_t->queue_id, prv_device_context_switch_end); dleyna_task_processor_cancel_queue(priv_t->queue_id); /* Create a new construction task chain */ context = dlr_device_get_context(device); queue_id = prv_create_device_queue(&priv_t); prv_update_device_context(priv_t, upnp, udn, device, context->ip_address, queue_id); /* Start tasks from current construction step */ dlr_device_construct(device, context, upnp->connection, upnp->interface_info, queue_id); } else if (subscribed && !device->timeout_id) { DLEYNA_LOG_DEBUG("Subscribe on new context"); device->timeout_id = g_timeout_add_seconds(1, prv_subscribe_to_service_changes, device); } } on_error: return; } static void prv_on_context_available(GUPnPContextManager *context_manager, GUPnPContext *context, gpointer user_data) { dlr_upnp_t *upnp = user_data; GUPnPControlPoint *cp; cp = gupnp_control_point_new( context, "urn:schemas-upnp-org:device:MediaRenderer:1"); g_signal_connect(cp, "device-proxy-available", G_CALLBACK(prv_server_available_cb), upnp); g_signal_connect(cp, "device-proxy-unavailable", G_CALLBACK(prv_server_unavailable_cb), upnp); gssdp_resource_browser_set_active(GSSDP_RESOURCE_BROWSER(cp), TRUE); gupnp_context_manager_manage_control_point(upnp->context_manager, cp); g_object_unref(cp); } dlr_upnp_t *dlr_upnp_new(dleyna_connector_id_t connection, guint port, guint push_host_port, const dleyna_connector_dispatch_cb_t *dispatch_table, dlr_upnp_callback_t found_server, dlr_upnp_callback_t lost_server) { dlr_upnp_t *upnp = g_new0(dlr_upnp_t, 1); upnp->connection = connection; upnp->interface_info = dispatch_table; upnp->found_server = found_server; upnp->lost_server = lost_server; upnp->server_udn_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, dlr_device_delete); upnp->server_uc_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); upnp->context_manager = gupnp_context_manager_create(port); g_signal_connect(upnp->context_manager, "context-available", G_CALLBACK(prv_on_context_available), upnp); dlr_host_service_new(&upnp->host_service, push_host_port); return upnp; } void dlr_upnp_delete(dlr_upnp_t *upnp) { if (upnp) { dlr_host_service_delete(upnp->host_service); g_object_unref(upnp->context_manager); g_hash_table_unref(upnp->server_udn_map); g_hash_table_unref(upnp->server_uc_map); g_free(upnp); } } GVariant *dlr_upnp_get_server_ids(dlr_upnp_t *upnp) { GVariantBuilder vb; GHashTableIter iter; gpointer value; dlr_device_t *device; DLEYNA_LOG_DEBUG("Enter"); g_variant_builder_init(&vb, G_VARIANT_TYPE("ao")); g_hash_table_iter_init(&iter, upnp->server_udn_map); while (g_hash_table_iter_next(&iter, NULL, &value)) { device = value; g_variant_builder_add(&vb, "o", device->path); } DLEYNA_LOG_DEBUG("Exit"); return g_variant_ref_sink(g_variant_builder_end(&vb)); } GHashTable *dlr_upnp_get_server_udn_map(dlr_upnp_t *upnp) { return upnp->server_udn_map; } void dlr_upnp_set_prop(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_device_t *device; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; device = dlr_device_from_path(task->path, upnp->server_udn_map); if (!device) { cb_data->cb = cb; cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Cannot locate a device for the specified object"); (void) g_idle_add(dlr_async_task_complete, cb_data); } else { dlr_device_set_prop(device, task, cb); } } void dlr_upnp_get_prop(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_device_t *device; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Path: %s", task->path); DLEYNA_LOG_DEBUG("Interface %s", task->ut.get_prop.interface_name); DLEYNA_LOG_DEBUG("Prop.%s", task->ut.get_prop.prop_name); device = dlr_device_from_path(task->path, upnp->server_udn_map); if (!device) { DLEYNA_LOG_WARNING("Cannot locate device"); cb_data->cb = cb; cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Cannot locate a device for the specified object"); (void) g_idle_add(dlr_async_task_complete, cb_data); } else { dlr_device_get_prop(device, task, cb); } DLEYNA_LOG_DEBUG("Exit"); } void dlr_upnp_get_all_props(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_device_t *device; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Path: %s", task->path); DLEYNA_LOG_DEBUG("Interface %s", task->ut.get_prop.interface_name); device = dlr_device_from_path(task->path, upnp->server_udn_map); if (!device) { cb_data->cb = cb; cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Cannot locate a device for the specified object"); (void) g_idle_add(dlr_async_task_complete, cb_data); } else { dlr_device_get_all_props(device, task, cb); } DLEYNA_LOG_DEBUG("Exit"); } void dlr_upnp_play(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_device_t *device; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); device = dlr_device_from_path(task->path, upnp->server_udn_map); if (!device) { cb_data->cb = cb; cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Cannot locate a device for the specified object"); (void) g_idle_add(dlr_async_task_complete, cb_data); } else { dlr_device_play(device, task, cb); } DLEYNA_LOG_DEBUG("Exit"); } void dlr_upnp_pause(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_device_t *device; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); device = dlr_device_from_path(task->path, upnp->server_udn_map); if (!device) { cb_data->cb = cb; cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Cannot locate a device for the specified object"); (void) g_idle_add(dlr_async_task_complete, cb_data); } else { dlr_device_pause(device, task, cb); } DLEYNA_LOG_DEBUG("Exit"); } void dlr_upnp_play_pause(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_device_t *device; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); device = dlr_device_from_path(task->path, upnp->server_udn_map); if (!device) { cb_data->cb = cb; cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Cannot locate a device for the specified object"); (void) g_idle_add(dlr_async_task_complete, cb_data); } else { dlr_device_play_pause(device, task, cb); } DLEYNA_LOG_DEBUG("Exit"); } void dlr_upnp_stop(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_device_t *device; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); device = dlr_device_from_path(task->path, upnp->server_udn_map); if (!device) { cb_data->cb = cb; cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Cannot locate a device for the specified object"); (void) g_idle_add(dlr_async_task_complete, cb_data); } else { dlr_device_stop(device, task, cb); } DLEYNA_LOG_DEBUG("Exit"); } void dlr_upnp_next(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_device_t *device; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); device = dlr_device_from_path(task->path, upnp->server_udn_map); if (!device) { cb_data->cb = cb; cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Cannot locate a device for the specified object"); (void) g_idle_add(dlr_async_task_complete, cb_data); } else { dlr_device_next(device, task, cb); } DLEYNA_LOG_DEBUG("Exit"); } void dlr_upnp_previous(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_device_t *device; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); device = dlr_device_from_path(task->path, upnp->server_udn_map); if (!device) { cb_data->cb = cb; cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Cannot locate a device for the specified object"); (void) g_idle_add(dlr_async_task_complete, cb_data); } else { dlr_device_previous(device, task, cb); } DLEYNA_LOG_DEBUG("Exit"); } void dlr_upnp_open_uri(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_device_t *device; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); device = dlr_device_from_path(task->path, upnp->server_udn_map); if (!device) { cb_data->cb = cb; cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Cannot locate a device for the specified object"); (void) g_idle_add(dlr_async_task_complete, cb_data); } else { dlr_device_open_uri(device, task, cb); } DLEYNA_LOG_DEBUG("Exit"); } void dlr_upnp_seek(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_device_t *device; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); device = dlr_device_from_path(task->path, upnp->server_udn_map); if (!device) { cb_data->cb = cb; cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Cannot locate a device for the specified object"); (void) g_idle_add(dlr_async_task_complete, cb_data); } else { dlr_device_seek(device, task, cb); } DLEYNA_LOG_DEBUG("Exit"); } void dlr_upnp_set_position(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_device_t *device; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); device = dlr_device_from_path(task->path, upnp->server_udn_map); if (!device) { cb_data->cb = cb; cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Cannot locate a device for the specified object"); (void) g_idle_add(dlr_async_task_complete, cb_data); } else { dlr_device_set_position(device, task, cb); } DLEYNA_LOG_DEBUG("Exit"); } void dlr_upnp_goto_track(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_device_t *device; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); device = dlr_device_from_path(task->path, upnp->server_udn_map); if (!device) { cb_data->cb = cb; cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Cannot locate a device for the specified object"); (void) g_idle_add(dlr_async_task_complete, cb_data); } else { dlr_device_goto_track(device, task, cb); } DLEYNA_LOG_DEBUG("Exit"); } void dlr_upnp_host_uri(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_device_t *device; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); device = dlr_device_from_path(task->path, upnp->server_udn_map); if (!device) { cb_data->cb = cb; cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Cannot locate a device for the specified object"); (void) g_idle_add(dlr_async_task_complete, cb_data); } else { dlr_device_host_uri(device, task, upnp->host_service, cb); } DLEYNA_LOG_DEBUG("Exit"); } void dlr_upnp_remove_uri(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_device_t *device; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); device = dlr_device_from_path(task->path, upnp->server_udn_map); if (!device) { cb_data->cb = cb; cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Cannot locate a device for the specified object"); (void) g_idle_add(dlr_async_task_complete, cb_data); } else { dlr_device_remove_uri(device, task, upnp->host_service, cb); } DLEYNA_LOG_DEBUG("Exit"); } void dlr_upnp_get_icon(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb) { dlr_device_t *device; dlr_async_task_t *cb_data = (dlr_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); device = dlr_device_from_path(task->path, upnp->server_udn_map); if (!device) { DLEYNA_LOG_WARNING("Cannot locate device"); cb_data->cb = cb; cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Cannot locate a device for the specified object"); (void) g_idle_add(dlr_async_task_complete, cb_data); } else { dlr_device_get_icon(device, task, cb); } DLEYNA_LOG_DEBUG("Exit"); } void dlr_upnp_lost_client(dlr_upnp_t *upnp, const gchar *client_name) { dlr_host_service_lost_client(upnp->host_service, client_name); } void dlr_upnp_unsubscribe(dlr_upnp_t *upnp) { GHashTableIter iter; dlr_device_t *device; DLEYNA_LOG_DEBUG("Enter"); g_hash_table_iter_init(&iter, upnp->server_udn_map); while (g_hash_table_iter_next(&iter, NULL, (gpointer *)&device)) dlr_device_unsubscribe(device); DLEYNA_LOG_DEBUG("Exit"); } void dlr_upnp_rescan(dlr_upnp_t *upnp) { DLEYNA_LOG_DEBUG("re-scanning control points"); gupnp_context_manager_rescan_control_points(upnp->context_manager); } GUPnPContextManager *dlr_upnp_get_context_manager(dlr_upnp_t *upnp) { return upnp->context_manager; } dleyna-renderer-0.6.0/libdleyna/renderer/upnp.h000066400000000000000000000066541305660374100215210ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #ifndef DLR_UPNP_H__ #define DLR_UPNP_H__ #include #include #include "server.h" #include "task.h" enum dlr_interface_type_ { DLR_INTERFACE_INFO_PROPERTIES, DLR_INTERFACE_INFO_ROOT, DLR_INTERFACE_INFO_PLAYER, DLR_INTERFACE_INFO_PUSH_HOST, DLR_INTERFACE_INFO_DEVICE, DLR_INTERFACE_INFO_MAX }; typedef void (*dlr_upnp_callback_t)(const gchar *path); typedef void (*dlr_upnp_task_complete_t)(dlr_task_t *task, GError *error); dlr_upnp_t *dlr_upnp_new(dleyna_connector_id_t connection, uint port, uint push_host_port, const dleyna_connector_dispatch_cb_t *dispatch_table, dlr_upnp_callback_t found_server, dlr_upnp_callback_t lost_server); void dlr_upnp_delete(dlr_upnp_t *upnp); GVariant *dlr_upnp_get_server_ids(dlr_upnp_t *upnp); GHashTable *dlr_upnp_get_server_udn_map(dlr_upnp_t *upnp); void dlr_upnp_set_prop(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_upnp_get_prop(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_upnp_get_all_props(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_upnp_play(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_upnp_pause(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_upnp_play_pause(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_upnp_stop(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_upnp_next(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_upnp_previous(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_upnp_open_uri(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_upnp_seek(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_upnp_set_position(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_upnp_goto_track(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_upnp_host_uri(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_upnp_remove_uri(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_upnp_get_icon(dlr_upnp_t *upnp, dlr_task_t *task, dlr_upnp_task_complete_t cb); void dlr_upnp_lost_client(dlr_upnp_t *upnp, const gchar *client_name); void dlr_upnp_unsubscribe(dlr_upnp_t *upnp); void dlr_upnp_rescan(dlr_upnp_t *upnp); GUPnPContextManager *dlr_upnp_get_context_manager(dlr_upnp_t *upnp); #endif /* DLR_UPNP_H__ */ dleyna-renderer-0.6.0/m4/000077500000000000000000000000001305660374100151225ustar00rootroot00000000000000dleyna-renderer-0.6.0/m4/compiler-flags.m4000066400000000000000000000041631305660374100202740ustar00rootroot00000000000000dnl dnl dLeyna dnl dnl Copyright (C) 2012-2017 Intel Corporation. All rights reserved. dnl dnl This program is free software; you can redistribute it and/or modify it dnl under the terms and conditions of the GNU Lesser General Public License, dnl version 2.1, as published by the Free Software Foundation. dnl dnl This program is distributed in the hope it will be useful, but WITHOUT dnl ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or dnl FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License dnl for more details. dnl dnl You should have received a copy of the GNU Lesser General Public License dnl along with this program; if not, write to the Free Software Foundation, Inc., dnl 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. dnl dnl Ludovic Ferrandis dnl Regis Merlino dnl AC_DEFUN_ONCE([DLEYNA_SERVER_COMPILER_FLAGS], [ if test x"${CFLAGS}" = x""; then CFLAGS="-Wall" AS_VAR_APPEND([CFLAGS], [" -O2"]) AS_VAR_APPEND([CFLAGS], [" -D_FORTIFY_SOURCE=2"]) fi if test x"$USE_MAINTAINER_MODE" = x"yes"; then AS_VAR_APPEND([CFLAGS], [" -Wextra"]) AS_VAR_APPEND([CFLAGS], [" -Wno-unused-parameter"]) AS_VAR_APPEND([CFLAGS], [" -Wno-missing-field-initializers"]) AS_VAR_APPEND([CFLAGS], [" -Wdeclaration-after-statement"]) AS_VAR_APPEND([CFLAGS], [" -Wmissing-declarations"]) AS_VAR_APPEND([CFLAGS], [" -Wredundant-decls"]) AS_VAR_APPEND([CFLAGS], [" -Wcast-align"]) AS_VAR_APPEND([CFLAGS], [" -Wstrict-prototypes"]) AS_VAR_APPEND([CFLAGS], [" -Wmissing-prototypes"]) AS_VAR_APPEND([CFLAGS], [" -Wnested-externs"]) AS_VAR_APPEND([CFLAGS], [" -Wshadow"]) AS_VAR_APPEND([CFLAGS], [" -Wformat=2"]) AS_VAR_APPEND([CFLAGS], [" -Winit-self"]) AS_VAR_APPEND([CFLAGS], [" -std=gnu99"]) AS_VAR_APPEND([CFLAGS], [" -pedantic"]) AS_VAR_APPEND([CFLAGS], [" -Wno-overlength-strings"]) AS_VAR_APPEND([CFLAGS], [" -DG_DISABLE_DEPRECATED"]) AS_VAR_APPEND([CFLAGS], [" -DGLIB_DISABLE_DEPRECATION_WARNINGS"]) fi AS_VAR_APPEND([CFLAGS], [" -Wno-format-extra-args"]) AS_VAR_APPEND([CFLAGS], [" -Wl,--no-undefined"]) ]) dleyna-renderer-0.6.0/m4/log.m4000066400000000000000000000037311305660374100161510ustar00rootroot00000000000000dnl dnl dLeyna dnl dnl Copyright (C) 2012-2017 Intel Corporation. All rights reserved. dnl dnl This program is free software; you can redistribute it and/or modify it dnl under the terms and conditions of the GNU Lesser General Public License, dnl version 2.1, as published by the Free Software Foundation. dnl dnl This program is distributed in the hope it will be useful, but WITHOUT dnl ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or dnl FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License dnl for more details. dnl dnl You should have received a copy of the GNU Lesser General Public License dnl along with this program; if not, write to the Free Software Foundation, Inc., dnl 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. dnl dnl Ludovic Ferrandis dnl AC_DEFUN([_DLEYNA_LOG_LEVEL_CHECK_VALUE], [ AS_CASE($1, [[[1-6]]], [AS_IF([test "x${log_unique}" = xyes], [ AC_MSG_ERROR(["Log levels 0, 7 and 8 cannot be combined with other values"], 1) ]) : $((log_level_count++)) ], [0|7|8], [AS_IF([test ${log_level_count} -ne 0], [ AC_MSG_ERROR(["Log level $1 cannot be combined with other values"], 1) ]) log_unique=yes ], [AC_MSG_ERROR(["$1 is not a valid value"], 1)] ) ] ) AC_DEFUN([DLEYNA_LOG_LEVEL_CHECK], [ AC_MSG_CHECKING([for --with-log-level=$1]) old_IFS=${IFS} IFS="," log_ok=yes log_unique=no log_level_count=0 LOG_LEVEL=0 for log_level in $1 do IFS=${old_IFS} _DLEYNA_LOG_LEVEL_CHECK_VALUE([$log_level]) IFS="," log_name=LOG_LEVEL_${log_level} eval log_value=\$${log_name} : $((LOG_LEVEL |= ${log_value})) done IFS=${old_IFS} AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL], [${LOG_LEVEL}], [Log level flag for debug messages]) AC_MSG_RESULT([ok]) ] ) AC_DEFUN([DLEYNA_LOG_TYPE_CHECK], [ AC_MSG_CHECKING([for --with-log-type=$1]) AS_CASE($1, [0|1], [], [AC_MSG_ERROR(["$1 is not a valid value"], 1)] ) AC_MSG_RESULT([ok]) ] ) dleyna-renderer-0.6.0/server/000077500000000000000000000000001305660374100161105ustar00rootroot00000000000000dleyna-renderer-0.6.0/server/Makefile.am000066400000000000000000000016361305660374100201520ustar00rootroot00000000000000AM_CFLAGS = $(GLIB_CFLAGS) \ $(GIO_CFLAGS) \ $(DLEYNA_CORE_CFLAGS) \ -I$(top_builddir)/libdleyna/renderer \ -include config.h libexec_PROGRAMS = dleyna-renderer-service dleyna_renderer_service_SOURCES = daemon.c dleyna_renderer_service_LDADD = $(GLIB_LIBS) \ $(GIO_LIBS) \ $(DLEYNA_CORE_LIBS) \ $(top_builddir)/libdleyna/renderer/libdleyna-renderer-1.0.la dbusservicedir = $(DBUS_SERVICE_DIR) dbusservice_in_files = com.intel.dleyna-renderer.service.in dbusservice_DATA = com.intel.dleyna-renderer.service # Replace the 'libexecdir' marker with its fully expanded value %.service: %.service.in Makefile $(AM_V_GEN) $(SED) -e "s|\@libexecdir\@|$(libexecdir)|" $< > $@ pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = dleyna-renderer-service-1.0.pc EXTRA_DIST = $(dbusservice_in_files) CLEANFILES = $(dbusservice_DATA) DISTCLEANFILES = $(pkgconfig_DATA) dleyna-renderer-0.6.0/server/com.intel.dleyna-renderer.service.in000066400000000000000000000001311305660374100250410ustar00rootroot00000000000000[D-BUS Service] Name=com.intel.dleyna-renderer Exec=@libexecdir@/dleyna-renderer-service dleyna-renderer-0.6.0/server/daemon.c000066400000000000000000000026501305660374100175220ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * Regis Merlino * */ #include #include #include #include #define DLR_RENDERER_SERVICE_NAME "dleyna-renderer-service" static gboolean prv_quit_handler(gpointer user_data) { dleyna_main_loop_quit(); return FALSE; } int main(int argc, char *argv[]) { int retval; g_unix_signal_add (SIGTERM, prv_quit_handler, NULL); g_unix_signal_add (SIGINT, prv_quit_handler, NULL); retval = dleyna_main_loop_start(DLR_RENDERER_SERVICE_NAME, dleyna_control_point_get_renderer(), NULL); return retval; } dleyna-renderer-0.6.0/server/dleyna-renderer-service-1.0.pc.in000066400000000000000000000003471305660374100240570ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libexecdir=@libexecdir@ Name: @PACKAGE@-service Description: UPnP & DLNA service to discover and manipulate renderers Requires.private: glib-2.0 gio-2.0 dleyna-core-1.0 Version: @VERSION@ dleyna-renderer-0.6.0/test/000077500000000000000000000000001305660374100155615ustar00rootroot00000000000000dleyna-renderer-0.6.0/test/dbus/000077500000000000000000000000001305660374100165165ustar00rootroot00000000000000dleyna-renderer-0.6.0/test/dbus/cap.py000077500000000000000000000207651305660374100176500ustar00rootroot00000000000000#!/usr/bin/python # cap # # Copyright (C) 2012-2017 Intel Corporation. All rights reserved. # # This program is free software; you can redistribute it and/or modify it # under the terms and conditions of the GNU Lesser General Public License, # version 2.1, as published by the Free Software Foundation. # # This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., # 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. # # Mark Ryan # from gi.repository import Gtk, Gdk, GdkPixbuf import cairo import dbus import dbus.service import dbus.mainloop.glib import tempfile class Renderer: def __init__(self, path): bus = dbus.SessionBus() obj = bus.get_object('com.intel.dleyna-renderer', path) self.__propsIF = dbus.Interface(obj, 'org.freedesktop.DBus.Properties') self.__hostIF = dbus.Interface(obj, 'com.intel.dLeynaRenderer.PushHost') self.__playerIF = dbus.Interface(obj, 'org.mpris.MediaPlayer2.Player') def get_prop(self, prop_name, iface = ""): return self.__propsIF.Get(iface, prop_name) def push_file(self, fname): try: self.__hostIF.RemoveFile(fname) except: pass self.__playerIF.Stop() uri = self.__hostIF.HostFile(fname) self.__playerIF.OpenUri(uri) class Renderers: def __init__(self, cb): bus=dbus.SessionBus() obj = bus.get_object('com.intel.dleyna-renderer', '/com/intel/dLeynaRenderer') self.__manager = dbus.Interface(obj, 'com.intel.dLeynaRenderer.Manager') self.__cb = cb self.__manager.connect_to_signal("LostRenderer", self.__servers_changed) self.__manager.connect_to_signal("FoundRenderer", self.__servers_changed) def __servers_changed(self, server): self.__cb() def get_renderers(self): retval = [] for path in self.__manager.GetRenderers(): retval.append((path, Renderer(path))) return retval class UI: def delete_event(self, widget, event, data=None): return False def destroy(self, widget, data=None): Gtk.main_quit() def __create_renderers_store(self): servers_store = Gtk.ListStore(str, str) for server in self.__Renderers.get_renderers(): servers_store.append([server[0], server[1].get_prop("Identity")]) return servers_store def __reset_renderers(self): print "Renderers Changed" entry = self.__combo.get_child() servers_store = self.__create_renderers_store() self.__combo.set_model(servers_store) if len(servers_store) > 0: self.__combo.set_active(0) else: entry.set_text("") def draw_rect(self, widget, x, y): if self.__pixmap != None: ctx = cairo.Context(self.__pixmap) ctx.set_source_rgb(0, 0, 0) ctx.rectangle(x -3, y -3, 6, 6) ctx.fill() widget.queue_draw_area(x -3, y -3, 6, 6) def __mouse_button_pressed_cb(self, widget, event): self.draw_rect(widget, event.x, event.y) return True def __mouse_moved_cb(self, widget, event): if event.state & Gdk.ModifierType.BUTTON1_MASK: self.draw_rect(widget, event.x, event.y) event.request_motions() return True def __draw_cb(self, da, ctx): if self.__pixmap: ctx.set_source_surface(self.__pixmap, 0, 0) ctx.rectangle(0, 0, da.get_allocated_width(), da.get_allocated_height()) ctx.fill() @staticmethod def __blank_pixmap(width, height): new_pixmap = cairo.ImageSurface(cairo.FORMAT_RGB24, width, height) ctx = cairo.Context(new_pixmap) ctx.set_source_rgb(0xff, 0xff, 0xff) ctx.rectangle(0, 0, width, height) ctx.fill() return (new_pixmap, ctx) def __configured_cb(self, widget, event): allocation = widget.get_allocation() width = allocation.width height = allocation.height new_pixmap, ctx = UI.__blank_pixmap(width, height) if self.__pixmap: old_width = self.__pixmap.get_width() old_height = self.__pixmap.get_height() dest_x = (width - old_width) / 2 dest_y = (height - old_height) / 2 ctx.set_source_surface(self.__pixmap, dest_x, dest_y) ctx.rectangle(0, 0, width, height) ctx.fill() self.__pixmap = new_pixmap return True def push_cb(self, button): tree_iter = self.__combo.get_active_iter() if tree_iter != None: self.__pixmap.write_to_png(self.__tmp_file) model = self.__combo.get_model() ren = Renderer(model[tree_iter][0]) ren.push_file(self.__tmp_file) def pick_cb(self, button): dialog = Gtk.FileChooserDialog("Please choose a file", self.__window, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) response = dialog.run() if response == Gtk.ResponseType.OK: print "Open clicked" pick_file = dialog.get_filename() tree_iter = self.__combo.get_active_iter() if tree_iter != None: model = self.__combo.get_model() ren = Renderer(model[tree_iter][0]) dialog.destroy() ren.push_file(pick_file) elif response == Gtk.ResponseType.CANCEL: print "Cancel clicked" dialog.destroy() def clear_cb(self, button): allocation = self.__area.get_allocation() self.__pixmap, ctx = UI.__blank_pixmap(allocation.width, allocation.height) self.__area.queue_draw_area(0,0, allocation.width, allocation.height) def __init__(self): self.__Renderers = Renderers(self.__reset_renderers) self.__tmp_file = tempfile.mktemp(".png") self.__pixmap = None window = Gtk.Window() window.set_default_size(640, 480) window.set_title("Create and Push!") container = Gtk.VBox(False, 0) area = Gtk.DrawingArea() area.set_events(Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.POINTER_MOTION_HINT_MASK) area.connect("button_press_event", self.__mouse_button_pressed_cb) area.connect("motion_notify_event", self.__mouse_moved_cb) area.connect("configure-event", self.__configured_cb) area.connect("draw", self.__draw_cb) container.pack_start(area, True, True, 4); button_bar = Gtk.HBox(False, 0) pick_button = Gtk.Button("Pick & Push"); pick_button.connect("clicked", self.pick_cb) push_button = Gtk.Button("Push"); push_button.connect("clicked", self.push_cb) clear_button = Gtk.Button("Clear"); clear_button.connect("clicked", self.clear_cb) servers_store = self.__create_renderers_store() self.__combo = Gtk.ComboBox.new_with_model_and_entry(servers_store) self.__combo.set_entry_text_column(1) if len(servers_store) > 0: self.__combo.set_active(0) self.__combo.get_child().set_property("editable", False) button_bar.pack_start(pick_button, True, True, 4) button_bar.pack_start(push_button, True, True, 4) button_bar.pack_start(clear_button, True, True, 4) button_bar.pack_start(self.__combo, True, True, 4) container.pack_start(button_bar, False, False, 4); window.add(container) window.show_all() window.connect("delete_event", self.delete_event) window.connect("destroy", self.destroy) self.__window = window self.__area = area if __name__ == "__main__": dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) ui = UI() Gtk.main() dleyna-renderer-0.6.0/test/dbus/lost_client_test.py000077500000000000000000000041351305660374100224540ustar00rootroot00000000000000# test_lost_client # # Copyright (C) 2012-2017 Intel Corporation. All rights reserved. # # This program is free software; you can redistribute it and/or modify it # under the terms and conditions of the GNU Lesser General Public License, # version 2.1, as published by the Free Software Foundation. # # This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., # 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. # # Regis Merlino # import sys import gobject import dbus import dbus.mainloop.glib def handle_reply(props): print "Total Items: " + str(len(props)) print loop.quit() def handle_error(e): print "An error occured" loop.quit() def make_async_call(): root.GetAll("", reply_handler=handle_reply, error_handler=handle_error) root.GetAll("", reply_handler=handle_reply, error_handler=handle_error) root.GetAll("", reply_handler=handle_reply, error_handler=handle_error) root.GetAll("", reply_handler=handle_reply, error_handler=handle_error) root.GetAll("", reply_handler=handle_reply, error_handler=handle_error) # Test: force quit - this should cancel the search on server side loop.quit() if __name__ == '__main__': dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() root = dbus.Interface(bus.get_object( 'com.intel.dleyna-renderer', '/com/intel/dLeynaRenderer/server/0'), 'org.freedesktop.DBus.Properties') gobject.timeout_add(1000, make_async_call) loop = gobject.MainLoop() loop.run() dleyna-renderer-0.6.0/test/dbus/rendererconsole.py000066400000000000000000000202531305660374100222630ustar00rootroot00000000000000# -*- coding: utf-8 -*- # renderer-console # # Copyright (C) 2012-2017 Intel Corporation. All rights reserved. # # This program is free software; you can redistribute it and/or modify it # under the terms and conditions of the GNU Lesser General Public License, # version 2.1, as published by the Free Software Foundation. # # This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., # 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. # # Sébastien Bianti # import dbus import json import xml.etree.ElementTree as ET ROOT_OBJECT_PATH = '/com/intel/dLeynaRenderer' RENDERER_BUS = 'com.intel.dleyna-renderer' PROPS_IF_NAME = 'org.freedesktop.DBus.Properties' INTROSPECTABLE_IF_NAME = 'org.freedesktop.DBus.Introspectable' DEVICE_IF_NAME = 'com.intel.dLeynaRenderer.RendererDevice' PUSH_HOST_IF_NAME = 'com.intel.dLeynaRenderer.PushHost' MANAGER_INTERFACE = 'com.intel.dLeynaRenderer.Manager' MEDIAPLAYER2_IF_NAME = 'org.mpris.MediaPlayer2' PLAYER_IF_NAME = 'org.mpris.MediaPlayer2.Player' global bus_type bus_type = dbus.SessionBus() def print_json(props): print json.dumps(props, indent=4, sort_keys=True) def get_interface(path, if_name): return dbus.Interface(bus_type.get_object(RENDERER_BUS, path), if_name) class Renderer(object): "Represent a renderer service" def __init__(self, object_path): self.__path = object_path self.__propsIF = get_interface(object_path, PROPS_IF_NAME) self.__playerIF = get_interface(object_path, PLAYER_IF_NAME) self.__pushhostIF = get_interface(object_path, PUSH_HOST_IF_NAME) self.__deviceIF = get_interface(object_path, DEVICE_IF_NAME) def get_interfaces(self): try: introspectable_IF = get_interface(self.__path, INTROSPECTABLE_IF_NAME) except: print(u"Failed to retrieve introspectable interface") introspection = introspectable_IF.Introspect() tree = ET.fromstring(introspection) return [i.attrib['name'] for i in tree if i.tag == "interface"] def interfaces(self): for i in self.get_interfaces(): print i def get_prop(self, prop_name, inner_if_name = ""): return self.__propsIF.Get(inner_if_name, prop_name) def get_props(self, inner_if_name = ""): return self.__propsIF.GetAll(inner_if_name) def print_props(self, inner_if_name = ""): print_json(self.get_props(inner_if_name)) def set_prop(self, prop_name, if_name, val): """ Sets only the following properties : Rate and Volume """ return self.__propsIF.Set(if_name, prop_name, val) # Control methods def play(self): self.__playerIF.Play() def pause(self): self.__playerIF.Pause() def play_pause(self): self.__playerIF.PlayPause() def next(self): self.__playerIF.Next() def open_uri(self, uri): self.__playerIF.OpenUri(uri) def open_uri_ex(self, uri, metadata): self.__playerIF.OpenUriEx(uri, metadata) def open_next_uri(self, uri, metadata): self.__playerIF.OpenNextUri(uri, metadata) def set_uri(self, uri, metadata): self.__playerIF.SetUri(uri, metadata) def previous(self): self.__playerIF.Previous() def seek(self, offset): self.__playerIF.Seek(offset) def byte_seek(self, offset): self.__playerIF.ByteSeek(offset) def goto_track(self, trackID): self.__playerIF.GotoTrack(trackID) def set_position(self, trackID, position): self.__playerIF.SetPosition(trackID, position) def set_byte_position(self, trackID, position): self.__playerIF.SetBytePosition(trackID, position) def stop(self): self.__playerIF.Stop() def print_icon(self, mime_type, resolution): bytes, mime = self.__deviceIF.GetIcon(mime_type, resolution) print "Icon mime type: " + mime # Push Host methods def host_file(self, path): return self.__pushhostIF.HostFile(path) def remove_file(self, path): self.__pushhostIF.RemoveFile(path) class Manager(object): """ High level class for detecting Renderers and doing common operations on dLeynaRenderer """ def __init__(self): self.__manager = get_interface(ROOT_OBJECT_PATH, MANAGER_INTERFACE) self.__renderers = [] self._propsIF = get_interface(ROOT_OBJECT_PATH, PROPS_IF_NAME) def update_renderers(self): self.__renderers = self.__manager.GetRenderers() def get_renderers(self): self.update_renderers() return self.__renderers def renderer_from_name(self, friendly_name): retval = None for i in self.__manager.GetRenderers(): renderer = Renderer(i) renderer_name = renderer.get_prop("FriendlyName").lower() if renderer_name.find(friendly_name.lower()) != -1: retval = renderer break return retval def renderer_from_udn(self, udn): retval = None for i in self.__manager.GetRenderers(): renderer = Renderer(i) if renderer.get_prop("UDN") == udn: retval = renderer break return retval def renderers(self): self.update_renderers() for path in self.__renderers: try: renderer = Renderer(path) renderer_name = renderer.get_prop("Identity") print(u"%s : %s" % (path, renderer_name)) except: print(u"Failed to retrieve Identity for interface %s" % path) def get_version(self): return self.__manager.GetVersion() def version(self): print self.get_version() def release(self): self.__manager.Release() def rescan(self): self.__manager.Rescan() def white_list_enable(self, enable): self.set_prop("WhiteListEnabled", enable) def white_list_add(self, entries): white_list = set(self.get_prop('WhiteListEntries')) white_list = (white_list | set(entries)) - set('') self.set_prop("WhiteListEntries", list(white_list)) def white_list_remove(self, entries): white_list = set(self.get_prop('WhiteListEntries')) white_list = white_list - set(entries) self.set_prop("WhiteListEntries", list(white_list)) def white_list_clear(self): self.set_prop("WhiteListEntries", ['']) def get_props(self, iface = ""): return self._propsIF.GetAll(iface) def get_prop(self, prop_name, iface = ""): return self._propsIF.Get(iface, prop_name) def set_prop(self, prop_name, val, iface = ""): return self._propsIF.Set(iface, prop_name, val) def print_prop(self, prop_name, iface = ""): print_json(self._propsIF.Get(iface, prop_name)) def print_props(self, iface = ""): print_json(self._propsIF.GetAll(iface)) if __name__ == "__main__": print("\n\t\t\tExample for using rendererconsole:") print("\t\t\t¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯\n") manager = Manager() print("Version = %s" % manager.get_version()) print("¯¯¯¯¯¯¯") print "\nRenderer's list:" print("¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯") manager.renderers() renderer_list = manager.get_renderers() for name in renderer_list: renderer = Renderer(name) interface_list = renderer.get_interfaces() print("\nInterfaces of %s:" % name) print("¯¯¯¯¯¯¯¯¯¯¯¯¯¯" + "¯" * len(name)) for i in interface_list: print i if_name = DEVICE_IF_NAME if (if_name in interface_list) : print("\nProperties of %s on %s:" % (if_name, name)) print("¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯" + (len(name) + len(if_name)) * "¯") renderer.print_props(if_name) renderer.print_icon("", "") dleyna-renderer-0.6.0/test/dbus/slidepush.py000077500000000000000000000340561305660374100211030ustar00rootroot00000000000000#!/usr/bin/python # slidepush # # Copyright (C) 2012-2017 Intel Corporation. All rights reserved. # # This program is free software; you can redistribute it and/or modify it # under the terms and conditions of the GNU Lesser General Public License, # version 2.1, as published by the Free Software Foundation. # # This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., # 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. # # Mark Ryan # # For slidepush to work you must have imagemagick and libreoffice installed # slidepush will not work if libre office is running so you must quit it first from gi.repository import Gtk from gi.repository import Gdk from gi.repository import GObject from gi.repository.GdkPixbuf import Pixbuf from gi.repository import GdkPixbuf import subprocess import tempfile import os import shutil import glob import dbus import dbus.service import dbus.mainloop.glib import shutil class Renderer: def __init__(self, path): bus = dbus.SessionBus() obj = bus.get_object('com.intel.dleyna-renderer', path) self.__propsIF = dbus.Interface(obj, 'org.freedesktop.DBus.Properties') self.__hostIF = dbus.Interface(obj, 'com.intel.dLeynaRenderer.PushHost') self.__playerIF = dbus.Interface(obj, 'org.mpris.MediaPlayer2.Player') def get_prop(self, prop_name, iface = ""): return self.__propsIF.Get(iface, prop_name) def push_file(self, fname): try: self.__hostIF.RemoveFile(fname) except: pass if self.get_prop("PlaybackStatus") == "Playing": self.__playerIF.Stop() uri = self.__hostIF.HostFile(fname) self.__playerIF.OpenUri(uri) class Renderers: def __init__(self, cb): bus=dbus.SessionBus() obj = bus.get_object('com.intel.dleyna-renderer', '/com/intel/dLeynaRenderer') self.__manager = dbus.Interface(obj, 'com.intel.dLeynaRenderer.Manager') self.__cb = cb self.__manager.connect_to_signal("LostRenderer", self.__servers_changed) self.__manager.connect_to_signal("FoundRenderer", self.__servers_changed) def __servers_changed(self, server): self.__cb() def get_renderers(self): retval = [] for path in self.__manager.GetRenderers(): retval.append((path, Renderer(path))) return retval class OpenProgress(object): CONVERT_INIT = 0 CONVERT_TO_PDF = 1 CONVERT_TO_PNG = 2 CONVERT_FINISHED = 2 def __pulse(self, userdata): retval = True try: if self.__state == OpenProgress.CONVERT_INIT: self.__state = OpenProgress.CONVERT_TO_PDF print self.__temp_dir self.__handle = subprocess.Popen( ['libreoffice', '--headless','--invisible', '--convert-to', 'pdf', '--outdir', self.__temp_dir, self.__filename]) else: result = 0 if self.__handle: result = self.__handle.poll() if self.__handle == None or result != None: self.__handle = None if result != 0: raise Exception("Error processing file") if self.__state == OpenProgress.CONVERT_TO_PDF: pdf_files = glob.glob(self.__temp_dir + "/*.pdf") if len(pdf_files) == 0: raise Exception("PDF File not created") self.__pdf_file = pdf_files[0] output_fname = self.__temp_dir + "/slide.jpg" self.__handle = subprocess.Popen(['convert', self.__pdf_file, output_fname]) self.__state = OpenProgress.CONVERT_TO_PNG self.__text.set_text( "Converting to JPG files") else: os.remove(self.__pdf_file) self.__dialog.destroy() retval = False self.__state = OpenProgress.CONVERT_FINISHED self.__progress_bar.pulse() except Exception, e: print e self.__text.set_text("Unable to process " + self.__filename) self.__button.set_label("Ok") self.activity_mode = False self.__progress_bar.set_pulse_step(1.0) retval = False return retval def __close(self, button): self.__dialog.destroy() def __init__(self, window, filename, temp_dir): self.__temp_dir = temp_dir self.__filename = filename dialog = Gtk.Dialog("Opening " + filename, window, Gtk.DialogFlags.MODAL) dialog.set_default_size(320, 100) progress_bar = Gtk.ProgressBar() button = Gtk.Button("Cancel") button.connect("clicked", self.__close) if filename.lower().endswith(".pdf"): self.__state = OpenProgress.CONVERT_TO_PDF shutil.copy(self.__filename, self.__temp_dir) else: self.__state = OpenProgress.CONVERT_INIT self.__handle = None text = Gtk.Label("Converting to PDF") vbox = dialog.get_content_area() vbox.pack_start(text, True, True, 0) vbox.pack_start(progress_bar, False, False, 8) vbox.pack_start(button, False, False, 0) self.__timeout_id = GObject.timeout_add(100, self.__pulse, None) self.__progress_bar = progress_bar self.__button = button self.__text = text dialog.show_all() self.__dialog = dialog def run(self): retval = True self.__dialog.run() if self.__state != OpenProgress.CONVERT_FINISHED: shutil.rmtree(self.__temp_dir) retval = False elif self.__handle: self.__handle.kill() return retval class MainWindow(object): @staticmethod def key_from_path(key): retval = 0 start = key.find("/slide-") if start != -1: end = key.rfind(".jpg") if end != -1: key = key[start + len("/slide-"):end] retval = int(key) return retval def __update_ui(self): model = Gtk.ListStore(Pixbuf, str, str) slides = [x for x in glob.glob(self.__temp_dir + '/*.jpg')] slides.sort(key=MainWindow.key_from_path) i = 1 for slide in slides: image = Gtk.Image() try: image.set_from_file(slide) pixbuf = image.get_pixbuf() pixbuf = pixbuf.scale_simple(96, 96, GdkPixbuf.InterpType.BILINEAR) model.append([pixbuf, "Slide " + str(i), slide]) i = i + 1 except Exception: pass self.__slide_list.set_model(model) self.__slide_list.set_pixbuf_column(0) self.__slide_list.set_text_column(1) if i > 0: first_slide = Gtk.TreePath(0) self.__slide_list.select_path(first_slide) self.__slide_list.set_cursor(first_slide, None, False) self.__slide_list.grab_focus() def __open_file(self, button): dialog = Gtk.FileChooserDialog("Select Presentation", self.__window, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) file_filter = Gtk.FileFilter() file_filter.set_name("Presentations") file_filter.add_pattern("*.odp") file_filter.add_pattern("*.pdf") file_filter.add_pattern("*.ppt") file_filter.add_pattern("*.ppx") dialog.add_filter(file_filter) response = dialog.run() if response == Gtk.ResponseType.OK: temp_dir = tempfile.mkdtemp() filename = dialog.get_filename() dialog.destroy() conv = OpenProgress(self.__window, filename, temp_dir) if conv.run(): if self.__temp_dir: shutil.rmtree(self.__temp_dir) self.__temp_dir = temp_dir self.__update_ui() else: dialog.destroy() def __prepare_image(self, image): x = 0 y = 0 rect = self.__slide_canvas.get_allocation() width_scale = image.get_width() / float(rect.width) height_scale = image.get_height() / float(rect.height) if ((width_scale < 1.0 and height_scale < 1.0) or (width_scale >= 1.0 and height_scale >= 1.0)): if width_scale < height_scale: divisor = height_scale x = (rect.width - int(image.get_width() / divisor)) / 2 else: divisor = width_scale y = (rect.height - int(image.get_height() / divisor)) / 2 elif width_scale > 1.0: divisor = width_scale y = (rect.height - int(image.get_height() / divisor)) / 2 else: divisor = height_scale x = (rect.width - int(image.get_width() / divisor)) / 2 self.__scaled_pixmap = image.scale_simple( int(image.get_width() / divisor), int(image.get_height() / divisor), GdkPixbuf.InterpType.BILINEAR) self.__scaled_x = x self.__scaled_y = y def __configured_cb(self, widget, event): self.__slide_clicked(self.__slide_list) def __slide_clicked(self, obj): model = obj.get_model() selected = obj.get_selected_items() if len(selected) > 0: tree_iter = model.get_iter(selected[0]) image = Gtk.Image() try: image.set_from_file(model[tree_iter][2]) pixbuf = image.get_pixbuf() self.__prepare_image(pixbuf) self.__slide_canvas.queue_draw() except Exception: pass def __slide_activated(self, obj, path): slide_model = obj.get_model() slide_iter = slide_model.get_iter(path) renderer_model = self.__combo.get_model() ren_iter = self.__combo.get_active_iter() if ren_iter and slide_iter: ren = Renderer(renderer_model[ren_iter][0]) ren.push_file(slide_model[slide_iter][2]) def __draw_cb(self, obj, ctx): if self.__scaled_pixmap: Gdk.cairo_set_source_pixbuf(ctx, self.__scaled_pixmap, self.__scaled_x, self.__scaled_y) ctx.paint() def __create_renderers_store(self): servers_store = Gtk.ListStore(str, str) for server in self.__Renderers.get_renderers(): servers_store.append([server[0], server[1].get_prop("Identity")]) return servers_store def __reset_renderers(self): print "Renderers Changed" entry = self.__combo.get_child() servers_store = self.__create_renderers_store() self.__combo.set_model(servers_store) if len(servers_store) > 0: self.__combo.set_active(0) else: entry.set_text("") def __init__(self): self.__Renderers = Renderers(self.__reset_renderers) window = Gtk.Window() window.connect("delete-event", Gtk.main_quit) window.set_title("Slide Push!") window.set_resizable(True) window.set_default_size(640, 480) container = Gtk.VBox(False, 0) slide_container = Gtk.HBox(False, 0) slide_list = Gtk.IconView() slide_list.set_pixbuf_column(0) slide_list.set_text_column(1) slide_list.set_columns(1) slide_canvas = Gtk.DrawingArea() slide_scroll = Gtk.ScrolledWindow() slide_scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); slide_scroll.add(slide_list) slide_container.pack_start(slide_scroll, False, False, 4); slide_container.pack_start(slide_canvas, True, True, 4); button_bar = Gtk.HBox(False, 0) open_button = Gtk.Button("Open File ...") open_button.connect("clicked", self.__open_file) servers_store = self.__create_renderers_store() combo = Gtk.ComboBox.new_with_model_and_entry(servers_store) combo.set_entry_text_column(1) if len(servers_store) > 0: combo.set_active(0) combo.get_child().set_property("editable", False) button_bar.pack_start(open_button, True, True, 4) button_bar.pack_start(combo, False, True, 4) container.pack_start(slide_container, True, True, 4); container.pack_start(button_bar, False, False, 4); window.add(container) window.show_all() self.__window = window self.__temp_dir = None self.__scaled_pixmap = None self.__slide_list = slide_list self.__slide_canvas = slide_canvas self.__slide_scroll = slide_scroll self.__combo = combo slide_canvas.connect("draw", self.__draw_cb) slide_list.connect("selection-changed", self.__slide_clicked) slide_list.connect("item-activated", self.__slide_activated) slide_canvas.connect("configure-event", self.__configured_cb) window.show_all() def start(self): Gtk.main() if __name__ == '__main__': dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) win = MainWindow() win.start() dleyna-renderer-0.6.0/test/dbus/stress-test.py000077500000000000000000000036631305660374100214030ustar00rootroot00000000000000# stress-test # # Copyright (C) 2012-2017 Intel Corporation. All rights reserved. # # This program is free software; you can redistribute it and/or modify it # under the terms and conditions of the GNU Lesser General Public License, # version 2.1, as published by the Free Software Foundation. # # This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc., # 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. # # Regis Merlino # import sys import gobject import dbus import dbus.mainloop.glib def handle_reply(objects): print "Total Items: " + str(len(objects)) def handle_error(err): if err.get_dbus_name() == 'com.intel.dleyna.Cancelled': print "Cancelled..." else: print "An error occured" print err loop.quit() def make_async_calls(): i = 0 while i < 5: root.GetAll("", reply_handler=handle_reply, error_handler=handle_error) i += 1 device.Cancel() gobject.timeout_add(1000, make_async_calls) dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() root = dbus.Interface(bus.get_object( 'com.intel.dleyna-renderer', '/com/intel/dLeynaRenderer/server/0'), 'org.freedesktop.DBus.Properties') device = dbus.Interface(bus.get_object( 'com.intel.dleyna-renderer', '/com/intel/dLeynaRenderer/server/0'), 'com.intel.dLeynaRenderer.RendererDevice') gobject.timeout_add(1000, make_async_calls) loop = gobject.MainLoop() loop.run()