pax_global_header00006660000000000000000000000064134571353560014526gustar00rootroot0000000000000052 comment=1b253fb302504d6ddcad7f705db49e49a8e6e901 osdlyrics-0.5.5~rc1+dfsg1/000077500000000000000000000000001345713535600153545ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/.gitignore000066400000000000000000000004621345713535600173460ustar00rootroot00000000000000*.o *.gmo *.lo src/tests/*test Makefile Makefile.in *.a src/osdlyrics config.h config.h.in configure config.guess config.sub py-compile install-sh configure.scan po/POTFILES missing .deps aclocal.m4 stamp-h1 compile config.log depcomp po/stamp-it autom4te.cache config.status cscope.out *.pyc *~ *.service osdlyrics-0.5.5~rc1+dfsg1/LICENSE000066400000000000000000001045151345713535600163670ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, 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 them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If 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 convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU 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 Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state 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 program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . osdlyrics-0.5.5~rc1+dfsg1/Makefile.am000066400000000000000000000001621345713535600174070ustar00rootroot00000000000000SUBDIRS = src data po players daemon python lyricsources tools ACLOCAL_AMFLAGS = -I m4 EXTRA_DIST = m4/ChangeLog osdlyrics-0.5.5~rc1+dfsg1/README.md000066400000000000000000000064621345713535600166430ustar00rootroot00000000000000# OSD Lyrics Show synced lyrics with your favorite media player on Linux. ## Introduction OSD Lyrics is a desktop application to view lyrics compatible with various media players. It is not a plugin but a standalone program. OSD Lyrics shows lyrics on your desktop, in the style similar to KaraOK. It also provides another displaying style, in which lyrics scroll from bottom to top. OSD Lyrics can download lyrics from the network automatically. ![Screenshot in OSD mode](screenshot.png) ## License OSD Lyrics is released under GPL v3. See [LICENSE](LICENSE) for more detail. ## How to use To use OSD Lyrics, just launch your media player, then launch OSD Lyrics. OSD Lyrics will detect and connect to the media player. ## How to install OSD Lyrics is available for Ubuntu in [our PPA](https://launchpad.net/~osd-lyrics/+archive/ubuntu/ppa) and for ArchLinux through [community repo](https://www.archlinux.org/packages/community/x86_64/osdlyrics) and through [AUR](https://aur.archlinux.org/packages/osdlyrics-git). ## Build instructions ### Requirements On Ubuntu, the following packages need to be installed to compile OSD Lyrics: - Python 2.6+/3.4+ - `libdbus-glib-1-dev` - `intltool` - `libappindicator-dev` (optional) ### Runtime Dependencies - Python (either one): - Python 2: python (>= 2.6), python-future, python-gi (>= 3.11.1) - Python 3: python3 (>= 3.4), python3-future, python3-gi (>= 3.11.1) - Supported players: - MPRIS2 - MPRIS - [MPD](https://www.musicpd.org): mpd (>= 0.16.0), python-mpd ## Cloning repository You can use git to clone our repository with the latest changes, use the following command: ``` git clone https://github.com/osdlyrics/osdlyrics.git ``` ## Building After cloning the repository and installing dependencies, issue the following commands at the cloned directory to build and install: ``` ./autogen.sh ./configure --prefix=/usr PYTHON=/usr/bin/python2 make sudo make install ``` You can read more at [Building wiki page](https://github.com/osdlyrics/osdlyrics/wiki/Building). ## Troubleshooting Check [Troubleshooting wiki page](https://github.com/osdlyrics/osdlyrics/wiki/Troubleshooting) for more detailed help. ## Contact us The official source repository is on Github: https://github.com/osdlyrics/osdlyrics If there is any feature request, suggestion, or bug, feel free to report them in [issues page](https://github.com/osdlyrics/osdlyrics/issues). You can contact the developers for fast support through [our discord server](https://discord.gg/anUy3K). ## How to contribute We adopt [GitHub flow](https://guides.github.com/introduction/flow/index.html) for development, this means you can use GitHub issues, projects and pull requests pages to respectively submit bugs/suggestions, take a look at current roadmap/Kanban and submit changes. You can read more at [Contributing wiki page](https://github.com/osdlyrics/osdlyrics/wiki/Contributing). ## Acknowledgements Thanks to all the people who have directly or indirectly helped in the development of this project. Special thanks to the original author "[Tiger Soldier](mailto:tigersoldi@gmail.com)", first contributors "[Sarlmol Apple](mailto:sarlmolapple@gmail.com)", "[Simply Zhao](mailto:simplyzhao@gmail.com)" and everybody else listed at [contributors page](https://github.com/osdlyrics/osdlyrics/graphs/contributors). osdlyrics-0.5.5~rc1+dfsg1/autogen.sh000077500000000000000000000002741345713535600173600ustar00rootroot00000000000000#!/usr/bin/env bash set -e DIR=`dirname $0` args=$@ if [ -e /usr/bin/python2 ]; then args="$args PYTHON=/usr/bin/python2" fi autoreconf --install --force $DIR $DIR/configure $args osdlyrics-0.5.5~rc1+dfsg1/config.rpath000066400000000000000000000436471345713535600176770ustar00rootroot00000000000000#! /bin/sh # Output a system dependent set of variables, describing how to set the # run time search path of shared libraries in an executable. # # Copyright 1996-2007 Free Software Foundation, Inc. # Taken from GNU libtool, 2001 # Originally by Gordon Matzigkeit , 1996 # # This file is free software; the Free Software Foundation gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. # # The first argument passed to this file is the canonical host specification, # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM # or # CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM # The environment variables CC, GCC, LDFLAGS, LD, with_gnu_ld # should be set by the caller. # # The set of defined variables is at the end of this script. # Known limitations: # - On IRIX 6.5 with CC="cc", the run time search patch must not be longer # than 256 bytes, otherwise the compiler driver will dump core. The only # known workaround is to choose shorter directory names for the build # directory and/or the installation directory. # All known linkers require a `.a' archive for static linking (except MSVC, # which needs '.lib'). libext=a shrext=.so host="$1" host_cpu=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` host_vendor=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` host_os=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` # Code taken from libtool.m4's _LT_CC_BASENAME. for cc_temp in $CC""; do case $cc_temp in compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; \-*) ;; *) break;; esac done cc_basename=`echo "$cc_temp" | sed -e 's%^.*/%%'` # Code taken from libtool.m4's AC_LIBTOOL_PROG_COMPILER_PIC. wl= if test "$GCC" = yes; then wl='-Wl,' else case "$host_os" in aix*) wl='-Wl,' ;; darwin*) case $cc_basename in xlc*) wl='-Wl,' ;; esac ;; mingw* | cygwin* | pw32* | os2*) ;; hpux9* | hpux10* | hpux11*) wl='-Wl,' ;; irix5* | irix6* | nonstopux*) wl='-Wl,' ;; newsos6) ;; linux* | k*bsd*-gnu) case $cc_basename in icc* | ecc*) wl='-Wl,' ;; pgcc | pgf77 | pgf90) wl='-Wl,' ;; ccc*) wl='-Wl,' ;; como) wl='-lopt=' ;; *) case `$CC -V 2>&1 | sed 5q` in *Sun\ C*) wl='-Wl,' ;; esac ;; esac ;; osf3* | osf4* | osf5*) wl='-Wl,' ;; rdos*) ;; solaris*) wl='-Wl,' ;; sunos4*) wl='-Qoption ld ' ;; sysv4 | sysv4.2uw2* | sysv4.3*) wl='-Wl,' ;; sysv4*MP*) ;; sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) wl='-Wl,' ;; unicos*) wl='-Wl,' ;; uts4*) ;; esac fi # Code taken from libtool.m4's AC_LIBTOOL_PROG_LD_SHLIBS. hardcode_libdir_flag_spec= hardcode_libdir_separator= hardcode_direct=no hardcode_minus_L=no case "$host_os" in cygwin* | mingw* | pw32*) # FIXME: the MSVC++ port hasn't been tested in a loooong time # When not using gcc, we currently assume that we are using # Microsoft Visual C++. if test "$GCC" != yes; then with_gnu_ld=no fi ;; interix*) # we just hope/assume this is gcc and not c89 (= MSVC++) with_gnu_ld=yes ;; openbsd*) with_gnu_ld=no ;; esac ld_shlibs=yes if test "$with_gnu_ld" = yes; then # Set some defaults for GNU ld with shared library support. These # are reset later if shared libraries are not supported. Putting them # here allows them to be overridden if necessary. # Unlike libtool, we use -rpath here, not --rpath, since the documented # option of GNU ld is called -rpath, not --rpath. hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' case "$host_os" in aix3* | aix4* | aix5*) # On AIX/PPC, the GNU linker is very broken if test "$host_cpu" != ia64; then ld_shlibs=no fi ;; amigaos*) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes # Samuel A. Falvo II reports # that the semantics of dynamic libraries on AmigaOS, at least up # to version 4, is to share data among multiple programs linked # with the same dynamic library. Since this doesn't match the # behavior of shared libraries on other platforms, we cannot use # them. ld_shlibs=no ;; beos*) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; cygwin* | mingw* | pw32*) # hardcode_libdir_flag_spec is actually meaningless, as there is # no search path for DLLs. hardcode_libdir_flag_spec='-L$libdir' if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then : else ld_shlibs=no fi ;; interix[3-9]*) hardcode_direct=no hardcode_libdir_flag_spec='${wl}-rpath,$libdir' ;; gnu* | linux* | k*bsd*-gnu) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; netbsd*) ;; solaris*) if $LD -v 2>&1 | grep 'BFD 2\.8' > /dev/null; then ld_shlibs=no elif $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) case `$LD -v 2>&1` in *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*) ld_shlibs=no ;; *) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-rpath,$libdir`' else ld_shlibs=no fi ;; esac ;; sunos4*) hardcode_direct=yes ;; *) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; esac if test "$ld_shlibs" = no; then hardcode_libdir_flag_spec= fi else case "$host_os" in aix3*) # Note: this linker hardcodes the directories in LIBPATH if there # are no directories specified by -L. hardcode_minus_L=yes if test "$GCC" = yes; then # Neither direct hardcoding nor static linking is supported with a # broken collect2. hardcode_direct=unsupported fi ;; aix4* | aix5*) if test "$host_cpu" = ia64; then # On IA64, the linker does run time linking by default, so we don't # have to do anything special. aix_use_runtimelinking=no else aix_use_runtimelinking=no # Test if we are trying to use run time linking or normal # AIX style linking. If -brtl is somewhere in LDFLAGS, we # need to do runtime linking. case $host_os in aix4.[23]|aix4.[23].*|aix5*) for ld_flag in $LDFLAGS; do if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then aix_use_runtimelinking=yes break fi done ;; esac fi hardcode_direct=yes hardcode_libdir_separator=':' if test "$GCC" = yes; then case $host_os in aix4.[012]|aix4.[012].*) collect2name=`${CC} -print-prog-name=collect2` if test -f "$collect2name" && \ strings "$collect2name" | grep resolve_lib_name >/dev/null then # We have reworked collect2 : else # We have old collect2 hardcode_direct=unsupported hardcode_minus_L=yes hardcode_libdir_flag_spec='-L$libdir' hardcode_libdir_separator= fi ;; esac fi # Begin _LT_AC_SYS_LIBPATH_AIX. echo 'int main () { return 0; }' > conftest.c ${CC} ${LDFLAGS} conftest.c -o conftest aix_libpath=`dump -H conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } }'` if test -z "$aix_libpath"; then aix_libpath=`dump -HX64 conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } }'` fi if test -z "$aix_libpath"; then aix_libpath="/usr/lib:/lib" fi rm -f conftest.c conftest # End _LT_AC_SYS_LIBPATH_AIX. if test "$aix_use_runtimelinking" = yes; then hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" else if test "$host_cpu" = ia64; then hardcode_libdir_flag_spec='${wl}-R $libdir:/usr/lib:/lib' else hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" fi fi ;; amigaos*) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes # see comment about different semantics on the GNU ld section ld_shlibs=no ;; bsdi[45]*) ;; cygwin* | mingw* | pw32*) # When not using gcc, we currently assume that we are using # Microsoft Visual C++. # hardcode_libdir_flag_spec is actually meaningless, as there is # no search path for DLLs. hardcode_libdir_flag_spec=' ' libext=lib ;; darwin* | rhapsody*) hardcode_direct=no if test "$GCC" = yes ; then : else case $cc_basename in xlc*) ;; *) ld_shlibs=no ;; esac fi ;; dgux*) hardcode_libdir_flag_spec='-L$libdir' ;; freebsd1*) ld_shlibs=no ;; freebsd2.2*) hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes ;; freebsd2*) hardcode_direct=yes hardcode_minus_L=yes ;; freebsd* | dragonfly*) hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes ;; hpux9*) hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' hardcode_libdir_separator=: hardcode_direct=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes ;; hpux10*) if test "$with_gnu_ld" = no; then hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' hardcode_libdir_separator=: hardcode_direct=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes fi ;; hpux11*) if test "$with_gnu_ld" = no; then hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' hardcode_libdir_separator=: case $host_cpu in hppa*64*|ia64*) hardcode_direct=no ;; *) hardcode_direct=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes ;; esac fi ;; irix5* | irix6* | nonstopux*) hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' hardcode_libdir_separator=: ;; netbsd*) hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes ;; newsos6) hardcode_direct=yes hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' hardcode_libdir_separator=: ;; openbsd*) if test -f /usr/libexec/ld.so; then hardcode_direct=yes if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then hardcode_libdir_flag_spec='${wl}-rpath,$libdir' else case "$host_os" in openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*) hardcode_libdir_flag_spec='-R$libdir' ;; *) hardcode_libdir_flag_spec='${wl}-rpath,$libdir' ;; esac fi else ld_shlibs=no fi ;; os2*) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes ;; osf3*) hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' hardcode_libdir_separator=: ;; osf4* | osf5*) if test "$GCC" = yes; then hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' else # Both cc and cxx compiler support -rpath directly hardcode_libdir_flag_spec='-rpath $libdir' fi hardcode_libdir_separator=: ;; solaris*) hardcode_libdir_flag_spec='-R$libdir' ;; sunos4*) hardcode_libdir_flag_spec='-L$libdir' hardcode_direct=yes hardcode_minus_L=yes ;; sysv4) case $host_vendor in sni) hardcode_direct=yes # is this really true??? ;; siemens) hardcode_direct=no ;; motorola) hardcode_direct=no #Motorola manual says yes, but my tests say they lie ;; esac ;; sysv4.3*) ;; sysv4*MP*) if test -d /usr/nec; then ld_shlibs=yes fi ;; sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) ;; sysv5* | sco3.2v5* | sco5v6*) hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-R,$libdir`' hardcode_libdir_separator=':' ;; uts4*) hardcode_libdir_flag_spec='-L$libdir' ;; *) ld_shlibs=no ;; esac fi # Check dynamic linker characteristics # Code taken from libtool.m4's AC_LIBTOOL_SYS_DYNAMIC_LINKER. # Unlike libtool.m4, here we don't care about _all_ names of the library, but # only about the one the linker finds when passed -lNAME. This is the last # element of library_names_spec in libtool.m4, or possibly two of them if the # linker has special search rules. library_names_spec= # the last element of library_names_spec in libtool.m4 libname_spec='lib$name' case "$host_os" in aix3*) library_names_spec='$libname.a' ;; aix4* | aix5*) library_names_spec='$libname$shrext' ;; amigaos*) library_names_spec='$libname.a' ;; beos*) library_names_spec='$libname$shrext' ;; bsdi[45]*) library_names_spec='$libname$shrext' ;; cygwin* | mingw* | pw32*) shrext=.dll library_names_spec='$libname.dll.a $libname.lib' ;; darwin* | rhapsody*) shrext=.dylib library_names_spec='$libname$shrext' ;; dgux*) library_names_spec='$libname$shrext' ;; freebsd1*) ;; freebsd* | dragonfly*) case "$host_os" in freebsd[123]*) library_names_spec='$libname$shrext$versuffix' ;; *) library_names_spec='$libname$shrext' ;; esac ;; gnu*) library_names_spec='$libname$shrext' ;; hpux9* | hpux10* | hpux11*) case $host_cpu in ia64*) shrext=.so ;; hppa*64*) shrext=.sl ;; *) shrext=.sl ;; esac library_names_spec='$libname$shrext' ;; interix[3-9]*) library_names_spec='$libname$shrext' ;; irix5* | irix6* | nonstopux*) library_names_spec='$libname$shrext' case "$host_os" in irix5* | nonstopux*) libsuff= shlibsuff= ;; *) case $LD in *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") libsuff= shlibsuff= ;; *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") libsuff=32 shlibsuff=N32 ;; *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") libsuff=64 shlibsuff=64 ;; *) libsuff= shlibsuff= ;; esac ;; esac ;; linux*oldld* | linux*aout* | linux*coff*) ;; linux* | k*bsd*-gnu) library_names_spec='$libname$shrext' ;; knetbsd*-gnu) library_names_spec='$libname$shrext' ;; netbsd*) library_names_spec='$libname$shrext' ;; newsos6) library_names_spec='$libname$shrext' ;; nto-qnx*) library_names_spec='$libname$shrext' ;; openbsd*) library_names_spec='$libname$shrext$versuffix' ;; os2*) libname_spec='$name' shrext=.dll library_names_spec='$libname.a' ;; osf3* | osf4* | osf5*) library_names_spec='$libname$shrext' ;; rdos*) ;; solaris*) library_names_spec='$libname$shrext' ;; sunos4*) library_names_spec='$libname$shrext$versuffix' ;; sysv4 | sysv4.3*) library_names_spec='$libname$shrext' ;; sysv4*MP*) library_names_spec='$libname$shrext' ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) library_names_spec='$libname$shrext' ;; uts4*) library_names_spec='$libname$shrext' ;; esac sed_quote_subst='s/\(["`$\\]\)/\\\1/g' escaped_wl=`echo "X$wl" | sed -e 's/^X//' -e "$sed_quote_subst"` shlibext=`echo "$shrext" | sed -e 's,^\.,,'` escaped_libname_spec=`echo "X$libname_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` escaped_library_names_spec=`echo "X$library_names_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` escaped_hardcode_libdir_flag_spec=`echo "X$hardcode_libdir_flag_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` LC_ALL=C sed -e 's/^\([a-zA-Z0-9_]*\)=/acl_cv_\1=/' </dev/null || date -u -r ChangeLog +%Y%m%d) | sed 's/-//g'; fi])) m4_define([OL_VERSION], [OL_MAJOR_VER.OL_MINOR_VER.OL_MICRO_VER[]OL_BUILD[]OL_BUILD_VER]) AC_INIT([osdlyrics], [OL_VERSION], [https://github.com/osdlyrics/osdlyrics/issues]) AM_INIT_AUTOMAKE([foreign subdir-objects -Wall -Werror]) m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) AC_CONFIG_SRCDIR([src/ol_main.c]) AC_CONFIG_HEADERS([config.h]) AC_DEFINE([PROGRAM_NAME], ["OSD Lyrics"], [The full name of the program to display]) AC_SUBST([PROGRAM_NAME], ["OSD Lyrics"]) # Checks for header files. AC_PATH_X AC_CHECK_HEADERS([fcntl.h libintl.h limits.h locale.h stddef.h stdlib.h string.h sys/time.h unistd.h]) # Checks for programs. AC_PROG_CC AC_PROG_CXX AC_PROG_MAKE_SET AM_PROG_CC_C_O AC_PROG_RANLIB AM_PATH_PYTHON([2.6]) # Checks for typedefs, structures, and compiler characteristics. AC_HEADER_STDBOOL AC_C_INLINE AC_TYPE_INT32_T AC_TYPE_INT64_T AC_TYPE_PID_T AC_TYPE_SIZE_T AC_TYPE_SSIZE_T # Checks for library functions. # AM_GNU_GETTEXT AC_FUNC_FORK AC_FUNC_MALLOC AC_FUNC_REALLOC AC_CHECK_FUNCS([ftruncate gettimeofday memset pathconf sqrt strcasecmp strchr strdup strerror strrchr strstr]) AM_ICONV #define paths OL_ICONDIR='${datadir}/${PACKAGE}/icons' AC_SUBST(OL_ICONDIR) OL_GUIDIR='${datadir}/${PACKAGE}' AC_SUBST(OL_GUIDIR) # gprof support AC_ARG_WITH(gprof-support, [ --with-gprof-support=DIR Include gmon.out file renaming support --without-gprof-support Don't do gmon.out file renaming (default)], [ case "$withval" in yes) AC_MSG_RESULT(yes) AC_DEFINE([WITH_GPROF_SUPPORT], [], [Support gprof profiling]) CFLAGS="-pg $CFLAGS" LDFLAGS="-pg $LDFLAGS" AC_MSG_WARN(--with-gprof-support called without argument - using default) gmondir="/tmp" AC_SUBST(gmondir) ;; no) AC_MSG_RESULT(no) ;; *) AC_MSG_RESULT(yes) AC_DEFINE([WITH_GPROF_SUPPORT], [], [Support gprof profiling]) CFLAGS="-pg $CFLAGS" LDFLAGS="-pg $LDFLAGS" gmondir="$withval" AC_SUBST(gmondir) ;; esac ]) # dev restrict support AC_ARG_WITH(werror, [ --with-werror Build with -Wall -Werror], [ case "$withval" in yes) AC_MSG_RESULT(yes) CFLAGS="-Wall -Werror $CFLAGS" ;; *) AC_MSG_RESULT(no) ;; esac ]) dnl app indicators in Ubuntu APPINDICATOR_REQUIRED=0.0.7 AC_ARG_ENABLE(appindicator, AS_HELP_STRING([--enable-appindicator[=@<:@no/auto/yes@:>@]],[Build support for application indicators ]), [enable_appindicator=$enableval], [enable_appindicator="auto"]) # Checks for libraries. AC_CHECK_LIB([dl], [exp]) PKG_CHECK_MODULES([GTK2], [gtk+-2.0 >= 2.20.0, gthread-2.0, gmodule-export-2.0, gio-2.0 >= 2.26, gio-unix-2.0]) PKG_CHECK_MODULES([X11], [x11]) dnl For XGrabKey PKG_CHECK_MODULES([DBUS_GLIB], [dbus-glib-1]) PKG_CHECK_MODULES([NOTIFY], [libnotify]) PKG_CHECK_EXISTS([libnotify >= 0.7], AC_DEFINE(HAVE_LIBNOTIFY_0_7, 1, [Have libnotify >= 0.7])) dnl AppIndicator support for Ubuntu if test x$enable_appindicator = xauto ; then PKG_CHECK_EXISTS([appindicator-0.1 >= $APPINDICATOR_REQUIRED], enable_appindicator="yes", enable_appindicator="no") fi if test x$enable_appindicator = xyes ; then PKG_CHECK_EXISTS([appindicator-0.1 >= $APPINDICATOR_REQUIRED],, AC_MSG_ERROR([appindicator-0.1 is not installed])) PKG_CHECK_MODULES(APP_INDICATOR, appindicator-0.1 >= $APPINDICATOR_REQUIRED) AC_SUBST(APP_INDICATOR_CFLAGS) AC_SUBST(APP_INDICATOR_LIBS) AC_DEFINE(HAVE_APP_INDICATOR, 1, [Have AppIndicator]) fi AM_CONDITIONAL(HAVE_APP_INDICATOR, test x"$enable_appindicator" = xyes) # i18n support from GNU gettext. GETTEXT_PACKAGE=osdlyrics AC_SUBST(GETTEXT_PACKAGE) AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,"$GETTEXT_PACKAGE", [Gettext package]) IT_PROG_INTLTOOL([0.35.0]) AM_GLIB_GNU_GETTEXT # Checks for header files. AC_CHECK_HEADERS([stdlib.h string.h]) # Checks for typedefs, structures, and compiler characteristics. # Checks for library functions. AC_CONFIG_FILES([ po/Makefile.in Makefile src/Makefile src/tests/Makefile data/Makefile daemon/Makefile python/__init__.py players/Makefile players/mpris1/Makefile players/mpris2/Makefile players/http/Makefile players/mpd/Makefile python/Makefile python/dbusext/Makefile lyricsources/Makefile lyricsources/xiami/Makefile lyricsources/lrc123/Makefile lyricsources/viewlyrics/Makefile lyricsources/netease/Makefile tools/Makefile ]) AC_OUTPUT AC_MSG_NOTICE([OSD Lyrics compilation options:]) AC_MSG_NOTICE([ * Version: $PACKAGE_VERSION]) AC_MSG_NOTICE([ * Prefix: $prefix]) if test "x$enable_appindicator" = "xyes"; then AC_MSG_NOTICE([ * AppIndicator: yes]) else AC_MSG_NOTICE([ * AppIndicator: no]) fi osdlyrics-0.5.5~rc1+dfsg1/daemon/000077500000000000000000000000001345713535600166175ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/daemon/Makefile.am000066400000000000000000000002211345713535600206460ustar00rootroot00000000000000daemon_PYTHON = \ ini_config.py \ main.py \ lrcdb.py \ lyrics.py \ player.py \ lyricsource.py \ $(NULL) daemondir = $(pkglibdir)/daemon osdlyrics-0.5.5~rc1+dfsg1/daemon/ini_config.py000066400000000000000000000221721345713535600213010ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from future import standard_library standard_library.install_aliases() from builtins import str, super import configparser import dbus import dbus.service from gi.repository import GLib from osdlyrics.app import App from osdlyrics.consts import CONFIG_BUS_NAME, CONFIG_OBJECT_PATH import osdlyrics.errors import osdlyrics.utils class MalformedKeyError(osdlyrics.errors.BaseError): pass class ValueNotExistError(osdlyrics.errors.BaseError): def __init__(self, key=''): super().__init__('Value of key %s does not exist' % key) class IniConfig(dbus.service.Object): """ Implement org.osdlyrics.Config """ def __init__(self, conn, filename=osdlyrics.utils.get_config_path('osdlyrics.conf')): super().__init__(conn=conn, object_path=CONFIG_OBJECT_PATH) self._conn = conn self._confparser = configparser.RawConfigParser() osdlyrics.utils.ensure_path(filename) self._confparser.read(filename) self._filename = filename self._save_timer = None self._signal_timer = None self._changed_signals = {} def _split_key(self, key, add_section=True): parts = key.split('/') if len(parts) != 2: raise MalformedKeyError( '%s is an invalid key. Keys must be in the form ' 'of "Section/Name"' % key ) if not parts[0] or not parts[1]: raise MalformedKeyError( 'Malformed key "%s". Section or name must not be empty' % key) if add_section and not self._confparser.has_section(parts[0]): self._confparser.add_section(parts[0]) return parts[0], parts[1] @dbus.service.method(dbus_interface=CONFIG_BUS_NAME, in_signature='s', out_signature='b') def GetBool(self, key): section, name = self._split_key(key) try: return self._confparser.getboolean(section, name) except (configparser.NoSectionError, configparser.NoOptionError): raise ValueNotExistError(key) @dbus.service.method(dbus_interface=CONFIG_BUS_NAME, in_signature='s', out_signature='i') def GetInt(self, key): section, name = self._split_key(key) try: return self._confparser.getint(section, name) except (configparser.NoSectionError, configparser.NoOptionError): raise ValueNotExistError(key) @dbus.service.method(dbus_interface=CONFIG_BUS_NAME, in_signature='s', out_signature='d') def GetDouble(self, key): section, name = self._split_key(key) try: return self._confparser.getfloat(section, name) except (configparser.NoSectionError, configparser.NoOptionError): raise ValueNotExistError(key) @dbus.service.method(dbus_interface=CONFIG_BUS_NAME, in_signature='s', out_signature='s') def GetString(self, key): section, name = self._split_key(key) try: return self._confparser.get(section, name) except (configparser.NoSectionError, configparser.NoOptionError): raise ValueNotExistError(key) @dbus.service.method(dbus_interface=CONFIG_BUS_NAME, in_signature='s', out_signature='as') def GetStringList(self, key): value = self.GetString(key) try: return split(value) except (configparser.NoSectionError, configparser.NoOptionError): raise ValueNotExistError(key) def _set_value(self, key, value, overwrite=True): section, name = self._split_key(key, True) if overwrite or not self._confparser.has_option(section, name): self._confparser.set(section, name, str(value)) self._changed_signals[key] = True self._schedule_save() self._schedule_signal() @dbus.service.method(dbus_interface=CONFIG_BUS_NAME, in_signature='sb', out_signature='') def SetBool(self, key, value): self._set_value(key, 'true' if value else 'false') @dbus.service.method(dbus_interface=CONFIG_BUS_NAME, in_signature='si', out_signature='') def SetInt(self, key, value): self._set_value(key, value) @dbus.service.method(dbus_interface=CONFIG_BUS_NAME, in_signature='sd', out_signature='') def SetDouble(self, key, value): self._set_value(key, value) @dbus.service.method(dbus_interface=CONFIG_BUS_NAME, in_signature='ss', out_signature='') def SetString(self, key, value): self._set_value(key, value) @dbus.service.method(dbus_interface=CONFIG_BUS_NAME, in_signature='sas', out_signature='') def SetStringList(self, key, value): self._set_value(key, join(value)) @dbus.service.method(dbus_interface=CONFIG_BUS_NAME, in_signature='a{sv}', out_signature='') def SetDefaultValues(self, values): for k, v in values.items(): if isinstance(v, list): v = join(v) self._set_value(k, v, False) def _schedule_save(self, filename=None): if self._save_timer is None: self._save_timer = GLib.timeout_add(1000, lambda: self.save(filename)) def save(self, filename=None): if filename is None: filename = self._filename with open(filename, 'w') as f: self._confparser.write(f) if self._save_timer is not None: GLib.source_remove(self._save_timer) self._save_timer = None def _schedule_signal(self): if self._signal_timer is None: self._signal_timer = GLib.timeout_add(500, lambda: self.emit_change()) def emit_change(self): if self._signal_timer is not None: GLib.source_remove(self._signal_timer) self._signal_timer = None changed = list(self._changed_signals.keys()) self.ValueChanged(changed) self._changed_signals = {} @dbus.service.signal(dbus_interface=CONFIG_BUS_NAME, signature='as') def ValueChanged(self, changed): pass def split(value, sep=';'): r""" >>> split('') [] >>> split(' ') [' '] >>> split('single') ['single'] >>> split('one;two') ['one', 'two'] >>> split('one;') ['one'] >>> split(';one;two;') ['', 'one', 'two'] >>> split(r'one\;two;three\\;four') ['one;two', 'three\\', 'four'] >>> split(r'\one\\\;two;\\three\\\\;four;') ['\\one\\;two', '\\three\\\\', 'four'] >>> split('; ') ['', ' '] """ start = 0 ret = [] item = [] curr = 0 while curr <= len(value): if curr == len(value) or value[curr] == sep: if start < curr: item.append(value[start:curr]) if curr != len(value) or item: ret.append(''.join(item)) item = [] start = curr + 1 elif value[curr] == '\\' and curr < len(value) - 1: tag = value[curr + 1] if tag == '\\' or tag == sep: item.append(value[start:curr]) start = curr + 1 curr = start curr = curr + 1 return ret def join(values, sep=';'): r""" >>> join([]) '' >>> join(['']) ';' >>> join(['one']) 'one;' >>> join(['one', 'two']) 'one;two;' >>> join(['one;', 'two']) 'one\\;;two;' >>> print join([r'on\e', 't;wo']) on\\e;t\;wo; """ if not values: return '' result = [] for item in values: result.append(item.replace('\\', '\\\\').replace(sep, '\\;')) return sep.join(result) + sep def test(): import doctest doctest.testmod() def run(): app = App('Config') if len(sys.argv) > 1: ini_conf = IniConfig(app.connection, sys.argv[1]) else: ini_conf = IniConfig(app.connection) # noqa: F841 app.run() if __name__ == '__main__': import sys if '--test' in sys.argv: test() else: run() osdlyrics-0.5.5~rc1+dfsg1/daemon/lrcdb.py000066400000000000000000000175611345713535600202710ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from __future__ import unicode_literals from future import standard_library standard_library.install_aliases() from builtins import object import logging import os.path import sqlite3 from osdlyrics.consts import (METADATA_ALBUM, METADATA_ARTIST, METADATA_TITLE, METADATA_TRACKNUM) import osdlyrics.utils __all__ = ( 'LrcDb', ) def query_param_from_metadata(metadata): """ Generate query dict from metadata """ param = { METADATA_TITLE: metadata.title or '', METADATA_ARTIST: metadata.artist or '', METADATA_ALBUM: metadata.album or '', METADATA_TRACKNUM: max(metadata.tracknum, 0), } return param class LrcDb(object): """ Database to store location of LRC files that have been manually assigned """ TABLE_NAME = 'lyrics' METADATA_LIST = [METADATA_TITLE, METADATA_ARTIST, METADATA_ALBUM, METADATA_TRACKNUM] CREATE_TABLE = """ CREATE TABLE IF NOT EXISTS {0} ( id INTEGER PRIMARY KEY AUTOINCREMENT, {1} TEXT, {2} TEXT, {3} TEXT, {4} INTEGER, uri TEXT UNIQUE ON CONFLICT REPLACE, lrcpath TEXT ) """.format(TABLE_NAME, *METADATA_LIST) ASSIGN_LYRIC = """ INSERT OR REPLACE INTO {0} ({1}, {2}, {3}, {4}, uri, lrcpath) VALUES (?, ?, ?, ?, ?, ?) """ .format(TABLE_NAME, *METADATA_LIST) UPDATE_LYRIC = """ UPDATE {0} SET lrcpath=? WHERE uri=? """.format(TABLE_NAME) DELETE_LYRIC = 'DELETE FROM {0} WHERE '.format(TABLE_NAME) FIND_LYRIC = 'SELECT lrcpath FROM {0} WHERE '.format(TABLE_NAME) QUERY_LOCATION = 'uri = ?' QUERY_INFO = ' AND '.join('{0}=:{0}'.format(m) for m in METADATA_LIST) def __init__(self, dbfile=None): """ Arguments: - `dbfile`: The sqlite db to open """ if dbfile is None: dbfile = osdlyrics.utils.get_config_path('lrc.db') self._dbfile = dbfile osdlyrics.utils.ensure_path(dbfile) self._conn = sqlite3.connect(os.path.expanduser(dbfile)) self._create_table() def _create_table(self): """ Ensures the table structure of new open dbs """ c = self._conn.cursor() c.execute(LrcDb.CREATE_TABLE) self._conn.commit() c.close() def assign(self, metadata, uri): # type: (osdlyrics.metadata.Metadata, Text) -> None """ Assigns a uri of lyrics to tracks represented by metadata """ c = self._conn.cursor() location = metadata.location or '' if self._find_by_location(metadata): logging.debug('Assign lyric file %s to track of location %s', uri, location) c.execute(LrcDb.UPDATE_LYRIC, (uri, location,)) else: title = metadata.title or '' artist = metadata.artist or '' album = metadata.album or '' tracknum = max(metadata.tracknum, 0) logging.debug('Assign lyrics file %s to track %s. %s - %s in album %s @ %s', uri, tracknum, artist, title, album, location) c.execute(LrcDb.ASSIGN_LYRIC, (title, artist, album, tracknum, location, uri)) self._conn.commit() c.close() def delete(self, metadata): """ Deletes lyrics association(s) for given metadata Deletes all lyrics associations that would be found by find(self, metadata) """ c = self._conn.cursor() if metadata.location: c.execute(LrcDb.DELETE_LYRIC + LrcDb.QUERY_LOCATION, (metadata.location,)) c.execute(LrcDb.DELETE_LYRIC + LrcDb.QUERY_INFO, query_param_from_metadata(metadata)) self._conn.commit() c.close() def find(self, metadata): """ Finds the location of LRC files for given metadata To find the location of lyrics, firstly find whether there is a record matched with the ``location`` attribute in metadata. If not found or ``location`` is not specified, try to find with respect to ``title``, ``artist``, ``album`` and ``tracknum`` If found, return the uri of the LRC file. Otherwise return None. Note that this method may return an empty string, so use ``is None`` to figure out whether an uri is found """ ret = self._find_by_location(metadata) if ret is not None: return ret ret = self._find_by_info(metadata) if ret is not None: return ret return None def _find_by_condition(self, where_clause, parameters=None): query = LrcDb.FIND_LYRIC + where_clause logging.debug('Find by condition, query = %s, params = %s', query, parameters) c = self._conn.cursor() c.execute(query, parameters) r = c.fetchone() logging.debug('Fetch result: %s', r) if r: return r[0] return None def _find_by_location(self, metadata): if not metadata.location: return None return self._find_by_condition(LrcDb.QUERY_LOCATION, (metadata.location,)) def _find_by_info(self, metadata): return self._find_by_condition(LrcDb.QUERY_INFO, query_param_from_metadata(metadata)) def test(): """ >>> import dbus >>> from osdlyrics.metadata import Metadata >>> db = LrcDb('/tmp/asdf') >>> db.assign(Metadata.from_dict({'title': 'Tiger', ... 'artist': 'Soldier', ... 'location': 'file:///tmp/asdf'}), ... 'file:///tmp/a.lrc') >>> db.find(Metadata.from_dict({'location': 'file:///tmp/asdf'})) 'file:///tmp/a.lrc' >>> db.find(Metadata.from_dict({'location': 'file:///tmp/asdfg'})) >>> db.find(Metadata.from_dict({'title': 'Tiger', ... 'location': 'file:///tmp/asdf'})) 'file:///tmp/a.lrc' >>> db.find(Metadata.from_dict({'title': 'Tiger', })) >>> db.find(Metadata.from_dict({'title': 'Tiger', ... 'artist': 'Soldier'})) 'file:///tmp/a.lrc' >>> db.assign(Metadata.from_dict({'title': 'ttTiger', ... 'artist': 'ssSoldier', ... 'location': 'file:///tmp/asdf'}), ... 'file:///tmp/b.lrc') >>> db.find(Metadata.from_dict({'artist': 'Soldier', })) >>> db.find(Metadata.from_dict({'title': 'Tiger', ... 'artist': 'Soldier', })) 'file:///tmp/b.lrc' >>> db.find(Metadata.from_dict({dbus.String('title'): dbus.String('Tiger'), ... dbus.String('artist'): dbus.String('Soldier'), })) 'file:///tmp/b.lrc' >>> metadata_uni = Metadata.from_dict({'title': '\u6807\u9898', 'artist': '\u6b4c\u624b', }) >>> db.assign(metadata_uni, '\u8def\u5f84') >>> db.find(metadata_uni) '\u8def\u5f84' >>> db.find(Metadata.from_dict({'title': 'Tiger', 'artist': 'Soldiers', })) >>> db.find(Metadata()) >>> db.delete(Metadata.from_dict({'location': 'file:///tmp/asdf'})) >>> db.find(Metadata.from_dict({'title': 'Tiger', ... 'artist': 'Soldier', ... 'location': 'file:///tmp/asdf'})) """ import doctest doctest.testmod() if __name__ == '__main__': test() osdlyrics-0.5.5~rc1+dfsg1/daemon/lyrics.py000066400000000000000000000340401345713535600204770ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from builtins import super from future import standard_library standard_library.install_aliases() import logging import os import os.path import re import urllib.parse import urllib.request import chardet import dbus import dbus.service import osdlyrics from osdlyrics.app import App import osdlyrics.config import osdlyrics.lrc from osdlyrics.metadata import Metadata from osdlyrics.pattern import expand_file, expand_path import lrcdb LYRICS_INTERFACE = 'org.osdlyrics.Lyrics' LYRICS_OBJECT_PATH = '/org/osdlyrics/Lyrics' DEFAULT_FILE_PATTERNS = [ '%p-%t', '%t-%p', '%f', '%t', ] DEFAULT_PATH_PATTERNS = [ '~/.lyrics', '%', ] SUPPORTED_SCHEMES = [ 'file', # 'tag', 'none', ] DETECT_CHARSET_GUESS_MIN_LEN = 40 DETECT_CHARSET_GUESS_MAX_LEN = 100 class InvalidUriException(Exception): """ Exception of invalid uri. """ def __init__(self, uri): super().__init__("Invalid URI: %s" % uri) class CannotLoadLrcException(Exception): def __init__(self, uri): super().__init__("Cannot load lrc file from %s" % uri) class CannotSaveLrcException(Exception): def __init__(self, uri): super().__init__("Cannot save lrc file to %s" % uri) def metadata_description(metadata): if metadata.title is None: return '[Unknown]' if metadata.artist is None: return metadata.title return '%s(%s)' % (metadata.title, metadata.artist) def decode_by_charset(content): # type: (bytes) -> Text r""" Detect the charset encoding of a string and decodes to unicode strings. >>> decode_by_charset(u'\u4e2d\u6587'.encode('UTF-8')) '\u4e2d\u6587' >>> decode_by_charset(u'\u4e2d\u6587'.encode('HZ-GB-2312')) '\u4e2d\u6587' """ encoding = chardet.detect(content)['encoding'] # Sometimes, the content is well encoded but the last few bytes. This is # common in the files downloaded by old versions of OSD Lyrics. In this # case,chardet may fail to determine what the encoding it is. So we take # half of the content of it and try again. if not encoding and len(content) > DETECT_CHARSET_GUESS_MIN_LEN: logging.warning('Failed to detect encoding, try to decode a part of it') content_half = len(content) // 2 slice_end = min(max(DETECT_CHARSET_GUESS_MIN_LEN, content_half), DETECT_CHARSET_GUESS_MAX_LEN) encoding = chardet.detect(content[:slice_end])['encoding'] logging.warning('guess encoding from part: ' + encoding) if not encoding: logging.warning('Failed to detect encoding, use utf-8 as fallback') encoding = 'utf-8' # When we take half of the content to determine the encoding, chardet may # think it be encoded with ascii, however the full content is probably # encoded with utf-8. As ascii is an subset of utf-8, decoding an ascii # string with utf-8 will always be right. if encoding == 'ascii': encoding = 'utf-8' return content.decode(encoding, 'replace') def is_valid_uri(uri): """ Tell if a URI is valid. A valid URI must begin with the schemes defined in SUPPORTED_SCHEMES """ for scheme in SUPPORTED_SCHEMES: if uri.startswith(scheme + ':'): return True return False def ensure_uri_scheme(uri): # type: (Text) -> Text """ Converts a file path to an URI with scheme of "file:", leaving other URI not changed If the uri doesn't have any scheme, it is considered to be a file path. """ if uri: url_parts = urllib.parse.urlparse(uri) if not url_parts.scheme: uri = osdlyrics.utils.path2uri(uri) return uri def _load_from_file(urlparts): """ Load the content of file from urlparse.ParseResult Return the content of the file, or None if error raised. """ path = urllib.request.url2pathname(urlparts.path) try: with open(path, 'rb') as f: return f.read() except IOError as e: logging.info("Cannot open file %s to read: %s", path, e) return None def load_from_uri(uri): # type: (Text) -> Optional[Text] """ Load the content of LRC file from given URI If loaded, return the content. If failed, return None. """ URI_LOAD_HANDLERS = { 'file': _load_from_file, 'none': lambda uri: b'', } url_parts = urllib.parse.urlparse(uri) content = URI_LOAD_HANDLERS[url_parts.scheme](url_parts) if content is None: return None content = decode_by_charset(content).replace('\0', '') return content def _save_to_file(urlparts, content, create): # type: (Any, bytes, bool) -> bool """ Save the content of file to urlparse.ParseResult Return True if succeeded """ path = urllib.request.url2pathname(urlparts.path) if not create: if not os.path.isfile(path): logging.warning("Cannot write to file %s: file not exists", path) return False else: dirname = os.path.dirname(path) if not os.path.isdir(dirname): try: os.makedirs(os.path.dirname(path), 0o755) except OSError as e: logging.warning("Cannot create directories for %s: %s", path, e) return False try: file = open(path, 'wb') except IOError as e: logging.info("Cannot open file %s to write: %s", path, e) return False file.write(content) return True def save_to_uri(uri, content, create=True): # type: (Text, bytes, bool) -> bool """ Save the content of LRC file to given URI. Return True if succeeded, or False if failed. """ URI_SAVE_HANDLERS = { 'file': _save_to_file, 'none': lambda urlparts, content, create: True, } url_parts = urllib.parse.urlparse(uri) return URI_SAVE_HANDLERS[url_parts.scheme](url_parts, content, create) def update_lrc_offset(content, offset): r""" Replace the offset attributes in the content of LRC file. >>> update_lrc_offset('no tag', 100) '[offset:100]\nno tag' >>> update_lrc_offset('[ti:title]\n[offset:200]\nSome lrc', 100) '[ti:title]\n[offset:100]\nSome lrc' >>> update_lrc_offset('[ti:title][offset:200]Some lrc\nanother', 100) '[ti:title][offset:100]Some lrc\nanother' >>> update_lrc_offset('Some [offset:200] lrc', 100) '[offset:100]\nSome [offset:200] lrc' >>> update_lrc_offset('[[offset:200]] lrc', 100) '[offset:100]\n[[offset:200]] lrc' """ search_result = re.search(r'^(\[[^\]]*\])*?\[offset:(.*?)\]', content, re.MULTILINE) if search_result is None: return '[offset:%s]\n%s' % (offset, content) return '%s%s%s' % (content[:search_result.start(2)], offset, content[search_result.end(2):]) class LyricsService(dbus.service.Object): def __init__(self, conn): super().__init__(conn=conn, object_path=LYRICS_OBJECT_PATH) self._db = lrcdb.LrcDb() self._config = osdlyrics.config.Config(conn) self._metadata = Metadata() def find_lrc_from_db(self, metadata): uri = self._db.find(metadata) if uri == '': return 'none:' return ensure_uri_scheme(uri) def find_lrc_by_pattern(self, metadata): return ensure_uri_scheme(self._expand_patterns(metadata)) def assign_lrc_uri(self, metadata, uri): self._db.assign(metadata, uri) if metadata == self._metadata: self.CurrentLyricsChanged() @dbus.service.method(dbus_interface=LYRICS_INTERFACE, in_signature='a{sv}', out_signature='bsa{ss}aa{sv}') def GetLyrics(self, metadata): ret, uri, content = self.GetRawLyrics(metadata) if ret: attr, lines = osdlyrics.lrc.parse_lrc(content) return ret, uri, attr, lines else: return ret, uri, {}, [] @dbus.service.method(dbus_interface=LYRICS_INTERFACE, in_signature='a{sv}', out_signature='bss') def GetRawLyrics(self, metadata): if isinstance(metadata, dict): metadata = Metadata.from_dict(metadata) uri = self.find_lrc_from_db(metadata) lrc = None if uri: if uri == 'none:': return True, uri, '' lrc = load_from_uri(uri) if lrc is not None: return True, uri, lrc uri = self.find_lrc_by_pattern(metadata) if uri: lrc = load_from_uri(uri) if lrc is not None: logging.info("LRC for track %s not found in db but found by pattern: %s", metadata_description(metadata), uri) if lrc is None: logging.info("LRC for track %s not found", metadata_description(metadata)) return False, '', '' else: logging.info("LRC for track %s found: %s", metadata_description(metadata), uri) return True, uri, lrc @dbus.service.method(dbus_interface=LYRICS_INTERFACE, in_signature='', out_signature='bsa{ss}aa{sv}') def GetCurrentLyrics(self): return self.GetLyrics(self._metadata) @dbus.service.method(dbus_interface=LYRICS_INTERFACE, in_signature='', out_signature='bss') def GetCurrentRawLyrics(self): return self.GetRawLyrics(self._metadata) @dbus.service.method(dbus_interface=LYRICS_INTERFACE, in_signature='a{sv}ay', out_signature='s', byte_arrays=True) def SetLyricContent(self, metadata, content): metadata = Metadata.from_dict(metadata) # Remove any existing file association and save the new lyrics content # to the configured patterns. self._db.delete(metadata) uri = self._save_to_patterns(metadata, content.rstrip(b'\0')) if uri and metadata == self._metadata: self.CurrentLyricsChanged() return uri @dbus.service.method(dbus_interface=LYRICS_INTERFACE, in_signature='a{sv}s', out_signature='') def AssignLyricFile(self, metadata, uri): metadata = Metadata.from_dict(metadata) self.assign_lrc_uri(metadata, uri) @dbus.service.signal(dbus_interface=LYRICS_INTERFACE, signature='') def CurrentLyricsChanged(self): pass @dbus.service.method(dbus_interface=LYRICS_INTERFACE, in_signature='si', out_signature='') def SetOffset(self, uri, offset_ms): if not is_valid_uri(uri): raise InvalidUriException(uri) content = load_from_uri(uri) if content is None: raise CannotLoadLrcException(uri) content = update_lrc_offset(content, offset_ms).encode('utf-8') if not save_to_uri(uri, content, True): raise CannotSaveLrcException(uri) def _save_to_patterns(self, metadata, content): """ Save content to file expanded from given patterns Arguments: - `metadata`: - `content`: """ file_patterns = self._config.get_string_list('General/lrc-filename', DEFAULT_FILE_PATTERNS) path_patterns = self._config.get_string_list('General/lrc-path', DEFAULT_PATH_PATTERNS) for path_pat in path_patterns: try: path = expand_path(path_pat, metadata) except osdlyrics.pattern.PatternException: continue for file_pat in file_patterns: try: filename = expand_file(file_pat, metadata) except osdlyrics.pattern.PatternException: continue fullpath = os.path.join(path, filename + '.lrc') uri = osdlyrics.utils.path2uri(fullpath) if save_to_uri(uri, content): return uri return '' def _expand_patterns(self, metadata): file_patterns = self._config.get_string_list('General/lrc-filename', DEFAULT_FILE_PATTERNS) path_patterns = self._config.get_string_list('General/lrc-path', DEFAULT_PATH_PATTERNS) for path_pat in path_patterns: try: path = expand_path(path_pat, metadata) except osdlyrics.pattern.PatternException: logger.exception('expand gg') continue for file_pat in file_patterns: try: filename = expand_file(file_pat, metadata) except osdlyrics.pattern.PatternException: continue fullpath = os.path.join(path, filename + '.lrc') if os.path.isfile(fullpath): return fullpath return None def set_current_metadata(self, metadata): logging.info('Setting current metadata: %s', metadata) self._metadata = metadata def doc_test(): import doctest doctest.testmod() def test(): app = App('Lyrics', False) lyrics_service = LyricsService(app.connection) # noqa: F841 app.run() if __name__ == '__main__': doc_test() test() osdlyrics-0.5.5~rc1+dfsg1/daemon/lyricsource.py000066400000000000000000000271131345713535600215400ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from builtins import str, super import logging import dbus import osdlyrics.config from osdlyrics.consts import (LYRIC_SOURCE_PLUGIN_INTERFACE, LYRIC_SOURCE_PLUGIN_OBJECT_PATH_PREFIX) LYRIC_SOURCE_INTERFACE = 'org.osdlyrics.LyricSource' LYRIC_SOURCE_OBJECT_PATH = '/org/osdlyrics/LyricSource' LYRIC_SOURCE_PLUGIN_BUS_NAME_PREFIX = 'org.osdlyrics.LyricSourcePlugin.' STATUS_SUCCESS = 0 STATUS_CANCELLED = 1 STATUS_FAILURE = 2 def validateticket(component): def decorator(func): def dec_func(self, source_id, ticket, *args, **kwargs): if source_id not in self._sources: logging.warning('%s is not in source list', source_id) return source = self._sources[source_id] if ticket not in source[component]: logging.warning('%s is not valid %s ticket of source %s', ticket, component, source_id) return func(self, source_id, ticket, *args, **kwargs) return dec_func return decorator class LyricSource(dbus.service.Object): """ Implement org.osdlyrics.LyricSource interface """ def __init__(self, conn): super().__init__(conn=conn, object_path=LYRIC_SOURCE_OBJECT_PATH) self._sources = {} self._search_tasks = {} self._n_search_tickets = 0 self._download_tasks = {} self._n_download_tickets = 0 self._detect_sources() self._config = osdlyrics.config.Config(conn) def _detect_sources(self): for bus_name in map(str, self.connection.list_names()): try: self._connect_source(bus_name, False) except Exception as e: logging.warning('Fail to connect source %s: %s', bus_name, e) for bus_name in map(str, self.connection.list_activatable_names()): try: self._connect_source(bus_name, True) except Exception as e: logging.warning('Fail to connect source %s: %s', bus_name, e) def _connect_source(self, bus_name, activate): if not bus_name.startswith(LYRIC_SOURCE_PLUGIN_BUS_NAME_PREFIX): return logging.info('Connecting to lyric source %s', bus_name) source_id = bus_name[len(LYRIC_SOURCE_PLUGIN_BUS_NAME_PREFIX):] if source_id in self._sources: return if activate: try: self.connection.activate_name_owner(bus_name) except Exception as e: logging.warning('Cannot activate lyric source %s: %s', bus_name, e) return path = LYRIC_SOURCE_PLUGIN_OBJECT_PATH_PREFIX + source_id proxy = dbus.Interface(self.connection.get_object(bus_name, path), LYRIC_SOURCE_PLUGIN_INTERFACE) property_iface = dbus.Interface(proxy, 'org.freedesktop.DBus.Properties') source = { 'proxy': proxy, 'name': property_iface.Get(LYRIC_SOURCE_PLUGIN_INTERFACE, 'Name'), 'id': source_id, 'search': {}, 'download': {}, } self._sources[source_id] = source proxy.connect_to_signal('SearchComplete', lambda t, s, r: self.search_complete_cb(source_id, t, s, r)) proxy.connect_to_signal('DownloadComplete', lambda t, s, c: self.download_complete_cb(source_id, t, s, c)) @validateticket('search') def search_complete_cb(self, source_id, ticket, status, results): logging.info('Search complete from %s, ticket: %s, status: %s, result: %s', source_id, ticket, status, len(results)) source = self._sources[source_id] myticket = source['search'].pop(ticket) if myticket not in self._search_tasks: return if status == STATUS_SUCCESS: mytask = self._search_tasks[myticket] mytask['failure'] = False if (status == STATUS_SUCCESS and results) or \ status == STATUS_CANCELLED: self.SearchComplete(myticket, status, results) else: # STATUS_FAILURE mytask = self._search_tasks[myticket] # mytask['failure'] is set to True only when all sources fail to search. # To ensure that, we set this value to None when task is created, and # set it to False when a search task succeeds, thus `not task['failure']` # will return True. The value is set to True only when it was not False, # so no search tasks from other sources succeed. if status == STATUS_FAILURE and mytask['failure'] is not False: mytask['failure'] = True if not mytask['sources'] or mytask['sources'][0] != source_id: logging.warning('Error, no source exists or source id mismatch with current id') self.SearchComplete(myticket, STATUS_FAILURE, results) else: mytask['sources'].pop(0) self._do_search(myticket) @validateticket('download') def download_complete_cb(self, source_id, ticket, status, content): logging.info('Download complete from %s, ticket: %s, status: %s, content length: %s', source_id, ticket, status, len(content)) source = self._sources[source_id] myticket = source['download'].pop(ticket) if myticket not in self._download_tasks: return self.DownloadComplete(myticket, status, content) def _get_source_proxy(self, sourceid): return self._sources[sourceid]['proxy'] def _get_source_search(self, sourceid, sourceticket): return self._sources[sourceid]['search'][sourceticket] def _set_source_search(self, sourceid, sourceticket, value): if sourceticket in self._sources[sourceid]['search']: raise KeyError('ticket %d exists in source search tasks', sourceticket) self._sources[sourceid]['search'][sourceticket] = value def _del_source_search(self, sourceid, sourceticket): del self._sources[sourceid]['search'][sourceticket] def _get_source_download(self, sourceid, sourceticket): return self._sources[sourceid]['download'][sourceticket] def _set_source_download(self, sourceid, sourceticket, value): if sourceticket in self._sources[sourceid]['download']: raise KeyError('ticket %d exists in source download tasks', sourceticket) self._sources[sourceid]['download'][sourceticket] = value def _del_source_download(self, sourceid, sourceticket): del self._sources[sourceid]['download'][sourceticket] def _do_search(self, ticket): task = self._search_tasks[ticket] nextsource = None while task['sources']: if task['sources'][0] in self._sources: nextsource = task['sources'][0] break else: logging.warning('Source %s not exist', task['sources'][0]) task['sources'].pop(0) if nextsource is None: status = STATUS_SUCCESS if not task['failure'] else STATUS_FAILURE self.SearchComplete(ticket, status, []) else: newticket = self._get_source_proxy(nextsource).Search(task['metadata']) self._set_source_search(nextsource, newticket, ticket) task['ticket'] = newticket self.SearchStarted(ticket, nextsource, self._sources[nextsource]['name']) @dbus.service.signal(dbus_interface=LYRIC_SOURCE_INTERFACE, signature='iiaa{sv}') def SearchComplete(self, ticket, status, results): if ticket in self._search_tasks: del self._search_tasks[ticket] @dbus.service.signal(dbus_interface=LYRIC_SOURCE_INTERFACE, signature='iiay') def DownloadComplete(self, ticket, status, content): if ticket in self._download_tasks: del self._download_tasks[ticket] @dbus.service.signal(dbus_interface=LYRIC_SOURCE_INTERFACE, signature='iss') def SearchStarted(self, ticket, sourceid, sourcename): pass @dbus.service.method(dbus_interface=LYRIC_SOURCE_INTERFACE, in_signature='a{sv}as', out_signature='i') def Search(self, metadata, sources): self._n_search_tickets += 1 ticket = self._n_search_tickets task = { 'metadata': metadata, 'sources': [str(id) for id in sources], 'ticket': None, 'failure': None, # See comments in search_complete_cb() } self._search_tasks[ticket] = task self._do_search(ticket) return ticket @dbus.service.method(dbus_interface=LYRIC_SOURCE_INTERFACE, in_signature='i', out_signature='') def CancelSearch(self, ticket): if ticket not in self._search_tasks: return task = self._search_tasks[ticket] sourceticket = task['ticket'] sourceid = task['sources'][0] self._get_source_proxy(sourceid).CancelSearch(sourceticket) @dbus.service.method(dbus_interface=LYRIC_SOURCE_INTERFACE, in_signature='sv', out_signature='i') def Download(self, source_id, downloaddata): if source_id not in self._sources: return -1 sourceticket = self._get_source_proxy(source_id).Download(downloaddata) if sourceticket < 0: return -1 self._n_download_tickets += 1 ticket = self._n_download_tickets self._download_tasks[ticket] = { 'ticket': sourceticket, 'source': source_id, } self._set_source_download(source_id, sourceticket, ticket) return ticket @dbus.service.method(dbus_interface=LYRIC_SOURCE_INTERFACE, in_signature='i', out_signature='') def CancelDownload(self, ticket): if ticket not in self._download_tasks: return task = self._download_tasks[ticket] sourceticket = task['ticket'] sourceid = task['source'] self._get_source_proxy(sourceid).CancelDownload(sourceticket) @dbus.service.method(dbus_interface=LYRIC_SOURCE_INTERFACE, in_signature='', out_signature='aa{sv}') def ListSources(self): enabled = self._config.get_string_list('Download/download-engine') sources = [ {'id': id, 'name': v['name'], 'enabled': id in enabled} for id, v in self._sources.items() ] order = {id: i for i, id in enumerate(enabled)} return sorted(sources, key=lambda it: (-it['enabled'], order.get(it['id'], 1 << 31))) osdlyrics-0.5.5~rc1+dfsg1/daemon/main.py000066400000000000000000000117631345713535600201250ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from __future__ import print_function from builtins import super import logging import dbus from osdlyrics import PACKAGE_VERSION from osdlyrics.app import AlreadyRunningException, App from osdlyrics.consts import (CONFIG_BUS_NAME, DAEMON_BUS_NAME, DAEMON_INTERFACE, DAEMON_MPRIS2_NAME, DAEMON_OBJECT_PATH, MPRIS2_OBJECT_PATH) from osdlyrics.metadata import Metadata import lyrics import lyricsource import player logging.basicConfig(level=logging.WARNING) class InvalidClientNameException(Exception): """ The client bus name in Hello is invalid """ def __init__(self, name): """ Arguments: - `name`: The invalid client bus name """ super().__init__('Client bus name %s is invalid' % name) class MainApp(App): def __init__(self, ): super().__init__('Daemon', False) self._player = player.PlayerSupport(self.connection) self._lyrics = lyrics.LyricsService(self.connection) self._connect_metadata_signal() self._activate_config() self.request_bus_name(DAEMON_MPRIS2_NAME) self._daemon_object = DaemonObject(self) self._lyricsource = lyricsource.LyricSource(self.connection) self._lyrics.set_current_metadata(Metadata.from_dict( self._player.current_player.Metadata)) def _connect_metadata_signal(self, ): self._mpris_proxy = self.connection.get_object(DAEMON_BUS_NAME, MPRIS2_OBJECT_PATH) self._metadata_signal = self._mpris_proxy.connect_to_signal( 'PropertiesChanged', self._player_properties_changed) def _activate_config(self, ): try: self.connection.activate_name_owner(CONFIG_BUS_NAME) except Exception: logging.error("Cannot activate config service") def _player_properties_changed(self, iface, changed, invalidated): if 'Metadata' in changed: self._lyrics.set_current_metadata(Metadata.from_dict( changed['Metadata'])) def is_valid_client_bus_name(name): """Check if a client bus name is valid. A client bus name is valid if it starts with `org.osdlyrics.Client.` Arguments: - `name`: The bus name of client """ return name.startswith('org.osdlyrics.Client.') class DaemonObject(dbus.service.Object): """ DBus Object implementing org.osdlyrics.Daemon """ def __init__(self, app): super().__init__(conn=app.connection, object_path=DAEMON_OBJECT_PATH) self._watch_clients = {} self._app = app @dbus.service.method(dbus_interface=DAEMON_INTERFACE, in_signature='s', out_signature='') def Hello(self, client_bus_name): logging.info('A new client connected: %s', client_bus_name) if is_valid_client_bus_name(client_bus_name): if client_bus_name not in self._watch_clients: self._watch_clients[client_bus_name] = \ self.connection.watch_name_owner( client_bus_name, lambda owner: self._client_owner_changed( client_bus_name, owner)) else: raise InvalidClientNameException(client_bus_name) @dbus.service.method(dbus_interface=DAEMON_INTERFACE, in_signature='', out_signature='s') def GetVersion(self): return PACKAGE_VERSION @dbus.service.method(dbus_interface=DAEMON_INTERFACE, in_signature='', out_signature='') def Quit(self): self._app.quit() def _client_owner_changed(self, name, owner): if owner == '': logging.info('Client %s disconnected', name) if name in self._watch_clients: self._watch_clients[name].cancel() del self._watch_clients[name] if not self._watch_clients: logging.info('All client disconnected, quit the daemon') self._app.quit() def main(): try: app = MainApp() app.run() except AlreadyRunningException: print('OSD Lyrics is running') if __name__ == '__main__': main() osdlyrics-0.5.5~rc1+dfsg1/daemon/player.py000066400000000000000000000464711345713535600205010ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from builtins import str, super import logging from dbus.exceptions import DBusException import dbus.service from gi.repository import GLib from osdlyrics import PROGRAM_NAME from osdlyrics.app import App from osdlyrics.consts import (MPRIS2_OBJECT_PATH, MPRIS2_PLAYER_INTERFACE, PLAYER_PROXY_INTERFACE, PLAYER_PROXY_OBJECT_PATH_PREFIX) from osdlyrics.dbusext.service import (Object as DBusObject, property as dbus_property) import osdlyrics.timer MPRIS2_ROOT_INTERFACE = 'org.mpris.MediaPlayer2' PLAYER_INTERFACE = 'org.osdlyrics.Player' PLAYER_OBJECT_PATH = '/org/osdlyrics/Player' PLAYER_PROXY_BUS_NAME_PREFIX = 'org.osdlyrics.PlayerProxy.' class PlayerSupport(dbus.service.Object): """ Implement org.osdlyrics.Player Interface """ DETECT_PLAYER_TIMEOUT = 1000 def __init__(self, conn): """ Arguments: - `conn`: DBus connection of the object """ super().__init__(conn=conn, object_path=PLAYER_OBJECT_PATH) self._active_player = None self._player_proxies = {} self._connect_player_proxies() self._start_detect_player() self._mpris2_player = Mpris2Player(conn) def _start_detect_player(self): self._detect_timer = GLib.timeout_add(self.DETECT_PLAYER_TIMEOUT, lambda: not self._detect_player()) def _detect_player(self): """ Detects active players. This is a callback function of a timer. If active player detected, try to connect to the player and remove the timer. """ detected = False for proxy in self._player_proxies.values(): try: active_players = proxy.ListActivePlayers() for player_info in active_players: if self._connect_player(proxy, player_info): detected = True break if detected: break except Exception: pass if detected and self._detect_timer: GLib.source_remove(self._detect_timer) self._detect_timer = None return detected def _connect_proxy(self, bus_name, activate): if not bus_name.startswith(PLAYER_PROXY_BUS_NAME_PREFIX): return logging.info('Connecting to player proxy %s', bus_name) proxy_name = bus_name[len(PLAYER_PROXY_BUS_NAME_PREFIX):] if activate: try: self.connection.activate_name_owner(bus_name) except Exception as e: logging.warning('Cannot activate proxy %s: %s', bus_name, e) self.connection.watch_name_owner(bus_name, lambda name: self._proxy_name_changed(proxy_name, len(name) == 0)) def _connect_player_proxies(self): """ Activates all player proxy services """ active_names = map(str, self.connection.list_names()) for bus_name in active_names: self._connect_proxy(bus_name, False) activatable_names = map(str, self.connection.list_activatable_names()) for bus_name in activatable_names: self._connect_proxy(bus_name, True) def _connect_player(self, proxy, player_info): """ Tries to connect player through proxy Return True if connect successful """ try: path = proxy.ConnectPlayer(player_info['name']) except DBusException as e: if e._dbus_error_name != 'org.osdlyrics.Error.ConnectPlayer': logger.exception('BugReport') return False player = self.connection.get_object(proxy.bus_name, path) self._active_player = {'info': player_info, 'player': player, 'proxy': proxy} self._mpris2_player.connect_player(player) self.PlayerConnected(player_info) return True def _player_lost_cb(self, player_name): if self._active_player and self._active_player['info']['name'] == player_name: logging.info('Player %s lost', player_name) self._active_player = None self._mpris2_player.disconnect_player() self.PlayerLost() self._start_detect_player() def _proxy_name_changed(self, proxy_name, lost): bus_name = PLAYER_PROXY_BUS_NAME_PREFIX + proxy_name if not lost: logging.info('Get player proxy %s', proxy_name) proxy = self.connection.get_object( bus_name, PLAYER_PROXY_OBJECT_PATH_PREFIX + proxy_name) proxy.connect_to_signal('PlayerLost', self._player_lost_cb) self._player_proxies[proxy_name] = dbus.Interface( proxy, PLAYER_PROXY_INTERFACE) else: if proxy_name not in self._player_proxies: return logging.info('Player proxy %s lost', proxy_name) proxy = self._player_proxies[proxy_name] # If current player is provided by the proxy, it is lost. if self._active_player and self._active_player['proxy'] == proxy: self._player_lost_cb(self._active_player['info']['name']) del self._player_proxies[proxy_name] # Try to reactivate proxy try: self.connection.activate_name_owner(bus_name) except Exception: pass @dbus.service.method(dbus_interface=PLAYER_INTERFACE, in_signature='', out_signature='aa{sv}') def ListSupportedPlayers(self): ret = [] for proxy in self._player_proxies.values(): try: ret = ret + proxy.ListSupportedPlayers() except Exception: pass return ret @dbus.service.method(dbus_interface=PLAYER_INTERFACE, in_signature='', out_signature='aa{sv}') def ListActivatablePlayers(self): ret = [] for proxy in self._player_proxies.values(): try: ret = ret + proxy.ListActivatablePlayers() except Exception: pass return ret @dbus.service.method(dbus_interface=PLAYER_INTERFACE, in_signature='', out_signature='ba{sv}') def GetCurrentPlayer(self): if not self._active_player and not self._detect_player(): return False, {} return True, self._active_player['info'] @dbus.service.signal(dbus_interface=PLAYER_INTERFACE, signature='') def PlayerLost(self): pass @dbus.service.signal(dbus_interface=PLAYER_INTERFACE, signature='a{sv}') def PlayerConnected(self, player_info): pass @property def current_player(self): return self._mpris2_player class Mpris2Player(DBusObject): def __init__(self, conn): super().__init__(conn=conn, object_path=MPRIS2_OBJECT_PATH) self._signals = [] self._player = None self._timer = osdlyrics.timer.Timer() self._clear_properties() def _clear_properties(self): self.LoopStatus = 'None' self.PlaybackStatus = 'Stopped' self.Metadata = dbus.Dictionary(signature='sv') self.Shuffle = False self._timer.stop() self._timer.time = 0 def connect_player(self, player_proxy): if self._player == player_proxy: return if self._player is not None: self.disconnect_player() self._player = player_proxy self._signals = [] self._signals.append(self._player.connect_to_signal('Seeked', self._seeked_cb)) self._signals.append(self._player.connect_to_signal('PropertiesChanged', self._properties_changed_cb)) self.PlaybackStatus = self._player.Get(MPRIS2_PLAYER_INTERFACE, 'PlaybackStatus') self.LoopStatus = self._player.Get(MPRIS2_PLAYER_INTERFACE, 'LoopStatus') self.Shuffle = self._player.Get(MPRIS2_PLAYER_INTERFACE, 'Shuffle') self.Metadata = self._player.Get(MPRIS2_PLAYER_INTERFACE, 'Metadata') self._setup_timer_status(self._playback_status) self._timer.time = self._player.Get(MPRIS2_PLAYER_INTERFACE, 'Position') def disconnect_player(self): for signal in self._signals: signal.remove() self._signals = [] del self._player self._player = None self._clear_properties() def _setup_timer_status(self, status): status_map = { 'Playing': 'play', 'Paused': 'pause', 'Stopped': 'stop', } if status in status_map: getattr(self._timer, status_map[status])() def _seeked_cb(self, position): self._timer.time = position // 1000 self.Seeked(position) def _properties_changed_cb(self, iface, changed, invalidated): accepted_properties = set(['PlaybackStatus', 'LoopStatus', 'Rate', 'Shuffle', 'Metadata', 'Volume', 'MinimumRate', 'MaximumRate', 'CanGoNext', 'CanGoPrevious', 'CanPlay', 'CanPause', 'CanSeek', ]) for k, v in changed.items(): if k in accepted_properties: setattr(self, k, v) ################################################ # org.mpris.MediaPlayer2.Player interface ################################################ @dbus.service.method(dbus_interface=MPRIS2_PLAYER_INTERFACE, in_signature='', out_signature='') def Next(self): self._player.Next() @dbus.service.method(dbus_interface=MPRIS2_PLAYER_INTERFACE, in_signature='', out_signature='') def Previous(self): self._player.Previous() @dbus.service.method(dbus_interface=MPRIS2_PLAYER_INTERFACE, in_signature='', out_signature='') def Pause(self): self._player.Pause() @dbus.service.method(dbus_interface=MPRIS2_PLAYER_INTERFACE, in_signature='', out_signature='') def Stop(self): self._player.Stop() @dbus.service.method(dbus_interface=MPRIS2_PLAYER_INTERFACE, in_signature='', out_signature='') def Play(self): self._player.Play() @dbus.service.method(dbus_interface=MPRIS2_PLAYER_INTERFACE, in_signature='x', out_signature='') def Seek(self, offset): self._player.Seek(offset) @dbus.service.method(dbus_interface=MPRIS2_PLAYER_INTERFACE, in_signature='ox', out_signature='') def SetPosition(self, trackid, position): self._player.SetPosition(trackid, position) @dbus.service.method(dbus_interface=MPRIS2_PLAYER_INTERFACE, in_signature='', out_signature='') def PlayPause(self): self._player.PlayPause() @dbus.service.method(dbus_interface=MPRIS2_PLAYER_INTERFACE, in_signature='s', out_signature='') def OpenUri(self, uri): self._player.OpenUri(uri) @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='s', writeable=False) def PlaybackStatus(self): return self._playback_status @PlaybackStatus.setter def PlaybackStatus(self, status): self._playback_status = status self._setup_timer_status(status) @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='s') def LoopStatus(self): return self._loop_status @LoopStatus.setter def LoopStatus(self, loop_status): self._loop_status = loop_status @LoopStatus.dbus_setter def LoopStatus(self, loop_status): self._player.Set(MPRIS2_PLAYER_INTERFACE, 'LoopStatus', loop_status) @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='d') def Rate(self): return self._player.Get(MPRIS2_PLAYER_INTERFACE, 'Rate') @Rate.setter def Rate(self, rate): pass @Rate.dbus_setter def Rate(self, rate): self._player.Set(MPRIS2_PLAYER_INTERFACE, 'Rate', rate) @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='b') def Shuffle(self): return self._shuffle @Shuffle.setter def Shuffle(self, shuffle): self._shuffle = shuffle @Shuffle.dbus_setter def Shuffle(self, shuffle): self._player.Set(MPRIS2_PLAYER_INTERFACE, 'Shuffle', shuffle) @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='a{sv}', writeable=False) def Metadata(self): return self._metadata @Metadata.setter def Metadata(self, metadata): self._metadata = metadata self._timer.time = 0 if not self._player else self._player.Get( MPRIS2_PLAYER_INTERFACE, 'Position') @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='d') def Volume(self): return self._player.Get(MPRIS2_PLAYER_INTERFACE, 'Volume') @Volume.setter def Volume(self, volume): pass @Volume.dbus_setter def Volume(self, volume): return self._player.Set(MPRIS2_PLAYER_INTERFACE, 'Volume', volume) @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='x') def Position(self): return self._timer.time * 1000 @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='d') def MinimumRate(self): return self._player.Get(MPRIS2_PLAYER_INTERFACE, 'MinimumRate') @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='d') def MaximumRate(self): return self._player.Get(MPRIS2_PLAYER_INTERFACE, 'MaximumRate') @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='b', writeable=False) def CanGoNext(self): return self._player.Get(MPRIS2_PLAYER_INTERFACE, 'CanGoNext') @CanGoNext.setter def CanGoNext(self, value): pass @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='b', writeable=False) def CanGoPrevious(self): return self._player.Get(MPRIS2_PLAYER_INTERFACE, 'CanGoPrevious') @CanGoPrevious.setter def CanGoPrevious(self, value): pass @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='b', writeable=False) def CanPlay(self): return self._player.Get(MPRIS2_PLAYER_INTERFACE, 'CanPlay') @CanPlay.setter def CanPlay(self, value): pass @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='b', writeable=False) def CanPause(self): return self._player.Get(MPRIS2_PLAYER_INTERFACE, 'CanPause') @CanPause.setter def CanPause(self, value): pass @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='b', writeable=False) def CanSeek(self): return self._player.Get(MPRIS2_PLAYER_INTERFACE, 'CanSeek') @CanSeek.setter def CanSeek(self, value): pass @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='b') def CanControl(self): return self._player.Get(MPRIS2_PLAYER_INTERFACE, 'CanControl') @dbus.service.signal(dbus_interface=MPRIS2_PLAYER_INTERFACE, signature='x') def Seeked(self, position): pass ################################################ # org.mpris.MediaPlayer2 interface ################################################ @dbus.service.method(dbus_interface=MPRIS2_ROOT_INTERFACE, in_signature='', out_signature='') def Raise(self): pass @dbus.service.method(dbus_interface=MPRIS2_ROOT_INTERFACE, in_signature='', out_signature='') def Quit(self): pass @dbus_property(dbus_interface=MPRIS2_ROOT_INTERFACE, type_signature='b') def CanQuit(self): return False @dbus_property(dbus_interface=MPRIS2_ROOT_INTERFACE, type_signature='b') def Fullscreen(self): return False @Fullscreen.dbus_setter def Fullscreen(self, value): pass @dbus_property(dbus_interface=MPRIS2_ROOT_INTERFACE, type_signature='b') def CanSetFullscreen(self): return False @dbus_property(dbus_interface=MPRIS2_ROOT_INTERFACE, type_signature='b') def CanRaise(self): return False @dbus_property(dbus_interface=MPRIS2_ROOT_INTERFACE, type_signature='b') def HasTrackList(self): return False @dbus_property(dbus_interface=MPRIS2_ROOT_INTERFACE, type_signature='s') def Identity(self): return PROGRAM_NAME @dbus_property(dbus_interface=MPRIS2_ROOT_INTERFACE, type_signature='s') def DesktopEntry(self): return 'osdlyrics' @dbus_property(dbus_interface=MPRIS2_ROOT_INTERFACE, type_signature='as') def SupportedUriSchemes(self): return dbus.Array(signature='s') @dbus_property(dbus_interface=MPRIS2_ROOT_INTERFACE, type_signature='as') def SupportedMimeTypes(self): return dbus.Array(signature='s') def test(): app = App('osdlyrics') mpris2_name = dbus.service.BusName('org.mpris.osdlyrics', app.connection) # noqa: F841 player_support = PlayerSupport(app.connection) # noqa: F841 app.run() if __name__ == '__main__': test() osdlyrics-0.5.5~rc1+dfsg1/data/000077500000000000000000000000001345713535600162655ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/data/.gitignore000066400000000000000000000000431345713535600202520ustar00rootroot00000000000000osdlyrics-daemon osdlyrics.desktop osdlyrics-0.5.5~rc1+dfsg1/data/Makefile.am000066400000000000000000000034541345713535600203270ustar00rootroot00000000000000trayicondir = ${datadir}/icons/hicolor/64x64/apps dist_trayicon_DATA = \ osdlyrics-trayicon.png guidir = @OL_GUIDIR@ dist_gui_DATA = \ dialogs.glade icondir = ${datadir}/icons/hicolor/64x64/apps dist_icon_DATA = osdlyrics.png animdir = ${datadir}/icons/hicolor/16x16/animations dist_anim_DATA = osdlyrics-loading.png osd_icondir = ${datadir}/icons/hicolor/scalable/actions dist_osd_icon_DATA = \ osdlyrics-osd-play.svg \ osdlyrics-osd-pause.svg \ osdlyrics-osd-stop.svg \ osdlyrics-osd-next.svg \ osdlyrics-osd-prev.svg \ osdlyrics-scroll-close.svg bgdir = ${datadir}/icons/hicolor/scalable/apps dist_bg_DATA = osdlyrics-osd-bg.svg defaulticondir = @OL_ICONDIR@ dist_defaulticon_DATA = osdlyrics.png #svgdir = ${datadir}/icons/hicolor/scalable/apps #svg_DATA = osdlyrics.svg bin_SCRIPTS = osdlyrics-daemon osdlyricsdaemondir = $(pkgdatadir)/setup servicedir = $(datadir)/dbus-1/services service_in_files = \ org.osdlyrics.Daemon.service.in \ org.osdlyrics.Config.service.in\ $(NULL) service_DATA = $(service_in_files:.service.in=.service) @INTLTOOL_DESKTOP_RULE@ desktopdir = $(datadir)/applications Desktop_in_files = osdlyrics.desktop.in desktop_DATA = $(Desktop_in_files:.desktop.in=.desktop) EXTRA_DIST = \ $(service_in_files) \ $(Desktop_in_files) \ osdlyrics-daemon.in \ $(NULL) osdlyrics-daemon: osdlyrics-daemon.in @sed -e "s|\@pkglibdir\@|$(pkglibdir)|" -e "s|\@PYTHON\@|$(PYTHON)|" $< > $@ org.osdlyrics.Daemon.service: org.osdlyrics.Daemon.service.in @sed -e "s|\@pkglibdir\@|$(pkglibdir)|" -e "s|\@PYTHON\@|$(PYTHON)|" $< > $@ org.osdlyrics.Config.service: org.osdlyrics.Config.service.in @sed -e "s|\@pkglibdir\@|$(pkglibdir)|" -e "s|\@PYTHON\@|$(PYTHON)|" $< > $@ CLEANFILES = \ org.osdlyrics.Daemon.service \ org.osdlyrics.Config.service \ $(bin_SCRIPTS) \ $(desktop_DATA) \ $(NULL) osdlyrics-0.5.5~rc1+dfsg1/data/dialogs.glade000066400000000000000000004220551345713535600207150ustar00rootroot00000000000000 5.0 0.1 1.0 1 0.01 0.10000000000000001 1 0.01 0.10000000000000001 1 1 0.01 0.10000000000000001 5 center-on-parent normal Copyright 2009-2011 The OSD Lyrics project. An OSD lyric show compatible with various media players and supports lyrics downloading. https://github.com/osdlyrics/osdlyrics https://github.com/osdlyrics/osdlyrics GPL v3 Tiger Soldier <tigersoldi@gmail.com> SarlmolApple <sarlmolapple@gmail.com> SimplyZhao <simplyzhao@gmail.com> translator-credits easy <leaeasy@gmail.com> True True 2 True end False end 0 Customize HTTP SOCKS4 SOCKS5 Always By lines 5 Preferences center-on-parent dialog True 2 True True True True True 1 0 5 Display mode: False False 0 True OS_D True True True False True 0 Sc_rolling True True True False True 1 1 False False 0 True 2 2 True list-startup-player 0 1 2 True 1 5 Auto la_unch player: True startup-player-cb GTK_FILL True 1 5 Pla_yer command: True startup-player 1 2 GTK_FILL True True â— 1 2 1 2 False 1 True True 1 5 False 0 Show _bubble notification True True False True True False 1 False 2 True _General True False True True True 1 5 _Font: True osd-font False 0 True True True True 1 0 True True 1 5 Out_line: True outline-width False 0 True True 2 • 1 adj-osd-outline False 1 1 True True 1 5 _Blur: True osd-blur-radius False 0 True 1 5 Weak False 1 True True adj-osd-blur-radius False True 2 True 1 5 Strong False 3 2 True True 1 5 Lyric lines: True False False 0 Si_ngle line True True False True True 1 _Double lines True True False True True True line-count-1 2 3 True True 1 5 Mode: True False False 0 Doc_k True True False True True True 1 Nor_mal True True False True True osd-window-mode-dock 2 4 True True False 0 T_ranslucent on mouse over True True False True 0.55000001192092896 True False 1 5 True 0 none True 12 True 2 2 True 1 5 F_irst line: True lrc-align-0 True GTK_FILL True True adj-lrc-align-0 right 1 2 True 1 5 _Second line: True lrc-align-1 True 1 2 GTK_FILL True True adj-lrc-align-1 right 1 2 1 2 True <b>Lyric _Alignment</b> True True lrc-align-0 False 6 True 0 none True 12 True True True False 5 Color _Theme: True color-theme False 0 True False list-color-scheme 0 0 1 C_ustomize colors True False True False True True True False 2 0 True 2 7 True 1 5 Acti_ve lyrics: True True True True #666626260000 2 3 True top 1 2 True middle 3 4 GTK_FILL True True True #ffffffff0000 4 5 True bottom 5 6 True True True #ffff80800000 6 7 True 1 5 Inactiv_e lyrics: True 1 2 True top 1 2 1 2 True True True #9999ffffffff 2 3 1 2 True middle 3 4 1 2 True True True #00000000ffff 4 5 1 2 True bottom 5 6 1 2 True True True #9999ffffffff 6 7 1 2 1 True <b>Lyric _Color</b> True True color-theme False 7 1 True _OSD True 1 False True True 0 none True 12 True True True 1 5 _Font: True scroll-font False 0 True True True 1 False 0 True True 1 5 Ac_tive color: True scroll-active-lrc-color False 0 True True True #000000000000 False 1 False 1 True True 1 5 _Inactive color: True scroll-inactive-lrc-color False 0 True True True #000000000000 False 1 False 2 True True 1 5 Scroll _mode: True scroll-scroll-mode False 1 True list-scroll-mode 0 False 2 False 3 True <b>Lyrics</b> True False 0 True 0 none True 12 True True True 1 5 Colo_r: True scroll-bg-color False 0 True True True #000000000000 False 1 False 0 True 2 2 True 1 5 O_pacity: True scroll-opacity GTK_FILL GTK_FILL True True adj-scroll-opacity False bottom 1 2 True True 0 Transparent 0 True 1 Opaque 1 1 2 1 2 1 True <b>Background</b> True False 1 2 True _Scrolling True 2 False True True 0 none True 12 True True True automatic automatic 100 True True False 0 True True True â— 0 True True True True True none True gtk-directory 0 True True True none True True gtk-add 1 False 1 False 1 True 0 A single "%" means the directory where the playing music is. False 2 True <b>_Path</b> True True 0 True 0 none True 12 True True True automatic automatic 100 True True False 0 True True True %t - Title %p - Artist %a - Album %n - Track number %f - File name (without extension) â— 0 True True True True none True gtk-edit 0 True True True none True gtk-add 1 False 1 False 1 True 0 False 2 True <b>_Filename</b> True True False 1 3 True _Lyric Location True 3 False True True 0 none True 12 True True True 0 Lyric _sites: True download-engine False False 0 True False True 1 False False 0 Do_wnload the first candidate True True False True True False 1 True <b>Download</b> True False 0 True 0 none True 12 True No _proxy True True False True 0 True True False 0 Au_to-detect system settings True True False True True True proxy-no 1 Use _manual settings True True False True 0 True proxy-no False 2 True 12 True True True 1 5 _Host: True proxy-host False 0 True True • False 1 True 5 Po_rt: True False 2 True True 5 • adj-proxy-port True True True False 3 False 0 True True 1 5 _Type: True proxy-type False 0 True 0 none 0 True list-proxy-type 0 1 1 True True 1 5 _Username: True False 0 True True • 1 2 True 2 2 True 1 5 Passwo_rd: True proxy-passwd GTK_FILL True True • 1 2 Show passwor_d True True False True True 1 2 1 2 3 False 3 True <b>Proxy</b> True False 1 4 True _Network True 4 False 2 True end gtk-close True True True True False False 0 gtk-about True True True True False False 2 True False end 0 option-close option-aboug True True Scrolling _mode True True OSD _mode True True If locked, you can't move OSD window by mouse _Lock OSD position True True Hide the OSD Window _Hide OSD True True _Search lyric... True Search lyrics from Internet True download-image False _Assign lyric... True Assign local LRC file to current music True _No lyric True Don't assign lyric to this music True Lyric delay - True Adjust lyrics delay by -0.2 s Lyric delay + True Adjust lyric delay by +0.2 s True gtk-media-play True True True accelgroup1 gtk-media-pause True True True accelgroup1 gtk-media-stop True True True accelgroup1 gtk-media-previous True True True accelgroup1 gtk-media-next True True True accelgroup1 True gtk-preferences True True True accelgroup1 gtk-quit True True True accelgroup1 300 200 5 Choose LRC file to download normal True 2 True True automatic automatic True True vertical 1 D_on't ask me again True True False If there are more than one lrc files matched with the search condition, download the first one without prompting the user. True True False 2 True end _Download True True True True False False 0 gtk-cancel True True True True False False 1 False end 0 lrc-download lrc-cancel True True The title of the current track %t - Title True True The artist of the current track %p - Artist True True The title of album of the current track %a - Album True True The number of the current track %n - Track number True True The filename of the current track %f - Filename True True gtk-find 1 10 1 3 1024 10000 1 10 300 5 Search lyrics normal True 2 True 3 3 True 1 5 _Title: True search-title GTK_FILL True 1 5 _Artist: True search-artist 1 2 GTK_FILL True True â— 1 2 True True â— 1 2 1 2 _Search True True True search-image True 2 3 3 GTK_FILL True 1 0 5 _From: True 2 3 True False True 1 2 2 3 False 1 True osd-lyrics-loading False 0 True 0 1 False 2 True True automatic automatic 150 True True vertical 3 True end _Download True True True True False False 0 gtk-close True True True True False False 1 False end 0 search-download search-close True gtk-find True gtk-go-back 1 True gtk-go-forward 1 True gtk-remove 1 True gtk-open 1 1080 1 65535 1 10 osdlyrics-0.5.5~rc1+dfsg1/data/icons/000077500000000000000000000000001345713535600174005ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/data/icons/osdlyric/000077500000000000000000000000001345713535600212305ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/data/icons/osdlyric/128x128/000077500000000000000000000000001345713535600221655ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/data/icons/osdlyric/128x128/osdlyric.png000077500000000000000000001250551345713535600245360ustar00rootroot00000000000000‰PNG  IHDR€€´9Q] pHYs  šœgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅF©£IDATxÚbùÿÿ?% €Xal& fe``ÿÉÀð,°6UŒÁUùCÙ·†Tîà z–‡XDYþœöf`‰ÝÁpúÚc€bA2 d€ÎÿÿÿÎì-´ý$Íwžïן¿ >³2ä2/d`úÊÂð䢻‚ë®]È. dä‹_ûϰõоöé_ìàe`aff`Í4ŸùÛÝ·v[0¨‰+‚-.-Ã@,P›%X˜‹§Çx_ß{–áÝë¿ ·}Äê1üùÁVÁ±‹‹é;ßÙv s¢¼Ó €‰2±c¼'C@ éE¾ƒWd ¹NozÍ óŒ›Aòï•¢ L¬œœ ÿÎù|Ÿ·‚‘±2 Ls·ÿ½>É”AJ“™áÑ‹— >0$ÿ”g8ÆÆ$xrõ[7KáßÌ| ßgàädcø?S‚2à/?SUbâçhüÇð&“‡Áˆï3Ëÿ² ¯9þó©]`äïÞ¿¡ø‡Š²<§€Ð;Ì ç®\/ºäÌÐ@ d€°½}ïÿS¦ÕŒ&LZf Ògö2(rÜbðØÈÌ8ÉK‰K`ƒtØ<î[½ L@/°°°0ܸÂð©ñ<@ PbáŸs˜üßìÿÏ ój7ÃÏ Š< qVB bl /|gPÜÊ !ÆÀøíÃ_ §#Ž0¬ýbkÀ@ Þ30üeOa|ûÊù¿ð_ ¿”„d´%”¯}gXþôƒ#¿ ë‹W:L`°ÿZÍ ,.Ìðõ78 dÀ ^ K>Q1fÏ'×ÿ2üÙø–AÂN“ÁN“áË «o{öì%@±PZryÊÿÿç´fdü~Äý¿Y”áéÅ @71|ÔÌe<ÒÃðY3‡A-28 —oØÉ@ÈÙ˜"™‰êûî[õ ?~c¸rò0åñ³ô_†ÏO1ˆ³s5#Š f0Ãþÿòýý  o2€"ìL@åÌÀ´ÏÄ Æð’á§ŒÌ5ÑËp™˜Yˆ¢™A ëK]ÄôÔŸ1ü~+ÌÀþ€ÁRŸ‡“RXÍ``oß<û/õ·‰…Ío€IK$ýÝÈÄkõ’aç F†5šÏ:–J1°©½døò󃘂.Ãÿ?@ï •@­ÿoÚ¯¿Å8;PY” € ÙùŸèÊív îÒ‡Ô¹õÞ3ˆÙË2üQÕcP‹Ú{di䓨7Ž §O20‹¸Ëx ÕÀÞ pRúæÕý‹_õÝ ^î`xù‡“¡üÅE†„½* W–†Û,œ²ò¾Ñ•ŠlÜ" ¬ÌÀˆÏy‹ð¯ 2@óÿÖ"FÆ ß‹3í9ÿü`fPÿ÷“Á'"Ÿy^ºÊ•Y Èùl 7P3;'ÃÛYÿõžÝ½p €@X0løÇð¹þ?ç¡=ÇŒ>ÿebzô¥ CØžË íöª \\Û~J‡Cú(á ²³0ˆÉ0^9zé!@ øÎøçÐÿDF;ÃGßþó9K¼ƒÙ½Ï ɶ@ç±33ܹ ,¶_´2¼óìb~½‘¡uõ)A ¾Vºr 2`Ë3Îg|jÄ úh€$/ƒÞ_f†÷?uÄý ­+Ï3xyú3Çù0\YPv¿vÃG`’e8v÷@A¾ÿéýýýäüƒ%ߤûõüm¼‘OEýúú€Z1–¤Ì ÖAx!ʤONLû‡‡ü”$9͘‡‘á?°x ŒT 7møÜÀXçòÄûÎxñw; pkk3<}ò’ãã§ÃAÞ_”)•é cÞß+ÿ t”øÊO2f111~¦cN`E"´ÀôËã :G¸³L‰XÍÎ_Í¡Ë4õÂíÛ·T|za;0z¾2]øwè¢ûÀûhè3 þ´@ ˆ_ñ¥w#âÿyP*<JHÄ‚#›3Ak¬ÿŸñ2ðIþbàäøÉðã'Lš“áa«ƒBÍC†+%Ì \ü¼ =¿*ÿLgxɯÇÀ!eÏ &ßÂðz)ƒdÁ†[; ”„nƒ›àLÌJ§®>b¸{û:@as(£ËQPøÍ“úÊ Œ°_@Ë3œ…Âuþ¨r¾gþþýÃÙj)†Œÿ¾ÿfaHcXÇp…Í”Aœá— 0êö‚,ad$\ 6ð±.0,þÿgêþüâ?ÃÃe\ ï{XÿϹü™AMø ÃûÇœ@W22°33‚Š`8ýûJáóÆ=Œ–0ܼ჆3°Ù1‡‘‘™“£å †@!;T, ¥äŸ=c8,%õgÛ•eÜ l¿¾1üüù™aC‰6ƒ6³õML /þ~ef`ZúìS fQf`Pý:Šù?Ðpæ ¼ŒÏ^oêúÏ&fÍÀò|² XŒÔ¥þì:z €`í#P™dªÕ–ÿo|ÿ—áJ½¡×ÂåŸô¥2¼þÎÈpÈç*ƒÜc†_ïß1|l2cj¸ÀÀü‡\¸€ð”5AÍÆÿÀRä 6 ø†@šïó%†Š~ÿIš˜eT…^¹ €X Xˆõ€F84w2~hÒbe¿ÈnúŸáù'&†o˜ŒØþ2œˆ8Ëðù ÎUAI†­›ýûþø7#3/Ëm6V e†ÿ@U 3;7ë—/žœ9ÌpªY`à”ñ²Xx0¼â‡@ü¡, qÀÔWoÁðçÃ`ù Ì‚Àòè#ÃýßÿMyJ¦ÝùßçĮ́!ænäÍÆÍ¤Â 9ùhƒÛoeçoçÅX8ø˜€…0«# uÌ@š‡—5ßΕq:ˆrý>À©ä f–ï ~ÿ=>0þû⣩ýíœcŒîØ3̾ø‹!Šñ8ÐD`¼nz÷…ÁÏç/ã¾KþÊñ1^ÿû€APô6ÃÅ¥)Ö_¿I2ÈýØþ÷ÓÇ×Àê‹•™… Ô2F¸^äÚùõÎ÷€Ëµÿü}uàä-Hüÿ5ž¿‚E©8ôÿu[ï~ïgb}ÍzüÀ¶ØmºßÀ”k/þ†áþ{†ó¿¿0JbgüÍÉðíÍ_†¯_3ˆ+K3|öfþ'úƒAàéjFvP)j¤ƒêTF~~Þç§›©02œ½ýŠùé“'ˆšíAÏÏÈõçêÿ^fí××€´Çÿx¿=fHZþ“T{ýeà—çcV“eøñä Ã/é¿ Oþ~c¸´ÜI–‡áé•_ ~¾f^ÎÀ#åÌð[5ŽAèÑB†_lâ  ¶;+ ÃÑ»®8Î`V.¿™UÅ@I– €@¸Í‚ì@G<`,þóèlÉVÿ_•ˆÿ J·Ç‘ëþwµ¿Äx¹Ä_~g¸ñâ;0Ž1,xø!_IˆáÆÚs ¿%ŠŽ_g¸pó)û£¥À2óŸ’Ñ…kæ;.fN‚$9†¿Wï½eøÌÈÊ@ ¼€4’žB0£ã¾TfóÏ/þ1ü¹÷áÐÿ@é%?ó]É5n œÏ?0ðqñÂÂpûÕN®Ÿ ;_>`øaçØÞÈpx÷n†v0c¸óúCH| £Àoc !g™ÀõÿÆu;Žþåÿ@,†††¬7oÞäùùó§äßÿä5€®£6#óóG@·Cà;ý),¤U€µ€ÿî3À0ÐfvÕ`àQf`¦w¿îØòßeÑ–_@ ØÕµôMaà“g˜Èôÿ‡×:‡@=ùsŠÊª¬ììl¿l-,¿ë›ÿ –{÷îqüùóÔ–Fµ ЮŒ>üf¸̘ÿ™  °Ä•d`EÆRFÆâ—ß1°_>ÎAÀ:ã//¤,a”ær`Œ‘Ù/&F0”å=³ò †+\ º{€¢úå³§\\\Üÿ.^»ÆqïñãßĬk9ùô~Ä'€VÛÀèYúh00‚ÄÀv"Ã]&fÆO,,ŒŸ€uÄm`Uûœ JK¨W Xð(üûϨøóçwÙÍK—þÞnzŸø­lÉ#*-}Œ+}—ý¯coû}¨aÙfíhż|ùò_lÆ3ÿüýû7++ë}`H€Úð瀆 }ÁjqÙ¿A–ÍúêHÅ@ô7 9î7PÔþçò~ýþ} Èš)rÐ PÏ„éÞ‰¯Oî~9z„ñùáÿÿ =Æ+ý]±b@QÜ? rÿKƒ„è¾ßÀJå'Ф¦¶Ãu`ƒ¾´32¼|ÅÄp]ОÁèÇ>Pbøø ˜`cÿ3|»¿‚QS–ãJ°Áÿ ØØ»Ê³oͶ _}V€ÂÕ a„ ~üø÷ûß ÔÊœ…áÞë_ ÿK›-¿®ÿ ÌëŸ |&¸FFB €X°øœZ:[¿^|ÿìiýçUeû·:?Y­ÙU“«†ëçs†+w¾3üú tã6†_¼ü Âß1\d³~dòç¨< . tüƒ6Å€]ñ“‡™.rsÿcèH•·\Tüo•¢ü-†7÷YÞó$È¿¬L JÔþÉÀÁø…A”Qî‡Êÿü œÀæ)»ìjH» ·‹ˆ-è^væ€EÎ_+3F¡Ÿoþ0ø¾,ÜxóCûw†—÷x€-¡ÿ`sA ƒ‰‰bÐAlÿ¾2H0~©Vÿ…/„ÊÝ{Š-:{ MÄ‚ôÿ ÁÎÀÇÊÀ$,±žË苬 ßÕªîz†/ÑøEþ3üüÎiŽÚ„ÿ¡M2ÌÎ ðç ÃO½'—™8+éxîccâP‹íç/€bA PK˜ȵúõŸ¹Ÿ‘QôÅÛ«/>>ed˜|ç5ƒ°|›Àð«ìƒ;;°-øj93´MÈîyØÌ@1<'ó†_"Š{__Ê(ÞL.iˆ×Ô1ø³táüŸ Pî&k&#`GeJ€ë¯KëõŽîËÀúó7Ðißþ0Üys¯Ë¿í"Ë'.P00A-·­\F°åÿ€µ$8z8¾10h‡³ôJÆ?8€²ÀÐû«(ÌÂ0yÞšÿk-gкќáÑ?†êiÙŸl öp3hI¾gFÎnž· ž_Dd˜Þ0œ«çapnfR`T0±0}ð÷ï?pºµ’Y€ U`…I#ÿ€Žz{‰áÿE«SŒ,ŒfÀPÊ«“u?CZI@±@Ú  "Sèd5`ù¢Æù”éåÇW ¡ú¿.¿¶ÿ¿3ÃÇmÀçÕ 1†ÛÀ†¦Øï Ÿ?úÇÏ/ÀÄ,$ÇÀÃÊÄð"ÿ9„옿Ì@Ïÿæì§ê¦Œ¢w@¡5VúÎê©b Xà€6ÍÙæÍû÷+Ö€‰X'ýf71Öÿ ·U­2û †MÏêôž¿eáW`X´æî;A¶ÿ/>0Ió|`SSWeáà¶@Å'7°•ôáá£û ·n¯cø7ý£cÃä\@,ÐÔªÙ€‘õïWò|†{fõŠ Ì÷„€ê>ö˜7þUØ[;½—ß2ä ð2|‘}ÁPQ/Äæ·ç¢…èSîdsfÎ[·yÍœ˜~ýÆp÷!°óòë°°úÏÀÁÎÊÀÉÉÁðð®Øf†Û(y €@ÕjÀfÃ+``|ŸTõ_E§öÃæGž3 ⬌ _ؾ›ä_~K1ð12¼zñ÷?óÆ“çŽ3üÛ®/œxðν—gß¾üÀÀÈ-z‰ÿûofN`ßd1h¬Ø,ÿ lõýãa¸Su››6ZûŸ €²!ÓÏg7™í¤Ô¿Èmý!ãfùŸaÖ!#†hÎs ’À¸ý l’3q 0|xó…aÒÛ‡ÿ+yU¿+Ö“×0ܦ2aÚçžßúÆÉÆÈÿ•›ØJµþ˜ C×À’ƒ è G—د? Ì?!n hþ +‹Kªëïkñ3p³œeÈuñdX·[šÁìßSQF†;Ÿ0h«?f¸wû?#/ÃógŸ€Ùí7Ã}æ] ¹¹]3°rr¿cøý‹<¼ ê; –9°dbàvÓ¾Oû ÁÇÀþ,0eÿÿÇ@°¡.þè(Æ`H°0|àùÀð èR†¿|b ܯž2S²ÓC†[À(1abÜóàÝ-nÆOÿ0°=ÊðˆuCRh÷Û·çxߟÖQì@_3FËÁE4hÿïï7ÀFÝ{.Žß„üùîžÈ² r@]X—@KD¾7ÿÁµCû3>zì0k(`cèb!v`¯—AˆaþµGŒ |tž¾þÃÀÎÍÎÀ,¤˜X%>rÛ3ˆ|9t/8øAƒsL ž3Íl`Úª }[»ëÄOv`[ó/@A;¦ÿêz/Öz0Iþ™È¬÷ææ_`ã‹ãù3`ùó”¯ÿ­ýÏpŸEœAaÖJü WŸ}eáå`øøìÐ6 Ë/1ps3|2+c½<‹‘•hÉp6c&¼÷WŽôƒ|ìfñ÷ÊÃO ïÞ½a X"Ö€Œ?*üÔðê?çÏÀ*ñç/`6‚Ô¨\Ï€}ÿP á÷ÿ>ýùΠð—‘aÝã— M&Ê ÷?þcøøäˆß ¢Z÷ÞÞ–`Ödßþq€»f ª›[B˜aÝf¾ ` ƒƒYGžLÈï åÀ?Á¦ÿ—Þ3è ]ý¨î7°}ÌJ/ù´Ô”AàËO†¿Àrþ°·ò XÄZ+13”^øÎÐcì=¿gbàåcx}j=‡6ý‹ ÖׂÀ©ŠŸ‡áȺµ7ãV[Ùpp ÕÄí–ß„Œ»ÿgZËxnž:ƒV ?(§üE0Á²ór20¬ú~{ð¿‚wþüdpùÊ0õ&/C²Ä'†k«n1h4t0¸&•3,\ºëÇs†¿\Ò ï?|f(>,¬`ךWÞ00Ü¿s› €X ]²3Ðþ!¨AÂú]€•‘‰÷/ÃßWÀîÐŒÀŒÊºï.»£ë·Ÿ R‚LÀPøËð(ùøý¡Ï î23ìxôŸáèñ“ Õå õ­ G6w0(Š`hÙÉàxê ¸À[~Ø4øøñ#8ˆZ >€VH2 ª,ÛÿìÏÿgøõœˆAà O¤CÍ?aàegfâ`ø ‘?ÀŽã `àãüÂàähŰfÆU §3¼}ûŽCÁá´Ü»ïÓ[„ µº@Y“íõ½ë?¹…Dþ¸Lääädö 9ÿýûÏ\æÎ¨öããð¬Ô?èTˆfFеÄõ Ÿ"…ÿ9ED1‰<~ÌÛϘþü¦f`9ÇÉÀÁt“¡³³‘! ,ŽáöÃW Ò‚îõ;pÞúøßÒ@‚ó¬…µ «¢´ä·óOŸ3ÿ•Ö3" Ä"%%ÅùúõkP;Pˆ’ì˜Ø¿ßþwåÐØÀ`J–? & Þ_ÏÛ³¬=“¥ô$8øy?ÿag¸ñè÷ÿ+ÿýöRdbstrbX7»‘áõ§K â ŸwO<öíûÆãÇŽñó?‹ ;Ç÷ŽÞn†ïß¾1 0.Ø€ñÊâ "Y£îï‡ÿÙÌ_!SPxtˆ(0uœùÌô 4†,ã߯¾ÄÀÄÀüJ–œÐV÷ÕžÖMŽò˾a–usdøóá;ËMïÀíë7¼5F9ùx8÷îÝ÷ €@=#À³‘ Æ °*ÿlc °F—€ `SX ºª—{€m?.†ÿ¸e;¨OI¢ ‚в„”Ènž9ü^ÕÝ_äüŽ Îþ S²2\XpX±ýé{°Àhgœ1c3@±üýûØrbu2AåÁ`2yÍØõçÏBéÿŠq2@ÞoHiÁu©_™ßK¦¿Ð(´+ tÌWP?4|¤9;¾8›o'a©ÔÍ »U!~‹ãñ6á5ŒìÿÙ„€j@Éê@@À?€ •ƒ úÄÀÅ2°1L¥ßãŸ2>‹ Î:ï ƒŠÿ?²°0uÝ|†Vè¬@}B@Ë?€Ä„þ£Øû'ùÿï6ã¾M/'²üúÛù[öðÃC ±·mþo±¼ÆÈÅÉÅ¡  ðWMMí@;§,,,Ÿ]ôg@/, ÈG  ýôñ+ U Fý{%@ƒ0ÈBИ#($¾éÏ@3¾|úôTÇ~σÍéÿ÷ïËã%/@Ãäg°¿úéŰfóšŸ?þ¯¨¨È@ ø ´ì'ÈP }H¿ÒÀÚ‹HÿÖë€ñJ`Ë@­(Ð(_ƒ‚¨ötüà ÿ¨æ%9@è? ƒ™o‚?þÿücÙÚe ÐЀðñ„k|Ûx#tÖÖÛ&d^`¿ˆIXZ]E##0Ë;?~b3‚ƒAYM”a]Øý–'pѹ Z ôÅÇð÷íW†^Þ\–Ë †?Ž-ýÉðœß”Aíãi†Gš% ü'{ÄÃ~0°{†Ïç12ð;.gàÒeøþëÃãý+TEoûGÌ ÿ><…À;ÝH~MÔãNjÒ| §n>e¸wó:Ó“‡Yˆ©c‚f v¨çA™ ”ÏW,ý·0<ŠY`׎_9™9™r,¥¬–µ|¶¿ß2|6¿}¶Ÿ¾d¸ìH»ùoax ØÑÑþ$Á ^bÉpRö0ƒu>°Ïö…˜€E$h9(©320ƒ<*@¾þ) 1™õõçþ3Bâä?x8˜ä!Íu&`;”lßo1üâUÚÇÊãõ‘íó6Æ2 )MH1ËÈL¿`~ük¦.ÍpîÖó¿OŸ¾ü @ÈÀMîüÐ)\PWÄÊÝÑ3†¹LN 3Xþåax•zp‘AX†>˜*ËpÔê!ƒG7ǯ,ÁÌô<°úØùáööX™ÿS ¨übúÏòZ€Õ2@‚:Ê*/YXþÇ@¾3|xpžA@)ñÍÿ¾ù²ŒEOºê fª«kpÞ¸qý/¶F@± ÎC§®Á¥=0þƒI¦mÚ¿O­1À߆?¯ÿ3p2>cpUüÇðMê?Ãk`¬¿þ ŒkP¿ Økø ì<‰3þfÐæaf¸évŸáîu†Ê ÿ0ü4yËÀÌÉ`e§Ë°|å#þççÿÊr3€ZõÿA}}`áÏÀÃÆÀÈt‰ˆ°‡ $'°·LþÀ ¤àü)þ3@<àÌlÀ¥6!ð “w÷¯3pY=~ßz_°úç›7o€F?ÿþú•á+ÿ%HiÌ@°5™ÿ¡ôO( -AË™?0Zýy|¡NPV_ì=ÇŸ¬ÿ˜„Aƒ~œÀÒØga ¾ý *¨~2(ƒQ‘‘á¶É†YRju5„Y^?ÿÂpnÇkmFwæe‡þü{ûìÁ;æ»t%¸þüefº LM·Þ`aýÀ¦"ÏÅÌ+¡ÄÀÎÁÅÀü˜ €µ;¨hbV»ß>ýÄðö͆ï U˜ÀÔñû7¨ÛðXÕ²2ügãg0Ub[¤i¤pýÜÍÿ¸Z¼K 0u¿€ &aP 9Z3ûþçÓ6`8w–á Ã#þk Âì¾ýƒTOlÀn);0Ÿr€zÏÀzû²~D¢’ ½ÇÞ1˜qr18iq“ö?`U ª"ß3¤ºË2±±;‰ì½jÿÓ¶õøy†ï×_YH3¨0 ÝþÏÈ|ëÝ7nwýûœ¼R ¿|bøýý;0þ‚†ýÿƒ†û@Ã~@ûÙ˜Á';+PüÃïÿì Fõ7þ—ÃïJ0g²¾`à­zÆ €‚¥€Ðå#ß!ÓÃLÀ®¨ ÈȽÿè_†ýGD=.g0JùÁ }‹‡áã^'†u7¿3ˆüºÁ`Íñž´¦Øbà:ŒÜdýî?20)3ÿĸþ3ìvwßÞþ­-ÃôéçÆO?Yn~{ÄÀ+þ’Á\C“ýçfo‹ßß Ú®>xû~ãW>.–—?þ_{òI•á=û÷ÿ ÿØØ€æ1çÉ  *ÐÈ/¸™ Ì* ÈÅ>+ƒO\¼< g¯óþyïÿ\&¦ßàÂìÓÿ @°IX£‡| …°ÿÿ³„­YÁð*;òï—þ8K‘öéçØ%?0°K\eˆà×a¸óRŸaÞíï ÞŒçDÿÿæÉ¿àB ØGeàd¶œyÎÜ Y~`0Ug`8~ê-“×qF6v†_¾0ü¶þÿÿÃðãói†‚÷Ø ÖØ*|ünÎ0yɆw 7N}gû÷Ÿ“Gœgÿþú iÉ1Ag¤ st TðÚÂcc„´ð@…*h.ŽëåÕ?-Ÿؾ>bx"¨YÿÎôÿÀ@°ÒŸZõà ÿužHP$†Fþ?ò8øø®ô—Ç,¬œÀ¨ýýô%ÃW.MuU^†«ï~0}£Âàñÿ&ƒÀÿà˜``eæÙ/ ÏŸ\b0‘øËð›Xh“¯ðÆŒƒwþn ÐbÀøêÕ ¿þ1püâV‹Ÿž`xùó.·1Ci\Э 2Â@'Þhe`Vã,À| ªAÉ2/šüö8hhúÎ÷Øe{ ´èÁ_>`eÁò‹áÙÏo ÂJEprdÿÍ®J@mþ_,Áô{Ð’`Àh2q1üàÿϾã6C·­ƒ0%¼ùÍðíí>V†OÀ–"ó§ R_~±+1Èpj0üûÉÇðPy"ƒìýl`•÷ XÝ ‚D°ÉQP]ÀÎ Ìð†x2„ جùõ‘ãÃ9­•ÏÊZÛ…~²ÈK¾~ýøéŸŸ¿~Ã*D€‚€ƒÊB&‘¬‚ÿïÒ|þË1|ebø½š)æÉý ²ÀÞù‹ _󰔨~ô0Ãá7L î_Þ23ü`­^ý ª €™ì/ÐÓ< ñLPSØ ì>y&ÄÀÅÉÆ +ý‹áÌão }çž3lñQe8þŽ‘áÉ;P0ÿWwì¼\ ¿¾¿– Ÿ¸„¾20‚óÓK†Ç2å êÜ÷˜^œaø÷ XÚƒæÇY ÙÒ±a‚Ž{ÿ¿0?á­‡néšsŸ]ØØÁ°pZÇ;1‰wמNÞÇt{ò¾rÿ+˜ÄD-•oÿ3«^`LëÛ_ j¼ƒ”ì@Íx“´˜B¾Ùˆ2|w—¶õÿ1vk?þeàÆ0}6œØ˜ÿ2ps²2(ÿgè½ó™!NšŸA ôO€)ëïOV†¯¾1üúÅÌÀó‡›áÛ§7 ¼ßÙê ïw’ ·5 $%ž3<ÛÌÀôó°Ï ®w˜ {hÄ…^ݽÃðöÁ½ à ›:zÑGˆ@@ xÔd… 0Eü-PºÃØñï9¨u8ÏA,Qê?ó` ¼ÿ É,à™ø? ðª 5ßKL˜ÙÙ¸¿ë°ß ™QFN1 Ÿ“ƒïÛo†×À{ÿÛÀÅ ìZËÿbXÿæ3ÏG†c˜^ýà`øñ XŸ¿¾ÅÀlaýþÆÊ ßÎ ªžnˆºpAŸùƒô¿“à"P°Þç”g8ºi/P߇P§ÕŒk@=´`ø‡\÷zü!0…¸ðˆ €X Ó_ àñfèLÍkÈn%p¹ ^ ÌIç>Ôg?kË(ÄÃZ—ôÜš·(¡u*hqËÆ; ìÙÆÀŽ ;ÓçŸ@›YA+Ÿx•,70V9þ2ðSçç? oü6MÙô…€Uáÿ wX€žed`¬¶~2ùÅ3¨gOføöñ=ÑG÷<50ðr±1ÜcûÍðXÔþÇÀêÊi2|zò”áȲµç§ŸdŒX™ýdúÑöÅ6¨'tlÏI†ó÷>‚³ÌÏŸ?Ÿ «À:aUÈ$>¤S¤E¯¥1 ñ~ùÏðæñ?ðÇ¿ßX…48€úŒ—?0|÷ÙËÀ×­ÇÀ.l›=úÔ.` Þ°3°3ÿfàûžÏ¿Þk÷ß@ó_ôDþ0¼{ûŠaÁ3^†S÷À¿óâQ†µ‹§3¬÷añŠÍ 6ê 2ÌújÄ l…}ÚÜqµ{ʹ ¯O@<ý“ 9ÖBZu ^þ6›0p|yù?x- @Á– ü†¦Pþ`­ÁM³>°PÿçÄÌÆ$ñ§žY„áù_†—oAmn ¦Ÿ ŒjžÂš 4[£lNV]bx.ûO7Û \B?~ ŒÉ× ì,ÿx€ín`aJ ‚À2âí`U ÌŸ€Á,"É`Åû!ÍŽ‡áïǯ gž³2xðƒÁÒÑ¡£©‘!Ü߉!¯´ÁÞ΂ÁÞÒ„áþ• ŸzkO>·È(#&ñÄ—7wøøTTU™øxyÿ¯\µ XfÈ2¬Úw‰›—Uî?ï½·/_ÿýÇ L¥Z;Êøüùs&`/‰óÏŸ?ü eœÀêHŒ4³ñïŸ"¨f8XÊ(Æìô|üÂþ|¿ A žI…²Ms NùÙÍy"ÄËÀ¤Æ è¨ÎÀb¤¥È ,#à ¥,õx@}Y†oÀŒýâãO†·Ÿ0¼ûøáÝ»ï ¯>|bø ,ßý|ößÓÊñÄ…‹ mííà@>ûø%C_Ž+ƒ °>”QgaüÊðcQÀÔ{ÇnÄ0 0Z½~õú߃‡xeä• Œ ÿ}xóö—‹¶Ð¯ió—^ÓU–d°±µgˆ‰aPQR` –—/_²ÿøñƒˆyùD˜7@c2Àd+ ísl»ÿÿ·5+ +´Ê…&.P²ùõ( ‰Ô¿ $Å)€M&ðÊm úï>ÿÿ?ç,ל³ YÀûÀj÷>x 0¬8€ 6fðÔ7h–•˜-,˜à–޲&ÇµÏ ì·-û÷:4ƒIÒ€›‘ñÐéŸ@¥?x·ÐÃ@Ï‚6b‚RÄ/hÍ쵂×RxÜbX Û €d­õ6] µ (X@ į aÿŸÐî7#¸5ç¹!ÃïìÀHù{÷ú…?Ú|ŒŸÄõYú ñ«Šð2Ü:r‰MB„AÅ@Aèå†×À>ëĆ#ŒsÌŸˆ àÞ6Ù~ÿþ ò4hZ¼X€)þÏ‚ £££´x´z™’ýÿ-„u‹#“ÈþÏðÖpÆŸÿBœ Boý™8ù¿ÛöÀ"´ ´ûT±£ ØÐcP¦ÞKLí_~þ´‘‰ñÿ+øDbì‘X ±Á¯ÝÁŽ´ƒF4i t0È9¡ÁÄðòâïø9¬oÜ"}¸ÛÔ¹8ÿ¾yÏðüÚ#†ºÌ Åî3¼ºt!B?Á2E7sýÞcÉÀâ÷³ h ó  MÐ\$Û? ýïñãÇ Äò<žXM4-Í|‚6&‚ÀÐîF ‡@)ý(k2®—÷ G±¨p3°…ñ0pˆW0Üßø…á°Zul;€Öü2ügabüôïßÐä'h´â/¤Ñöï?h­'Âó\ Ä  Q`°ó°°± 232‰cèP`€Ú)ÿÿÿfÙµ|ýû]ÀrV—ó‡Á6~E6V 1^n`‹sí¶¥ rr IY×\’vì¾¢ý0ÿã5fVV`ßá?0ü{ðàéS§¤¤¤þ Øaà5‡a3®oþ‚ ‚Û ÿ?-­m­ãíÇ´õyØ`kû Öó \‹0ÿÿ ÌØ_æ€R (V}ZúZ^þMÆCüt<’äqFHj€xÈÿÿ÷³(£Àß¿ÿ…¡«@ò\€`øõàç—Ï 'ÿÿ|qòû»› q03 X±K\~!Á'ÄÎ ¬ÌõYÐ÷e\GRTåԾʩȲ,_¾ü_ddä¿/^0œ={– €X­CÆ_ Ðzþ?t¹úG šfæÒ<ÐùrPÌs2" |`·áÿo(û+ÐÓ ˜þ ¢A3¥ þd=È<T hpLS3h#ØCÿÙ¡©”2@±Îùó篷PO hhÙÄ §&èPþôÇßÏ_þy ØO¼ûŽá×ûß A+̰pÿe8zúCº·À@`a@µ®Â@jÔþôþiP¡b÷Å„DLÚîÖ%Óܽ fŽM3#]¢c-œUPkxè bºmÆ‹e0ÝàT¼ðÍt ÷I" ¯–üŒ"þñÜ‚ÓZ@˜à&±ˆ³@ý¤9ðóW± ‡¸FnÉ:&‡’•m„&ýjÕ}|EÎëøîýÊ#€Füú€baá €ˆ] †s_$öˆø3TÿœÇÀú÷-ÃNP !ùáÃSÕ2®S] r‰ÀéNF†oÀÜ-Yì×ÚÃðdÏB5ÙwÀþ °&ûñ•áß³ó ìµe‘Ÿ*>Pï TH|úÏpñüY†OÞƒN~ÛúDl `Dê\@Ô˜E ³IWñ㯿 ý`Àját]rç´&ó?ddw @‘²B„uŸÉ_æ{¾³™ÿ18}–á ó?fœaðå;¤kúðÝ/†¿=‚ L 9—Ÿ îÃÍÛ@Ãyìÿÿ@«&†ÿð|‹‘†@Ä3ÃvøüŒÿüúxæ¨Ì ÚèvÚÌ\ÆôÔIÄú]q†í ¢j–I>0`·=ó›]“é °L EõŸáú ´Â›éï;6¶ï<þ]Øñ•mõ/`‡”…áø„Ê×àDìfÄ~K0ýº÷V")–A¤Œç—‰«Ý+†S'DÕì ¿ßŒ=zb¬ªœÌy?»Ño¾Ãë¨?ýÒ¢bU£A±Ì,ÄÂΘôû÷—¤‡ÌŠÀª• X÷}mRe¸]ÿSˆ¾iv浅 ‘…l™ „ ê0Ë;[ÿõ™»ˆ¥F߀IøøY†83àê8¾µì??30þxÅðëóW†/¿~‚EÿCªb6xIÊÏû3@ÆùY™¾æ° Í Ú†Zwë¿V+×!W«Øï3€÷ ¯þ¥ 1)€ ií4ÅExþZì9œ©þ0üxÈÌ`ÊdÛáý©äóÓ[ ü"ß~ò}gøvØ©ö”þ32Á×a€rù&hØ‚Æô>ú PéÚylE3ýþÉÀñï#Ã.`GDÐîí‰{ßXÅVé\P uµá€œAÈ€b!2éó@Ø&gJ¹¿þÌŽù/þ¾gdøpQˆ!Dáu û×g »ï|apb`PÖøËðä*?7Û_p†x2|ÄÛû ÝçêQþýÏ >Ô8û ênƒZ‹¿0°ýþÌÀÍÊÏðƒQ0ìÂÑ0ɯl¸e?¯cæ 4Oü'°@Š^¤2B½@¸€¶Õ û‚ÐêN ˜yõut@ã…ÿ~[ûÿÞKÄ·¬ _þgyÎð›ýÃC ø'î œ,ÿx€]ƒß@k8þ‚׾Àê"Ð.aÈp d=xÄ4ãË® þƒ»Ü_¸ÿ``ccxÍeräÃï׫Íc ŠºÏÀÄÌ Á, Œ Z‘^Q @¸6’þ…Æ<4Ù BOÍRêäg1˃OzòáßF†Ÿ¯€áù™™Yáá«L м, –öÀP¶Æd1¼»Ë 21üøðáç]6`’egØ{î7ÃÍU¼ 9ÀÄòYì6ÿüO ‚r¿>?`/ƒa`‚Ìâ‚Gb!+  …d¬„‰ñ?4p ›#A „ÿ ÿ¡;4Y€½±o |?3üäòø'¡sRüûlsV/`*ù $ =ìlXØÕd8qõ(vÿ€+ &€bÁÒàa@Z2à f½¸0&Ç…+ÿ{;u›·®ùÏTÓÌÌðØïúýXqý6[?1œ=ÎÈ  L$o€áöjµ>ÃÕo'L"1ˆ²0|ÿý\÷ÿ‡¦fX €WËÁRø„3pj Âó ÕaàÙ_&ÈÔ(1qýúÈð“[Üì=£Ä¥ÿ7×ê1›ÏÜ ž Ç'HÊÈ2|úôáÿÍ×þ½}û €XÐ<ψ´NšXô%…þ¹/\Éà¼nÕТ~f{uya†[~abøú–XxýfxÅþƒáã~ `‘q˜ö¥¿Š2èïõdxòø,ƒFüg®— ¿‚Vþþ—à ýe€¬y¼D3CÖ‚²xº ´’ì/dº\x2ÿ‚6†1ýýÍÀôë 0u?r.“x»' ärÆ©@¥S ûóûJ/ÏÏøç$001³üþóç/@± 5ua1Ï )õY@§]8={Ëâ jüMîgx&Ä ¦f(ÆÈ°y1ƒæ†·¯€lÑÝ}ýŸá'ëÐ<1п>ý|à õ›‡é‰!°r˜ÁÚ‹áÕ=&ðAPµœY@¥>df¶N²pÒf•Ú,Îx=!hä”…Rm‚.&y¦ŸÏþñÊD2³¨­e`¸µ¥þå3ÚP)`*à»}óÿÛ×ïˆ)tØa;©¡Ÿ0託ÆRf Ë^_ø÷Ÿñç¡ZM•Û/î1<ÍÉ %øƒá+¨¬„ q!VÅ/ —ž½b0ñXE𿬠Ä~ïQgØ#s‹ÁRXˆáÛ{&†Ÿ!±ÎÌòºPã<÷!–Ñ2‚—ÅA¦¿YÀjÀR E–ÌÐ2hX”rþý``þþ–QTgÍ…#A£o VmgHó’•‘ü”UüyÞ´^¸%²üŒ ë÷œÚÏÂ@,Hm}vè<€ bÎu¿¿ šá×–jÒªr‚ŸÞ}aeøô•™áüU1þ Ÿ¾3‚×ñƒì±ÀG»À6þo†Gß?0(|dgPäPf¸9ÿÃÓ¼g ²¼Â ÿ?ÿ/–W]ÌÿáK]À9Ÿ\B @Fð´;h¨úwIY˜ U#dÕ(8;€RÃï/ ß~þd0pñ8÷ÿË&#Æp/`€GŒJæNí9Ä›A£S°äðùÛw†?1 Ò^nhU]0ÁÄÿ…ôÕâ/’ߘ˜#ÍXÄß=ËÌÆ쿳 & ß¾C&C@ëtŸÉ|g¸øáƒ°,x Œ‰¯ß>0H|ãaþ,Ãðhëkå¸ ì?Éû/ä8è.SPž5…ÿþ‡¬ ü] õZ€S¸|/gnG€S ÈùÀZæÏ Cù$ oIˆ20¼<Ï/¯ûòãÃË'¡þ ¡ýB.÷ìg ØJQNhÀ–ÉŠAw·Ó2#$É}ÿÍ ¡xŸéö=i†c~1˜(~eÐûÁðìd‡#(lùAëE¸öK¾e|Î ÔÌÁðØx|ò˜¼„žm`¸`òžÁ\I‚áÝG`ûáç?`aÌÍÿm$VpLór²3ünAIþ/¤°ûoÉAÊ &è*QtŽÂ?`Rþ4ã÷_вý? jºFË¿o×àî½òŸ™sã;9mì~¡7vLld ¤…Ò°ü/ˆØÀÀúüÃIy`ålè°þû (r#?€–}ü [Ì  #$8ù*|aXûþ#CÊ1†¯À¬ðX€Š2|a ãgøÂõ‹áö×À:ù0Éþaøþãû?Ð)LÜÜLì\Ü ?@c,Ll œÀ<õû÷oð’;LÐj¼ ”rþƒ—Š‚g¨À5¤€üûç«””úK1y`/õáÝÀhêlû^ Ìjööm€bAêëÃF}ù ©t¬ ÓÏîÿ>öÍ``:,å“íß? ‚ õ?ÿ{yŒ  I ÉR`•ò \z¾e`çøÉðÍà-ÃL ®@¯ÿRzÖû;ƒ˜ƒÈi† 3Oÿ³äúþ—‰ƒéÓ?Pù/ûçOyÙ>1pr¼f‘føÃ(ÂÀlÐüúõ ÒŠd‚ìeüÏ ©)@eø Pfv6ðRYš¯_¿0|º}‡›c:Ð9n OîÙµ“a÷Î oÞ½›[& Ä‚¶b6]έôoeøÖwö¯?°&øÄÌðç#tÑ2#/°`ú Þ$š`aø,‘¸Ù¾0hûÏ€¦Ü4yÎ`ö‰›A3šƒá17°‡wïƒ8¿ƒ¼–ÓžSç¾ ²þý!ÉÅÀú“ hÜwf ¡BÿüzÈ($ñƒñŸ ''ÃO°£ÿB< êB³€<Í ,‹ ñã{÷éÃÇwoUì†ïLÜ *bl®Wt`nœBÄ‚´FøtÉ,Ò:JиÃ{ŸjF¡-µ|À~úP»Xq1ƒâí?–þÀ úÁü¼Â4åË Œ ÿØ/†ÍÂÌ«3ð¾üÎðh‡äG=5A6gî­‡_þþáú'{á_¢‚Œ/31>ûþïǯ?ÿÙ™_2Érñ0þac`ge׬ í4à†ìÇ·>>ÿÄð«ÿ!­CÈ¢jÐa'ÿ@£p¼ªRònw캆³ƒ@,Hþ]%ò(`kˆ_Üâøýå­ÇÛÏ»¤9ÿ1|a„ÔÜ «Ÿ@xY)èñ?@ š‚60Pd€ýȇ86œÿÄà¯ÅËpé0y~úÃÀÎþ‘ÁZWŽQQT‚7qÇŽ½'¯€ŽdìôdÑgaúõß_–÷¹y93ʉ‹#N`Ù ÌËï —Ï¯^2|øðâYð|&´AìP±±±‚ˇŸÀLõå3ƒ 7GÊö¤ÏµŒ»p÷ˆi0lµØwH@01°²ÿî­ý/f/ÍÌËõŠ—áè †'¼÷Œyß[iÁ¡Ãà :2h+°ÐúäýúÔ8adg`øÌÉͰ÷ÑG%V&Q9†3¼ùZç÷ˆAL\•aR†®hå*^ÙÃ÷Þ(ßþ÷S—Û_ 6n&æso€U=ó/>ÑGLŸùìóû·à$h¡4¸IŠiÈê0&F&hî5«KÐø°‹þçÿËϵÿ­V CûH €`‹í¿Ð)&ÐÉ"?~ÿdüÜ:™A&÷hêmƒÎ"†;Gtü{À`ÀúˆA¼G ØHô ÐÐ7h¨½ÿXZ11Š2ð°}gxÏú‹aÚ'ÿ;¥Ô>ýþÆøèØWÏÞ“î59m†­%zJŠÒ­ œj:ôåi¿÷?™×ß~S8»øÛOì¿þq“4¸Álƒj ð1&Ä&,Èša`¶¶œþýg6YA³µzû/}`P’¦–?жæ”Ž@Á Á¿He(|ÿAV1}yùòï³XÅG˜¤ãn2püde¸÷X“aÇK&ÛߤØA!:~︀ü4ñÛ/P Ê ô '0@¾ƒŽó`ø'ü•áÚŸÿÔ´8™ýÆÀÊ ŠÕ÷ ¯¸ï3ˆ i1T…ê±ËJHšU/Ùúàǧg¿A5ÛÐ1?þ²sð³kÈ6P´ü–ÚuŽ@gÖüc »±€W‹ƒX¬, í4Là&úã×!äÕâа äZ–@þ@”1ÉÿyÇdÌ"ô÷Êå㜿ßmÖaò=ÅÀiz›Aõ£<ÃV9†Íwþ1Dò?/)ù ´4ðšùÉ*¬x@MðÁ&  œÀVÅá'_ÍþJ1Üøô™˜bþ°13|xóŠá×FA!Co[vFÖß 7¶/xÍÁÄðã+°9 ŒQN ÇzƒŒÿÀk‚¡Qÿz¬Ôðêÿà%9Lþ6ødF`ÃtÅ;PÁ YÉ `Dš˜ äåòìöq!vF>fAFNuÎïߘ¾ß¸ÅÃjõ´0ò'«Ð+ƒÿ¢ O?I2lv‡c8ëÿÿ É†¯Àç°E÷ý;Ûïÿäe€½F WüúÂ:Ä… Xh|ûü‡™ØÊºŠùïs†OL ¼\ îFÚìì ±â{V/~%úá7°yÂ- tôGðŠpتUãÓà¡ðñ±и3°Ìaþù–t8ŸúG ùe¡ëaÿB8@"®da N›!Š\(WDÿ¡o~¨Ÿ¤7O" J[)éJ5fq9äôNÉKÞ›`ø&üØBd·³ÇÒ[oþ5)È1ß­þÂÄÀ¬Vå ÓÇ/ ,À@øÎÚEÊÌ`ebÁúê—9ëK¶ l?W2üå‘72Àžþ[*Ý/🲱’z®0!³;GÀ¶&Ã'#i`âøúóýÿþ`,ÀÄÕë Äàâ]@‰äÀŸA_€Í—twõ1| WG#ÑÅŸ¨1B‡’ öJŒî7õÚï§iû•¾ƒ¶ÀS¡á|Ù™˜G«Em®*4 q{8±ãz? Q’<%Ý®ìŠj×Ô$åJà6›Ç»,Èé·6-=ýLktgâSø=ëœ Sd,–”²Eš€K> C aw #o 7œA0Ü€Sj¢[‡Pð9¹6¼ƒ)~vÚ´Ð$1€ Ð뾿àq‡$}å©Pë$B¬Ëü ¤`k´Bô/“¸DòÙP•ÈZ8õKGX)¼6…?ü`Àz•‹áþµ— OÜgÐþóšá/äÀ Öÿµn u4Àþ'0@@ãC8~02ˆ0s2üâøÁ0ëê+Æ|K†—ß~2|u€ý V ™ŒàRê¿8ßߠ›x€I›‡áƒp0ƒâ£`êÐ7}Á1Í9Ä”@“+,ÐÉÿ!çæFÌìLOŸ¾ÿ˰jÊÝ7+r”yžÿÁóõóçÿ… t] ?ÿƒt‹_Aú„Ñ.ÌJ9Óþ¾ `e†±2ü½ "`p|ùóá։à WŸ½:àƒгÿA&<Ø€± ¬8Ðæ .`¾­Õýý›A„tÞ7Ãå÷߀U&°SÅÉÄðì#ÈÁÿ~[ˆL,À|þõ;ý”•Ÿá70k°1  HA†ïÒÎ œ/.0üà‘Æ>¤ ê*ÿý.À›¨ Ãîl ÍUà!ÛçåÇš\–\†Ç¿îØwüóß_?á›(v.?$%ü€Œ_f´üûž1‘™S€õÍtf«Ÿ •‘>½üÇÀü‰á°”ÝýîÃï׿2ÿ;¼ Ý­€%/hÒèi6 çÙE2Ð’Ÿ¿A«ÊI˜Èøy~1ýæ`xþáÃñ—_¢u%6|zl+›¯ß€ ¹ÿ –·ï]î ,¼œ ÿík¶ŸŒ oDíÔ€­¹¿ÏCŒIZ1p¾ZÞ? ž(ÎýûYuñ:z švýòÇ…» »&œ…t¯]<*'þ?ùüÿß³'là¥ò  ¤9nh`üø ª&æ `¾~÷Ø` óÜNüóX˜³ÅßÏà}_¡‡-z ö#hÔú•úï´$€â< \Àrã?° úl™ñªˆr›¦¿êN?gh°–¶Ù[ÿ¾}…ì»aåàv®~30½{ÌÀ%,DYøSá-°Vö™ü–* -3ÐÍ °%7LУ˹ûèÑvèñaH‡ÈK£Y\CÜáUrÄ‚DC·Ð²Ók©ü¿üÏÖ±ðÛ÷o¿ýcøû¼Gt /hh›´<Z‚1tÙÄgpçŒá»«*°Zã`àýô›á°/ðƒ \#ü†üÀŽ +3Ãý×n¼ýÅ ÃÏÌpóÐÐÁÒÀÔñƒë0à€%0YY_2° ˆ1üùþØÛ– Àvû“ýÀú]Øüe‡lƒ.×ÕÀ"ÿÏï¯ û/ÿœ»ç³t#,|Êñú¸@Áö ý„ˆ°Cð¿Ï¿0¾Ë.ûÃ>5‘‰Uø #ÃûÀ* ÔEít†6'ÿB«TfèÒy¦/¥ô_ÂuX-A®/?Á ì p± t8Ù_†ß@ÿvE€bß…X–<þÎP Ì& >ÅC` 2óü×?ÁÙ…ƒ‰ ØïÿÌÀÿÿ°šýÂðù¿Ã=•LvAæ——-:`Ï|’&x“ÐNPcèȉs‡ž}eØzâÒ;ä!À?s€@Ä]e ÃBˆ#4þ¿žv‘áÇ”Rs=X±úö×ظ*l6¸!b‡,—ÿ¡ÍÀ$ÄÇÀññ;xØšXWÿub8€ó˜ˆ@Kc@³ÂÿåÁ?†ûÀîñ„ÿZ4€…+ÐРe€fýf36NV†_ï_0¼}þ’á¿/“Š)í¿ò _ø“4>42°|t(?¸ À Z-îe¾d8víÇäSŸy¡[ød/ŠÇA[b.ÜÿÊ@°ÝÞ@=/- ÿCÖöþ5ï6㟽ÏÄØ1p +øw_ ;D Ãjà]#Ðýçu„˜ÕÅXž—ÎÌllàÆÓÈ ^àó›ñ/x¼ÿÛÏ¿ þ"ƒ$Ï_†'0lyÊÍ`/ù›áè¼<`KðÇû ?ß{Ÿ| JFá 1sž}çfxòìÃëËGtç;ð )°àâÖ À.ïòm—çkÙ5gn|Ã:9òüS È“ûŸ>¾|Á@°=Co¡{…Ø‘T€N'ýÿð+ãoÆ= bÿ­ùšßüŸÌØ2=Ÿ4áÁ*ÎÃð÷Ù7𠌬˜~ƒ'åÁ©à7è¼UPöpÀJ…¨F‚ïÃŽ· s0ØHüb¸wã2Ã#`µË.l¤(Ø2(&-¦&9Йnû1p¿¿ÄÀóû*ÃW6P€—؈âæ`Ø·eïã'Ÿ&÷]ƒz‡eÕÄ/ çA[‘Þ½þÎðíÛW°[–`"f¤óhD $HNÿÍ °îÓ¯ M`ãîhS°ÐbAêcƒ†Ân¿g`: ¬¿µäþ?|ÇÀ ìõýíFfþÞ$ Z*DÛ ¿9ÿƒÛ ÿ€e‚0;°|àúưóåo†{/¿2Ø+c) uÅÌ© â–Î O]nÿLô ŒMÙàÈ`/SAøëF^`åÅÅɰ{Çþo»Îmì?Çvžá׌ØÅüÅ'ß~C4Üþÿ¤O @,Ð!°Ð€¥V¤š8AÎÌlÿ_ƒª´ÿð´ÐlžÂu—g]bà®ãc`–ágø÷ø xΟ˜Lx€µ(@»Í~ýc†d‰¿†ËO`u"lP° xúØ'jaH(ª;ð=Ъy«æ1\ºp–aêÌÙ ÊÊš Œ¶! GO 1˜a~±˜aûêÓï>gè_uƒa.è¾ä˜µÿ;A ií÷Àz”•‡:£ÄêC3ì|Û/HcÐþ„ Úö_¬ÌØQêýt4»dÒŸ{XÿÊLÝ'zí¥ °ãòå7df ˜_9„¿ÜÀv0<ÿüa$ýóýOcó±v™ÿexÆ£ ϳ¯ßa`Öí¼\ ‡cP€’”æ×?î32~eø¹óBíòû SÑ€áß_HùLñག¯n^bà—‘gø÷ç ÐA„<,*5>@ Á÷ˆþh‰:gg°ýyæÃwÊ?ÿá»Ç`À€8mì'°>ûÖ{‰A¸Ù“áÇ… @Î úôxǨfÖûY ù ´——r" °t~y CþÙmÀ¦™0C}[/CZf1ÃêÅóŽÜÏ )%ÍàäæÁàfkÎðòë`"´açø ì$5&ƒ=jjjÆpúô)ô¡þrr]1caùõçhƉñ¨ Ø (¬ªø ÚÈ Aèá³ÿd=@ç8DŸ U#4ù³@¦Äà=ì¿àÁXðç/—^1|¿õ¹9#Ó7 ¿Ÿ>ÏÈ2³A"!í~PërÀ"ä0jþ¬ ,?3|¹xáÛï¿ U¬ “ú:´uMN;ɰoûVW`hi(wvîœÛÃ`$ÆÓ,³àðÔæ[†WÏŸ2˜›™1ÊÉÉý?wö ÃÝûþuÏ^È›/Ìð“‰ã7#ûÝOo?2‰‹‚Ï¢ ø ...`áÞ„Ú­ñ±‘Z°Ú˜¸A«ÁþANt¹õ73"öýG¬­ϱqBîm8иùïC[Ö¥Ælœz BÀæ+Ë— ?ß¿gxÃþX9|—ÿAæ‚úLŒà®,;7x’õË»· ·Ž.dØu*“ÁÐÆ˜áËçX~ I†[Þ2 Îççw0üy´‡AêÛ;A]— 6] (öÓcøùçÇïß%¤eÿ~ùòåÏÖ¥ þóq¿ŽŒõùÿàêpl}æ¦ÂŸ Ä¢§§Çtÿþ}Ðæ)ÐŽ Ю ~ æ:IR0ýgVö®ŽCf§@Iø?4½€ËAhþÿ;€T°Bf‹Þp @¶ªp‚#•<ÖÿŸ©ÈmA~ý½™Ü m›€.7°´{ÇpwÙ †_@Ì£Èÿ†SKâ §¼›'³7äœøïÀ*Ø|gø¬6_¼ûÍððÓÿÿŸ¿2ü»Ì9ürŠ,ù¡ðÅ\çÖÍc`z{ŠAI[ŸÔ$æ¶eÕ~~xàôóBopU¿ýÚ¶Âß_>~böø1#ÛÇO_˜¿}ÿñwã5—,ù#+'Çøúí»ÿ,Ü| Äòþý{Ð ئ%nèæ(^ÐÖ à#»€]¸‡À¤É…zþ/tüü?¤OËìÐi½[†Ó˜^@*GpKóãýÿYïÖ47!U-7èV7VЈõ_pêíïVà‘kXÖ|uûþßIs—3$G2îkÏe¸xlƒ€¬ƒ •xþ° e湟~0ð_g—f1Óo-Cá¦ßþ°ýúXþúÏ ڨöüÙ+ææ–++«ŽŽŽÀž#Ç€bùõë#hï Ðóì@€¶¯ÁEÃD?¦îùÏ’íÂÄÂr…Þ¨f„®ìí¼þ݉•sÀV)ùàc£?Bw…‹|Ff䡸¯ÿþ3}ý [†ÏÈŠèŒÁv—‚Û›ÿ ³Óÿ-^²”]—ù9›/°±þüý†§·_0ˆ«JÏbúìˆÉ;`l G/•ùppIoúñí)0…þc–kÿ¡º@¼€Nufü§ÐÉ/hRív–§ ÐÊ-.u¿ú¿ 4ÃL_€ÅçWèÍg,˜ýÊrœÐ½ˆ¬Ðí³ÜH9"ã?tU34rþ¿¾{égÍ’ÿ›BMø\”¾0Üÿ•aó¯s ·å¿0\ø÷˜Aáí7`@Û„U–Uú÷ãßO^å3BV×0‚ö ƒ³è¼ÏŸ?3ñ_€eF¨…  Ž Š?ó tÉþ fûI¦ýN0| ¸õO`½·¤b=lm90¨"1­Zñîÿp×<{ýÿ;¸+=¤ ºÑþ|Ç󇋟• ´uü?4Eý¶ þ‹ŠŠ2TUUý Xríª†íÿ í"¿ƒÊlÿ‡âø}Ã;†ŒïAù•-F€ÓØO½ó›áÕÊÏ W_üõ9þ¿€ ´ŸÀ¾úÿÿýîQ†•¡ {?Á’>ÐŽµÌ }H ^3ã/Vvv`Hýùÿó'øø,ã¹À;Èܽzþ{èÕËL&šÜì¯<³J{ò0ƒÆÿ0~c¸sù6°!cä¾Ìd;òK!¿®üûÈÆÎÊÅÌ>r Øùó_]]ýttô_nnn€‚­ ‹A{|?A·æ„†>3Džë?tM"¸È’w ¿–@:Rï ÛVÁ%>hR”Ü€žïëýò<è*Lç@[n¡;ÓA[uA±Ï ½F TN€öÿ+hûÿß¿¿üv¥ÿA&ƒ9 …4(‘|9sýã7¦ëÀ"i#×/·?¢‚ŽÜ ÿ~}cøûø'ƒ‚±R°Í"•«×kžiÿÙÉú]€ƒSLLôŸŽŽÈã   ÔŠ PiùºŸ´cü¨M€”2@I–¼a³Lÿ‰ÿ°Ò‡ìg%[Њ?p¬‡–% 󾃻ठLŽà=ÃÐ-ô ‹Ï@Rbÿüÿÿ߯Ÿ?¿–Jù~@g„ÿ@ó2+´†ø j› ­O¯}½ÿìÚ7N¾Mß„9Œ™YX>2ß»ú‘áË®7·?XjˆëWЖýóõÛ×óæÍûwäÈ‘ÿ {Çbbbˆz¨ôŒyX¯ð͈ A·#ƒ6m^æU_ lß:ÿÔØùÞ=þ ì¡ÿà$ r8hKþ_ÈA.€€Nü‡ž1ž™†¦ÐùÀ»ÏAÏ¿ÿ»B@[ïAóÚ;ýÛ¸ÙGÁšîMJ¿ûËðÿíû{?>2Ü_1 ìd|ÿËÂÉ|NSZ÷¢Š¡2Ãú­ë`µ £ 8ccc°uíJ‚0 NVÿÿÛ\põPµ-©.22ðj“Àq‚5€GˆʯP†äÔƒ¥•´tÿÃ×+§F[0ß&ðÚçùUxH[n,ö|&Ü›ð(§O–+ØëdCÑöQÏðHßdÍ=.ýZ#Z|iP3eMèŒ[êUײå8øÑø)ü9`¥<°uö:‚0–èààû?§&‘¯^ ƒL$°\II¯Àkåøå=”ËD(* À/=Ó±«´\ž4Žö™Fˆÿݱö|†}ÏyÒƒ૬kÙì;¦52ãöe O—ÞÉzYý"Lçlôú•6®]|!ÃãþÁ³ºÏÔP–€ò&dàï/ÈNð‘ PO~‡ Ž€f›¿P5ÿ Éû(É‚Rè^A¨Çagü‚–5 ðR»€×÷±‚“$ÅýŠÿ‚ühÒ„-ÙUËß Y€Úž€eEP*}4æ3¢[/À‘ö `€ñçŒt@,£A0²@‘r~±€ÇND¤óݘÀsñlÀ.ÉÇç8Dþ ­_ddøöé?7ô, Üv`An>ðI…,¬Ì Ê*B iVl þò¿TêáÔs±ˆ‰AO‡‘áË‹¿ ߀ö|gÏÿûò•á°¿—׋á?;+ƒ×ß³ †.2üÿóXþ}àRªdàùÿžAêÓðhÈcí&`g™÷|øQ©è ,lìàPù¸è·gßu9—iø6¥o?ö{Œœ wÍV2 j÷€ ¼_À+xút=Üÿooþx ? Ÿ‘ @çe<}ü˜áÍ›W ß¾~aøš‚ž]º\õÄBƒˆGÞ}ùM:Ð^ÏÀÇÄÆ rüÔG¯ÿ@¾+PØ\aj†T‡°ù,âðñ;¤K:äÄ~ l<ûø¯ž7 µí? ƒ›LàžÎè¦bØ ŒÐQFŒí¥ŒX¶š3Po[?]@±P)â™ö3 ÑÈaó2bºä'Ûo¿>;yÿÝŠîpÿ)Cœ‚È9Òþýbv68ï‚GÂ~ý$Ú!5d\îÅó_ 7žÿg—eüÁþ´†WLJœ]Aà¿(x&4ÿíßþü}ñ†áßÕ‡ÿ2›ã,ÿ¾püÿÉôé톯þ00þþ ½ãæ8 ßDù?ü…œˆ5À¤È†´ÁˆÄgBJ¿¡€Öê¾ÒÂåHÿ9=#Ðùœ u÷2¼¢,wÖÎS¾ûðͽ²&, XV :æ_ RœÒ•vÿ8Ò<$\xÄ­øy=>ýàbø÷“áË?†wßX^ÿacx´õ6°4øùîxóÿÈ=.eM@hº4¶ššüÃÊÀ 쉰ñü`üû!—a#¨»ûü;÷Óç ⹘Xñ0~=ÂÄø÷ Ãï,  ©Ã¿ð.+À»÷1ìØ“ÁÔì  s>z©ÇŠ´Ì‚i¬;ò± ÜŸ³HK`ï›ÄÃÍ º–޾3%3h3ü­.db¸õX'?ÅÀôç.h^X”ûÇàëwÿ‚öæ_AèF†ÿˆ)\Ô¢¶‰Z123üeaçüŸÿ9%ÆH~eæ5yË¢¬ÿ9ÁÇçqÓ1×ÿ_×¹þÏüÁÌp™Iw+ôœfè5‘àÑèÁ)ðóoñh@,æ|Øö6hÌÁF(¸‘öâÂvdƒ¬Âuú™ -ÿ Ì›÷/FAƒY a$dû `›­¼†éÒÇ+b Ë’$âùëÄ™•þ6 üøÊðøÚK ²¯ ñ@†¿Àºžéï?ðQ¬ üë ÐN¿aÃrŽ#¼9 =ŸB!Ž¡&¤?à‰.† <ŒïDA»ÀÀ?±2ücdcøñ—›áû.Í'̲îqi1€®lb}t–ÿçå-Àô³J\†o1è>X™Z/ÛíÊ/"¾¡@,Öû HÅ>+4â¹ ËîY  àd2貦ÿz@EÌý•Ä" hð3ÐÑô?ÿ3° <`åÔÞúW•GD_î°4ÈpâĆ¿¿0È 21È+þcà’ÿÃðç3Ãë›\ __²3°rþ…œU‹~LЦ´|.¦†—Pß0aÿÚm»á 6Ä™û°ýÿÀÀÁôXº¥}žþ}»ˆmû}Q†_UâlG5Íã_Ì e&FhÉÀ™…]$’@)L Œ(4‚Ik›ˆ…DóP§D¡Çy§ôø  @šóE ·êʃ†Z™Y˜¬ýd4Ï%B½út’þç¿ÀÜñá'°V}}’“áë%v‘û"ì\øX¿3¼øð“áæ[F†¯ÀîÕ+Pè)ýc0–MGþeø¬$>ƒöl‚WÙó?´ÏĽð´fFîR0BÑÈÈ&Â[hâLàÖb£:b£!xËlñ èRŒ¿?€ÕÂ7~æ·Àꈙá';?ÃgcÅ—ÌË_ÝÃùÿÙM^®³³DÄûØ@ùºÂø dßäÄeè&8†ÿ”•à¹jȈ%ÔHØÂV„€b!£ÈgBª×açÊñ@s»(R©MÛñ%P\çe[áLˆ¦ÃW`Ÿø'°±Æ l”ƒö(ý~ÃÈðý,°åÿè/§È/†ÿüÿ¾äÙF†_ß@G®üb’ùÅ )ó|‚,È$!YðÑ ß>r0üùì7³‹a6HsìÿÈye ÐŒõ„=ÝTŸÛŸ õ&Dô¢hÞg‚®þ1Z’0ÀoHƒ—, >0!2–]ýþÄ ZKÊÈ:€á+‡‚úùÈÞ7ßžöruÙ%y!U(få#67P×—‰M\µ€fûÿþÅìW˜@c(ììlà}3?°€×ºýƒq°ñ€b!±±‡|û"ì¨ 6Ä0Í=zzŸú[T^މÝ\´Ú ê‹7ÿÀ×û‚λæìߟ€%Às†?@,Ò¹~1¼þöáÄ¥àA䃎ê?-Ư B|Á'¯ü]k%õ‹¼/Xüþµàÿ@N_abDYÍ„8s)⑊Yfh‚å&h’‡uñ˜P%þC#ž -!@¶pC Zð,&äÊL¶ÿßX¿ßdúq‹á»ÃW·—?_=ü87üã_†Ä/œiçx%4ÀËä µ%x×ùP‡ 4¢ÚÂDZ-ÈÊÈpýéGðrÐ¥®¿þüa`¶JA}`fÐZF€b!²ÈgB*1`,1"ÍÊðA‚ ¨Èççû¯ÞYǤöíÛ?É¢º¿ ¥¹>ú'¸u3÷@gFn6Pyð ¼tI h#°Eÿï=3(3Ã`£àôÝ_ —O[à79„þ 0˜3ð3¨^â`øq‰‘áîòW /00ûe6+Y3°½¦í½: m2ðSŒLHÇaü‡_Áóäl"äbÚ8„í¿‚Í)–ÿCs:fB€ 1@KDÂcÚó¼Pœí踗 ?9þ—´§Þ†ogùß­yÈÿ埄ÅVuðÙ)½°ÀIM’ýá!¹“äïêmS`úøžñëçÏ ?~þdþýã7+0qþ <Ï€¥ÏŽÔÚ‡5ò@þ ƒZóö~~ÿøWÍgrf‚QTÞ~ðX3H?½É"øìÎ/ùß ß_ýgø ¬×|cÆÂ Z%ÿ“ádž'¯Ž|6ò€AM`ä+Ó×k† Vñ1q{ßÒ oI2üÜÿŠá™ùc1ËŸ BÀˆåÿËðç' ø²H‘ã¡ ~2#r±Šz7+¤Ã-X˜PNú…U)ÿ¡(DUxÈ™ð„%x)žùüŽXŽ¿_8>]eøÃ)ÆðSÂIþÕÆýï^=½Ïz}Bó†3 Ev"|К¥ê¶0~ÿýƒ €ÐÐcFÊñȃ9°þ0ȳ1óˆ1°}×fx¾Fáã¡ç Ï\3|³úÄ ÆÁÉ ÀÎÊðý3h¿Ø?È©" ³¨ Ëq ðÖ•aD­ó™`]@H´Âº”L,’à?tO,¬Ša‚& D·ªŸ‘)`ÿ3 Òlu1+ä`iÅöç#ˇó ÿ¸DþŠk(2*œf{³·^ðËË&p#Pòÿüñ—WˆáóçwÈ£°ŒH}k}%Q†ë?0¼acûÿúíÛ¿¿~üf`–˜Ä‚VÔ3 õÛÑ[ù°ß?hëT·kZ÷22Læ3š ?žþf¨igxòö2#sŠ‚ª?·”ŽÒoÖ@‡^¼ÆÉpþî_'‹o ¢À¢ûë§¿ _¿@rÙ§ŸŒ ŸÕÝë·ÿA·Ø0üøÁðøõ&`•  /ô‡áÅ÷· ß ,yQ i >`º´óûÀƘóovÕ_  ÛOØÙ˜%äôµ¿  ¡§®€hè„…¿Ð(þ÷5ÔþAs:3d’Šñ JÕËñ º‚VuÀÛ˜ Zö¢ ^¿‚4`Ê= l+8Ž@ÝH†ßþ½>l3ÛÒ2î ÿ¾¤20í ¾xâç)øq&‹hÐÇ¿ÐÈGâ‚gb8Д`¸þä#ÿË— ß¾|gøýç/@± µi˜ÑZ÷°Ã`4lû h4O´q¨ÍÊÅŒ‰{ê$FMÄtÀ†·×þÞ<ÃÀÂ/Ê'Tæ(¡¬*ÅÉðñç[†c·~0¼þ ¬û?±0œ6ìäÅAû‡þ€ÿ×`7´z —,0å¿RúÅð˜û!çk¢ Ö_E€Žâ'?>þÿÍðX|ý~ÿœçÛna%F9†·÷$ÎÜ»ËÀ¤ÿ†AËä7ƒ²4Ð|Ðu›ÀÈûý¥À|YÈH„€Îcþ=œù´q fXmä1CK€ÿ°„Á€Òk`DêBBWLÐ":¶ÀÄ -#žºáXm1CÖ~0þÖ‰?ß1üûÎÌð›W†YØþ¤ŒêËò ]3v|‚·Þ—¬Ýþ7:ÈTíjj¨«€.Ÿx Œù»V–ïÎ?Á€ÖvC$~†µ{NK$fp ´ºži$vž" ´UÏàùéÞý0§nð_Pœï?è¾?ཙ8™c]¥e¾¾ä6@¾2¼ýø|‰0?;ƒà†G¯˜¾ao’äûì¦üŸÃº¼ö/äžÃ¯@_m~/ôáù·_àu‰ K¥Á 7ÿSâ/`¢ù LD, \bœ ÒŒ< œ5ÞøÀðòÉ]†Ÿö_”ä„…€Õ °¡ùXÂüùí1±@= k,2#Šôÿˆ!Âÿ°»éþCr9t5¼kÄëIÀÆ‘  Ëáÿ¡Íü:Ýt³¸¤øk0‚wZûŒÿ~l¾ƒ˜‹K‹AX¯sú¬s<Ó›êgC"ìÁ³÷ K—¯ùºOZ$<;}ìøÖ‡ŸÞWàãÀ¹ èᣧ o^¾aøl€ ÒŒ¬®gEZv$ƒ&!hÈ€N²'+È(lõCV„‚ŽUx ŒÐ¿œµ_r}ÿÁðç+/óoìÀú?†G_ ·póþcâÿÍ lôjYPÎÝ” š÷Ÿÿƒn›-·åaex­ÀÀ°—ó5Ã͇?l~ 0Hör¦(èú¾w?¾0ü~ŸAŸAˆCØÊffxtFˆá=°1ùÊï/›ÅOvfv^ Y {þþ…× #!œÿƒŒ1AGã gY‚V³0CÎÊûžK€ô*à}ihÝÿ6ÐŽè¿(LFh |@8t`‰i<VŽÿcB:´½ê/°dúþ‘áç‹Ë ¬üÀhxZûÑñ½Ú¬‡“ÒýV“žÿméÒœä(P\¨@åŒò¼ì EÄßõ¼cò0ví: Ap‘“Q(j*^BâWh(ÒBIÁgP!þšŽß /E´($›8±ÍìyO@GáâdŸt¾½ÓíØ³3¿E„=Ä ux+¯§áÅ+ýÐ㊃G”¯ýÖäŽ%žÿ``&‚w}”p¶0J# ÞDj¶ÀözÎ eè„sâRž_ðžÔÒ/î²ôÅž®]Nè2ÑÅl#Ås;ÃÕpŽÍa û¦|2+³S‘ qBŠo¾8É"É+<Ýñ²4ÅÞ s‚¸–«—"Áœ™ør—eÁ„¾s‰[Óˆ ŸarX4­åkusö,8¼îjm½´º2F«VýÏIþ`ù´ ¼L­ß]tþÒΧ)&Y„x£³:;<>Eÿ ‹³îyÝïä¨'—°Dn¶vvñpwûoˆx}yo¸º–”ˆ úò›vŒ% sÜzE/áfã <pãQÜ»s!ˆâÊÝÉfÂhÒ&F»­ªÎÄà.!©âÕ§û½†€àß–î°Ã·æ ¥Ð½Ç RÜÞ¡>™AŸŸÑ󩇨!O¯r)Áàò„õÛöÒ³ÔM£ç¿ª ,ÈøŸ­?S~×bg£7³TBå9^&([Û¦7åÉÞâ¶jØÅtB‹*0Ǧ”tÎÐDzQ¹¸ïar#œÄ(Š ÷ïä© LUi’È0æZæ,ë––ûé¶2ÙçñY©‹¯ÍxÚW~¨6Én#™[< •蹉ùÎßÒdá˜*‰ÔÀÐ}ãÙÚ?õÊ5bôbò®g!ʼn$‹¡~bÄନÛ6h?rDc2Ws|e³ÝëùòòâæuñÁËó“Õucó,³E±D¥µ#…w’ýéœ^œø€²+Öi¢Ï±ÓTv¢€ÄÐ2 Ä„Ø+?Ò‰oéwð |3 @1¨šViHlîlG #Ù"ÙQüîlß}ï†^€(@Ÿ5«Êï#Û¸UxVÓêíÓ&×s˜‹c©§\Š’P-èK·¯øÖ¸ð4 NXèÃ`µmºN8Û]uL:oÓ ߆ Â4YpI/ 5s(I%WäÜï/ñô°ƒó×=œªê„Úžuh´Å÷­ãÓIIQÒgx4 Þ›Ê}Tk;nÐf ]NçDÜ~ƒç·ñ x.:-kélmwõR䆇|³ø"|ž±î¨Ï(Ça¶º™]¾Ü^wsü±þ¹ïž_8»–œ†a :Nš¸4M€T$6,Y²pz.Á¡Xq$»¢Š–ÇÄùؼ±S ÁŽ]’ÅD‰íyoÆ3Ͻƒ/èÏ1ô¾ä¸ß]Û_¹s «Â7÷ÍûÃBß]%”"HÕ•?uØ´]ú„w4f¬¯ˆ¼%™âyº Ùý'yÙ7ìšs´ˆƒnñ†|Î/•0T©nÉlptuÒÔ`TsŸBeƒV¹‚\1É4ZTy ŸûtaÁmúøþ±0œDy)nzκTž¥²dY¥ÔMâi,yå3xi‚îN†Áy×G›büÕq†m<¿O‚Ñë'Ù˜Ì9Ý.Úƒø¢&2¦·Ð ± Ëæ4€–&¿Zo_4lÇã–µbñ“‚£6¡á-ãdˆÉæ,È~È’.xÎÌ6“ˆG›‚ìº.ÿ{P›‰ú#“P7jäUc¢u”È0ñÜlõ›ñÝÕáû\cV>RâÜ®þ¹€ž>ü m2B°s•ÿ@»‡ B×ÒŒ‘'!蟘š6»½°’b¾ÎÀðR–áÿ© ·Ï+0\öïÙ¾~ef,Â_=ÿ¼“4ÂjMƒ®&åšÿÐä$ûÙtþ º$Ô~øŸÕ `wë?ƒ7+¸!öhØ»¯ß&^ùÂ`ôˆ‹ÁM™ŸA\Œá=°«ñT±€nüøÅðéýg†¯?0<âË ((Î - Ä03Q˜{·”Ú‘kJJ;O¿~ݺþâãÿ¯Ÿ}¸þæóçëG„=Tÿ ˜Ë2° ó1°½þÌôã°râŠÈ,ùYß3~þó˜.AgÅþeøþõ;h&°Z€DäÒ 6`4ŽÉ 9dD¬?`„³Â:LÐsžû"@m0­`¶?¸^þügþòæ?E¡Ï€ÅÃÿi»™‰Ÿ:@×µë4 CÑÇ¡Q[Ô‡Rª¨ˆ $$$†|| ÂÌ?°!1°ÂP¡NЩbªJU:@+BÛÔusc7( žÝXOAo·sJz:Ñ`ÈXâe„µY^Ìuòaa–.@ I@{fÎz]y¼¿¶qódVúýG$wieñxˆY<îJH‘¥¨Ø­§‚Y”(-¤¡»¸‹!¦9=) ˆü‘þç w UÉû—ðônKTõf4Î1oRšÅo"ÕäÿŒ—yloZ‰ªT%˜{uZ_ †_غrb :¶÷Êj1,—DƒE ñž.âèø Ê”üŸ@AMh‘ *8"H¢Þ+fÆvV)R¬VZKÛxÆ~s½·˜ K:}eÚ|{%h½’ð½Ë+~@KÉ6{ë^h*Þ;,ÊnovÍ.ƒ£ÓDû} Vý2!M 2€Îš¿C ×ï>¼þDðÜʾ  @ÒI mUá;{r!‹ 8䈸‹°a¼ › 7=ÜXe(ýWã|‰a"šèg5ÕÏ«„¥¨´òp³ ¢Iù&ñSá·*ƒ@¬Rÿ`¤Ð“Ä_Kᬽž4Ãû§­òñánXô?T>ùÜl3 M(†ø/á†áº¤»«tÞ0§óæöíFìõœæÛÑý™ãõt1Ïe"]‘É6­Z±9âFtÚ°çš*ޏ‡ZÒŒlÛ jª—s][bÿ0u; BAp)Pð™-©5¦½h¢‰1ñàɳg¿Á/ñèÁ¿ñªÿ ÆCã­11jÔZD¤E^á9û€Â°»Ù6³V#ñF#ñNC®ÎЍ~þ✕|°½§Ü.ec HÉdÑÝ9ˆÔäÖ¦'?) %[O”®ð ©Óñèl?§Ñ»GWcžC[·Í]pþöR‘*Qr`$oø˜…Š×øÍ¥v튿"æÌ)õ7ò}Ì_ÐE^‚a©ç#û¡‡‰0L´PFªkZ„v‡ÙÀ ÓÌB!i'PFæéŒZïÐøž½R"†`ë䊞[G‡Ýëû±ÝÝÄ«Ÿ ckCX¾ßÃlt´f¦þtgá-åú2˜*îÂ¥½yu®L|½ATÈŒEÍ…”êßÓJS{ÃÛ˜•i6'¼ôqê$Nßëã'—Ú=Øy™ê¦­§å=çºþ`ëZv†à8u”ºiBáÂC$ å®üW®œø7>qDA(â!”µI ¶ÃÚNª"qÌ%YÙÙñz5׆;jÝØ?~r¡Ó“7®Vºmì=Ð{Ù¾h€ übøôýõå„·wòüät¤—÷D¬©ª¬µýú5kŸß }Ñ#QQ! ¦)B¶ˆl‹<Î&wÌ®`$Š“+?tÞÛ»Íô úܾD˜™rþo¤ x~鎎N¶EfþöZ¿ÚŸÏñŠ ÄÎ]óv³À6ý‹ 5&z®E³a°ãPY}a+ÇÉ2ÜÌË÷µ±œ$DIåv)žÓ5aD¸Ùf‹ 0LŸ*”"•¹DWO¹9¦æx?sö†ÍjvÀD)AôhPîšÂ“Ë'&Q*@}|S`¤xGX"#µ€ÿPæ„â+Ýˤ\¬¼]Rã J’{JËGääÏÈ:QgÛ#ø5‡uþ0‘´‚Ü^Ï€:_”Ÿó¾²þ)»h™M‹ô¯hYÌžýwzÉ‹ÊïnÞâð,8Ôæ`8šŒ—uY­W‹d´ÊJžüßuõ< ÃPð§I…5]P%ÔÙX˜Ø™*UH•*~ŒüÄb¡¨…¨QRB I ÏΣDâx°}ïã|¾g7”¼N#ì~°?vÑNµrBlDp†»¦.ð_kQŽ…œž ´éàñH}c$Ì'?mœDA»NcäËnŸæ¸[”Ë{ê Ë(¨Í0=o¹bÓ9›_-XímB–e\úë)›¥AFUëôt2J©Â/;©Çô|‰ù{‡8”rv‰£~Ï­‡ÖfŸš¨Q†…Ó´¬£9Ývé(UPu‘ÓÅP+J1tAi½Jú„R×8ÿj vÖ)¡~‰Û§ sGÍà­^øI™Â1aHA²¤œ5ƒ‚ç?4oñ˶ÿ‡k–¬iô;®cÈ­oj…[Ýîó`˜Ä¯e'“s\ÞœW×Óûñøbëð`¿Õ C½-dže•a#ÿT¬›¥`êêY‚èÛœç]1i’BÐBì""‚ âok½½VZXˆéÅBI¡"ØøA´HÈÇ%^r·ñÍÞŤºæ¸…½7ïÍÌÎŒ 3Uð1Núüb2R=q~©ú¥eŒ¶6iuæÏÞÛA¾s¢vOËÊ»xÖ(<:T)¸Üé7ÙxM€q³$”©øiöðôPAU;hµ‰~@×XqFXWÑBÒ;ÖÄá´i/hlÃDITL†÷ 9ÒG_.ˆ ¤ûp‚ü ¿ï’ô)d3ä <¿ ™wù>†¸¯{ÈQC/R‘äÉC>ÈMD9HÖ@šÒDùü®7ÚæÞþ€z/ð{üng® ;m @ב¢ô£– P5s”Ûȶn‘ì}Â+´”åÄ·r¢rñ„g ØÛ[jÒyPMjü#€Mò—"iõ†>jf÷«Þ­-ÙõÇr¿;ÞO›çKå¦svuÕ¼»EU}{µB­CI{J€i,ø€ª+Öi¢7MÝPQÁEba`becEbgâº3"þ?`CŒ,‰ÄÀ‚*$@ VbÞ³S[6[>çî½»ó»iþãttýuØšòÅÅumv÷’Ù³Cµ-cs7äÆ{=ÕîHwÈ[οïEdL]m6d~%!•Gnmâv¿9-Ï8ØÁbÅ8Ù„Àॠâ¡ZßèEi6!ºHv u`dÖ´Šœ†1»Í%ð1©r¸Fnxž¦b; Fínü±¤8æé¨ÀDö×e€P4µ ‚¼`,ESã‰ÙÉ,4ÄÝ9•ıµ{Å:`ù|Ðÿ­<õß­ÌÔœ¬¥üp¢ú}ÉW·¥ã$}¼Äé=…cô¬57y}Õj@`Ó?À¹¿Ù–$hKìRL”E^ `°9º½?>¿“£“¹¬8¸úßÕ‚ñlm¬ùok…‚E“Ï¥êF±ÆÄ”Ÿv8yù€«kY‰‚5qŸÉJÖƒ,ˆ‚?àÅðè§øC~‚gaD¼é(Q6ຠ«l2‰‰U3k ž‡0a¦§»«èîj¯ì¶PAKODìŸÚ[êú>1Ùôö|ßô°ªÝJ–VØ(ËÍG…·l=Qõ5Èe‰üè­ssfÿ„ g•ÄѸOêí°Uƒ­IS3çëÄÄ t¾ýQUW—Q—±iÈXñâB˜< ‚Èœ˜biá„T]/fÜðÂ\cI#xJ—¸¸Íqvãdâ˜Þ`¾)±¶•#o Õ&J ®ÐEY F„‘ÚGÓÙh´¹øÈòCÂatËÄä½ ªh&±¡åa|±Å\-ïì2ܼûI”<iŸ4 ÷NªÕWðÛ£à`ÿ}Ø `8Z$¯˜¥ow§ææê±N/ìÿFHÌ’çæ‘k š”sV6Æ'C±äD+ü>?ðu-¹ Ã@tâÈ$ è‚BQ—EâÃz.ÁªGèÊø¤›lhDÛa>iH»`‘]+{b¿yóÞ¯j¶…¿bñõJÃg1É„¹óx¦’éÂ>¾N¼8x&þŸGš­ŒÆñ™ß‰â/OŽªãÆZau¼^À•†_™'\0ÞQC)·ÂŽ"PÃ/œ:-KÙ‡àº-°m&q†0/“‚å…R¹{ÓœRžøÑ2…lÐiÀ°A‚/wÌ×9ÿ*FOMhÔð9Úà‡dÑ%cû„~¼›-Q!ˆÙ ‚s!nuŒ È1°ËõzŻOÜëˆTTŒgñÃ7ž0NœEr?*•GÊÈ©G´¤ô M‰­Su„àx„Õf›~¬öoi;x÷â¼R¾©t¼ý~õ «t]ËJÃP=yؤUÓ‚Ò*"î»ò \¹ðãü¤ÿQ\êt)‚à·Ú<škϹIl(B ¹3¹ggfêÆùi#,¼läü•]àj ‘Ý„—w~¾suaYBèðÉΜ;ŸÒñ³Ç61Ì+…6eŠ45¿êæ7ow ³ÑX¦ú6k^ze";ÜA2ì´J‹Gº¤{|å}dÑ1¾¢3î»Ý˜†ñ„ò ­{iàWì wýsÖõ»ºÖD”Ð<”Ÿáùåõãým:ºÀÍuÖšõºº7­©ÿM^‡ó!tóX ÀÖÕ¬4 á¯Y’4#AìÁ¢xòàY<Ôwðu|ñú¾ƒ§ 9ˆ ÞD…šÒü Í6Ù:³ÎZ žr Ù,³ßÌ|3ŸÓÏù’ y~;‚áš/hd‡Åù8këq†ôúÌÛ¾‘Ǩ•Ó"ÃÚViÍbM~ðd¯ë í\™INº’åa&0p\I›J{’bqz=L Š–Â,mcmŸ½|t>gó`kò<“P/M»jºø±Ð©Ö%„ÆdØ$Uø¤¾”Oec‰©ç»Ž 8òdè7¯s¦¬±>§„¦ÜÅÌ„W“„ôc"‹Üµ&¬0«°,¦È ¨ŽLía¡ò0ë ñ¾3ÂVzA“!ÎoÏï Z^ }TõÿA| ?ðpŸÑÌÆLø–®ôU?‡¸¤^áÉÄpËÔ&Ó9F£·áã7½®õô²p¢×™ªùwþ+ôš /RmtE -Œ´Zí¢˜Ô!§Øù€­kYiˆ¢§6'Õ´qáFЕˆ 7â´ÒŸ*…®ºó ü×õÕM-(´‚%„Ò¤yxÏL†VñB†¹7wsŽ­¶ Ćª‡•/WUr4ªðØÙ@¥¯msˆô8X^¸Ó}FvëA α󨔚D°®cÅŸñt¹y]¹0tR<~¡~£>-¶îú“@ö%ZßÈÿ¯Óô«Ì¿KT¦c4ON‘&Kí¢DxÉ?…‹ÊFòG± ­ùƒ-ù ås@#œiqèOh ñi±(l/!l9xzù'+óÿ‡À†8ŸÀÈL_!-Œ¿?ÿAÖAça½<†ÿhµÈ–§?~·_b`6çg`ñTa`Ôã¡U %) E¢ÿA}dÐ0hÏ-#tÃ%hq¨µºQñì¿!+uÙÙ ƒ. ™»/? ·Á– –¹€Þâ&m1ö_ ¯r/?cXûÃN`›AX¢8˜Û3˜D›0(»‡#^Ž_RŠvÛ{6/˜ÆpþØ1n>.''7/ee90~ ôûÛü O>90¼zw€Aô÷}Ñ_—íÐÂïà ç_#þþ¿—¾¯v.Žï¸Ï°èä;–ÿ@·K‚7YcDúôHg€6 A9ÿ9°y÷ú5‹°$|'úY„œœþ"­ øMÈÐ"œ )â¹·åÀ†”ÁWnI3Çê&>Ðf‘—À5°zülTÚ<ÿ¡‹Aÿÿ‚ í1þCº-z(¸$­Ýg>,®®e`s“f`³”g`–`øÿæ;Ã_P"øýù ƒAu=ènBÐ Ü_&È5@{ü€4ÛoР °Õþ›<ìûë7d;há&3h8˜(~þõ™ÿ‚×.*ðÛl¿Þ0eøÌ,Äpꃦ}!CTŒ?FQûõå+†wÏ1\¿r’áÅ£› ¿ýìg00 òó «‘ViRfú Î=g¸þXÇóp1ˆ2Ü–.¯~Ý\ÆpþÀ‘/‡1¬ûþáÎÉ' ‹_112py©0°Ë 10»ƒA—:‚Vþ"mÐ]8Í ÝÚ ÞÃçgfãºÐ–Ô6øL,ÀH-Šz )ZÐîáß¿ÿ‚oÚÝ ÍšøóA‚éÃÝÓn8Ù3hH 0\¿yŸáãÇW ¢Àé7°ñ#ÉàâäÍðRUáÞà Ÿ>}c8yø';7ƒ‹‹'xå(gIr}bxÍü ØÞøÎpçŸÃkS†"Ê¿˜LïÜ¿ûbõÆÝûެûõ9ÂYÁÚß¿~ Çõô béø¶F`Qº14êøýë{hCÅ@cÿö& ZŸñ7Òú@h‘Ï ½Ê€iUðwø‘œ ÿE g±éÊ2±÷ø#_(|ãÃg`“óÇWÐð-#4‚–ÿFì[ýmB§Á!aÄ’¬AÓM ýÈÀ²íç¾/ ÿ®^`øUnÿŸËY4^ÏÈÊês‚k€l¨±€†ÿ!'…€®hÝn Z@Ú„T^uÌù´Häø O ™h`r YƒŸ‚ïÅaײÎþþ›EˆáÅ¡) yëú>Ãò#0Q ªò08…0D&–1˜›ë3˜šè3¼{÷‰áО} §N`xôøÃÌ)½ îcðõ b07³dÐÒPcxõö+ïÏïî¿‘fûðKQטS'ZÁùõ]—Þ¸}xX ÈL6VV† W®0€N÷­iüÌI KSÀÎ /þyH€·X AÎo¿~³ýý ÚÿÍöþ#ãŸoï>0ðˆŠ‚ª‚LÐF ¶¥ƒÐ)HFhäCïèmÞ8ú|Ó xEht”¤c­˜X:]9ÀÍô×À†h)öoHcïß¿ÿðÛÀ°ÏMÿ%òŠV&¨ð¶=ð<Ó7 +žL=øÿë%ùÿüzZŒF2L¢¬ß‘ Ì% »®™ «‡ÀÛ¶þüçhÐr0N`«÷×&ðjä_@w²#¿Ù!%ÁoýrüH#ó/3 x”Ž—“™A t#šä`÷í'ëŸo ß~a8>séCûj»f2øûx€×Ïèi1|æ¸ÿìÀnìÇ÷ b ?Žþþ2V?Äéßg†Ww1¼>µáÛ×' ÚbšAÚÖåI¾ÝŽÇmX9oÚçK'÷1ü6à””ØeŒþóñòÛü ìlÿ?zðoÏÞýàZÔÇP’ARB)0&1"6•™•“G€[ØuùöTͧž?b`O£ƒFlACÓº0º·œ÷˜999Ù€bÿþÝ+Æ ºÅÈ]«Çž5&„ÿ öÒðÍó|°æœ¿éIU Ïþƒ[ð?¡Ã¼°ãQÿA——ÂÆûÁõ>#âÀ ðÚ”C6P.`‚IÅf`ùÂÀ´lÕÃïí ~ýLv^’ \!fœ"Ò²L&zªŒ¼¢ <À&4ã‡/À4ñ˜~1°q@tx #Q YBоÀ/?€U¯ßà•ÿø °4õ~€'Šþï8u#AûÁ‡L€vk€uœ†ú'†/Ì/ÿw·50^}ð…!-Æ‹AEEAF^‘ÁëhöƒáíûŸ /Ÿ>bسy#ÃÏ×í—û <ÜßXÿ~bø¬šØ9~0p€æM^\`x²!HJÐ4sQQ\îlË`ï }ƒw¯ï]d¸{ïÞ¯û÷îñ‹ïÿ¼üLâR |‚ÿÍLMñûûïïo —þþ»prÃßïo_|SÒT{YSZÈðøÅ)A&p•¶tñ2†];·3¨«©ç<²Û0u-; QôtZ(‘"E DB õˆK—&þƒßâ/¸ÔŸ€k—î4®|$,4F)Ljµ ´Þ ²èbM's{ï=wfÎ=Z¯×Sƒ `¶m3ιN%‚†¡Ì÷Bžƒ /åÌü•R{kôqÙn¿Ancz8ä¢F)ËWJ¦ÚŠKÅT“ouç<^ž€¥¸`¾v5yEZLþœrQî¹ Äðñ^€JàÐüœL‡=æ ”ÚÐÌ*Ø^ÇÐ %í¦Åt*êŠ9ùrYú ›$ØHfÈxBË•^i„0Áˆ@ (#Ç^(çÅ Ì÷‘ã%ÑÄ‹‡c/o´(-=)ojêaw[1»)l‹Gvð Lê:Ïø¸»„g߃Q-^·LT¬duòÈf—Bí.&ÜÆìó¿ƒ|=½¿ý8rÏ!×4 r[a#6â<Ížô\V(Ĺ4vÜh:…r}u1·Lc^ŠýøøäT+[I@(Ü´Z0ÌÕæ5Ù:>xºÅŸT]1KÃ@¾»—´i’Æ*ˆâä ÐA;ÄYüÎâÐ!`‡nþ%AGm—Ò6¥… IÕ†»ó½K¬qÈBÈ%/ï»ïݽï³â8¶ñ†?:àA» ?Ii–ZÅéœO¾’˜ü’ŒŒéþꎙjnQîÔfgÎRç a®¬èìù„-‚‚®‘ú H]r×*i¡lTdV!eáúøB¦bB^×dV01R¹–Qwˆ5ĉêã r×7Úæ$¼¼Ã›{”ñ T©Ðg›¹7¬ŒÙa|þmê=üƒ|0I8M*çÒ,~.3Å ·ÏŽ[°Ðú‡¥ýûÖ{z`¯Ïw¦Ö]Gz¸³wÈœF3 à˜ ÷ µ’Št 3f…QÝ»¾dÛÍ“óÉ`àÜŽØÕéÑîb:î“z-Mû3Mô—ë)×ñY,²LXàº6I ~ŸÉ‹vG±vG‡a(£(ÒAPçë›jeuM§ÌVµÆ~ËçÏŸAW¹ô:(Ð F°Ù@vè­ª°#`ù þq@‹iè^Âÿ*¶3}cúù#@Ø´äÿÏÀý Ô˜b„œ{÷±Ýø?´Á» v6töŒa—ä°Àé‚î5Þø€øмÛ?˜>núÂph0h^ö;$iÀEùzÂxÁl”ù ô:ÈF| ü/ ûÊ!b,àYDDС"ªõïÇÇ?‹gOÿ}ìôE6'O&)!–—0p==ÇÀÿ÷°ûù›™‡á¿$0 10³1pp²20±ÈÀTö4÷ ^ðLaÿx@·n±|þÊð÷Æ>†—[ÕÓ?}bûjg`3ýãg¯!ƒÑÀ×À®úôáÝ?ð63` Xuƒ‡ÿXYYÿsrq±@ï(bxøðcTT£ŒŒÌosssVuuõßzzúà¶@±€ ÝþôçÏІ +4AAw ±‚øÐ‹ÊX‘§‹¡÷%2#í"úrûÞ?–®ïLÌ©ÿX„AË­¸=(ЭÓÿ`;°taa;Qÿ!apí6- j¸€nnõÊ~[àÛ>3\Ùü…á$qBV!0‚)#âth"/ºGßô‚fA:õœ‰fƒÓ°%}àeŠðñK°“ï^8öˆÖ‚Ô±I³XŠqÄÈò0j þgxþ‰á#0ž_ÜyÌðþÍ{A)1~)!`ÃÔef`ûô±ýf8ñk3Ãî­—tžp¼}ÿù =prR0ò™ —ßý‡6Ø¡G‹f&`¦þÿŸt_*(ÂwìØñïÛ·o IIIL@±ÿº<t(õ€‚ ëü IƒÅ¡Ww"] m¼3‚7òü<ĸ?ÿǺœåÿË¿\ÚÀØâ& {߀7Ã2Â~°žÐÝ^Ðg‚öc¡G¬LÐ,ʹŽT´¨:Duá7ÃÛª× ûÅÆEÈ9ÄÀ¢Ÿ<€ÌʈÈùLÈl衘LXŽ»eBšõdGÚ'|"*;Ò fèŒí?Ø×ÿÿ(©ú¤ûÌøó;‹à¿Ûœ<Ü*ÀF˜÷^`À?ùð‡áó» ߀Ó_o?1ÈË+0ˆ ÐãïY¾1¼üó†á©4°¨ÆžRVBÉ7¿ù®oÚÇ .¡S÷ÌàõHŒ°8]þ÷zÿ%ø62ȇ°>Õ&P©º!MDD„ñýû÷ÿ7lØÀÈÁÁñ €À—gB¯.C΢°øÒPlÝììÿ¿ÐË!W-C&öÜþês›áï.OWuð©¢¼S߃Îú½m¶ü :Eüœ,°ƒ<™¡§T®d…&qÅ3_2­îϰh;è”ðÝr  =˜ ‘íÍ 'ZH'Òµeʳ!åtÍ„F³"E<Ò8+Ò2zP ‡zØ,³@À¯'ÿÎ{ÿóò;ƒ÷-”9Œ¹™X…øÄØ~0ˆƒv üÁÀùØKFú/¦ ßx˜®ó¾e¸òçÃ#`ñÿÿãui~`Ê6f’—×ÒÐ0=uàúAØ>MÐ’T`s11AÚÐ@3t²ÔØ–ƒ– àítàâžBƒ¶óóóƒÏ ðjQÐ>'`Àü‡Ü(„Ü8ÿ šùt>h€ý„Æ!hÕè$fH= ÖÞòï¶ýÏW] ¼óô™8ÅA³1ÿÿ@ƒòbâïÔ`ØJFH‹Ô;‰nb¾ LZ×?0Üh{ǰîø×ÿw i¨‚ö3BNGøþ|é#òiÌðNÄo êi8›ZÅÁÛ ;‘"ži†|gôòRFÈΤQRP€³Bôtʯ_?ú{ýõã?Ö+2™ªsùòó°; ÿaüÄýádžÝìçNqføÎ÷Ÿá½Ð_ð<¨AÄrHK~g¸¥óƒ]†]ÒªV±›Aä7ïÕÅ&}øë °Uñ™éËwæ¿L\ œŒÌÀÄÀº’Th#´ Ô&ø»3”@@Àè^D&))©ÿ ³äaØ„HgÿiÝèø ”Ì 4x>€‰ ¼_”Ó`È=o† ºõí4°þö¤‚Îc–S–…¯Øþ–l,Æœ1\І¸AÁIU M ¯~Cʓ͟.øÆt~Ý'†ƒà£Â!é…|J¤.þÅYÛö)Á¢ k3A–×þ‡Þ?ÊŒT§ƒº@#6ŸÁ U« @]a6h‚‰s”0@?‚¯ç%èEϬR‘ÖFº2òwÏþXw÷ÚïM\¢ì6ª¢\RÙþéýd8Èü’áÏW Šß ì¿Y„~ó0³r«@†ïï¿0\8q™A€O˜ALP„Á,J­Ž7‚Íñ¢'e_Wþ>ýãáŸ? Y>±|adbfeäbfõ˜þ112#<]Ï©Åÿÿÿôé#°øÿ'++˘‘‘ñÛÓÓ<\ €«+Xi¢³Ùdiö`©à¹_Oñzðo¼äz÷Z¼ôÜSñ’RD%i“¸Ûuf3‹‡°%Ëî¼ÍãíÌ W¾„%#rºäç¦_ÖW ÙôÙmÐ;]Ð(z/S `õWNÓ]ß-Êù;üüó1pzèŽé¶ù´°E !!xÃPGÞ@Aû$7o<åJqê€Þ`ÖÆëd°Jæï<Á’-P‡dX†Ð=Oœ§¸;Ÿ/Iíøj+£hJIÖ¡J›CÛu-2(Óa€©ãjªÜ¿8 .ÒÞûåºh–kìÿ©ßrÇÓHÝŒ£ó™Šñ\Š)üï ÚÖPé¾äLN'0»¼Mïî¯WoåóË¢œ×æµÉMƒ£® ¡:QRª¿P’$±q"ˆ<Ïuš¦6Ë2ò¬…¢(,±‡_˜º–œ†a :‘]Ç)•ªT!!V] ±à"É–-K®‘StƒÄ¸@EâS)"]@“8ûÜfYJ,ÏLfÞÌ$ó$€CìXƒ€ÀZœ tÔ¤Š>5‹Ø‹k¨X¸SZæNã´üA½¿ÚáÛ¦8™¤G) û'ÇYEƒŠÖ?­‡*p]wÐx“ (<‚€XlŒ È“FPPx}`9ÿEˆ`P̯מþåÄä‡ÿÒ÷Ož1´—˜>ÈeÈÐbÏâ}ð’ÆÖˆ!h8k»[÷Ýnýݾ½ðšPf)s=WùìNæÂgT‰òáâ}KS¹¥Ë«9å×t¾Ô÷7·‹×ÍãÇS½èY“þ2Ö˜©Mö&8ò\š¦Ì–îØåEAUU¹º®©i*Ë’þ`ê v„ahgEÑpðÂDÿÁ‹_ȧy1Á Æxõ`4˜Q®[+»/¬Ùºö½Ñî´Áôr™¬E^‰­DÉšd¥5§kTW'Í d›KèäÌ=ð$(*9ÛWHú!L·Âm¤¤ÅÀJ DiŒ'k° µ­7§®lmdg" ÄãÙ¬H¦úÏñ½Hákqîg,0åT@ŽÒÁøâ}‰”í›f攟Q_{šPfÛ~/ö±³ Á¡8×ýÀ|ÓÔÒULÈûE5piF°Ç_'b,ï…:†éNº ÌÌU°0&¿úóç¡T·›Õúí2y½] È‹º4M¡8º,KÆA}žç`Òà%RÁ$Ià'[×’ƒ0D›ëIHzï¿Ð…§°IÓDkùˆÌð&Åè¢éª¡ óƒÞë@`¬¥„kRøá$DÆ‚„…^PrD¶Nƒ"ôv/±Pâ ²oÖ~½ö%Sqµ"â¸o‹Í«æ¥]ýzr‹3¸=TÎ$EÉ¿v=ÉÁþu.à€±³Û¡p˜ïÿ¾µõ³ƒß1ê*·¶Ò4ƒCë¶‘0«$?‘¼FÇäîd¡b0`0ÊÑJ×Íë^®ôw±µåSìÏǰ÷×s¦r;*á þd¼-÷iÔ~AŒ÷.4ÚöN^LöUàIý fµ#ˆ¶Ë+è%Ò¡ü@€êé+ý£ùÙQqr†ƒÁšIvöil\{9¬ÓB}.¯Ÿr¿¶ÕüJ·ô¶c4îÙ«IEND®B`‚osdlyrics-0.5.5~rc1+dfsg1/data/icons/osdlyric/22x22/000077500000000000000000000000001345713535600220075ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/data/icons/osdlyric/22x22/osdlyric.png000077500000000000000000000042371345713535600243560ustar00rootroot00000000000000‰PNG  IHDR³³\­ pHYs  šœgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFIDATxÚbùÿÿ? ‹½½=ƒ‰à…ÿÆ< ÄÀàxëá›×¦]ÂÄ‚¬ €XÞvñþ?q©¾jå§&€baÝÿ•ᎸJcÕ¢uíÄÂÆÆÆP“ÏXüO K„™Kø‰YçkY€b‘‘•cHùú ýÔÇ ?Ë1سÊhè3 ÃÿìR3´Y˜þp°±=yümö­ŠŒÔ €B±ˆ…‰‰áHëqA66‹8F€b‘–cxûðÃ/uÝ_ZìïÙžšû €X¤Ü¿«ñCÝæûlÙYá ÜÇKûQ©v~«)Ta8ÍlÄ;ý@±œ:z˜ßè³#Ké…«?¦XÈòì nnn°_¿þexûœåÈÓyÊÿ|{aÏÄÊùž›_¶[NÝq›¤0ƒnÂ"€-Òÿ'''õôç/§_Ï›uä$¤8  ï‘ôà°‘ÏÄþš‘•o-Òÿïðò}ìoIà °VÝùÉÿ'C0 ìÐ÷ 'ÿf×h-ÒÿEDBhn«gÖ+ëZ^Y*ªÅìç¯ÌBkr é­×ïùK°¶¼ÿ-ÒÿÂÂÂøûú  ààüdvÎKËõýâ)Æ}VªtHT›ëé0¼ ŒÁkðâÄÂÃÃÃÒàø£¶Î^ïÁ·‡/Û˜þ ²±³SSðùŧók/» Ã7N€-Òÿûûû/.ˆa‚Íkð.aýEìòäù ýùTI­ðíð-Òÿ+,/¢wÍн¤Ôø÷¸Ló ý¬ðîé  ô-Òÿ ¹¸ ¾JòTç4öáâêµ/Î #0èçç æuQ†­ï£ˆåÒå+¼qÖ¼Ü_¸Ul¹/1,¿ÿä >³÷qŸ{ ߯2p H0è)XtEg,-ÒÿÿK ë:èÔí÷ûÿ êÜý ò" þùŸ©ª6ta-ÒÿBBEÞ`’ªˆ¡ú‡ûV/Mîþ®ÌñÎè l”ÁeôÔ 8ÁN#˜64Ä?0¸¸ËÀðwÖEÍ2ÛŒ,L02¨² 3lcŠ]øåãnæ/¯ý –©S¦pJ3ÜwgdpøüŸá»FÊ1M 6I`xýù–žgŸfž}¶ädÁ”î&€-Òÿýýü ×‚£þžøþý ܤ‰¯Çï–ÑÌ5+R Qyjy«ˆ…‹‹ %J¿g`ptø­¶~½€ó5½MonÊ3üxy„Oüÿ AÆ__ÿ3XD~üqûÜÇÿ  ü¬l LÒ ?>½gà×TgXwO–¡¾©•¡¼®… €X`‚’Çïßl ? Ìù{øûÉœ7wŸc–”:ÌðûÃS&6FFv¦#;˜˜þ³|aøÁÆqXHÜ —‰‘é"3 È`*aF<3@±üúõ‹áß? å"ý^Â|OXáÜÌÜ/ß|eü¾P‰á¿Œ ƒhÌ[~AíÌ@Ãh6##£íÏGÛ.0ÿÿùæüç×¶*víw.ž?ýþœˆ¥¼¼h(ÃÑ#û8þŸ: m"ûZýÒ†ÇÀ ¹mÿœáÇíŸ V…>2½ýÌ÷õÞ#NNN!qN`&`ÚðèÊ_¿XDÄ?ŸßŸmhPZÝô’—‹ƒ Y¦ÿ²²²ûü!èô#ñøæðîüÉ üµìæþC ÿ#L(Ôì µ(æ²è ]  ‡N töõŠuûY¦ÿ ôôà8€sò‹¿¯Ê@óá;÷'ìø2÷üçÀèÆÐ ÿ,ëÛ#Ný1j öç ôÑ ü× óì;† üY¦ÿææâñöüȈvù»ž8ÜÿÇÿöÓþø%ôû׿øØì"øøõî¶$!EõÐÜßåÆ^ŒÐ‰áô¹ü?DJY¦ÿnnn¶¼¿‚G½¨×¥ñ£ òúû-&úàóÓáóÈòûNU€ì ôç$þ×įx×ù2‹.L5­‹žóîìY¦ÿÙÙÙ¢£¤ÿÞàßœDôõäéþöÿÎê˜ÃêÏ è;$<üý÷ }vhZpˆëÜË.z¹ŸÆ¿ÜáÞÔx`MýIIIûY¦ÿ¸¸¸òòò üúûþ]³anýmE1øÿÿýŒð»ªò¬êúòºÓè¬ÿÿÿ 'Çor&!,??EñQÿo˜ß„SÖÕØæˆEFFæÏ“'O~|V%¿ýº7õíßÊIÿþ33  ã.v¦ìßøùùÓ0¸T¦ýëïß¿|ôê•ô•/§ÿ_ñÿ#0‘?yçànÿ7))‰ À[Ó9lÁ<9IEND®B`‚osdlyrics-0.5.5~rc1+dfsg1/data/icons/osdlyric/32x32/000077500000000000000000000000001345713535600220115ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/data/icons/osdlyric/32x32/osdlyric.png000077500000000000000000000076461345713535600243670ustar00rootroot00000000000000‰PNG  IHDR }Jb pHYs  šœgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFIDATxÚbùÿÿ?2 FFF–e9Œ¿”TØØXˆ(¨#É Ípð<#ƒ@±ðóó_Ùõ„9Æ”áê]€bA7 €X¶/czÅUd!ªYõ-þ¤.@±'2¼è =!Ê/XÂp²-þ@±Ä3I¼Áñö¹às¡[/Ü ¨VÑß™_Ýèäaø÷‡•‘…ƒAÖ¦‰±qç €)àò³g=w©âÃô+ Šjºï#åo >Ü_ò_ïÙw€)Ð{wí‹Äÿ 9 “öܼ%!Ë`¦ø—A·õÄ[€báââºôÍ÷««ØþT].jœ»ïI†^Ž ¿ ?@aø(l€a˜·#Kr¢×f&.^Þ6y çê9=ä³µiª½û}aȨd`fde`fa®Z½sד’Å«¦Hÿs–;Ž_®¿ÖdØväÚÓ4g)im†i±ºß¦Kb S°”èŒ õÛ_2%ÙIsÿ}ÏÀÎÉÅàÕ±ƒ !ÞÿŸ¤þãŒá÷8 N-<õÞÂèûk•Ó™T‰ø×ÿˆ…™™yõ·¿ÿB¿Î¾Ì`cøoÏ•L*\\ ­'D['†²2È›\>çî¿û˸öY㥓ÿþýs­_ëä¸IëcWc;@± y™ ˆ9º»Y&•–þIñ6²Œ3ç RúÃúŠYv—׋mâb† ?dýø~œ`(9,Ä ¡gÎ@0€4£¥1Sk˜£–·z²Qä+ÖŸÿ™XD˜_çÿgdaxýî*ÃÿoÏNÝûÉkÎÈ(ÌpëÖu€ÀÄy³2Ë\exïüÃpÔö ÿvù"f†®ý–”e“bdbbÈ`ÆÇþùOI'cKK @ øÄßv_gù*À«úô7ƒˆÈG†5foì/ 3Èé³Þa5üõñÁfn^~fVf`D±2œì²7ùÆ÷ô @ ðbeat_åùWìùœ7 JŸþ1üúËÀðNŽ•aßí7 öçE¸…O°1™º2ˆ3¾``üû“…‰‘A5ùà™À€°õ÷Ÿÿ,Œåù>º0Y¼ ~NKeù¯ û®|`xóYŽAL«á—‚Ãá¹ù·ÂkVªÖw2¸ºù0 ///Çׯ_•ÅxþKÿúÅÀðæ0P~gO::á¿8ІûÒ=K-D'E™ ëZ©…º6Îêiš´zîä/Šg&`bØòêËß§âGþËÿg`úÈÄÈøˆ……ñ)0Ð>å9q¯ÄfùËO˜þ_vâ+ÐÐë‚ü_Ÿ={ö €æB €Àù 8˜ÙfÌ`*Îÿ·&ÉQ<Î_ó¯;÷3q–/óÄtŸó H101~g`\ÅÒ·|。%$f 6ܰžq™Ò{~“­ßæÿ{÷ ¨ïŒþþgl}õñéW^!E †ÿß¿ » €@€œ êéÎT¢ËàpnãoÕ§b Üv¿˜€ÑÅÈÈÌJ@ÌL?¹ß<;ðY„—_ ˜æŸƒ4ýþ €@ˆ]Û–Àí·c3ÃËïþí¦B1£?@L`ç“!Ø ?Ì,ÏêËËœœÙŸÆÏ2@óùffOvÖO;O2ðþùÏ ÅÏΠÀ+İïñ;G&.ÁÝ ŒŠÞ 2Ÿö2ü– ûN¿ŸŸ\góÙÝËŸ €@|tèú{î¬ £‰ÌÿÿÌÙYݵ ý`øú÷3Ãç_L Î>c°t1aøqå"÷¿w Y+ÞÕéÚ2ÔõÍb k€Xf×OFݘÿ¹~ÿúÍ ðŸ…AWž—á#°Â8}û-Cáìó V-a01ÉgÈ w3ûôƒáÿò݇ïß¼ÏA¾ÿùúúûn5 æ½þõÿß‘ÊüÞC$ý7%ýW'ÿˆµæÒµrxƒw{™^ysû132ûˆ˜Ëd.¼÷úÿPÁøX>üÆx½jÖÝK w€Y™Aˆåëó7êr y©?}üWŠŠÈ?3Æ_,¿þücø,ÌþþcUJ Ú ·Þ0Š>çøÇ¼‰éÏ﹌ÿŸ†ú/ tp±›ëé2†í߯äñÿ Ë#ÜÁ’?ƒµTÞ2°üãbøö†˜'…°¸a•dÀÒ‚‰ñк/’ŒÿÒ}ý”þðÏÇ}ÿ9cyØ8Ÿ1°²¢XÄ ,@:Vž±1/+‡1•¦Ç3:/íãÿyañN¶ï ‡.~aà^¯ÈÀ»Yƒ×îƒÓ{–ÿ`›3BŠ+páÉe3Bõ÷Óÿ'ž~þ÷yýÂ-?çþæâä[ÜXSô?³°ê?@Qb#VVFçl·ÿzß9¹8­¹nÞþÍðÌ¿X~3Ü6xÍðëÒÍ ߯‰2ˆ'|dàddøóç/°œdf`ecc€UŒŒ‡]ÄðŸá?+`¼ ËÍ+Û¸f`Ìýÿ”øñöÓW€bF¨y øû÷áÒ6æ7k’ÿÈ©q=a‘fdxøXƒê1Å÷ ·¤Þ dic]~†×¼ gþøuïÖýüÌߘä¸ÔåØeäµ$ 0mücøùXœ|ûÆðýûw†Ÿ¿ÿ+vðM讪­(†×„²ü'¿âìl ?„dÿ³1q0|8¥Àðåê;¶×ÀÔÊÈpX±±Š0\{ý—áé‰Wÿ]”E%$˜Ù C]DÞæþ²åØW÷ß<ç6ÿzG‹›‡T±38 °þaffeàÆù&V×­ñ/ÔÙÿ3Ü€¥O€9@ˆ%TUÍ–,gòHŽ`|ºsÆÿ?‘ÖOXµ._dPøw›•‘áßϯ ßÅþ0Ü|÷ñÂ=îÿZ߸¿¾É %ÍÏc‘âÏóî3û?þŸÙ¿?f`dfWaà´®þþüÆüØz¢øµÄ;¿?¿þr@[Ú ,ÅϨ®ôŒ‘³Åû¿„÷WæOg˜.ý¼Æ õö°†„˜ ㆿÀJö /ÃÉgï¹Ù™¤þƒíó'†ïŸ3È(3ý¶``ÿlæ}½ÎjÊjv`•ÉÀ ¬Gž}þr¶ +ÿŒŒóÕ?UµŒß¾~ù@,Ð|/qðì¿w¹ÿŸ®pa”æ{ùŸáÛç¿ †¿Þk- ì?`¶çfÐ’fûLx/0p³ÿf¸þé×ÿŒ êÿY^ìϯ÷Ä3üÒeø/ê üé$°”¶íYXÞxÿºiíÃܳ/¾ž]6‹aã²Yÿ}Cc䀳@ ²æû‘GÿÿÈÌcxê/ ¼@ƒQø°:ù j|²«Q]y1)~ĸîÁûò;ëO¿ö½ùË ÎÁÌ ¢nÈðß8œAHÙá÷§g Ÿ/=`üs›aÿÙ+Ö¼Œ»ñòß#KW†þù«þ|ûÎðýÇO€9à¨BÒ¿€9ŽMSœAs²'“ðÏ»ÿ@M `Ëðh)ÓÜ« Œ¦’ |^ JÀC”AZ èï -Êßÿ1<¿u‹Á\í5Ãã—ûÄ$$´=¦2lݶáÙüû[òÞ3$.þŸ‰_X<ýÊùéÕ‹ï,Ü<ÿˆELLŒëýû÷ÒÿþUæOU#Y&AF†A.6Y€40 x×£ÓÏ?û¬~~Ø0á–`ä•ägáàfgbûù‡åÍ׿Ì/Þ?`:ùœ“µ¹ªœãõñ 'ÏÍav¥Ê½NÎ`eŠÜ°uÓþûw1†9ý´±±etvrd –/_¾p-æ§F†ÿKÏü{òý%#óiF~!f`0‚“ÃÝ_ oóž3Î&ëÀ¢…í°KúèÃnh *jàêåÃ8:Ìæg­Ï¬!ÂùP÷#ÃŽ?_b·1úóóÏ?&f&–Ó§OƒS6@·ÀìòèˆóÀ, ìÖü¶îÉÿ[ë3ýÌøhá[`2~tÜ`NúÌNŸ€Yë ´kŠB^`u ª¼Ä€‰MþÏŸ?Šÿÿÿ“ÙxøïÆÃÀÎ/+ÓAmv&~ vSµ4‘­ŸO2$°¿ã¹¥¨¢È””ô €€zX€qÿÿ(€ên AÇ€4Ïg„Zô$Ä߀ŽáŸ@üû?°£¨‡HƒBB„jAå Çß?ÿ¾¾ðý_``øpTƒ|~ûÞm†]»v15oQm„ÇIEND®B`‚osdlyrics-0.5.5~rc1+dfsg1/data/icons/osdlyric/48x48/000077500000000000000000000000001345713535600220275ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/data/icons/osdlyric/48x48/osdlyric.png000077500000000000000000000175251345713535600244020ustar00rootroot00000000000000‰PNG  IHDR00 É pHYs  šœgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFËIDATxÚbùÿÿ?6@,ŒŒŒ œìŒ­‡¬%«øØXYäƒ&0 P’ëy+cUËáç ª>Dz œ_šÿ €@Š~00„ð01Üù«,ðáãWii)€I<Ÿ9ëÿ§úZs¾­ÛŸ1hØ­a013c ^^ÞIz_y¯w×rqÇÔC>^œ"¬*Ä‚ËUrÛ³}Ì??e`ºˆ‰K¢E§öz-@ì˜ueí†ÿâB ÂoîüSRªùúåK-@±Ü½À~}<ê½÷ò㙾~½Í°ýÈ€bQ6ø3û±áÝ\¹Hc†›—¿1(j³3ˆÈʽ ìï¾þg0(?×ËÀpÎsO{šV˜‘"@± 9DjÆtæ·7‰ïN1g˜ôã÷_†ï,ÂßUù¹¸¸,ʸø™¸Í⤈}Ö6-ü}õ¿ævAß²ßäÕY˜˜ÿqþgúöÿÛ¹&ÿ‰·8…„ÄH££súwþ·íŒ {Âo3,¿Ëû?BK…q÷ö3Oý-¤¥ÙY™¦§(}ï:#@48s³18þ,úÁÀûö?ƒ›'Ã'1Nƃ»Þ0¨jHÿSòbûu”áúG‡lf201Îÿºóóó..êáýáô5X%þ ¯Çå7HuÊ©íY™ÖK•ˆåïß¿‚À¹#¶ôßÒÿ L˜Ž3µ^¾ ´™Mæ-ëă@Ñ%ëÁ{`ÖÔ^s€Âw¸@SˆbùUKX<än+·³ÿyÏËÀÄÂÀÁÎ^)Åô°ƒO#Œa#sÃOF.€‚ŃΊ¹LûÄŽ J?bøöT€™™‰‰éoûGVåv>&°©ÿþýc °ÉÓ;™Öü<*%¸ãýÏ23¥ªw ,ÌÌ@ @M@úÃÛÛÿý“|í=|ƒHƒFWÏƽÙO^`b8¦ñŒáÝk¾¿¿Ÿ^ÿájcÀÍÆÂÂðäé%n–Cï¿ýe †×ß~üÿý™ÝŒÁFà ÃÂOŒ êþ2ËØåp]¼4ÿ°˜ ; ÃÎ䌿P£ÉËÏÿß)ð¼gøöë?ƒŽèS†›äÿóŠìg´ª`὚™…áØ#æ÷"Q+¨á¿Þ2fË;n30ýd`P6RaØñîããl <"Ûxy8þ“[Ò¬ÛÿÍ0d “¶ptýÕ{aÆhûûûCe†7_™v_ûÊ`ê±€áÁ³s +Ûcj´ L&/ÚÀ@,lllwÔEËÿñŸá#ІÖÉ'ßudX ýcbgè) ùVĨ6•íO?ÇÉÍ (i(^~Êð‘Aˆáû?†ÕW&¬Í;& LŠ@Ûõí$犿kѪ{\ôhÊò+þ0¸™Ÿ%7+]a¸ÎÀÈx†™™ñ20üw#I(î2ÿ°Ï®/ø´y4"" ¶ä"&ô°-[õkÕ©S´ÆÇŨõŸ»À–áÏ6ñŸ‚lŒ \ì¸y”8¾``qXÉÀõb-ƒz(ÃÆ;¬ QQQ ó—o` d @ÉEˆ}½™rSSþøÚÿ‘YúÈô-‹²(+ÿßÌ  t ‚BŒ¿Áì߀‘þý#ƒ0£ 7?Ë6l‰ €`°±;côÆUŒE7·ñ ß\ü‰aî7,‡U>Û10H»}dø ô #3óÈ&F -L"[?ªaøýëeÔ²7v˅ž}ýÂôä铲€ˆÍ€Ø{f1cšÑk&ÿœ®^üËÀ.ð“aüKb ?Ÿ 3È'¾gøþíóvvNFn>~ áŒ`ÌÌ @Ëÿ°J/ ù3;@4ôR80>þ\@ @ tïìÿÞk ò^axËÌÈÀö è8ýW ûäþ3xÉýc`üÍÇðø¿þ¯¢ÎmתžÉû™Š ýi_¿ýf6 eLÌ ˜8ÂŽç²Öº°=Y@ 8A¾`2}ûüßߟüÏØ%5n_fd0å¸ÎÀ Ôôö÷7†Åwßÿý¥Ê((q“}K›½áo6½ÿß?üÇòû#7?8'ƒ|Le ŸEíÌÍ£›×ƒ #€Y` ÄÖÎòŒŠÁN,ÂL?2ÜÿƒAˆ9ÿ³ó†/yÊ4ýíf†9a|w¥50|~À.ÜXA¾eŒ8þ¼0üõ…íÒ›óO¨i%Yâ² €X\/jðó2q¼ûÏøú0Ã#Œ ªjÿ~kM`FæVL`A4Ó¿Ïï,ü%, Él?þ€ÌÊÊ̬ÿ˜Úü‚YÌm½Íl?ïùÍðäÃW†o§¥î?VcȺËÀÎÊ)žAe¨¸ÛÅ)º~°~ùñôÓ–-‡Ù¢B ./Èø_PÕò €@pƒZ@ׄD:üWV¨ðo÷îÇÀ†ÎW†[ò¯10H¬TbPO~Æðÿ?¤xFÆ æ ³üyÏjïh¶¬µ{rêÞÃÇEm-°sp2 ´3ª1øu…‘—ó)“ªÈw†›/~¨‚Ê â×>02hé¨2pË 0l?öæ‹Óûÿüì¯ØôµØùø­PX3|úô™áíã§ ÓÃ9“-##'FGF‚- á ˆù*.^°‹ÿåÿúE”ñƒ<P(£(ÊÆpàê;`%ïèb“:ýÅ-#Ž«œ¿ÿ?5Õeáø4øßß?àzX*0˶´—ïE+Ø€Y,7¾š1²Ÿ{ü‰Ñ)އáìiYÖ§ €•é׿" —þ¾e°xÍÇðŒý)ÃÜB=µ5DßÝ{¹ï«ê§oÿ˜8€¾V4 ¶:30ؘYäýÿ€4%Vna²ÌsƬïk6 †çß1H1ÿd¸¬Í”¤~0\ñõ¿3;#+çU† '{¡+WÙ~²pÜF:3¸M ¦1þýÍú¡ð+ã¯À‘@ ”ÔT­ŸùÿõÖ«·?ï2ð³#îû_`3Bù=àkö>~Ǩ,,ËðþÙo6îS Ú®ìÿ>23pþ|m-3óÈ·ïï=¸rœOZBä0¢ÿÈ•û÷ÿÿ7cgâ?˜ÃÂÿûÞ?†ŸÏ2X}ÿÉðŽÀ&ÛO` V¿þ0°«&6Î/ l<ç~së1ˆ°½fî lLIÿ®=ù¼3­Úåã±Ë•ÖO€çƒßÿÿ]²øÿoÿ«oŸÁU&+°–á´Tb|L%À*n÷ó÷ Áò" žÿa`çyÄ ¤lÎp«3p}¹ of†µg¿Ne^+]Yp³ €@<5BÖü»2טAÇhÉ_HCæ·¿6ƒ'+ƒ ë_†¯¾k%&†ãoÿ0 23¼ü•AP‰‘áž|.ƒÎõ ûÿ29s}û’³î€ æbÿ°8€Ypˆ…Uöïß _ÿdxtí:ïŸ?dÁc ¹ê‰zŒÀÒ˜á°mj‰1}Æ  -ÃÀ#ÈÊ 'ÂìBücø4àÉŸ¿ G¯ßdPR7f¸vù¯FÃÄíÝU?˜?¼g`xÇÈÆÁÆô÷÷ïŒ<Œÿˆ…ƒƒƒ X]rmªuaâÿ}îÃïÿ¶<¨@»>ýÔ¯fR|Lå85„YعX}b¸¾mƒ’Ö2 9 ëO,dˆÒe˜½g× U5Q†Ó_}ýýÝ¿Æ@ ˜šä@ͳOÿÿÉÿg`Yì1-xý›ñmÙŠç×ËV<ãç6“8X8¿ÿfàøô X=äÕ~w‚UÎP•›±85¥¬¾³}''ç÷ì¬LÆŠŠŠ_Ĭ¼A_aq«ÿ½¿hÆ(òh°©ÉÀ ¬º^3®%o` äþô›ˆùê%€ê¥&v¶ü+ sd9w‡™ÍV’á¡ñ5 †?ŒÓf6…˜vîÜÉ@àÆ/¨É|~ÿ¿ÞÁÿ_Z%„Y™~v>cÜpóçÿkÀ"ú3°Xµ>@õ†Vf¬ß@ÇÉõ¯Úÿ'ØIEè¾Õ%6NY7^ÑÿwXîûZÿ‚ƒƒÿ(ˆ~ñ# >ÔøãÁoÁ¨GŒ?€áóXŸ-¦Û¿€>‘Y»ïÎGæƒ /žX|ââ•ålåø æ÷ã×wFVVÖÿêsýjzÔ²àóƒk+FƯ@±@—¾‡Ò?€b@Gÿûlcëß' ÿP½_â`Ôþø?æ)ãs†‡~2Èiþføþ‚‹áÛ[vHçàÆ´eL32þFëg Ç>Ë0þf¬zöükÕ_vãÏüO×5r2¹M``â–RŸ »h@!·K¡!Ï u¼Š¤“Cf2£kß”ÿÿ¿>`äýx˜}€¯¯À°: LŒ»Å€­5>†»wÞ2ðÙ¿¦O`¤ýƒ nP0AŒ…‘ŒÐö,#|Hšõÿ=￯Ÿz>Þ]ßÎÉÂÒþó󛦋¿þýÈæËઅpóÏŸ?6®[ÅÀøæÍ[f€b:œ4¸ Äü$èÀ½r£ÛìIÿŸVºIsñ¾øÍx3Бl¿ž½ûÎpX= 0ó3ÜúöAúˆ0Ã[ò -ž1H‹p óÿ›°ÒúŒHlxk~°˜ccøÊúÿ[Ýo.±˜gŸ¯î9tþ’¾í+vYYFY¹_O?bز~Õ¿?þü÷ Œø@,PÇóCûŒ ÌÎÎ`æðßèÛ…ÿ Žb‚R_µ9/CýþCV~®ß I.Þ¿ ·µÞ2ü½l•~a`úýA|§8ÃÃ+d¾2ð‰°0üý q4,‚Á&xìC|“‡«g ÿÀþ¦äm«üåÖµ‘úºk7-ZºBíÁÇÿŸ=vçË—/ |<¼ '.\g X³šTƒ€Ò;¨Ö®Ô‘½ ÿ|„€ðŒÜœï,eÿ3¼:ôùGFp=Ê L*â@×Íž3¼¼ËÃ`üqRoD\¹Þ 0\¿~ÿïß÷ ÖÅPg…“…‘‡‡“Y@P„‘XBz0Ðü p×é?Äc vÿÇ_¸|,dæýøóÀÏÃÕ阮ƒ¥…ä@ pñ'úe3ƒj´‰›ÿ¿ø'Èaó›í÷G` 0½ÊÌ!ÀTÄðâ/Há7^Q†—ÂßN~ûÅà,)Îðá×WÖ·¬ RZZLOÞsþ?|öÑûçï}VúÏ!/øSˆý>»ª'›Œœ"3'°{ lÉñ_†?¾3|ÿþ ˆ½Xp§â‘LìÂ:Üù7_ÿ??}ÿ…ï/\@ÉÇò¨}ø Tñ;Í(©«ÂÀïYø•“]\áñi†ßoÞ3ÿ}É Áüƒ‰¨ë%ÐOo€ o11†WÀ¢qåµÿ­Äø4~32¾üú†AH„ƒ9)@Käõ'sI›n??võÚK¹O|/¿þà2üýˆ‡ƒ““ù?0¿°€ÂÔ0†-x~#L 3ÐÀN*#“K¦ékµRK–‹ ¨ã ›n`…Ò,,LŒ"‹3o_õïçŒáï•5ß¹-ï2ü¾©Ãp㩠×O€éìÐ"`wR¬ÿ9^2ò2}{zØñaùÇÅÍÇüÔàg„ö*`™™‘ :Ž é»2þûÅý•SFú©bÈÅ¿¿¾1 ×ÿÄ-2%XNÑ|Åj&/U9FÞôÔ•ßs¾¿ÂÄ-)ú‹é×GE†S?3|xýAôÏGpOƒñÿ/†?¿0hˆþcxÏÂÈpåÓOÆí÷ÞþPcüôê/ÃïŸ~~ÏðçÇ)NAF{[kþßL¾ l¿30¿> ¹àÐf‚:”r@lh)Å ”ûþåçç÷ü&ŒLú9¢?>¿ýüÿÿ?ø0@< )}¥øxdD¾2rqÝc`6eSäýŦLû¿î02|bþÈpû×-†ÿÏß2ˆÿþjc2pƒ˜Ï~1Chó¯ŸÌ R,\ÀÎâÆ=Þ1¸) 1|{ÿ—á°–þ ìOüúvŸGä+¯*Ã/V)†?b. ¢ßN10ÿ ʲ@‹RÈð¬@beafæ¿W÷ïØpqÑÄÓ¯ëÛûY”TÔ¿~ýn„ t`E ¨\âáSÆÏ--ÿ^ÏŽe’èÆ(ÿ ˜#þk^ÆoÀ”úã ƒÖ¯ÿ Àû÷Ò``&!>PE€ƒá+—Ã篬 bü¿Éë/Ã`’Ú~÷-ƒ£œÃŸŸŒàéÏ/Ì@ö{ná‹ \‚o :`Ý!êÌ öõ$ó¯O ¾5†z2 \À\(,[Y?ýe— ÀÎÍÁÆð ¨oÿë ÖBœ ÜÀ\öåhØgú l}æ žÛ ?…­¾HÆ2H~?ËÀóî,´Lad`†üûŸ¿m;q§¡sÿï5Ðt¦V-™ËSÙÌàæÀ@ <„fàßPüÿá§ÿ¿½¶þÅËÌ(:Qõ¿X€ 'htTºK8g„8˜,U¸€æ–D¼@ÌŒž? ü~ë‹ß ì_3|eö?}gfâ`Ðåedøùì%ÃËÏ| B–ž ! ‚2 —Οexwû<°6ý Ž&V†÷ž>\ràaåÂsŒ«?~ù÷”'\}ƒÿ7÷Ïbøýõ+°çLÚÀV@<ð  Añ— Ñ. V ©Í•aba6¨ÿ¾üNÇ €“ÐS`>šuÓU‘M_’íÏ?vV`L±3=Ä\, />03¼vâ8Xþ0|ýù‘aßGN†È‚ù ®Þ ·îÞcX²|1?/C|JÃc¡\†£—MØïù²}ãÊYkÎ~›úè#8pA¶ý7±´g˜¶r5ëǯ>~xÇÀÉ'®Üˆš|@9ž X”z°€%ÛO!îÿâóý˜$8X?Þ&#`Ü“xÚæ/¬&*ü³ñ.Ã×Gßþq8¨2Šð±2 sbØóäüÉ ÈÅÌðØ3} ôÄÛ/L œ?þ0lœÎ°¶7…AÆ4„Á;2›áòùÓ kV®`ðô b0·vd8³û ª¯––…ÔÍÿŒÀæ;°SÇÌôøÞ­ 'Nf±sòâùÿëû‡/¿üãý@,JJJl¯^½âýñã‡(Ðñ2 t›,° /h&ÏÈöýñp¨ƒ d†Ÿ Áþÿ`¥;`Ú~rîùˆMÏï2³2°+ 2ð¨IrrÈ‹q³ ó³³°ËÄï@ Ÿ~þezõñÃË?ÿ?{÷ëßÕχ™<ÂÒYc¢£NØÂ°nów^Ưá–ò›¾|2IÞµmý-`ï‹Ø•ÿ;£§éÏ®µ‹>ûúù1ðÛB¼<Ü Äòöí[6`næ:\ˆÅ m"Ž ÿŸ{üÿ÷|E`ú‡Æ#¨_òêÐØ;° Ìíø¬¿þ2\fl®sÏØÎ=ûLt?X¡¥+´WÇmññv†'×X­lí™Ô8b´8ÍÎÀ''ÍÀÁì¯qr1°«|ÓÑüÉTxâ _õçWüüñý7 ã½û˜fΜõ[BBâo``à_€ÍŒ²Î u#¨‚ø Ì/ßœ75ÿý;xA´]žØ–câ:ö;óýƒ4U^þføTðŒaÃÖO geøX[a]QN¨ÃAð £ ( @ƒ- äŸ_ØO^¸òçÙ¹1fªÜ:¯Þ°žâzÆpC˜IùX­Dìø´Yõ>?cÜÃÈ„Àžƒ†JÍi†çÏŸ3h˜˜ô™þüû÷4kò4Ê@qi G„v¿fx¶ûÈAÀ¢‰á¯$ °üÇðòÝð˜Õ{ ð`+ò hØšŸ~, ®@=Ámí{—ÿ˜YYEYYXÄýú% @5†Œ÷Þ8ç2ë]šÌ``†¡;Èvûÿÿó´ó@èœy%=+HiŠ–šuý*ŸÕ¤†øÃÜ ¤*‘× íF6¤»AÀˆKd~àÃI @>w ;È `¹´žV&L[&tk_çø)ó M>E-£xnK€ûìÊ"³¨eïIEND®B`‚osdlyrics-0.5.5~rc1+dfsg1/data/icons/osdlyric/64x64/000077500000000000000000000000001345713535600220235ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/data/icons/osdlyric/64x64/osdlyric.png000077500000000000000000000241061345713535600243670ustar00rootroot00000000000000‰PNG  IHDR@@ÝnAH pHYs  šœgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅF'¼IDATxÚ|Á?/qÀñïó¤—H¹êIE$‡ÜÒÊIÓD &“7`³H$«W`« ‰ÁÄf‘4Ò©CDÔõô"-¡¸Ÿwàók-ÿɈˆi'™îㆋŠâ8¥ÕgPÑå=½XOéìñ<|>O¼U¶Q+pvl.Haw¼Áa#ø”ß/Í&¯›c‰£ij»Q¶ÂäP›‘Î}ßÃð"¹‚O=ê… ôûFŠåóf*\ÆïÄ·G“enÿ§ªÀõÓoI ¶OjùéAŽï¾¸úž" CcLADÎê%¬ª.©êŠˆÔf×|ëºîÀÿÿb$6¼ŽÔ¨måý÷†™™™‡—7E¾ôî\X8¨ï©ÒÛ´zÁAA!AANvö9¯&ëÖÝùž‰Á^‡õ*³¢Ù†+o8–í¿÷êÛ· oß¾nüõó##£5³ #ƒ'CÛú ÖÎfb ì< ü¢² ÷=c`úÿÿ?Ëß 8ä­ÅnyËpW·áÅÛÏOZÊÒ˜XXXfÿefbø´ú.Cˆ­ Ã3–_ ü‚ ¾³¿ëVuÌd``ffŽge|-ȸ–™™9†‘‘q##ã˰ý¦ÿUTTØÿÿ\½kSQ@ÏïÞ4‰“÷^ZK°Ä@Q­U«‹ BlÄê` ‚¸t‘*hÿA:dp±“•"Ø¡šEZ¥HTŒEK|&`B>Ì»×¥.g;Ãá`­eó…[ñÈ €Øƒ³ñþÕéžÝ33—Ô?ïÄZ‹ˆ(à@$"÷>Íë“…É>òw~—=´R(%ˆ(´Ötì¯S ÀãÂ<Ä€‰[çÕík#2ðzÍðæ«Ç®ç>{îGˆ|_ ýíL¥PJ‚EãN½v­ý£7ssm8xù†ê W’ ê*‹Q2Í2دjÉSáÆ‡W]ƒÖÍv—v'äâÞjñ]âÌšc"2¾1«ÆíG ¡k`ÙsX(÷rÌóéÙÙÃûØAv0ÒW—z_,½ïN“?ÆÔ‰ÎºÁH\åK¥ÅË•*¥Ÿ!w护½ŸŸäíЕ‚³ü­Ñ¨ÄuÝ­µZ-o­.—\¥lhZ8Z’9À ø/ {ÙÑýMkqöæÓs¾ï×Åq¯ÕjeÂ0< u ¤”Z‘ˆcrÖÚakí ž¹®û(‚Æ_ÂÉ'¶É:ŽÃÏ÷÷¾oÛ·}Û²¡ l¬¦››°Á6$¢ “dÆ@Œ¯J<‰œ”‹„Äì‚Ñ=8H¢Ù`( ¸…­fulŒu£´Û:íþ´}?ÈQùœžÓsz>ýÃæ€ˆ8"âÞ› îú—ÃWÞ wNvëÿOð $6t=£œ9!û¦¿ôlª …’ûUÜ::Þ».ÿ%ˆ+¥ÞÊþ&=S§kˆœ#{J(˺_ Rˆ,Ëb]êi‹—?×ç.\$W˜³•ˆÄ€gsפçøáZú³.ÝðÇÍ NÐÅuC„Ü®ë …ÈÏ ùÝÝÝöîç»ÔhæVUIyÅ¿*¼ÐV ¼gsD.E™ÏçLaú¶v' `;Ÿíý°jŒ>rh? ¨c‚Óí¬QÐ6wα´ÃǤ:¤û¸ þxyxѱmnOL0ug’…bñ@~¾„ïkP‘Ðøü2~â)Ö»†D0γin]¿Ë÷G÷tffíâøT;\C \KÔ‹2<6Ã…!ÐV˜¶^ü®/½T _”.Òà ú/›üh𝾹6âEñ<°! ³²´ÔlY66Ð1sŠÊ±çÄ9ÛO²l¨·…˜$868ÉÕ:B^?*¹ƒde d§ïíêÚôçú–ÇQ@©õ`õ3¢IÞ­`òÞ¶‰Í5lÛ° W„éÌ ‹¥4¥h"°ÿtöÉŸ†¦t<CßWR?­,ÀrbÑ“1Úó¸¶\â¥úèͤ{ô5>8yùÈÏ#¥ou-='zÅr]W´Ö[î¼mí^Õü]òÀ$…)¿1TåŠÏW'?aß¡©³xñ††¾Á̧¹‰ŒiiiÕ¶ïûk1ÍÆ€o`·Z ¶¾{±ˆ1 îÔÐÞØû;S^ý–¦Æb±øÈ¹Þ3s ³¹’¥”Jcž8ú«v__E"j!í£rPD®ŠHZDò"b/Ìb¾òœóÛ57tÚ¾ÙÔÔT×uוËå6cÌVcŒ'"“À¸R*«”ZÖZ¯ÑZ·c¶)àwå¨oý²ÿ5úÒË56®£ Àß™»÷î{s½ÛkÇÎn;©ãTTµ‰ƒhª$¥ 4URÄë( ¥BU „T‰ðRà?€„UÕ!x¨M ô‘&5iRœºMgãØ›}ÙÞǽw†¾DBˆ‘Fgf¤3gÎÑœïÌÈÊÒ›Æÿ0þK{DÄ ã¶ P_|T=øÕÇÕW²®?xä@q߆¼{È5 v„‚•,:’¤nõùhíó±ÞmGÖ}úgú>€ˆØ@/p§ˆ|`þšõó†ù£)šeEÕYfmƦU‰!(A‰ÜÐE$œ+Á²lšéá™Í;ýè-^þâ·G±”º…gVX]òJ©=³Óê‡wÜœúõ×£[Ówfàúnc a“\/ ~¬ü/ƒÿî nY#”Aªp©ÿ3¿/Üä¨:òÄSºR©!€ ô·‹Èø‡äàžMÒ?ûÚz޾^g {« xc&Æöëq#kûóä¼L,©ÞePÝdX0Jaœ4û¾w&õóc“™þÁA¯ãëcY+`#°¸KDFç©Ûü¶¡Ñ6ÌÖ„©9áZÛPÑ/õ2±+MwOŠÉó•ŽTÞY¶”¨®8ÎÆþ¸Ó_V"‚R …`€ùr™Zµ†g»ÁÖoNÇ|cüѰBiY`À¶¤ðågU®zf„Öõyi“@C ‚•æO¯Ï™a;.«WÛÖÐØxLܱèמ.½©¼E¦k4Õ¨×i4ê,6ê­q¢QlÛQûßSf:ÿ©ÒÔôe.\,qãÆ÷+¥6{^=ôÒqs­òâ`÷λßV]é1^œK3êÍ“…A(±|[Ê31“ðlñZ 2é*û>twO_a[²O*,‹íĉDDœ$ÊI‰&ˆ:Q‚ì lþäd»µ "D€-À¨1fçXFenWí6ÎZúeÃSØ•vrÚà)h§Úœ[LðźdÍÚnãUºÖJ51†“"]} %+•]”Aøû¹³/|îŽuêÙ?¾Bß@A °x?°ÉR2våa5DÕ@Ã`Z¬ (ÐP+pÕrxk¾ÃLÙ§Ö ˆqF»@“Œ³ª'B2·…®\žLùø ÉÂ$üåïþúØž1‡³÷þtµR&¼~$Ð&Úûã ,"¥ ³ÆhðõʲÿNº"2Ús5=n‹ s-æëþP6ìÊÅ™}õÍû°wÿa&O$^›Äökø(ýý_ýåßý³yêÄ…j¦>™ì*$|]e€`‡ˆì™:hmO¼¥•WÒx7x‡tHö½è@³ÔÖ”*mÞžoqi¡My) [Ã=”ˆŠ0¿0Çîû>ÌÔ›çÍ7¾sx¸:wñâéÓ§5ÀßfíÒù³'™2R,c¥R)ïûþ„Öz°ûê#VÑ;«i^Ñ,ûÐ4+é’P°eJNF#¤7®ÆÊšD6íK]m5[ÓÔUŽóÓ³±¿ù ~å*šÀ[Zæ\bÇGìánWÖ3:茷úÒ·Éd2]Íf³Á6cÌv`S1IñøVÉ5¯hšš”À“UyúGeyÅãÑ!ɰn¬1îÎý¼'½·»]7<ĉÑö† —¾5ñÓ»,Ëj:ŽÓI$^±Xô$•Ju{ž·*‚µZëQcÌf`Mˆf TYàrX›kÀ’ˆx€2Ƥ€¬1¦Çvœ‚ïù… ð׈ˆ (¥èä'ñÞ{“}¹T·š:3÷¾ÈI÷µ‘‘ÿ¹cÏ⺮ÛétbAĵÖ)cLÆ㆞)cŒ'"­Ð貈,*¥ZJ©cl­uRk1Æd1Ý@_(Óáà¢1æe`¸:fþÉ›™ÆÆu^gø9ßÝfå 9”¸‰4e‰’EK^d9¶%«Šëfµ U ­[´Fó«hÂ0ZýÑݲ¡È:Mƒ"vš4±Û:v-ÛI¬Å²,Y2%EÅeLq’3œåÎÌݾþ˜+•5 hÐþø€{ç^à;çó½ç}Ïý¹ýÁÿ™Nø€(ù$*î †mÛC‹‹Ñ;==A>ÈÔñnósxýŽ'Ž#­U/¿¸ÿ‹KŸ;€Ø(%âJN†±}yE^:þ²|ü/ž’¹?9<öÕ‚Óº?­ë(ü`ieSW=håÛ%~½çO›sÿÛÌø8mSJÝ9M}ÉÖØG¾pË©D{aß®ñ&y'Þvƒبˆ¬$nbdzܾ´ÇùƒÿóЧ€A`÷{ô#VˆÍÃXòÚ¾ãs>÷öz4’ÿ­ú@”BèjOo-ÊP½ë‹#Ÿè{¢ø£yó$‹ËkDHÇäÆÐ›1ô£‡îS÷÷d´= å “‹W…=/ŒRšê Î,ËĶ,,ÛŠï;ðƵe‘ŒªøFö¥å/o{tcã;·ãºM|ßÇó<[Å™ÛñæC‡öËo~ö1Ù?bô¬NÂrÉçí9—™0$úúAZ®‰muœ¦eZØ–ÝYè<7±TDät=»üçöîë$I>ûè/+¥†ë©8ó`›ˆ<ôƒoŸ¾ðº–Gn5—'³EäÚ4˜ž[¤úâ6B­0LÓ4Q†B†20 ÃèüfFçú…sÜ{“yùÊ•® —®ä†( ˜qÕ{€ÝÑŒæ3÷l-/ZL-Ÿ” §{+ì[Íѽ\ãêó6{~Cw¤¹ð_kBÅ×€ Ú+òOö}ËŽ¿W¯7îðÂèí¦RozÉX<‹Ù lŽæ¡ V¤/5ÃÎM.&hû¬!ó«MòÛ³\]l±4?½?u9\_¹µ¼ “µRÐ0n ”Îæ~û®+“IŸVJH;&}ù*ÖfÌýþw^Ô ©A"Š(ØBÁ,„¡Âïš"ó»°¶Ã…Mƒü¤˜¨[’ÚÛWëîÔä¿ÑèÌF®ÿ¦aàµÚ¼_,2yñ,ßx¸rèƒVψÛj/pЯBòû¶mÊäÅE……Ðö B2#LM†“&vJ³ûŽÛO<LÏ,ÕÛ¹*–ÍÁA£Ñhpma‰Jy†ÛÄ0Ê0ÁéîÿË—Vž9}ñ}–¢+ét¦@¨‰HøÑ_•ÜðaKáNœHS05}ä H2„«BÊe­ë%µR‘çž:°ËÜkÕÚ´&iMNYZ­¢TçÈÚæõ#›$‘Nß3³P'Œ"NŸŸâ_~|£ "b¿ú¯jï÷¾©’ª±kÔâ¸w  ›Mèµ åØ4=Ÿ×‹+º¶ÑX (W.óg¿³ØÙ¼;åúøŽebXb§ÑVœ,ØY,ÛÁ²ìD«é"‚e™7ô`¸åÎ¥PnêVù„ƒPä[ ÌêÍtiè5ÀkT¸y¤BI×$%&Õúb ¿vž‡>ùp~ÓíŸJ‹Æ¶mlËêpƒicš~0ì”x¾çø^ €T¶ ‘¯þìø¾^›­H;ç·Ñ%Ÿä…Æ$hòZ±Õ)sUCÞ0åÔµšëM‰iX‰yÄø·ßî5‡Lý¢†(ˆGÆJ)jÕ•ÙÒÒb.™Jù"”禮 bØ\Ÿ{WVz>y+=Á²æÇ''ع6ò!BM—‚¼“äØBY2¶Â­hpצð[óÔí±¶åü'Kš¦ÉJµ}öW>~ß*èJà¼yäßP@·Öºç‰ÏG‹z¾ò µ™åc5`Ïâ âj W“hEä5˜Æ=i›þn‹‹Ë.a(TKmÜrHØœÄQU7?Ô)<ÓÄ0ŒóÝKsÏDa¨g§¯pæÔ‰<êçNi% %.k5 ¬¶ÂÚ9F_>A¡ËfKO‚ó —´¥ðšBµÔb}~…È+R[ñ7´éŽ’i4½êËÓ™#€N$SüðŸÿ±CåÀ ÐÐZ·>òL4ùÎeíé²Fj¹ ›µ øènr‡B‚͖‚ÃsEŸ´nUhÖ„jñ­uOLne9EÑ à} ¹QþóV$×U¿sï}ïõëuöÑ,šÑ‚vÛc#*8–pLLFÆå‚$ÄTR8 * *,HʉCILL±TÅP&ÁÅ&#“REƒ¼I²lY»¬ef4kÏôt÷ë·Ý{òãݵœäG¶®ºÓo¦{ªßùî9÷žû}_ÿ¿åþ·ê¿t‚&êä+Ú×\(zŽ2cž¯½‘è§ÿÁÿ¾êƒÿgà?Í{t•–3QË+¥zŒ1¯œ_t]à½wWÆw?¾Ð>gËb>§>v³7ºmÓšÛ=¡¯’6*厣˂=» 6†…lˆ4<é*:âRúl¯žÚï:âøØ§êÿgØà•e#KŠv¸Rʱ»ïäëø+q¯ãÁC<øÞáÏŒoÙ:Züƒ¢h ¨Å^ý32V–ÝhÓ, @-|„¢ - y=:¼øùÞ;ƒ›nâÿQ:RÞµ”M·Ýª„#Ì|ÕÓOÊë7c!Àˌڱ"Ÿ)@q ‡.$5¸òê_(Füü@ÛE+ãʯxŸpòh9ýÆæÑÞàùOönß}ŒvìàÿV:‚÷ìi½×ž[Ö!¢×þú]´ùûÅ6Scp.æ~RáØ ªo¸µâh‚°é v¡œ-dÚeʬ—^§•¼¸D¼­€×ÎVĹÁÙrxê÷…wkýoßÿª@üý×ÿB ø¹\'©' ˆW°éNõ^²Á¯B¼q×N\ó‡;®Þ®]½ÎY÷èßñjÝd¤M@7 ³ç\<ñc§†0ýÝyÌŽUÑÿKÓèóËè «3¨ËÉôVj_Ó+^£"K©?RË5^¾wàCÇþy$š1[]Æî={¡TvFk4›b©VíÙnÃ^G½w !6HÁ7=(ïé)Áð æÄùƒ}ùkÇÔ¨Q®ÞVG³ÎZÀ…yàÈq‰ñýãèOÂHW Ý}]8»ú¶îÐ:|Æ\ÉÎÙ%¼"í©ƒåZyÁR³¯x/XÁÂy<|îî¡? ,V‚™µ+qâ¡ü¶1˜ž™ÅÐððÊŒ{vqë¶mØ€"zãý§îy'm1ËÀžï.o-8åV¬±ïx?À@%@#b,‡@µ LœÌảãȱ€þœƒþÑ!TÑDþÆIŒorÊ:Ñ‘ÖvpY)ud%È+ ²t€ÂPuéÅÞºáþôÐW¿úUy×»ïºe9ŒÍ…©©½C#«C!Šb<þø^tww¯ÔzÑdCFŒÑ€+ü(mÛµMõ…Ùn¼4) u€r®‰j“±­ˆàâr7fæµgJØRÏC ƒa’(z>ýâ–*¶]‘ƒ6Œ8Ñ`Ö 0Hd»«Ÿs ýœò¸ôã²b€³¾8kï¤ V] 'ì}Û›þ&=@ïÚµ‹¿ñÈ#ŒKÂ# 5¼"ÚWì*?`åJ‡ˆhõ¶ÕÜû ±ŽC€@Ç@˜0‚˜Q‹€jƒPmµÐL U˜ !§PJ¸afz60 ÛòWx*à™g¦¸Ø˜H$&ebH ÀwXä¢Þž.éº)_,d«ÿ+ÖŒºƒD[ð# ŠbÔ‚Ë~¹zñüMoù‡h €N™Í«)Ržåç,ëíÐGD]׌`ø+÷ô ªy0c€X‚+©_ €¥hšŒî PMËÍnY¾!q€|—ƒRWFñìÉ$™žš^Îg“¢Yp2~¹ä²ÓW0Κ¡Y쾟‡hׇ3ÍÂV õzÆ Ðí&UCB“ƒ¼Ôú—ûÕ}_Ús^'ÆÕ îÀ±Û] z€×Ñãïx ¼ý1xóÈÖÂìy š£)¸ˆ‘¤@+šP ‹!°˜Ëˆ£Œ¥f³K Öå\\»&ÊrE·@(÷öCS/>—6¿ü£“9µü¶Í<ðúí+g¼bòÛÆ+ž›+ˆ(l]Z„´pðerQö'B*\€$¤”¢váõ{g&Zµ¢8BEhµxžƒžJmGª°™P´RoåÊ ÔõäsÌǸë¦;Mníèõt¸‡k][Ô‚ä Ea8C9°ìG½¥Ñˆ ær³\³®\ f€ Æ”Ðê¡¢{ÇöýïzóÕ#gÃrRI.j£S“h à’“ó©E,]@¸€p逄R¤p@RBHP9(å@)åº^u~æqqÍÏ´¢„HãŽÝ Ã(†´gß–A€"ÚúÚMؼ÷€|óìq´jÓ…ÖÚÂÚ|<'€“F+ƒHž^¬`•©#Ï’ I¹yh§„s ºÊuxåçfS¬v‹$Œ@ÔJÁ ˜TCG‹Epóº~óúÂàø•Å‰Ø 'yíH¯—/÷ !%e˜R*Hå@(B¹2#¥’ŽåæD°<÷ÃðмÈÌHâZë•lÉùHËJ¶·¾A"ºö=ïÀõßü‘ÜOB¿çæhj8ßí£ðæ×$ÒY6@-†òGQ)º˜[NЊ%ãeHmÕ #èd¥BN.…&`¡záBd®®Pi °1ÐF"[P¢ˆžW¢M¶xý›nòý¡ ­¿ %éRð²€LšSBerý]J £¹y.ú–Üò®8l5Ñit©ÍæÌêe0"„°jçmb5Ÿ¦~ÆMúbÌhiAûµf à9 0;WÅbu¥êEˆpRÀO€²É¶• 1*‚PbB<W‹¯<,Ö„FXžŽT5êó!ê3ç ÃH£ äT¦ ª#»V¬¸®ãf ­ëÀõ8Ž— ¤ž ÇÍÄå8Žƒf£vlîÌÁã_þÛÏŸš¸ÀÌÔe 3àçK8ð³} ·Øý@™ˆ¶Þr¶>üÇr+j×3Š›ÈÜ3-@Ç„C^«1¶ÇYú3gö¢”!X™=cÚx˜iQ‹MٺƠ—Ã{¯ÂärÀã2rE‚Wpàæ =Ex•Q3$.ŒŸÃH~bö(ˆuÖЬ‘b›uœñ Ù*¯MúÃ'žþôKµÒçÿü±åÿ`ßAcŒAœÄøÎ7¾‚êü4$€Í¶çïÐKDâÌ ºÍÔ›zPà‹–¨h0D1´ØÄúV'b D PP ’Á>Äå~h‘‡´.2×È»©`ì;³Œ-½y¸’¦@i¤A䀙a’%8*Á>îCYÖ L!^á\:Új³‡O|ïÄ´~ð3Óí­ï×Þÿ»Ìl°oÏw±ÿ‰Ak iÝ Ûÿ·]¿æg/ß|šõm½ðË-,3P¸É  "«o&Yàœd׬ÂëÖì‚,ùV©l ÇAJ ¸ŽD© p¨jУús ”G ŒfQЀàE¨t ‘(ciàVPyN\ÍÔ‹Ë£ìœñÓGÛªþ™OïI^lw»÷4ÓÓøÂg?†¹™)Š%(¥@6†,“9n׃~+\úDTt=¶ Ýwõsž´µâÙÃçJÅ`nÙ˜õåCˆSƒV ,KAŠjSc±™ÙþÂØ J –"ßøø…> A‚@ !ä†G°êʰiÇo¢å­ÅââfN>…-µïÁ&/Ó½›‘޾»÷¹‡_š¡Ï?ødr @òäéšaB:¤RH¢g޽€ÓGŸÇé—ž‡´MQËjÕ½N,r”ÅHÑžyˆ‘¸¦<0ÃLD×0iv͵¢™ÀÝ0¥$\¥à¸ 9GÂs$²€ü¯n†\×b@ªÌYìHBÎð]BN¢à ÔZ"ˆ4†…A˜'»7áö{>ƒ(h YŸE­:‹s§czònß¹ [®|ž‹ Ç›a³­û¿óO|·öǦÍ9 Z¹w6™ãPHR’t\ÇâÅIž) G@Úâ¶)LJéQ‘ˆÊDT°åƒÛið£o]¼ ˜& C@G™£5µòYj¿I`ìˆÍãbJZŽ’çû$` „§‰[ztt”6lØ@»ùÝ÷á{éÄ ÅððX^AôÅQP7Q„¤ÕD† ­[·ªééi¯Õj•Ó4í7ÆŒ1ó:k˜y#€ÍÏ~XŽ&Pfš¡çé2#m2R$œÕæKß3R”S1Z©±.ä×õ²¿q•ë¬îSCƒ=¢»«" Å<¥T,6Ôš1ë1j!/ÔÔ›×RŸ¯¾å½âŸü¥$P]¬âðO¾ƒ™C»1ÐíBå{`âÍj fËýÆûÞÿq)e"¥4å®nv]ÇÄa˜ú¾oÞúÖ·¦;wîä–ê³,ƒ Aäã8î6Æ cÖXÏÌãvqÜðÙ·Qï=kEAO3Ì#©1’€ ± Ķ ʆKÀ¿4é쇧pÔ’«mJÝ ”€ô¸ŽÌ(t’2N!c© „6,„òM7Þì½óÎw»ù‹‡…™<%5ú×­A®âƒr\¤Íábý·>ô'_üëξ|" ¢XJ™ !ŒR*õ}_—ËeÝßßon¼ñF³ý—ï6R)åk­}­uÅÊIý–é¶D‰Ø{ÎëzÉYëBq 0`’¬ö š.e€´³o€øw&éá†ÁŒµ«¶Ç€ªaTcj+Aµ•`±•`9LÑH Z†3QÚÊ™|þì™tϾ©Fzcã–c~¦†8Q®T 50;¢Q»còŒÚ·wßÞp& Ú„Šb…K’„’$Áôô4š “®ëµÖ.3ç˜9oO„y{@ÊÙ¬N¾}ŠÍSSÌ·õ’綘XÛÓ7]J{WdSýD?}çyúRÃ`ŽˆÛ’ª3vÌÛQ%¢EØ6Ë.À±õ ëÎ}nnvÖ<}~±ÕêÅ•Ýy¥ÒˆËIì_5‰g*gq¦y– Nw”ÄÑôÚ“dD"¥ÔVÄ1Bv]×ø¾oŠÅ"ÆÆÆ˜ …€1Ƶâd™™¬À8lMȽ¶AÊ·µÚæAlðͨ¿aâtŒ‹4„BˆÈÓvc Ó<©ÍEzÀûÌ\PRŽÓ#ˆº’$)[‡vÞ–’RB§©ZÕï+±^Èþëü\~Ôqý—8&Tœ »^|îÜïW¿ÿ«”2ÍårZ)Å¥RI÷ööò}÷ݧwìØÁT,{1ŽÖÚg朡ËJÄýV­­Ørð,dû‡˜ˆZmÍÚn©-" ¬¶Z‰9¶[mjgµMÇ+ @Îf[ž™ ¶3- !ºµÖ]ö´Z¶÷³÷Ð&v"5!)rËBø£Ê•%R*G¡PÎËKçƒÝ«â±”J%“Ëå¸Sš*•JÝÆÇ£¬ݳåà(Xô v–\;Ȧ¦¶{njƒm°mùºm}Oìµ ‰ÈØL3»VÏY0ÚŸWj·èḭ́àøn`uø‹DtN1EDóI’ÌÚ’Z±Ë#û6ÎJ¦ÇÑifàccŒ!"Í̱1¦`™™Ž}VÙjÍ’ˆÈê÷Ì̺#¸Ä>Ç6蔈R!DJDšˆÚŸA6„1Fc\f^ƒˆrÌœ·€ä‰ÈïÈÏÞut²Ëæ™yÖ3o;ú¶véËÿ6aü$ØÈªÛIEND®B`‚osdlyrics-0.5.5~rc1+dfsg1/data/org.osdlyrics.Config.service.in000066400000000000000000000001311345713535600242540ustar00rootroot00000000000000[D-BUS Service] Name=org.osdlyrics.Config Exec=@PYTHON@ @pkglibdir@/daemon/ini_config.py osdlyrics-0.5.5~rc1+dfsg1/data/org.osdlyrics.Daemon.service.in000066400000000000000000000001231345713535600242530ustar00rootroot00000000000000[D-BUS Service] Name=org.osdlyrics.Daemon Exec=@PYTHON@ @pkglibdir@/daemon/main.py osdlyrics-0.5.5~rc1+dfsg1/data/osdlyrics-daemon.in000066400000000000000000000000641345713535600220710ustar00rootroot00000000000000#!/bin/sh exec @PYTHON@ @pkglibdir@/daemon/main.py osdlyrics-0.5.5~rc1+dfsg1/data/osdlyrics-loading.png000066400000000000000000000006551345713535600224270ustar00rootroot00000000000000‰PNG  IHDR€,vj»sRGB®ÎéPLTEÿÿÿÂÂÂBBBbbb‚‚‚’’’clîtRNS@æØfbKGDˆH pHYs  šœtIMEÚ&NûÚØ"tEXtCommentCreated with ajaxload.info1ô†­ÓIDAT8ËRA „0 ,^ö©ÞEý€?Øþ ¼gÁ~cµ ¦íeBÈ$™I¹¿¦l¿‚ Vµ){Ã,è&Á‰TÚ5Wt¼ŒÈUõ¶Ê.Dê.×Ü_/s­GŽˆx™„Öj»ÄË(¸­’C±‹9­A”`ýQĒúš'Oµ\9L™å§¾£*1ãIú{w¯z’ž—VA<°ä¿¥ÓïˆÇ¾%ÿ-U‚8ðîKÇ m«ÌÎ7hzx9ÌÖïVXTb¡æ%€ŠØVŒÏ ‰‡{1¾ÌJóîŸ;}u5zIEND®B`‚osdlyrics-0.5.5~rc1+dfsg1/data/osdlyrics-osd-bg.svg000066400000000000000000000143621345713535600222000ustar00rootroot00000000000000 image/svg+xml osdlyrics-0.5.5~rc1+dfsg1/data/osdlyrics-osd-next.svg000066400000000000000000000314411345713535600225630ustar00rootroot00000000000000 image/svg+xml osdlyrics-0.5.5~rc1+dfsg1/data/osdlyrics-osd-pause.svg000066400000000000000000000316641345713535600227310ustar00rootroot00000000000000 image/svg+xml osdlyrics-0.5.5~rc1+dfsg1/data/osdlyrics-osd-play.svg000066400000000000000000000266361345713535600225640ustar00rootroot00000000000000 image/svg+xml osdlyrics-0.5.5~rc1+dfsg1/data/osdlyrics-osd-prev.svg000066400000000000000000000310701345713535600225570ustar00rootroot00000000000000 image/svg+xml osdlyrics-0.5.5~rc1+dfsg1/data/osdlyrics-osd-stop.svg000066400000000000000000000240071345713535600225720ustar00rootroot00000000000000 image/svg+xml osdlyrics-0.5.5~rc1+dfsg1/data/osdlyrics-scroll-close.svg000066400000000000000000000066341345713535600234310ustar00rootroot00000000000000 image/svg+xml osdlyrics-0.5.5~rc1+dfsg1/data/osdlyrics-trayicon.png000066400000000000000000000237501345713535600226430ustar00rootroot00000000000000‰PNG  IHDR@@ÝnAH pHYs  šœgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅF'^IDATxÚ|ƱJÃ@€áÿ.Í ­-—Q0Bj)…V¡S'ßÀA'Ñw)N‚ÎÎ}N" :5EÔI*Š54’¤ž“«ßô c ÿÉü%ú°Mÿ0Û¶)ï¾ pÜÊ\´~HNçp­5áIÅÄɧ/Œ1”¼B¾½šÆGÝ"KµFeúÎ c¡å—–Ïq2ÈV©MÑo3OS¢àzô^Fó îʽÊy—fuË0% ÎÈå²X›Áµ"‘Fïйºí¬5&h÷FÜ|/²³µnãûþ˜Rjö¾Œ±,«©”* !¶—÷=ãºîø/ÿÿb$:ŽÖªÿçý÷†™™™ŸŸ7Y¦øîE³ WÞp0üyÿ`â·o?^¿yÝôëç&ffæ fAFO.†¶õž ›ä3°ó0ð‹Ê2Ü{ôŒñÿÿÿ /ÿÿ´VfØôô?ßVéô) â§rï¥oáWf dþËÌÄðiõ]†[†g,¿ø…¼gÿ2¬ê˜ÉÀÀËË+¬ËÅXZ"ÆØÎÆÆ¦ÎÂÂbÆÈÈ8)l¿é---6ÿÿ\R±ka}ß÷ÝyÆ4IﻦK#‚BѬ ¥ˆº)ââÐE vÒNű›(ÒþŽ­ CqÒBº¨Ôb«1$9[Mc>)ÉÑ3Üý~$Þðxïñx`æô%­‘.æÝt{nßæñ¿®‹ž!Ó)klã‘x½tc“wšhù””R@ ¥¢TÞž.y°¸ôôŸÁ“[ÖâhS¯6 okŽ=ÀМ×l˜„Îõ§û])¥ ÀPÐÓïTÄL²›.µ¼Ÿ8'pÈqq1k°~¡ó‰QÏ×_õ•_ÛµjmkguÓ†Ù °vS]~þbýOÛ¶nßåMþÌ@“ðÆK¡ä;8ã ÀÎù±8ˆTÜ1½¾ª–W?RÏõKŠ;[" 93ŽJ#ÄʇŸøò#Æí‡kÈz.*&饞QC#'Ñn6 ŠÅ¢U.—÷c&jç±Ðð { œ­Xÿ–$¢3ï]=}-?~ ïÑ)ÍÏ>žÐZï‰B¡`W«ÕD†}q§‰è)¥lÙ¶mÇ ™YA0EÑ(€€ï¹\nÙÿ懿 '›Ø(Ê0Žÿßw>ß™Ùv‰›ÒÝ.-,Ú.…¥A£MI4‘‚ÑC%ñP´F¸ÀAOؘX ‰‰r< !L/F"ˆJRR¤±­mmiËÒe˜m»»n§;³óއM€çôä9üò|üž'þÓ‚þ¿°ôPÙówž;ª·?ÓÖ?ðŸÞÜ'‡f ª˜8»4Ù‘ø¨çÙ›ÑvõnƆ¨ÕBÖ"!ŒÍd108ZX÷028»²ƒ¿Ì•¹­yŠ¢/9ë N4\êÑ6éK7ù7÷GÝÔ‹ä@ˆhÿVZ­h°Ñ™­ÒyK ‰´$RH¸jœ] >«î9ü»f­B\IÃz½¾wCSnÍ/ªC¶åzô«†yæqÊm2ÚЕЉwx«åt½ê¦"*¯Ú$UôȰ½Æ“¨¼‚«?»õÅÇîúà&qøØkbÃÐèµµ•k[ß|ç!ñc*êÓ6HPÊeÑry»è“+T=EXE˜J»€¦w8B²ß&–¹…tf€žÒ‹\†xáØñƒ~ožØó‰½¦R.ÝX,ËúúÅ[õO£W/¦¶Ò†RCs¾ØæìR›bͧа˜ÉDh_º@ïݲçá'™;ñ£o}'¨ ÕsG|úgÕ¿*?X­¬… §7y=Çq¶.|‰ãöé4]Åe½Ã‰¹$¾ò´Ò4;šB¹Ã¹b›ìr‡RSá…ob÷}cK›âòwÞýIæÿ}Úœ^ì¦'œÚÌÌ /çnáô¿|77ö¾}û¬l6+*•Šô ”­"% ¨µ…Â?ZUmU©¢Њ•Ò—j¤Ð&$JLÒ$NÈÃ1Nl_ûÚ¾¶ïÛ÷±{÷5Ó÷:qBjûƒ•V;»³:óí™3g¾ïìÎ(¾àãs ‰®ë‹¦¦üsuunÍUû6±ÄéÞEØŒ0âxJC<ôý ëð¹SÀ9_;“À)¤Ù­Æ¾úóÇfßReqS•,€Â3IUh=À´ÞµüÂìÉÿ€¦i]ãâœâCÿø·«ö“ÑMË»-cZeØy<`>#â˜úÂH·:¸J{f¦ø_OÁœÁ­ß ÷2—ê"Ô{£›.Ö78°¦ŸÉ~B)<ÔØ‘ë¢dA¡þ¥…„¿}¾ýýGŽc:žÏÂÌ ¾âúÒbG€ÌyëÞYŒÄH¹pƹ•sp•WîËðr›sD©þGêWͷݽ¬ ¦iÁu]8Ž£^`ûcôi¡y»¬á¶­©! žpqbÂĨïCì¾%SÊËJ“+*WËç<å~œ ¸¼æìOÕsöz;PJQ,šý[ÿóÓß|òŽÌnXBº.ž§‹J Ë"ÓÈíé„/)˜¢@Q”2Ñ` Œ20ÆÀXùc¬ü<8-wûá3wÐD2Y=1¯€Ö­‡ÃWÇ€”²AŒ ¬a—48F¢ . 8ÕÅM©êây ÿMŪ­²LÍ ®Ž Zi—;P;Ivvë¯656n·mgã‹“–ë£`9×Ä€+b@H&ÕfcËM(°»]¤˜XÊBmW5†§KÈ%Æ ÙÉ‹i+3i•¯üÕ”Îó»ì!Õ¨Ù¶}ËR¦ij¥Uš‚æZãj¡Pèðñ£8Jò€.Â*AX!à XNãã #nÒKM¦69V þÛ=œ™-œ˜]š(˵‘¹)PƒS²1bhà ž_1|÷ç¦ât:m~<$LÀ}‚šu A…÷†faÍxpL“´ÝxÛÆ_Œ=°ç|ªêuMUöâ‘£fÑDdl‘HñDŒQhª+Ðþ\ÆtåѳcH¤s`ŸP__otn"v<ä`Ð_MH„‰f 1 €0©lFŠBÂA:Åó[»Ïë÷ß\ ãg†"Q̤r ´¼dUenÉ UëG§ ð…À©þ¼{èô¡P(8t.ÿξ×åbä±|1ÇQç%Ф 04–ãâàxÒͧŠi™ìE<ºaáSÇì[n pÄ5®€q D­‚äA@«ÔjpUçª^²LøžWÑŸÊ…Bá>Òˆ®À@昂(î^ƘlBà³X²0‹¸Ì3ƒ(È%¦ópóýXº¦gc¬í‘Û‰ZUU¡r^Î Š E)ç¦ÄqÍuJ£º¦¼ 7oÞL?ØG¶‰38r|‚Œ‡\2á"°€CBAÀ“¨•×i K D¥/–÷»ÂST\°OÐÒ¶¢ËËû.€P FÊÒˆ J)Šùd$ŸËÕd&F.]ñ@ª†œÁOÞ‰»d ð⇎ŸÇ²t Ô A¢†µZ½± ªfV¢˜ÌôÜR õú²T\»’%EA¶àœÚ²auYÏõpdÿÞ2€½{÷Šo>+ß“cÀ·V§`)«¦“ ¦3%ô’@­ÖŠú*-uq¾OKØ03>|kÍ¡¸äËåÀS0Æ.opÃc㯀ð}9¹„Ó']ñcìíÉ~9 $$dFBÏK à6_v=šku„kT´×ëè/š¨âŽEK”0KB8Qä“î¼mºL(­’“%Õ€0ðö_^+§ò9Žã Ýü*ùúxTŽÊŒÉ¤ˆàÝ·¡ †Ö°ŽÖ:¡ Šö°†7£.ª8`æ¬:õƒ†mûõȱÑ!Ìf D^KH@ûáîÒÛí ÖIY‘fúÈj€˜ŽÄdÆÅðt ÑT 9ˇU4pGÕ,ü†,ßþ3ܰúNÿ矱&µpâÜл÷ÿú¯_9ù’”¢<ž¦ 2åZ>àû~0ÔÁ–HFàÛ€k^Ö9= Uáê-!íaÍ5*ª4£ÊÄàu÷âñߟÀðø öîyk7|ãĹK‡NFÌÇ ®”Šª€ë” ̦f0¹D¯ÐðªÑQë{€ë®8P쀄…TÚê4,lÔÐâ04†ÄÈ |ç‰-X¹r F†‡ÍåêÜ€‘u/g=?Êâ–ö*"¥$¶Y4Šé8(§ey¾cÇzñâEÚ××W»ïÛÖá®8npFì‚„-Ê*B!@FÅÄìÝíùµ7­[î›Y$g‹ˆ&mD“%ÌdØ>AÓ]Ï §§Ž]‚œ>‰é³`®}zÓ³On=¼xI—ßÔPï>õÜO„¾¾¥§§GI¥R¤X,RÛ¶ëë RWþ9x°%àI@'@ƒÜ†—§Ÿ¸±åÝ›×.77°„Ô¤,Vu)áɉY˜ß}Àèn® GßzÁj‚êŽÅ ãûW(nè/j‹ì|ÔÙµk—×¶ìVɪ««Y:Vòù¼J)u²I§scYíe$(UÄ©J 6yéïy:0S ãg¦hßÑ(=øÑ9xzRžË`2c‘⇌õŸ>YßaǃÁp=I ´"Ô3ÿ8¶H×uqöìYܶ®[ÒL&£˜¦©zžÇ…Êúñ¾ïÃ'(Kr•-OÁ“¬·R !Ä`Br„8!dÀtÉ*Î|tfà€ËU1s>‚œfãƒÛ'‘P&)¥>!DJ)áyéíí%Ì0 Õó)¥…lW®.!Ä#„8ìŠè¤%Ë}Óîà¢%lÕ«‹S;“C×ú…›G÷¤_✠]×EKK‹¿sçNAÚÚÚsð¯Ø¶­¸®« !t!„&„ÐÌ ‚bSJMƘÉs8çž”’€‚Ú¶mxž×,„hÐ(¥T !%!$Á {c÷Ÿ.<¼õaYI’ kÖ¬á©TŠY–¥xžG+µ.¥äBˆË¬‰âSJƘ£(Š mιôÚ:ˆƒ±‚`µ‘@Á×È‹_´xå@3²Ÿ túõfÞK!3‡ÀìÔŽÔÛ{½[\÷È!Ú¼™¯H)7Üó9¼õ–wÒ6]ap– “OAD8ø"%u×ÔQX” ôLT›üÕ<2 ³ÛeB̧ټ˜#ÞfÁ›É6òˆî‰ÖøÈŸUÜ[8ëÃ_xÕ@îý÷ïCH×qæ“zBƒø·À²¬Õ÷ƒÏ½t#îì]I7]÷óýÿô™d›ò©(0qÂÂãOtýrza£³5‡‰¾:¯C‡Û„ùaÍêt@z+ͼ§3ÎÑËAdi#u{+yÿ¥ÛÚ?zð¾WúÿŘ(UñÈO…a4f´ºç‰éJMüFÇ锬;ò‚ø†k ½vŸùùw—êžÿqmøX{N ^áÀà°ïDÿÓýèLLäMBoK­-8¾øVožëœ1WrþêÒ™GóX®Ù5{Æß ؓ͟Ͼ«ø ¿”a%˜YMN{èn+|ý;ÛYk`l| .üÍ% ¥¼úÁoÑ{/Yƒwë*pø™eO;5^Ä »yÈ»>ºš}Ô#F5J0tØÁEúá°€ …NÇDç¢(ÁCnÓ0úWše›Æyi=?ÐùœV Dó²&#ȳ ²4ü‚’:üßWõü}ºgß¾}ÆŠå+®ª†±V$M•Ž„(ŠñÓŸ>ŠÖÖÖWࢋ.’×>ö¯tÛê¼Yg©~j¢û‡”òÑäx(yŒéb J€Ñj+Ƨ€¥ÇŠ8»–ƒ I¢`»¨wjˆ+K8g¥q¢À¬@`0¸Ž )èÊcîå´b€‰?`¶LU^|ôMgýCúþô§?;î¼³A‹Ï{¤š_[¦xçõ%_z7ý ‡'€Š0aø1£¥:¡T*ीŸ÷ !§0‹X?Þƒ¶åŒü9¹56,ÈcàÙѤ;<Õd&ƒ’,¤!ر$Yù¦–"Ù­N®otÿ3zÆ,ÝA"½ŒQ£â'¨ùê˜ë^¶úîp§¯èk6Áb±Øzýë¼mwlíØÖ)§ÐˆÁ1›ú%˜O7ĸP¥„Qõ\Ó»yBb¹ÅZ°ÿ$OZqùéUòÀ`Á‚íšl òa¢JÒ ZÛÚ;­¦.×us3õ!ÀŒºWG¨ÕjЙ¬}*H(2Q0Ôçöú=wËt¢5óT¯Àe—]&wïÞ½è¡{Õ–&—?ؽqâ$àûúhb$)¤€%ŸPrT F¦½<&¦œåZúõý9A9‚S0`å MíТ]–Ì#OS÷<þ졇>»þØ›.莖×tW”¨ ;ÚZ–+™+Da0ׄÌààÓ䢯G„TXIH)‡rÞàúÉ?*Dq„8Š>lÛD[sá•¥ÂË.»LV*þ‰°×ÝÄ?[Ù¿å®ã¥7L“hAxà<ìòQ ưD– &ÃÖ“™U8 Ñ­#hÊQ¡Ž÷D—µØµ0A.1¡“IäÚ<±¤{ÁŠU‹:¾rÓë¾<0tîVãG/õ6×G‡Ê8äùQ»p¬‚’9 Ó:‘M3q‘A$ÀdÀ¢†¡GFçäD² a8œ¤1¤§p 0]­¿ò0ôÔSOsá²Þ'.Nnÿ¦~s øùöž¸ú„âi¸Íc¸øu-hY´'« ”&­è„.hw,äµH££µŽ¶VÆA¯L“å„SO :£^JQ›ðQ= ¯zŽ›Vwß<²à½{2¹b½AÌv¾Ù1óí°l{–è%-¦å@Ú.¤‡aå`YvC¿³m˜¶k’atQ"B‡§mË7ÿò 0 ã’»þ’·~ø3b!8•ÿé×NmyOnxj„&i)€Ú)MK±¸+“¥õ.¬LÅs*­BéÖt3ê˜f ‘©õ½ãá­ô:~ÀðC§ ’ØDŸB¡Õ e¸ö"ó¬U¯¿ÒWë™e•Üá¦9O%˜)€ æn§ó²Dií•òkSëeaàŸb­*s»ZFš&§g@OOÃ̽›7ÑF> ðažœœÄÏS¼²}¢êVè€'ÀÄd åÒ$Š¥Qˆ0SÀM€& 4h&F³ ä™Ð0•óͽ£` °"ÔK@u,‚_R¨M…¨Ÿ€ _D Á– 15£Ô{ó¬×2­Æ [&,Û„iÚ Ô¶`Z 1Ä0MHÓDàU÷×OÜñȃ'ª•Šf¦­™˜7WÄÀS»^Ñ»ö¦÷_Î[¿ðQq*×< {h¸g@Å„=vÊ¥âr¬Àܰ¥D‰P@Y%cÚÆxP@-d”=…‰šB·íà½ç-Àp5„fÀ´N`çMX9|[vó"Ù $´ë 77 1qÄj6 (óÇÍgâQJ§O<½û=]_ÙüÅñÞõkÖZ#Nbüð¾o¢45öòX¶lÙÎoí<ºô¢Ný£·ŸK×7Ï‚Ä ¦ •m-¸aêa f8À!ìí€mæQð4”T0¤†e*äl ѸçWCxÛŠX’Ä@½ D~Œ|‹ F½„|k°B$]a¡;#*1Tã´!8täû©¶~0|cgk€9Á“;ÆÞg~|±éÕoƒ†al8¿]_{ßUt{ £ À)ƒR€2C ž3áhÇ€¿n)Ò‚XixPõJžBÙOQñ5ê‘B˜0⯸¤ÍÅ✅½è9w=Vnþ{)ÊåIŒþ%ή<7> € VÁ®_<û5?‘÷¬ýbé¸#U ¬RHÓ†4 $QˆcŸÇÑ{qtÿÞߎÈæ‚~wßti7®F°ßØzêy`€±¢æ–峎 Ã4jaŠŠ¯pª– ä¥˜öSÔC…(ÑHcÜ“0cà =,=ÿbt­¿Åë1tü0¦ÆG°öüµhmoÃñ£/"7þ8ÚË¿€•N#©_?èÉ#C§î­P÷ösþbgÔ¿|Ò¤aSdÍ`­¡RMžZ¥¨MŽ¿¶‰ûòË/—·Þ»7^Ú)®ÆCû •Z!k<óÖaß)ÄûN!÷ÖUËÚ@ H£á,6%Á1\K ïHämНPñSø‘ÂB¡¦ÀáÖ•¸áÖ»ùuxµ TJ8qôƆOà†­7ãìs/À³‘Æsñ*P0U+üÙW|þóçÿù/§[¨T!Ž7š“dHRq åÑa¶ ) ¦xyìܹ“Ž=ŠÝ»wÓää$=øàƒ·Þs=Ýpóº^1ô)†ª2”ÇPºqë×ó’€2ªYoXRizãjÇ1a#ö U ¥a¢QªA£,¦³l¨ ~¬¥z¶©Mš¯ÃmŸ¸¬žÙ Ö ®¸êz¬Xµ§ÆÇðüc?Àñ—ŽúÖ²MÛÛù³oïÚñpÒÔÔ¤{{{µmÛüÀðû>üQzë;>຦ÝÕb€Y7\6÷ß?íØ±ƒ©^¯“çyEÅqL'NœèWJÝxàoĶ–:ÚôCM1Ò*#õ©n 3{ ‘i‰cÿ‡<ñ¹•¼º¯•W,írzûzºÚÛÚÛŠn¾X4×S–5?AÙKPñb”k1JÕP•ëAZó¢´ª\uÞ–÷¹ŸüÔfJ¥r Ï=ùCŒïy]­Œ\tÁ+U€snþ·|ð>)¥L¥”ºµ½ƒÇÑWWÅbQoÚ´Imݺ•£#ƒ—A7n4<Ï£ÌÂEaŠ0 eš¦"Š"'‚?þÏ÷Ðu[šiƒÊ2 ©0Ÿk É@ˆ³20©ñ´(kúΕ'ÍÏÌ,2hŽ™¼Å¹6m¶&ÍÈ)Bªàz1[A 7V°Rņ”²°åšëW¿åÆ·õYƒŽzAR¡sÙ8Í.Xk¦…Ôó–+ô\wïçïýêÝGï󅉔R !´a*—Ë©|>¯;;;õ¦M›ô†kߥeww·¨T*²R©žçA˜I’iššÌ,µÖCìÑî5‹ia¡@G€Nµ¯P4—2[}Að>2*ÿj$ÊÏÚ$R"ŠMõjD¥r@£Ó!L‡­Å4¤˜N5yš‘bæøØáC£?~è¿ö…Ò_Zä¾"bLWû!šš›!0Ñ«Py{?†Ì—äc»vþO8ª¼ŒPa!„¦Œ4QJ!ŽcŒÁ;5 麮ˆãX¦i*µÖRkm0³Ì|·JqàÛûôØP‰ëW´Ðré³Á*›¾i.í-Ño÷GØ~õqñ©‘šˆf‚×8Óh"RD”f€$™¼fbb˜ýe.逞Ú;æäîeÆÙE«ÍHBñœÆÓ=ÃøUóqóŽS®ËhÒ$œØS{\²1›RJ6 ƒ-ËbÛ¶¹P( ¯¯©¿¿ß‰¢HdÆe¡µ–™Üjh­M­µ`ÆÈl0™9GÌM76aÉùwFŒè Ÿ†÷Pž1:HÄBˆ@4£)gÁ(!„š!¹f ÒY™XÌìäòùf"ÑâÕëE¥ÒæìK‘9–ašfÇý‹zò}XNm:]n¯™sZ‚cB³Ù ëäþçOÞîý‡Øi†ž ¼X,êÅ‹óí·ß®7oÞÌ´fͳV«É dš¦b„ìi2³‘©´æ<äO:CgÔóV.B$‰"%"™™iFbž#[˲ì‚ï{yfnaæfÍÌœ`gN+“°iPÞj‘î"£]—6ÅÒ0—ýG6,¼üáÞÞ^¾öÚkù–÷ÜrÚm.¾øb£Z­ ß÷Eš¦”IÔBk-”RóKb¦,f$k‘57šWã "&"±N* >ÉJI !RÃ0”iš³<¹ÖšÎ0’RŠÒ452Ó€«”*dÁç³£•ŸfVàQMJY'¢0 Ã*›K>M‰¡¹÷F__éR©„(Š( C!X)¥…š™Sf޵Ö3+#æ¥,ͧղày^­ë,h-¥Ô–e)×uu{{»š¯6€çydY·¶¶2T* ‚€jµšW¯×$IJq›Zk›™mVš¦òŒ’ iš†Ù÷ÒÙÖ‹— €ÿ-ɳÓ>üa•IEND®B`‚osdlyrics-0.5.5~rc1+dfsg1/data/osdlyrics.desktop.in000066400000000000000000000005271345713535600223040ustar00rootroot00000000000000[Desktop Entry] Exec=osdlyrics Version=1.0 _Name=OSD Lyrics _GenericName=Lyrics Show _Comment=Download and show lyrics for your favorite media player Icon=osdlyrics StartupNotify=true Terminal=false Type=Application Categories=GTK;AudioVideo;Audio;Player;X-Ximian-Main;X-Novell-Main;X-Red-Hat-Base; MimeType= X-Ubuntu-Gettext-Domain=osdlyrics osdlyrics-0.5.5~rc1+dfsg1/data/osdlyrics.png000066400000000000000000000237501345713535600210150ustar00rootroot00000000000000‰PNG  IHDR@@ÝnAH pHYs  šœgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅF'^IDATxÚ|ƱJÃ@€áÿ.Í ­-—Q0Bj)…V¡S'ßÀA'Ñw)N‚ÎÎ}N" :5EÔI*Š54’¤ž“«ßô c ÿÉü%ú°Mÿ0Û¶)ï¾ pÜÊ\´~HNçp­5áIÅÄɧ/Œ1”¼B¾½šÆGÝ"KµFeúÎ c¡å—–Ïq2ÈV©MÑo3OS¢àzô^Fó îʽÊy—fuË0% ÎÈå²X›Áµ"‘Fïйºí¬5&h÷FÜ|/²³µnãûþ˜Rjö¾Œ±,«©”* !¶—÷=ãºîø/ÿÿb$:ŽÖªÿçý÷†™™™ŸŸ7Y¦øîE³ WÞp0üyÿ`â·o?^¿yÝôëç&ffæ fAFO.†¶õž ›ä3°ó0ð‹Ê2Ü{ôŒñÿÿÿ /ÿÿ´VfØôô?ßVéô) â§rï¥oáWf dþËÌÄðiõ]†[†g,¿ø…¼gÿ2¬ê˜ÉÀÀËË+¬ËÅXZ"ÆØÎÆÆ¦ÎÂÂbÆÈÈ8)l¿é---6ÿÿ\R±ka}ß÷ÝyÆ4IﻦK#‚BѬ ¥ˆº)ââÐE vÒNű›(ÒþŽ­ CqÒBº¨Ôb«1$9[Mc>)ÉÑ3Üý~$Þðxïñx`æô%­‘.æÝt{nßæñ¿®‹ž!Ó)klã‘x½tc“wšhù””R@ ¥¢TÞž.y°¸ôôŸÁ“[ÖâhS¯6 okŽ=ÀМ×l˜„Îõ§û])¥ ÀPÐÓïTÄL²›.µ¼Ÿ8'pÈqq1k°~¡ó‰QÏ×_õ•_ÛµjmkguÓ†Ù °vS]~þbýOÛ¶nßåMþÌ@“ðÆK¡ä;8ã ÀÎù±8ˆTÜ1½¾ª–W?RÏõKŠ;[" 93ŽJ#ÄʇŸøò#Æí‡kÈz.*&饞QC#'Ñn6 ŠÅ¢U.—÷c&jç±Ðð { œ­Xÿ–$¢3ï]=}-?~ ïÑ)ÍÏ>žÐZï‰B¡`W«ÕD†}q§‰è)¥lÙ¶mÇ ™YA0EÑ(€€ï¹\nÙÿ懿 '›Ø(Ê0Žÿßw>ß™Ùv‰›ÒÝ.-,Ú.…¥A£MI4‘‚ÑC%ñP´F¸ÀAOؘX ‰‰r< !L/F"ˆJRR¤±­mmiËÒe˜m»»n§;³óއM€çôä9üò|üž'þÓ‚þ¿°ôPÙówž;ª·?ÓÖ?ðŸÞÜ'‡f ª˜8»4Ù‘ø¨çÙ›ÑvõnƆ¨ÕBÖ"!ŒÍd108ZX÷028»²ƒ¿Ì•¹­yŠ¢/9ë N4\êÑ6éK7ù7÷GÝÔ‹ä@ˆhÿVZ­h°Ñ™­ÒyK ‰´$RH¸jœ] >«î9ü»f­B\IÃz½¾wCSnÍ/ªC¶åzô«†yæqÊm2ÚЕЉwx«åt½ê¦"*¯Ú$UôȰ½Æ“¨¼‚«?»õÅÇîúà&qøØkbÃÐèµµ•k[ß|ç!ñc*êÓ6HPÊeÑry»è“+T=EXE˜J»€¦w8B²ß&–¹…tf€žÒ‹\†xáØñƒ~ožØó‰½¦R.ÝX,ËúúÅ[õO£W/¦¶Ò†RCs¾ØæìR›bͧа˜ÉDh_º@ïݲçá'™;ñ£o}'¨ ÕsG|úgÕ¿*?X­¬… §7y=Çq¶.|‰ãöé4]Åe½Ã‰¹$¾ò´Ò4;šB¹Ã¹b›ìr‡RSá…ob÷}cK›âòwÞýIæÿ}Úœ^ì¦'œÚÌÌ /çnáô¿|77ö¾}û¬l6+*•Šô ”­"% ¨µ…Â?ZUmU©¢Њ•Ò—j¤Ð&$JLÒ$NÈÃ1Nl_ûÚ¾¶ïÛ÷±{÷5Ó÷:qBjûƒ•V;»³:óí™3g¾ïìÎ(¾àãs ‰®ë‹¦¦üsuunÍUû6±ÄéÞEØŒ0âxJC<ôý ëð¹SÀ9_;“À)¤Ù­Æ¾úóÇfßReqS•,€Â3IUh=À´ÞµüÂìÉÿ€¦i]ãâœâCÿø·«ö“ÑMË»-cZeØy<`>#â˜úÂH·:¸J{f¦ø_OÁœÁ­ß ÷2—ê"Ô{£›.Ö78°¦ŸÉ~B)<ÔØ‘ë¢dA¡þ¥…„¿}¾ýýGŽc:žÏÂÌ ¾âúÒbG€ÌyëÞYŒÄH¹pƹ•sp•WîËðr›sD©þGêWͷݽ¬ ¦iÁu]8Ž£^`ûcôi¡y»¬á¶­©! žpqbÂĨïCì¾%SÊËJ“+*WËç<å~œ ¸¼æìOÕsöz;PJQ,šý[ÿóÓß|òŽÌnXBº.ž§‹J Ë"ÓÈíé„/)˜¢@Q”2Ñ` Œ20ÆÀXùc¬ü<8-wûá3wÐD2Y=1¯€Ö­‡ÃWÇ€”²AŒ ¬a—48F¢ . 8ÕÅM©êây ÿMŪ­²LÍ ®Ž Zi—;P;Ivvë¯656n·mgã‹“–ë£`9×Ä€+b@H&ÕfcËM(°»]¤˜XÊBmW5†§KÈ%Æ ÙÉ‹i+3i•¯üÕ”Îó»ì!Õ¨Ù¶}ËR¦ij¥Uš‚æZãj¡Pèðñ£8Jò€.Â*AX!à XNãã #nÒKM¦69V þÛ=œ™-œ˜]š(˵‘¹)PƒS²1bhà ž_1|÷ç¦ât:m~<$LÀ}‚šu A…÷†faÍxpL“´ÝxÛÆ_Œ=°ç|ªêuMUöâ‘£fÑDdl‘HñDŒQhª+Ðþ\ÆtåѳcH¤s`ŸP__otn"v<ä`Ð_MH„‰f 1 €0©lFŠBÂA:Åó[»Ïë÷ß\ ãg†"Q̤r ´¼dUenÉ UëG§ ð…À©þ¼{èô¡P(8t.ÿξ×åbä±|1ÇQç%Ф 04–ãâàxÒͧŠi™ìE<ºaáSÇì[n pÄ5®€q D­‚äA@«ÔjpUçª^²LøžWÑŸÊ…Bá>Òˆ®À@昂(î^ƘlBà³X²0‹¸Ì3ƒ(È%¦ópóýXº¦gc¬í‘Û‰ZUU¡r^Î Š E)ç¦ÄqÍuJ£º¦¼ 7oÞL?ØG¶‰38r|‚Œ‡\2á"°€CBAÀ“¨•×i K D¥/–÷»ÂST\°OÐÒ¶¢ËËû.€P FÊÒˆ J)Šùd$ŸËÕd&F.]ñ@ª†œÁOÞ‰»d ð⇎ŸÇ²t Ô A¢†µZ½± ªfV¢˜ÌôÜR õú²T\»’%EA¶àœÚ²auYÏõpdÿÞ2€½{÷Šo>+ß“cÀ·V§`)«¦“ ¦3%ô’@­ÖŠú*-uq¾OKØ03>|kÍ¡¸äËåÀS0Æ.opÃc㯀ð}9¹„Ó']ñcìíÉ~9 $$dFBÏK à6_v=šku„kT´×ëè/š¨âŽEK”0KB8Qä“î¼mºL(­’“%Õ€0ðö_^+§ò9Žã Ýü*ùúxTŽÊŒÉ¤ˆàÝ·¡ †Ö°ŽÖ:¡ Šö°†7£.ª8`æ¬:õƒ†mûõȱÑ!Ìf D^KH@ûáîÒÛí ÖIY‘fúÈj€˜ŽÄdÆÅðt ÑT 9ˇU4pGÕ,ü†,ßþ3ܰúNÿ矱&µpâÜл÷ÿú¯_9ù’”¢<ž¦ 2åZ>àû~0ÔÁ–HFàÛ€k^Ö9= Uáê-!íaÍ5*ª4£ÊÄàu÷âñߟÀðø öîyk7|ãĹK‡NFÌÇ ®”Šª€ë” ̦f0¹D¯ÐðªÑQë{€ë®8P쀄…TÚê4,lÔÐâ04†ÄÈ |ç‰-X¹r F†‡ÍåêÜ€‘u/g=?Êâ–ö*"¥$¶Y4Šé8(§ey¾cÇzñâEÚ××W»ïÛÖá®8npFì‚„-Ê*B!@FÅÄìÝíùµ7­[î›Y$g‹ˆ&mD“%ÌdØ>AÓ]Ï §§Ž]‚œ>‰é³`®}zÓ³On=¼xI—ßÔPï>õÜO„¾¾¥§§GI¥R¤X,RÛ¶ëë RWþ9x°%àI@'@ƒÜ†—§Ÿ¸±åÝ›×.77°„Ô¤,Vu)áɉY˜ß}Àèn® GßzÁj‚êŽÅ ãûW(nè/j‹ì|ÔÙµk—×¶ìVɪ««Y:Vòù¼J)u²I§scYíe$(UÄ©J 6yéïy:0S ãg¦hßÑ(=øÑ9xzRžË`2c‘⇌õŸ>YßaǃÁp=I ´"Ô3ÿ8¶H×uqöìYܶ®[ÒL&£˜¦©zžÇ…Êúñ¾ïÃ'(Kr•-OÁ“¬·R !Ä`Br„8!dÀtÉ*Î|tfà€ËU1s>‚œfãƒÛ'‘P&)¥>!DJ)áyéíí%Ì0 Õó)¥…lW®.!Ä#„8ìŠè¤%Ë}Óîà¢%lÕ«‹S;“C×ú…›G÷¤_✠]×EKK‹¿sçNAÚÚÚsð¯Ø¶­¸®« !t!„&„ÐÌ ‚bSJMƘÉs8çž”’€‚Ú¶mxž×,„hÐ(¥T !%!$Á {c÷Ÿ.<¼õaYI’ kÖ¬á©TŠY–¥xžG+µ.¥äBˆË¬‰âSJƘ£(Š mιôÚ:ˆƒ±‚`µ‘@Á×È‹_´xå@3²Ÿ túõfÞK!3‡ÀìÔŽÔÛ{½[\÷È!Ú¼™¯H)7Üó9¼õ–wÒ6]ap– “OAD8ø"%u×ÔQX” ôLT›üÕ<2 ³ÛeB̧ټ˜#ÞfÁ›É6òˆî‰ÖøÈŸUÜ[8ëÃ_xÕ@îý÷ïCH×qæ“zBƒø·À²¬Õ÷ƒÏ½t#îì]I7]÷óýÿô™d›ò©(0qÂÂãOtýrza£³5‡‰¾:¯C‡Û„ùaÍêt@z+ͼ§3ÎÑËAdi#u{+yÿ¥ÛÚ?zð¾WúÿŘ(UñÈO…a4f´ºç‰éJMüFÇ锬;ò‚ø†k ½vŸùùw—êžÿqmøX{N ^áÀà°ïDÿÓýèLLäMBoK­-8¾øVožëœ1WrþêÒ™GóX®Ù5{Æß ؓ͟Ͼ«ø ¿”a%˜YMN{èn+|ý;ÛYk`l| .üÍ% ¥¼úÁoÑ{/Yƒwë*pø™eO;5^Ä »yÈ»>ºš}Ô#F5J0tØÁEúá°€ …NÇDç¢(ÁCnÓ0úWše›Æyi=?ÐùœV Dó²&#ȳ ²4ü‚’:üßWõü}ºgß¾}ÆŠå+®ª†±V$M•Ž„(ŠñÓŸ>ŠÖÖÖWࢋ.’×>ö¯tÛê¼Yg©~j¢û‡”òÑäx(yŒéb J€Ñj+Ƨ€¥ÇŠ8»–ƒ I¢`»¨wjˆ+K8g¥q¢À¬@`0¸Ž )èÊcîå´b€‰?`¶LU^|ôMgýCúþô§?;î¼³A‹Ï{¤š_[¦xçõ%_z7ý ‡'€Š0aø1£¥:¡T*ीŸ÷ !§0‹X?Þƒ¶åŒü9¹56,ÈcàÙѤ;<Õd&ƒ’,¤!ر$Yù¦–"Ù­N®otÿ3zÆ,ÝA"½ŒQ£â'¨ùê˜ë^¶úîp§¯èk6Áb±Øzýë¼mwlíØÖ)§ÐˆÁ1›ú%˜O7ĸP¥„Qõ\Ó»yBb¹ÅZ°ÿ$OZqùéUòÀ`Á‚íšl òa¢JÒ ZÛÚ;­¦.×us3õ!ÀŒºWG¨ÕjЙ¬}*H(2Q0Ôçöú=wËt¢5óT¯Àe—]&wïÞ½è¡{Õ–&—?ؽqâ$àûúhb$)¤€%ŸPrT F¦½<&¦œåZúõý9A9‚S0`å MíТ]–Ì#OS÷<þ졇>»þØ›.莖×tW”¨ ;ÚZ–+™+Da0ׄÌààÓ䢯G„TXIH)‡rÞàúÉ?*Dq„8Š>lÛD[sá•¥ÂË.»LV*þ‰°×ÝÄ?[Ù¿å®ã¥7L“hAxà<ìòQ ưD– &ÃÖ“™U8 Ñ­#hÊQ¡Ž÷D—µØµ0A.1¡“IäÚ<±¤{ÁŠU‹:¾rÓë¾<0tîVãG/õ6×G‡Ê8äùQ»p¬‚’9 Ó:‘M3q‘A$ÀdÀ¢†¡GFçäD² a8œ¤1¤§p 0]­¿ò0ôÔSOsá²Þ'.Nnÿ¦~s øùöž¸ú„âi¸Íc¸øu-hY´'« ”&­è„.hw,äµH££µŽ¶VÆA¯L“å„SO :£^JQ›ðQ= ¯zŽ›Vwß<²à½{2¹b½AÌv¾Ù1óí°l{–è%-¦å@Ú.¤‡aå`YvC¿³m˜¶k’atQ"B‡§mË7ÿò 0 ã’»þ’·~ø3b!8•ÿé×NmyOnxj„&i)€Ú)MK±¸+“¥õ.¬LÅs*­BéÖt3ê˜f ‘©õ½ãá­ô:~ÀðC§ ’ØDŸB¡Õ e¸ö"ó¬U¯¿ÒWë™e•Üá¦9O%˜)€ æn§ó²Dií•òkSëeaàŸb­*s»ZFš&§g@OOÃ̽›7ÑF> ðažœœÄÏS¼²}¢êVè€'ÀÄd åÒ$Š¥Qˆ0SÀM€& 4h&F³ ä™Ð0•óͽ£` °"ÔK@u,‚_R¨M…¨Ÿ€ _D Á– 15£Ô{ó¬×2­Æ [&,Û„iÚ Ô¶`Z 1Ä0MHÓDàU÷×OÜñȃ'ª•Šf¦­™˜7WÄÀS»^Ñ»ö¦÷_Î[¿ðQq*×< {h¸g@Å„=vÊ¥âr¬Àܰ¥D‰P@Y%cÚÆxP@-d”=…‰šB·íà½ç-Àp5„fÀ´N`çMX9|[vó"Ù $´ë 77 1qÄj6 (óÇÍgâQJ§O<½û=]_ÙüÅñÞõkÖZ#Nbüð¾o¢45öòX¶lÙÎoí<ºô¢Ný£·ŸK×7Ï‚Ä ¦ •m-¸aêa f8À!ìí€mæQð4”T0¤†e*äl ѸçWCxÛŠX’Ä@½ D~Œ|‹ F½„|k°B$]a¡;#*1Tã´!8täû©¶~0|cgk€9Á“;ÆÞg~|±éÕoƒ†al8¿]_{ßUt{ £ À)ƒR€2C ž3áhÇ€¿n)Ò‚XixPõJžBÙOQñ5ê‘B˜0⯸¤ÍÅ✅½è9w=Vnþ{)ÊåIŒþ%ή<7> € VÁ®_<û5?‘÷¬ýbé¸#U ¬RHÓ†4 $QˆcŸÇÑ{qtÿÞߎÈæ‚~wßti7®F°ßØzêy`€±¢æ–峎 Ã4jaŠŠ¯pª– ä¥˜öSÔC…(ÑHcÜ“0cà =,=ÿbt­¿Åë1tü0¦ÆG°öüµhmoÃñ£/"7þ8ÚË¿€•N#©_?èÉ#C§î­P÷ösþbgÔ¿|Ò¤aSdÍ`­¡RMžZ¥¨MŽ¿¶‰ûòË/—·Þ»7^Ú)®ÆCû •Z!k<óÖaß)ÄûN!÷ÖUËÚ@ H£á,6%Á1\K ïHämНPñSø‘ÂB¡¦ÀáÖ•¸áÖ»ùuxµ TJ8qôƆOà†­7ãìs/À³‘Æsñ*P0U+üÙW|þóçÿù/§[¨T!Ž7š“dHRq åÑa¶ ) ¦xyìܹ“Ž=ŠÝ»wÓää$=øàƒ·Þs=Ýpóº^1ô)†ª2”ÇPºqë×ó’€2ªYoXRizãjÇ1a#ö U ¥a¢QªA£,¦³l¨ ~¬¥z¶©Mš¯ÃmŸ¸¬žÙ Ö ®¸êz¬Xµ§ÆÇðüc?Àñ—ŽúÖ²MÛÛù³oïÚñpÒÔÔ¤{{{µmÛüÀðû>üQzë;>຦ÝÕb€Y7\6÷ß?íØ±ƒ©^¯“çyEÅqL'NœèWJÝxàoĶ–:ÚôCM1Ò*#õ©n 3{ ‘i‰cÿ‡<ñ¹•¼º¯•W,írzûzºÚÛÚÛŠn¾X4×S–5?AÙKPñb”k1JÕP•ëAZó¢´ª\uÞ–÷¹ŸüÔfJ¥r Ï=ùCŒïy]­Œ\tÁ+U€snþ·|ð>)¥L¥”ºµ½ƒÇÑWWÅbQoÚ´Imݺ•£#ƒ—A7n4<Ï£ÌÂEaŠ0 eš¦"Š"'‚?þÏ÷Ðu[šiƒÊ2 ©0Ÿk É@ˆ³20©ñ´(kúΕ'ÍÏÌ,2hŽ™¼Å¹6m¶&ÍÈ)Bªàz1[A 7V°Rņ”²°åšëW¿åÆ·õYƒŽzAR¡sÙ8Í.Xk¦…Ôó–+ô\wïçïýêÝGï󅉔R !´a*—Ë©|>¯;;;õ¦M›ô†kߥeww·¨T*²R©žçA˜I’iššÌ,µÖCìÑî5‹ia¡@G€Nµ¯P4—2[}Að>2*ÿj$ÊÏÚ$R"ŠMõjD¥r@£Ó!L‡­Å4¤˜N5yš‘bæøØáC£?~è¿ö…Ò_Zä¾"bLWû!šš›!0Ñ«Py{?†Ì—äc»vþO8ª¼ŒPa!„¦Œ4QJ!ŽcŒÁ;5 麮ˆãX¦i*µÖRkm0³Ì|·JqàÛûôØP‰ëW´Ðré³Á*›¾i.í-Ño÷GØ~õqñ©‘šˆf‚×8Óh"RD”f€$™¼fbb˜ýe.逞Ú;æäîeÆÙE«ÍHBñœÆÓ=ÃøUóqóŽS®ËhÒ$œØS{\²1›RJ6 ƒ-ËbÛ¶¹P( ¯¯©¿¿ß‰¢HdÆe¡µ–™Üjh­M­µ`ÆÈl0™9GÌM76aÉùwFŒè Ÿ†÷Pž1:HÄBˆ@4£)gÁ(!„š!¹f ÒY™XÌìäòùf"ÑâÕëE¥ÒæìK‘9–ašfÇý‹zò}XNm:]n¯™sZ‚cB³Ù ëäþçOÞîý‡Øi†ž ¼X,êÅ‹óí·ß®7oÞÌ´fͳV«É dš¦b„ìi2³‘©´æ<äO:CgÔóV.B$‰"%"™™iFbž#[˲ì‚ï{yfnaæfÍÌœ`gN+“°iPÞj‘î"£]—6ÅÒ0—ýG6,¼üáÞÞ^¾öÚkù–÷ÜrÚm.¾øb£Z­ ß÷Eš¦”IÔBk-”RóKb¦,f$k‘57šWã "&"±N* >ÉJI !RÃ0”iš³<¹ÖšÎ0’RŠÒ452Ó€«”*dÁç³£•ŸfVàQMJY'¢0 Ã*›K>M‰¡¹÷F__éR©„(Š( C!X)¥…š™Sf޵Ö3+#æ¥,ͧղày^­ë,h-¥Ô–e)×uu{{»š¯6€çydY·¶¶2T* ‚€jµšW¯×$IJq›Zk›™mVš¦òŒ’ iš†Ù÷ÒÙÖ‹— €ÿ-ɳÓ>üa•IEND®B`‚osdlyrics-0.5.5~rc1+dfsg1/doc/000077500000000000000000000000001345713535600161215ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/doc/dbus.rst000066400000000000000000000461571345713535600176250ustar00rootroot00000000000000=============================== OSD Lyrics DBus specification =============================== Bus Names ========= The daemon of OSD Lyrics uses ``org.osdlyrics.Daemon`` as well-known bus name. Data Structures =============== Lyrics Data ----------- ``aa{sv}`` Lyrics are repesented as array of dicts. Each dict in array represent a line of lyric. dicts are in ascending order by timestamp. Fields of lyrics dict are: - id: uint32. Starts from 0 and increases according to timestamp. - timestamp: int64. The start time of the lyric text in microseconds. - text: string. The lyric text itself. Lyric URI ---------- ``s`` The path of an LRC file. It is in url format. Currently available schemas are: - `file:` The lyrics are stored in local file system. The path of the lyrics is the path of the URI. Example: file:///home/osdlyrics/track1.lrc - `tag:` The lyrics are stored in ID3 tag of the track. The path of the music file to store the ID3 tag is specified in the path of the URI. Example: tag:///home/osdlyrics/track1.ogg - `none:` The track is assigned not to show any lyrics. Example: none: For compatability reasons, an empty string is considered to identical to `none:`. Player Info -------- ``a{sv}`` An dict of information of supported player application. Avaliable fields are: - name: string. The name used to identify the player in OSD Lyrics. It SHOULD be composed with alphabet, underscore, or digits only. - appname: string. The display name of the player application. - binname: string. The name of executable file of the player applicatoin. - cmd: string. The command line to launch the player. - icon: string. *(optional)* The name of icon of the player application. May be empty if it is a cli player. Metadata -------- ``a{sv}`` Use the format described in `MPRIS Metadata `_, with the following fields: - title: (string) - artist: (string) - album: (string) - location: (string) - tracknumber: (string) - arturl: (string) - time: (uint32) The duration in seconds - mtime: (uint32) The duration in milliseconds Search Result ------------- ``a{sv}`` - source: (string) The id of lyric source plugin that provides the lyric - title: (string) The title of the matched lyric - artist: (string) The artist of the matched lyric - album: (string) The album of the matched lyric - sourceid: (string) The ID of the source that provides this result. Lyric Source implementations MUST set this value correctly. - downloadinfo: (variable) The private data provided by the lyric source. It's used to download the lyric. Lyric sources can set any value to it as long as the source plugin can figure out how to download the lyric with this value. Usually it's a URL string of the lyric content. GUI clients should pass the value to method. Lyric Source ------------ ``a{sv}`` - id: (string) The id of lyric source plugin - name: (string) The localized name of the lyric source plugin - enabled: (boolean) True if the source is enabled in config. Interfaces ========== The Daemon ---------- The object path is ``/org/osdlyrics/Daemon``. The interface is ``org.osdlyrics.Daemon``. Methods ~~~~~~~ Hello(s:client_bus_name) -> None Notifies that a client has connected to the daemon. The daemon watches all the bus names of clients that says hello to it. If all clients disappeared, which means the bus names of clients vanished, the daemon quits automatically. A valid bus name of the client should be in the format of ``org.osdlyrics.Client.ClientName``. It is possible to not notify the daemon. However, the daemon may quit at any time, even if the client is still running. GetVersion() -> s Returns the current version of the daemon. Quit() -> None Quits the daemon Player Controlling ------------------ OSD Lyrics follows `MPRIS1 `_ and `MPRIS2 specification `_ for controlling players. OSD Lyrics uses the bus name ``org.mpris.MediaPlayer2.osdlyrics`` as an alias name according to the specification of MPRIS2. Player Support -------------- These are features about players not covered by MPRIS2. The object path is ``/org/osdlyrics/Player``. The interface is ``org.osdlyrics.Player`` Methods ~~~~~~~ ListSupportedPlayers() -> aa{sv} Gets an array of infomation of supported players. The returned value is an array of dicts. The fields of the dict is described in `Player Info`_. ListActivatablePlayers() -> aa{sv} Similar to ``GetSupportedPlayers``, but the array contains the supported players which are installed in the computer only. GetCurrentPlayer() -> b, a{sv} Gets the infomation of player currently connected to. If no supported player is running, the first returned value is False. Otherwise the first returned value is True, and the second value is the infomation of the player in the format described in `Player Info`_. Signals ~~~~~~~ PlayerLost() Emit when the currently connected player quits. PlayerConnected(a{sv}) Emit when a support player is launched and connected as current player. player_info(a{sv}): The info of connected player. The format is described in `Player Info`_ Lyrics ------ The object path for lyrics manipulation is ``/org/osdlyrics/Lyrics``. The interface is ``org.osdlyrics.Lyrics``. Methods ~~~~~~~ GetLyrics(a{sv}:metadata) -> b, s, a{ss}, aa{sv} Gets the lyircs of specified metadata. Return values: - ``found(b)``: Whether the lyrics file is found. - ``uri(s)``: The URI of the lyrics file. See `Lyrics URI`_ for more details. If no lyrics found, the uri is an empty string. - ``attributes(a{ss})``: The key-value attributes in the LRC file, such like [title:The title]. - ``content(aa{sv})``: The content of the lyrics. See `Lyrics Data`_ for more details. If no lyrics found, an empty array will be returned. GetCurrentLyrics() -> b, s, a{ss}, aa{sv} Similar to GetLyrics. Returns the lyrics of the current playing track. GetRawLyrics(a{sv}:metadata) -> b, s, s Gets the content of LRC file of specified metadata. Return values: - ``found(b)``: Whether the lyrics file is found. - ``uri(s)``: The URI of the lyrics file. See `Lyrics URI`_ for more details. If no lyrics found, the uri is an empty string. - ``content(s)``: The content of the LRC file. If no lyrics found, an empty string will be returned. GetCurrentRawLyrics() -> b, s, s Similar to GetRawLyrics. Returns the content of LRC file of current playing track. SetLyricContent(a{sv}:metadata, ay:content) -> s Sets the lyrics of specified metadata by content of LRC file. The content is a byte string rather than utf-8 string so that the content can be encoded in other charsets. Returns the URI of assigned lyrics. The URI follows the format described in `Lyric URI`_. If the given metadata cannot be expended to a valid path, or errors raised when saving the content to the file, an empty string is returned and the lyrics to the metadata is not changed. AssignLyricFile(a{sv}:metadata, s:uri) -> nothing Assigns an LRC file to given metadata. The ``uri`` should follow the format described in `Lyric URI`_. SetOffset(s:uri, i:offset_ms) Sets the offset of an LRC file. The ``uri`` should be a valid lyrics URI described in `Lyric URI`_. The ``offset`` is in milliseconds. Errors will be raise as exceptions. Signals ~~~~~~~ CurrentLyricsChanged() The current lyrics is changed by ``SetLyricContent`` or ``AssignLyricFile``, or lyrics downloaded. This signal will be emitted only when the lyrics of the SAME track is changed. If the track is changed, the signal will not be emitted. Search/download lyrics ---------------------------- The object for lyric searching/download in ``/org/osdlyrics/LyricSource``. This object implements ``org.osdlyrics.LyricSource`` interface. Methods ------- ListSources() -> aa{sv}: lyricSources List all available lyric sources. Returns: - ``lyricsSources``: An array of `Lyric Source_`. The lyric sources in the array are in the order of the priority defined in the config item ``Download/download-engine`` Search(a{sv}:metadata, as:sources) -> int32:ticket Search lyrics for a track with given metadata. Returns an integer to identify the search task. Parameters: - ``metadata``: The metadata of the track to be searched for. The metadata SHOULD contain at least ``title`` or ``uri``. - ``sources``: Array of IDs of lyric sources. The elements must be the ``id`` of `Lyric Source_` returned by ``ListSources``. If ``sources`` is an empty array, the available sources will be chosen from user config. Search request will be send to the first lyric source in the array, then the second if the first one returns nothing, and so on. When the search request is sent to a source, a ``SearchStarted`` signal will be emitted, with the name of the source. When search is complete or failed, a ``SearchStatusChanged`` signal will be emitted. Returns: - ``ticket``: An integer to identify the search task. The ticket can be used in ``CancelSearch`` or ``SearchStatusChanged``. CancelSearch(int32:ticket) ->nothing Cancel a search task. Parameter: - ``ticket``: The ticket to identify the search task to be cancelled. Download(s:source, v:downloaddata) -> int32:ticket Download lyric content. Parameters: - ``source``: The id of lyric source to download from. Id MUST be the same as the ``source`` field in `Lyric Source_`. - ``downloadinfo``: The ``downloadinfo`` field in `Lyric Source_`. ``downloadinfo`` and ``source`` must be taken from the same `Lyric Source_`. CancelDownload(int32:ticket) ->nothing Cancel a download task. Parameter: - ``ticket``: The ticket to identify the download task to be cancelled. Signals ------- SearchStarted(int32:ticket, s:sourceid, s:sourcename) Notify that the OSD Lyrics start to search lyrics from a source. For each search task, there may be more than one ``SearchStarted`` signal. Searching request will be sent to the first source of the ``sources`` parameter in ``Search`` call. If the first source fail to search or returns nothing, the search request is sent to the second source and a second ``SearchStarted`` signal is emitted, and so on. Parameters: - ``ticket``: The ticket to identify the search task. - ``sourceid``: The id of the lyric source to start with. - ``sourcename``: The name of the source. SearchComplete(int32:ticket, int32:status, aa{sv}:results) Emit when a search request is finished, cancelled or failed. Note that if all source returns nothing, the status is finished with empty ``results``, not failed. Parameters: - ``ticket``: The ticket to identify the search task. - ``status``: The status of the task. MUST be one of the following: - 0: Search finished. Search result is saved in ``results``. - 1: Search is cancelled. ``results`` SHOULD be an empty array. - 2: Search failed. ``results`` SHOULD be an empty array. - ``results``: An array of `Search Result_`, the results returned from sources. DownloadComplete(int32:ticket, int32:status, ay:content) Emit when a download task is finished, cancelled or failed. Parameters: - ``ticket``: The ticket to identify the search task. - ``status``: The status of the task. MUST be one of the following: - 0: Download finished. If the lyric downloaded assiged to the currently playing track, clients SHOULD receive a ``CurrentLyricsChanged`` signal from ``org.osdlyrics.Lyrics`` interface. - 1: Search is cancelled by user. - 2: Search is failed. - ``content``: The content of the lyric Configure Service ================= The well-known bus name of configure module is ``org.osdlyrics.Config`` The object path of configuration is ``/org/osdlyrics/Config``. The interface is ``org.osdlyrics.config``. The name of configure options ----------------------------- All the name used in configure options MUST be in the format of "group_name/options_name". For example, the visibility of OSD Window should be configured in "OSD/visible". Methods ------- GetInt(s:name) -> int32 Gets an int32 in config. If the value of name does not exists, ``org.osdlyrics.Error.ValueNotExist`` exception will be raised. SetInt(s:name, int32:value) Sets an int32 value. GetString(s:name) -> s Gets a string. If the value of name does not exists, ``org.osdlyrics.Error.ValueNotExist`` exception will be raised. SetString(s:name, s:value) Sets a string value. GetBool(s:name) -> b Gets a boolean value. If the value of name does not exists, ``org.osdlyrics.Error.ValueNotExist`` exception will be raised. SetBool(s:name, b:value) Sets a boolean value. GetDouble(s:name) -> d Gets a double value. If the value of name does not exists, ``org.osdlyrics.Error.ValueNotExist`` exception will be raised. SetDouble(s:name, d:value) Sets a double value. GetStringList(s:name) -> as Gets an array of strings. If the value of name does not exists, ``org.osdlyrics.Error.ValueNotExist`` exception will be raised. SetStringList(s:name, as:value) Sets an array of string. SetDefaultValues(a{sv}:values) Sets a set of default values. The existing values will not be overwrited, only values that not exists will be set. Parameters: - `values`: a dictionary, the key is the name of the value, and the value is the value itself. The value should be one of the following types: b, i, d, s, as, which are boolean, integer, double, string, string list, respectively. Signals ------- ValueChanged(as:name_list) Emit when one or more config value has been changed. ``name_list`` is a list of names of changed values. Exceptions ---------- org.osdlyrics.Error.ValueNotExistError If the name does not exist when calling Get series methods, a ``org.osdlyrics.Error.ValueNotExist`` will be raised. org.osdlyrics.Error.MalformedKeyError If the key does not follow the "group/name" format, a ``org.osdlyrics.Error.MalformedKey`` will be raised. Player Proxy ============ A player proxy is a client to support one or more players. A player proxy MUST have a unique name, like ``Mpris`` or ``Exaile03``. The well-known bus name and object path MUST be of the form ``org.osdlyrics.PlayerProxy.proxyname`` and ``/org/osdlyrics/PlayerProxy/proxyname``, where ``proxyname`` is the unique name. For instance, a player proxy of MPRIS2 may have a unique name ``Mpris2``, and provides the bus name ``org.osdlyrics.PlayerProxy.Mpris2`` with object path ``/org/osdlyrics/PlayerProxy/Mpris2``. The interface of player proxy is ``org.osdlyrics.PlayerProxy`` Methods ------- ListActivePlayers() -> aa{sv} Lists supported players that are already running. Returns an array of dict. The dict represents the information of a player described in `Player Info`_. ListActivatablePlayers() -> aa{sv} Lists supported players installed on the system. Returns an array of dict. The dict represents the information of a player described in `Player Info`_. ListSupportedPlayers() -> aa{sv} Lists all supported players which can be launched on system. ConnectPlayer(s:player_name) -> o Connect to an active player. The player proxy SHOULD create an dbus object with the path of ``/org/osdlyrics/PlayerProxy/proxyname/player_name``. The ``player_name`` is the ``name`` field described in `Player Info`_. The path of created object is returned. The created player object MUST implement interfaces described in `Player Instance`_. If the player cannot be create with ``player_name``, a ``org.osdlyrics.Error.ConnectPlayerError`` SHOULD be raised. Signals ------- PlayerLost(s) The player of name s is lost Exceptions ---------- org.osdlyrics.Error.ConnectPlayerError Raised when ``ConnectPlayer`` fails. Player Instance =============== Player instances are created by `ConnectPlayer` method of `Player Proxy`_. A player instance is used to control a single player. Player instance MUST implement `org.mpris.MediaPlayer2.Player`_ interface of `MPRIS2`_ specification. The object path MUST be the path returned by `ConnectPlayer` method of `Player Proxy`_ instead of `/org/mpris/MediaPlayer2`. Lyric Source Plugins ============================== To write a plugin to support a new lyric source to download or upload(optional), the plugins should follow the specification here. A lyric source plugin Must have a unique name, linke ``ttplayer`` or ``xiami``. The well-known bus name should be ``org.osdlyrics.LyricSourcePlugin.``. The object path should be ``/org/osdlyrics/LyricSourcePlugin/``. ```` here stands for the unique name of the plugin. All lyric source plugin should implement ``org.osdlyrics.LyricSourcePlugin`` interface. The interface is defined below: Methods ------- Search(a{sv}:metadata) -> int32: ticket Search lyrics for a track with given metadata. Parameters: - `metadata`: The metadata of the track to be searched. The metadata should contain at least title or uri information. Otherwise searching will fail instantly. Return: - `ticket`: An unique integer to identify the search task in ``SearchComplete`` signal and ``CancelSearch`` method. CancelSearch(int32: ticket) -> nothing Cancel a search task. Parameters: - `ticket`: The ticket returned by ``Search``. Download(v: downloadinfo) -> int32: ticket Download lyric with given downloadinfo. Parameters: - `downloadinfo`: The information to download the lyric content. This information should be the value of ``downloadinfo`` in `Search Result_`. Return: - `ticket`: An unique integer to identify the download task. This value should be used in ``DownloadComplete`` signal and ``CancelDownload`` method. CancelDownload(int32: ticket) -> nothing Cancel a download task with given ticket. Parameters: - `ticket`: The ticket returned by ``Download`` Properties ---------- Name: string, readonly The name of the lyric source. It's used to show to users, not the unique name. The name should be localized by the lyric source plugin. Signals ------- SearchComplete(i: ticket, i: status, aa{sv}: results) Emit when a search task is succeeded, canceled or failed. Parameters: - `ticket`: The ticket to identify the search task. This MUST be the same ticket that ``Search`` returns for this task. - `status`: The status of the search task. MUST be one of the following: - 0: Search succeed, the `results` value MUST be the search results. Even there is no matched results, the `status` should be succeed, not failure - 1: Search task is canceled. The `result` value SHOULD be an empty array. - 2: Search fail. The `result` value SHOULD be an empty array. - `results`: Array of `Search Result_`. It SHOULD be set if `status` is succeed (0), otherwise it SHOULD be an empty array. DownloadComplete(i: ticket, i: status, ay: content) Emit when a download task is succeeded, canceled or failed. Parameters: - `ticket`: The ticket to identify the download task. This MUST be the same ticket that ``Download`` returns for this task. - `status`: The status of the download task. MUST be one of the following: - 0: Search succeed, the `results` value MUST be the lyric content. - 1: Search task is canceled. The `result` value SHOULD be an empty byte array. - 2: Search fail. The `result` value SHOULD be an empty byte array. osdlyrics-0.5.5~rc1+dfsg1/doc/http.rst000066400000000000000000000051741345713535600176410ustar00rootroot00000000000000================================================ OSD Lyrics Player Proxy HTTP API Specification ================================================ Types ===== Status ------ One of `playing`, `paused`, `stopped` Capability ---------- Comma seperated string with values of `play`, `pause`, `stop`, `next`, `prev`, `seek` Connect ======= Connect to OSD Lyrics. This MUST be called first. - path: `connect` - parameters: - `name`: The name of the player. - `caps`: Player capabilities, described in `Capability`_. - return values: - `id`: The id of the player connection. Disconnect ========== Disconnect to OSD Lyrics. This SHOULD be called when the player is going to be closed. After calling that, the connection created by `Connect`_ is closed. You need to call `Connect`_ again if you want to create a new connection. - path: `disconnect` - parameters: - `id`: The id returned from `connect`. - return values: nothing Track Change ============ Notifies the track has been changed. - path: `track_changed` - parameters: the metadata of the track - `id`: The id returned from `connect` - `status`: Playing status, described in `Status`_ - `title`: Title of the track - `artist`: (optional) Artist of the track - `album`: (optional) Album the track in. - `arturl`: (optional) URL of the album art - `tracknum`: (optional, int) - `length`: (int) Status Change ============= Notifies the playing status has been changed. - path: `status_changed` - parameters: - `id`: The id returned from `connect` - `status`: Playing status, described in `Status`_ - return values: nothing Position Change =============== Sync the position of currently playing track with the server. - path: `position_changed` - parameters: - `id`: The id returned from `connect` - `pos`: The position in milliseconds. - return values: nothing Query ===== Gets the command sends by user. The client should query the server in every 1 seconds. There is a timestamp to indicate the last query time. Always use the timestamp returned by server. The timestamp of the first query should be 0. - path: `query` - parameters: - `id`: The id returned from `connect` - `timestamp`: The timestamp described above - return values: - `cmds`: An array of objects. Each object represents a command. The object is described below. - `timestamp`: New timestamp An command object has two members: `cmd` and `params`. `params` is a object, and differs according to `cmd`. Available `cmd` are: - `play`: no param - `pause`: no param - `stop`: no param - `prev`: no param - `next`: no param - `seek`: `params` has a member `pos`, the position to seek in milliseconds. osdlyrics-0.5.5~rc1+dfsg1/lyricsources/000077500000000000000000000000001345713535600201025ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/lyricsources/Makefile.am000066400000000000000000000000521345713535600221330ustar00rootroot00000000000000SUBDIRS = xiami lrc123 viewlyrics netease osdlyrics-0.5.5~rc1+dfsg1/lyricsources/lrc123/000077500000000000000000000000001345713535600211105ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/lyricsources/lrc123/Makefile.am000066400000000000000000000007361345713535600231520ustar00rootroot00000000000000lrc123_PYTHON = lrc123.py lrc123dir = $(pkglibdir)/lyricsources/lrc123 servicedir = $(datadir)/dbus-1/services service_in_files = org.osdlyrics.LyricSourcePlugin.lrc123.service.in service_DATA = $(service_in_files:.service.in=.service) EXTRA_DIST = \ $(service_in_files) \ $(NULL) $(service_DATA): $(service_in_files) @sed -e "s|\@pkglibdir\@|$(pkglibdir)|" -e "s|\@PYTHON\@|$(PYTHON)|" $< > $@ CLEANFILES = \ org.osdlyrics.LyricSourcePlugin.lrc123.service \ $(NULL) osdlyrics-0.5.5~rc1+dfsg1/lyricsources/lrc123/lrc123.py000066400000000000000000000066471345713535600225050ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2012 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from builtins import super from future import standard_library standard_library.install_aliases() import http.client import logging import re import pycurl from osdlyrics.lyricsource import BaseLyricSourcePlugin, SearchResult from osdlyrics.utils import get_proxy_settings, http_download HOST = 'www.lrc123.com' SEARCH_URL = '/?keyword=%s&field=all' RESULT_PATTERN = re.compile(r'
(.*?).*?href="/\?field=album.*?>(.*?).*?href="/\?field=song.*?>(.*?).*?href="/download/lrc/(.*?)">LRC', re.DOTALL) DOWNLOAD_URL_PREFIX = '/download/lrc/' class Lrc123Source(BaseLyricSourcePlugin): """ Lyric source from xiami.com """ def __init__(self): super().__init__(id='lrc123', name='LRC123') def do_search(self, metadata): # type: (osdlyrics.metadata.Metadata) -> List[SearchResult] keys = [] if metadata.title: keys.append(metadata.title) if metadata.artist: keys.append(metadata.artist) urlkey = (' '.join(keys)) params = {'keyword': urlkey, 'field': 'all'} try: status, content = http_download( url=HOST + '/', params=params, proxy=get_proxy_settings(config=self.config_proxy)) except pycurl.error as e: logging.error('Download failed. %s', e.args[1]) return [] if status < 200 or status >= 400: raise http.client.HTTPException(status) match = RESULT_PATTERN.findall(content) result = [] if match: for artist, album, title, url in match: title = title.replace('', '').replace('', '') artist = artist.replace('', '').replace('', '') album = album.replace('', '').replace('', '') url = DOWNLOAD_URL_PREFIX + url result.append(SearchResult(title=title, artist=artist, album=album, sourceid=self.id, downloadinfo=url)) return result def do_download(self, downloadinfo): # type: (Any) -> bytes status, content = http_download(url=HOST + downloadinfo, proxy=get_proxy_settings(self.config_proxy)) if status < 200 or status >= 400: raise http.client.HTTPException(status, '') return content if __name__ == '__main__': lrc123 = Lrc123Source() lrc123._app.run() osdlyrics-0.5.5~rc1+dfsg1/lyricsources/lrc123/org.osdlyrics.LyricSourcePlugin.lrc123.service.in000066400000000000000000000001641345713535600323270ustar00rootroot00000000000000[D-BUS Service] Name=org.osdlyrics.LyricSourcePlugin.lrc123 Exec=@PYTHON@ @pkglibdir@/lyricsources/lrc123/lrc123.py osdlyrics-0.5.5~rc1+dfsg1/lyricsources/netease/000077500000000000000000000000001345713535600215265ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/lyricsources/netease/Makefile.am000066400000000000000000000007441345713535600235670ustar00rootroot00000000000000netease_PYTHON = netease.py neteasedir = $(pkglibdir)/lyricsources/netease servicedir = $(datadir)/dbus-1/services service_in_files = org.osdlyrics.LyricSourcePlugin.netease.service.in service_DATA = $(service_in_files:.service.in=.service) EXTRA_DIST = \ $(service_in_files) \ $(NULL) $(service_DATA): $(service_in_files) @sed -e "s|\@pkglibdir\@|$(pkglibdir)|" -e "s|\@PYTHON\@|$(PYTHON)|" $< > $@ CLEANFILES = \ org.osdlyrics.LyricSourcePlugin.netease.service \ $(NULL) osdlyrics-0.5.5~rc1+dfsg1/lyricsources/netease/netease.py000066400000000000000000000052731345713535600235330ustar00rootroot00000000000000from __future__ import unicode_literals from future import standard_library standard_library.install_aliases() from builtins import map, str, super import gettext import http.client import json from osdlyrics.lyricsource import BaseLyricSourcePlugin, SearchResult from osdlyrics.utils import get_proxy_settings, http_download _ = gettext.gettext NETEASE_HOST = 'music.163.com' NETEASE_SEARCH_URL = '/api/search/get' NETEASE_LYRIC_URL = '/api/song/lyric' gettext.bindtextdomain('osdlyrics') gettext.textdomain('osdlyrics') class NeteaseSource(BaseLyricSourcePlugin): """ Lyric source from music.163.com """ def __init__(self): super().__init__(id='netease', name=_('Netease')) def do_search(self, metadata): # type: (osdlyrics.metadata.Metadata) -> List[SearchResult] keys = [] if metadata.title: keys.append(metadata.title) if metadata.artist: keys.append(metadata.artist) url = NETEASE_HOST + NETEASE_SEARCH_URL urlkey = '+'.join(keys).replace(' ', '+') params = 's=%s&type=1' % urlkey status, content = http_download(url=url, method='POST', params=params.encode('utf-8'), proxy=get_proxy_settings(self.config_proxy)) if status < 200 or status >= 400: raise http.client.HTTPException(status, '') def map_func(song): if song['artists']: artist_name = song['artists'][0]['name'] else: artist_name = '' url = NETEASE_HOST + NETEASE_LYRIC_URL + '?id=' + str(song['id']) + '&lv=-1&kv=-1&tv=-1' return SearchResult(title=song['name'], artist=artist_name, album=song['album']['name'], sourceid=self.id, downloadinfo=url) parsed = json.loads(content.decode('utf-8')) result = list(map(map_func, parsed['result']['songs'])) return result def do_download(self, downloadinfo): # type: (Any) -> bytes status, content = http_download(url=downloadinfo, proxy=get_proxy_settings(self.config_proxy)) if status < 200 or status >= 400: raise http.client.HTTPException(status) parsed = json.loads(content.decode('utf-8')) if 'nolyric' in parsed: raise ValueError('This item has no lyrics.') lyric = parsed['lrc']['lyric'] return lyric.encode('utf-8') if __name__ == '__main__': netease = NeteaseSource() netease._app.run() osdlyrics-0.5.5~rc1+dfsg1/lyricsources/netease/org.osdlyrics.LyricSourcePlugin.netease.service.in000066400000000000000000000001671345713535600333660ustar00rootroot00000000000000[D-BUS Service] Name=org.osdlyrics.LyricSourcePlugin.netease Exec=@PYTHON@ @pkglibdir@/lyricsources/netease/netease.py osdlyrics-0.5.5~rc1+dfsg1/lyricsources/viewlyrics/000077500000000000000000000000001345713535600223025ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/lyricsources/viewlyrics/Makefile.am000066400000000000000000000007661345713535600243470ustar00rootroot00000000000000viewlyrics_PYTHON = viewlyrics.py viewlyricsdir = $(pkglibdir)/lyricsources/viewlyrics servicedir = $(datadir)/dbus-1/services service_in_files = org.osdlyrics.LyricSourcePlugin.viewlyrics.service.in service_DATA = $(service_in_files:.service.in=.service) EXTRA_DIST = \ $(service_in_files) \ $(NULL) $(service_DATA): $(service_in_files) @sed -e "s|\@pkglibdir\@|$(pkglibdir)|" -e "s|\@PYTHON\@|$(PYTHON)|" $< > $@ CLEANFILES = \ org.osdlyrics.LyricSourcePlugin.viewlyrics.service \ $(NULL) org.osdlyrics.LyricSourcePlugin.viewlyrics.service.in000066400000000000000000000002001345713535600346230ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/lyricsources/viewlyrics[D-BUS Service] Name=org.osdlyrics.LyricSourcePlugin.viewlyrics Exec=@PYTHON@ @pkglibdir@/lyricsources/viewlyrics/viewlyrics.py osdlyrics-0.5.5~rc1+dfsg1/lyricsources/viewlyrics/viewlyrics.py000066400000000000000000000114461345713535600250620ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2012 PedroHLC # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from __future__ import unicode_literals from future import standard_library standard_library.install_aliases() from builtins import bytes, super import hashlib import http.client import string import unicodedata import xml.etree.ElementTree as xet from osdlyrics.lyricsource import BaseLyricSourcePlugin, SearchResult from osdlyrics.utils import get_proxy_settings, http_download VIEWLYRICS_HOST = 'search.crintsoft.com' VIEWLYRICS_SEARCH_URL = '/searchlyrics.htm' VIEWLYRICS_BASE_LRC_URL = 'http://viewlyrics.com/' VIEWLYRICS_QUERY_FORM = '' VIEWLYRICS_AGENT = 'MiniLyrics' VIEWLYRICS_KEY = b'Mlv1clt4.0' def normalize_str(s): """ If s is a unicode string, only keep alphanumeric characters and remove diacritics """ return ''.join(x for x in unicodedata.normalize('NFKD', s) if x in string.ascii_letters + string.digits).lower() class ViewlyricsSource(BaseLyricSourcePlugin): def __init__(self): super().__init__(id='viewlyrics', name='ViewLyrics') def do_search(self, metadata): # type: (osdlyrics.metadata.Metadata) -> List[SearchResult] title = metadata.title or '' artist = metadata.artist or '' result = [] page = 0 pagesleft = 1 while pagesleft > 0: pageresult, pagesleft = self.real_search(title, artist, page) result += pageresult page += 1 # Remove non-lrc (plain text) results, they cannot be displayed by # OSDLyrics for now result = [r for r in result if r._downloadinfo.endswith('.lrc')] # Prioritize results whose artist matches if metadata.artist and metadata.title: n_artist = normalize_str(artist) def res_has_same_artist(result): return normalize_str(result._artist) == n_artist result.sort(key=res_has_same_artist, reverse=True) return result def real_search(self, title='', artist='', page=0): query = VIEWLYRICS_QUERY_FORM query = query.replace('%title', title) query = query.replace('%artist', artist) query = query.replace('%etc', ' client="MiniLyrics" RequestPage=\'%d\'' % page) # Needs real RequestPage query = query.encode('utf-8') queryhash = hashlib.md5() queryhash.update(query) queryhash.update(VIEWLYRICS_KEY) masterquery = b'\2\0\4\0\0\0' + queryhash.digest() + query url = VIEWLYRICS_HOST + VIEWLYRICS_SEARCH_URL status, content = http_download(url=url, method='POST', params=masterquery, proxy=get_proxy_settings(self.config_proxy)) if status < 200 or status >= 400: raise http.client.HTTPException(status, '') contentbytes = bytearray(content) codekey = contentbytes[1] deccontent = bytes(map(codekey.__xor__, contentbytes[22:])) root = xet.fromstring(deccontent) # tagName == 'return' pagesleft = int(next((v for k, v in root.items() if k.lower() == 'PageCount'.lower()), 0)) result = [ SearchResult( title=entry.get('title', ''), artist=entry.get('artist', ''), album=entry.get('album', ''), sourceid=self.id, downloadinfo=VIEWLYRICS_BASE_LRC_URL + entry.get('link'), ) for entry in root.findall('fileinfo[@link]') ] return result, (pagesleft - page) def do_download(self, downloadinfo): # type: (Any) -> bytes # `downloadinfo` is what you set in SearchResult status, content = http_download(url=downloadinfo, proxy=get_proxy_settings(self.config_proxy)) if status < 200 or status >= 400: raise http.client.HTTPException(status, '') return content if __name__ == '__main__': viewlyrics = ViewlyricsSource() viewlyrics._app.run() osdlyrics-0.5.5~rc1+dfsg1/lyricsources/xiami/000077500000000000000000000000001345713535600212115ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/lyricsources/xiami/Makefile.am000066400000000000000000000007301345713535600232450ustar00rootroot00000000000000xiami_PYTHON = xiami.py xiamidir = $(pkglibdir)/lyricsources/xiami servicedir = $(datadir)/dbus-1/services service_in_files = org.osdlyrics.LyricSourcePlugin.xiami.service.in service_DATA = $(service_in_files:.service.in=.service) EXTRA_DIST = \ $(service_in_files) \ $(NULL) $(service_DATA): $(service_in_files) @sed -e "s|\@pkglibdir\@|$(pkglibdir)|" -e "s|\@PYTHON\@|$(PYTHON)|" $< > $@ CLEANFILES = \ org.osdlyrics.LyricSourcePlugin.xiami.service \ $(NULL) osdlyrics-0.5.5~rc1+dfsg1/lyricsources/xiami/org.osdlyrics.LyricSourcePlugin.xiami.service.in000066400000000000000000000001611345713535600325260ustar00rootroot00000000000000[D-BUS Service] Name=org.osdlyrics.LyricSourcePlugin.xiami Exec=@PYTHON@ @pkglibdir@/lyricsources/xiami/xiami.py osdlyrics-0.5.5~rc1+dfsg1/lyricsources/xiami/xiami.py000066400000000000000000000113231345713535600226720ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2012 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from future import standard_library standard_library.install_aliases() from builtins import str, super # import urlparse import gettext import html.parser import http.client import re from osdlyrics.lyricsource import BaseLyricSourcePlugin, SearchResult from osdlyrics.utils import get_proxy_settings, http_download _ = gettext.gettext XIAMI_HOST = 'www.xiami.com' XIAMI_SEARCH_URL = '/search' XIAMI_SONG_URL = '/song/' XIAMI_LRC_URL = '/song/playlist/id/' XIAMI_SEARCH_PATTERN = re.compile(r'().*?()', re.DOTALL) XIAMI_ID_PATTERN = re.compile(r' List[SearchResult] keys = [] if metadata.title: keys.append(metadata.title) if metadata.artist: keys.append(metadata.artist) urlkey = '+'.join(keys).replace(' ', '+') url = XIAMI_HOST + XIAMI_SEARCH_URL status, content = http_download(url=url, params={'key': urlkey}, proxy=get_proxy_settings(self.config_proxy)) if status < 200 or status >= 400: raise http.client.HTTPException(status, '') match = XIAMI_SEARCH_PATTERN.findall(content.decode('utf8')) result = [] if match: for title_elem, id, artist_elem, album_elem in match: title = TITLE_ATTR_PATTERN.search(title_elem).group(1) artist = TITLE_ATTR_PATTERN.search(artist_elem).group(1) album = TITLE_ATTR_PATTERN.search(album_elem).group(1) url = self.get_url(id) if url is not None: result.append(SearchResult(title=title, artist=artist, album=album, sourceid=self.id, downloadinfo=url)) return result def get_songid(self, id): status, content = http_download(url=XIAMI_HOST + XIAMI_SONG_URL + str(id), proxy=get_proxy_settings(self.config_proxy)) if status < 200 or status >= 400: return None match = XIAMI_ID_PATTERN.search(content) if not match: return None songid = match.group(1).strip() return songid def get_url(self, id): songid = self.get_songid(id) status, content = http_download(url=XIAMI_HOST + XIAMI_LRC_URL + str(songid), proxy=get_proxy_settings(self.config_proxy)) if status < 200 or status >= 400: return None match = XIAMI_URL_PATTERN.search(content) if not match: return None url = match.group(1).strip() if url.lower().endswith('.lrc'): return url else: return None def do_download(self, downloadinfo): # type: (Any) -> bytes # parts = urlparse.urlparse(downloadinfo) status, content = http_download(downloadinfo, proxy=get_proxy_settings(self.config_proxy)) if status < 200 or status >= 400: raise http.client.HTTPException(status) if content: content = html.parser.HTMLParser().unescape(content.decode('utf-8')) return content.encode('utf-8') if __name__ == '__main__': xiami = XiamiSource() xiami._app.run() osdlyrics-0.5.5~rc1+dfsg1/m4/000077500000000000000000000000001345713535600156745ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/m4/ChangeLog000066400000000000000000000006151345713535600174500ustar00rootroot000000000000002009-07-08 gettextize * gettext.m4: New file, from gettext-0.17. * iconv.m4: New file, from gettext-0.17. * lib-ld.m4: New file, from gettext-0.17. * lib-link.m4: New file, from gettext-0.17. * lib-prefix.m4: New file, from gettext-0.17. * nls.m4: New file, from gettext-0.17. * po.m4: New file, from gettext-0.17. * progtest.m4: New file, from gettext-0.17. osdlyrics-0.5.5~rc1+dfsg1/m4/gettext.m4000066400000000000000000000345701345713535600176330ustar00rootroot00000000000000# gettext.m4 serial 60 (gettext-0.17) dnl Copyright (C) 1995-2007 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. dnl dnl This file can can be used in projects which are not available under dnl the GNU General Public License or the GNU Library General Public dnl License but which still want to provide support for the GNU gettext dnl functionality. dnl Please note that the actual code of the GNU gettext library is covered dnl by the GNU Library General Public License, and the rest of the GNU dnl gettext package package is covered by the GNU General Public License. dnl They are *not* in the public domain. dnl Authors: dnl Ulrich Drepper , 1995-2000. dnl Bruno Haible , 2000-2006. dnl Macro to add for using GNU gettext. dnl Usage: AM_GNU_GETTEXT([INTLSYMBOL], [NEEDSYMBOL], [INTLDIR]). dnl INTLSYMBOL can be one of 'external', 'no-libtool', 'use-libtool'. The dnl default (if it is not specified or empty) is 'no-libtool'. dnl INTLSYMBOL should be 'external' for packages with no intl directory, dnl and 'no-libtool' or 'use-libtool' for packages with an intl directory. dnl If INTLSYMBOL is 'use-libtool', then a libtool library dnl $(top_builddir)/intl/libintl.la will be created (shared and/or static, dnl depending on --{enable,disable}-{shared,static} and on the presence of dnl AM-DISABLE-SHARED). If INTLSYMBOL is 'no-libtool', a static library dnl $(top_builddir)/intl/libintl.a will be created. dnl If NEEDSYMBOL is specified and is 'need-ngettext', then GNU gettext dnl implementations (in libc or libintl) without the ngettext() function dnl will be ignored. If NEEDSYMBOL is specified and is dnl 'need-formatstring-macros', then GNU gettext implementations that don't dnl support the ISO C 99 formatstring macros will be ignored. dnl INTLDIR is used to find the intl libraries. If empty, dnl the value `$(top_builddir)/intl/' is used. dnl dnl The result of the configuration is one of three cases: dnl 1) GNU gettext, as included in the intl subdirectory, will be compiled dnl and used. dnl Catalog format: GNU --> install in $(datadir) dnl Catalog extension: .mo after installation, .gmo in source tree dnl 2) GNU gettext has been found in the system's C library. dnl Catalog format: GNU --> install in $(datadir) dnl Catalog extension: .mo after installation, .gmo in source tree dnl 3) No internationalization, always use English msgid. dnl Catalog format: none dnl Catalog extension: none dnl If INTLSYMBOL is 'external', only cases 2 and 3 can occur. dnl The use of .gmo is historical (it was needed to avoid overwriting the dnl GNU format catalogs when building on a platform with an X/Open gettext), dnl but we keep it in order not to force irrelevant filename changes on the dnl maintainers. dnl AC_DEFUN([AM_GNU_GETTEXT], [ dnl Argument checking. ifelse([$1], [], , [ifelse([$1], [external], , [ifelse([$1], [no-libtool], , [ifelse([$1], [use-libtool], , [errprint([ERROR: invalid first argument to AM_GNU_GETTEXT ])])])])]) ifelse([$2], [], , [ifelse([$2], [need-ngettext], , [ifelse([$2], [need-formatstring-macros], , [errprint([ERROR: invalid second argument to AM_GNU_GETTEXT ])])])]) define([gt_included_intl], ifelse([$1], [external], ifdef([AM_GNU_GETTEXT_][INTL_SUBDIR], [yes], [no]), [yes])) define([gt_libtool_suffix_prefix], ifelse([$1], [use-libtool], [l], [])) gt_NEEDS_INIT AM_GNU_GETTEXT_NEED([$2]) AC_REQUIRE([AM_PO_SUBDIRS])dnl ifelse(gt_included_intl, yes, [ AC_REQUIRE([AM_INTL_SUBDIR])dnl ]) dnl Prerequisites of AC_LIB_LINKFLAGS_BODY. AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) AC_REQUIRE([AC_LIB_RPATH]) dnl Sometimes libintl requires libiconv, so first search for libiconv. dnl Ideally we would do this search only after the dnl if test "$USE_NLS" = "yes"; then dnl if { eval "gt_val=\$$gt_func_gnugettext_libc"; test "$gt_val" != "yes"; }; then dnl tests. But if configure.in invokes AM_ICONV after AM_GNU_GETTEXT dnl the configure script would need to contain the same shell code dnl again, outside any 'if'. There are two solutions: dnl - Invoke AM_ICONV_LINKFLAGS_BODY here, outside any 'if'. dnl - Control the expansions in more detail using AC_PROVIDE_IFELSE. dnl Since AC_PROVIDE_IFELSE is only in autoconf >= 2.52 and not dnl documented, we avoid it. ifelse(gt_included_intl, yes, , [ AC_REQUIRE([AM_ICONV_LINKFLAGS_BODY]) ]) dnl Sometimes, on MacOS X, libintl requires linking with CoreFoundation. gt_INTL_MACOSX dnl Set USE_NLS. AC_REQUIRE([AM_NLS]) ifelse(gt_included_intl, yes, [ BUILD_INCLUDED_LIBINTL=no USE_INCLUDED_LIBINTL=no ]) LIBINTL= LTLIBINTL= POSUB= dnl Add a version number to the cache macros. case " $gt_needs " in *" need-formatstring-macros "*) gt_api_version=3 ;; *" need-ngettext "*) gt_api_version=2 ;; *) gt_api_version=1 ;; esac gt_func_gnugettext_libc="gt_cv_func_gnugettext${gt_api_version}_libc" gt_func_gnugettext_libintl="gt_cv_func_gnugettext${gt_api_version}_libintl" dnl If we use NLS figure out what method if test "$USE_NLS" = "yes"; then gt_use_preinstalled_gnugettext=no ifelse(gt_included_intl, yes, [ AC_MSG_CHECKING([whether included gettext is requested]) AC_ARG_WITH(included-gettext, [ --with-included-gettext use the GNU gettext library included here], nls_cv_force_use_gnu_gettext=$withval, nls_cv_force_use_gnu_gettext=no) AC_MSG_RESULT($nls_cv_force_use_gnu_gettext) nls_cv_use_gnu_gettext="$nls_cv_force_use_gnu_gettext" if test "$nls_cv_force_use_gnu_gettext" != "yes"; then ]) dnl User does not insist on using GNU NLS library. Figure out what dnl to use. If GNU gettext is available we use this. Else we have dnl to fall back to GNU NLS library. if test $gt_api_version -ge 3; then gt_revision_test_code=' #ifndef __GNU_GETTEXT_SUPPORTED_REVISION #define __GNU_GETTEXT_SUPPORTED_REVISION(major) ((major) == 0 ? 0 : -1) #endif changequote(,)dnl typedef int array [2 * (__GNU_GETTEXT_SUPPORTED_REVISION(0) >= 1) - 1]; changequote([,])dnl ' else gt_revision_test_code= fi if test $gt_api_version -ge 2; then gt_expression_test_code=' + * ngettext ("", "", 0)' else gt_expression_test_code= fi AC_CACHE_CHECK([for GNU gettext in libc], [$gt_func_gnugettext_libc], [AC_TRY_LINK([#include $gt_revision_test_code extern int _nl_msg_cat_cntr; extern int *_nl_domain_bindings;], [bindtextdomain ("", ""); return * gettext ("")$gt_expression_test_code + _nl_msg_cat_cntr + *_nl_domain_bindings], [eval "$gt_func_gnugettext_libc=yes"], [eval "$gt_func_gnugettext_libc=no"])]) if { eval "gt_val=\$$gt_func_gnugettext_libc"; test "$gt_val" != "yes"; }; then dnl Sometimes libintl requires libiconv, so first search for libiconv. ifelse(gt_included_intl, yes, , [ AM_ICONV_LINK ]) dnl Search for libintl and define LIBINTL, LTLIBINTL and INCINTL dnl accordingly. Don't use AC_LIB_LINKFLAGS_BODY([intl],[iconv]) dnl because that would add "-liconv" to LIBINTL and LTLIBINTL dnl even if libiconv doesn't exist. AC_LIB_LINKFLAGS_BODY([intl]) AC_CACHE_CHECK([for GNU gettext in libintl], [$gt_func_gnugettext_libintl], [gt_save_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $INCINTL" gt_save_LIBS="$LIBS" LIBS="$LIBS $LIBINTL" dnl Now see whether libintl exists and does not depend on libiconv. AC_TRY_LINK([#include $gt_revision_test_code extern int _nl_msg_cat_cntr; extern #ifdef __cplusplus "C" #endif const char *_nl_expand_alias (const char *);], [bindtextdomain ("", ""); return * gettext ("")$gt_expression_test_code + _nl_msg_cat_cntr + *_nl_expand_alias ("")], [eval "$gt_func_gnugettext_libintl=yes"], [eval "$gt_func_gnugettext_libintl=no"]) dnl Now see whether libintl exists and depends on libiconv. if { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" != yes; } && test -n "$LIBICONV"; then LIBS="$LIBS $LIBICONV" AC_TRY_LINK([#include $gt_revision_test_code extern int _nl_msg_cat_cntr; extern #ifdef __cplusplus "C" #endif const char *_nl_expand_alias (const char *);], [bindtextdomain ("", ""); return * gettext ("")$gt_expression_test_code + _nl_msg_cat_cntr + *_nl_expand_alias ("")], [LIBINTL="$LIBINTL $LIBICONV" LTLIBINTL="$LTLIBINTL $LTLIBICONV" eval "$gt_func_gnugettext_libintl=yes" ]) fi CPPFLAGS="$gt_save_CPPFLAGS" LIBS="$gt_save_LIBS"]) fi dnl If an already present or preinstalled GNU gettext() is found, dnl use it. But if this macro is used in GNU gettext, and GNU dnl gettext is already preinstalled in libintl, we update this dnl libintl. (Cf. the install rule in intl/Makefile.in.) if { eval "gt_val=\$$gt_func_gnugettext_libc"; test "$gt_val" = "yes"; } \ || { { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" = "yes"; } \ && test "$PACKAGE" != gettext-runtime \ && test "$PACKAGE" != gettext-tools; }; then gt_use_preinstalled_gnugettext=yes else dnl Reset the values set by searching for libintl. LIBINTL= LTLIBINTL= INCINTL= fi ifelse(gt_included_intl, yes, [ if test "$gt_use_preinstalled_gnugettext" != "yes"; then dnl GNU gettext is not found in the C library. dnl Fall back on included GNU gettext library. nls_cv_use_gnu_gettext=yes fi fi if test "$nls_cv_use_gnu_gettext" = "yes"; then dnl Mark actions used to generate GNU NLS library. BUILD_INCLUDED_LIBINTL=yes USE_INCLUDED_LIBINTL=yes LIBINTL="ifelse([$3],[],\${top_builddir}/intl,[$3])/libintl.[]gt_libtool_suffix_prefix[]a $LIBICONV $LIBTHREAD" LTLIBINTL="ifelse([$3],[],\${top_builddir}/intl,[$3])/libintl.[]gt_libtool_suffix_prefix[]a $LTLIBICONV $LTLIBTHREAD" LIBS=`echo " $LIBS " | sed -e 's/ -lintl / /' -e 's/^ //' -e 's/ $//'` fi CATOBJEXT= if test "$gt_use_preinstalled_gnugettext" = "yes" \ || test "$nls_cv_use_gnu_gettext" = "yes"; then dnl Mark actions to use GNU gettext tools. CATOBJEXT=.gmo fi ]) if test -n "$INTL_MACOSX_LIBS"; then if test "$gt_use_preinstalled_gnugettext" = "yes" \ || test "$nls_cv_use_gnu_gettext" = "yes"; then dnl Some extra flags are needed during linking. LIBINTL="$LIBINTL $INTL_MACOSX_LIBS" LTLIBINTL="$LTLIBINTL $INTL_MACOSX_LIBS" fi fi if test "$gt_use_preinstalled_gnugettext" = "yes" \ || test "$nls_cv_use_gnu_gettext" = "yes"; then AC_DEFINE(ENABLE_NLS, 1, [Define to 1 if translation of program messages to the user's native language is requested.]) else USE_NLS=no fi fi AC_MSG_CHECKING([whether to use NLS]) AC_MSG_RESULT([$USE_NLS]) if test "$USE_NLS" = "yes"; then AC_MSG_CHECKING([where the gettext function comes from]) if test "$gt_use_preinstalled_gnugettext" = "yes"; then if { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" = "yes"; }; then gt_source="external libintl" else gt_source="libc" fi else gt_source="included intl directory" fi AC_MSG_RESULT([$gt_source]) fi if test "$USE_NLS" = "yes"; then if test "$gt_use_preinstalled_gnugettext" = "yes"; then if { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" = "yes"; }; then AC_MSG_CHECKING([how to link with libintl]) AC_MSG_RESULT([$LIBINTL]) AC_LIB_APPENDTOVAR([CPPFLAGS], [$INCINTL]) fi dnl For backward compatibility. Some packages may be using this. AC_DEFINE(HAVE_GETTEXT, 1, [Define if the GNU gettext() function is already present or preinstalled.]) AC_DEFINE(HAVE_DCGETTEXT, 1, [Define if the GNU dcgettext() function is already present or preinstalled.]) fi dnl We need to process the po/ directory. POSUB=po fi ifelse(gt_included_intl, yes, [ dnl If this is used in GNU gettext we have to set BUILD_INCLUDED_LIBINTL dnl to 'yes' because some of the testsuite requires it. if test "$PACKAGE" = gettext-runtime || test "$PACKAGE" = gettext-tools; then BUILD_INCLUDED_LIBINTL=yes fi dnl Make all variables we use known to autoconf. AC_SUBST(BUILD_INCLUDED_LIBINTL) AC_SUBST(USE_INCLUDED_LIBINTL) AC_SUBST(CATOBJEXT) dnl For backward compatibility. Some configure.ins may be using this. nls_cv_header_intl= nls_cv_header_libgt= dnl For backward compatibility. Some Makefiles may be using this. DATADIRNAME=share AC_SUBST(DATADIRNAME) dnl For backward compatibility. Some Makefiles may be using this. INSTOBJEXT=.mo AC_SUBST(INSTOBJEXT) dnl For backward compatibility. Some Makefiles may be using this. GENCAT=gencat AC_SUBST(GENCAT) dnl For backward compatibility. Some Makefiles may be using this. INTLOBJS= if test "$USE_INCLUDED_LIBINTL" = yes; then INTLOBJS="\$(GETTOBJS)" fi AC_SUBST(INTLOBJS) dnl Enable libtool support if the surrounding package wishes it. INTL_LIBTOOL_SUFFIX_PREFIX=gt_libtool_suffix_prefix AC_SUBST(INTL_LIBTOOL_SUFFIX_PREFIX) ]) dnl For backward compatibility. Some Makefiles may be using this. INTLLIBS="$LIBINTL" AC_SUBST(INTLLIBS) dnl Make all documented variables known to autoconf. AC_SUBST(LIBINTL) AC_SUBST(LTLIBINTL) AC_SUBST(POSUB) ]) dnl gt_NEEDS_INIT ensures that the gt_needs variable is initialized. m4_define([gt_NEEDS_INIT], [ m4_divert_text([DEFAULTS], [gt_needs=]) m4_define([gt_NEEDS_INIT], []) ]) dnl Usage: AM_GNU_GETTEXT_NEED([NEEDSYMBOL]) AC_DEFUN([AM_GNU_GETTEXT_NEED], [ m4_divert_text([INIT_PREPARE], [gt_needs="$gt_needs $1"]) ]) dnl Usage: AM_GNU_GETTEXT_VERSION([gettext-version]) AC_DEFUN([AM_GNU_GETTEXT_VERSION], []) osdlyrics-0.5.5~rc1+dfsg1/m4/iconv.m4000066400000000000000000000137531345713535600172650ustar00rootroot00000000000000# iconv.m4 serial AM6 (gettext-0.17) dnl Copyright (C) 2000-2002, 2007 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. dnl From Bruno Haible. AC_DEFUN([AM_ICONV_LINKFLAGS_BODY], [ dnl Prerequisites of AC_LIB_LINKFLAGS_BODY. AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) AC_REQUIRE([AC_LIB_RPATH]) dnl Search for libiconv and define LIBICONV, LTLIBICONV and INCICONV dnl accordingly. AC_LIB_LINKFLAGS_BODY([iconv]) ]) AC_DEFUN([AM_ICONV_LINK], [ dnl Some systems have iconv in libc, some have it in libiconv (OSF/1 and dnl those with the standalone portable GNU libiconv installed). AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles dnl Search for libiconv and define LIBICONV, LTLIBICONV and INCICONV dnl accordingly. AC_REQUIRE([AM_ICONV_LINKFLAGS_BODY]) dnl Add $INCICONV to CPPFLAGS before performing the following checks, dnl because if the user has installed libiconv and not disabled its use dnl via --without-libiconv-prefix, he wants to use it. The first dnl AC_TRY_LINK will then fail, the second AC_TRY_LINK will succeed. am_save_CPPFLAGS="$CPPFLAGS" AC_LIB_APPENDTOVAR([CPPFLAGS], [$INCICONV]) AC_CACHE_CHECK([for iconv], am_cv_func_iconv, [ am_cv_func_iconv="no, consider installing GNU libiconv" am_cv_lib_iconv=no AC_TRY_LINK([#include #include ], [iconv_t cd = iconv_open("",""); iconv(cd,NULL,NULL,NULL,NULL); iconv_close(cd);], am_cv_func_iconv=yes) if test "$am_cv_func_iconv" != yes; then am_save_LIBS="$LIBS" LIBS="$LIBS $LIBICONV" AC_TRY_LINK([#include #include ], [iconv_t cd = iconv_open("",""); iconv(cd,NULL,NULL,NULL,NULL); iconv_close(cd);], am_cv_lib_iconv=yes am_cv_func_iconv=yes) LIBS="$am_save_LIBS" fi ]) if test "$am_cv_func_iconv" = yes; then AC_CACHE_CHECK([for working iconv], am_cv_func_iconv_works, [ dnl This tests against bugs in AIX 5.1 and HP-UX 11.11. am_save_LIBS="$LIBS" if test $am_cv_lib_iconv = yes; then LIBS="$LIBS $LIBICONV" fi AC_TRY_RUN([ #include #include int main () { /* Test against AIX 5.1 bug: Failures are not distinguishable from successful returns. */ { iconv_t cd_utf8_to_88591 = iconv_open ("ISO8859-1", "UTF-8"); if (cd_utf8_to_88591 != (iconv_t)(-1)) { static const char input[] = "\342\202\254"; /* EURO SIGN */ char buf[10]; const char *inptr = input; size_t inbytesleft = strlen (input); char *outptr = buf; size_t outbytesleft = sizeof (buf); size_t res = iconv (cd_utf8_to_88591, (char **) &inptr, &inbytesleft, &outptr, &outbytesleft); if (res == 0) return 1; } } #if 0 /* This bug could be worked around by the caller. */ /* Test against HP-UX 11.11 bug: Positive return value instead of 0. */ { iconv_t cd_88591_to_utf8 = iconv_open ("utf8", "iso88591"); if (cd_88591_to_utf8 != (iconv_t)(-1)) { static const char input[] = "\304rger mit b\366sen B\374bchen ohne Augenma\337"; char buf[50]; const char *inptr = input; size_t inbytesleft = strlen (input); char *outptr = buf; size_t outbytesleft = sizeof (buf); size_t res = iconv (cd_88591_to_utf8, (char **) &inptr, &inbytesleft, &outptr, &outbytesleft); if ((int)res > 0) return 1; } } #endif /* Test against HP-UX 11.11 bug: No converter from EUC-JP to UTF-8 is provided. */ if (/* Try standardized names. */ iconv_open ("UTF-8", "EUC-JP") == (iconv_t)(-1) /* Try IRIX, OSF/1 names. */ && iconv_open ("UTF-8", "eucJP") == (iconv_t)(-1) /* Try AIX names. */ && iconv_open ("UTF-8", "IBM-eucJP") == (iconv_t)(-1) /* Try HP-UX names. */ && iconv_open ("utf8", "eucJP") == (iconv_t)(-1)) return 1; return 0; }], [am_cv_func_iconv_works=yes], [am_cv_func_iconv_works=no], [case "$host_os" in aix* | hpux*) am_cv_func_iconv_works="guessing no" ;; *) am_cv_func_iconv_works="guessing yes" ;; esac]) LIBS="$am_save_LIBS" ]) case "$am_cv_func_iconv_works" in *no) am_func_iconv=no am_cv_lib_iconv=no ;; *) am_func_iconv=yes ;; esac else am_func_iconv=no am_cv_lib_iconv=no fi if test "$am_func_iconv" = yes; then AC_DEFINE(HAVE_ICONV, 1, [Define if you have the iconv() function and it works.]) fi if test "$am_cv_lib_iconv" = yes; then AC_MSG_CHECKING([how to link with libiconv]) AC_MSG_RESULT([$LIBICONV]) else dnl If $LIBICONV didn't lead to a usable library, we don't need $INCICONV dnl either. CPPFLAGS="$am_save_CPPFLAGS" LIBICONV= LTLIBICONV= fi AC_SUBST(LIBICONV) AC_SUBST(LTLIBICONV) ]) AC_DEFUN([AM_ICONV], [ AM_ICONV_LINK if test "$am_cv_func_iconv" = yes; then AC_MSG_CHECKING([for iconv declaration]) AC_CACHE_VAL(am_cv_proto_iconv, [ AC_TRY_COMPILE([ #include #include extern #ifdef __cplusplus "C" #endif #if defined(__STDC__) || defined(__cplusplus) size_t iconv (iconv_t cd, char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft); #else size_t iconv(); #endif ], [], am_cv_proto_iconv_arg1="", am_cv_proto_iconv_arg1="const") am_cv_proto_iconv="extern size_t iconv (iconv_t cd, $am_cv_proto_iconv_arg1 char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);"]) am_cv_proto_iconv=`echo "[$]am_cv_proto_iconv" | tr -s ' ' | sed -e 's/( /(/'` AC_MSG_RESULT([$]{ac_t:- }[$]am_cv_proto_iconv) AC_DEFINE_UNQUOTED(ICONV_CONST, $am_cv_proto_iconv_arg1, [Define as const if the declaration of iconv() needs const.]) fi ]) osdlyrics-0.5.5~rc1+dfsg1/m4/lib-ld.m4000066400000000000000000000065311345713535600173060ustar00rootroot00000000000000# lib-ld.m4 serial 3 (gettext-0.13) dnl Copyright (C) 1996-2003 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. dnl Subroutines of libtool.m4, dnl with replacements s/AC_/AC_LIB/ and s/lt_cv/acl_cv/ to avoid collision dnl with libtool.m4. dnl From libtool-1.4. Sets the variable with_gnu_ld to yes or no. AC_DEFUN([AC_LIB_PROG_LD_GNU], [AC_CACHE_CHECK([if the linker ($LD) is GNU ld], acl_cv_prog_gnu_ld, [# I'd rather use --version here, but apparently some GNU ld's only accept -v. case `$LD -v 2>&1 conf$$.sh echo "exit 0" >>conf$$.sh chmod +x conf$$.sh if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then PATH_SEPARATOR=';' else PATH_SEPARATOR=: fi rm -f conf$$.sh fi ac_prog=ld if test "$GCC" = yes; then # Check if gcc -print-prog-name=ld gives a path. AC_MSG_CHECKING([for ld used by GCC]) case $host in *-*-mingw*) # gcc leaves a trailing carriage return which upsets mingw ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; *) ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; esac case $ac_prog in # Accept absolute paths. [[\\/]* | [A-Za-z]:[\\/]*)] [re_direlt='/[^/][^/]*/\.\./'] # Canonicalize the path of ld ac_prog=`echo $ac_prog| sed 's%\\\\%/%g'` while echo $ac_prog | grep "$re_direlt" > /dev/null 2>&1; do ac_prog=`echo $ac_prog| sed "s%$re_direlt%/%"` done test -z "$LD" && LD="$ac_prog" ;; "") # If it fails, then pretend we aren't using GCC. ac_prog=ld ;; *) # If it is relative, then search for the first ld in PATH. with_gnu_ld=unknown ;; esac elif test "$with_gnu_ld" = yes; then AC_MSG_CHECKING([for GNU ld]) else AC_MSG_CHECKING([for non-GNU ld]) fi AC_CACHE_VAL(acl_cv_path_LD, [if test -z "$LD"; then IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}${PATH_SEPARATOR-:}" for ac_dir in $PATH; do test -z "$ac_dir" && ac_dir=. if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then acl_cv_path_LD="$ac_dir/$ac_prog" # Check to see if the program is GNU ld. I'd rather use --version, # but apparently some GNU ld's only accept -v. # Break only if it was the GNU/non-GNU ld that we prefer. case `"$acl_cv_path_LD" -v 2>&1 < /dev/null` in *GNU* | *'with BFD'*) test "$with_gnu_ld" != no && break ;; *) test "$with_gnu_ld" != yes && break ;; esac fi done IFS="$ac_save_ifs" else acl_cv_path_LD="$LD" # Let the user override the test with a path. fi]) LD="$acl_cv_path_LD" if test -n "$LD"; then AC_MSG_RESULT($LD) else AC_MSG_RESULT(no) fi test -z "$LD" && AC_MSG_ERROR([no acceptable ld found in \$PATH]) AC_LIB_PROG_LD_GNU ]) osdlyrics-0.5.5~rc1+dfsg1/m4/lib-link.m4000066400000000000000000000720551345713535600176500ustar00rootroot00000000000000# lib-link.m4 serial 13 (gettext-0.17) dnl Copyright (C) 2001-2007 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. dnl From Bruno Haible. AC_PREREQ(2.54) dnl AC_LIB_LINKFLAGS(name [, dependencies]) searches for libname and dnl the libraries corresponding to explicit and implicit dependencies. dnl Sets and AC_SUBSTs the LIB${NAME} and LTLIB${NAME} variables and dnl augments the CPPFLAGS variable. dnl Sets and AC_SUBSTs the LIB${NAME}_PREFIX variable to nonempty if libname dnl was found in ${LIB${NAME}_PREFIX}/$acl_libdirstem. AC_DEFUN([AC_LIB_LINKFLAGS], [ AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) AC_REQUIRE([AC_LIB_RPATH]) define([Name],[translit([$1],[./-], [___])]) define([NAME],[translit([$1],[abcdefghijklmnopqrstuvwxyz./-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ___])]) AC_CACHE_CHECK([how to link with lib[]$1], [ac_cv_lib[]Name[]_libs], [ AC_LIB_LINKFLAGS_BODY([$1], [$2]) ac_cv_lib[]Name[]_libs="$LIB[]NAME" ac_cv_lib[]Name[]_ltlibs="$LTLIB[]NAME" ac_cv_lib[]Name[]_cppflags="$INC[]NAME" ac_cv_lib[]Name[]_prefix="$LIB[]NAME[]_PREFIX" ]) LIB[]NAME="$ac_cv_lib[]Name[]_libs" LTLIB[]NAME="$ac_cv_lib[]Name[]_ltlibs" INC[]NAME="$ac_cv_lib[]Name[]_cppflags" LIB[]NAME[]_PREFIX="$ac_cv_lib[]Name[]_prefix" AC_LIB_APPENDTOVAR([CPPFLAGS], [$INC]NAME) AC_SUBST([LIB]NAME) AC_SUBST([LTLIB]NAME) AC_SUBST([LIB]NAME[_PREFIX]) dnl Also set HAVE_LIB[]NAME so that AC_LIB_HAVE_LINKFLAGS can reuse the dnl results of this search when this library appears as a dependency. HAVE_LIB[]NAME=yes undefine([Name]) undefine([NAME]) ]) dnl AC_LIB_HAVE_LINKFLAGS(name, dependencies, includes, testcode) dnl searches for libname and the libraries corresponding to explicit and dnl implicit dependencies, together with the specified include files and dnl the ability to compile and link the specified testcode. If found, it dnl sets and AC_SUBSTs HAVE_LIB${NAME}=yes and the LIB${NAME} and dnl LTLIB${NAME} variables and augments the CPPFLAGS variable, and dnl #defines HAVE_LIB${NAME} to 1. Otherwise, it sets and AC_SUBSTs dnl HAVE_LIB${NAME}=no and LIB${NAME} and LTLIB${NAME} to empty. dnl Sets and AC_SUBSTs the LIB${NAME}_PREFIX variable to nonempty if libname dnl was found in ${LIB${NAME}_PREFIX}/$acl_libdirstem. AC_DEFUN([AC_LIB_HAVE_LINKFLAGS], [ AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) AC_REQUIRE([AC_LIB_RPATH]) define([Name],[translit([$1],[./-], [___])]) define([NAME],[translit([$1],[abcdefghijklmnopqrstuvwxyz./-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ___])]) dnl Search for lib[]Name and define LIB[]NAME, LTLIB[]NAME and INC[]NAME dnl accordingly. AC_LIB_LINKFLAGS_BODY([$1], [$2]) dnl Add $INC[]NAME to CPPFLAGS before performing the following checks, dnl because if the user has installed lib[]Name and not disabled its use dnl via --without-lib[]Name-prefix, he wants to use it. ac_save_CPPFLAGS="$CPPFLAGS" AC_LIB_APPENDTOVAR([CPPFLAGS], [$INC]NAME) AC_CACHE_CHECK([for lib[]$1], [ac_cv_lib[]Name], [ ac_save_LIBS="$LIBS" LIBS="$LIBS $LIB[]NAME" AC_TRY_LINK([$3], [$4], [ac_cv_lib[]Name=yes], [ac_cv_lib[]Name=no]) LIBS="$ac_save_LIBS" ]) if test "$ac_cv_lib[]Name" = yes; then HAVE_LIB[]NAME=yes AC_DEFINE([HAVE_LIB]NAME, 1, [Define if you have the $1 library.]) AC_MSG_CHECKING([how to link with lib[]$1]) AC_MSG_RESULT([$LIB[]NAME]) else HAVE_LIB[]NAME=no dnl If $LIB[]NAME didn't lead to a usable library, we don't need dnl $INC[]NAME either. CPPFLAGS="$ac_save_CPPFLAGS" LIB[]NAME= LTLIB[]NAME= LIB[]NAME[]_PREFIX= fi AC_SUBST([HAVE_LIB]NAME) AC_SUBST([LIB]NAME) AC_SUBST([LTLIB]NAME) AC_SUBST([LIB]NAME[_PREFIX]) undefine([Name]) undefine([NAME]) ]) dnl Determine the platform dependent parameters needed to use rpath: dnl acl_libext, dnl acl_shlibext, dnl acl_hardcode_libdir_flag_spec, dnl acl_hardcode_libdir_separator, dnl acl_hardcode_direct, dnl acl_hardcode_minus_L. AC_DEFUN([AC_LIB_RPATH], [ dnl Tell automake >= 1.10 to complain if config.rpath is missing. m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([config.rpath])]) AC_REQUIRE([AC_PROG_CC]) dnl we use $CC, $GCC, $LDFLAGS AC_REQUIRE([AC_LIB_PROG_LD]) dnl we use $LD, $with_gnu_ld AC_REQUIRE([AC_CANONICAL_HOST]) dnl we use $host AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT]) dnl we use $ac_aux_dir AC_CACHE_CHECK([for shared library run path origin], acl_cv_rpath, [ CC="$CC" GCC="$GCC" LDFLAGS="$LDFLAGS" LD="$LD" with_gnu_ld="$with_gnu_ld" \ ${CONFIG_SHELL-/bin/sh} "$ac_aux_dir/config.rpath" "$host" > conftest.sh . ./conftest.sh rm -f ./conftest.sh acl_cv_rpath=done ]) wl="$acl_cv_wl" acl_libext="$acl_cv_libext" acl_shlibext="$acl_cv_shlibext" acl_libname_spec="$acl_cv_libname_spec" acl_library_names_spec="$acl_cv_library_names_spec" acl_hardcode_libdir_flag_spec="$acl_cv_hardcode_libdir_flag_spec" acl_hardcode_libdir_separator="$acl_cv_hardcode_libdir_separator" acl_hardcode_direct="$acl_cv_hardcode_direct" acl_hardcode_minus_L="$acl_cv_hardcode_minus_L" dnl Determine whether the user wants rpath handling at all. AC_ARG_ENABLE(rpath, [ --disable-rpath do not hardcode runtime library paths], :, enable_rpath=yes) ]) dnl AC_LIB_LINKFLAGS_BODY(name [, dependencies]) searches for libname and dnl the libraries corresponding to explicit and implicit dependencies. dnl Sets the LIB${NAME}, LTLIB${NAME} and INC${NAME} variables. dnl Also, sets the LIB${NAME}_PREFIX variable to nonempty if libname was found dnl in ${LIB${NAME}_PREFIX}/$acl_libdirstem. AC_DEFUN([AC_LIB_LINKFLAGS_BODY], [ AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) define([NAME],[translit([$1],[abcdefghijklmnopqrstuvwxyz./-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ___])]) dnl Autoconf >= 2.61 supports dots in --with options. define([N_A_M_E],[m4_if(m4_version_compare(m4_defn([m4_PACKAGE_VERSION]),[2.61]),[-1],[translit([$1],[.],[_])],[$1])]) dnl By default, look in $includedir and $libdir. use_additional=yes AC_LIB_WITH_FINAL_PREFIX([ eval additional_includedir=\"$includedir\" eval additional_libdir=\"$libdir\" ]) AC_LIB_ARG_WITH([lib]N_A_M_E[-prefix], [ --with-lib]N_A_M_E[-prefix[=DIR] search for lib$1 in DIR/include and DIR/lib --without-lib]N_A_M_E[-prefix don't search for lib$1 in includedir and libdir], [ if test "X$withval" = "Xno"; then use_additional=no else if test "X$withval" = "X"; then AC_LIB_WITH_FINAL_PREFIX([ eval additional_includedir=\"$includedir\" eval additional_libdir=\"$libdir\" ]) else additional_includedir="$withval/include" additional_libdir="$withval/$acl_libdirstem" fi fi ]) dnl Search the library and its dependencies in $additional_libdir and dnl $LDFLAGS. Using breadth-first-seach. LIB[]NAME= LTLIB[]NAME= INC[]NAME= LIB[]NAME[]_PREFIX= rpathdirs= ltrpathdirs= names_already_handled= names_next_round='$1 $2' while test -n "$names_next_round"; do names_this_round="$names_next_round" names_next_round= for name in $names_this_round; do already_handled= for n in $names_already_handled; do if test "$n" = "$name"; then already_handled=yes break fi done if test -z "$already_handled"; then names_already_handled="$names_already_handled $name" dnl See if it was already located by an earlier AC_LIB_LINKFLAGS dnl or AC_LIB_HAVE_LINKFLAGS call. uppername=`echo "$name" | sed -e 'y|abcdefghijklmnopqrstuvwxyz./-|ABCDEFGHIJKLMNOPQRSTUVWXYZ___|'` eval value=\"\$HAVE_LIB$uppername\" if test -n "$value"; then if test "$value" = yes; then eval value=\"\$LIB$uppername\" test -z "$value" || LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$value" eval value=\"\$LTLIB$uppername\" test -z "$value" || LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$value" else dnl An earlier call to AC_LIB_HAVE_LINKFLAGS has determined dnl that this library doesn't exist. So just drop it. : fi else dnl Search the library lib$name in $additional_libdir and $LDFLAGS dnl and the already constructed $LIBNAME/$LTLIBNAME. found_dir= found_la= found_so= found_a= eval libname=\"$acl_libname_spec\" # typically: libname=lib$name if test -n "$acl_shlibext"; then shrext=".$acl_shlibext" # typically: shrext=.so else shrext= fi if test $use_additional = yes; then dir="$additional_libdir" dnl The same code as in the loop below: dnl First look for a shared library. if test -n "$acl_shlibext"; then if test -f "$dir/$libname$shrext"; then found_dir="$dir" found_so="$dir/$libname$shrext" else if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then ver=`(cd "$dir" && \ for f in "$libname$shrext".*; do echo "$f"; done \ | sed -e "s,^$libname$shrext\\\\.,," \ | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \ | sed 1q ) 2>/dev/null` if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then found_dir="$dir" found_so="$dir/$libname$shrext.$ver" fi else eval library_names=\"$acl_library_names_spec\" for f in $library_names; do if test -f "$dir/$f"; then found_dir="$dir" found_so="$dir/$f" break fi done fi fi fi dnl Then look for a static library. if test "X$found_dir" = "X"; then if test -f "$dir/$libname.$acl_libext"; then found_dir="$dir" found_a="$dir/$libname.$acl_libext" fi fi if test "X$found_dir" != "X"; then if test -f "$dir/$libname.la"; then found_la="$dir/$libname.la" fi fi fi if test "X$found_dir" = "X"; then for x in $LDFLAGS $LTLIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) case "$x" in -L*) dir=`echo "X$x" | sed -e 's/^X-L//'` dnl First look for a shared library. if test -n "$acl_shlibext"; then if test -f "$dir/$libname$shrext"; then found_dir="$dir" found_so="$dir/$libname$shrext" else if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then ver=`(cd "$dir" && \ for f in "$libname$shrext".*; do echo "$f"; done \ | sed -e "s,^$libname$shrext\\\\.,," \ | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \ | sed 1q ) 2>/dev/null` if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then found_dir="$dir" found_so="$dir/$libname$shrext.$ver" fi else eval library_names=\"$acl_library_names_spec\" for f in $library_names; do if test -f "$dir/$f"; then found_dir="$dir" found_so="$dir/$f" break fi done fi fi fi dnl Then look for a static library. if test "X$found_dir" = "X"; then if test -f "$dir/$libname.$acl_libext"; then found_dir="$dir" found_a="$dir/$libname.$acl_libext" fi fi if test "X$found_dir" != "X"; then if test -f "$dir/$libname.la"; then found_la="$dir/$libname.la" fi fi ;; esac if test "X$found_dir" != "X"; then break fi done fi if test "X$found_dir" != "X"; then dnl Found the library. LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$found_dir -l$name" if test "X$found_so" != "X"; then dnl Linking with a shared library. We attempt to hardcode its dnl directory into the executable's runpath, unless it's the dnl standard /usr/lib. if test "$enable_rpath" = no || test "X$found_dir" = "X/usr/$acl_libdirstem"; then dnl No hardcoding is needed. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" else dnl Use an explicit option to hardcode DIR into the resulting dnl binary. dnl Potentially add DIR to ltrpathdirs. dnl The ltrpathdirs will be appended to $LTLIBNAME at the end. haveit= for x in $ltrpathdirs; do if test "X$x" = "X$found_dir"; then haveit=yes break fi done if test -z "$haveit"; then ltrpathdirs="$ltrpathdirs $found_dir" fi dnl The hardcoding into $LIBNAME is system dependent. if test "$acl_hardcode_direct" = yes; then dnl Using DIR/libNAME.so during linking hardcodes DIR into the dnl resulting binary. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" else if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then dnl Use an explicit option to hardcode DIR into the resulting dnl binary. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" dnl Potentially add DIR to rpathdirs. dnl The rpathdirs will be appended to $LIBNAME at the end. haveit= for x in $rpathdirs; do if test "X$x" = "X$found_dir"; then haveit=yes break fi done if test -z "$haveit"; then rpathdirs="$rpathdirs $found_dir" fi else dnl Rely on "-L$found_dir". dnl But don't add it if it's already contained in the LDFLAGS dnl or the already constructed $LIBNAME haveit= for x in $LDFLAGS $LIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-L$found_dir"; then haveit=yes break fi done if test -z "$haveit"; then LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir" fi if test "$acl_hardcode_minus_L" != no; then dnl FIXME: Not sure whether we should use dnl "-L$found_dir -l$name" or "-L$found_dir $found_so" dnl here. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" else dnl We cannot use $acl_hardcode_runpath_var and LD_RUN_PATH dnl here, because this doesn't fit in flags passed to the dnl compiler. So give up. No hardcoding. This affects only dnl very old systems. dnl FIXME: Not sure whether we should use dnl "-L$found_dir -l$name" or "-L$found_dir $found_so" dnl here. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name" fi fi fi fi else if test "X$found_a" != "X"; then dnl Linking with a static library. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_a" else dnl We shouldn't come here, but anyway it's good to have a dnl fallback. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir -l$name" fi fi dnl Assume the include files are nearby. additional_includedir= case "$found_dir" in */$acl_libdirstem | */$acl_libdirstem/) basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem/"'*$,,'` LIB[]NAME[]_PREFIX="$basedir" additional_includedir="$basedir/include" ;; esac if test "X$additional_includedir" != "X"; then dnl Potentially add $additional_includedir to $INCNAME. dnl But don't add it dnl 1. if it's the standard /usr/include, dnl 2. if it's /usr/local/include and we are using GCC on Linux, dnl 3. if it's already present in $CPPFLAGS or the already dnl constructed $INCNAME, dnl 4. if it doesn't exist as a directory. if test "X$additional_includedir" != "X/usr/include"; then haveit= if test "X$additional_includedir" = "X/usr/local/include"; then if test -n "$GCC"; then case $host_os in linux* | gnu* | k*bsd*-gnu) haveit=yes;; esac fi fi if test -z "$haveit"; then for x in $CPPFLAGS $INC[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-I$additional_includedir"; then haveit=yes break fi done if test -z "$haveit"; then if test -d "$additional_includedir"; then dnl Really add $additional_includedir to $INCNAME. INC[]NAME="${INC[]NAME}${INC[]NAME:+ }-I$additional_includedir" fi fi fi fi fi dnl Look for dependencies. if test -n "$found_la"; then dnl Read the .la file. It defines the variables dnl dlname, library_names, old_library, dependency_libs, current, dnl age, revision, installed, dlopen, dlpreopen, libdir. save_libdir="$libdir" case "$found_la" in */* | *\\*) . "$found_la" ;; *) . "./$found_la" ;; esac libdir="$save_libdir" dnl We use only dependency_libs. for dep in $dependency_libs; do case "$dep" in -L*) additional_libdir=`echo "X$dep" | sed -e 's/^X-L//'` dnl Potentially add $additional_libdir to $LIBNAME and $LTLIBNAME. dnl But don't add it dnl 1. if it's the standard /usr/lib, dnl 2. if it's /usr/local/lib and we are using GCC on Linux, dnl 3. if it's already present in $LDFLAGS or the already dnl constructed $LIBNAME, dnl 4. if it doesn't exist as a directory. if test "X$additional_libdir" != "X/usr/$acl_libdirstem"; then haveit= if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem"; then if test -n "$GCC"; then case $host_os in linux* | gnu* | k*bsd*-gnu) haveit=yes;; esac fi fi if test -z "$haveit"; then haveit= for x in $LDFLAGS $LIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-L$additional_libdir"; then haveit=yes break fi done if test -z "$haveit"; then if test -d "$additional_libdir"; then dnl Really add $additional_libdir to $LIBNAME. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$additional_libdir" fi fi haveit= for x in $LDFLAGS $LTLIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-L$additional_libdir"; then haveit=yes break fi done if test -z "$haveit"; then if test -d "$additional_libdir"; then dnl Really add $additional_libdir to $LTLIBNAME. LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$additional_libdir" fi fi fi fi ;; -R*) dir=`echo "X$dep" | sed -e 's/^X-R//'` if test "$enable_rpath" != no; then dnl Potentially add DIR to rpathdirs. dnl The rpathdirs will be appended to $LIBNAME at the end. haveit= for x in $rpathdirs; do if test "X$x" = "X$dir"; then haveit=yes break fi done if test -z "$haveit"; then rpathdirs="$rpathdirs $dir" fi dnl Potentially add DIR to ltrpathdirs. dnl The ltrpathdirs will be appended to $LTLIBNAME at the end. haveit= for x in $ltrpathdirs; do if test "X$x" = "X$dir"; then haveit=yes break fi done if test -z "$haveit"; then ltrpathdirs="$ltrpathdirs $dir" fi fi ;; -l*) dnl Handle this in the next round. names_next_round="$names_next_round "`echo "X$dep" | sed -e 's/^X-l//'` ;; *.la) dnl Handle this in the next round. Throw away the .la's dnl directory; it is already contained in a preceding -L dnl option. names_next_round="$names_next_round "`echo "X$dep" | sed -e 's,^X.*/,,' -e 's,^lib,,' -e 's,\.la$,,'` ;; *) dnl Most likely an immediate library name. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$dep" LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$dep" ;; esac done fi else dnl Didn't find the library; assume it is in the system directories dnl known to the linker and runtime loader. (All the system dnl directories known to the linker should also be known to the dnl runtime loader, otherwise the system is severely misconfigured.) LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name" LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-l$name" fi fi fi done done if test "X$rpathdirs" != "X"; then if test -n "$acl_hardcode_libdir_separator"; then dnl Weird platform: only the last -rpath option counts, the user must dnl pass all path elements in one option. We can arrange that for a dnl single library, but not when more than one $LIBNAMEs are used. alldirs= for found_dir in $rpathdirs; do alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$found_dir" done dnl Note: acl_hardcode_libdir_flag_spec uses $libdir and $wl. acl_save_libdir="$libdir" libdir="$alldirs" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag" else dnl The -rpath options are cumulative. for found_dir in $rpathdirs; do acl_save_libdir="$libdir" libdir="$found_dir" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag" done fi fi if test "X$ltrpathdirs" != "X"; then dnl When using libtool, the option that works for both libraries and dnl executables is -R. The -R options are cumulative. for found_dir in $ltrpathdirs; do LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-R$found_dir" done fi ]) dnl AC_LIB_APPENDTOVAR(VAR, CONTENTS) appends the elements of CONTENTS to VAR, dnl unless already present in VAR. dnl Works only for CPPFLAGS, not for LIB* variables because that sometimes dnl contains two or three consecutive elements that belong together. AC_DEFUN([AC_LIB_APPENDTOVAR], [ for element in [$2]; do haveit= for x in $[$1]; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X$element"; then haveit=yes break fi done if test -z "$haveit"; then [$1]="${[$1]}${[$1]:+ }$element" fi done ]) dnl For those cases where a variable contains several -L and -l options dnl referring to unknown libraries and directories, this macro determines the dnl necessary additional linker options for the runtime path. dnl AC_LIB_LINKFLAGS_FROM_LIBS([LDADDVAR], [LIBSVALUE], [USE-LIBTOOL]) dnl sets LDADDVAR to linker options needed together with LIBSVALUE. dnl If USE-LIBTOOL evaluates to non-empty, linking with libtool is assumed, dnl otherwise linking without libtool is assumed. AC_DEFUN([AC_LIB_LINKFLAGS_FROM_LIBS], [ AC_REQUIRE([AC_LIB_RPATH]) AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) $1= if test "$enable_rpath" != no; then if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then dnl Use an explicit option to hardcode directories into the resulting dnl binary. rpathdirs= next= for opt in $2; do if test -n "$next"; then dir="$next" dnl No need to hardcode the standard /usr/lib. if test "X$dir" != "X/usr/$acl_libdirstem"; then rpathdirs="$rpathdirs $dir" fi next= else case $opt in -L) next=yes ;; -L*) dir=`echo "X$opt" | sed -e 's,^X-L,,'` dnl No need to hardcode the standard /usr/lib. if test "X$dir" != "X/usr/$acl_libdirstem"; then rpathdirs="$rpathdirs $dir" fi next= ;; *) next= ;; esac fi done if test "X$rpathdirs" != "X"; then if test -n ""$3""; then dnl libtool is used for linking. Use -R options. for dir in $rpathdirs; do $1="${$1}${$1:+ }-R$dir" done else dnl The linker is used for linking directly. if test -n "$acl_hardcode_libdir_separator"; then dnl Weird platform: only the last -rpath option counts, the user dnl must pass all path elements in one option. alldirs= for dir in $rpathdirs; do alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$dir" done acl_save_libdir="$libdir" libdir="$alldirs" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" $1="$flag" else dnl The -rpath options are cumulative. for dir in $rpathdirs; do acl_save_libdir="$libdir" libdir="$dir" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" $1="${$1}${$1:+ }$flag" done fi fi fi fi fi AC_SUBST([$1]) ]) osdlyrics-0.5.5~rc1+dfsg1/m4/lib-prefix.m4000066400000000000000000000150361345713535600202040ustar00rootroot00000000000000# lib-prefix.m4 serial 5 (gettext-0.15) dnl Copyright (C) 2001-2005 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. dnl From Bruno Haible. dnl AC_LIB_ARG_WITH is synonymous to AC_ARG_WITH in autoconf-2.13, and dnl similar to AC_ARG_WITH in autoconf 2.52...2.57 except that is doesn't dnl require excessive bracketing. ifdef([AC_HELP_STRING], [AC_DEFUN([AC_LIB_ARG_WITH], [AC_ARG_WITH([$1],[[$2]],[$3],[$4])])], [AC_DEFUN([AC_][LIB_ARG_WITH], [AC_ARG_WITH([$1],[$2],[$3],[$4])])]) dnl AC_LIB_PREFIX adds to the CPPFLAGS and LDFLAGS the flags that are needed dnl to access previously installed libraries. The basic assumption is that dnl a user will want packages to use other packages he previously installed dnl with the same --prefix option. dnl This macro is not needed if only AC_LIB_LINKFLAGS is used to locate dnl libraries, but is otherwise very convenient. AC_DEFUN([AC_LIB_PREFIX], [ AC_BEFORE([$0], [AC_LIB_LINKFLAGS]) AC_REQUIRE([AC_PROG_CC]) AC_REQUIRE([AC_CANONICAL_HOST]) AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) dnl By default, look in $includedir and $libdir. use_additional=yes AC_LIB_WITH_FINAL_PREFIX([ eval additional_includedir=\"$includedir\" eval additional_libdir=\"$libdir\" ]) AC_LIB_ARG_WITH([lib-prefix], [ --with-lib-prefix[=DIR] search for libraries in DIR/include and DIR/lib --without-lib-prefix don't search for libraries in includedir and libdir], [ if test "X$withval" = "Xno"; then use_additional=no else if test "X$withval" = "X"; then AC_LIB_WITH_FINAL_PREFIX([ eval additional_includedir=\"$includedir\" eval additional_libdir=\"$libdir\" ]) else additional_includedir="$withval/include" additional_libdir="$withval/$acl_libdirstem" fi fi ]) if test $use_additional = yes; then dnl Potentially add $additional_includedir to $CPPFLAGS. dnl But don't add it dnl 1. if it's the standard /usr/include, dnl 2. if it's already present in $CPPFLAGS, dnl 3. if it's /usr/local/include and we are using GCC on Linux, dnl 4. if it doesn't exist as a directory. if test "X$additional_includedir" != "X/usr/include"; then haveit= for x in $CPPFLAGS; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-I$additional_includedir"; then haveit=yes break fi done if test -z "$haveit"; then if test "X$additional_includedir" = "X/usr/local/include"; then if test -n "$GCC"; then case $host_os in linux* | gnu* | k*bsd*-gnu) haveit=yes;; esac fi fi if test -z "$haveit"; then if test -d "$additional_includedir"; then dnl Really add $additional_includedir to $CPPFLAGS. CPPFLAGS="${CPPFLAGS}${CPPFLAGS:+ }-I$additional_includedir" fi fi fi fi dnl Potentially add $additional_libdir to $LDFLAGS. dnl But don't add it dnl 1. if it's the standard /usr/lib, dnl 2. if it's already present in $LDFLAGS, dnl 3. if it's /usr/local/lib and we are using GCC on Linux, dnl 4. if it doesn't exist as a directory. if test "X$additional_libdir" != "X/usr/$acl_libdirstem"; then haveit= for x in $LDFLAGS; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-L$additional_libdir"; then haveit=yes break fi done if test -z "$haveit"; then if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem"; then if test -n "$GCC"; then case $host_os in linux*) haveit=yes;; esac fi fi if test -z "$haveit"; then if test -d "$additional_libdir"; then dnl Really add $additional_libdir to $LDFLAGS. LDFLAGS="${LDFLAGS}${LDFLAGS:+ }-L$additional_libdir" fi fi fi fi fi ]) dnl AC_LIB_PREPARE_PREFIX creates variables acl_final_prefix, dnl acl_final_exec_prefix, containing the values to which $prefix and dnl $exec_prefix will expand at the end of the configure script. AC_DEFUN([AC_LIB_PREPARE_PREFIX], [ dnl Unfortunately, prefix and exec_prefix get only finally determined dnl at the end of configure. if test "X$prefix" = "XNONE"; then acl_final_prefix="$ac_default_prefix" else acl_final_prefix="$prefix" fi if test "X$exec_prefix" = "XNONE"; then acl_final_exec_prefix='${prefix}' else acl_final_exec_prefix="$exec_prefix" fi acl_save_prefix="$prefix" prefix="$acl_final_prefix" eval acl_final_exec_prefix=\"$acl_final_exec_prefix\" prefix="$acl_save_prefix" ]) dnl AC_LIB_WITH_FINAL_PREFIX([statement]) evaluates statement, with the dnl variables prefix and exec_prefix bound to the values they will have dnl at the end of the configure script. AC_DEFUN([AC_LIB_WITH_FINAL_PREFIX], [ acl_save_prefix="$prefix" prefix="$acl_final_prefix" acl_save_exec_prefix="$exec_prefix" exec_prefix="$acl_final_exec_prefix" $1 exec_prefix="$acl_save_exec_prefix" prefix="$acl_save_prefix" ]) dnl AC_LIB_PREPARE_MULTILIB creates a variable acl_libdirstem, containing dnl the basename of the libdir, either "lib" or "lib64". AC_DEFUN([AC_LIB_PREPARE_MULTILIB], [ dnl There is no formal standard regarding lib and lib64. The current dnl practice is that on a system supporting 32-bit and 64-bit instruction dnl sets or ABIs, 64-bit libraries go under $prefix/lib64 and 32-bit dnl libraries go under $prefix/lib. We determine the compiler's default dnl mode by looking at the compiler's library search path. If at least dnl of its elements ends in /lib64 or points to a directory whose absolute dnl pathname ends in /lib64, we assume a 64-bit ABI. Otherwise we use the dnl default, namely "lib". acl_libdirstem=lib searchpath=`(LC_ALL=C $CC -print-search-dirs) 2>/dev/null | sed -n -e 's,^libraries: ,,p' | sed -e 's,^=,,'` if test -n "$searchpath"; then acl_save_IFS="${IFS= }"; IFS=":" for searchdir in $searchpath; do if test -d "$searchdir"; then case "$searchdir" in */lib64/ | */lib64 ) acl_libdirstem=lib64 ;; *) searchdir=`cd "$searchdir" && pwd` case "$searchdir" in */lib64 ) acl_libdirstem=lib64 ;; esac ;; esac fi done IFS="$acl_save_IFS" fi ]) osdlyrics-0.5.5~rc1+dfsg1/m4/nls.m4000066400000000000000000000022661345713535600167400ustar00rootroot00000000000000# nls.m4 serial 3 (gettext-0.15) dnl Copyright (C) 1995-2003, 2005-2006 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. dnl dnl This file can can be used in projects which are not available under dnl the GNU General Public License or the GNU Library General Public dnl License but which still want to provide support for the GNU gettext dnl functionality. dnl Please note that the actual code of the GNU gettext library is covered dnl by the GNU Library General Public License, and the rest of the GNU dnl gettext package package is covered by the GNU General Public License. dnl They are *not* in the public domain. dnl Authors: dnl Ulrich Drepper , 1995-2000. dnl Bruno Haible , 2000-2003. AC_PREREQ(2.50) AC_DEFUN([AM_NLS], [ AC_MSG_CHECKING([whether NLS is requested]) dnl Default is enabled NLS AC_ARG_ENABLE(nls, [ --disable-nls do not use Native Language Support], USE_NLS=$enableval, USE_NLS=yes) AC_MSG_RESULT($USE_NLS) AC_SUBST(USE_NLS) ]) osdlyrics-0.5.5~rc1+dfsg1/m4/po.m4000066400000000000000000000446061345713535600165660ustar00rootroot00000000000000# po.m4 serial 15 (gettext-0.17) dnl Copyright (C) 1995-2007 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. dnl dnl This file can can be used in projects which are not available under dnl the GNU General Public License or the GNU Library General Public dnl License but which still want to provide support for the GNU gettext dnl functionality. dnl Please note that the actual code of the GNU gettext library is covered dnl by the GNU Library General Public License, and the rest of the GNU dnl gettext package package is covered by the GNU General Public License. dnl They are *not* in the public domain. dnl Authors: dnl Ulrich Drepper , 1995-2000. dnl Bruno Haible , 2000-2003. AC_PREREQ(2.50) dnl Checks for all prerequisites of the po subdirectory. AC_DEFUN([AM_PO_SUBDIRS], [ AC_REQUIRE([AC_PROG_MAKE_SET])dnl AC_REQUIRE([AC_PROG_INSTALL])dnl AC_REQUIRE([AM_PROG_MKDIR_P])dnl defined by automake AC_REQUIRE([AM_NLS])dnl dnl Release version of the gettext macros. This is used to ensure that dnl the gettext macros and po/Makefile.in.in are in sync. AC_SUBST([GETTEXT_MACRO_VERSION], [0.17]) dnl Perform the following tests also if --disable-nls has been given, dnl because they are needed for "make dist" to work. dnl Search for GNU msgfmt in the PATH. dnl The first test excludes Solaris msgfmt and early GNU msgfmt versions. dnl The second test excludes FreeBSD msgfmt. AM_PATH_PROG_WITH_TEST(MSGFMT, msgfmt, [$ac_dir/$ac_word --statistics /dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1 && (if $ac_dir/$ac_word --statistics /dev/null 2>&1 >/dev/null | grep usage >/dev/null; then exit 1; else exit 0; fi)], :) AC_PATH_PROG(GMSGFMT, gmsgfmt, $MSGFMT) dnl Test whether it is GNU msgfmt >= 0.15. changequote(,)dnl case `$MSGFMT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in '' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) MSGFMT_015=: ;; *) MSGFMT_015=$MSGFMT ;; esac changequote([,])dnl AC_SUBST([MSGFMT_015]) changequote(,)dnl case `$GMSGFMT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in '' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) GMSGFMT_015=: ;; *) GMSGFMT_015=$GMSGFMT ;; esac changequote([,])dnl AC_SUBST([GMSGFMT_015]) dnl Search for GNU xgettext 0.12 or newer in the PATH. dnl The first test excludes Solaris xgettext and early GNU xgettext versions. dnl The second test excludes FreeBSD xgettext. AM_PATH_PROG_WITH_TEST(XGETTEXT, xgettext, [$ac_dir/$ac_word --omit-header --copyright-holder= --msgid-bugs-address= /dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1 && (if $ac_dir/$ac_word --omit-header --copyright-holder= --msgid-bugs-address= /dev/null 2>&1 >/dev/null | grep usage >/dev/null; then exit 1; else exit 0; fi)], :) dnl Remove leftover from FreeBSD xgettext call. rm -f messages.po dnl Test whether it is GNU xgettext >= 0.15. changequote(,)dnl case `$XGETTEXT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in '' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) XGETTEXT_015=: ;; *) XGETTEXT_015=$XGETTEXT ;; esac changequote([,])dnl AC_SUBST([XGETTEXT_015]) dnl Search for GNU msgmerge 0.11 or newer in the PATH. AM_PATH_PROG_WITH_TEST(MSGMERGE, msgmerge, [$ac_dir/$ac_word --update -q /dev/null /dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1], :) dnl Installation directories. dnl Autoconf >= 2.60 defines localedir. For older versions of autoconf, we dnl have to define it here, so that it can be used in po/Makefile. test -n "$localedir" || localedir='${datadir}/locale' AC_SUBST([localedir]) dnl Support for AM_XGETTEXT_OPTION. test -n "${XGETTEXT_EXTRA_OPTIONS+set}" || XGETTEXT_EXTRA_OPTIONS= AC_SUBST([XGETTEXT_EXTRA_OPTIONS]) AC_CONFIG_COMMANDS([po-directories], [[ for ac_file in $CONFIG_FILES; do # Support "outfile[:infile[:infile...]]" case "$ac_file" in *:*) ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; esac # PO directories have a Makefile.in generated from Makefile.in.in. case "$ac_file" in */Makefile.in) # Adjust a relative srcdir. ac_dir=`echo "$ac_file"|sed 's%/[^/][^/]*$%%'` ac_dir_suffix="/`echo "$ac_dir"|sed 's%^\./%%'`" ac_dots=`echo "$ac_dir_suffix"|sed 's%/[^/]*%../%g'` # In autoconf-2.13 it is called $ac_given_srcdir. # In autoconf-2.50 it is called $srcdir. test -n "$ac_given_srcdir" || ac_given_srcdir="$srcdir" case "$ac_given_srcdir" in .) top_srcdir=`echo $ac_dots|sed 's%/$%%'` ;; /*) top_srcdir="$ac_given_srcdir" ;; *) top_srcdir="$ac_dots$ac_given_srcdir" ;; esac # Treat a directory as a PO directory if and only if it has a # POTFILES.in file. This allows packages to have multiple PO # directories under different names or in different locations. if test -f "$ac_given_srcdir/$ac_dir/POTFILES.in"; then rm -f "$ac_dir/POTFILES" test -n "$as_me" && echo "$as_me: creating $ac_dir/POTFILES" || echo "creating $ac_dir/POTFILES" cat "$ac_given_srcdir/$ac_dir/POTFILES.in" | sed -e "/^#/d" -e "/^[ ]*\$/d" -e "s,.*, $top_srcdir/& \\\\," | sed -e "\$s/\(.*\) \\\\/\1/" > "$ac_dir/POTFILES" POMAKEFILEDEPS="POTFILES.in" # ALL_LINGUAS, POFILES, UPDATEPOFILES, DUMMYPOFILES, GMOFILES depend # on $ac_dir but don't depend on user-specified configuration # parameters. if test -f "$ac_given_srcdir/$ac_dir/LINGUAS"; then # The LINGUAS file contains the set of available languages. if test -n "$OBSOLETE_ALL_LINGUAS"; then test -n "$as_me" && echo "$as_me: setting ALL_LINGUAS in configure.in is obsolete" || echo "setting ALL_LINGUAS in configure.in is obsolete" fi ALL_LINGUAS_=`sed -e "/^#/d" -e "s/#.*//" "$ac_given_srcdir/$ac_dir/LINGUAS"` # Hide the ALL_LINGUAS assigment from automake < 1.5. eval 'ALL_LINGUAS''=$ALL_LINGUAS_' POMAKEFILEDEPS="$POMAKEFILEDEPS LINGUAS" else # The set of available languages was given in configure.in. # Hide the ALL_LINGUAS assigment from automake < 1.5. eval 'ALL_LINGUAS''=$OBSOLETE_ALL_LINGUAS' fi # Compute POFILES # as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).po) # Compute UPDATEPOFILES # as $(foreach lang, $(ALL_LINGUAS), $(lang).po-update) # Compute DUMMYPOFILES # as $(foreach lang, $(ALL_LINGUAS), $(lang).nop) # Compute GMOFILES # as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).gmo) case "$ac_given_srcdir" in .) srcdirpre= ;; *) srcdirpre='$(srcdir)/' ;; esac POFILES= UPDATEPOFILES= DUMMYPOFILES= GMOFILES= for lang in $ALL_LINGUAS; do POFILES="$POFILES $srcdirpre$lang.po" UPDATEPOFILES="$UPDATEPOFILES $lang.po-update" DUMMYPOFILES="$DUMMYPOFILES $lang.nop" GMOFILES="$GMOFILES $srcdirpre$lang.gmo" done # CATALOGS depends on both $ac_dir and the user's LINGUAS # environment variable. INST_LINGUAS= if test -n "$ALL_LINGUAS"; then for presentlang in $ALL_LINGUAS; do useit=no if test "%UNSET%" != "$LINGUAS"; then desiredlanguages="$LINGUAS" else desiredlanguages="$ALL_LINGUAS" fi for desiredlang in $desiredlanguages; do # Use the presentlang catalog if desiredlang is # a. equal to presentlang, or # b. a variant of presentlang (because in this case, # presentlang can be used as a fallback for messages # which are not translated in the desiredlang catalog). case "$desiredlang" in "$presentlang"*) useit=yes;; esac done if test $useit = yes; then INST_LINGUAS="$INST_LINGUAS $presentlang" fi done fi CATALOGS= if test -n "$INST_LINGUAS"; then for lang in $INST_LINGUAS; do CATALOGS="$CATALOGS $lang.gmo" done fi test -n "$as_me" && echo "$as_me: creating $ac_dir/Makefile" || echo "creating $ac_dir/Makefile" sed -e "/^POTFILES =/r $ac_dir/POTFILES" -e "/^# Makevars/r $ac_given_srcdir/$ac_dir/Makevars" -e "s|@POFILES@|$POFILES|g" -e "s|@UPDATEPOFILES@|$UPDATEPOFILES|g" -e "s|@DUMMYPOFILES@|$DUMMYPOFILES|g" -e "s|@GMOFILES@|$GMOFILES|g" -e "s|@CATALOGS@|$CATALOGS|g" -e "s|@POMAKEFILEDEPS@|$POMAKEFILEDEPS|g" "$ac_dir/Makefile.in" > "$ac_dir/Makefile" for f in "$ac_given_srcdir/$ac_dir"/Rules-*; do if test -f "$f"; then case "$f" in *.orig | *.bak | *~) ;; *) cat "$f" >> "$ac_dir/Makefile" ;; esac fi done fi ;; esac done]], [# Capture the value of obsolete ALL_LINGUAS because we need it to compute # POFILES, UPDATEPOFILES, DUMMYPOFILES, GMOFILES, CATALOGS. But hide it # from automake < 1.5. eval 'OBSOLETE_ALL_LINGUAS''="$ALL_LINGUAS"' # Capture the value of LINGUAS because we need it to compute CATALOGS. LINGUAS="${LINGUAS-%UNSET%}" ]) ]) dnl Postprocesses a Makefile in a directory containing PO files. AC_DEFUN([AM_POSTPROCESS_PO_MAKEFILE], [ # When this code is run, in config.status, two variables have already been # set: # - OBSOLETE_ALL_LINGUAS is the value of LINGUAS set in configure.in, # - LINGUAS is the value of the environment variable LINGUAS at configure # time. changequote(,)dnl # Adjust a relative srcdir. ac_dir=`echo "$ac_file"|sed 's%/[^/][^/]*$%%'` ac_dir_suffix="/`echo "$ac_dir"|sed 's%^\./%%'`" ac_dots=`echo "$ac_dir_suffix"|sed 's%/[^/]*%../%g'` # In autoconf-2.13 it is called $ac_given_srcdir. # In autoconf-2.50 it is called $srcdir. test -n "$ac_given_srcdir" || ac_given_srcdir="$srcdir" case "$ac_given_srcdir" in .) top_srcdir=`echo $ac_dots|sed 's%/$%%'` ;; /*) top_srcdir="$ac_given_srcdir" ;; *) top_srcdir="$ac_dots$ac_given_srcdir" ;; esac # Find a way to echo strings without interpreting backslash. if test "X`(echo '\t') 2>/dev/null`" = 'X\t'; then gt_echo='echo' else if test "X`(printf '%s\n' '\t') 2>/dev/null`" = 'X\t'; then gt_echo='printf %s\n' else echo_func () { cat < "$ac_file.tmp" if grep -l '@TCLCATALOGS@' "$ac_file" > /dev/null; then # Add dependencies that cannot be formulated as a simple suffix rule. for lang in $ALL_LINGUAS; do frobbedlang=`echo $lang | sed -e 's/\..*$//' -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'` cat >> "$ac_file.tmp" < /dev/null; then # Add dependencies that cannot be formulated as a simple suffix rule. for lang in $ALL_LINGUAS; do frobbedlang=`echo $lang | sed -e 's/_/-/g' -e 's/^sr-CS/sr-SP/' -e 's/@latin$/-Latn/' -e 's/@cyrillic$/-Cyrl/' -e 's/^sr-SP$/sr-SP-Latn/' -e 's/^uz-UZ$/uz-UZ-Latn/'` cat >> "$ac_file.tmp" <> "$ac_file.tmp" <, 1996. AC_PREREQ(2.50) # Search path for a program which passes the given test. dnl AM_PATH_PROG_WITH_TEST(VARIABLE, PROG-TO-CHECK-FOR, dnl TEST-PERFORMED-ON-FOUND_PROGRAM [, VALUE-IF-NOT-FOUND [, PATH]]) AC_DEFUN([AM_PATH_PROG_WITH_TEST], [ # Prepare PATH_SEPARATOR. # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then echo "#! /bin/sh" >conf$$.sh echo "exit 0" >>conf$$.sh chmod +x conf$$.sh if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then PATH_SEPARATOR=';' else PATH_SEPARATOR=: fi rm -f conf$$.sh fi # Find out how to test for executable files. Don't use a zero-byte file, # as systems may use methods other than mode bits to determine executability. cat >conf$$.file <<_ASEOF #! /bin/sh exit 0 _ASEOF chmod +x conf$$.file if test -x conf$$.file >/dev/null 2>&1; then ac_executable_p="test -x" else ac_executable_p="test -f" fi rm -f conf$$.file # Extract the first word of "$2", so it can be a program name with args. set dummy $2; ac_word=[$]2 AC_MSG_CHECKING([for $ac_word]) AC_CACHE_VAL(ac_cv_path_$1, [case "[$]$1" in [[\\/]]* | ?:[[\\/]]*) ac_cv_path_$1="[$]$1" # Let the user override the test with a path. ;; *) ac_save_IFS="$IFS"; IFS=$PATH_SEPARATOR for ac_dir in ifelse([$5], , $PATH, [$5]); do IFS="$ac_save_IFS" test -z "$ac_dir" && ac_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if $ac_executable_p "$ac_dir/$ac_word$ac_exec_ext"; then echo "$as_me: trying $ac_dir/$ac_word..." >&AS_MESSAGE_LOG_FD if [$3]; then ac_cv_path_$1="$ac_dir/$ac_word$ac_exec_ext" break 2 fi fi done done IFS="$ac_save_IFS" dnl If no 4th arg is given, leave the cache variable unset, dnl so AC_PATH_PROGS will keep looking. ifelse([$4], , , [ test -z "[$]ac_cv_path_$1" && ac_cv_path_$1="$4" ])dnl ;; esac])dnl $1="$ac_cv_path_$1" if test ifelse([$4], , [-n "[$]$1"], ["[$]$1" != "$4"]); then AC_MSG_RESULT([$]$1) else AC_MSG_RESULT(no) fi AC_SUBST($1)dnl ]) osdlyrics-0.5.5~rc1+dfsg1/players/000077500000000000000000000000001345713535600170335ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/players/Makefile.am000066400000000000000000000001331345713535600210640ustar00rootroot00000000000000SUBDIRS = \ mpris1 \ mpris2 \ http \ mpd \ $(NULL) osdlyrics-0.5.5~rc1+dfsg1/players/http/000077500000000000000000000000001345713535600200125ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/players/http/Makefile.am000066400000000000000000000007771345713535600220610ustar00rootroot00000000000000http_PYTHON = \ http-player.py \ server.py \ validator.py \ error.py \ $(NULL) httpdir = $(pkglibdir)/players/http servicedir = $(datadir)/dbus-1/services service_in_files = org.osdlyrics.PlayerProxy.Http.service.in service_DATA = $(service_in_files:.service.in=.service) EXTRA_DIST = \ $(service_in_files) \ $(NULL) $(service_DATA): $(service_in_files) @sed -e "s|\@pkglibdir\@|$(pkglibdir)|" -e "s|\@PYTHON\@|$(PYTHON)|" $< > $@ CLEANFILES = \ org.osdlyrics.PlayerProxy.Http.service \ $(NULL) osdlyrics-0.5.5~rc1+dfsg1/players/http/error.py000066400000000000000000000030171345713535600215160ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from future import standard_library standard_library.install_aliases() import http.client class InvalidRequestException(Exception): def __init__(self, message): self._message = message def __str__(self): return repr(self._message) class HttpError(Exception): """ Error response """ def __init__(self, code, message=''): self.code = code self.message = message class NotFoundError(HttpError): """ HTTP Error 404 """ def __init__(self, message=''): HttpError.__init__(self, http.client.NOT_FOUND, message) class BadRequestError(HttpError): """ HTTP Error 400 """ def __init__(self, message=''): HttpError.__init__(self, http.client.BAD_REQUEST, message) class PlayerNotFoundError(Exception): pass osdlyrics-0.5.5~rc1+dfsg1/players/http/http-player.py000066400000000000000000000123351345713535600226410ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from builtins import super import datetime import logging import time from gi.repository import GLib from osdlyrics.metadata import Metadata from osdlyrics.player_proxy import (STATUS, BasePlayer, BasePlayerProxy, PlayerInfo) import osdlyrics.timer import server CONNECTION_TIMEOUT = 1000 class HttpPlayerProxy(BasePlayerProxy): def __init__(self): super().__init__('Http') self._server = server.HttpServer(('', 7119), self) self._server_watch = GLib.io_add_watch(self._server.fileno(), GLib.PRIORITY_DEFAULT, GLib.IOCondition.IN, self._handle_req) self._players = {} self._connection_timer = GLib.timeout_add(CONNECTION_TIMEOUT, self._check_connection) self._player_counter = 1 def _handle_req(self, fd, event): logging.debug('new request %s, %s', fd, event) self._server.handle_request() return True def add_player(self, name, caps): if name in self._players: name = '%s%s' % (name, self._player_counter) self._player_counter = self._player_counter + 1 self._players[name] = HttpPlayer(self, name, caps) return name def remove_player(self, name): try: del self._players[name] except KeyError: pass def get_player(self, name): return self._players[name] def do_list_active_players(self): return [PlayerInfo(v.name) for v in self._players.values()] def do_list_supported_players(self): return [] def do_list_activatable_players(self): return [] def do_connect_player(self, name): if name in self._players: return self._players[name] return None def _check_connection(self): for player in self._players.values(): player.check_connection() return True class HttpPlayer(BasePlayer): def __init__(self, proxy, name, caps): super().__init__(proxy, name) self._status = STATUS.STOPPED self._caps = caps self._metadata = Metadata() self._last_ping = datetime.datetime.now() self._timer = osdlyrics.timer.Timer() self._cmds = [] def _ping(self): self._last_ping = datetime.datetime.now() def check_connection(self): now = datetime.datetime.now() duration = now - self._last_ping if duration.total_seconds() * 1000 > CONNECTION_TIMEOUT * 2: logging.warning('%s connection timeout', self.name) self.disconnect() def disconnect(self): self.proxy.remove_player(self.name) BasePlayer.disconnect(self) def do_update_track(self, metadata): self._ping() self._metadata = metadata self.track_changed() self._timer.stop() def do_update_status(self, status): self._status = status if status == STATUS.STOPPED: self._timer.stop() elif status == STATUS.PAUSED: self._timer.pause() else: self._timer.play() self.status_changed() def do_update_position(self, pos): if self._timer.set_time(pos): self.position_changed() def get_metadata(self): return self._metadata def get_status(self): return self._status def get_position(self): return self._timer.time def get_caps(self): return self._caps def query(self, timestamp): self._ping() cmds = [] i = 0 for cmd in self._cmds: if cmd[0] >= timestamp: break i = i + 1 if i > 0: self._cmds = self._cmds[i:] for cmd in self._cmds: cmds.append(cmd[1]) return cmds, int(time.time() * 10) def play(self): self._add_cmd('play') def pause(self): self._add_cmd('pause') def prev(self): self._add_cmd('prev') def next(self): self._add_cmd('next') def stop(self): self._add_cmd('stop') def set_position(self, pos): self._add_cmd('seek', {'pos': pos}) def _add_cmd(self, cmd, params={}): self._cmds.append((int(time.time() * 10), {'cmd': cmd, 'params': params})) if __name__ == '__main__': proxy = HttpPlayerProxy() proxy.run() osdlyrics-0.5.5~rc1+dfsg1/players/http/org.osdlyrics.PlayerProxy.Http.service.in000066400000000000000000000001521345713535600300130ustar00rootroot00000000000000[D-BUS Service] Name=org.osdlyrics.PlayerProxy.Http Exec=@PYTHON@ @pkglibdir@/players/http/http-player.py osdlyrics-0.5.5~rc1+dfsg1/players/http/server.py000066400000000000000000000133641345713535600217010ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from future import standard_library standard_library.install_aliases() import http.server import json import logging import urllib.parse from osdlyrics.metadata import Metadata from osdlyrics.player_proxy import CAPS, STATUS from error import BadRequestError, HttpError, NotFoundError from validator import (param_enum, param_int, param_set, param_str, validate_params) PARAM_STATUS = param_enum({'playing': STATUS.PLAYING, 'paused': STATUS.PAUSED, 'stopped': STATUS.STOPPED}) PARAM_CAPS = param_set({'play': CAPS.PLAY, 'pause': CAPS.PAUSE, 'next': CAPS.NEXT, 'prev': CAPS.PREV, 'seek': CAPS.SEEK}) def parse_query(query): """ Parse query strings in GET or POST to a dict The return value is a dictionary with query keys as query names and values as the query values. If a query name does not have a value, the value to the key is True. If more than one value assigned to the query name, any one may be assigned to the key. Arguments: - `query`: A string like 'query1=value&query2=value' """ result = urllib.parse.parse_qs(query) ret = {} for k, v in result.items(): if v: ret[k] = v[0] else: ret[k] = True return ret class RequestHandler(http.server.BaseHTTPRequestHandler): """ Handles HTTP request """ server_version = 'OsdLyricsHttp/1.0' def _send_content(self, content): self.send_response(200) self.end_headers() self.wfile.write(content) def _send_error(self, error): self.send_error(error.code, error.message) def _processquery(self, params): url = urllib.parse.urlparse(self.path) cmd = url.path[1:] if hasattr(self, 'do_' + cmd): try: content = getattr(self, 'do_' + cmd)(params) self._send_content(content) except HttpError as e: self._send_error(e) else: self._send_error(NotFoundError('Invalid request: %s' % cmd)) def do_GET(self): url = urllib.parse.urlparse(self.path) params = parse_query(url.query) self._processquery(params) @validate_params({'name': param_str(), 'caps': PARAM_CAPS, }) def do_connect(self, params): logging.debug('caps: %s', params['caps']) return json.dumps({'id': self.server.player_proxy.add_player(params['name'], params['caps']), }) @validate_params({'id': param_str(), 'timestamp': param_int(), }) def do_query(self, params): cmds, timestamp = self.get_player(params['id']).query(params['timestamp']) return json.dumps({'cmds': cmds, 'timestamp': timestamp}) @validate_params({'id': param_str(), 'status': PARAM_STATUS, 'title': param_str(), 'artist': param_str(optional=True), 'album': param_str(optional=True), 'arturl': param_str(optional=True), 'tracknum': param_int(optional=True), 'length': param_int() }) def do_track_changed(self, params): player = self.get_player(params['id']) status = params['status'] del params['status'] del params['id'] metadata = Metadata(**params) player.do_update_track(metadata) player.do_update_status(status) return '' @validate_params({'id': param_str(), 'status': PARAM_STATUS, }) def do_status_changed(self, params): player = self.get_player(params['id']) player.do_update_status(params['status']) @validate_params({'id': param_str(), 'pos': param_int()}) def do_position_changed(self, params): player = self.get_player(params['id']) player.do_update_position(params['pos']) @validate_params({'id': param_str()}) def do_disconnect(self, params): player = self.get_player(params['id']) player.disconnect() def get_player(self, name): try: return self.server.player_proxy.get_player(name) except Exception: raise BadRequestError('Invalid player id: %s' % name) class HttpServer(http.server.HTTPServer): """ Lyrics Http server """ def __init__(self, server_address, player_proxy): """ Arguments: - `server_address`: """ http.server.HTTPServer.__init__(self, server_address, RequestHandler) self._player_conter = 1 self._connected_players = {} self._player_proxy = player_proxy @property def player_proxy(self): return self._player_proxy osdlyrics-0.5.5~rc1+dfsg1/players/http/validator.py000066400000000000000000000067241345713535600223620ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from builtins import object from error import BadRequestError __all__ = ( 'baseparam', 'param_int', 'param_str', 'param_enum', 'param_set', 'validate_params', ) class baseparam(object): def __init__(self, optional=False): self.optional = optional def validate(self, value): raise NotImplementedError() class param_int(baseparam): def __init__(self, max=None, min=None, optional=False): baseparam.__init__(self, optional) self._max = max self._min = min def validate(self, value): try: v = int(value) if (self._max is None or self._max <= v) and \ (self._min is None or self._min >= v): return True, v else: return False, v except Exception: return False, value class param_str(baseparam): def __init__(self, nonempty=False, optional=False): baseparam.__init__(self, optional) self._nonempty = nonempty def validate(self, value): return True if not self._nonempty else isinstance(value, str), value class param_enum(baseparam): def __init__(self, valid_values, optional=False): baseparam.__init__(self, optional) self._valid_values = valid_values def validate(self, value): if value not in self._valid_values: return False, '' try: v = self._valid_values[value] return True, v except Exception: return True, value class param_set(baseparam): def __init__(self, valid_values, optional=False): baseparam.__init__(self, optional) self._valid_values = valid_values def validate(self, value): ret = set() for k in value.split(','): k = k.strip() if k not in self._valid_values: return False, ret try: v = self._valid_values[k] ret.add(v) except Exception: ret.add(k) return True, ret def validate_params(param_def): def dec(func): def dec_func(handler, params, *args, **kargs): valid_params = {} for k, v in params.items(): if k in param_def: valid, value = param_def[k].validate(v) if not valid: raise BadRequestError('query "%s=%s" is invalid' % (k, v)) v = value valid_params[k] = v for k, v in param_def.items(): if not v.optional and k not in params: raise BadRequestError('missing "%s" in query' % k) return func(handler, valid_params, *args, **kargs) return dec_func return dec osdlyrics-0.5.5~rc1+dfsg1/players/mpd/000077500000000000000000000000001345713535600176135ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/players/mpd/Makefile.am000066400000000000000000000007711345713535600216540ustar00rootroot00000000000000mpd_PYTHON = \ mpd_proxy.py \ $(NULL) mpddir = $(pkglibdir)/players/mpd servicedir = $(datadir)/dbus-1/services service_in_files = org.osdlyrics.PlayerProxy.Mpd.service.in service_DATA = $(service_in_files:.service.in=.service) EXTRA_DIST = \ $(service_in_files) \ $(NULL) $(service_DATA): $(service_in_files) @sed -e "s|\@pkglibdir\@|$(pkglibdir)|" -e "s|\@PYTHON\@|$(PYTHON)|" $< > $@ CLEANFILES = \ org.osdlyrics.PlayerProxy.Mpd.service \ $(NULL) osdlyrics-0.5.5~rc1+dfsg1/players/mpd/mpd_proxy.py000066400000000000000000000356741345713535600222250ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2012 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # """MPD support for OSD Lyrics. Requires MPD >= 0.16 and mpd-python >= 0.3 """ from __future__ import unicode_literals from builtins import object, super import logging import os import select import sys import dbus import dbus.service from gi.repository import GLib try: import mpd assert hasattr(mpd.MPDClient(), 'send_idle') except (ImportError, AssertionError): logging.error('python-mpd >= 0.3 is required.') sys.exit(1) from osdlyrics.consts import PLAYER_PROXY_INTERFACE from osdlyrics.metadata import Metadata from osdlyrics.player_proxy import (CAPS, REPEAT, STATUS, BasePlayer, BasePlayerProxy, PlayerInfo) from osdlyrics.timer import Timer from osdlyrics.utils import cmd_exists PLAYER_NAME = 'Mpd' DEFAULT_HOST = 'localhost' DEFAULT_PORT = 6600 class NoConnectionError(Exception): pass class CommandCallback(object): def __init__(self, command, callback): self.command = command self.callback = callback def call(self, *args): if callable(self.callback): self.callback(*args) class Cmds(object): CONFIG = 'config' CURRENTSONG = 'currentsong' IDLE = 'idle' NEXT = 'next' NOIDLE = 'noidle' PAUSE = 'pause' PLAY = 'play' PREVIOUS = 'previous' RANDOM = 'random' REPEAT = 'repeat' REPLAY_GAIN_MODE = 'replay_gain_mode' REPLAY_GAIN_STATUS = 'replay_gain_status' SEEK = 'seek' SEEKCUR = 'seekcur' SEEKID = 'seekid' SETVOL = 'setvol' SINGLE = 'single' STATUS = 'status' STOP = 'stop' class MpdProxy(BasePlayerProxy): def __init__(self): super().__init__('Mpd') self._player = None self._init_address() self._client = None self._player_info = PlayerInfo(name=PLAYER_NAME, appname='mpd', binname='mpd', cmd='mpd') self._player = None self._io_watch = None self._fetch_queue = [] self._on_idle = False def _init_address(self): """ Initialize the host and port of MPD daemon """ if 'MPD_HOST' in os.environ: self._host = os.environ['MPD_HOST'] else: self._host = DEFAULT_HOST if 'MPD_PORT' in os.environ and os.environ['MPD_PORT'].isdigit(): self._port = os.environ['MPD_PORT'] else: self._port = DEFAULT_PORT def _connect_mpd(self): if self._is_connected(): return True if not self._client: self._client = mpd.MPDClient() try: self._client.connect(self._host, self._port) except IOError as e: logging.info("Could not connect to '%s': %s", self._host, e.strerror) return False except mpd.MPDError as e: logging.info("Could not connect to '%s': %s", self._host, e) return False self._io_watch = GLib.io_add_watch(self._client, GLib.PRIORITY_DEFAULT, GLib.IOCondition.IN, self._on_data) return True def do_list_active_players(self): if self._connect_mpd(): return [self._player_info] else: return [] def do_list_supported_players(self): return [self._player_info] def do_list_activatable_players(self): if cmd_exists('mpd'): return [self._player_info] else: return [] def do_connect_player(self, playername): if playername != PLAYER_NAME: return None if self._player: return self._player if not self._connect_mpd(): return None self._player = MpdPlayer(self, playername) self._start_idle() return self._player def _on_data(self, client, condition): while self._fetch_queue: try: cmd_item = self._fetch_queue.pop(0) logging.debug('fetch cmd: %s', cmd_item.command) if not callable(cmd_item.callback): continue logging.debug('client pending: %s', self._client._pending) retval = getattr(self._client, 'fetch_' + cmd_item.command)() cmd_item.call(retval) except Exception as e: logging.exception(e) self._on_disconnect() return True # no pending data, socket might be closed data = os.read(self._client.fileno(), 1024) if not data: # connection closed logging.info('connection closed') self._on_disconnect() return else: raise RuntimeError('Unexpected data: %s', data) def _on_disconnect(self): if self._io_watch: GLib.remove_source(self._io_watch) self._io_watch = None self._client.disconnect() self._player.disconnect() self._player = None self._fetch_queue = [] self._on_idle = False def _is_connected(self): return True if self._io_watch else False def send_command(self, command, callback, *args): if not self._is_connected: raise NoConnectionError() on_idle = self._is_on_idle() logging.debug('send %s %s', command, args) logging.debug('on idle: %s', on_idle) if on_idle: self._stop_idle() getattr(self._client, 'send_' + command)(*args) if callable(callback): self._enqueue_callback(command, callback) if on_idle: self._start_idle() logging.debug('cmd queue: %s', [item.command for item in self._fetch_queue]) def send_command_sync(self, command, callback, *args): self.send_command(command, callback, *args) logging.debug('send sync to fetch: %s', self._fetch_queue) while len(self._fetch_queue) > 1 \ or (len(self._fetch_queue) == 1 and self._fetch_queue[0].command != Cmds.IDLE): select.select((self._client,), (), ()) self._on_data(self._client, GLib.IOCondition.IN) def _enqueue_callback(self, command, callback): self._fetch_queue.append(CommandCallback(command, callback)) def _is_on_idle(self): return self._fetch_queue and self._fetch_queue[-1].command == Cmds.IDLE def _start_idle(self): if not self._is_connected() or self._is_on_idle(): return logging.debug('start idle') self.send_command(Cmds.IDLE, self._fetch_idle) def _stop_idle(self): if not self._is_connected() or not self._is_on_idle(): return logging.debug('stop idle') self._client.send_noidle() self._enqueue_callback(Cmds.NOIDLE, None) def _fetch_idle(self, changes): if self._player: self._player.handle_changes(changes) self._start_idle() @dbus.service.method(in_signature='', out_signature='a{sv}', dbus_interface=PLAYER_PROXY_INTERFACE) def DebugInfo(self): ret = {} ret['host'] = self._host ret['port'] = dbus.UInt32(self._port) ret['connected'] = dbus.Boolean(self._is_connected()) ret['on_idle'] = dbus.Boolean(self._is_on_idle()) ret['fetch_queue'] = \ '[' + ','.join([item.command for item in self._fetch_queue]) + ']' if self._player: ret['player'] = self._player.debug_info() return ret class MpdPlayer(BasePlayer): CMD_HANDLERS = { Cmds.CURRENTSONG: '_handle_currentsong', Cmds.NEXT: None, Cmds.PAUSE: None, Cmds.PLAY: None, Cmds.PREVIOUS: None, Cmds.RANDOM: None, Cmds.REPEAT: None, Cmds.REPLAY_GAIN_MODE: None, Cmds.REPLAY_GAIN_STATUS: '_handle_replay_gain_status', Cmds.SEEK: None, Cmds.SEEKCUR: None, Cmds.SEEKID: None, Cmds.SETVOL: None, Cmds.SINGLE: None, Cmds.STATUS: '_handle_status', Cmds.STOP: None, } CHANGE_CMDS = { 'player': [Cmds.STATUS], 'options': [Cmds.STATUS], } STATUS_CHANGE_MAP = { 'songid': (int, 'track'), 'playlist': (int, 'track'), 'repeat': (int, 'repeat'), 'single': (int, 'repeat'), 'random': (int, 'shuffle'), 'state': ('_parse_status', 'status'), } def __init__(self, proxy, playername): super().__init__(proxy, playername) self._inited = False self._metadata = None self._songid = -1 self._playlist = -1 self._repeat = None self._single = None self._random = None self._state = None self._elapsed = Timer(100) self._send_cmd(Cmds.STATUS, sync=True) self._inited = True def _send_cmd(self, cmd, *args, **kwargs): """ Send a cmd. Can use sync=[True|False] to send in a blocking or non-blocking way. Default is non-blocking """ sync = False if 'sync' not in kwargs else kwargs['sync'] if cmd not in self.CMD_HANDLERS: raise RuntimeError('Unknown command: %s', cmd) handler = self.CMD_HANDLERS[cmd] if handler is not None: handler = getattr(self, handler) else: handler = self._handle_nothing if sync: self.proxy.send_command_sync(cmd, handler, *args) else: self.proxy.send_command(cmd, handler, *args) def _handle_status(self, status): logging.debug('status\n%s', status) changes = set() for prop, handler in self.STATUS_CHANGE_MAP.items(): if prop not in status: value = None else: func = handler[0] if not callable(func): func = getattr(self, func) value = func(status[prop]) if value != getattr(self, '_' + prop): logging.debug('prop %s changed to %s', prop, value) setattr(self, '_' + prop, value) changes.add(handler[1]) if 'track' in changes: if self._songid is None: self._metadata = Metadata() else: self._send_cmd(Cmds.CURRENTSONG, sync=True) if 'status' in changes: if self._state == STATUS.PAUSED: self._elapsed.pause() elif self._state == STATUS.PLAYING: self._elapsed.play() else: self._elapsed.stop() if self._state == STATUS.STOPPED: elapsed = 0 else: elapsed = float(status['elapsed']) * 1000 if self._elapsed.set_time(elapsed): changes.add('position') if not self._inited: # Initializing, do not emit the change signals changes = set() for change in changes: getattr(self, change + '_changed')() def _handle_currentsong(self, metadata): logging.debug('currentsong: %s', metadata) args = {} for key in ('title', 'artist', 'album'): if key in metadata: args[key] = metadata[key] if 'time' in metadata: args['length'] = int(metadata['time']) * 1000 if 'track' in metadata: args['tracknum'] = int(metadata['track'].split('/')[0]) self._metadata = Metadata(**args) @staticmethod def _parse_status(value): status_map = { 'play': STATUS.PLAYING, 'pause': STATUS.PAUSED, 'stop': STATUS.STOPPED, } if value not in status_map: raise RuntimeError('Unknown status ' + value) return status_map[value] def _handle_replay_gain_status(self, status): pass def _handle_nothing(self, *args): pass def handle_changes(self, changes): cmds = set() for change in changes: if change in self.CHANGE_CMDS: for cmd in self.CHANGE_CMDS[change]: cmds.add(cmd) logging.debug('changes: %s', changes) logging.debug('cmds: %s', cmds) for cmd in cmds: self._send_cmd(cmd) def get_status(self): return self._state def get_metadata(self): return self._metadata def get_position(self): return self._elapsed.time def get_caps(self): return set([CAPS.PLAY, CAPS.PAUSE, CAPS.NEXT, CAPS.PREV, CAPS.SEEK]) def get_repeat(self): if not self._repeat: return REPEAT.NONE if not self._single: return REPEAT.ALL return REPEAT.TRACK def set_repeat(self, mode): repeat_mode_map = { REPEAT.NONE: (0, 0), REPEAT.TRACK: (1, 1), REPEAT.ALL: (1, 0), } if mode not in repeat_mode_map: raise ValueError('Unknown repeat mode: %s', mode) self._repeat = repeat_mode_map[mode][0] self._single = repeat_mode_map[mode][1] self._send_cmd(Cmds.REPEAT, self._repeat) self._send_cmd(Cmds.SINGLE, self._single) def get_shuffle(self): return bool(self._random) def set_shuffle(self, shuffle): self._random = 1 if shuffle else 0 self._send_cmd(Cmds.RANDOM, self._random) def play(self): if self._state == STATUS.PAUSED: self._send_cmd(Cmds.PAUSE, 0) elif self._state == STATUS.STOPPED: self._send_cmd(Cmds.PLAY) def pause(self): self._send_cmd(Cmds.PAUSE, 1) def stop(self): self._send_cmd(Cmds.STOP) def prev(self): self._send_cmd(Cmds.PREVIOUS) def next(self): self._send_cmd(Cmds.NEXT) def set_position(self, pos): self._send_cmd(Cmds.SEEK, self._songid, int(pos // 1000)) def debug_info(self): ret = dbus.Dictionary(signature='sv') ret.update({ 'state': self._state, 'metadata': self._metadata.to_mpris1(), 'repeat': self._repeat, 'single': self._single, 'position': self.get_position() }) return ret if __name__ == '__main__': proxy = MpdProxy() proxy.run() osdlyrics-0.5.5~rc1+dfsg1/players/mpd/org.osdlyrics.PlayerProxy.Mpd.service.in000066400000000000000000000001461345713535600274200ustar00rootroot00000000000000[D-BUS Service] Name=org.osdlyrics.PlayerProxy.Mpd Exec=@PYTHON@ @pkglibdir@/players/mpd/mpd_proxy.py osdlyrics-0.5.5~rc1+dfsg1/players/mpris1/000077500000000000000000000000001345713535600202465ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/players/mpris1/Makefile.am000066400000000000000000000007151345713535600223050ustar00rootroot00000000000000mpris1_PYTHON = mpris1.py mpris1dir = $(pkglibdir)/players/mpris1 servicedir = $(datadir)/dbus-1/services service_in_files = org.osdlyrics.PlayerProxy.Mpris1.service.in service_DATA = $(service_in_files:.service.in=.service) EXTRA_DIST = \ $(service_in_files) \ $(NULL) $(service_DATA): $(service_in_files) @sed -e "s|\@pkglibdir\@|$(pkglibdir)|" -e "s|\@PYTHON\@|$(PYTHON)|" $< > $@ CLEANFILES = \ org.osdlyrics.PlayerProxy.Mpris1.service \ $(NULL) osdlyrics-0.5.5~rc1+dfsg1/players/mpris1/mpris1.py000066400000000000000000000162161345713535600220410ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2012 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from builtins import str, super from contextlib import contextmanager import logging import dbus from osdlyrics.metadata import Metadata from osdlyrics.player_proxy import (CAPS, REPEAT, STATUS, BasePlayer, BasePlayerProxy, PlayerInfo) MPRIS1_INTERFACE = 'org.freedesktop.MediaPlayer' MPRIS1_PREFIX = 'org.mpris.' # These constants map flags/enums from MPRIS1-specific values to OSDLyrics values. CAPS_MAP = { 1 << 0: CAPS.NEXT, 1 << 1: CAPS.PREV, 1 << 2: CAPS.PAUSE, 1 << 3: CAPS.PLAY, 1 << 4: CAPS.SEEK, } STATUS_MAP = { 0: STATUS.PLAYING, 1: STATUS.PAUSED, 2: STATUS.STOPPED, } class ProxyObject(BasePlayerProxy): """ The DBus object for MPRIS1 player proxy """ def __init__(self): """ """ super().__init__('Mpris1') def _get_player_from_bus_names(self, names): return [ PlayerInfo.from_name(name[len(MPRIS1_PREFIX):]) for name in names if name.startswith(MPRIS1_PREFIX) and not name.startswith(MPRIS1_PREFIX + 'MediaPlayer2.') ] def do_list_active_players(self): return self._get_player_from_bus_names(map(str, self.connection.list_names())) def do_list_supported_players(self): return self.do_list_activatable_players() def do_list_activatable_players(self): players = self._get_player_from_bus_names(map(str, self.connection.list_activatable_names())) return players def do_connect_player(self, player_name): player = Mpris1Player(self, player_name) return player class Mpris1Player(BasePlayer): def __init__(self, proxy, player_name): super().__init__(proxy, player_name) self._signals = [] self._name_watch = None self._status_tuple = None, None, None, None self._use_cached_status = False try: self._player = dbus.Interface(self.connection.get_object(MPRIS1_PREFIX + player_name, '/Player'), MPRIS1_INTERFACE) mpris1_service_name = MPRIS1_PREFIX + player_name self._signals.append(self._player.connect_to_signal('TrackChange', self._track_change_cb)) self._signals.append(self._player.connect_to_signal('StatusChange', self._status_change_cb)) self._signals.append(self._player.connect_to_signal('CapsChange', self._caps_change_cb)) self._name_watch = self.connection.watch_name_owner(mpris1_service_name, self._name_lost) except Exception as e: logging.error('Fail to connect to mpris1 player %s: %s', player_name, e) self.disconnect() def _name_lost(self, name): if name: return self.disconnect() def disconnect(self): for handler in self._signals: handler.remove() self._signals = [] if self._name_watch: self._name_watch.cancel() self._name_watch = None self._player = None super().disconnect() def next(self): self._player.Next() def prev(self): self._player.Prev() def pause(self): self._player.Pause() def stop(self): self._player.Stop() def play(self): self._player.Play() def set_repeat(self, repeat): if repeat in (REPEAT.TRACK, REPEAT.ALL): self._player.Repeat(True) else: self._player.Repeat(False) def get_status(self): status_tuple = (self._status_tuple if self._use_cached_status else self._player.GetStatus()) status, shuffle, repeat, loop = status_tuple return STATUS_MAP.get(status, STATUS.STOPPED) def get_repeat(self): status_tuple = (self._status_tuple if self._use_cached_status else self._player.GetStatus()) status, shuffle, repeat, loop = status_tuple if repeat: return REPEAT.TRACK if loop: return REPEAT.TRACK return REPEAT.NONE def get_shuffle(self): status_tuple = (self._status_tuple if self._use_cached_status else self._player.GetStatus()) status, shuffle, repeat, loop = status_tuple return True if shuffle == 1 else False def get_metadata(self): mt = self._player.GetMetadata() logging.debug(repr(mt)) return Metadata.from_dict(mt) def get_caps(self): caps = set() mpris1_caps = self._player.GetCaps() for bit, cap in CAPS_MAP.items(): if mpris1_caps & bit: caps.add(cap) return caps def set_volume(self, volume): volume = int(volume * 100) if volume < 0: volume = 0 if volume > 100: volume = 100 self._player.VolumeSet(volume) def get_volume(self): volume = float(self._player.VolumeGet()) / 100 if volume > 1.0: volume = 1.0 if volume < 0.0: volume = 0.0 return volume def set_position(self, time_in_mili): self._player.PositionSet(time_in_mili) def get_position(self): return self._player.PositionGet() def _track_change_cb(self, metadata): metadata = Metadata.from_dict(metadata) self.track_changed(metadata) def _status_change_cb(self, status_tuple): status, shuffle, repeat, loop = status_tuple old_status, old_shuffle, old_repeat, old_loop = self._status_tuple self._status_tuple = status_tuple with self._reuse_cached_status_tuple(): if status != old_status: self.status_changed() if repeat != old_repeat or loop != old_loop: self.repeat_changed() if shuffle != old_shuffle: self.shuffle_changed() def _caps_change_cb(self, caps): self.caps_changed() @contextmanager def _reuse_cached_status_tuple(self): try: self._use_cached_status = True yield finally: self._use_cached_status = False def run(): mpris1 = ProxyObject() mpris1.run() if __name__ == '__main__': run() osdlyrics-0.5.5~rc1+dfsg1/players/mpris1/org.osdlyrics.PlayerProxy.Mpris1.service.in000066400000000000000000000001501345713535600305010ustar00rootroot00000000000000[D-BUS Service] Name=org.osdlyrics.PlayerProxy.Mpris1 Exec=@PYTHON@ @pkglibdir@/players/mpris1/mpris1.pyosdlyrics-0.5.5~rc1+dfsg1/players/mpris2/000077500000000000000000000000001345713535600202475ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/players/mpris2/Makefile.am000066400000000000000000000007151345713535600223060ustar00rootroot00000000000000mpris2_PYTHON = mpris2.py mpris2dir = $(pkglibdir)/players/mpris2 servicedir = $(datadir)/dbus-1/services service_in_files = org.osdlyrics.PlayerProxy.Mpris2.service.in service_DATA = $(service_in_files:.service.in=.service) EXTRA_DIST = \ $(service_in_files) \ $(NULL) $(service_DATA): $(service_in_files) @sed -e "s|\@pkglibdir\@|$(pkglibdir)|" -e "s|\@PYTHON\@|$(PYTHON)|" $< > $@ CLEANFILES = \ org.osdlyrics.PlayerProxy.Mpris2.service \ $(NULL) osdlyrics-0.5.5~rc1+dfsg1/players/mpris2/mpris2.py000066400000000000000000000176761345713535600220560ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from builtins import str, super import logging import dbus import dbus.service import dbus.types from osdlyrics.consts import (DAEMON_MPRIS2_NAME, MPRIS2_OBJECT_PATH, MPRIS2_PLAYER_INTERFACE, MPRIS2_PREFIX) from osdlyrics.metadata import Metadata from osdlyrics.player_proxy import (CAPS, REPEAT, STATUS, BasePlayer, BasePlayerProxy, PlayerInfo) # These constants map flags/enums from MPRIS2-specific values to OSDLyrics values. CAPS_MAP = { 'CanGoNext': CAPS.NEXT, 'CanGoPrevious': CAPS.PREV, 'CanPlay': CAPS.PLAY, 'CanPause': CAPS.PAUSE, 'CanSeek': CAPS.SEEK, } class ProxyObject(BasePlayerProxy): """ The DBus object for MPRIS2 player proxy """ def __init__(self): """ """ super().__init__('Mpris2') def _get_player_from_bus_names(self, names): """ Returns list of `PlayerInfo` objects according to names. The bus names in names with prefix of MPRIS2_PREFIX will be treated as MPRIS2 players. The suffix of these names will be treated as player name Arguments: - `names`: list of bus names """ return [ PlayerInfo.from_name(name[len(MPRIS2_PREFIX):]) for name in names if name.startswith(MPRIS2_PREFIX) and name != DAEMON_MPRIS2_NAME ] def do_list_active_players(self): return self._get_player_from_bus_names(map(str, self.connection.list_names())) def do_list_supported_players(self): return self.do_list_activatable_players() def do_list_activatable_players(self): players = self._get_player_from_bus_names(map(str, self.connection.list_activatable_names())) return players def do_connect_player(self, player_name): player = Mpris2Player(self, player_name) return player class Mpris2Player(BasePlayer): def __init__(self, proxy, player_name): super().__init__(proxy, player_name) self._properties_changed_signal = None self._seeked_signal = None self._name_watch = None try: mpris2_object_path = MPRIS2_PREFIX + player_name self._player = dbus.Interface( self.connection.get_object(mpris2_object_path, MPRIS2_OBJECT_PATH), MPRIS2_PLAYER_INTERFACE) self._player_prop = dbus.Interface( self.connection.get_object(mpris2_object_path, MPRIS2_OBJECT_PATH), dbus.PROPERTIES_IFACE) self._properties_changed_signal = ( self._player_prop.connect_to_signal( 'PropertiesChanged', self._player_properties_changed)) self._seeked_signal = self._player.connect_to_signal( 'Seeked', self._player_seeked) self._name_watch = self.connection.watch_name_owner( mpris2_object_path, self._name_lost) except Exception: self.disconnect() def _name_lost(self, name): if name: return self.disconnect() def disconnect(self): if self._properties_changed_signal: self._properties_changed_signal.remove() self._properties_changed_signal = None if self._name_watch: self._name_watch.cancel() self._name_watch = None self._player = None self._player_prop = None BasePlayer.disconnect(self) def _player_properties_changed(self, iface, changed, invalidated): caps_props = ['CanGoNext', 'CanGoPrevious', 'CanPlay', 'CanPause', 'CanSeek'] prop_map = {'PlaybackStatus': 'status_changed', 'LoopStatus': 'repeat_changed', 'Shuffle': 'shuffle_changed', 'Metadata': 'track_changed', } # status_props = ['PlaybackStatus', 'LoopStatus', 'Shuffle'] logging.debug('Status changed: %s', changed) for caps in caps_props: if caps in changed: self.caps_changed() break for prop_name, method in prop_map.items(): if prop_name in changed: getattr(self, method)() def _player_seeked(self, position): self.position_changed() @property def object_path(self): return self._object_path @property def connected(self): return self._connected def next(self): self._player.Next() def prev(self): self._player.Previous() def pause(self): self._player.Pause() def stop(self): self._player.Stop() def play(self): self._player.Play() def set_repeat(self, repeat): try: if repeat == REPEAT.TRACK: self._player_prop.Set(MPRIS2_PLAYER_INTERFACE, 'LoopStatus', 'Track') elif repeat == REPEAT.ALL: self._player_prop.Set(MPRIS2_PLAYER_INTERFACE, 'LoopStatus', 'Playlist') else: self._player_prop.Set(MPRIS2_PLAYER_INTERFACE, 'LoopStatus', 'None') except Exception: pass def get_status(self): playback_dict = {'Playing': STATUS.PLAYING, 'Paused': STATUS.PAUSED, 'Stopped': STATUS.STOPPED} try: return playback_dict[self._player_prop.Get(MPRIS2_PLAYER_INTERFACE, 'PlaybackStatus')] except Exception as e: logging.error('Failed to get status: %s', e) return STATUS.PLAYING def get_repeat(self): repeat_dict = {'None': REPEAT.NONE, 'Track': REPEAT.TRACK, 'Playlist': REPEAT.ALL} try: return repeat_dict[self._player_prop.Get(MPRIS2_PLAYER_INTERFACE, 'LoopStatus')] except Exception: return REPEAT.NONE def get_shuffle(self): try: return bool(self._player_prop.Get(MPRIS2_PLAYER_INTERFACE, 'Shuffle')) except Exception: return False def get_metadata(self): metadata = self._player_prop.Get(MPRIS2_PLAYER_INTERFACE, 'Metadata') return Metadata.from_mpris2(metadata) def get_caps(self): caps = set() for k, cap in CAPS_MAP.items(): if self._player_prop.Get(MPRIS2_PLAYER_INTERFACE, k): caps.add(cap) return caps def set_volume(self, volume): self._player_prop.Set(MPRIS2_PLAYER_INTERFACE, 'Volume', volume) def get_volume(self): return self._player_prop.Get(MPRIS2_PLAYER_INTERFACE, 'Volume') def set_position(self, time_in_mili): track_id = self._player_prop.Get( MPRIS2_PLAYER_INTERFACE, 'Metadata')['mpris:trackid'] self._player.SetPosition(track_id, time_in_mili * 1000) def get_position(self): return self._player_prop.Get(MPRIS2_PLAYER_INTERFACE, 'Position') // 1000 def run(): mpris2 = ProxyObject() mpris2.run() if __name__ == '__main__': run() osdlyrics-0.5.5~rc1+dfsg1/players/mpris2/org.osdlyrics.PlayerProxy.Mpris2.service.in000066400000000000000000000001501345713535600305030ustar00rootroot00000000000000[D-BUS Service] Name=org.osdlyrics.PlayerProxy.Mpris2 Exec=@PYTHON@ @pkglibdir@/players/mpris2/mpris2.pyosdlyrics-0.5.5~rc1+dfsg1/po/000077500000000000000000000000001345713535600157725ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/po/.gitignore000066400000000000000000000000261345713535600177600ustar00rootroot00000000000000.intltool-merge-cache osdlyrics-0.5.5~rc1+dfsg1/po/ChangeLog000066400000000000000000000007161345713535600175500ustar00rootroot000000000000002009-07-08 gettextize * Makefile.in.in: New file, from gettext-0.17. * Rules-quot: New file, from gettext-0.17. * boldquot.sed: New file, from gettext-0.17. * en@boldquot.header: New file, from gettext-0.17. * en@quot.header: New file, from gettext-0.17. * insert-header.sin: New file, from gettext-0.17. * quot.sed: New file, from gettext-0.17. * remove-potcdate.sin: New file, from gettext-0.17. * POTFILES.in: New file. osdlyrics-0.5.5~rc1+dfsg1/po/LINGUAS000066400000000000000000000000661345713535600170210ustar00rootroot00000000000000cs de es fr it ms pt_BR ro ru tr uk zh_CN zh_HK zh_TW osdlyrics-0.5.5~rc1+dfsg1/po/Makefile.in.in000077700000000000000000000000001345713535600267472/usr/share/intltool/Makefile.in.inustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/po/Makevars000066400000000000000000000034161345713535600174720ustar00rootroot00000000000000# Makefile variables for PO directory in any package using GNU gettext. # Usually the message domain is the same as the package name. DOMAIN = $(PACKAGE) # These two variables depend on the location of this directory. subdir = po top_builddir = .. # These options get passed to xgettext. XGETTEXT_OPTIONS = --keyword=_ --keyword=N_ # This is the copyright holder that gets inserted into the header of the # $(DOMAIN).pot file. Set this to the copyright holder of the surrounding # package. (Note that the msgstr strings, extracted from the package's # sources, belong to the copyright holder of the package.) Translators are # expected to transfer the copyright for their translations to this person # or entity, or to disclaim their copyright. The empty string stands for # the public domain; in this case the translators are expected to disclaim # their copyright. COPYRIGHT_HOLDER = Free Software Foundation, Inc. # This is the email address or URL to which the translators shall report # bugs in the untranslated strings: # - Strings which are not entire sentences, see the maintainer guidelines # in the GNU gettext documentation, section 'Preparing Strings'. # - Strings which use unclear terms or require additional context to be # understood. # - Strings which make invalid assumptions about notation of date, time or # money. # - Pluralisation problems. # - Incorrect English spelling. # - Incorrect formatting. # It can be your email address, or a mailing list address where translators # can write to without being subscribed, or the URL of a web page through # which the translators can contact you. MSGID_BUGS_ADDRESS = # This is the list of locale categories, beyond LC_MESSAGES, for which the # message catalogs shall be used. It is usually empty. EXTRA_LOCALE_CATEGORIES = osdlyrics-0.5.5~rc1+dfsg1/po/POTFILES.in000066400000000000000000000005501345713535600175470ustar00rootroot00000000000000# List of source files which contain translatable strings. src/ol_menu.c src/ol_main.c src/ol_trayicon.c src/ol_option.c src/ol_lyric_candidate_list.c src/ol_search_dialog.c src/ol_notify.c src/ol_player_chooser.c src/ol_app_info.c src/ol_scroll_window.c data/dialogs.glade data/osdlyrics.desktop.in lyricsources/netease/netease.py lyricsources/xiami/xiami.py osdlyrics-0.5.5~rc1+dfsg1/po/Rules-quot000066400000000000000000000033761345713535600200060ustar00rootroot00000000000000# Special Makefile rules for English message catalogs with quotation marks. DISTFILES.common.extra1 = quot.sed boldquot.sed en@quot.header en@boldquot.header insert-header.sin Rules-quot .SUFFIXES: .insert-header .po-update-en en@quot.po-create: $(MAKE) en@quot.po-update en@boldquot.po-create: $(MAKE) en@boldquot.po-update en@quot.po-update: en@quot.po-update-en en@boldquot.po-update: en@boldquot.po-update-en .insert-header.po-update-en: @lang=`echo $@ | sed -e 's/\.po-update-en$$//'`; \ if test "$(PACKAGE)" = "gettext"; then PATH=`pwd`/../src:$$PATH; GETTEXTLIBDIR=`cd $(top_srcdir)/src && pwd`; export GETTEXTLIBDIR; fi; \ tmpdir=`pwd`; \ echo "$$lang:"; \ ll=`echo $$lang | sed -e 's/@.*//'`; \ LC_ALL=C; export LC_ALL; \ cd $(srcdir); \ if $(MSGINIT) -i $(DOMAIN).pot --no-translator -l $$ll -o - 2>/dev/null | sed -f $$tmpdir/$$lang.insert-header | $(MSGCONV) -t UTF-8 | $(MSGFILTER) sed -f `echo $$lang | sed -e 's/.*@//'`.sed 2>/dev/null > $$tmpdir/$$lang.new.po; then \ if cmp $$lang.po $$tmpdir/$$lang.new.po >/dev/null 2>&1; then \ rm -f $$tmpdir/$$lang.new.po; \ else \ if mv -f $$tmpdir/$$lang.new.po $$lang.po; then \ :; \ else \ echo "creation of $$lang.po failed: cannot move $$tmpdir/$$lang.new.po to $$lang.po" 1>&2; \ exit 1; \ fi; \ fi; \ else \ echo "creation of $$lang.po failed!" 1>&2; \ rm -f $$tmpdir/$$lang.new.po; \ fi en@quot.insert-header: insert-header.sin sed -e '/^#/d' -e 's/HEADER/en@quot.header/g' $(srcdir)/insert-header.sin > en@quot.insert-header en@boldquot.insert-header: insert-header.sin sed -e '/^#/d' -e 's/HEADER/en@boldquot.header/g' $(srcdir)/insert-header.sin > en@boldquot.insert-header mostlyclean: mostlyclean-quot mostlyclean-quot: rm -f *.insert-header osdlyrics-0.5.5~rc1+dfsg1/po/boldquot.sed000066400000000000000000000003311345713535600203150ustar00rootroot00000000000000s/"\([^"]*\)"/“\1â€/g s/`\([^`']*\)'/‘\1’/g s/ '\([^`']*\)' / ‘\1’ /g s/ '\([^`']*\)'$/ ‘\1’/g s/^'\([^`']*\)' /‘\1’ /g s/“â€/""/g s/“/“/g s/â€/â€/g s/‘/‘/g s/’/’/g osdlyrics-0.5.5~rc1+dfsg1/po/cs.po000066400000000000000000000347541345713535600167540ustar00rootroot00000000000000# # Czech translation for OSD lyrics # This file is distributed under the same license as the osdlyrics package. # OndÅ™ej Svoboda , 2015. # msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-12-12 03:10+0000\n" "Last-Translator: OndÅ™ej Svoboda \n" "Language: cs\n" "Content-Type: text/plain; charset=UTF-8\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n>=2 && n<=4 ? 1 : 2;\n" #: ../src/ol_menu.c:126 msgid "LRC files" msgstr "Soubory *.lrc" #: ../src/ol_menu.c:132 msgid "Choose LRC file to assign" msgstr "Zvolte soubor s textem (*.lrc)" #: ../src/ol_main.c:59 msgid "" "The level of debug messages to log, can be 'none', 'error', 'debug', or " "'info'" msgstr "" #: ../src/ol_main.c:61 msgid "Show version information" msgstr "" #: ../src/ol_main.c:253 #, c-format msgid "Searching lyrics from %s" msgstr "Hledá se text na %s…" #: ../src/ol_main.c:238 msgid "Lyrics not found" msgstr "Text se nenaÅ¡el" #: ../src/ol_main.c:192 ../src/ol_search_dialog.c:239 msgid "Fail to download lyric" msgstr "NepodaÅ™ilo se stáhnout text" #: ../src/ol_main.c:209 msgid "Downloading lyric" msgstr "Stahuje se text" #: ../src/ol_main.c:471 #, c-format msgid "No supported player is running, exit.\n" msgstr "" #: ../src/ol_main.c:597 msgid "" "debug level should be one of ``none'', ``error'', ``debug'', or ``info''" msgstr "" #: ../src/ol_main.c:645 msgid "Another OSD Lyrics is running, exit." msgstr "" #: ../src/ol_trayicon.c:36 ../src/ol_notify.c:27 msgid "Unknown title" msgstr "Neznámý název" #: ../src/ol_trayicon.c:37 ../src/ol_notify.c:28 msgid "Unknown artist" msgstr "Neznámý autor" #: ../src/ol_trayicon.c:73 ../data/osdlyrics.desktop.in.h:1 msgid "OSD Lyrics" msgstr "" #: ../src/ol_option.c:1598 msgid "Choose on startup" msgstr "vybírat pÅ™i spuÅ¡tÄ›ní" # This is used to mean a "custom command" but also "custom (colour) theme". #. gtk_list_store_append (liststore, &iter); #. gtk_list_store_set (liststore, &iter, #. 0, "Customize", #. 1, "", #. -1); #: ../src/ol_option.c:1620 ../data/dialogs.glade.h:7 msgid "Customize" msgstr "vlastní" # msgstr "vlastní příkaz" #: ../src/ol_option.c:1682 msgid "Select a folder" msgstr "Zvolte adresář s texty" #: ../src/ol_lyric_candidate_list.c:47 msgid "Title" msgstr "Název" #: ../src/ol_lyric_candidate_list.c:52 msgid "Artist" msgstr "Autor" #: ../src/ol_lyric_candidate_list.c:57 msgid "Album" msgstr "Album" #: ../src/ol_search_dialog.c:29 #, c-format msgid "Searching lyrics from %s..." msgstr "Hledají se texty na %s…" #: ../src/ol_search_dialog.c:30 msgid "Ooops, no lyric found :(" msgstr "NenaÅ¡el se žádný text." # TODO: is ngettext() used? #: ../src/ol_search_dialog.c:31 #, c-format msgid "%d lyrics found :)" msgstr "NaÅ¡lo se %d textů." #: ../src/ol_search_dialog.c:32 msgid "Fail to search. Please check network connection" msgstr "Stahování se nedaří, zkontrolujte síťové pÅ™ipojení" #: ../src/ol_search_dialog.c:33 msgid "Cancelled" msgstr "ZruÅ¡eno" #: ../src/ol_search_dialog.c:96 msgid "Downloading..." msgstr "Stahuje se…" #: ../src/ol_search_dialog.c:228 msgid "Download complete, but fail to assign to the track" msgstr "Staženo, ale nepodaÅ™ilo se text pÅ™iÅ™adit ke skladbÄ›" #: ../src/ol_search_dialog.c:232 msgid "Download complete" msgstr "Staženo" #: ../src/ol_player_chooser.c:247 msgid "Supported players" msgstr "Podporované pÅ™ehrávaÄe" #: ../src/ol_player_chooser.c:248 msgid "All players" msgstr "VÅ¡echny pÅ™ehrávaÄe" #: ../src/ol_player_chooser.c:252 ../src/ol_player_chooser.c:324 msgid "Choose a player to launch" msgstr "Vyberte, který pÅ™ehrávaÄ spustit" #: ../src/ol_player_chooser.c:268 msgid "Use command:" msgstr "Příkaz pro pÅ™ehrávaÄ:" #: ../src/ol_player_chooser.c:273 msgid "Launch" msgstr "Spustit" #: ../src/ol_player_chooser.c:286 msgid "Remember my choice" msgstr "Zapamatovat mou volbu" #: ../src/ol_player_chooser.c:438 #, c-format msgid "Failed to launch %s" msgstr "NepodaÅ™ilo se spustit %s" #: ../src/ol_player_chooser.c:448 #, c-format msgid "Launching %s" msgstr "SpouÅ¡tím %s" #: ../src/ol_player_chooser.c:450 #, c-format msgid "" "OSD Lyrics is trying to launch and connect to %s. Please wait for a second." msgstr "Pokouším se spustit %s a pÅ™ipojit se k nÄ›mu. PoÄkejte prosím chvilku." #: ../src/ol_player_chooser.c:560 msgid "There is no supported player running" msgstr "MomentálnÄ› neběží žádný z podporovaných pÅ™ehrávaÄů." #: ../src/ol_player_chooser.c:561 msgid "Please choose a player below to launch" msgstr "Vyberte prosím, který spustit." #: ../src/ol_player_chooser.c:565 #, c-format msgid "Failed to connect to %s" msgstr "" #: ../src/ol_player_chooser.c:567 #, c-format msgid "" "%s is not supported by OSD Lyrics, or not running. Please launch another " "player" msgstr "" #: ../src/ol_app_info.c:179 ../src/ol_app_info.c:189 msgid "Unnamed" msgstr "" #: ../src/ol_scroll_window.c:51 msgid "" "Drag to move the window\n" "Hold CTRL to seek" msgstr "" #: ../src/ol_scroll_window.c:52 msgid "Drag to move the window" msgstr "Okno můžete posouvat, když ho chytíte" #: ../data/dialogs.glade.h:60 #, no-c-format msgid "" " %t - Title\n" " %p - Artist\n" " %a - Album\n" " %n - Track number\n" " %f - File name (without extension)" msgstr "" " %t - název\n" " %p - autor\n" " %a - album\n" " %n - Äíslo stopy\n" " %f - název souboru (bez přípony)" #: ../data/dialogs.glade.h:110 #, no-c-format msgid "%a - Album" msgstr "%a - album" #: ../data/dialogs.glade.h:116 #, no-c-format msgid "%f - Filename" msgstr "%f - název souboru" #: ../data/dialogs.glade.h:113 #, no-c-format msgid "%n - Track number" msgstr "%n - Äíslo stopy" #: ../data/dialogs.glade.h:107 #, no-c-format msgid "%p - Artist" msgstr "%p - autor" #: ../data/dialogs.glade.h:104 #, no-c-format msgid "%t - Title" msgstr "%t - název" #: ../data/dialogs.glade.h:54 msgid "Background" msgstr "Pozadí" #: ../data/dialogs.glade.h:70 msgid "Download" msgstr "Stahování" #: ../data/dialogs.glade.h:36 msgid "Lyric _Alignment" msgstr "_Zarovnání řádků (vodorovné)" #: ../data/dialogs.glade.h:44 msgid "Lyric _Color" msgstr "Barva _textu" #: ../data/dialogs.glade.h:49 msgid "Lyrics" msgstr "Text" #: ../data/dialogs.glade.h:80 msgid "Proxy" msgstr "" #: ../data/dialogs.glade.h:65 msgid "_Filename" msgstr "_Názvy souborů s texty" #: ../data/dialogs.glade.h:58 msgid "_Path" msgstr "_Cesty" #: ../data/dialogs.glade.h:57 #, no-c-format msgid "A single \"%\" means the directory where the playing music is." msgstr "Samotné % znamená adresář, v nÄ›mž je pÅ™ehrávaná skladba." #: ../data/dialogs.glade.h:46 msgid "Ac_tive color:" msgstr "Barva _aktuálního řádku:" #: ../data/dialogs.glade.h:39 msgid "Acti_ve lyrics:" msgstr "PÅ™e_hraný:" #: ../data/dialogs.glade.h:97 msgid "Adjust lyric delay by +0.2 s" msgstr "Zpozdit text o 0,2 s" #: ../data/dialogs.glade.h:95 msgid "Adjust lyrics delay by -0.2 s" msgstr "Snížit posun textu oproti skladbÄ› o 0,2 s" #: ../data/dialogs.glade.h:11 msgid "Always" msgstr "neustále" #: ../data/dialogs.glade.h:2 msgid "" "An OSD lyric show compatible with various media players and supports lyrics " "downloading.\n" msgstr "" "Karaoke v pokoji. Program spolupracuje s mnoha pÅ™ehrávaÄi a umí stahovat " "texty z nÄ›kolika serverů.\n" #: ../data/dialogs.glade.h:91 msgid "Assign local LRC file to current music" msgstr "PÅ™iÅ™adit aktuální skladbÄ› vlastní soubor *.lrc z disku" #: ../data/dialogs.glade.h:72 msgid "Au_to-detect system settings" msgstr "Použít nastavení _systému" #: ../data/dialogs.glade.h:17 msgid "Auto la_unch player:" msgstr "_SpouÅ¡tÄ›t pÅ™ehrávaÄ:" #: ../data/dialogs.glade.h:12 msgid "By lines" msgstr "po řádcích" #: ../data/dialogs.glade.h:38 msgid "C_ustomize colors" msgstr "_Upravit barvy" #: ../data/dialogs.glade.h:98 msgid "Choose LRC file to download" msgstr "" #: ../data/dialogs.glade.h:50 msgid "Colo_r:" msgstr "_Barva:" #: ../data/dialogs.glade.h:37 msgid "Color _Theme:" msgstr "_Barevné téma:" #: ../data/dialogs.glade.h:1 msgid "Copyright 2009-2011 The OSD Lyrics project." msgstr "Projekt OSD Lyrics. Copyright 2009–2015." #: ../data/dialogs.glade.h:99 msgid "D_on't ask me again" msgstr "_Znovu se neptat" #: ../data/dialogs.glade.h:14 msgid "Display mode:" msgstr "Způsob zobrazení:" #. In preference/download, automately download the most matched lyric #: ../data/dialogs.glade.h:69 msgid "Do_wnload the first candidate" msgstr "_Stáhnout prvního kandidáta" #: ../data/dialogs.glade.h:31 msgid "Doc_k" msgstr "zakázané" #: ../data/dialogs.glade.h:93 msgid "Don't assign lyric to this music" msgstr "K této skladbÄ› nezobrazovat text" #: ../data/dialogs.glade.h:34 msgid "F_irst line:" msgstr "_Horní:" #: ../data/dialogs.glade.h:5 msgid "GPL v3" msgstr "" #: ../data/dialogs.glade.h:8 msgid "HTTP" msgstr "" #: ../data/dialogs.glade.h:86 msgid "Hide the OSD Window" msgstr "Skrýt panel s textem" #: ../data/dialogs.glade.h:84 msgid "If locked, you can't move OSD window by mouse" msgstr "Znemožnit posouvání panelu s textem myší" #: ../data/dialogs.glade.h:100 msgid "" "If there are more than one lrc files matched with the search condition, " "download the first one without prompting the user." msgstr "" #: ../data/dialogs.glade.h:43 msgid "Inactiv_e lyrics:" msgstr "_Nadcházející:" #: ../data/dialogs.glade.h:67 msgid "Lyric _sites:" msgstr "_Servery s texty:" #: ../data/dialogs.glade.h:96 msgid "Lyric delay +" msgstr "Posunout text dopÅ™edu" #: ../data/dialogs.glade.h:94 msgid "Lyric delay -" msgstr "Posunout text dozadu" #: ../data/dialogs.glade.h:27 msgid "Lyric lines:" msgstr "Řádků s textem:" #: ../data/dialogs.glade.h:30 msgid "Mode:" msgstr "Hýbaní s panelem:" #: ../data/dialogs.glade.h:71 msgid "No _proxy" msgstr "_Nepoužívat" #: ../data/dialogs.glade.h:32 msgid "Nor_mal" msgstr "po_volené" #: ../data/dialogs.glade.h:82 msgid "OSD _mode" msgstr "PÅ™epnout text pÅ™es obra_zovku" #: ../data/dialogs.glade.h:15 msgid "OS_D" msgstr "pÅ™es obra_zovku" #: ../data/dialogs.glade.h:51 msgid "O_pacity:" msgstr "_Viditelnost:" #: ../data/dialogs.glade.h:53 msgid "Opaque" msgstr "úplná" #. The outline width of text in OSD window #: ../data/dialogs.glade.h:23 msgid "Out_line:" msgstr "Ob_rys:" #: ../data/dialogs.glade.h:78 msgid "Passwo_rd:" msgstr "_Heslo:" #: ../data/dialogs.glade.h:18 msgid "Pla_yer command:" msgstr "Pří_kaz pÅ™ehrávaÄe:" #: ../data/dialogs.glade.h:75 msgid "Po_rt:" msgstr "" #: ../data/dialogs.glade.h:13 msgid "Preferences" msgstr "Nastavení" #: ../data/dialogs.glade.h:9 msgid "SOCKS4" msgstr "" #: ../data/dialogs.glade.h:10 msgid "SOCKS5" msgstr "" #: ../data/dialogs.glade.h:16 msgid "Sc_rolling" msgstr "v oknÄ›" #: ../data/dialogs.glade.h:48 msgid "Scroll _mode:" msgstr "Způsob _posouvání:" #: ../data/dialogs.glade.h:83 msgid "Scrolling _mode" msgstr "PÅ™epnout text do _okna" #: ../data/dialogs.glade.h:117 msgid "Search lyrics" msgstr "Hledat texty" #: ../data/dialogs.glade.h:89 msgid "Search lyrics from Internet" msgstr "Vyhledat texty na podporovaných serverech" #: ../data/dialogs.glade.h:19 msgid "Show _bubble notification" msgstr "_Oznamovat pÅ™ehrávané skladby" #: ../data/dialogs.glade.h:79 msgid "Show passwor_d" msgstr "_Zobrazovat heslo" #: ../data/dialogs.glade.h:28 msgid "Si_ngle line" msgstr "_jeden" #: ../data/dialogs.glade.h:26 msgid "Strong" msgstr "výrazné" #: ../data/dialogs.glade.h:33 msgid "T_ranslucent on mouse over" msgstr "Z_průhledňovat panel pod kurzorem myÅ¡i" #: ../data/dialogs.glade.h:105 msgid "The artist of the current track" msgstr "Autor aktuální skladby" #: ../data/dialogs.glade.h:114 msgid "The filename of the current track" msgstr "Název souboru se skladbou" #: ../data/dialogs.glade.h:111 msgid "The number of the current track" msgstr "Číslo aktuální skladby" #: ../data/dialogs.glade.h:108 msgid "The title of album of the current track" msgstr "Album aktuální skladby" #: ../data/dialogs.glade.h:102 msgid "The title of the current track" msgstr "Název aktuální skladby" #: ../data/dialogs.glade.h:52 msgid "Transparent" msgstr "průhledná" #: ../data/dialogs.glade.h:73 msgid "Use _manual settings" msgstr "_ManuálnÄ›" #: ../data/dialogs.glade.h:25 msgid "Weak" msgstr "slabé" #: ../data/dialogs.glade.h:119 msgid "_Artist:" msgstr "_Autor:" #: ../data/dialogs.glade.h:90 msgid "_Assign lyric..." msgstr "_PÅ™iÅ™adit text…" #: ../data/dialogs.glade.h:24 msgid "_Blur:" msgstr "Roz_mazání:" #: ../data/dialogs.glade.h:29 msgid "_Double lines" msgstr "_dva" #: ../data/dialogs.glade.h:101 msgid "_Download" msgstr "_Stáhnout" #: ../data/dialogs.glade.h:21 msgid "_Font:" msgstr "_Písmo:" #. From witch web site to search and download lyrics #: ../data/dialogs.glade.h:122 msgid "_From:" msgstr "_Kde:" #: ../data/dialogs.glade.h:20 msgid "_General" msgstr "_Obecné" #: ../data/dialogs.glade.h:87 msgid "_Hide" msgstr "_Skrýt" #: ../data/dialogs.glade.h:74 msgid "_Host:" msgstr "_Server:" #: ../data/dialogs.glade.h:47 msgid "_Inactive color:" msgstr "Barva _ostatních řádků:" #: ../data/dialogs.glade.h:85 msgid "_Lock Position" msgstr "_Zakázat hýbání s panelem" #: ../data/dialogs.glade.h:66 msgid "_Lyric Location" msgstr "_Texty" #: ../data/dialogs.glade.h:81 msgid "_Network" msgstr "_Internet" #: ../data/dialogs.glade.h:92 msgid "_No lyric" msgstr "_Bez textu" #: ../data/dialogs.glade.h:45 msgid "_OSD" msgstr "Obra_zovka" #: ../data/dialogs.glade.h:55 msgid "_Scrolling" msgstr "_Okno" #: ../data/dialogs.glade.h:120 msgid "_Search" msgstr "_Hledat" #: ../data/dialogs.glade.h:88 msgid "_Search lyric..." msgstr "_Hledat texty…" #: ../data/dialogs.glade.h:35 msgid "_Second line:" msgstr "Spo_dní:" #: ../data/dialogs.glade.h:118 msgid "_Title:" msgstr "_Název:" #: ../data/dialogs.glade.h:76 msgid "_Type:" msgstr "_Protokol:" #: ../data/dialogs.glade.h:77 msgid "_Username:" msgstr "_Uživatel:" #: ../data/dialogs.glade.h:42 msgid "bottom" msgstr "spodek" #: ../data/dialogs.glade.h:41 msgid "middle" msgstr "prostÅ™edek" #: ../data/dialogs.glade.h:40 msgid "top" msgstr "vrÅ¡ek písmen" #: ../data/dialogs.glade.h:6 msgid "translator-credits" msgstr "OndÅ™ej Svoboda " #: ../data/osdlyrics.desktop.in.h:2 msgid "Lyrics Show" msgstr "PÅ™ehrávaÄ textů" #: ../data/osdlyrics.desktop.in.h:3 msgid "Download and show lyrics for your favorite media player" msgstr "Stahuje a zobrazuje Äasovaný text k pÅ™ehrávaným skladbám" #: ../lyricsources/xiami/xiami.py:49 msgid "Xiami" msgstr "" osdlyrics-0.5.5~rc1+dfsg1/po/de.po000066400000000000000000000362741345713535600167360ustar00rootroot00000000000000# German translation for osd-lyrics # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the osd-lyrics package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: osd-lyrics\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-11-17 23:18+0800\n" "PO-Revision-Date: 2011-02-25 17:51+0000\n" "Last-Translator: Dennis Baudys \n" "Language-Team: German \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2011-06-04 13:40+0000\n" "X-Generator: Launchpad (build 13144)\n" #: ../src/ol_menu.c:119 msgid "LRC files" msgstr "LRC Dateien" #: ../src/ol_menu.c:125 msgid "Choose LRC file to assign" msgstr "Wähle LRC Datei für die Zuweisung" #: ../src/ol_main.c:60 msgid "" "The level of debug messages to log, can be 'none', 'error', 'debug', or " "'info'" msgstr "" "Ausführlichkeit beim Protokollieren von Debug-Meldungen, entweder »none«, " "»error«, »debug« oder »info«" #: ../src/ol_main.c:62 msgid "Show version information" msgstr "" #: ../src/ol_main.c:147 msgid "Download failed" msgstr "Download fehlgeschlagen" #: ../src/ol_main.c:162 #, fuzzy, c-format msgid "Searching lyrics from %s" msgstr "Suche auf %s nach Liedtexten" #: ../src/ol_main.c:185 msgid "Cannot create the lyric directory" msgstr "Liedtexte-Ordner kann nicht erstellt werden" #: ../src/ol_main.c:201 msgid "Lyrics not found" msgstr "Liedtexte nicht gefunden" #: ../src/ol_main.c:507 #, c-format msgid "No supported player is running, exit.\n" msgstr "Es laufen keine unterstützten Player, OSD Lyrics wird beendet.\n" #: ../src/ol_main.c:652 #, fuzzy msgid "" "debug level should be one of ``none'', ``error'', ``debug'', or ``info''" msgstr "" "Ausführlichkeit beim Protokollieren von Debug-Meldungen, entweder »none«, " "»error«, »debug« oder »info«" #: ../src/ol_main.c:675 msgid "Another OSD Lyrics is running, exit." msgstr "" "Eine andere Instanz von OSD Lyrics läuft bereits, das Programm wird beendet." #: ../src/ol_trayicon.c:36 ../src/ol_notify.c:26 msgid "Unknown title" msgstr "Unbekannter Titel" #: ../src/ol_trayicon.c:37 ../src/ol_notify.c:27 msgid "Unknown artist" msgstr "Unbekannter Interpret" #: ../src/ol_trayicon.c:71 ../src/ol_trayicon.c:149 msgid "OSD Lyrics" msgstr "OSD Lyrics" #: ../src/ol_option.c:1631 msgid "Choose on startup" msgstr "" #. gtk_list_store_append (liststore, &iter); #. gtk_list_store_set (liststore, &iter, #. 0, "Customize", #. 1, "", #. -1); #: ../src/ol_option.c:1651 ../data/dialogs.glade.h:43 msgid "Customize" msgstr "Anpassen" #: ../src/ol_option.c:1713 msgid "Select a folder" msgstr "Verzeichnis auswählen" #: ../src/ol_lrc_fetch_ttplayer.c:274 msgid "ttPlayer" msgstr "" #: ../src/ol_lrc_fetch_xiami.c:237 msgid "Xiami" msgstr "" #: ../src/ol_lrc_candidate_list.c:43 msgid "Title" msgstr "Titel" #: ../src/ol_lrc_candidate_list.c:48 msgid "Artist" msgstr "Interpret" #: ../src/ol_search_dialog.c:31 #, c-format msgid "Searching lyrics from %s..." msgstr "Suche auf %s nach Liedtexten" #: ../src/ol_search_dialog.c:32 msgid "Ooops, no lyric found :(" msgstr "Ups, kein Liedtext gefunden :(" #: ../src/ol_search_dialog.c:33 #, c-format msgid "%d lyrics found :)" msgstr "%d Liedtexte gefunden :)" #: ../src/ol_player_chooser.c:243 msgid "Supported players" msgstr "" #: ../src/ol_player_chooser.c:244 msgid "All players" msgstr "" #: ../src/ol_player_chooser.c:248 ../src/ol_player_chooser.c:319 #, fuzzy msgid "Choose a player to launch" msgstr "Der Befehl, der den Player startet" #: ../src/ol_player_chooser.c:264 #, fuzzy msgid "Use command:" msgstr "_Befehl" #: ../src/ol_player_chooser.c:268 msgid "Launch" msgstr "" #: ../src/ol_player_chooser.c:281 msgid "Remember my choice" msgstr "" #: ../src/ol_player_chooser.c:387 #, c-format msgid "Failed to launch %s" msgstr "" #: ../src/ol_player_chooser.c:397 #, c-format msgid "Launching %s" msgstr "" #: ../src/ol_player_chooser.c:399 #, c-format msgid "" "OSD Lyrics is trying to launch and connect to %s. Please wait for a second." msgstr "" #: ../src/ol_player_chooser.c:510 #, fuzzy msgid "There is no supported player running" msgstr "Es laufen keine unterstützten Player, OSD Lyrics wird beendet.\n" #: ../src/ol_player_chooser.c:511 #, fuzzy msgid "Please choose a player below to launch" msgstr "Der Befehl, der den Player startet" #: ../src/ol_player_chooser.c:515 #, c-format msgid "Failed to connect to %s" msgstr "" #: ../src/ol_player_chooser.c:517 #, c-format msgid "" "%s is not supported by OSD Lyrics, or not running. Please launch another " "player" msgstr "" #: ../src/ol_app_info.c:183 ../src/ol_app_info.c:193 msgid "Unnamed" msgstr "" #: ../src/ol_scroll_window.c:52 msgid "" "Drag to move the window\n" "Hold CTRL to seek" msgstr "" #: ../src/ol_scroll_window.c:53 msgid "Drag to move the window" msgstr "" #: ../data/dialogs.glade.h:2 #, no-c-format msgid "" " %t - Title\n" " %p - Artist\n" " %a - Album\n" " %n - Track number\n" " %f - File name (without extension)" msgstr "" " %t - Titel\n" " %p - Interpret\n" " %a - Album\n" " %n - Titelnummer\n" " %f - Dateiname (ohne Dateiendung)" #: ../data/dialogs.glade.h:8 #, no-c-format msgid "%a - Album" msgstr "%a - Album" #: ../data/dialogs.glade.h:10 #, no-c-format msgid "%f - Filename" msgstr "%f - Dateiname" #: ../data/dialogs.glade.h:12 #, no-c-format msgid "%n - Track number" msgstr "%n - Stück" #: ../data/dialogs.glade.h:14 #, no-c-format msgid "%p - Artist" msgstr "%p - Interpret" #: ../data/dialogs.glade.h:16 #, no-c-format msgid "%t - Title" msgstr "%t - Titel" #: ../data/dialogs.glade.h:17 msgid "Background" msgstr "" #: ../data/dialogs.glade.h:18 msgid "Download" msgstr "" #: ../data/dialogs.glade.h:19 msgid "Lyric _Alignment" msgstr "Text _Position" #: ../data/dialogs.glade.h:20 msgid "Lyric _Color" msgstr "Tex_t Farbe" #: ../data/dialogs.glade.h:21 msgid "Lyrics" msgstr "" #: ../data/dialogs.glade.h:22 msgid "Proxy" msgstr "" #: ../data/dialogs.glade.h:23 msgid "_Filename" msgstr "_Dateiname" #: ../data/dialogs.glade.h:24 msgid "_Path" msgstr "_Pfad" #: ../data/dialogs.glade.h:26 #, no-c-format msgid "A single \"%\" means the directory where the playing music is." msgstr "" "Ein einzelnes \"%\" steht für das Verzeichnis, in dem sich die Musik " "befindet." #: ../data/dialogs.glade.h:27 msgid "Ac_tive color:" msgstr "" #: ../data/dialogs.glade.h:28 msgid "Acti_ve lyrics:" msgstr "Aktuelle Lyrics:" #: ../data/dialogs.glade.h:29 msgid "Adjust lyric delay by +0.2 s" msgstr "Ändert die Verzögerung um +0,2 Sekunden" #: ../data/dialogs.glade.h:30 msgid "Adjust lyrics delay by -0.2 s" msgstr "Ändert die Verzögerung um -0,2 Sekunden" #: ../data/dialogs.glade.h:31 msgid "Always" msgstr "" #: ../data/dialogs.glade.h:32 msgid "" "An OSD lyric show compatible with various media players and supports lyrics " "downloading.\n" msgstr "" "Eine Anzeige von Liedtexten mit Unterstützung für diverse Media-Player und " "unterstüzt das Herunterladen von Liedtexten.\n" #: ../data/dialogs.glade.h:34 msgid "Assign local LRC file to current music" msgstr "Der aktuellen Musik einen Liedtext zuweisen" #: ../data/dialogs.glade.h:35 msgid "Au_to-detect system settings" msgstr "" #: ../data/dialogs.glade.h:36 msgid "Auto la_unch player:" msgstr "_Player automatisch starten" #: ../data/dialogs.glade.h:37 msgid "By lines" msgstr "" #: ../data/dialogs.glade.h:38 msgid "C_ustomize colors" msgstr "Farben _anpassen" #: ../data/dialogs.glade.h:39 msgid "Choose LRC file to download" msgstr "Wähle die passenden Lyrics" #: ../data/dialogs.glade.h:40 msgid "Colo_r:" msgstr "" #: ../data/dialogs.glade.h:41 msgid "Color _Theme:" msgstr "_Farbschema:" #: ../data/dialogs.glade.h:42 #, fuzzy msgid "Copyright 2009-2011 The OSD Lyrics project." msgstr "Copyright 2009 OSD Lyrics Projekt" #: ../data/dialogs.glade.h:44 msgid "D_on't ask me again" msgstr "_Nicht mehr nachfragen" #: ../data/dialogs.glade.h:45 msgid "Display mode:" msgstr "" #. In preference/download, automately download the most matched lyric #: ../data/dialogs.glade.h:47 msgid "Do_wnload the first candidate" msgstr "_Immer das erste Ergebnis herunterladen" #: ../data/dialogs.glade.h:48 msgid "Doc_k" msgstr "" #: ../data/dialogs.glade.h:49 msgid "Don't assign lyric to this music" msgstr "Dieser Musik keinen Liedtext zuweisen" #: ../data/dialogs.glade.h:50 msgid "F_irst line:" msgstr "_Erste Zeile:" #: ../data/dialogs.glade.h:51 msgid "GPL v3" msgstr "GPL v3" #: ../data/dialogs.glade.h:52 msgid "HTTP" msgstr "" #: ../data/dialogs.glade.h:53 msgid "Hide the OSD Window" msgstr "Versteckt die Lyrics" #: ../data/dialogs.glade.h:54 msgid "If locked, you can't move OSD window by mouse" msgstr "Wenn gesperrt, können die Lyrics nicht mit der Maus verschoben werden." #: ../data/dialogs.glade.h:55 msgid "" "If there are more than one lrc files matched with the search condition, " "download the first one without prompting the user." msgstr "" "Wenn die Suchmaschine mehr als eine LRC Datei vorschlägt, einfach die erste " "herunterladen ohne den Benutzer zu fragen." #: ../data/dialogs.glade.h:56 msgid "Inactiv_e lyrics:" msgstr "Inaktive Lyrics:" #: ../data/dialogs.glade.h:57 #, fuzzy msgid "Lyric _sites:" msgstr "Liedtextzeilen" #: ../data/dialogs.glade.h:58 msgid "Lyric delay +" msgstr "Verzögerung +" #: ../data/dialogs.glade.h:59 msgid "Lyric delay -" msgstr "Verzögerung -" #: ../data/dialogs.glade.h:60 msgid "Lyric lines:" msgstr "Liedtextzeilen" #: ../data/dialogs.glade.h:61 msgid "Mode:" msgstr "" #: ../data/dialogs.glade.h:62 msgid "No _proxy" msgstr "" #: ../data/dialogs.glade.h:63 msgid "Nor_mal" msgstr "" #: ../data/dialogs.glade.h:64 msgid "OSD _mode" msgstr "" #: ../data/dialogs.glade.h:65 msgid "OS_D" msgstr "" #: ../data/dialogs.glade.h:66 msgid "O_pacity:" msgstr "" #: ../data/dialogs.glade.h:67 msgid "Opaque" msgstr "" #. The outline width of text in OSD window #: ../data/dialogs.glade.h:69 msgid "Out_line:" msgstr "_Umrandung:" #: ../data/dialogs.glade.h:70 msgid "Passwo_rd:" msgstr "" #: ../data/dialogs.glade.h:71 msgid "Pla_yer command:" msgstr "_Befehl" #: ../data/dialogs.glade.h:72 msgid "Po_rt:" msgstr "" #: ../data/dialogs.glade.h:73 msgid "Preferences" msgstr "Einstellungen" #: ../data/dialogs.glade.h:74 msgid "SOCKS4" msgstr "" #: ../data/dialogs.glade.h:75 msgid "SOCKS5" msgstr "" #: ../data/dialogs.glade.h:76 msgid "Sc_rolling" msgstr "" #: ../data/dialogs.glade.h:77 msgid "Scroll _mode:" msgstr "" #: ../data/dialogs.glade.h:78 msgid "Scrolling _mode" msgstr "" #: ../data/dialogs.glade.h:79 msgid "Search lyrics" msgstr "Nach Lyrics suchen" #: ../data/dialogs.glade.h:80 msgid "Search lyrics from Internet" msgstr "Suche im Internet nach Liedtexten" #: ../data/dialogs.glade.h:81 msgid "Show _bubble notification" msgstr "" #: ../data/dialogs.glade.h:82 msgid "Show passwor_d" msgstr "" #: ../data/dialogs.glade.h:83 msgid "Si_ngle line" msgstr "_eine" #: ../data/dialogs.glade.h:84 msgid "Strong" msgstr "" #: ../data/dialogs.glade.h:85 msgid "T_ranslucent on mouse over" msgstr "_Transparent bei Mausberührung" #: ../data/dialogs.glade.h:86 msgid "The artist of the current track" msgstr "Interpret des aktuellen Liedes" #: ../data/dialogs.glade.h:87 msgid "The filename of the current track" msgstr "Der Dateiname des aktuellen Liedes" #: ../data/dialogs.glade.h:88 msgid "The number of the current track" msgstr "Die aktuellen Liednummer" #: ../data/dialogs.glade.h:89 msgid "The title of album of the current track" msgstr "Titel des aktuellen Albums" #: ../data/dialogs.glade.h:90 msgid "The title of the current track" msgstr "Titel des aktuellen Liedes" #: ../data/dialogs.glade.h:91 msgid "Transparent" msgstr "" #: ../data/dialogs.glade.h:92 msgid "Use _manual settings" msgstr "" #: ../data/dialogs.glade.h:93 msgid "Weak" msgstr "" #: ../data/dialogs.glade.h:94 msgid "_Artist:" msgstr "_Interpret:" #: ../data/dialogs.glade.h:95 msgid "_Assign lyric..." msgstr "_Liedtext zuweisen..." #: ../data/dialogs.glade.h:96 msgid "_Blur:" msgstr "" #: ../data/dialogs.glade.h:97 msgid "_Double lines" msgstr "_zwei" #: ../data/dialogs.glade.h:98 msgid "_Download" msgstr "_Herunterladen" #: ../data/dialogs.glade.h:99 msgid "_Font:" msgstr "_Schriftart:" #. From witch web site to search and download lyrics #: ../data/dialogs.glade.h:101 msgid "_From:" msgstr "_Von:" #: ../data/dialogs.glade.h:102 msgid "_General" msgstr "_Allgemein" #: ../data/dialogs.glade.h:103 msgid "_Hide" msgstr "_Verbergen" #: ../data/dialogs.glade.h:104 msgid "_Host:" msgstr "" #: ../data/dialogs.glade.h:105 msgid "_Inactive color:" msgstr "" #: ../data/dialogs.glade.h:106 msgid "_Lock Position" msgstr "_Position sperren" #: ../data/dialogs.glade.h:107 msgid "_Lyric Location" msgstr "_Speicherort" #: ../data/dialogs.glade.h:108 msgid "_Network" msgstr "" #: ../data/dialogs.glade.h:109 msgid "_No lyric" msgstr "_Kein Liedtext" #: ../data/dialogs.glade.h:110 msgid "_OSD" msgstr "_OSD" #: ../data/dialogs.glade.h:111 msgid "_Scrolling" msgstr "" #: ../data/dialogs.glade.h:112 msgid "_Search" msgstr "_Suche" #: ../data/dialogs.glade.h:113 msgid "_Search lyric..." msgstr "_Suche Liedtext..." #: ../data/dialogs.glade.h:114 msgid "_Second line:" msgstr "_Zweite Zeile:" #: ../data/dialogs.glade.h:115 msgid "_Title:" msgstr "_Titel:" #: ../data/dialogs.glade.h:116 msgid "_Type:" msgstr "" #: ../data/dialogs.glade.h:117 msgid "_Username:" msgstr "" #: ../data/dialogs.glade.h:118 msgid "bottom" msgstr "Unten" #: ../data/dialogs.glade.h:120 msgid "middle" msgstr "Mitte" #: ../data/dialogs.glade.h:121 msgid "top" msgstr "Oben" #: ../data/dialogs.glade.h:122 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" " Dennis Baudys https://launchpad.net/~thecondordb\n" " JarosÅ‚aw Ogrodnik https://launchpad.net/~goz\n" " Jens Kammann https://launchpad.net/~info-jens-kammann\n" " Jens Maucher https://launchpad.net/~jensmaucher\n" " Max https://launchpad.net/~maxlp-deactivatedaccount\n" " Philipp Stampfli https://launchpad.net/~phildst\n" " Stefan Reichel https://launchpad.net/~bomret\n" " Tiger Soldier https://launchpad.net/~tigersoldi\n" " thoughtcrime https://launchpad.net/~thoughtcrime" #~ msgid "Searching lyrics" #~ msgstr "Liedtexte werden gesucht" #~ msgid "Preview" #~ msgstr "Vorschau" #~ msgid "None" #~ msgstr "Kein(e)" #~ msgid "Sogou" #~ msgstr "Sogou" #~ msgid "Qianqian" #~ msgstr "Qianqian" #~ msgid "_Width:" #~ msgstr "_Breite:" #~ msgid "_Lock" #~ msgstr "S_perren" #~ msgid "Choose witch site you want to download from." #~ msgstr "" #~ "Wähle aus, von welcher Webseite die Lyrics heruntergeladen werden sollen." #~ msgid "Download _engine:" #~ msgstr "_Suchmaschine" #~ msgid "_Search lyric" #~ msgstr "_Suchen" #~ msgid "_Player control" #~ msgstr "_Player-Steuerung" #~ msgid "_Display notification" #~ msgstr "_Benachrichtigung anzeigen" #~ msgid "If no player detected, launch the given player" #~ msgstr "Falls kein Player gefunden wurde, wird dieser gestartet" #~ msgid "Scrolling" #~ msgstr "Bildlauf" #~ msgid "OSD" #~ msgstr "OSD" #~ msgid "Display mode" #~ msgstr "Anzeige-Modus" #~ msgid "MiniLyrics" #~ msgstr "MiniLyrics" osdlyrics-0.5.5~rc1+dfsg1/po/en@boldquot.header000066400000000000000000000024711345713535600214240ustar00rootroot00000000000000# All this catalog "translates" are quotation characters. # The msgids must be ASCII and therefore cannot contain real quotation # characters, only substitutes like grave accent (0x60), apostrophe (0x27) # and double quote (0x22). These substitutes look strange; see # http://www.cl.cam.ac.uk/~mgk25/ucs/quotes.html # # This catalog translates grave accent (0x60) and apostrophe (0x27) to # left single quotation mark (U+2018) and right single quotation mark (U+2019). # It also translates pairs of apostrophe (0x27) to # left single quotation mark (U+2018) and right single quotation mark (U+2019) # and pairs of quotation mark (0x22) to # left double quotation mark (U+201C) and right double quotation mark (U+201D). # # When output to an UTF-8 terminal, the quotation characters appear perfectly. # When output to an ISO-8859-1 terminal, the single quotation marks are # transliterated to apostrophes (by iconv in glibc 2.2 or newer) or to # grave/acute accent (by libiconv), and the double quotation marks are # transliterated to 0x22. # When output to an ASCII terminal, the single quotation marks are # transliterated to apostrophes, and the double quotation marks are # transliterated to 0x22. # # This catalog furthermore displays the text between the quotation marks in # bold face, assuming the VT100/XTerm escape sequences. # osdlyrics-0.5.5~rc1+dfsg1/po/en@quot.header000066400000000000000000000022631345713535600205620ustar00rootroot00000000000000# All this catalog "translates" are quotation characters. # The msgids must be ASCII and therefore cannot contain real quotation # characters, only substitutes like grave accent (0x60), apostrophe (0x27) # and double quote (0x22). These substitutes look strange; see # http://www.cl.cam.ac.uk/~mgk25/ucs/quotes.html # # This catalog translates grave accent (0x60) and apostrophe (0x27) to # left single quotation mark (U+2018) and right single quotation mark (U+2019). # It also translates pairs of apostrophe (0x27) to # left single quotation mark (U+2018) and right single quotation mark (U+2019) # and pairs of quotation mark (0x22) to # left double quotation mark (U+201C) and right double quotation mark (U+201D). # # When output to an UTF-8 terminal, the quotation characters appear perfectly. # When output to an ISO-8859-1 terminal, the single quotation marks are # transliterated to apostrophes (by iconv in glibc 2.2 or newer) or to # grave/acute accent (by libiconv), and the double quotation marks are # transliterated to 0x22. # When output to an ASCII terminal, the single quotation marks are # transliterated to apostrophes, and the double quotation marks are # transliterated to 0x22. # osdlyrics-0.5.5~rc1+dfsg1/po/es.po000066400000000000000000000373021345713535600167460ustar00rootroot00000000000000# Spanish translation for osd-lyrics # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the osd-lyrics package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: osd-lyrics\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-11-17 23:18+0800\n" "PO-Revision-Date: 2010-11-15 19:25+0000\n" "Last-Translator: Fitoschido \n" "Language-Team: Spanish \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2011-06-04 13:40+0000\n" "X-Generator: Launchpad (build 13144)\n" #: ../src/ol_menu.c:119 msgid "LRC files" msgstr "Archivos LRC" #: ../src/ol_menu.c:125 msgid "Choose LRC file to assign" msgstr "Elija el archivo LRC a asignar" #: ../src/ol_main.c:60 msgid "" "The level of debug messages to log, can be 'none', 'error', 'debug', or " "'info'" msgstr "" "El nivel de mensajes de depuración para el registro, puede ser «none», " "«error», «debug» o «info»" #: ../src/ol_main.c:62 msgid "Show version information" msgstr "" #: ../src/ol_main.c:147 msgid "Download failed" msgstr "La descarga ha fallado" #: ../src/ol_main.c:162 #, fuzzy, c-format msgid "Searching lyrics from %s" msgstr "Buscando letras de canciones de %s..." #: ../src/ol_main.c:185 msgid "Cannot create the lyric directory" msgstr "No se puede crear el directorio de letras" #: ../src/ol_main.c:201 msgid "Lyrics not found" msgstr "No se encontraron letras" #: ../src/ol_main.c:507 #, c-format msgid "No supported player is running, exit.\n" msgstr "Ningún reproductor soportado está en ejecución, saliendo.\n" #: ../src/ol_main.c:652 #, fuzzy msgid "" "debug level should be one of ``none'', ``error'', ``debug'', or ``info''" msgstr "" "El nivel de mensajes de depuración para el registro, puede ser «none», " "«error», «debug» o «info»" #: ../src/ol_main.c:675 msgid "Another OSD Lyrics is running, exit." msgstr "Ya ha otro OSD Lyrics en ejecución, saliendo." #: ../src/ol_trayicon.c:36 ../src/ol_notify.c:26 msgid "Unknown title" msgstr "Título desconocido" #: ../src/ol_trayicon.c:37 ../src/ol_notify.c:27 msgid "Unknown artist" msgstr "Artista desconocido" #: ../src/ol_trayicon.c:71 ../src/ol_trayicon.c:149 msgid "OSD Lyrics" msgstr "Letras de canciones OSD" #: ../src/ol_option.c:1631 msgid "Choose on startup" msgstr "" #. gtk_list_store_append (liststore, &iter); #. gtk_list_store_set (liststore, &iter, #. 0, "Customize", #. 1, "", #. -1); #: ../src/ol_option.c:1651 ../data/dialogs.glade.h:43 msgid "Customize" msgstr "Personalizar" #: ../src/ol_option.c:1713 msgid "Select a folder" msgstr "Seleccione una carpeta" #: ../src/ol_lrc_fetch_ttplayer.c:274 msgid "ttPlayer" msgstr "" #: ../src/ol_lrc_fetch_xiami.c:237 msgid "Xiami" msgstr "" #: ../src/ol_lrc_candidate_list.c:43 msgid "Title" msgstr "Título" #: ../src/ol_lrc_candidate_list.c:48 msgid "Artist" msgstr "Artista" #: ../src/ol_search_dialog.c:31 #, c-format msgid "Searching lyrics from %s..." msgstr "Buscando letras de canciones de %s..." #: ../src/ol_search_dialog.c:32 msgid "Ooops, no lyric found :(" msgstr "Vaya, no se encontró Letra: (" #: ../src/ol_search_dialog.c:33 #, c-format msgid "%d lyrics found :)" msgstr "letras encontrado %d :)" #: ../src/ol_player_chooser.c:243 msgid "Supported players" msgstr "" #: ../src/ol_player_chooser.c:244 msgid "All players" msgstr "" #: ../src/ol_player_chooser.c:248 ../src/ol_player_chooser.c:319 #, fuzzy msgid "Choose a player to launch" msgstr "la orden del ereproductor a lanzar" #: ../src/ol_player_chooser.c:264 #, fuzzy msgid "Use command:" msgstr "Or_den del reproductor:" #: ../src/ol_player_chooser.c:268 msgid "Launch" msgstr "" #: ../src/ol_player_chooser.c:281 msgid "Remember my choice" msgstr "" #: ../src/ol_player_chooser.c:387 #, c-format msgid "Failed to launch %s" msgstr "" #: ../src/ol_player_chooser.c:397 #, c-format msgid "Launching %s" msgstr "" #: ../src/ol_player_chooser.c:399 #, c-format msgid "" "OSD Lyrics is trying to launch and connect to %s. Please wait for a second." msgstr "" #: ../src/ol_player_chooser.c:510 #, fuzzy msgid "There is no supported player running" msgstr "Ningún reproductor soportado está en ejecución, saliendo.\n" #: ../src/ol_player_chooser.c:511 #, fuzzy msgid "Please choose a player below to launch" msgstr "la orden del ereproductor a lanzar" #: ../src/ol_player_chooser.c:515 #, c-format msgid "Failed to connect to %s" msgstr "" #: ../src/ol_player_chooser.c:517 #, c-format msgid "" "%s is not supported by OSD Lyrics, or not running. Please launch another " "player" msgstr "" #: ../src/ol_app_info.c:183 ../src/ol_app_info.c:193 msgid "Unnamed" msgstr "" #: ../src/ol_scroll_window.c:52 msgid "" "Drag to move the window\n" "Hold CTRL to seek" msgstr "" #: ../src/ol_scroll_window.c:53 msgid "Drag to move the window" msgstr "" #: ../data/dialogs.glade.h:2 #, no-c-format msgid "" " %t - Title\n" " %p - Artist\n" " %a - Album\n" " %n - Track number\n" " %f - File name (without extension)" msgstr "" " %t - Título\n" " %p - Artista\n" " %a - Album\n" " %n - Número de pista\n" " %f - Nombre del archivo (sin extensión)" #: ../data/dialogs.glade.h:8 #, no-c-format msgid "%a - Album" msgstr "%a - Ãlbum" #: ../data/dialogs.glade.h:10 #, no-c-format msgid "%f - Filename" msgstr "%f - Nombre_del_archivo" #: ../data/dialogs.glade.h:12 #, no-c-format msgid "%n - Track number" msgstr "%n - Número de la pista actual" #: ../data/dialogs.glade.h:14 #, no-c-format msgid "%p - Artist" msgstr "%p - Artista" #: ../data/dialogs.glade.h:16 #, no-c-format msgid "%t - Title" msgstr "%t - Título" #: ../data/dialogs.glade.h:17 msgid "Background" msgstr "" #: ../data/dialogs.glade.h:18 msgid "Download" msgstr "" #: ../data/dialogs.glade.h:19 msgid "Lyric _Alignment" msgstr "Alineación de la letra" #: ../data/dialogs.glade.h:20 msgid "Lyric _Color" msgstr "Color de letra" #: ../data/dialogs.glade.h:21 msgid "Lyrics" msgstr "" #: ../data/dialogs.glade.h:22 msgid "Proxy" msgstr "" #: ../data/dialogs.glade.h:23 msgid "_Filename" msgstr "_Nombre del archivo" #: ../data/dialogs.glade.h:24 msgid "_Path" msgstr "_Ruta" #: ../data/dialogs.glade.h:26 #, no-c-format msgid "A single \"%\" means the directory where the playing music is." msgstr "" "Un único símbolo \"%\" indica el directorio donde se encuentran las canciones" #: ../data/dialogs.glade.h:27 msgid "Ac_tive color:" msgstr "" #: ../data/dialogs.glade.h:28 msgid "Acti_ve lyrics:" msgstr "Letras acti_vas:" #: ../data/dialogs.glade.h:29 msgid "Adjust lyric delay by +0.2 s" msgstr "Ajustar desfase de la letra en +0.2[s]" #: ../data/dialogs.glade.h:30 msgid "Adjust lyrics delay by -0.2 s" msgstr "Ajustar desfase de la letra en -0.2[s]" #: ../data/dialogs.glade.h:31 msgid "Always" msgstr "" #: ../data/dialogs.glade.h:32 msgid "" "An OSD lyric show compatible with various media players and supports lyrics " "downloading.\n" msgstr "" "Una función de letras de canciones en OSD compatible con varios " "reproductores multimedia y que soporta la descarga de letras.\n" #: ../data/dialogs.glade.h:34 msgid "Assign local LRC file to current music" msgstr "Asignar archivo LRC local a la canción actual" #: ../data/dialogs.glade.h:35 msgid "Au_to-detect system settings" msgstr "" #: ../data/dialogs.glade.h:36 msgid "Auto la_unch player:" msgstr "Lanzar el reprodutior automáticamente:" #: ../data/dialogs.glade.h:37 msgid "By lines" msgstr "" #: ../data/dialogs.glade.h:38 msgid "C_ustomize colors" msgstr "Personalizar colores" #: ../data/dialogs.glade.h:39 msgid "Choose LRC file to download" msgstr "Escoger el archivo LRC para descargar" #: ../data/dialogs.glade.h:40 msgid "Colo_r:" msgstr "" #: ../data/dialogs.glade.h:41 msgid "Color _Theme:" msgstr "Color del _Tema" #: ../data/dialogs.glade.h:42 #, fuzzy msgid "Copyright 2009-2011 The OSD Lyrics project." msgstr "Copyright 2009 El proyecto OSD Lyrics" #: ../data/dialogs.glade.h:44 msgid "D_on't ask me again" msgstr "No volver a preguntar" #: ../data/dialogs.glade.h:45 msgid "Display mode:" msgstr "" #. In preference/download, automately download the most matched lyric #: ../data/dialogs.glade.h:47 msgid "Do_wnload the first candidate" msgstr "Descargar el primer candidato" #: ../data/dialogs.glade.h:48 msgid "Doc_k" msgstr "" #: ../data/dialogs.glade.h:49 msgid "Don't assign lyric to this music" msgstr "No asignar letra a esta canción" #: ../data/dialogs.glade.h:50 msgid "F_irst line:" msgstr "Pr_imera línea:" #: ../data/dialogs.glade.h:51 msgid "GPL v3" msgstr "GPL v3" #: ../data/dialogs.glade.h:52 msgid "HTTP" msgstr "" #: ../data/dialogs.glade.h:53 msgid "Hide the OSD Window" msgstr "Ocultar la ventana OSD" #: ../data/dialogs.glade.h:54 msgid "If locked, you can't move OSD window by mouse" msgstr "Si está bloqueada, no podrás mover la ventana OSD con el ratón." #: ../data/dialogs.glade.h:55 msgid "" "If there are more than one lrc files matched with the search condition, " "download the first one without prompting the user." msgstr "" "Si hay mas de un archivo lrc que cumpla con las condiciones de búsqueda, " "descargar el primero sin notificar al usuario." #: ../data/dialogs.glade.h:56 msgid "Inactiv_e lyrics:" msgstr "Letras inactiv_as:" #: ../data/dialogs.glade.h:57 #, fuzzy msgid "Lyric _sites:" msgstr "Líneas de la letra:" #: ../data/dialogs.glade.h:58 msgid "Lyric delay +" msgstr "Desfasar la letra [+]" #: ../data/dialogs.glade.h:59 msgid "Lyric delay -" msgstr "Desfasar la letra [-]" #: ../data/dialogs.glade.h:60 msgid "Lyric lines:" msgstr "Líneas de la letra:" #: ../data/dialogs.glade.h:61 msgid "Mode:" msgstr "" #: ../data/dialogs.glade.h:62 msgid "No _proxy" msgstr "" #: ../data/dialogs.glade.h:63 msgid "Nor_mal" msgstr "" #: ../data/dialogs.glade.h:64 msgid "OSD _mode" msgstr "" #: ../data/dialogs.glade.h:65 msgid "OS_D" msgstr "" #: ../data/dialogs.glade.h:66 msgid "O_pacity:" msgstr "" #: ../data/dialogs.glade.h:67 msgid "Opaque" msgstr "" #. The outline width of text in OSD window #: ../data/dialogs.glade.h:69 msgid "Out_line:" msgstr "Borde" #: ../data/dialogs.glade.h:70 msgid "Passwo_rd:" msgstr "" #: ../data/dialogs.glade.h:71 msgid "Pla_yer command:" msgstr "Or_den del reproductor:" #: ../data/dialogs.glade.h:72 msgid "Po_rt:" msgstr "" #: ../data/dialogs.glade.h:73 msgid "Preferences" msgstr "Preferencias" #: ../data/dialogs.glade.h:74 msgid "SOCKS4" msgstr "" #: ../data/dialogs.glade.h:75 msgid "SOCKS5" msgstr "" #: ../data/dialogs.glade.h:76 msgid "Sc_rolling" msgstr "" #: ../data/dialogs.glade.h:77 msgid "Scroll _mode:" msgstr "" #: ../data/dialogs.glade.h:78 msgid "Scrolling _mode" msgstr "" #: ../data/dialogs.glade.h:79 msgid "Search lyrics" msgstr "Buscar letras" #: ../data/dialogs.glade.h:80 msgid "Search lyrics from Internet" msgstr "Buscar letras desde internet" #: ../data/dialogs.glade.h:81 msgid "Show _bubble notification" msgstr "" #: ../data/dialogs.glade.h:82 msgid "Show passwor_d" msgstr "" #: ../data/dialogs.glade.h:83 msgid "Si_ngle line" msgstr "U_na línea" #: ../data/dialogs.glade.h:84 msgid "Strong" msgstr "" #: ../data/dialogs.glade.h:85 msgid "T_ranslucent on mouse over" msgstr "T_ransparente al tener el ratón encima" #: ../data/dialogs.glade.h:86 msgid "The artist of the current track" msgstr "El artista de la pista actual" #: ../data/dialogs.glade.h:87 msgid "The filename of the current track" msgstr "El nombre del archivo de la pista actual" #: ../data/dialogs.glade.h:88 msgid "The number of the current track" msgstr "El número de la pista actual" #: ../data/dialogs.glade.h:89 msgid "The title of album of the current track" msgstr "El título del álbum de la canción actual" #: ../data/dialogs.glade.h:90 msgid "The title of the current track" msgstr "El título de la pista actual" #: ../data/dialogs.glade.h:91 msgid "Transparent" msgstr "" #: ../data/dialogs.glade.h:92 msgid "Use _manual settings" msgstr "" #: ../data/dialogs.glade.h:93 msgid "Weak" msgstr "" #: ../data/dialogs.glade.h:94 msgid "_Artist:" msgstr "_Artista:" #: ../data/dialogs.glade.h:95 msgid "_Assign lyric..." msgstr "_Asignar letra..." #: ../data/dialogs.glade.h:96 msgid "_Blur:" msgstr "" #: ../data/dialogs.glade.h:97 msgid "_Double lines" msgstr "Lineas dobles" #: ../data/dialogs.glade.h:98 msgid "_Download" msgstr "_Descargar" #: ../data/dialogs.glade.h:99 msgid "_Font:" msgstr "_Fuente:" #. From witch web site to search and download lyrics #: ../data/dialogs.glade.h:101 msgid "_From:" msgstr "_Desde:" #: ../data/dialogs.glade.h:102 msgid "_General" msgstr "_General" #: ../data/dialogs.glade.h:103 msgid "_Hide" msgstr "_Ocultar" #: ../data/dialogs.glade.h:104 msgid "_Host:" msgstr "" #: ../data/dialogs.glade.h:105 msgid "_Inactive color:" msgstr "" #: ../data/dialogs.glade.h:106 msgid "_Lock Position" msgstr "_Bloquear posición" #: ../data/dialogs.glade.h:107 msgid "_Lyric Location" msgstr "Ubicación de la letra" #: ../data/dialogs.glade.h:108 msgid "_Network" msgstr "" #: ../data/dialogs.glade.h:109 msgid "_No lyric" msgstr "Si_n letra" #: ../data/dialogs.glade.h:110 msgid "_OSD" msgstr "_OSD" #: ../data/dialogs.glade.h:111 msgid "_Scrolling" msgstr "" #: ../data/dialogs.glade.h:112 msgid "_Search" msgstr "_Buscar" #: ../data/dialogs.glade.h:113 msgid "_Search lyric..." msgstr "_Buscar letra..." #: ../data/dialogs.glade.h:114 msgid "_Second line:" msgstr "_Segunda línea" #: ../data/dialogs.glade.h:115 msgid "_Title:" msgstr "_Título:" #: ../data/dialogs.glade.h:116 msgid "_Type:" msgstr "" #: ../data/dialogs.glade.h:117 msgid "_Username:" msgstr "" #: ../data/dialogs.glade.h:118 msgid "bottom" msgstr "abajo" #: ../data/dialogs.glade.h:120 msgid "middle" msgstr "en medio" #: ../data/dialogs.glade.h:121 msgid "top" msgstr "arriba" #: ../data/dialogs.glade.h:122 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" " Albert Abril https://launchpad.net/~abrilc\n" " DiegoJ https://launchpad.net/~diegojromerolopez\n" " Feder Sáiz https://launchpad.net/~federsaiz-deactivatedaccount\n" " Fitoschido https://launchpad.net/~fitoschido\n" " Francisco Castillo https://launchpad.net/~panchokoster\n" " Jonay https://launchpad.net/~jonay-santana\n" " José A. Fuentes Santiago https://launchpad.net/~joanfusan\n" " Juan L Rodas https://launchpad.net/~jlrodasr\n" " Macafyc https://launchpad.net/~macafyc\n" " Monkey https://launchpad.net/~monkey-libre\n" " Paco Molinero https://launchpad.net/~franciscomol\n" " Rodribuntu https://launchpad.net/~rajc-56\n" " Tiger Soldier https://launchpad.net/~tigersoldi\n" " alakran https://launchpad.net/~alakran-1\n" " hhlp https://launchpad.net/~hhlp\n" " smaug https://launchpad.net/~marcoantoniofrias" #~ msgid "Searching lyrics" #~ msgstr "Buscando letras" #~ msgid "Preview" #~ msgstr "Vista previa" #~ msgid "None" #~ msgstr "Ninguno" #~ msgid "_Lock" #~ msgstr "B_loquear" #~ msgid "Download _engine:" #~ msgstr "Motor de d_escarga:" #~ msgid "TODO: Add options here" #~ msgstr "Añadir opciones aqui" #~ msgid "_Width:" #~ msgstr "_Ancho:" #~ msgid "Sogou" #~ msgstr "Sogou" #~ msgid "Qianqian" #~ msgstr "Qianqian" #~ msgid "_Download lyric" #~ msgstr "_Descargar letras" #~ msgid "_Display notification" #~ msgstr "_Mostrar las notifiaciones" #~ msgid "MiniLyrics" #~ msgstr "MiniLyrics" #~ msgid "Choose witch site you want to download from." #~ msgstr "Elija desde que sitio desea descargar las letras." #~ msgid "If no player detected, launch the given player" #~ msgstr "Si no se detecta ningún reproductor, lanza el reproductor dado" #~ msgid "Display mode" #~ msgstr "Modo de pantalla" #~ msgid "Scrolling" #~ msgstr "Desplazamiento" #~ msgid "OSD" #~ msgstr "OSD" osdlyrics-0.5.5~rc1+dfsg1/po/fr.po000066400000000000000000000364141345713535600167510ustar00rootroot00000000000000# French translation for osd-lyrics # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the osd-lyrics package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: osd-lyrics\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-11-17 23:18+0800\n" "PO-Revision-Date: 2011-04-05 22:41+0000\n" "Last-Translator: Nabil Psychoman \n" "Language-Team: French \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Launchpad-Export-Date: 2011-06-04 13:40+0000\n" "X-Generator: Launchpad (build 13144)\n" #: ../src/ol_menu.c:119 msgid "LRC files" msgstr "Fichiers LRC" #: ../src/ol_menu.c:125 msgid "Choose LRC file to assign" msgstr "Assigner un fichier LRC" #: ../src/ol_main.c:60 msgid "" "The level of debug messages to log, can be 'none', 'error', 'debug', or " "'info'" msgstr "" "Le quantité de messages à enregistrer, peut-être " "'none' (aucun),'error' (erreurs), 'debug' (débogage) ou 'info' (informations)" #: ../src/ol_main.c:62 msgid "Show version information" msgstr "" #: ../src/ol_main.c:147 msgid "Download failed" msgstr "Échec du téléchargement" #: ../src/ol_main.c:162 #, fuzzy, c-format msgid "Searching lyrics from %s" msgstr "Recherche de paroles à partir de %s..." #: ../src/ol_main.c:185 msgid "Cannot create the lyric directory" msgstr "Impossible de créer le dossier de paroles" #: ../src/ol_main.c:201 msgid "Lyrics not found" msgstr "Paroles introuvables" #: ../src/ol_main.c:507 #, c-format msgid "No supported player is running, exit.\n" msgstr "Pas de lecteur pris en charge détecté, arrêt.\n" #: ../src/ol_main.c:652 #, fuzzy msgid "" "debug level should be one of ``none'', ``error'', ``debug'', or ``info''" msgstr "" "Le quantité de messages à enregistrer, peut-être " "'none' (aucun),'error' (erreurs), 'debug' (débogage) ou 'info' (informations)" #: ../src/ol_main.c:675 msgid "Another OSD Lyrics is running, exit." msgstr "" "Une autre instance de OSD Lyrics est en cours d'exécution, arrêt en cours." #: ../src/ol_trayicon.c:36 ../src/ol_notify.c:26 msgid "Unknown title" msgstr "Titre inconnu" #: ../src/ol_trayicon.c:37 ../src/ol_notify.c:27 msgid "Unknown artist" msgstr "Artiste inconnu" #: ../src/ol_trayicon.c:71 ../src/ol_trayicon.c:149 msgid "OSD Lyrics" msgstr "Paroles OSD" #: ../src/ol_option.c:1631 msgid "Choose on startup" msgstr "" #. gtk_list_store_append (liststore, &iter); #. gtk_list_store_set (liststore, &iter, #. 0, "Customize", #. 1, "", #. -1); #: ../src/ol_option.c:1651 ../data/dialogs.glade.h:43 msgid "Customize" msgstr "Personnaliser" #: ../src/ol_option.c:1713 msgid "Select a folder" msgstr "Sélectionner un dossier" #: ../src/ol_lrc_fetch_ttplayer.c:274 msgid "ttPlayer" msgstr "" #: ../src/ol_lrc_fetch_xiami.c:237 msgid "Xiami" msgstr "" #: ../src/ol_lrc_candidate_list.c:43 msgid "Title" msgstr "Titre" #: ../src/ol_lrc_candidate_list.c:48 msgid "Artist" msgstr "Artiste" #: ../src/ol_search_dialog.c:31 #, c-format msgid "Searching lyrics from %s..." msgstr "Recherche de paroles à partir de %s..." #: ../src/ol_search_dialog.c:32 msgid "Ooops, no lyric found :(" msgstr "Woops, pas de paroles trouvées =(" #: ../src/ol_search_dialog.c:33 #, c-format msgid "%d lyrics found :)" msgstr "%d paroles trouvées =)" #: ../src/ol_player_chooser.c:243 msgid "Supported players" msgstr "" #: ../src/ol_player_chooser.c:244 msgid "All players" msgstr "" #: ../src/ol_player_chooser.c:248 ../src/ol_player_chooser.c:319 #, fuzzy msgid "Choose a player to launch" msgstr "Assigner un fichier LRC" #: ../src/ol_player_chooser.c:264 #, fuzzy msgid "Use command:" msgstr "Commandes du Lec_teur:" #: ../src/ol_player_chooser.c:268 msgid "Launch" msgstr "" #: ../src/ol_player_chooser.c:281 msgid "Remember my choice" msgstr "" #: ../src/ol_player_chooser.c:387 #, c-format msgid "Failed to launch %s" msgstr "" #: ../src/ol_player_chooser.c:397 #, c-format msgid "Launching %s" msgstr "" #: ../src/ol_player_chooser.c:399 #, c-format msgid "" "OSD Lyrics is trying to launch and connect to %s. Please wait for a second." msgstr "" #: ../src/ol_player_chooser.c:510 #, fuzzy msgid "There is no supported player running" msgstr "Pas de lecteur pris en charge détecté, arrêt.\n" #: ../src/ol_player_chooser.c:511 #, fuzzy msgid "Please choose a player below to launch" msgstr "Assigner un fichier LRC" #: ../src/ol_player_chooser.c:515 #, c-format msgid "Failed to connect to %s" msgstr "" #: ../src/ol_player_chooser.c:517 #, c-format msgid "" "%s is not supported by OSD Lyrics, or not running. Please launch another " "player" msgstr "" #: ../src/ol_app_info.c:183 ../src/ol_app_info.c:193 msgid "Unnamed" msgstr "" #: ../src/ol_scroll_window.c:52 msgid "" "Drag to move the window\n" "Hold CTRL to seek" msgstr "" #: ../src/ol_scroll_window.c:53 msgid "Drag to move the window" msgstr "" #: ../data/dialogs.glade.h:2 #, no-c-format msgid "" " %t - Title\n" " %p - Artist\n" " %a - Album\n" " %n - Track number\n" " %f - File name (without extension)" msgstr "" " %t - Titre\n" " %p - Artiste\n" " %a - Album\n" " %n - Numéro de piste\n" " %f - Nom du fichier (sans extension)" #: ../data/dialogs.glade.h:8 #, no-c-format msgid "%a - Album" msgstr "%a - Album" #: ../data/dialogs.glade.h:10 #, no-c-format msgid "%f - Filename" msgstr "%f - Nom du fichier" #: ../data/dialogs.glade.h:12 #, no-c-format msgid "%n - Track number" msgstr "%n - Numéro de piste" #: ../data/dialogs.glade.h:14 #, no-c-format msgid "%p - Artist" msgstr "%p - Artiste" #: ../data/dialogs.glade.h:16 #, no-c-format msgid "%t - Title" msgstr "%t - Titre" #: ../data/dialogs.glade.h:17 msgid "Background" msgstr "" #: ../data/dialogs.glade.h:18 msgid "Download" msgstr "" #: ../data/dialogs.glade.h:19 msgid "Lyric _Alignment" msgstr "_Alignement des paroles" #: ../data/dialogs.glade.h:20 msgid "Lyric _Color" msgstr "_Couleur des paroles" #: ../data/dialogs.glade.h:21 msgid "Lyrics" msgstr "" #: ../data/dialogs.glade.h:22 msgid "Proxy" msgstr "" #: ../data/dialogs.glade.h:23 msgid "_Filename" msgstr "Nom de _fichier" #: ../data/dialogs.glade.h:24 msgid "_Path" msgstr "_Chemin" #: ../data/dialogs.glade.h:26 #, no-c-format msgid "A single \"%\" means the directory where the playing music is." msgstr "" "Un seul « % » indique le répertoire dans lequel se trouve la musique lue " "actuellement." #: ../data/dialogs.glade.h:27 msgid "Ac_tive color:" msgstr "" #: ../data/dialogs.glade.h:28 msgid "Acti_ve lyrics:" msgstr "Paroles acti_ves :" #: ../data/dialogs.glade.h:29 msgid "Adjust lyric delay by +0.2 s" msgstr "Retarder les paroles de +0.2 secondes" #: ../data/dialogs.glade.h:30 msgid "Adjust lyrics delay by -0.2 s" msgstr "Ajuster le délai des paroles de -0.2 s" #: ../data/dialogs.glade.h:31 msgid "Always" msgstr "" #: ../data/dialogs.glade.h:32 msgid "" "An OSD lyric show compatible with various media players and supports lyrics " "downloading.\n" msgstr "" "Un afficheur de paroles OSD compatible avec différents lecteurs de musique " "et prenant en charge le téléchargement de paroles.\n" #: ../data/dialogs.glade.h:34 msgid "Assign local LRC file to current music" msgstr "" "Assigner un fichier LRC sur votre ordinateur à la musique lue actuellement" #: ../data/dialogs.glade.h:35 msgid "Au_to-detect system settings" msgstr "" #: ../data/dialogs.glade.h:36 msgid "Auto la_unch player:" msgstr "La_ncement automatique du lecteur :" #: ../data/dialogs.glade.h:37 msgid "By lines" msgstr "" #: ../data/dialogs.glade.h:38 msgid "C_ustomize colors" msgstr "Personnaliser les co_uleurs" #: ../data/dialogs.glade.h:39 msgid "Choose LRC file to download" msgstr "Choisissez le fichier LRC à télécharger" #: ../data/dialogs.glade.h:40 msgid "Colo_r:" msgstr "" #: ../data/dialogs.glade.h:41 msgid "Color _Theme:" msgstr "Couleur du _Thème :" #: ../data/dialogs.glade.h:42 #, fuzzy msgid "Copyright 2009-2011 The OSD Lyrics project." msgstr "Copyright 2009 Le projet OSD Lyrics." #: ../data/dialogs.glade.h:44 msgid "D_on't ask me again" msgstr "_Ne me le redemande pas" #: ../data/dialogs.glade.h:45 msgid "Display mode:" msgstr "" #. In preference/download, automately download the most matched lyric #: ../data/dialogs.glade.h:47 msgid "Do_wnload the first candidate" msgstr "Té_lécharger le résultat le plus cohérent" #: ../data/dialogs.glade.h:48 msgid "Doc_k" msgstr "" #: ../data/dialogs.glade.h:49 msgid "Don't assign lyric to this music" msgstr "Ne pas assigner de paroles à cette musique" #: ../data/dialogs.glade.h:50 msgid "F_irst line:" msgstr "Prem_ière ligne" #: ../data/dialogs.glade.h:51 msgid "GPL v3" msgstr "GPL v3" #: ../data/dialogs.glade.h:52 msgid "HTTP" msgstr "" #: ../data/dialogs.glade.h:53 msgid "Hide the OSD Window" msgstr "Cacher la fenètre OSD" #: ../data/dialogs.glade.h:54 msgid "If locked, you can't move OSD window by mouse" msgstr "Si bloqué, impossible de déplacer la fenètre OSD avec la souris" #: ../data/dialogs.glade.h:55 msgid "" "If there are more than one lrc files matched with the search condition, " "download the first one without prompting the user." msgstr "" "S'il existe plus d'un fichier lrc qui correspond à la recherche, télécharger " "le premier automatiquement." #: ../data/dialogs.glade.h:56 msgid "Inactiv_e lyrics:" msgstr "Paroles inactiv_e :" #: ../data/dialogs.glade.h:57 #, fuzzy msgid "Lyric _sites:" msgstr "lignes de paroles" #: ../data/dialogs.glade.h:58 msgid "Lyric delay +" msgstr "Retarder les paroles +" #: ../data/dialogs.glade.h:59 msgid "Lyric delay -" msgstr "Retarder les paroles -" #: ../data/dialogs.glade.h:60 msgid "Lyric lines:" msgstr "lignes de paroles" #: ../data/dialogs.glade.h:61 msgid "Mode:" msgstr "Mode:" #: ../data/dialogs.glade.h:62 msgid "No _proxy" msgstr "" #: ../data/dialogs.glade.h:63 msgid "Nor_mal" msgstr "" #: ../data/dialogs.glade.h:64 msgid "OSD _mode" msgstr "" #: ../data/dialogs.glade.h:65 msgid "OS_D" msgstr "" #: ../data/dialogs.glade.h:66 msgid "O_pacity:" msgstr "" #: ../data/dialogs.glade.h:67 msgid "Opaque" msgstr "" #. The outline width of text in OSD window #: ../data/dialogs.glade.h:69 msgid "Out_line:" msgstr "Contour" #: ../data/dialogs.glade.h:70 msgid "Passwo_rd:" msgstr "" #: ../data/dialogs.glade.h:71 msgid "Pla_yer command:" msgstr "Commandes du Lec_teur:" #: ../data/dialogs.glade.h:72 msgid "Po_rt:" msgstr "" #: ../data/dialogs.glade.h:73 msgid "Preferences" msgstr "Préférences" #: ../data/dialogs.glade.h:74 msgid "SOCKS4" msgstr "" #: ../data/dialogs.glade.h:75 msgid "SOCKS5" msgstr "" #: ../data/dialogs.glade.h:76 msgid "Sc_rolling" msgstr "" #: ../data/dialogs.glade.h:77 msgid "Scroll _mode:" msgstr "" #: ../data/dialogs.glade.h:78 msgid "Scrolling _mode" msgstr "" #: ../data/dialogs.glade.h:79 msgid "Search lyrics" msgstr "Recherche de Paroles" #: ../data/dialogs.glade.h:80 msgid "Search lyrics from Internet" msgstr "Recherche de paroles depuis Internet" #: ../data/dialogs.glade.h:81 msgid "Show _bubble notification" msgstr "" #: ../data/dialogs.glade.h:82 msgid "Show passwor_d" msgstr "" #: ../data/dialogs.glade.h:83 msgid "Si_ngle line" msgstr "Simple lig_ne" #: ../data/dialogs.glade.h:84 msgid "Strong" msgstr "" #: ../data/dialogs.glade.h:85 msgid "T_ranslucent on mouse over" msgstr "T_ransparent au passage de la souris" #: ../data/dialogs.glade.h:86 msgid "The artist of the current track" msgstr "L'artiste de la piste en cours" #: ../data/dialogs.glade.h:87 msgid "The filename of the current track" msgstr "Le nom de fichier du morceau en cours" #: ../data/dialogs.glade.h:88 msgid "The number of the current track" msgstr "Le numéro de la piste en cours" #: ../data/dialogs.glade.h:89 msgid "The title of album of the current track" msgstr "Le titre de l'album de la piste en cours" #: ../data/dialogs.glade.h:90 msgid "The title of the current track" msgstr "Le titre de la piste en cours" #: ../data/dialogs.glade.h:91 msgid "Transparent" msgstr "" #: ../data/dialogs.glade.h:92 msgid "Use _manual settings" msgstr "" #: ../data/dialogs.glade.h:93 msgid "Weak" msgstr "" #: ../data/dialogs.glade.h:94 msgid "_Artist:" msgstr "_Artiste :" #: ../data/dialogs.glade.h:95 msgid "_Assign lyric..." msgstr "_Assigner paroles..." #: ../data/dialogs.glade.h:96 msgid "_Blur:" msgstr "" #: ../data/dialogs.glade.h:97 msgid "_Double lines" msgstr "_Double lignes" #: ../data/dialogs.glade.h:98 msgid "_Download" msgstr "_Téléchargement" #: ../data/dialogs.glade.h:99 msgid "_Font:" msgstr "_Police :" #. From witch web site to search and download lyrics #: ../data/dialogs.glade.h:101 msgid "_From:" msgstr "_Depuis :" #: ../data/dialogs.glade.h:102 msgid "_General" msgstr "_Général" #: ../data/dialogs.glade.h:103 msgid "_Hide" msgstr "Cac_her" #: ../data/dialogs.glade.h:104 msgid "_Host:" msgstr "" #: ../data/dialogs.glade.h:105 msgid "_Inactive color:" msgstr "" #: ../data/dialogs.glade.h:106 msgid "_Lock Position" msgstr "_Verrouiller Position" #: ../data/dialogs.glade.h:107 msgid "_Lyric Location" msgstr "Emplacement des paro_les" #: ../data/dialogs.glade.h:108 msgid "_Network" msgstr "" #: ../data/dialogs.glade.h:109 msgid "_No lyric" msgstr "_Pas de paroles" #: ../data/dialogs.glade.h:110 msgid "_OSD" msgstr "_OSD" #: ../data/dialogs.glade.h:111 msgid "_Scrolling" msgstr "" #: ../data/dialogs.glade.h:112 msgid "_Search" msgstr "_Recherche" #: ../data/dialogs.glade.h:113 msgid "_Search lyric..." msgstr "_Recherche de paroles..." #: ../data/dialogs.glade.h:114 msgid "_Second line:" msgstr "Deu_xième ligne" #: ../data/dialogs.glade.h:115 msgid "_Title:" msgstr "_Titre :" #: ../data/dialogs.glade.h:116 msgid "_Type:" msgstr "" #: ../data/dialogs.glade.h:117 msgid "_Username:" msgstr "" #: ../data/dialogs.glade.h:118 msgid "bottom" msgstr "bas" #: ../data/dialogs.glade.h:120 msgid "middle" msgstr "milieu" #: ../data/dialogs.glade.h:121 msgid "top" msgstr "haut" #: ../data/dialogs.glade.h:122 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" " BELATAR Mohammed Ousama https://launchpad.net/~bel3atar\n" " Floriang https://launchpad.net/~floriang\n" " Jeremy Moiny https://launchpad.net/~joliegrenouille2\n" " Nabil Psychoman https://launchpad.net/~bele-nyx\n" " Pierre Slamich https://launchpad.net/~pierre-slamich\n" " Thibault Févry https://launchpad.net/~thibaultfevry" #~ msgid "Searching lyrics" #~ msgstr "Recherche des paroles en cours" #~ msgid "Preview" #~ msgstr "Aperçu" #~ msgid "_Site:" #~ msgstr "_Site:" #~ msgid "None" #~ msgstr "Aucun" #~ msgid "Sogou" #~ msgstr "Sogou" #~ msgid "Qianqian" #~ msgstr "Qianqian" #~ msgid "_Width:" #~ msgstr "Lar_geur :" #~ msgid "_Lock" #~ msgstr "Verroui_ller" #~ msgid "Download _engine:" #~ msgstr "Mot_eur de téléchargement :" #~ msgid "_Download lyric" #~ msgstr "_Télécharger les paroles" #~ msgid "MiniLyrics" #~ msgstr "MiniLyrics" #~ msgid "Display mode" #~ msgstr "Mode d'affichage" #~ msgid "Choose witch site you want to download from." #~ msgstr "Choisissez le site d'origine des paroles." #~ msgid "OSD" #~ msgstr "Menu à l'écran (OSD)" #~ msgid "Scrolling" #~ msgstr "Défilement" #~ msgid "_Display notification" #~ msgstr "_Afficher notification" #~ msgid "Dock" #~ msgstr "Dock" #~ msgid "Normal" #~ msgstr "Standard" osdlyrics-0.5.5~rc1+dfsg1/po/insert-header.sin000066400000000000000000000012401345713535600212340ustar00rootroot00000000000000# Sed script that inserts the file called HEADER before the header entry. # # At each occurrence of a line starting with "msgid ", we execute the following # commands. At the first occurrence, insert the file. At the following # occurrences, do nothing. The distinction between the first and the following # occurrences is achieved by looking at the hold space. /^msgid /{ x # Test if the hold space is empty. s/m/m/ ta # Yes it was empty. First occurrence. Read the file. r HEADER # Output the file's contents by reading the next line. But don't lose the # current line while doing this. g N bb :a # The hold space was nonempty. Following occurrences. Do nothing. x :b } osdlyrics-0.5.5~rc1+dfsg1/po/it.po000066400000000000000000000415701345713535600167550ustar00rootroot00000000000000# Italian translations for l package. # Copyright (C) 2010 THE l'S COPYRIGHT HOLDER # This file is distributed under the same license as the l package. # Canonical OEM, 2010. # msgid "" msgstr "" "Project-Id-Version: l 10n\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-11-17 23:18+0800\n" "PO-Revision-Date: 2011-04-12 04:14+0000\n" "Last-Translator: Milo Casagrande \n" "Language-Team: Italian\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2011-06-04 13:40+0000\n" "X-Generator: Launchpad (build 13144)\n" #: ../src/ol_menu.c:119 msgid "LRC files" msgstr "Documenti LRC" #: ../src/ol_menu.c:125 msgid "Choose LRC file to assign" msgstr "Scegli il file LRC da assegnare" #: ../src/ol_main.c:60 msgid "" "The level of debug messages to log, can be 'none', 'error', 'debug', or " "'info'" msgstr "" "Il livello di messaggi di debug da registrare, può essere 'nessuno', " "'errori', 'debug', o 'info'" #: ../src/ol_main.c:62 msgid "Show version information" msgstr "" #: ../src/ol_main.c:147 msgid "Download failed" msgstr "Download non riuscito" #: ../src/ol_main.c:162 #, fuzzy, c-format msgid "Searching lyrics from %s" msgstr "Ricerco i testi da %s..." #: ../src/ol_main.c:185 msgid "Cannot create the lyric directory" msgstr "Impossibile creare la cartella dei testi" #: ../src/ol_main.c:201 msgid "Lyrics not found" msgstr "Testi non trovato" #: ../src/ol_main.c:507 #, c-format msgid "No supported player is running, exit.\n" msgstr "Nessun lettore supportato sta andando, uscita\n" #: ../src/ol_main.c:652 #, fuzzy msgid "" "debug level should be one of ``none'', ``error'', ``debug'', or ``info''" msgstr "" "Il livello di messaggi di debug da registrare, può essere 'nessuno', " "'errori', 'debug', o 'info'" #: ../src/ol_main.c:675 msgid "Another OSD Lyrics is running, exit." msgstr "Un altro OSD Lyrics è in esecuzione, esco." #: ../src/ol_trayicon.c:36 ../src/ol_notify.c:26 msgid "Unknown title" msgstr "Titolo sconosciuto" #: ../src/ol_trayicon.c:37 ../src/ol_notify.c:27 msgid "Unknown artist" msgstr "Artista sconosciuto" #: ../src/ol_trayicon.c:71 ../src/ol_trayicon.c:149 msgid "OSD Lyrics" msgstr "Testi OSD" #: ../src/ol_option.c:1631 msgid "Choose on startup" msgstr "" #. gtk_list_store_append (liststore, &iter); #. gtk_list_store_set (liststore, &iter, #. 0, "Customize", #. 1, "", #. -1); #: ../src/ol_option.c:1651 ../data/dialogs.glade.h:43 msgid "Customize" msgstr "Personalizza" #: ../src/ol_option.c:1713 msgid "Select a folder" msgstr "Seleziona una cartella" #: ../src/ol_lrc_fetch_ttplayer.c:274 msgid "ttPlayer" msgstr "" #: ../src/ol_lrc_fetch_xiami.c:237 msgid "Xiami" msgstr "" #: ../src/ol_lrc_candidate_list.c:43 msgid "Title" msgstr "Titolo" #: ../src/ol_lrc_candidate_list.c:48 msgid "Artist" msgstr "Artista" #: ../src/ol_search_dialog.c:31 #, c-format msgid "Searching lyrics from %s..." msgstr "Ricerco i testi da %s..." #: ../src/ol_search_dialog.c:32 msgid "Ooops, no lyric found :(" msgstr "Ops, nessun testo trovato :(" #: ../src/ol_search_dialog.c:33 #, c-format msgid "%d lyrics found :)" msgstr "%d testi trovati :)" #: ../src/ol_player_chooser.c:243 msgid "Supported players" msgstr "" #: ../src/ol_player_chooser.c:244 msgid "All players" msgstr "" #: ../src/ol_player_chooser.c:248 ../src/ol_player_chooser.c:319 #, fuzzy msgid "Choose a player to launch" msgstr "Scegli il file LRC da assegnare" #: ../src/ol_player_chooser.c:264 #, fuzzy msgid "Use command:" msgstr "Comando Pla_yer" #: ../src/ol_player_chooser.c:268 msgid "Launch" msgstr "" #: ../src/ol_player_chooser.c:281 msgid "Remember my choice" msgstr "" #: ../src/ol_player_chooser.c:387 #, c-format msgid "Failed to launch %s" msgstr "" #: ../src/ol_player_chooser.c:397 #, c-format msgid "Launching %s" msgstr "" #: ../src/ol_player_chooser.c:399 #, c-format msgid "" "OSD Lyrics is trying to launch and connect to %s. Please wait for a second." msgstr "" #: ../src/ol_player_chooser.c:510 #, fuzzy msgid "There is no supported player running" msgstr "Nessun lettore supportato sta andando, uscita\n" #: ../src/ol_player_chooser.c:511 #, fuzzy msgid "Please choose a player below to launch" msgstr "Scegli il file LRC da assegnare" #: ../src/ol_player_chooser.c:515 #, c-format msgid "Failed to connect to %s" msgstr "" #: ../src/ol_player_chooser.c:517 #, c-format msgid "" "%s is not supported by OSD Lyrics, or not running. Please launch another " "player" msgstr "" #: ../src/ol_app_info.c:183 ../src/ol_app_info.c:193 msgid "Unnamed" msgstr "" #: ../src/ol_scroll_window.c:52 msgid "" "Drag to move the window\n" "Hold CTRL to seek" msgstr "" #: ../src/ol_scroll_window.c:53 msgid "Drag to move the window" msgstr "" #: ../data/dialogs.glade.h:2 #, no-c-format msgid "" " %t - Title\n" " %p - Artist\n" " %a - Album\n" " %n - Track number\n" " %f - File name (without extension)" msgstr "" " %t - Titolo\n" "%p - Artista\n" "%a - Album\n" "%n - Numero di traccia\n" "%f - Nome File (senza estensione)\n" "\tNota: \tThere are line breaks here. Each one represents a line break. " "Start a new line in the equivalent position in the translation.\n" "\tNote: \tThere are leading/trailing spaces here. Each one represents a " "space character. Enter a space in the equivalent position in the translation." #: ../data/dialogs.glade.h:8 #, no-c-format msgid "%a - Album" msgstr "%a - Album" #: ../data/dialogs.glade.h:10 #, no-c-format msgid "%f - Filename" msgstr "%f - Nome file" #: ../data/dialogs.glade.h:12 #, no-c-format msgid "%n - Track number" msgstr "%n - Numero traccia" #: ../data/dialogs.glade.h:14 #, no-c-format msgid "%p - Artist" msgstr "%p - Artista" #: ../data/dialogs.glade.h:16 #, no-c-format msgid "%t - Title" msgstr "%t - Titolo" #: ../data/dialogs.glade.h:17 msgid "Background" msgstr "" #: ../data/dialogs.glade.h:18 msgid "Download" msgstr "" #: ../data/dialogs.glade.h:19 msgid "Lyric _Alignment" msgstr "Allineamento testo" #: ../data/dialogs.glade.h:20 msgid "Lyric _Color" msgstr "Colore testo" #: ../data/dialogs.glade.h:21 msgid "Lyrics" msgstr "" #: ../data/dialogs.glade.h:22 msgid "Proxy" msgstr "" #: ../data/dialogs.glade.h:23 msgid "_Filename" msgstr "Nome file" #: ../data/dialogs.glade.h:24 msgid "_Path" msgstr "Percorso" #: ../data/dialogs.glade.h:26 #, no-c-format msgid "A single \"%\" means the directory where the playing music is." msgstr "Un singolo \"%\" significa la cartella dove si trova la musica" #: ../data/dialogs.glade.h:27 msgid "Ac_tive color:" msgstr "" #: ../data/dialogs.glade.h:28 msgid "Acti_ve lyrics:" msgstr "Testi attivi" #: ../data/dialogs.glade.h:29 msgid "Adjust lyric delay by +0.2 s" msgstr "Regola il ritardo del testo di +0,2 s" #: ../data/dialogs.glade.h:30 msgid "Adjust lyrics delay by -0.2 s" msgstr "Regola il ritardo del testo di -0,2 s" #: ../data/dialogs.glade.h:31 msgid "Always" msgstr "" #: ../data/dialogs.glade.h:32 msgid "" "An OSD lyric show compatible with various media players and supports lyrics " "downloading.\n" msgstr "" "OSD lyric è compatibile con vari lettori multimediali e supporta lo " "scaricamento dei testi\n" #: ../data/dialogs.glade.h:34 msgid "Assign local LRC file to current music" msgstr "Assegna file locale LRC alla musica corrente" #: ../data/dialogs.glade.h:35 msgid "Au_to-detect system settings" msgstr "" #: ../data/dialogs.glade.h:36 msgid "Auto la_unch player:" msgstr "Avvia a_utomaticamente il player:" #: ../data/dialogs.glade.h:37 msgid "By lines" msgstr "" #: ../data/dialogs.glade.h:38 msgid "C_ustomize colors" msgstr "Personalizza colori" #: ../data/dialogs.glade.h:39 msgid "Choose LRC file to download" msgstr "Scegli il testo da scaricare" #: ../data/dialogs.glade.h:40 msgid "Colo_r:" msgstr "" #: ../data/dialogs.glade.h:41 msgid "Color _Theme:" msgstr "Tema colore" #: ../data/dialogs.glade.h:42 #, fuzzy msgid "Copyright 2009-2011 The OSD Lyrics project." msgstr "Copyright 2009 The OSD Lyrics project." #: ../data/dialogs.glade.h:44 msgid "D_on't ask me again" msgstr "N_on chiedermelo più" #: ../data/dialogs.glade.h:45 msgid "Display mode:" msgstr "" #. In preference/download, automately download the most matched lyric #: ../data/dialogs.glade.h:47 msgid "Do_wnload the first candidate" msgstr "Scarica il primo candidato" #: ../data/dialogs.glade.h:48 msgid "Doc_k" msgstr "" #: ../data/dialogs.glade.h:49 msgid "Don't assign lyric to this music" msgstr "Non assegnare un testo a questa musica" #: ../data/dialogs.glade.h:50 msgid "F_irst line:" msgstr "Prima linea" #: ../data/dialogs.glade.h:51 msgid "GPL v3" msgstr "GPL v3" #: ../data/dialogs.glade.h:52 msgid "HTTP" msgstr "" #: ../data/dialogs.glade.h:53 msgid "Hide the OSD Window" msgstr "Nascondi la finestra OSD" #: ../data/dialogs.glade.h:54 msgid "If locked, you can't move OSD window by mouse" msgstr "se bloccato, non puoi muovere la finestra OSD con il mouse" #: ../data/dialogs.glade.h:55 msgid "" "If there are more than one lrc files matched with the search condition, " "download the first one without prompting the user." msgstr "" "Se piu' di un file Irc combacia con la condizione di ricerca, scarico il " "primo senza suggerire l'utente" #: ../data/dialogs.glade.h:56 msgid "Inactiv_e lyrics:" msgstr "Testi inattivi" #: ../data/dialogs.glade.h:57 #, fuzzy msgid "Lyric _sites:" msgstr "Riga del testo" #: ../data/dialogs.glade.h:58 msgid "Lyric delay +" msgstr "Ritardo del testo +" #: ../data/dialogs.glade.h:59 msgid "Lyric delay -" msgstr "Ritardo del testo -" #: ../data/dialogs.glade.h:60 msgid "Lyric lines:" msgstr "Riga del testo" #: ../data/dialogs.glade.h:61 msgid "Mode:" msgstr "Modalita':" #: ../data/dialogs.glade.h:62 msgid "No _proxy" msgstr "" #: ../data/dialogs.glade.h:63 msgid "Nor_mal" msgstr "" #: ../data/dialogs.glade.h:64 msgid "OSD _mode" msgstr "" #: ../data/dialogs.glade.h:65 msgid "OS_D" msgstr "" #: ../data/dialogs.glade.h:66 msgid "O_pacity:" msgstr "" #: ../data/dialogs.glade.h:67 msgid "Opaque" msgstr "" #. The outline width of text in OSD window #: ../data/dialogs.glade.h:69 msgid "Out_line:" msgstr "Profilo" #: ../data/dialogs.glade.h:70 msgid "Passwo_rd:" msgstr "" #: ../data/dialogs.glade.h:71 msgid "Pla_yer command:" msgstr "Comando Pla_yer" #: ../data/dialogs.glade.h:72 msgid "Po_rt:" msgstr "" #: ../data/dialogs.glade.h:73 msgid "Preferences" msgstr "Preferenze" #: ../data/dialogs.glade.h:74 msgid "SOCKS4" msgstr "" #: ../data/dialogs.glade.h:75 msgid "SOCKS5" msgstr "" #: ../data/dialogs.glade.h:76 msgid "Sc_rolling" msgstr "" #: ../data/dialogs.glade.h:77 msgid "Scroll _mode:" msgstr "" #: ../data/dialogs.glade.h:78 msgid "Scrolling _mode" msgstr "" #: ../data/dialogs.glade.h:79 msgid "Search lyrics" msgstr "Cerca testi" #: ../data/dialogs.glade.h:80 msgid "Search lyrics from Internet" msgstr "Cerca testi da Internet" #: ../data/dialogs.glade.h:81 msgid "Show _bubble notification" msgstr "" #: ../data/dialogs.glade.h:82 msgid "Show passwor_d" msgstr "" #: ../data/dialogs.glade.h:83 msgid "Si_ngle line" msgstr "Linea singola" #: ../data/dialogs.glade.h:84 msgid "Strong" msgstr "" #: ../data/dialogs.glade.h:85 msgid "T_ranslucent on mouse over" msgstr "Traslucido quando passa il mouse" #: ../data/dialogs.glade.h:86 msgid "The artist of the current track" msgstr "L'artista della traccia corrente" #: ../data/dialogs.glade.h:87 msgid "The filename of the current track" msgstr "Il nome del file della traccia corrente" #: ../data/dialogs.glade.h:88 msgid "The number of the current track" msgstr "Il numero della traccia corrente" #: ../data/dialogs.glade.h:89 msgid "The title of album of the current track" msgstr "Il titolo dell'album della traccia corrente" #: ../data/dialogs.glade.h:90 msgid "The title of the current track" msgstr "Il titolo della traccia corrente" #: ../data/dialogs.glade.h:91 msgid "Transparent" msgstr "" #: ../data/dialogs.glade.h:92 msgid "Use _manual settings" msgstr "" #: ../data/dialogs.glade.h:93 msgid "Weak" msgstr "" #: ../data/dialogs.glade.h:94 msgid "_Artist:" msgstr "_Artista:" #: ../data/dialogs.glade.h:95 msgid "_Assign lyric..." msgstr "_Assegna testo..." #: ../data/dialogs.glade.h:96 msgid "_Blur:" msgstr "" #: ../data/dialogs.glade.h:97 msgid "_Double lines" msgstr "Linea doppia" #: ../data/dialogs.glade.h:98 msgid "_Download" msgstr "Scarica" #: ../data/dialogs.glade.h:99 msgid "_Font:" msgstr "Tipo di carattere" #. From witch web site to search and download lyrics #: ../data/dialogs.glade.h:101 msgid "_From:" msgstr "_Da:" #: ../data/dialogs.glade.h:102 msgid "_General" msgstr "_Generale" #: ../data/dialogs.glade.h:103 msgid "_Hide" msgstr "Nascondi" #: ../data/dialogs.glade.h:104 msgid "_Host:" msgstr "" #: ../data/dialogs.glade.h:105 msgid "_Inactive color:" msgstr "" #: ../data/dialogs.glade.h:106 msgid "_Lock Position" msgstr "B_locca Posizione" #: ../data/dialogs.glade.h:107 msgid "_Lyric Location" msgstr "Posizione testo" #: ../data/dialogs.glade.h:108 msgid "_Network" msgstr "" #: ../data/dialogs.glade.h:109 msgid "_No lyric" msgstr "_Nessun testo" #: ../data/dialogs.glade.h:110 msgid "_OSD" msgstr "OSD" #: ../data/dialogs.glade.h:111 msgid "_Scrolling" msgstr "" #: ../data/dialogs.glade.h:112 msgid "_Search" msgstr "Cerca" #: ../data/dialogs.glade.h:113 msgid "_Search lyric..." msgstr "Cerca testo..." #: ../data/dialogs.glade.h:114 msgid "_Second line:" msgstr "Seconda linea" #: ../data/dialogs.glade.h:115 msgid "_Title:" msgstr "_Titolo" #: ../data/dialogs.glade.h:116 msgid "_Type:" msgstr "" #: ../data/dialogs.glade.h:117 msgid "_Username:" msgstr "" #: ../data/dialogs.glade.h:118 msgid "bottom" msgstr "Basso" #: ../data/dialogs.glade.h:120 msgid "middle" msgstr "Medio" #: ../data/dialogs.glade.h:121 msgid "top" msgstr "Alto" #: ../data/dialogs.glade.h:122 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" " Davide Lunghi https://launchpad.net/~davide-lunghi\n" " Martino Barbon https://launchpad.net/~martins999\n" " Milo Casagrande https://launchpad.net/~milo\n" " morphine https://launchpad.net/~maledettopc\n" " simone.sandri https://launchpad.net/~lexluxsox" #~ msgid "Searching lyrics" #~ msgstr "Ricerca testi" #~ msgid "Preview" #~ msgstr "Anteprima" #~ msgid "_Site:" #~ msgstr "_Sito" #~ msgid "None" #~ msgstr "Nulla" #~ msgid "Sogou" #~ msgstr "Sogou" #~ msgid "Qianqian" #~ msgstr "Qianqian" #~ msgid "_Lock" #~ msgstr "Blocca" #~ msgid "_Width:" #~ msgstr "Larghezza" #~ msgid "Download _engine:" #~ msgstr "Motore di ricerca" #~ msgid "MiniLyrics" #~ msgstr "MiniLyrics" #~ msgid "Choose witch site you want to download from." #~ msgstr "Scegli da quale sito scaricare." #~ msgid "Normal" #~ msgstr "Standard" #~ msgid "OSD" #~ msgstr "OSD" #~ msgid "Scrolling" #~ msgstr "Scorrimento verticale" #~ msgid "_Display notification" #~ msgstr "_Mostra notifica" #~ msgid "Dock" #~ msgstr "Barra Principale" #~ msgid "Display mode" #~ msgstr "Modalita' Display" #~ msgid "Eject" #~ msgstr "Espelli" #~ msgid "Trash" #~ msgstr "Cestino" #~ msgid "%n item in trash" #~ msgid_plural "%n items in trash" #~ msgstr[0] "%n elemento nel cestino" #~ msgstr[1] "%n elementi nel cestino" #~ msgid "Empty Trash" #~ msgstr "Svuota cestino" #~ msgid "Keep In Launcher" #~ msgstr "Mantieni nel Launcher" #~ msgid "Remove From Launcher" #~ msgstr "Rimuovi dal Launcher" #~ msgid "Quit" #~ msgstr "Esci" #~ msgid "Workspaces" #~ msgstr "Spazi di lavoro" #~ msgid "Find Media Apps" #~ msgstr "Trova applicazioni multimediali" #~ msgid "Find Internet Apps" #~ msgstr "Trova applicazioni per Internet" #~ msgid "Find More Apps" #~ msgstr "Trova altre applicazioni" #~ msgid "Find Files" #~ msgstr "Trova file" #~ msgid "Browse the Web" #~ msgstr "Esplora il web" #~ msgid "View Photos" #~ msgstr "Visualizza foto" #~ msgid "Check Email" #~ msgstr "Controlla email" #~ msgid "Listen to Music" #~ msgstr "Ascolta musica" #~ msgid "See %1 more results" #~ msgstr "Mostra altri %1 risultati" #~ msgid "See fewer results" #~ msgstr "Mostra meno risultati" #~ msgid "Type" #~ msgstr "Tipo" #~ msgid "%n item(s) in trash" #~ msgstr "%n oggetto/i nel cestino" #~ msgid "Search for %1" #~ msgstr "Cerca %1" #~ msgid "Search" #~ msgstr "Cerca" #~ msgid "Open" #~ msgstr "Apri" #~ msgid "Workspace Switcher" #~ msgstr "Selettore spazio di lavoro" #~ msgid "Empty all items from Trash?" #~ msgstr "Svuotare il cestino da tutti gli oggetti presenti?" #~ msgid "All items in the Trash will be permanently deleted." #~ msgstr "" #~ "Tutti gli oggetti nel cestino verranno eliminati in modo definitivo." #~ msgid "Keep in Launcher" #~ msgstr "Mantieni nei lanciatori" #~ msgid "Office" #~ msgstr "Ufficio" #~ msgid "Games" #~ msgstr "Giochi" #~ msgid "Email & Chat" #~ msgstr "Email e chat" #~ msgid "Music" #~ msgstr "Musica" #~ msgid "Photos & Videos" #~ msgstr "Foto e video" #~ msgid "Web" #~ msgstr "Web" #~ msgid "Get New Apps" #~ msgstr "Nuove applicazioni" #~ msgid "Files & Folders" #~ msgstr "File e cartelle" osdlyrics-0.5.5~rc1+dfsg1/po/ms.po000066400000000000000000000342671345713535600167650ustar00rootroot00000000000000# Malay translation for osd-lyrics # Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010 # This file is distributed under the same license as the osd-lyrics package. # FIRST AUTHOR , 2010. # msgid "" msgstr "" "Project-Id-Version: osd-lyrics\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-11-17 23:18+0800\n" "PO-Revision-Date: 2010-10-21 03:15+0000\n" "Last-Translator: abuyop \n" "Language-Team: Malay \n" "Language: ms\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2011-06-04 13:40+0000\n" "X-Generator: Launchpad (build 13144)\n" #: ../src/ol_menu.c:119 msgid "LRC files" msgstr "Fail LRC" #: ../src/ol_menu.c:125 msgid "Choose LRC file to assign" msgstr "Pilih fail LRC untuk ditentukan" #: ../src/ol_main.c:60 msgid "" "The level of debug messages to log, can be 'none', 'error', 'debug', or " "'info'" msgstr "" "Aras mesej nyahpepijat untuk dilog, boleh jadi 'tiada', 'ralat', " "'nyapepijat', atau 'info'" #: ../src/ol_main.c:62 msgid "Show version information" msgstr "" #: ../src/ol_main.c:147 msgid "Download failed" msgstr "Muat turun gagal" #: ../src/ol_main.c:162 #, fuzzy, c-format msgid "Searching lyrics from %s" msgstr "Menggelintar lirik dari %s..." #: ../src/ol_main.c:185 msgid "Cannot create the lyric directory" msgstr "Tidak dapat mencipta direktori lirik" #: ../src/ol_main.c:201 msgid "Lyrics not found" msgstr "Lirik tidak ditemui" #: ../src/ol_main.c:507 #, c-format msgid "No supported player is running, exit.\n" msgstr "Pemain yang disokong tidak dijalankan, tutup.\n" #: ../src/ol_main.c:652 #, fuzzy msgid "" "debug level should be one of ``none'', ``error'', ``debug'', or ``info''" msgstr "" "Aras mesej nyahpepijat untuk dilog, boleh jadi 'tiada', 'ralat', " "'nyapepijat', atau 'info'" #: ../src/ol_main.c:675 msgid "Another OSD Lyrics is running, exit." msgstr "Lirik OSD yang lain sedang dijalankan, tutup." #: ../src/ol_trayicon.c:36 ../src/ol_notify.c:26 msgid "Unknown title" msgstr "Tajuk tidak diketahui" #: ../src/ol_trayicon.c:37 ../src/ol_notify.c:27 msgid "Unknown artist" msgstr "Artis tidak diketahui" #: ../src/ol_trayicon.c:71 ../src/ol_trayicon.c:149 msgid "OSD Lyrics" msgstr "Lirik OSD" #: ../src/ol_option.c:1631 msgid "Choose on startup" msgstr "" #. gtk_list_store_append (liststore, &iter); #. gtk_list_store_set (liststore, &iter, #. 0, "Customize", #. 1, "", #. -1); #: ../src/ol_option.c:1651 ../data/dialogs.glade.h:43 msgid "Customize" msgstr "Suaikan" #: ../src/ol_option.c:1713 msgid "Select a folder" msgstr "Pilih folder" #: ../src/ol_lrc_fetch_ttplayer.c:274 msgid "ttPlayer" msgstr "" #: ../src/ol_lrc_fetch_xiami.c:237 msgid "Xiami" msgstr "" #: ../src/ol_lrc_candidate_list.c:43 msgid "Title" msgstr "Tajuk" #: ../src/ol_lrc_candidate_list.c:48 msgid "Artist" msgstr "Artis" #: ../src/ol_search_dialog.c:31 #, c-format msgid "Searching lyrics from %s..." msgstr "Menggelintar lirik dari %s..." #: ../src/ol_search_dialog.c:32 msgid "Ooops, no lyric found :(" msgstr "Maaf, tiada lirik ditemui :(" #: ../src/ol_search_dialog.c:33 #, c-format msgid "%d lyrics found :)" msgstr "%d lirik ditemui :)" #: ../src/ol_player_chooser.c:243 msgid "Supported players" msgstr "" #: ../src/ol_player_chooser.c:244 msgid "All players" msgstr "" #: ../src/ol_player_chooser.c:248 ../src/ol_player_chooser.c:319 #, fuzzy msgid "Choose a player to launch" msgstr "Pilih fail LRC untuk ditentukan" #: ../src/ol_player_chooser.c:264 #, fuzzy msgid "Use command:" msgstr "Perintah pe_main:" #: ../src/ol_player_chooser.c:268 msgid "Launch" msgstr "" #: ../src/ol_player_chooser.c:281 msgid "Remember my choice" msgstr "" #: ../src/ol_player_chooser.c:387 #, c-format msgid "Failed to launch %s" msgstr "" #: ../src/ol_player_chooser.c:397 #, c-format msgid "Launching %s" msgstr "" #: ../src/ol_player_chooser.c:399 #, c-format msgid "" "OSD Lyrics is trying to launch and connect to %s. Please wait for a second." msgstr "" #: ../src/ol_player_chooser.c:510 #, fuzzy msgid "There is no supported player running" msgstr "Pemain yang disokong tidak dijalankan, tutup.\n" #: ../src/ol_player_chooser.c:511 #, fuzzy msgid "Please choose a player below to launch" msgstr "Pilih fail LRC untuk ditentukan" #: ../src/ol_player_chooser.c:515 #, c-format msgid "Failed to connect to %s" msgstr "" #: ../src/ol_player_chooser.c:517 #, c-format msgid "" "%s is not supported by OSD Lyrics, or not running. Please launch another " "player" msgstr "" #: ../src/ol_app_info.c:183 ../src/ol_app_info.c:193 msgid "Unnamed" msgstr "" #: ../src/ol_scroll_window.c:52 msgid "" "Drag to move the window\n" "Hold CTRL to seek" msgstr "" #: ../src/ol_scroll_window.c:53 msgid "Drag to move the window" msgstr "" #: ../data/dialogs.glade.h:2 #, no-c-format msgid "" " %t - Title\n" " %p - Artist\n" " %a - Album\n" " %n - Track number\n" " %f - File name (without extension)" msgstr "" " %t - Tajuk\n" " %p - Artis\n" " %a - Ãlbum\n" " %n - Nombor trek\n" " %f - Nama fail (tanpa sambungan)" #: ../data/dialogs.glade.h:8 #, no-c-format msgid "%a - Album" msgstr "%a - Ãlbum" #: ../data/dialogs.glade.h:10 #, no-c-format msgid "%f - Filename" msgstr "%f - Nama Fail" #: ../data/dialogs.glade.h:12 #, no-c-format msgid "%n - Track number" msgstr "%n - Nombor trek" #: ../data/dialogs.glade.h:14 #, no-c-format msgid "%p - Artist" msgstr "%p - Artis" #: ../data/dialogs.glade.h:16 #, no-c-format msgid "%t - Title" msgstr "%t - Tajuk" #: ../data/dialogs.glade.h:17 msgid "Background" msgstr "" #: ../data/dialogs.glade.h:18 msgid "Download" msgstr "" #: ../data/dialogs.glade.h:19 msgid "Lyric _Alignment" msgstr "_jajaran Lirik" #: ../data/dialogs.glade.h:20 msgid "Lyric _Color" msgstr "Wa_rna Lirik" #: ../data/dialogs.glade.h:21 msgid "Lyrics" msgstr "" #: ../data/dialogs.glade.h:22 msgid "Proxy" msgstr "" #: ../data/dialogs.glade.h:23 msgid "_Filename" msgstr "_Nama Fail" #: ../data/dialogs.glade.h:24 msgid "_Path" msgstr "_Laluan" #: ../data/dialogs.glade.h:26 #, no-c-format msgid "A single \"%\" means the directory where the playing music is." msgstr "\"%\" tunggal menunjukkan dimana direktori muzik dimainkan." #: ../data/dialogs.glade.h:27 msgid "Ac_tive color:" msgstr "" #: ../data/dialogs.glade.h:28 msgid "Acti_ve lyrics:" msgstr "Lirik akti_f:" #: ../data/dialogs.glade.h:29 msgid "Adjust lyric delay by +0.2 s" msgstr "Laras lengahan lirik ke +0.2 s" #: ../data/dialogs.glade.h:30 msgid "Adjust lyrics delay by -0.2 s" msgstr "Laras lengahan lirik ke -0.2 s" #: ../data/dialogs.glade.h:31 msgid "Always" msgstr "" #: ../data/dialogs.glade.h:32 msgid "" "An OSD lyric show compatible with various media players and supports lyrics " "downloading.\n" msgstr "" "Paparan Lirik OSD dengan pelbagai jenis pemain media dan menyokong muat " "turun lirik.\n" #: ../data/dialogs.glade.h:34 msgid "Assign local LRC file to current music" msgstr "Tetapkan fail lirik setempat dengan muzik semasa" #: ../data/dialogs.glade.h:35 msgid "Au_to-detect system settings" msgstr "" #: ../data/dialogs.glade.h:36 msgid "Auto la_unch player:" msgstr "Lancar pemain secara automatik:" #: ../data/dialogs.glade.h:37 msgid "By lines" msgstr "" #: ../data/dialogs.glade.h:38 msgid "C_ustomize colors" msgstr "Sua_ikan warna" #: ../data/dialogs.glade.h:39 msgid "Choose LRC file to download" msgstr "Pilih fail LRC untuk dimuat turun" #: ../data/dialogs.glade.h:40 msgid "Colo_r:" msgstr "" #: ../data/dialogs.glade.h:41 msgid "Color _Theme:" msgstr "_Warna Tema:" #: ../data/dialogs.glade.h:42 #, fuzzy msgid "Copyright 2009-2011 The OSD Lyrics project." msgstr "Hakcipta 2010 Projek OSD Lyrics." #: ../data/dialogs.glade.h:44 msgid "D_on't ask me again" msgstr "Jan_gan tanya saya lagi" #: ../data/dialogs.glade.h:45 msgid "Display mode:" msgstr "" #. In preference/download, automately download the most matched lyric #: ../data/dialogs.glade.h:47 msgid "Do_wnload the first candidate" msgstr "Muat turun pilihan pertama" #: ../data/dialogs.glade.h:48 msgid "Doc_k" msgstr "" #: ../data/dialogs.glade.h:49 msgid "Don't assign lyric to this music" msgstr "Jangan tetapkan lirik kepada muzik ini" #: ../data/dialogs.glade.h:50 msgid "F_irst line:" msgstr "_Baris pertama:" #: ../data/dialogs.glade.h:51 msgid "GPL v3" msgstr "GPL v3" #: ../data/dialogs.glade.h:52 msgid "HTTP" msgstr "" #: ../data/dialogs.glade.h:53 msgid "Hide the OSD Window" msgstr "Sembunyi Tetingkap OSD" #: ../data/dialogs.glade.h:54 msgid "If locked, you can't move OSD window by mouse" msgstr "" "Jika dikunci, anda tidak boleh alihkan tetingkap OSD menggunakan tetikus" #: ../data/dialogs.glade.h:55 msgid "" "If there are more than one lrc files matched with the search condition, " "download the first one without prompting the user." msgstr "" "Jika terdapat lebih dari satu fail lrc yang sepadan dengan syarat " "penggelintaran, muat turun yang pertama dahulu tanpa maklumkan pengguna." #: ../data/dialogs.glade.h:56 msgid "Inactiv_e lyrics:" msgstr "Lirik ti_dak aktif:" #: ../data/dialogs.glade.h:57 #, fuzzy msgid "Lyric _sites:" msgstr "Baris lirik:" #: ../data/dialogs.glade.h:58 msgid "Lyric delay +" msgstr "Lengah lirik +" #: ../data/dialogs.glade.h:59 msgid "Lyric delay -" msgstr "Lengah lirik -" #: ../data/dialogs.glade.h:60 msgid "Lyric lines:" msgstr "Baris lirik:" #: ../data/dialogs.glade.h:61 msgid "Mode:" msgstr "" #: ../data/dialogs.glade.h:62 msgid "No _proxy" msgstr "" #: ../data/dialogs.glade.h:63 msgid "Nor_mal" msgstr "" #: ../data/dialogs.glade.h:64 msgid "OSD _mode" msgstr "" #: ../data/dialogs.glade.h:65 msgid "OS_D" msgstr "" #: ../data/dialogs.glade.h:66 msgid "O_pacity:" msgstr "" #: ../data/dialogs.glade.h:67 msgid "Opaque" msgstr "" #. The outline width of text in OSD window #: ../data/dialogs.glade.h:69 msgid "Out_line:" msgstr "Garis Bentuk:" #: ../data/dialogs.glade.h:70 msgid "Passwo_rd:" msgstr "" #: ../data/dialogs.glade.h:71 msgid "Pla_yer command:" msgstr "Perintah pe_main:" #: ../data/dialogs.glade.h:72 msgid "Po_rt:" msgstr "" #: ../data/dialogs.glade.h:73 msgid "Preferences" msgstr "Keutamaan" #: ../data/dialogs.glade.h:74 msgid "SOCKS4" msgstr "" #: ../data/dialogs.glade.h:75 msgid "SOCKS5" msgstr "" #: ../data/dialogs.glade.h:76 msgid "Sc_rolling" msgstr "" #: ../data/dialogs.glade.h:77 msgid "Scroll _mode:" msgstr "" #: ../data/dialogs.glade.h:78 msgid "Scrolling _mode" msgstr "" #: ../data/dialogs.glade.h:79 msgid "Search lyrics" msgstr "Gelintar lirik" #: ../data/dialogs.glade.h:80 msgid "Search lyrics from Internet" msgstr "Gelintar lirik dari internet" #: ../data/dialogs.glade.h:81 msgid "Show _bubble notification" msgstr "" #: ../data/dialogs.glade.h:82 msgid "Show passwor_d" msgstr "" #: ../data/dialogs.glade.h:83 msgid "Si_ngle line" msgstr "S_atu baris" #: ../data/dialogs.glade.h:84 msgid "Strong" msgstr "" #: ../data/dialogs.glade.h:85 msgid "T_ranslucent on mouse over" msgstr "L_utsinar bila penuding diatas" #: ../data/dialogs.glade.h:86 msgid "The artist of the current track" msgstr "Artis bagi trek semasa" #: ../data/dialogs.glade.h:87 msgid "The filename of the current track" msgstr "Nama fail bagi trek semasa" #: ../data/dialogs.glade.h:88 msgid "The number of the current track" msgstr "Nombor trek semasa" #: ../data/dialogs.glade.h:89 msgid "The title of album of the current track" msgstr "Tajuk album bagi trek semasa" #: ../data/dialogs.glade.h:90 msgid "The title of the current track" msgstr "Tajuk trek semasa" #: ../data/dialogs.glade.h:91 msgid "Transparent" msgstr "" #: ../data/dialogs.glade.h:92 msgid "Use _manual settings" msgstr "" #: ../data/dialogs.glade.h:93 msgid "Weak" msgstr "" #: ../data/dialogs.glade.h:94 msgid "_Artist:" msgstr "_Artis:" #: ../data/dialogs.glade.h:95 msgid "_Assign lyric..." msgstr "_Tetapkan lirik..." #: ../data/dialogs.glade.h:96 msgid "_Blur:" msgstr "" #: ../data/dialogs.glade.h:97 msgid "_Double lines" msgstr "_Dua baris" #: ../data/dialogs.glade.h:98 msgid "_Download" msgstr "_Muat Turun" #: ../data/dialogs.glade.h:99 msgid "_Font:" msgstr "_Fon:" #. From witch web site to search and download lyrics #: ../data/dialogs.glade.h:101 msgid "_From:" msgstr "_Dari:" #: ../data/dialogs.glade.h:102 msgid "_General" msgstr "_Am" #: ../data/dialogs.glade.h:103 msgid "_Hide" msgstr "_Sembunyi" #: ../data/dialogs.glade.h:104 msgid "_Host:" msgstr "" #: ../data/dialogs.glade.h:105 msgid "_Inactive color:" msgstr "" #: ../data/dialogs.glade.h:106 msgid "_Lock Position" msgstr "_Kunci Kedudukan" #: ../data/dialogs.glade.h:107 msgid "_Lyric Location" msgstr "_Lokasi Lirik" #: ../data/dialogs.glade.h:108 msgid "_Network" msgstr "" #: ../data/dialogs.glade.h:109 msgid "_No lyric" msgstr "_Tiada lirik" #: ../data/dialogs.glade.h:110 msgid "_OSD" msgstr "_OSD" #: ../data/dialogs.glade.h:111 msgid "_Scrolling" msgstr "" #: ../data/dialogs.glade.h:112 msgid "_Search" msgstr "_Gelintar" #: ../data/dialogs.glade.h:113 msgid "_Search lyric..." msgstr "_Gelintar lirik..." #: ../data/dialogs.glade.h:114 msgid "_Second line:" msgstr "Ba_ris kedua:" #: ../data/dialogs.glade.h:115 msgid "_Title:" msgstr "_Tajuk:" #: ../data/dialogs.glade.h:116 msgid "_Type:" msgstr "" #: ../data/dialogs.glade.h:117 msgid "_Username:" msgstr "" #: ../data/dialogs.glade.h:118 msgid "bottom" msgstr "bawah" #: ../data/dialogs.glade.h:120 msgid "middle" msgstr "tengah" #: ../data/dialogs.glade.h:121 msgid "top" msgstr "atas" #: ../data/dialogs.glade.h:122 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" " abuyop https://launchpad.net/~abuyop" #~ msgid "Searching lyrics" #~ msgstr "Menggelintar lirik" #~ msgid "Preview" #~ msgstr "Pratonton" #~ msgid "None" #~ msgstr "Tiada" #~ msgid "Qianqian" #~ msgstr "Qianqian" #~ msgid "Sogou" #~ msgstr "Sogou" #~ msgid "MiniLyrics" #~ msgstr "MiniLyrics" #~ msgid "Display mode" #~ msgstr "Mod paparan judul" #~ msgid "Choose witch site you want to download from." #~ msgstr "Pilih laman yang anda ingin muat turun lirik." #~ msgid "Download _engine:" #~ msgstr "_Enjin muat turun:" #~ msgid "OSD" #~ msgstr "_OSD" #~ msgid "Scrolling" #~ msgstr "Penatalan" #~ msgid "_Display notification" #~ msgstr "_Papar pemberitahuan" #~ msgid "_Width:" #~ msgstr "_Lebar:" osdlyrics-0.5.5~rc1+dfsg1/po/pt_BR.po000066400000000000000000000356711345713535600173540ustar00rootroot00000000000000# Brazilian Portuguese translation for osd-lyrics # Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010 # This file is distributed under the same license as the osd-lyrics package. # FIRST AUTHOR , 2010. # msgid "" msgstr "" "Project-Id-Version: osd-lyrics\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-11-17 23:18+0800\n" "PO-Revision-Date: 2010-08-30 16:48+0000\n" "Last-Translator: Guilherme Lindner \n" "Language-Team: Brazilian Portuguese \n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Launchpad-Export-Date: 2011-06-04 13:40+0000\n" "X-Generator: Launchpad (build 13144)\n" #: ../src/ol_menu.c:119 msgid "LRC files" msgstr "Arquivo LRC" #: ../src/ol_menu.c:125 msgid "Choose LRC file to assign" msgstr "Escolha o arquivo LRC para atribuir" #: ../src/ol_main.c:60 msgid "" "The level of debug messages to log, can be 'none', 'error', 'debug', or " "'info'" msgstr "" "O nível das mensagens do log de depuração, podem ser 'none', 'error', " "'debug', ou 'info'" #: ../src/ol_main.c:62 msgid "Show version information" msgstr "" #: ../src/ol_main.c:147 msgid "Download failed" msgstr "Falha ao baixar" #: ../src/ol_main.c:162 #, fuzzy, c-format msgid "Searching lyrics from %s" msgstr "Procurando por letras de %s..." #: ../src/ol_main.c:185 msgid "Cannot create the lyric directory" msgstr "Não é possível criar a pasta da letra" #: ../src/ol_main.c:201 msgid "Lyrics not found" msgstr "Letras não encontradas" #: ../src/ol_main.c:507 #, c-format msgid "No supported player is running, exit.\n" msgstr "Player em execução não suportado, sair.\n" #: ../src/ol_main.c:652 #, fuzzy msgid "" "debug level should be one of ``none'', ``error'', ``debug'', or ``info''" msgstr "" "O nível das mensagens do log de depuração, podem ser 'none', 'error', " "'debug', ou 'info'" #: ../src/ol_main.c:675 msgid "Another OSD Lyrics is running, exit." msgstr "Outro Lyrics OSD está sendo executado, sair." #: ../src/ol_trayicon.c:36 ../src/ol_notify.c:26 msgid "Unknown title" msgstr "Título desconhecido" #: ../src/ol_trayicon.c:37 ../src/ol_notify.c:27 msgid "Unknown artist" msgstr "Artista desconhecido" #: ../src/ol_trayicon.c:71 ../src/ol_trayicon.c:149 msgid "OSD Lyrics" msgstr "Letras OSD (exibição na tela)" #: ../src/ol_option.c:1631 msgid "Choose on startup" msgstr "" #. gtk_list_store_append (liststore, &iter); #. gtk_list_store_set (liststore, &iter, #. 0, "Customize", #. 1, "", #. -1); #: ../src/ol_option.c:1651 ../data/dialogs.glade.h:43 msgid "Customize" msgstr "Personalize" #: ../src/ol_option.c:1713 msgid "Select a folder" msgstr "Selecione uma pasta" #: ../src/ol_lrc_fetch_ttplayer.c:274 msgid "ttPlayer" msgstr "" #: ../src/ol_lrc_fetch_xiami.c:237 msgid "Xiami" msgstr "" #: ../src/ol_lrc_candidate_list.c:43 msgid "Title" msgstr "Título" #: ../src/ol_lrc_candidate_list.c:48 msgid "Artist" msgstr "Artista" #: ../src/ol_search_dialog.c:31 #, c-format msgid "Searching lyrics from %s..." msgstr "Procurando por letras de %s..." #: ../src/ol_search_dialog.c:32 msgid "Ooops, no lyric found :(" msgstr "Ops, nada encontrado :(" #: ../src/ol_search_dialog.c:33 #, c-format msgid "%d lyrics found :)" msgstr "%d letras encontradas :)" #: ../src/ol_player_chooser.c:243 msgid "Supported players" msgstr "" #: ../src/ol_player_chooser.c:244 msgid "All players" msgstr "" #: ../src/ol_player_chooser.c:248 ../src/ol_player_chooser.c:319 #, fuzzy msgid "Choose a player to launch" msgstr "O comando para abrir o player" #: ../src/ol_player_chooser.c:264 #, fuzzy msgid "Use command:" msgstr "Comando do pla_yer:" #: ../src/ol_player_chooser.c:268 msgid "Launch" msgstr "" #: ../src/ol_player_chooser.c:281 msgid "Remember my choice" msgstr "" #: ../src/ol_player_chooser.c:387 #, c-format msgid "Failed to launch %s" msgstr "" #: ../src/ol_player_chooser.c:397 #, c-format msgid "Launching %s" msgstr "" #: ../src/ol_player_chooser.c:399 #, c-format msgid "" "OSD Lyrics is trying to launch and connect to %s. Please wait for a second." msgstr "" #: ../src/ol_player_chooser.c:510 #, fuzzy msgid "There is no supported player running" msgstr "Player em execução não suportado, sair.\n" #: ../src/ol_player_chooser.c:511 #, fuzzy msgid "Please choose a player below to launch" msgstr "O comando para abrir o player" #: ../src/ol_player_chooser.c:515 #, c-format msgid "Failed to connect to %s" msgstr "" #: ../src/ol_player_chooser.c:517 #, c-format msgid "" "%s is not supported by OSD Lyrics, or not running. Please launch another " "player" msgstr "" #: ../src/ol_app_info.c:183 ../src/ol_app_info.c:193 msgid "Unnamed" msgstr "" #: ../src/ol_scroll_window.c:52 msgid "" "Drag to move the window\n" "Hold CTRL to seek" msgstr "" #: ../src/ol_scroll_window.c:53 msgid "Drag to move the window" msgstr "" #: ../data/dialogs.glade.h:2 #, no-c-format msgid "" " %t - Title\n" " %p - Artist\n" " %a - Album\n" " %n - Track number\n" " %f - File name (without extension)" msgstr "" " %t - Título\n" " %p - Artista\n" " %a - Ãlbum\n" " %n - Numero da faxa\n" " %f - Nome do aquivo (sem extenção)" #: ../data/dialogs.glade.h:8 #, no-c-format msgid "%a - Album" msgstr "%a - Ãlbum" #: ../data/dialogs.glade.h:10 #, no-c-format msgid "%f - Filename" msgstr "%f - Nome do arquivo" #: ../data/dialogs.glade.h:12 #, no-c-format msgid "%n - Track number" msgstr "%n - Numero da faixa" #: ../data/dialogs.glade.h:14 #, no-c-format msgid "%p - Artist" msgstr "%p - Artista" #: ../data/dialogs.glade.h:16 #, no-c-format msgid "%t - Title" msgstr "%t - Título" #: ../data/dialogs.glade.h:17 msgid "Background" msgstr "" #: ../data/dialogs.glade.h:18 msgid "Download" msgstr "" #: ../data/dialogs.glade.h:19 msgid "Lyric _Alignment" msgstr "_Alinhamento da Linha" #: ../data/dialogs.glade.h:20 msgid "Lyric _Color" msgstr "_Cor da letra" #: ../data/dialogs.glade.h:21 msgid "Lyrics" msgstr "" #: ../data/dialogs.glade.h:22 msgid "Proxy" msgstr "" #: ../data/dialogs.glade.h:23 msgid "_Filename" msgstr "_Nome do arquivo" #: ../data/dialogs.glade.h:24 msgid "_Path" msgstr "_Pasta" #: ../data/dialogs.glade.h:26 #, no-c-format msgid "A single \"%\" means the directory where the playing music is." msgstr "" "Um único \"%\" significa a pasta onde se encontra a música que está tocando" #: ../data/dialogs.glade.h:27 msgid "Ac_tive color:" msgstr "" #: ../data/dialogs.glade.h:28 msgid "Acti_ve lyrics:" msgstr "Ati_var letras:" #: ../data/dialogs.glade.h:29 msgid "Adjust lyric delay by +0.2 s" msgstr "Ajusta o atraso da letra em +0.2 s" #: ../data/dialogs.glade.h:30 msgid "Adjust lyrics delay by -0.2 s" msgstr "Ajusta o atraso da letra em -0.2 s" #: ../data/dialogs.glade.h:31 msgid "Always" msgstr "" #: ../data/dialogs.glade.h:32 msgid "" "An OSD lyric show compatible with various media players and supports lyrics " "downloading.\n" msgstr "" "Um mostrador de letras de música OSD compatível com vários reprodutores, que " "suporta baixar letras.\n" #: ../data/dialogs.glade.h:34 msgid "Assign local LRC file to current music" msgstr "Atribuir arquivo LRC local a musica em execução" #: ../data/dialogs.glade.h:35 msgid "Au_to-detect system settings" msgstr "" #: ../data/dialogs.glade.h:36 msgid "Auto la_unch player:" msgstr "Abrir player automaticamente:" #: ../data/dialogs.glade.h:37 msgid "By lines" msgstr "" #: ../data/dialogs.glade.h:38 msgid "C_ustomize colors" msgstr "P_ersonalisa as cores" #: ../data/dialogs.glade.h:39 msgid "Choose LRC file to download" msgstr "Escolha o arquivo LRC para baixar" #: ../data/dialogs.glade.h:40 msgid "Colo_r:" msgstr "" #: ../data/dialogs.glade.h:41 msgid "Color _Theme:" msgstr "_Tema de Cores:" #: ../data/dialogs.glade.h:42 #, fuzzy msgid "Copyright 2009-2011 The OSD Lyrics project." msgstr "Copyright 2009 The OSD Lyrics project." #: ../data/dialogs.glade.h:44 msgid "D_on't ask me again" msgstr "Nã_o perguntar novamente" #: ../data/dialogs.glade.h:45 msgid "Display mode:" msgstr "" #. In preference/download, automately download the most matched lyric #: ../data/dialogs.glade.h:47 msgid "Do_wnload the first candidate" msgstr "Bai_xar o primeiro candidato" #: ../data/dialogs.glade.h:48 msgid "Doc_k" msgstr "" #: ../data/dialogs.glade.h:49 msgid "Don't assign lyric to this music" msgstr "Não atribuir letra para a musica em execução" #: ../data/dialogs.glade.h:50 msgid "F_irst line:" msgstr "P_rimeira linha:" #: ../data/dialogs.glade.h:51 msgid "GPL v3" msgstr "GPL v3" #: ../data/dialogs.glade.h:52 msgid "HTTP" msgstr "" #: ../data/dialogs.glade.h:53 msgid "Hide the OSD Window" msgstr "Ocultar a janela OSD" #: ../data/dialogs.glade.h:54 msgid "If locked, you can't move OSD window by mouse" msgstr "Se travado, você não poderá mover a janela OSD pelo mouse" #: ../data/dialogs.glade.h:55 msgid "" "If there are more than one lrc files matched with the search condition, " "download the first one without prompting the user." msgstr "" "Se houver mas de um arquivo de letra de música que corresponda à condição da " "pesquisa, baixar a primeira da lista sem perguntar ao usuário." #: ../data/dialogs.glade.h:56 msgid "Inactiv_e lyrics:" msgstr "Letras inati_vas:" #: ../data/dialogs.glade.h:57 #, fuzzy msgid "Lyric _sites:" msgstr "Linhas da letra:" #: ../data/dialogs.glade.h:58 msgid "Lyric delay +" msgstr "Atraso da letra +" #: ../data/dialogs.glade.h:59 msgid "Lyric delay -" msgstr "Atraso da letra -" #: ../data/dialogs.glade.h:60 msgid "Lyric lines:" msgstr "Linhas da letra:" #: ../data/dialogs.glade.h:61 msgid "Mode:" msgstr "" #: ../data/dialogs.glade.h:62 msgid "No _proxy" msgstr "" #: ../data/dialogs.glade.h:63 msgid "Nor_mal" msgstr "" #: ../data/dialogs.glade.h:64 msgid "OSD _mode" msgstr "" #: ../data/dialogs.glade.h:65 msgid "OS_D" msgstr "" #: ../data/dialogs.glade.h:66 msgid "O_pacity:" msgstr "" #: ../data/dialogs.glade.h:67 msgid "Opaque" msgstr "" #. The outline width of text in OSD window #: ../data/dialogs.glade.h:69 msgid "Out_line:" msgstr "Cont_orno" #: ../data/dialogs.glade.h:70 msgid "Passwo_rd:" msgstr "" #: ../data/dialogs.glade.h:71 msgid "Pla_yer command:" msgstr "Comando do pla_yer:" #: ../data/dialogs.glade.h:72 msgid "Po_rt:" msgstr "" #: ../data/dialogs.glade.h:73 msgid "Preferences" msgstr "Preferências" #: ../data/dialogs.glade.h:74 msgid "SOCKS4" msgstr "" #: ../data/dialogs.glade.h:75 msgid "SOCKS5" msgstr "" #: ../data/dialogs.glade.h:76 msgid "Sc_rolling" msgstr "" #: ../data/dialogs.glade.h:77 msgid "Scroll _mode:" msgstr "" #: ../data/dialogs.glade.h:78 msgid "Scrolling _mode" msgstr "" #: ../data/dialogs.glade.h:79 msgid "Search lyrics" msgstr "Pesquisar letras" #: ../data/dialogs.glade.h:80 msgid "Search lyrics from Internet" msgstr "Pesquisar letras da internet" #: ../data/dialogs.glade.h:81 msgid "Show _bubble notification" msgstr "" #: ../data/dialogs.glade.h:82 msgid "Show passwor_d" msgstr "" #: ../data/dialogs.glade.h:83 msgid "Si_ngle line" msgstr "Ú_nica linha" #: ../data/dialogs.glade.h:84 msgid "Strong" msgstr "" #: ../data/dialogs.glade.h:85 msgid "T_ranslucent on mouse over" msgstr "T_ransparente quando o mouse estiver em cima" #: ../data/dialogs.glade.h:86 msgid "The artist of the current track" msgstr "O artista da faixa em execução" #: ../data/dialogs.glade.h:87 msgid "The filename of the current track" msgstr "O nome do aquivo da faixa em execução" #: ../data/dialogs.glade.h:88 msgid "The number of the current track" msgstr "O numero da faixa em execução" #: ../data/dialogs.glade.h:89 msgid "The title of album of the current track" msgstr "O título do album da faixa em execução" #: ../data/dialogs.glade.h:90 msgid "The title of the current track" msgstr "O título da faixa em execução" #: ../data/dialogs.glade.h:91 msgid "Transparent" msgstr "" #: ../data/dialogs.glade.h:92 msgid "Use _manual settings" msgstr "" #: ../data/dialogs.glade.h:93 msgid "Weak" msgstr "" #: ../data/dialogs.glade.h:94 msgid "_Artist:" msgstr "_Artista:" #: ../data/dialogs.glade.h:95 msgid "_Assign lyric..." msgstr "_Atribuir letra..." #: ../data/dialogs.glade.h:96 msgid "_Blur:" msgstr "" #: ../data/dialogs.glade.h:97 msgid "_Double lines" msgstr "_Duas linhas" #: ../data/dialogs.glade.h:98 msgid "_Download" msgstr "_Baixar" #: ../data/dialogs.glade.h:99 msgid "_Font:" msgstr "_Fonte:" #. From witch web site to search and download lyrics #: ../data/dialogs.glade.h:101 msgid "_From:" msgstr "_De:" #: ../data/dialogs.glade.h:102 msgid "_General" msgstr "_Geral" #: ../data/dialogs.glade.h:103 msgid "_Hide" msgstr "_Ocultar" #: ../data/dialogs.glade.h:104 msgid "_Host:" msgstr "" #: ../data/dialogs.glade.h:105 msgid "_Inactive color:" msgstr "" #: ../data/dialogs.glade.h:106 msgid "_Lock Position" msgstr "_Travar Posição" #: ../data/dialogs.glade.h:107 msgid "_Lyric Location" msgstr "_Local da letra" #: ../data/dialogs.glade.h:108 msgid "_Network" msgstr "" #: ../data/dialogs.glade.h:109 msgid "_No lyric" msgstr "_Nenhuma letra" #: ../data/dialogs.glade.h:110 msgid "_OSD" msgstr "_OSD" #: ../data/dialogs.glade.h:111 msgid "_Scrolling" msgstr "" #: ../data/dialogs.glade.h:112 msgid "_Search" msgstr "Pe_squisar" #: ../data/dialogs.glade.h:113 msgid "_Search lyric..." msgstr "_Pesquisar letra..." #: ../data/dialogs.glade.h:114 msgid "_Second line:" msgstr "_Segunda linha:" #: ../data/dialogs.glade.h:115 msgid "_Title:" msgstr "_Título:" #: ../data/dialogs.glade.h:116 msgid "_Type:" msgstr "" #: ../data/dialogs.glade.h:117 msgid "_Username:" msgstr "" #: ../data/dialogs.glade.h:118 msgid "bottom" msgstr "em baixo" #: ../data/dialogs.glade.h:120 msgid "middle" msgstr "meio" #: ../data/dialogs.glade.h:121 msgid "top" msgstr "em cima" #: ../data/dialogs.glade.h:122 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" " Douglas Santos https://launchpad.net/~douglasrpg\n" " Emerson Vinicius https://launchpad.net/~emersonvinicius\n" " Guilherme Lindner https://launchpad.net/~gui666\n" " Pedro Folha https://launchpad.net/~pedrovi\n" " Sam Samuels https://launchpad.net/~gemini-sam-samuels\n" " Teylo Laundos Aguiar https://launchpad.net/~teylo.aguiar" #~ msgid "Searching lyrics" #~ msgstr "Pesquisando letras" #~ msgid "Preview" #~ msgstr "Pré-visualização" #~ msgid "None" #~ msgstr "Nenhuma" #~ msgid "If no player detected, launch the given player" #~ msgstr "Se não for encontrado nenhum palyer, abrir o padrão" #~ msgid "_Display notification" #~ msgstr "_Mostrar notificação" #~ msgid "_Width:" #~ msgstr "_Largura:" #~ msgid "Choose witch site you want to download from." #~ msgstr "Escolha o site do qual as letras serão baixadas." #~ msgid "Download _engine:" #~ msgstr "_Motor de busca:" #~ msgid "Qianqian" #~ msgstr "Qianqian" #~ msgid "Sogou" #~ msgstr "Sogou" #~ msgid "MiniLyrics" #~ msgstr "MiniLyrics" #~ msgid "Scrolling" #~ msgstr "Rolagem" #~ msgid "OSD" #~ msgstr "Mensagem na tela" #~ msgid "Display mode" #~ msgstr "Modo de exibição" osdlyrics-0.5.5~rc1+dfsg1/po/quot.sed000066400000000000000000000002311345713535600174530ustar00rootroot00000000000000s/"\([^"]*\)"/“\1â€/g s/`\([^`']*\)'/‘\1’/g s/ '\([^`']*\)' / ‘\1’ /g s/ '\([^`']*\)'$/ ‘\1’/g s/^'\([^`']*\)' /‘\1’ /g s/“â€/""/g osdlyrics-0.5.5~rc1+dfsg1/po/remove-potcdate.sin000066400000000000000000000006601345713535600216050ustar00rootroot00000000000000# Sed script that remove the POT-Creation-Date line in the header entry # from a POT file. # # The distinction between the first and the following occurrences of the # pattern is achieved by looking at the hold space. /^"POT-Creation-Date: .*"$/{ x # Test if the hold space is empty. s/P/P/ ta # Yes it was empty. First occurrence. Remove the line. g d bb :a # The hold space was nonempty. Following occurrences. Do nothing. x :b } osdlyrics-0.5.5~rc1+dfsg1/po/ro.po000066400000000000000000000352341345713535600167610ustar00rootroot00000000000000# Romanian translation for osd-lyrics # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the osd-lyrics package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: osd-lyrics\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-11-17 23:18+0800\n" "PO-Revision-Date: 2011-05-14 04:13+0000\n" "Last-Translator: Tuhut Vladut Emanuel \n" "Language-Team: Romanian \n" "Language: ro\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n == 1 ? 0: (((n % 100 > 19) || ((n % 100 " "== 0) && (n != 0))) ? 2: 1));\n" "X-Launchpad-Export-Date: 2011-06-04 13:40+0000\n" "X-Generator: Launchpad (build 13144)\n" #: ../src/ol_menu.c:119 msgid "LRC files" msgstr "FiÈ™iere LRC" #: ../src/ol_menu.c:125 msgid "Choose LRC file to assign" msgstr "AlegeÈ›i fiÈ™ierul LRC pentru atribuire" #: ../src/ol_main.c:60 msgid "" "The level of debug messages to log, can be 'none', 'error', 'debug', or " "'info'" msgstr "" "Nivelul de depanare a mesajelor din jurnal, poate fi \"nici unul\", \"eroare" "\", \"debug\", sau \"info\"" #: ../src/ol_main.c:62 msgid "Show version information" msgstr "" #: ../src/ol_main.c:147 msgid "Download failed" msgstr "Descărcare eÈ™uată" #: ../src/ol_main.c:162 #, fuzzy, c-format msgid "Searching lyrics from %s" msgstr "Se caută versuri pentru %s..." #: ../src/ol_main.c:185 msgid "Cannot create the lyric directory" msgstr "Nu se poate crea directorul cu versuri" #: ../src/ol_main.c:201 msgid "Lyrics not found" msgstr "Nu s-au găsit versuri" #: ../src/ol_main.c:507 #, c-format msgid "No supported player is running, exit.\n" msgstr "Un player nesuportat rulează, iÈ™ire\n" #: ../src/ol_main.c:652 #, fuzzy msgid "" "debug level should be one of ``none'', ``error'', ``debug'', or ``info''" msgstr "" "Nivelul de depanare a mesajelor din jurnal, poate fi \"nici unul\", \"eroare" "\", \"debug\", sau \"info\"" #: ../src/ol_main.c:675 msgid "Another OSD Lyrics is running, exit." msgstr "Un alt OSD Lyrics rulează, ieÈ™ire" #: ../src/ol_trayicon.c:36 ../src/ol_notify.c:26 msgid "Unknown title" msgstr "Titlul necunoscut" #: ../src/ol_trayicon.c:37 ../src/ol_notify.c:27 msgid "Unknown artist" msgstr "Artist necunoscut" #: ../src/ol_trayicon.c:71 ../src/ol_trayicon.c:149 msgid "OSD Lyrics" msgstr "OSD Lyrics" #: ../src/ol_option.c:1631 msgid "Choose on startup" msgstr "" #. gtk_list_store_append (liststore, &iter); #. gtk_list_store_set (liststore, &iter, #. 0, "Customize", #. 1, "", #. -1); #: ../src/ol_option.c:1651 ../data/dialogs.glade.h:43 msgid "Customize" msgstr "Personalizează" #: ../src/ol_option.c:1713 msgid "Select a folder" msgstr "SelectaÈ›i un director" #: ../src/ol_lrc_fetch_ttplayer.c:274 msgid "ttPlayer" msgstr "ttPlayer" #: ../src/ol_lrc_fetch_xiami.c:237 msgid "Xiami" msgstr "" #: ../src/ol_lrc_candidate_list.c:43 msgid "Title" msgstr "Titlu" #: ../src/ol_lrc_candidate_list.c:48 msgid "Artist" msgstr "Artist" #: ../src/ol_search_dialog.c:31 #, c-format msgid "Searching lyrics from %s..." msgstr "Se caută versuri pentru %s..." #: ../src/ol_search_dialog.c:32 msgid "Ooops, no lyric found :(" msgstr "Ooops, versuri negăsite :(" #: ../src/ol_search_dialog.c:33 #, c-format msgid "%d lyrics found :)" msgstr "%d versuri găsite :)" #: ../src/ol_player_chooser.c:243 msgid "Supported players" msgstr "" #: ../src/ol_player_chooser.c:244 msgid "All players" msgstr "" #: ../src/ol_player_chooser.c:248 ../src/ol_player_chooser.c:319 #, fuzzy msgid "Choose a player to launch" msgstr "AlegeÈ›i fiÈ™ierul LRC pentru atribuire" #: ../src/ol_player_chooser.c:264 #, fuzzy msgid "Use command:" msgstr "Comandă pla_yer:" #: ../src/ol_player_chooser.c:268 msgid "Launch" msgstr "" #: ../src/ol_player_chooser.c:281 msgid "Remember my choice" msgstr "" #: ../src/ol_player_chooser.c:387 #, c-format msgid "Failed to launch %s" msgstr "" #: ../src/ol_player_chooser.c:397 #, c-format msgid "Launching %s" msgstr "" #: ../src/ol_player_chooser.c:399 #, c-format msgid "" "OSD Lyrics is trying to launch and connect to %s. Please wait for a second." msgstr "" #: ../src/ol_player_chooser.c:510 #, fuzzy msgid "There is no supported player running" msgstr "Un player nesuportat rulează, iÈ™ire\n" #: ../src/ol_player_chooser.c:511 #, fuzzy msgid "Please choose a player below to launch" msgstr "AlegeÈ›i fiÈ™ierul LRC pentru atribuire" #: ../src/ol_player_chooser.c:515 #, c-format msgid "Failed to connect to %s" msgstr "" #: ../src/ol_player_chooser.c:517 #, c-format msgid "" "%s is not supported by OSD Lyrics, or not running. Please launch another " "player" msgstr "" #: ../src/ol_app_info.c:183 ../src/ol_app_info.c:193 msgid "Unnamed" msgstr "" #: ../src/ol_scroll_window.c:52 msgid "" "Drag to move the window\n" "Hold CTRL to seek" msgstr "" #: ../src/ol_scroll_window.c:53 msgid "Drag to move the window" msgstr "" #: ../data/dialogs.glade.h:2 #, no-c-format msgid "" " %t - Title\n" " %p - Artist\n" " %a - Album\n" " %n - Track number\n" " %f - File name (without extension)" msgstr "" " %t - Titlu\n" " %p - Artist\n" " %a - Album\n" " %n - Număr melodie\n" " %f - Nume fiÈ™ier (fără extensie)" #: ../data/dialogs.glade.h:8 #, no-c-format msgid "%a - Album" msgstr "%a - Album" #: ../data/dialogs.glade.h:10 #, no-c-format msgid "%f - Filename" msgstr "%f - Nume fiÈ™ier" #: ../data/dialogs.glade.h:12 #, no-c-format msgid "%n - Track number" msgstr "%n - Număr melodie" #: ../data/dialogs.glade.h:14 #, no-c-format msgid "%p - Artist" msgstr "%p - Artist" #: ../data/dialogs.glade.h:16 #, no-c-format msgid "%t - Title" msgstr "%t - Titlu" #: ../data/dialogs.glade.h:17 msgid "Background" msgstr "Fundal" #: ../data/dialogs.glade.h:18 msgid "Download" msgstr "Descarcă" #: ../data/dialogs.glade.h:19 msgid "Lyric _Alignment" msgstr "Aliniere_versuri" #: ../data/dialogs.glade.h:20 msgid "Lyric _Color" msgstr "Culoare versuri" #: ../data/dialogs.glade.h:21 msgid "Lyrics" msgstr "Versuri" #: ../data/dialogs.glade.h:22 msgid "Proxy" msgstr "Proxy" #: ../data/dialogs.glade.h:23 msgid "_Filename" msgstr "_Nume FiÈ™ier" #: ../data/dialogs.glade.h:24 msgid "_Path" msgstr "_Cale" #: ../data/dialogs.glade.h:26 #, no-c-format msgid "A single \"%\" means the directory where the playing music is." msgstr "Un singur \"%\" reprezintă directorul de unde se află muzica" #: ../data/dialogs.glade.h:27 msgid "Ac_tive color:" msgstr "Culoare ac_tivă" #: ../data/dialogs.glade.h:28 msgid "Acti_ve lyrics:" msgstr "Versuri acty_ve" #: ../data/dialogs.glade.h:29 msgid "Adjust lyric delay by +0.2 s" msgstr "Ajustează sincronizarea versurilor cu +0.2 s" #: ../data/dialogs.glade.h:30 msgid "Adjust lyrics delay by -0.2 s" msgstr "Ajustează sincronizarea versurilor cu -0.2 s" #: ../data/dialogs.glade.h:31 msgid "Always" msgstr "ÃŽntotdeauna" #: ../data/dialogs.glade.h:32 msgid "" "An OSD lyric show compatible with various media players and supports lyrics " "downloading.\n" msgstr "" "Un vizualizator de versuri compatibil cu o varietate de playere si suportă " "descărcarea versurilor.\n" #: ../data/dialogs.glade.h:34 msgid "Assign local LRC file to current music" msgstr "Asociază fiÈ™ierul LRC local cu melodia curentă" #: ../data/dialogs.glade.h:35 msgid "Au_to-detect system settings" msgstr "Au_to-detectează setătile sistemului" #: ../data/dialogs.glade.h:36 msgid "Auto la_unch player:" msgstr "Auto ru_lează player:" #: ../data/dialogs.glade.h:37 msgid "By lines" msgstr "De linii" #: ../data/dialogs.glade.h:38 msgid "C_ustomize colors" msgstr "P_ersonalizează culorile" #: ../data/dialogs.glade.h:39 msgid "Choose LRC file to download" msgstr "AlegeÈ›i fiÈ™ierul LRC pentru descărcare" #: ../data/dialogs.glade.h:40 msgid "Colo_r:" msgstr "Culoa_re:" #: ../data/dialogs.glade.h:41 msgid "Color _Theme:" msgstr "Culoare_Temă" #: ../data/dialogs.glade.h:42 #, fuzzy msgid "Copyright 2009-2011 The OSD Lyrics project." msgstr "Drepturi de autor 2009 Proiectul OSD Lyrics." #: ../data/dialogs.glade.h:44 msgid "D_on't ask me again" msgstr "N_u mă mai întreba" #: ../data/dialogs.glade.h:45 msgid "Display mode:" msgstr "Mod afiÈ™are" #. In preference/download, automately download the most matched lyric #: ../data/dialogs.glade.h:47 msgid "Do_wnload the first candidate" msgstr "De_scarcă primul fiÈ™ier" #: ../data/dialogs.glade.h:48 msgid "Doc_k" msgstr "Doc_k" #: ../data/dialogs.glade.h:49 msgid "Don't assign lyric to this music" msgstr "Nu asocia versuri melodiei" #: ../data/dialogs.glade.h:50 msgid "F_irst line:" msgstr "P_rima linie" #: ../data/dialogs.glade.h:51 msgid "GPL v3" msgstr "GPL v3" #: ../data/dialogs.glade.h:52 msgid "HTTP" msgstr "HTTP" #: ../data/dialogs.glade.h:53 msgid "Hide the OSD Window" msgstr "Ascunde fereastra OSD" #: ../data/dialogs.glade.h:54 msgid "If locked, you can't move OSD window by mouse" msgstr "Dacă este blocat, nu puteÈ›i muta fereastra OSD cu mausul" #: ../data/dialogs.glade.h:55 msgid "" "If there are more than one lrc files matched with the search condition, " "download the first one without prompting the user." msgstr "" "Dacă sunt mai multe fiÈ™iere LRC se potrivesc căutării, descarcă primul " "fiÈ™ier fără a interoga utilizatorul." #: ../data/dialogs.glade.h:56 msgid "Inactiv_e lyrics:" msgstr "Versuri inactiv_e" #: ../data/dialogs.glade.h:57 #, fuzzy msgid "Lyric _sites:" msgstr "Linii versuri:" #: ../data/dialogs.glade.h:58 msgid "Lyric delay +" msgstr "Sincronizare versuri +" #: ../data/dialogs.glade.h:59 msgid "Lyric delay -" msgstr "Sincronizare versuri -" #: ../data/dialogs.glade.h:60 msgid "Lyric lines:" msgstr "Linii versuri:" #: ../data/dialogs.glade.h:61 msgid "Mode:" msgstr "Mod:" #: ../data/dialogs.glade.h:62 msgid "No _proxy" msgstr "Fără_proxy" #: ../data/dialogs.glade.h:63 msgid "Nor_mal" msgstr "Nor_mal" #: ../data/dialogs.glade.h:64 msgid "OSD _mode" msgstr "mod_OSD" #: ../data/dialogs.glade.h:65 msgid "OS_D" msgstr "OS_D" #: ../data/dialogs.glade.h:66 msgid "O_pacity:" msgstr "O_pacitate:" #: ../data/dialogs.glade.h:67 msgid "Opaque" msgstr "Opac" #. The outline width of text in OSD window #: ../data/dialogs.glade.h:69 msgid "Out_line:" msgstr "Con_tur" #: ../data/dialogs.glade.h:70 msgid "Passwo_rd:" msgstr "Parol_ă" #: ../data/dialogs.glade.h:71 msgid "Pla_yer command:" msgstr "Comandă pla_yer:" #: ../data/dialogs.glade.h:72 msgid "Po_rt:" msgstr "Po_rt:" #: ../data/dialogs.glade.h:73 msgid "Preferences" msgstr "PreferinÈ›e" #: ../data/dialogs.glade.h:74 msgid "SOCKS4" msgstr "SOCKS4" #: ../data/dialogs.glade.h:75 msgid "SOCKS5" msgstr "SOCKS5" #: ../data/dialogs.glade.h:76 msgid "Sc_rolling" msgstr "De_rulare" #: ../data/dialogs.glade.h:77 msgid "Scroll _mode:" msgstr "Mod_derulare" #: ../data/dialogs.glade.h:78 msgid "Scrolling _mode" msgstr "Mod_derulare" #: ../data/dialogs.glade.h:79 msgid "Search lyrics" msgstr "Se caută versuri" #: ../data/dialogs.glade.h:80 msgid "Search lyrics from Internet" msgstr "Se caută versuri pe Internet" #: ../data/dialogs.glade.h:81 msgid "Show _bubble notification" msgstr "AfiÈ™ează_notificarea balon" #: ../data/dialogs.glade.h:82 msgid "Show passwor_d" msgstr "Arată parol_a" #: ../data/dialogs.glade.h:83 msgid "Si_ngle line" msgstr "O linie" #: ../data/dialogs.glade.h:84 msgid "Strong" msgstr "" #: ../data/dialogs.glade.h:85 msgid "T_ranslucent on mouse over" msgstr "T_ranslucid sub cursor" #: ../data/dialogs.glade.h:86 msgid "The artist of the current track" msgstr "Artistul melodiei curente" #: ../data/dialogs.glade.h:87 msgid "The filename of the current track" msgstr "Numele fiÈ™ierului melodiei curente" #: ../data/dialogs.glade.h:88 msgid "The number of the current track" msgstr "Numărul melodiei curente" #: ../data/dialogs.glade.h:89 msgid "The title of album of the current track" msgstr "Numele albumului melodiei curente" #: ../data/dialogs.glade.h:90 msgid "The title of the current track" msgstr "Titlul melodiei curente" #: ../data/dialogs.glade.h:91 msgid "Transparent" msgstr "Transparent" #: ../data/dialogs.glade.h:92 msgid "Use _manual settings" msgstr "Setătile utilizatorului" #: ../data/dialogs.glade.h:93 msgid "Weak" msgstr "" #: ../data/dialogs.glade.h:94 msgid "_Artist:" msgstr "_Artist:" #: ../data/dialogs.glade.h:95 msgid "_Assign lyric..." msgstr "_Asociere versuri..." #: ../data/dialogs.glade.h:96 msgid "_Blur:" msgstr "" #: ../data/dialogs.glade.h:97 msgid "_Double lines" msgstr "_Linii duble" #: ../data/dialogs.glade.h:98 msgid "_Download" msgstr "_Descărcare" #: ../data/dialogs.glade.h:99 msgid "_Font:" msgstr "_Font:" #. From witch web site to search and download lyrics #: ../data/dialogs.glade.h:101 msgid "_From:" msgstr "_De la:" #: ../data/dialogs.glade.h:102 msgid "_General" msgstr "_General" #: ../data/dialogs.glade.h:103 msgid "_Hide" msgstr "A_scunde" #: ../data/dialogs.glade.h:104 msgid "_Host:" msgstr "_Gazdă" #: ../data/dialogs.glade.h:105 msgid "_Inactive color:" msgstr "_Culoare inactiv" #: ../data/dialogs.glade.h:106 msgid "_Lock Position" msgstr "_PoziÈ›ie blocată" #: ../data/dialogs.glade.h:107 msgid "_Lyric Location" msgstr "_LocaÈ›ia versurilor" #: ../data/dialogs.glade.h:108 msgid "_Network" msgstr "_ReÈ›ea" #: ../data/dialogs.glade.h:109 msgid "_No lyric" msgstr "_Fără versuri" #: ../data/dialogs.glade.h:110 msgid "_OSD" msgstr "_OSD" #: ../data/dialogs.glade.h:111 msgid "_Scrolling" msgstr "_Derulare" #: ../data/dialogs.glade.h:112 msgid "_Search" msgstr "_Caută" #: ../data/dialogs.glade.h:113 msgid "_Search lyric..." msgstr "_Se caută versuri" #: ../data/dialogs.glade.h:114 msgid "_Second line:" msgstr "_A doua linie" #: ../data/dialogs.glade.h:115 msgid "_Title:" msgstr "_Titlu:" #: ../data/dialogs.glade.h:116 msgid "_Type:" msgstr "_Tip:" #: ../data/dialogs.glade.h:117 msgid "_Username:" msgstr "_Utilizator:" #: ../data/dialogs.glade.h:118 msgid "bottom" msgstr "jos" #: ../data/dialogs.glade.h:120 msgid "middle" msgstr "mijloc" #: ../data/dialogs.glade.h:121 msgid "top" msgstr "sus" #: ../data/dialogs.glade.h:122 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" " Tuhut Vladut Emanuel https://launchpad.net/~ithreexas" #~ msgid "Searching lyrics" #~ msgstr "Se caută versurile" #~ msgid "Preview" #~ msgstr "Previzualizare" #~ msgid "_Site:" #~ msgstr "_Site:" #~ msgid "None" #~ msgstr "Nespecificat" #~ msgid "MiniLyrics" #~ msgstr "MiniLyrics" #~ msgid "Sogou" #~ msgstr "Sogou" #~ msgid "Qianqian" #~ msgstr "Qianqian" #~ msgid "Display mode" #~ msgstr "Mod afiÈ™are" #~ msgid "OSD" #~ msgstr "OSD" #~ msgid "Scrolling" #~ msgstr "Derulare" #~ msgid "_Display notification" #~ msgstr "_Arată notificare" osdlyrics-0.5.5~rc1+dfsg1/po/ru.po000066400000000000000000000417241345713535600167700ustar00rootroot00000000000000# Russian translation for osd-lyrics # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the osd-lyrics package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: osd-lyrics\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-11-17 23:18+0800\n" "PO-Revision-Date: 2011-01-27 13:29+0000\n" "Last-Translator: Stanislav Hanzhin \n" "Language-Team: Russian \n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Launchpad-Export-Date: 2011-06-04 13:40+0000\n" "X-Generator: Launchpad (build 13144)\n" #: ../src/ol_menu.c:119 msgid "LRC files" msgstr "LRC-файлы" #: ../src/ol_menu.c:125 msgid "Choose LRC file to assign" msgstr "Выверите назначаемый файл LRC" #: ../src/ol_main.c:60 msgid "" "The level of debug messages to log, can be 'none', 'error', 'debug', or " "'info'" msgstr "" "Уровень отладочных Ñообщений в логе может быть 'none', 'error', 'debug' или " "'info'" #: ../src/ol_main.c:62 msgid "Show version information" msgstr "" #: ../src/ol_main.c:147 msgid "Download failed" msgstr "Ошибка загрузки данных" #: ../src/ol_main.c:162 #, fuzzy, c-format msgid "Searching lyrics from %s" msgstr "ПоиÑк текÑтов в %s..." #: ../src/ol_main.c:185 msgid "Cannot create the lyric directory" msgstr "Ðевозможно Ñоздать папку лирики" #: ../src/ol_main.c:201 msgid "Lyrics not found" msgstr "ТекÑÑ‚ пеÑни не найден" #: ../src/ol_main.c:507 #, c-format msgid "No supported player is running, exit.\n" msgstr "Ðет доÑтупных плееров.\n" #: ../src/ol_main.c:652 #, fuzzy msgid "" "debug level should be one of ``none'', ``error'', ``debug'', or ``info''" msgstr "" "Уровень отладочных Ñообщений в логе может быть 'none', 'error', 'debug' или " "'info'" #: ../src/ol_main.c:675 msgid "Another OSD Lyrics is running, exit." msgstr "Приложение уже запущено." #: ../src/ol_trayicon.c:36 ../src/ol_notify.c:26 msgid "Unknown title" msgstr "ÐеизвеÑтный заголовок" #: ../src/ol_trayicon.c:37 ../src/ol_notify.c:27 msgid "Unknown artist" msgstr "ÐеизвеÑтный иÑполнитель" #: ../src/ol_trayicon.c:71 ../src/ol_trayicon.c:149 msgid "OSD Lyrics" msgstr "OSD-показ текÑтов." #: ../src/ol_option.c:1631 msgid "Choose on startup" msgstr "" #. gtk_list_store_append (liststore, &iter); #. gtk_list_store_set (liststore, &iter, #. 0, "Customize", #. 1, "", #. -1); #: ../src/ol_option.c:1651 ../data/dialogs.glade.h:43 msgid "Customize" msgstr "ÐаÑтроить" #: ../src/ol_option.c:1713 msgid "Select a folder" msgstr "Выберите каталог" #: ../src/ol_lrc_fetch_ttplayer.c:274 msgid "ttPlayer" msgstr "" #: ../src/ol_lrc_fetch_xiami.c:237 msgid "Xiami" msgstr "" #: ../src/ol_lrc_candidate_list.c:43 msgid "Title" msgstr "Заголовок" #: ../src/ol_lrc_candidate_list.c:48 msgid "Artist" msgstr "ИÑполнитель" #: ../src/ol_search_dialog.c:31 #, c-format msgid "Searching lyrics from %s..." msgstr "ПоиÑк текÑтов в %s..." #: ../src/ol_search_dialog.c:32 msgid "Ooops, no lyric found :(" msgstr "ТекÑÑ‚ не найден." #: ../src/ol_search_dialog.c:33 #, c-format msgid "%d lyrics found :)" msgstr "%d текÑÑ‚ найден." #: ../src/ol_player_chooser.c:243 msgid "Supported players" msgstr "" #: ../src/ol_player_chooser.c:244 msgid "All players" msgstr "" #: ../src/ol_player_chooser.c:248 ../src/ol_player_chooser.c:319 #, fuzzy msgid "Choose a player to launch" msgstr "Выверите назначаемый файл LRC" #: ../src/ol_player_chooser.c:264 #, fuzzy msgid "Use command:" msgstr "Команда плеера:" #: ../src/ol_player_chooser.c:268 msgid "Launch" msgstr "" #: ../src/ol_player_chooser.c:281 msgid "Remember my choice" msgstr "" #: ../src/ol_player_chooser.c:387 #, c-format msgid "Failed to launch %s" msgstr "" #: ../src/ol_player_chooser.c:397 #, c-format msgid "Launching %s" msgstr "" #: ../src/ol_player_chooser.c:399 #, c-format msgid "" "OSD Lyrics is trying to launch and connect to %s. Please wait for a second." msgstr "" #: ../src/ol_player_chooser.c:510 #, fuzzy msgid "There is no supported player running" msgstr "Ðет доÑтупных плееров.\n" #: ../src/ol_player_chooser.c:511 #, fuzzy msgid "Please choose a player below to launch" msgstr "Выверите назначаемый файл LRC" #: ../src/ol_player_chooser.c:515 #, c-format msgid "Failed to connect to %s" msgstr "" #: ../src/ol_player_chooser.c:517 #, c-format msgid "" "%s is not supported by OSD Lyrics, or not running. Please launch another " "player" msgstr "" #: ../src/ol_app_info.c:183 ../src/ol_app_info.c:193 msgid "Unnamed" msgstr "" #: ../src/ol_scroll_window.c:52 msgid "" "Drag to move the window\n" "Hold CTRL to seek" msgstr "" #: ../src/ol_scroll_window.c:53 msgid "Drag to move the window" msgstr "" #: ../data/dialogs.glade.h:2 #, no-c-format msgid "" " %t - Title\n" " %p - Artist\n" " %a - Album\n" " %n - Track number\n" " %f - File name (without extension)" msgstr "" " %t - Ðазвание\n" " %p - ИÑполнитель\n" " %a - Ðльбом\n" " %n - Ðомер дорожки\n" " %f - Ð˜Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° (без раÑширениÑ)" #: ../data/dialogs.glade.h:8 #, no-c-format msgid "%a - Album" msgstr "%a - Ðльбом" #: ../data/dialogs.glade.h:10 #, no-c-format msgid "%f - Filename" msgstr "%f - Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°" #: ../data/dialogs.glade.h:12 #, no-c-format msgid "%n - Track number" msgstr "%n - номер дорожки" #: ../data/dialogs.glade.h:14 #, no-c-format msgid "%p - Artist" msgstr "%p - ИÑполнитель" #: ../data/dialogs.glade.h:16 #, no-c-format msgid "%t - Title" msgstr "%t - Заголовок" #: ../data/dialogs.glade.h:17 msgid "Background" msgstr "" #: ../data/dialogs.glade.h:18 msgid "Download" msgstr "" #: ../data/dialogs.glade.h:19 msgid "Lyric _Alignment" msgstr "_Выравнивание лирики" #: ../data/dialogs.glade.h:20 msgid "Lyric _Color" msgstr "_Цвет лирики" #: ../data/dialogs.glade.h:21 msgid "Lyrics" msgstr "" #: ../data/dialogs.glade.h:22 msgid "Proxy" msgstr "" #: ../data/dialogs.glade.h:23 msgid "_Filename" msgstr "Ð˜Ð¼Ñ _файла" #: ../data/dialogs.glade.h:24 msgid "_Path" msgstr "_Путь" #: ../data/dialogs.glade.h:26 #, no-c-format msgid "A single \"%\" means the directory where the playing music is." msgstr "" "ПроÑто \"%\" означает каталог, в котором раÑположена Ð¸Ð³Ñ€Ð°ÑŽÑ‰Ð°Ñ ÑÐµÐ¹Ñ‡Ð°Ñ Ð¼ÑƒÐ·Ñ‹ÐºÐ°." #: ../data/dialogs.glade.h:27 msgid "Ac_tive color:" msgstr "" #: ../data/dialogs.glade.h:28 msgid "Acti_ve lyrics:" msgstr "_ÐÐºÑ‚Ð¸Ð²Ð½Ð°Ñ Ð»Ð¸Ñ€Ð¸ÐºÐ°:" #: ../data/dialogs.glade.h:29 msgid "Adjust lyric delay by +0.2 s" msgstr "УÑтановить задержку лирики +0.2 Ñ." #: ../data/dialogs.glade.h:30 msgid "Adjust lyrics delay by -0.2 s" msgstr "УÑтановить задержку лирики -0.2 Ñ." #: ../data/dialogs.glade.h:31 msgid "Always" msgstr "" #: ../data/dialogs.glade.h:32 msgid "" "An OSD lyric show compatible with various media players and supports lyrics " "downloading.\n" msgstr "" "Вывод текÑта пеÑен на Ñкран (OSD) ÑовмеÑтим Ñ Ñ€Ð°Ð·Ð»Ð¸Ñ‡Ð½Ñ‹Ð¼Ð¸ медиа-" "проигрывателÑми и поддерживает загрузку текÑтов из Ñети.\n" #: ../data/dialogs.glade.h:34 msgid "Assign local LRC file to current music" msgstr "Ðазначить локальный LRC-файл текущей музыке" #: ../data/dialogs.glade.h:35 msgid "Au_to-detect system settings" msgstr "" #: ../data/dialogs.glade.h:36 msgid "Auto la_unch player:" msgstr "ÐвтоматичеÑки запуÑкаемый плеер:" #: ../data/dialogs.glade.h:37 msgid "By lines" msgstr "" #: ../data/dialogs.glade.h:38 msgid "C_ustomize colors" msgstr "_ÐаÑтроить цвета" #: ../data/dialogs.glade.h:39 msgid "Choose LRC file to download" msgstr "Выберите LRC-файл Ð´Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸" #: ../data/dialogs.glade.h:40 msgid "Colo_r:" msgstr "" #: ../data/dialogs.glade.h:41 msgid "Color _Theme:" msgstr "Ð¦Ð²ÐµÑ‚Ð¾Ð²Ð°Ñ _тема:" #: ../data/dialogs.glade.h:42 #, fuzzy msgid "Copyright 2009-2011 The OSD Lyrics project." msgstr "Copyright 2009 Проект OSD Lyrics." #: ../data/dialogs.glade.h:44 msgid "D_on't ask me again" msgstr "Больше не Ñпрашивать" #: ../data/dialogs.glade.h:45 msgid "Display mode:" msgstr "" #. In preference/download, automately download the most matched lyric #: ../data/dialogs.glade.h:47 msgid "Do_wnload the first candidate" msgstr "Загрузить первого кандидата" #: ../data/dialogs.glade.h:48 msgid "Doc_k" msgstr "" #: ../data/dialogs.glade.h:49 msgid "Don't assign lyric to this music" msgstr "Ðе назначать лирики Ñтой музыке" #: ../data/dialogs.glade.h:50 msgid "F_irst line:" msgstr "_ÐŸÐµÑ€Ð²Ð°Ñ Ñтрока:" #: ../data/dialogs.glade.h:51 msgid "GPL v3" msgstr "GPL v3" #: ../data/dialogs.glade.h:52 msgid "HTTP" msgstr "" #: ../data/dialogs.glade.h:53 msgid "Hide the OSD Window" msgstr "Скрыть OSD" #: ../data/dialogs.glade.h:54 msgid "If locked, you can't move OSD window by mouse" msgstr "Когда окно OSD закрепленно, вы не можете перемещать его мышкой" #: ../data/dialogs.glade.h:55 msgid "" "If there are more than one lrc files matched with the search condition, " "download the first one without prompting the user." msgstr "" "ЕÑли два и более LRC-файлов ÑоответÑтвуют уÑловию поиÑка, загружать первый, " "не ÑÐ¿Ñ€Ð°ÑˆÐ¸Ð²Ð°Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ" #: ../data/dialogs.glade.h:56 msgid "Inactiv_e lyrics:" msgstr "_ÐÐµÐ°ÐºÑ‚Ð¸Ð²Ð½Ð°Ñ Ð»Ð¸Ñ€Ð¸ÐºÐ°:" #: ../data/dialogs.glade.h:57 #, fuzzy msgid "Lyric _sites:" msgstr "Строки текÑта произведениÑ" #: ../data/dialogs.glade.h:58 msgid "Lyric delay +" msgstr "Задержка лирики +" #: ../data/dialogs.glade.h:59 msgid "Lyric delay -" msgstr "Задержка лирики -" #: ../data/dialogs.glade.h:60 msgid "Lyric lines:" msgstr "Строки текÑта произведениÑ" #: ../data/dialogs.glade.h:61 msgid "Mode:" msgstr "" #: ../data/dialogs.glade.h:62 msgid "No _proxy" msgstr "" #: ../data/dialogs.glade.h:63 msgid "Nor_mal" msgstr "" #: ../data/dialogs.glade.h:64 msgid "OSD _mode" msgstr "" #: ../data/dialogs.glade.h:65 msgid "OS_D" msgstr "" #: ../data/dialogs.glade.h:66 msgid "O_pacity:" msgstr "" #: ../data/dialogs.glade.h:67 msgid "Opaque" msgstr "" #. The outline width of text in OSD window #: ../data/dialogs.glade.h:69 msgid "Out_line:" msgstr "ОтÑтуп" #: ../data/dialogs.glade.h:70 msgid "Passwo_rd:" msgstr "" #: ../data/dialogs.glade.h:71 msgid "Pla_yer command:" msgstr "Команда плеера:" #: ../data/dialogs.glade.h:72 msgid "Po_rt:" msgstr "" #: ../data/dialogs.glade.h:73 msgid "Preferences" msgstr "ÐаÑтройки" #: ../data/dialogs.glade.h:74 msgid "SOCKS4" msgstr "" #: ../data/dialogs.glade.h:75 msgid "SOCKS5" msgstr "" #: ../data/dialogs.glade.h:76 msgid "Sc_rolling" msgstr "" #: ../data/dialogs.glade.h:77 msgid "Scroll _mode:" msgstr "" #: ../data/dialogs.glade.h:78 msgid "Scrolling _mode" msgstr "" #: ../data/dialogs.glade.h:79 msgid "Search lyrics" msgstr "ИÑкать лирику" #: ../data/dialogs.glade.h:80 msgid "Search lyrics from Internet" msgstr "ПоиÑк текÑтов в Интернет" #: ../data/dialogs.glade.h:81 msgid "Show _bubble notification" msgstr "" #: ../data/dialogs.glade.h:82 msgid "Show passwor_d" msgstr "" #: ../data/dialogs.glade.h:83 msgid "Si_ngle line" msgstr "ÐžÐ´Ð¸Ð½Ð°Ñ€Ð½Ñ Ð»Ð¸Ð½Ð¸Ñ" #: ../data/dialogs.glade.h:84 msgid "Strong" msgstr "" #: ../data/dialogs.glade.h:85 msgid "T_ranslucent on mouse over" msgstr "Полупрозрачный при наведением курÑором мышки" #: ../data/dialogs.glade.h:86 msgid "The artist of the current track" msgstr "ИÑполнитель текущей дорожки" #: ../data/dialogs.glade.h:87 msgid "The filename of the current track" msgstr "Файловое Ð¸Ð¼Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ¹ дорожки" #: ../data/dialogs.glade.h:88 msgid "The number of the current track" msgstr "Ðомер текущей дорожки" #: ../data/dialogs.glade.h:89 msgid "The title of album of the current track" msgstr "Ðльбом текущей дорожки" #: ../data/dialogs.glade.h:90 msgid "The title of the current track" msgstr "Заголовок текущей пеÑни" #: ../data/dialogs.glade.h:91 msgid "Transparent" msgstr "" #: ../data/dialogs.glade.h:92 msgid "Use _manual settings" msgstr "" #: ../data/dialogs.glade.h:93 msgid "Weak" msgstr "" #: ../data/dialogs.glade.h:94 msgid "_Artist:" msgstr "_ИÑполнитель:" #: ../data/dialogs.glade.h:95 msgid "_Assign lyric..." msgstr "Ðазначить лирику..." #: ../data/dialogs.glade.h:96 msgid "_Blur:" msgstr "" #: ../data/dialogs.glade.h:97 msgid "_Double lines" msgstr "Двойные линии" #: ../data/dialogs.glade.h:98 msgid "_Download" msgstr "_Скачать" #: ../data/dialogs.glade.h:99 msgid "_Font:" msgstr "_Шрифт:" #. From witch web site to search and download lyrics #: ../data/dialogs.glade.h:101 msgid "_From:" msgstr "_От:" #: ../data/dialogs.glade.h:102 msgid "_General" msgstr "_Общие" #: ../data/dialogs.glade.h:103 msgid "_Hide" msgstr "_Скрыть" #: ../data/dialogs.glade.h:104 msgid "_Host:" msgstr "" #: ../data/dialogs.glade.h:105 msgid "_Inactive color:" msgstr "" #: ../data/dialogs.glade.h:106 msgid "_Lock Position" msgstr "Запомниить раÑположение" #: ../data/dialogs.glade.h:107 msgid "_Lyric Location" msgstr "РаÑпо_ложение текÑта" #: ../data/dialogs.glade.h:108 msgid "_Network" msgstr "" #: ../data/dialogs.glade.h:109 msgid "_No lyric" msgstr "Ðет текÑта" #: ../data/dialogs.glade.h:110 msgid "_OSD" msgstr "_OSD" #: ../data/dialogs.glade.h:111 msgid "_Scrolling" msgstr "" #: ../data/dialogs.glade.h:112 msgid "_Search" msgstr "_ПоиÑк" #: ../data/dialogs.glade.h:113 msgid "_Search lyric..." msgstr "_ПоиÑк текÑта ..." #: ../data/dialogs.glade.h:114 msgid "_Second line:" msgstr "_Ð’Ñ‚Ð¾Ñ€Ð°Ñ Ñтрока:" #: ../data/dialogs.glade.h:115 msgid "_Title:" msgstr "Ð_азвание:" #: ../data/dialogs.glade.h:116 msgid "_Type:" msgstr "" #: ../data/dialogs.glade.h:117 msgid "_Username:" msgstr "" #: ../data/dialogs.glade.h:118 msgid "bottom" msgstr "Ñнизу" #: ../data/dialogs.glade.h:120 msgid "middle" msgstr "по Ñередине" #: ../data/dialogs.glade.h:121 msgid "top" msgstr "Ñверху" #: ../data/dialogs.glade.h:122 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" " Alexey Kotlyarov https://launchpad.net/~koterpillar\n" " Ivan Ysh https://launchpad.net/~gadjin\n" " Klim https://launchpad.net/~klim\n" " Nkolay Parukhin https://launchpad.net/~parukhin\n" " Stanislav Hanzhin https://launchpad.net/~hanzhin-stas\n" " Tiger Soldier https://launchpad.net/~tigersoldi\n" " Vitaly Antonov https://launchpad.net/~legidium\n" " gruzdev https://launchpad.net/~gruzdev\n" " Ðндрей МоиÑеев https://launchpad.net/~o2" #~ msgid "Searching lyrics" #~ msgstr "ПоиÑк текÑта" #~ msgid "Preview" #~ msgstr "Предварительный проÑмотр" #~ msgid "None" #~ msgstr "ПуÑто" #~ msgid "_Lock" #~ msgstr "_Заблокировать" #~ msgid "_Width:" #~ msgstr "Ширина:" #~ msgid "gtk-cancel" #~ msgstr "Отмена" #~ msgid "gtk-ok" #~ msgstr "ОК" #~ msgid "TODO: Add options here" #~ msgstr "TODO: Добавить Ñюда параметры" #~ msgid "" #~ "An OSD lyric show supporting multiple media players and downloading.\n" #~ msgstr "" #~ "OSD lyric поддерживает различные мультимедиа проигрыватели и Ñкачивание " #~ "лирики.\n" #~ msgid "Sogou" #~ msgstr "Sogou" #~ msgid "Qianqian" #~ msgstr "Qianqian" #~ msgid "Download _engine:" #~ msgstr "СиÑтема Ð´Ð»Ñ Ð¿Ð¾Ð¸Ñка текÑтов:" #~ msgid "Choose witch site you want to download from." #~ msgstr "Выберите Ñайт Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ð¾Ð³Ð¾ вы хотите Ñкачать." #~ msgid "Display mode" #~ msgstr "Режим отображениÑ" #~ msgid "MiniLyrics" #~ msgstr "MiniLyrics" #~ msgid "OSD" #~ msgstr "Экранное меню" #~ msgid "Scrolling" #~ msgstr "Прокрутка" #~ msgid "_Display notification" #~ msgstr "Показать уведомление" osdlyrics-0.5.5~rc1+dfsg1/po/stamp-po000066400000000000000000000000121345713535600174460ustar00rootroot00000000000000timestamp osdlyrics-0.5.5~rc1+dfsg1/po/tr.po000066400000000000000000000352461345713535600167710ustar00rootroot00000000000000# Turkish translation for osd-lyrics # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the osd-lyrics package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: osd-lyrics\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-11-17 23:18+0800\n" "PO-Revision-Date: 2010-11-27 04:10+0000\n" "Last-Translator: Engin BAHADIR \n" "Language-Team: Turkish \n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Launchpad-Export-Date: 2011-06-04 13:40+0000\n" "X-Generator: Launchpad (build 13144)\n" #: ../src/ol_menu.c:119 msgid "LRC files" msgstr "LRC Dosyaları" #: ../src/ol_menu.c:125 msgid "Choose LRC file to assign" msgstr "Atamak için LRC dosyası seçin" #: ../src/ol_main.c:60 msgid "" "The level of debug messages to log, can be 'none', 'error', 'debug', or " "'info'" msgstr "" "Kayda alınacak hata iletilerinin seviyesi, 'none', 'error', 'debug', veya " "'info' olarak belirtilebilir" #: ../src/ol_main.c:62 msgid "Show version information" msgstr "" #: ../src/ol_main.c:147 msgid "Download failed" msgstr "İndirme baÅŸarısız" #: ../src/ol_main.c:162 #, fuzzy, c-format msgid "Searching lyrics from %s" msgstr "%s içinde ÅŸarkı sözleri aranıyor..." #: ../src/ol_main.c:185 msgid "Cannot create the lyric directory" msgstr "Åžarkı Sözleri dizini oluÅŸturulamıyor" #: ../src/ol_main.c:201 msgid "Lyrics not found" msgstr "Åžarkı sözleri bulunamadı" #: ../src/ol_main.c:507 #, c-format msgid "No supported player is running, exit.\n" msgstr "Desteklenen bir ortam oynatıcı yok, çık.\n" #: ../src/ol_main.c:652 #, fuzzy msgid "" "debug level should be one of ``none'', ``error'', ``debug'', or ``info''" msgstr "" "Kayda alınacak hata iletilerinin seviyesi, 'none', 'error', 'debug', veya " "'info' olarak belirtilebilir" #: ../src/ol_main.c:675 msgid "Another OSD Lyrics is running, exit." msgstr "BaÅŸka bir OSD Lyrics çalışıyor, çık." #: ../src/ol_trayicon.c:36 ../src/ol_notify.c:26 msgid "Unknown title" msgstr "Bilinmeyen baÅŸlık" #: ../src/ol_trayicon.c:37 ../src/ol_notify.c:27 msgid "Unknown artist" msgstr "Bilinmeyen sanatçı" #: ../src/ol_trayicon.c:71 ../src/ol_trayicon.c:149 msgid "OSD Lyrics" msgstr "OSD Lyrics" #: ../src/ol_option.c:1631 msgid "Choose on startup" msgstr "" #. gtk_list_store_append (liststore, &iter); #. gtk_list_store_set (liststore, &iter, #. 0, "Customize", #. 1, "", #. -1); #: ../src/ol_option.c:1651 ../data/dialogs.glade.h:43 msgid "Customize" msgstr "ÖzelleÅŸtir" #: ../src/ol_option.c:1713 msgid "Select a folder" msgstr "Bir klasör seçiniz" #: ../src/ol_lrc_fetch_ttplayer.c:274 msgid "ttPlayer" msgstr "" #: ../src/ol_lrc_fetch_xiami.c:237 msgid "Xiami" msgstr "" #: ../src/ol_lrc_candidate_list.c:43 msgid "Title" msgstr "BaÅŸlık" #: ../src/ol_lrc_candidate_list.c:48 msgid "Artist" msgstr "Sanatçı" #: ../src/ol_search_dialog.c:31 #, c-format msgid "Searching lyrics from %s..." msgstr "%s içinde ÅŸarkı sözleri aranıyor..." #: ../src/ol_search_dialog.c:32 msgid "Ooops, no lyric found :(" msgstr "Åžarkı sözü bulunamadı :(" #: ../src/ol_search_dialog.c:33 #, c-format msgid "%d lyrics found :)" msgstr "%d adet ÅŸarkı sözü bulundu :)" #: ../src/ol_player_chooser.c:243 msgid "Supported players" msgstr "" #: ../src/ol_player_chooser.c:244 msgid "All players" msgstr "" #: ../src/ol_player_chooser.c:248 ../src/ol_player_chooser.c:319 #, fuzzy msgid "Choose a player to launch" msgstr "Atamak için LRC dosyası seçin" #: ../src/ol_player_chooser.c:264 #, fuzzy msgid "Use command:" msgstr "Oynatıcı komutu:" #: ../src/ol_player_chooser.c:268 msgid "Launch" msgstr "" #: ../src/ol_player_chooser.c:281 msgid "Remember my choice" msgstr "" #: ../src/ol_player_chooser.c:387 #, c-format msgid "Failed to launch %s" msgstr "" #: ../src/ol_player_chooser.c:397 #, c-format msgid "Launching %s" msgstr "" #: ../src/ol_player_chooser.c:399 #, c-format msgid "" "OSD Lyrics is trying to launch and connect to %s. Please wait for a second." msgstr "" #: ../src/ol_player_chooser.c:510 #, fuzzy msgid "There is no supported player running" msgstr "Desteklenen bir ortam oynatıcı yok, çık.\n" #: ../src/ol_player_chooser.c:511 #, fuzzy msgid "Please choose a player below to launch" msgstr "Atamak için LRC dosyası seçin" #: ../src/ol_player_chooser.c:515 #, c-format msgid "Failed to connect to %s" msgstr "" #: ../src/ol_player_chooser.c:517 #, c-format msgid "" "%s is not supported by OSD Lyrics, or not running. Please launch another " "player" msgstr "" #: ../src/ol_app_info.c:183 ../src/ol_app_info.c:193 msgid "Unnamed" msgstr "" #: ../src/ol_scroll_window.c:52 msgid "" "Drag to move the window\n" "Hold CTRL to seek" msgstr "" #: ../src/ol_scroll_window.c:53 msgid "Drag to move the window" msgstr "" #: ../data/dialogs.glade.h:2 #, no-c-format msgid "" " %t - Title\n" " %p - Artist\n" " %a - Album\n" " %n - Track number\n" " %f - File name (without extension)" msgstr "" " %t - BaÅŸlık\n" "%p - Sanatçı\n" "%a - Albüm\n" "%n - Parça Numarası\n" "%f - Dosya Adı (uzantı olmadan)" #: ../data/dialogs.glade.h:8 #, no-c-format msgid "%a - Album" msgstr "%a - Albüm" #: ../data/dialogs.glade.h:10 #, no-c-format msgid "%f - Filename" msgstr "%f - Dosya Adı" #: ../data/dialogs.glade.h:12 #, no-c-format msgid "%n - Track number" msgstr "%n - Parça numarası" #: ../data/dialogs.glade.h:14 #, no-c-format msgid "%p - Artist" msgstr "%p - Sanatçı" #: ../data/dialogs.glade.h:16 #, no-c-format msgid "%t - Title" msgstr "%t - BaÅŸlık" #: ../data/dialogs.glade.h:17 msgid "Background" msgstr "" #: ../data/dialogs.glade.h:18 msgid "Download" msgstr "" #: ../data/dialogs.glade.h:19 msgid "Lyric _Alignment" msgstr "Åžarkı Sözü Hiz_ası" #: ../data/dialogs.glade.h:20 msgid "Lyric _Color" msgstr "Åžarkı Sözü Ren_gi" #: ../data/dialogs.glade.h:21 msgid "Lyrics" msgstr "" #: ../data/dialogs.glade.h:22 msgid "Proxy" msgstr "" #: ../data/dialogs.glade.h:23 msgid "_Filename" msgstr "_Dosya Adı" #: ../data/dialogs.glade.h:24 msgid "_Path" msgstr "_Yol" #: ../data/dialogs.glade.h:26 #, no-c-format msgid "A single \"%\" means the directory where the playing music is." msgstr "Tekil \"%\" iÅŸareti çalınan müziÄŸin dizinini tanımlar." #: ../data/dialogs.glade.h:27 msgid "Ac_tive color:" msgstr "" #: ../data/dialogs.glade.h:28 msgid "Acti_ve lyrics:" msgstr "Etkin ÅŸarkı sö_zleri:" #: ../data/dialogs.glade.h:29 msgid "Adjust lyric delay by +0.2 s" msgstr "Åžarkı sözü gecikmesini +0.2 s olarak ayarla" #: ../data/dialogs.glade.h:30 msgid "Adjust lyrics delay by -0.2 s" msgstr "Åžarkı sözü gecikmesini -0.2 s olarak ayarla" #: ../data/dialogs.glade.h:31 msgid "Always" msgstr "" #: ../data/dialogs.glade.h:32 msgid "" "An OSD lyric show compatible with various media players and supports lyrics " "downloading.\n" msgstr "" "OSD lyric birçok ortam yürütücü ile uyumludur ve ÅŸarkı sözlerini indirmeyi " "destekler.\n" #: ../data/dialogs.glade.h:34 msgid "Assign local LRC file to current music" msgstr "Güncel parçaya yerel bir LRC dosyası ata" #: ../data/dialogs.glade.h:35 msgid "Au_to-detect system settings" msgstr "" #: ../data/dialogs.glade.h:36 msgid "Auto la_unch player:" msgstr "Oynatıcı_yı otomatik baÅŸlat:" #: ../data/dialogs.glade.h:37 msgid "By lines" msgstr "" #: ../data/dialogs.glade.h:38 msgid "C_ustomize colors" msgstr "_Renkleri özelleÅŸtir" #: ../data/dialogs.glade.h:39 msgid "Choose LRC file to download" msgstr "İndirilecek LRC Dosyasını Seçin" #: ../data/dialogs.glade.h:40 msgid "Colo_r:" msgstr "" #: ../data/dialogs.glade.h:41 msgid "Color _Theme:" msgstr "Renk _Teması:" #: ../data/dialogs.glade.h:42 #, fuzzy msgid "Copyright 2009-2011 The OSD Lyrics project." msgstr "Copyright 2009 The OSD Lyrics project." #: ../data/dialogs.glade.h:44 msgid "D_on't ask me again" msgstr "Tekrar s_orma" #: ../data/dialogs.glade.h:45 msgid "Display mode:" msgstr "" #. In preference/download, automately download the most matched lyric #: ../data/dialogs.glade.h:47 msgid "Do_wnload the first candidate" msgstr "İlk sürümü in_dir" #: ../data/dialogs.glade.h:48 msgid "Doc_k" msgstr "" #: ../data/dialogs.glade.h:49 msgid "Don't assign lyric to this music" msgstr "Bu ÅŸarkıya ÅŸarkı sözü atama" #: ../data/dialogs.glade.h:50 msgid "F_irst line:" msgstr "_İlk satır" #: ../data/dialogs.glade.h:51 msgid "GPL v3" msgstr "GPL v3" #: ../data/dialogs.glade.h:52 msgid "HTTP" msgstr "" #: ../data/dialogs.glade.h:53 msgid "Hide the OSD Window" msgstr "OSD Penceresini Gizle" #: ../data/dialogs.glade.h:54 msgid "If locked, you can't move OSD window by mouse" msgstr "EÄŸer kilitliyse, OSD penceresini hareket ettiremezsiniz" #: ../data/dialogs.glade.h:55 msgid "" "If there are more than one lrc files matched with the search condition, " "download the first one without prompting the user." msgstr "" "EÄŸer arama kalıbınıza uyan birden fazla lrc dosyası varsa, kullanıcıyı " "uyarmadan ilkini indir." #: ../data/dialogs.glade.h:56 msgid "Inactiv_e lyrics:" msgstr "Etkin olmayan ÅŸarkı sözl_eri:" #: ../data/dialogs.glade.h:57 #, fuzzy msgid "Lyric _sites:" msgstr "Åžarkı sözü satırları:" #: ../data/dialogs.glade.h:58 msgid "Lyric delay +" msgstr "Åžarkı sözü gecikmesi +" #: ../data/dialogs.glade.h:59 msgid "Lyric delay -" msgstr "Åžarkı sözü gecikmesi -" #: ../data/dialogs.glade.h:60 msgid "Lyric lines:" msgstr "Åžarkı sözü satırları:" #: ../data/dialogs.glade.h:61 msgid "Mode:" msgstr "" #: ../data/dialogs.glade.h:62 msgid "No _proxy" msgstr "" #: ../data/dialogs.glade.h:63 msgid "Nor_mal" msgstr "" #: ../data/dialogs.glade.h:64 msgid "OSD _mode" msgstr "" #: ../data/dialogs.glade.h:65 msgid "OS_D" msgstr "" #: ../data/dialogs.glade.h:66 msgid "O_pacity:" msgstr "" #: ../data/dialogs.glade.h:67 msgid "Opaque" msgstr "" #. The outline width of text in OSD window #: ../data/dialogs.glade.h:69 msgid "Out_line:" msgstr "_Çıkış satırı:" #: ../data/dialogs.glade.h:70 msgid "Passwo_rd:" msgstr "" #: ../data/dialogs.glade.h:71 msgid "Pla_yer command:" msgstr "Oynatıcı komutu:" #: ../data/dialogs.glade.h:72 msgid "Po_rt:" msgstr "" #: ../data/dialogs.glade.h:73 msgid "Preferences" msgstr "Tercihler" #: ../data/dialogs.glade.h:74 msgid "SOCKS4" msgstr "" #: ../data/dialogs.glade.h:75 msgid "SOCKS5" msgstr "" #: ../data/dialogs.glade.h:76 msgid "Sc_rolling" msgstr "" #: ../data/dialogs.glade.h:77 msgid "Scroll _mode:" msgstr "" #: ../data/dialogs.glade.h:78 msgid "Scrolling _mode" msgstr "" #: ../data/dialogs.glade.h:79 msgid "Search lyrics" msgstr "Åžarkı Sözü Ara" #: ../data/dialogs.glade.h:80 msgid "Search lyrics from Internet" msgstr "Åžarkı sözlerini internette ara" #: ../data/dialogs.glade.h:81 msgid "Show _bubble notification" msgstr "" #: ../data/dialogs.glade.h:82 msgid "Show passwor_d" msgstr "" #: ../data/dialogs.glade.h:83 msgid "Si_ngle line" msgstr "Tekil satır" #: ../data/dialogs.glade.h:84 msgid "Strong" msgstr "" #: ../data/dialogs.glade.h:85 msgid "T_ranslucent on mouse over" msgstr "Fa_re üzerindeyken saydamlaÅŸtır" #: ../data/dialogs.glade.h:86 msgid "The artist of the current track" msgstr "Güncel parçanın solisti" #: ../data/dialogs.glade.h:87 msgid "The filename of the current track" msgstr "Güncel parçanın dosya adı" #: ../data/dialogs.glade.h:88 msgid "The number of the current track" msgstr "Güncel parçanın numarası" #: ../data/dialogs.glade.h:89 msgid "The title of album of the current track" msgstr "Güncel parçanın albüm baÅŸlığı" #: ../data/dialogs.glade.h:90 msgid "The title of the current track" msgstr "Güncel parçanın baÅŸlığı" #: ../data/dialogs.glade.h:91 msgid "Transparent" msgstr "" #: ../data/dialogs.glade.h:92 msgid "Use _manual settings" msgstr "" #: ../data/dialogs.glade.h:93 msgid "Weak" msgstr "" #: ../data/dialogs.glade.h:94 msgid "_Artist:" msgstr "S_anatçı:" #: ../data/dialogs.glade.h:95 msgid "_Assign lyric..." msgstr "Åž_arkı sözü ata..." #: ../data/dialogs.glade.h:96 msgid "_Blur:" msgstr "" #: ../data/dialogs.glade.h:97 msgid "_Double lines" msgstr "Çi_fte satırlar" #: ../data/dialogs.glade.h:98 msgid "_Download" msgstr "İn_dir" #: ../data/dialogs.glade.h:99 msgid "_Font:" msgstr "_Yazı tipi:" #. From witch web site to search and download lyrics #: ../data/dialogs.glade.h:101 msgid "_From:" msgstr "_Kaynak:" #: ../data/dialogs.glade.h:102 msgid "_General" msgstr "_Genel" #: ../data/dialogs.glade.h:103 msgid "_Hide" msgstr "_Gizle" #: ../data/dialogs.glade.h:104 msgid "_Host:" msgstr "" #: ../data/dialogs.glade.h:105 msgid "_Inactive color:" msgstr "" #: ../data/dialogs.glade.h:106 msgid "_Lock Position" msgstr "Konumu Ki_litle" #: ../data/dialogs.glade.h:107 msgid "_Lyric Location" msgstr "Åžarkı Sözü _Dizini" #: ../data/dialogs.glade.h:108 msgid "_Network" msgstr "" #: ../data/dialogs.glade.h:109 msgid "_No lyric" msgstr "Åžarkı sözü _yok" #: ../data/dialogs.glade.h:110 msgid "_OSD" msgstr "_OSD" #: ../data/dialogs.glade.h:111 msgid "_Scrolling" msgstr "" #: ../data/dialogs.glade.h:112 msgid "_Search" msgstr "Ara_ma" #: ../data/dialogs.glade.h:113 msgid "_Search lyric..." msgstr "Åžarkı _sözü ara..." #: ../data/dialogs.glade.h:114 msgid "_Second line:" msgstr "İkinci Dize" #: ../data/dialogs.glade.h:115 msgid "_Title:" msgstr "_BaÅŸlık:" #: ../data/dialogs.glade.h:116 msgid "_Type:" msgstr "" #: ../data/dialogs.glade.h:117 msgid "_Username:" msgstr "" #: ../data/dialogs.glade.h:118 msgid "bottom" msgstr "alt" #: ../data/dialogs.glade.h:120 msgid "middle" msgstr "orta" #: ../data/dialogs.glade.h:121 msgid "top" msgstr "üst" #: ../data/dialogs.glade.h:122 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" " Engin BAHADIR https://launchpad.net/~enginbah\n" " GüneyTekfidan https://launchpad.net/~ubuntuturk-hotmail\n" " Tiger Soldier https://launchpad.net/~tigersoldi\n" " zeugma https://launchpad.net/~sunder67" #~ msgid "Searching lyrics" #~ msgstr "Åžarkı Sözleri aranıyor" #~ msgid "Preview" #~ msgstr "Önizleme" #~ msgid "None" #~ msgstr "Hiçbiri" #~ msgid "_Width:" #~ msgstr "_GeniÅŸlik:" #~ msgid "Choose witch site you want to download from." #~ msgstr "Hangi Siteden İndirmek İstediÄŸinizi Seçin" #~ msgid "MiniLyrics" #~ msgstr "MiniLyrics" #~ msgid "Sogou" #~ msgstr "Sogou" #~ msgid "Qianqian" #~ msgstr "Qianqian" #~ msgid "Display mode" #~ msgstr "Görünüm kipi" #~ msgid "OSD" #~ msgstr "OSD" #~ msgid "Scrolling" #~ msgstr "Kaydırma" #~ msgid "_Display notification" #~ msgstr "Bilgilen_dirmeyi görüntüle" #~ msgid "Download _engine:" #~ msgstr "İndirm_e motoru:" osdlyrics-0.5.5~rc1+dfsg1/po/uk.po000066400000000000000000000410361345713535600167550ustar00rootroot00000000000000# Ukrainian translation for osd-lyrics # Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010 # This file is distributed under the same license as the osd-lyrics package. # FIRST AUTHOR , 2010. # Launchpad Translations Administrators , 2010. # msgid "" msgstr "" "Project-Id-Version: osd-lyrics\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-11-17 23:18+0800\n" "PO-Revision-Date: 2010-07-14 15:11+0000\n" "Last-Translator: Sergiy Gavrylov \n" "Language-Team: Ukrainian \n" "Language: uk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Launchpad-Export-Date: 2011-06-04 13:40+0000\n" "X-Generator: Launchpad (build 13144)\n" #: ../src/ol_menu.c:119 msgid "LRC files" msgstr "Файли LRC" #: ../src/ol_menu.c:125 msgid "Choose LRC file to assign" msgstr "Вибрати файли LRC Ð´Ð»Ñ Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ" #: ../src/ol_main.c:60 msgid "" "The level of debug messages to log, can be 'none', 'error', 'debug', or " "'info'" msgstr "" "Рівень налагоджувальних повідомлень у журналі може бути Ñк «немає», " "«помилка», «налагодженнÑ» та «інфо»" #: ../src/ol_main.c:62 msgid "Show version information" msgstr "" #: ../src/ol_main.c:147 msgid "Download failed" msgstr "Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð½Ðµ вдалоÑÑŒ" #: ../src/ol_main.c:162 #, fuzzy, c-format msgid "Searching lyrics from %s" msgstr "Пошук текÑтів піÑень на %s..." #: ../src/ol_main.c:185 msgid "Cannot create the lyric directory" msgstr "Ðе вдаєтьÑÑ Ñтворити теку Ð´Ð»Ñ Ñ‚ÐµÐºÑтів піÑень" #: ../src/ol_main.c:201 msgid "Lyrics not found" msgstr "ТекÑÑ‚ піÑні не знайдений" #: ../src/ol_main.c:507 #, c-format msgid "No supported player is running, exit.\n" msgstr "Працює не підтримуваний програвач, вихід.\n" #: ../src/ol_main.c:652 #, fuzzy msgid "" "debug level should be one of ``none'', ``error'', ``debug'', or ``info''" msgstr "" "Рівень налагоджувальних повідомлень у журналі може бути Ñк «немає», " "«помилка», «налагодженнÑ» та «інфо»" #: ../src/ol_main.c:675 msgid "Another OSD Lyrics is running, exit." msgstr "Працює інший OSD Lyrics, вихід." #: ../src/ol_trayicon.c:36 ../src/ol_notify.c:26 msgid "Unknown title" msgstr "Ðевідома назва" #: ../src/ol_trayicon.c:37 ../src/ol_notify.c:27 msgid "Unknown artist" msgstr "Ðевідомий виконавець" #: ../src/ol_trayicon.c:71 ../src/ol_trayicon.c:149 msgid "OSD Lyrics" msgstr "OSD Lyrics" #: ../src/ol_option.c:1631 msgid "Choose on startup" msgstr "" #. gtk_list_store_append (liststore, &iter); #. gtk_list_store_set (liststore, &iter, #. 0, "Customize", #. 1, "", #. -1); #: ../src/ol_option.c:1651 ../data/dialogs.glade.h:43 msgid "Customize" msgstr "Ðалаштувати" #: ../src/ol_option.c:1713 msgid "Select a folder" msgstr "Вибрати теку" #: ../src/ol_lrc_fetch_ttplayer.c:274 msgid "ttPlayer" msgstr "" #: ../src/ol_lrc_fetch_xiami.c:237 msgid "Xiami" msgstr "" #: ../src/ol_lrc_candidate_list.c:43 msgid "Title" msgstr "Ðазва" #: ../src/ol_lrc_candidate_list.c:48 msgid "Artist" msgstr "Виконавець" #: ../src/ol_search_dialog.c:31 #, c-format msgid "Searching lyrics from %s..." msgstr "Пошук текÑтів піÑень на %s..." #: ../src/ol_search_dialog.c:32 msgid "Ooops, no lyric found :(" msgstr "Ой, текÑÑ‚ не знайдений :(" #: ../src/ol_search_dialog.c:33 #, c-format msgid "%d lyrics found :)" msgstr "ТекÑÑ‚ %d знайдений :)" #: ../src/ol_player_chooser.c:243 msgid "Supported players" msgstr "" #: ../src/ol_player_chooser.c:244 msgid "All players" msgstr "" #: ../src/ol_player_chooser.c:248 ../src/ol_player_chooser.c:319 #, fuzzy msgid "Choose a player to launch" msgstr "Команда Ð´Ð»Ñ Ð·Ð°Ð¿ÑƒÑку програвача" #: ../src/ol_player_chooser.c:264 #, fuzzy msgid "Use command:" msgstr "_Команда програвача:" #: ../src/ol_player_chooser.c:268 msgid "Launch" msgstr "" #: ../src/ol_player_chooser.c:281 msgid "Remember my choice" msgstr "" #: ../src/ol_player_chooser.c:387 #, c-format msgid "Failed to launch %s" msgstr "" #: ../src/ol_player_chooser.c:397 #, c-format msgid "Launching %s" msgstr "" #: ../src/ol_player_chooser.c:399 #, c-format msgid "" "OSD Lyrics is trying to launch and connect to %s. Please wait for a second." msgstr "" #: ../src/ol_player_chooser.c:510 #, fuzzy msgid "There is no supported player running" msgstr "Працює не підтримуваний програвач, вихід.\n" #: ../src/ol_player_chooser.c:511 #, fuzzy msgid "Please choose a player below to launch" msgstr "Команда Ð´Ð»Ñ Ð·Ð°Ð¿ÑƒÑку програвача" #: ../src/ol_player_chooser.c:515 #, c-format msgid "Failed to connect to %s" msgstr "" #: ../src/ol_player_chooser.c:517 #, c-format msgid "" "%s is not supported by OSD Lyrics, or not running. Please launch another " "player" msgstr "" #: ../src/ol_app_info.c:183 ../src/ol_app_info.c:193 msgid "Unnamed" msgstr "" #: ../src/ol_scroll_window.c:52 msgid "" "Drag to move the window\n" "Hold CTRL to seek" msgstr "" #: ../src/ol_scroll_window.c:53 msgid "Drag to move the window" msgstr "" #: ../data/dialogs.glade.h:2 #, no-c-format msgid "" " %t - Title\n" " %p - Artist\n" " %a - Album\n" " %n - Track number\n" " %f - File name (without extension)" msgstr "" " %t - Ðазва\n" " %p - Виконавець\n" " %a - Ðльбом\n" " %n - Ðомер доріжки\n" " %f - Ðазва файла (без розширеннÑ)" #: ../data/dialogs.glade.h:8 #, no-c-format msgid "%a - Album" msgstr "%a - альбом" #: ../data/dialogs.glade.h:10 #, no-c-format msgid "%f - Filename" msgstr "%f - назва файла" #: ../data/dialogs.glade.h:12 #, no-c-format msgid "%n - Track number" msgstr "%n - номер доріжки" #: ../data/dialogs.glade.h:14 #, no-c-format msgid "%p - Artist" msgstr "%p - виконавець" #: ../data/dialogs.glade.h:16 #, no-c-format msgid "%t - Title" msgstr "%t - назва" #: ../data/dialogs.glade.h:17 msgid "Background" msgstr "" #: ../data/dialogs.glade.h:18 msgid "Download" msgstr "" #: ../data/dialogs.glade.h:19 msgid "Lyric _Alignment" msgstr "Вир_Ñ–Ð²Ð½ÑŽÐ²Ð°Ð½Ð½Ñ Ñ‚ÐµÐºÑту піÑні" #: ../data/dialogs.glade.h:20 msgid "Lyric _Color" msgstr "_Колір текÑту" #: ../data/dialogs.glade.h:21 msgid "Lyrics" msgstr "" #: ../data/dialogs.glade.h:22 msgid "Proxy" msgstr "" #: ../data/dialogs.glade.h:23 msgid "_Filename" msgstr "_Ðазва файла" #: ../data/dialogs.glade.h:24 msgid "_Path" msgstr "_ШлÑÑ…:" #: ../data/dialogs.glade.h:26 #, no-c-format msgid "A single \"%\" means the directory where the playing music is." msgstr "Окремий \"%\" означає теку з відтворюваною музикою." #: ../data/dialogs.glade.h:27 msgid "Ac_tive color:" msgstr "" #: ../data/dialogs.glade.h:28 msgid "Acti_ve lyrics:" msgstr "Ðкти_вні текÑти:" #: ../data/dialogs.glade.h:29 msgid "Adjust lyric delay by +0.2 s" msgstr "Ð’Ñтановити затримку текÑту +0.2 Ñ" #: ../data/dialogs.glade.h:30 msgid "Adjust lyrics delay by -0.2 s" msgstr "Ð’Ñтановити затримку текÑту -0.2 Ñ" #: ../data/dialogs.glade.h:31 msgid "Always" msgstr "" #: ../data/dialogs.glade.h:32 msgid "" "An OSD lyric show compatible with various media players and supports lyrics " "downloading.\n" msgstr "" "Програма OSD lyric ÑуміÑна з різними медіа-плеєрами Ñ– підтримує Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ " "текÑтів піÑень.\n" #: ../data/dialogs.glade.h:34 msgid "Assign local LRC file to current music" msgstr "Призначає локальний LRC-файл Ð´Ð»Ñ Ð¿Ð¾Ñ‚Ð¾Ñ‡Ð½Ð¾Ñ— піÑні" #: ../data/dialogs.glade.h:35 msgid "Au_to-detect system settings" msgstr "" #: ../data/dialogs.glade.h:36 msgid "Auto la_unch player:" msgstr "_ÐвтозапуÑк програвача:" #: ../data/dialogs.glade.h:37 msgid "By lines" msgstr "" #: ../data/dialogs.glade.h:38 msgid "C_ustomize colors" msgstr "Ðа_Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð»ÑŒÐ¾Ñ€Ñ–Ð²" #: ../data/dialogs.glade.h:39 msgid "Choose LRC file to download" msgstr "Вибрати LRC-файл Ð´Ð»Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ" #: ../data/dialogs.glade.h:40 msgid "Colo_r:" msgstr "" #: ../data/dialogs.glade.h:41 msgid "Color _Theme:" msgstr "_Тема кольорів:" #: ../data/dialogs.glade.h:42 #, fuzzy msgid "Copyright 2009-2011 The OSD Lyrics project." msgstr "ÐвторÑьке право: 2009 Проект OSD Lyrics." #: ../data/dialogs.glade.h:44 msgid "D_on't ask me again" msgstr "Ðе _питати знову" #: ../data/dialogs.glade.h:45 msgid "Display mode:" msgstr "" #. In preference/download, automately download the most matched lyric #: ../data/dialogs.glade.h:47 msgid "Do_wnload the first candidate" msgstr "За_вантажити перший варіант" #: ../data/dialogs.glade.h:48 msgid "Doc_k" msgstr "" #: ../data/dialogs.glade.h:49 msgid "Don't assign lyric to this music" msgstr "Ðе призначати текÑÑ‚ Ð´Ð»Ñ Ñ†Ñ–Ñ”Ñ— музики" #: ../data/dialogs.glade.h:50 msgid "F_irst line:" msgstr "П_ерший Ñ€Ñдок:" #: ../data/dialogs.glade.h:51 msgid "GPL v3" msgstr "Ð›Ñ–Ñ†ÐµÐ½Ð·Ñ–Ñ GPL v3" #: ../data/dialogs.glade.h:52 msgid "HTTP" msgstr "" #: ../data/dialogs.glade.h:53 msgid "Hide the OSD Window" msgstr "Приховати вікно OSD" #: ../data/dialogs.glade.h:54 msgid "If locked, you can't move OSD window by mouse" msgstr "" "Ðе можливо переміÑтити вказівником миші вікно OSD, Ñкщо воно заблоковане" #: ../data/dialogs.glade.h:55 msgid "" "If there are more than one lrc files matched with the search condition, " "download the first one without prompting the user." msgstr "" "Якщо Ñ” кілька LRC-файлів, Ñкі відповідають умовам пошуку, завантажувати " "першої варіант без Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувачем." #: ../data/dialogs.glade.h:56 msgid "Inactiv_e lyrics:" msgstr "Ð_еактивні текÑти:" #: ../data/dialogs.glade.h:57 #, fuzzy msgid "Lyric _sites:" msgstr "РÑдки текÑту:" #: ../data/dialogs.glade.h:58 msgid "Lyric delay +" msgstr "Затримка текÑту +" #: ../data/dialogs.glade.h:59 msgid "Lyric delay -" msgstr "Затримка текÑту -" #: ../data/dialogs.glade.h:60 msgid "Lyric lines:" msgstr "РÑдки текÑту:" #: ../data/dialogs.glade.h:61 msgid "Mode:" msgstr "" #: ../data/dialogs.glade.h:62 msgid "No _proxy" msgstr "" #: ../data/dialogs.glade.h:63 msgid "Nor_mal" msgstr "" #: ../data/dialogs.glade.h:64 msgid "OSD _mode" msgstr "" #: ../data/dialogs.glade.h:65 msgid "OS_D" msgstr "" #: ../data/dialogs.glade.h:66 msgid "O_pacity:" msgstr "" #: ../data/dialogs.glade.h:67 msgid "Opaque" msgstr "" #. The outline width of text in OSD window #: ../data/dialogs.glade.h:69 msgid "Out_line:" msgstr "С_хема:" #: ../data/dialogs.glade.h:70 msgid "Passwo_rd:" msgstr "" #: ../data/dialogs.glade.h:71 msgid "Pla_yer command:" msgstr "_Команда програвача:" #: ../data/dialogs.glade.h:72 msgid "Po_rt:" msgstr "" #: ../data/dialogs.glade.h:73 msgid "Preferences" msgstr "ÐалаштуваннÑ" #: ../data/dialogs.glade.h:74 msgid "SOCKS4" msgstr "" #: ../data/dialogs.glade.h:75 msgid "SOCKS5" msgstr "" #: ../data/dialogs.glade.h:76 msgid "Sc_rolling" msgstr "" #: ../data/dialogs.glade.h:77 msgid "Scroll _mode:" msgstr "" #: ../data/dialogs.glade.h:78 msgid "Scrolling _mode" msgstr "" #: ../data/dialogs.glade.h:79 msgid "Search lyrics" msgstr "Пошук текÑтів піÑень" #: ../data/dialogs.glade.h:80 msgid "Search lyrics from Internet" msgstr "Пошук текÑтів піÑень в Інтернеті" #: ../data/dialogs.glade.h:81 msgid "Show _bubble notification" msgstr "" #: ../data/dialogs.glade.h:82 msgid "Show passwor_d" msgstr "" #: ../data/dialogs.glade.h:83 msgid "Si_ngle line" msgstr "Оди_н Ñ€Ñдок" #: ../data/dialogs.glade.h:84 msgid "Strong" msgstr "" #: ../data/dialogs.glade.h:85 msgid "T_ranslucent on mouse over" msgstr "Ð_апівпрозоре під вказівником" #: ../data/dialogs.glade.h:86 msgid "The artist of the current track" msgstr "Виконавець поточної доріжки" #: ../data/dialogs.glade.h:87 msgid "The filename of the current track" msgstr "Ðазва файла поточної доріжки" #: ../data/dialogs.glade.h:88 msgid "The number of the current track" msgstr "Ðомер поточної доріжки" #: ../data/dialogs.glade.h:89 msgid "The title of album of the current track" msgstr "Ðазва альбому поточної доріжки" #: ../data/dialogs.glade.h:90 msgid "The title of the current track" msgstr "Ðазва поточної доріжки" #: ../data/dialogs.glade.h:91 msgid "Transparent" msgstr "" #: ../data/dialogs.glade.h:92 msgid "Use _manual settings" msgstr "" #: ../data/dialogs.glade.h:93 msgid "Weak" msgstr "" #: ../data/dialogs.glade.h:94 msgid "_Artist:" msgstr "_Виконавець:" #: ../data/dialogs.glade.h:95 msgid "_Assign lyric..." msgstr "Призн_ачити текÑÑ‚..." #: ../data/dialogs.glade.h:96 msgid "_Blur:" msgstr "" #: ../data/dialogs.glade.h:97 msgid "_Double lines" msgstr "_Два Ñ€Ñдки" #: ../data/dialogs.glade.h:98 msgid "_Download" msgstr "_Завантажити" #: ../data/dialogs.glade.h:99 msgid "_Font:" msgstr "_Шрифт:" #. From witch web site to search and download lyrics #: ../data/dialogs.glade.h:101 msgid "_From:" msgstr "_З:" #: ../data/dialogs.glade.h:102 msgid "_General" msgstr "_Загальне" #: ../data/dialogs.glade.h:103 msgid "_Hide" msgstr "_Приховати" #: ../data/dialogs.glade.h:104 msgid "_Host:" msgstr "" #: ../data/dialogs.glade.h:105 msgid "_Inactive color:" msgstr "" #: ../data/dialogs.glade.h:106 msgid "_Lock Position" msgstr "_Заблокувати положеннÑ" #: ../data/dialogs.glade.h:107 msgid "_Lyric Location" msgstr "Мі_Ñце Ñ€Ð¾Ð·Ñ‚Ð°ÑˆÑƒÐ²Ð°Ð½Ð½Ñ Ñ‚ÐµÐºÑтів" #: ../data/dialogs.glade.h:108 msgid "_Network" msgstr "" #: ../data/dialogs.glade.h:109 msgid "_No lyric" msgstr "_Ðемає текÑту" #: ../data/dialogs.glade.h:110 msgid "_OSD" msgstr "_OSD" #: ../data/dialogs.glade.h:111 msgid "_Scrolling" msgstr "" #: ../data/dialogs.glade.h:112 msgid "_Search" msgstr "_Пошук" #: ../data/dialogs.glade.h:113 msgid "_Search lyric..." msgstr "П_ошук текÑту піÑні..." #: ../data/dialogs.glade.h:114 msgid "_Second line:" msgstr "_Другий Ñ€Ñдок:" #: ../data/dialogs.glade.h:115 msgid "_Title:" msgstr "_Ðазва:" #: ../data/dialogs.glade.h:116 msgid "_Type:" msgstr "" #: ../data/dialogs.glade.h:117 msgid "_Username:" msgstr "" #: ../data/dialogs.glade.h:118 msgid "bottom" msgstr "внизу" #: ../data/dialogs.glade.h:120 msgid "middle" msgstr "в Ñередині" #: ../data/dialogs.glade.h:121 msgid "top" msgstr "вгорі" #: ../data/dialogs.glade.h:122 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" " Sergiy Gavrylov https://launchpad.net/~gavro" #~ msgid "Searching lyrics" #~ msgstr "Пошук текÑту піÑні" #~ msgid "Preview" #~ msgstr "Попередній переглÑд" #~ msgid "None" #~ msgstr "Ðемає" #~ msgid "Qianqian" #~ msgstr "Qianqian" #~ msgid "Sogou" #~ msgstr "Sogou" #~ msgid "MiniLyrics" #~ msgstr "MiniLyrics" #~ msgid "If no player detected, launch the given player" #~ msgstr "Якщо програвач не знайдений, запуÑкаєтьÑÑ Ð·Ð°Ð´Ð°Ð½Ð¸Ð¹" #~ msgid "_Display notification" #~ msgstr "_Показувати ÑповіщеннÑ" #~ msgid "_Width:" #~ msgstr "_Ширина:" #~ msgid "Choose witch site you want to download from." #~ msgstr "Виберіть веб-Ñайт Ð´Ð»Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ñ‚ÐµÐºÑтів." #~ msgid "Download _engine:" #~ msgstr "Ру_шій завантаженнÑ:" #~ msgid "OSD" #~ msgstr "OSD" #~ msgid "Display mode" #~ msgstr "Режим показу" #~ msgid "Scrolling" #~ msgstr "ГортаннÑ" osdlyrics-0.5.5~rc1+dfsg1/po/zh_CN.po000066400000000000000000000400341345713535600173340ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Free Software Foundation, Inc. # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: osd-lyrics 0.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-09-06 12:39+0800\n" "PO-Revision-Date: 2012-09-06 12:40+0800\n" "Last-Translator: Tiger Soldier \n" "Language-Team: Simplified Chinese \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2010-06-04 13:48+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../src/ol_menu.c:126 msgid "LRC files" msgstr "LRC文件" #: ../src/ol_menu.c:132 msgid "Choose LRC file to assign" msgstr "选择è¦å…³è”çš„LRC文件" #: ../src/ol_main.c:59 msgid "" "The level of debug messages to log, can be 'none', 'error', 'debug', or " "'info'" msgstr "è¦è®°å½•的调试信æ¯çº§åˆ«ï¼Œå¯ä»¥ä¸º'none'ã€'error'ã€'debug'或'info'" #: ../src/ol_main.c:61 msgid "Show version information" msgstr "显示版本信æ¯" #: ../src/ol_main.c:192 ../src/ol_search_dialog.c:239 msgid "Fail to download lyric" msgstr "下载歌è¯å¤±è´¥" #: ../src/ol_main.c:209 msgid "Downloading lyric" msgstr "正在下载歌è¯" #: ../src/ol_main.c:238 msgid "Lyrics not found" msgstr "找ä¸åˆ°åŒ¹é…的歌è¯" #: ../src/ol_main.c:253 #, c-format msgid "Searching lyrics from %s" msgstr "正在从%sæœç´¢æ­Œè¯" #: ../src/ol_main.c:471 #, c-format msgid "No supported player is running, exit.\n" msgstr "没有检测到支æŒçš„æ’­æ”¾å™¨ï¼Œç¨‹åºè‡ªåŠ¨é€€å‡ºã€‚\n" #: ../src/ol_main.c:597 msgid "" "debug level should be one of ``none'', ``error'', ``debug'', or ``info''" msgstr "调试级别应该为\"none\"ã€\"error\"ã€\"debug\"或\"info\"" #: ../src/ol_main.c:643 msgid "Another OSD Lyrics is running, exit." msgstr "OSD Lyricså·²ç»è¿è¡Œï¼Œç¨‹åºè‡ªåŠ¨é€€å‡ºã€‚" #: ../src/ol_trayicon.c:36 ../src/ol_notify.c:27 msgid "Unknown title" msgstr "未知歌å" #: ../src/ol_trayicon.c:37 ../src/ol_notify.c:28 msgid "Unknown artist" msgstr "未知歌手" #: ../src/ol_trayicon.c:73 ../data/osdlyrics.desktop.in.h:1 msgid "OSD Lyrics" msgstr "OSD Lyrics" #: ../src/ol_option.c:1598 msgid "Choose on startup" msgstr "å¯åŠ¨æ—¶é€‰æ‹©" #. gtk_list_store_append (liststore, &iter); #. gtk_list_store_set (liststore, &iter, #. 0, "Customize", #. 1, "", #. -1); #: ../src/ol_option.c:1620 ../data/dialogs.glade.h:7 msgid "Customize" msgstr "自定义" #: ../src/ol_option.c:1682 msgid "Select a folder" msgstr "选择文件夹" #: ../src/ol_lyric_candidate_list.c:47 msgid "Title" msgstr "æ­Œå" #: ../src/ol_lyric_candidate_list.c:52 msgid "Artist" msgstr "歌手" #: ../src/ol_lyric_candidate_list.c:57 msgid "Album" msgstr "专辑" #: ../src/ol_search_dialog.c:29 #, c-format msgid "Searching lyrics from %s..." msgstr "正在从%sæœç´¢æ­Œè¯..." #: ../src/ol_search_dialog.c:30 msgid "Ooops, no lyric found :(" msgstr "å›§, 找ä¸åˆ°æ­Œè¯:(" #: ../src/ol_search_dialog.c:31 #, c-format msgid "%d lyrics found :)" msgstr "找到%d个歌è¯:)" #: ../src/ol_search_dialog.c:32 msgid "Fail to search. Please check network connection" msgstr "æœç´¢å¤±è´¥ã€‚请检查网络连接" #: ../src/ol_search_dialog.c:33 msgid "Cancelled" msgstr "已喿¶ˆ" #: ../src/ol_search_dialog.c:96 msgid "Downloading..." msgstr "正在下载..." #: ../src/ol_search_dialog.c:228 msgid "Download complete, but fail to assign to the track" msgstr "ä¸‹è½½å®Œæ¯•ï¼Œå…³è”æ­Œè¯å‡ºé”™" #: ../src/ol_search_dialog.c:232 msgid "Download complete" msgstr "下载完毕" #: ../src/ol_player_chooser.c:247 msgid "Supported players" msgstr "支æŒçš„æ’­æ”¾å™¨" #: ../src/ol_player_chooser.c:248 msgid "All players" msgstr "所有播放器" #: ../src/ol_player_chooser.c:252 ../src/ol_player_chooser.c:324 msgid "Choose a player to launch" msgstr "选择è¦å¯åŠ¨çš„æ’­æ”¾å™¨" #: ../src/ol_player_chooser.c:268 msgid "Use command:" msgstr "自定义命令:" #: ../src/ol_player_chooser.c:273 msgid "Launch" msgstr "å¯åЍ" #: ../src/ol_player_chooser.c:286 msgid "Remember my choice" msgstr "è®°ä½æˆ‘的选择" #: ../src/ol_player_chooser.c:438 #, c-format msgid "Failed to launch %s" msgstr "å¯åЍ%s失败" #: ../src/ol_player_chooser.c:448 #, c-format msgid "Launching %s" msgstr "正在å¯åЍ%s" #: ../src/ol_player_chooser.c:450 #, c-format msgid "" "OSD Lyrics is trying to launch and connect to %s. Please wait for a second." msgstr "OSD Lyrics正在å¯åЍ%s并与其进行通信,请ç¨å€™ã€‚" #: ../src/ol_player_chooser.c:560 msgid "There is no supported player running" msgstr "没有检测到支æŒçš„æ’­æ”¾å™¨" #: ../src/ol_player_chooser.c:561 msgid "Please choose a player below to launch" msgstr "请从下方选择è¦å¯åŠ¨çš„æ’­æ”¾å™¨" #: ../src/ol_player_chooser.c:565 #, c-format msgid "Failed to connect to %s" msgstr "无法与%s进行通信" #: ../src/ol_player_chooser.c:567 #, c-format msgid "" "%s is not supported by OSD Lyrics, or not running. Please launch another " "player" msgstr "%sä¸å—OSD Lyrics或没有正常å¯åŠ¨ã€‚è¯·å¯åŠ¨å…¶ä»–æ’­æ”¾å™¨" #: ../src/ol_app_info.c:179 ../src/ol_app_info.c:189 msgid "Unnamed" msgstr "未知åç§°" #: ../src/ol_scroll_window.c:52 msgid "" "Drag to move the window\n" "Hold CTRL to seek" msgstr "" "用鼠标拖动窗å£\n" "按ä½CTRLé”®æ”¹å˜æ’­æ”¾è¿›åº¦" #: ../src/ol_scroll_window.c:53 msgid "Drag to move the window" msgstr "用鼠标移动窗å£" #: ../data/dialogs.glade.h:1 msgid "Copyright 2009-2011 The OSD Lyrics project." msgstr "ç‰ˆæƒæ‰€æœ‰ 2009-2011 OSD Lyrics 项目组。" #: ../data/dialogs.glade.h:2 msgid "" "An OSD lyric show compatible with various media players and supports lyrics " "downloading.\n" msgstr "支æŒå¤šç§æ’­æ”¾å™¨å’Œè‡ªåŠ¨æ­Œè¯ä¸‹è½½çš„æ¡Œé¢æ­Œè¯ç§€\n" #: ../data/dialogs.glade.h:5 msgid "GPL v3" msgstr "GPL v3" #: ../data/dialogs.glade.h:6 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" " Tiger Soldier https://launchpad.net/~tigersoldi" #: ../data/dialogs.glade.h:8 msgid "HTTP" msgstr "HTTP" #: ../data/dialogs.glade.h:9 msgid "SOCKS4" msgstr "SOCKS4" #: ../data/dialogs.glade.h:10 msgid "SOCKS5" msgstr "SOCKS5" #: ../data/dialogs.glade.h:11 msgid "Always" msgstr "总是滚动" #: ../data/dialogs.glade.h:12 msgid "By lines" msgstr "按行滚动" #: ../data/dialogs.glade.h:13 msgid "Preferences" msgstr "首选项" #: ../data/dialogs.glade.h:14 msgid "Display mode:" msgstr "显示模å¼:" #: ../data/dialogs.glade.h:15 msgid "OS_D" msgstr "æ¡Œé¢æ­Œè¯(_D)" #: ../data/dialogs.glade.h:16 msgid "Sc_rolling" msgstr "滚动歌è¯(_R)" #: ../data/dialogs.glade.h:17 msgid "Auto la_unch player:" msgstr "自动å¯åŠ¨æ’­æ”¾å™¨(_U):" #: ../data/dialogs.glade.h:18 msgid "Pla_yer command:" msgstr "播放器命令(_P):" #: ../data/dialogs.glade.h:19 msgid "Show _bubble notification" msgstr "显示气泡通知(_B)" #: ../data/dialogs.glade.h:20 msgid "_General" msgstr "常规(_G)" #: ../data/dialogs.glade.h:21 msgid "_Font:" msgstr "字体(_F):" #. The outline width of text in OSD window #: ../data/dialogs.glade.h:23 msgid "Out_line:" msgstr "轮廓(_L):" #: ../data/dialogs.glade.h:24 msgid "_Blur:" msgstr "模糊(_B):" #: ../data/dialogs.glade.h:25 msgid "Weak" msgstr "å¼±" #: ../data/dialogs.glade.h:26 msgid "Strong" msgstr "强" #: ../data/dialogs.glade.h:27 msgid "Lyric lines:" msgstr "æ­Œè¯è¡Œæ•°:" #: ../data/dialogs.glade.h:28 msgid "Si_ngle line" msgstr "å•行(_N)" #: ../data/dialogs.glade.h:29 msgid "_Double lines" msgstr "åŒè¡Œ(_D)" #: ../data/dialogs.glade.h:30 msgid "Mode:" msgstr "模å¼:" #: ../data/dialogs.glade.h:31 msgid "Doc_k" msgstr "Doc_k" #: ../data/dialogs.glade.h:32 msgid "Nor_mal" msgstr "普通(_M)" #: ../data/dialogs.glade.h:33 msgid "T_ranslucent on mouse over" msgstr "鼠标移到歌è¯ä¸Šæ—¶åŠé€æ˜Ž(_R)" #: ../data/dialogs.glade.h:34 msgid "F_irst line:" msgstr "首行(_I):" #: ../data/dialogs.glade.h:35 msgid "_Second line:" msgstr "次行(_S):" #: ../data/dialogs.glade.h:36 msgid "Lyric _Alignment" msgstr "æ­Œè¯å¯¹é½(_A)" #: ../data/dialogs.glade.h:37 msgid "Color _Theme:" msgstr "é…色方案(_T):" #: ../data/dialogs.glade.h:38 msgid "C_ustomize colors" msgstr "自行é…色(_U)" #: ../data/dialogs.glade.h:39 msgid "Acti_ve lyrics:" msgstr "已唱歌è¯(_V):" #: ../data/dialogs.glade.h:40 msgid "top" msgstr "上" #: ../data/dialogs.glade.h:41 msgid "middle" msgstr "中" #: ../data/dialogs.glade.h:42 msgid "bottom" msgstr "下" #: ../data/dialogs.glade.h:43 msgid "Inactiv_e lyrics:" msgstr "未唱歌è¯(_E):" #: ../data/dialogs.glade.h:44 msgid "Lyric _Color" msgstr "æ­Œè¯é…色(_C)" #: ../data/dialogs.glade.h:45 msgid "_OSD" msgstr "æ¡Œé¢æ­Œè¯(_O)" #: ../data/dialogs.glade.h:46 msgid "Ac_tive color:" msgstr "已唱颜色(_U):" #: ../data/dialogs.glade.h:47 msgid "_Inactive color:" msgstr "未唱颜色(_I):" #: ../data/dialogs.glade.h:48 msgid "Scroll _mode:" msgstr "滚动方å¼(_M):" #: ../data/dialogs.glade.h:49 msgid "Lyrics" msgstr "æ­Œè¯" #: ../data/dialogs.glade.h:50 msgid "Colo_r:" msgstr "颜色(_R):" #: ../data/dialogs.glade.h:51 msgid "O_pacity:" msgstr "逿˜Žåº¦(_P):" #: ../data/dialogs.glade.h:52 msgid "Transparent" msgstr "逿˜Ž" #: ../data/dialogs.glade.h:53 msgid "Opaque" msgstr "ä¸é€æ˜Ž" #: ../data/dialogs.glade.h:54 msgid "Background" msgstr "背景" #: ../data/dialogs.glade.h:55 msgid "_Scrolling" msgstr "滚动歌è¯(_S)" #: ../data/dialogs.glade.h:57 #, no-c-format msgid "A single \"%\" means the directory where the playing music is." msgstr "“%â€è¡¨ç¤ºå½“剿’­æ”¾æ­Œæ›²æ‰€åœ¨ç›®å½•。" #: ../data/dialogs.glade.h:58 msgid "_Path" msgstr "路径(_P)" #: ../data/dialogs.glade.h:60 #, no-c-format msgid "" " %t - Title\n" " %p - Artist\n" " %a - Album\n" " %n - Track number\n" " %f - File name (without extension)" msgstr "" " %t - æ­Œå\n" " %p - 歌手\n" " %a - 专辑\n" " %n - 曲目编å·\n" " %f - 文件å" #: ../data/dialogs.glade.h:65 msgid "_Filename" msgstr "文件å(_F)" #: ../data/dialogs.glade.h:66 msgid "_Lyric Location" msgstr "æ­Œè¯ä½ç½®(_L)" #: ../data/dialogs.glade.h:67 msgid "Lyric _sites:" msgstr "æ­Œè¯ç«™ç‚¹(_S):" #. In preference/download, automately download the most matched lyric #: ../data/dialogs.glade.h:69 msgid "Do_wnload the first candidate" msgstr "ä¸‹è½½ç¬¬ä¸€ä¸ªåŒ¹é…æ­Œè¯(_W)" #: ../data/dialogs.glade.h:70 msgid "Download" msgstr "下载" #: ../data/dialogs.glade.h:71 msgid "No _proxy" msgstr "ä¸ä½¿ç”¨ä»£ç†(_P)" #: ../data/dialogs.glade.h:72 msgid "Au_to-detect system settings" msgstr "自动检测系统设置(_T)" #: ../data/dialogs.glade.h:73 msgid "Use _manual settings" msgstr "手动设置代ç†(_M)" #: ../data/dialogs.glade.h:74 msgid "_Host:" msgstr "主机(_H):" #: ../data/dialogs.glade.h:75 msgid "Po_rt:" msgstr "端å£(_R):" #: ../data/dialogs.glade.h:76 msgid "_Type:" msgstr "类型(_T):" #: ../data/dialogs.glade.h:77 msgid "_Username:" msgstr "用户å(_U):" #: ../data/dialogs.glade.h:78 msgid "Passwo_rd:" msgstr "密ç (_R):" #: ../data/dialogs.glade.h:79 msgid "Show passwor_d" msgstr "显示密ç (_D)" #: ../data/dialogs.glade.h:80 msgid "Proxy" msgstr "代ç†" #: ../data/dialogs.glade.h:81 msgid "_Network" msgstr "网络(_N)" #: ../data/dialogs.glade.h:82 msgid "OSD _mode" msgstr "æ¡Œé¢æ­Œè¯æ¨¡å¼(M)" #: ../data/dialogs.glade.h:83 msgid "Scrolling _mode" msgstr "æ»šåŠ¨æ­Œè¯æ¨¡å¼(_M)" #: ../data/dialogs.glade.h:84 msgid "If locked, you can't move OSD window by mouse" msgstr "é”定åŽå°†æ— æ³•ç”¨é¼ æ ‡ç§»åŠ¨æ¡Œé¢æ­Œè¯" #: ../data/dialogs.glade.h:85 msgid "_Lock Position" msgstr "é”定ä½ç½®(_L)" #: ../data/dialogs.glade.h:86 msgid "Hide the OSD Window" msgstr "éšè—æ¡Œé¢æ­Œè¯çª—å£" #: ../data/dialogs.glade.h:87 msgid "_Hide" msgstr "éšè—(_H)" #: ../data/dialogs.glade.h:88 msgid "_Search lyric..." msgstr "æœç´¢æ­Œè¯(_S)..." #: ../data/dialogs.glade.h:89 msgid "Search lyrics from Internet" msgstr "从网上æœç´¢æ­Œè¯" #: ../data/dialogs.glade.h:90 msgid "_Assign lyric..." msgstr "å…³è”æ­Œè¯(_A)..." #: ../data/dialogs.glade.h:91 msgid "Assign local LRC file to current music" msgstr "将本机LRC文件关è”到当å‰éŸ³ä¹" #: ../data/dialogs.glade.h:92 msgid "_No lyric" msgstr "ä¸è¦æ­Œè¯(_N)" #: ../data/dialogs.glade.h:93 msgid "Don't assign lyric to this music" msgstr "ä¸è¦ç»™è¿™é¦–歌关è”任何歌è¯" #: ../data/dialogs.glade.h:94 msgid "Lyric delay -" msgstr "æå‰æ­Œè¯" #: ../data/dialogs.glade.h:95 msgid "Adjust lyrics delay by -0.2 s" msgstr "å°†æ­Œè¯æå‰0.2ç§’" #: ../data/dialogs.glade.h:96 msgid "Lyric delay +" msgstr "å»¶åŽæ­Œè¯" #: ../data/dialogs.glade.h:97 msgid "Adjust lyric delay by +0.2 s" msgstr "将歌è¯å»¶åŽ0.2ç§’" #: ../data/dialogs.glade.h:98 msgid "Choose LRC file to download" msgstr "选择è¦ä¸‹è½½çš„æ­Œè¯" #: ../data/dialogs.glade.h:99 msgid "D_on't ask me again" msgstr "以åŽä¸å†æç¤ºæˆ‘(_O)" #: ../data/dialogs.glade.h:100 msgid "" "If there are more than one lrc files matched with the search condition, " "download the first one without prompting the user." msgstr "如果有多个LRCæ­Œè¯åŒ¹é…æœç´¢æ¡ä»¶ï¼Œç›´æŽ¥ä¸‹è½½ç¬¬ä¸€ä¸ªï¼Œä¸å†è¿›è¡Œæç¤ºã€‚" #: ../data/dialogs.glade.h:101 msgid "_Download" msgstr "下载(_D)" #: ../data/dialogs.glade.h:102 msgid "The title of the current track" msgstr "当剿­Œæ›²å" #: ../data/dialogs.glade.h:104 #, no-c-format msgid "%t - Title" msgstr "%t - æ­Œå" #: ../data/dialogs.glade.h:105 msgid "The artist of the current track" msgstr "当剿­Œæ›²æ­Œæ‰‹" #: ../data/dialogs.glade.h:107 #, no-c-format msgid "%p - Artist" msgstr "%p - 歌手" #: ../data/dialogs.glade.h:108 msgid "The title of album of the current track" msgstr "当剿­Œæ›²çš„专辑å" #: ../data/dialogs.glade.h:110 #, no-c-format msgid "%a - Album" msgstr "%a - 专辑" #: ../data/dialogs.glade.h:111 msgid "The number of the current track" msgstr "当剿­Œæ›²çš„ç¼–å·" #: ../data/dialogs.glade.h:113 #, no-c-format msgid "%n - Track number" msgstr "%n - 曲目编å·" #: ../data/dialogs.glade.h:114 msgid "The filename of the current track" msgstr "当剿­Œæ›²çš„æ–‡ä»¶å" #: ../data/dialogs.glade.h:116 #, no-c-format msgid "%f - Filename" msgstr "%f - éŸ³ä¹æ–‡ä»¶å" #: ../data/dialogs.glade.h:117 msgid "Search lyrics" msgstr "æœç´¢æ­Œè¯" #: ../data/dialogs.glade.h:118 msgid "_Title:" msgstr "æ­Œå(_T):" #: ../data/dialogs.glade.h:119 msgid "_Artist:" msgstr "歌手(_A):" #: ../data/dialogs.glade.h:120 msgid "_Search" msgstr "æœç´¢(_S)" #. From witch web site to search and download lyrics #: ../data/dialogs.glade.h:122 msgid "_From:" msgstr "下载自(_F):" #: ../data/osdlyrics.desktop.in.h:2 msgid "Lyrics Show" msgstr "æ­Œè¯ç§€" #: ../data/osdlyrics.desktop.in.h:3 msgid "Download and show lyrics for your favorite media player" msgstr "è®©ä½ çš„éŸ³ä¹æ’­æ”¾å™¨æ”¯æŒä¸‹è½½å’Œæ˜¾ç¤ºæ­Œè¯" #: ../lyricsources/xiami/xiami.py:50 msgid "Xiami" msgstr "虾米" #~ msgid "Cannot create the lyric directory" #~ msgstr "无法创建歌è¯ç›®å½•" #~ msgid "ttPlayer" #~ msgstr "åƒåƒé™å¬" #~ msgid "Preview" #~ msgstr "预览" #~ msgid "Searching lyrics" #~ msgstr "正在æœç´¢æ­Œè¯" #~ msgid "_Site:" #~ msgstr "站点(_S):" #~ msgid "None" #~ msgstr "æ— " #~ msgid "Qianqian" #~ msgstr "åƒåƒ" #~ msgid "Sogou" #~ msgstr "æœç‹—" #~ msgid "MiniLyrics" #~ msgstr "è¿·ä½ æ­Œè¯" #~ msgid "Dock" #~ msgstr "桌é¢ç½®é¡¶" #~ msgid "Normal" #~ msgstr "普通窗å£" #~ msgid "Choose witch site you want to download from." #~ msgstr "选择从哪个网站下载的歌è¯ã€‚" #~ msgid "_Width:" #~ msgstr "宽度(_W):" #~ msgid "If no player detected, launch the given player" #~ msgstr "如果没有检测到播放器,则è¿è¡ŒæŒ‡å®šæ’­æ”¾å™¨" #~ msgid "_Lock" #~ msgstr "é”定(_L)" #~ msgid "gtk-cancel" #~ msgstr "gtk-cancel" #~ msgid "gtk-ok" #~ msgstr "gtk-ok" #~ msgid "TODO: Add options here" #~ msgstr "TODO: Add options here" #~ msgid "" #~ "%s\n" #~ " %s" #~ msgstr "" #~ "%s\n" #~ " %s" #~ msgid "" #~ "%s\n" #~ " %s - %s" #~ msgstr "" #~ "%s\n" #~ " %s - %s" osdlyrics-0.5.5~rc1+dfsg1/po/zh_HK.po000066400000000000000000000317151345713535600173440ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Free Software Foundation, Inc. # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: osd-lyrics 0.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-11-17 23:18+0800\n" "PO-Revision-Date: 2010-01-04 19:10+0000\n" "Last-Translator: Tiger Soldier \n" "Language-Team: Simplified Chinese \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Launchpad-Export-Date: 2011-06-04 13:40+0000\n" "X-Generator: Launchpad (build 13144)\n" #: ../src/ol_menu.c:119 msgid "LRC files" msgstr "" #: ../src/ol_menu.c:125 msgid "Choose LRC file to assign" msgstr "" #: ../src/ol_main.c:60 msgid "" "The level of debug messages to log, can be 'none', 'error', 'debug', or " "'info'" msgstr "" #: ../src/ol_main.c:62 msgid "Show version information" msgstr "" #: ../src/ol_main.c:147 msgid "Download failed" msgstr "下載失敗" #: ../src/ol_main.c:162 #, fuzzy, c-format msgid "Searching lyrics from %s" msgstr "正在æœå°‹æ­Œè©ž" #: ../src/ol_main.c:185 msgid "Cannot create the lyric directory" msgstr "無法建立歌詞資料夾" #: ../src/ol_main.c:201 msgid "Lyrics not found" msgstr "找ä¸åˆ°ç¬¦åˆçš„æ­Œè©ž" #: ../src/ol_main.c:507 #, c-format msgid "No supported player is running, exit.\n" msgstr "æ²’æœ‰åµæ¸¬åˆ°æ”¯æ´çš„æ’­æ”¾å™¨ï¼Œç¨‹å¼è‡ªå‹•çµæŸã€‚\n" #: ../src/ol_main.c:652 msgid "" "debug level should be one of ``none'', ``error'', ``debug'', or ``info''" msgstr "" #: ../src/ol_main.c:675 msgid "Another OSD Lyrics is running, exit." msgstr "" #: ../src/ol_trayicon.c:36 ../src/ol_notify.c:26 msgid "Unknown title" msgstr "" #: ../src/ol_trayicon.c:37 ../src/ol_notify.c:27 msgid "Unknown artist" msgstr "" #: ../src/ol_trayicon.c:71 ../src/ol_trayicon.c:149 msgid "OSD Lyrics" msgstr "OSD Lyrics" #: ../src/ol_option.c:1631 msgid "Choose on startup" msgstr "" #. gtk_list_store_append (liststore, &iter); #. gtk_list_store_set (liststore, &iter, #. 0, "Customize", #. 1, "", #. -1); #: ../src/ol_option.c:1651 ../data/dialogs.glade.h:43 msgid "Customize" msgstr "自訂" #: ../src/ol_option.c:1713 msgid "Select a folder" msgstr "" #: ../src/ol_lrc_fetch_ttplayer.c:274 msgid "ttPlayer" msgstr "" #: ../src/ol_lrc_fetch_xiami.c:237 msgid "Xiami" msgstr "" #: ../src/ol_lrc_candidate_list.c:43 msgid "Title" msgstr "æ­Œå" #: ../src/ol_lrc_candidate_list.c:48 msgid "Artist" msgstr "歌手" #: ../src/ol_search_dialog.c:31 #, c-format msgid "Searching lyrics from %s..." msgstr "" #: ../src/ol_search_dialog.c:32 msgid "Ooops, no lyric found :(" msgstr "" #: ../src/ol_search_dialog.c:33 #, c-format msgid "%d lyrics found :)" msgstr "" #: ../src/ol_player_chooser.c:243 msgid "Supported players" msgstr "" #: ../src/ol_player_chooser.c:244 msgid "All players" msgstr "" #: ../src/ol_player_chooser.c:248 ../src/ol_player_chooser.c:319 #, fuzzy msgid "Choose a player to launch" msgstr "é¸å–è¦ä¸‹è¼‰çš„æ­Œè©ž" #: ../src/ol_player_chooser.c:264 msgid "Use command:" msgstr "" #: ../src/ol_player_chooser.c:268 msgid "Launch" msgstr "" #: ../src/ol_player_chooser.c:281 msgid "Remember my choice" msgstr "" #: ../src/ol_player_chooser.c:387 #, c-format msgid "Failed to launch %s" msgstr "" #: ../src/ol_player_chooser.c:397 #, c-format msgid "Launching %s" msgstr "" #: ../src/ol_player_chooser.c:399 #, c-format msgid "" "OSD Lyrics is trying to launch and connect to %s. Please wait for a second." msgstr "" #: ../src/ol_player_chooser.c:510 #, fuzzy msgid "There is no supported player running" msgstr "æ²’æœ‰åµæ¸¬åˆ°æ”¯æ´çš„æ’­æ”¾å™¨ï¼Œç¨‹å¼è‡ªå‹•çµæŸã€‚\n" #: ../src/ol_player_chooser.c:511 #, fuzzy msgid "Please choose a player below to launch" msgstr "é¸å–è¦ä¸‹è¼‰çš„æ­Œè©ž" #: ../src/ol_player_chooser.c:515 #, c-format msgid "Failed to connect to %s" msgstr "" #: ../src/ol_player_chooser.c:517 #, c-format msgid "" "%s is not supported by OSD Lyrics, or not running. Please launch another " "player" msgstr "" #: ../src/ol_app_info.c:183 ../src/ol_app_info.c:193 msgid "Unnamed" msgstr "" #: ../src/ol_scroll_window.c:52 msgid "" "Drag to move the window\n" "Hold CTRL to seek" msgstr "" #: ../src/ol_scroll_window.c:53 msgid "Drag to move the window" msgstr "" #: ../data/dialogs.glade.h:2 #, no-c-format msgid "" " %t - Title\n" " %p - Artist\n" " %a - Album\n" " %n - Track number\n" " %f - File name (without extension)" msgstr "" " %t - æ­Œå\n" " %p - 歌手\n" " %a - 專輯\n" " %n - 曲目編號\n" " %f - 檔å" #: ../data/dialogs.glade.h:8 #, no-c-format msgid "%a - Album" msgstr "%a - 專輯" #: ../data/dialogs.glade.h:10 #, no-c-format msgid "%f - Filename" msgstr "%f - 音樂檔å" #: ../data/dialogs.glade.h:12 #, no-c-format msgid "%n - Track number" msgstr "%n - 曲目編號" #: ../data/dialogs.glade.h:14 #, no-c-format msgid "%p - Artist" msgstr "%p - 歌手" #: ../data/dialogs.glade.h:16 #, no-c-format msgid "%t - Title" msgstr "%t - æ­Œå" #: ../data/dialogs.glade.h:17 msgid "Background" msgstr "" #: ../data/dialogs.glade.h:18 msgid "Download" msgstr "" #: ../data/dialogs.glade.h:19 msgid "Lyric _Alignment" msgstr "歌詞å°é½Š(_A)" #: ../data/dialogs.glade.h:20 msgid "Lyric _Color" msgstr "歌詞é…色(_C)" #: ../data/dialogs.glade.h:21 msgid "Lyrics" msgstr "" #: ../data/dialogs.glade.h:22 msgid "Proxy" msgstr "" #: ../data/dialogs.glade.h:23 msgid "_Filename" msgstr "檔å(_F)" #: ../data/dialogs.glade.h:24 msgid "_Path" msgstr "路徑(_P)" #: ../data/dialogs.glade.h:26 #, no-c-format msgid "A single \"%\" means the directory where the playing music is." msgstr "“%â€è¡¨ç¤ºç›®å‰æ’­æ”¾æ­Œæ›²æ‰€åœ¨ç›®éŒ„。" #: ../data/dialogs.glade.h:27 msgid "Ac_tive color:" msgstr "" #: ../data/dialogs.glade.h:28 msgid "Acti_ve lyrics:" msgstr "已唱歌詞(_V):" #: ../data/dialogs.glade.h:29 msgid "Adjust lyric delay by +0.2 s" msgstr "" #: ../data/dialogs.glade.h:30 msgid "Adjust lyrics delay by -0.2 s" msgstr "" #: ../data/dialogs.glade.h:31 msgid "Always" msgstr "" #: ../data/dialogs.glade.h:32 msgid "" "An OSD lyric show compatible with various media players and supports lyrics " "downloading.\n" msgstr "支æ´å¤šç¨®æ’­æ”¾å™¨å’Œè‡ªå‹•æ­Œè©žä¸‹è¼‰çš„æ¡Œé¢æ­Œè©žç§€\n" #: ../data/dialogs.glade.h:34 msgid "Assign local LRC file to current music" msgstr "" #: ../data/dialogs.glade.h:35 msgid "Au_to-detect system settings" msgstr "" #: ../data/dialogs.glade.h:36 msgid "Auto la_unch player:" msgstr "" #: ../data/dialogs.glade.h:37 msgid "By lines" msgstr "" #: ../data/dialogs.glade.h:38 msgid "C_ustomize colors" msgstr "自行é…色(_U)" #: ../data/dialogs.glade.h:39 msgid "Choose LRC file to download" msgstr "é¸å–è¦ä¸‹è¼‰çš„æ­Œè©ž" #: ../data/dialogs.glade.h:40 msgid "Colo_r:" msgstr "" #: ../data/dialogs.glade.h:41 msgid "Color _Theme:" msgstr "é…色方案(_T):" #: ../data/dialogs.glade.h:42 #, fuzzy msgid "Copyright 2009-2011 The OSD Lyrics project." msgstr "版權所有 2009 OSD Lyrics 項目組。" #: ../data/dialogs.glade.h:44 msgid "D_on't ask me again" msgstr "" #: ../data/dialogs.glade.h:45 msgid "Display mode:" msgstr "" #. In preference/download, automately download the most matched lyric #: ../data/dialogs.glade.h:47 msgid "Do_wnload the first candidate" msgstr "" #: ../data/dialogs.glade.h:48 msgid "Doc_k" msgstr "" #: ../data/dialogs.glade.h:49 msgid "Don't assign lyric to this music" msgstr "" #: ../data/dialogs.glade.h:50 msgid "F_irst line:" msgstr "首行(_I):" #: ../data/dialogs.glade.h:51 msgid "GPL v3" msgstr "GPL v3" #: ../data/dialogs.glade.h:52 msgid "HTTP" msgstr "" #: ../data/dialogs.glade.h:53 msgid "Hide the OSD Window" msgstr "éš±è—æ¡Œé¢æ­Œè©žè¦–窗" #: ../data/dialogs.glade.h:54 msgid "If locked, you can't move OSD window by mouse" msgstr "鎖定åŽå°‡ç„¡æ³•ç”¨æ»‘é¼ ç§»å‹•æ¡Œé¢æ­Œè©ž" #: ../data/dialogs.glade.h:55 msgid "" "If there are more than one lrc files matched with the search condition, " "download the first one without prompting the user." msgstr "" #: ../data/dialogs.glade.h:56 msgid "Inactiv_e lyrics:" msgstr "未唱歌詞(_E):" #: ../data/dialogs.glade.h:57 #, fuzzy msgid "Lyric _sites:" msgstr "歌詞行數:" #: ../data/dialogs.glade.h:58 msgid "Lyric delay +" msgstr "" #: ../data/dialogs.glade.h:59 msgid "Lyric delay -" msgstr "" #: ../data/dialogs.glade.h:60 msgid "Lyric lines:" msgstr "歌詞行數:" #: ../data/dialogs.glade.h:61 msgid "Mode:" msgstr "" #: ../data/dialogs.glade.h:62 msgid "No _proxy" msgstr "" #: ../data/dialogs.glade.h:63 msgid "Nor_mal" msgstr "" #: ../data/dialogs.glade.h:64 msgid "OSD _mode" msgstr "" #: ../data/dialogs.glade.h:65 msgid "OS_D" msgstr "" #: ../data/dialogs.glade.h:66 msgid "O_pacity:" msgstr "" #: ../data/dialogs.glade.h:67 msgid "Opaque" msgstr "" #. The outline width of text in OSD window #: ../data/dialogs.glade.h:69 msgid "Out_line:" msgstr "" #: ../data/dialogs.glade.h:70 msgid "Passwo_rd:" msgstr "" #: ../data/dialogs.glade.h:71 msgid "Pla_yer command:" msgstr "" #: ../data/dialogs.glade.h:72 msgid "Po_rt:" msgstr "" #: ../data/dialogs.glade.h:73 msgid "Preferences" msgstr "個人å好" #: ../data/dialogs.glade.h:74 msgid "SOCKS4" msgstr "" #: ../data/dialogs.glade.h:75 msgid "SOCKS5" msgstr "" #: ../data/dialogs.glade.h:76 msgid "Sc_rolling" msgstr "" #: ../data/dialogs.glade.h:77 msgid "Scroll _mode:" msgstr "" #: ../data/dialogs.glade.h:78 msgid "Scrolling _mode" msgstr "" #: ../data/dialogs.glade.h:79 msgid "Search lyrics" msgstr "" #: ../data/dialogs.glade.h:80 msgid "Search lyrics from Internet" msgstr "" #: ../data/dialogs.glade.h:81 msgid "Show _bubble notification" msgstr "" #: ../data/dialogs.glade.h:82 msgid "Show passwor_d" msgstr "" #: ../data/dialogs.glade.h:83 msgid "Si_ngle line" msgstr "單行(_N)" #: ../data/dialogs.glade.h:84 msgid "Strong" msgstr "" #: ../data/dialogs.glade.h:85 msgid "T_ranslucent on mouse over" msgstr "滑鼠移到歌詞上時åŠé€é€š(_R)" #: ../data/dialogs.glade.h:86 msgid "The artist of the current track" msgstr "ç›®å‰æ­Œæ›²æ­Œæ‰‹" #: ../data/dialogs.glade.h:87 msgid "The filename of the current track" msgstr "ç›®å‰æ­Œæ›²çš„æª”å" #: ../data/dialogs.glade.h:88 msgid "The number of the current track" msgstr "ç›®å‰æ­Œæ›²çš„編號" #: ../data/dialogs.glade.h:89 msgid "The title of album of the current track" msgstr "ç›®å‰æ­Œæ›²çš„專輯å" #: ../data/dialogs.glade.h:90 msgid "The title of the current track" msgstr "ç›®å‰æ­Œæ›²å" #: ../data/dialogs.glade.h:91 msgid "Transparent" msgstr "" #: ../data/dialogs.glade.h:92 msgid "Use _manual settings" msgstr "" #: ../data/dialogs.glade.h:93 msgid "Weak" msgstr "" #: ../data/dialogs.glade.h:94 msgid "_Artist:" msgstr "" #: ../data/dialogs.glade.h:95 msgid "_Assign lyric..." msgstr "" #: ../data/dialogs.glade.h:96 msgid "_Blur:" msgstr "" #: ../data/dialogs.glade.h:97 msgid "_Double lines" msgstr "雙行(_D)" #: ../data/dialogs.glade.h:98 msgid "_Download" msgstr "下載(_D)" #: ../data/dialogs.glade.h:99 msgid "_Font:" msgstr "å­—åž‹(_F):" #. From witch web site to search and download lyrics #: ../data/dialogs.glade.h:101 msgid "_From:" msgstr "" #: ../data/dialogs.glade.h:102 msgid "_General" msgstr "常è¦(_G)" #: ../data/dialogs.glade.h:103 msgid "_Hide" msgstr "éš±è—(_H)" #: ../data/dialogs.glade.h:104 msgid "_Host:" msgstr "" #: ../data/dialogs.glade.h:105 msgid "_Inactive color:" msgstr "" #: ../data/dialogs.glade.h:106 msgid "_Lock Position" msgstr "" #: ../data/dialogs.glade.h:107 msgid "_Lyric Location" msgstr "歌詞ä½ç½®(_L)" #: ../data/dialogs.glade.h:108 msgid "_Network" msgstr "" #: ../data/dialogs.glade.h:109 msgid "_No lyric" msgstr "" #: ../data/dialogs.glade.h:110 msgid "_OSD" msgstr "æ¡Œé¢æ­Œè©ž(_O)" #: ../data/dialogs.glade.h:111 msgid "_Scrolling" msgstr "" #: ../data/dialogs.glade.h:112 msgid "_Search" msgstr "" #: ../data/dialogs.glade.h:113 msgid "_Search lyric..." msgstr "" #: ../data/dialogs.glade.h:114 msgid "_Second line:" msgstr "次行(_S)" #: ../data/dialogs.glade.h:115 msgid "_Title:" msgstr "" #: ../data/dialogs.glade.h:116 msgid "_Type:" msgstr "" #: ../data/dialogs.glade.h:117 msgid "_Username:" msgstr "" #: ../data/dialogs.glade.h:118 msgid "bottom" msgstr "下" #: ../data/dialogs.glade.h:120 msgid "middle" msgstr "中" #: ../data/dialogs.glade.h:121 msgid "top" msgstr "上" #: ../data/dialogs.glade.h:122 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" " Tiger Soldier https://launchpad.net/~tigersoldi" #~ msgid "Preview" #~ msgstr "é è¦½" #~ msgid "Qianqian" #~ msgstr "åƒåƒ" #~ msgid "Sogou" #~ msgstr "æœç‹—" #~ msgid "_Width:" #~ msgstr "寬度(_W):" #~ msgid "Download _engine:" #~ msgstr "下載引擎(_E):" #~ msgid "_Lock" #~ msgstr "鎖定(_L)" #~ msgid "_Download lyric" #~ msgstr "下載歌詞(_D)" #~ msgid "TODO: Add options here" #~ msgstr "TODO: Add options here" #~ msgid "gtk-cancel" #~ msgstr "gtk-cancel" #~ msgid "gtk-ok" #~ msgstr "gtk-ok" osdlyrics-0.5.5~rc1+dfsg1/po/zh_TW.po000066400000000000000000000317151345713535600173740ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Free Software Foundation, Inc. # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: osd-lyrics 0.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-11-17 23:18+0800\n" "PO-Revision-Date: 2010-01-04 19:10+0000\n" "Last-Translator: Tiger Soldier \n" "Language-Team: Simplified Chinese \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Launchpad-Export-Date: 2011-06-04 13:40+0000\n" "X-Generator: Launchpad (build 13144)\n" #: ../src/ol_menu.c:119 msgid "LRC files" msgstr "" #: ../src/ol_menu.c:125 msgid "Choose LRC file to assign" msgstr "" #: ../src/ol_main.c:60 msgid "" "The level of debug messages to log, can be 'none', 'error', 'debug', or " "'info'" msgstr "" #: ../src/ol_main.c:62 msgid "Show version information" msgstr "" #: ../src/ol_main.c:147 msgid "Download failed" msgstr "下載失敗" #: ../src/ol_main.c:162 #, fuzzy, c-format msgid "Searching lyrics from %s" msgstr "正在æœå°‹æ­Œè©ž" #: ../src/ol_main.c:185 msgid "Cannot create the lyric directory" msgstr "無法建立歌詞資料夾" #: ../src/ol_main.c:201 msgid "Lyrics not found" msgstr "找ä¸åˆ°ç¬¦åˆçš„æ­Œè©ž" #: ../src/ol_main.c:507 #, c-format msgid "No supported player is running, exit.\n" msgstr "æ²’æœ‰åµæ¸¬åˆ°æ”¯æ´çš„æ’­æ”¾å™¨ï¼Œç¨‹å¼è‡ªå‹•çµæŸã€‚\n" #: ../src/ol_main.c:652 msgid "" "debug level should be one of ``none'', ``error'', ``debug'', or ``info''" msgstr "" #: ../src/ol_main.c:675 msgid "Another OSD Lyrics is running, exit." msgstr "" #: ../src/ol_trayicon.c:36 ../src/ol_notify.c:26 msgid "Unknown title" msgstr "" #: ../src/ol_trayicon.c:37 ../src/ol_notify.c:27 msgid "Unknown artist" msgstr "" #: ../src/ol_trayicon.c:71 ../src/ol_trayicon.c:149 msgid "OSD Lyrics" msgstr "OSD Lyrics" #: ../src/ol_option.c:1631 msgid "Choose on startup" msgstr "" #. gtk_list_store_append (liststore, &iter); #. gtk_list_store_set (liststore, &iter, #. 0, "Customize", #. 1, "", #. -1); #: ../src/ol_option.c:1651 ../data/dialogs.glade.h:43 msgid "Customize" msgstr "自訂" #: ../src/ol_option.c:1713 msgid "Select a folder" msgstr "" #: ../src/ol_lrc_fetch_ttplayer.c:274 msgid "ttPlayer" msgstr "" #: ../src/ol_lrc_fetch_xiami.c:237 msgid "Xiami" msgstr "" #: ../src/ol_lrc_candidate_list.c:43 msgid "Title" msgstr "æ­Œå" #: ../src/ol_lrc_candidate_list.c:48 msgid "Artist" msgstr "歌手" #: ../src/ol_search_dialog.c:31 #, c-format msgid "Searching lyrics from %s..." msgstr "" #: ../src/ol_search_dialog.c:32 msgid "Ooops, no lyric found :(" msgstr "" #: ../src/ol_search_dialog.c:33 #, c-format msgid "%d lyrics found :)" msgstr "" #: ../src/ol_player_chooser.c:243 msgid "Supported players" msgstr "" #: ../src/ol_player_chooser.c:244 msgid "All players" msgstr "" #: ../src/ol_player_chooser.c:248 ../src/ol_player_chooser.c:319 #, fuzzy msgid "Choose a player to launch" msgstr "é¸å–è¦ä¸‹è¼‰çš„æ­Œè©ž" #: ../src/ol_player_chooser.c:264 msgid "Use command:" msgstr "" #: ../src/ol_player_chooser.c:268 msgid "Launch" msgstr "" #: ../src/ol_player_chooser.c:281 msgid "Remember my choice" msgstr "" #: ../src/ol_player_chooser.c:387 #, c-format msgid "Failed to launch %s" msgstr "" #: ../src/ol_player_chooser.c:397 #, c-format msgid "Launching %s" msgstr "" #: ../src/ol_player_chooser.c:399 #, c-format msgid "" "OSD Lyrics is trying to launch and connect to %s. Please wait for a second." msgstr "" #: ../src/ol_player_chooser.c:510 #, fuzzy msgid "There is no supported player running" msgstr "æ²’æœ‰åµæ¸¬åˆ°æ”¯æ´çš„æ’­æ”¾å™¨ï¼Œç¨‹å¼è‡ªå‹•çµæŸã€‚\n" #: ../src/ol_player_chooser.c:511 #, fuzzy msgid "Please choose a player below to launch" msgstr "é¸å–è¦ä¸‹è¼‰çš„æ­Œè©ž" #: ../src/ol_player_chooser.c:515 #, c-format msgid "Failed to connect to %s" msgstr "" #: ../src/ol_player_chooser.c:517 #, c-format msgid "" "%s is not supported by OSD Lyrics, or not running. Please launch another " "player" msgstr "" #: ../src/ol_app_info.c:183 ../src/ol_app_info.c:193 msgid "Unnamed" msgstr "" #: ../src/ol_scroll_window.c:52 msgid "" "Drag to move the window\n" "Hold CTRL to seek" msgstr "" #: ../src/ol_scroll_window.c:53 msgid "Drag to move the window" msgstr "" #: ../data/dialogs.glade.h:2 #, no-c-format msgid "" " %t - Title\n" " %p - Artist\n" " %a - Album\n" " %n - Track number\n" " %f - File name (without extension)" msgstr "" " %t - æ­Œå\n" " %p - 歌手\n" " %a - 專輯\n" " %n - 曲目編號\n" " %f - 檔å" #: ../data/dialogs.glade.h:8 #, no-c-format msgid "%a - Album" msgstr "%a - 專輯" #: ../data/dialogs.glade.h:10 #, no-c-format msgid "%f - Filename" msgstr "%f - 音樂檔å" #: ../data/dialogs.glade.h:12 #, no-c-format msgid "%n - Track number" msgstr "%n - 曲目編號" #: ../data/dialogs.glade.h:14 #, no-c-format msgid "%p - Artist" msgstr "%p - 歌手" #: ../data/dialogs.glade.h:16 #, no-c-format msgid "%t - Title" msgstr "%t - æ­Œå" #: ../data/dialogs.glade.h:17 msgid "Background" msgstr "" #: ../data/dialogs.glade.h:18 msgid "Download" msgstr "" #: ../data/dialogs.glade.h:19 msgid "Lyric _Alignment" msgstr "歌詞å°é½Š(_A)" #: ../data/dialogs.glade.h:20 msgid "Lyric _Color" msgstr "歌詞é…色(_C)" #: ../data/dialogs.glade.h:21 msgid "Lyrics" msgstr "" #: ../data/dialogs.glade.h:22 msgid "Proxy" msgstr "" #: ../data/dialogs.glade.h:23 msgid "_Filename" msgstr "檔å(_F)" #: ../data/dialogs.glade.h:24 msgid "_Path" msgstr "路徑(_P)" #: ../data/dialogs.glade.h:26 #, no-c-format msgid "A single \"%\" means the directory where the playing music is." msgstr "“%â€è¡¨ç¤ºç›®å‰æ’­æ”¾æ­Œæ›²æ‰€åœ¨ç›®éŒ„。" #: ../data/dialogs.glade.h:27 msgid "Ac_tive color:" msgstr "" #: ../data/dialogs.glade.h:28 msgid "Acti_ve lyrics:" msgstr "已唱歌詞(_V):" #: ../data/dialogs.glade.h:29 msgid "Adjust lyric delay by +0.2 s" msgstr "" #: ../data/dialogs.glade.h:30 msgid "Adjust lyrics delay by -0.2 s" msgstr "" #: ../data/dialogs.glade.h:31 msgid "Always" msgstr "" #: ../data/dialogs.glade.h:32 msgid "" "An OSD lyric show compatible with various media players and supports lyrics " "downloading.\n" msgstr "支æ´å¤šç¨®æ’­æ”¾å™¨å’Œè‡ªå‹•æ­Œè©žä¸‹è¼‰çš„æ¡Œé¢æ­Œè©žç§€\n" #: ../data/dialogs.glade.h:34 msgid "Assign local LRC file to current music" msgstr "" #: ../data/dialogs.glade.h:35 msgid "Au_to-detect system settings" msgstr "" #: ../data/dialogs.glade.h:36 msgid "Auto la_unch player:" msgstr "" #: ../data/dialogs.glade.h:37 msgid "By lines" msgstr "" #: ../data/dialogs.glade.h:38 msgid "C_ustomize colors" msgstr "自行é…色(_U)" #: ../data/dialogs.glade.h:39 msgid "Choose LRC file to download" msgstr "é¸å–è¦ä¸‹è¼‰çš„æ­Œè©ž" #: ../data/dialogs.glade.h:40 msgid "Colo_r:" msgstr "" #: ../data/dialogs.glade.h:41 msgid "Color _Theme:" msgstr "é…色方案(_T):" #: ../data/dialogs.glade.h:42 #, fuzzy msgid "Copyright 2009-2011 The OSD Lyrics project." msgstr "版權所有 2009 OSD Lyrics 項目組。" #: ../data/dialogs.glade.h:44 msgid "D_on't ask me again" msgstr "" #: ../data/dialogs.glade.h:45 msgid "Display mode:" msgstr "" #. In preference/download, automately download the most matched lyric #: ../data/dialogs.glade.h:47 msgid "Do_wnload the first candidate" msgstr "" #: ../data/dialogs.glade.h:48 msgid "Doc_k" msgstr "" #: ../data/dialogs.glade.h:49 msgid "Don't assign lyric to this music" msgstr "" #: ../data/dialogs.glade.h:50 msgid "F_irst line:" msgstr "首行(_I):" #: ../data/dialogs.glade.h:51 msgid "GPL v3" msgstr "GPL v3" #: ../data/dialogs.glade.h:52 msgid "HTTP" msgstr "" #: ../data/dialogs.glade.h:53 msgid "Hide the OSD Window" msgstr "éš±è—æ¡Œé¢æ­Œè©žè¦–窗" #: ../data/dialogs.glade.h:54 msgid "If locked, you can't move OSD window by mouse" msgstr "鎖定åŽå°‡ç„¡æ³•ç”¨æ»‘é¼ ç§»å‹•æ¡Œé¢æ­Œè©ž" #: ../data/dialogs.glade.h:55 msgid "" "If there are more than one lrc files matched with the search condition, " "download the first one without prompting the user." msgstr "" #: ../data/dialogs.glade.h:56 msgid "Inactiv_e lyrics:" msgstr "未唱歌詞(_E):" #: ../data/dialogs.glade.h:57 #, fuzzy msgid "Lyric _sites:" msgstr "歌詞行數:" #: ../data/dialogs.glade.h:58 msgid "Lyric delay +" msgstr "" #: ../data/dialogs.glade.h:59 msgid "Lyric delay -" msgstr "" #: ../data/dialogs.glade.h:60 msgid "Lyric lines:" msgstr "歌詞行數:" #: ../data/dialogs.glade.h:61 msgid "Mode:" msgstr "" #: ../data/dialogs.glade.h:62 msgid "No _proxy" msgstr "" #: ../data/dialogs.glade.h:63 msgid "Nor_mal" msgstr "" #: ../data/dialogs.glade.h:64 msgid "OSD _mode" msgstr "" #: ../data/dialogs.glade.h:65 msgid "OS_D" msgstr "" #: ../data/dialogs.glade.h:66 msgid "O_pacity:" msgstr "" #: ../data/dialogs.glade.h:67 msgid "Opaque" msgstr "" #. The outline width of text in OSD window #: ../data/dialogs.glade.h:69 msgid "Out_line:" msgstr "" #: ../data/dialogs.glade.h:70 msgid "Passwo_rd:" msgstr "" #: ../data/dialogs.glade.h:71 msgid "Pla_yer command:" msgstr "" #: ../data/dialogs.glade.h:72 msgid "Po_rt:" msgstr "" #: ../data/dialogs.glade.h:73 msgid "Preferences" msgstr "個人å好" #: ../data/dialogs.glade.h:74 msgid "SOCKS4" msgstr "" #: ../data/dialogs.glade.h:75 msgid "SOCKS5" msgstr "" #: ../data/dialogs.glade.h:76 msgid "Sc_rolling" msgstr "" #: ../data/dialogs.glade.h:77 msgid "Scroll _mode:" msgstr "" #: ../data/dialogs.glade.h:78 msgid "Scrolling _mode" msgstr "" #: ../data/dialogs.glade.h:79 msgid "Search lyrics" msgstr "" #: ../data/dialogs.glade.h:80 msgid "Search lyrics from Internet" msgstr "" #: ../data/dialogs.glade.h:81 msgid "Show _bubble notification" msgstr "" #: ../data/dialogs.glade.h:82 msgid "Show passwor_d" msgstr "" #: ../data/dialogs.glade.h:83 msgid "Si_ngle line" msgstr "單行(_N)" #: ../data/dialogs.glade.h:84 msgid "Strong" msgstr "" #: ../data/dialogs.glade.h:85 msgid "T_ranslucent on mouse over" msgstr "滑鼠移到歌詞上時åŠé€é€š(_R)" #: ../data/dialogs.glade.h:86 msgid "The artist of the current track" msgstr "ç›®å‰æ­Œæ›²æ­Œæ‰‹" #: ../data/dialogs.glade.h:87 msgid "The filename of the current track" msgstr "ç›®å‰æ­Œæ›²çš„æª”å" #: ../data/dialogs.glade.h:88 msgid "The number of the current track" msgstr "ç›®å‰æ­Œæ›²çš„編號" #: ../data/dialogs.glade.h:89 msgid "The title of album of the current track" msgstr "ç›®å‰æ­Œæ›²çš„專輯å" #: ../data/dialogs.glade.h:90 msgid "The title of the current track" msgstr "ç›®å‰æ­Œæ›²å" #: ../data/dialogs.glade.h:91 msgid "Transparent" msgstr "" #: ../data/dialogs.glade.h:92 msgid "Use _manual settings" msgstr "" #: ../data/dialogs.glade.h:93 msgid "Weak" msgstr "" #: ../data/dialogs.glade.h:94 msgid "_Artist:" msgstr "" #: ../data/dialogs.glade.h:95 msgid "_Assign lyric..." msgstr "" #: ../data/dialogs.glade.h:96 msgid "_Blur:" msgstr "" #: ../data/dialogs.glade.h:97 msgid "_Double lines" msgstr "雙行(_D)" #: ../data/dialogs.glade.h:98 msgid "_Download" msgstr "下載(_D)" #: ../data/dialogs.glade.h:99 msgid "_Font:" msgstr "å­—åž‹(_F):" #. From witch web site to search and download lyrics #: ../data/dialogs.glade.h:101 msgid "_From:" msgstr "" #: ../data/dialogs.glade.h:102 msgid "_General" msgstr "常è¦(_G)" #: ../data/dialogs.glade.h:103 msgid "_Hide" msgstr "éš±è—(_H)" #: ../data/dialogs.glade.h:104 msgid "_Host:" msgstr "" #: ../data/dialogs.glade.h:105 msgid "_Inactive color:" msgstr "" #: ../data/dialogs.glade.h:106 msgid "_Lock Position" msgstr "" #: ../data/dialogs.glade.h:107 msgid "_Lyric Location" msgstr "歌詞ä½ç½®(_L)" #: ../data/dialogs.glade.h:108 msgid "_Network" msgstr "" #: ../data/dialogs.glade.h:109 msgid "_No lyric" msgstr "" #: ../data/dialogs.glade.h:110 msgid "_OSD" msgstr "æ¡Œé¢æ­Œè©ž(_O)" #: ../data/dialogs.glade.h:111 msgid "_Scrolling" msgstr "" #: ../data/dialogs.glade.h:112 msgid "_Search" msgstr "" #: ../data/dialogs.glade.h:113 msgid "_Search lyric..." msgstr "" #: ../data/dialogs.glade.h:114 msgid "_Second line:" msgstr "次行(_S)" #: ../data/dialogs.glade.h:115 msgid "_Title:" msgstr "" #: ../data/dialogs.glade.h:116 msgid "_Type:" msgstr "" #: ../data/dialogs.glade.h:117 msgid "_Username:" msgstr "" #: ../data/dialogs.glade.h:118 msgid "bottom" msgstr "下" #: ../data/dialogs.glade.h:120 msgid "middle" msgstr "中" #: ../data/dialogs.glade.h:121 msgid "top" msgstr "上" #: ../data/dialogs.glade.h:122 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" " Tiger Soldier https://launchpad.net/~tigersoldi" #~ msgid "Preview" #~ msgstr "é è¦½" #~ msgid "Qianqian" #~ msgstr "åƒåƒ" #~ msgid "Sogou" #~ msgstr "æœç‹—" #~ msgid "_Width:" #~ msgstr "寬度(_W):" #~ msgid "Download _engine:" #~ msgstr "下載引擎(_E):" #~ msgid "_Lock" #~ msgstr "鎖定(_L)" #~ msgid "_Download lyric" #~ msgstr "下載歌詞(_D)" #~ msgid "TODO: Add options here" #~ msgstr "TODO: Add options here" #~ msgid "gtk-cancel" #~ msgstr "gtk-cancel" #~ msgid "gtk-ok" #~ msgstr "gtk-ok" osdlyrics-0.5.5~rc1+dfsg1/python/000077500000000000000000000000001345713535600166755ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/python/.gitignore000066400000000000000000000000141345713535600206600ustar00rootroot00000000000000__init__.py osdlyrics-0.5.5~rc1+dfsg1/python/Makefile.am000066400000000000000000000017661345713535600207430ustar00rootroot00000000000000# # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # SUBDIRS = dbusext ol_PYTHON = \ app.py \ config.py \ consts.py \ pattern.py \ lrc.py \ utils.py \ errors.py \ player_proxy.py \ timer.py \ metadata.py \ lyricsource.py \ $(NULL) nodist_ol_PYTHON = \ __init__.py \ $(NULL) EXTRA_DIST = \ __init__.py.in \ $(NULL) oldir = @pkgpythondir@ osdlyrics-0.5.5~rc1+dfsg1/python/__init__.py.in000066400000000000000000000015031345713535600214120ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # PROGRAM_NAME = '@PROGRAM_NAME@' PACKAGE_NAME = '@PACKAGE_NAME@' PACKAGE_VERSION = '@PACKAGE_VERSION@' osdlyrics-0.5.5~rc1+dfsg1/python/app.py000066400000000000000000000116361345713535600200360ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from builtins import object from optparse import OptionParser import dbus import dbus.mainloop.glib from dbus.mainloop.glib import DBusGMainLoop import dbus.service from gi.repository import GLib from .consts import DAEMON_BUS_NAME APP_BUS_PREFIX = 'org.osdlyrics.' dbus.mainloop.glib.threads_init() class AlreadyRunningException(Exception): """ Raised when a process with given bus name exists. """ pass class App(object): """ Basic class to create a component application for OSD Lyrics. The application creates a mainloop, owns a DBus name, and exits when the bus name of OSD Lyrics disappears. Once the app is created, it will parse `sys.argv`. The accepted arguments are: - `-w`, `--watch-daemon`: If not empty, watch the daemon with given bus name. The default value is the bus name of OSD Lyrics To create an component application owning the bus name ``org.osdlyrics.MyApp``, just simply follow the code below: app = App('MyApp') app.run() """ def __init__(self, name, watch_daemon=True, singleton=True): """ Arguments: - `name`: The suffix of the bus name. The full bus name is `org.osdlyrics.` + name - `watch_daemon`: Whether to watch daemon bus - `singleton`: If True, raise AlreadyRunningException if the bus name already has an owner. """ self._name = name self._namewatch = None self._watch_daemon = watch_daemon self._loop = GLib.MainLoop() self._conn = dbus.SessionBus(mainloop=DBusGMainLoop()) self._bus_names = [] try: self.request_bus_name(APP_BUS_PREFIX + name, singleton) except dbus.NameExistsException: raise AlreadyRunningException( 'Process with bus name %s is already running' % ( APP_BUS_PREFIX + name)) self._parse_options() def _parse_options(self): parser = OptionParser() parser.add_option('-w', '--watch-daemon', dest='watch_daemon', action='store', default=DAEMON_BUS_NAME, metavar='DAEMON_BUS_NAME', help=('A well-known bus name on DBus. Exit when the' ' name disappears. If set to empty string,' ' this player proxy will not exit.')) options, args = parser.parse_args() if self._watch_daemon: self._watch_daemon_bus(options.watch_daemon) def _watch_daemon_bus(self, name): if name: self._namewatch = self._conn.watch_name_owner(name, self._daemon_name_changed) def _daemon_name_changed(self, name): if not name: self._loop.quit() if (self._namewatch is not None): self._namewatch.cancel() self._namewatch = None @property def connection(self): """The DBus connection of the app""" return self._conn @property def loop(self): return self._loop def run(self): """ Runs the main loop Return True if the loop is quited by the program. False if quited by Ctrl+C """ try: self._loop.run() except KeyboardInterrupt: return False return True def run_on_main_thread(self, target, args=(), kwargs={}): """Run a callable on main thread. This is useful for notifying a thread is finished. """ def timeout_func(*user_data): target(*args, **kwargs) return GLib.SOURCE_REMOVE GLib.timeout_add(0, timeout_func) def quit(self): """Quits the main loop""" self._loop.quit() def request_bus_name(self, bus_name, do_not_queue=False): """ Request for additional well-known name on DBus """ self._bus_names.append(dbus.service.BusName(bus_name, self.connection, do_not_queue=do_not_queue)) osdlyrics-0.5.5~rc1+dfsg1/python/config.py000066400000000000000000000132271345713535600205210ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from builtins import object import logging import dbus from .consts import CONFIG_BUS_NAME, CONFIG_OBJECT_PATH CONFIG_INTERFACE = 'org.osdlyrics.Config' class Config(object): """ Helper class to retrive configs from OSD Lyrics through DBus It provides a set of get_ functions to get config values with default values. If the value with given key exists, return the value. Otherwise the value to the key is set to the default value, and returns it. If no default value specified and the key does not exist, raise an exception. Values can be monitored by connect_change function. """ def __init__(self, conn, follow_name_owner_changes=True): """ Arguments: - `conn`: DBus connection """ self._conn = conn self._proxy = conn.get_object(CONFIG_BUS_NAME, CONFIG_OBJECT_PATH, follow_name_owner_changes=follow_name_owner_changes) self._proxy = dbus.Interface(self._proxy, CONFIG_INTERFACE) self._signals = {} self._proxy.connect_to_signal('ValueChanged', self._value_changed_cb) def get_bool(self, key, default=None): try: return self._proxy.GetBool(key) except Exception as e: if default is not None: try: self._proxy.SetBool(key, default) except Exception: pass return default raise e def set_bool(self, key, value): self._proxy.SetBool(key, value) def get_int(self, key, default=None): try: return self._proxy.GetInt(key) except Exception as e: if default is not None: try: self._proxy.SetInt(key, default) except Exception: pass return default raise e def set_int(self, key, value): self._proxy.SetInt(key, value) def get_double(self, key, default=None): try: return self._proxy.GetDouble(key) except Exception as e: if default is not None: try: self._proxy.SetBool(key, default) except Exception: pass return default raise e def set_double(self, key, value): self._proxy.SetDouble(key, value) def get_string(self, key, default=None): try: return self._proxy.GetString(key) except Exception as e: if default is not None: try: self._proxy.SetString(key, default) except Exception: pass return default raise e def set_string(self, key, value): self._proxy.SetString(key, value) def get_string_list(self, key, default=None): try: return self._proxy.GetStringList(key) except Exception as e: if default is not None: try: self._proxy.SetStringList(key, default) except Exception: pass return default raise e def set_string_list(self, key, value): self._proxy.SetStringList(key, value) def connect_change(self, key, func): """ Calls func once a config value with specified key is changed The callback function `func` receives one argument, the key that has changed """ if not callable(func): return self._signals.setdefault(key, []).append(func) def disconnect_change(self, key=None, func=None): if key is None: self._signals = {} return if key in self._signals: if func is None: del self._signals[key] else: self._signals[key].remove(func) def _value_changed_cb(self, name_list): for name in name_list: for handler in self._signals.get(name, []): handler(name) def test(): def value_changed(name): typename = name.split('/')[1] logging.debug('%s has been changed to %s', name, getattr(config, 'get_' + typename)(name)) from gi.repository import GLib from dbus.mainloop.glib import DBusGMainLoop loop = GLib.MainLoop() dbus_mainloop = DBusGMainLoop() conn = dbus.SessionBus(mainloop=dbus_mainloop) config = Config(conn) testcase = {'bool': False, 'int': 123, 'double': 123.54, 'string': 'Foobar', 'string_list': ['Foo', 'bar'], } for k in testcase: config.connect_change('test/' + k, value_changed) for k, v in testcase.items(): getattr(config, 'set_' + k)('test/' + k, v) loop.run() if __name__ == '__main__': test() osdlyrics-0.5.5~rc1+dfsg1/python/consts.py000066400000000000000000000031051345713535600205570ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # # D-Bus names, interfaces and object paths DAEMON_INTERFACE = 'org.osdlyrics.Daemon' DAEMON_BUS_NAME = 'org.osdlyrics.Daemon' DAEMON_OBJECT_PATH = '/org/osdlyrics/Daemon' MPRIS2_PREFIX = 'org.mpris.MediaPlayer2.' DAEMON_MPRIS2_NAME = 'org.mpris.MediaPlayer2.osdlyrics' CONFIG_BUS_NAME = 'org.osdlyrics.Config' CONFIG_OBJECT_PATH = '/org/osdlyrics/Config' PLAYER_PROXY_INTERFACE = 'org.osdlyrics.PlayerProxy' PLAYER_PROXY_OBJECT_PATH_PREFIX = '/org/osdlyrics/PlayerProxy/' MPRIS2_PLAYER_INTERFACE = 'org.mpris.MediaPlayer2.Player' MPRIS2_OBJECT_PATH = '/org/mpris/MediaPlayer2' LYRIC_SOURCE_PLUGIN_INTERFACE = 'org.osdlyrics.LyricSourcePlugin' LYRIC_SOURCE_PLUGIN_OBJECT_PATH_PREFIX = '/org/osdlyrics/LyricSourcePlugin/' # Metadata keys METADATA_TITLE = 'title' METADATA_ARTIST = 'artist' METADATA_ALBUM = 'album' METADATA_TRACKNUM = 'tracknum' METADATA_URI = 'location' osdlyrics-0.5.5~rc1+dfsg1/python/dbusext/000077500000000000000000000000001345713535600203535ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/python/dbusext/Makefile.am000066400000000000000000000015541345713535600224140ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # ol_dbus_PYTHON = \ __init__.py \ service.py \ property.py \ $(NULL) ol_dbusdir = @pkgpythondir@/dbusext CLEANFILES = \ *.pyc \ $(NULL) osdlyrics-0.5.5~rc1+dfsg1/python/dbusext/__init__.py000066400000000000000000000013341345713535600224650ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # osdlyrics-0.5.5~rc1+dfsg1/python/dbusext/property.py000066400000000000000000000176221345713535600226210ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from builtins import object, str import dbus.exceptions class AccessDeniedError(dbus.exceptions.DBusException): def __init__(self, *args): dbus.exceptions.DBusException.__init__(self, dbus_error_name='org.osdlyrics.Error.AccessDenied', *args) class Property(object): """ DBus property class """ def __init__(self, dbus_interface, type_signature, emit_change=True, readable=True, writeable=True, name=None, fget=None, fset=None, dbus_set=None): """ Arguments: - `type_signature`: (string) Type signature of this property. This parameter is used for introspection only - `dbus_interface`: (string) The DBus interface of this property - `emit_change`: (boolean or string) Whether to emit change with `PropertiesChanged` D-Bus signal when the property is set. Possible values are boolean value True or False, or a string 'invalidates'. - `readable`: Whether the property is able to visit with `Get` D-Bus method. - `writeable`: Whether the property is able to write with `Set` D-Bus method. A property is writeable only when `writeable` is set to True and a setter function is set. """ self._type_signature = type_signature # CAVEAT: python-dbus uses "_dbus_interface" internally (in service.py) self._interface = dbus_interface self._fset = fset self._fget = fget self.__name__ = name self._dbusset = dbus_set if emit_change not in [True, False, 'invalidates']: raise ValueError('Value of emit_change must be one of True, False, or \'invalidates\'') self._emit_change = emit_change self._readable = readable self._writeable = writeable @property def interface(self): """ Return the dbus interface of this property """ return self._interface @property def readable(self): return self._readable @property def writeable(self): return self._writeable @property def emit_change(self): return str(self._emit_change).lower() @property def type_signature(self): return self._type_signature def __get__(self, obj, objtype=None): if obj is None: return self if self._fget is None: raise AttributeError("unreadable attribute") return wrap_dbus_type(self._type_signature, self._fget(obj)) def __set__(self, obj, value): if self._fset is None: raise AttributeError("can't set attribute") self._set_value(obj, value, self._fset) def dbus_set(self, obj, value): """ Set a property through D-Bus. This is intended to called by dbusext.service.Object to implement D-Bus property features. Don't call it directly. If dbus setter is set by `dbus_setter` decorator, the dbus setter will be used the handle this request. Otherwise, if `writeable` is set to `True`, the general setter set by `setter` decorator will be used. """ if callable(self._dbusset): self._set_value(obj, value, self._dbusset) elif self.writeable and callable(self._fset): self._set_value(obj, value, self._fset) else: raise AccessDeniedError('Property %s is not writeable' % self.__name__) def _set_value(self, obj, value, setter): changed = setter(obj, value) if not self._emit_change: return if changed is None or changed: changed = True else: changed = False if changed and getattr(self, '__name__', None) and getattr(obj, '_property_set', None): obj._property_set(self.__name__, self._emit_change is True) def setter(self, fset): """ Sets the setter function of the property. Return the property object itself. The setter function should return a boolean value to tell if the value is changed. Returning None is considered as True. When ``emit_change`` is True in constructor, the property will emit changes when value is set. If the owner object has a function named ``_property_set``, and the ``__name__`` attribute of the property object is set by the class, the function will be invoked to notify the property has been set when the setter is called, with the name of property as the first argument, and an boolean as the second argument to tell whether the value is changed. This is usually used as an decorator:: class A(osdlyrics.dbus.Object): @osdlyrics.dbus.property(type='s', dbus_interface='example.property') def x(self): return self._x @x.setter def x(self, value): if self._x != value: self._x = value return True else: return False @x.dbus_setter def x(self, value): if self._do_something(value): self._x = value return True return False By default, the setter is used as both python property setter and dbus property setter. If you want a different set-handler to handle dbus property set request, use `dbus_setter`. """ self._fset = fset return self def dbus_setter(self, fset): """ A decorator to create a D-Bus property set handler. The new value set through D-Bus interface is passed to this setter. If dbus setter not set, the general setter set by `Property.setter` decorator is used as D-Bus property set handler. Arguments: - `fset`: The setter handler. Takes a parameter as the new value, and return a boolean value to indicate whether the property is changed. If None is returned, it is treated as True. See `setter` for example. """ self._dbusset = fset return self DBUS_TYPE_MAP = { 'y': dbus.Byte, 'b': dbus.Boolean, 'n': dbus.Int16, 'q': dbus.UInt16, 'i': dbus.Int32, 'u': dbus.UInt32, 'x': dbus.Int64, 't': dbus.UInt64, 'd': dbus.Double, 's': dbus.String, 'o': dbus.ObjectPath, 'g': dbus.Signature, } def wrap_dbus_type(signature, value): if signature in DBUS_TYPE_MAP: dbustype = DBUS_TYPE_MAP[signature] if isinstance(value, dbustype): return value else: return dbustype(value) elif signature.startswith('a{'): if isinstance(value, dbus.Dictionary): return value else: return dbus.Dictionary(value, signature=signature) elif signature.startswith('a'): if isinstance(value, dbus.Array): return value else: return dbus.Array(value, signature=signature) elif signature.startswith('('): if isinstance(value, dbus.Struct): return value else: return dbus.Struct(value, signature=signature) osdlyrics-0.5.5~rc1+dfsg1/python/dbusext/service.py000066400000000000000000000407431345713535600223750ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from abc import ABCMeta import logging import sys import xml.etree.ElementTree as xet import dbus import dbus.exceptions import dbus.service from gi.repository import GLib from .property import Property # Use the default encoding in ElementTree.tostring under Python 2, but prefer # Unicode under Python 3 to obtain a 'str', not 'bytes' instance. # TODO: remove once we have fully migrated to Python 3 INTROSPECT_ENCODING = 'unicode' if sys.version_info >= (3, 0) else 'us-ascii' class ObjectTypeCls(dbus.service.InterfaceType, ABCMeta): def __init__(cls, name, bases, dct): property_dict = {} for k, v in dct.items(): if isinstance(v, Property): property_dict.setdefault(v.interface, {})[v.__name__] = v property_table = getattr(cls, '_dbus_property_table', {}) for base in bases: if hasattr(base, '_dbus_property_table'): modulename = base.__module__ + '.' + base.__name__ for interface, props in list(base._dbus_property_table[modulename].items()): clsprops = property_dict.setdefault(interface, {}) clsprops.update(props) cls._dbus_property_table = property_table property_table[cls.__module__ + '.' + cls.__name__] = property_dict super(ObjectTypeCls, cls).__init__(name, bases, dct) ObjectType = ObjectTypeCls('ObjectType', (dbus.service.Object, ), {}) class Object(ObjectType): """ DBus object wrapper which provides DBus property support """ # __metaclass__ = ObjectType def __init__(self, conn=None, object_path=None, bus_name=None): """ Either conn or bus_name is required; object_path is also required. Arguments: - `conn`: (dbus.connection.Connection or None) - The connection on which to export this object. If None, use the Bus associated with the given bus_name. If there is no bus_name either, the object is not initially available on any Connection. For backwards compatibility, if an instance of dbus.service.BusName is passed as the first parameter, this is equivalent to passing its associated Bus as conn, and passing the BusName itself as bus_name. - `object_path`: (str or None) - A D-Bus object path at which to make this Object available immediately. If this is not None, a conn or bus_name must also be provided. - `bus_name`: (dbus.service.BusName or None) - Represents a well-known name claimed by this process. A reference to the BusName object will be held by this Object, preventing the name from being released during this Object's lifetime (unless it's released manually). """ dbus.service.Object.__init__(self, conn=conn, object_path=object_path, bus_name=bus_name) self._changed_props = {} self._prop_change_timer = None def _prop_changed_timeout_cb(self): self._prop_change_timer = None changed_props = {} for k, v in self._changed_props.items(): iface = getattr(self.__class__, k).interface changed_props.setdefault(iface, {'changed': {}, 'invalidated': []}) if v: changed_props[iface]['changed'][k] = getattr(self, k) else: changed_props[iface]['invalidated'].append(k) self._changed_props = {} for k, v in changed_props.items(): self.PropertiesChanged(k, v['changed'], v['invalidated']) return False def _property_set(self, prop_name, emit_with_value): """ Callback for properties when a new value is set This method is called by properties of type osdlyrics.dbus.Property Arguments: - `prop_name`: - `changed`: """ self._changed_props[prop_name] = emit_with_value if not self._prop_change_timer: self._prop_change_timer = GLib.idle_add(self._prop_changed_timeout_cb) @dbus.service.method(dbus_interface=dbus.PROPERTIES_IFACE, in_signature='ss', out_signature='v') def Get(self, iface_name, prop_name): """ DBus property getter Arguments: - `iface_name`: - `prop_name`: """ prop = getattr(self.__class__, prop_name, None) if isinstance(prop, Property) and \ (not iface_name or prop.interface == iface_name): return getattr(self, prop_name) raise dbus.exceptions.DBusException('No property of %s.%s' % (iface_name, prop_name)) @dbus.service.method(dbus_interface=dbus.PROPERTIES_IFACE, in_signature='ssv', out_signature='') def Set(self, iface_name, prop_name, value): """ DBus property setter Arguments: - `iface_name`: - `prop_name`: - `value`: """ prop = getattr(self.__class__, prop_name, None) if isinstance(prop, Property) and \ (not iface_name or prop.interface == iface_name): prop.dbus_set(self, value) else: raise dbus.exceptions.DBusException('No property of %s.%s' % (iface_name, prop_name)) @dbus.service.method(dbus_interface=dbus.PROPERTIES_IFACE, in_signature='s', out_signature='a{sv}') def GetAll(self, iface_name): """ List all DBus properties Arguments: - `iface_name`: """ ret = {} property_dict = self._dbus_property_table[self.__class__.__module__ + '.' + self.__class__.__name__] if iface_name != '' and iface_name in property_dict: for prop_name, prop in property_dict[iface_name].items(): if prop.readable: ret[prop_name] = prop.__get__(self) elif iface_name == '': for prop_list in property_dict.values(): for prop_name, prop in prop_list.items(): if prop.readable: ret[prop_name] = prop.__get__(self) return ret @dbus.service.signal(dbus_interface=dbus.PROPERTIES_IFACE, signature='sa{sv}as') def PropertiesChanged(self, iface_name, changed_props, invalidated_props): logging.debug('%s changed: %s invalidated: %s', iface_name, changed_props, invalidated_props) pass @dbus.service.method(dbus.service.INTROSPECTABLE_IFACE, in_signature='', out_signature='s', path_keyword='object_path', connection_keyword='connection') def Introspect(self, object_path, connection): """ Patch for dbus.service.Object to add property introspection data """ xml = dbus.service.Object.Introspect(self, object_path, connection) property_dict = self._dbus_property_table[self.__class__.__module__ + '.' + self.__class__.__name__] if property_dict == {}: return xml node = xet.XML(xml) iface_list = node.findall('interface') appended_iface = set() for iface in iface_list: iface_name = iface.get('name') if iface_name in property_dict: for prop_name, prop in property_dict[iface_name].items(): iface.append(_property2element(prop)) appended_iface.add(iface_name) for iface_name, prop_list in property_dict.items(): if iface_name in appended_iface: continue iface = xet.Element('interface', name=iface_name) for prop in prop_list.values(): iface.append(_property2element(prop)) node.append(iface) return '\n' + \ xet.tostring(node, encoding=INTROSPECT_ENCODING) def property(type_signature, dbus_interface, emit_change=True, readable=True, writeable=True): """ Decorator to define dbus properties as a class member. To use this decorator, define your class and member as below:: class Obj(osdlyrics.dbusext.service.Object): def __init__(*args, **kwargs): osdlyrics.dbusext.service.Object.__init__(*args, **kwargs) self._x = 0 @osdlyrics.dbusext.service.property( type_signature='i', dbus_interface='org.example.Interface') def x(self): return self._x @x.setter def x(self, value): if self._x != value: self._x = value return True else: return False You can use the dbus property member as a property of python Arguments: - `type_signature`: (string) The D-Bus type signature of the property - `dbus_interface`: (string) The D-Bus interface of the property - `emit_change`: (boolean or string) Whether to emit change with `PropertiesChanged` D-Bus signal when the property is set. Possible values are boolean value True or False, or a string 'invalidates'. - `readable`: Whether the property is able to visit with `Get` D-Bus method. - `writeable`: Whether the property is able to write with `Set` D-Bus method. A property is writeable only when `writeable` is set to True and a setter function is set. """ def dec_handler(fget): """ Arguments: - `fget`: getter function """ return Property(type_signature=type_signature, dbus_interface=dbus_interface, emit_change=emit_change, name=fget.__name__, readable=readable, writeable=writeable, fget=fget) return dec_handler def _property2element(prop): """ Convert an osdlyrics.dbusext.service.Property object to an xml.etree.ElementTree.ElementTree object. """ access = '' if prop.readable: access += 'read' if prop.writeable and (callable(prop._fset) or callable(prop._dbusset)): access += 'write' elem = xet.Element('property', name=prop.__name__, type=prop.type_signature, access=access) if prop.emit_change != 'true': annotation = xet.Element('annotation', name='org.freedesktop.DBus.Property.EmitsChangedSignal', value=prop.emit_change) elem.append(annotation) return elem def test(): BUS_NAME = 'org.example.test' IFACE = 'org.example.test' PATH = '/org/example/test' DEFAULT_VALUE = 'default value of x' class TestObj(Object): def __init__(self, loop): Object.__init__(self, conn=dbus.SessionBus(), object_path=PATH) self._loop = loop self._foo = DEFAULT_VALUE self._bar = 'yes' self._baz = 'baz' @property(type_signature='s', dbus_interface=IFACE) def foo(self): return self._foo @foo.setter def foo(self, value): if value != self._foo: self._foo = value return True else: return False @property(type_signature='s', dbus_interface='another.iface', readable=False, writeable=True, emit_change='invalidates') def bar(self): return self._bar @bar.setter def bar(self, value): self._bar = value @property(type_signature='s', dbus_interface='another.iface') def baz(self): return self._baz @baz.dbus_setter def baz(self, value): self._baz = value return True @baz.setter def baz(self, value): raise Exception('Shouldn\'t be called') @dbus.service.method(dbus_interface=IFACE, in_signature='', out_signature='') def Quit(self): self._loop.quit() def get_reply_handler(expected_value): def handler(value): if value != expected_value: logging.warning('Get failed, expect %s but %s got', expected_value, value) else: logging.debug('Get succesful') return handler def get_all_reply_handler(expected_dict): def handler(value): for k, v in value.items(): if k not in expected_dict: logging.warning('GetAll: unexpected key %s', k) elif v != expected_dict[k]: logging.warning('GetAll: expected value of key %s is %s but %s got', k, expected_dict[k], v) for k in expected_dict: if k not in value: logging.warning('GetAll: missing key %s', k) logging.debug('GetAll finished') return handler def set_reply_handler(): logging.debug('Set succeed') def error_handler(e): logging.error('Error %s', e) def introspect_reply_handler(xml): # print xml pass def msg_handler(msg): def handler(*args, **kwargs): logging.debug(msg) return handler def test_timeout(): proxy = conn.get_object(BUS_NAME, PATH) proxy.GetAll('', reply_handler=get_all_reply_handler({'foo': DEFAULT_VALUE, 'baz': 'baz'}), error_handler=error_handler) proxy.GetAll('another.iface', reply_handler=get_all_reply_handler({'baz': 'baz'}), error_handler=error_handler) proxy.Get('', 'foo', reply_handler=get_reply_handler(DEFAULT_VALUE), error_handler=error_handler) proxy.Set('', 'foo', 'new value of x', reply_handler=set_reply_handler, error_handler=error_handler) proxy.Get('', 'foo', reply_handler=get_reply_handler('new value of x'), error_handler=error_handler) proxy.Introspect(reply_handler=introspect_reply_handler, error_handler=error_handler) proxy.Set('another.iface', 'foo', 'should not success', reply_handler=msg_handler('Set error, should raise an error'), error_handler=msg_handler('Set succesful, an expected exception')) proxy.Set('another.iface', 'baz', 'new value of baz', reply_handler=set_reply_handler, error_handler=error_handler) proxy.Get('another.iface', 'baz', reply_handler=get_reply_handler('new value of baz'), error_handler=error_handler) proxy.Set('another.iface', 'bar', 'new value of bar', reply_handler=set_reply_handler, error_handler=error_handler) proxy.Get('another.iface', 'bar', reply_handler=get_reply_handler('new value of bar'), error_handler=error_handler) return False class TestObjSub(TestObj): def __init__(self, loop): TestObj.__init__(self, loop) from gi.repository import GLib from dbus.mainloop.glib import DBusGMainLoop loop = GLib.MainLoop() dbus_mainloop = DBusGMainLoop() conn = dbus.SessionBus(mainloop=dbus_mainloop) bus_name = dbus.service.BusName(BUS_NAME, conn) # noqa: F841 testobj = TestObjSub(loop) # noqa: F841 GLib.timeout_add(100, test_timeout) loop.run() if __name__ == '__main__': test() osdlyrics-0.5.5~rc1+dfsg1/python/errors.py000066400000000000000000000031621345713535600205650ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from builtins import super import dbus.exceptions class BaseError(dbus.exceptions.DBusException): """ Base class for raising an exception through D-Bus """ def __init__(self, *args, **kwargs): """ Arguments: - `name`: The name of exception to send through D-Bus. If not set, 'org.osdlyrics.Error.' + the name of the class (without 'Error' suffix, if any) will be returned. - `*args`: - `*kwargs`: """ dbus_error_name = kwargs.pop('name', None) if dbus_error_name is None: error_name = self.__class__.__name__ if error_name.endswith('Error'): error_name = error_name[:-len('Error')] dbus_error_name = 'org.osdlyrics.Error.' + error_name kwargs['name'] = dbus_error_name super().__init__(*args, **kwargs) class PatternException(Exception): pass osdlyrics-0.5.5~rc1+dfsg1/python/lrc.py000066400000000000000000000112111345713535600200230ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from __future__ import print_function from builtins import object import re import dbus.types __all__ = ( 'AttrToken', 'TimeToken', 'StringToken', 'tokenize', 'parse_lrc', ) LINE_PATTERN = re.compile(r'(\[[^\[]*?\])') TIMESTAMP_PATTERN = re.compile(r'^\[(\d+(:\d+){0,2}(\.\d+)?)\]$') ATTR_PATTERN = re.compile(r'^\[([\w\d]+):(.*)\]$') class AttrToken(object): """ Represents tags with the form of ``[key:value]`` """ def __init__(self, key, value): self.key = key self.value = value def __repr__(self): return '{%s: %s}' % (self.key, self.value) class StringToken(object): """ Represents a line of lyric text """ def __init__(self, text): self.text = text def __repr__(self): return '"%s"\n' % self.text class TimeToken(object): """ Represents tags with the form of ``[h:m:s.ms]`` The time attribute is the timestamp in milliseconds """ def __init__(self, string): parts = string.split(':') parts.reverse() factor = 1000 ms = int(float(parts[0]) * factor) for s in parts[1:]: factor = factor * 60 ms = ms + factor * int(s) self.time = ms def __repr__(self): return '[%s]' % self.time def tokenize(content): """ Split the content of LRC file into tokens Returns a list of tokens Arguments: - `content`: UTF8 string, the content to be tokenized """ def parse_tag(tag): m = TIMESTAMP_PATTERN.match(tag) if m: return TimeToken(m.group(1)) m = ATTR_PATTERN.match(tag) if m: return AttrToken(m.group(1), m.group(2)) return None def tokenize_line(line): pos = 0 tokens = [] while pos < len(line) and line[pos] == '[': has_tag = False m = LINE_PATTERN.search(line, pos) if m and m.start() == pos: tag = m.group() token = parse_tag(tag) if token: tokens.append(token) has_tag = True pos = m.end() if not has_tag: break tokens.append(StringToken(line[pos:])) return tokens lines = content.splitlines() tokens = [] for line in lines: tokens.extend(tokenize_line(line)) return tokens def parse_lrc(content): """ Parse an lrc file Arguments: - `content`: LRC file content encoded in UTF8 Return values: attr, lyrics - `attr`: A dict represents attributes in LRC file - `lyrics`: A list of dict with 3 keys: id, timestamp and text. The list is sorted in ascending order by timestamp. Id increases from 0. """ tokens = tokenize(content) attrs = {} lyrics = [] timetags = [] for token in tokens: if isinstance(token, AttrToken): attrs[token.key] = token.value elif isinstance(token, TimeToken): timetags.append(token.time) else: for timestamp in timetags: lyrics.append({'timestamp': dbus.types.Int64(timestamp), 'text': token.text}) timetags = [] lyrics.sort(key=lambda a: a['timestamp']) i = 0 for lyric in lyrics: lyric['id'] = dbus.types.UInt32(i) i = i + 1 return attrs, lyrics def test(): TEST_CASE1 = \ """[ti:ç„”ã®æ‰‰~hearty edition][ar:FictionJunction YUUKA] [al:ç„”ã®æ‰‰] [02:45.59]ãã®æ—¥ã¾ã§ [52.78] [03:48][35]ç„”ã®æ‰‰ã¸ [1:03:56.66][03:14.77] ãŠã‚り """ def test_tokenizer(): tokens = tokenize(TEST_CASE1) print(tokens) def test_parser(): attr, lyrics = parse_lrc(TEST_CASE1) print(attr) for line in lyrics: print('%s: %s -> %s' % (line['id'], line['timestamp'], line['text'])) test_tokenizer() test_parser() if __name__ == '__main__': test() osdlyrics-0.5.5~rc1+dfsg1/python/lyricsource.py000066400000000000000000000360061345713535600216170ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2012 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from future import standard_library standard_library.install_aliases() from builtins import chr, object, range, str, super import logging import threading import dbus from .app import App from .config import Config from .consts import (LYRIC_SOURCE_PLUGIN_INTERFACE, LYRIC_SOURCE_PLUGIN_OBJECT_PATH_PREFIX) from .dbusext.service import Object as DBusObject, property as dbus_property from .metadata import Metadata SEARCH_SUCCEED = 0 SEARCH_CANCELLED = 1 SEARCH_FAILED = 2 DOWNLOAD_SUCCEED = 0 DOWNLOAD_CANCELLED = 1 DOWNLOAD_FAILED = 2 def onmainthread(func): def decfunc(self, app, *args, **kwargs): def timeout_cb(): func(self, *args, **kwargs) return False app.run_on_main_thread(timeout_cb) return decfunc class SearchResult(object): """ Lyrics that match the metadata to be searched. """ def __init__(self, sourceid, downloadinfo, title='', artist='', album='', comment=''): """ Arguments: - `title`: The matched lyric title. - `artist`: The matched lyric artist. - `album`: The matched lyric album. - `downloadinfo`: Some additional data that is needed to download the lyric. Normally this value is the url or ID of the lyric. ``downloadinfo`` MUST be composed with basic types such as numbers, lists, dicts or strings so that it can be converted to D-Bus compatible dict with `to_dict` method. """ self._title = title self._artist = artist self._album = album self._comment = comment self._sourceid = sourceid self._downloadinfo = downloadinfo def to_dict(self, ): """ Convert the result to a dict so that it can be sent with D-Bus. """ return {'title': self._title, 'artist': self._artist, 'album': self._album, 'comment': self._comment, 'sourceid': self._sourceid, 'downloadinfo': self._downloadinfo} class BaseTaskThread(threading.Thread): """ Base thread for search or download tasks. Plugins MUST provide a callable object as the `target` argument in the initializer. The target does the task and returns the results. If task fails, an Exception SHOULD be raised in the target. """ def __init__(self, onfinish, onerror, target, args=(), kwargs={}): """ Initialize the thread. The main thread should provide to callbacks to notify the main thread that the thread is finished or an error occurs. Please note that the two callbacks are run in the new thread, not the main thread. So the callback may notify the main thread to handle the results with App.run_on_main_thread. Arguments: - `onfinish`: A callable object to be invoked when `target` finish. The callable SHOULD receive the values returned by `do_task`. - `onerror`: A callable object to be invoked when `target` raise an exception. The callable SHOULD receive the exception raised by `do_task`. - `target`: The callable object to be invoked by the run() method. - `args`: The argument tuple for the target invocation. Defaults to `()`. - `kwargs`: A dictionary of keyword arguments for the target invocation. Defaults to `{}`. """ super().__init__() self._onfinish = onfinish self._onerror = onerror self._args = args self._kwargs = kwargs self._target = target def run(self): """ Runs the task thread. Do NOT override this method. Override `do_task` instead. """ try: ret = self._target(*self._args, **self._kwargs) self._onfinish(ret) except Exception as e: logging.exception('Got exception in thread') self._onerror(e) import sys sys.stdout.flush() class BaseLyricSourcePlugin(DBusObject): """ Base class for implementing a lyric source plugin """ def __init__(self, id, name=None, watch_daemon=True): """ Create a new lyric source instance. Arguments: - `id`: The unique ID of the lyric source plugin. The full bus name of the plugin will be `org.osdlyrics.LyricSourcePlugin.id` - `name`: (optional) The name of the plugin, which should be properly localized. If `name` is missing, the plugin will take `id` as its name. - `watch_daemon`: Whether to watch daemon bus. """ self._id = id self._app = App('LyricSourcePlugin.' + id, watch_daemon=watch_daemon) super().__init__(conn=self._app.connection, object_path=LYRIC_SOURCE_PLUGIN_OBJECT_PATH_PREFIX + self._id) self._search_count = 0 self._download_count = 0 self._search_tasks = {} self._download_tasks = {} self._name = name if name is not None else id self._config = None def do_search(self, metadata): """ Do the real search work by plugins. All plugins MUST implement this method. This method runs in a seperate thread, so don't worry about block IO. Parameters: - `metadata`: The metadata of the track to search. The type of `metadata` is osdlyrics.metadata.Metadata Returns: A list of SearchResult objects """ raise NotImplementedError() @onmainthread def do_searchsuccess(self, ticket, results): if ticket in self._search_tasks: del self._search_tasks[ticket] dbusresults = [result.to_dict() for result in results] self.SearchComplete(ticket, SEARCH_SUCCEED, dbusresults) @onmainthread def do_searchfailure(self, ticket, e): if ticket in self._search_tasks: del self._search_tasks[ticket] logging.info('Search fail, %s', e) self.SearchComplete(ticket, SEARCH_FAILED, []) @dbus.service.method(dbus_interface=LYRIC_SOURCE_PLUGIN_INTERFACE, in_signature='a{sv}', out_signature='i') def Search(self, metadata): ticket = self._search_count self._search_count = self._search_count + 1 thread = BaseTaskThread(onfinish=lambda result: self.do_searchsuccess(self._app, ticket, result), onerror=lambda e: self.do_searchfailure(self._app, ticket, e), target=self.do_search, kwargs={'metadata': Metadata.from_dict(metadata)}) self._search_tasks[ticket] = thread thread.start() return ticket @dbus.service.method(dbus_interface=LYRIC_SOURCE_PLUGIN_INTERFACE, in_signature='i', out_signature='') def CancelSearch(self, ticket): if ticket in self._search_tasks: del self._search_tasks[ticket] self.SearchComplete(ticket, SEARCH_CANCELLED, []) def do_download(self, downloadinfo): """ Do the real download work by plugins. All plugins MUST implement this method. This method runs in a seperate thread, so don't worry about block IO. Parameters: - `downloadinfo`: The additional info taken from `downloadinfo` field in SearchResult objects. Returns: A string of the lyric content """ raise NotImplementedError() @onmainthread def do_downloadsuccess(self, ticket, content): if ticket in self._download_tasks: del self._download_tasks[ticket] self.DownloadComplete(ticket, DOWNLOAD_SUCCEED, content) @onmainthread def do_downloadfailure(self, ticket, e): if ticket in self._download_tasks: del self._download_tasks[ticket] self.DownloadComplete(ticket, DOWNLOAD_FAILED, str(e).encode('utf-8')) @dbus.service.method(dbus_interface=LYRIC_SOURCE_PLUGIN_INTERFACE, in_signature='v', out_signature='i') def Download(self, downloadinfo): ticket = self._download_count self._download_count = self._download_count + 1 thread = BaseTaskThread(onfinish=lambda content: self.do_downloadsuccess(self._app, ticket, content), onerror=lambda e: self.do_downloadfailure(self._app, ticket, e), target=self.do_download, kwargs={'downloadinfo': downloadinfo}) self._download_tasks[ticket] = thread thread.start() return ticket @dbus.service.method(dbus_interface=LYRIC_SOURCE_PLUGIN_INTERFACE, in_signature='i', out_signature='') def CancelDownload(self, ticket): if ticket in self._download_tasks: del self._download_tasks[ticket] self.DownloadComplete(ticket, DOWNLOAD_CANCELLED, '') @dbus_property(dbus_interface=LYRIC_SOURCE_PLUGIN_INTERFACE, type_signature='s') def Name(self): return self._name @dbus.service.signal(dbus_interface=LYRIC_SOURCE_PLUGIN_INTERFACE, signature='iiaa{sv}') def SearchComplete(self, ticket, status, results): logging.debug('search complete: ticket: %d, status: %d', ticket, status) pass @dbus.service.signal(dbus_interface=LYRIC_SOURCE_PLUGIN_INTERFACE, signature='iiay') def DownloadComplete(self, ticket, status, result): logging.debug('download complete: ticket: %d, status: %d%s', ticket, status, '' if status == DOWNLOAD_SUCCEED else ', result: %s' % result) pass def run(self): """ Run the plugin as a standalone application """ self._app.run() @property def app(self): """ Return the App object that the plugin uses. """ return self._app @property def id(self): """ Return the ID of the lyric source """ return self._id @property def config_proxy(self): if self._config is None: self._config = Config(self._app.connection) return self._config def test(): class DummyLyricSourcePlugin(BaseLyricSourcePlugin): def __init__(self): super().__init__(id='dummy', watch_daemon=False) def do_search(self, metadata): if metadata.title: logging.info('title: %s', metadata.title) results = [SearchResult(title=metadata.title + str(i), artist=metadata.artist + str(i), album=metadata.album + str(i), sourceid=i, downloadinfo='\n'.join((metadata.title, metadata.artist, metadata.album))) for i in range(10)] return results raise Exception('Title must not be empty') def do_download(self, downloadinfo): return b'dummy-downloaded-content' search_tickets = {} download_tickets = {} def search_reply(ticket, expect_status): if ticket in search_tickets: logging.warning('Error: search ticket %d exists', ticket) else: search_tickets[ticket] = expect_status def search_complete_cb(ticket, status, results): if ticket not in search_tickets: logging.warning('Error! search ticket not exists') return if search_tickets[ticket] != status: logging.warning('Error! expect search %d with status %d but %d got', ticket, search_tickets[ticket], status) return logging.debug('Search #%d with status %d', ticket, status) if status == 0: downloadinfo = results[0]['downloadinfo'] source.Download(downloadinfo, reply_handler=lambda t: download_reply(t, 0), error_handler=dummy_error) del search_tickets[ticket] check_quit() def download_reply(ticket, expect_status): if ticket in download_tickets: logging.warning('Error: download ticket %d already exists', ticket) else: download_tickets[ticket] = expect_status def download_complete_cb(ticket, status, content): if ticket not in download_tickets: logging.warning('Error! download ticket not exists') return if download_tickets[ticket] != status: logging.warning('Error! expect download status %d but %d got', download_tickets[ticket], status) return if status == 0: logging.debug('Download #%d success', ticket) logging.debug('Downloaded content: \n%s', ''.join([chr(b) for b in content])) else: logging.warning('Download #%d fail, msg: %s', ticket, ''.join([chr(b) for b in content])) del download_tickets[ticket] check_quit() wait_count = [4] def check_quit(): wait_count[0] -= 1 if wait_count[0] == 0: app.quit() def dummy_error(e): logging.warning('Error: ' + e) dummysource = DummyLyricSourcePlugin() app = dummysource.app conn = app.connection source = conn.get_object('org.osdlyrics.LyricSourcePlugin.dummy', '/org/osdlyrics/LyricSourcePlugin/dummy') source.connect_to_signal('SearchComplete', search_complete_cb) source.connect_to_signal('DownloadComplete', download_complete_cb) source.Search({'title': 'dummytitle', 'artist': 'dummyartist', 'album': 'dummyalbum'}, reply_handler=lambda t: search_reply(t, 0), error_handler=dummy_error) source.Search({'foo': 'bar'}, reply_handler=lambda t: search_reply(t, 2), error_handler=dummy_error) source.Download(123, reply_handler=lambda t: download_reply(t, 2), error_handler=dummy_error) app.run() if __name__ == '__main__': test() osdlyrics-0.5.5~rc1+dfsg1/python/metadata.py000066400000000000000000000317271345713535600210410ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from __future__ import unicode_literals from builtins import object import logging import re import dbus from .consts import METADATA_ALBUM, METADATA_ARTIST, METADATA_TITLE class Metadata(object): """ Metadata of a track This class helps to deal with different metadata formats defined by MPRIS1, MPRIS2 and OSD Lyrics. It is recommended to parse a metadata dict from D-Bus with `Metadata.from_dict()`. Metadata provides following properties: `title`, `artist`, `album`, `location`, `arturl`, `length`, and `tracknum`, where `length` and `tracknum` are integers, the others are strings. """ # Possible MPRIS metadata keys, taken from # http://xmms2.org/wiki/MPRIS_Metadata#MPRIS_v1.0_Metadata_guidelines""" MPRIS1_KEYS = set(['genre', 'comment', 'rating', 'year', 'date', 'asin', 'puid fingerprint', 'mb track id', 'mb artist id', 'mb artist sort name', 'mb album id', 'mb release date', 'mb album artist', 'mb album artist id', 'mb album artist sort name', 'audio-bitrate', 'audio-samplerate', 'video-bitrate']) # Possible MPRIS2 metadata keys, taken from # http://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata MPRIS2_KEYS = set(['xesam:albumArtist', 'xesam:asText', 'xesam:audioBPM', 'xesam:autoRating', 'xesam:comment', 'xesam:composer', 'xesam:contentCreated', 'xesam:discNumber', 'xesam:firstUsed', 'xesam:genre', 'xesam:lastUsed', 'xesam:lyricist', 'xesam:useCount', 'xesam:userRating']) def __init__(self, title=None, artist=None, album=None, arturl=None, tracknum=-1, location=None, length=-1, extra={}): """ Create a new Metadata instance. Arguments: - `title`: (string) The title of the track - `artist`: (string) The artist of the track - `album`: (string) The name of album that the track is in - `arturl`: (string) The URI of the picture of the cover of the album - `tracknum`: (int) The number of the track - `location`: (string) The URI of the file - `length`: (int) The duration of the track in milliseconds. - `extra`: (dict) A dict that is intend to store additional properties provided by MPRIS1 or MPRIS2 DBus dicts. The MPRIS1-related values will be set in the dict returned by `to_mpris1`. The MPRIS2-related values are treated in a similar way. """ self.title = title self.artist = artist self.album = album self.arturl = arturl self.tracknum = tracknum self.location = location self.length = length self._extra = extra def __eq__(self, other): """ Two metadatas are equal if: - The locations are not empty and are equal, or - The titles, artists and albums are equal. See also: src/ol_metadata.c:ol_metadata_equal, thougn they aren't consistent. """ if self is other: return True if self.location == other.location and self.location != '': return True for key in [METADATA_TITLE, METADATA_ARTIST, METADATA_ALBUM]: if getattr(self, key) != getattr(other, key): return False return True def to_mpris1(self): """ Converts the metadata to mpris1 dict """ ret = dbus.Dictionary(signature='sv') for k in ['title', 'artist', 'album', 'arturl', 'location']: if getattr(self, k) is not None: ret[k] = dbus.String(getattr(self, k)) if self.tracknum >= 0: ret['tracknumber'] = dbus.String(self.tracknum) if self.length >= 0: ret['time'] = dbus.UInt32(self.length // 1000) ret['mtime'] = dbus.UInt32(self.length) for k, v in self._extra.items(): if k in self.MPRIS1_KEYS and k not in ret: ret[k] = v return ret def to_mpris2(self): """ Converts the metadata to mpris2 dict >>> mt = Metadata(title='Title', artist='Artist1, Artist2,Artist3', ... album='Album', arturl='file:///art/url', ... location='file:///path/to/file', length=123, ... tracknum=456, ... extra={ 'title': 'Fake Title', ... 'xesam:album': 'Fake Album', ... 'xesam:useCount': 780, ... 'xesam:userRating': 1.0, ... 'custom value': 'yoooooo', ... }) >>> dict = mt.to_mpris2() >>> print(dict['xesam:title']) Title >>> print(dict['xesam:artist']) [dbus.String('Artist1'), dbus.String('Artist2'), dbus.String('Artist3')] >>> print(dict['xesam:url']) file:///path/to/file >>> print(dict['mpris:artUrl']) file:///art/url >>> print(dict['mpris:length']) 123 >>> print(dict['xesam:trackNumber']) 456 >>> print(dict['xesam:userRating']) 1.0 >>> 'custom value' in dict False >>> mt2 = Metadata.from_dict(dict) >>> print(mt2.title) Title >>> print(mt2.artist) Artist1, Artist2, Artist3 >>> print(mt2.album) Album >>> print(mt2.location) file:///path/to/file """ ret = dbus.Dictionary(signature='sv') mpris2map = {'title': 'xesam:title', 'album': 'xesam:album', 'arturl': 'mpris:artUrl', 'location': 'xesam:url', } for k in ['title', 'album', 'arturl', 'location']: if getattr(self, k) is not None: ret[mpris2map[k]] = dbus.String(getattr(self, k)) if self.artist is not None: ret['xesam:artist'] = [dbus.String(v.strip()) for v in self.artist.split(',')] if self.length >= 0: ret['mpris:length'] = dbus.Int64(self.length) if self.tracknum >= 0: ret['xesam:trackNumber'] = dbus.Int32(self.tracknum) for k, v in self._extra.items(): if k in self.MPRIS2_KEYS and k not in ret: ret[k] = v return ret @classmethod def from_mpris2(cls, mpris2_dict): """ Create a Metadata object from mpris2 metadata dict """ string_dict = {'title': 'xesam:title', 'album': 'xesam:album', 'arturl': 'mpris:artUrl', 'location': 'xesam:url', } string_list_dict = {'artist': 'xesam:artist'} kargs = {} for k, v in string_dict.items(): if v in mpris2_dict: kargs[k] = mpris2_dict[v] for k, v in string_list_dict.items(): if v in mpris2_dict: kargs[k] = ', '.join(mpris2_dict[v]) if 'xesam:trackNumber' in mpris2_dict: kargs['tracknum'] = int(mpris2_dict['xesam:trackNumber']) if 'mpris:length' in mpris2_dict: kargs['length'] = int(mpris2_dict['mpris:length']) kargs['extra'] = mpris2_dict return cls(**kargs) @classmethod def from_dict(cls, dbusdict): """ Create a Metadata object from a D-Bus dict object. The D-Bus dict object can be MPRIS1 metadata or MPRIS2 metadata format. If the dict both compatable with MPRIS1 and MPRIS2, MPRIS1 will be used. >>> title = 'Title' >>> artist = 'Artist' >>> arturl = 'file:///art/url' >>> location = 'file:///location' >>> tracknumber = 42 >>> md1 = Metadata.from_dict({'title': title, ... 'artist': artist, ... 'arturl': arturl, ... 'location': location, ... 'tracknumber': str(tracknumber) + '/2'}) >>> md1.title == title True >>> md1.artist == artist True >>> md1.arturl == arturl True >>> md1.location == location True >>> md1.tracknum == tracknumber True >>> md2 = Metadata.from_dict({'xesam:title': title, ... 'xesam:artist': [artist], ... 'mpris:artUrl': arturl, ... 'xesam:url': location, ... 'xesam:trackNumber': tracknumber}) >>> md2.title == title True >>> md2.artist == artist True >>> md2.arturl == arturl True >>> md2.location == location True >>> md2.tracknum == tracknumber True >>> md3 = Metadata.from_dict({'title': title, ... 'artist': artist, ... 'arturl': arturl, ... 'location': location, ... 'tracknumber': str(tracknumber) + '/2', ... 'xesam:title': title + '1', ... 'xesam:artist': [artist + '1', '1'], ... 'mpris:artUrl': arturl + '1', ... 'xesam:url': location + '1', ... 'xesam:trackNumber': tracknumber + 1}) >>> md3.title == title True >>> md3.artist == artist True >>> md3.arturl == arturl True >>> md3.location == location True >>> md3.tracknum == tracknumber True >>> timedict = {'time': 10, 'mtime': 20, 'mpris:length': 3000} >>> Metadata.from_dict(timedict).length 20 >>> del timedict['mtime'] >>> Metadata.from_dict(timedict).length 3 >>> del timedict['mpris:length'] >>> Metadata.from_dict(timedict).length 10000 """ string_dict = {'title': ['title', 'xesam:title'], 'album': ['album', 'xesam:album'], 'arturl': ['arturl', 'mpris:artUrl'], 'artist': ['artist'], 'location': ['location', 'xesam:url'], } string_list_dict = {'artist': 'xesam:artist', } kargs = {} for k, v in string_dict.items(): for dict_key in v: if dict_key in dbusdict: kargs[k] = dbusdict[dict_key] break # artist for k, v in string_list_dict.items(): if k not in kargs and v in dbusdict: kargs[k] = ', '.join(dbusdict[v]) # tracknumber if 'tracknumber' in dbusdict: tracknumber = dbusdict['tracknumber'] if isinstance(tracknumber, int): # the specification requires tracknumber be a string. However, # tracknumber in audacious is int32 kargs['tracknum'] = tracknumber else: if not re.match(r'\d+(/\d+)?', tracknumber): logging.warning('Malfromed tracknumber: %s', tracknumber) else: kargs['tracknum'] = int(dbusdict['tracknumber'].split('/')[0]) if 'tracknum' not in kargs and 'xesam:trackNumber' in dbusdict: kargs['tracknum'] = int(dbusdict['xesam:trackNumber']) # length if 'mtime' in dbusdict: kargs['length'] = dbusdict['mtime'] elif 'mpris:length' in dbusdict: kargs['length'] = dbusdict['mpris:length'] // 1000 elif 'time' in dbusdict: kargs['length'] = dbusdict['time'] * 1000 kargs['extra'] = dbusdict return cls(**kargs) def __str__(self): attrs = ['title', 'artist', 'album', 'location', 'length'] attr_value = [' %s: %s' % (key, getattr(self, key)) for key in attrs] return 'metadata:\n' + '\n'.join(attr_value) if __name__ == '__main__': import doctest doctest.testmod() osdlyrics-0.5.5~rc1+dfsg1/python/pattern.py000066400000000000000000000142211345713535600207240ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from __future__ import unicode_literals from future import standard_library standard_library.install_aliases() from builtins import str import os.path import urllib.parse import urllib.request from .errors import PatternException def expand_file(pattern, metadata): """ Expands the pattern to a file name according to the infomation of a music The following are supported place holder in the pattern: - %t: Title of the track. 'title' in metadata - %p: Performer (artist) of the music. 'artist' in metadata - %a: Album of the music. 'album' in metadata - %n: Track number of the music. 'tracknumber' in metadata - %f: Filename without extension of the music. 'location' in metadata. - %%: The `%' punctuation Arguments: - `pattern`: The pattern to expand. - `metadata`: A dict representing metadata. Useful keys are listed above. If the pattern cannot be expand, raise an PatternException. Otherwise return the expended pattern. >>> from osdlyrics.metadata import Metadata >>> from_dict = Metadata.from_dict >>> metadata = from_dict({'artist': 'Foo', ... 'title': 'Bar', ... 'tracknumber': '1', ... 'album': 'Album', ... 'location': 'file:///%E6%AD%8C%E6%9B%B2/%E7%9A%84/%E5%9C%B0%E5%9D%80.mp3'}) >>> expand_file('%p - %t', metadata) 'Foo - Bar' >>> expand_file('foobar', metadata) 'foobar' >>> print(expand_file('name is %f :)', metadata)) name is åœ°å€ :) >>> expand_file('%something else', metadata) '%something else' >>> expand_file('%%a - %%t', metadata) '%a - %t' >>> expand_file('%%%', metadata) '%%' >>> expand_file('%n - %a:%p,%t', metadata) '1 - Album:Foo,Bar' >>> expand_file('%t', from_dict({})) Traceback (most recent call last): ... osdlyrics.errors.PatternException: title not in metadata """ keys = {'t': 'title', 'p': 'artist', 'a': 'album', 'n': 'tracknum', } start = 0 parts = [] while start < len(pattern): end = pattern.find('%', start) if end > -1: parts.append(pattern[start:end]) has_tag = False if end + 1 < len(pattern): tag = pattern[end + 1] if tag == '%': has_tag = True parts.append('%') elif tag == 'f': location = metadata.location if not location: raise PatternException('Location not found in metadata') uri = urllib.parse.urlparse(location) if uri.scheme == '': path = uri.path elif uri.scheme == 'file': path = urllib.request.url2pathname(uri.path) else: raise PatternException('Unsupported file scheme %s' % uri.scheme) basename = os.path.basename(path) root, ext = os.path.splitext(basename) has_tag = True parts.append(root) elif tag in keys: value = getattr(metadata, keys[tag]) if not value: raise PatternException('%s not in metadata' % keys[tag]) if not isinstance(value, str): value = str(value) has_tag = True parts.append(value) if has_tag: start = end + 2 else: start = end + 1 parts.append('%') else: parts.append(pattern[start:]) break return ''.join(parts) def expand_path(pattern, metadata): """ Expands the pattern to a directory path according to the infomation of a music The pattern can be one of the three forms: - begin with `/': the path is an absolute path and will not be expanded - begin with `~/': the path is an relative path and the `~' wiil be expanded to the absolute path of the user's home directory - `%': the path will be expanded to the directory of the music file according to its URI. ``location`` attribute is used in metadata Arguments: - `pattern`: The pattern to expand. - `metadata`: A dict representing metadata. Useful keys are listed above. If the pattern cannot be expand, raise an PatternException. Otherwise return the expended pattern. >>> from osdlyrics.metadata import Metadata >>> from_dict = Metadata.from_dict >>> expand_path('%', from_dict({'location': 'file:///tmp/a.lrc'})) '/tmp' >>> expand_path('/bar', from_dict({})) '/bar' >>> expand_path('%', from_dict({'Title': 'hello'})) Traceback (most recent call last): ... osdlyrics.errors.PatternException: Location not found in metadata """ if pattern == '%': location = metadata.location if not location: raise PatternException('Location not found in metadata') uri = urllib.parse.urlparse(location) if uri.scheme != 'file': raise PatternException('Unsupported scheme: %s' % uri.scheme) path = urllib.request.url2pathname(uri.path) return os.path.dirname(path) path = os.path.expanduser(pattern) if not os.path.isabs(path): raise PatternException('Path is not absolute: %s' % path) return path if __name__ == '__main__': import doctest doctest.testmod() osdlyrics-0.5.5~rc1+dfsg1/python/player_proxy.py000066400000000000000000000515441345713535600220150ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from __future__ import unicode_literals from builtins import object, super from future.utils import raise_from from abc import abstractmethod import logging import dbus import dbus.service from . import errors, timer from .app import App from .consts import (MPRIS2_PLAYER_INTERFACE, PLAYER_PROXY_INTERFACE, PLAYER_PROXY_OBJECT_PATH_PREFIX) from .dbusext.service import Object as DBusObject, property as dbus_property class CAPS(object): NEXT = 1 << 0 PREV = 1 << 1 PAUSE = 1 << 2 PLAY = 1 << 3 SEEK = 1 << 4 PROVIDE_METADATA = 1 << 5 class REPEAT(object): NONE = 0 TRACK = 1 ALL = 2 class STATUS(object): PLAYING = 0 PAUSED = 1 STOPPED = 2 class ConnectPlayerError(errors.BaseError): """ Exception raised when BasePlayerProxy.do_connect_player() fails """ pass class BasePlayerProxy(dbus.service.Object): """ Base class to create an application to provide player proxy support """ def __init__(self, name): """ Arguments: - `name`: The suffix of the bus name. The full bus name is `org.osdlyrics.PlayerProxy.` + name """ self._app = App('PlayerProxy.' + name) super().__init__(conn=self._app.connection, object_path=PLAYER_PROXY_OBJECT_PATH_PREFIX + name) self._name = name self._connected_players = {} @property def name(self): return self._name def run(self): self._app.run() @dbus.service.method(dbus_interface=PLAYER_PROXY_INTERFACE, in_signature='', out_signature='aa{sv}') def ListActivePlayers(self): return [player.to_dict() for player in self.do_list_active_players()] @dbus.service.method(dbus_interface=PLAYER_PROXY_INTERFACE, in_signature='', out_signature='aa{sv}') def ListSupportedPlayers(self): return [player.to_dict() for player in self.do_list_supported_players()] @dbus.service.method(dbus_interface=PLAYER_PROXY_INTERFACE, in_signature='', out_signature='aa{sv}') def ListActivatablePlayers(self): return [player.to_dict() for player in self.do_list_activatable_players()] @dbus.service.method(dbus_interface=PLAYER_PROXY_INTERFACE, in_signature='s', out_signature='o') def ConnectPlayer(self, player_name): if self._connected_players.setdefault(player_name, None): return self._connected_players[player_name].object_path try: player = self.do_connect_player(player_name) except TypeError as e: raise_from(errors.BaseError( '%s cannot instantiate Player[%s, %s]' % (type(self).__name__, self.name, player_name) ), e) if player and player.connected: player.set_disconnect_cb(self._player_lost_cb) self._connected_players[player_name] = player logging.info('Connected to %s', player.object_path) return player.object_path else: raise ConnectPlayerError('%s cannot be connected', player_name) @dbus.service.signal(dbus_interface=PLAYER_PROXY_INTERFACE, signature='s') def PlayerLost(self, player_name): pass def _player_lost_cb(self, player): if player.name in self._connected_players: del self._connected_players[player.name] self.PlayerLost(player.name) @abstractmethod def do_list_active_players(self): """ Lists supported players that are aready running Returns an list of `PlayerInfo` objects. """ raise NotImplementedError() @abstractmethod def do_list_supported_players(self): """ Lists supported players. Returns an list of `PlayerInfo` objects. """ raise NotImplementedError() @abstractmethod def do_list_activatable_players(self): """ Lists supported players installed on the system. Returns an list of `PlayerInfo` objects. """ raise NotImplementedError() def do_connect_player(self, playername): """ Creates an Player object according to playername. Returns the created `BasePlayer` object, or None if cannot connect to the player with `playername`. """ raise NotImplementedError() class PlayerInfo(object): """Information about a supported player """ def __init__(self, name, appname='', binname='', cmd='', icon=''): self._name = name self._appname = appname self._binname = binname self._cmd = cmd self._icon = icon @classmethod def from_name(cls, name): return cls(name, icon=name) @property def name(self): return self._name @property def appname(self): return self._appname @property def binname(self): return self._binname @property def cmd(self): return self._cmd @property def icon(self): return self._icon def to_dict(self): """ Converts the PlayerInfo object to an dict that fits the specification """ keys = ['name', 'appname', 'binname', 'cmd', 'icon'] ret = {} for k in keys: ret[k] = getattr(self, '_' + k) return ret class BasePlayer(DBusObject): """ Base class of a player Derived classes MUST reimplement following methods: - `get_metadata` - `get_position` - `get_caps` Derived classes SHOULD reimplement following methods if supported. - `get_status` - `get_repeat` - `set_repeat` - `get_shuffle` - `set_shuffle` - `play` - `pause` - `prev` - `next` - `set_position` - `set_volume` - `get_volume` """ def __init__(self, proxy, name): """ Arguments: - `proxy`: The BasePlayerProxy object that creates the player - `name`: The name of the player object """ self._object_path = PLAYER_PROXY_OBJECT_PATH_PREFIX + proxy.name + '/' + name super().__init__(conn=proxy.connection, object_path=self._object_path) self._name = name self._proxy = proxy self._disconnect_cb = None self._connected = True self._timer = None self._status = None self._loop_status = None self._metadata = None self._current_trackid = 0 self._caps = None self._shuffle = None def set_disconnect_cb(self, disconnect_cb): self._disconnect_cb = disconnect_cb @property def name(self): return self._name @property def proxy(self): return self._proxy def disconnect(self): if self._connected: self._connected = False self.remove_from_connection() if callable(self._disconnect_cb): self._disconnect_cb(self) def get_status(self): """ Return the playing status. The return value should be one of STATUS.PLAYING, STATUS.PAUSED, and STATUS.STOPPED Derived classes that supports playing status should reimplement this. """ raise NotImplementedError() @abstractmethod def get_metadata(self): # type: () -> Metadata """ Return metadata of current track. The return value is of the type Metadata """ raise NotImplementedError() @abstractmethod def get_position(self): """ Gets the ellapsed time in current track. Returns the time in milliseconds. """ raise NotImplementedError() @abstractmethod def get_caps(self): """ Return capablities of the players. The return value should be a set of CAPS.PLAY, CAPS.PAUSE, CAPS.NEXT, CAPS.PREV, CAPS.SEEK """ raise NotImplementedError() def get_repeat(self): """ Gets the repeat mode of the player Returns one of REPEAT.NONE, REPEAT.TRACK, or REPEAT.ALL The default implementation returns REPEAT.NONE """ return REPEAT.NONE def set_repeat(self, mode): """ Sets the repeat mode of the player Arguments: - `mode`: REPEAT.NONE, REPEAT.TRACK, or REPEAT.ALL """ raise NotImplementedError() def get_shuffle(self): """ Gets the shuffle mode of the player Returns True if the playlist is shuffle, or False otherwise. The default implementation returns False """ return False def set_shuffle(self, shuffle): """ Set whether the tracks in track list should be played randomly. Arguments: - `shuffle`: boolean, True if shuffle """ raise NotImplementedError() def play(self): """ Start/continue playing the current track """ raise NotImplementedError() def pause(self): """ Pause the current track """ raise NotImplementedError() def stop(self): """ Stop playing. """ raise NotImplementedError() def prev(self): """ Play the previous track. """ raise NotImplementedError() def next(self): """ Play the next track. """ raise NotImplementedError() def set_position(self, pos): """ Seek to the given position. Arguments: - `pos`: Seek time in millisecond """ raise NotImplementedError() def get_volume(self): """ Gets the volume of the player. Return the volume in the range of [0.0, 1.0] """ raise NotImplementedError() def set_volume(self, volume): """ Sets the volume of the player. Arguments: - `volume`: volume in the range of [0.0, 1.0] """ raise NotImplementedError() def _setup_timer_status(self, status): status_map = { STATUS.PAUSED: 'pause', STATUS.PLAYING: 'play', STATUS.STOPPED: 'stop', } if self._timer: getattr(self._timer, status_map[status])() def _setup_timer(self): if self._timer is None: self._timer = timer.Timer() self._setup_timer_status(self._get_cached_status()) def _get_cached_position(self): """ Get the current position from cached timer if possible """ if self._timer is None: self._setup_timer() if self._get_cached_status() != STATUS.STOPPED: self._timer.time = self.get_position() return self._timer.time def _get_cached_loop_status(self): if self._loop_status is None: self._loop_status = self.get_repeat() return self._loop_status def _get_cached_status(self): if self._status is None: self._status = self.get_status() return self._status def _get_cached_metadata(self): if self._metadata is None: self._metadata = self._make_metadata(self.get_metadata()) return self._metadata def _make_metadata(self, metadata): dct = metadata.to_mpris2() dct['mpris:trackid'] = self._get_current_trackid() return dct def _get_current_trackid(self): return '/%s' % self._current_trackid def _get_cached_caps(self): if self._caps is None: self._caps = self.get_caps() return self._caps def _get_cached_shuffle(self): if self._shuffle is None: self._shuffle = self.get_shuffle() return self._shuffle @property def connected(self): return self._connected @property def object_path(self): return self._object_path @dbus.service.method(dbus_interface=MPRIS2_PLAYER_INTERFACE, in_signature='', out_signature='') def Next(self): self.next() @dbus.service.method(dbus_interface=MPRIS2_PLAYER_INTERFACE, in_signature='', out_signature='') def Previous(self): self.prev() @dbus.service.method(dbus_interface=MPRIS2_PLAYER_INTERFACE, in_signature='', out_signature='') def Pause(self): self.pause() @dbus.service.method(dbus_interface=MPRIS2_PLAYER_INTERFACE, in_signature='', out_signature='') def Stop(self): self.stop() @dbus.service.method(dbus_interface=MPRIS2_PLAYER_INTERFACE, in_signature='', out_signature='') def Play(self): self.play() @dbus.service.method(dbus_interface=MPRIS2_PLAYER_INTERFACE, in_signature='x', out_signature='') def Seek(self, offset): pos = self._get_cached_position() pos += offset // 1000 if pos < 0: pos = 0 self.set_position(pos) @dbus.service.method(dbus_interface=MPRIS2_PLAYER_INTERFACE, in_signature='ox', out_signature='') def SetPosition(self, trackid, position): if trackid != self._get_current_trackid(): return self.set_position(position // 1000) @dbus.service.method(dbus_interface=MPRIS2_PLAYER_INTERFACE, in_signature='', out_signature='') def PlayPause(self): if hasattr(self, 'play_pause'): self.play_pause() else: status = self._get_cached_status() if status == STATUS.PLAYING: self.pause() else: self.play() @dbus.service.method(dbus_interface=MPRIS2_PLAYER_INTERFACE, in_signature='s', out_signature='') def OpenUri(self, uri): self.open_uri(uri) @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='s', writeable=False) def PlaybackStatus(self): status_map = { STATUS.PLAYING: 'Playing', STATUS.PAUSED: 'Paused', STATUS.STOPPED: 'Stopped', } return status_map[self._get_cached_status()] @PlaybackStatus.setter def PlaybackStatus(self, status): self._status = status @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='s') def LoopStatus(self): status_map = { REPEAT.NONE: 'None', REPEAT.ALL: 'Playlist', REPEAT.TRACK: 'Track', } return status_map[self._get_cached_loop_status()] @LoopStatus.setter def LoopStatus(self, loop_status): self._loop_status = loop_status @LoopStatus.dbus_setter def LoopStatus(self, loop_status): status_map = { 'None': REPEAT.NONE, 'Playlist': REPEAT.ALL, 'Track': REPEAT.TRACK, } if loop_status not in status_map: raise ValueError('Unknown loop status ' + loop_status) self.set_repeat(status_map[loop_status]) @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='d') def Rate(self): return 1.0 @Rate.setter def Rate(self, rate): pass @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='b') def Shuffle(self): return self._get_cached_shuffle() @Shuffle.setter def Shuffle(self, shuffle): self._shuffle = shuffle @Shuffle.dbus_setter def Shuffle(self, shuffle): self.set_shuffle(shuffle) @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='a{sv}', writeable=False) def Metadata(self): return self._get_cached_metadata() @Metadata.setter def Metadata(self, metadata): self._metadata = metadata @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='d') def Volume(self): return self.get_volume() @Volume.dbus_setter def Volume(self, volume): if volume < 0.0: volume = 0.0 if volume > 1.0: volume = 1.0 self.set_volume(volume) @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='x') def Position(self): return self._get_cached_position() @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='d') def MinimumRate(self): return 1.0 @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='d') def MaximumRate(self): return 1.0 @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='b', writeable=False) def CanGoNext(self): return CAPS.NEXT in self._get_cached_caps() @CanGoNext.setter def CanGoNext(self, value): pass @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='b', writeable=False) def CanGoPrevious(self): return CAPS.PREV in self._get_cached_caps() @CanGoPrevious.setter def CanGoPrevious(self, value): pass @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='b', writeable=False) def CanPlay(self): return CAPS.PLAY in self._get_cached_caps() @CanPlay.setter def CanPlay(self, value): pass @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='b', writeable=False) def CanPause(self): return CAPS.PAUSE in self._get_cached_caps() @CanPause.setter def CanPause(self, value): pass @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='b', writeable=False) def CanSeek(self): return CAPS.SEEK in self._get_cached_caps() @CanSeek.setter def CanSeek(self, value): pass @dbus_property(dbus_interface=MPRIS2_PLAYER_INTERFACE, type_signature='b') def CanControl(self): return True @dbus.service.signal(dbus_interface=MPRIS2_PLAYER_INTERFACE, signature='x') def Seeked(self, position): pass def track_changed(self, metadata=None): self._current_trackid += 1 if self._timer is not None: self._timer.time = self.get_position() if metadata is None: metadata = self.get_metadata() self.Metadata = self._make_metadata(metadata) def status_changed(self): """ Notify that the playing status has been changed. """ status = self.get_status() self._setup_timer_status(status) self.PlaybackStatus = status def repeat_changed(self): """ Notify the repeat mode has been changed. """ self.LoopStatus = self.get_repeat() def shuffle_changed(self): """ Notify the shuffle mode has been changed. """ self.Shuffle = self.get_shuffle() def caps_changed(self): """ Notify the capability of the player has been changed. """ orig_caps = self._caps self._caps = self.get_caps() if orig_caps is not None: caps_map = { CAPS.NEXT: 'CanGoNext', CAPS.PREV: 'CanGoPrevious', CAPS.PLAY: 'CanPlay', CAPS.PAUSE: 'CanPause', CAPS.SEEK: 'CanSeek', } for cap, method in caps_map.items(): if cap in orig_caps != cap in self._caps: setattr(self, method, cap in self._caps) def position_changed(self): """ Notify that the position has been changed """ if self._timer is not None: self._timer.time = self.get_position() self.Seeked(self._timer.time * 1000) osdlyrics-0.5.5~rc1+dfsg1/python/timer.py000066400000000000000000000046131345713535600203730ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from builtins import object from datetime import datetime class Timer(object): """ A timer to account the elapsed playing. """ def __init__(self, accuracy=0): """ Arguments: - `accuracy`:The accuracy of the time provided by players. If the difference between the time provided by players and the time of the timer is greater than accuracy, the time of the timer will be adjust to the time of the player. """ self._accuracy = accuracy self._started = False self._time = 0 self._begintime = None def play(self): """ Starts the timer """ if not self._started: self._started = True self._begintime = datetime.now() def pause(self): """ Stops the timer """ self._time = self.time self._begintime = None self._started = False def stop(self): """ Stops the timer and sets the elapsed time to 0 """ self.pause() self._time = 0 @property def time(self): now = datetime.now() if self._started: return self._time + int((now - self._begintime).total_seconds() * 1000) else: return self._time @time.setter def time(self, value): self.set_time(value) def set_time(self, value): """ Adjust the time. If the time needs to be adjusted, return True. """ time = self.time if abs(time - value) > self._accuracy: self._time = value self._begintime = datetime.now() return True return False osdlyrics-0.5.5~rc1+dfsg1/python/utils.py000066400000000000000000000335741345713535600204230ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from __future__ import unicode_literals from future import standard_library standard_library.install_aliases() from builtins import object, str import io import os import os.path import stat import sys import urllib.parse import urllib.request import pycurl __all__ = ( 'cmd_exists', 'ensure_path', 'get_config_path', 'http_download', 'path2uri', ) pycurl.global_init(pycurl.GLOBAL_DEFAULT) if sys.version_info < (3, 0): # make sure the default encoding is utf-8 if sys.getdefaultencoding() != 'utf-8': reload(sys) sys.setdefaultencoding('utf-8') class ProxySettings(object): """ """ def __init__(self, protocol, host='', port=0, username=None, password=None): """ """ self.protocol = protocol self.host = host self.port = port self.username = username self.password = password def get_config_path(filename='', expanduser=True): """ Gets the path to save config files Arguments: - `filename`: (optional string) The filename of config file. - `expanduser`: (optional bool) If the leading "~" should be expanded as user's home directory >>> get_config_path(expanduser=False) '~/.config/osdlyrics/' >>> get_config_path('osdlyrics.conf', False) '~/.config/osdlyrics/osdlyrics.conf' """ path = os.path.join('~/.config/osdlyrics/', filename) if expanduser: path = os.path.expanduser(path) return path def path2uri(path): # type: (Text) -> Text r""" Converts a path to URI with file sheme. If a path does not start with a slash (/), it is considered to be an invalid path and returned directly. >>> path2uri('/path/to/file') 'file:///path/to/file' >>> path2uri('file:///path/to/file') 'file:///path/to/file' >>> path2uri(u'/path/to/file') 'file:///path/to/file' >>> path2uri('invalid/path') 'invalid/path' >>> path2uri('/\u8def\u5f84/\u6587\u4ef6') 'file:///%E8%B7%AF%E5%BE%84/%E6%96%87%E4%BB%B6' """ path = os.path.expanduser(path) if not os.path.isabs(path): return path return 'file://' + urllib.request.pathname2url(path) def get_proxy_settings(config=None, conn=None): r""" Return proxy settings as a ProxySettings object The caller must specify either config or conn. Arguments: - `config`: A osdlyrics.config.Config object, this object is used to retrive proxy settings. If it is not set, the caller MUST set conn to a valid D-Bus connection to create a Config object - `conn`: A D-Bus connection object, this is used when `config` is not specified. """ if config is None and conn is None: raise ValueError('Either config or conn must be specified') if config is None: config = config.Config(conn) proxy_type = config.get_string('Download/proxy').lower() if proxy_type == 'no': return ProxySettings(protocol='no') if proxy_type == 'manual': protocol = config.get_string('Download/proxy-type') host = config.get_string('Download/proxy-host') port = config.get_int('Download/proxy-port') username = config.get_string('Download/proxy-username') passwd = config.get_string('Download/proxy-passwd') return ProxySettings(protocol=protocol, host=host, port=port, username=username, password=passwd) if proxy_type == 'system': return detect_system_proxy() def detect_system_proxy(): r""" Detects and return system proxy settings. Support following proxy settings: - Environment variables - GNOME 2 (TODO) - GNOME 3 - KDE """ desktop = detect_desktop_shell() if desktop == 'gnome' or desktop == 'unity': proxy = get_gsettings_proxy() if proxy is not None: return proxy elif desktop == 'kde': proxy = get_kde_proxy() if proxy is not None: return proxy return get_envar_proxy() def get_envar_proxy(): r""" Return proxy settings from environment variable `http_proxy` """ envars = ['http_proxy', 'HTTP_PROXY'] proxies = [os.environ.get(v) for v in envars] for proxy in proxies: if proxy: if '://' not in proxy: proxy = 'http://' + proxy parts = urllib.parse.urlparse(proxy) if parts.scheme not in ('http', 'socks4', 'socks5', ''): continue return ProxySettings(protocol=parts.scheme or 'http', host=parts.hostname, port=parts.port or 8080, username=parts.username, password=parts.password) return ProxySettings(protocol='no') def detect_desktop_shell(): r""" Detect the currently running destop shell. Returns: 'gnome', 'unity', 'kde', or 'unknown' """ envar = os.environ.get('DESKTOP_SESSION') if envar.startswith('gnome'): return 'gnome' if envar.startswith('kde'): return 'kde' if envar.startswith('ubuntu') or envar.startswith('unity'): return 'unity' return 'unknown' def get_gsettings_proxy(): r""" Return proxy settings from gsetting, this is used in GNOME 3 """ try: from gi.repository import Gio except ImportError: return None if not hasattr(Gio, 'Settings'): return None if 'org.gnome.system.proxy' not in Gio.Settings.list_schemas(): return None settings = Gio.Settings('org.gnome.system.proxy') if settings.get_string('mode') != 'manual': return ProxySettings(protocol='no') protocol_map = {'http': 'http', 'socks5': 'socks'} for protocol, key in protocol_map.items(): settings = Gio.Settings('org.gnome.system.proxy.' + key) host = settings.get_string('host').strip() port = settings.get_int('port') if host == '' or port <= 0: continue username = '' password = '' if key == 'http' and settings.get_boolean('use-authentication'): username = settings.get_string('authentication-user') password = settings.get_string('authentication-password') return ProxySettings(protocol=protocol, host=host, port=port, username=username, password=password) return ProxySettings(protocol='no') def get_kde_proxy(): r""" Detect KDE4 proxy settings """ try: import PyKDE4.kdecore as kdecore except ImportError: return None config = kdecore.KConfig('kioslaverc', kdecore.KConfig.NoGlobals) if not config.hasGroup('Proxy Settings'): return None group = config.group('Proxy Settings') proxy_type, _ = group.readEntry('ProxyType', 0).toInt() if proxy_type in [0, 2, 3]: # no proxy, PAC or auto detect in KDE settings. We don't support # PAC proxy, so treat them as no proxy return ProxySettings('no') elif proxy_type in [1, 4]: for key in ['httpProxy', 'socksProxy']: value = str(group.readEntry(key)) if value.strip(): # KDE 4.8 uses whitespace to seperate port and hostname value = value.replace(' ', ':') if '://' not in value: value = 'http://' + value parts = urllib.parse.urlparse(value) host = parts.hostname port = parts.port if host is not None and host.strip() != '' and \ port is not None and 0 < port < 65536: protocolmap = {'httpProxy': 'http', 'socksProxy': 'socks5'} return ProxySettings(protocolmap[key], host=host, port=port) return ProxySettings('no') def http_download(url, port=0, method='GET', params={}, headers={}, timeout=15, proxy=None): # type: (Text, int, Text, Union[bytes, Dict[Text, Any]], Dict[Text, Any], int, Any) -> Tuple[int, bytes] r""" Helper function to download files from website This function will apply proxy settings and deal redirections automatically. To apply proxy settings, pass an ProxySettings object as the `proxy` parameter. If `'User-Agent'` is not set in `headers`, it will be set to `'OSD Lyrics'`. Arguments: - `url`: The url of the content. Must be a bytes or an ascii-encodable unicode. - `port`: (optional) The port. - `method`: (optional) The HTTP method to download contents. Available values are `'POST'` or `'GET'`. The default value is `'GET'`. - `params`: (optional) The parameters of the request. It is either a dict or bytes. If it is a dict, it will be utf8-urlencoded. If `method` is `'GET'`, `params` will be append to the url as the param part. If `method` is `'POST'`, `params` will be added to request body as post data. - `headers`: (optional) A dict of HTTP headers. - `proxy`: (optional) A ProxySettings object to sepcify the proxy to use. >>> code, content = http_download('http://www.python.org/') >>> code 200 >>> b'Python' in content True """ c = pycurl.Curl() buf = io.BytesIO() c.setopt(pycurl.NOSIGNAL, 1) c.setopt(pycurl.DNS_USE_GLOBAL_CACHE, 0) c.setopt(pycurl.FOLLOWLOCATION, 1) c.setopt(pycurl.MAXREDIRS, 5) c.setopt(pycurl.WRITEFUNCTION, buf.write) if isinstance(params, dict): params = urllib.parse.urlencode(params) if method == 'GET' and params: url = url + ('/' if '/' not in url else '') + ('?' if '?' not in url else '&') + params elif method == 'POST': c.setopt(pycurl.POST, 1) if params: c.setopt(pycurl.POSTFIELDS, params) c.setopt(pycurl.POSTFIELDSIZE, len(params)) c.setopt(pycurl.URL, url) if 0 < port < 65536: c.setopt(pycurl.PORT, port) real_headers = {'User-Agent': 'OSD Lyrics'} real_headers.update(headers) curl_headers = ['%s:%s' % (k, v) for k, v in real_headers.items()] c.setopt(pycurl.HTTPHEADER, curl_headers) if proxy is not None and proxy.protocol != 'no': if proxy.username != '' and proxy.username is not None: proxyvalue = '%s://%s:%s@%s:%d' % (proxy.protocol, proxy.username, proxy.password, proxy.host, proxy.port) else: proxyvalue = '%s://%s:%d' % (proxy.protocol, proxy.host, proxy.port) c.setopt(pycurl.PROXY, proxyvalue) else: c.setopt(pycurl.PROXY, '') c.perform() return c.getinfo(pycurl.HTTP_CODE), buf.getvalue() def ensure_path(path, ignore_file_name=True): """ Create directories if necessary. This function tries to create directories for `path`. Unlike `os.makedirs`, no error will be raised if the leaf directory exists. Arguments: - `path`: The path. - `ignore_file_name`: (optional) If set to `True`, the path to be create will be the directory part of `path` which is get from `os.path.dirname(path)`. Otherwise `path` is considered to be path to a directory rather than a file. The default value is `True`. """ if ignore_file_name: path = os.path.dirname(path) if os.path.isdir(path): return os.makedirs(path) def find_file_in_dirs(filename, dirs, filter_func=None): """ Find file in specified directories. Return a list of full path of found files Arguments: - `filename`: the filename to find - `dirs`: a list of directory paths - `filter_func`: a filter function that takes the full path of a file and returns a boolean value. If `False` is returned by the function, the path will not be in the returned list. Default value is None """ ret = [] for dir in dirs: path = os.path.join(dir, filename) if os.path.isfile(path) and \ (not callable(filter_func) or filter_func(path)): ret.append(path) return ret def cmd_exists(cmd): """ Check if a command exists. >>> cmd_exists('ls') True >>> cmd_exists('cmd_not_likely_exists') False """ cmdfiles = find_file_in_dirs(cmd, os.environ['PATH'].split(':'), is_exec_file) return len(cmdfiles) > 0 def is_exec_file(filepath): """ Check if a file is executable by current user """ try: st = os.stat(filepath) except Exception: return False uid = os.getuid() gid = os.getgroups() if st.st_uid == uid: return (st.st_mode & stat.S_IXUSR) != 0 if st.st_gid in gid: return (st.st_mode & stat.S_IXGRP) != 0 return (st.st_mode & stat.S_IXOTH) != 0 if __name__ == '__main__': import doctest doctest.testmod() osdlyrics-0.5.5~rc1+dfsg1/screenshot.png000066400000000000000000004616241345713535600202540ustar00rootroot00000000000000‰PNG  IHDR^rĸÐ@zTXtRaw profile type exifxÚ­—[’ä6EÿµŠYÁ@,‡ÉïÀËŸCe¹ÜÝvøÃ3©È*¥D‘ îгÿí<ÿá““ô§6ëꪉOõêypÒÓçSùN¾9%™ŸKe§?>òÞ—Ï'µT¾.ÿt£}= ,÷ŽïÇ?÷Çç¿n=ºT-VrÊ+Ͻ ¦óÐcAï¯Rò.°7¤÷ûóäz#ú¬’Œ¦×¯ùçëÍ’¥>ç¬~Î~‹­¯ Hk7‚‘I¥4QI–³‰Ç>ƒÈs©yÊ"­åE”¹–¢€Óó]›gLÞ±¹åÏeä­\w€Ukƒ>VÑš×ÑJ«­5mÖzó6´hÕ¦ ¯¯‡«ÖLͬ›Ûè¥×Þºvë½{ž½<è´¹ºyw÷1Xt0óàéÁˆ1fžeÖÙ¦N›}ú}¢F ‹cåUžUW[ºlõåklÙPi×ݶnÛ}ûªrêiž~üŒoÔäKy?¡ö+rÿŒš|¡–_ ÊÃûFËfL!×VÚÅ Är·‹„γԥÖ|‘›¨æIžQEËDÙ.8K.b X·ävä»?‘û[ÜžÚÿnùWäž Ýÿ¹çB÷ rÅíoP[×jãEìUáó&5äwŠO#/«–Ñcá[Y[Ç®yÍŽ‘¹5Ya¤VÏÉÙOV'€Ãòå‰Yo­ =å½óòdvšô°º½ï³l RqJÏ5ÝCw4çÓÛ*ìù9»16¢Y> Äü–ƒ Ps¯ÊÉw7‰ ÙÑ\¢©çí"y´5G®=æ#1![íÛbˆ\8êNì¯C°©eI>^s"NieæÀ)GøEB4£têÈÖZuDòQ[_ŒY{QÖÒT2¿µ ²÷ɽkÓÂ)ÞWÏž±žv-Ç\æoÅ`ͺ—ê¼þ~¬Ï]E§Bâ 'RZc"ÊÎYL(²Î¹åò±ãÐy%óÌm·9ÕòÀ¥*ÛC%rZfñ ´2qvoNálvV…¯Ó¬¶'¯^ó˜èÈe{k36âd.8jÒX>Z®$"”ü¬83I +!W«¢ÕîÏÉAê wq *ËRÉÆ¦­´‘= ´¦€¬ÕuK$ñÂÝ]ÆÒpç…f=?|íÕÝ€gWí„Uà50ް¶k 2ËùRÛ§@Q¨” Äö,ïy¯4ˆÃKô’'¼À,OÂ)ÅÎÖ^bU~zÇ”‰©E±&ÏëA® ½ïŽëX}æÞk Y$˜~àÜÚ–;9 †ã[g¢œ.ƪôT kÒ"O›ø Jߌ𢫰Œ£5íú]žsîÆ*»gQ8MÏIMï"²èÖàÑTŠ;}z@ ¥Ð#l– édf|Ž:oq©Håž+ZŽÜfõK©çšÑ(´Çå[-ô ­¬•£ÍÞõ2ž¤nM€ Óó|«á¡™”¨é‘Û‰xÁ7Âwv‹1µr9óF–dÔwRÁ¨Ð'\fÿ¦yY) ‘B&¨U*®â#$ ŸÀãØÕ°Kû±ÇhQ! C_”!:Sœg[Ç-‘>ÂâÎx6v# ±Ãœä;ÍYüºõÛ·¬>z „b¡ÜŽÅ^<&Fô-ߘZ?¯qò{‘…Ù½tx]©2žó Èw}cW‚i¤âP îlqªFýÙWKáû:[Ù›vÜžrtÃ˼Óú¢@~P×d Ú?*ƒñ±_œ{û)æÕô” ãvÕ Šž¬F=ªJAÃýmPyÐUœÛC^ÅäÉ®çm¢‹vàÇ+¬¤~ë#-5wˆâªG¦c«Ž¨{ã6L·Î}Pj¨·«%EiÅ­:‚!¨ˆJC÷4'ä落DIŠå*”‡V;œ(† Oœ#+mi›ºœ’q@"ÆA­ï$:ùF0 •ç6eqϾ¾v —Å ¨‚ñ(ÖC ½öˆ}׺½ÀD¥XE Ä”wë§RK°¸2A>Ó¢9,O ~À oä´î°6êãjNä÷ Ÿ ü+bЪñd)PÐØ.ÌçŠÊ.óI?ª·gP ¸M3v]Ižmwþªhb¨ô¢¹o½Ý8TÈÐIs›‘x7áâfˆ’î ÌâU Ðâ'ÒÝtö}Ç7 ÇÚó–_…¯î~_*°–gõdwJ2]…·CÝsí{Pøw¿²à- evFi®ewòÖ›Ì]æ×§Vª¹4»Å~°g~AŒ’vÒé#ú<Ô˯ÙËtÅ+ 2¼B& ‡¼ùˆ8½Õ&;Ô¡6CC¢Q>õëº=U£e ¤STÑi(oˆHŠY™}B—à5K1±F+€Ù肞·A±¨ÛŒrM›òrà¶´(—ÖÓa?ÈøÐ3¨u=ªR–Q+uŒŒ`mÇ›åÅ;ε>8–Ò}^JSüÓ!9F“ˆnI6Þ VkǺ8ÈŽkç cÍ9Ö pHYs ‰ ‰7ÉË­ IDATxœ”¼KeÍ’¦õ˜™»¯µwDFf~çÔwêÔé*P_• $=Ab‚HˆßÁ ‰õ/ó ÄEÌ[ Z¨Ôt!͹|—ÌŒ½÷Zînf |W1î¥R™;Ör7í}sù÷þñ¿ŸÇpR''‰%Ü'm+¼\?òõËsL‚Iå“:h*”éAkPêÆíýªKãèu+€@?ùþÓ'îìüáçP&Åa•ê)JŸŽ§riˆB&DÎþÀ¬2H*ÂôIzGˆ¡e¿VŠ·³£V¹˜¢ypÌA±Êp$q1¡ä-1+™ƒÈAdcjaÏ‘‰clZØ[p?…Ú”ZŒã@Š\j1F¤ƒ4¦OÔ‘"[…ÅT‰ÞytG L„­B“Çi\öBñΔBºS4‘ZiVˆ˜ôÉF1G"Ù-É¢€rvÇuÇŠ<…9;bäIÉNyù€Ï ΉÕÊV’˜AwI®Z¹Ïƒs}e³É!¦RõD¬ .8“V7b:~´½1Ä™=ÐvÅ#p?)Zà‚ZAæAqá¡‚d€V+Á’ Ññ —j¤)ª "¸÷IZagdÁcr)F1ÃJòå "„KgfRµòzyáÞïœ>Ð4Ò×R-[¥Ÿ$Ò^(éØuçöõN¸¯wf8"…jІóöñ•ó< ÁEƒÛTB ÷ÁvÙîD:.…bN³‚©1]ˆpúHGm=‹>‚¦Â~}áöH"{«x8S Z+%:³NÝ1Œ‚3rbLÌIE%Ááôd/jätš*g81mÊ&‘.PŠ¢2‘Ai;·Ó‘0¦OZ1êeíCÀ=heBJA7ÐP2…è¾~w±d¸c.Õ9rv>ØÉ¿öþ!öù_áŸüÓÿãö+Ô ®K¸à Iîa\p´nä줡pi…Ën|}?x¸QÂQfÝ©zò8Ÿ‰iPš‘$‚”ÎeªV(éLWÒ„œŽ”BQãqv¬¢ š$=f %˜È@"p©¼îFæäv€š€'D¥l£ã&H8%…ºo”ê£0Ãi*¤Ãœ¦Ê™…bI!1”DÛ†´x·1°™H32 c&R‚«Áq:ªP/• !„*)˜ÅèÌT¶}göƒ@ ¡ˆ3ÔH3Šî“3•RyÞ¸˜P› >Œ³Ob/QŠCOgfg‹Âë^øòz Ä„M‹É˶qÁT î“áƒ>‚ë–‰©0‡qŽ” EB*Göµ/'<¸ˆPš‚%G®sж®AÍä¼Mꫲeá}:YábŠqQ¨"•&ŽIá‡÷“Z ËzÆ… VãýÄ$yÝ .Éq >¿\ùr›Œ€²mŒÔ T¡cúɉÅ\ûÜ'ûöJÄNÀpìb˜NS˜“Ì$FŸNQØ·#!û`†’ ÚÖ¸D‡ s04q* 5¥µ“V6Ô”ñÜŽ¤¶A‘Â¥5ã1…¶o„OŽÇIJ0Cykæ¸s;NÚeÃ)HŸ¨žXÝh"ô€1&IPHŸˆÉE3  nÔ"˜Œ¬¤'Rká˜T(p1e¯ß~;¨ª˜º(%’4¥LW¡X@(s D“­g‡Ò*&‚£S2'ì¥2Xõ¢ˆdâ!H<ýÛ}«ÎŒBÉAji«~»+×çqÒQ„dOg’Lo8ÊR&ƒÑ(„ÈZ[­Ðr0½3B¨™ÈüÄLãxÜ—­’­1ï_ùþãg¤>¼^¸Â9&™Bà ª;’ƒž‰Ï@˜˜&WMá¢Â÷¿üÌc&$ˆéRh»á!øLJ©eàñÜàS‰ÒPqŠ*’§ ‘t©Ô’€sPÊD3IÙ¨ÕP F$ŽÐÇDsÐÏNw(qD «´¶Qr2ht­™˜ô¾DD¦£bH$Õt‰¬B@&µÄ@‹!º1ÏB éÈš(J2DID’éŽûDd¢ª IŽ;Ÿ |ü£_ÐG°YaNA Æ<ðLD‘Kh¤;Ç|ÐS«¤yòý§KÙèþ@ÕPQª$¯Wáëmâ@­FŸ‘¨x) ï¤;)Æ‘J-BªòÝÇ ­\¹'Z I"œÐFµ‚©0ÜSÔ‰¤4¡’¸$’“k+L)ŒÈ8é‘D&¦NOåº_IS÷ƒZŒ€`̉‹1ù4CQR˜îßU+F•Âj5@QL„ŒÁV­&c&p¡4GÂ1iüùŸ~æWß}Ïï?‘$žÊÔ RðTÌ‚Z ÇdýŒÖ˜Ë 0%ÅxÙ ¥lœ!BsvŽ9¨­Ò¶¶Ö°f*y|ÃGçÆ'ó/þþêŸý3úãH%½`V˜³“&Œ¨l6©m2bïԭѶʜ'ãè±ê—•$ãù{Réé¨&¥ÁFÕ‚*I©JÎà˜É¥á  BÒÔ e½ï£ÐD™â(¢¼î åÊp˜)`†Õ‰³Öª ˜f¬N’Ȥ˜ Z¨Û24²š‰’~ÅöÆËeCâä~›˜¿üôÆÞ6ˆ;šëÀ•L"•­*>¬ÐL¦;’FTMÄ%1Ob&s(ÚvL‚jÂð ­R ¦C°êi5I4¯ÈU‹&FZEÄ0SÌ”"“ÈJdå1ß)êìVs†´ÂðÉÒdO#AòDEÉ,ˆSÁ,aœ|úp%ÚÆ™Š`ô`Î$% |$V…ËžA xÛhIª¬w†ƒÏ„p´Æz7i˜‚‡1E¸6CT)ºqÎIø ÔB¦sÜîD?xœ'½2¡g’ ‘ЍeGòÆ·Y©ðÿ«1KŠ “©IÉFŠRšÑ]ðé¨ ­B¡ žLS6ÆyðñzY"ĵ€ÊªTË%°Ý5ÚfT[bM¼s©‚¶©ôP N38Ü1uD‚ «NÉO¡TeôÁ9îd»`ÛN“É…IˆÑFðíŒãF)ʘƒûqq€‚–†E’Åpm½64„Þ²¬­3¢šbu æ™JQ0-Â¥)%d" ©‰{0§îÎeoT Ÿõ¿µd7e:`…ÌIÞNøÉ8½OÂGD´€(E×ÿL^ªR«rÌÀ$ñ2Í€"E®ÕˆË Z•p'i$ ™¡„•e.Å`L(ªXÝxœÝ¦Ê #焈,‘‰ØFP×¹7ŽHb“0<ðì¨%þ4ÍúT!­’¡„h¥Z!cj¤6Š&hBš²ÙNÛ—|ŽÉ )lÛ†J’1Ñ"l J²4Œ5'ÓØwQúLÖY>ƒ@ÐÒb´ráðõ|<…1Ì”ZÁ=9CL0ÅŸZ¬Ö‰‡qžƒ³?Öž==Ö ’Îwh˜ 9©f„€$ØtB'‘0ØñLcPË2d–Ù“8;ì q”ÈäáË0•}®ºàŠi‚[=•Y64!QªM$Á§# ›(wŸô²ãsrøIŸI“ÂŒA!ÔpwD•Ô‚„ „ODÀþâ/þõ¿ìc h‰@bâZ÷ÇWP§µÂ¹œ„œôp,Oúfö%:Ëf½3§3ºCœL’GŒ@Dy¿ß¹Ýï\jaûð™x?p z@+)-m Ø™Dj5¶çà =¢Áã †Lo׎”\&ˆA)Љ£ZqѤX%Î|ºøÅVA©µòáR@–X4+TMÒZ*bJ­Ë!“½ÇèÌq2ÇIJâ~€$×ý•¶mtŸ$NÆêÐ.9QSzÚÓHݗäëàêY±"4(•©…LA<SÁ¬Pj£÷A÷A©É«)¢Î"&E„ b‚ŠrÙvÆ„ï7^Z#YÉÄn«`íµá.Ô’«0].ç¶²Q̱"˜%ª•û„ô ªh@c0I/ •Ô gE3‰k¡êF1Á£c¤*"pÆ ‰!"ض†«1ª…¦I1ènŒ¹š£pÎÉ^­^x<f‘ÄݨõŠ…,h)ô §jÁ£ƒ$U HÇô‚Iòq°JŸN­¢†Œ‰±oF-†G’oŹnW=xŒ@D¨eg†"xòáåŠYò§w&ÀyÙ”"ŠOÅŠ¡4MŠôqòý§7 J_=*ÊÅàœ… )1ÈŒõ™VÐZ¢~"ÈڟŰL"k;V”3‘Ja¿¼pÁÊ–’>!>ÿÛç_ð׿ý™˜˜Ó¥ˆ1#Ø´!º’>a°mp±•\®×½0ÆÀc¬T$à»W¥ÏIαÜúžÊ˜“Çqçñ¸sÞïÜïwb89Ìu˜Š­CÅd‰P‘`΂ʳ›NçÏógHÙøùöL‚V ÑJw£4£íDAr­Ñ"'×û¦œž\7CŽ+}JgZCJcºPkC5‰åN°gÅëÚo3•s&B)Ê‘…ã<)Æt6M‚†Å Z5ÜWáãe9â“bÂtÁÅÙ¬Àkÿh!T¹´Õ£œS©ÞûÉ1‚™3G¬\ª`58ŽÛ1æ ÙJ¯úL&H#†ÙJHz§Æ’Ë­ %7´&AIg/…-¢…ðIMÏÆ_ÎñL¯Ä(ZpÞ§óRa3c¦"c°×JÛ“ÇÙW …R Er*Djºê «±yi ¹¼ðíhe£‡±•J±eÌ­zÔË s ÷ãˆóz½0û2ë¶ËÆuk¼Ÿºêƒ$%’M8礊ñÖ^ð9ñH"„ÎV [ `2 c—äÒ”c&DóŽ{gzð8î ?ðœN¦¥J.C-:)Y'Þ3u_éîp[böY/Bl mÒ @æJ¥j«yÁ@R¸´Š$QØ·J†±R_͉؎{P*Àäv:­€õ¬÷9D¨-(3—1°­}ˆ cL<‚—Z˜9W"ðlbªæ3õ4dŒE DpÜT R”M>Ç:Œ(k¿ä2ºT@'Lm¨pÜ1-φzUIÑe‹5Z™˜–§›4Sêu'F‡0ZY QSˆ42;šÎDQcf#͈0ª Jq"O¶gŠ~vgDÐ}r÷οù3Þù=.°ßïÔ"ë{{ð±8oe£û g‡,t­6æp"„ó݇7J¡nu CØßÞÀ'cNF?9ŽoĘüð›?ef Š¢–ôL"…/Uÿÿ÷ñ³ôpLB…zmäô‘HÂ9ÔŠáu¯L…c öÖhôÞéYˆ~.£ŠýããßùË2ŠÐeôsN¼T^jA‹jœ$ÂÈdx®7:gkÌÎì[}:mÛ39x°i§šÐçXŽ>Jdôb…s®Øtup& 2¨b˜VÜV´52A+îÉëEhmã~8ž¹b:-)NÈJ6ƒ´™ÉkSöë+ƒÄ13ê(‚bE1YE O0s.U‰\[X["¹¢N3a# [‡¼Dç¿ù¯ÅñŸ}äÛ»ó×ÿ—qyûÀ1D€6j[NYµ…6õ(4u>\À­­˜n®ÆDbàÒ*`‰cT+Œ˜H uÈ€DQ^¯…"Kx.ŽAM8½Z±b´¢D$ 4t£Z¥Ê &ø\¹©ú\1£-7FbàÓI-ô ›äJ!0,¢©þ] P«RB Ì–‹>1@D¡^‘y>YErýZ"†¤Òç V£Ô†É1šõW¿æpÅ/;ï÷wþäÞØ^Á_¿Q´p¦ÒÇIµ&A)É9“'MÓJ†±ÕBL¤c:]ˆ\CI¸ÝoTƒe ,üÅÃ15LR ÷óŽHáº7ZŽÞAŒ¢ :ƒöá•c®1ιÞﯮ7þì»ï4ÞÏΘ“M+ï3L$WÓ$"ë8‰…j”ÚPQS˜¹"Éè÷ k[ûйË4fN6_åîÁyN$J⑜‘´­ð‡/7~ÿÛß!O\bÌ•LÊ%£`0‡“Vq·åêãˆ}*ý\®ôˆ`¯•íåÊéEÔ(²œŠÉ;Í /—òŒ¡ ³ä¾ûŽÇ„/ß¾r­³;‚ˆb¥¢æD„>ƒR Yå<—ÛîZÑhÌîP &ŽIrF·‹óv¹ò8NDe­}2¢‚HPY $¦92s™ MÉJXPŠ©Ë½Ïå¤×ªa–TÀ„…y"˜b$1&]w4ç: µ0ÒO”BmB5±\MÅ©eÕ4yºÖK,®÷©¥a²¢r§¢-ñá̬>^”V“ûpj­Ô"Œ‘ A?'vC4™nˆÛ^1IrND žÑ¹špÙB Z±ÖÈi¨±ž›’ÂÄR÷ħʾ“áÀ$r%µ<Åœ­½‘ iÃå‰wˆ¢õ‚žKtzÛÐfô¹¢ø˜Ò“áfR­Qu™,$ô'j(OÜïLŸB¢+Íœ*ñx篯W~xyÑ©½SŠñáõŠTÃ=– “íõÓjpF(šT8öKá¥F&¶Uăp§üêúÆ8'‡*B‰ HÞ¶ …ÛÓý¾.³mÛ¨!œc%?fÆpgÎe” B>Í¥‘I£è`<ÝašN†¾&¨wÔ· 5!BÙ¶¶Rò~Gg.Á—7ÁK-ä<ê2†L…—k£–Æýþ@ X†“=˜™¸VF—&<¦òr¹é%''Ê^¡iá§ožß˜1;÷ûµÆÇË+·óA»4åðU_ÃŒ;œ.Ìx ²'ÇèüþW¿æë/EŽAë *.M“6ÍJ–ô¡<ÿd.DQ€½ØZŸáx´²S•‚<íYÜÒ‘~g¸S÷ Í I_õ'œÏ¯/|wÝ8| .üâåJÄÁH!(LÖ3+ # Ru:n“І­4º.dsÛvfÜcY°ª4S ¸n…©¨ E‚*ëyK$Õ²èO\ Û6öí‰e Å'†<õ€¡9ð¸@8bTSv‡™pÌNf¢¶PYw'çÀ¤ñÚà6rPóoÓØ·+çŒD Ô;?þ#ΟÁŒùòÊåçŸ\ÑÅ «ñÇ¿[ø[ B-E¡TŠ!JO¡:X‹Õ¤£Ì¿Mé|àˆ‚Ïà'ï¿þ ÇÇ_ ýÁuX- ‘6£ÖÆ›­={z¬$¡TÔ÷À#é,š"u™é‘3 ¡É@ͱ…5[Y5NÁ Øðþ'©rò8OÎdÀ9œÍ`¯;÷3 ƒÀ³gœzpôÁyx?™s!*½ç±ºåÙ!œó<9ŽjÑÅ´•ËË>½;>'­ [«ÄLècŸ¦«Kó\L'‘LV7%Ÿüù¿ýoò»¯yû²Js½BNæèˆ#œmS†´Íï˜ð<üx*Ò$§ 9˜ç××¹=UŠ.¾Q|à³áüWÿåG¾ÿ^ùÿ—ÿë?}pœ´R´Q à ÉR™9¨*”Z˜}¥-V*Ÿ_‘¶^âÌDæêÀçóu¹³¢mÛ‘’hµ¡‡qtŠÛ¶œŠ{5°ÆÖÊÂx"騬â>}Pöǹ°ŠRwë>hT°¹b+—ëþø»Olö U@âεU.mçë}R´º\¹9&!—•tœ±½ZxŠÑFVʲ—B)Á9—#Z.Wƒ3 XÅêÆ·¬ôq÷™°·Â_oüøÓ;öŒˆÅö1ß‘RÙ·ÆÖ Í@uchAEB$˜œÉµV¦*š`k:R·'fÖèþ”)*ô¾ÖÐÇ— ªÂVAgG4Ѳ‘ºÐ§ã}pï|¾ÀëË•/}°çÁ?øþ…÷?úù_N~øÝoÊß")ËÅ‹«Æ9o ±\^µ$Ý願ÀËÞP+t>¾ì\7Ás²oÙï̘ˆãø„Z hbe!*"cñ¿EÉÙñCyÙWt,U;=W„© ^Œ*𶨬¢ ‡CøI±ÊÆåR8b¡sˆáŒµ÷Ó™óAÆÉ-ƒñúDw®_¾°}xC÷úÑùôÒ¸îÊá†Ú¶œÅryÆÐ ãúù§x|a«;Í&¥VpaN04]ÈâÈ„‹m¸­úÒš­ß;j“©;­5‚FƤe2ËÎé†{'tcÄší©7ßùðbÏÂ"h©L_ë}³ŠÉj -N0°Ò(^/ —Åïj įî,¡Q¦ºDs©ÊÑ“vÝifŒÈõt ÀK5Þ®;·±’Ï×Ê9V·—‰•-GÐÎ ¨*Z¡ÏÁ9«1ÅPsª)ïçäËýAÓ騢 aa ËYxáZä9›&ëÁËÅ(j¼¼þ’¯¯”<ÈRsR$øÍ/Íðä롘r.U̸^ ÷%¨kYI“GrÇ6?¹Yó2 ƒ‰Ô*ËDqÅj²—%d«-,cÎeš•mgÌÁèc5µ0Ô0™”Kã~*Çq‡'ýDr°UåÑû3QŠqr“$ÙdùïÚµŒÁ˜“˜…¶­çæTfL¾ßî·…é¸,Ý‚ÖHéHÌ5_Èd¯Ñ˜“ž…î -ëîàƒ1g?øöÇB”Jûò3õ´Ò°bëw§4ÃÒ–ºÒ=b%éˆ5"knåR4Éð¾ÿ5æ/³³µºPè(“”•žG ËbƒjÎôÀ|‰â²ç bº)^½O´ÀÅVó›(µæ1H_}jáq:s ÌÙi/D½wJYs„ˆPJrAT§¬†5FÇÊÎÌ…EaÂÙ'32í­âÁŸ)ùD4¸ì>'/—ÅîŸ#yÙ6v1#ÀŒÓ!е‚çÂ|M6èƘ`J–5»Õ‡=é1p*!á¾0§,øHúŒ}§¿¬sàõÇß’cÒZååb<†óÓãÁÀ±í²ô®B¥ 3Q ´)¯— ÞÛ3éáˆ(î“*àw<&1îßÿš4£}ý™½¯YØ ¨m#£°•B0Ö)LeçVw$’9&V ¥<ÝYgr¸ k˜þÉ‘¦À#ꢼ¶&ÂiFÄDYCÙU A˜"¼ŸÉÉcR+v}¡Ÿ'Ó'AÅj’¾óÛ›Óúm !aüóŸ&¿ûïÿg~ÿÃRL¡Ÿ;ªƒRÏ|º¾Á&•nd8–𘫹Ü*<“Òvç€Ù9ÚÇ+…÷û±!šUJ…eãᓱº0Ä ˜p !³°¿môxº/8Z*»% _(› 9·±>C„pæ"Wõ²qôƒèCe IDAT·· k:ó‰ý€ÊNmÁ­wªO<ï+åÉí³ôL…û9¸ÿá|÷á‡+/Åxݕʀ÷wöm±¶‘Á]+E“¹jL8] ¥­þÿqíÒ8ý±.\ð¤êi>O¢¦ŠTbƒ ØÓm™žÌ8©!“ª0g"1Ù,9…™B>^·ãäH]ƒÒÙG‡+NÝ ñ~$›Mß_¸4¡?:÷±ŒË*•1c5Rðt6])„ì°úzŽ˜äùÀ]y XwR¶FÙ+Çí\ñ>•Ï[çÖO¾M£éäá+K8X$X`ÚxxP ªmÌ\æit –•e]þ¹&¶r.N’­)c:#m”ä\˜1ZÈåD’⌹f:ÁSø?~ÿ74ï´m‡ÎÕs!1“î ²æB åЗ2‰lZãÃvá÷Ç·ÕÌ—z!¤0ó†e¥è¤J¡œIÔÂ=ÖZM™¸’kÀ;‚'ôTlœÌtŠ M@J0},½R.X 0a+•)݃>Ì–ÊÙ—ŽiAÄéiT•×ן~ÄKeÖËÏ?P=°¶‘8Ǚ쭑¹æÒ.e]²ÐowF]sS6ŸÎÃ…Ðd¨â=0&E´Ñý$̰yrëóÙL­u%*ëÌËMzz}åmÊ<™ºó˜'݃Táëáç¢@êdsç㉠MÞ^Œó\V„Ö•Úk%K’>yÙ/Ñ}u·õZù°)?ÿp_"Ayv¶Êíèàƒ‹2]Ìö¿ô—.Æ®lJ£v'¢RjþÝ­([:Â3Z§”ùäÄ„‘…—²˜âÛ££:©6ÉöÊÏ?üDÓÅÙE®TÁYøý<01ªVÔ@U˜ò‹“ëV9c IÙ~eFaÌû* ™ÑQ±ÅŽj!ó PÈìˆ,ôBd.$&ƃ1ây³’Rˆ<1Y¸…V°k]ª·oߺ>?c ¹Ôm‡±†Ú4'¨Ä¨¬álk¬ÛÆJbÊx"Fuã’I•\̰ús%Ø¢`U¨3ˆflZ9ï^öÆ×[R’ŒììUéC˜L<“è[ò©ñö›¿Gü|rþá¤Tq[¹ÖÂ1b¡4ÉZG R7æìëæšbÜîwÊÛ†;¸—bôî uTŸ7ßÄX‚ŠŸ“àÁÁ½¬.œŽÆɺùÄlÛѹ÷$Ç ÏÉÛçOÜŸ³æ©¤5^-éŒ`Ó/Ÿv¾ÝÖ-B^Öí!ÛbŸoQÙt°á¼ì/¼wgä üt½k¬mYvßõ›ÏµöÞçquë]ÕíîjwÛÝnbÇ'¼ ‘cÇ&ä‘ ñd02HD CbV+QHY|B‚"'ívÛÛݶ»«U]·îãܳkÍ×|»ÊÝv²¥{¿\ÝsÏ=k®9çãÿÿý¥A·Ð ºHtuBdé¤õ™gkg}tK¯æÒ™¤ zDýö¬—fŠNDÎ@m¼-Àz,¸>ˆ1²táôðê3Áfß­ ˆÛŒï £kð|­ô^éÀ£.L“]JFPͦ¤ò•1"ÃqžnL 5JCò0ì ?íÓ›*„é£á½¢É aRÆ:ã'ü0Í-¨©'a“Ú+Ám9É€¬Ô¤"ɳVA] N kçþ¥CÝŽwn3%Gw‚ƒ}-DpÑSj¥¥«NÒyÓ@ë°ËGˆ‘eXço„úšù¬9¼ïøÄ€ÇLƒÛÙøÁA…«í)j»ÐØ¥ k®6&}èMH!p¬…£FrðÌÑá\BÚ˜Þ›´ìîU¤¥‰ÉŒ¬ÙáQm”áXÄžcïÊã2ðÑœr\½GÐ[†4ÞÞƒ"vŽ‘”Q*!'’¯¬EHÛHÊœ”Q”˜LÑuHŽMô,­F§3·ªv"B[âŠ3OPh\fÇípxcÐz›icðÔyÒf‹gûJÎôZ^ΣR¢j$»Òh£á½Ã'˜ý†z:Ð[1bx´T\®ø)¡êq®QDp¢h«,máÉåŽáöÒIé~CÞìÎ4-Å©;ÓÈl= g 3§vN®eØ”SÄÖ±óˆ•Ä”2ˆ¹s}}I&ý·ÍÜ,…µÛ°…ìmª½´ó3C˜r‚gb‹à>ظ­BFD̰z^«^„e¦0Œ²e]•ÍäXeÂ÷ÁÁUè¬#SúžÖØâ†C¼c›…R£9bV|€Ò•‹4ÀiqL[¡I'Èlͳ³Oª­ƒM.¨EsJ6q_*‡¡Lq&‡Æ~íöŒœ¼’/8oò7íêQa7EDk81—É3Z:µthâðü Œ4±{û-\_Idt{É¥o 8­æcR5oÑP5ªVpŽ:pêQÂûÏÒ¤U‚ŠZ‘ä)‡˜)Ø;´WèB÷vˆ‡˜fš€¨MðÕ{œ(¸Ak ïyeDÁJDçŒ`çÔ€ex`µ …Gѳ”exÇœú¶ö@ §ƒäAj!“¼µq.¥1ü€^—÷%wÚà°èl"s–ÜLI+Œ¸œfF­äç(IL{?ÅÊk¯~„­n¾ú „Ì. ”»1CfßÝ%\8±‡ 9ž]2¥ÃÓqq0刎ÂÒ Íet,FÔ“„™ÞÁ7›’“çqj3ù•¶›#C’=WÚ± yCÝî§b¹»ž€˜GnhÀ!$?'Ï”LŠDðKwLœ(½§ Ú:CA¡=0dŽ™2k_ÈØûéœ=—®ç5ã<›˜Ù¹Îñ¦óìùäG¾‹×7wù¹ÏþMÖ¾¢:P5àÄàÇ@uмGF¡:!«„¡yE˜CBÏfîà§á"„L)…(uôBEÖ•ÓÚ¨­‘}':5ç½÷„ÏÏo¯xëé#¾ïn¬c!¦_VÈè|í  ÇOþÔþ‹?ûŒÝ`ÞméšÙÌ3ÓnG똬(­"µÕÆòó¥â›rŽ¢{\W†xÖÞÝÝÒLÙ!Lg\ÁÇ ½w–bØ(‡ÇŸÿÞvžyKYÃ0©÷ð°”[âPRˆöâ ÓõŠ@ÔFŽ¡qyUÇþp6f‹¹ñQʆsU!¸Att£U±Q:κð66ÛQÕ\ÿÞ9C[æD2(•Ù€€Ã¡¢ä`fÆ, ­gB/t?PÍx ÄlÔˆ&—aJtŸã µ$N]ÉiâîåDï…µRŒÜ™7û…µ®ÌH)´e5ã]4ÀMól& Óœ9žV~ãaá7þ÷ÿ“˜Œ,R›M:•2l¤:T(嬸ÕJëÖQ\V+ª¦ìxz(Ìa|¢;Ï Ç!ë7Q4d‚ëìoo˜¶ÃD=U4t´w”Jö°É§žÃŸ.ÑËÀ#¸9l¹¼Œ<ºm¨‹61õÑ4¯¬“8|šŒŽvÖ´çUí°=uo¤ßÉÞ›Þ¼/$¨õ¥ èºCÄpThàXQ:õH$QŠR]`Ö#= ]v´Ö˜'8ˆ£Kdxmt§ÔScãÎŒ ’§±0ú` ÞŒÚàIN89Çð;AKçŒ8=¸ÍDë•Þ£™‰EÜœÉâ t6AÙæÀñXqÓÄ"ZŸÑžW.‚c3%gùÉVmÔÞÛDvÕt¸­ÑFGûB?©}eùíæ)§!èês§4£#y¯„P1ݹ÷Þ΄ êÁEVÖôQZ/LyËZñx8›^{iô>ègÛ¬u4‚43Á»™"ž—'á[¯&~ñÉÊI=8Èè•z¦öe©´n=ú\OÌóÄÍ¶Ó +¸Ù“2\»D_y+¦s+4 Ð »9’c¢÷Aò‰æ`“í[æ’Œsµ.„næí^+Y@:N"â3ajD1Q9âq Ž]j\m£ û¥r[»yÝÔÖi¥â ÄÑð]7ãPr6A¯)ØTh—•e1GöV,×NSátÿy¦¯½Ibd¹µpžì8ï¹›ûÞÙäp&s™Ç¦wƒƒ: F|Ó÷ýBí…à*¤Ø¤ß{´FÖêHÞÍÁ„œ­Adj0C$»@1¿£T†ù»ØÚñފΡVl„êÍ/˜\çrÎÜTpm$r<ûÃbˆ¸6P¯T1fOïïÏoíøáYz§éÀõÆmÌ)±Í Ä9 |`ÞΛ2qžž/Jô ae ÏfwÉ›O¿Ær8âμšŸuˆ!sÛJÓh$剨+mé”>p1[ãZ+}5p >à[géJô —½WvóÄ”·KÁ§ˆ4arŽÛ}åòr—•Nbi't ¦<Ѻb †a(P± ¥ÒfœÚýÌ#Ôþ›÷›íÜX–[Öfò¡8MdÕ jîß¿Ãöt`<0²çfuD9Zõ1¤°ÓA+†zVç˜æKü͆3_€ÏÉÃz}Á>FêôæØ¤Ì²ì çæTQ¬ÙR aêÔÀ'¸H©Â.ÛsÆ™*B±)VŠæ3Ks$Þù‡Û#K)ŒVh+Ä£š¦Ï93¶L!C?¢µpŒÑ4†ºAh<]+µÚ˜Jµ†í-°úˆ‹3â`í‘äu(ºVbLøˆ&q¤¾ú¥/'cÓ®#àÄÑ¥€7àá`h§œùdEGcä=ù£tÐFÃéJTggd¨ ä ͯxõFûÉ‘µq¦™ùTÕ6FçM!C‘*Ô¾7 oôô~»DfTÚÒ™7ÇZ˜ÃjZ`ŸŒ_¯ŠÞx§ÁºPI kQö‡MÏ–ŽWÓÐøÞ!¨9ïCgéuްõl.ï2·œj@I„VÑ0dPéDïñTsãws—±`»ê`ÑA ™í4aW±3iÂ7Ó$‡ÄŤÖŠêŒWðzbU+4w³pZu]Éé’J·Q÷€ÑÍD•6Êé`èGu,Tü Cœzð)Úî”ÙAí¶!lg¡5Ï£§OI)°Ýn¨-béŽM0^üiD>ÿ•/ÙeTŒWÝœ×Ej±BT_O´`Lä&&§lsÁ9G;Uë¬t“ *†a,6Ƀϵš.|@¬†9¾MOÇI=pµ»Ã  Í:)!М2FÆËÊèjÄ^Êûò!+Ÿ"7-à†à7R¦5ÁûÀÌ LÂávp± ç8Ž…¦ÅxÕ†š6^ý žsIh>êçŒ 6J‚”È“âÙàã0Òpˆ®½Â  ýD-Æ ;p›€ 3@N 'N«cJJʉá"kí\îfNMÌ$¦ƒ)%Jê0s^$âCaÃ`ß'”Æ&*kóFYËÞ4­¢ ìëÜÙl`(§²Ò}6Mêù0p ÐJ[OôZˆ ư¢c ¡F¦8ÜDO‡çbР¬EØxO£›g@• ´Ú m¡†@/Šv¡‹í']a­F3! ,䉡:Lótž{ªÞ ïä=ÃyP}GêœgxÏ3›Ðí}÷‰ÖøaÅ›ˆgô…<Íì¶[Žå™¡›s 7bHNi+¾z©,¥ eeôõýµ·¬G¢Â&EÊþ =šAO|â"DbŒ Ô (øœlò(ŠøÌPwF´¢ÖŽó‹å"h± dëÜžý,ÛKÌL³çNôœJ6r‹&çY‡òÅ“R]æz7sZ¥à{ŵNÑJp ¥*vÕ³c$¦‰ê#ÐDçx~Z¹Þ46n “ð¨g•-ïÙŸµ46ѱHGƒ"ÝÑÚY+¯œ/ÃÍôÑÞ1û/ŽérK–µã}¤ôA©W¾1å-‡ãŠ|Ë·ÿCèiáéçâh4¢v¢N´˜/¦ pJŒž^=iT®/¶ÔSaRmt¤†"é•ýoʱéMÌʺ:4Ã6Ûek<Í9;Tm2“³Inœù ¡ãmz­é“‚ïô¦,b²©L*"Þ,fæ­.$çp1&XÑEDV¢3èEë í…"_*Åò&г´*œ5[ʤK€5F|kD:]²6Bù­¶Š­ËT‰q¦J£ôŠ›¬­s†"#½Ä“vYW¦9 £M®">1³jéŒU˜½5”{çì r ŸXÛÊpÉ:= šPßðË‘)nWGÅI5ýJkçÚ°¿E;C`Bð-1¼GFeY „ð7i”̓IÃyVWY[#† d°tëØbSx—ªƒà à ‹¦”In˜ùZ<»äTÇZ:xn³¥ÞÞòì¸gdO-Å2@ÔÁr6xK¡‰Ý‡èûëêÉñNqx1´$᳟ú¿ùåÞð½ÐjguBhBoƒµ†# 43z1Ó9X+m2ŠbéƒÝ¸“"×"L.âQâ×îy¶¯ôUÁMäÍàz·áá³BÝwܨLS†4q¬ ².DQüœ‘ˆ,¤8#3ì·g Cc"øˆO‘ó<ÓFBÅ$m]ÏÒ“=ôprè §‹,kg³¢àt Nì¥wž˜åý±Ô®í‚¤g“›ÚyqÄs8ƒ: êµ#>’qÜ»˜¸óòs—ycô˜jR ¥žz ÷ʱT»èxOÁãb¤¦>m˜¸0£ê ÓlfM"èÂÝíÌ2„ÛÛgŒVimáT«aqç ­iÒLó)šÏhœIybŒAšf¦0ˆÁ¼28%%3E¦€á+»¢VQ²ƒ£ ‰ |§ÕAlçïÞc/ƒvši.ðîqÁ%C0W¬®¢2h£p:)µ°®¤,‘÷×^oň&£qól!¥™œ¢u’óŽíÅ–œÞ“tx4ŽUqœ p8gHÝÞ ªtÆj9(Œ(†ƒýD-3»ÝÈîrÇí©p:àu}â´.¬ËB¯ŠŒÁ(·–Л]p†ZÇ>DæiBZB63S‹0:WW·?è\og–ãÊמÞR‹rÐKúÙ§–7‰¼Û¢ÜÔj9B`ôŠŽŠâÈ!!ÚpÞ³6!TͦOγsGœf–²"íHð«o~‰¾ž ¡»è"¢Ötté3-œó4uduh ,M©O8él7‘ÃéŒuðì´0ξ…áÓ7<ˆ«ptp‘åhµ³DÏÕv2ß[<{]t©Œ1 $Ô-ßð,aH§𮟋UgoÅR …jYE"$é&þŠ‘.J8gÑ /¨‡¥4DVº }±s~ôÂÐã<‚†NÁt¼FvÞ²”úY2"giíq†uÔfŸèá0§+­Ú”V´Ó{CÔB¶™Éyš2ŵóåÖ9b€¦Ê‘á2£ .wª}íøÉãO†ØìÎ2p‘èM~Šx´VÂpÈdºxúZgòª>*ë*”Ѩ­R{·€J¼ùFþqÏÿ çŸüÝ‘×_‹ì÷ð…_>õ‹…Ÿù¯”ÃÑÃz¤a›‡0'¿!ÅÌ& j-æô˜´@:w·‘ƒþŸ¿ýi‚.vÉXójã­'‚8‚Ÿ¬Ó'¨!'K%fc)¯¥½OP´Þð ŸøxäGÿùÄ?þ»¯¾šØï•Ïÿºðé_üÅ¿2¸¹Äè.Rw/H>ÈÛ_û:V äÐ{gm•²®<¸×ø‘òü¾ÚóÚ«ŽË ÏÃw¿ò«¿ö×áþ_;OG¦4ãÆ҅9cíFða=­Ô²²6a´ÕFXÞ“#üþVøÈñ­oxî\ÃÛ_þÞ§;ÿõWùì¯d*‰i \ž(íȽËÄè3ãpâ†Foƒ€£”ʨBí‚¶Î;ê+¯2v—Œ”ñ£N Ó“GÌO«:Ol¼£¥'GŽwn+: Å ûºmtF;ÐJ¡æ Ç/Òçf4e\«øZI‡gÌ·ÏØ®G|H8¶xgÎêSœ,ªH”¯ž§{aN v)Ð`ÈÖèÝD+ -lS¢5¿„é ¯ªŒ¡xo7+y•Ö·u%æ‰Ä^)cÀ|Éuô§c¸ÁÉ@jcµ—† ÕþÈ̳ ÁÓÆ†aë=KS$tÂñ°ÀXЇû÷(wî yFBÄ·B<-LO¡ÇýñCz¸|…ó³Ý¦¸A¥ñø§S¥•ÅÒ$QJ÷·1|¶‘´ŽF]WJkt­ô^,@.FRò´åööÄË-›á9œ:½6ZsT=s˜Ï>ÈïÝÎÏò¡.0Ūì&c§/‹Ñ¼œyú.ûʲ®ÔR‘¶çTVZoH]"Ähg›qš7wïѦ‰1Í8òáÈõÓ.u"¸ú“á?kå0êùòV ÜâjáÙ”8=x޾Ý2’qìs-¤›g\>{ÂîêšD$ÇÁ´5yß*­Q;AqšÌ/êÍŒ6…Èa$36¢9ãLrï,]9I'9!úÈÓã@ôDÀã4RÇÊ ]g K[)ºÒ—g,íÄñò’õùç)¡ç.cXVv‡[.önD.þ IDAT¹¾wÉe¸âíý-» ìòÌiìÙŠRŽð¬V´a=ÒוVW ¸R»/9s¼¼¢^li²îhYÉëÊnÿŒûþ j áØäHF•Hœ“ulSiôbSI»s2úJÁÜ—NyK ÂãÓzžx$æ”8 ±üŽ`!QM;rl<:Vêñ@]ެKa´…^N4çÞ_{½™Î}mu‘N´<±3Õ{bϤm‚ê õ÷&ž'ÕÒQß3§Ê°B aìv,÷Ð.¯9ãF'¬…|:rõô4IÌÙQÕr#NÞ3º³ðF,K§u¡Õ­,ô²rÚÌìï? owŒœñ£‘–•ÍÍ®nolßðÂýÀ–[.žOüS˜w>û ~îopo“xä&8ÑÞYêÞG¦œ‘æ8¬žÉ>²ÇLÐfYnfwX{GüLžá¸Wœ,¸iâ8Ý>%ÄDí±¯¼sóË4<û»Ïqzé>#OhŒÄVɧ=ùñ»Èþ@ –+ã·‰)$|SËïq)µÚJ«ŒnÓÁÖúûϲaç‚ó”å,MŒGF}äv± ÞåUÆ›øÉY8¦WÏÙ”h ƒe%„ˆ^Ü¡¾ø2ýú.2O¸Þ ˉtÜsùè]j\Œ™è¼Is6Ìô9§¥ÅŒkÅžM9Ñj§Â³iËáùP·Hžð½N'æ§ye]èÒ¨«É[œóøXQMôbAqmt\wŒv Ê`4kvZøªù¥\w@¥t¥N™ÓË¢o¶ŒÍ'B~vý·¿Lƒ\;£#†ç'ð[êX™ó ;e_²LaCi•9&Fì+\æ C…¡ G'GÏ" ¾³# Î$L÷®wd7óîñÀºv–õÈ© NÓÄþ¥×ió–‘g4f\¯„ZÉû[¶‡ÇìÖB¯‹éÅLL›<± S"ê@dP›‘#utSºˆ˜tËÛQ+¬R`r)mmJß:ÃÙr펼‰„Ñ ›Ìt¡‹ÐêbS‚ËK–^¦_\"ÙÖ‡?îIÞ!|íM+:ß3;ƒäôº’6™Í ‡UÈTÄ@oôf_{m+u –‹KÖÏ3.®)ãzߎÌßåòé#D9R;ÕïŒdWޤ`^ÃRm²o¢ ¥Wj4uPŽàÃoéCQ™ˆ¼vºÎ8 äìñ]XÎòç‡pl…3…# .à¨(iÞfjI¹%„-:\Ypž>{ÆgþÎàÞ]Ï?  ò?˜ø—~ä=Ò‘™-w;xñEø=ß èÂþ …7ßÌÓ×*%L– AÆB)v ôa´Ÿ=7ÝFs£)/\^ðü wùÚãë²€zz0ÞýÎ÷ótBˆ2ði¢Ô#½ ÞùóÿÝÒ‰7SçOþñ‰?ò‡Ïé› Æl·ð ð{~üÐ(ßÿ#Ê“g)Çwz¼å^ªl®6d/ùìW¾ÊZ¾ÿŸiü§ÿ‘ãúêÌp<^{Í~}ßï…¿û©Á¿ño¾þȌ՗aËeNHNŒª¬½Ú¥Z”Ó²ð©¿uäÞ]øØw6[Ï_ú™Èw~Ò½ÿóøàí×üðmà>ãײÁq±Í<=VŠzRNÈ0µj[é}e•Á³7>B»{ŸoüHØ yC»såtàê Ÿ#Ž•62´¶wXÝ„W3z«DN$gÜáæÃßJ½ºËoýè´A¦ ýòšå%Ø/ Wo}•ØW¢d¦áPoÆ®Ö?åÍšR›ÅÄÇœhÅ4\´$XÛ9óâýKÞ~Z‘º09KÎ\t´á((*ÑBEràÍ7¾€»oþ:×§#ÏÝÛ"!±®…”næ}Í_Ö#u]x÷ÃoÐ..ÙPÒL¹ºÃq9òàÍ/Öïì3/m#ï>é|î3‡ònb_=k+l‚BØrÓ<];Ip½“°ï'¤hØLß;eiTÔd–£±¨~ßÕÓ\q,Ë ô†´•ýÕöøðoÛ«ÚiÓ–ãÝØïo¹ûëŸãALøž¹/˜gµÐ% c°”7ßö ÚÅ7¿‡ï}ê?òOPÿÏò•Ï~ŠÉ)Ù uX¤Šâ]"N£.8 رg)Jê‡?F{åßü,§„LÚû¬Ï½ÄsŸÿeJ/„æˆÁ0äϲHg“°Ö­6FíœFãá«o°^ßû¦ïqä #o¨wîs:í¹ÿkŸc‹A(FÏF8‡v“` ' Þzõ ÖËëoúz÷>÷)æ'ïÐÔsû-£¼ôúoß¼Èm+Lß!“ˆá^#N<Þ’¼$z ¸ÞÉN¨¬F ž!©§ºˆ z~'½))lˆ¡£jRWB¦ŒÂ©T–µqXWNCyçC¥\óý@ƒóíòš#¯ñt9rç+_äzÝÛ:õõÂ4‹zª %TËyô;¾Ié°*àâË_$?}—¥–ä‚â‰uH¯è0ô©u+2ÊñDÓÎáƒf}éÕßò=GÆ43î=À¿ò:ñÓÿïûvxnå‰SbrE,Å ŽÃÉ€&EÏ>ô1úýûŸ¯¿áx÷9Öãž‹ÿïÓ<7:N”*GTfÒ9«†níñ°™c†VéUw/’¯¹9¬`ŒßêÁ·Ak…ƒFrNVEÇĉjnjŒ¹oJjs¢{oÎùý~e7g6q¢È`ÿä Û”Nß—Õ²ZæŽïøváOÿä–7> ¿ôÙÊöÓÊÏÚó~ßïuüÄ^~~ìvþõƒ¼i¨3wŒž¸ùè+/ó(oyë+_f猧Þ]ć‰m¬Ô²rtžwKåðõZ-ÐMMçCà2 ƒHÔ|¦'U† ‰†â¨ÎŠ÷¦$ó¬üÿ͆ïýG=ŸþLãÏüŒãï}ÆñÎCxð\ç‡~ì^y þsðÇ~|Ï´Ûç Ž§‚?vv9q¸)¼xwB'«Hÿ÷UþüO[`Ñ~Mù3Aø»Ÿò¬%ðò‹Â÷ÿsÊ¿ò£Žø»à¿ýËðƒ?ê¹=d–¥àÕÆŽsôßYê`YŒQAí™}Ç·þó?åyíåÁßúÛÊ_øËŽ_ú¬É‡¾÷{àÇÿ-Ïk¯/¿ÀñéàN]X¥V»<§`” –ÜXÖbše7ý8}{@züx{C<î‘yC»¼C}ðcwɳ}‚ø¹_BûŠ žÓq¡Ož"M^*¥vÇJ©•Çßþ Æfkº¸‡o“Ÿ>"ÔjR¯)³m å¹éÛ-7üW¿ö+ …âÛà½/>nhÒÖ™²³DS±ôÔè1:š(DÇ;;öû#_}ç–ø4¡ÞL…“N¤ÙsH5ÐÛ-¥AîxÜÓ/®8ݹKzüˆÇ7·lwƒ”64¢u­»mˆ¥-´ÞéÞÑ.í»:Üš¨›Þ¢JÑÆ¨G¢vn®¯9|è[ðˉÍ[o2-'´zÌ”{ÏQ_z…~yÍãŒç>ÿYR-Ü¿ºÆO‰gKgÖ€£RºX×iÔ÷Ç›"é ªˆ(A>šÞ²5A{£·ÕŽçݤˆ}dª*¡TbH{Dñ¾ã² #€ MÄ4qõø Ì­Ñֵô‹4¢qƒgŠ| •bÔ|;Þ[0ŸbÞ£³Ùï©*ŒåÈøðG©/¾ @|ô.áö n¿gýäwÙÝMfᆑµ Z/gòƒIbÖ&<ýÀ«ôó¥2Ý<%ï÷¤åHÏíâ‚rÿ}³ã×^ãÁ›oÂz Ï´›>GÔ'J®gO÷VHÁ¨B•‰*FKJqâêbÃq)×Aïƒ)ÌÔVlsLj7.†÷q¬M¡÷Æ({úiÏM ܾú:a]Ø>ü:›õd©¼9S¯ï°>ÿmwÁWbâù/‘ËͽÐ`D „UÏ!:Ò9.«™I£ÆÄúÆG‘Í–x:±}÷ÒáD•–ëåË˯!yâÑÕ%=!ÇÄ—GÀË`Ž>RFǑ؞)Q·êYº0«’S@]¦‰qºk-¤y6Ïš*a¬ ÞmØf‡°R›°ðqXA‰;¯Þ?½x¯Pï¥Cüæï8ˆ½1’¥ žÛYóª ÒT‡úð›’çY?þ;wî{.¾MZNèÚèÞSîÝc}õudš¸¹ÍÃw˜.®qSd%ºÀ¤ÎwcaHaÔ#ÇZyú¡o¡o¬øÍO‘ö·ÄÓÉ™zyM}ð"m{Á;zƒé‹Ÿc]:oßF6_ü:>Å‘¸ípPOƒâWäÓ‚,çÉËö’ ‘Þ;^ W-N¸9ÜB·ü“²˜¤mŽwˆ bt½¤8\]sûÆÇl¯:m¯:Ü’èHž8ÞyžÓK¯Ó¯®yü±O²ù/°uƒD§Kc]a;G´¬àS xµÔ]¾ái~Ó³T“ù|ã'ÔB#i³ÁûH@"óÖðzïYí;¿¹{Ÿ°ÆüÕ/‘{|+ô˜(^¢¼þ!dž¹}éUòW¿DM†ª.'{ßË0Äæ(ÑWN2xûCßF;Ÿ—ùÉ»¤gOH§½W×”/ÓwW<úèwðüç>MZÆqÆõ¦ ]<ŽH ¨‰Þû¹¼·¶4±QØ¿þaÊ+´}ðá[øgOðûÖßñ=6–}½½4¹SÌÄÍíƒZ™„ׄºÁÕ&‘ŽU,ÛH…¯&èn†¡\Å;D£žÀ 1g›à÷ÆÍÁÖwï¥Þú¶ï¤ov8Lßbóä¾Üh}Þ±Þ½ÇúÜËôíO>ü1îüÊghÒéeàÖFr”6fZV#;ªF{¥ËzøÆÏPË®Ѳ²Ž¥³É‘äèÝü&ó&s<(¥Rµqø–P^´‚ <}ÌüÖɧ“QçL}áÚ‹¯Ò¾ûCƒÉ õ=ªÂºVšËø˜ìÿ²6¡Jí•›O~’±»:Ÿ_ïž=ÁÝ>C7;Æ»Ôç_f\\±ÿäwãñSÌ~Ϙ\<³øáI±Biï" 3ù-œ¬Ä^?”")zÖ>èâ™B6Ô‘@ðß*Û%Fœ³¤Qï ÉT( tN\ÏqY¸z0áÆÑº;;±›0Mަ ç,aôgÿRæò²ó'~~úÏa#d—P×ùÕÏ~íó Ï7þµ?âøá€ÿø§„GO•”àÓ ™§½Óptƒ'/<ÏØ]˜œä—?E:™’%?ÇhñÞËñ‡úè!O_ý Ûý-™`k$ \œÈÀfž9- w–»Tw¾zŒFœ \TžìOhäyÂõJvÊp@‡¨<+Â&™ùsÑLwD™ž>¦_Ý¡Ü}Žáô›­¡!àC¢·÷"Ú=c„ÿŸ²wéµ-Ëò»~c¾×Z{ïsÎ}Æ#ŸQ•QY•.—Ї…ddÀcÙHþ 4@@Ç Z|Ñ  .À !BF²dJB†*WV•++³"_‘q#îãÜóÚ{¯µæ‹ÆØ÷Ü »½Î>kÏ5לcŽñ¿?R*‡‹3-+æóòÕˆ ĺâÈ\*s^ñÒ8ÃͪÁÞëW<øèOõPnUj‘Ö…á³9\¾âî7‹6Ž\}ý[ÈÇ¿àÙÍ‘*! ,⑦å}kEåTÎ&9+-¬¬'M­Àô‚Õ'ž·‘NðêŠù…í6r,uYè(R´6CLÖ-WßÍn#.E$Ld 5Èq^eG%WZîÄA*wóÂ2v›Dýò ·7¬Ëžã{ï±~ë;Tïáâ‚õßFJfüÉGøËËS3¹¿÷7:ïŒjÿ1â, mG>ÞqýÞÊ´ÞØüäǤëbˆôi‡™~sÍúò9Wþ5 \=~ÌðâÖLu'ó//T±Y¹›3ÑMX?qÀàú M³‚¥6x½/PºŠ™Ä‘jÍ7½C`n¢æK­àš%иÙ˜çÌ’W-sûÁ¯‚1øëלýü§DÁÆa¤÷Âñ‹gÄ«×Ü|÷שÃÀë§ï2}ñœj#Þ'šU¬t™'ÜvïÀ‹1¿÷[tç>ùg——ø4ÒC‚0µâ_¼ ¹Àòî{,’_½b-•P Áˆ³´Ö¨ïDZ ûnis%¥N­ê¦ÁOžzs£Ž«FMžŠ4¢uøÖY]bž+çÑ’Æ WDžЕԹÈןÁr\q\2û¯ÀòÁ¯³¸ÀÕî!ó·~ É+›ý!æå+®Ž½§uPj%×Îë53Ĩ}AµÑzÀù…d, ŽÐ;¶g–±IÁ4õ-0NçoæB#2´L™ÅX2ÁxZíx2.T˺¬\>~3·;ç?ø=Âí .$’8¬uTéôý ìo8<û%Wßút{C6†d‚ö f•ó(èÒ)dAÀDxç¿O©º.¶†)jáø/þUú8*yÈtæµðä§WÃR3Õ –N5B)…è­Êðs¡Ô¢ÉÈ÷4é~ù1ÃO?":«UD}^ˆ?ýó‹gó·ß\Å`]24c j©„ ÎXnŽGL[¹þð»Ôí9´Êø'ˆ¿¼ÄíÕr%sxùŒå³_°ÿ ÿßeþí¯ñ_þׯDîö3ðÓ–aŒÄàøÿÞ(Åd¾ý+•W·Gn_|É.6c‚\™ ¬Yõèûß?2¤ÌÕUæßúw<¥FRœˆiË'Ұŧ‘ßû~ä?ùÏ@ÈüÍ¿¶òáw–S壹Q¤ƒhùÄâÕû~pžùþvçïüw‰Þl€‰4îÆ››ÀôC3B·ŽWãD)‹6tåJ­(vl=rw8R-ÜnÏY©4cûËŸ2Þ݇‰i‰)2Ž#ÃrfüÙ@„åÉ{}¤·Ž°p>¢¶ðªÐzf¹x¨ÍN×—Ä›k¢wŒq`H#vØÒ޳í9øa4†w~ù OŽ{ŒX„w=Áµ^?Ì•’gL´¸à@,F¥W‚· Äé>¹ÖÕ¼`œÐ[DºfJ{ëÌE©&½E« Ækipº|©/¾1,ç”\˜KƬˆ„QÙÂåHk ó£Ç Bxù‚ä<ç»-F”~ßèä¢ek.¼xü”nµ„»û臌)1Œ;œÛ’Æ3L˜71ÍÒ'kF÷ÑÖÁ³oð=cz¦·‚kF}/Œ¢øî3d½S›fX{ïÔõ@£SÔ¦&0ðöúÞ­©·HKcÅØÆÕÝB]`Æ{hN¯©ð„ñœ‹óŒ›36ÖÇgD§•„èRŒ k†£uX¥¥B4ï‡âÁ%šŸ°VÍ«¡#ùÃï°ý³’3aÚ’Æ Óý½ÛÞÁx¼s¬³2âc)ûó²ç.8ÖÓ<>û”´?òàÑCÎ<"Lw¤ñŒXã/t¼ç‡ØÓ(ë|r¬]Íâ†èmJ¥—®låÌ@êW CÝSוֵÂO¼óä¡yu‘åÀF2Á¬ë‰Ö+e=ÒÖ7ê<©…³_|BL[Ƴ‡LçOÇa:g·{À¶†Ï?Óû¾xÀ­wËBn [:¦4|Ôƒ²f#åTº÷Œ?û »×—ÄqKšÎ7ç ÛôÁ†xýÞToœ§–L«E‘¦“D›J[7l‚cã ÉÀZ Ö€5 ï…²ÎôÚ°ÎãCÀ[aðžÞ M„è*ç[ȹqu§Õ5KÆûHÜì¸Ãʼn0LظÁûp?œs„4‡3âæCÜ0¤>m1afÄ·FkBÎ` µ-4 ÛÁã oߘ~ò#^_3 ÆqGL[v瘶X‰——oÇ$EŽËÊ\¬­[ãz¿°,…ãI;½˃G ÂøéÏîîðiƒŸ¶Œ»ÛÍ9Ó°#•Âæg?öžrg…×û…/Ž»eá°6²)F½5ÁyBò„ÉRóŠÔŽ˜‚a%x‡Äˆq ãµ.´R±LWê_t°‹"RVj©Ü}ãÛ§µ*³ûèŒ)1¦Èfq$„ &L„Þᓟƒùñ;¬ÓÄ~™iY=€j/ . œmÎxpvŽŒõoßgkñÎcd&Æa‡õà ѫ™ª­Ð+g1œ»ª#ê*ü•g9üð8{ù%1„”pÃDŒ[Bñ.Ÿ?»–yN˜ÖN"±Ô¦Íè¦)‚ón˜Xê~9ýâÇœïoˆ)1à{Ûâ:³ùéu¿|ú>9ж,ª´ptL/ldåqhØBÂZOGßÑ: ?üsœýàŸ0ÝÝR"Oôoä-€Ñ`SW¢µô’‘23G‘íˆHeFèNß›fÝ ÝÊZÈÝ­#ì½*1¢4êrd®3Í'UÃÒkÍ÷sÛ]¿Â]]â}b ãà˜Æ‰Ý˜¦íöœÂ;?û1¯ŸcºfL_ÞaƒSS6çi@l`j@êÄ3žÐŸÞØ“üôÞÑìs[æX8‚IänqÒñ6PËB_ËÉ„°°ÿàC»ý ›Ÿ„wë#ÍҴŇ„sŽxýšô'p?Ö"‚ˆÅ»U‰~®eJ?jU)gn§-ù$yM?û3Æ«WŒã@'\µéÚ'†ãé£è?—•*V‰&bÈÕ’çáÙ@G“„wÒÀ8MÄíi:'„{ªš` ¸@&â81Œ[üvb·›˜† tŒ.vgS¢TÝ \ÛÔ#§‹»¯< _~ÎÃÃ-Ïs¶»`_ôˆ!FBŒ„8hÓhÑæØüI–©îÍž†õê5ÓòÂͣǧ5¶°}ñ%qÐûŒqƒw„a»ïÃõ+Ìi½9D©+¹ KŠt^^ÝB÷ idÍ–µYšuJ6iÖOD›îT‚ †D£-•†Ú)–»ÓÊ‘^\½#ùÀ˜"½wÅ%?}Œ!=û×;qx÷”d¼˜†€wžà,Ó‹/îçÕüø©ÆkFpVÞÁY¤Å[sQ³¦ÎÝ?KëÞG…$ûgvÀw‘1ML)кŊ#“Xj¤Z |¥Š>ûéò%1&¦a`;L¤0b¢Þ¿wÛAJ¾—êÔVYz¡÷J¤ô¦•Îuž¹{çýûýrüâ3¬ñ 1’R`UéàC ÄÈøúfUtîÝ£§J%16¬ëذ!1ÚŽõ†!Zbp«ÍÒõ½oѶç¤Ï?f³˜¦1%‚xÓu–FŠ Ó ÆˆÊ}¼Ç¶ï¦ÂƒpB~.\»ÆS©M(E+œËºPºÇR±¥±_…l¼V‘Q3Æè„^i ¥-ÔÓ>oóŠˆÅ!Ó )bb$¹äµ¯ØÙnÆbtŒ£ö¿ä½¼zŒ(°dÍ'塮 &b]ÀZë0†RQŠW­¬‡B‘Há°ÖSÐc$S¤i5§Öí9m³ÕùýÉÏԤѫ÷ʃÍ»!àà½&œÒÕ%’u-£iµëãÌÒ:-+Ý gYs%×Êáëß¼}Ò³Ïp!â1¯ ‘à#ÖÒ«ç÷óäøä]Öu¡÷Œío ÃqYù|»4ŽÅp˜ ËÒ©¶0æÌËgÏqR1Q8ÜeÆRɇ[Êþç>ðp Œ}æiäý­åÑ‚;e Òçé,ŒbnYÖ=Þ8+T;ðÇŸ¾à'ý׫jA­1÷ßñûÆkùGl(=3gã¸;jÓ’P¸8/˜Á†Ñ°5 û»™—¯ñ¥ªu´s898“i­à)¤ Œ(«c•Z`YW¦³3d÷ˆË«×P+¦fæY%R8Ow‚‡œª…Â~g»-Þ`œ:,6Ë`Õà«·J^õw>8«$+¤ÁÇ„µBt–Äjá_þËRÌ…ÿåÅY™@òŽ1©3ànÚx„ÈÏ=ÿèwµÇáßø×ŽÔUICËZ)­ÓºZnÑâ­¹¿ïßû'BpŽ’3ÆzBšé ’&bš¨%à._ƒÊùÃSãmU{ž¾Òò‘íàûëÃ' †ðéLj®€t1 €!¤Qƒ5Q×ÁðËOTÆuñP%!­QËJ;‘‡h 烖|‹.ÐõìJÝ |y}Çñx@ÐÓzë°M–aŒØ4à­ÅZ‹uJApXÌ +–œ£äFÍ+ ®Öb0"¼qξÀÌHK9™óÂv“ˆ^˜Kć„÷žºÜ"¹aÅ2x0γ üwéåóÓo|Dõu…Ÿòå¸h=³u•²—Ìüð!ˆÁ, ñîH3¬SÜŸs`…\+kžY.ÎNºDC|õ ë«õÌÝcœ¡û-Ý&|ŠïH­ã^_ê½+¥V¤B]:kóøÔ; Ä4ž*«àªZSÚ®«QK³Xëq6"!¦>L i":‡ó#bNޤÖ!µ±{öKbšÇ-v|ˆK;ͲæŒ_3Ñâ4p¶Ùñëî{Ô8RP@R7Âr~¡ãýùçXQ¼Ãq.XŸh]} |ÒM'~ù…Ž÷ù9½Vî–Z£6ƒ3†º Kî Ò8ŸFšSS#:¦k†}·¸fD›—®t¹Yjï\×ÊÏ÷ W–ÜUKk,¥$x›ñ~ž¤«+Ò8áȈá|»!¥8 ÄIÆÁXâÍ ˆáp~Noj´ÎÆcœzˆ(™ENÏÝø11ApÞ“ÒD‘D3žîFÞ¸¹šÓ fnêÌ|·ÂUÍ6ÎJÇËŠ=Éú¾¯ ÌŒŒ÷/ FQ>MD)$Yi&"ÈÀm)DÛ‰ÁÑü"ð%ÅŸÆSvÙÝÏwc<ÉG¶›QÍ•Æ7!ÖàCD| ‹åXÆUlLxï(§lc3çÄ`ŽGª \#Üu‡K0ÖµòíVh¥«Œ º@©Šî¢²ZV¬Å9Uª ñóO1Ý´oBÀÄD×> }þˆáxñ˜eU„î¼ s††gép¹:ü8’ÅLÖ22í wæÔ9ØÐûœ! êÝ`]$Z‡s–äB?2/…ãqeYfJÓäÁñüâíZuùb#e1B ÆÈ8mè.ajÅ]¾1,ŸR+ˆ·xï1Öcâˆ1 ÑWó©S“7b0¢(ðºÛr'ãœM4s‹tt.ë Ò£þ]ºž¾Ë÷$¯ÉÉjμW÷<6¼³Èéoº÷'iˆz- *~´¨[ñrÚ/Ó§céˆQŸ¦ÚÍ}’nˆÎàÅ>Óýryð„V;ÂÊÎ/DßÙD‡µÂ]­¼þ˜ï<øæIe0Æ|e¬œú1>$†àØ qˆlÇ?qǾƒ·‹54ãÀX"‹zK`Î7˜µ»ß8Åf.êwäíÊä ® %¯Ü¬jZê‘c®Øáã””µvõ†˜\À‰:m#†|öÚ;¹­kQGùRX°Ì6ââ#ºŸG?⣔FkŽMÔä\n ÎÀGÅŒ*P*àü€N½úÛýND0ÁX¬=ÌåF##Å‘³`z§fKi3½UÖ'Oõï[×ùí".Žøt†Ÿs5ˆ›}éÑu¢u•ß{ç°>²ÏEiJ‚4¼×ý1ÄO~ŠíÞ*µu6)k£vGðÚÏÛ:Ä_þBÇðáSZ…RÕ‹CÝ::¹CÅbÖµ`ZÖl±sÄ!RLDÄ’ÌÌZŽ© £áÉùÄxþ€«¹‘9’6Ža˜øÚYçŸýK¿Í»~Ÿêê«V1V݃¥°®w$k¹[ÊR ]ˆ^KæÇ£6õöU{Æ9Ô²ºvÄZbJ´fˆÑã£WÄœ–åô?»:zW‡f…o}ó}\ؽÁŠ`[Sƒ!¹'rUÇÅE&._]LÇwK”•.žŸ}úŠËgÏyS5ŒÁ‘‚ÇxŒAî{ 2b ûµÐzçb3²ÝlçÀŽXç)Í`ŒÔÓX‚³ ž»ýžVUÒâ(Äø‹¿µ"’ɹðÃ?ó¤IcBâ†céxƒ)l¦DÜÄùÇ¿oÉ|ýý…³óB?~‚µ`uc]ë)»zfbº ¹€qˆXëÇsRêø¤î†åæF˧Ör¨Žã’©yÅYKÇrµß+–kw®MJ"ØëË“ÔØRûÝÞLYôZUnBÃîï´¬¹ÙaÐz¥‹šÈ€œb5*{#=hÓ†Ãw“µeÊr$¯ ‡u¦vå®ß5ØuFùíѦ­¨Ž}·KìLÌÒ´²»gúvËËcet ++Eü ©ö†¶¤™ã¥(ž–Þt+»pX;]È[7 —¯Tnd Ç‹‡J/h+L-+¹­ä\¨µŸ>Âóçx+l†Èè ´“$§7¼eO›7µnÜ~Æ»iXKBÄ‘¼fÕ{Uì¥8»¾Ò1Fâ9ï°^ÉRb•0FýŠH:ÍV¬qšAqBmYõøµÖûk¡vË"•ÃþÀ:wRT¶šéêÆØÅá 4Ó¶MÆØç:–EÉh6 v¦Õ²!Gˆ–!Du“”µ,B1º%Щ½ ’Hcîï+\¾älš8Ûn°ibÚ ìÎ7l·¼ÿùsÞÿô λ# [^ ÿçŸþ„–3Ò2­®ÌC|;¿ï®14 Ô‚SŸ„Ž¢sSà {Ðù]Æ‰Ö 5WÚZÖhÇŒ‰Nƒã‘qu¥ÏwŒ^Ø%‹˜FK1–Y M «ŠêðFãnÏÏ/‹"A¥0WeÖ÷ZÉùHÞ4Öù¬ {"ÝO,ÄDJ7Ô4!a¤»w«Ï2&îjcmšvbœö]:eî¿_c-Ö†4 Ökp…͉6ŒÁHW ­œ¤G½#ÝLgŒ•h„’g¢Yq¢Ä)ã„à¥v¾õð‚oaƒÇ¥-ÝoñÒ”v׃#¹‰%«ã´wžÞ­¾ëo¤HÆb}B\dHç=ÇZhν«M™_•äZ©½ÓºöÞi@×i½pˆé~šë׺VN.ͺi˵tZ¯Ú›µ×gY¦:ͯ…»œY»@pôZéyÆJ%çJ©‰” ÇU›:Çà.²q†Ý˜˜P zHò¹ÁíºPü±‰Z:ÔNG›6ëîì~­ŠÇ™iØPkçõõklˆøa‹˜+œžÖû>LT+ç…ýqO) ½5\Ü 8;’‹óîth°_Y4ˆoè²ä¢xPÓèEÇ.&í­ðÁi“toSé'©ôéÙ`°.RįÕo±Öá¬ÁàTWÿfÝTŒ„VŒR mW7øV3󸹖îú•:붆iê-PꢒÁ^iÍ(úN÷â:m©½â{ã_øî‡|ïÉÀd÷x ¾ÒSdqÊ©÷N1®oî+½|Æà cl¦-Î7<ÙFvS⃟þ ßþèy¼ó .\¢”N 3Ž‹r‡uæÕñÀB¥-Bí‰î ¦e6)`‡A}ŒÊù¦iÐþ„žñöäIU*]`50wCpZÝÒ}~ËíoüEÖÞ™sæ°Ÿ9®+ËzGYä%ãªeÎ0$ƒs–Ú,"º—ÎkѬ¾ÕDÆ8h"µ– Õ h…H¯‘¯¬c‚s‚MOÇövR&xÄw¼¬JÙ4ŽÒ´÷£œ?еãp‹éU䬗w•ÚÎ`¬"õàªsËAºö08S¹˜ tÏZ*ãtAxüµ·ûÎÍåé5Ò}­p^ Ko:ÿ:ÈÍ)VÚìðVQ½4íõëuÁÙ@”†3Ö Ò0FSd-·óŠÆs(¹;G¾ó”µ®n~ÌzÌdãx¸K » ßúµ_ç3ÎøÁtè'÷Š´ÊÚy/9aÈÅC­ã,£Q{òvÿ·*,kµC°æ„ÕÒ±R¡ê+–ãÑjÀØO¶ã¹’,q|òâ•6<Cí‚o…µ¯Hé`á%¹2n øÄá°§’çbÚqu½Ç›ª¥›ÒÙ/””ÿ[{Å9ƒ åº7¿]D°ÎªæÒ9ÄyL'ã,÷&O]5ˆXîæ™$wK¥—Jí–\V¾÷k0¼xi1èèg{gLžÇç¼|}G0™b…¼|éïïçƒV¾ÿ's­D[± Œ)8cY{?T›š|5em{ÑbŒ¬µaåäi&VOÿ§Owš}i]BVÔ ò˜ åñWðg7Wí¢Ÿ÷à¢>å“iˆmºY°¿=• 5zJ·äÒ1þ4V6¨síf$¿ø‚ÃÓ÷h1‘ßÿWžÒ¾ü”íÍåæï,emØÞ¨~`ÉZYX øÐãHi*Á†5†:¥6ºq¼¾=(~4Z–¼ ÚILãNÁîóË;Ê\HÉRû̲bˆìç/'‘ÖÕ5цŽÌ3Nÿê9ëãwX?!?F›WR°¸>SêJï K9SÔêxù’8$j‡\ C ëY{×ͼwê ÃgÞTܬ¡Š;YÕW•¸yO“‰\ ¹UÎòæ³ÛÒö™^36f¨LUT(oi¢jm„ Mhætà±bXŒQ|Š´1qD,Î4uÎ…Þà|·åå~á07ú†«™ì<ÉkðFç¿Ä’»Ç¸Æ:a;h³²3†äº:¨ZuN7má{_‡O.g¾|þ1ÎZ¾J//é.QÃ@òã0ñjmøa&ÉJ¯±–Ü•wMéjßÊʺÞŽÝí5ÅzÄ9Ü–½ài )p\–e¦åU¥ ei畃N!Ó˜ ìºeˆB)•e¿6Ò;7 ôšÁ¼5P`íŒVS¤t––Uîä"«t¬xœï—ÆÝñŽÖV,2N§y²"ÆÒÄÒºfëmï§®äe)'€‚Å–·Ïñý²Â¨AˆtufÎáíèêZáü ÆG.‡Á{V/Tg9Š(± TH#töT‹u"‘µ«QPi©nÕ/äÙsGð`̆µÙg5t®‘«£6Gë‚H# åîé×h1±¾÷M^=|ÊøËŸ1¾zκ¿Ó>3 ÖªuqÜ, wÃŒƒ§5Út¦±,•b ÖT\®Ä”(T–ÕC Y«ÛoF©Ýr{(¤Ý9†s¾¼{®;¢KºOÕJ[¤,ªn˜¶:¿—YMs=È·¢Š Ô5kE 8̪D¬ŽV„KÓàž®p˜ }î\®¸|òîý³³7Wê¦ÎB^;¯rÆY­Øç“ ³õ ‡Ãé,ÙJ§3 S ÎZÎf¸µ¡' 1xŽüö×~…Ëáüþ?ý#¤°µÐz¦É–ÿã±õÂ.z¬³T*‡CáÇŸÝñã¿÷÷y~» u†ÓËg•EÛ¡4KˆŽµ¤7âàÉyÅCnaÑà¹-Xg€tz‰,Bc‘#“3LÏøìÅkZ{ƒ ƒÚâéÙ9d ¦ŒÖeÅta0Ž£íDcøõoü?üâ%²\áÅSšÀªÿ»ÙH²kó¼p>:º$®îîo;Ò;"– +¹tÜét§÷ÝA<ÆhvcΙ”gNÏ®X*Á;ÊÑÝ_/8¬x|HÄ1"½çƼZJ-:u¯•N+ ÚŒqŽ(V³{1iöT:-g‚õÚ¼Ü;Õ;èšIk%³¸@Ëˉ„Óî˘ íèÖºÜwƱsƒH#CÊT»XvµÐ~ô‡¼ú•ï²L;zð\ý®wwËÝË/I/_°µ É”’BÄ´£Ú̲TZW’A—ÎþPpÁaD¡Ó›ÐJ#Áë˜Ô¾b›žÌ»Ñ¢¸Žå@Y¬ºÕ²P—Ž1`}‡²à½¥.ŽðÅ3ÖGO(»3ëIu!wO/§ÓWÍì<€®Õ“¡d$ˆ1TIŒ:éÖNiPz§:}.²qÁ`œ£¶Î’,‰µZmÐÀYÁæù~¼oÖʦ6ÖZñÕPé,Y¸Î賤7:šõ¦Wœw´æÉµÒM&·Bïçäþzé^3¡y÷ýwùò.³Ì…Í0°”™a™IfäXœXBï”jè.‘|¦ËD´ŽèÀ–Î\\ã| ,¥Q[ÁØH^.6Â~IjFÓaï«Ü±öŽÒøúiŒVüa¡œ=`¶ÓÄf÷ˆãå-)8œ޹ž²àV«VR8κ±jíÛù½Î¬^ Ì¸Þ(ÝŽw+˲ÐO®¨½—ûñ®ÞbMCL£…H´b¡f‹G%QËbï£ÁÔÀq]heæP,ç‘Àa>⢺ ÏÖÞIÁÓp,9Ó£¥+¹(殽aòÂ.xÖjˆ¥‘­Á#Ę4kŸÄ:Õ»¾¹oÑJee­cÔÔRýþ¹74h2bè¦C-tçï±YTkÞD¿«ëÁ ”Îz:<Å4ÑÖŽ‹Â"ž$\à°fVñ¸9.ܜІ½) ew:Æ8zËÔ;cÈ)’—Ìr(Já’37ka2Gšw䬨Ú&‚•†ÇÒ½’fNg(´Öœ°I ã-^ÝVî¨ôjè)9rÑjŒˆe^²BXhoŸ–Űâ4bÁ˜†ˆº9›ÞÔììÍõ½Ó4SØ.‰VïFñ½Å½Ý/kάÎÑNRNÛYé­Ó{¥¶J¯¬ßÅ:îîöüoÿðg3Y¬Ùê:Z;gÃˆÏ W]ƒä´še!\_"ÓŽfÒ;Ÿ¼Ç_ûWþ¿÷»ÿ3ËOÿ„ºzV§DÖÊ4äR¹>&W¹ØD.ï,‡Uã£%'roT¼Ô•6OÌE}¢3¸nY[`èˆ0a`];®Î@féÂ`-ÿø÷xõá÷(»sZÜ}ð]î>ø.îî†áÅ3Ò‹çøuÆ;…Y$<Ý wkcè*½É½bƒbk—õHqíÔ4·ÃÉ[h}»i²¢’¤±Ï kþRýj´“I-µpÌ HÀ[NzO˜g†äD+4g°R¹íšÔ.õTIjoæ•®7Áuº8ê¢ÉÓÖŽäåÈjßΓœ Õ¹¥czf­c4N-]qÿý+ó¤;ƒä®Àg@".XÒèqµluHoHžùö¯}ÀÓþ ÿ÷O>FæÄ[\-`…ó>†Èš3†»E‰ÖÅcäŽÃþÀqIzÀêº(×¶jÖ ÎC£np%ã¥ÓB¤•“QQ$jE:Û-sÚ²ðµGgôxÁ««[æµ"½ê&D<•óÑYvggìWuՅІbÁ8¬-|y¼#7ØØDÃë ~ÎétŸpÖr—µDn$cH/ç(Íh©¥9àé§² ½žîÕ¨ŸÁkÛ!9˺(.UMPâ©ddqÖ¨ùº²Û8nk ·#½W¶[E±¾¼48ï NÁ6¦g™+ôBôž»c¥uÃå+«Ï8ۥŴU}‡Q)$'fó›ñž×Žíã&J³P:H#7¥3øÄlç“NP'XóŽ2«.:ZË\ ¶)²7ºsÿÕ¿ÁÿçÏ›…ÿ„úÃZºõX'l‚çX,ëZq~dSîü§¼Löï¿OÞhu¢LwÓÜ}ãÛÜ\B9C× IDAT½âìÓ_0̳n â0a᪭—”•ßµêcÀͶwcYÅà鄵Pű¢:aÓ ½ŠþN1X.[V:Ò357ú)SÕ{å;7¼>V>>¨‹ë>¯tçX>dzñ©jzÒòB^VmíøüKÞÈ«œ¢u˜^Xs£w=HôS°'óBëõÞC@,ôÚ®Ù3Ó:N‹ˆê5ï é«f£–•ŽÕþêi‘ÓÄp§ß¼äL<­8™Y‡û@®›äÅfƒ¸ÀõíÑ ÁAê’ÉÎ!ÁáJǶL=5K©ÄMÔ’l­–LÇPÇc9¬•9w‚J@ê`%¦@’“›ESˆ¾ç™=µš¶a¥ÐU!xª|DdJ®‘Bqîl,“ƒ‚»ˆ¤‚/Öaš ÁØZ%Å…\ø(ÄS¡­øáR´Jb ”çT!6Ð(¶‘s"˜¼hC´+Íâ¤ûqï€ ؆ñ4\ßæYÙ.®jþá7 âGÃèª,Q8M­™}댽þËùO¾s!HˆPפDŒÊȰ™ûi ÚÙLH"”¸L¤rǸ¼‡èay@È¡× ³DP·,.ya›'šêîÙ4šN”I0ë®Í²D•†Jó5ØÛ·öN"çÄèÆn3Ya>e"‰¶UÅü½ ßz—‘(©t ‹ûç§H І±keŽlA EÙȶ2‡ÀM¾Õ´9—1:¹Lˆzcƒ °ýÔúö3Z-­{8ÚTÐnØÐŸ:Onÿ?î˶¸mnkàîäSY†g¸T٠јRòy‡)¡mäxœ)¦Âº=ñ·ÿçÿ–Ë»Ÿ ÓLiÐ% 9C€ÖÕ Ø$|ÿõŸa[ßBT\òWé{¦ŠÑƒsîSbP¢V4⡃èíG³À¦ËQˆßˆâiÒK)ŒÎy«Ì¿õðüø)O?ø9úý‡{þŽçó/ñüs¿H~û†ó?úGœtg?¦Ö’„ÁBä™s™è͸§ïFXÈÌMÙûF0#ÆoíYs:·à¦fÎÙwç\†“¨„´0¢S‰^Ö[ÝŽ÷íû>–™n>oš ô­R{÷ ”}Øó>U"9@×J«¯(#zìK‰õ/ýëâuJ¡ØNJí7,M0ï¯+)š#úб™ÿâ¿û¹Î¿M¼ÞÌÃ,#¦œ’‡5k´íÓ2ñÕE©·}¯¼ ü¡‘’{”«)Û¶SJ‡Ù·AL3F'ÄÈœà:Jõø;¬²Ü=0ºÐoÏüøéæX)ÌthÝGº"[ë´7oX¦9%rŽT‚u¤ïŒxûå×dÍlšî²oâÑÌ}Ù#”tGÊÐÈ‚¡§B°„ ÷UsÝ‚sjðY ÷ý–IÅyÇû^9åNê Í BïèÚÀ‹›!ÆÞýߦ˜¨ "þ95sR• 2p7GN9Yh¬ÎË̾®.ö;ž§"”€«Ý{´! ÈÎËtG ³Hr<É9^ÑËÑR†sžEøFG ØH1¥ÙLûÁýÖ(üOøO‘”ˆp—átNÔf”ÜXï(·Â5'®×g>ßžyýÿkÜY?ù‚öàG}õ)_=¾æî‡Àg_¿aEPî(s!&¡¡œ–Ä’OÍ=‚‡d%™Q™0uÆØ(âœLË®öªˆVO”·]|-…É-Ön–i!’¦ÌAùê'ìßý>õ‹/°/DÛ6bŽ>ª¡?<‚ó›/ 1CA‚PÍ(ÇZ‰&Dñ˃ƒAÀ&ôÖ˜'@"±dªuPCÅœôÁ½æxGA\l@ ~Á¡Y w/ð0×MˆDOú ¾·ˆÑùñÃÇ£]í8T¡_[#е1?:xÑ¡FH®ê4%‚ÁؾzûDžÖçXw›Âö¶»Ó‘u§u„‰ç½!âÖ¦:Œ»IØú ¤ƒÚæß×°I"þ.cá|Âöp錔„œéF¶‰&•‹þÆÑ_ü©Öwž0qÄi¶A7Hb*´ Xä"#Eƶ£Ý8‡39ÀSH쇞2fƒš U%Sè  ‰¹lÜÆìÞ±N0cØ Y@Ó̲Lì&`…S¬ºƒp¸ß|ü®&a.Æ,Ç9á)Ê^&}X'z°&%f4eoc¢„$(I^žE¦Y&ž”1˜¦ ¨Ã©F`ëƒÑª~~J¢¢$1RqwÔˆÑ2EpmJ÷ó÷i|÷³À"ïß_"¨D‚©7–Ç÷…žœ7ÇÀM;Ü„S ¬20Rš9Kâ:6vk˜5ßw!!GQ™;Ê!ì#šñE`];2â‹>ãeïØÁÁŽ,u$/ȃ¸ØŸf ƘP‹ IÜM‰’Á„”Œ«º­á)'7̼)Ùˆ$n× Ï—Bá®›´ìÃS_çóɃQ§KDá|š¿µ¡÷ÎùœèdBŒ(ÓùÄhýæûñÛgUPJÆb$EwÓ‰8ÊÓtøÒ÷C»õñ7Ê"ä€È4e6€˜Í¡9Ç£y´ƒ–œªcv„F—õv‰JDCÀ$ƒu$¸6U×c}8sÄÓ†—$hÌxå`Ôá Êúû2ðxzd¨âX 4öÖèÖˆq"ˆ1Æ8&½þû‡à#a$Þ¾}Çzy¦õ#‰:+y2Œ:šOvò‰d¯ß°_o„ó­ù](Í…þ­3ß»æ®é Ôi£)FÒ”X¶Ý'ð׫®˜Mh˜ˆ)1-7½°œ™|»ððÛÿ'7qǼúéw_¼Û'ŸñîÕ'Ôßÿ]^}ù#j D2"‚¤ÊƶwŸfwà¢ïÑ £UO)îÑÃãcC`Ç]‰ JŒD‰Ä(XµŒZ÷tçàÀb¤¼SýK„%'Jîš×ª;)ÊK{ÃÌÜ’ÛŒÑÝÜxAcatú€?Ýž?´ šg²uˆŠêD·DúÁüèéJ¯ðÍP7ìòû¨Ím†„J$ñ~­ ,S¢ªÑjcŸô õVÙ÷•ÑwàÑ—º)Ö©ÌhœÉ쎘 ˜$2Ö+¦ÂýùÌmÝR§ü¿_½%ÕÎÃõ™ýí×l(—O¾àö³?‡…ÈågŽ4Ÿ^näÚHóä5´ 7*I¥D¡È­U·vKºER·Î5%æDìÍVB¬^Qòæ‚ø½1Î̩𣷽݈¹êÊüåÙ¿ó]ÆéÌS”ýJíÊö½ï9ÚúüLî˜Ü ”HŠƒhÎe­ý¦K<~ïé m¹[E—@²à‡ *h'—’é8¹Ç¤ :‘<1†|1 © "ã#Ú…2t@ê ÛÈhWú¾Fåßùœ?ì.üÇ” µFœè$®:8‹º°,dÂPªv”Hʃ7êIâŸß?òÍM1×}ª»>ÌSFF!!-ì:HÁ ºZ;#¹¶ëî´‹vðzÃ^„˜ŸÞ%vIÔ sˆ6Ï:ˆ‘Hd†öNb°©ñX:©¾nýXßWNÿûÿJÊ3iš‰©¸xWüÒåÐ3LAБ4SN÷äÓ‰´< DJ)ŒPÐCX¹X£©‘tp:êéM›ÂÉ”Ôw§ùG®mï\¶Û”u’æ̱ÓcgÕáìêÞ| —Ý©CU¹Õ˜ÜΘèuõÉ…9»v#k€8:&™˜¼ ×î—ÇîÆÇ´É:"‘„H¢Ç„ çúÿ4-ƒVëk§.(¯æ _oR&hcoבzvÉ= Ý" É3¨£Ò()t¾sÐ&üpëœCAûJïJ3ûø\¾3Dbt­]ŠB JŸrçù¢¤(Ì“p{‚ÌìA€aá˜$˜š™#fnz"¢‡»ÌåºÒoÇäã[Ô;Å9þ§ì¨çóf¨úƒîk0Ü.œþ·¿ëv•e&¦Œ¤D0ÏxQ¥DjS7ó0q»ÂÓrqAs3wb ™¡»‡KêŽqc J…Í"±Sô4øu¿RÊ=%íh¬%ûT];#œÐ¬3õ< °í é¬`Ù×:Œy ¤’IâVžû¨P¢ø]ŸËGT¼:-,O?Ü"ˆa¤IyFu‡ÑÉ|ë] ”K­™p¾£D9µAÁdAZ%ÇJ%¹Ç‚ ·€È" ä˜rt6A „X¨ÍõV"Ö~z}á^6§F B ‰R܆Säzáü¿üä”I1“rôÂÝ|jåx‹?Ù ÆD ‘”‚ß#92—ÂsÄœÒ4K`·Âmìl*ÏAÆ^·­“0e´]©23c«Ê q7CžWQŽbö¶7™TW·¥.^Òà¹%ÖÉûÞHÉ:Ç^Y4 m#—™ÞÏœb ‡Áú¼2,pžÏ ®`¡›#üó×_ÒòGÔ”¸}ò]ÖŸÿ³XˆÜ~á}_¼}Ã}~`N‰}lœ¤èNž3ÃÛíB +1·Ý))vrÿnÔ¬#Pìy[)ÓÍT9 ·3ýêjì;„a"מ9cì7úp{$SÁ†¢c’£Î˜‚Œu]‰êäP{)0Ív:‘Ú™çL"sk»‹nÍ•üU" ú–veí(f#";yva“jgo…ÓI˜hܶ±ô«HŽÎèNŒÑ7(B0ìÉÆC&Ô Iƒ4¡c0.(9U4ò4¹ˆx«œN0•áWø³¿Æßù¿ }óƒ¥'¶8ÈɽèGøP\žŸoÄèžè¦æÉ‰Sd}¿‘1–”Ø_šˆˆˆÎ+Ðcfž ‡Œ¶F ™oÞ¹ÓÑçŸB€§ËN9ÏÀDÖÁ¾5š >}X¸~óŽ ÊçŸE>L ¾þz'Rq"¤àÔ "µGšŽfÆq÷'‹‚ŽvXfj4sMÈ|>s´É­¹XÑ"[Ã9¹pm2˜Yfÿÿçi"晽kî(/ºß )NS"äÂ9áÖ%¸«TJŽ¢jm<Þßñê“ïðã*L«Ç‡ÜÉõJùúKÊ›óþW~ -ï¿ÿg¸ûûÿyŠìmah'FckŠô‰yVÖ½3§BŒ‚˜_ŒÛТ âÀêðôÏirë̘s&/oÞ®ˆ âT؆²éN™Î”Щ{IL·a[Ñ2Q?ý”ø¿ïÂNŒúé§ ÊüæKîÎ÷œî0õ஦ ‹££¡ÐBwÔ©UPÅbBµÃpí;Ý„”'F_Qsá~,éå]†^I±pÛ…eäT¸mÐÕˆ)úçT_2·˜õc$pšgê&üð˯¹­7?àÔ£]ßëT¢V­DÐήîj%½£µ1ÍP·Ä”ÝÕÄF'¡¶ŠöÈ<+c¯¨ % ˆ–&"Šöq$+YŒ.P‚¿7õ×¿Ç š#Wµ)¯&Þ®náy.‰‰Öæhœ³14r Ñyò,qõóLœOœîÉó™n‘)p¸D¸æ#J`BÙć ”q>RpKÞ y¹PUЉFŒèØé–ÑÐ4³ŽÂX/¼N•=L\:ÌçÌÔ>…9vÒxwñ4Ï”ÜÏ~¨¢!aÚ=ˆG|RÔĈµR[ ŽŽá!BxY'±wR&‰@&뎉.Ð;Ö‰èpÎá.1F@Ê¡SB°0ñbU®=‡é@˜ÜýªªgŽ<«b!rºs$¯v§=uuϬ4v¾¸;±öÈ»nÔᵜ¶;ÕtJó”~£1qGGº2zG¬;äÃ÷À'¥L K XgS×"Õ^©K¦A‡g$1n(û·~ ×§Éy"áBö1”m]¼ÁAíãïmÊT2Ï»S6r Ü‚C$ö{¾raš¦i!O33E„SœËßì’:E• "‘ 1gˆŽ+Þ¸H9Peó‰ußÑuJ¥›pÓÁišA:ß\Âàñn¦ŽÀe5r ˜Á^…8ÍÄÔÐê¶¹áxnÙⓘ)YVBδ۠SÏ` ËËo"Ã×Ö’•4-„äéó[­Ä±“ÓÀºOUE>þ–¦J0Ã$ÒN:<}X„ÓÁ˜mÌ%24Amˆ)Hðp³+/ï¦÷ãÎÒA9%RZ÷³%˜Ó¾À^>/æ€RW!ÊB ‘¦Õßóq~“²æÖ©išHñ°ÇÄi}È …1˜rDB<ôI ªÊyÎtUÌ j:6?KEŒS…¡ˆeš 1Ð$íXŠD1VõIáëÙµCŽ«Va7A‚OOsô÷ÒÌ÷pˆ­í “)%F¦)òl‘6”tÐÑ c¥®¸£!kFKÓäß¼â<ß1¥Fm¸e6 -™¸âÿòÕñþWÿ<6M\ÿÌ/ððö Á"µvR)Œ,Ð3¢•ªiš ó‰`F¡±‡ÎÚ…n-ïÞÅßeŒžÝ,zó;ªºÞ#‰Û1²9åQraŽSaGˆÃøÞÃgŒí=oê³ÓåУۇ3Âmj£dÌS6zíN1ÁÅùªÊÑ &b,‡‘¤R°cʯæçãµ>c)û秉¹L\¶uD7-É€‘nïŸüKˆe°Þ:§<3-¯y~z&E%›BRô…”>4 •% ’}X=l¾Ž¦ È cÌ'¬LZˆ#ëæ¡7¯¢?ÿ§³à‚õŽÅDž®Í;-÷nþÐd"ÃyÜ ¹®(…®†ô2C{méJ0Þ)¬+œËä(˜$ö–P‰äùL)… æèBêÖ·—ÃþCS í†öÆÖŒOîiN<¯;:Ì=¸›¢_š¤=º¨t& (ÿçw"ÿâ_˜ùâ36†x’ù@ ‰ËfüÌwÈaæ«7oÙ{çóO]Èl¿ýß’ šÛÞÈ“#6zpl?<·ÙÊPuÊÂÔHR1óC_Íh—N8ÖΘ³GKpK¼äe^WVS,Ed9GdÊev+Àe. £íä`h󀘒fJ1öˆIYæ3%ž¸®êz¥î˜„8e¾¹u¾Y¿ä¡6IÌ=±ÕšœS¬× ûÛŸýe,Úéä aÛÃ[:I¢ ´“¸°8eSoz"ÄJ‘@’9{ƒÒöˆ¥~ˆÌªrÙ6’¤Äh;!ºŸt´NÎm@¤”Ì^wò—?aÿ™Ð>û‚ü~0lš ƒéÍWôÓûn,Ù‘ÞscM+A„)'FÊÄÛ•þð€åäv¥cǶ·ivOñªXØÓ(ÇPZúÈÌ[#œdÊnÍ7EöÝQ4õªÌ)?c(]Ù%̆ìl[ófáiµ¡I=Pˆà‚>ɉS‰xàN ¦Îr>ÑFGûÎs¾W†RŽƒ˜âÁH]'O…ÛÓÅ tf,ÌLæ¢0KÛgÛ¨ih߯àpµÄ›êóJ*Å/T‰"ä0èGØÍ””§ÂÓ–Ù;dÛŽõˆËÌ4r‡Ì÷¼^î¼áÖä|SëŒ! 1²bŽÄ8ay¢L31Ft úð‚ÝâÂ)dTvKhs¦LðIUÝ(9óÝû‰HæË÷Na¢Ve(L§±Hí±NœNÄmõ°§m¥OX 5⨜£#T·ËÊëéÄ+Úbž¶úÁ4 õK$…I‰)ϛӂ‚„—÷.â:©>”ª‘)¸±ð]4nÒ±`޾ Ÿb ¤HÌ®‰ bE³‘ YF¢qÎÂ:²[ã†ÄÖîaŠ!0ë@ˆh*y*Ü4r©ƒ c5%4å{zˆŽ‡è÷ÊtÙà¦Ù*E3§Ð¤x ¾ùŒÄÆeï t1%EGøãÛUsJ×Á}‚ÐÍm:=])¢Ãøñ7ñÝÇÖ”ÓÙiq½·úމ²n\_ÖàÉ-ç…¼<²pŸ"ßõÈÚûåÆÔµFpƒ¨kµ,%™¡Ð{eÎ…UÀâ „Âm(YoÌG±ÇàrÝù|ŠHž¹"˜BÈ Õf”,DëÖèCYJ!¯+õîM‘Þ9÷ðq:-¤˜¹ØÅéa‡®EËÇ©fºÝ°#ð4Fc ÐŽð®33¶VPhúñmÃ3UD<”Í´y^€Ë2y£f†ÆHJž…Ñ…1`_Û1)ø8õÙ†‘»-]Ž`R¨€¶µÁÇ?œ²<Çâú™¬ÒíêŸIæ³7“Q-)z˜)‰€ŠzØewC…I¼é1<3 ôýþïéG£s¬/#ú½/Þ0 ¨væ¬L!±ÖFÇ©wC ³ŒŽéFˆ…XÒI” ERðTëuk¨„ÃŒãø®êM‚!ŒªÜ£µÁ­¯ÄpǰBzáímA‰H LåLj—Ö)rÂØ0Û"VŸ™þàwØ~éŸÂR¤Þéc'F·€·n kˆÂ<-ØèÄz%O3õhð’¸áI¯Í©gæ&=÷) FQZÊ\(­»xÚ©PWFNŒ”¸õ’‹îaðÍåkZs ­È@­¼—Ž?I@éhïÔôˆa”X¨©“oW¶ëdZˆ)'–<3åÀ d´ž|2„h *¬×ÎÃéÄ'ùÌ›qQ+é„åêØi$¦óÂ:Ú¯‰‡ßñ!Ì–€q#ôTÚz#–‚©rÛ;}x8âØ" tE脸®Ï<ÚŒ.&¡dªB‘g²¼ãÒ`{÷äè”i¾xdp~uǺ*±]énŒíÅ2ƒÌ)xAP’2Ÿ"·›‘’óm(ìýx±®yˆ)#–ˆ¡ú@ïpkU(§¿ùÿcòoý-†$$ŸYòq°V%YbÃuþñà[CÒÊÞê»NJ2„…Ûz;’?gçã†` Ëžú<œ§;¤Bäïý–‚,Ì üüÏÃÿq'éº #xªè¶ðö¸ì^ìýs¿ú²ðgg]…û{O{•)¡A ³FÚËï÷ˆvt8S4ÐÕ°¶¯X«´>(§“o–á\î&æ)11U)I˜×gžŽÃx=?ðx½Sö¦!4QTs{Ì}¹§œ€AΞ•0|s«tÛiûqAhG·¡DwÐ@…@—…9dçÊ3ù8pZÎÈê4«2p·Œ9eGmoì­ÓÕÈt, fÁ]7B"Ì'úåF“Ý‘/ÐùíkUú”¼µ9+êm£…9_øäþ5µÓ›7ìßÿ>¶,´e!>¿£ïgü`xzGV=¦M ó)MDkÃiqb„áQí— »)&Ч‰jÊ) Šù_¦‰ÛºRÇÀˆ˜)u9ƒ)ñv#?“v˜î¸¬‹äÌ×ëÓK±ôU†í¥4N1±Þvú¾ƒ6äp”ú€~ÁÓv÷Žœ¼q ¢ÔQW¤Ì\®ÎÇ Ñ)'w¥P F6E»#0Kv/qDHXp±Þ0§~‘ï÷ÀPeŒÈ4ž¢¹KÈñLÝ éÚ:1-~àép=Ñè|1%¦‡G~÷Ë+'vîâÌZN„<1¯——õ]ï95=¬4Ìîq.Á§ÖÈ­±šG¤¤Âž -]èÖÜrUÜÍd²ÎØv¢Â c°7£)”%Uh­òõ`,¯NžÞûÕ¦ŒÚQ«ÜçÀuDBpzRˆy;Š–­+u䲓¤piW¢6òh<Ý®ìûN}|íëdÛ)9eO"Ÿ²0ÈhØBÚmECÁ*•š®DnâvÐ>ÀAÜ|lŒ@·_ì‰É›k‚òþ"´XY¤³w/v–lô]\—‚bCÙCà”…Ñ2÷sázs;ÇFwC›æÀ¦ñ@¾V7Ò4›ß­i)W±3³Aë>"­5–%³õÁu¦æ`\׎ˆŒ'4¦<³ŽÔFØVœV΀) µÃ­Q°ëìxb{É8ÅÒ­Áéü}]‘1z'PÙM¸­…U/0Ò8|Š5*K™é½¢Ñœ–üí{>eÚö„†{Š¦É !èAGOäà49 O½³Öæ5áñ®]g×Pi£QRCjF,¢Ý¨e¡I÷45ÒHÄmcœÎŒ»;*ÜvRRBšQ8im0¬áæ|kMy‚òKòlƒ-c÷Ú,ßž>Ò“^BxzG.‚7a˜ËD;S‰”žØZ##nð3Œ»9ðý×È&l7c–"„í@`$$¦ˆ£q¹lôªÇp' c^&°Š„ÂèQ;1ÂÞ×ÛÕ“ ‡¶Ž:Ï¿3úŽNIjJ¯çgT?Øj`ʪÎÿ­Û`vN”¹k#ðå×Ϭk‡2;5Hôpj˜€!Ø{€tf}_]¬e‡pÒ”Ý]çb|vp¾‚0tæÜ ëD½R¯O¼{ªÔVyÿ¼ñ|½R›_^9;7V‚úŒùxÄEDÐ+AŒÈºÖºôM}ø<ìMѱ“§ÄëWèðwþ'èÝ?û¯ý+'Ö}ãzÛX·ÍmEEùáO¾æé²Ñzežñ_ú˜ù¯þ›ÉüyE ªÂ’%KÏa&Ô£@ª­s¹ÝØëê´…fÜjã“O…ðêLÉï¿¡Dç0Æœ±àtª0Ÿ!5òó{0eÿÎ÷fŒÞYJ$FPkŒÃÕD‚Q{åR+[súCmî01z…ÚuãZ+k½r¹®\®o/ï/W®ëÆÚ V¥éÀT)y:M¾ÅíTƒ™R¦—è1ëæ¢ÄºíH6ÊTH1¡qò±šA“̾WÞ¾¿Ñ5"e9{^!’ÒĽOæY&píÂÂ]ltÞ<­ˆuæÖ7¿ Æ_ fŒ/¾ë¨é›¯)eâ4MHȤhÖ:1G$ݑ„çuõñ)ýõ#ÚwÖ}«¨6joô¶a{uÁœtÆã=˜R¾yKŒ39db™¼x!sm+Aþ2)Ñ{¥[cŒFÝ7Þ==±­´^ÑÞY•—ß\ I&å~™É!‚ÑkcŽÆw¾ø.]j³M‘j,s!'ÃK΄tÆt¤s]7Úhäâ^úxU*FcÝ*…•Q/ܶõö}çÅÔŒ¶ïìëÛžiûFÛ/ävátvzÄÌÀWÏ+Ëœ™‚p©Wzˆ¤iæ,r\Bûg¯ÑÑ1mÔuÛÝ]£o̶±U¸ÕFh7î³ÑGƒv¡Ø d§hx¸ZÌ F‹5N¼^&&ÊARå~Žd‰Üº1´1%áG·Î“¹6iWwºU! <)÷tf99í¼ ˜ûyÝýF½^XwåÖ”±_ÙûFk•®vçÙòôLžFŒÆˆ™ L(w¨xÓPŽbrýìSrTÂk;[SZ¯è¸1—DVü è;è»Í¤ÓÌØÂp»Œy!”cçñ4‘$Rk%'£’yw«Ä¼ðÏüÊ_à—éÏ¡!²mƒ&‘óéž ‘3Š8UeZó#©,”éD™ ÷·gÕõþÑv´ßè½¹ÛÔ~cß®„Vi­1Lé¾ß}íêØöJcv+ÊiuÅmiÇ°à„¹û™4²”‚U¸]ßRÇÆ@.•frp tX ‡Ì„º•Ûñ÷ ‰ŒøT_ $$/æõã: Ãs€’TRTbRÌI<y¹/ëw¾ëv’ÚÉvžÇðPкa­³UXkcÔ•Ú•§ëî`‘ÖÌ5=²íÊÖV6íÐw¤—©/f0:½í´ÖÙjãV«kh&è}gWHgOEd0FuËË1hc¥:ÎÑ;·mÇú äÈ4'š)æâ¦51Co‚vHóÄn÷ÏWZœŠŸ*ÐûƭݰÚ¦lûÆíòL­+6ÜÒ¸››˜V"ã§š0;šta7£ÄÄœ 2*—ÛîNh­òþù‰ëÞ¸î+£6ú× Õö µnÔ®Ô¾q¹Þ¸m¡ ÕF§d¥àÔú6Ìӷͧ¯íõ+ÚÜÖAÝ”V7Æè ´Ú­Ñr|qŸB ‹®Cd‚tî"¤‰13ÕNzzçëä{ß÷\%Ž0´à´½V7T½ñØ{gôNÎ…h0Zã'ï¾æ÷/?æw–’Ü9-Ecžg:Æv½²1#RÀ)f†Fz_Í PB§ }?|P»ÿx}xáöâ`Þ:#/™]ÄhD ;g½9… /Êc¸€9'ƒk­Üe¯Áu„c:áE­’¬ž¦¸RaÓgtì–˜èXHÄ œ$±‹Aõ.~·Õød޼k†ê@DØ«Uôü;3ãÚ†„àt¤x¤÷™®[gH>î‡&bB50FÃ^;M__®Ä(¤xÿlü÷¿±ó—ÿÒ+þÚ_ýó×ÿ¾{Ï 4”ËÕhûJèµñoÿÕ×ÜߟiÍø/ÿë+qòÂ8J`ø ÕÃ'|Œ—çþ·þÍŸá?ÿ[¿Mk;ûöLJÍÇø­Q÷Úø7þÊk~ýw|ŒþæÂ4!1"VèÉ!2Ï…Þç7oxwº£Ý=p»¿'^¯\¯WÊbôZ±^]Ù’k;B‹,ÜaöôåaÔÞhm£®+O)sÞvÄÜÎ/ÆBˆ ›¸Ö{Ô}c|ïûÎír­Ä”!M<¡ë`#Q f6ÝѨ»bÉ©SÆ@¦Hk²‹ŠoFXú–90øêÉœ¯™½J™G¦‡‰½Q”‰é«7¬?øYô;ßGÿà÷чGƒùéy>“’PRDš0âÎ9ÍÌKâ¶76‰HÌÄœ‰{£|óŽúêÛßaúɾ"9Ó×J `bô¶Ñ·ÛgŸº}ÚPæ·ï(åD–VÍ* Ý dâ¶2rAç™Zw²¸EÝ0§•ºÓêʶo\ùq%¹[R†äá1!»–¤vŸ» û^ ax¸™îÎ ïeç«[#´\ÄH)!z™Ü…«·Ù„õJ"&¡·ÁíyG[Ãêíval+6GPÃÆ@ÆŠô™m½xË2³dáÖŒ”nm#êàÂûõÌSM¤x%æ…|:óê鯗ÓL?¹Ì…°^ñäoË3CœÓÚkÇúN ÆU q¹g‘3©ã”0‰ mÄàÂ60þøyãÓ%Êëê—PÅ–#âû6 ûÍÇà-&J*ØXi#óDÝw,ÌÌ93=_ØïÎÜ^¿æò{?ôåJ'‡Â¥=ÓÖ}¿r½?cG6ÁãmcºûÒÌT&Zš°~!AeÐšß ¦.„ÌJŽ„ grìÄŒÈ#W3/ÀÕŽ1}âðñºEüŒ B2§«X0w6RØ{d–FßG1*ëº#’x`Þ$ÝÌ%£Ü5$A|z2H¨ M‰3·m'¤g·Øž[ ˆE¢TH)K'&\¼Þ Š˜[òA#à‚æ‚'! ÖÞ˜-PÄA³ØwL‡óÆÕƒ)åc²ìY ƒ4MëìýÊÝÛw¼Úùž÷ÓÂé¶y#¦o™¢ÑJd4a=îmL}‚%&>ýL9Sr"êÀFc#RÒïÖ8%Su¥k¦{ïü½ßû]T Õ™¡æÎ,]üûíâ91%rÌÑíʲܡµ1¿b{xàöég\Þ|E_o,­£s!XÀt§V_SO¯_¿œUwïÞsžg†L§S%%ÐÝ“ØÕ'‹Y„ÞÝ~Ws¡—®Wºe.çËÛ€A…hîúöþ™eJ”˜¨Íÿ$F,9ÊQ|¸+M*‘óI˜¦È:Ôï!I®¯û@A3Žwy¸IIDRA€\fÎe°Iåù:8õ“ã¾|d½ _/HØ(Öiá|4HƒÖ›k>p£“Çň9ó¼£aQZhPæLÖΛ}eëÞ+6Æq*½7n¨wóB ÀTˆqf 2BëŽO¾zݹ›ŒÇ‡W¼ÛϺ%1TmÜÅ€N¥$¢DÖ}wÍDdw«nI~ÇlÕ°)À¾aÃèÍ`ï`;ï§ÂtÛ0[©óD‰‚6AGghc¯7æÃI²=¾z¹ç§ÍƒÃæ!"Jä½1ç™Ú<¡õ¯ÿ/ùWÿâwùâó…¿ñ×ÿ þÿw¹öÎõ ´Ñè­ñçÿ܉ÿø?ü'á?ù›ÿ˜þa£Ü?`ùÂwÎ3énáþø'TU÷ã=žûŸý§þ³ÿ4ò×þ½ÀûçA)[ ³ÞøçíÄôü"¿þï6Òmå“V±0»&%vΧ…jJ«B…÷—ËÿGÙ»ÄÊ–åùYßú¯çÞ;"ιçÜ{óQYYUÙÕUmh5ÚB¶' ,Yy€d{€A# Y–%KfB02 䑚² ™4ø%„é¶Ñ Ýîzå;ïÍ{^±ëÉ`Ef7ª2’Ï,:7nÄŽ½×ú¯ßïû˜ÄÝžã·¾üà”§'ºbŒá+re­™è,#O‘÷Î3M,)7¬T”64ò%NTx´–ßz‹ñtäÅëÏ‘­Oéz¦Y‘k%ç#ÇiÏùù[=’³mìJ†a$+EÆ#Þ]º)Ô‘.õT4‹k+ÞYΧDSžýd9Î F ‰Ž Ck×Äó xñòšÓÓ±Ÿ„Ô†2B&;0J1Ž–` 9Mäû7,ßøÍ9Ê÷~¡¿Î‡{LS(í@Jw¶úaw gͼž(EºúBiѱûì îûÎ-ÿ›ì>ýˆ²­ÄÉærý§Äê5ë»=¦^½fÔñÊy”í›s¡PTmÙOÇm#î*Ë~OЊ\ÚR°6±¦>)%súþw)û}/´_Êv8éǰ[ލÉb»ØÆ.ŠÛq$*Ͳ-<® ëw¨1ÌÈ%RšFI#ÅÝ4Pc¦¤BÓc…-^€M°:Q–™4/lç¹ÛTKøÃ ð¶±Ê èèiÚp<)ŽKc?Ä(´ieå‹Y£lÅBY>ì D†Ó#ö<“ÆÀéo#}†,½³¡}ÿë×F«™U+¯GlYy'+œñ`2nÜ!ºçËŶ”phv46ÝpXÚ–QíÌàOÞÎ(åðÞQK%3¢¤aÚ‰¤5{«®ËSî×Ì8Xr ȶçÙã=ŸU ÷/ohŸ~‰IëÎ`4)'Ò¶0kÅüòZezxÂÙ€wàFªv\…@Eq·FĸÞùê(Õ)A€‘J’€Í37s7ZR´Ý¼ýUTFKÓ®Ó>TåÝëD~|ÿ qSfbY[C¤ J/f÷2ñBÔ}Pä¥b(´ÚˆÆR¶.m2Z–XXÖŒ÷UÓËÛ´d½P9ì<³íw¬/n?ø1‹6„ÖHÉ]J¤õµñÙ0î©6–X(¹ÇZßÅô›Üe2«µÂYOÓ#A{|ÛHºorÓŒÎòáFD_6 Ø*R A Újb*¬bØŸ8NÄiäþß ýèG°ÛMTcxøÆ{Ü|ô0‘5éKLÑ·…Å;æo|ór¯úI•údÕÍÕ¸_Kz´^>æJq¶KãŠ;Y¯ž±½ó6ÃÝ´åtq}XœhZ¶zÆC÷}SH&8CY çRIE!_EZ/Ò×”Xb·LÃ¥Q[DA'…ÑxR Þ¹ž±/´ëѽ²uZ'öo˜Ÿn‰û§¾‹þýßCm39´îƒÃ‚Pk!ZÍã{ïácÂ~ù)·Ê1Kjm=&·î³)‰7O 1®ÄÜáåtbŽ ºf‘îb©\P:C³l)"Þâr%7M–ÆÛo¿Ã¿øíoóÿ?à!Îd]))c•!¨ª27ƒS”"­5l©¸RHNQ¤±ÄRp6ãl ÛOúJ"©Æùôý\òÆy&j¹Ð+•¾>9ÖÂÓÕ5ñíw{Äl]ãÖû5Òð:€N¹­hÝØæÄ’•Ì–6j-}­s‰ÿáé^w„´œ/8Eë·^¤·=²ÖbÂë€í ¯>g}ù6åp`yëõÕkJé£Fx”V™á_ ^:„Ý·ÕOP½Œª¬9¡j—ñ‰j4éxO|z ï,?ÿ}äÿù¿‘ù܇]jë’’úuv,ï¿]Wn¾ø¤á‚ôk<5ã{lÉXíúñ¾x‹5ërêˆÖ kZ ZWM.#ëö%ò~ãóÝïNüÓ~þú_ýeþú_ýåŸú÷éßýþ‡¿3w.«îËôJ… £|µXVÑ?h4lëÙª¤M|Ï+MÌ¥¨€BcD è~„h ÇTð¦ÑTÁÇžŽ8:º¯*ÖmAê†C(Zu2 i…Z .XîžÎ½…:+rQØbÏ–‹åàb÷Ôªa„.ÁRªO´!‰¢¾~Ý(K¶\ñ! Ì@Î3FZç&gˇþÚøÿñßøÿÚŸù&ßúÖÿÅõ#~ó·ÌsæÝwî_Ÿç/¾µÂÿþ›ü'ÿé' ~ì¥+ꥌw§L-}r¼Õ_³ò<=eþÔŸx—øÏùoÿÖ‡ü¯ÿžÏ_%Þ~9ògþô-éßî¿›zÏÕÇÑD3y‡³†18†`È[EÛp¡T€¦òò³Ïøì}Cqž‡ï~ÿp‡=¶>ÑŠÎ’vϘon@)–6ðì<ãÖHÜÅM§ˆI/yý‡Ý4æÝŽw?‡{xd8=â¶þJF“ljåï‘vû>•h•_|Ê4MèaGðŽÁëÞhÈФP³Â‹5šT¾Ò†CÜ‘p1wS£ö$’zL !XeyH™vš‘¦ÑºŸb¨šð÷Ç£ ˆSYN3*6Ìñ‰¼ÛQ_¼ìÑ¡»;¬qÝ­××®ÓbÎëBÌ–‚é™Á³mg#SžfvÂé½wI7×|þ‹W˜ªd²1¤·ž³½|Þ'ï§ÏŸŽ »=EÕ0øNÌ©Y0Jµˆ®Ÿ™¯¯i"<þÜÏ1üäC|ŠýdÉZâÍÛ‹ç ðái»=ùÙ³¾€ªð|ˆüÊ·Ÿñzøá1²¬ åºìHZ!m I[¾Ù‰±â†^üm©2DZ¼€8Ûc(J°ÞQ•â`{Tà~Ùh¹âT㸞øô™g{ë›ý~9¢­ÁóðK¿ô5þÍÔÆwæ…œÁ èFÑ Õ AúÆo­– .‘ W¤”¸ýò‰×o)г<¾ÿëÓ·lìËFŠ&Ne¿Kƒ›scR‚2™j*(SÖ0ZƱlÂP¢4h¯kmÄfðÊÓšb)Ž V‚@nœC5‡Ö•Z„¤FV¬J‡^d–’ˆwÜ݈û‰»o†7¸8#[£a¹šXn®€†]V^,…aÿ œó(ã8•‚©ý$«hÃjü×ÓQ%‚ÆP¤Ç]šmlU}`Õ&öëE–Q‚±ýûhŒN5“1|¨Ie©ë,“qĪØj¡ÅÌÕÐ'Ç)Wœ‚&ާ-cTAÍV5ÖÔî+ˆ€nIJ¡ªÂiEQÅ1 䉛Ç>Ù{¾ý>»7oHÛ½Í4çˆÎqÚ®âÆûM¡¹¤ŽšÂˆ±—KNýwÔÆ 0´ªºuÙBk]¸U•F\ÀÔõ…[¢sâÅ 17¦°ë˜És&•Û/ïxe„â÷|ÀòøÀyYØ>'­+‹±Ì×{æëkPŠ5vço µT–%âFK)@î›ÕÜ¡‹ õ’¨ëBñKñ•àBÏ1bi& jaïI[¤U‚2à´ ˆÚ÷ÅsÊ,_|ÆÃ[o³]]ñÚ}—é‹Ïq[¸$+¬/_²¼xѯÁӑïqÓŽb\/—šÀZ“éåÖÎVœ×Ò©QÎSËÂøáGllGF¿ÿ‹ Ÿ~ŒÙV$nÌ!ЦóóçLØyîC(ÑHKÐ"Á )õçø×ý­û‰sf ©Fœ± 45åQ}@Øû!ª“­Œ¡T…µ`­¦&OMÊx¦QñÖgó©ýÅ{¾ÿ ,÷oÇËëR¼gÙ_u¹¥R,cãêá5W^h%“²°ZÃÓy¦±á$ñÃwÞc&íky\žþå?ôg¬Í‰~ò‚(\H(åQQjʉZE C0|úpÇÇÿÛ‡ÄÔÄõ[+ŠqÔx'|yÊhQ¤Jk†sàóW¯1Îc•0·DR`š¢Å…îéè ãÌéêh¬WW|qøEÜÃ=þé·,¨R(Ö“§=ËísҾ߫¨…›Ÿüg{—opaÕ¨s‡Q è­øô~éŸºŽ¥UÒ·? }ûƒŸúOoÿ“ßC猗îUA%-Ôj1º>üˆ¸?PC`ýöäaÀñ9.mdíÈ×Ö÷¾Ùé…?þ!õù-uÚ ­²sÓ8èÄ“¢Ø‡=%W¬ÞØÿè‡<þü÷¨!púcÿۛ׸§GÌ|îïû°g»º&¾x Jw;¦7_bkÁ• e22Ž<+91X‹ñZ£Š`¤g¯†WŠA¢õl[B׎Fò¦ò°®äÖÙÉê2åþgÿQ4Ý DEVôÑŒc½à-¿:)@é.à1žh£@Ýz6*•LnõG³üZú„©R±RÉt£ÓBÉ…¤2>BT2¶–^†SBŽ»…Æ|žU)Êá‚"¯™Ò:?»äžW³©lœÖíâ’è¯[”ëÇ=óe‰ ¤õRMm´*¨ö‡Y~Æ ¥±´F^;º´!(ãøÛ¿v‡V?ä¯ý•ïóK¿ø’ÿú?ù3ßáÿñ×?çßû+¿Clš›Ñbw×Äà“ÓJŠ+NZG –¯z ž¿ñý#þ¥_¾åßüóð—õùË¿úÓ¿ûË7‘öÃO9(…3a˜0‚×¶6´dÀÑœåúpÃýqF}ü¯^¾Å6MlWýÈëôS¿½ãoïî˜j¨bXEÓ™&šª„!X¾ùpǧç·Ï©F³]Ø®?û’»œܾzÅÅ&†iÀKàZÁ©rÙ¨*&Ûˆeî™Qe:¿Øœ 4 ÆXT­lµ!)¢é™EU3Ò*&8ŒÒ¬)ÓLE5zñ,%4=î#h@ Æàïß§±CjÅŸÌ8&SùÖÍ5w-ðøæˆ%ôMnMd*F Úxb,(çÙ?áã9¿ó6yäo¿ÿ3ßóøÈÍ«{šq¤ÁК¡åÎþ®ëö– * ûÏ>çøÎ[”!pú…ïõÏð’ ‡¾ñØÿä'è×_röCŸió$#/€îˆSc2¾²¶ý¤°$ŠRx05SKÿe6æ VÍtÇköÀw¾G£Z¥ØÙ@•ÀÙ(zæÚKð;Fk±Æ`¼åù¨¸“ÂñXðƒ#ï(.Ümë:™Iú‰VÑ–eK/§_ñ¸SJ1×0Š8’v%Sª¢K.™Ö:ÕFkºaÙt)øU •DE‘re˜0¡L£åJJ™øúŽûçWäÁóðÞ»?ó{ô ßlŠ”3Þ+´µ¨šÉ¶Oúµ¾^àoq̓³XÑ;NÃZ Ê#ÙqZí×' ÚFÚÖ2Y˱âåqÚOˆÕ¸m‘œö» IDAT/ݱ#ëáÀz8pÿ3>O#7÷O¸\°£â°ÈEÑ”¦´N¬Kd«Ý1‰Ð¶#U U †&«*ª”¶æº é¼ñL^‘Kéd—Tz‘=ŒTÉ]LW;iëåÝ—ÈgŸpÿò-r<~ëÛ?ó=ö÷Ü|ñ ãºSA)a°‡`ðÁ0Õ†3°n¸?=‘W¡lšdN¾œ¸þðÇ<¾÷y7qüÞ÷æŸsÍ‹yé{Õ¼á& ´˜xàLÓ_}–µÓ€´Ã(G£Pj¤ê¡³šF.CZ½ÈךŽ(—ßUñ5Ž3Å<Ã8b¥ð´DTâ2ø´´t"&…vä|°iÇ3-gLËmXRÔ Š‡têP-¤­ÓŒ²žV5U<±“Ç”Ì{¯>á³åÌã󷍯üÿ¼ô{κrýñ‡\åqÚ£%‹´mGj°<ž6èÂU¼”vÿYj¥ j­a­ŠR^<£2-6Êïý.çïþà PE4’ª–Ž',8”rBÄaÜ¥z~øúæ%÷ó™ýlæÿöû×>äïþ½;ì8`­ë;e/¤Ma­Ãz¡•DÜŽ<Ï”uæÿúÇ–Û›À¯þûÿ€ÿæoý>ò_y—¿ðo}?þ+/yùbà|Î|ôÉ™ÿéþœÿî¿ÿ˜ãY¼gÜh— Ô‹·$°¥Âd…˜"%oäœy:>°n OÖð4ŽÄa tî¹gÂiæ°­8íñÃÀÍÕHV9öèRPÒ‚weK<>‰yá8ŽÄýŽä=Õ^²ë9#9c·ññ‰ëªðaÀzËè'Ì᯻­²áðLI¸øÄÏ¿7‘ô-}ùÄ¢ŽO36GÒÅ/¡ò†j–¹ ÏÆŠ÷ž×¥dD÷âYË3£Ó¤ÚpbÐ~ä¼lä² c£é²n¬óËéže]9}ç[”gÏp÷wܾzÀ »iäÅÕXÖy–9“ªïE¸ZY) )ŠsÞð¬äy||bX÷;âaêHIѨ˜ÐËÂxš [ÁØÝxÀˆí ?büˆ”*¡ªbÛ5.fNëÌZײ÷ï VtLøÇ#îñÜ£Bô8†6ëÜ0a°Þ¬ëÖÙ»ìÈ^÷ü{îÜòÁuìcTà©(ÓHEH)u¾¿® ‘@£³§+¾» ÊFZ–óÛé±;Jì9QÕãL˨ºQÙMøqìÞÂXM°ÍHYi4ü0aɤ&lT\ =ÚÊ^5~ðÅ—ÄõÈQžœ!:C1½`JÁÇŒ_+SõøiÂOÏÇkÔ0ñr7Ç'÷¼œvœæÄVáÙÁ²ÄDl–V;Õï•§”™¬áÙ¨9m…%wâÙÍPÙæ³RŒbjÄ,Œ–-óÌv”ì''ÒÚ! i{ä¼Í-,ƒ£éáœq1s•*W¼Ÿ˜vʸ'„«U<µ)T™$±, ÷÷_ö‚hkýswžaºÆÕOLƒ#o©ÇïÖGÖù‰ãé 3Í:ì01îoã˜B°TU»0Í:´17´ªé(R+à´ª°åÂ$™¬© ÅVfòåÿ“±FX“FML3Jkørîà—VNó™u=²ÌGRZ¸,‹5×ém:L.ìcâVøñÀ¸»b·Ûã¬á´AÜžð5×™û‡;”ê‹&&ü0±÷h!–FÖ Öõñ §u¦•Œ¢|À;‹„‰ûçØÝ>&L©¤ùȺÍÔ²2ŸÎœÏœ5œ§@ žb,’~Yæ…}®; øqÇè-H`M™Á9vÁ3/'²²—>Ï…Ô5 Ô¦©¥†¼k‚§ÆFSqïÐ6àüÈÞ5v®"Æóúaf=/4Õð1fæó#çÓ#ëyæøø†sŽ< e¿§CA§„]WƧG†ó†uë=Ö;F3 ME5÷†ç¦ðη¾ÃŸú“–¿ûë¿Æï~üCbõœŸŽ¨mÆXøìþ­fŽ/_Ç‘â=Mkd‹˜¸1OLKì® ÚöÞIŠ…R3µ6¶uÒÍæb Þyœ }0séašîGÕ™eYÉ%u¢ŸÐÖwwÄ41KÉŠV3šLn]̺¦uÛhÛÊ£Ó÷×ÄiGµ—Ï2%Ü|bx:±;/ŒNãÆ€3;ªRø–1®‹6)•Zç…’ªiÎëÒQËM¡.¨_-- g ZàåágǼ°›v8ç¹;ÍXº8¢u ¶GNr6˜ÖPf&5K®B ÖkB6 Ô 7%*–­KêC'¤­RÂÃÎÂqÛ˜—ó–I1±•Âi¿cÛíˆÃHµÇIÎèœ0ëÊxÿ@XÎ|@Yô;0…5­(„ÁvDò9&ÖXØæ•5o=J•3¥5j+ý$¥uœpU—âé§ÌB§=i%ýŸ•gš®Pº‹¯Æ‰Wçc¸68m ˳kâÍ3Ê0ôk.'ÌÓ‰ñÍ+ÂÒQðˆÅÁZËV”´²w…d&N±³ä½tžR_«Æid½º"M;šs¨1§áñ‘átÄ[ƒÃ0L ΑK"µ‚±#ÁÁF‡4Í9VRì}Ÿ®|vZ&õ¾€4ŒbFW(ưÕJpB.™|Ù5ÓÑYª4ÒÅÁ(\ÄE¥—jSÝ8x}ÉÎ+…Õ]fÑT'-ìvWäfÉÇGL3]p%—Çó#¦I,µÁ0xŠ ¬9â´éùmÕc+Ò4R6hšª,K,x½"8•ÆíàhF±­‘”¡l½É_ ”´aL¡aɵAëq"«»ý´*™FÜÒV´óµCå»/o9™k~òégèÒÈFQšÅÇä<Íi–u뉬ÀòV0b)`…N^u^±Ü߯üÍÿò'ügó÷úf¿)j-ˆ˜Î!#F+D âT)+4 %I!eEiš¬„î…è„k,¿õžøÍÿà·»Ú^¬Rº—ÑŒÃ9‡uÝHÜÊ@-Ý’[J×X ¡u]PäêhÃ!ž9œêé[q/ºõ†FaØ0J³›¢'–˜1¦ÒŠ•`ggPš°Ûá’f,™òå=¨ àÒ91¦—ç°mFB0^cÅ kCIéx¸–iEÓŒE5Åͳg´ýû|øåï¢ÒŒQ q‚©’­ÄB*¤E¬b2š˜¡´F£³£&¬ó‘š ;ßÍ„«‰XU€H+±ï ¡zÞv¼»Ç˜€Ó!ð¸BS‚.­4ûÑóTm;2ƒ±ùtä½ýHn#ß}I„ Ç3õþ±çfµ!åH©‚¶»ÝD±YÄZŒP–¶Í½ãb-…Š÷ 78b²8,·:r»DÎ÷8Øb¦ÕFU5ì{ÚØn/o c=Æ;š{éÌH5uœhÓ=fQ3MúÔÌ(ÅÖVª (2®ÍÍnÇ’7F•ï˜c¯Îm0ª›–OEášeÈÊruØ«å´mHÍ}!B¥wy¼7„qG‘@Û³ñ!jé _­¸ß„CSh×úÄÙ ;¿£µÀ¹)Æ+‡ó måyI”9ÑT/òõ~ïwŽæ'ª= ]`CplZLLF÷^G‹è’y<÷ü¸Rª MÏýZ@23[ÂçÊ96f5â¥^¸—`UÑÁq¿mÄ­0†¢íAµ ØåÄ7òÓŠR_EA4˜F¦ÁãÇ;^ƒ(Ê’” V¡Å!E“ÊLÑ ·n‡®Ýóá†=. ¬UQSA¤« ¬¥´ 0Jïy8£H: ÝˆÍZ^¬éÕ”‚a©ÂÎL-,©Q•"WݱÉ-õMÞ¥ÇQZ¥ÖŒÉBMË+hM³­u†ˆp»3x£xµ$@ðÖ’§fØ0¶™)FÒIO3Š~ª$bðnx#Z[jÓÄ&ˆWxuM]DÉì¯nqª°fCó»Qµ§m—œm J³ß1hE-ý¹+_åÞ½å!÷¾‘‚Wc`±Ø)ÍÞ^kÍÎNž–¯é;¢.˜nëуe‡@3–¢Áz #¡óÂèµ9¶5vú‘õ8©=­Ô«*,Up¦†‘ÃÞq{ØqóìºÇhZâ‹Ç{×R§ùŒÎ ï5¥ r¡, Á/'®J$½¹¿|؉V+ÚXŠö´Átzž 81„`PÒmÈÛ–xÒBysϯÿý¿ÃÓ鎥jRK«pãòBp]=ãã‘úðȶmµ÷¶t/[mú‰•6øq"¦Œ’Ä4Ž´¼2yÃiŽä{'HuÒ—·­“à¤ôhí`몘‹ÏÂaÇ^ÜFkTQ4é9ªA«•ýÐË´ …›”¹~ó†üåë>Ðhœ×”"‹ßíÐV]°™h £¶kÙZèDn–1ö»·ÝO>ý––{D¥”VBÃkKE“¥COÆ0‘ª ¶•ƒV=Žh­ ÒRïAÙŒ4aKÎ8JVƳ ”hXÓŠÇdǤH( ÙbFéNÓ(œ±lªø¸Ð^Ÿ»#.“l-#¢{!Ì)³<1ÃR6ž?»f‰ ŸG¶yFµÌš*1eœ‚š A{OÐÛq`63g»NYc$*Ad‡VLAKL…Z3Núé9ÓD0Ö2#±4ŠxÆp9ö–‚n™Sôì¼fÐ]*'¥ÒÌÄoýø‰œ~‹X5)×ÞuQ tÔFj‚;ý)7ŽçNMòFPÞ’òÊ` §¹PÊBž%mœ¶L.†V §¸ãF®…Z ë[/†ž|Vè`0CÀ8‡Á€Ö¨Ú3¨ÑDÊâ0vàæZá¬ãÃ9sʆ nljSl Wó¶‚At ® P)â Ó-£H c:{:7a3šëW@0&?0Ÿ3Mv;Åsì\ã£ûÀ›E‚ªŠˆAMÐÓ?P{QØíÀìÈRÐÄ^*­•ŠBJ(ç(¥ aÀHçç—¢ÉÍP[ÆIBŽü˜)^÷ûB^ñ¦²RaX+˜&Ýž­ZöHNà'ßÑy¹TŒÛ‘[ÁIf#I‚6ãz”H+¶Ò7Ä»VѺ‹k¨¥Êi¤jb:s}k©eG)+-—^U•ZKª„`5ãî@ÂÓœgF‚©¬‘¾YTüô„h…GÓלÄQÑàÊ7šR¬kB™¾@5µŸÈfÑýaß.®Š´0T0ÁQ”ÆHêæj7Òìˆrž<ì ëBÍKÍ9s؆"jØã½aT‚taÙER¸4…KƳó -?LÔ\ѶSæRULN›aËc0¡¼àÇ­‚FL·ë:ÓEZ1mk !7´ô“ÕÚ¶.ø‘=-ª1 MQŒBdž)±ÛIsãæ` yNk$ÅJÊ}´æiÞº5W4Õu㬷BM–  >ŒÔ’9o‘©Æ;hÝì*8´Ó¸qDÄҌǨ‹|È4JðDÝã@’Wž¡YœgN•y-Dï(Ò˱ÔDð“–¬˜!hÃ9ƒ \@9¨%õœÀ­ƒjw[÷FM‡Æºy¬/ä–{Ï¡ZÉ”’0.à¬ï÷ZchÆudÕfØš` ]8WJ+Ä W^õ{öZÉv‡Uý9Wièà±ÓÈõaâÝw_òÁ·Ÿsu¸æéé„ýès¾pgÎ÷”œ)¹oàw`Ðt:–1}“/‚²m½GcÍ_•/û÷o+ |Ü0ÊrŽPî‰Û™yŽ(¥Ñ¥bD3HåT³»ÆÑ:ì •ã,åÆ:´ªZä-N÷gZ† –XY\PxΰY4ºÂU¦Éf5'Z³XmiUhºÓç¬o/òV­ñÖQrÛåð $/c‰±q½pÖ±Î+VeÖ [-}jÝh¼xÖã²%÷l|«p–QäbCoˆÒ”f˜œF¤°œ?# –Éîœæé8S[8ªV!W†j»q¤–N‡«MÈ5¢œÁ(!Õˆ®¥-ÁPÈVSJÁÕˆsšZ…ëaâͼ¢tï.6 -+V4±4¤JÓ'ÕúBÆjÖ£Ta¯#Ãm¸å“LJ.‹¬ …°æŒV¾;m´¥4Ï ëû¤ÜJ&!;0µj×~OùòÔ­Ë:0(E P›ïB×b WÎs'Š5P*R Ay¶™„‡óžÒVŽ­‚Ù—H¦Q”a©¦c®ýÐñä-q=^æ'Ž­âÄ€¦»²Ö¸a`]7‘#Bn‰Ñh:è°ì€C<1¢iºãÝ×´bk%¸€ÑV+é+,8Ù­¥¡ƒ%7……èµ6¨3:EÎÙb¶êùðÍ” Œ£J㜠"BQa V¯×•Oÿ¨¥ ÅRµ¦:!„LM”ÂVÖj¦ÝÀ9g `”ðâå5_>>R[c­šQ Šu]YjS{Ü'mX¨JÓ\/йšÐbˆ›=aÄ •ôÊÓÃk0 ÚdD¤c¸›àƒ&çD) '‘›goñT*ÃrG3\*JQ†U:)Ŷ3Éõ N˜Ž+„ŠÊK¡ªÂš*o]í¸;-}‘!ÕLŸð8ÅSƒÿåw~¯Ìà Ñ(Òš ¡KBÖs/,9Q4ë)méÙ;³Ã‹¤Â¶E²œQ”ƒm¼Ü?çõ©qs‰cEÝ ˆÅh¨ÕôEL©+=òT`”¾°)qî¸ÕÚ:ÒU ˆêzu­ƒµˆ8* ¬Çä̤+~t7ÕQ¬KÆ».ÄÙJc0ï&æ¬Fƒx6|Ó7^>¿æn.´ù‘­n|üÅËšˆ)Óò‚ª çâ/һ«¨æÈËkƒ²ŠW³Âs)à4 r9öV¬™ÞÿX6:ÄCáZÃK#iË Õô Gi" ­—R3U ©öQ†rmk´2tÅzC] EE(BÓ Ñ] E,Ê5JNLn¤z‡iß¹½âƒ_ú~ãïýƒ^¸´ù¬XÏ‘¡Öº—µo baF¶ÛgP ׯ^1¸F¼î|zç-Ê(NI7al†QõRÝ›§ÂW¬Û‘ذÚóìݷȯ_‘âŒõ#£ÚȪ¿¿¥ÕLUŽ&Â9+Œ\eY·ËÂÙÀÐÈ[ÃúÀ)ohmðRÄ_Q‚CoG®lCiCMm5F{jNåp®£ ­Ñh=ÐŒÆä•œ¡áÀö“/¯41&N”ð”Î=‹ap½|3´j¹¿»GaÝFpŽÜ2CK Tf œ"­hsS†•IU,Jukö†%Lkk(3¢øÒ#ZÃÛ“fVšBë±<Ð8xET0ÍM=ËÆèÁj&gÑjãi­ÌÛš¹)R3¨Z¼í±¸š8•]ó5£´¦0¢kÃæÆ¬…{lÊìt¢a.“_aòµLŽk rmYÖ… UϺ¶†n‘`M?/ ß…X×7Ù©ZTp”¸P¦|±š–ØGKCI¼Gyƒª™ MÒc{É^µF`Bå08%Ä6RÑØÉÚRÄRµbǵÍĉª"%td¡¡Ä~ò[•~ò™ŒG(±’,Ž€!ºîÿpõ2Ñkg+¢Mw+äHÃà”c ™¥V´Ê8§(µpÅÜ,µ&¬ èVtÿêò6ƒ¥µ5.l1a&.+µu0Q Êt°x”(šÊ}¡ÛU VàÆ[rÔÜÅAªÕºûcD!U±µŠõ7èQ¢qº «akÛÈi™"ã¦ÐzÄ9á´ñþA¯;Ç>õÓzd£¶ŠU #ª ¶±­+ºuªáj÷Çx-<ŸžVGª…q(ýÞZ 9+âÖ}>ިעqÖ-s½·Ü¼óßùç‘ï¼ýœ×Ÿ¿!8Î2”ÌS©ÄZ¥zA·6tÐhoÉæÀó©Û±·œ9Ÿ7g¶m¡4¹LWQ×DÁVbJTV• E™Õ…NiÝa°…ˆû‰N‹d™üD.+F,JÁZŒ­§U ©x´Q Mq¼°éwÞö?¯(ì0 )l©ƒ DzðK3:Á›ó¶=þÖTU¬Y°¢0t:bQýôÆÁº†Ê…ÉjìÁ³m¯;µN¤¢ÛFÃ’”'hP*’3Œ£¥µÂ9krP¥§3h7²ÆQ–q¨ rLèhÑê²Ìu¬=Ð?/Õ¤S{L`ʉ¤{×D´a+•X )…HFg…2Ž5g”Sœ¨3`iäVXs§ÚºBkTšÑõÈ8îÑH꤆®Š¢4a72 ϸÉ­uižqÖõ€EÍ¥ZFÔ%²mú5TéÄÌN÷lUáE±³•sò  Ô6Šô¡ƒT&×ñØ©h«¡q¥R%àÜ-#KIì}@ù ÝZZ‘ØXtÀê å±zžòÆ.£ Úhhc…1 œ×DÖ½ î­%"%ÜÃ9wŸ—Ö]+J2M5œ¨¥¢MŒÅÿ—¨·éµlKÖ³žˆc̹ÖÚ{gæù¾Uu?ªìë¯{14À‚BFî!„Ü?€dþÕ§ÅÏh!$ !6W6Wö-»Î©:Ÿy2sïµæœcŒ±Êt2÷^kÎ1"Þ÷y¶ƒÓš‚Þ‚õÁù|ªq»&Õ¨–@FpÌtÞŒ’(álN«rYsL)Ë™ÆÃbtVö¾SD Ë)I·…°ŽMp”R³u]’MûþeciH™a¸e©X[™ûú༠õRùáÇ4ž£`­ðâ Å©¶$ƒÜ N–¿T„µ¤%³ûïþì}:KM ¢®ùoZµsë0Îã«…}®üúÛïsâP’È£ÅrÝäGyUÏ€âTË|×vƒ®“§UÙgF{~üðžR ¬"¢¼zl·É/ˆË f%B8›°áÛ[GÎÅÝñ˜,M©µ1Ü™~fŸ‚õÎ`b«ÓY8&YÜ’Wž+î(…PmBuŽ©hÏ ¶UE)ó`h#JÉIº…ÀTùp©Ë}ÞŽïÓ´Ë…ÛqpY”ןÿ>Çwßq|xK¹§›ÆRT&MV^­Á¥9¯?yÅ›^óôúÛÇvðýß±—¯xÿá™ýýwÜ6åek¨gѼVãvC•°—‚1‘Òhjô¾ã‘ºî1û&è’ØZæ1£‡%ÍÁ"hFâç¬5ÿs:çfŒŒ‘Û®ã•\aŸ 9¹ðcægcNºwÄÎX¦¥av÷/ß ïþß’9RömKñK›ô—÷è¼!óF?n|x<óòÙ'Îéíœf`§JigÜeseð Êûª\¹xæ‘R¸Û¯·QÙß}Å0ΈIÒ.BÑ;KµÑ¨X+ô>è}*—sá1Y‹rHËÏo1†®S±òÀ÷[æ8CN<œ'‡4öªœÍ@•C5Ëvq°´{9aôÌÕ:UOX tî8ʲÆà% î°Í@Êä• /ǤúÌCh5ƒõ|ÉXŽtÍ—`‹¼lx—Œ©Ó Øœ„œ8¤J Z l½à"';ªQn0Ĩ1 16÷|Ü "Á»mÇhL²€Ýc€Mê‰ §•’½˜¼Z*ZW¾{?i—ÕO܆s„s© K^öJË"ïŽ"õÌbƒÂA£¡²3#ØFA×”ì“ÔvBF–úB’¯ï‘–ç² (÷\p—`-`ýÞˆ f<4˜{€;§׌¨ŠN} —¬b|;ádÎ6&SJº""X]èaølNjU¼uÞã€" «Tï,âÔËÂ>+ÚV¶íÀ§SN'N&0O e IDATÌ{4éw›.Wåi)ôé¼xŠ—¦L–ÊÖsóPkEî‡zå\&}N†gGFd!dºWh…éxãy:-”£ƒÜßKÃa­+ÏèqÍMƒTbü¼ú¤ÎJ½S©D`YÎ+PcÛ'‚¨2ºÃŽs!ºS8°v¢{bW=:2%c>ð™øÒtQ8g˜ !© “Â>²û0¥AÐ)êÜöµ=·bB]xÓàóW'þô¯ýMö}ãÿþ³ÿPÞ—•1w"6ÖÙè8ÇÑÒøʸwˆ|öŒašÀ¡8z¤Ú ç{|Ëw0C)õMjËB«Êc3nÈÒÒ¢Ù!YŠpòÉ1 Ÿ;'*Òwf$*ò˜N5A}²3)Eé ΧBß»§lµêÁ12YGg¡4CìNýŸ…'¤ÇœIf“J©05(b„ª(®†I^Îû8˜޲RæÖPœ5‘Â6ûÝÁ±$âüöÌ>Z*ÁZRÈHÄjáN´%A¡ˆTZ‹LÏI~v$»s΃îPÊ9cMûÆì²ž¨U¸îΪóßÄØ¬Á™Þƒ¯·„.t z¹`ãàÒ_NŒ-û¤ÈFÈܲ×VïSýi…`e©ƒ“ㆄ¶’[SsBŒC*;ÇT–ê,M}p•‚–‚y‡™ÂEer^WúØð=‹½£ ˜¡åàõz¦Eãíö#âF´<¬O/)æÇŠQM`³ä;Î0–ÆuÏÝæƒÃ%Ï]¦ÌØ‚Rj¢RKâ”Ïr /ÊTcÑ‚–üoc:M9„1£³ÖÊ$/Röæ£~_$mŒÞysiH^n;q\ùÙǯyüôç|ÿÃ7 “îy¸X-Rp6s…–™Ê<8=­u¹àk…vÊìÝ1zN%­‘G>ìm†i>Æ >SOÞ,‰*ZSDㆠbšH3Ϭ3µàc$y(ÝDDÌÄÒMgr'&iŽŽ>X›QÅ!*¢…ˆ#‹Ë’kĈu¡»€wZÎç6âAgRi<*­—˵§­ ZÞ¦=:/Gp;jº †óñåÄO~ïs¾Û&ctj 0\rºc©$÷0Ð…ˆ’$–2™ wŽ#(5ûÖ–ÅÃU(Ò÷tOÔª‡³cyñóƒÿîïýÇœO'þÁÿüçüãÿë;.ç'´ë™§^ÝÅa“wÛ õ¥Ñ§!˜(k >ùè ÁðnØÃ§+?ùü~öó?àoüÉ/økú×áÕç¼ïÊí€ã¸¡ *}#jKI– KS–»ÅÔÉb`µrx®ÒÏ­%ÁãTwôî–¨Vˆ¨ùp>&§¥RŠ1\8­ Ó Çð¤¤PèÝ‘’‡Ñ$3ä—ÎC(4¦Y®g§GÆI47Šˆß7k~à3À-_Û3sŸ=ð9©žÑ—Ûgï|¿~üø5ûãËó•7ï^X×êò@Xát>aT ÏK\5*+/%ó—4\Á4ÑŽmiLÍ¿ËGO8Έ‚˜$ú±4æ„¶.´šÓåC”bU;ÂAÜ¿¯.i£.µ’>ް’±‰ûÅD§Õ…v·H/-5ôE•õra GdA#ØÇ`ëƒ/ÎÊùræ]WnÛ”4‚úX¸\ ß>OjùÝæÎ(­¥ö0>¹k|ˆCXÁ«âRÒÂÜ#¢5Ѫ ™6Vq†´ÜUKiàí‰)|óÜùúË/ùê7_óý»·Ü^^c"XŠ*£g<±–ÚxXÎ|üñk~þïüûØéÂÛo¿á¶íÜÆàzëˆ *“>))ZRÍg^Ó •Jkðrt˜ya9Øg¿÷Q2R1މ—ÆÃiA"'ÜE K@[+ ‡ZÙÝÓdâ~ÇÇŽ@Ñûÿ£2º°.‚i¥GÍþªŽL]¸£¥@Þ Øe¡, sÇ㜕‹fv—É&¶¸Ëå±Â »´ê~GíÂË÷!Õ UMË»äP­ $ßZ4£w.i|¦P#‡‚R<ñº^)5;ö7ñW~yyxâeÛ#p…ÅœÑ;}*O­ &|óÃ×Äê² ÞèÊðIHgŒNJ'ÆÁZY.|ضl¿;TTÒì[› µRžÚ‰—½S|§4eÖÌ™š7nžò¶d!Ïòó!JW°XZ MéÒL¼x!rÌ锊¤˂ªB;å¶Å tÉ•v‚†„â½'c*Ïsã:• 0&>-Êê‚Áw×ÁËv¥˜ÝâãàýgDížOî°Ì19™1#§Ôç’‡ÌЂ•`;fZ,'̓2…9§šVϱ6þþó·9ÎüÿðŸógþÃÝ·µ.-D¿rôÎÂ¥ú(ô!ø¼*H1D÷/àó ©PÏ+çÇG>yõš?üùOùÓ÷oð³O?a}óÈ£r{÷ÂËË;–Z0œÇ¯>þŒ·ï?Pæà„ðÉÃ}:ûL"ÊôH‹ãÞñ"ÌÙi1yýôÀÎïQ)6™sAòÔ©#§—ÌUŒî¹ÂoÅÙ{ÐÇd8MÓ|x™*µLzïÄ>ÑÓ’€±s®³Â gŒÈ²}HZBåþ»™ƒy·}o½3ŽÁo¼<œòÎÃÛ÷¼þpðp>qZÎ [9¯ÆZv‡O_-¬KåeL¤§5zjšf™Òm¢eÉcÔ F+†Ú@ÊJ˜²Zv´æƒ÷¡ .kæçËtæà>Ö{Q jQÖÓ…ë¬Yl–¬¥spæwå+‚{e¶ÖR¡X-”Pšy¡œB¯Â6ån4þúvp;‚Z ¡c,U1ë€ÐD¿Õš¹ùDÿúÝ´ HPÖR<'$c¢sp^5e¥!÷Ãã¡ÂDµ²Ï$ò‘,ýC §2L3BmBÕ•ÕC'ûýºVÖÕ(«ñÙ'O|þékþà÷Êïñ \øöÇïŸ>ÜvLœîp¥ X Ž0UÎ’ØÑÐÊqÜmŸ2“äV jÆ.Jqg‘Â៼yÃn•ç[§ÚJ÷އ)Ð- ×kâ‚wO4çS«tŒ[N¥Rו}@)Á²,¸(G(­6N¶phƒM ‹çAG­2"¥u¥5†']¦µÂŒ¼žªÜ ÂÉ*–æPQx²Â¬…} * îYªÖR©1Y›ðW¿xäeý*€®ØxaLádºä!«Ü§ºvÿ÷e$¶T V@ãÀd`¢ˆ”ü¸aE¨ª‰´†EçÑò§ÅŒSk´™¿÷¡5£^š¾˜Ñ_:{й´JÕÆ·vÌ2‚GÈ]@&äppä*jþ½¦ã»f,‹{¤õã‡3á;#m…"p¥aõÄ"Îì;K«SsZ:'ÃPçÜ„v•å„Yã²(kžGnR‚¼ŒŠ8…‚iÒÞ܃Àatô/;Kgì^x³6o̲`¦ì·ß|ÿ–¯óþÕ¿ú ¿ýÍo¹~¸²mÄŽka¿uŽí9#Z"MAÚZ&¿þÕ¿äW_~É/7Þ??g`›ø˜L>z8ý`Ô’0±±,ʶM®/W¾øø¥žŸ9ŽºZk\?ì¬Ë‚-- ‡!P³0­µäg}QºAÇf§¨qx>¡ µrÊ‡Û ×#˜}Ò$˜E2¢ÀD´æ¡+`œx/ bIÑR5TWj1<àê^€²Ð÷#ŸÛ3WŠÖ.”øþ1òÜTªóùë3ÃÓ0½cµ$ñmNº–,{£Œ 'f5ÆÈp“L­´²p:-ìÇN±BSãù€Ožy½6b4Ž–VXlr=„é™2Ç‘EÍ!q`“W …ÆiiyÈŽ‰{ÃåÊyYiwlˆðxª43¢Lk˜$|¢ÐèXk|Øò"¡¸-Èœ˜kÒ÷bðñÙ`§åÄÙÞ]ßóÊC ¶(Ì퉃³4ÂgèÏwñ&T1¦w†çûJ ÊÝóæ"ܶ>zzäñrâýó;öP¢TFœËJ¨óþp¤,÷Ÿƒ1ædÐÓ}d+ª†F¡©âî ÙÉ”ÙÙÝ(’qG÷@Mï}Í'#hk4« µ¼¸—8¨áøœàΩäPfâI´B-)ýMZ¢QŠàãýûÉO~ÿ—Â3ÇPB󰩘;.äÅû†û jeª"ÕBÕÀgO¥t[xÞ;Û±åAÃdÐ}pDdÖÏ•)ÁçÉø§¿øáÆõ¶Q«à r¤-rL†O–e!|ÒlFÞ¢pö­#\+‹LÞ¼ù˜} f zmÜ;bX­´ueçÍ©°®+O‹òtjèz‚˜H Ö*<=½a;¶,„zæåĦä¾, .$:Ô`%×mÝs-í‘Ss@:;»Ôì0<¿çé\ôn„wjŽ™E¬b÷rU»?¼ï þEk•i)µ™¦üìUå~ú_½{ÏËóÅ’¿Ùpa¹ß¼—,E¸>þþßû;œNgþÁÿòÏø§ÿì–ÖP©X1´ç%_æöIŸAh⺬Üsª¢¬kåa©œ/Œ9±ræ±ÁýìcÖ>çôðÄù¼òÍÿâ7ϼûá>üð/ïß1®Ï¼{÷Ì>œ9z’A®Óy™‚U£,Q‡èÝÀíÄ ×æ¥%¦ë8p‹ì«(”"td\iÄîS¸e1ÌÏ—„ÍòÖû`DÏ5çÁÎÏ ×Ý0‘œL‰z)K%ºsÜ…E¡©2×ñ˜ìÜ¢î\žo|ôã•' k;SO—ä¤×B©'ªˆ^úä´6¢®ìÛA­%Å&}r9ÒÛ‘Lc-š”»ÑµYRBB¡‰b¨YN¶¦äÁ§£æ¸ .Jk¤°‡ Ô{®ä—Bˆq›Jåàñ’ϹÊ>É‹P~:™À›Ç•?úìß½ì(I€Bó2éb(ÁÒ’‘Þª±ˆ°x9f¾HçäéN ša<•½gB4¸X°œÎ\{G#˜]ÀJþœ-3BUŽÐJK!™ E‚‚»pÒ ÌÁd—Æãe¥“V…W—åîfH:ŒÕJ« k+Ô¶p݃ož7~ûí{~xåÝû÷ûÆíÈL° HLFL¦(E…½,ªˆ‚»qP8"û sfÿbVaˆñnÏ1é1ˆ<.`Ò±PÔ`‘œ™ä&E,§ÜÆ´$Lj‹%ÝÊçäðJ“ì_Ú˜Å(2s«TîÅ»›àð‰Cí„k!ÄïñAãlËc££Hd¼2Ð$-D0Ù=8È GÓž>Ua]V®ÛÁçOÊ·­SÏ uOÅy<çtk)«Â¼çýÅòb¤€ÒÜi%­¬£÷;*°pHJ»T“pt*0G.Î#ê¹™žbTƒÃæÖ¥ñP³wåZÑèI˹o÷=/Ú¬‰„—{\Æ}fô¥Þÿ~t( 37(%…OÇ<­Iƒ9<’2‡°4CKn­ˆìfÝ:œê¤ªÞ ‰Þ•(hQÜŒ¾í˜¦hn´Üãyðõ;*ŽÎE æwéæbÌ,U8Ÿ²„yf°ÞŸ©‡­LWÞ?oÜnÏlÛÆËÛ·¼lm?r«ãÎuï™ñŽœd™ôž¯7ömãùåÊûÛ–¬ûmP–É1ÃaŸƒãèY 6ÉKýÔ–;ƒ~¸'HFåºm”jéWðŽøàz…ŽÊ‰N`Ërgëg„øèqË‹ª.ŒÈçE)Xµ,Éλy:‡)·Å|ã¤/FŸÂôìDÅTÎÙó3XeRôÄueμœÑ™^Sì7Æ}ƒTè3­ç&Âï´ð¦ Þ?*—3Çp¶1 žh7–Ó‰¢Þ9ú µôu‡vOHl!9%Æž¹óÚ@§þæÕ™Çóä±·¡¼øÄ*øy¹î¬EZ8ö`©5Ebž(°ßûôÂû-hmáó7+—Óàõ²pz|¢ßpǦ*û1î݉HôâÜYïMѼ}ô |òt!|ðrÜuÕ1Ræt6ÂÎô}fö0µ–Œ‘XN‰I‘,ùª ûHLÖÚ8­gfäDTTX–ÆÓåµ;Û1y|8±.'Þ~óŽçã 5I#à˜L:…Õ%oˆVîëò’k¶€ÿö¿þO9ÏüÃÿõŸóOþì›l¸/‰Á›#؆¢ÇŽ;ŒM9UxzýÈ-ŽèÔÔ¶°»³m©…URfÒKaÒ¸=¿ç¤7þéW_ýù¯x÷Õ—¼ûö·l/ïÙ¯Wnó`?@™Œh°¬ŒÞ“¬¹ Z«ÒZa¿Ý°Úšë·ëíFÜ)-c:‹Ëj”R(qcñg¬­Áу=”¡-Ð T”)9p¾¬ù%Og‡1ŰHœh½ösâ Ã=Åx‹0,%b³æÆGšTCîåּذN¸ôÉ9ŒµœhˉÓeÅê™ÒNœj¡¶BÓ5&7²lk>Ø<  ¶–ѱ%ãÜ1}Å2Û,íD„2}³d$ÊÀ˜|ñ&sÎ×c`š«\<¨Ñ9Fêùjä¨D«¤Ë¤(GÀƒ.«1Ý8|ÉŒ£'o8Š2f¡i‚¶Y3ºgÜîÃY÷ÂÞ+Å‚S+p§°ä ÂQ)œN•𞇱VØ}0e¢÷ƒïÃÙ4úpFϵ)Q¨eaD£ÏIYµ>PÌ9ân°VeFa50ñ\µÓ¨h"—]Ïh¹ÚÖŒWìs¢5ß|˜%ýkß;Ǽÿpã_ýŽ>¼ðîºý€›£Q˜: …. 7¿ÇIp†”ñ¹pŒÁ›"L#]'%K¹jp•Æã,š}Ô„=”óbœj!nždó 0¹jÅKÆK* U2Ó-9] T=Ëoc•ð¢—%§Ì/C8$×ÐîA)+ˆRHcoJ[ o ·=ûfjcRcš°xÏ‚tSt¨æabÀý¹Z˨OzOûHóõ4åa…Z:®.5ÓC1,#’'ÍCË- ?5FOür:ÑÌ9údYÎ,U2Ÿ>•¦†ù†¶Æmæ¡Ó,¨H=10Zk;R¡Ï‘Ÿ1Y8“6ãÁ“Ï/HS`°M»—Š¡éä©*×9P áÐL‹ Ã…<µÂÍGnÛ¬6Dò%¿Œ-§øZéÄÌXˆZ!| žÖSv¯Â±¥â3 Åk:I6ÏKCi|®ªà;®Æáai ”[ßj‰«tèÓy]•7§GÞÞnwi“ÑÙ8·ŠÏDÏ ýèÌy0úÁ­ï„÷Œœ•Æ*Âm:×­sÛ6^zp»¾Ð —`¸±Ïýþ®„V ~l´Òè®øîÔsår)|óöÊìq§:¥c=¶-»h©\ EFÆQˆÀ€1„¡AD0E³ß'9ì¡änö‘‘Ób*^VG}p:­L‚u9ÑÔØŽQXŠæÍÂ~½aUùè©fO!à'¯Ágòê‹~Ü’SR‚ZÍXªSô`mF«IGSUÞ]ûÖ8|bkMÖìÁî“…ÂZ²kED’~|œz¦@¤±–`ß7-\ÖÊ_úÅ_ç?ú›ÿ ß~õ%¿þðÜ(¡¼ì}:ë ªT:­(O•1'}DAï?Gd0´ÒH»³–`Ìô$ˆ´ŒxF!Bñ¸ALB³°Ãc£ØQyéÙ)Ë ´ñþÇÌÓWÉNçTeÅSšæÎm¿‚)ß|øŽŽ—„`´3÷€ Ǭ,œZeÆœÖà´6nCp)¨Ì–µ2}C­QÚÂÓéDø•~Üè,EèCPS´µ,ôqC}8¥-2uR¡µŒ:;Âùt¦ðr\‘P¤¦)E³Ìî2ówLÆyçÝÃf¶pí“mtBŒ¥”ÜÌ„€1v½#Ô•‡%Ÿ¹öÉç?ÿåáŽxÏBªUâxa÷‰ÄJ«ðpjY´â„‰à‰ü å´£On)ßè;?¾ß³¡m¹VkzÇY©1§ƒÒ 1hcÂ0¡Y ¿[?…!š+§§ËŠÔ ÷:KÉXÎŒÌm¹:V–d÷–÷IЈ0”ÍH’÷Û™¦)ùØÞÓLXêÂ'KçoÿÂßþÏþKþì7ψ¹S¨%§3Pjrˆµ!qäag¤&þ6:e¹$zlÀJFŸ'KâGK>õuG-0[XM9-`1YZ°³°*ÜnÃG^l¼ðÒ'CfÊÝÄ8¸ÎÉ·[¿3­/xÀR—{ ¦À½t—83ˆ9ùh­¨ÿÛÿþçü÷ÿÃÿÄÿñÿk-÷¸B¥´‘…ë·0Ö%‰OýžO5»O¢z ) òB±AØÂôÂ6cn|ÿõWüù¿ü5¿ùõ_ðÕ¯~Í—¿ù-rý‘—­\GgŒàÔr ½EÁ€}NÖ¶àѸ'ÌøüÑ0)Œ)LÑ´kÃt¢>)óÊ/žvþ½?ýüøüBÐe%d lÇ•nݡʘòáÈ’Z–äb‹u^½b£æ4O"ÊmÚ¢4189­Ð Ïî°;ºš)LÏ «Ž!œîÜñb…s«h«D½Ð4±ƒV+05‘“ÕsãA)Ë©ä÷A¡”–h=1Ô §b‡ça—¡hIÞýZQ{'+¬-íš*à3’¡Aµ…óÅ8z"Íœ Ï Ì,Ô©dž±,Š.Þ>wŒü™¸dÖÚ4«ò¡Õ¸w,/x}gD¤—e²”²PK¡"¹•Sáõ¥á}âm©\ž. —{l%·póÈ—¼«²¨¦¨.§J³Œ˜Ä˜˜–¼x”ÞñáL¹oN¬° GÜh:›ÇjÌ, ¢„{ÀÔSJUhÚ8zž·!8æÂT¡…güÀ”+[?¨>8¤ädÎ 0¸Mç`bV« y¨P¡ª dOHENÌÈ÷³ HS†jn8Bî÷CD°èN ‡èT‘LÕIÃÇFŨ¥áýÀ%(¢ÜöNŒÁ’±!:Ôc(2vÞm“p–BÜ`s¥ÅŽ¢ì¡ÜFçæ= QXžb°÷ƒ—#ÉXÛíÆ¶o}p݄ɠZÒ©˜3]ªcÒŠqë=£ªÛƶmž—BõÁã’]œ­ ¡‰vîÒRj×o|Z•c¶¸J§ãôâ¸rsAl¡ÔB— jåR²í8M•vZyÙ^_„Ëù‘m&¡®˜R¬3£0Âï´¦PN,–åÒˆI-5£Ï{†*”Ú²”::"A­Pî<üऑ?#Äð1áÌ€uX ¥’ŽŒ•ç]ùòí-Ó ­`µRÌ3-%7ö·ë‘g$) ÉïP•Ré™L%Ÿ÷X)…Ëã#:íüÄúæ#Æ„o¾ý×|Øw|¬ Í$C[9ãß’»Ž£#bˆßáÖû½È»°@¾§0zŠÃŠ8ç RVü¸1¥3Æ$JEC“,TÑ…EHD IDATÅ&£ßØB»k©–,ûK‡£OÔ”©ÁˆŒ½Å±Ñ¥#<sâtʽ§§V¨Õé½sí·Ä™jL–¢ìóà@> ·c¢æ90‹`=­øhLé„!7#´0nÏÌP\ÆŽü"ËbÐI&^æ`•©¹ÊU„ÖÈi\ßY~öÅØzâ¸m\Î çµåC‰@«3úH«dÏÛgÜé+{?cDHª‚i¡š`R9Ûàgü³/ØEù°íüú«÷\_Ž\Ã/g”Bاé`–ƒ*Ð.'–UyÍʈNEdÒeõĸÓYÜ;Ûvp½v¾ûáÊÛ·oyÿ¼ãû3ÛvdÌêxÇ›V«,²óÉãÊõiØ3¥Y>Pa÷àlð‘íüÑÞ?ßXJZ5ÑÈRÚR~úé…ŸþÕ¿Å¿þò+ϯÐ×?å‡ï~ ×4{ܧø% L’7nU‚1…^ú–ükôD 8­>ïQ1»[³5Rq¨O$4ãSShu½$-¹Äç B%|æ!Å3:S0Ìò%SkáˆÆˆ,ï[-¸””î4ù7Jô“Zì>™Ãj"ƒ¶²˜ÐçAïY>Fü›‰Šª±ÔDî[‰{_ÀÝŠ-„$‚ïÃÕÙ`]Óä9†$J× ß;µZžÚú„Ò*Ǿ³šðpz@,ÉF…à˜—’!x8_¸õ”=œm›I…’ILgx –›ÃUgĤ«ñªUæœÜFäæLZ©P9­¿`µ<œ¸ kÓÎTÙfnú ZÇuÍü;Îã90½p›“‡ §ïž7^®G®iý–¶k|âÒyéŽ##˜áT36©,2¸”™[ ‘æÌÝê}Kù}À—W|óöûŒóÍŒüÂ.žî3Öš²Á!ðX³\íE4c¢hY‰\ŸfdÎÊÐìë¨Y îÜñ;éÓ¬’ñ¥œ¹b†ú‘ÅCrÛ1qŸw¡ZPH)Ò!Ê©(­-1ðCÐÖrú( ·ýFUÅŠfìÈaŽAød½ç‘%Pc!½CŒm‡¾;­8¨ò|=ð1±2Ò—ÐÔ8î¨í÷¡œÚì¸;-ßí‹QkJ$ûH*U÷Ɉ èš%j%mßÓ‰™‘­mt¶£Xí}rLr: Ìy¤%xNü.±c$Æ|&x$¼ßéÕ¹“mB)†H`jhUNKáu]y¿ œjéq´$IÈ#»Fç&˜¤3#b2Õº²TãØwÂŒWkþÞ÷™‘K4¡Ä Ê V… #jcg’ôp0­¨M$*µ.$÷(­Ú¡pZEýà´6jmìc"–ÓÛºž -™õoÆRîÒ«hØtv. Ó³sÕTèû•Þ##ÍÓ9fctL„’ßsËDȸp¥ŠÓªa¶k­œÏg*Îûï¿ã«ßüÛve߃Ã7NµQf E (qÌD…›‰~öô°<«ù@$rx!…Ù7.gjm„E3>Ô2†%z/Og¡»ªå¡Õ-Œ›£*Fï9$Ä3ž.¶b£§Ï£Xn?•ÎŒGzµ,:sDJæd‘ß÷µ(EwÄûtš8s|öô†sTÞî/9ŒmñÛnG¦00Šû>؆3¦rjÊ¢A‰‰„ÓÖ…/Ö'¶}K™lIØÄvL„ì` ˜3ËêâêŠ4Áè˜;‹Û4STŒ! &IFJžPv§Ì(c"SñZÙÆ–ÑÂÒpïy¥‹(Ù|þÙ/?9ÆóËsšÛj¥÷Á~þħðs¾þþÇ\[kfÂŽØiª|öñ|eôgÖ5Mv[ïôcG)ÉýB®ˆð™?Ï>4>if”ˆd*KN_ºv_ËwÖó¹Óo¦'Fi¨ÜÉ#•Ɇ“"A+†ÏíeG'DM"Åï‚¿ûŸÿ|cŸñÃoÿœ²4vW¾ÿöþÑ?ú?yûÝW¸w^¶,ÆL?c°Æ<‚)•R/ÎÂåẳNŒ‘Â)5è9 Q‰”Ø0˜£Ç€ÂVz™«d½mæŸ~üH]ûõ=‹V>z*\ž­=@(ǧ¥7¡VN§å^¨ž<Ö,§™;¦Â'+¶œ³c°$¶óQ_øËŸUþÎßý¯øço ¿ýêkJ­X©÷Øï^Æ)Ϩ"ŒXا³ª#ªl3rræ™;R ãàõÃ{$'¼Ff:cô—Û·}òòã ~¼gÜ>°oÑsÿÀ›§¤@©çüÃ1釣÷U×BÁ™‰ÐYjåÿÒ_᥾á·ß|Iq’“³äæiÐxû2ùñÜzðãKgÿðcÐJÆzÆØï—UMðPdŒ$ËYù€"0®KÊ«ÆÀétë7DñÈB[ GŸt*mŒŒÂá¹Õcm…9Î —157 ETТŨûÁqäÜÐcÒÉá ḓ™þ¢ÞeG¶,IÏûÌÖm»{DœsòRÕUEv·Ø-v“-J€À‰ ‘q H‚ÀE“šèùA€¤)ªÙtWefå¹D¸ï½.fØÎâ Dæ‰pß{-³ÿÿ¾zm-&<}„4EÝXѬÊèÂç5ãg;§Ò\p¾ 0»ÇUV˜5Ñe¿‡ü)Iu÷UDÐ’pI‘SÌÊâ$fhÄ5'žZ¦]*c–+¯_¾Àì¬ÒضÆêƒÑ{ÌRæv©LWr>£F’H©Rr¢H£Êâåzá> ª“…« 'Ãi8ðò|Ãó•ϯw®YYµp?Œ’•šÏÓ‚KÒ.ÕÁóy8,0¢NÒ°V‰Ë’¡|½ Ú.¼Ž˜æj®HrNj••¼v0ø¼¾Œ…ñs¼/ª9‡V’Ä ÙJÄùjR–/—EA¼iȪ„ èšã:,øé 逅üIŒ÷y°‰QåàCÆåVX^Éšh¾b{%NA(âl¢$p—´T¢L¼œD®‰-‡àÇsÁò†ã|ºï±>ùúåÆX MSRËìnäü@<¤fE„\$¶^¦˜ E!ŸÈ0|šbÎ̉ |üqç·¿û-Ë•5`wå ’kaZ”Í[=©=šCöh- ÉBòˆ5¥=ÑXmÉ8‹’7¦dÆŠKéVÞÚ=6^"aESVBæ@OJZƒ–3CYc&D2_?oØ8˜f8g•šãû½’ ¥d’/2‹î°ùŠMˉÝÍî _|¸mì=D”Jb«¥™¹â"ª`9b“>‚«&)z*Êißýòs»7'03¯ˆ!]òÄ<3-¨kÓRbK‡#.^)ùÙsˆýZ [0ÖÄ9ÎhŒIÃ4°Â2Ø(ìë`yHû ”«ä¸| K±ùYŽäÐo&I$R÷1'ÿñÓçð•Ï(‰áˆ1ȧMRaxæ1åŒQ Ü¢O¢©D§HåDTwÆŠþƒû¢÷E:·èšË‰ mù£Ô¶¡ñeØBD(Yæìkѧ°¯¸X‹†Ó嚌z>VjôµÈº0óø½ÚDÅ~ÔÍb¼ÚBt£¤„éd™q CK£nOÜ÷7^ëà–kÀO|ࢴšù“¯Þ‘WæwÇä¶e4k J-¼¿6žŸ¾áëo~Ix{tæôiÌÝY)qÛ ¶̵¸´|Æ39yÀÜéUë"¢TWÆ:p9ÈrAVˆÝL£p¿|QSÄ¥æÔØÒ×}KóŒyBæÚy÷ô‚šð¦ñoM>«¥.ðm ÄÏîÇ"Æu©—ØV¥Bb“Ä‚5#¡…5ã=üáúDöÅ—±ó:.¥œFï¥Ó’=^ %5 ‘u%ú½[QdufJ¤oÞÿò×?BN Õ%ß1OäíFzzáóŸq;9ÈËP34 i»pôõÎZ‹4&kýôrÌÏ×L»]ytçÃ-ÇÜcš¾Ödº³4#çm+Wž¿ù9÷·Ï1±vcÎŽªð~+&5äÒ²‘BQeqbIEb‹P/$·¸ìäàCËüíŸøñÇß!cPrÄ‹¾ìß_ùáãgûb ‹Âà\-jiô5éû äÆÓ·?ãoþþÜö@By0ÂõšÜýo_2ïžn¼®‰æD-™-9[bò¼ûÀóûoøx%÷;írã—?¿ñg¿¸ðçÿô¿DŸ¾fŒk)|9îøQ€9<$fž©õÊV_Ý6ŒÌÇ·±Nu0*­%P˜ÿ÷_ÿ¿ùûcÅá-WðFIÂnÊBYDî°µî™læÁ”F½]QSv‹F»ÍÁkrõXOør¿3÷;iK¼Á<&³OîÇàÓ1ð™øîóbÔ§wüð:Øï3n¾)±’aSp;Ð"xº°›òïÿæïùî»ß ¾BYoàæ¸Æ²sf>ï§Qxtð9XR¹Æ;ª™M…”¹D?ª†yŠŽBRT‚Hƒt®­0NE»yt3’*o(Õg’46_?odöY'SÚFÊ9(WI€‚äêø¥´´EÑÊ”!JA¸¥ƒÐ-QT¸¥L·I߃œõ{ƒ5Œ±„—kæé²ÅFÀÇZ䔘ǸԲ‘Kc#$)Ë'Z# T;1™™‰sœ|æ” ^*k*Ev’¯x‘3iÛ•éÊã1bzæ“—[ã?ûŸ_?s?&·\©[åáJñXâ·¬\/H8K‹Œ¤`ö×* ‰‹CQŲÆtÅ#.õ˜G·ÛˆrÜwZI˜F¡«¤ÄûêÀAQåAa¹Á¼A-_‘¥à©•¬Ê-oÃùx ×-z(ûP( +0—Ÿ†E<¥H.á_iÊ\A¨JY¨L޳˜›!…ÃØ)´’‘òòžýŠ@N—‰‘¸£cpÏíål’0ž®‰Á_üÑÏøóüØïoìV8|ÐSåR+ØZèr–E7G5ƒfrÍ‘Û^k9èCZ iu9#ù‚¤¸Pµq&¼#RQiüpìÃKµ•èÓMã Ov.-14óóë;2Æ[ß™\ȗؾöÁÊ%¼!9º9¶e»”肚AŠgD·AÍÆ‡—+ûã¾÷E­•nÀôð‡xÂ%†®Éáêƒh‰çnbÁˆ¸á% Œ‚¥JJ“¯…à­Æ`ˆ¼qÌGqRŽÎ~_@Ç—$V‚t¢I— ê“Ô^~õkw%’Ž Y0õFʃùé/¿ýó‰ªl|`ÄÁb¯Ì½G¹IìÌ ‡BÑ ï¤Â²Å~4ñ &¬3¿ .‘DF+Š¢†0æ"†X°Ïçt–Dãâ3=Èjš n!OÔ”X;xŽpRÖQ_¤äñù_FšãŒZôÝ•"+R™"äQÞãd ‘X÷'þöÒ6j«<hVnïß}XDgÑx` áRù•@¦NaB.Ž,‹3Äô¹H>™6m¬5B^å§uU…V”yžöc Ii¹’PJbŽWÞ].‰±qú½¨\ îNŒ¶ÅazNG<Ÿ‡WwÔ•÷ÏsÈZ,Ï´K‰¼»È5оDdJ_?½Ocóà¹áÞišOJP•4Å2$-±ÅZ†èƇ熯IŨucH燿åÓñ`šGâb .¢¨ªt+=»Iâñ,!âŒRæ~¥R*¢Æè“£ïÜZ¾öXò›¿çã~gú)°ï÷vɤZø‡økþáþ?¦Ô„ûã•aùHFÝ×=¼1f‚ø¢f£–0x—Óý2<6·–Ѧ¼~zyŽˆîDϯb'ÝK~*ݸàœ3)h¿\Zù$ µAÝ[™,†–(mO[]‡½ƒe Ñ…y Sƒù‘&*B)ÃZëÙŸ bÛ‡§5 {F5.E•\·ˆÅgXçóÖRFÖ‚uJ~9å–Ĺ&-ܱ)*ÉMÑéSÝð¢±Et(2°øÖA;IfY2nbS”ëVÐu§ïÝÜä!ÔNʱݬe#ea®HÏlIC¤o¿úů±P—¿¼\1/ÜûÁS‰‡mJ%>äñ`,CJ|Œ6a¯  >À[˜T=6>;î3(0"t‹u‘ \^n€"ž0‚uGÆzŽ7t B#ÉâýVxùðß}|eŒIŠ{t QmQ·F—(c”9™£3]X6iµ0¦Ä-¿V7<7,9ÇXŒõ¹z¬7×Z WlF=…i,v‹®Ù²‰ù º`Cq74 9KЖò´pìqi+ªÇþˆÈUVþìý/ña|²ƒ­lhJ§‰Õ©"ø*hœœn!ý[¶°ÕÏþ@ t ÎÆ”íì œA ²Z+ô1ñ³ç41Ì”DàÞY‘«¸ +Mâ¶Ï…ÏLÔ¼á¢Ç¾vj¦ s(­M}r<_î¨ÐvݸdÅ×¼Ü K4¢V’:}îñê¥H è’u¶lL‹sIË™}º‡IÖ™Ù:B—KfŒ?þ–_;ŸwcÓàÚ/”b™%0s ¸EsÀ?\ðTñæ¨6ã°×bQ_{\ÜòFnW’æ b–j¡¤ŒxÁQŠ$DÂ1ƒ8óþÆ?ü=÷þà¾Ö¸3æâiÛ˜kDìJd2Ö<1¢!¸›¯#1æ"kXáñؼŽýÀÖˆ(\ŠD﫜yü —ÒØJ¸Jš. Š v¤Azcçm,ö>È2‘œðXŠ­°¯« i{FÝÈêŒ!ç3“K@(öÃ:ðgt#Äk>zH Œ9È-£bl9ü Ž0§…Ca>èÓIÉ6Às`‰øûp§ ¼´Æ°ø¾o-¯3.ÑÅÝNºçbÎJ’D.1:Ïf–¢wãNsáZjlsŽINDdkí¨KoŒ~PÕâýêFJ–"¦ç³“µ’¾úù/~-%ܽO–;—¼Ó-äTSÉî _ƒ\”’% Ï—´‚j¨aÎ8(%n%IœZ/dâA½¯À‰Ú°p!XXIÍOËâ îö‡Ëß|xä‰}v¬÷Óâe|ót ]ûX ÙŸøš‘ xòJ |Úµ]©›²úƒï¾åÍ…ýxÅ{ÃÝIA™‡Á>FÐk|1Sˆ1²'ÞÝ¢8ŽøÒÜÚâÝ»?|÷å,«Ìßg–åäž_ªâ&爵TZ páÞ'¯cakòÕË…¯žáŸÿg¿à¿þÿWþú»OØÇÇÜþûoÿ/~ü›¿âËcòãÛÀǃ·cF¦ÜŒ1cÄ´àZ"Ïë§Œ}×­äÊòAߤZbÕûxeŒ U¨Oæê˜^Y=ÖêÊ>}t¶šIˆ0ɉª“}IðË“p÷LÎs‡#^b%%Ö$¢HGf«†}«6ô IDAT¦˜h}9v~øþƈ ÿí¢hμugÍÓ£Ã%;6#£*o®œ¢,¹<~¹è(Ãú‚šŒ1œ×=ŠnÉ&‰‡ËhNl5b;æ…çK )àTcr, 3rÄJ$ò¢‰Í1ƒšY3,Á(á˜8åzóí Çì<çuooätÞ&Ë…5A¾~wƒ”Ùûâ}QÚí‰/>¨Îï'Ò—VÏR¹sKJkO±ž&L!J±+VZ·s[—ÝL—10=&,rV$KNx»P Ä—à3ûÄ×…e3H>’IíF»Ò?<2¿œèÄ\bÊoN¹TLbš)¾BX/5-¶²1|ÃQš†óáªaÕT°”˜+ñœï.ïøòxàĤSܰ¢ìX¾•”Á“üÄLÊ- [©|ð˜" 'QKÌ  ïið9)#¥ß¿`E÷•I}ÐÔxE°Ò°¥ÜÊ䛫ñ˶óË›’ĸÓxäƒÑ’²1]8lQ¶Æ“.  F·ÒÜÜ‚V–œy› «mLÍ$‰ß{’A®f¶SÖ´d šfÖù;¹0WŠ i†Tϲ srIÎV3Y¡¥Lbñ\# ¸¼<"7é4Aäl[¦¤Äm«äœ¸¯ÏXëTwæ~Jýf”`%MH­)&y"¸:³ ÇŒ(@d´AZA*ÎÖâw“KfS`v†‡tÒ‰&PÈZäR"‚ãh#‰r«“%Â}8h0Z ´tkWºÔØÈÎIö½‡‹8>§'¸æÄ£G¾ýYc»°Ï8P÷9È9zÓœ!QÞÿ)¦øè¿úðŸ|ø†ïï?¢L.µñ:¡p)ñ ^…¯7þð«_²†19øLçÍv”è+”hæÇ Ë+â±Ý ")c6¡wîû>Mà“ÿ$uŸ$™Ì1ùòˆAƒ&Á-\î .T1î=bƒ×,ìCI¥1û[àªó!á¶~ÿ=é-:S…×c2öÏhnVcc…SÔùÅW™e‰Oc¡9’9gÜkÈž<âeŸauo­1æjT¢b­‰c9f;Ócw)ÐÍ™,6ñ°ÏZÂæÁ‘S ¥0Dx ç±ïáI•Õ‡VŽKP´ü<úVIX}`6¹´xöë² æZÆ$ jÎ÷`;Ž3\‘$ ….áL×›Åc²÷1&÷ûý3½¢øSÜEÉNK‹–àREúIRšqÁKŽ˜ïEÁ–Ó§³m É™îFÝ„éÊêJɱÕFl,â`µ.™îpÑGlÕí,ÿËAA!gYÄò–C®ª“‚`L›“–Œµ&ÝÁ×NÊŽ.q‰W=màð8"†üRc3òú8½š» I”¦[QÊì¶¢L3ûÙ5F7žªòÔ*kÖ‰^=I_¾­ìáX’I—S&xööèЪ¡2Ù=‘F¼‹<é). °Ì>»—­¡&nhŽDʦ•„§Öb[¼ÂÇc*Am\×BÚžþð×QÖSú:5Cªg™Q) +>€¥]b³}ì<Ý^Èí…¦;ß~õrZ…ÁDq œ)e»D9B {LK ¤ç°Â¥â¦t"ÿa¯ý§Å’“Ô#çÄXÂn¹ÜX"ø0¾Þ6®O•ÞÆI!Q|q´ŒÕãppŒS©RãEóÝdÆpZ'I ¶ìq°¦’ÔÑe‘Á'IAÝy},˜F ØB‡]‹Q\Y¥0W|C„3 GQÊz˜’*­•á0†ñ¸þòÿý+>þð=Ÿ¾LþÝ_ý÷ǃ%…Ça¬cg¬Éc†:»±\ÎU4Ó‚Û½rÂ\˜Ìs«SY¦AÄ¥x~¾² ï3¢5çÇX”R¾ ¥eºŸ²+_ëÄ=:ø ý¼ŸÑƒÜûÎ:©0øD%hNû±³?öÈèÎõ½Þ'Öwžž_ð¹ùŸOqÃÅ9öÅSMh©ô}b3 „®ß:V ã|Nž›q»µ˜†Íu–âŠÆ‚¸Q]ŒŸ!ŽžâÍÎ Á–œ”2‡~qžJøªÂÄéfŒ ³wÖØÙcNæ´½–µˆUtŠUr+輎@^«àå‰9Þ_2ÇRl %-| Ç(Ƨc1v 2¼› 0’N¾4ˆIöH$ŸÙÇ(ÏÊ9Ñ\D,"+ãÆÌâ…®)rûºè¹ uc­Óšèή•¥‚KP~Ä'Y’ÂT ÝTö˜¤óòëáAÁJæùV(µ@)ÔRx.ù÷Åë1…§¦lÛÆÇ‡s©qIêîl‰¸$c¤µhY˜'õÝ)%K 6I¨7 S³c¸+I3霰5…ዹÏ:ÁûŠçÖ(¦e”e3]2®õÉUï/Ë=Š•ª¥ô$Ÿ¦Ì‰\KŽ9[6áá+¦b–*)ðÂ)ú !‹ ³¥BVEýॠ×Ôùó_½ç_üÁ§·ïFÏ|EÈ$쵞#zÕí|½™Øˆ ZNL+Dõ.P̨²Ë–Œ¢pMÁŒZÙŒœ#g®ÍÙ-Ê|Aׂæw$&àÓã&sF i.gß-.ä51B=–‘2MÃ+@Î÷\Æ–2Xâ±:·¶1S!嘄½Y&ËŠêÊåR3—{Òç… %âì!¾ƒ8ÜKÞ”þŠ„³ÕXÓ£‰’œ–„§ H#_*Ù”%MaX+ é˃ft¥´b3’j 4e´¤pòÌ3’£“JÊʶ»ÅeVGuÊu”œh¥bDL _oýqæê…··7¾¹)K¯÷ø[»‡åO pø`¹qô…¦ ?¿^£àèÎå²!>X8‡oT1²fޱ£öàÃíÏÓ9ÈvNdGs"¥¸,&.OÍß©\PÍ}n-±¥Àî& §¸s? c+z˜si™¬Ñ‘ÉmcöIQÅGçÒθ‹FÔk‰† Æo®11~Œˆf ‹¾L"Ž«.\jæÞ5¢ŒÛjô W`§Ï¤ÖˆC«6Tb£ìgQÙ ªL|ïŸZxplÎû*¨„CeïyBSÐËh)Î_ñ·9XT´6’ÆÏË+üÓ±Á%Ÿ…ÔiTMa¢ÎŽ ÈR.µDï¢Ú‚ÊÃÀZ2Ù&‡Öê¬}G4wF™¹ylqXðt)´mã14ÌÊR¨švÙ"K¿Â74=¶¦ËƒZÉ [RER;{g¼K&"gÇÎÿw‰ä…"¸*¬EÑÄ[·ʤ̥Åçt‰†\$â}k²úbRh)QUhµÕH©°•&Â’ð7¨êIóYL3Jݘ¢ø\a¶ R9Æ`xý’c£i¾Xã`M¡m®•yú“R,¥%@l²×£d‘ã$⳯KP àƒHb–ºpZÿødrœÎ(–XY$Úù?¾ßÿî#_îƒ9o#rÍ>÷G7ñb‚À÷IrVÙÀv®²G™e -Á 2û&2´˜ìv·#¦þ*÷—8Xåã–b:[Ù9oÓ8 ­•"¦±œuØRˆ—ÆX¿³•ÿË¿†ÿýküwÿmâï~sð·Rµ¨š7D ·­à©ð:;[2²*÷ÇàÑ;[‰Rà$.é¶ +…>ODRä›û¾ÈU©-ŠóêÁU_)c9TÕL÷Œ¬¸¹„ÌÅT‘¢Q:=ÍhÆRãZ£øš’ðÒnt$¦ÀÄ‹Hj ä¢ -—Œ%†jËy”;X·&8)U®M©)ì ùòÂËVhµP·'^žS©&¾y ~uK÷—ÆÛþF—‚—„ÎIöÉá‹,3Ð~ËO\á™7—EïÁ)s‹ a+¾BöT4"5ÓN¤£ƒºsË…¯/‘ØGL©§‡È+£ S¾x¢˜#)bŽÃ„]röæ`H¸WVZèZ¬)T…­EYºx\º×ê<·ÊJŠí“¯oÀ¹¥;šdÔ‚s«‰§kawÎ5|¢•¤°%ckñ·þþó;ÜWA–£.dI´@!O[9™×ŠØböƒ¼U\ >œÚ„R„䉚2[†¢“!™n!š9ž2ùŒ#=Fl¸²xtĘ)¢¤rsÑs°sÇ)Œ$¬Ç¶EÈ>ÙJûOϯœÈC—DšGd«[á0eöÉÇGÇfç}K,©aÆÃ~Ê V!4ÊA=K˹µ9åd„-'^gD&µh˜×qà\¥„"¼»ÄÄ2µ-irÝ —–"ÆañìÅN{ë"ºxý1ƒÄ£× ™¨ –LÌ‚›nl%(fËâr'qYˆ:oÓé#6¤}N^÷úÂ’žìt‰!ÂI"©š©×Œï/;š·ˆ€8%òØY8FG0Z ¤ëô úÄQ~’1þàÝ 2ß½>°Asäú³ú ÔÙtEnÜ•À†N`?~jüø¶ØWô¦ˆY2Ú [9-íeƒ5IýÎc9'SÝ(—ć—0ŸßpDLjH\J‰½¬e¸õ3£¿"*(1\ÊÙð9‚Z¦ƒ3_xjÑÁJ1rqŽ^A6Ã5#z¥¥ˆRyª¤œ¹Ïx¯õÇàn IùülD®_Õ±ãÀgÇò5ž­sg-ƒ\‘þ“ù8°Åé´È“ MÏ¡£)qL£&e,L„Z`žØy$ÐÀÉäî„éýÚ”§Ú½ãÏÄc ŠÙ/iÂhLÂÖ;füÝã®`pöMÖŠ-vr!ÓØ2:ç S 7È96/s-éï ‰£Ÿ1?%ô¹–Œ±NÈÍ4±µˆÐØ wš±Ù©5ŸµN+§9\+I:Ug¼kÝ£{1Y`ˆNyÎ3:ŒšaIÅN§)¶{û°xǨR¶LYË¢+“ÏsÄcMª)â;Y*_Õg®ºØ‡1|rmwíÂãºQO[ûP¡”Þå”ÝŽõèqfn—x†peE ÀwÐ5±bè-Bg1Wœ¹Ò×?ÿÃ_+7x÷³ >öX[ébË•_|õž{,˜<–ç—'¶¶áó A:0–OžZ¥”Æëþàêƒo¿ý†{ðöé)‰ÙuÀå,ž%Á'v£*ÔR)¶XµÅÓå†-eÎ=¢®´º±â„{–\âÁC[f£ÒNªpÈ…}MTwJ Y’÷ƒ§$¼ÿúk\3%d™1¡ðÄÛãA#¢ý<0p0¨¬5©M@à ï‹Ë%3­2ÓÆnú¤ªQZ¦¢¸M`QjLµ‹_¨)f2}ÌAj5èF(ïU IBv!ÙdZô:°>bú©2™~ÐgiÜ=rä7šÀ1{È¢¬“çÁWï.lÅù£¯ÿ€ÏváxÜqÝšÁ¶ •R[²k b,‰œeˉ¹&Ï·†¥Ÿ&‡ ’¡$ÆX\¤ðôtãÞ` 0¹rANÂŽÐrâÍ"r½„º$0{¹ñ¶Ã~t.E˜}Ò$VªâŽE§àã@tò>øŸÿ'åÏþTùG¿>Yüÿ§Ó—…µQ ­VjR}PÓá€;e«H-ìc R™b¬µÿžB“]ÆS)ä,1‘òk­xÉ(N±ÁÌ/ÈšliD‡fß´P³ÐRл²ÓcBú´)û8X~ÚMûÁ\Ιf\n‘×Å&>ƒ1]‹Dœ`E¿fKÎM…å…Z![¢¶FËpU¥¥Ê–•o®•Û3×÷ïx÷òÌûç+õr¥JƧàsñTÿìOÿ ož¹¿¾¡)‡t¥ßùp¹°wèã´]SYËØdb)x"ž”[j JÒê`VÏNYØ<œôŒëVy[ξ:O[%­À:Ñ=0‰È˜æÌ^B-…Ç¡9²±û\‚Š£KѵØ2|s¹0–³Ïx œa‹\÷¹¸¯(í^râãFÍŽ'aØàW ÞßÞñi6cí«çäµ›2ÒÆ§×Îw¿ýž>•}%äè´tæ©Eø°åÀì™PSØêÛilÎIâ½2SÏ.ÅJLŸÌ"<§Å³Æ%Ú—s)ÆSéd7Lb [> Ë9+U†'ªLV·3ó ÙˆœMúñ`ßßе(.xVZªÎZpñ…y Ù<ØûÄ]ø4$·þÓ¡¾ŒÄ°8 Á¸[ã’'ï.…ë%Ân#憹vsˆkBK:˱‹KMÌUyX§Ì våšyF§”‚—t:<Ãqà)„sDÏÏv ê–‚;ả±è3$K×–9ŽIï=Jv6OòóåØÖV•ÛíÂî…¹ËR6RÙèÇŠh“?¹ )2¿L×`‰[.ˆ:#Ë@RÜþ×êx?8ŽÉôIv‹抿“+lùtPxd(95‘ZI©1Žƒ¿ûøÊÛ\qXXæ0±´Ÿküÿ”½ë¯mivŸõ¼×9çZkŸ}ΩSU]ínw·íŽÛ—Øø¦Xn;!iDZE$ËFYÙR@$þü|Š"„ÈÅ„kQ$Œ"8!;b‡D!iÛò­\Õ]]UçìËZk^ÞË|³Z™HœïUg½×šë}Çøýž'|òk@µó+ÿ§ðËÿPÈéH :V®lâÈ>B› Aí߬üY·²OslZQö/O¤³l&åibQ!ºŒVµË¹“'9‡Ãì¼¶4}vFœvÓÓ‰Uü¾Ñ©¨Bkb+cïy2˜·«M$¼mÝÄcù uø!!~Ÿ©@ƒ'!ì%IL§Ä4yÄ'+Zæ‘8xv̼xvÃkoÜò5o=çÍןr˜&šßtqâ±GÞyÿž»‡3uëœç…,};ó¸\)ËFô¼ÝB¸”÷u³ ò)."Ͷ<Ï&åͧOMÜÕ;Õ± àìï x®âQïw޽åŒ%I,6pÁq$"Å=zÆ`Ãç"Ý)>‚·C]Îób$hº¸cïâ`ý︮2ã ™ˆ‚8jH”Yjc ØkKØ¡ WŸYÅJšO§'T/[·Y»‚‹D˜µ‘±}W8ÐŒ//ÕâL1ívd%$hnÀ¤É6~¢L¡òµã=ßølÄ+l­CúªÜ0OKƒÅä| °G>»­àBtëv%ËÌ<_yœZ/Æzß'_½;V ®Á=BJä˜ >2x𸠨O†±uJÔ!Y[Ä(]PgO“¼u1èž¹1‘H¸ý3”£m‹J·"w_ñڹܽd[.ˆ Ú G_yëù3¾í»¿‡ãÍî^¾Ïumˆœ˜u5eãÝ_¯k¯h;¸ª S²Ò6jæúc¶‹mŒ5àÍb8$»Ø}+£Gj¡÷uŽS¶‰f\p¤àl[¬ÐÊLSÇ1Û6Ô‡ÀalÀâ ùij1óÚxbéÂ`8å*$'k­T;,»ÈÜ6Bpôjßý‡1 µÐ¶Fˆ#í£žÂi8ÐåŠHf+{ïÐhêûö¬k$ž§‡‘²V46L\Òp®³u ÍÄ©EiI¤’Bã_üø7ñ8_9·uWzÆÑpšN¸`[Io%÷˜Œ¦UÕS›mqu»âCbŠÞ&üÔgºïH_bÐ ð¤aœó„á@Е«XlÑGm­XoÏ9ë „À.ù ˆWZ3{7ÁB&Œ“ššœ×ãì{ÞJ5¿Dß â£gwo™|¾ú¬ÊÉÃä(ÛFó‰1Ø3£,öçÕú½"EÈé@G;3PâÈ7¾õÍÄmã¡U67ð·¿Ë¹^À8¡Dzv‹YÆ@oÖå Î$½ n§º`/~¿ü5Äe²ï¨Ú–Þ–“¨+gƒô}¡ ëŒw6½NÏ>þç<‚z+=f§–» f ¬N ®*«Âqð lju¾°lBŠvËëdªzB·hËã<ãÚ…Ó8Q¸.3Óp"ʼn.…P,Kd“بfˆí`&jÛPsàæ8¢ÞÄJsµÜb#Û§tíÔèS7 Ú=ë¢ÑMà«D›ÞtØ…~/áÝ/+çó™ Ȉ؃ÍrbŽÚl¢B ˆaG…âíðKà#C̬»rÊ ³êåBí í&ÍT÷¥RUðÉÈ@µØ-»«àµ±3{møÐìAÚ<ÓdÓç²Î,åJßèf´lrä!ÑCæ²­Ð !Ò£ús:ô:#êqD•œËî+i1ƒµ³èIS‡6‹‘µqذÜxh­ñd T±÷ó“§'<º÷$ K¯xUrʨSD”ìtÚ×{ë*TgëMÆ"jÖ=!Z’îa$iAšEG¢64(Òï}¹ðáCéüÝ_êüüÿäx|ˆOGb­È¸Û¨‹´¾BŒø8Rª}­Åhj¼EתaN“êžÙ ÑòÄM‡(tgxSs…8$nÙ,ðàÅ¥zŒP Î5Ä%Ö¦ø® ÃDí•è#K¤¢½²,W’«Æ#¯æÍr›£EOU#7…˜éb¶s¯Ê“ãH wוFüÞcF›eË}Ž„qä­?çé¯óüù Ÿûì[|êk?Æ4˜ñÌ[áºUêV(ÅÄ­Ùe@êLYÎ\®z«ö\èŠßsª.z¼ô< ÌÍ6†Ç!àEy2 |ýg?Ëû6é5WÅ@i+Ѳ­0ÐÄ"‹9Ûyp‚k¿Ë¡6o™ÔÉWÔgD!€•Äcgt™KUî¶FŽ![\³j"㸠aŒ\{7>½·H@¡ªÙ˜£WÖb9ü†#'ë2E±IòÕtÏÁK¼l•µUŽk±¾Åäe±‹n)¶!¥qôßþ±7¨]x¬û¶M÷‹„ë†\t&•tN¥pt3oœßôu_¿ƒfº3Îzë‰xgýD3I׎3v]¨Dª4Úõއ‡WÜßßÑæG(3¯@e‹l¥hª­«Bp ¥ IDATmÂÁ&ªN*OGoôuô0‚_sO#y‹®µ‘ƒ“g[®-ƒß™…Lv™qÊ! ­*e,ç3çË#e]¨e¥ÖJo›ÃDˆ‰»‡+÷÷¯¨e£†LQZótu`ëJÝ:â÷ ªèWé2]=G3Ê·j“ú•¤–GÆY—kì²Ç{Çem ÉñÚi2¢^«f†×γqàZÛW{7Â0À\̉\%¦hbagÆôÖ¬k"4«ÑÄŒ—ÌÒWÛ°:Å7ë$ }¯`Ÿì5ª·ÎR ¶YL"Hot° ¹3Äuó‰³ ¢º¢(¥Xlgp+ŽÆÇ^|ŒïûÜwsÿê%ëÅÊ«Í Ó9&º8šOé&…œ=ÞOÆÇÎ-N }ÝÀY>hG0Œ°³€)Möúµ£›P«ÇGì€ßaôJNÔ.¯Á« ñ³ƒ‡‹\[qÞˆfs®bÛGé&ºØÐ2ùŽºÈcµކL[+·§Ñ ÖNYKç\ ý™öM»J³hPÊøî!ŠÚ–+Fl}&ÁUB°Ë¥W!„ÌWÖéõ‰î>˜6> gCd‡»íº÷}2³Â»nžŠ¦àœãù“t+Èþj:ÙˆÞÍ] 8{uxíH{OC¦ 8­T7R]":Ç¥¯¼8<ãÓ-E7œ6šlT'<ËOHÚ×lXä íÞ>Úª¤†uA;uªàˆæ°ʲ\Íà2)Ô çE‰®C«T#ˆ›$ÚEþºUœZðk?ýl¯8׋¤·jaDU´ˆ7ŽD æq® /¶Yîê·§7£²-Õ¢°®¡âìõŠR1+ò„Ø@»u²è²w|¤HqdŠÙ:MÏ_ÿÔŸë͸ó·ÇDŒ# <µÝ…È”=kó²gÌ‘ë&$1¬ÛLmŽàAÆàÆÌ\G‚Ï4ÝQ¤ ÓèÙö//\æáº²,r¸…jíoß7ŽÁq<$^]f¼v+8IGeG39‡È`¬Õ†áÀa¨UéÝUmíÔÖÑÄExá0DªÂ+ÏÖMÃv6yté0_gJm¤<2í{ÎS žä“Q„º‘hš¬0ݽUó$8c¶«s–ÑoЍ'&Çép`žWÚ6Ó£½±½:\)x®kÛQW¦àV1é ®Ñš­M/(¥qŠ‘,f7ic7 X@l&Mƒ!íb.m$ L·ÏI²Y!ûi Ö ކ4Ǻ-”u&øÆý¿õÓÊ›/:¿÷vcY 1üp`®‘ÉðVUš7j†ì™oà08†ý _Z3¢U5Kà=[µ¿¶BÝ[ox â£oxª»íœÏgNÉ&ÕµyDbFµ!*T9ÆÂo[‡Ýží4¸ˆåÐ y)Hí€gžBoÉ ½ÓÕQkG±òã{ï üâßOü½÷ç‘áxƒ¨·Ìÿ`¤‹Š©MS‚s¢vE¶FëJ¦îD+c ›æ½Y«7“5‡—Â,JDŒoâ=C¶Ì³sÑpÎJI¶*V#Dëˆ8U¤7$ µÙHZ”cô8­¬­ðòz5L"&p9M†ñs!Qk5«kŒŒ)±Ø¦›!f:ž•N°‰i ã@ôcr ã-i<ñÖ›ÏùúO¾ÁÇnO|P=ï¼ÿÈù|AJc-3}½Ò½²••õrOYîøžoŸýÇ_òìváw=‹Œ¸8rsŒlTÌ2ë# ÐjUÞŸg~çý÷©¥1ÄÀ¼,lâ¬ïìv²C‘0°H䦯8º­Êæó#åü€¬ò}ßü{ü©}›§§ ¿ñ¶ã±È)ïaxš:Ò:u÷S ®’uŒÎ›™És2Ù*J@­#,ŠçB¤îeÈ'G3mƒg’É«BÀ%‹ºx©$¯ÄäÈÁQ·Îui{œ¤P¶ÖG(|Ç·|‰Ÿú“ðô4óö»‡5òX:çË•ÇeåƒU¹£è=ñBÊ[7a˜”¶·.9<ÐkéÛÝ Tx\ ®5ûlºFÙ/Ë!† œ×ÂF{4#ÕHXêÊ“q$wEÔLëàûAÍ9ÛrkB¨xC –Qvº(TÍ,Í>Ó9%4X¶^p”êè{ÿÍ5ë5D_ ÀÒö"z̨KD1lÔŠ!ǧŸ<#8¸ªà“ýÑÙ¤Ô–ÇÍ6 ˆùºu°3ç^xùê+,Õ "ˆXÆÞuœë´®{4z÷†Ä!V®'6Y­M ½³¸k1ÒšTC¢Æ1™è©KµMi°QŸ´NŽ˜ HBÄSF´fï#É©w ¾íÏu3AkÙZ‡dz'1©J'§Ä2y$l ê½CÖŽ÷¹†¢áfݾÍBdôÂì<ÎuD„äöß; ÞbÞ1º~·Ë{JkøzLÎqʨ0;+ûÄÖÄ¡"R%ĉÞ mù²”ý‚g?«!R YϳõJkÕÁú„¢~Ÿê[o­‹à‚q˜R¨æŠóÙ޳ɛ+À†`²??$»ü•‰QÌip:Ø–«•Çý´Ù6=D†1X7¯7JëH8Ø9ª+Î%¢o Ö3yq{d Gk…^SЬ›fØÏȘz=i­a]R©¼ûî¯s'”ñMDt3#r0ËÁWB°Õà6ÄÔ'¦Ñh^ËÖ­Ÿ”ÑF„ŒÒ½ h±q3:Ø‹x$`´&ïÞ¨gU[]l .Ú‘(ø ÜL™Å¨ç¡;²ïÈ^4½Š0D»Íž—•@FR l3·§'\¯ ½näab˜J¹2dµ $k·J_0ÑÛº‹± ¶å#„ZÙº°Hã6šª>I¤4ËE5÷Á0žhÛJ¯Í°…Þ qk錡à’g<®¯Y£ãhªë^¨D¯ yø¨%ß .¡*¤ËZœ2œF>|,С^/F žç¯=åº óùBKFX°Uhß÷Jµ›\v|âŸà݇…ûË=ÁÁíaàZ*SŠ´"¨ øœ™‹ÛE iVt ±“lÁHÒ`+ÊéÖ3¨cÇÚ„A š6mDM2xÙ©fÉÎáëŽTÑùDº³º[J„ì¡]Y–«E·šRûJò¦ÃŽz-{qT:óíßâø—ÿ„ç|¶ Nï#÷—°|`mgˆ*Ùy†1SÄ&mÇAIæUÐ)u¥H±\¥Ú$aÛ&Z8X«M,—nkº² gÀœc†CrÌö]Åýb¿•¨aèœrä~±•Ÿë‘uëf®fòÁ“¼³¸Nkˆ·Rôéh"̋쓌bÄ¢åi‚f‚/&íÑ@ÎG|ÌBD‡xÍV&‡Ñ µÙ…Ï5±ÒlJÛMˆÎ²ühãötâ|]h«X}k´]¨ãÅ3ÅHQEº|²ucŒ¸d…o×A 9'Š(ëÖ¬8Þ6ÂÜ)Ú9«Éw¦ì)näîüˆ¶NÃqðŠxϹ'+‚§š=9¤uJë¼þôÈÖ¡•…)e4$–ý;¥È1E£…D¹ ëÄÝù–/¾óÈt\yï~c[®„ue/”mAdf]miÛŒ«|ëg/üñZ˜/…¿Ý<­dÆhkBúÊ*ž«3RLÓHÇ3 ©x0¢ƒî“)"ÛÚpº0:¨s±²¿(ÑVËÕ±¦ÄPnéj¹u›¸zº¶ÎVÑÛ¥ ”Âà0áòNl]rMÙpbï»AyôÊ‹!3shv;4ÒpÓ‚®…"ÊÐV#êà ¡[eÝRöDK5ZK«}¿ÜUf Olµóøo“R†é††‘¸º™ÉÛÃL)Wè…V•¥7j€p ^ÈÁˆR1V#=X¹6:϶mtUü‰ÍÛ¯lÈv¡× ±_øÜgîøÂXª;ÐÇŒ§SI|ØÎgo$”Î^ôöŽMR§c´^ƒªp 5Ð}Æ«r;AGl('D©hw°­Ä¤¬Ý¢‡hèRd‡JŒ‘e©Ð½æË_h|ëg¯üð-\.•Þ2K9p8 <.ëÔ¡n@6_èѰ›–b0ác¥©£®(X­HY»E—®ˆs ñ£2¥·îH)á¤sî‚’‰¡³n+Ã8ò5ÏŸðÎãFSûÎÝŠñ¤†ÉÓ÷’mS>ìݓĵt‚kCJ¬sEƒÐijuûÎöÒÐ=æ¥Ý†WÎuz´-ùG± û½8|ßH!Óªg“Bk&£ëÞñäÉ-\¼(Žë`ù ÈÖð^8/)c„*U"ËÛ¿Ç«/ áqµâpróDFߌrä’+km&b­n u—B&\Ù¸ˆr¦˜x1¶Rù`]¹;3Ly°-ž*Áyü TmôšˆÑŠÛe³ÖûBE!„Z©$Z[­O$ÂZ=ÓÁÃnö&›ÈQñ1àuÛ‹»‘Öë5lÝäT®Q4“£0¨Pµ28H1³ÖF«Âä•ìWÊ>”õxªEç­œì ñ]{ŹÈÒfÆ!∔VÀÛP¥WϘ=!A©ÑLç^pë™)yJsH 8 ›ˆÙË¥Ó›gA³·áˆ()hT“gÕŠø£m]ÀKÃpÝpùăQëèCð©JNF¼uL*lKaaòžà*=@ÏÃò€î—›;µëÖ¾Q¤£-à]‚nç ߺ¹±XIþHh™»õžê2a_w Ù–ÔÐ÷ÄEB¢àœQËÆ¶6Ö Ï‘ºUq§£¬×k±Yó .nW÷¥ìIx”L„V pQp1‘#éHÛV6 Œù ×í‘>$F±­ Õ ˆ½áâfqOqÄèÔ¦ÇÝÁåž:¥ »õŠw‘µ 9v’7|ZJ;¦-?ÞΊËÈVbÎO!S€âÅZæuµéqL\Š˜<ªoàF”"À—ÂÑ”Åõ¨$ídבmªd¬»Ðö)^[ùÜž ·ÀãêÈnÅÈä];ÇáÀÙ ½aÁZ]•Û KôùÌ)xÄ{šŠôÎåºPÅha/ix?@€u™9D[ÍødÓ‘ßzï}¼o£™,Šâ|fÑ€v›¦GÝ!f>|’¥yJíLã ÎòC†gCäp|ß|ï%Ú Ïn޼öÉÏð~ý·éMQvö¹ó†.”FJZßöI¸§U›Òm Ç@ð¥$¦\ñ1S«ì¦ÃÀ6_‰1°–•ÈF¸ÎW¦4E´šÌiµòØt”´m†4u’"¥zûøØéÀÒ;Ja[­[bÈZû€/[aH‚”ÀuÏeƺ‹šS\÷„`$–•(Ö¤Ù­ºÕ íM¡—3q,Ô=c?D+ÌJÊmj/ö[hï*Aã>¡R^»Mî1&J-´¢LÞ(së¤àM$£)ÝÄ6eåqÙðjØÖFcu§Ö1émCTY2¼õ†ç©¹[ŒXÖXÒ ƒs¼v€Ë*T'àák'gÇä”G ;#[¹ë‰V<‡q½’Z'ºLÝQÁ}G?¶À‰=Ã\)rä\-ZàRV†ð ÄP©½Cg[m2ÊÆ²+¿¶+o]“LJA9Ïû°V¤5b†7ÞÔ=å|y‹#ŽOŒ­ðúñÈÜ<çí ~àå2C¹Ò·+¾Î”­sV `HÔñ!p{L|êżׯjåÉq)“|Ç »¬]¯l×GÖùóù‘°¼¤Ö¨ôëzFú=³Sâpb8¬«#Õ9´F!Ñ’çÅ!ÐĶmÏO{Ì  4δµSôJFr¶³zÏë§ÀãYY[!åD¢l„ÖqÞz#«xX Y\ËŠÛ.”6Ó·3燴Ý!mÙ_·°^Ï\ꀢ<9.ø<ác"ñˆó#×<ÐgH;ö»€´Š÷ÊF£_ÕVQíœï‘^ð*¬½Ó›}–uhȈI$rLÉЊ«vQ}zÊx6\„ˆʘsƒXˆÑüR°ž_§«‘[Z÷Üß?2LIUÎ%ÆdÛ厠u!áX— ÊGbÛ@z;<¯¨WœîÓX„Ðè”eCWÏß[~ ¿HçÅZŠÐ‹r3(]+wu´A¹<Úáy(9—X\â=§¸J ªpJ‘&}ò±ÑY›Ðk±C- uòÀ!th‘k™ÙNϬœº<¢=ã¼g›´+1&†C$ùÌe«ˆ+LiàæpÃËKemœ¼ÅP]` =½@7’TJ[ÙŠ½fW:ÕrÚþ³8:GC¬ö¾âÒQ"­U†±òt˜¸ß¬‡Ö¶ÎºQ&'5œ7¥R€AË#ç¶q³Ù†Þ »üÌ…ÎÐ¥™X/ˆ @"B‚ˉà+IM^J \–@Yyð 9ƒoFª«¡8ÝûeÁ&ãƒ#4AµâÂh‡û¨U]!šã©*S¶‹×¬öýœ¹…Öj‹à Qû€ë !Bމª•Lãyž¸_;M3Ã5||<£CÂIB0„ ÚÙ‚G‚Ç•ŠÏà+ÓYÓ@Í|©t¼"…2ˆcYw:R30Ipj' Ž­x¼7ìnsÚBè´­òa[ˆ>’ilíÀ3´‹6<æÒyv:q])µ²¹´ãœ3#®›2¯3KÌlmeFFß¹».xŒÃ…&ëbEôµs@âÀн¢Š’\»ò™Ï|œøÉïäþç_$G+pÝ™gѳ#ù ž&•­Û1yÇÃýLJÁZô4j³¿oâ-kf½L©6Ñ™’!¥+§ü„Ú:ÅmCdH#U½:HjâjæÊª’xöäÄÝ¥!u¡¢¨ kSžL‰o.|K´ek†ãÒÊÓÛ'|å±}Ù3§à42¯‘ϼñ”û&̯^Y³~Ÿè¼vsdëÊËÇ…wÆywøm- „!±öýHˆÐ|aÝ6¦àvæ½m$ûƒ§m3ÒmE<U5rJwž^*8¥w—+/×  ƒ:æR9ÿöoqòÊ8(½AÙ˽"àÅŒ¤ÞÙJq ‘”6^­q„ä;[?Rñ„¶¦,yR Þñððh&im¬­±.Wêº ƒœó¨V¤Uhá00—r‰ÞQcä8™iXêѼ ¿{7ï9c3K÷zµn@7O…TxeSËú51L˜¶.ïàéMäàŸý†¯ã®žøàý÷È .—Ʋ®Æîí…âQ !^¼xƒm³(n ú¾{×Ú@AÚ•Z:iùðþ|$Îyº4Ê\]Vjk,›!»û½¤Ô¤àCgÒ —"ksÄdT†¥ /ž<¥_Ï´jŸ„µêu¥–j%¦Ý6Üš m£íâ?‡gÝ™ÿcÈÓ‘œ'|ö,Û…¬§!êñNœM¥o´µRûŠÖe™5™žsÓ$<½žÞ ·7Âë¯u¾ã[ßÿ½Ž·ÞT~ö¯Ýòý×}‹äq4Gš¸(T:#ì#sUæÚ9Œà'o¯µ0$OgãƒeåíÇÊX^ÁÝ…›h‘»mã|¹ðpž)/Y^q¾<°]ïYçG¼žiuµÂ¹U*×ël—Vn¦M×¶‘Ò ŸGtÈn`¡¹Õ™|'녲̬êqó#u~$°q]Öm£‹!yk ʆ„_”ÃS”h¾!OÉØØ}1ž|tÌ%2¯•1™àižc/è!ŽC¤UHTDMΤªt5vÄë9s)•U;›œË*£\ù䓉ÇKg^>@áÕeÆoWЬ\®œ’£RôÊ“›Î'ŸWž ¯=«|î› ßÿ]½Þù«ÿí×ò_ý‘Þ&¨3.Ýp3N¬s¡n‚ôéë·ž™/¬óÝ6ºÚå8Ħ5hë‘/ºŒ'Æá×,KÝðl®"Záz<>²œ™Ïw,ïË=½\qNQ–ËK o3§ã ¬#s>ñtœÒØno8æ@%ðr ÒŠã©«6Ž®s•@¯ðÚQùʵòpnÕëB‹'^5“J…!sÊ&S:ø‘u½PÅòí± ô+º-Ôu¥­´Ë•z¹g[îQY¨;å¥÷Æz½g[€Ú©ËÊx8R" #.Á ªæ ÉÁ³ÖŠ \z…maÝ e>3_ÎH»Ú$ •ltRð#þt"yG]G–1’#¶±tæayûå•è·Q¸¨P]¹ÎóÑs]…&b,8Ì8ïÑf¶¹šçÉ8PØéWŒfô{F:ÙÊʵ,¸VÑbÆfRbtœ4ËM«•·Í®öå,†J­e#ÈŠ7(§p-àb§¶Î“Q¹»\ØÖZçr¹"½Pªî3ì/‘/}ð@NEóŶ Þ;Î2àyÄɆO™¹ꕜ&<0÷·ÏÊCYiuÃg,ÿŽZë¶"]˜‚1i«(AQU=W xà˜2Ù®U 7žMy˜¸,Æ?FgÊ*h缕ˆ¦„h®Q·Îx‰®çÕ:8®mÅõB+!,×GNRÙ7=¼]¦¬Tgyø'¹¡!S¶jÆöJdÇÖÃpÝÊÚ.DæÒ b„¢²­P+¥lôZè­Ð«ià†xku£áááç!&Zd7PV‡à˜”D¤ÒÔÑ›c[ [Ûµ÷e£µÍb5*o¨,§–zÛø?,|á_ò|öë•›ÇïüüÜÏ%3"#Ú‡!p-lµ¢RHZ¾ÒÃ8ñpÿ@&HFði›p{L¬µãzÖ™ëQxYæe¯Û4ðÎH4>1/gz©Ì›³h–½ïjöcdŸŽ #y0ÔÜ8²ó ™!uާ#óJ«‹Eº4àz¥–+u[L.Ó:k7ȳÛÂç?¿ñ…?ÜùÖo†q„¯¼_üÀÿ·3ÿû?Z™Û-“ƒñtƒª’³gLžëÒBeÝ*ëõÂVW´Î|Ý'¯üÈYùïk|ú“Âé1Z!ÿÿëϧ>1óé·6¾ôÁD²¾ÚºFBq­CÑøüíˬUn¥ÀWfá½Òˆ^y\#µm¨+Ȩœ\`{l\ë•r¹°^îY®¯˜íÀ¾^îÑH4Jé¼û¥wyx8ó‰3uŽø4Ò‰ñ ¼–›Îk ¸ì𴽈٩ÛL[ty€þÀøwüð]ø¶Ï^.ô¿ûnä—eâþ—º|œZàñÁq8ÁF!NÄpgöá×F¥¤@h ÑçÇK]YêŠô |¤¹@BlÓçG¤eÄwÖeµŒ²s|ü”Ùðœ¯M™<Œ¸ka™ ÷w¬ë#Ÿyó}~ðû^ò‡¾sæk?^8ÍnûÏûó™Ÿùس+_iŽc4 ÜÛw/i¢ô튜_r>ßÃrÏëO_òù?úŸÿ®3ŸúD!xøò‘ðü­_~‹ßyÿMª(OŸ>'ßfÛ„•F,+ë¼Ñ–B¹Þ³Ü}ÀüxÇz~EèHÛp8Zm”¥³œÛ¶P®LÓD:ÜàŽOyr¨´!S|0 én±]Öº›pï„jä{5Åà„ófÅÖÛi¦yËÂÇ(ø >qpÑŒÓMqq¤é•‡¢¨tê¶ðøðÈ|¹°]¸\œ‘^ùÈ%²®Ëű”Æ´^éëq`:Þ0œªï¶®A CBzEV5 `]ÐõÊùáLÒ{¾çs/ùcŸ?ó¿¥òâ¹Ò¼ýŽç—þÌÿðwüÖFÛ'$ IDAT—T Ãá +P‚gœà”쌼“|¦ÉLPgedupŒ¸ÁbÃÚ¹vÁù !:\ B¥‚Q"]Ô™Þ+—ëŒëÕ`Ýsôú¢äaD‹Ôtë°í½PÊʶ.”²d…ÕKGRrÞ½©±õʺ¬\¯3C(|ïwÎüØ+üÁo‡¯AoÊïþžãýûŽ¿ù·o¿#ô˜ˆØ*1 ¸º"uA5QZEµR–•¾³å7§¤àiëÂ=œ'’ ÄX¹¬­6´¢tk"B>Þ\²Ÿ¥˜ëE ÙÏ8b6‘4&Ïä„"!G6íôf'Jïjd@m‹p‡‘œ3[k –£Ž®3fO÷‘ëu/û²áÇmrt®+ÉïÓÖmeL‘Úé1GZ‚ºE’*¯Ý$fàr)í†c*øhR ykT6<1º‘0Dq±S6»<;¾÷;¿™wæöO~q“I4œÐºg ¢®§'T"ÛõÔ$øÄš½y›Ý² °†¶f…–`¥•^9¦ã‘ó ¥VÆP oœdu1F‚shœÐ¶X~-y–ֈꙆDé­2t) F<9—ÊÎYi‰Ä$\‹ñ|׺q›ÏŸ>ãåÕaP®œ?’ƒ£Ìft©ÂÓÁZµÈræ[?uäÓŸû~þίþ_”‡—¬=[DÅoÔæ­ÿÐv}v/Z«½ñê…ÿÑ3ÿÞ¿yåãÓþ ãÿõç?þÙ‰¿ð—n`8œì'ÆpïM¨ëÆV~ìG®ü»úʧ>!_ýoÿÆÏ ÿÁŸW~í7”?òyÏ¿ÿg=ßõ»»íÿñçWþ¡ç¯üì‘òOo™†‘ª/•”þÌÏÜñoü«+óLüù¿tâÝwnó’¤ã, ¾Ó¤S×/ËùÌýñ{þìŸ^x<{þÿ|Ë/ýÊ †t`ªÞ6¾°Í+?þ'ßãÏüô#‡Ãïÿ3ø¥ÿ-ñ—ÿÓçüÚo¾ ÓDN7TŸ8†Æx|Â\ µZæºnu¾£Í~ê_¹ãßùéþÊîù Ññƒß/üÛ?#|î_°ÿïG®·ŸßÏWøOþ‹‘ÿ쯿`ã§Ó q<££÷dVîvæ|^¸<Üqs¼çOýÄ•Ÿú‰…gOív¹˜lèxTR„Þ•/þºòO¿(¬›ãñy÷Ë™ßü½g¼ûþklÜr8Þ€;¡ÑncÂ<^#Óä¨8:GSV4¬[E†Ñý€ªã˜…ò5Ï¿ÂOýÄ{|ág†á÷¿¿÷~à¿üÏøù¿ûIμÁÍÓ×8ÝÜÂé&Ö=3¬‹q,l "ß§ÚžëåÂr÷eòúÈzý€?ô¿ÅOÿÄ{|êå÷ý™|ø*ò×~þ-þ»_ü:dø8OŸ½Éôô9ñô‚›àØæÎg®ç—üÀ·ý ÿÚücÞü¿){ÓoÛ²²NóyßÙ¬fï}ι]4ADAˆ€ ÈLH´@tXj›´Ê.IµGY”U娪¿¡¾X]6•©9Ò,E[ÔTÓ†@ET"ntD÷FÜæœ³÷^kͶ>Ì Y£šQ#ï×{Æ÷콚9çûû=Ï¥å?è»üÀÇîãçíM ¿:Á9ORËÉZ"Km˜Ñ’JÛÐÇÔÌÝ’6i›l'RŒ”aDâb\‹š½Å&A,L)¦ÀÙ~GÙß`~ÎßyËÃüàw>ÉK.ÿ^ƒy%øèkéV—pG—1â±N-LA(Ó)»íq:'œÝäåW®ó÷¿óïyçîÿó|îšòüü†ýæe¢\@û#ŽÆ ~èè{Ç`7çÊ蔲l±Z9Ÿ#±ö8 Vé¬2…Â’EZ\7ïO™r#÷©IˆéÙ¸r`_6™4³ÛïëMþÁ÷<Çwûm~ý“=?û <ýÜã/0v޾÷ìŠeÉí^OQ!Üf^¶|Ó×]ç‡à”Ó3å'úøÐ1«Õ ݸÆ:‹¦ÄnÞ³?;c»;ç•/;å~ÿžoüúD×ý¿ÎÏ=/üÔÏX>úñŽðÞ²^õ¨ï Á—˲P%3Í{ÞûÝ[Þÿ¾™?ú”áÿÉŸ{lÃÊ{†uO®‡²u˜˜§svóÂ~ÿÌûßøÓO+ÿø'}tÄ+g‡¥ísÏ´÷=¥sd3"luQØ¥æŠY¡¬pvK)©JB­ˆøf˜ÿÒÃ^¡— ª x±z ‚J› ƒG¤õÜj)ôf¦Ú#ÔrŠØª‡4E/8Éøœ Ò±KÀcZ?¡¨v€œÐšˆVÛ‚y TD57¼r»çt&$èLÁÚJ,Ll•FlCyÂGÕÊœÑñI4w-±¶®aµJʲ`ìú@–kS•XáÂñÔýÌ Ómœ©Xr†}œPß“–DWfÖ}Ç>Â>nQ{ÜÞS$J\(Õ³ už8•, &-”n@¤Ms;Û±,ç$éÉ!#ª¨8-h×38Ëîö­™²íPäöù‚”Àh#KtmlÛÄÃõžÝ$HU)ÌÑ2ºLoÀÙ%Mìçé<¶Rn²Ìö¦CjEQB‹VÛ V2R„cçIf „ÚbIšj*¨em-‰ÀR+:KI- ¦•-‚ÑBNK#ÏÐÚPö`r,q¦sŽÎWÂT(±²Í#à\O) -‚w"‚µ-[RA%“¤IO®Qmó+;±D0©ÑcÔ(Kç,± ¦ÐÌ’*ƒ$î¾óN^¸=óÂù9ŸøwŸ¡³+:Uö! êq¾¢KÁ¨!EÔ¢â°Òؼ •%‚­…¡ë©"U±6“ŒkÙðêpâéºsÖ''œ-†ÊÂØ{R Mm,Õõ<^ãìm1•+g“eí ÖÎö3ÎtX_ȹbuCebyS™«C%¢Þ°ŸÛi؆ñ‹ÕóÂùK%f¥/™±Ü\ t–R Z “I¦ƒì8 ûÉóèó OÝþ#–yƨâLi]€,¦}eÐÄviö[ç”z )…¥`–9³ßÿûE»wç„Ûnõÿþç|+<½iäkФíŽÞ­œek€Ðrë5eæ)³ß·X÷•ÕPèlâío~ìý–¯~³òðÕÊoÿná3Ÿ­|íÛ ßõÃé¾²ÝFD;¦’0N8?‡«Tž¿–yÍý™Wߣ|þ)J–™ f Òrȳîo2vÜxù‘‡þ¬ã±Ç %FèÚdÄRY­:Îç†z5¦¶ÛÀþÿùG*Ä”8 £ïÚÌɪJ{r±XkJf²B5î`#l˜ÍK*ïÿ¡Êw}G³DþÔÏ>òozZ}åMo |ßweÞþÕ™w¿sÏßóû,ɳª–c¿á…až˜ÏÛiøÑÑžù¾S¾÷;ölwð/~ÚñÁ_îyòYKŒp¼É|ã»f~è{#W.~´ð¯~NÙO†~³¡ï×l6nÓ‘é0Ré­`:AiF]#•j2!V¥÷‘Ñ !59_ߌ…³h81™b•Ó(ØXXòŽ’vtµEÇ2†Z`Y ÓÔ€¿Ð6.ÎAŒ bV«4´ »½òÜ‹†”•yI¨dŽŽ,Tƒ”V˜¬!‘–=qÚòÒK×ù¡ïy–÷¼sÇ­SáW½çc¿µâÙg•qTÞø•oÿ¦-_õ•úî눫ü¯YÊ2’ú‘ºÆUÏ BÂSs@rbkœvZ¸u¾cL–½æZ‹ò¬’Lß„@RÙ- RPÓÌtåØƒxžöûSVö9¾ï›Ÿâ?ûO®³ß ?û¡#~éyæúHŒÂ8.¼ëm7ùþï8åÊ¥ÈGW~æB>f¼p'Ç—^ÆÉ¥ mÛ ã–™ºl‘yË­Óø»_û?øÏrùB≧ð'+þâḾjáoÛòÚfÞûmOaMæ·cÙõø¾7OÅõŠmƒ7B¨Ls=ìnËA Ô¾ÇÛw)òå/“ÝÞòâMÏRÎ:úƒ[ÀúJÐÉž¶TmÔµR毿M›ûNK" ô°D²3Db4ôÀYIX7âjÂCïû”™+H®ÿþ¬4ŸÍÿÿ5hxá†gN¯­l"e‡,PÊD)‘œæÝ9/¿|÷}Ïøúwn¹u[øè¯9>þ=Ï<ëð}åM¯ü½o™ùª×G~ä½7ñ]áC¿jé–f*UoˆK£â ¾Gjbè;bΨLJºæ«ÈJV°UˆâQÉ„ÚÞ›1WLil ˜Örµú¹píÅÌ£Áµë…îÝó’—*Ÿ½j8¹²b[”A:.ÏowÔ2“ËÄ&´žs÷];^ö’À§ÿ|äáGͮŤÒr æAX¼^÷ª‰÷~ß9ßøîÈ­Ûð+W>ök†Ï^ézxóW¾ëÛ2ozcáýï‹ô}á–|-–t;ÐÍäÆ[ çÛó¶§õÆ–Zmå)‡‚©PHõ@uË©ÑrH\o©,sÀOç›Õ< Æ$Q¥¡$Ã’Y­<«Ú¨2…Š\ÝÄ…£u¦Ëí󌩂·†®.İǃï )Š)f¡Ÿvxë8K±EÏ<’3}×:i燸Š0JÆj›üS sØ£µG¢6 €1ôÞaóBŠ–(©!7+Ä4aÕSòŒ‹–úž˜2ŠÅuÚ¾·Òp­ízÛB¥Ö†Ôv’˜f¨M;€ÍŠ©Tè©-âš¿¡æ†ÖìzHIÀv„X989¹¾[ØOMÔ'ÞR’ðÂíÛID³¡ŠÐ;¥„‚¥ÆÖÌ¥õ¸ÐrÀdl™A IôË=!μíÍ3ÿà ëuå ¯Ïüí·ÞøúÂÏ|°òByü‰Fºçîf>\lw‘m®”ÐL©Ô†p|øQáá«•wþíă÷-üÖïÍPz¼sX)™ªà¼ 1²Ë‰»îž¸ÿÞ™yN<ñäÀ¯ tÆck³ûn$„©½òmÇ¿þ…>ðÁ‘œg–%B5¿÷{#ÿÝÕNmS,©°°¦¡K•±ï 3L1¶˜@ʉ˜ ¹DD"ßö-m ü§Ÿ6ü£ŸXñÐ_Žx×SÄp{—øß[ØM[6«Ä›Þxó&ú‹™’"KÊLcćˆä™œ&¾é=·ùÖo8çÅð¿þäÈ/~tƒXú¡ËÔùå_õ<÷Ü9?ö#?úà Oø¯~Þµ ýpD]uŒcw •j,ûܘÑN&ÈU ^l“¾mw,#ÌÅ"ºZ°+ßPŒ" óW*V:¨â â>ñ{÷ð+¿vÄöì&yº×?ú_Lüðdþõ/)?þOznž®±ý€u]ס݀w#ãÉ íFz?€@6nVKÃÇy‹­·ùæ¯{Žw¿ã”g¾`øg?}ÂÇ~óUFœ·ÌIù7¿3óûŸ:åGþþ‹|ç7où–w½À³ÏüÞŸ¯Ûçbˆ]!š†‚íÔÒÙL"0%n sž KhÑžd-M;W™c:L( E+î@±‡‘·10'!ï'–ý9ó´ç›ßþE¾ù׸q þÅÏÝÁÇ?ù6ã1wß±æÿ,ÿõ¼Àûÿó€Ú|´ÇkúÕšq}‚[] z‹©JZa·å|·ão|šoÿ†g¸p¼ð‘ß8âŸÿÜ˹vó®ï\ùãÏ>ôëgüƒïüßýwoò®¯yŽ'¿¸á¡G7øÕšah q›|¿âw?ýZ>þÉ+œÞ¸ÎÙíørƒÿæžò¾÷.üü‡ÿó?ÛpºÝМï±]ϸZ!Ã%†U³Ã&Ûäb^üîC„AKAD(Ö¢[™­’µà­°Òž¥‚³ ÆDÚ&[‡T¨iÂÑÊûÒ[\çÈ#džßúã{ùÕ_;bvƒÝù)Ýò£?¼ç‡ ñÁ_6üOÿdàÖnë׸~dFúnÄ#ãÚ¡¦ÃÕJ¦ùnÔ$B „ýŽ­X;×|!ÆYJØ·n¦XDÐRq›ëA…\<%м ûBm?“ ¹9HH«ÊÍ] eÁ¹‚ÖŠ¨•–2RUÐ:“sâÊ¥{—È“á´?‚e† ‹ÖΡef)g-©*) 5ÔÖçeI [Kjódg°b™²§ÊÌ…—¼”g[ÒÙmwÄ'ÚD{ÅÏŸÌvKŽB§xH¢Ì¥i7z7€L tVÈ8qŒG—yáô&9Ç6>AÈ!s£W,©P4µüZî0b*$µ8#ÌKä _¼†‰mQì}cUGãZK>5úÊG–¢”l‘¼GºÔì˜í⊵•+jòܨ‹¶< @‘æAP`3(%r- õhzLÜ7jA7²Ò Rmc5ï˜æ “-%[bN8“>/&¬(b×ÌûHNc#•ž*B‰‰ªÁìó‡l::í„§d%ÛƒžžÂõ³À.]gí·c+^Y©dlMädXÒBW1rÄCI_B—IË}f81çÌ‘U¹:d –6ÎÊΠƢa†TQM‰ ›vèXßa}z‡wBV‡xÅKNØUÃí[»fê±ï{T2K)hi›Lc89ª|û·ÀW~Eáÿ¢üøÿæyþF›•’Qm´ž˜¤™EšQ· KL}Üóð£ðuoöÉÌ+îœxÏ;NyËëžç¯¿“2màè"¶;Â¥8ÁK‹yz×ä‹b¦Œuçíyb:üjÃÆÞÁ°¹ÀÑñ†¢®•°‡cÆ®ãh5ý€Uᢇ›¡áô”¥±Ð“Xj)®2¤b3‡fKñΆ\YJ3:Uš›$dC$b¼g¼t{~ÊÞNj›B‹uTã0Yq®]ƒÖXúÕŠ•^`X1ŒÇ¸®oQßcì€7Í'bƒŒNØM™T"aÙñ¶¯|ޝyóMrJ|ä×7üÆï^àäøÕ­À8T MçÜ<ó|ô7 ÷ܵð5kâ«ßtʧ?ûK~µ4¹]¯`´Å¨RlrΦZñm‚¬µ=soGªÍp?8{@ŒvÍXÍ3£_ E,©ŠÍ¨ó<úyÏ#*çm‘ûï›xå+ö<þÜŠqƲd¼üÚòìõ-)'¸oáÁûg®>axì ‹SKRÃÊyÔXBŠÌóLXfÞþ–-_óÕ;rŠ|øc+>ñ›m#Û¹fÒ%Ræ…†¼pÏ+û«ÞþVå³ÓÂä‚é-š)´R©è—,´jA©ÐçÊEßs+ì[„Ú4g@Cµgr¥Qèô@gS Õ8@ªÒ:l HIŒ¦ÐÙB®•×¼äN.ù5Ÿ}îq*‰;7ÉkžÙnéK“ª6iâ’l-:Dz¯t®¡3 –©Ñèlëë‰BÑÚ6/±¬³tj!2ͺ j dL hñ@UçÀZ‡ÔŒ(b”4‘•Ó^ KU Bõ¨R\š€M{j4Mèå…#óBÃ|–@‘ -Ž\,ÅHKgh\Òl¦óœÍ ½D2b{¢R ¡Î8íè˜ µÝ»š ¶ 2/‡Ø”¶nÑèYæ…¡j£BÒµFÍj.†šg¬Dpc[tïf0Jßu Ó+‰,p¬lnIÔÀè<%drŠ­äƒ¿ee*Õ¶M¨–ö³Ö8¦šÿD„T1VŠ´@ç,I…\—ÃTÏ7plë¥C:ëˆaFmƒÏTñžkûå=£µ„e‹SÛÒjØåLŠÊP3Y Æ$œ­dmðcÚ!ŠÕZ¹tiÅ<ó.6UµQnïöÌKÓÌ»Òad“pêÚIL¸,tÒ2ùuç• žšµŽú×ܸuÞòj£ålYš ²Þ4©PfÇØÂ­}» ]?2Ïç‘ÆÙW¯ä˜)}ÏØÙFBÌfJn/Öu7’Š¡"t,Ô¾§æÊ²L¨::+D ”:sìVHH,š˜ªÂ’6 CwÀxUqĤM ­†$4qÔ6ዸ594¡[^2GÝHˆ™TÛ"ÕyÆ0ïÔœŸRJ@¥ÇT &“ë65©—‚S¡L‰ÜÜ€Mù^¡êá†3…ó}eGÂØÃʑÌŠxË\#V=F NZ¤&–Úì·U9_©[z“1fd…ÂÊ9æ’"£„:¢%cjSÅ×0Þ"&6¶¾:¼ëÇ ¢£»zr…Pà%Op›槯aÄ©Y-³àÔãV´ì³óì·7›PG+_ñŸ‚” IDATÚvÊþgŸQ>öžov8¿Â›JŠKÛ‹P1fš(4Yœ) GÆ•ÂP›¹Ûv=L;Jª,¹4Þ¼LCƒ"‰Ï~ÎòÁô\}bƒß\äxµÂyÇR N E÷¼xcáú‹·©$ŽV Ž"·÷žœ %Ï­´žóœxà ÷¾râöYáꓞN7¬6kŽN.Q¤Ãä@Ž=¦xÊYá™/x®]ÛòÚ×,¼öµ‰¿xÌ Þ!Ýo¢wÐJ§‘³ÐNÓª6JÓè”âÛ„I‹áFÎëIÕãleô•]ÌŒ¶«£ï3®TöóBÊgçu‘z-,¥ârÅä€ØÛ uB7®É¬q« ØÍ í½ÁÕoè* †³Ú¨4®B1•¥$rÞóÚû·‘ùë¿™yÛ4óN¼úÞýå £ÐÙ5½wd2˶ÙÀåðŒm1µŠ7‚5•ëq¦Ô†êD¡Zhö§öó ˆÄ4ãHûSZcéU‘îÜ¥+xé‘/6jÒ¶r<£³ÍD[E©©ÐÀJMÄå :ó’J“¼eÏÚ4GrhVb'–€E{ð%“j{îÚÎPBŰ`ûæÝKĺæç)µÃš‚sç¥Q­²¡m‹ø"l³2Y Ê~ ÔZŒ²3’íA˜š¨Uñ¶£,‘Fj²ÅwžZ*™fƒnej±}f^„,+ /mj9-©{‚mïÖR1dã‘z†U‹øž, ½›Úù/%äöo÷£‡’ˆ±‹EÍŠÈX¨”b‘š›ñÙ­H!R +­0Ž1”Á¾KŠlÅŠ’Ë‚ÃÒ%äÌ.U.ô;­Ùz}íàw=(˾_¬3¨V–T È`l‡EMseåÔä}ªåàq(ÔÁ{j#•fî¸üJüí3=Cq¦‰Ûj@jÁhC!«Jƒ—d%ƒ/‰¹fª±Çí-ÄxJ?ôT:jØ#ó)—W+öÙ‘BaÉ•uo™Éhn93c)[J:§e›`Šû&–ñM°S²§sa %´2Aˆ-K&µ2:µ…e.tΓ ÔÔ°¢­ø!gr Xí0ÓðÚZþV`å,çeà| –$t’!gÄŽMl@%$ÈI點ÑV._¼È¹9æùk×ÐxNÈP®tŽW¼êuûðš¿z¸ãÕ÷œóŠ;¶\}vábœ)’ñ šq:ŒgU å$³ìwÀkÚZ°Ú¬ðåýÉK莎ÅX¥˜kÀ¦Ìk˽W®ðù[;òvfÂÞv¾36 ÙÄ9¡¢ì´'çf„ ¡×j¨Y©DÄXc‘´@mñ¾˜ 1GL¿"w”<Ú_¤+…0ÏX†/_ƒ¢íyÙ¹5ÇGGØadX­éMÏT„-½S蔜¨)Qk€¸ð²+·¸ç®ÓÃ5xÄ£O¯Ñnƒë6¬6Ì¡ëf×Sš]þ‘Çzžyî¿{ϽwMüÉg ¦\ÍÍ“bZÅXë+ÖR5zT„"¥•( ‡BhL;Jmï¡å»%ÂR•ÞÀ`•jVÜ ¾ó<öÄÀÕÇ,_ÿµ{¼â“¿Ÿ¨"ÅÖÑ«•N÷œow¼æž™ï›Ùn3O>éyáÆH¿ö«ˆ1*%7aàÝ/Ý󪻷XyìóŸz ïGÔ¬z@LG‡ZËÙiâáÇÎxæxõ«^uoä ½¥¤™…žZÛ©xpJóa·w˜HCQWÓè-ÖzJP3V¡NÈ—~¾j›2h3žg™P jÙtT5ôrÎ^,gs%•„ï-O>ù—ˆXK]Ó>4›²UœfTU&öØZbñ)Aœï#ÉZ\i^‡­ÍUS«’Lfì ©´©†Þ‘(¨1èá3ÁϪKìfÅYCÊBJÒ`E§fÊ¡cÄ£60M3½w'¤9‘ªÐ h5⊑‰Y›Ë§Š9L íXQðÎPcû;ßîZæ’±¶"ìð>7“{µ¤qqÁˆ`’miñßâÉ´îq®cÆA˜‰Õ…v¬zÃ4erI”˜(¦£–…Pµ_T"©¹ªÓ›†%®è¬ y&ªP³² ±tH5-ƒ¡8×HNDFãˆU>*¡Óæz®¶Ábr³zG,Vr“íå„׆%§ÖFìËÍ R§¨!¤„¨Åk;¨¬EÚ¤A /Ì{4ÜÆøë˹Š¡7†^ »,¸Rpv„’Yu–9.Tí t°S#V¦9°„…]X¨V9¹|¿±Ú^N7§f-v΃X–¥Ùæ2?:Œ.më•Raš27oÜÀ ›KÄäHâ¡´ ­5–”¢•%FnÏìõ2zC•†ÒÆ9gŒµx—ñ!—öðKâ8-J ù a©XU¦ÒapŒ’èÄšßPRÌ„ZyætÏÙÙ‹8 ­l5øV®qÜc6xÖH³4Š0'áæöí}C‚•µP%³ŸËvϨ#µ-Ò”ëj%⪲̉ãNxË߀ô—©ißþ_µ–H(Šo~tBmÜઠ¶ÉÝRPÔ-â$‚„i»©2-‰~(X…\:–¤£Ž˜-5MÄ Kõ,¥í¦ WK3Ýi£I•T)ÒN¤5¨(®§ë=ÞŒõmç*±l6?Ánݾ€w-s§¥ÐuŽÕhñƒãÑ'=_m¾†îßqßý{jÉLæØNžs ”2óÀ};^sÿžG®>ÑáúžÛK$„H×[ íEYÃ8G‰ X\×㺎ÕÐÑ;‡Ó&v‰¨&ªò«q‡|a ‰ün-ÍŽì¤$SnH_IX“9{Ö}OçL×ãú¡Ñ ¬¡ŠÃª#—ÚèŽCÁrFlGu½úÞ£T¬dŒj‰äRIs†KŒtý¦q³i˜R’ј6"E•N ½÷ MKaË@2F*Šëšè¨ÓB®™(†¾ï„¹dåh=âTp4„ï>,¬Öw½ò%xoØŒ†•µ ¾Ãx±†¬Þ ®k/º/_ƒíþ®êâœ%ûÆõdëÁyT3Y…‹gšè©DHqáòÅw\ÞQJâæMÇ<Ó+Švh¾À'—¸|á¯~õ=\¸x™k/Ž|áyåèháÒÉ«{$ìÑ\‰8Šú–÷--_zÜ+Iç)Ó{4ëï` ±¶ŽJd+¹Å£¤cm„^<·‚#ÄˆÏ 5ElÍm3NÅh<Ä´qÕÃâJ,êFiuD°µRKjߥi÷x5q5í»¨ËDž.ßæåwÜÆhàùëʵk ¹pº$Φ™išØÍ{´S –Âó/nܬ\º¸çÂñž”©€ä@_"G6q¹+Ü9(›ÕH?ެ×Ù4 ¤Hl†hcñƒ¡;̸Á®ŽX#êF³fè,ãzÃpáŒÃIÆzeè2¥(Åx#–£õ€j"«ÇZ?l"U3`ÉÕPD˜‹’Š‹’ª’ÅÓÇìƒPRÆ[Gç-Öu¨:Db÷¹T|çñýH7¬89¹ˆë6HßÑù®™`ûáwlïÁJ%9g._ÜsåòžR×oynoèúug,~1ÈéG\7Òûžçÿ/×à•K3mk¥™jkT'T+ÔdÈÙµ…Œ·8£18o©ÖR«¥bPÛ£Rð&±1Ê~‰k±N™¤YiK–jqýÀSÏ_äêãH‹ËÂRZl|š¦D\…œ—fº¦bj9ˆîJmŸZ-xi¿CAȵYàë¼°ìÎ lÉ$ r²î9;j)”2ëB–‚J;$ÈRY¦DÌ-RåmıR¤à°¨z K„*„ܶ…Ö¹6ÍÏ«t&RBhÏd£TMQBVRiïÖ0J–&ït¤6ù^±í}^Õaj&çæ8xÉÚra³¦†Ø6jÎ!Æ1Q1Æ€bM…y¹ÁmPÓBI{<E™ƒ£7ÓUŠdê™cæÈ)[#V%1Xá®Ëw²íîà‰'ÁS <ñÄuŠ€¸#‰y1c± )4K­–Äfð, bL­ÔWh–biÈ¥)$ίÝ$±`ªÇÓ=Ö{:õ¨€­ ÇGSLl§À<œ?œRÔÂñÐ7Û^‰Ô*,)`mÛÝ ÒFk(~ˆ¨8œ‰­Ì3\Þ":ÐC¨‰ôâŠë·§»ÆwÚ¸ý¾ZÁÆFž; <ó{Ñyiˆfã)9ãàÎÏÜP±2B] +¢MÌ4)J®¶™Šs"ËN:$î<ê¡w|æñG‘ÜÌx+o(¾ãtªxm‹¤¨5¥¶`p=E”\#„ε2TÊ•„\A¬RËg{j"ζ\¿jC–dpNéG¨”’è'äFbðE8Oœ¶h“ªid= 1µ•oh;ìQ ûmÄÝÿIÙÛÅZ—eçYÏcιÖÚûœóýÔOwu»Ó]]U]mwp|c‚0„„‚á PHH‰¢H¹Fp2Bpq2ÄvbÓŠ"ìÄvÜ?õ×í®¶»«ª«¾¿³÷^kÍ¿ÁÅÜÝáê¦TRIßùö^g®1Çû¾Ï‹„Æ©¸`ÆãØy09»gò¢´ÈÔQ¾SÆÏç EÉj¿óÎÌ;ïˆó ïn¨G^ ÀÍ1!¾cÓÒ¨‘"Hï컑ká%9ðî ïÿÂ×ß®|íï¾Sdz,³Ñ~|—VÞúêÆ^ëüæoy÷ƒ±‰E'ÄÇdRG-% ûnBd­,e°„þdѱé®twT”"2‚T6JVuo×‹ÐØ~˜ b£û”"ŸÿâÏñÉýKl•áÏE®8Ü6BÕ!‘Ûx±ä\Éû~UUÆ%¯TØsA-p^§sä•— o;½ò–¹Ä™8 §ý2<^(ݹ½í<¼sNçÀù‡:ohÁ±L«&¾óÙ=ÑË2xA;sÛŠ½\ðtËÝМq¶½qÙ a2ã¾÷dÅ>ÛènøZ¹=,#ìx?”·G‹ñÂ#Ï^ ®üàÁèõy‰TŒÝfb¼e3¯Þ:EÏrÅRäROK#Q®žàNíÊÙ—U.k`Ý#Uư"é†6éIx.Þ"?üÞg|zÙéX÷ 8óðnçÁƒÌ¡×ʬÊlcñröÑŽ~*‘Ð+­ &…»dlYQ [7¼‡á7X<Çy„vmí¼bs~ð\®['o‘ó%ñÒ£7…½nÜ– !o\0rLÔºû-;·ÇÌÃÛÎù9†êTu®4q'´W^9ñÒà P>ùTøñG…šî™ÌHmãtJ45Z+äˉ|¹çé^xöÜ&ÜÝìÌñŒ—Ú:â;Ç`üÙ7þ ëZ¸|ïOH1 kg ¨Ú˜Tq‹¤efN—¦Â²L\|áez£pÓõÌÇßù>[Õî•(¦t7ŠÀEW'¦@mû°šªÑ¯ÖŸ‡‡™ÞS›¨eç¶7b0žUÇe`kÕ”îóH¢³ïg¬—yÕñþÏàxCT–åM#¬ ˆ2²Xª~õ¨ûðç7(­ðøÁÆËÚx×€ªŽ!s9ÐÒ¨FÐÆMSˆJ ‰Ó–¸¬ÃËþÒÃÂË çÎø x,Kà>;µ;I†ò}Z1©Ì1`˜¬rs˜Ø›Œ"Nƒå8°œªÜ ꂉ"¾Ãä<ò#4¸¼Þyáýï'Þ~«ðõ77Þ}¿°žOY…mÝY¦Ê›_Ýxíó¿÷Í;>øãÌéÀíí !Œ¥‡— QŒ—u^~ÜÇg²‚F$%æiæfN´ Mw¼u¢5Îë ¤òÊãÎ+ Ïžf–)¡a¢˜­sj}¢PTl”Õi àéT&ƒÇKä³ ×$.‚,NH h˜(:ã-ÓÔ W:8óÔw²ÌÌ>ˆ~ÊX–˜ËóüR˜®ßCíŸ"]g™ÃñÈž¡ÕÎÍ<Ós§˜ólw¶½]Ññ™Z‡³À\É[‹íj7ÖàÃVãC¿¶pÙ¡Tº:f::‚èC}Q'*ä&Ð"»<…´m#úxïK;A^}ðe^ïx÷ãï°wÇç -iFưff1s..è.TFC°ŠÒ8A¨î7Ø;ÜÎÖJ˜&ZÝØjÅ$…¢ÆÞ‰¨½ôN­£¡{Ûϸ$Özý¾˜ñ>ú)bPºK «Ö!Y¤æ#­‚†sjì]Xóh>Ìgë0ž˜ ËÈÅyvÎæ¬iظêiü‰²Œ<‰ya™'¥³ã¢ß¡ÕÁ¦q¦—<,q8µ²?'Æ ï‚³3£{ä²&…Cœ(müf;C¯ô,x tf,Y³^»}‰¼?瓾±ùÆÞZ#‘÷—ÂI N+¹<§º2yc«ÆGc_k•®E e§jÀT0«Ì>a1‘óŽ)<;¯b3v)ÊÃ1OáÕË(ÿZwb¼vHgÍ•ÒF #"ô[ÝýÂMº¡ôJBH3­ÍL6lIÞ Ó)¥RÜ){ˆÈwq‚8·FnÁ¸TAž?'‰_­•\Ú …Nó>¡¢Ì³át´æY9Þ^¬J«Ã$ÌS¾­u”UÕb•ƒ µâ cÔ}œ%åÔœ”;µw‚;L‰µ7¬;mt-œÛFÒ1øèdœ$£Axâ‰V¹6%D)*WÇ”v^¹‰ä0s兀¾…¨½4Òäh…] ÇjÝg:0£X÷Ê(¦Ë­F±¤«y/1Få@‰ÇeØ¥A+…i^ˆ^^žñþ¥‘?øpçüí÷ØvG»QûØÜ .>×MJâ|1ÞyÿÈG%æÙ(µ§SPw,$BL4t­Ã|d÷†ïIg¨9ß}ÿ†ï¼»óoüÅ·ßìLóÆ%G ÑdÇKáKodÞúªðäé‘ï¾ÃgOg· ÃQ éz ¡u‚r›}CغÙu{hÀ JJÎ]Tz)CúÆÖ`ž’4ºÐ·» Ôá ÝÏôùì³g¬û›Û,iâ܆üj:.Cź7Oô>¸ë‹vÖ]Í…Ü…?þÑÌtËÛožùÒKºó„o3ÞθWZ«´²ÒËÊ>WùÜ+‰ÿ4ð£¤4SZãÆ•nÌXô*…:ô¦Lj.¹L˜m؇.½PúP§|Ë,Í&î+ÄZ9µq©»K‚Î3“nR`ÍÒ ‡+L%ÏXQáV”É"„?9u¶!,¨Ág¶µ‘U!Š8Þ*ËËá£å:X§•«2 ƒÔ¦m™–Ä(+»–[ÝÙ \ªñ¤.C2ÖBH‰—ÆzzÄòˆ7_ÿŒ/}~'…{Öó RŒ´uecoÛŸÓÛ…—_Î|îåÀ?ºá£'™¦Äd‘ Â­v΢\ܸ4gI•ã¢lÛÓ ÁÛ™Ÿ’Ê[gjh¿Z JÆúʾ_¸¬XF„´ƒŠ°»rï‘>¹°oûU©´Á0GÝ𠈢ˆ¦l=bn[(J®ðìJÓ‹°oH‡ÆPOic’Tµ’|ZP¶=S-ò,7šÂ¢`^1[.¨ê°³´‚ÔW‹4z ”},­‰×gpú)‰ÑèôN²ÎŽ ¢M°èx#²ÓzcI²Œgp[ÓPL™Ä‡m¢ "Њ¡! ìuN\¶ȤdXløzµÓiGìÀÞ… •dÃÔ{ÃzãfZè¾ãÞ‡]fJ,æ´ó“@“È©f z-1;0¦”@&z»ç’!¥…ï}xË;ï]ø×þ••·¾š¹9f¶œQ3NEØ/'¾ñöÊ›¯wž>;òþ?à´Þq¼Kh6•Ž mg6X{å01>“3¬—ÃŒª¤401u¤U|wÖµG¶=q¾ŒÅÄ4‡Càþ4_©xãýÒ²ÕQg¬“ÕÁ{Ç=,±åÎV†•yX_ ˜A"ΰîÔ6 JĪ‘ÀùÀîD8t¨7Ayívæ“§'î¥tâÑ¡cªÜð<¼öÁFÉݳû•¨pˆÊe]é^¸I39.tRPös& ´¾Qó º ¾}ñ‚¢D΂êNÒ‘À+2À+Ò9÷ÀÔ Q: ô‰>²›]œ&†‰bZ¨e pA8•3¥l4Wº –‡‡Þ¢â-q¿5B8ò‹_þ³|øãïp¿]F‡Õ>º‚úî4Ơ晫xŠõý9sAœR¾ËtÒ€käööÈþâq: ûM;,¤Ò S¥îJo£ß ×JšY Éyô-h@¹ ibíBêAH4\—êãBLc+@%Ä@¶È¥ ‚\('H”H¯s*\\®Ý…¶;&FuÊQwÔÙ+Å:„He”»êê¨&ºDL…¨#„¯Zye ì}t>$lv|bŽ;â£AºY#ëDj bä _ù%öÀÓÓS$JÓè[Ž3¥*Ÿ={Aï'Bl±}´£Nßž IDATÚ…(´sÎjtM„6°yyo<XÍS2&ö [] )‘âH¤oû‰Þi^ZYsü8¨²·Æé²sX犌[ñ]˜éf”^ 997‚4¼9S\@;Sš8i€Ós¾ð`A—ÇüèÙ î÷NŒ3ÒO4:¸0O3¦3OŸü„Ͻöˆ›—_å‡}HþìDîÆ4+UÇÁЛ£1ÑiÔý̪†3Ÿ˜+ûžñ½QJµà2@Õgæ›06uÇš±#$S•l>pƒí‚-ÊzA£ ^FÝ…ÅŸüç—BÎŽ†ÉG!çÊ! ‡ãÂî[ul;SÔéî£_!(.Ó¡d\öLÀ¹™Ç/¶J.•\Ñ5¡×Ž¡Ã<Ú¤@߸l3wfsNí:Ø0.p2.:€7Eí¬EXìÊ’—@h™Ò*—rà¾ý>Û"sa®ÐŠSd H,a&.[çr T…IU*¦M£½¹õFÔÈ4Os£fGZ£‹“rD(YùáŸN¼ûÁ‘\„·ßRÞ|½ò¾5Hb£8åk_-¼ý–ò÷¼óÁ𭪋NØ4p ‰:7ŒR©‹VNjXCp£#.ס~Bˆ$MÃ3ÅDèȹ²–•Ö‡¢€€‹‚Ìt"çK¦n…¶Tîmð=@ÙF¯ƒ¢¨È’`¯VÎE8>Zè»ËMœøÞ‡øƒoŸø•?ßùçÿ¹Æïýá‰ÿóï*­;%Lqzô_üú‰_ù§ãÍÄ~ç–o¿ÿ)ME®HHÁ)}ü®õ®˜Ž «hDé [©&á³Kþ^œ.ÂÞ *31Y*}ÛWš5ŠFŠ&Jsö¾‚g”>Âå2ãíJ :K_1½ÅD™pJs¶ZH&ŽùÙJ tvÅ4%>{jœö„^é2>€€4Ÿ®ÃùÎÖ&FÞ#—}™I) òÖ¹‘ÑÜÚ;ãìlŽeå%)QKrj.œ%Ñ’V$BLÐ:Y—Ú(¹“ºMqK¬Ý aƦåáryƒ÷¾ÿ¿üç.üÊ/_ø‡ßþ˜¿÷GJÎÌó-h§æÌzÿœ_xý'ü³¿tápLüÑûŸãÛ¾†Ä™ÔâÕJ‰ZiÄ$LSd^œ¿öW:í¯<žðÿퟙ0MÌSÂÔÈ8©&ÎmæÃÏ.WIß‘¦ÐEÆ`&cxk}|~”³'qaráÒ‡ êæw'`\Ôˆ¢!i#W%]Ø{¾JóNŒv@úz½’4Tgî1ÌA £}d(˜ŠÑ(t<èPÚxW@øÙÏFº$¼dè;–Xï8†HçRàp«•*•µPzƒæLIISäÉcÝ#ÓQc¨óÀêfO3—<±î3HfJÁïG®g«ÊƒÅ©‡ëc e!°÷ÊA"ƒ,%@s.9ÓtÆâ=Ò:&‰% +äZ iŽÓX‚íÛJšÛ®ÌóÄ'?>ðÞ÷oÈÅùڛ—¿tá[ï$(x¡·Ìë_Þxë x÷ƒÞûþ‘¸8, &º+íz~ì­S\˜’‘¦‰'O„Ë|üÖ;s.nÔÖ ¦#»Ó…õ ëA„i Xè#gçFjB÷‘_\màºÇwgà6Л6¬! 789¬¹ ¦È Œ]eÃÂí`àwAƒ ýÞª]¯…¤ï¡r|éÀƒÛÇ|ú|c/ ÏŽ‘(¾ÑÚèdJu _1¦kÞ"øx÷n,´+‘aýiŒöÝ^2 ·Ä˨@É‘6JŸh¥3OXâ%R{Å£ú°‚ª“BPð*HwÖ^Ñ ž¡Ž&aT‘i&·¡¼^NŸò\Aê†ÛB•xµWxÌïýøs6ÜPÁ…É u;ã"T Ãê¼–QÐYk/¨Ò#µwò®L©ƒ~òüÙðáË ìRX×{æitm¸¦ØðblÚ &$h-з‚$%×Dt¥[§i ‰Á~¡hqœKÞ…,J_•ªc‘®{ ¡2õÆ ·5ãU)Áh¥Òt"‡8à1¹B¹â•C³aÅ2®VöFë‘sî×L`ƒ^˜¦#ÇØ^ìT"Ú+S¬T47R4ö:^É ¨Pë…ßûîo $rœèY)*´Þ ¯>~Ìš+O_úyÞÿá€FΕ#½ ­î¸„áϺ?ª Ç%pÉ‚M‰cšÆ°\3/tB“çÎÏ3Ó¬dZFKß}n¤o,ºòåãÕW÷·ŸããK¡®+û¾s;)«"÷L7ÓÌŸ>ç‡ÉyøàÈiÏ\HLõ‚”™"FÞOôn¸8jF'xÅ£²7a’ˆL†ç±“I©5³mƒd´ï™®‚ÚDÙ+¶ÁÜÚf°fl:Žm” ÉŠôpU܆uçfYÐd´þœ)ÂVµŒãÝŽmL4LãEÕîÎÖ‡Xe*\jáÒ%¢A» ÍGLÍBöV®èÄ*§=BÄÊ:Ôœ”p¨üÔcÏ8lgk~þˆnIéaBÓLôÂÚÇp&F³±CԆ˒Ñ^x°è¹RN&—Κuäz˜¨ÖÂØè6CZCzfVåÒ*}Ϩjt?ÛôuŒKi°ˆ  ÚgTÁ®íëH` ƒ<†1$¦™¹b‡^¥p„êΔC0ü«!ŽK3BM }oÄeÈðKÑÑ|ÎÊ<¥98®•H¡»‘4P]xr[s™¹Gª[e!ÐÕ ÌÖè:ñ¢'^åW~¹ñ­÷ñw÷‹ÜïŸãç>÷ˆ)Mh˜8+$¹'錪af¨NWåîÿÿ?Ž„ã bÖAÓè³ÌdmkÔpƯŒöñû2ƒ¤aŸhjg` £å¶¸¡=GÎ¥cA°vµúD!x£7£o£ï¦coƾe’ ©Ý¥²*½EÆÂøïIÖF)-® ¥¢t‚Hç0y±ÿTá˜éŒËLðŽ†€¹œ%¤ùµ?§Ð¯–¾l6 3a5Ô0žAQºZëCe‹9-ôQmÏÔ†Åj…qѾþù.iX™l Ä’ŒÚ;s Ã;XîÊós _*• F%¢K%É(e ud,0<°åN’Î’kuŽË‘våÏ¿ûÁ‘÷¿×øú[Î[¯7þñ·2­\®öÛ•·^ï¼öù‰¿óÛ ßÿá-Ó”)¡)!nÔ|!ç}P_tx^E¦íöHáî<_Ç{³ÖF½ö.¹¶k6 a,˜ú •!Ci«u¼¯!0Þxr¥¸xcoÊCuZÛ@ Ä„ø™)…«29ì{‹F†y0ÞSÚÉ"™êhŸ-BÕ]˜¦ -ðôþÄÇ÷ωzàá|ÃÞVÖ\˜b"…N'#¦¸Ïø¾3˸˜ö®„‰AàÉh€Þˆ(!J¯¸fºÜ+J!ûp+DƒÞ"U PúX6ôrÁƒÑD(>ŠKk)<ñ B` ¹ã´Ð¸‚JD¯Ù·Š8ˆå©hÂÏROalj'WçÇONLÁ8Î §½°öÂö|g‰ŠÇÄÞ;‡ l{§öŒÑØ=\ó ŠEJ@—5ne%7!.3K…Ó¾rŽ !£y§éRÇE›°íBEèÕYqn‚a6ðÚô3¢BkƒÏUòNk•.‘a[Xlçtm›¿QEÍ©M‘ÒXŽŠj䯄çëÎÞ …F-€$Ôvº çÆïUR"h!ÁÄhu£Y‡,ˆ6¢@!¹R»ðìtOŸoi›s8:‡¸ðt=Q]9„‚,ÃJ’ò`N¥àÆøÁ›ó•Wðè_â·¾ùõÂÖúh ìãô€›/½ÅûŸ|‚íÏiü4g`ˆ1xéˆ1Ò›°o÷ä:ã¸?_ˆ¿Ì³\IOž /ÿvÏ”.Bïc€rå&– y9òƒ'>ù“wèi½’³q˜ÂªÝ¡I´e C§(ã.ŒÚA‹‘˜&nØY·Æê)1Ntïä:ä# ƒý¿•N/;‹.ƒ1%¸SeZã&Q Ì] †3ŒF¿mo´îX‡É‡­Æ‚q¿¡tžž‡E9„ȶ²Bô@½´õÚ°VÑÐ ãûZ|ÑÝ 8ÞaYÆC¼UæŸw7GžÞ¯x/РΔf¶mCEÈÍi}„_¹¼ÐPCw«Š¿È˜7–ùfT“×Ì‹ó Ô;{h0ßIaBÂèÐÙ4Pšc†qüþìßÞ;Få°,ìÕi>6ê a? ¹sš1 Ù¶:ʵ_!EÞý`æÛïÀ/~~á­Æñpá| ¸*o¼^xóÈG ï¼gœN‘ãÝL´Ä&Âm°Q[ï…ÇÒyí¿ð²ó¹;c™'§ï~ràÓ-Â…hí:LÐϸš]ýä:´j2út¼tÕ…%v¦igÛìšM˜Fà;êµ"ÝÆ!#¼(°ú ~áWi{\1³8­p{<ಱú‚…•?Mü§ÿeâ'Ÿÿþ_2þóÿþúœyïû…ó¾ôEáË_rKâ¾5ñßü·üÎï=f≯M´x axë4‹LæpÄfSŠËÌmtZSŠ(‹ ’œ­9Ï׊õa¯Ysƒ צÅÖ¶rwHHq2JQÅ|e¦ÐÄapç‡9gC¹_7yfì×LR¿ßfñ*=6~mîíj9Y÷@)3‡–C\hbk÷†ZCG4”ÛÔ9,ãâWêB÷te–ìÂÃ``Î¥gb ÜþÕÖØµ“B$áìµ :Ô N}bòF…%Îx¬w³@Åy°$.5Q|¢Jä£OþƯÝðÙ'Î_úÕÆö×/üÕ§òÁ‡ÓYù¹×:_þBeYœ?z÷1ÿÃßü"¿÷îë<þÜ#âÍöù†#³úè# ƒÒTJbÏ Ÿ=5þƯ ÿóßœ˜7ÌËoˆ)^{`±éÀÍí-w/½Âíí-7‡åg £«fJ5Š)MøÙwé$º†ŸµÈvC®É€€_d¢ˆòÀVåàûVÔA Å„žU âÌ“±·Í:,`½oLAi©T´50:bÂÌPzv3Â3]Šâ!"*¨E¸ZØ”„ŠR[£ ã¸`wr-õ“ë;fÐën—D9öïZ¥,,3£…Yh”¥cˆA){¥—‚{ã0;Çy<ƒ9' ‹ÂPëÅ”ì:0£€xåþ´Ól¡'8ˆQªã8¦::R’ßpÊ£ 4EÁBy"B ‚w¸KÆ¥O”räîxá‡?Xyç½Æ7~¾òµ7`Y6îÏéŸÿFåͯ*²ðþ÷NëÄ݃8Š.õkÆÁ=Ư¸é2s˜…e¹’Ûzý™J£IÍ·EµÃ4ÁaI€ÒŠÓ«‘ÌPj igÍjüìòàtPãx8Pê…ÜÄÉ+ÕÁÍ@ã?ùÿ}lÔE:½•a3™¼íàUÝ%¤õñW¥¶B H…Ö3f‡=v°ê:=7¦)µc×¥&Þ0ï¤y¦°{¡T%XáæxË©E.ûJ¸ÚVz…LÅT‘0QG/<¨™Ö;]ÓÆ°·qa*Œ­ \¿ûâ õž¼ÞQžïÇåKF‘œÊ¯ ¿ÿ°põrµHö‚¶¦ÞÏÎŒ©„½em™ÜGye«‘eröÒ(uØ«¡²L·Äh||ÿc Ð{¥U%“IS¢¡tE$PKcº²³+Š“Â@ö*ĘèìXhÒÝ…%„HkƒH%6laûµ(W,%sÔápøér5(ˆ %ï\ú€u$S¤CˆNv s5bÔê„kؽ` ‚SÆ»•#7X’ñé©pA8Dˆ}FMXp­Ü¯;b‘ .Šzä …²e>Ë•›`ì=²µÀAdÂCevXÛxƒ¹”3/=~Ìós&_v^yõ–âó‹L‘Ù+â¶ï\DøîÇÏñýAbb­ ­Cä[O_P¿ù¨4 3½BõJ4=â.Üo}àž0ê¾R] &Î¥£e'wÁká²1h39_ÎDu ­ÕQQSpçƒNÏÒ„o'ž\:§œ™ûÌC”h­²Ä‰®£8¬×ŽtG\™’¡AÇ/¾ïÜ££'CDˆŒÀ¨X h "ø4ìgöíL5áÍ/…ç—>ý„eIL ÷y´é¯¿8s§·L²HZ&Ö:ˆ *ÎÞêh¼¬;Øs¥vgÖ„¦™½oàNiSãnÖ6JKnÒÄý©à-£“aΰ!”F[ -ÃNa{öbØq»¢dîM¨Íly”Ùï¹ZˆŒ±fÁxA=Žb˜÷k¦·>¸ÄelQ\æQ$#®}X•ˆô¾’¤qwsýCÇEu¼¤«;“b߯aP\"M QE5Q½Ã¶EbˆävBé̇#?úxãÝ÷ŒûSàëoUÞ~cã›ÿ`#™òµ×•¯¿5óÝ÷á”G³uˆáÈ¥æÄ™…—n„/}ñÀ«ßøóäÛש¿ÿwxÔÏØ‹ŒhãpXݯ—¦N#ŒÚó&,.LVØ®/áÙ •†j'hç‹_þ<‡g‘×! T”GGxíó¹Ïax]È}tAˆü“M«_ÃdAe({sæÅ¨»ŒZ_ø¼ñö[Ê^”þ¨ssT~éFγð»ÿ~ý7ßþûÏN‰Ë‡ã-Sºn"U™Mhf´¦ÜDeWgÆîÒ‡Jp^´<è6½sP¥Š°•N µ&R«üÜãÈÊÄOžZ¨L&u©fW«”N ®›Á(Ã{ê:hEgb ÌQȵt˜tépê›áš_Ë̺Í<¸qnŽÃRÚx¡Ì“‘s™U X¦z½̬y¡î1LcÃNÅæØé]Xu„Tµî\Ęº³#Ä`Ì©š„Œ`éÞ±UÜ;ÑëØü™PE1O\j¥ˆJQT„/¾¦¼õFb/¾åÜ…êíÁ\~ üÃo-ü­où?x…½|Ž›GŸG–—ÈáÀr˜)ð¾'{IìûÂq1îî„ãÍ‘éî%nï^b:Þaia F¥3™`ŒÒ,æ…åxàîö!rx€¦ÛñÙ(4©ƒT6«ã3þ)cBJ“Cw!ÕÂQ¬ÙEi­ðÀœ§ÝY&¥¹°©U.¼êU†×RéÝI!†+Ü¦Îæà™RÆgy”‚ûŽ3ÑBÂ4`ê­Ù  bxœF0Ók¶jF6ÃͰV(=ãrK`lœ¼6brP(›)‚lcÛÓÏžÁÃl N\Ú8³Ué .÷ÐÜe‰€±n‘}OXš˜l°ñçe!ÔA+ª2Ðy+l ÒÆˆ¶ŽKg÷±—)rP§»Qk%hàîp༎²OÆ&ÊzAl"Æ?yÿû…ÓÙøÚ[·_¯üýß­€óúŸQÞújâ½ï)屮1„Q˜¥Š™€Nƒj˜3Á”¼%ÖMxp 7‹Œž$*¸X§´AĚ̹¿œ¡Un~ýL"TeÛ„žŒ»$*•Z‡MY$0Ôî†w¸ä4ß ¤í<Ú„¥;SŠ\ÖCÅ&…sé<¯ÎÍÝDÿé…WÖêèÀÑ)¾“z‡‡C$_òÀ:§aI«ée( Þò@´·Žï޹RZAšà!Qrgo+KRnçDQ(-óôùg£1×+Ž”B•0μPy¤ I{O#¹aP®´?j¦–Ë t5'7Ç® &F£÷«+*•& Z†’׺û5ˆïD C‘°€öÂ^‚tÃ%P}Xu>HG2è}{›0ß™x:× uãÔœÒd²†1}ÝߟXtâ( ^2%L(•žW²Fn‘™N/Np'§¦ø”±¸©×‘ÑÛä5Óc'8\jg²Ž¨“Åðm#ØL š k­$3ªÏdÏïÄà¼|xvq.¥ÐEIц-.ØÈ„–Êê3A “듈Ӻ’Hgá²_¨M6ƒÀ}åfvíÏÛ‹3Iå²]ûn\èAˆÙsE¢QzænŽP…öBõÆ /ã¬YÛNo#ƒdеB·‘¶úì)o±'ž=6Ö0‘¬î|ñá ÏöÎéÅS%‹³7x0.eG4 ´:xkc( A%P:´rf™‡\Új WjÑ ÝfÊÒħÏV¼¯h Ô®üä´rˆÊ ¶N5XBg—kÃÛ¸Éï×4¿%¤Á3j/ƒÄàÇN4\Ü*¾ Fñd‘l…RG+p’HéJ5,sJ½AiÚÅœ}ÛÇMoN =;Ë,‘ç/žrÚ+h"Ã%³˜¬@ÉU%i[®Òˆ:³‡DèY2{ ›qÌøÉémà—€w‹pÑ*í>ŠœnT)­ó|>Áeši½ <¦JW¸½]X÷ æB«‰5 f(m§ûÌO^dbû 4_‘:kGðŸn¦½Qkf¯Òy?#ñȺeŽIo¾;]#iR¬Tr׫¬&ìê”+E¡_‡d ß@t4ˆªPs¦iàp‚q¿Vº jé™=°_·FH%ÙÝ­ìtŒo½g|çãç¿–xûÍÂoÿ_¤|ý­‡|áµ#ÿÛßÊüàœ&–dÌ鎄‡a·ªtJ¯Ðvjm”åȳù-§?BöHëpºìô6<¥Ð$£LDýÈ'k ÆÑ\|öÎb æPa­ÆÞ¹JۑñàæÀÛo}…o¿÷'Üoî0÷N‰°1üª*Œ\Âdz%~ÅáYk ©Ž´ÎK…÷WüËAø¯ÿû{þ‹_;qÞ ‹Ó8|EGGÅtdN3‡ÛiŽhŠLË ]•j qrf>[{ë#ð;“ƒ3Q¼c¾qåyª ïŽäÒØ×JïÊlµ†Ðùѳ¨ †]ϽË(n)UpoÔ<¼·“Êõ¥.àBaü#\Ã[c4Ÿ‹ ;HÚ3Á+« “85$,&žC8¨B¼>.‹ áñK¯þë‰áWÿíÿ´ñ_ýwµÎXš‘0R"N i>2ß<äæácäöYn–ùÚ|ÙIqAéœ%ÃijÓOŸ-̳òê˃GGä𘻗^áîÑCZ¼%…‰¤IÛØÊŠs»¤A¨›Ž„¸Ðã9LÔ0±:DZÖójì=ŒåÊu8€ñ0¶sî,*4›g²Løõ{(¢xS¤+®£`¦k瑦Î5ঠlî4/L>lH¯Ü,|úâ6¾ôè%þô”ÙÚ™GK`æ–g— §^Ï€›0lgç „6¬ &ýg {ê%rÍ” Ïú$™¤KHtc §@omïÜ,Ëxú"òäÙÄk¯BšR;ž+qÖÚSs^l‘ k[‰±pX"OŸ)&ŽîelÐ}´H‹¦‰:5$;® ]êØš;$Ù‡uSvÊ5ãµµ±”¸l;®$ƒt¬ Çêìb¨Dï|`¼óžðõ·&^ÿÊ=¿õ;nðÖë·¼öùÿÇov>üÓ™Ë2ˆJKH´kØRC£Ï`6ñôÙΓ§ÂkŸ3楱çFk#Ó½FGÚí@çö9 ÷‰?ZùÉS'B{gkÂ^:{qœxU¨ Çp$Fï=¯XŠ´>–ÞÆ²F1#lèð]1©[FtƬЈÉÂ¥¢)tRÎ0MwÑ8•ŒÍ°egõ€÷3­l´}ü=½ûÈ*É(mŒE†“ ™»Cb½Tžl;­¬xƒMê˜H˜ÇÒÅ™A-H¯¨¡ …\¯Öê^F³v•±…N‰= ݇µ‡h{†Ø©q"V!F =ב•”H¸6æÞß7zS\Æ}/B§PÛÆa–8˜%òxiœ·Â¹ Ô•(•­9FÆ|ƤSb_«T¦(ÔVhûȈ$É:uÝ8•‚ÙD³±‰âWûYÅh¨ YÖ ÂIgjÁã}y£èÃ.Úµ½ÐK&- £Ó·ç´”¨h¢|z_ÇÌòÓg½B'Ä9&e/sVÆi™Ø·2²|b •YëxOvØê蘦™SM¨WÄ}¨k8ÁR7§øìübÄâ-±î5lˆé mC¬T*ôD¡|µ×¢pÚÎ0ψ貃«vãé‹ /zÄ ƒ ÂZ3½5hÙ©võK«!¢˜ÒµVœ.Ü{çîx‡OøôÓOQ&K˜m 3¸QKA‚ ÿeolÙyžç=ÿ¼Öæ§HW IDATžÎé Al€1a’"MѤ$Ò¢’eJ¢ªDÉ%E¥0Š¢TÊ©Êm.’ÊMb+åXC•ã e¥¢X¶¥X¶)s)’H 3S7úô™öÞk­úrño2‘_äTõåîsö°Ö^ëûÞ÷yŠGÒˆT“0biÑ[zo9]¤©2ÓgÏžã`“ÉSËG škëãË~N*Áµ/Û’qÎ2ÄÚ‡óލ›õuÄ-›E¹cœÚóÕ0ó`\eØ”f@Í`mÛÝòÚÓ0bÂÏ5¢R„7NQÆÓµW›HhÍ6©VŽ3m %2£?Ö‘’'RT¸Î¬F3’Qxß!¬8übÉÑ:ÒÙf"¬£BtËÝn†6᪥•±´qˆª­À#Ò¼&ÆÚ.%3 ޾Ä5l)Y@Ϩ¦4ú€¶T)RZ±KP}›‚IÜEXÆ–íÌ#beMU)gÈ…8Nœn·äÜhÛ©cæ5Úª$DÅæƒq­°z4DÖÛĘ5ãvMI‘Lû®5X¬+,$²-GÙùT#'ë©À45ÇPJ”Z(%ã´EúHŠÍaªÇ[°6´~JÝ™ƒ(4lj6Ô1’É9!Ê"j¤dx²£ZT/–‚”Ì4 ¾ÓHJH-äâu÷]/*£ºùNÖXZlL; •IÚñ4H®l¨ÌŒj‰j©ªbTÛúkõ®ŒÝU²í©EZ)cA°Ö‘r»a0B;¯ÑÆó>xBI =#Ž,xY4è* Ú†h’ÔÐýœm1peÚ¦;§‚è]›Û"IÛIQuÛì—4µ8z±Œb)’´C)Jµ”TPSU,Õ„ÓaJTm[¥-ÓzhÕ€8Œ) »k F—Ö«¨BÁ¢E“ô„Çb š »8fAOa6'Mc :f¦˜ñóÀþ,0 ”Pu&gM!à”8±Í‘lºVj‹; €Rœ xë¹jšqõøMlœ (€L©]³ØU!gÕdÞ0ógÏïóê›kò8a{C5£3)Û6U3†~(&µ•—uL5sf>k8LÝ$ºT¼o׸v÷Âx?gN8¶X#*SŽà<•Œum¾XuœŽ3ÅKrÒäêX¹& D#ÊPŠ@ʄЦ0JUD×–G­ÍάeBUÓ„-TRJÄ4aµa«å’c©q 3Æb[7ÀzFNЂ h“)JcE(*‘u‹<©”ÉJSMÁ‹a’¶…P”6Äh©¢mĈf¶è‰ë„¶M’Ö º¡ª©­;(‘*]!EÅ”ÚdÏûïAI¢ qÞˆ1G&󠨮£&AJçÉ9£SN•Ò\‡ZeJšH¥Ò’Àê)%L“GÄÐÍ=¡·Œce–‹Ž®?Ãx<¡M¥D…’öܪmün«QeŠ ¼y}Ë¥gáàºå¾»—Ü}×·Þ¸÷{|óéÈÓÏ€ÛÖï¦ÇXCRŠ,[3[ãè*Ð÷–ùl¢’Z,07ƒzQ ²FGC4pj\Ó9KRºõ¼¬¡C3(ÐγcYðÜ« ^¿Zyànáþ;…Wÿ4’âHÜN¤¹cÑy¶JÍPL¯¸éÌYNGÅk' m„ζ VºÔ6E¡4-£m+çâŠ6‚˜âš<­‘qFŒíË®0x"töHRôJ·m‚Œj¢=)‚6 wr?¤‰÷´pT„×G¡ó–½%{’_6˨‚hjjÇü×äd8©§5ÇäÚ´Î&œŽlóÀ°Ýâœ#Wa@£j¦Ö‰`{z7ã¸TŠœ÷¤ÃêŽ_¼x9Q«âŽÛ"wÜ’ùúók”óØ`‰Z1ÆJÞž2žž@ÞrÇm†·Ýøê× /¿ÖÓÍæXkÐJ‘ÑåwÑBqíÇ m`aµ¢ªF%" íBQ[jè´&ºÆ¡wƲ2Á;iŸËXðós¼ýâ} ›?c•Nƒ%LžÃë†KÏ*®_wÜóŽ%ïxû1·¾5pÏ;V<ýláÙ*Úy„å" M@J¥âP6·m†é±ÖðÊ+‹/Yj ¼ý¢pñ– ßxf¢›FŠÑLhD‰‘4 ‰·ß>ãâÛ–|å‰g_hƒ·ÎYì®·$4º”XCÊŽ]'„n 96|³VLâˆcf#G¼›Ø_îã}û^W2¢”¢Ö&®2GT!«„k!f®õ÷Jl‚3ë›­9i®£ÔÌ(5ŒŒy‹ #?óxà§>~žý3†/|qäw~÷˜§Ÿªìïï£tG® caLBM™qð=<þ ~êã+Μ±üé—~ë·OøæS… mF2}ðŒq¢¤BI‘í°¥ë ?þã=?ññ%gξô¥ßùÝSžy:‚j½›Û´ÜjS­‡b¥‘Œh‚­;!Y®mo]t‡Ó‰Z 9%d*X«#€Æ+Óz†S–¡Ä]ß(Ó£éÔÈ6Y´©hÓ¶ÂE"½4Ä«ßu1DMXcÙ&)R”Bvޤ"…¹),½¡ìˆ’SŽh1ä2a]f¨=a×ýÔ¥«Â›€ab(-• #‘h¾‚MkZWÆ2bjÛüŸëÔ,¼œh<§ GnNN`)(çÈS¥–‰ Êz&Uä6¬ýöPQé„Ñ–Jf,‚cÂKRS5ª:ÄЄT¼2ízÀ¶„@’ÖM*Ò(—2š”3*”õDvÈ c ª¶Ø ¥2 Ê ªLm2Øi ™œZé§ÔBÖÆï¬oÝSS[Ûy£qb’&ÛRIÀiÞXo™¦L§1”+(å©Å¢$³¥T¨Ê,XfÁ±ÙTNO7x•ß(ø¦³l¶´Ðw†ÎyÖYñæÕ‘®SÓ±?ïIq$Eœ& ¡rÆH$«°+ÈŒX£ β)š-­0&­cŒ¨"XÓîr+Ž)¬¡ŽÄ¢é¬ÅZ¡PðFsÙ%§©’‡ oÛ]£BPÞR28«ÈU1¤ Y°Öá´0ÅD'#ÖÌðÝŒÍtÌ0`Ť ž€PÍ!ó.0䊈&˜F·ÉÅ¢Ý*àÄ‘J¡(…¶-RÕÔ厓Rè”pÁé EeÑwĈ ( …¶ÚCCž°º¶ ²/äÃ-ŠÍƱ·X.WÞ,ˆJÔíç[lC£ˆÅ¢sA‡ÀX=²­(%›ÚJbÓ´ešNÉqKÉăr†œÙlqN“C;‹³Š}ïÈb9ÙN¨œðÎM¦Ô„Rž…5ˆ1æÖð7âéûžo>›øæÓƒœå½ï^³ZYn~Ë’ßûg<ÙàBÎ4½j¤&)‘l<425ñÚåm$Æ8BŒ ]#ã˜8ݬÉãHɤ]¤ÄTØn·í"£Vœ+d)ÌÃŒjY;…$#CÑ\#‰Ð£”€i n‡Eg8ÍŠ"šœ ÛÜ&­èÙ£”F©±)iÃruoa;H»P0ŽÃCËÕÍ}÷tüG¿èxûísžø‹õ¶­Èµn_8W¯\=HlFOGÆR1a´Ãw ¥+Å;ú`·]7@Q–Ú¤0RYÌ8E‡RÎhˆÊài9]7ëÃ’œ-®tÍN̸£ÝfVSÇÚÒ Ðmz;e(:p°ÛÒ-º ºC´&yÅÂOœJeÊ@Qmº¬3˜ÊfÜ`¬b²3’;C”5×O#Û­°˜ }H\}ã”$†”2>¬);œ1ŽC5ÌȬ ø`Š´ õÞ³Ýx.=oxáeÍ#ïô<òÀ–¯=uD k ¨SbjÜ0¬¹ÿ®ÈcÍ0¦ãÒó‰/[”¸]×CICªîv”ªˆë)…¤€,„®ÃÕJ¬Um©)2)ƒ×Í›s}TçѦ;R*3.]˼ôÆQh²Z<¬]Ïl±ÇK—OøÖ³ðÀ½gxÏ_[³ZZÞrÓ’ö‡'¼|ÙpfiXM*B–f·w®CÆq¢¦‚±Uõ\z.óÂK†GÚ㑇 þä1Ç¢ˆµQõR.Äq ¥‰ûîqüµG—Óñ­K#Ï=Wð¡GAã‚g]2 Tqx$[V«žÙ섚ÆÇv!?5€JLiÜðø-ùѹ€sµ‹\ieðºXÚùÎï&¶…ªaÑä¶jdA¤b­ÅàÐ9r˜"Ó4ðøÌøù¿{޽•àûþÆŒœ,¿ýÛk^»<Ñ/=ÎÏHµìâ3‘¡4aâÿ肟ÿ¹³ìíµÈÛ‡>8#'ÇoþÃCÞx#j¤M. g4Sn¿,ðC?8çïþÜyöwýœ‘’åý‡¼òJ¦ØŒdß‘2d ›[O¬ Ñ=n¦‘qd[-Qf*cÜŒõX8 ç3‘1N»²¾£«†¢3Ç%R±Ìœ´n"g5Nš—Dk¡äL§*iTÌ— Š0fEΊ¹÷XÉ[ ºf¼®lSCoš¸¥êöüâÁ«F‡Î–²»þ+F#¢¨q¦2W†õ9ñ'B§4Þ®aæ Õ\ÓxJ.Š™i=†³Ýœ“q¤–Êܶب. g^FhÑö<v‹ ¬)²ÐY‡Ž#V9ª®xÓ‘‹•%› ¾6ù똩%aäXb¢¡Ã²¦¤H˜zçˆ1s¼n+=§›T˹Liåei§GQ™,MÄ5Æ5ÑÏ SL)ƒiöäÕžgH†xºf~60ïö¨J£!‰b}¼aœ&RLìŸY±"%mXGÇɘpu`˜¿æ˜oÇ ¶Î™;ÙÎ#µç¤[òÚµ}>÷Dâž;ßÿ×{ÖÛ‘ßú§×8<Ýã'J°yb1K|ÿû3÷½Ýð§ß¸À3oÞÆÒÍèº9.’Ñ$iDZ*`Œ¡·ïYÍf\;ì¸r­òÐýÂýwnyúùrÌLóÙwzn^)B×a—ûÌW𽕣+ +Ltt¾²G)­X·-BV%‚W§ó®× ðº€k±æ ˆëYÖDÔ¶‘2Æf&mÐ:qV{6uÆph™÷žÃ£ÀW+¼3ðλyêÒaÛþn7 ™(‚Ög=ø,½¶­Èlº@W3y6ç+O|á+ð‰íyüoZ^~åˆõÙÌÐJà5Ô’¸áLâÇ>¶â½­øÚSŸ±Íûtó%bz"–NkðŽ"àJ!çD*à†ôh#Ô) DYª·˜©4´k©•Q kESk50¦‚ˆ£”Jƒu6¦ƒÂ‡‰q°<ó"\zVñ¾w/ùÛëV^º¬yþeƒhOðŠ=¯9¬…j!‰Â•„Î+š¬3]Ôù’¯üù!Ÿÿ"üôÇgüèk^y%óÏÿ¯Sr)í{±fbNÜp¶ðãßÈûÞ{¯=¹æ³_)SÀÌÕ¶‹Áº3×öÖ2hÅñ‘âàºáûV/í_I,SIÌueÞõ ¹£µa› ²9!•ʪ_!iK©M\Š´þ€€­‘”qNJ4!¨€¤L´ízØÛÒÊÏ(gwHîL§-½±$iP§&†Zy«– R=U;(‚‘JLB)`¬BU0gϼå¿ëñºJ3žzS°(™·<—D¤&”8rN <½¯PcõälˆÓ.;ž#÷¼å,ýÞ[9<=¦w]4†vÇgh¥ JBa›ö¡Ú ƒ® a*Šš *N¬f+&­WÃòÜÄ:!âÉ¥6äÚTI1±M;4”jºD¦ÆŠ®•\2SŠô®JÊ‚èÖF7:¦TÑÖ›VÖQ•,m•s2F6›C泎¡(Ò8lÃß 95DiQŒã)*g:µ;pœa4ÃÔ0¦egN´Î!©loΗ{$Ó3ŒF+rõí¢i u㑪Únpä84 MXU…œ04|ŸÓ‚®¶q…•¦¦ÆMÆUJRL¹¢«nέU¨Ó€¨gÚʹÖÖUÐZc•¢L‚O©‰ËoŒœÛw¼ï=çyøÁ=Οw\¿¶%N™ÕJxøžŸÿ¹›øÈ÷ïqý°ðúLC9kÈ©2eC>ösþ›ÿê"ÿÅ~ÿÛ7sñâ’Ţ烸À§é"Ÿþå[ù™Ÿ¼áߨ¢¬¡(CRç;”© ¶¥¬kFe ã´+ê†6£F¶ë‰3gݿǷïqáœ/|iÃüÑ1›!`\Gpg›,Ç)°Ú0ÒV‘ZETÌlÆÌįþìŒßüooæWþÛøô/¿•_ÿÕ·ñÿñ|߇n¤ïß¶ä§âþ³O_ä×å~íoäÓ¿pŽsg O>=Q²¥ók XË'ò´áá:Þû®=^}½òÅ?œœ(|?'a‰Ò|A ¢ R#÷ßmy÷£KÞ¸&üÙ_T®w,æ+Œse鼦”Lœ5M<ñÍ‘¿øÆ€VŠÙÌÓwk(û—þyï¹óŽ9ûþ³yúéaEÚŸõÛ±ŽšYg0V“R£KH*»Ív :-Üó¶‹lUàëÇ(Ó#F¡¦‰3]OЧAM`ÕÀ¨¹@gí ãH‚JɼxyËÙ•â=ι½•ðæµÍfËþ|äÁw~êGàÃïëG•““m:–ÆR´ÃY°ÊR“a=$^¿¹q_ñ®ç<òàœÎ)Ž®o†5{ó¾[øÔÏíó½ï[rýHóGÿnÎWŸÞ',Ïbç+´é¡ë1xBçQƶ˜ˆln“Ûy#¤Á·¬Qœí4¢-­ô޵nª"J›*ÆShhè‰ÂfÌHn}•§.%¾öÍ‘ʬwtÿÞ÷Òqûm½ßÓ¹ÈS—&j4x­Øë=K?cÐí<!ÖÊ«¯DtIÜs§å‘zÞóe9QËò)7ìoøÞwE~á'5ûçâ­†“­á…×fTwÓ\×á•C+ËdÁ*EÕŠ\397¬ãå×G]æ]÷;î»Ë³·(\»¶e½Þàí–{nÝòñL¼÷á‰ëÇpº 806 4è~†H¥Ô¶QÑzÝ< Õ(´õ8mé½#„VŒc±ZÓYƒˆ&KFkµbTT±º±ê‡¢(EQ¨L±m_¦qäÕË[öðØC=÷ßÓ³¿×¶Œ›-!LÜ{{â'ÿÍûÞ<O¾±à Æ‚uL¥‘ïÆ±põZâÂYûZðØÃ n:/®Yo¶œYF>ô>Ço|êßÿ½gxóþñÿùÌW{æûçY.÷èú®ë]ÀZK©¼åT¦V·ë?´›SZ·G©‚IXºð”вX«©¹.*BÐíûHIE…F7ºWäÊ4DŽŽÎ…ûÇM0³ IDATï^pûÅçÏÏùâWþèßnÇÛ-‰Îú9Aµ˜G­•T+‘Š3†±3gˆSæ•×n¸x×#û¼ë‘=n~‹ãød`šFÎì þÀ‚ßøôÛøßÀÕ7+¿óOøWŸèfsf!´çÔu¤œpÎtëj]½ºå G>Ë]w.èzÍK/žr²žX,ßûþï?½ÈOÿÄ-|ã[[þä³GìíõŸ*¾úÄÀá‰AÛ€Q…Ð9”õ;ã²"ìÎeì|Á|¨LSE\cšZRe*‰Î$~xÉý÷ïÿ¥cöÚðg_^óÆUýßU•fG­‘R2ÖU}dÅõ±×+úg.¿0‚68ßa•ÐÙÈ8d¦ZÐ6óîGÏpßý{鱕/yÍ /g‚ ̺6Œ•Ú ñÆjÒ¸e€h–Ö»Èjn¡!·ë§Yg[¤²D Y,’›Ÿ% cÌ8Õ‘•´Á¬Öi„#£øBévƒî¬C{!æ*à ÆÏ‰›M‹kEÁ •'‹ÃªÒ.¦'iéU()\ÕNJкâë.û¿»)6ZCÕ-ì Á”º+ÕGzÙê%RbbÈßùVB”“„sJITK¢MØ#Ë.ÕÑÊþ eš£ÉYË”c ™ÖKµäÖ²E«ö|3Šœ2Q;bÉmˆïBÛB”„rž,£„)Mˆ‚ 4Ú :ômzã 瘌!­O¨53J&Š5ñS Á@ö&:×2H¹Å2©%žŽ9S,P,o•êÖ Sc*Zt“dM;+é±bкKÂ1ÐÝÊQ93 £O¹ë–·rÛƒ«tÎb(”ª¸zåµAW51‰n'¹V´ie ­ J4NulkfÑWº¢v¥1G0²!XMΊ1%ú0§:Ï0®QÅP•%N ßÍI1QKÓqõxÀTÁ¹Ð°ˆ¥ÐyÝØù›Èl>GlÏ8ŒèIÛ ªßÇxEš&Âîo,Eá0„®RmàÍ“Sršè\{“TmX½’¡·­dÞÛ†4³ ÊÔʯ“j½ˆ~1c½‰Uq6¦J1m ¡sŒ›m‹m)0Òâ]Eɵ©Ü]»kŒjIh)8m£b”Øž¯óØÒ±ð?ÿÞ!«½žû‘›øµOá×>ußwM'O=s™¯}ë]G”tlr1¶»Æ½fµœ±·êÿÒcû®ýPdfÝ)Vœ HmÛˆ’3™J­ãD¯›K¢HE» .xâd˜MÌmÂòô3‘o|+ró[öIYxêRâùËÐuÖÎ1¶!kµ2Ø–L ¢•!¢˜ŒÆ÷‘4iòN õÿ÷G1¢ˆU§Œé’29*”£mªCë‚ñ‘jýNææ™ù&æÉ±¢´ÅY·C…vh]1fÂZCE#U׌ÓD‰SCÈa0Úó¥'F¾øå7Ѽъ‰¢v'N¡ë„Ï)Þû®%çñyèþ%?ñx`³=àÿþ†:to&:¥éC² EC‚¯•TƒèC0‚ÏçŸ|ª¡ =±6 “ï¹:5Ò§lËŒ#3ŒÒdçQ,6Cp=Û4¡ý•¿ÿ¯7ìïW>öáŸúÙŸúÙï~­O„çÞPk©Uxs,hkØë4S®$Ñ-®h,VW6¢ðQˆZs¬R@ëBÑÐ;Ëàf|íÙÊW¾1BÍ(ç¾6¶s•÷+?`ù[sÉ;ïñc LSâŸþ›ë”ÍœãÙ†¬f6ǘÀR–ÌëyræŸ&1?öƒ–»nï¸ëöåw¿(Àz+üÉ—á_~~ƘË=MÑž,­û „êЪÅIS·Ä/&Âx–i»æ??±ì#ýÞÀ/|bÉ/|â»ÇÉ)¼z \ùji]7¥-ª$²´ª¹nd7%•è 6NÔ* ->'²q`*½¶E#iB”f#vǦMò·UÈCbå[o[Î;\è™ÏWM[~ÿ_³X~ðà ~é“+~é“ÿ_ŸÁÊ+W*¯^möêÞL˜A),œbÓW²L¼øšð?üOkb¶|ø} >ù“ >ù“ßýÿ=÷âÄ?ú_¶üÛ/jæ³=V‹‹EØ€sc{\È5ˆô&“KÅk…ÔcÎØRÁ´­ÚÌE4ŽœÚU’²Ô܆CÔÒ†G¢ÐIZäªÔæ© G¡º6óNóÌ3ߺ”yËM{ä,<û\æÅˉ3g:‹%® `­!V‹*íÜQEÑ9‡É‰½ÅŒË¯fþÁo‘²áC8Ë'?±â“Ÿ¸øÝ¯Éó[~ó¾ÊüÑÊÎP64ÒanÛš™m>Ь ¥jN‡Àþñ†Ûn=å#ßwÿäW÷ùµ_¹·Ù¶h­ÆÂüáü÷ÿàÞò–½óFŒ6íõuŠÙr¤BJm F9Ò$t^˜’P$£´'§‰s½#î-¹¾]cÆa&™íVñúÕÈÓÏD®hΞóßyN—ž=á™ç3‚Fk݈ZQuA² Uqt Ï>“88Pœ;¾óØgž9ä…gר`°Þ‘cÄ{ÏT5‡–ÈÑAä©K×çÿ_½ôìÏ¿0t»1œ”!àÐJHñ»Mº®™Tss+aiAƒR‰2SªÌ;MJ«›Lg5G¨-2­tÇ™3ûl7'LS¥u ,ÊYÆ2¡µÁ2aœ#§@L[L0d5cJ#â•Q+áB·ä`˜Sl J4eç¶QÆæ°tp¼œW+:ôdI„ª‰ilB‰Š°t™h”Tj5¬µÆÖ‰ZF¢œ2¤" 4k„2V¢òx¢##9éæ8Ácdá-n®‰ˆU¨9“ë Õø6Ý%•1 Êj¡¹»œÁHAlƒýXmÚ¨&œ#Íyunæˆã¡£N©ü¼eÜNhÔ½½_4•41‚N•£i¢ëÛVÔ– ¶ÀX5ÆjH-ŸnŒ¡ªŠw©¥¡ÊÆI7‚FÓƒ™/Øn7¨Ò ¡Þ+¤:rÎÄ<5!Ÿ6xic Q-£Lã+% Äw£jû(‡2Â&ƒ8å©U0NXøŽŒbÈ O˜ŠÇ9ÏÌd¦š‡ÓvgtËt¡#¦«mCc¬Ç‚ÕkÚFC‹«óDuãÚ‡¢(µPk“˜eeXO†NE´Ö +jœÀj&QÌj!Ó¬|ZQ­ÅÅ¥Û¤Õ7:ÄéP)¹RÓ…¶tc ¹6UXž Õ*ŠXz×¢-7,÷æl·#’#Ƈ¦Ø.BÁXEŠ•Ù6±S3w "‰ªaag”nÆ4nÐ’(ÅãTb(cãk§‰š)Lãg"ï}lŸùØÍÜï7ßÔ3N…_Þò¥¯\ç_üËk\z1ƒï˜õs‚›‘ãÈXàl¯(eâÚáqËÑ•JŒíÍmHÊ–Û4¶ x¼é·b>ŸáŒE™Àjxc;bÓÄj¹ÇÉv®–"žù¬`½çÚÕ#DFrZ“¦5§GÇüðGÏñ÷~ý>bþë¿ÿŸùÜ„ï:fû7|›HÐ…fÚ‹Q ¤iÉ­Œ˜R88]“6Ǥ4Qò„(¹P)h¥ÚÍ—Vß‘d9£ Ú8\7ÇõsóŽVh]™YÛG'Œqbœ6­àf ®[f æ‹":²(J®l·'˜t‚%²Ù¦ÆŒ6öèç |? ŽÌáñHN)2rzºmEL»“©&¥ifZˆqd³¸õFá×~ñv>þø­<ùÔ ÿÝïòÄ·zçob1kY}mZ·­”p}jÈA£ = «×Sn!Ö’Æ6Õ͆&Àª­i½¦J#½TgÀu,•àtf=V4‰iØ0nNXŸž‡#¹'óÑôÜwWÇ7x¦±òÊë‘'¾ùÌ—*¯^›£W7±:{žn¾Ä»µ[ó—¸åp}Êæô˜ztSŽxð™~pƃ÷Î8³o™¦Ê —G>÷g‘Ï~E8Ü,Ù?ž½½ ¸ÅŠå|NÁsR=Õ6~P]Y)RÉhåqº¶-§Ò(k)RÉÊahk‰¥ÿKG{+ˆw ©™qˆÈpÌpz““CÔé!uZ#» ¹³«4iWèÕ52¥D6Œ›cέ~ù§Ïó£?tO=3ò;ÿ<ùÚÍ,oz+û{ç «½ëbDÖN¯qrt…íá5ÎÎÖ¼ïQáýxî¸Õ1Ÿi¶ÛÊó—3_~2ò¥' ¯Ÿ¬Xí]àÌ… ì¹€YœÁ„~æ1ºÉؤ&ŠjI”aÍúäƒ7¯2\{Íñ›Ü}q˼G󎋞.XÆ©òê뙿¸TøÜžë›³ôgn`qö«Õ>ÖÎÐÒz](½›B © E,S.Ô”èTÂÇÁ[ÁèÂIuí˜K"›€ªS£¹6u‹Yµ‹(Ñšœ'6› §§GLÇ×Ùž0Ž'<ôŽÂGþzǽwöÜxƒg —_‹<ñÏ|©òÆõÝâýâ a¶À8C¬ Ç 9N¬§c6'ÆÍ)^Žx×ý†|pÅ;ïqvß2ÅÊó/ ü»?]óÇ_¸vØÑ-÷Y­ÎpþÜy&c±¦‘ô±¨:¡DᥭCÕL*éÿ&ìÝ‚¶M³º¾ßZ뺮û¾Ÿçyß÷û¾îéžéži6220CTp@BY–UA¤*‰‹„ˆT6•òÔärÊN+˜„MJ¨)‰Z¤&À ²Õd3ÃŒÓLo¾Íû>›û¾¶9XÏLí>í®ú¾gw_×Zÿÿ$M:Ò\ø&£»Þfdž·ÌÚ<ÚÛÛpMËô\ˆ!R{aÒJÆÖ*í²²mî_<çáÙg¹¿Î7Ã-ÿéŸÿ µþûïÿ4?ýóÇ/¿—Û›[n{J¯ÔR¢¬W/ÈŽópYéÙ· —õÈår"Zæ+¾lâÛ¾ùe>ü¯ßñøqdÛ¿õÛG>ö“Où¿üþ囓„†™Ýí­W–0HH=s:ɵ²^ÎL)ó _÷ˆoýæWùà—Üp8DÞ~{ãç~þþîßû]~ñWž“W›HAÙrf=½àt<Óµð-ø%þÔw|€Ç'~êãoóC?òi~ó“•›ýŽdAü ¼ę̂ mgNç Xå[¾é%¾ó;¿''~òãoñƒ?ü;üöoežœˆæ}¯†›´/ëÆýñµÂ7ËK|Ç¿ó…<~2ññ¿Åÿñ#¿Ã›Ÿ*Ä8±Ì{â2̽yŒY•ÚQ/¤èØõ¶eZˆ 6—VÖF»CbGåíç+—:h7§É'ò-s)FûÝÄΧ§Œ´CêF²­llÛÃv`~a]ÂF¹wz"‰p“‘b¤öˆr$†èûHãÚÓk0JcÈ`Z|;Øêðø¦ j7Š &&4¶¦qtíZ;C<ÚÛšbæÐ íPc¤/ä>@ѵf—·‰RÇÕÚޔȅ˜&§5¡Ð-R‘bœsè[‡I"µAS§@^ƒq¨vÎ[a7®Øì´M1šÌÀ†  ^¯ ‰œWBùâ¯üÆ¡çg(>ù4Sú§:äj ›‰W£±½oÛWÀ”˜= 1 Ò²ãùÃJ¡ŒH”JL;/ÊöF\&Ÿ84/SªÔ– k£O31etBT+2G¢\m°}ãTÓrø/%p³t»e;=…n$ `•Jˆ‡Çô\(uã´9Á¤‡@Ïéö—¡ÌÒPmœK`4%…FS!v…‘ݬW£_*´‡i‰Ôî·Ø´›¸ eê™2|ýén^·3–­‘dEÂâ?ÖúµÄ66'ϤɧÍfÆÕ®¢.Öcmʤ•%-{6ЉcX[yïë_ÀÛÏžR.÷LqºÆ³:i:xÁy;ûäPŒÜ Ô‚Zdg‘R …€„Æzîè´'J&Ze+Ji´ÓèÔëtä­F»P¶Í7ݱ|É|óB`™öþJL{4íˆS dÈÙWÚ"ƒµeZöÜ2ÒdÜŸ›OSQ4)‡ÝLÍÐIŒyÇ~òG ^Þ]»{FíLÚ¹\ñ[Ñ„Z2uœÉÛÊvzŽö ÿÙ÷~)ßûÝ_ÆýõOò_ÿ¿É³û‰Gw/1ÝÜ0$\cD‰Ò ]gÌ\pDˆÖ¹¬ÖõˆµÊy+0 ´FJíIÄ1™~0‚b†Zð‹ÙrCJ×ËBä²2òJ.)ùŠDÛ‘Ôè1ñÒ“A/Îj¡õÂéô.’3ØärSBºeÈŒ†Ê5¦¤|âÍgäãsŒÌyu^uL3)%,F‚Ÿl KÞXžq>ùö?òþ‹ïýRnnÿÝÿúYþÖg^zò ï^Aö7ôqÒ•© [v_ÔÆ¢e·ãûLÔÁ"/ú@2¾Éè«æÒH*—…Þ”™Êþ°ç‚B92$ ½RË™\*m½°Ý?ã||N;­ÀJcPq»²Í;¦Ý ûÛG¤ÃæÃ-w‡M±Ú«¸I³œ)/¨ïp9=cÔ ÛyeÝ6ÇýIQ¹Û/ŒùÀîö1óÍîvX i>pÜ„–Q…2f”æJO†dÝ}.ÜÉ"„½1¡ÜçîSc<«…zÝm­3ÚF;Ÿ¸ßáøü]E9Ý#½ÒìÑ‘i‚°#&èŒÖèyãáþ)Ûówxxú6ôë&¾ïÏ}€Ã!ñýsåÇá1^{ƒùñ+ì¨f»|¦çÏOœ^p¹N??°®g¶Zœ…¯†ÆÈ´ì˜—»ÃÝÝ+ܽü„'‡²ÎÕJ“o>d°PxQkmÄ5C½ðüþ9§goq|ñ”õÅ ¶‡ç\ÊŠt‡< ›ÑifÙݰÜ=æÑÝc–ýcæå}(±¬tÎBt¡Z« 5·‘FÙ(]µóžið™KÇ’#©.ÒÓ²2¤S»›ÆÑNÞ:¯¦ÌÝxg<ß”Ëvár¾‡Ó‘ÓùãñóñHÉZÏNÔët`)¡ó»Ã#v7æåµˆ§"åÚ ^ØSy89OäË=ëz¡• -g>#H˜iÞ³¿}Äîö1i:°ßí±­gNÇ£oNÔ‹Ÿ½û#†à²4U†)¦´Ð-MHÁØš›Ÿ'‰½n¾Í­3!QT 4¶õÌ~Ùc]ÉuE¨ä—ËÊ–3ÛŨ!…ÈØªtUë¨ÜÄDœ#Ï.÷¼1¾ük¾ŠŸúµÏòü“¿A§rÎ ]O„è:÷eTzŠ‘Z6÷ uÇÕ­s$aؚ҆*Bƒn¡P™˜ØÎ'vZ¸½{ÄùPÖÆ~·’q)n`N¥RäÑÜ9ç3çjìB$6¡·ŽÚFb+‰ÚWBšA©¹_%Hˆz¦Êc[·FP#¯Ò<—߀̆"¥µÓºqع½oÍRFæñ!Òûš£5’re§{B)ŒØD9c!Q&!JÄ’a_Jbh¦ô@(Š^íŽOß~Î<¾è‹¾„ûíÂgŸm¸Oä¸#%öF•ˆúæà\6Öz¥! ÏíM²b)qÞ”)vî–‰Ké¼8_è–Z™lÐR`ô@mŽæ”1„8:fF— KÌ!QÂì…¶& Âk/’íøäÛg³À¼xÎ:‘.Š ß 0cÖI:1ÌüÇ© ¥BVq>0¢²µ†ˆÛh˺‘%Ó¶LÙ2£6¾æ£¯òµ}?o¿;øå~æ{c·KÜî B½û…pÔŽÑÁI„D'«O°utÌ`NÂå¾Ðt¢S¡ZÙØr½þÿ™¨ ʺfÊÖ¸tH¢¬uã’+¹Ã¹l”Rø_ù2ýª÷ñÎSøg¿¾r>'Oö´XÌ-ç[Þ°U"ߌÍÓìÑ)ÑJ¦Y"MŽÊ-&„ya>øù¤1üûRÚgâÄíf”jŒ¶2ªÐ†]-×BÂèŠ.;T*‘]ߘCà~óIq‘0%‚—bŒýùfJÀ@*Sðíªª^ÙðJïZã•›;J+¼»BŠ”°£4(þL(ƒ]ªô]r^½\-Ù½£qÁ$0B‚ Ö=0â-ôJ¤Sˆ„dÜh¤—‰QÖ«ü10a™fwÙ£«@ôßzmJ­+·ËL¶ÁR"Á`[Ì‚1OÞuËtßVFÇñJï”ÞPë&Îu`†$.¥Òº`£ób݈!PΙ‡ìùþkÔ6Hfl£#í%Íôfl²Ò•Rýò)B0¨uu‚YJœîýr{8GÜ„wŽ…]š¸IJ.•Ñ!Àf‘‚hc[ SJ˜$ØõBslçô‘™ú`6åF%eP«1jCÆæg¡6è­ztL#ï®gÚV\ÈjΜ’[ˬëÊZ…¹¹ ¶Ï‘±žÀ7-÷—•‰Aº;°°’Oçk maä"‘Ð *î‡Új`ÞO<1áé}&tߊ؜Ç#¦F§3ÔËÈÞKˆ`Õ#|e«Âh¾®ÛðÛŸ aH‚+ífU_ÉÖ:‰% Ë´ã“OW~÷cÿˆ#Ëá†ãÑ%E°™ÑVgªnŠÙÊfN-Pè3c6a aíJˆ°‹ƒû,´Þ ž}áRˈˆÀ³gÆ<'ê(tÄ™ÏE½e>*ÿò¾U©Úغ2e‹ÑQ7Bê(­ÎÛà0 _«õJ¬ •3³ºeVÄóöÒÓ¬œ«—IB€Ö jÃ.9‚´Ê$Ê¥.hkטT'Ò%B©dhóƒgÑDتgÄ ê)sÝçÓ4³ÌÞ¸œWâ<ûÄ tnnÏÎ+nu&¢!j5F…1²ç{C€Ri› TÐÎÎŒ–„<ºe¢ú4åÅzaTXt¦ÈY‚ÇL(ð?çKI9öÈÃé„ĈYDèÄü}ÿ@›ÑÒÙª‹Ìêú„ɸ ùzzº°Ÿœ¿»­…0š"½xÞ]¬c!qªŽ«xÙ<ŸÎñÍVGé6¨—LÙÈë™7^[øãßþA~ßGÞà‡þÆ¿à~ì-T#a¾¡t`ø%¹GÊ%5$v¬såz.Ë@6ŒÛ}$Ì;ìùÉ×:!A©Du ½Qh>¬Âås$/5˜¥5&XFŠì¦ÈÎfÎGBØ: »(Œ(äˆÖÁhür›Ç‚£MéÆ6 ‰uDjmˆ çÜX7Øï¼öêŽ'ûÆ[÷g÷P)(Ä™ÚnÔËJΙ6 _ôÆ#ÞxãÞzçÂý‡c˜ij÷(Ø(ʹäi r4ñ‡G”^8 ´ D…Q(Îh“atSض:– IDAT»þh"Ôáíú㻤H™‹Üq÷H Ô2(½±Se§ [º!ZÇl‚°øDêºmµsR£Í‰TwsÄv•SZ¨õLl>ím½± ƒ}˜8‡™eŽÌ]‰1ÂØÑØÏûæØ„—^pvzJmÝÝ^2¡TªB0 ŒèÛ„ã9S6a™w¼|wÏμxxêøãºA8Q››½·m#Ÿ(ç µ^í%>ðþ—yçiæþ(¤i!ÍSòˆâ"`Á/·"¬‹12ÄÝSÝ8t–Ô:\#'æ)AHhš°Q „Üö+ U¨â¿é‘Aþ{N©Ø´øgjÞ©S¾ð¨5ÎïΠH0RÚÑãÌÂõ¢e öÖ}!CØz„á>Æ6Žzt0š@ï„‘6Ñ r7ÏÙJgí•©ûƦ‰Å#)%’B wHY±‡Ã…iíž•^6Ř$‘ÂÄ~N íLsd'ÊCiœ+Liaô¥ÀŒ[¥.~ÝR‹4):1L3iN sðF]­šµ7jÞÐe4â~‚¡ØjÍÈÈlt¶¬ÌSd±À¶êS|©g^›oxȃ§X+sÍdi”áYù’OœÏ'ÎÇ{Ö‡¼ç%ø·ÿèó‘¯x?óo‚Ÿú¹#Óî–9Îè¼@ºšU‡ T#ÃÁmPKåÑÞ8f¼Î•Ôĵ §Læ¨PþŒi—•­ì˜&…Øja´ÁÀK±“62Ä?×¢n@ŽŠv!,‹ƒ²>ç0%nÍ·òyçù¹l˜9ÉPD™æ€õL#!ÚÐ1¨×qÞ¢Óh„Ã$ôQ ­Ñ;ŒÏAO† ñ–U-.ôQÒ}«¬öyAl®Îànç¯ûñ¼!:1Å€©§ˆBA50©pØGÖKÇL‰*¸ ÓÓ)Š;+,U°µM¤(̳›œŸ>-#1 Z0 ¹ a©^¨V:)8>ô’3“6Ò¤Ž›V¼xª@ëtm„^ýµ°N™áÂ$ì(ÆÚ:½øÀÍZõ¨3F)ʼn‰ª£w"•1ŽÈn‚q+…cvóy£¡Ý‹ºµT äVü‚œB¹„î%] —<Æ£!¡s¾^4çØ°(´fôš1Éä@&´­ãKÍ“6­A«…d ‰T¢Dj\h½ÒÛµ Õ+}ùxB¥3ÔAÝ)Fœjè½—‚;¥dSŽšMQúüäÛ!5!’P-œ{cØ#%z/%†öùiön®×…Ñ AYvR…ÐÎ ‘:Œ4Ì¥e—{;šMlÙmˆ¨ç›ŒÈ8Ùg=BôiÍ›¯‚{¥•@HÊí’Øzd[/h)Äd,ÈeƒVÝÒ8GI1SA´ÑéÅH R¸ÒB±ˆn/˜¦e7VÏp!$…Çîx÷þÄŽÎüèeÞ~ó3ÄEQ«lXÔ½1$¦¤n>ŸÝ€7<âÔzd™Ãþ†gÛ…µ ‚ “UJŽ<£SZÅZEL0UT:Q2”yŠ(›5L'ÂõË“[§–LÔHSÁzevôüÀ±eÄ"„Ó†ÙÌÖ6ΧÌͲÇZCJÁ‚h» LðVºÊ•,â¹ý÷ßÖùÌC&¥”‚iòGâíK<}ë-ÌܸzÚ*£ùÖ:6M¨Fê0¤PDá x´,hŒ¼½vöAZ©¤ÉóÏ—KgŸSŒkáøœ;eDÖx ö ! Á¥i&‰Nf­Îo¿¹IœÏºc6«Ó0²02ÏäãJ]O”¾ò•~Âþ=¿Ÿoüú/àÿâgùÛ?öiްÜÜ2\dfŽ •îŠv­nP6¥wØ,):gý:)'ô<Æ\ÖìÞ4ÁÐëD)iŒè9ÆI:(D:7w }mÜ×F\"MŒV7ÔÜ„F¤™²Ò—Îf Ií&ÔR˜R¤ªùÃ_;E„¤ ¢Añ(„)OŸUž>‡ë뾈_ø•güÈÿù/XG§m‘®`r”¶1ʆ´Â·ÿ‘7ø®ïø ÝÝò±Ÿ~ʯýæ™Iw¨âädƒç¹.“Ê¥5šB­„¡ä‘h’iÁØ™°*‰Ù­»l‰1mtœ&e:XÚÀû:)]Kw>ù säe.X¿ãi6Rï.ÈÊÞ …‰¬ê4.5Œ‰ûÞi[qÑšÀ¹x8ìºd·£[R¯L­¢ÉÐákói5F6„¡wšH;Lg줇 ×5³ZSo¼È¨æ½*3§T e4X0æ(œE™TÙ8U!÷„¦A-‘¦.s|z_xvoü›_ý^~éŸù[ÿmÊzáyœ™¢q“"/ŠÁ[^Éëßú ¯ò'ÿع»½ág~á->õf'M“D… ƒ4ÌÓ&h˜iÖQ[]ˆ{édÔsà&X\S@-PÄ¥>&æž õØŸI'uq¡–¨J Æ6²³“c`ŠK*P˜zàÒü¼uˆ,±3p—‡k³·¦¬(—ªDšÇ ª 4† : V7æV J¡H´J …uúµ˜úÊÁ: X«"À“}âYQJSD]œðF(Ê´m´l¤)a <ßûVéÒY{Çn†ÑÅ8q– ¤ÂÜ;•i½’¢Qí¡žYd°I¢Õ‚öBí¾ASsb † ÕåóÆõÚ µe4ös¡É•t'ƒKïÎyJœ§ËF…7nSóx3Šx¡ZZe´Ü*7·3ǧ­V’R€r>Ù(µ°=œ8Ÿs:>ãË~ï-ßóg¿‚¯ÿCïçåþÁÿóY^Ü ËMâÑã=Äqó˜EŒ‘Ë%s·‹$MÜogŸð·ÆÈÃhRø`PT™æÙ“-:MG•u:ñî©û®o•)Ìäº!VMÈXyeJ¬#’GE:””Œ– #ËtÇÝ~¦–N € ¦è“û>öĤ$¬—B[ q6úðË?ÚHjœ.`ÁËÊ¥ûÙ`t÷ H𳊠E[˜©­2Å„(Å?ct’ ¶1cásÅáÔ¬^oµ_ñºcc¤Àƒ{£T±a@C罺³Ä$Òh:ÕÑ5.çÊ~°îŽ!4Ä ×C#½¯l—N:“4Št¶ ¬ÛÀj¦–ÆÎM¨œÎiŽö"FfîË=0éÌid¿ðÇLkv žoÔ/ôn ýqâÑÒxï“;^¥× óœúg ¥wÏ·©l«DB4zÛó5xîƒÊÄ(Â.|îFTiµðòKÓƒ44Δ¼r3EvËÄÃæ+0†ÐZpÕuÝ1Ø¥€„„„+T·&ÓÌû^z‰¦‘R+ïy²gmB«0D ÁD&ʵ4Ùc5T”-Wz/´V9„Ð8ãù2:ÃÜPjf+ÊÈÅׂ!`QXÔY¶f ‹‰Ëº!€„@î‚)…\„Ñ–"*B%ÅÉŸc@m•ÛÝr-ŒV×u]fÃoz2±aLAPM¦kQd " ×—¦6÷>hà~ÄV¹´Å¦ýŒŽáˆºò@Þ:uÙ›’D÷!¨]íÊZ*ykØõ‚$W¼4DŒ`!Ì* †Æ‰ ‚0Ó$ðòÝÌû^{wîÏÀ`™ü}è×òsÆ4%b b¤ö@Se”áï.œ H4ˆn&„}‚5oÄhhR.lgç 36Dw{ø¶?üþâ÷}5ỿŠ/ÿÐûøíOùËÿ˯ò±ôYlºñ®ÃrC°€š2Âä‡#„Ú\²£ÝˆÀŒ"A*˜¢æò£Š°¶æÛ‚iG”DŠÂ~—˜§D)ÐÅ'1x/d?ooy~nβFï…½ –ý¯â¯äæÚ;™@ºr…³ø!¢VŸ’ê<#*4Qr‡ ÊÈÝAúpjغ®¼ñÚ ¿ïïóµ_õ~ÿG^%%XRgŽÝÒxï+í÷øÖozƒ¿ø¾šï;?Â{_¹ã—ÿé3þú~‚O~ª0Í;¦‰t݈UŒ]Ãñ†©Æ}1rÿÿy畎]ÙèB²˜0t0•9NLZó(Ädîy|y— Ö>˜Bï¬b˜Dâ<—Ä]ôNHŽ7Ì:°i&L{jsçdJ¾f–ââµ6„aÉ¿¿10B ãL3Ä"2'–+ ¡é,óB%Zg!÷A2etX‡G¿¦á¸EM³o?øØ,°¨p™÷Dgw‹L¡³ˆ04úf$jR¬wFäíÌñáÈë/E>ü¡—ùèW¾ÂG>tCÔÌl‰ A7Þ{WøàkÆ7~ôß÷g¾Œ?õÇ¿„WßsÃ?ûõ#?úŸñÉ·ö/½ÊÝ£'Äù€Ä=Ý„@—æ‰DöÉ#c„x¯ø*Љ'»™´,ÔÙO“øùMÏû§ 1d(E>‡9ÆýÁHª´®„ L4^?T¾øÉµŠ÷ æDÂHYC¢î&õcƒdÂh™X7©’›o ’‘NÝùÞ(â&»Œ.N’‹*^ïPÅ®~žBÑ/øÆ¦K§0{œQý¿íQ‹¤ë³N‚çΣ!™wÄ(ÖB .Bº ¤á U/Iúª(-¡*.b@Ø!š˜çHš&ÖÞ ¦è€–Ï<”AÈ6ØèÚ®BG¼DiæT¹î˜Ê&?4 ü9Z ½úhÔ¢XÞ|ñY˜bDt°õÎ>:|áÅÃsvqåkÿàþüŸûßýïÿ|èK_哟:ñ?üëüÌÏ?e9ÜqXn™bâ’ýà\û +,K¢vå˜ýà¶vØr'Æ™ R;¥úTtˆÇÀ E41š°µB/ƒx{fMLâñ–<:U„ÍŸYÒØêF—™AZf½"”÷×ÏÒ¹TzoN( Ê>)çV©m°›…iJÔ:¨Ã7O£7†N4ñm~Tá:v#…™]pa4’ ASºš›kç)úÙÁµߩÊn¿wDkHèÄ4­²a °$C,PGbJPõBp„ix‰wˆsD •2¢Ÿ—¤ûàJ'ÌSò¢úèƒý~ç ”ÖÞ×aïB©Š$¡eôá‰Ú«YE‘äQÃÏÍ­¼­*Û¶qlÜ"Á2ã Œ_Kgê…fFnú½N'?ü÷î~w- „ˆˆKßL…Ü+c@©•Ö)ª×ÐÄnó.JδRX"Hݘ焉;µæ—™ÜÕĤƒµUŽU>1‡«ŒUaž#µ+aT¦É®Û¥LÝã>Ö*Û¾h¾Ù61‹¿NÅÔ¡ø3BGåñ2¡ ÎÝÏR;ƒ‰y¾ÆSGg´3o¯™ŠÓ&cHÄÞ°›[ lå ­PªĶÌ.)÷çNkù’/ÿšû bÎŽ3¹lÔêS6e;T™˜ÕuÌ¥;–tšwÌa°nŠsì[S‚vÌöŒ‘9˜O&T:»eÇñ\£R†ÆT™§™gN/ˆ1ÂÂV6zÍìRb:Üøá|;¡¨aõ³ËΦÝ/)nŒîlZz£KbЕӹ1,Ѽ”¤.»Ð>ض⽉#SZæÒ#!N (¹q©•;„yá"ÁÍÈV8­Î¶ž'ãx¾ "Lû™Òc;ûTÕ€­e,R˜ÙÊJÏF°îE—2W™[Uš¢þ%¬øD^-1›ÐP_Y±‘uA“ ¹aæ“öܪ?Ô0­e¤5Яüo´Ö‘kt'FeV¿<ØT1”1ôz1ºRÀd¡²afN¬Gqª_½3¨Í@9d4í8]¥\Ø-³OBƒêù¾y†ÁB-gL"„˹¡c­ƒ9¬ƻ«0%☈—¸‡ J©Ns)m uuªK;q¹?ñõ}•ÿä{þþ²W¨­óÿÿíÿô³ü¯¼‹îné–0Í,óĤ‹^˜µ^ "\†àRšà:ÁÚ }2ÚEÙ:$ «T8hÅÒÌ9w¦pÝ”LÚˆÒ *lU(½2J&ŒÆE#lhW°5x·ç.zëåÜ…Ç1S‡Q[`™'j-È´h”Ö\öUÖr¥Æ©aSòâÞ%wšEt n§ÂÖ H×Èb•ÛýÞ‹œ]ÙO‹K›gu“y|-“ˆ£³ÓÎFðŸ˜G<–ê­f Ñ`2 …çŰÍËÏÛ¼:+Ïj$—ÂãÅ#rµvt æè›¬Å£5:éšWÉãe[ët‹ô8‘€ãèÌêñÌ^`P ÜYQîÌ gøf?2;…M íYFcE‰Á1±¨‘µQN+—ûw8¾ùiŽoŠ×ùÓìu¾ú+߃©ü«ßË­òÓÿäm~àÿú ¿õÙÄ˯¼Ý«ïãpû„i¿gÒ #atn Žâ™(ç’i¢$5þ;g]ˆÁWÔ¥‡+ÚØ‘Ÿ•@ka~!h:è¦á¿U—:(£»c¤U·ë¶Ê͸0§ÀóSåL¤öAj•!ÂÛYX‚6¿TÊ€âE> ¬nÜL¦ŽÑ¬Å‡? Î=F¾Nã D0Í”a´z½H3ØéFíþ=&>u¥ %^aNFñõʥujhß@µ*­5l4ºBi‰R )¦©A:cl¾ªÙ¯"*´’Y{"Ú`ižÉ½Z…²±ÕÆÚ%¶r%Ÿ5zmÞÍ ÆŽÂV*…À¤ÉÌã~e£‰gªy<ØÊ…„—7Ñô3í²q9yñð‚?ø‘;þôw}ˆ}ð%ZëüôÏþÿó_û%~ù×_°Û?âöæ–ÝþöšgÞ1k¥´LL Uã|Z=:S:ÇÒé *Ð Cœ¸7F%h`ö7‚ô ]긵ÑÓDÍÙ§­óD9{ie¶Ê‹¼¡añÙê)ˆ¡N¹TBŒ×÷·ñÊî€öÊgO/˜âžKͨçÖ÷æÝÑ L×®LgN‰¾®¬½#]™B£J¢ÔŒŒê”Ö(eDé2`TDƒKͬÑQ/ÔjbŠ\¶VK Ę8ÎÌ;õ[Fo@óh*}@)gïPhBµ.eò‹á¨è¤>‘¿nÞÂ¥8 #ÍŽÊD…¨^’¢ÓLo+ç‹PÁQ̵bxÒqäêvµ(OlÔáBÕKnœ¶Æ.x/33±K Շ¹:võîjäÚ¡®´Ò©¥Ó\„‡\ØÍ ¦=Ò6ò !(1hP‚5vs й‡¡Ã*)…m,PŽÔrABäÉ~áþ²2®ýœJg±‰6Îä6H’Å©aб]a âøÛ­ÛÅ%«ÓÕÝ))µú0J¤#]¦PΔÚI‡[z-×`wZDóaÚ%7Jm™%\7§Ñ“”F—†êÌ(¤’R"™V6§½éŒ:£ŽAêš, ‚½òÊkIÔ769ûöÜŒh‘Y2QüfÞ$0Z!©²ßß1Ç“]Jc4a BdðžWn9w/÷LËD.…óV¼ymžÌ‘l;ε³õƨ—w‘i™™ãŒÄ‰²VÐ@#1zG£PÅiìl8ÍB"":A• ½êäѵøßA iƒ¦Ñ¿,&ô¢>ý “A)îTÃûö¹édòÜzm•Ê k£º^\%FnÙ‹)µ4‚ &ÜÚù¹8UëÐû…­ èž‚¦ôšÑ±ÚÅmvÑPšS€bd”æFÔ!~Û_U*c4æ äV]¶a 2£WºMبÀ ÖÎ0ˆ‚2lbï´0tâ\2ª™Öš³éˆÚçÉS·!°è—b>‰’šÉrë)»ÝBÃØò…1:#wÿûÒy"¥ÎÛ…yšÈj\ž_èX{g ¯¿ï5ž÷‰ûûç ”Ì¸þÛ &¤Ð)=Í #ÿ²¥ ÁÿÛi^ȹ°,ðó¿ô»ü—ÿÍOòWÿ÷_áÍÏ^aÇœv¤y‡M³SêìS|íHš¡5Bšñiƒµû®ÓgGwaCÌHAQ1Bì¦H×DÉR଻©s·ÌÜw!wOöäá_RQAñ"PÏ™×Ý`Ëžã¦hDÒÄqTªLLÚ™Ìè£Õ}RºÿPK@Û¸^ò}Õ(¢|æÍ|üg>ÍÏýÂgx~Ÿ9½k`iÍxëÝ•ßøÄ=¿ø«oñ£?öüWÿÃ?æ‡ÿÎoòì!`ûG¤ù1}¹¡j`?¼÷Ñ*C"hwW‰nw³O•è¤Y(yãÝ»Q¶ÁËKB–…Ú;C”†rns5†©ç~UiÝØ§N ŸÃ¹u48aá=·~ù¾?û$7ã?º±ÔŸÁ^Š•àÛ“^Èæ™*9Mlr•¤]·TQÇš%¶áñ¨]kèZP¦)¢C6˜ÔHYi ÂN…­ ¶«­}íê%É …ÁŒ},A Ñãbµ›g{“ÇdL…ѧœ¨v¶>xóÝÌOüì›ü“_}‡ûûÂéÜY7Á4Ò»ñöÓÂo}êȯþÚs~ìcoò—è7ø{?ñœc¿ãö•W¹{ô2·û'ý{0ÇÄ.~2Îâ±IQõÉÿÿÇÔ»,Ë–%YVCU×Z{o³sîÑ$Q@PU’‚Pü`|M¾¿AB‘d"I>"ÃÃï½ç˜íõP¥¡ÛZ.!.bÇl?téœsL+ˆÖlf.ÆweÑJ¥»!v+éáÜÎoEˆë ,–jdÚ¥ƒçõŒ±è°rî’˜äÇ ¾öÉˆŠ›\™´7 ™[‰µ2|ÁÌ„U˸3*ÏP´äÁJÌ2Ÿæ+¢hœ¹UÕ,ÙÔ§'\#Õ_Cqæ4VÙ( UsS;—°—ô¨WIÿz¬IXÍüÑÊ@¥„fÁZy˜}ÝóûuÍ{g¹äsÇrI×Wö|lL¦\V©ÈßQ¦û4_ÙJÅ«sž¹m-·ðô÷µF™ƒbμî£TÍi•ùx߸ãÿôäôE-š„¯· ã/ƪ/üá-¨’þòŸº` t¦œø›—$ýÓ—wöf¸w® ®æ†ÓçƒÊ¤ŸÇû7ÞÎGnÊÆ“Ñ »ÆLsWÆVÛKÃõ•m?¨÷¼ìÛe±)õÀJ(ß\qÉÇ…Q]S°kÓ[ \ýë°\°ëïž" wq¾9üú¥!~ÎfÊonÁÞ”¿ù¨)wéÜ$øÆÆÃ•erNáÂ&Ár£Þ*¯-CgÏ™ ñ[…oº±…°‹òÕ·p>’¹°`/Ü+h6åì%T“‚^/Þ¾œ›[«|[rÙ. "äÏRiš¯V[L¤ªX Ý á‰^ml"¼E#Šr§˜1fövì±xžoçÉóñÆóëžúå_ú IDATΟþ…·÷?òþõ9ÌÙñ±(x±,íª«õxáÓçOÛ+íÓwl//|x¹QÚa™u¸©Â”Àä fP S [,NÄœb´ ’‹ UÇ<-wR²«"ZàR˜Ó"‹÷|ÙiÃ9› 6Þ‚•²G_ˆu={Y‹½_½dnL‚åÎò‰Œ@[rˬ,ìQ¥#°FvxÞ‹ÃÇ=mD͡Դl¼Íæ„gw #1Ä;Åó <7&îI :£0F 2˜l.ÆE“Õé}%õ¦߯“"•j—™Á5ò:"¹@ríÆcN>{§HðIo‰É¹G©BÀb±{ÏüðÉ.†D–k OõMJúÇ=”Ùgã‘v£sMö" +ð…ÍAŸOš o}ðøùÇûWz²Â1‰|ïÝ^¹ßöZÑíF)ZJª+³gØT fçyö?/:Ô/‘Ô£)†Ð9¬â«SdáòÎâVâ±V'¦ó¾R½£/úµôa—RYnø|b´mÃ/Õ‰ò®'½wJÝxÝ·ž¶QþŒ÷ŸL©ÄÙµ!ŠÊ“ƒðl¯½mêηóL–æµ:\i:õìž‹Ëp>Öþ:ƒª…ogÇeC%0=ÙëÎ×3ˆålŽïÏ'[Ù烥.úÊ×çWʦÈzgDÃ5­¦kf•$™! ´ž[xqØî/L²&™ïOŠl3¾ž°y0užÃ|Ùjfª¢1]PMÈx䬘UÕ#‡[&S&¦·ËN¥ÌÞÙvãì'膭oL†F‚ëÁê'xRÔz,W Z*rcmÌz îl-¸ÕBÑ…zV„"¹Ô hIÅO´%±j>YkQ­At¾<;›íiK òoŠÖ FOÅ'Oª¸n(#±¨$§ÁÖ¢»°µ÷ÅsÁ–¥”hà=ðuÒêB\ìhtZm™óXOÜ…~9\ªç¬šØk*$bާ›„îøZˆºï”vÒÄ«,Ü`Ca¸4úrìóõûûV³À¥/ìØñžAL0±<ýåéF]ûíFFÍŸ‰zj-9«s(¥ä>¸Šñzæ<ßÈÊZtmÿûâí§?ÒZe’¥Fcϱ’‚ž(¸¥2ÑHÛŠdzòâ£ç‰5Œ¾œæOÂ=Qñ㉢<»óíù–5õºèÏ,ŸÂ23!šEIàÌ)tË ÅÍR;»ó|&¸é~Ûsˆ_+³H–bH²kk+Œ ËÆ ö­’\d‘,OƒôRÞK s¥QKA½3ýd†â1q”×bÜ6åK_L7æJ”ßVóTì@Ý üöžÕÛ%É8‚Gæ*¾ÿtÃ/?y¹ +Ƨ—gw¾|<Ÿ'Í”fd)ˆFJÜ"„8.‹?|{pž5eÎÎZ57uîIf*ÂRCÏ™*d©¨¯,­C`ÕD‹àc1}aûA“‚cmç9”oß̾ÐR9 Œ>9CP1Îg`t޶ñ6Ïô-z0ıË9§ Z±Ddhú™¬/lÿˆîu»¡VØöÊç׃á¤Pê„Fg“ ŸÊZ'=’n…$v¶Ê̼åaîeWF)¨3|xÙ),O¢Æ·†,©¼ÔÊoŽÉ­:ޱú„HF¸Í Ùksj’IŠPJáF›ª,B ÑÄŸvwÐ1¢‹©ÕÌiÑk“•¶ãbo·r`uG·ƒ²h»ÑnÙ;Ûí#ÇíÎÇ—;mû˜j÷Ž}g»ß(ªl­âåb¬’ÈCµìïhMíÆ´d…ïJvRÌ<@‡AÈ^¸Š]›_Th%— e­ÈÌÉQ-K ÉðäG›ü‡ÿú?ã1Œ¯ß~BËÁ” g³‘ÞRÝ. \¡«r7åv_WÚû–SÊÀ9<;#–iúÉÍXv5¢¶Ä /1šD*{”Êr‘«òÞìS(j„æ0|¿H'*µ°¬r¡jÐ,­gEãÛ¶‚•ÂÙé’P­ ”|ù´P´GÛк±ßl{Ånw>Ü?R_>b÷O|ÿù3íåõã¯xýî>~ÿ#¯?üŠöù>}úžíþ‰¶¿ RṴ̀’ä,¯‹Å÷»àC¨ôQ l¦X1NRB¯5 .7S*N+ÆVŒ¢³BÕÜùøá;¶óÐ k5+Yx0PJYdY޳1è}еr|>*™ìæ[1ȼKÓ¾°éFhåh…mÏàXñ«”¡ÀWßX#ÃÉ{5þØÿòÖQY<¥²Â8Êä·w(ÇA÷JuÁbrF–êý4` PI¹ÙÃð˜ÙªlÊëQ9´P‹ñh©VT©•Âà;›Ü¨|õ€VrK¶•ÂX $elT£TåŃUxª1ÈÒ4+BQåƒ;~Ιʵq×Ëî§|0ÍM§UÌ…Ý„½¥eèºÖ ¥@ÝØo¯lÇ Ûí•ãÓìÃ÷|úð#ÇçOì¾çÓ§Ï|úü‘—Û'^n7Ö±³™±KÅ%·‹å3§¨  = ´1¯Ï§ª,QÀP Ž/U1Œª…£nÂRÍ « Ô ÷œ<‚TVzjM ×†80ÂÒÞcd·LL €è VZçÊ0ãÐÊû2lA³<¤ŒËb“¶ž@}¡1¹7ÍͯLÆüÅfSè¾ ,ÁÅ)5Ÿ…¾r µb1ÊeAª1±XŒ•ô! ƒGÚ4þ4ý:ÜN sÅn•‚ç0«AWá¹rÄÊÕ:ÿÈAl–ìÈÐÜlÎ(‡¦ÙŸzei •wj+i€›‹—’µ¯=mǵæ¤ qÐb‰@gŽÜÀ– Õ`ŒD–JÙ jªâ%»:´VD*n©vîÛNÝ^nwʶQÚÆ­U¬IeR8Î9NŠÔ¯÷žÔ1äµOÅ% ‘`Š'4qAD^ÓìÁ)• jlkðYÒFA«%ÅP3ÔR9-–Ö*‹ bb"t÷ÎQü<;áÁZ\þ°ïi“´f†óùH¥_¡B- bá³$Ff"–2p[;è¬ùàéðv R3À<`9=`zä¢t%Üciá\NyX94s:§.þ“ýΗoÞ÷;[MÛ zYWð¦¤Íe—$ÔydJ ƒù<O/½HZM­X’Šdi¿Ô™E.ŸÏ¾P1¬å!Àc±"Ñf Ñ,o¼íƘ‚¯Éœ£^ë‘MÑ/rùå°¯ ÓRÑòð›îX¤*b¾f@o/´£¤ûDÑŸðì_О}'C‚­$¾ú1N¦Z½%YÊ,矕™N>guNðκ–$iq «©Ì„æ÷]r_3snõºÎSºí…9fBj .Äè<Âùøòûáûßýþãq§n }žÔVÙ?}fJÁegHåœÎ¦Áí–hL0T bVÒW9Òr&˜kÁ’‹#«ôµò 6ô²•–þäâÝI"@EÐÙsÀŠ=C‘A—R24ã"%[u1Ï“ue¾½?8Šó?¼&ÌWÊ9" ¼÷'_Ï'±œ9Çå™4sîmã§1Ð9y9#&Ù 9VnÄ<• ÉSÿ8³à#lg¹e¡Ö:“¤iÑéËÑ ß¿ÞY<ïl¶2œ«†" Ä®yò³Ê*…9:r½¬kUÌSÖM‚ÏâãQQƒ3Ræ/{¡¶Â×Ç™uÝ>8ûĨø2–.ºæHœd´ =¶Õàv숯/•·ó" ˜ecɜפ\¬PDJ¦Œ“÷Þišv³÷‘ìöRrHˆHªG-‹e·ÓÑ•:•™Üâ(`¹pއ°kòíõØð%<ßÞ ØŠRo·¤7xþÓ«ŽÜ¨¥g™-éBRuÓIÃ=}wºr ùó# Uû¾£ÅÒ’Æ,–HnQðÄ»­6Í ™ï˜(Çžƒû9#®k¾µŸvã·õ[î¿þ-ÿÇÿùœ£#4\òän€¬pne2È-àV '…çsQc¦xÁ†–`‰ÑgÚ×^ïÙàKÑpj-8ÆN¾Å¬5ÌûÑh¥òr{%bÇn7üx¥¶;XK¹]½ÖkëTØjzNe%­cŒ‰Ri6`1'ÔXHÛQ„˜ƒ3k”p©˜ævú ¡ ˜M»[«™‰%,µ Uù¶ŒiO2ƒå¾d¨°ÕìtØŠZx_†KnÚ¥~5QFyH „âÂD¨5›g½ÕëÔ'ÌÚ×ûn…EÚX~óáÆ?|æŸßNº´D›²UåÛSi1Yš‡ÉJz´ÿ„`âÜ[f`vYL-P”*†¶$‰`8Õ ÙŽä2Äùq‡{Q†ç3a“B-Jiº7j9¸Õ_ôþ Û+^ïÜ´ýƇcç¶¿ä@g…›nH)Œb´jTN½6áh6Ï[ÈL [IJÊvYíR^«X> òP*RrpÍ!?ÜÜ~æ®"T2#â¦ùbÄ1M{ „I CX"ŒÅ Xö,‡§Ë¡äê)‡çî,ÏÓü¹”3K¦YžÃ¡,¡Ç“`¢ô,-2¨»fö¥ˆ(=œç2š(¥.j« ”Ÿ‡óÍR‘ð5`8/M …®`"ÙPrò²Ušœ +¹YÞ~õzã}L¦¢’ágj+HÉë9¤RJe« Xk¢q (Ãc8†¡5)vqYŸö™;‰´ÆüpìlVøyåïë>Yn÷lx3óY£¯ÂvYËeÏÉFv¥^å&•}K¥@ÂOFÈÏ€jÁå9ÇÌ0rDf¶šå••ÌöTMëE†S*"Y.8B2ਕ½^…k–Ë™£Öìù¹\ƒŽ_ÞéE*5¡c·E\9!•ÌÙ4tMÕ£H6à" 8ZÉჴ¦Œ‘‹(Ñ™ ¥‘¿õKmô98Ç +ñ­wsÌÐÊÊÏAn´—Oª>(¶çg WqÕ¸„‘K?J«ìM`jÚ÷,ðqådZ¾'Õ)×µYM×Ú‹óéô‘¥S¥æ¿· —¥Z.ÈMîÛM$èªR%­š˜–üný™Hc<œÛQXýú}K¹rŠæÁxÍ“ï·Màë­eþ©Ä…cÛXñ̃•fÙÎÝ£±#WWsi¦™{ ÙPÉÊ[ÚuˆTdM•p¥÷áåÃF ˆTäûšì²ÓŒuÖaN]yHõÜâŸRØ=8{æ…ŠzÂVLYÞ³¡Úa0æ¢{pßn¨8ocR#V*x"™aqOt³íTɼe .‹Z-{¶ÌãÈ*ÈõÜRmY8R­rÛó3k.ýŠ%J™ÖèŽÄàvTÜ3ã'k-ú\&5rB|?¯[ &†jZòåáÙáR¤Ð£@<)Õx¬ã‚ d®užÔ5KG†df©n¢AÌ`„RP ÖEfœ3—–%r6± M¿¤©ÑôÌ9 ‚Žý«ÿü_ÿþ½3ˇ»äûJ²vRR|ŽE‘EµÜH鉽” ÷™'ŒHe+@kfkq$EÔ˜kf^œb|rŽˆÒ^̾:­)kMÎçÒÿî!T™üõï>Òîßñ‡ù‰si–âÌõçÊñÞ_ÞÞ©‘òÑ9?j—º1g°¤¡4ï¨6Îá<Þ¾±Ö­w¦w”l’§/LN~ußQãºA”ÝÅ2ü‘xB4Éç鸦§¿¯‰xrã A+ô™1e¶ óÜP,_Ùþ¼œÏ_¡(›bk(ç ¬Ý¨||m …Çé”±øË¿ü-#`=ß‘­å ¶OŒƒ`e©™€èɶ&¿ùág?ýüäXmg¨ãÏ™V¢£ë剶]¹l­\™½ ço¶«ÍN’>&‚KK«+Aan¸\.Ý-ï‹iYì„@Uº3e§ŠPÄÙKàÚ°Zy©+m€eÇì—C_`v$bŸ¨­iF%7²!Æ#qË3Ríäº{ Ob¼SÜÙªr/—Ŧ» {¹F†¡5“p¹¨H’÷’ÈW œ\l%mnªÙ¯T/‘¸m)Yþ·€àÞ²cBòïn9ˆ]ïµ>u«X 6 jÉÁ^bNÖpdLN`ª KpI²\> 短qNvD½Ÿ¬ª•X3ýâa”­]aÐb¨wT…XÎ[?&¨Ý’5OˆÉŸÞ»Ðû¢¶´—²Q&Îý¶Q¸÷½`±ØR’Ì6a„2k.J5—JK”%%ã&ª¼½¿Ò@Re ÁûÁÌ·6µ(gÏÍö~kQhUÒ–8j‚Ö¤8l se¯A_0Î|7{É’@Ö<éÃS9Õ,ÕsÒpZË…¸\Ö·|v- ‚JBc ¹J沄^ÂèS8ŠsÓl·¢´XôÔÂTcõTŒž~ … §ƒVãÃýÎ×ǃjÑxz£Ùɬ;“k«'ÍPo|< fÚ}‹tÔÓR%š¿óÞƒ¼.€ ¢Ù¢MÚÞ2od˜uX ѼV ƒÑs1Wk†êÝ¥{»%´fæóvœ(G(KKûwÿú¿øýeöÅÇâX»QöM'1"-º2¨$i㈈‰i"d¥Egzg¹ã Ü„:~yB7 úç(Æýx!d”«Ú>ƒ 1ÛU„õ<­8ªÊaׯm|áw¿û+üã_ò÷ûwT°@Ê¥¡IDATu4xffŸÙžLÚp†ƒ¬ÉízÉöµh ­Vn ŽccÒé¶iÖÄ?žéÇ\’!' Ô[z•Çœù"Q3¬ÊU€tqo}^jJâæJÍ¢’jùsšŒì-ˆ´"ܶJDnÊÊ…ý\LdvštÛx“僭VZ Bôº@œÖ Ó;?¿½³|A¹±FÏž¥8R9Vª¡vð˜Ác&Ù5=¼›á’Á’…(ÆdŽÅSöü~5CRkæ—,¥¥sözh»ýÌaѪ¦; §—¼µ×Ë‹|üæ·ÿ%ÿôǦ۬­¤²Ï‰h£6£XnÁÜ Pb$òq¹3<éš±ÌjÊ•<‡‘0DÒ“«xâiƒôcGnÕ^o;h%ê‘ÔM ߌ‹šöw–mt’rå%©9\¯Ëâf"T3¶0L2«SËN_¿Äóúýö\œˆB­[ÚV^_[MœUo >ç¨I&)+Kÿ ‡ñÄÏ'ß9èèzrË# ¡éO}δ ´Œ¹p)©0…ã3·ôSR 9,9ÓÈíÍa¥0¯pd†QsS2ÃÒ®…îyÿím»ü²Â¾5Jk—\]øt߈3·ÖMœSkâ%Òèö*P#.V¼r×Eˆð˜Î­@=²ø/íäË_ƒZ“7/*h5Š*ìšhZ±,•;Äh&ìG–’é&<%ˆ2$ƒ²…VéRÒÎgùÂ".drQŠ,º`ã®p«Â*¹)Ú€Z‘ÉÒk“î9Ô’÷ÕQ³Äksøx(z;¸5åÓa¼—Ûξå뢸zRßHÛÑŠ Dv–Œ•‡¤s%1gxðs_Ixñ„DI¾öÝ’»†³]6.5CKæˆöšßƒJÚ‚jÀÍr[¨´îÍ’®dY’x¯FÕDõ6Ͱk b‘bX9×!K“Êæi Ù²mWÅ ®ž/|-T#ñœËêrt¥%Ç\Ñ)<$Xž¡?$=ÂS”3”¶òY5ÅÈ*½ –1¯VT“¼?‡;Ì ìŸ(}e–X@¦pH\€‰´Ä®’^ÞCó™´|б’y†á–¨mqT3t[J¢V±ú‹›,‰(TUNßÛˆb¹ µÉ{¤•HúTŠfT ƽÏáøÈ×Û°šD˜’HB"‡r‹€jÜš¥²§ÆM²åÞ%hšjIØIUSȾ ø<<(eG C”’øf±…ŒÅnYwÎ kÒJCöïnð¢îuaŨ<™Öö$ ²¸×…OK1@ RkªžáXÁ¯@¶'øcy"ª£\ÖTÂ/ñ–xM @Z™-³,áª%ûΑĸ SX#ÑÏۖЈs➟é”➠”’ý=â¿”H ¬TYKq~Õm.Þ%­5H´Ö2ZËÿîvÅÙ¨­¥ÿãhŠšñöœÄxR^^hÚRN*©Æúr5«Ùµ=Çþ|âìLŒ1ŸéÝ—K…’D¾fØ:˜3ÿÓA4²#ÂŒ£U” Vr+OÚt¬•,¾]Š_å{Ìy€’«(t¥z’ År®Zù}…¤Míœp·„Ä}¯LÉ"C+ÆýØžGâb’=O¡H)ÜôIž€rñ¢’VÄ<æE÷‘ ¢ÓW`Òð951ŸHG­ÌùÈ%gÝXkPæ“·Gg É ÈÌ%úV²ü×#3•{ú|¾s«÷™ðÝP¼³©#¦¬iì" ¶Úˆ!‚ 0‹¹ÛÖhÁµ±®Î„våAäú-¹\éÓ¯¾Éyµ-Ûoû¯~?B™cYf!5Oé½gB9KPµQ œ³³fðñ(”­ñó·¯Ù.¨šÞϵ28¤—4óËFÒôj žk°úÊZñZx}Ùy{œ—õÎQ™‰)Ä¿½?~ÿÂ?üÜùÿOüáþŸD®…¾3™¹¦¹ÅÍ¢†Åò’xÒZ( ¿~ÙÎsqöž[–þ X¹^ð˜™Ä’»Á¾¿Ú”Ò.IqÑÅ£O Á¶eÐE/ÔXUá(…G_ô[­Ä<¹ïr¼ðöT4KrÎùÐÚx?ÓÇkjôÈ’°¾ÒUÍQË {œ]iU®â–Ikà+ÃT1³³ýÒŽ¸µDŒÞŸ¸g?Ãô”´ª­ôàå¾½ÚÇPv¸¦d€Äb‰04üÚ¨MX9lY«½Æ`ź>ó¤Ia‘êÀfÝŸžÖ{’6Jôôðº\ £>ÉxOâ¼öZ2,wbJqB m ŠË"p4&âÁãÙ™VÑXNÕÜR;IéçÄ—5¼VÀWúQ½nÄ Þ¾éÿMUì*È*Z¯mr¡|<*^ ç8¹•WË€bTÞuÏþ +<ÆÅ  }ñ°Šª±¾Ü7Εï(•sYÊ­;|‹ºâš©„‚eî¥À÷ucxÚ6îG!–±ÖÈCŸä¦©)•nÉtI¹þ»aW¸©3#•£FáÉ1QtåýýPáfI]é²8Š(ß.ÊM#‡Íb…éša¹«™ÖM˜1Yžß‘«â¦˜dÁ7϶Û×" M/­¬ËûÌ F¡šä†NòpRÕ¨š È 8JÈ_šòñVùøZùü±ñáU‹ë ?XW1=%¿xYE²åryv²¸¦MèÛ´îR’ÿ¯Ù›r®´ízµó–BAYô•*€UÁp>·dê;BÔ$¨e¾g«ÊM²˜­YÁjnöËe'’"Éí­iL<ŒiX"TO&ûƒ´V‚¸4Å9s¨AÅh1Ò·+Ñ}½d3¨x."„~½,kLª&F4K•îŽz†üæ…*…ô:€6`IÚa"$‹¯aï±&­åat¬ÁV§¤IMxbQ=Ñ…i•Æš”²„wÌ D™L¿lkÊg× °;ä¡Ô6´ŠÆ)ي٨«ál¥qJz”KkLŒ{…ðI iýQ+Ü·=ñ¹F*‘¥2fZ9L3`>æyõ 4^«söŠMhÕyr ÁWÙX¶ÆF’¾Vg ô>P²ú¥-^þߞΦG’Û¢/™$«º{vW–¼È>Ðô¯ÕM€­Ýî*’™>DÉsÌe¦§?ÈŒŒxáð}üòö•Ïõ+ŸêÁ•3E2c;©ž$‚ÞvJ…q¼ð ‘ j• pÎÔ¹S™M¹$›²Y”àíöAáÓX4„v3¶–˜7¡œc`¡Ò(QnœDp‘\“Í&G­ÌH×%a¯ÉŒÒ;$¶Õ¢Á!raE¹˜z Kç_!ÉXú½½:^¿ì51'nÐ,©µQ®<çñØ  ˆ:mÊÕŠíUº™CT^q CY:™•œ¶sM¶íÒo²&³t¯²kÓo`y2BB)Q±bÔ˜Œ\ØRvhæäu*óÖ¶F­’ÂŽU/šNR×Ðë¤eŠÁ~ÿ€×Åy¼¨®‚°,KÆB"îý›Kè9¯œMÀK2§a4ÂJöÚØ¶Ês6ÚÐôÙ`kâsQ ^k4î®3Ì ôKXJ3 òé"HºéqÛF4’c¨QÜj»C/°dŒ7J¼”=É, ¯S[Ú’Z+áMCô˜ºÛ…W¼t¶ÞÈx²"˜ã„OêþÜà¢i¥‰Þ¯3aÑ-èe@&µkÂÄÏ•´"¼ eøü»Ÿþþï‡bì{%ÚÛ&BÉy,Úñâù|'Oáñ¶.,Ø—÷äõúƒ¿¼}dk;c jsÞîÖyàMOÌœp®¤,¡ì2ƒ½ËË|ÆbÛ`­äùE[ˆ´I.ýlÌAYƒ‘ÎïÇA ùcš¾·khY zzáðZˆO…ï=øüó/üç|}>éÖx<<ÏÁ˜“½5<tdoÔeŒD¾¹è¦þ×*íºÖÿ[”²_D4¦%͂ϟìîœÇbM±s >nÆ?~æ·?’êÏ{ßôF/z½±· àežÙ¢¶Ni;+& k5‹ÎAÌEñÅ­Šjá½+L6åK«µ@©Üüõ‡ïøv>™¯Å*W3uíÌ"@ #ª]ݪòNkœcJ ÷œÊ*ÔfœQ®ÈXÁª¬Ò:æZg·b¼^“ô…·C„+3øé‡¼/§h#nZÕÝÚºt~Øöd÷Æ: mInzC¨¼E¸µ¾¹Øò mÂ7Ji¬ÒÅ!4£û #YëdÎ)Oa1ƲŠR7¬8ļš=U7^s±µdDã9šqÙ6žQ(œÜ¼¯E„®²i¢?õ–äm"ÊSóñÞ Aáu8t_n ™~›Ak]þIK¬(ûœÉKÐ?¸ºZå|©åÕKåîƒbÆ9Õýÿ¯ÐŠûfI¿ˆËŒ/×±Z…¦fâLg5q•9˜EÃy^6”ÁƪaR¯jT]hk%<9Ò¡4zN†UVkj„4xÅRéL&»M>¶Æ—!Å] ý[™jn7cIö$ɲ8–BÉ;ÆS Dn@Xá=àyaˆ=R›¢Î/)e m2ŸSz¿LÖù¸ÁÛ^ùøáÆŸ?(üÐ+m›¼R=jOHŽ—,#a˜]ÂHðž!ÛEB梚¶d! Z5UêWÑÁuRÙËd#Y‘t/Ü·J è<ª¬bÅôÙP«¶$›W67ö~ µÒ7Ç»Ñj¡©Ê€Z/+Èž)ÉÅ‚^´:ŸiœSÊ­Ï–ìK˜q¤†š’€k8ŸÇ»p¤f‰7hqòæ…/á—¸%Õq0êÕM® EýÓÖbÎŒ B­ãqYf• ¹ð'îêž©W06Cd¥a§.¯ ŒSv¹,*Ìd¥üбœä•‘Ðö~áS­0 j¹‰ ÃÔ†låjËÆäç/ÎûRé¦üYtÖ»sÌ ”Æ ¤ÖÖÆÝ¡¹±r……=Tð—¦­‡å™¬™dmÀây ‡]‹—VǼb°L¯!G˜ñãXäD(TœîR~×ÉÞäy‰èÞ?ïßøÇß>óÏýJ;~çË·Á׵뵴%2SÿãIÁÆbåõJ:{­B¹'Út[R›S‹ô c¡!¿;¥ “ùéíÆ½B¯›6s„JõÖA ûçÐæ°}y -_ÖÈc&ç’=» ¿Ó]öÙ’)T'Ž·¥N²ˆ$N`J¡ïM÷ÁRè.Ë]—ãîyÊO~cÌÁIpÎ9`ܪº¾FЋãÕH‚[Pb¬ƒÖd§ÙnÎcÛa¾Óch›S ™Œ8åçO*Œ'¥,öíÁûsê‰F®“Õ7½WóÚ "Ñ ñÍ_ù”,OV6 52çufyŠw"&çÌóEªº9K™A”‡z?'Íw÷;¯sR-ih»r\[ô1 -Na¾çÒçÇyH”'Ô˜î cêóA°keV Éÿœq1U@ZjIEND®B`‚osdlyrics-0.5.5~rc1+dfsg1/src/000077500000000000000000000000001345713535600161435ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/src/Makefile.am000066400000000000000000000046301345713535600202020ustar00rootroot00000000000000bin_PROGRAMS = osdlyrics #ol_player_test #TEST = $(check_PROGRAMS) AM_CPPFLAGS = \ @GTK2_CFLAGS@ \ @DBUS_GLIB_CFLAGS@ \ @NOTIFY_CFLAGS@ \ @X11_CFLAGS@ \ @APP_INDICATOR_CFLAGS@ \ -DDATADIR='"$(datadir)"' \ -DICONDIR='"$(OL_ICONDIR)"' \ -DGUIDIR='"$(OL_GUIDIR)"' LDADD = \ @GTK2_LIBS@ \ @DBUS_GLIB_LIBS@ \ @NOTIFY_LIBS@ \ @X11_LIBS@ \ @APP_INDICATOR_LIBS@ \ -lm \ $(NULL) noinst_HEADERS = \ gettext.h \ ol_about.h \ ol_app.h \ ol_app_info.h \ ol_app_chooser_widget.h \ ol_cell_renderer_button.h \ ol_color.h \ ol_commands.h \ ol_config_updater.h \ ol_config_property.h \ ol_config_proxy.h \ ol_consts.h \ ol_debug.h \ ol_display_module.h \ ol_eggaccelerators.h \ ol_gui.h \ ol_image_button.h \ ol_intl.h \ ol_keybinder.h \ ol_keybinding_settings.h \ ol_keybindings.h \ ol_lyric_candidate_list.h \ ol_lyric_candidate_selector.h \ ol_lrc.h \ ol_lyrics.h \ ol_lyric_source.h \ ol_lyric_source_list.h \ ol_marshal.h \ ol_md5.h \ ol_menu.h \ ol_metadata.h \ ol_notify.h \ ol_option.h \ ol_osd_module.h \ ol_osd_render.h \ ol_osd_toolbar.h \ ol_osd_window.h \ ol_path_pattern.h \ ol_player.h \ ol_scroll_module.h \ ol_scroll_window.h \ ol_search_dialog.h \ ol_stock.h \ ol_timeline.h \ ol_trayicon.h \ ol_utils_cmdline.h \ ol_utils_dbus.h \ ol_utils_dcop.h \ ol_utils_network.h \ ol_gussian_blur.h \ ol_player_chooser.h \ ol_utils.h osdlyrics_SOURCES = \ ol_app_info.c \ ol_app_chooser_widget.c \ ol_debug.c \ ol_main.c \ ol_config_updater.c \ ol_config_proxy.c \ ol_gui.c \ ol_about.c \ ol_option.c \ ol_display_module.c \ ol_osd_window.c \ ol_osd_toolbar.c \ ol_osd_render.c \ ol_osd_module.c \ ol_scroll_module.c \ ol_scroll_window.c \ ol_image_button.c \ ol_player.c \ ol_md5.c \ ol_utils.c \ ol_utils_dbus.c \ ol_utils_dcop.c \ ol_utils_cmdline.c \ ol_utils_network.c \ ol_lrc.c \ ol_lyrics.c \ ol_search_dialog.c \ ol_trayicon.c \ ol_timeline.c \ ol_menu.c \ ol_lyric_candidate_selector.c \ ol_lyric_candidate_list.c \ ol_lyric_source.c \ ol_lyric_source_list.c \ ol_metadata.c \ ol_color.c \ ol_eggaccelerators.c \ ol_keybinder.c \ ol_keybindings.c \ ol_commands.c \ ol_path_pattern.c \ ol_cell_renderer_button.c \ ol_marshal.c \ ol_stock.c \ ol_notify.c \ ol_player_chooser.c \ ol_gussian_blur.c #osd_lyrics_CXXFLAGS = @GTK2_CFLAGS@ # Librarys SUBDIRS = tests localedir = $(datadir)/locale DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ osdlyrics-0.5.5~rc1+dfsg1/src/genmarshal.sh000077500000000000000000000002231345713535600206200ustar00rootroot00000000000000#!/bin/bash glib-genmarshal --header --prefix ol_marshal marshal > ol_marshal.h glib-genmarshal --body --prefix ol_marshal marshal > ol_marshal.c osdlyrics-0.5.5~rc1+dfsg1/src/gettext.h000066400000000000000000000224211345713535600200010ustar00rootroot00000000000000/* Convenience header for conditional use of GNU . Copyright (C) 1995-1998, 2000-2002, 2004-2006 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _LIBGETTEXT_H #define _LIBGETTEXT_H 1 /* NLS can be disabled through the configure --disable-nls option. */ #if ENABLE_NLS /* Get declarations of GNU message catalog functions. */ # include /* You can set the DEFAULT_TEXT_DOMAIN macro to specify the domain used by the gettext() and ngettext() macros. This is an alternative to calling textdomain(), and is useful for libraries. */ # ifdef DEFAULT_TEXT_DOMAIN # undef gettext # define gettext(Msgid) \ dgettext (DEFAULT_TEXT_DOMAIN, Msgid) # undef ngettext # define ngettext(Msgid1, Msgid2, N) \ dngettext (DEFAULT_TEXT_DOMAIN, Msgid1, Msgid2, N) # endif #else /* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which chokes if dcgettext is defined as a macro. So include it now, to make later inclusions of a NOP. We don't include as well because people using "gettext.h" will not include , and also including would fail on SunOS 4, whereas is OK. */ #if defined(__sun) # include #endif /* Many header files from the libstdc++ coming with g++ 3.3 or newer include , which chokes if dcgettext is defined as a macro. So include it now, to make later inclusions of a NOP. */ #if defined(__cplusplus) && defined(__GNUG__) && (__GNUC__ >= 3) # include # if (__GLIBC__ >= 2) || _GLIBCXX_HAVE_LIBINTL_H # include # endif #endif /* Disabled NLS. The casts to 'const char *' serve the purpose of producing warnings for invalid uses of the value returned from these functions. On pre-ANSI systems without 'const', the config.h file is supposed to contain "#define const". */ # define gettext(Msgid) ((const char *) (Msgid)) # define dgettext(Domainname, Msgid) ((void) (Domainname), gettext (Msgid)) # define dcgettext(Domainname, Msgid, Category) \ ((void) (Category), dgettext (Domainname, Msgid)) # define ngettext(Msgid1, Msgid2, N) \ ((N) == 1 \ ? ((void) (Msgid2), (const char *) (Msgid1)) \ : ((void) (Msgid1), (const char *) (Msgid2))) # define dngettext(Domainname, Msgid1, Msgid2, N) \ ((void) (Domainname), ngettext (Msgid1, Msgid2, N)) # define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \ ((void) (Category), dngettext(Domainname, Msgid1, Msgid2, N)) # define textdomain(Domainname) ((const char *) (Domainname)) # define bindtextdomain(Domainname, Dirname) \ ((void) (Domainname), (const char *) (Dirname)) # define bind_textdomain_codeset(Domainname, Codeset) \ ((void) (Domainname), (const char *) (Codeset)) #endif /* A pseudo function call that serves as a marker for the automated extraction of messages, but does not call gettext(). The run-time translation is done at a different place in the code. The argument, String, should be a literal string. Concatenated strings and other string expressions won't work. The macro's expansion is not parenthesized, so that it is suitable as initializer for static 'char[]' or 'const char[]' variables. */ #define gettext_noop(String) String /* The separator between msgctxt and msgid in a .mo file. */ #define GETTEXT_CONTEXT_GLUE "\004" /* Pseudo function calls, taking a MSGCTXT and a MSGID instead of just a MSGID. MSGCTXT and MSGID must be string literals. MSGCTXT should be short and rarely need to change. The letter 'p' stands for 'particular' or 'special'. */ #ifdef DEFAULT_TEXT_DOMAIN # define pgettext(Msgctxt, Msgid) \ pgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES) #else # define pgettext(Msgctxt, Msgid) \ pgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES) #endif #define dpgettext(Domainname, Msgctxt, Msgid) \ pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES) #define dcpgettext(Domainname, Msgctxt, Msgid, Category) \ pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, Category) #ifdef DEFAULT_TEXT_DOMAIN # define npgettext(Msgctxt, Msgid, MsgidPlural, N) \ npgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES) #else # define npgettext(Msgctxt, Msgid, MsgidPlural, N) \ npgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES) #endif #define dnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N) \ npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES) #define dcnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N, Category) \ npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, Category) #ifdef __GNUC__ __inline #else #ifdef __cplusplus inline #endif #endif static const char * pgettext_aux (const char *domain, const char *msg_ctxt_id, const char *msgid, int category) { const char *translation = dcgettext (domain, msg_ctxt_id, category); if (translation == msg_ctxt_id) return msgid; else return translation; } #ifdef __GNUC__ __inline #else #ifdef __cplusplus inline #endif #endif static const char * npgettext_aux (const char *domain, const char *msg_ctxt_id, const char *msgid, const char *msgid_plural, unsigned long int n, int category) { const char *translation = dcngettext (domain, msg_ctxt_id, msgid_plural, n, category); if (translation == msg_ctxt_id || translation == msgid_plural) return (n == 1 ? msgid : msgid_plural); else return translation; } /* The same thing extended for non-constant arguments. Here MSGCTXT and MSGID can be arbitrary expressions. But for string literals these macros are less efficient than those above. */ #include #define _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS \ (((__GNUC__ >= 3 || __GNUG__ >= 2) && !__STRICT_ANSI__) \ /* || __STDC_VERSION__ >= 199901L */ ) #if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS #include #endif #define pgettext_expr(Msgctxt, Msgid) \ dcpgettext_expr (NULL, Msgctxt, Msgid, LC_MESSAGES) #define dpgettext_expr(Domainname, Msgctxt, Msgid) \ dcpgettext_expr (Domainname, Msgctxt, Msgid, LC_MESSAGES) #ifdef __GNUC__ __inline #else #ifdef __cplusplus inline #endif #endif static const char * dcpgettext_expr (const char *domain, const char *msgctxt, const char *msgid, int category) { size_t msgctxt_len = strlen (msgctxt) + 1; size_t msgid_len = strlen (msgid) + 1; const char *translation; #if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS char msg_ctxt_id[msgctxt_len + msgid_len]; #else char buf[1024]; char *msg_ctxt_id = (msgctxt_len + msgid_len <= sizeof (buf) ? buf : (char *) malloc (msgctxt_len + msgid_len)); if (msg_ctxt_id != NULL) #endif { memcpy (msg_ctxt_id, msgctxt, msgctxt_len - 1); msg_ctxt_id[msgctxt_len - 1] = '\004'; memcpy (msg_ctxt_id + msgctxt_len, msgid, msgid_len); translation = dcgettext (domain, msg_ctxt_id, category); #if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS if (msg_ctxt_id != buf) free (msg_ctxt_id); #endif if (translation != msg_ctxt_id) return translation; } return msgid; } #define npgettext_expr(Msgctxt, Msgid, MsgidPlural, N) \ dcnpgettext_expr (NULL, Msgctxt, Msgid, MsgidPlural, N, LC_MESSAGES) #define dnpgettext_expr(Domainname, Msgctxt, Msgid, MsgidPlural, N) \ dcnpgettext_expr (Domainname, Msgctxt, Msgid, MsgidPlural, N, LC_MESSAGES) #ifdef __GNUC__ __inline #else #ifdef __cplusplus inline #endif #endif static const char * dcnpgettext_expr (const char *domain, const char *msgctxt, const char *msgid, const char *msgid_plural, unsigned long int n, int category) { size_t msgctxt_len = strlen (msgctxt) + 1; size_t msgid_len = strlen (msgid) + 1; const char *translation; #if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS char msg_ctxt_id[msgctxt_len + msgid_len]; #else char buf[1024]; char *msg_ctxt_id = (msgctxt_len + msgid_len <= sizeof (buf) ? buf : (char *) malloc (msgctxt_len + msgid_len)); if (msg_ctxt_id != NULL) #endif { memcpy (msg_ctxt_id, msgctxt, msgctxt_len - 1); msg_ctxt_id[msgctxt_len - 1] = '\004'; memcpy (msg_ctxt_id + msgctxt_len, msgid, msgid_len); translation = dcngettext (domain, msg_ctxt_id, msgid_plural, n, category); #if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS if (msg_ctxt_id != buf) free (msg_ctxt_id); #endif if (!(translation == msg_ctxt_id || translation == msgid_plural)) return translation; } return (n == 1 ? msgid : msgid_plural); } #endif /* _LIBGETTEXT_H */ osdlyrics-0.5.5~rc1+dfsg1/src/marshal000066400000000000000000000001141345713535600175110ustar00rootroot00000000000000VOID:STRING,STRING VOID:UINT,DOUBLE VOID:ENUM,POINTER VOID:ENUM,STRING,UINT osdlyrics-0.5.5~rc1+dfsg1/src/ol_about.c000066400000000000000000000042441345713535600201170ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include "config.h" #include "ol_intl.h" #include "ol_about.h" #include "ol_gui.h" #include "ol_debug.h" void ol_about_close_clicked (GtkWidget *widget); void ol_about_response (GtkDialog *dialog, gint response_id, gpointer user_data); void ol_about_response (GtkDialog *dialog, gint response_id, gpointer user_data) { ol_debugf ("response_id:%d\n", response_id); switch (response_id) { case GTK_RESPONSE_CANCEL: /* Close button in about dialog */ gtk_widget_hide (GTK_WIDGET (dialog)); break; } } void ol_about_close_clicked (GtkWidget *widget) { ol_log_func (); GtkWidget *toplevel = gtk_widget_get_toplevel (widget); if (GTK_WIDGET_TOPLEVEL (toplevel)) { gtk_widget_hide (toplevel); } } void ol_about_show () { static GtkWidget *window = NULL; if (window == NULL) { window = ol_gui_get_widget ("aboutdialog"); gtk_about_dialog_set_program_name (GTK_ABOUT_DIALOG (window), PROGRAM_NAME); gtk_about_dialog_set_version (GTK_ABOUT_DIALOG (window), VERSION); g_signal_connect (window, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL); GdkPixbuf *logo = gdk_pixbuf_new_from_file (ICONDIR "/osdlyrics.png", NULL); if (logo) { gtk_about_dialog_set_logo (GTK_ABOUT_DIALOG (window), logo); } } ol_assert (window != NULL); gtk_widget_show (GTK_WIDGET (window)); } osdlyrics-0.5.5~rc1+dfsg1/src/ol_about.h000066400000000000000000000016251345713535600201240ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_ABOUT_H_ #define _OL_ABOUT_H_ void ol_about_show (); #endif /* _OL_ABOUT_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_app.h000066400000000000000000000033641345713535600175740ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_APP_H_ #define _OL_APP_H_ #include #include "ol_player.h" #include "ol_lyrics.h" #include "ol_lyric_source.h" #include "ol_lrc.h" #include "ol_metadata.h" gboolean ol_app_download_lyric (OlMetadata *metadata); struct OlPlayer; struct OlLrc; /** * @brief Gets the current music * * @return */ OlMetadata* ol_app_get_current_music (void); OlPlayer* ol_app_get_player (void); OlLyricSource *ol_app_get_lyric_source (void); OlLyrics *ol_app_get_lyrics_proxy (void); OlLrc *ol_app_get_current_lyric (void); gboolean ol_app_assign_lrcfile (OlMetadata *info, const char *filepath, gboolean update); /** * @brief Adjust the offset of lyric by offset_ms * * The offset of the lyric will be original offset + offset_ms * @param offset_ms Incremental value of offset, in milliseconds */ void ol_app_adjust_lyric_offset (int offset_ms); #endif /* _OL_APP_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_app_chooser_widget.c000066400000000000000000000226611345713535600226550ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include #include "ol_app_chooser_widget.h" #include "ol_debug.h" #define OL_APP_CHOOSER_WIDGET_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), \ OL_TYPE_APP_CHOOSER_WIDGET, \ OlAppChooserWidgetPrivate)) static const char *DEFAULT_ICON_NAME = "media-playback-start"; static const int DEFAULT_N_COLUMN = 4; static const int IMAGE_SIZE = 64; static const int LABLE_WIDTH = 80; enum _OlAppChooserWidgetSignals { APP_ACTIVATE_SIGNAL = 0, LAST_SIGNAL, }; typedef struct _OlAppChooserWidgetPrivate OlAppChooserWidgetPrivate; struct _OlAppChooserWidgetPrivate { GPtrArray *app_list; guint n_columns; }; static guint _signals[LAST_SIGNAL]; static void _remove_child (GtkWidget *widget, gpointer userdata); static GtkWidget *_new_app_button (OlAppChooserWidget *chooser, guint index); static void _app_activate (GtkWidget *button, GAppInfo *info); static GtkWidget *_load_image_from_name (const char *icon_name); static GtkWidget *_load_image_from_gicon (GIcon *icon); static GtkWidget *_image_from_app_info (GAppInfo *app_info); static void _calc_size (guint count, guint *n_rows, guint *n_columns); static gint _app_info_cmp (GAppInfo **lhs, GAppInfo **rhs); static void ol_app_chooser_widget_destroy (GtkObject *object); G_DEFINE_TYPE (OlAppChooserWidget, ol_app_chooser_widget, GTK_TYPE_TABLE); static void ol_app_chooser_widget_class_init (OlAppChooserWidgetClass *klass) { GtkObjectClass *gtkobject_class; gtkobject_class = GTK_OBJECT_CLASS (klass); gtkobject_class->destroy = ol_app_chooser_widget_destroy; g_type_class_add_private (G_OBJECT_CLASS (klass), sizeof (OlAppChooserWidgetPrivate)); _signals[APP_ACTIVATE_SIGNAL] = g_signal_new ("app-activate", OL_TYPE_APP_CHOOSER_WIDGET, G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, /* class_offset */ NULL, /* accumulator */ NULL, /* accumulator data */ g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_APP_INFO); } static void ol_app_chooser_widget_init (OlAppChooserWidget *chooser) { OlAppChooserWidgetPrivate *priv = OL_APP_CHOOSER_WIDGET_GET_PRIVATE (chooser); priv->app_list = g_ptr_array_new_with_free_func (g_object_unref); priv->n_columns = -1; } static void ol_app_chooser_widget_destroy (GtkObject *object) { OlAppChooserWidgetPrivate *priv = OL_APP_CHOOSER_WIDGET_GET_PRIVATE (object); if (priv->app_list) { g_ptr_array_free (priv->app_list, TRUE); priv->app_list = NULL; } GTK_OBJECT_CLASS (ol_app_chooser_widget_parent_class)->destroy (object); } void ol_app_chooser_widget_set_app_list (OlAppChooserWidget *chooser, GList *app_list, guint n_columns) { ol_assert (OL_IS_APP_CHOOSER_WIDGET (chooser)); OlAppChooserWidgetPrivate *priv = OL_APP_CHOOSER_WIDGET_GET_PRIVATE (chooser); if (priv->app_list->len > 0) g_ptr_array_remove_range (priv->app_list, 0, priv->app_list->len); GList *iter; for (iter = app_list; iter != NULL; iter = g_list_next (iter)) { if (!G_IS_APP_INFO (iter->data)) { ol_errorf ("The data of app_list must be GAppInfo\n"); continue; } GAppInfo *info = iter->data; if (info != NULL && g_app_info_should_show (info)) g_ptr_array_add (priv->app_list, g_object_ref (info)); } g_ptr_array_sort (priv->app_list, (GCompareFunc) _app_info_cmp); guint n_rows = 0; _calc_size (priv->app_list->len, &n_rows, &n_columns); priv->n_columns = n_columns; gtk_container_foreach (GTK_CONTAINER (chooser), _remove_child, NULL); gtk_table_resize (GTK_TABLE (chooser), n_rows, n_columns); guint i, row, col; row = 0; col = 0; for (i = 0; i < priv->app_list->len; i++) { GtkWidget *app_button = _new_app_button (chooser, i); gtk_widget_show_all (app_button); gtk_table_attach (GTK_TABLE (chooser), app_button, col, col + 1, /* left, right */ row, row + 1, /* top, bottom */ GTK_EXPAND, 0, /* x and y options */ 0, 0); /* x and y padding */ row += (col + 1) / n_columns; col = (col + 1) % n_columns; } } static void _remove_child (GtkWidget *widget, gpointer userdata) { ol_assert (GTK_IS_WIDGET (widget)); GtkWidget *parent = gtk_widget_get_parent (widget); ol_assert (parent != NULL); gtk_container_remove (GTK_CONTAINER (parent), widget); } static void _calc_size (guint count, guint *n_rows, guint *n_columns) { ol_assert (n_rows != NULL && n_columns != NULL); if (*n_columns == 0) { *n_columns = ceil (sqrt (count)); if (*n_columns < DEFAULT_N_COLUMN) *n_columns = DEFAULT_N_COLUMN; } *n_rows = count / *n_columns; if (*n_rows == 0 || (*n_rows) * (*n_columns) < count) *n_rows = *n_rows + 1; } static GtkWidget * _new_app_button (OlAppChooserWidget *chooser, guint index) { OlAppChooserWidgetPrivate *priv = OL_APP_CHOOSER_WIDGET_GET_PRIVATE (chooser); GAppInfo *info = G_APP_INFO (g_ptr_array_index (priv->app_list, index)); if (info == NULL) return NULL; GtkWidget *image = _image_from_app_info (info); GtkWidget *label = gtk_label_new (g_app_info_get_display_name (info)); gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); gtk_widget_set_size_request (label, LABLE_WIDTH, -1); GtkWidget *vbox = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), image, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, TRUE, 0); GtkWidget *frame = gtk_aspect_frame_new (NULL, 0.5, 0.5, 1.0, FALSE); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE); gtk_container_add (GTK_CONTAINER (frame), vbox); GtkWidget *button = gtk_button_new (); gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); gtk_widget_set_tooltip_text (button, g_app_info_get_display_name (info)); gtk_container_add (GTK_CONTAINER (button), frame); g_signal_connect (button, "clicked", G_CALLBACK (_app_activate), info); return button; } static void _app_activate (GtkWidget *button, GAppInfo *info) { ol_assert (GTK_IS_WIDGET (button)); ol_assert (G_IS_APP_INFO (info)); GtkWidget *parent = gtk_widget_get_parent (button); if (OL_IS_APP_CHOOSER_WIDGET (parent)) { g_signal_emit (parent, _signals[APP_ACTIVATE_SIGNAL], 0, info); } else { ol_errorf ("Activating an app button, but its parent is not an OlAppChooserWidget.\n"); } } static GtkWidget * _load_image_from_name (const char *icon_name) { GtkIconTheme *icon_theme = gtk_icon_theme_get_default (); if (!gtk_icon_theme_has_icon (icon_theme, icon_name)) icon_name = DEFAULT_ICON_NAME; return gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON); } static GtkWidget * _load_image_from_gicon (GIcon *icon) { GtkIconTheme *icon_theme = gtk_icon_theme_get_default (); GtkIconInfo *icon_info; if ((icon_info = gtk_icon_theme_lookup_by_gicon (icon_theme, icon, GTK_ICON_SIZE_BUTTON, 0)) != NULL) { gtk_icon_info_free (icon_info); return gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_BUTTON); } else { return gtk_image_new_from_icon_name (DEFAULT_ICON_NAME, GTK_ICON_SIZE_BUTTON); } } static GtkWidget *_image_from_app_info (GAppInfo *app_info) { GIcon *icon = g_app_info_get_icon (app_info); GtkWidget *image = NULL; if (icon) { image = _load_image_from_gicon (icon); } else { image = _load_image_from_name (g_app_info_get_executable (app_info)); } gtk_image_set_pixel_size (GTK_IMAGE (image), IMAGE_SIZE); return image; } GtkWidget * ol_app_chooser_widget_new (void) { return GTK_WIDGET (g_object_new (OL_TYPE_APP_CHOOSER_WIDGET, NULL)); } static gint _app_info_cmp (GAppInfo **lhs, GAppInfo **rhs) { return strcasecmp (g_app_info_get_display_name (*lhs), g_app_info_get_display_name (*rhs)); } guint ol_app_chooser_widget_get_columns (OlAppChooserWidget *chooser) { ol_assert_ret (OL_IS_APP_CHOOSER_WIDGET (chooser), 0); OlAppChooserWidgetPrivate *priv = OL_APP_CHOOSER_WIDGET_GET_PRIVATE (chooser); return priv->n_columns; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_app_chooser_widget.h000066400000000000000000000053451345713535600226620ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_APP_CHOOSER_WIDGET_H_ #define _OL_APP_CHOOSER_WIDGET_H_ #include #define OL_TYPE_APP_CHOOSER_WIDGET \ ol_app_chooser_widget_get_type () #define OL_APP_CHOOSER_WIDGET(obj) \ GTK_CHECK_CAST (obj, OL_TYPE_APP_CHOOSER_WIDGET, OlAppChooserWidget) #define OL_APP_CHOOSER_WIDGET_CLASS(klass) \ GTK_CHECK_CLASS_CAST (klass, OL_TYPE_APP_CHOOSER_WIDGET, OlAppChooserWidgetClass) #define OL_IS_APP_CHOOSER_WIDGET(obj) \ GTK_CHECK_TYPE (obj, OL_TYPE_APP_CHOOSER_WIDGET) #define OL_APP_CHOOSER_WIDGET_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), \ OL_TYPE_APP_CHOOSER_WIDGET, \ OlAppChooserWidgetClass)) typedef struct _OlAppChooserWidget OlAppChooserWidget; typedef struct _OlAppChooserWidgetClass OlAppChooserWidgetClass; struct _OlAppChooserWidget { GtkTable parent; }; struct _OlAppChooserWidgetClass { GtkTableClass parent_class; }; GType ol_app_chooser_widget_get_type (void); GtkWidget *ol_app_chooser_widget_new (void); /** * Sets the app infos to the app chooser. * * @param chooser * @param app_list A GList of GAppInfo*. * @param n_columns The number of columns to show the apps. If it is set to 0, the * number of columns will be calculated according to the number * of apps. */ void ol_app_chooser_widget_set_app_list (OlAppChooserWidget *chooser, GList *app_list, guint n_columns); /** * Gets the number of columns of displayed apps. * * The number is guaranteed to be non-zero after setting the app_list. * @param chooser * * @return */ guint ol_app_chooser_widget_get_columns (OlAppChooserWidget *chooser); #endif /* _OL_APP_CHOOSER_WIDGET_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_app_info.c000066400000000000000000000553251345713535600206060ustar00rootroot00000000000000 /* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include "ol_app_info.h" #include "ol_intl.h" #include "ol_debug.h" struct _OlAppInfo { GObject parent; gchar *cmdline; gchar *name; GIcon *icon; gchar *binfile; gboolean should_show; }; static gchar *_str_join_argv (gchar **argv); static gboolean _shell_arg_need_quote (const gchar *arg); static gchar *_find_binfile (const gchar *binfile, gboolean match_prefix); static gchar *_find_file_in_path_list (GList *path_list, const gchar *prefix, const gchar *suffix, gboolean match_prefix, gboolean (*file_test_func) (const gchar *)); static gboolean _file_is_executable (const gchar *filename); static gboolean _file_exists (const gchar *filename); static GIcon *_icon_new_from_name (const gchar *icon_name); static void _app_info_set_from_desktop_file (OlAppInfo *info, enum OlAppInfoFlags flags); static void ol_app_info_finalize (GObject *object); static void ol_app_info_iface_init (GAppInfoIface *iface); static void ol_app_info_class_init (OlAppInfoClass *klass); static void ol_app_info_init (OlAppInfo *info); static void _strv_replace (gchar **argv, guint index, const gchar *new_value); static GList *_get_desktop_file_path_list (); static gpointer _get_desktop_file_path_list_once (gpointer data); static GList *_prepend_subdirs (GList *list); /* -----------GAppInfo interfaces--------------- */ static GAppInfo *_app_info_dup (GAppInfo *appinfo); static gboolean _app_info_equal (GAppInfo *appinfo1, GAppInfo *appinfo2); static const char *_app_info_get_id (GAppInfo *appinfo); static const char *_app_info_get_name (GAppInfo *appinfo); static const char *_app_info_get_display_name (GAppInfo *appinfo); static const char *_app_info_get_description (GAppInfo *appinfo); static const char *_app_info_get_executable (GAppInfo *appinfo); static const char *_app_info_get_commandline (GAppInfo *appinfo); static GIcon *_app_info_get_icon (GAppInfo *appinfo); static gboolean _app_info_launch (GAppInfo *appinfo, GList *files, GAppLaunchContext *launch_context, GError **error); static gboolean _app_info_supports_uris (GAppInfo *appinfo); static gboolean _app_info_supports_files (GAppInfo *appinfo); static gboolean _app_info_launch_uris (GAppInfo *appinfo, GList *uris, GAppLaunchContext *launch_context, GError **error); static gboolean _app_info_should_show (GAppInfo *appinfo); static gboolean _app_info_set_as_default_for_type (GAppInfo *appinfo, const char *content_type, GError **error); static gboolean _app_info_set_as_default_for_extension (GAppInfo *appinfo, const char *extension, GError **error); static gboolean _app_info_add_supports_type (GAppInfo *appinfo, const char *content_type, GError **error); static gboolean _app_info_can_remove_supports_type (GAppInfo *appinfo); static gboolean _app_info_remove_supports_type (GAppInfo *appinfo, const char *content_type, GError **error); static gboolean _app_info_can_delete (GAppInfo *appinfo); static gboolean _app_info_do_delete (GAppInfo *appinfo); G_DEFINE_TYPE_WITH_CODE (OlAppInfo, ol_app_info, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_APP_INFO, ol_app_info_iface_init)); static void ol_app_info_class_init (OlAppInfoClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = ol_app_info_finalize; } static void ol_app_info_init (OlAppInfo *info) { } static void ol_app_info_iface_init (GAppInfoIface *iface) { iface->dup = _app_info_dup; iface->equal = _app_info_equal; iface->get_id = _app_info_get_id; iface->get_name = _app_info_get_name; iface->get_description = _app_info_get_description; iface->get_executable = _app_info_get_executable; iface->get_icon = _app_info_get_icon; iface->launch = _app_info_launch; iface->supports_uris = _app_info_supports_uris; iface->supports_files = _app_info_supports_files; iface->launch_uris = _app_info_launch_uris; iface->should_show = _app_info_should_show; iface->set_as_default_for_type = _app_info_set_as_default_for_type; iface->set_as_default_for_extension = _app_info_set_as_default_for_extension; iface->add_supports_type = _app_info_add_supports_type; iface->can_remove_supports_type = _app_info_can_remove_supports_type; iface->remove_supports_type = _app_info_remove_supports_type; iface->can_delete = _app_info_can_delete; iface->do_delete = _app_info_do_delete; iface->get_commandline = _app_info_get_commandline; iface->get_display_name = _app_info_get_display_name; } static GAppInfo * _app_info_dup (GAppInfo *appinfo) { OlAppInfo *info = OL_APP_INFO (appinfo); OlAppInfo *new_info; new_info = g_object_new (OL_TYPE_APP_INFO, NULL); new_info->cmdline = g_strdup (info->cmdline); new_info->name = g_strdup (info->name); new_info->binfile = g_strdup (info->binfile); if (info->icon) new_info->icon = g_object_ref (info->icon); new_info->should_show = info->should_show; return G_APP_INFO (new_info); } static gboolean _app_info_equal (GAppInfo *appinfo1, GAppInfo *appinfo2) { OlAppInfo *info1 = OL_APP_INFO (appinfo1); OlAppInfo *info2 = OL_APP_INFO (appinfo2); return strcmp (info1->cmdline, info2->cmdline) == 0; } static const char * _app_info_get_id (GAppInfo *appinfo) { OlAppInfo *info = OL_APP_INFO (appinfo); return info->cmdline; } static const char * _app_info_get_name (GAppInfo *appinfo) { OlAppInfo *info = OL_APP_INFO (appinfo); if (info->name == NULL) return _("Unnamed"); return info->name; } static const char * _app_info_get_display_name (GAppInfo *appinfo) { OlAppInfo *info = OL_APP_INFO (appinfo); if (info->name == NULL) return _("Unnamed"); return info->name; } static const char * _app_info_get_description (GAppInfo *appinfo) { return ""; } static const char * _app_info_get_executable (GAppInfo *appinfo) { OlAppInfo *info = OL_APP_INFO (appinfo); return info->binfile; } static const char * _app_info_get_commandline (GAppInfo *appinfo) { OlAppInfo *info = OL_APP_INFO (appinfo); return info->cmdline; } static GIcon * _app_info_get_icon (GAppInfo *appinfo) { OlAppInfo *info = OL_APP_INFO (appinfo); return info->icon; } static gboolean _app_info_launch (GAppInfo *appinfo, GList *files, GAppLaunchContext *launch_context, GError **error) { OlAppInfo *info = OL_APP_INFO (appinfo); /* TODO: implement launching. */ GAppInfo* app = g_app_info_create_from_commandline (info->cmdline, "", 0, NULL); gboolean ret = g_app_info_launch (app, files, launch_context, error); g_object_unref (G_OBJECT (app)); return ret; } static gboolean _app_info_supports_uris (GAppInfo *appinfo) { return FALSE; } static gboolean _app_info_supports_files (GAppInfo *appinfo) { return FALSE; } static gboolean _app_info_launch_uris (GAppInfo *appinfo, GList *uris, GAppLaunchContext *launch_context, GError **error) { OlAppInfo *info = OL_APP_INFO (appinfo); /* TODO: implement launching. */ GAppInfo* app = g_app_info_create_from_commandline (info->cmdline, "", 0, NULL); gboolean ret = g_app_info_launch_uris (app, uris, launch_context, error); g_object_unref (G_OBJECT (app)); return ret; } static gboolean _app_info_should_show (GAppInfo *appinfo) { OlAppInfo *info = OL_APP_INFO (appinfo); return info->should_show; } static gboolean _app_info_set_as_default_for_type (GAppInfo *appinfo, const char *content_type, GError **error) { if (error) *error = g_error_new (g_quark_from_string ("OSD Lyrics"), 0, /* error code */ "OlAppInfo does not support set_as_default_for_type"); return FALSE; } static gboolean _app_info_set_as_default_for_extension (GAppInfo *appinfo, const char *extension, GError **error) { if (error) *error = g_error_new (g_quark_from_string ("OSD Lyrics"), 0, /* error code */ "OlAppInfo does not support set_as_default_for_extension"); return FALSE; } static gboolean _app_info_add_supports_type (GAppInfo *appinfo, const char *content_type, GError **error) { if (error) *error = g_error_new (g_quark_from_string ("OSD Lyrics"), 0, /* error code */ "OlAppInfo does not support add_supports_type"); return FALSE; } static gboolean _app_info_can_remove_supports_type (GAppInfo *appinfo) { return FALSE; } static gboolean _app_info_remove_supports_type (GAppInfo *appinfo, const char *content_type, GError **error) { if (error) *error = g_error_new (g_quark_from_string ("OSD Lyrics"), 0, /* error code */ "OlAppInfo does not support remove_supports_type"); return FALSE; } static gboolean _app_info_can_delete (GAppInfo *appinfo) { return FALSE; } static gboolean _app_info_do_delete (GAppInfo *appinfo) { return FALSE; } OlAppInfo * ol_app_info_new (const char *cmdline, const char *name, const char *icon_name, enum OlAppInfoFlags flags, GError **error) { if (cmdline == NULL) { if (error) *error = g_error_new (g_quark_from_string ("OSD Lyrics"), 0, "cmdline cannot be NULL"); return NULL; } int cmd_index = 0; if (flags & OL_APP_INFO_SECOND_IS_EXEC) cmd_index = 1; gchar **argv = NULL; gint argc = 0; GError *parse_error = NULL; if (!g_shell_parse_argv (cmdline, &argc, &argv, &parse_error)) { if (error) *error = g_error_new (g_quark_from_string ("OSD Lyrics"), 0, "Cannot parse cmdline: %s", parse_error->message); g_error_free (parse_error); return NULL; } if (argc <= cmd_index) { if (error) *error = g_error_new (g_quark_from_string ("OSD Lyrics"), 0, "Command name is required in cmdline"); g_strfreev (argv); return NULL; } OlAppInfo *info = g_object_new (OL_TYPE_APP_INFO, NULL); info->binfile = _find_binfile (argv[cmd_index], (flags & OL_APP_INFO_WITH_PREFIX) != 0); if (info->binfile == NULL) { info->binfile = g_strdup (argv[cmd_index]); info->cmdline = g_strdup (cmdline); info->should_show = FALSE; } else { info->should_show = TRUE; _strv_replace (argv, cmd_index, info->binfile); info->cmdline = _str_join_argv (argv); } if (!name) name = info->binfile; if (!icon_name) icon_name = info->binfile; info->icon = _icon_new_from_name (icon_name); info->name = g_strdup (name); if (flags & OL_APP_INFO_PREFER_DESKTOP_FILE) _app_info_set_from_desktop_file (info, flags); return info; } static gchar * _str_join_argv (gchar **argv) { GString *str_builder = g_string_new (""); gboolean first = TRUE; while (argv && *argv) { if (!first) g_string_append_c (str_builder, ' '); else first = FALSE; if (_shell_arg_need_quote (*argv)) { gchar *quoted = g_shell_quote (*argv); g_string_append (str_builder, quoted); g_free (quoted); } else { g_string_append (str_builder, *argv); } argv++; } return g_string_free (str_builder, FALSE); } static gboolean _shell_arg_need_quote (const gchar *arg) { if (arg == NULL) return FALSE; if (!strchr (arg, ' ') && !strchr (arg, '\t') && !strchr (arg, '\n') && !strchr (arg, '\'') && !strchr (arg, '\"') && !strchr (arg, '\\')) return FALSE; else return TRUE; } static gchar * _find_binfile (const gchar *binfile, gboolean match_prefix) { GList *path_list = NULL; gchar *ret = NULL; if (g_path_is_absolute (binfile)) { gchar *dirname = g_path_get_dirname (binfile); path_list = g_list_append (path_list, dirname); binfile += strlen (dirname); if (*binfile == G_DIR_SEPARATOR) binfile++; } else { const char *env_path = g_getenv ("PATH"); if (!env_path) { env_path = "/bin:/usr/bin"; } gchar **pathv = g_strsplit (env_path, G_SEARCHPATH_SEPARATOR_S, -1); gchar **pathiter; for (pathiter = pathv; *pathiter != NULL; pathiter++) { if (**pathiter != '\0') path_list = g_list_prepend (path_list, g_strdup (*pathiter)); } path_list = g_list_reverse (path_list); g_strfreev (pathv); } gchar *filepath = _find_file_in_path_list (path_list, binfile, "", match_prefix, _file_is_executable); if (filepath) { ret = g_path_get_basename (filepath); g_free (filepath); } for (; path_list != NULL; path_list = g_list_delete_link (path_list, path_list)) g_free (path_list->data); return ret; } /** * * * @param path_list A GList of gchar*. * @param prefix The prefix of the filename, cannot be #NULL. * @param suffix The suffix of filename. If #match_prefix is #TRUE, #NULL or * empty string means any suffix is acceptable. * @param match_prefix * * @return */ static gchar * _find_file_in_path_list (GList *path_list, const gchar *prefix, const gchar *suffix, gboolean match_prefix, gboolean (*file_test_func) (const gchar *)) { ol_assert_ret (prefix != NULL, NULL); if (suffix == NULL) suffix = ""; if (!file_test_func) file_test_func = _file_exists; GList *pathiter; gchar *ret = NULL; for (pathiter = path_list; pathiter != NULL; pathiter = g_list_next (pathiter)) { gchar *path = pathiter->data; if (!match_prefix) { gchar *filename = NULL; gchar *fullname = g_strdup_printf ("%s%s", prefix, suffix); filename = g_build_filename (path, fullname, NULL); g_free (fullname); if (file_test_func (filename)) ret = filename; else g_free (filename); } /* if !match_prefix */ else { GError *error = NULL; GDir *dir = g_dir_open (path, 0, &error); if (!dir) { ol_errorf ("Cannot open path %s: %s\n", path, error->message); g_error_free (error); continue; } const gchar *name; while ((name = g_dir_read_name (dir)) != NULL) { if (g_str_has_prefix (name, prefix) && g_str_has_suffix (name, suffix)) { gchar *filename = g_build_filename (path, name, NULL); if (file_test_func (filename) && (!ret || strcmp (filename, ret) < 0)) { g_free (ret); ret = filename; } else { g_free (filename); } } } g_dir_close (dir); } if (ret != NULL) break; } return ret; } static gboolean _file_is_executable (const gchar *filename) { return (g_file_test (filename, G_FILE_TEST_IS_EXECUTABLE) && !g_file_test (filename, G_FILE_TEST_IS_DIR)); } static gboolean _file_exists (const gchar *filename) { return (g_file_test (filename, G_FILE_TEST_EXISTS) && !g_file_test (filename, G_FILE_TEST_IS_DIR)); } static GIcon * _icon_new_from_name (const gchar *icon_name) { ol_assert_ret (icon_name != NULL, NULL); /* This is taken from gdesktopappinfo.c of GIO */ GIcon *icon = NULL; if (g_path_is_absolute (icon_name)) { GFile *file; file = g_file_new_for_path (icon_name); icon = g_file_icon_new (file); g_object_unref (file); } else { char *p; /* Work around a common mistake in desktop files */ if ((p = strrchr (icon_name, '.')) != NULL && (strcmp (p, ".png") == 0 || strcmp (p, ".xpm") == 0 || strcmp (p, ".svg") == 0)) { gchar *real_name = g_strndup (icon_name, p - icon_name); icon = g_themed_icon_new (real_name); g_free (real_name); } else { icon = g_themed_icon_new (icon_name); } } return icon; } static void _app_info_set_from_desktop_file (OlAppInfo *info, enum OlAppInfoFlags flags) { GList *path_list = _get_desktop_file_path_list (); gchar *filename = _find_file_in_path_list (path_list, info->binfile, ".desktop", (flags & OL_APP_INFO_WITH_PREFIX) != 0, NULL); if (!filename) { ol_debugf ("Cannot find desktop file for %s\n", info->binfile); return; } GKeyFile *keyfile = g_key_file_new (); GError *error = NULL; if (!g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, &error)) { ol_errorf ("Cannot open desktop file %s: %s\n", filename, error->message); g_error_free (error); } else { if (flags & OL_APP_INFO_USE_DESKTOP_NAME) { gchar *name = g_key_file_get_locale_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, NULL, NULL); if (name != NULL) { if (info->name != NULL) g_free (info->name); info->name = name; } } if (flags & OL_APP_INFO_USE_DESKTOP_CMDLINE) { gchar *cmdline = g_key_file_get_locale_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, NULL, NULL); if (cmdline != NULL) { if (info->cmdline != NULL) g_free (info->cmdline); info->cmdline = cmdline; } } if (flags & OL_APP_INFO_USE_DESKTOP_ICON) { gchar *icon_name = g_key_file_get_locale_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, NULL, NULL); GIcon *icon = _icon_new_from_name (icon_name); if (icon != NULL) { if (info->icon != NULL) g_object_unref (info->icon); info->icon = icon; } } info->should_show = !g_key_file_get_boolean (keyfile, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, NULL); } g_free (filename); g_key_file_free (keyfile); } static void ol_app_info_finalize (GObject *object) { OlAppInfo *info = OL_APP_INFO (object); if (info->name) { g_free (info->name); info->name = NULL; } if (info->cmdline) { g_free (info->cmdline); info->cmdline = NULL; } if (info->binfile) { g_free (info->binfile); info->binfile = NULL; } if (info->icon) { g_object_unref (info->icon); info->icon = NULL; } G_OBJECT_CLASS (ol_app_info_parent_class)->finalize (object); } static void _strv_replace (gchar **argv, guint index, const gchar *new_value) { g_free (argv[index]); argv[index] = g_strdup (new_value); } /** * Returns the list of paths to find desktop files. * * * @return A GList of gchar*. Should NOT be freed. */ static GList * _get_desktop_file_path_list () { GOnce once = G_ONCE_INIT; g_once (&once, _get_desktop_file_path_list_once, NULL); return (GList*) once.retval; } static gpointer _get_desktop_file_path_list_once (gpointer data) { GList *list = NULL; list = g_list_prepend (list, g_build_filename (g_get_user_data_dir(), "applications", NULL)); list = _prepend_subdirs (list); const gchar * const * data_dirs = g_get_system_data_dirs (); while (*data_dirs != NULL) { list = g_list_prepend (list, g_build_filename (*data_dirs, "applications", NULL)); list = _prepend_subdirs (list); data_dirs++; } return g_list_reverse (list); } static GList * _prepend_subdirs (GList *list) { gchar *path = list->data; GError *error = NULL; GDir *dir = g_dir_open (path, 0, &error); if (!dir) { ol_errorf ("Cannot open dir %s: %s\n", path, error->message); g_error_free (error); return list; } const gchar *name; while ((name = g_dir_read_name (dir)) != NULL) { gchar *filepath = g_build_filename (path, name, NULL); if (g_file_test (filepath, G_FILE_TEST_IS_DIR)) { list = g_list_prepend (list, filepath); list = _prepend_subdirs (list); } else { g_free (filepath); } } return list; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_app_info.h000066400000000000000000000071611345713535600206060ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_APP_INFO_H_ #define _OL_APP_INFO_H_ #include #include G_BEGIN_DECLS #define OL_TYPE_APP_INFO (ol_app_info_get_type ()) #define OL_APP_INFO(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OL_TYPE_APP_INFO, OlAppInfo)) #define OL_APP_INFO_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), OL_TYPE_APP_INFO, OlAppInfoClass)) #define OL_IS_APP_INFO(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OL_TYPE_APP_INFO)) #define OL_IS_APP_INFO_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OL_TYPE_APP_INFO)) #define OL_APP_INFO_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OL_TYPE_APP_INFO, OlAppInfoClass)) enum OlAppInfoFlags { OL_APP_INFO_FLAGS_NONE = 0, /// Find the first app info with prefix of given commandline. OL_APP_INFO_WITH_PREFIX = 1 << 0, /// The first argument is the real program name. Uses in the command /// with sudo/gksu OL_APP_INFO_SECOND_IS_EXEC = 1 << 1, /// Look up desktop file for application name. OL_APP_INFO_USE_DESKTOP_NAME = 1 << 2, /// Look up desktop file for application icon. OL_APP_INFO_USE_DESKTOP_ICON = 1 << 3, /// Look up desktop file for application command line. OL_APP_INFO_USE_DESKTOP_CMDLINE = 1 << 4, /// Use the information in desktop file rather than the parameters, if available. OL_APP_INFO_PREFER_DESKTOP_FILE = OL_APP_INFO_USE_DESKTOP_NAME | OL_APP_INFO_USE_DESKTOP_ICON | OL_APP_INFO_USE_DESKTOP_CMDLINE, }; typedef struct _OlAppInfo OlAppInfo; typedef struct _OlAppInfoClass { GObjectClass parent_class; } OlAppInfoClass; /** * Creates an GAppInfo according to cmdline. * * If #name is not #NULL, use the command name of #cmdline as the name. * * If #icon_name is #NULL, use the command name of #cmdline as the icon name. * * If #OL_APP_INFO_WITH_PREFIX is set in #flags, and the command specified in * #cmdline not exist, the command with the prefix of the command name, if any, * is used. * * If the #cmdline is started with gksu or somethine like that, you can use * #OL_APP_INFO_SECOND_IS_EXEC, so the first argument (the second) in the #cmdline * is use as the command name. * * If any of #OL_APP_INFO_USE_DESKTOP_NAME, #OL_APP_INFO_USE_DESKTOP_ICON, or * #OL_APP_INFO_USE_DESKTOP_CMDLINE is used, #OlAppInfo will try to find the * desktop file by the command name in #cmdline. * * @param cmdline The command line, cannot be #NULL. * @param name * @param icon_name * @param flags * * @return If failed, return #NULL, and the #error will be set. Otherwise * return the GAppInfo instance. */ OlAppInfo *ol_app_info_new (const char *cmdline, const char *name, const char *icon_name, enum OlAppInfoFlags flags, GError **error); G_END_DECLS #endif /* _OL_APP_INFO_H_ */ #include osdlyrics-0.5.5~rc1+dfsg1/src/ol_cell_renderer_button.c000066400000000000000000000531071345713535600232070ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include "ol_cell_renderer_button.h" #include "ol_marshal.h" #include "ol_debug.h" static void ol_cell_renderer_button_finalize (GObject *object); enum { EDITED, CLICKED, LAST_SIGNAL }; enum { PROP_0, PROP_TEXT, PROP_STOCK, /* PROP_MARKUP, */ /* PROP_ATTRIBUTES, */ /* PROP_SINGLE_PARAGRAPH_MODE, */ /* PROP_WIDTH_CHARS, */ /* PROP_WRAP_WIDTH, */ /* PROP_ALIGN, */ /* /\* Style args *\/ */ /* PROP_BACKGROUND, */ /* PROP_FOREGROUND, */ /* PROP_BACKGROUND_GDK, */ /* PROP_FOREGROUND_GDK, */ /* PROP_FONT, */ /* PROP_FONT_DESC, */ /* PROP_FAMILY, */ /* PROP_STYLE, */ /* PROP_VARIANT, */ /* PROP_WEIGHT, */ /* PROP_STRETCH, */ /* PROP_SIZE, */ /* PROP_SIZE_POINTS, */ /* PROP_SCALE, */ /* PROP_EDITABLE, */ /* PROP_STRIKETHROUGH, */ /* PROP_UNDERLINE, */ /* PROP_RISE, */ /* PROP_LANGUAGE, */ /* PROP_ELLIPSIZE, */ /* PROP_WRAP_MODE, */ /* /\* Whether-a-style-arg-is-set args *\/ */ /* PROP_BACKGROUND_SET, */ /* PROP_FOREGROUND_SET, */ /* PROP_FAMILY_SET, */ /* PROP_STYLE_SET, */ /* PROP_VARIANT_SET, */ /* PROP_WEIGHT_SET, */ /* PROP_STRETCH_SET, */ /* PROP_SIZE_SET, */ /* PROP_SCALE_SET, */ /* PROP_EDITABLE_SET, */ /* PROP_STRIKETHROUGH_SET, */ /* PROP_UNDERLINE_SET, */ /* PROP_RISE_SET, */ /* PROP_LANGUAGE_SET, */ /* PROP_ELLIPSIZE_SET, */ /* PROP_ALIGN_SET */ }; static guint cell_renderer_button_signals [LAST_SIGNAL]; #define OL_CELL_RENDERER_BUTTON_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), OL_TYPE_CELL_RENDERER_BUTTON, OlCellRendererButtonPrivate)) typedef struct _OlCellRendererButtonPrivate OlCellRendererButtonPrivate; struct _OlCellRendererButtonPrivate { gulong focus_out_id; PangoLanguage *language; PangoEllipsizeMode ellipsize; PangoWrapMode wrap_mode; PangoAlignment align; gulong populate_popup_id; gulong entry_menu_popdown_timeout; gboolean in_entry_menu; gint width_chars; gint wrap_width; GtkWidget *entry; }; G_DEFINE_TYPE (OlCellRendererButton, ol_cell_renderer_button, GTK_TYPE_CELL_RENDERER) static void ol_cell_renderer_button_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void ol_cell_renderer_button_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void ol_cell_renderer_button_get_size (GtkCellRenderer *cell, GtkWidget *widget, GdkRectangle *cell_area, gint *x_offset, gint *y_offset, gint *width, gint *height); static void ol_cell_renderer_button_render (GtkCellRenderer *cell, GdkWindow *window, GtkWidget *widget, GdkRectangle *background_area, GdkRectangle *cell_area, GdkRectangle *expose_area, GtkCellRendererState flags); static gint ol_cell_renderer_button_activate (GtkCellRenderer *cell, GdkEvent *event, GtkWidget *widget, const gchar *path, GdkRectangle *background_area, GdkRectangle *cell_area, GtkCellRendererState flags); static PangoLayout* get_layout (OlCellRendererButton *cellbutton, GtkWidget *widget, gboolean will_render, GtkCellRendererState flags); static void get_size (GtkCellRenderer *cell, GtkWidget *widget, GdkRectangle *cell_area, PangoLayout *layout, gint *x_offset, gint *y_offset, gint *width, gint *height); static void ol_cell_renderer_button_init (OlCellRendererButton *cellbutton) { OlCellRendererButtonPrivate *priv; priv = OL_CELL_RENDERER_BUTTON_GET_PRIVATE (cellbutton); GTK_CELL_RENDERER (cellbutton)->xalign = 0.0; GTK_CELL_RENDERER (cellbutton)->yalign = 0.5; GTK_CELL_RENDERER (cellbutton)->xpad = 2; GTK_CELL_RENDERER (cellbutton)->ypad = 2; GTK_CELL_RENDERER (cellbutton)->mode = GTK_CELL_RENDERER_MODE_ACTIVATABLE; /* cellbutton->font_scale = 1.0; */ /* cellbutton->fixed_height_rows = -1; */ cellbutton->font = pango_font_description_new (); cellbutton->stock_id = NULL; priv->width_chars = -1; priv->wrap_width = -1; priv->wrap_mode = PANGO_WRAP_CHAR; priv->align = PANGO_ALIGN_LEFT; /* priv->align_set = FALSE; */ } static void ol_cell_renderer_button_class_init (OlCellRendererButtonClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class); object_class->finalize = ol_cell_renderer_button_finalize; object_class->get_property = ol_cell_renderer_button_get_property; object_class->set_property = ol_cell_renderer_button_set_property; cell_class->get_size = ol_cell_renderer_button_get_size; cell_class->render = ol_cell_renderer_button_render; cell_class->activate = ol_cell_renderer_button_activate; g_object_class_install_property (object_class, PROP_TEXT, g_param_spec_string ("text", ("Text"), ("Text to render"), NULL, GTK_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_STOCK, g_param_spec_string ("stock", ("stock"), ("Stock name of icon"), NULL, GTK_PARAM_READWRITE)); cell_renderer_button_signals [EDITED] = g_signal_new (("edited"), G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (OlCellRendererButtonClass, edited), NULL, NULL, ol_marshal_VOID__STRING_STRING, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); cell_renderer_button_signals [CLICKED] = g_signal_new (("clicked"), G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (OlCellRendererButtonClass, clicked), NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); g_type_class_add_private (object_class, sizeof (OlCellRendererButtonPrivate)); } static void ol_cell_renderer_button_finalize (GObject *object) { OlCellRendererButton *cellbutton = OL_CELL_RENDERER_BUTTON (object); pango_font_description_free (cellbutton->font); g_free (cellbutton->text); G_OBJECT_CLASS (ol_cell_renderer_button_parent_class)->finalize (object); } static void ol_cell_renderer_button_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec) { OlCellRendererButton *cellbutton = OL_CELL_RENDERER_BUTTON (object); switch (param_id) { case PROP_TEXT: g_value_set_string (value, cellbutton->text); break; case PROP_STOCK: g_value_set_string (value, cellbutton->stock_id); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; } } static void ol_cell_renderer_button_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec) { OlCellRendererButton *cellbutton = OL_CELL_RENDERER_BUTTON (object); ol_logf (OL_DEBUG, "%s:%d\n", __FUNCTION__, param_id); switch (param_id) { case PROP_TEXT: ol_logf (OL_DEBUG, " text\n"); g_free (cellbutton->text); /* if (priv->markup_set) */ /* { */ /* if (cellbutton->extra_attrs) */ /* pango_attr_list_unref (cellbutton->extra_attrs); */ /* cellbutton->extra_attrs = NULL; */ /* priv->markup_set = FALSE; */ /* } */ cellbutton->text = g_strdup (g_value_get_string (value)); g_object_notify (object, "text"); break; case PROP_STOCK: ol_logf (OL_DEBUG, " stock\n"); g_free (cellbutton->text); cellbutton->stock_id = g_strdup (g_value_get_string (value)); g_object_notify (object, "stock"); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; } } GtkCellRenderer * ol_cell_renderer_button_new (void) { return g_object_new (OL_TYPE_CELL_RENDERER_BUTTON, NULL); } static GdkPixbuf* get_icon (OlCellRendererButton *cellbutton, GtkWidget *widget) { GdkPixbuf *icon = NULL; ol_logf (OL_DEBUG, "%s\n", __FUNCTION__); if (cellbutton->stock_id != NULL) { ol_logf (OL_DEBUG, " stock id = %s\n", cellbutton->stock_id); icon = gtk_widget_render_icon (widget, cellbutton->stock_id, GTK_ICON_SIZE_SMALL_TOOLBAR, NULL); } return icon; } static void get_icon_rect (OlCellRendererButton *cellbtn, GtkWidget *widget, GdkRectangle *cell_area, GdkPixbuf *icon, int index, int *x_offset, int *y_offset, int *width, int *height) { ol_logf (OL_DEBUG, "%s\n", __FUNCTION__); if (icon == NULL) icon = get_icon (cellbtn, widget); else g_object_ref (icon); gint icon_width, icon_height; if (icon == NULL) { icon_width = 0; icon_height = 0; } else { icon_width = gdk_pixbuf_get_width (icon); icon_height = gdk_pixbuf_get_height (icon); if (cell_area) { if (x_offset) *x_offset = cell_area->width - icon_width * (index + 1); if (y_offset) *y_offset = (cell_area->height - icon_height) / 2; } g_object_unref (icon); } if (width) *width = icon_width; if (height) *height = icon_height; ol_logf (OL_DEBUG, " icon size:%dx%d\n", icon_width, icon_height); } static void ol_cell_renderer_button_render (GtkCellRenderer *cell, GdkDrawable *window, GtkWidget *widget, GdkRectangle *background_area, GdkRectangle *cell_area, GdkRectangle *expose_area, GtkCellRendererState flags) { ol_logf (OL_DEBUG, "%s\n" " flags:%d\n", __FUNCTION__, (int) flags); OlCellRendererButton *cellbutton = (OlCellRendererButton *) cell; PangoLayout *layout; GtkStateType state = 0; gint x_offset; gint y_offset; layout = get_layout (cellbutton, widget, TRUE, flags); get_size (cell, widget, cell_area, layout, &x_offset, &y_offset, NULL, NULL); /* ol_logf (OL_DEBUG, " offset: (%d,%d)\n", x_offset, y_offset); */ if (!cell->sensitive) { state |= GTK_STATE_INSENSITIVE; } else if ((flags & GTK_CELL_RENDERER_SELECTED) == GTK_CELL_RENDERER_SELECTED) { if (GTK_WIDGET_HAS_FOCUS (widget)) state |= GTK_STATE_SELECTED; else state |= GTK_STATE_ACTIVE; } else if ((flags & GTK_CELL_RENDERER_PRELIT) == GTK_CELL_RENDERER_PRELIT) { state = GTK_STATE_PRELIGHT; } else { if (GTK_WIDGET_STATE (widget) == GTK_STATE_INSENSITIVE) state = GTK_STATE_INSENSITIVE; else state = GTK_STATE_NORMAL; } pango_layout_set_width (layout, (cell_area->width - x_offset - 2 * cell->xpad) * PANGO_SCALE); gtk_paint_layout (widget->style, window, state, TRUE, expose_area, widget, "cellrenderertext", cell_area->x + x_offset + cell->xpad, cell_area->y + y_offset + cell->ypad, layout); g_object_unref (layout); /* if (cell->sensitive && */ /* (flags & GTK_CELL_RENDERER_PRELIT) == GTK_CELL_RENDERER_PRELIT) */ { GdkPixbuf *icon = get_icon (cellbutton, widget); if (icon != NULL) { gint icon_width, icon_height, icon_x, icon_y; get_icon_rect (OL_CELL_RENDERER_BUTTON (cell), widget, cell_area, icon, 0, &icon_x, &icon_y, &icon_width, &icon_height); gdk_draw_pixbuf (window, widget->style->black_gc, icon, 0, /* src_x */ 0, /* src_y */ cell_area->x + icon_x - cell->xpad, cell_area->y + icon_y, icon_width, icon_height, GDK_RGB_DITHER_NORMAL, 0, 0); g_object_unref (icon); } /* if icon */ } /* if state */ } static void add_attr (PangoAttrList *attr_list, PangoAttribute *attr) { attr->start_index = 0; attr->end_index = G_MAXINT; pango_attr_list_insert (attr_list, attr); } static PangoLayout* get_layout (OlCellRendererButton *cellbutton, GtkWidget *widget, gboolean will_render, GtkCellRendererState flags) { ol_logf (OL_DEBUG, "%s\n", __FUNCTION__); PangoAttrList *attr_list; PangoLayout *layout; OlCellRendererButtonPrivate *priv; priv = OL_CELL_RENDERER_BUTTON_GET_PRIVATE (cellbutton); layout = gtk_widget_create_pango_layout (widget, cellbutton->text); attr_list = pango_attr_list_new (); pango_layout_set_single_paragraph_mode (layout, TRUE); if (will_render) { add_attr (attr_list, pango_attr_font_desc_new (cellbutton->font)); } pango_layout_set_ellipsize (layout, priv->ellipsize); if (priv->wrap_width != -1) { pango_layout_set_width (layout, priv->wrap_width * PANGO_SCALE); /* ol_logf (OL_DEBUG, " width: %d\n", (int)(priv->wrap_width * PANGO_SCALE)); */ pango_layout_set_wrap (layout, priv->wrap_mode); } else { pango_layout_set_width (layout, -1); pango_layout_set_wrap (layout, PANGO_WRAP_CHAR); } PangoAlignment align; if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) align = PANGO_ALIGN_RIGHT; else align = PANGO_ALIGN_LEFT; pango_layout_set_alignment (layout, align); pango_layout_set_attributes (layout, attr_list); pango_attr_list_unref (attr_list); return layout; } static void get_size (GtkCellRenderer *cell, GtkWidget *widget, GdkRectangle *cell_area, PangoLayout *layout, gint *x_offset, gint *y_offset, gint *width, gint *height) { ol_logf (OL_DEBUG, "%s\n", __FUNCTION__); OlCellRendererButton *cellbutton = (OlCellRendererButton *) cell; gint btn_width = (cell->xpad) * 2; gint btn_height = (cell->ypad) * 2; GdkPixbuf *icon = get_icon (cellbutton, widget); if (icon != NULL) { gint icon_width, icon_height, icon_x, icon_y; get_icon_rect (OL_CELL_RENDERER_BUTTON (cell), widget, cell_area, icon, 0, &icon_x, &icon_y, &icon_width, &icon_height); g_object_unref (icon); btn_width += icon_width; btn_height += icon_height; } if (btn_height < cell->height) btn_height = cell->height; gtk_cell_renderer_set_fixed_size (cell, btn_width, btn_height); if (height) *height = btn_height; if (width) *width = btn_width; if (cell_area) { if (x_offset) *x_offset = cell_area->x; if (y_offset) *y_offset = cell_area->y; ol_logf (OL_DEBUG, " offset: %dx%d\n", cell_area->x, cell_area->y); } ol_logf (OL_DEBUG, " size: %dx%d\n", btn_width, btn_height); /* PangoContext *context; */ /* PangoFontMetrics *metrics; */ /* PangoFontDescription *font_desc; */ /* gint row_height; */ /* font_desc = pango_font_description_copy_static (widget->style->font_desc); */ /* pango_font_description_merge_static (font_desc, cellbutton->font, TRUE); */ /* context = gtk_widget_get_pango_context (widget); */ /* metrics = pango_context_get_metrics (context, */ /* font_desc, */ /* pango_context_get_language (context)); */ /* row_height = (pango_font_metrics_get_ascent (metrics) + */ /* pango_font_metrics_get_descent (metrics)); */ /* pango_font_metrics_unref (metrics); */ /* pango_font_description_free (font_desc); */ /* gtk_cell_renderer_set_fixed_size (cell, */ /* cell->width, 2*cell->ypad + */ /* PANGO_PIXELS (row_height)); */ /* ol_logf (OL_DEBUG, */ /* " size: %dx%d\n", */ /* cell->width, */ /* 2*cell->ypad + */ /* PANGO_PIXELS (row_height)); */ /* if (layout) */ /* g_object_ref (layout); */ /* else */ /* layout = get_layout (cellbutton, widget, FALSE, 0); */ /* pango_layout_get_pixel_extents (layout, NULL, &rect); */ /* if (height) */ /* *height = cell->ypad * 2 + rect.height; */ /* if (width) */ /* *width = cell->xpad * 2 + rect.x + rect.width; */ /* if (cell_area) */ /* { */ /* ol_logf (OL_DEBUG, " cell_area:%dx%d\n", cell_area->width, cell_area->height); */ /* if (x_offset) */ /* { */ /* if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) */ /* *x_offset = (1.0 - cell->xalign) * (cell_area->width - (rect.x + rect.width + (2 * cell->xpad))); */ /* else */ /* *x_offset = cell->xalign * (cell_area->width - (rect.x + rect.width + (2 * cell->xpad))); */ /* *x_offset = MAX(*x_offset, 0); */ /* } */ /* if (y_offset) */ /* { */ /* *y_offset = cell->yalign * (cell_area->height - (rect.height + (2 * cell->ypad))); */ /* *y_offset = MAX (*y_offset, 0); */ /* } */ /* } */ /* else */ /* { */ /* if (x_offset) *x_offset = 0; */ /* if (y_offset) *y_offset = 0; */ /* } */ /* g_object_unref (layout); */ } static void ol_cell_renderer_button_get_size (GtkCellRenderer *cell, GtkWidget *widget, GdkRectangle *cell_area, gint *x_offset, gint *y_offset, gint *width, gint *height) { get_size (cell, widget, cell_area, NULL, x_offset, y_offset, width, height); } static gint ol_cell_renderer_button_activate (GtkCellRenderer *cell, GdkEvent *event, GtkWidget *widget, const gchar *path, GdkRectangle *background_area, GdkRectangle *cell_area, GtkCellRendererState flags) { ol_logf (OL_DEBUG, "%s\n" " event type:%d\n", __FUNCTION__, event->type); ol_logf (OL_DEBUG, " bg area: (%d,%d) %dx%d\n" " cell area: (%d,%d) %dx%d\n", background_area->x, background_area->y, background_area->width, background_area->height, cell_area->x, cell_area->y, cell_area->width, cell_area->height); if (event->type == GDK_BUTTON_PRESS) { GdkEventButton button = event->button; ol_logf (OL_DEBUG, " pointer (%d,%d)\n", (int)button.x, (int)button.y); if (button.x >= cell_area->x && button.x < cell_area->x + cell_area->width && button.y >= cell_area->y && button.y < cell_area->y + cell_area->height) { ol_logf (OL_DEBUG, " clicked\n"); g_signal_emit (cell, cell_renderer_button_signals[CLICKED], 0, path); } } return FALSE; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_cell_renderer_button.h000066400000000000000000000046251345713535600232150ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_CELLRENDERERBUTTON_H_ #define _OL_CELLRENDERERBUTTON_H_ #include #define OL_TYPE_CELL_RENDERER_BUTTON (ol_cell_renderer_button_get_type ()) #define OL_CELL_RENDERER_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), OL_TYPE_CELL_RENDERER_BUTTON, OlCellRendererButton)) #define OL_CELL_RENDERER_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), OL_TYPE_CELL_RENDERER_BUTTON, OlCellRendererButtonClass)) #define OL_IS_CELL_RENDERER_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), OL_TYPE_CELL_RENDERER_BUTTON)) #define OL_IS_CELL_RENDERER_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), OL_TYPE_CELL_RENDERER_BUTTON)) #define OL_CELL_RENDERER_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), OL_TYPE_CELL_RENDERER_BUTTON, OlCellRendererButtonClass)) typedef struct _OlCellRendererButton OlCellRendererButton; typedef struct _OlCellRendererButtonClass OlCellRendererButtonClass; struct _OlCellRendererButton { GtkCellRenderer parent; /*< private >*/ gchar *GSEAL (text); gchar *GSEAL (stock_id); PangoFontDescription *GSEAL (font); /* gint GSEAL (rise); */ /* gint GSEAL (fixed_height_rows); */ }; struct _OlCellRendererButtonClass { GtkCellRendererClass parent_class; void (* edited) (OlCellRendererButton *cell_renderer_button, const gchar *path, const gchar *new_text); void (* clicked) (OlCellRendererButton *cell_renderer_button, const gchar *path); }; GType ol_cell_renderer_button_get_type (void) G_GNUC_CONST; GtkCellRenderer *ol_cell_renderer_button_new (void); #endif /* _OL_CELLRENDERERBUTTON_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_color.c000066400000000000000000000050711345713535600201220ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include "ol_color.h" #include #include #include #include "ol_debug.h" OlColor ol_color_from_string (const char *color_str) { OlColor ret = ol_color_black; if (color_str == NULL) return ret; if (strlen (color_str) != 7) return ret; if (color_str[0] != '#') return ret; int i = 1; char colors[3][3] = {{0}}; for (i = 1; i < 7; i++) { if ((color_str[i] >= '0' && color_str[i] <= '9') || (color_str[i] >= 'a' && color_str[i] <= 'f') || (color_str[i] >= 'A' && color_str[i] <= 'F')) { colors[(i - 1) / 2][(i - 1) % 2] = color_str[i]; } else { return ret; } } unsigned int color_int[3]; for (i = 0; i < 3; i++) { sscanf (colors[i], "%x", &color_int[i]); } ret.r = color_int[0] / 255.0; ret.g = color_int[1] / 255.0; ret.b = color_int[2] / 255.0; return ret; } const char* ol_color_to_string (OlColor color) { static char ret[10] = ""; unsigned int r, g, b; r = color.r * 255; g = color.g * 255; b = color.b * 255; snprintf (ret, 10, "#%02x%02x%02x", r, g, b); ol_debugf ("color:%s\n", ret); return ret; } const OlColor ol_color_black = {0.0, 0.0, 0.0}; OlColor* ol_color_from_str_list (const char **str_list, int *len) { ol_assert_ret (str_list != NULL, NULL); int l = g_strv_length ((char**)str_list); OlColor *ret = g_new (OlColor, l); int i; for (i = 0; i < l; i++) { ret[i] = ol_color_from_string (str_list[i]); } if (len != NULL) *len = l; return ret; } char** ol_color_to_str_list (const OlColor *colors, int len) { ol_assert_ret (colors != NULL && len > 0, NULL); char **ret = g_new (char*, len + 1); ret[len] = NULL; int i; for (i = 0; i < len; i++) { ret[i] = g_strdup (ol_color_to_string (colors[i])); } return ret; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_color.h000066400000000000000000000043111345713535600201230ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_COLOR_H_ #define _OL_COLOR_H_ typedef struct __OlColor { double r; double g; double b; } OlColor; /** * @brief Parse a string in the form of #RRGGBB and return the corresponding OlColor * * @param color_str The color string * * @return The corresponding color, if color_str is not a valid color string, * return black (result of #000000) */ OlColor ol_color_from_string (const char *color_str); /** * @brief Convert a color to a string in the form of #RRGGBB * * @param color The color to be converted * * @return The converted string. It belongs to the function and should not be * freed. It will be changed after another ol_color_to_string is called. */ const char* ol_color_to_string (OlColor color); /** * @brief Converts a string list to color array * * @param str_list String array, should be terminated with NULL * @param len Return location of the count of colors * * @return Color array, should be free with g_free, or NULL if failed */ OlColor* ol_color_from_str_list (const char **str_list, int *len); /** * @brief Converts a color array to NULL terminated string array * * @param colors An array of OlColor * @param len The length of colors * * @return NULL terminated string array, free with g_strfreev */ char** ol_color_to_str_list (const OlColor *colors, int len); extern const OlColor ol_color_black; #endif /* _OL_COLOR_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_commands.c000066400000000000000000000025371345713535600206110ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include "ol_commands.h" #include "ol_config_proxy.h" #include "ol_debug.h" void ol_osd_lock_unlock () { OlConfigProxy *config = ol_config_proxy_get_instance (); ol_assert (config != NULL); ol_config_proxy_set_bool (config, "OSD/locked", !ol_config_proxy_get_bool (config, "OSD/locked")); } void ol_show_hide () { OlConfigProxy *config = ol_config_proxy_get_instance (); ol_assert (config != NULL); ol_config_proxy_set_bool (config, "General/visible", !ol_config_proxy_get_bool (config, "General/visible")); } osdlyrics-0.5.5~rc1+dfsg1/src/ol_commands.h000066400000000000000000000023241345713535600206100ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ /** * @file ol_commands.h * @author Tiger Soldier * @date Sun Aug 16 16:17:41 2009 * * @brief Common operation on OSD Lyrics * * */ #ifndef _OL_COMMANDS_H_ #define _OL_COMMANDS_H_ /** * @brief switch lock/unlock status on OSD windows * */ void ol_osd_lock_unlock (); /** * @brief switch show/hide status * */ void ol_show_hide (); #endif /* _OL_COMMANDS_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_config_property.h000066400000000000000000000077561345713535600222360ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ /** * @file ol_config_property.h * @author Tiger Soldier * @date Sun Jun 21 09:34:30 2009 * * @brief Defines properties that can be configured * * */ #ifndef _OL_CONFIG_PROPERTY_H_ #define _OL_CONFIG_PROPERTY_H_ #include "config.h" static const char *OL_CONFIG_DEFAULT_DOWNLOAD_ENGINE[] = { "ttPlayer", "xiami", NULL, }; static const char *OL_CONFIG_ACTIVE_LRC_COLOR[] = { "#FF8000", "#FFFF00", "#FF8000", NULL, }; static const char *OL_CONFIG_INACTIVE_LRC_COLOR[] = { "#99FFFF", "#0000FF", "#99FFFF", NULL, }; static const char *OL_CONFIG_DEFAULT_LRC_PATH[] = { "~/.lyrics", "%", NULL, }; static const char *OL_CONFIG_DEFAULT_LRC_FILENAME[] = { "%p-%t", "%t-%p", "%f", "%t", NULL, }; typedef struct _OlConfigStrListValue OlConfigStrListValue; struct _OlConfigStrListValue { const char *key; int len; const char **default_value; }; typedef struct _OlConfigBoolValue OlConfigBoolValue; struct _OlConfigBoolValue { const char *key; gboolean default_value; }; typedef struct _OlConfigStringValue OlConfigStringValue; struct _OlConfigStringValue { const char *key; char *default_value; }; typedef struct _OlConfigIntValue OlConfigIntValue; struct _OlConfigIntValue { const char *key; int min; int max; int default_value; }; typedef struct _OlConfigDoubleValue OlConfigDoubleValue; struct _OlConfigDoubleValue { const char *key; double min; double max; double default_value; }; static const OlConfigStrListValue config_str_list[] = { {"Download/download-engine", -1, OL_CONFIG_DEFAULT_DOWNLOAD_ENGINE}, {"OSD/active-lrc-color", 3, OL_CONFIG_ACTIVE_LRC_COLOR}, {"OSD/inactive-lrc-color", 3, OL_CONFIG_INACTIVE_LRC_COLOR}, {"General/lrc-path", -1, OL_CONFIG_DEFAULT_LRC_PATH}, {"General/lrc-filename", -1, OL_CONFIG_DEFAULT_LRC_FILENAME}, }; static const OlConfigBoolValue config_bool[] = { {"OSD/locked", TRUE}, {".visible", TRUE}, {"OSD/translucent-on-mouse-over", TRUE}, {"Download/download-first-lyric", FALSE}, {"General/display-mode-osd", TRUE}, {"General/display-mode-scroll", TRUE}, {"General/notify-music", TRUE}, }; static const OlConfigIntValue config_int[] = { {"OSD/width", 1, 10000, 1024}, {"OSD/outline-width", 0, 10, 3}, {"OSD/line-count", 1, 2, 1}, {"OSD/x", 0, 10000, 0}, {"OSD/y", 0, 10000, 0}, {"Download/proxy-port", 1, 65535, 7070}, {"ScrollMode/width", 1, 10000, 500}, {"ScrollMode/height", 1, 10000, 400}, }; static const OlConfigDoubleValue config_double[] = { {"OSD/lrc-align-0", 0.0, 1.0, 0.0}, {"OSD/lrc-align-1", 0.0, 1.0, 1.0}, {"ScrollMode/opacity", 0.0, 1.0, 0.9}, {"OSD/blur-radius", 0.0, 5.0, 2.0}, }; static const OlConfigStringValue config_str[] = { {"OSD/font-name", "serif 30"}, {"Download/proxy", "no"}, {"Download/proxy-type", "http"}, {"Download/proxy-host", ""}, {"Download/proxy-username", ""}, {"Download/proxy-password", ""}, {"General/startup-player", ""}, {"OSD/osd-window-mode", "dock"}, {"ScrollMode/font-name", "Sans 12"}, {"ScrollMode/bg-color", "#000000"}, {"ScrollMode/active-lrc-color", "#E3CF00"}, {"ScrollMode/inactive-lrc-color", "#FAEBD6"}, {"ScrollMode/scroll-mode", "always"}, }; #endif /* _OL_CONFIG_PROPERTY_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_config_proxy.c000066400000000000000000000460051345713535600215140ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2011 Tiger Soldier * * This file is part of OSD Lyrics. * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include "ol_config_proxy.h" #include "ol_consts.h" #include "ol_debug.h" const int DEFAULT_SYNC_TIMEOUT = 500; /* 0.5s */ #define OL_CONFIG_PROXY_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), \ OL_TYPE_CONFIG_PROXY, \ OlConfigProxyPrivate)) enum OlConfigProxySingals { SIGNAL_CHANGED = 0, LAST_SINGAL, }; enum _GetResult { GET_RESULT_OK = 0, GET_RESULT_FAILED, GET_RESULT_MISSING, }; typedef struct { GHashTable *temp_values; GVariantBuilder *default_builder; guint default_sync_handler; } OlConfigProxyPrivate; static guint _signals[LAST_SINGAL]; static OlConfigProxy *config_proxy = NULL; G_DEFINE_TYPE (OlConfigProxy, ol_config_proxy, G_TYPE_DBUS_PROXY); static OlConfigProxy *ol_config_proxy_new (void); static void ol_config_proxy_finalize (GObject *object); static void ol_config_proxy_g_signal (GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *parameters); static void ol_config_proxy_value_changed_cb (OlConfigProxy *proxy, GVariant *value); static GVariant *_str_list_to_variant (const gchar *const *value, gssize len); static gboolean _sync_default_cb (OlConfigProxy *config); static GVariant * _str_list_to_variant (const gchar *const *value, gssize len) { if (len < 0) len = g_strv_length ((gchar**) value); GVariantBuilder *builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); int i = 0; for (; (len < 0 || i < len) && value[i] != NULL; i++) { g_variant_builder_add (builder, "s", value[i]); } GVariant *gvalue = g_variant_new ("as", builder); g_variant_builder_unref (builder); return gvalue; } static gboolean _sync_default_cb (OlConfigProxy *config) { OlConfigProxyPrivate *priv = OL_CONFIG_PROXY_GET_PRIVATE (config); priv->default_sync_handler = 0; if (priv->default_builder != NULL) { GError *error = NULL; GVariant *ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (config), "SetDefaultValues", g_variant_new ("(a{sv})", priv->default_builder), G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout_secs */ NULL, /* cancellable */ &error); if (ret) { g_variant_unref (ret); } else { ol_errorf ("Cannot set config default values: %s\n", error->message); g_error_free (error); } g_variant_builder_unref (priv->default_builder); priv->default_builder = NULL; } return FALSE; } void ol_config_proxy_sync (OlConfigProxy *config) { OlConfigProxyPrivate *priv = OL_CONFIG_PROXY_GET_PRIVATE (config); if (priv->default_builder != NULL) { g_source_remove (priv->default_sync_handler); _sync_default_cb (config); } } static void ol_config_proxy_class_init (OlConfigProxyClass *klass) { GObjectClass *gobject_class; GDBusProxyClass *proxy_class; g_type_class_add_private (klass, sizeof (OlConfigProxyPrivate)); gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = ol_config_proxy_finalize; /* gobject_class->get_property = org_osdlyrics_lyrics_proxy_get_property; */ /* gobject_class->set_property = org_osdlyrics_lyrics_proxy_set_property; */ proxy_class = G_DBUS_PROXY_CLASS (klass); proxy_class->g_signal = ol_config_proxy_g_signal; /* proxy_class->g_properties_changed = ol_lyrics_g_properties_changed; */ _signals[SIGNAL_CHANGED] = g_signal_new ("changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED, 0, /* class_offset */ NULL, NULL, /* accumulator, accu_data */ g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); } static void ol_config_proxy_init (OlConfigProxy *proxy) { OlConfigProxyPrivate *priv = OL_CONFIG_PROXY_GET_PRIVATE (proxy); priv->temp_values = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref); } static void ol_config_proxy_finalize (GObject *object) { OlConfigProxyPrivate *priv = OL_CONFIG_PROXY_GET_PRIVATE (object); g_hash_table_destroy (priv->temp_values); priv->temp_values = NULL; ol_config_proxy_sync (OL_CONFIG_PROXY (object)); } static OlConfigProxy * ol_config_proxy_new (void) { GInitable *ret; ret = g_initable_new (OL_TYPE_CONFIG_PROXY, NULL, /* cancellable */ NULL, "g-flags", G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, "g-name", OL_SERVICE_CONFIG, "g-bus-type", G_BUS_TYPE_SESSION, "g-object-path", OL_OBJECT_CONFIG, "g-interface-name", OL_IFACE_CONFIG, NULL); if (ret != NULL) return OL_CONFIG_PROXY (ret); else return NULL; } static void ol_config_proxy_g_signal (GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *parameters) { if (strcmp (signal_name, "ValueChanged") == 0) { ol_config_proxy_value_changed_cb (OL_CONFIG_PROXY (proxy), parameters); } else { ol_errorf ("Unknown D-Bus signal: %s\n", signal_name); } } static void ol_config_proxy_value_changed_cb (OlConfigProxy *proxy, GVariant *parameters) { GVariantIter *iter = NULL; g_variant_get (parameters, "(as)", &iter); gchar *name = NULL; while (g_variant_iter_loop (iter, "s", &name)) { g_signal_emit (proxy, _signals[SIGNAL_CHANGED], g_quark_from_string (name), name); } g_variant_iter_free (iter); } OlConfigProxy* ol_config_proxy_get_instance (void) { if (config_proxy == NULL) config_proxy = ol_config_proxy_new (); return config_proxy; } void ol_config_proxy_unload (void) { if (config_proxy != NULL) { g_object_unref (config_proxy); config_proxy = NULL; } } static gboolean ol_config_proxy_set (OlConfigProxy *config, const gchar *method, const gchar *key, GVariant *value) { ol_assert_ret (key != NULL && key[0] != '\0', FALSE); if (key[0] == '.') { OlConfigProxyPrivate *priv = OL_CONFIG_PROXY_GET_PRIVATE (config); g_variant_ref_sink (value); g_hash_table_insert (priv->temp_values, g_strdup (key), g_variant_new ("(*)", g_variant_ref_sink (value))); g_signal_emit (config, _signals[SIGNAL_CHANGED], g_quark_from_string (key), key); g_variant_unref (value); return TRUE; } else { GError *error = NULL; GVariant *ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (config), method, g_variant_new ("(s*)", key, value), G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout_secs */ NULL, /* cancellable */ &error); if (ret) { g_variant_unref (ret); return TRUE; } else { ol_errorf ("Cannot set config value: %s\n", error->message); g_error_free (error); return FALSE; } } } gboolean ol_config_proxy_set_bool (OlConfigProxy *config, const gchar *key, gboolean value) { ol_assert_ret (OL_IS_CONFIG_PROXY (config), FALSE); ol_assert_ret (key != NULL, FALSE); return ol_config_proxy_set (config, "SetBool", key, g_variant_new ("b", value)); } gboolean ol_config_proxy_set_int (OlConfigProxy *config, const gchar *key, gint value) { ol_assert_ret (OL_IS_CONFIG_PROXY (config), FALSE); ol_assert_ret (key != NULL, FALSE); return ol_config_proxy_set (config, "SetInt", key, g_variant_new ("i", value)); } gboolean ol_config_proxy_set_double (OlConfigProxy *config, const gchar *key, gdouble value) { ol_assert_ret (OL_IS_CONFIG_PROXY (config), FALSE); ol_assert_ret (key != NULL, FALSE); return ol_config_proxy_set (config, "SetDouble", key, g_variant_new ("d", value)); } gboolean ol_config_proxy_set_string (OlConfigProxy *config, const gchar *key, const gchar* value) { ol_assert_ret (OL_IS_CONFIG_PROXY (config), FALSE); ol_assert_ret (key != NULL, FALSE); ol_assert_ret (value != NULL, FALSE); return ol_config_proxy_set (config, "SetString", key, g_variant_new ("s", value)); } gboolean ol_config_proxy_set_str_list (OlConfigProxy *config, const gchar *key, const gchar * const *value, gint len) { ol_assert_ret (OL_IS_CONFIG_PROXY (config), FALSE); ol_assert_ret (key != NULL, FALSE); ol_assert_ret (value != NULL, FALSE); return ol_config_proxy_set (config, "SetStringList", key, _str_list_to_variant (value, len)); } static gboolean ol_config_proxy_set_default (OlConfigProxy *config, const gchar *key, GVariant *value) { ol_assert_ret (key != NULL && key[0] != '\0', FALSE); OlConfigProxyPrivate *priv = OL_CONFIG_PROXY_GET_PRIVATE (config); if (key[0] == '.' && !g_hash_table_lookup (priv->temp_values, key)) { g_variant_ref_sink (value); g_hash_table_insert (priv->temp_values, g_strdup (key), g_variant_new ("(*)", g_variant_ref_sink (value))); g_signal_emit (config, _signals[SIGNAL_CHANGED], g_quark_from_string (key), key); g_variant_unref (value); } else { if (priv->default_builder == NULL) { priv->default_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); priv->default_sync_handler = g_timeout_add (DEFAULT_SYNC_TIMEOUT, (GSourceFunc) _sync_default_cb, config); } g_variant_builder_add (priv->default_builder, "{sv}", key, value); } return TRUE; } gboolean ol_config_proxy_set_bool_default (OlConfigProxy *config, const gchar *key, gboolean value) { ol_assert_ret (OL_IS_CONFIG_PROXY (config), FALSE); ol_assert_ret (key != NULL, FALSE); return ol_config_proxy_set_default (config, key, g_variant_new ("b", value)); } gboolean ol_config_proxy_set_int_default (OlConfigProxy *config, const gchar *key, gint value) { ol_assert_ret (OL_IS_CONFIG_PROXY (config), FALSE); ol_assert_ret (key != NULL, FALSE); return ol_config_proxy_set_default (config, key, g_variant_new ("i", value)); } gboolean ol_config_proxy_set_double_default (OlConfigProxy *config, const gchar *key, gdouble value) { ol_assert_ret (OL_IS_CONFIG_PROXY (config), FALSE); ol_assert_ret (key != NULL, FALSE); return ol_config_proxy_set_default (config, key, g_variant_new ("d", value)); } gboolean ol_config_proxy_set_string_default (OlConfigProxy *config, const gchar *key, const gchar* value) { ol_assert_ret (OL_IS_CONFIG_PROXY (config), FALSE); ol_assert_ret (key != NULL, FALSE); ol_assert_ret (value != NULL, FALSE); return ol_config_proxy_set_default (config, key, g_variant_new ("s", value)); } gboolean ol_config_proxy_set_str_list_default (OlConfigProxy *config, const gchar *key, const gchar * const *value, gint len) { ol_assert_ret (OL_IS_CONFIG_PROXY (config), FALSE); ol_assert_ret (key != NULL, FALSE); ol_assert_ret (value != NULL, FALSE); return ol_config_proxy_set_default (config, key, _str_list_to_variant (value, len)); } static enum _GetResult ol_config_proxy_get (OlConfigProxy *config, const gchar *method, const gchar *key, const gchar *format_string, gpointer retval) { ol_assert_ret (OL_IS_CONFIG_PROXY (config), GET_RESULT_FAILED); ol_assert_ret (key != NULL, GET_RESULT_FAILED); enum _GetResult ret = GET_RESULT_OK; GError *error = NULL; GVariant *value = NULL; OlConfigProxyPrivate *priv = OL_CONFIG_PROXY_GET_PRIVATE (config); if (key[0] == '.') { value = g_hash_table_lookup (priv->temp_values, key); if (value) g_variant_ref (value); } else { value = g_dbus_proxy_call_sync (G_DBUS_PROXY (config), method, g_variant_new ("(s)", key), G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout_secs */ NULL, /* cancellable */ &error); } if (!value) { if (g_dbus_error_is_remote_error (error)) { gchar *error_name = g_dbus_error_get_remote_error (error); if (strcmp (error_name, OL_ERROR_VALUE_NOT_EXIST) == 0) { ol_debugf ("Key %s not exists, use default value\n", key); ret = GET_RESULT_MISSING; } else { ol_errorf ("Failed to get config %s: %s\n", key, error->message); ret = GET_RESULT_FAILED; } g_free (error_name); } else { ol_errorf ("%s failed. Cannot get value %s from config: %s\n", method, key, error->message); ret = GET_RESULT_FAILED; } g_error_free (error); } else { g_variant_get (value, format_string, retval); g_variant_unref (value); } return ret; } gboolean ol_config_proxy_get_bool (OlConfigProxy *config, const gchar *key) { gboolean ret; switch (ol_config_proxy_get (config, "GetBool", key, "(b)", &ret)) { case GET_RESULT_OK: return ret; case GET_RESULT_MISSING: case GET_RESULT_FAILED: return FALSE; default: ol_error ("Unknown return value from ol_config_proxy_get"); return FALSE; } } gint ol_config_proxy_get_int (OlConfigProxy *config, const gchar *key) { gint ret; switch (ol_config_proxy_get (config, "GetInt", key, "(i)", &ret)) { case GET_RESULT_OK: return ret; case GET_RESULT_MISSING: case GET_RESULT_FAILED: return 0; default: ol_error ("Unknown return value from ol_config_proxy_get"); return 0; } } gdouble ol_config_proxy_get_double (OlConfigProxy *config, const gchar *key) { gdouble ret; switch (ol_config_proxy_get (config, "GetDouble", key, "(d)", &ret)) { case GET_RESULT_OK: return ret; case GET_RESULT_MISSING: case GET_RESULT_FAILED: return 0.0; default: ol_error ("Unknown return value from ol_config_proxy_get"); return 0.0; } } gchar* ol_config_proxy_get_string (OlConfigProxy *config, const gchar *key) { gchar *ret; switch (ol_config_proxy_get (config, "GetString", key, "(s)", &ret)) { case GET_RESULT_OK: return ret; case GET_RESULT_MISSING: return NULL; default: ol_error ("Unknown return value from ol_config_proxy_get"); return NULL; } } gchar** ol_config_proxy_get_str_list (OlConfigProxy *config, const char *key, gsize *len) { ol_assert_ret (OL_IS_CONFIG_PROXY (config), NULL); ol_assert_ret (key != NULL, NULL); GError *error = NULL; GVariant *value = g_dbus_proxy_call_sync (G_DBUS_PROXY (config), "GetStringList", g_variant_new ("(s)", key), G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout_secs */ NULL, /* cancellable */ &error); if (!value) { ol_errorf ("%s failed. Cannot get value %s from config: %s\n", "GetStringList", key, error->message); g_error_free (error); return NULL; } else { GVariantIter *iter = NULL; g_variant_get (value, "(as)", &iter); gsize list_size = g_variant_iter_n_children (iter); gchar **retval = g_new (gchar*, list_size + 1); gchar **ret_iter = retval; while (g_variant_iter_next (iter, "s", ret_iter)) ret_iter++; retval[list_size] = NULL; g_variant_unref (value); g_variant_iter_free (iter); if (len) *len = list_size; return retval; } } osdlyrics-0.5.5~rc1+dfsg1/src/ol_config_proxy.h000066400000000000000000000326571345713535600215310ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_CONFIG_PROXY_H_ #define _OL_CONFIG_PROXY_H_ #include #define OL_TYPE_CONFIG_PROXY \ (ol_config_proxy_get_type ()) #define OL_CONFIG_PROXY(obj) \ (G_TYPE_CHECK_INSTANCE_CAST ((obj), OL_TYPE_CONFIG_PROXY, OlConfigProxy)) #define OL_IS_CONFIG_PROXY(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), OL_TYPE_CONFIG_PROXY)) #define OL_CONFIG_PROXY_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST ((klass), OL_TYPE_CONFIG_PROXY, OlConfigProxyClass)) #define OL_IS_CONFIG_PROXY_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE ((klass), OL_TYPE_CONFIG_PROXY)) #define OL_CONFIG_PROXY_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), OL_TYPE_CONFIG_PROXY, OlConfigProxyClass)) typedef struct _OlConfigProxy OlConfigProxy; typedef struct _OlConfigProxyClass OlConfigProxyClass; struct _OlConfigProxy { GDBusProxy parent; }; struct _OlConfigProxyClass { GDBusProxyClass parent; }; GType ol_config_proxy_get_type (void); /** * @brief Gets the singleton instance of OlConfigProxy * * * @return An instance of OlConfigProxy, should not be freed */ OlConfigProxy* ol_config_proxy_get_instance (void); /** * @brief Sets an boolean property to the config * * @param config An OlConfigProxy * @param key The key of the config value, should be in the form of "group/name", or * any name started with a dot, like '.visible'. A key starting with a dot * indicates that it is a temporary config entry and should not be sync * to the config file. * @param value The value of the property * * @return If succeed, returns TRUE */ gboolean ol_config_proxy_set_bool (OlConfigProxy *config, const gchar *key, gboolean value); /** * @brief Sets an int property to the config * * @param config An OlConfigProxy * @param key The key of the config value, should be in the form of "group/name", or * any name started with a dot, like '.visible'. A key starting with a dot * indicates that it is a temporary config entry and should not be sync * to the config file. * @param value The value of the property * * @return If succeed, returns TRUE */ gboolean ol_config_proxy_set_int (OlConfigProxy *config, const gchar *key, gint value); /** * @brief Sets a double property to the config * * @param config An OlConfigProxy * @param key The key of the config value, should be in the form of "group/name", or * any name started with a dot, like '.visible'. A key starting with a dot * indicates that it is a temporary config entry and should not be sync * to the config file. * @param value The value of the property * * @return If succeed, returns TRUE */ gboolean ol_config_proxy_set_double (OlConfigProxy *config, const gchar *key, gdouble value); /** * @brief Sets a string property to the config * * @param config An OlConfigProxy * @param key The key of the config value, should be in the form of "group/name", or * any name started with a dot, like '.visible'. A key starting with a dot * indicates that it is a temporary config entry and should not be sync * to the config file. * @param value The value of the property * * @return If succeed, returns TRUE */ gboolean ol_config_proxy_set_string (OlConfigProxy *config, const gchar *key, const gchar* value); /** * @brief Sets a string list property to the config * * @param config An OlConfigProxy * @param key The key of the config value, should be in the form of "group/name", or * any name started with a dot, like '.visible'. A key starting with a dot * indicates that it is a temporary config entry and should not be sync * to the config file. * @param value The value of the property * @param len The length of the string property. -1 to indicate the list is * terminated with NULL. * * @return If succeed, returns TRUE */ gboolean ol_config_proxy_set_str_list (OlConfigProxy *config, const gchar *key, const gchar * const *value, gint len); /** * @brief Gets a boolean property from the config * * @param config An OlConfigProxy * @param key The key of the config value, should be in the form of "group/name", or * any name started with a dot, like '.visible'. A key starting with a dot * indicates that it is a temporary config entry and should not be sync * to the config file. * @param default_value The default value to return if the config service is * unavaliable or the key is not found in the config. * * @return If succeed, returns int value of the content. \ * If fail or the key does not exist, FALSE is returned. */ gboolean ol_config_proxy_get_bool (OlConfigProxy *config, const gchar *key); /** * @brief Gets a int property from the config * * @param config An OlConfigProxy * @param key The key of the config value, should be in the form of "group/name", or * any name started with a dot, like '.visible'. A key starting with a dot * indicates that it is a temporary config entry and should not be sync * to the config file. * @param default_value The default value to return if the config service is * unavaliable or the key is not found in the config. * * @return If succeed, returns int value of the content. \ * If fail or the key does not exist, 0 is retuened. */ gint ol_config_proxy_get_int (OlConfigProxy *config, const gchar *key); /** * @brief Gets a double property from the config * * @param config An OlConfigProxy * @param key The key of the config value, should be in the form of "group/name", or * any name started with a dot, like '.visible'. A key starting with a dot * indicates that it is a temporary config entry and should not be sync * to the config file. * @param default_value The default value to return if the config service is * unavaliable or the key is not found in the config. * * @return If succeed, returns double value of the content. \ * If fail or the key does not exist, 0.0 is returned. */ gdouble ol_config_proxy_get_double (OlConfigProxy *config, const gchar *key); /** * @brief Gets a string property of the config * * @param config An OlConfigProxy * @param key The key of the config value, should be in the form of "group/name", or * any name started with a dot, like '.visible'. A key starting with a dot * indicates that it is a temporary config entry and should not be sync * to the config file. * @param default_value The default value to return if the config service is * unavaliable or the key is not found in the config. * * @return If succeed, returns duplicated string value of the content, \ * must be freed by g_free. If fail or the key does not exist, returns NULL. */ gchar* ol_config_proxy_get_string (OlConfigProxy *config, const gchar *key); /** * @brief Gets a string property of the config * * @param config An OlConfigProxy * @param key The key of the config value, should be in the form of "group/name", or * any name started with a dot, like '.visible'. A key starting with a dot * indicates that it is a temporary config entry and should not be sync * to the config file. * @param len return location of length of string list, or NULL * * @return a NULL-terminated string array or NULL if the specified key cannot * be found. The array should be freed with g_strfreev(). * If fail or the key does not exist, NULL will be returned. */ gchar** ol_config_proxy_get_str_list (OlConfigProxy *config, const char *key, gsize *len); /** * Sets the default value to a boolean config entry. * It is similar to ol_config_proxy_set_bool(). The difference is when a key * exists, the value will not be overwritten. * * @param config An OlConfigProxy * @param key The key of the config value, should be in the form of "group/name", or * any name started with a dot, like '.visible'. A key starting with a dot * indicates that it is a temporary config entry and should not be sync * to the config file. * @param value The value of the property * * @return If succeed, returns TRUE */ gboolean ol_config_proxy_set_bool_default (OlConfigProxy *config, const gchar *key, gboolean value); /** * Sets the default value to a integer config entry. * * It is similar to ol_config_proxy_set_int(). The difference is when a key exists, * the value will not be overwritten. * * @param config An OlConfigProxy * @param key The key of the config value, should be in the form of "group/name", or * any name started with a dot, like '.visible'. A key starting with a dot * indicates that it is a temporary config entry and should not be sync * to the config file. * @param value The value of the property * * @return If succeed, returns TRUE */ gboolean ol_config_proxy_set_int_default (OlConfigProxy *config, const gchar *key, gint value); /** * Sets the default value to a double config entry. * * It is similar to ol_config_proxy_set_double(). The difference is when a key * exists, the value will not be overwritten. * * @param config An OlConfigProxy * @param key The key of the config value, should be in the form of "group/name", or * any name started with a dot, like '.visible'. A key starting with a dot * indicates that it is a temporary config entry and should not be sync * to the config file. * @param value The value of the property * * @return If succeed, returns TRUE */ gboolean ol_config_proxy_set_double_default (OlConfigProxy *config, const gchar *key, gdouble value); /** * Sets the default value to a string config entry. * * It is similar to ol_config_proxy_set_string(). The difference is when a key * exists, the value will not be overwritten. * * @param config An OlConfigProxy * @param key The key of the config value, should be in the form of "group/name", or * any name started with a dot, like '.visible'. A key starting with a dot * indicates that it is a temporary config entry and should not be sync * to the config file. * @param value The value of the property * * @return If succeed, returns TRUE */ gboolean ol_config_proxy_set_string_default (OlConfigProxy *config, const gchar *key, const gchar* value); /** * Sets the default value to a string list config entry. * * It is similar to ol_config_proxy_set_string_list(). The difference is when a key * exists, the value will not be overwritten. * * @param config An OlConfigProxy * @param key The key of the config value, should be in the form of "group/name", or * any name started with a dot, like '.visible'. A key starting with a dot * indicates that it is a temporary config entry and should not be sync * to the config file. * @param value The value of the property * @param len The length of the string property. -1 to indicate the list is * terminated with NULL. * * @return If succeed, returns TRUE */ gboolean ol_config_proxy_set_str_list_default (OlConfigProxy *config, const gchar *key, const gchar * const *value, gint len); /** * @brief Unload config module * */ void ol_config_proxy_unload (void); /** * Sync the config settings immediately. * */ void ol_config_proxy_sync (OlConfigProxy *config); #endif /* _OL_CONFIG_PROXY_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_config_updater.c000066400000000000000000000050051345713535600217720ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include "ol_config_updater.h" #include "ol_config_proxy.h" #include "ol_config_property.h" static void _update_default_values (void) { OlConfigProxy *config = ol_config_proxy_get_instance (); int i; for (i = 0; i < G_N_ELEMENTS (config_bool); i++) { ol_config_proxy_set_bool_default (config, config_bool[i].key, config_bool[i].default_value); } for (i = 0; i < G_N_ELEMENTS (config_int); i++) { ol_config_proxy_set_int_default (config, config_int[i].key, config_int[i].default_value); } for (i = 0; i < G_N_ELEMENTS (config_double); i++) { ol_config_proxy_set_double_default (config, config_double[i].key, config_double[i].default_value); } for (i = 0; i < G_N_ELEMENTS (config_str); i++) { ol_config_proxy_set_string_default (config, config_str[i].key, config_str[i].default_value); } for (i = 0; i < G_N_ELEMENTS (config_str_list); i++) { int len = 0; if (config_str_list[i].len > 0) { len = config_str_list[i].len; } else { while (config_str_list[i].default_value[len] != NULL) len++; } ol_config_proxy_set_str_list_default (config, config_str_list[i].key, config_str_list[i].default_value, len); } ol_config_proxy_sync (config); } void ol_config_update (void) { _update_default_values (); } osdlyrics-0.5.5~rc1+dfsg1/src/ol_config_updater.h000066400000000000000000000016261345713535600220040ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2011 Tiger Soldier * * This file is part of OSD Lyrics. * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_CONFIG_UPDATER_H_ #define _OL_CONFIG_UPDATER_H_ void ol_config_update (void); #endif /* _OL_CONFIG_UPDATER_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_consts.h000066400000000000000000000046161345713535600203260ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_CONSTS_H_ #define _OL_CONSTS_H_ /* The D-Bus well-known name for OSD Lyrics Daemon */ #define OL_SERVICE_DAEMON "org.osdlyrics.Daemon" /* The interface of daemon controlling */ #define OL_IFACE_DAEMON "org.osdlyrics.Daemon" /* The object path of daemon controlling */ #define OL_OBJECT_DAEMON "/org/osdlyrics/Daemon" /* The interface of player module */ #define OL_IFACE_PLAYER "org.osdlyrics.Player" /* The object path of player support */ #define OL_OBJECT_PLAYER "/org/osdlyrics/Player" /* The interface of lyrics module */ #define OL_IFACE_LYRICS "org.osdlyrics.Lyrics" /* The object path of lyrics module */ #define OL_OBJECT_LYRICS "/org/osdlyrics/Lyrics" /* The interface of LyricSource module */ #define OL_IFACE_LYRIC_SOURCE "org.osdlyrics.LyricSource" /* The object path of LyricSource module */ #define OL_OBJECT_LYRIC_SOURCE "/org/osdlyrics/LyricSource" /* The D-Bus well-known name for OSD Lyrics Config */ #define OL_SERVICE_CONFIG "org.osdlyrics.Config" /* The interface of config service */ #define OL_IFACE_CONFIG "org.osdlyrics.Config" /* The object path of config service */ #define OL_OBJECT_CONFIG "/org/osdlyrics/Config" /* The interface of MPRIS2 */ #define OL_IFACE_MPRIS2_PLAYER "org.mpris.MediaPlayer2.Player" /* The object path of player control of MPRIS1 */ #define OL_OBJECT_MPRIS2 "/org/mpris/MediaPlayer2" /* The bus name of the GUI process */ #define OL_CLIENT_BUS_NAME "org.osdlyrics.Client.Gtk" /* Exceptions */ #define OL_ERROR_MALFORMED_KEY "org.osdlyrics.Error.MalformedKey" #define OL_ERROR_VALUE_NOT_EXIST "org.osdlyrics.Error.ValueNotExist" #endif /* _OL_CONSTS_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_debug.c000066400000000000000000000051521345713535600200720ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include #include #include #include "ol_debug.h" #define COLOR_BOLD "\033[1m" #define COLOR_ITALIC "\033[3m" #define COLOR_RED "\033[31m" #define COLOR_YELLOW "\033[33m" #define COLOR_GREEN "\033[32m" #define COLOR_RESET "\033[0m" #define COLOR_ERROR COLOR_BOLD COLOR_RED #define COLOR_DEBUG COLOR_BOLD COLOR_YELLOW #define COLOR_INFO COLOR_BOLD COLOR_GREEN int gANSIEscapes = 0; static const char *LEVEL_MSG[] = { COLOR_ERROR "*Error*" COLOR_RESET, COLOR_DEBUG "*Debug*" COLOR_RESET, COLOR_INFO "*Info*" COLOR_RESET, }; static FILE *flog = NULL; static int debug_level = OL_ERROR; static int _ensure_flog () { if (flog == NULL) /* return ol_log_set_file ("-"); */ flog = stdout; return 1; } void ol_log_printf (int level, const char *file, int line, const char *funcname, const char *fmt, ...) { ol_assert (_ensure_flog ()); ol_assert (flog != NULL); ol_assert (level >= 0); ol_assert (level < OL_N_LEVELS); if (level > debug_level) return; va_list ap; va_start (ap, fmt); fprintf (flog, "%s: in function " COLOR_BOLD "%s" COLOR_RESET ": %s[%d]\n", LEVEL_MSG[level], funcname, file, line); vfprintf (flog, fmt, ap); va_end (ap); } void ol_log_set_level (enum OlDebugLevel level) { ol_assert (level >= -1); ol_assert (level < OL_N_LEVELS); debug_level = level; } int ol_log_set_file (const char *logfile) { ol_assert_ret (logfile != NULL, 0); if (flog != NULL) { fclose (flog); flog = NULL; } if (strcmp (logfile, "-") == 0) { flog = fdopen (STDOUT_FILENO, "w"); } else { flog = fopen (logfile, "w"); } return flog != NULL; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_debug.h000066400000000000000000000063031345713535600200760ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_DEBUG_H_ #define _OL_DEBUG_H_ #include enum OlDebugLevel { OL_LOG_NONE = -1, OL_ERROR = 0, OL_DEBUG = 1, OL_INFO = 2, OL_N_LEVELS, }; #define ol_logf(level, ...) do {ol_log_printf (level, \ __FILE__, \ __LINE__, \ __FUNCTION__, \ __VA_ARGS__); } while (0) #define ol_log_func() do {ol_logf (OL_INFO, "%s\n", __FUNCTION__); } while (0) #define ol_debugf(...) do {ol_logf (OL_DEBUG, __VA_ARGS__); } while (0) #define ol_debug(...) do {ol_logf (OL_DEBUG, "%s\n", __VA_ARGS__); } while (0) #define ol_errorf(...) do {ol_logf (OL_ERROR, __VA_ARGS__); } while (0) #define ol_error(...) do {ol_logf (OL_ERROR, "%s\n", __VA_ARGS__); } while (0) #define ol_infof(...) do {ol_logf (OL_INFO, __VA_ARGS__); } while (0) #define ol_info(...) do {ol_logf (OL_INFO, "%s\n", __VA_ARGS__); } while (0) #define ol_assert(assertion) do {if (!(assertion)) { \ ol_logf (OL_ERROR, "assert %s failed\n", #assertion); \ return; \ }} while (0) #define ol_assert_ret(assertion, ret) do {if (!(assertion)) { \ ol_logf (OL_ERROR, "assert %s failed\n", #assertion); \ return (ret); \ }} while (0) void ol_log_printf (int level, const char *file, int line, const char *funcname, const char *fmt, ...); /** * @brief Sets the message level to be logged * * @param level The level of message to be logged. There are 4 levels:
* OL_LOG_NONE: Log nothing * OL_ERROR: Log only messages with OL_ERROR * OL_DEBUG: Log messages with OL_ERROR and OL_DEBUG * OL_INFO: Log all messages */ void ol_log_set_level (enum OlDebugLevel level); /** * @brief Sets the file to store log messages. * * @param logfile The filename of the log file. if logfile is "-", it represents * standard output * @return Return 1 if succeeded, or 0 if failed. */ int ol_log_set_file (const char *logfile); #endif /* _OL_DEBUG_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_display_module.c000066400000000000000000000124421345713535600220160ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include "ol_player.h" #include "ol_display_module.h" #include "ol_osd_module.h" #include "ol_scroll_module.h" #include "ol_utils.h" #include "ol_debug.h" #define call(func, ...) do { if ((func) != NULL) (func) (__VA_ARGS__); } while (0) static struct OlDisplayClass* _get_class (const char *name); static void _class_free (struct OlDisplayClass *klass); /** * @brief Sets the data of module. * * @param OlDisplayModule * @param data */ static void _set_data (struct OlDisplayModule *module, void *data); static void _register_class (struct OlDisplayClass *klass); static GPtrArray *classes = NULL; void ol_display_module_init () { if (classes == NULL) { classes = g_ptr_array_new_with_free_func ((GDestroyNotify)_class_free); _register_class (ol_osd_module_get_class ()); _register_class (ol_scroll_module_get_class ()); } } void ol_display_module_unload () { if (classes != NULL) { g_ptr_array_free (classes, TRUE); classes = NULL; } } static void _register_class (struct OlDisplayClass *klass) { g_ptr_array_add (classes, klass); } static struct OlDisplayClass* _get_class (const char *name) { ol_assert_ret (name != NULL, NULL); int i; for (i = 0; i < classes->len; i++) { struct OlDisplayClass *klass = (struct OlDisplayClass *)(g_ptr_array_index (classes, i)); if (ol_stricmp (klass->name, name, -1) == 0) return klass; } return NULL; } struct OlDisplayClass* ol_display_class_new (const char *name, OlDisplayInitFunc init_func, OlDisplayFreeFunc free_func) { ol_assert_ret (name != NULL, NULL); ol_assert_ret (init_func != NULL, NULL); ol_assert_ret (free_func != NULL, NULL); struct OlDisplayClass *klass = g_new0 (struct OlDisplayClass, 1); klass->name = g_strdup (name); klass->init = init_func; klass->free = free_func; return klass; } static void _class_free (struct OlDisplayClass *klass) { ol_assert (klass != NULL); if (klass->name != NULL) { g_free (klass->name); klass->name = NULL; } g_free (klass); } static void _set_data (struct OlDisplayModule *module, void *data) { ol_assert (module != NULL); module->data = data; } void* ol_display_module_get_data (struct OlDisplayModule *module) { ol_assert_ret (module != NULL, NULL); return module->data; } struct OlDisplayModule* ol_display_module_new (const char *name, OlPlayer *player) { ol_assert_ret (name != NULL, NULL); struct OlDisplayModule *module = NULL; struct OlDisplayClass *klass = _get_class (name); if (klass == NULL) { ol_error ("Module %s not exists.\n", name); /* Fall back to the first display module. */ return g_ptr_array_index (classes, 0); } module = g_new0 (struct OlDisplayModule, 1); module->klass = klass; _set_data (module, klass->init (module, player)); return module; } void ol_display_module_free (struct OlDisplayModule *module) { ol_assert (module != NULL); module->klass->free (module); g_free (module); } void ol_display_module_set_played_time (struct OlDisplayModule *module, guint64 played_time) { ol_assert (module != NULL); call (module->klass->set_played_time, module, played_time); } void ol_display_module_set_lrc (struct OlDisplayModule *module, OlLrc *lrc) { ol_assert (module != NULL); call (module->klass->set_lrc, module, lrc); } void ol_display_module_set_message (struct OlDisplayModule *module, const char *message, int duration_ms) { ol_assert (module != NULL); call (module->klass->set_message, module, message, duration_ms); } void ol_display_module_search_message (struct OlDisplayModule *module, const char *message) { ol_assert (module != NULL); call (module->klass->search_message, module, message); } void ol_display_module_search_fail_message (struct OlDisplayModule *module, const char *message) { ol_assert (module != NULL); call (module->klass->search_fail_message, module, message); } void ol_display_module_download_fail_message (struct OlDisplayModule *module, const char *message) { ol_assert (module != NULL); call (module->klass->download_fail_message, module, message); } void ol_display_module_clear_message (struct OlDisplayModule *module) { ol_assert (module != NULL); call (module->klass->clear_message, module); } osdlyrics-0.5.5~rc1+dfsg1/src/ol_display_module.h000066400000000000000000000110361345713535600220210ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_DISPLAY_MODULE_H_ #define _OL_DISPLAY_MODULE_H_ #include "ol_metadata.h" #include "ol_lrc.h" #include "ol_player.h" struct OlLrc; struct OlPlayer; enum OlPlayerStatus; struct OlDisplayModule { const struct OlDisplayClass *klass; void *data; }; typedef void* (*OlDisplayInitFunc) (struct OlDisplayModule *module, OlPlayer *player); typedef void (*OlDisplayFreeFunc) (struct OlDisplayModule *module); struct OlDisplayClass { char *name; OlDisplayInitFunc init; OlDisplayFreeFunc free; void (*set_lrc) (struct OlDisplayModule *module, OlLrc *lrc_file); void (*set_played_time) (struct OlDisplayModule *module, guint64 played_time); void (*set_message) (struct OlDisplayModule *module, const char *message, int duration_ms); void (*search_message) (struct OlDisplayModule *module, const char *message); void (*search_fail_message) (struct OlDisplayModule *module, const char *message); void (*download_fail_message) (struct OlDisplayModule *module, const char *message); void (*clear_message) (struct OlDisplayModule *module); }; /** functions for implementing concrete modules **/ /** * @brief Create a new class for display module * * This should be used in ol_foo_moduel_get_class, where foo is the name of * the display module * * @param name The name of the new display module * @param init_func The function to initialize the private data * @param free_func The function to free the private data * * @return A new class of display module, with all functions but init and free * set to NULL */ struct OlDisplayClass* ol_display_class_new (const char *name, OlDisplayInitFunc init_func, OlDisplayFreeFunc free_func); void* ol_display_module_get_data (struct OlDisplayModule *module); /** functions for display module controlling **/ /** * Initialize the display module. * * This function must be called before any other functions */ void ol_display_module_init (); void ol_display_module_unload (); /** * @brief Create a display module of given type * * @param name The name of the type of display module, case insensitive * @param player The player proxy to use * * @return The new display module. If the display module with the given name * not exists, a default display module is returned. The returned module * must be freed with ol_display_module_free. */ struct OlDisplayModule *ol_display_module_new (const char *name, OlPlayer *player); void ol_display_module_free (struct OlDisplayModule *module); void ol_display_module_set_played_time (struct OlDisplayModule *module, guint64 played_time); void ol_display_module_set_lrc (struct OlDisplayModule *module, OlLrc *lrc); void ol_display_module_set_message (struct OlDisplayModule *module, const char *message, int duration_ms); void ol_display_module_search_message (struct OlDisplayModule *module, const char *message); void ol_display_module_search_fail_message (struct OlDisplayModule *module, const char *message); void ol_display_module_download_fail_message (struct OlDisplayModule *module, const char *message); void ol_display_module_clear_message (struct OlDisplayModule *module); #endif /* _OL_DISPLAY_MODULE_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_eggaccelerators.c000066400000000000000000000442171345713535600221430ustar00rootroot00000000000000/* eggaccelerators.c * Copyright (C) 2002 Red Hat, Inc.; Copyright 1998, 2001 Tim Janik * Developed by Havoc Pennington, Tim Janik * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "ol_eggaccelerators.h" #include #include #include enum { EGG_MODMAP_ENTRY_SHIFT = 0, EGG_MODMAP_ENTRY_LOCK = 1, EGG_MODMAP_ENTRY_CONTROL = 2, EGG_MODMAP_ENTRY_MOD1 = 3, EGG_MODMAP_ENTRY_MOD2 = 4, EGG_MODMAP_ENTRY_MOD3 = 5, EGG_MODMAP_ENTRY_MOD4 = 6, EGG_MODMAP_ENTRY_MOD5 = 7, EGG_MODMAP_ENTRY_LAST = 8 }; #define MODMAP_ENTRY_TO_MODIFIER(x) (1 << (x)) typedef struct { EggVirtualModifierType mapping[EGG_MODMAP_ENTRY_LAST]; } EggModmap; const EggModmap* egg_keymap_get_modmap (GdkKeymap *keymap); static inline gboolean is_alt (const gchar *string) { return ((string[0] == '<') && (string[1] == 'a' || string[1] == 'A') && (string[2] == 'l' || string[2] == 'L') && (string[3] == 't' || string[3] == 'T') && (string[4] == '>')); } static inline gboolean is_ctl (const gchar *string) { return ((string[0] == '<') && (string[1] == 'c' || string[1] == 'C') && (string[2] == 't' || string[2] == 'T') && (string[3] == 'l' || string[3] == 'L') && (string[4] == '>')); } static inline gboolean is_modx (const gchar *string) { return ((string[0] == '<') && (string[1] == 'm' || string[1] == 'M') && (string[2] == 'o' || string[2] == 'O') && (string[3] == 'd' || string[3] == 'D') && (string[4] >= '1' && string[4] <= '5') && (string[5] == '>')); } static inline gboolean is_ctrl (const gchar *string) { return ((string[0] == '<') && (string[1] == 'c' || string[1] == 'C') && (string[2] == 't' || string[2] == 'T') && (string[3] == 'r' || string[3] == 'R') && (string[4] == 'l' || string[4] == 'L') && (string[5] == '>')); } static inline gboolean is_shft (const gchar *string) { return ((string[0] == '<') && (string[1] == 's' || string[1] == 'S') && (string[2] == 'h' || string[2] == 'H') && (string[3] == 'f' || string[3] == 'F') && (string[4] == 't' || string[4] == 'T') && (string[5] == '>')); } static inline gboolean is_shift (const gchar *string) { return ((string[0] == '<') && (string[1] == 's' || string[1] == 'S') && (string[2] == 'h' || string[2] == 'H') && (string[3] == 'i' || string[3] == 'I') && (string[4] == 'f' || string[4] == 'F') && (string[5] == 't' || string[5] == 'T') && (string[6] == '>')); } static inline gboolean is_control (const gchar *string) { return ((string[0] == '<') && (string[1] == 'c' || string[1] == 'C') && (string[2] == 'o' || string[2] == 'O') && (string[3] == 'n' || string[3] == 'N') && (string[4] == 't' || string[4] == 'T') && (string[5] == 'r' || string[5] == 'R') && (string[6] == 'o' || string[6] == 'O') && (string[7] == 'l' || string[7] == 'L') && (string[8] == '>')); } static inline gboolean is_release (const gchar *string) { return ((string[0] == '<') && (string[1] == 'r' || string[1] == 'R') && (string[2] == 'e' || string[2] == 'E') && (string[3] == 'l' || string[3] == 'L') && (string[4] == 'e' || string[4] == 'E') && (string[5] == 'a' || string[5] == 'A') && (string[6] == 's' || string[6] == 'S') && (string[7] == 'e' || string[7] == 'E') && (string[8] == '>')); } static inline gboolean is_meta (const gchar *string) { return ((string[0] == '<') && (string[1] == 'm' || string[1] == 'M') && (string[2] == 'e' || string[2] == 'E') && (string[3] == 't' || string[3] == 'T') && (string[4] == 'a' || string[4] == 'A') && (string[5] == '>')); } static inline gboolean is_super (const gchar *string) { return ((string[0] == '<') && (string[1] == 's' || string[1] == 'S') && (string[2] == 'u' || string[2] == 'U') && (string[3] == 'p' || string[3] == 'P') && (string[4] == 'e' || string[4] == 'E') && (string[5] == 'r' || string[5] == 'R') && (string[6] == '>')); } static inline gboolean is_hyper (const gchar *string) { return ((string[0] == '<') && (string[1] == 'h' || string[1] == 'H') && (string[2] == 'y' || string[2] == 'Y') && (string[3] == 'p' || string[3] == 'P') && (string[4] == 'e' || string[4] == 'E') && (string[5] == 'r' || string[5] == 'R') && (string[6] == '>')); } /** * egg_accelerator_parse_virtual: * @accelerator: string representing an accelerator * @accelerator_key: return location for accelerator keyval * @accelerator_mods: return location for accelerator modifier mask * * Parses a string representing a virtual accelerator. The format * looks like "<Control>a" or "<Shift><Alt>F1" or * "<Release>z" (the last one is for key release). The parser * is fairly liberal and allows lower or upper case, and also * abbreviations such as "<Ctl>" and "<Ctrl>". * * If the parse fails, @accelerator_key and @accelerator_mods will * be set to 0 (zero) and %FALSE will be returned. If the string contains * only modifiers, @accelerator_key will be set to 0 but %TRUE will be * returned. * * The virtual vs. concrete accelerator distinction is a relic of * how the X Window System works; there are modifiers Mod2-Mod5 that * can represent various keyboard keys (numlock, meta, hyper, etc.), * the virtual modifier represents the keyboard key, the concrete * modifier the actual Mod2-Mod5 bits in the key press event. * * Returns: %TRUE on success. */ gboolean egg_accelerator_parse_virtual (const gchar *accelerator, guint *accelerator_key, EggVirtualModifierType *accelerator_mods) { guint keyval; GdkModifierType mods; gint len; gboolean bad_keyval; if (accelerator_key) *accelerator_key = 0; if (accelerator_mods) *accelerator_mods = 0; g_return_val_if_fail (accelerator != NULL, FALSE); bad_keyval = FALSE; keyval = 0; mods = 0; len = strlen (accelerator); while (len) { if (*accelerator == '<') { if (len >= 9 && is_release (accelerator)) { accelerator += 9; len -= 9; mods |= EGG_VIRTUAL_RELEASE_MASK; } else if (len >= 9 && is_control (accelerator)) { accelerator += 9; len -= 9; mods |= EGG_VIRTUAL_CONTROL_MASK; } else if (len >= 7 && is_shift (accelerator)) { accelerator += 7; len -= 7; mods |= EGG_VIRTUAL_SHIFT_MASK; } else if (len >= 6 && is_shft (accelerator)) { accelerator += 6; len -= 6; mods |= EGG_VIRTUAL_SHIFT_MASK; } else if (len >= 6 && is_ctrl (accelerator)) { accelerator += 6; len -= 6; mods |= EGG_VIRTUAL_CONTROL_MASK; } else if (len >= 6 && is_modx (accelerator)) { static const guint mod_vals[] = { EGG_VIRTUAL_ALT_MASK, EGG_VIRTUAL_MOD2_MASK, EGG_VIRTUAL_MOD3_MASK, EGG_VIRTUAL_MOD4_MASK, EGG_VIRTUAL_MOD5_MASK }; len -= 6; accelerator += 4; mods |= mod_vals[*accelerator - '1']; accelerator += 2; } else if (len >= 5 && is_ctl (accelerator)) { accelerator += 5; len -= 5; mods |= EGG_VIRTUAL_CONTROL_MASK; } else if (len >= 5 && is_alt (accelerator)) { accelerator += 5; len -= 5; mods |= EGG_VIRTUAL_ALT_MASK; } else if (len >= 6 && is_meta (accelerator)) { accelerator += 6; len -= 6; mods |= EGG_VIRTUAL_META_MASK; } else if (len >= 7 && is_hyper (accelerator)) { accelerator += 7; len -= 7; mods |= EGG_VIRTUAL_HYPER_MASK; } else if (len >= 7 && is_super (accelerator)) { accelerator += 7; len -= 7; mods |= EGG_VIRTUAL_SUPER_MASK; } else { gchar last_ch; last_ch = *accelerator; while (last_ch && last_ch != '>') { last_ch = *accelerator; accelerator += 1; len -= 1; } } } else { keyval = gdk_keyval_from_name (accelerator); if (keyval == 0) bad_keyval = TRUE; accelerator += len; len -= len; } } if (accelerator_key) *accelerator_key = gdk_keyval_to_lower (keyval); if (accelerator_mods) *accelerator_mods = mods; return !bad_keyval; } /** * egg_virtual_accelerator_name: * @accelerator_key: accelerator keyval * @accelerator_mods: accelerator modifier mask * @returns: a newly-allocated accelerator name * * Converts an accelerator keyval and modifier mask * into a string parseable by egg_accelerator_parse_virtual(). * For example, if you pass in #GDK_q and #EGG_VIRTUAL_CONTROL_MASK, * this function returns "<Control>q". * * The caller of this function must free the returned string. */ gchar* egg_virtual_accelerator_name (guint accelerator_key, EggVirtualModifierType accelerator_mods) { static const gchar text_release[] = ""; static const gchar text_shift[] = ""; static const gchar text_control[] = ""; static const gchar text_mod1[] = ""; static const gchar text_mod2[] = ""; static const gchar text_mod3[] = ""; static const gchar text_mod4[] = ""; static const gchar text_mod5[] = ""; static const gchar text_meta[] = ""; static const gchar text_super[] = ""; static const gchar text_hyper[] = ""; guint l; gchar *keyval_name; gchar *accelerator; accelerator_mods &= EGG_VIRTUAL_MODIFIER_MASK; keyval_name = gdk_keyval_name (gdk_keyval_to_lower (accelerator_key)); if (!keyval_name) keyval_name = ""; l = 0; if (accelerator_mods & EGG_VIRTUAL_RELEASE_MASK) l += sizeof (text_release) - 1; if (accelerator_mods & EGG_VIRTUAL_SHIFT_MASK) l += sizeof (text_shift) - 1; if (accelerator_mods & EGG_VIRTUAL_CONTROL_MASK) l += sizeof (text_control) - 1; if (accelerator_mods & EGG_VIRTUAL_ALT_MASK) l += sizeof (text_mod1) - 1; if (accelerator_mods & EGG_VIRTUAL_MOD2_MASK) l += sizeof (text_mod2) - 1; if (accelerator_mods & EGG_VIRTUAL_MOD3_MASK) l += sizeof (text_mod3) - 1; if (accelerator_mods & EGG_VIRTUAL_MOD4_MASK) l += sizeof (text_mod4) - 1; if (accelerator_mods & EGG_VIRTUAL_MOD5_MASK) l += sizeof (text_mod5) - 1; if (accelerator_mods & EGG_VIRTUAL_META_MASK) l += sizeof (text_meta) - 1; if (accelerator_mods & EGG_VIRTUAL_HYPER_MASK) l += sizeof (text_hyper) - 1; if (accelerator_mods & EGG_VIRTUAL_SUPER_MASK) l += sizeof (text_super) - 1; l += strlen (keyval_name); accelerator = g_new (gchar, l + 1); l = 0; accelerator[l] = 0; if (accelerator_mods & EGG_VIRTUAL_RELEASE_MASK) { strcpy (accelerator + l, text_release); l += sizeof (text_release) - 1; } if (accelerator_mods & EGG_VIRTUAL_SHIFT_MASK) { strcpy (accelerator + l, text_shift); l += sizeof (text_shift) - 1; } if (accelerator_mods & EGG_VIRTUAL_CONTROL_MASK) { strcpy (accelerator + l, text_control); l += sizeof (text_control) - 1; } if (accelerator_mods & EGG_VIRTUAL_ALT_MASK) { strcpy (accelerator + l, text_mod1); l += sizeof (text_mod1) - 1; } if (accelerator_mods & EGG_VIRTUAL_MOD2_MASK) { strcpy (accelerator + l, text_mod2); l += sizeof (text_mod2) - 1; } if (accelerator_mods & EGG_VIRTUAL_MOD3_MASK) { strcpy (accelerator + l, text_mod3); l += sizeof (text_mod3) - 1; } if (accelerator_mods & EGG_VIRTUAL_MOD4_MASK) { strcpy (accelerator + l, text_mod4); l += sizeof (text_mod4) - 1; } if (accelerator_mods & EGG_VIRTUAL_MOD5_MASK) { strcpy (accelerator + l, text_mod5); l += sizeof (text_mod5) - 1; } if (accelerator_mods & EGG_VIRTUAL_META_MASK) { strcpy (accelerator + l, text_meta); l += sizeof (text_meta) - 1; } if (accelerator_mods & EGG_VIRTUAL_HYPER_MASK) { strcpy (accelerator + l, text_hyper); l += sizeof (text_hyper) - 1; } if (accelerator_mods & EGG_VIRTUAL_SUPER_MASK) { strcpy (accelerator + l, text_super); l += sizeof (text_super) - 1; } strcpy (accelerator + l, keyval_name); return accelerator; } void egg_keymap_resolve_virtual_modifiers (GdkKeymap *keymap, EggVirtualModifierType virtual_mods, GdkModifierType *concrete_mods) { GdkModifierType concrete; int i; const EggModmap *modmap; g_return_if_fail (GDK_IS_KEYMAP (keymap)); g_return_if_fail (concrete_mods != NULL); modmap = egg_keymap_get_modmap (keymap); /* Not so sure about this algorithm. */ concrete = 0; i = 0; while (i < EGG_MODMAP_ENTRY_LAST) { if (modmap->mapping[i] & virtual_mods) concrete |= (1 << i); ++i; } *concrete_mods = concrete; } void egg_keymap_virtualize_modifiers (GdkKeymap *keymap, GdkModifierType concrete_mods, EggVirtualModifierType *virtual_mods) { GdkModifierType virtual; int i; const EggModmap *modmap; g_return_if_fail (GDK_IS_KEYMAP (keymap)); g_return_if_fail (virtual_mods != NULL); modmap = egg_keymap_get_modmap (keymap); /* Not so sure about this algorithm. */ virtual = 0; i = 0; while (i < EGG_MODMAP_ENTRY_LAST) { if ((1 << i) & concrete_mods) { EggVirtualModifierType cleaned; cleaned = modmap->mapping[i] & ~(EGG_VIRTUAL_MOD2_MASK | EGG_VIRTUAL_MOD3_MASK | EGG_VIRTUAL_MOD4_MASK | EGG_VIRTUAL_MOD5_MASK); if (cleaned != 0) { virtual |= cleaned; } else { /* Rather than dropping mod2->mod5 if not bound, * go ahead and use the concrete names */ virtual |= modmap->mapping[i]; } } ++i; } *virtual_mods = virtual; } static void reload_modmap (GdkKeymap *keymap, EggModmap *modmap) { XModifierKeymap *xmodmap; int map_size; int i; /* FIXME multihead */ xmodmap = XGetModifierMapping (gdk_x11_get_default_xdisplay ()); memset (modmap->mapping, 0, sizeof (modmap->mapping)); /* there are 8 modifiers, and the first 3 are shift, shift lock, * and control */ map_size = 8 * xmodmap->max_keypermod; i = 3 * xmodmap->max_keypermod; while (i < map_size) { /* get the key code at this point in the map, * see if its keysym is one we're interested in */ int keycode = xmodmap->modifiermap[i]; GdkKeymapKey *keys; guint *keyvals; int n_entries; int j; EggVirtualModifierType mask; keys = NULL; keyvals = NULL; n_entries = 0; gdk_keymap_get_entries_for_keycode (keymap, keycode, &keys, &keyvals, &n_entries); mask = 0; j = 0; while (j < n_entries) { if (keyvals[j] == GDK_Num_Lock) mask |= EGG_VIRTUAL_NUM_LOCK_MASK; else if (keyvals[j] == GDK_Scroll_Lock) mask |= EGG_VIRTUAL_SCROLL_LOCK_MASK; else if (keyvals[j] == GDK_Meta_L || keyvals[j] == GDK_Meta_R) mask |= EGG_VIRTUAL_META_MASK; else if (keyvals[j] == GDK_Hyper_L || keyvals[j] == GDK_Hyper_R) mask |= EGG_VIRTUAL_HYPER_MASK; else if (keyvals[j] == GDK_Super_L || keyvals[j] == GDK_Super_R) mask |= EGG_VIRTUAL_SUPER_MASK; else if (keyvals[j] == GDK_Mode_switch) mask |= EGG_VIRTUAL_MODE_SWITCH_MASK; ++j; } /* Mod1Mask is 1 << 3 for example, i.e. the * fourth modifier, i / keyspermod is the modifier * index */ modmap->mapping[i/xmodmap->max_keypermod] |= mask; g_free (keyvals); g_free (keys); ++i; } /* Add in the not-really-virtual fixed entries */ modmap->mapping[EGG_MODMAP_ENTRY_SHIFT] |= EGG_VIRTUAL_SHIFT_MASK; modmap->mapping[EGG_MODMAP_ENTRY_CONTROL] |= EGG_VIRTUAL_CONTROL_MASK; modmap->mapping[EGG_MODMAP_ENTRY_LOCK] |= EGG_VIRTUAL_LOCK_MASK; modmap->mapping[EGG_MODMAP_ENTRY_MOD1] |= EGG_VIRTUAL_ALT_MASK; modmap->mapping[EGG_MODMAP_ENTRY_MOD2] |= EGG_VIRTUAL_MOD2_MASK; modmap->mapping[EGG_MODMAP_ENTRY_MOD3] |= EGG_VIRTUAL_MOD3_MASK; modmap->mapping[EGG_MODMAP_ENTRY_MOD4] |= EGG_VIRTUAL_MOD4_MASK; modmap->mapping[EGG_MODMAP_ENTRY_MOD5] |= EGG_VIRTUAL_MOD5_MASK; XFreeModifiermap (xmodmap); } const EggModmap* egg_keymap_get_modmap (GdkKeymap *keymap) { EggModmap *modmap; /* This is all a hack, much simpler when we can just * modify GDK directly. */ modmap = g_object_get_data (G_OBJECT (keymap), "egg-modmap"); if (modmap == NULL) { modmap = g_new0 (EggModmap, 1); /* FIXME modify keymap change events with an event filter * and force a reload if we get one */ reload_modmap (keymap, modmap); g_object_set_data_full (G_OBJECT (keymap), "egg-modmap", modmap, g_free); } g_assert (modmap != NULL); return modmap; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_eggaccelerators.h000066400000000000000000000057661345713535600221560ustar00rootroot00000000000000/* eggaccelerators.h * Copyright (C) 2002 Red Hat, Inc. * Developed by Havoc Pennington * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifndef __EGG_ACCELERATORS_H__ #define __EGG_ACCELERATORS_H__ #include #include G_BEGIN_DECLS /* Where a value is also in GdkModifierType we coincide, * otherwise we don't overlap. */ typedef enum { EGG_VIRTUAL_SHIFT_MASK = 1 << 0, EGG_VIRTUAL_LOCK_MASK = 1 << 1, EGG_VIRTUAL_CONTROL_MASK = 1 << 2, EGG_VIRTUAL_ALT_MASK = 1 << 3, /* fixed as Mod1 */ EGG_VIRTUAL_MOD2_MASK = 1 << 4, EGG_VIRTUAL_MOD3_MASK = 1 << 5, EGG_VIRTUAL_MOD4_MASK = 1 << 6, EGG_VIRTUAL_MOD5_MASK = 1 << 7, #if 0 GDK_BUTTON1_MASK = 1 << 8, GDK_BUTTON2_MASK = 1 << 9, GDK_BUTTON3_MASK = 1 << 10, GDK_BUTTON4_MASK = 1 << 11, GDK_BUTTON5_MASK = 1 << 12, /* 13, 14 are used by Xkb for the keyboard group */ #endif EGG_VIRTUAL_META_MASK = 1 << 24, EGG_VIRTUAL_SUPER_MASK = 1 << 25, EGG_VIRTUAL_HYPER_MASK = 1 << 26, EGG_VIRTUAL_MODE_SWITCH_MASK = 1 << 27, EGG_VIRTUAL_NUM_LOCK_MASK = 1 << 28, EGG_VIRTUAL_SCROLL_LOCK_MASK = 1 << 29, /* Also in GdkModifierType */ EGG_VIRTUAL_RELEASE_MASK = 1 << 30, /* 28-31 24-27 20-23 16-19 12-15 8-11 4-7 0-3 * 7 f 0 0 0 0 f f */ EGG_VIRTUAL_MODIFIER_MASK = 0x7f0000ff } EggVirtualModifierType; gboolean egg_accelerator_parse_virtual (const gchar *accelerator, guint *accelerator_key, EggVirtualModifierType *accelerator_mods); void egg_keymap_resolve_virtual_modifiers (GdkKeymap *keymap, EggVirtualModifierType virtual_mods, GdkModifierType *concrete_mods); void egg_keymap_virtualize_modifiers (GdkKeymap *keymap, GdkModifierType concrete_mods, EggVirtualModifierType *virtual_mods); gchar* egg_virtual_accelerator_name (guint accelerator_key, EggVirtualModifierType accelerator_mods); G_END_DECLS #endif /* __EGG_ACCELERATORS_H__ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_gui.c000066400000000000000000000030571345713535600175720ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include "ol_gui.h" #include "ol_intl.h" #include "ol_debug.h" const char *BUILDER_FILE = GUIDIR "/dialogs.glade"; static GtkBuilder *builder = NULL; static void internal_init (); static void internal_init () { if (builder == NULL) { builder = gtk_builder_new (); ol_assert (builder != NULL); gtk_builder_set_translation_domain (builder, PACKAGE); gtk_builder_add_from_file (builder, BUILDER_FILE, NULL); gtk_builder_connect_signals (builder, NULL); } } GtkWidget* ol_gui_get_widget (const char *name) { ol_assert_ret (name != NULL, NULL); internal_init (); ol_assert_ret (builder != NULL, NULL); GObject *obj = gtk_builder_get_object (builder, name); if (obj != NULL && GTK_IS_WIDGET (obj)) return GTK_WIDGET (obj); else return NULL; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_gui.h000066400000000000000000000020541345713535600175730ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_GUI_H_ #define _OL_GUI_H_ #include /** * @brief Gets a widget in Glade file by name * * @param name name of the widget * * @return */ GtkWidget *ol_gui_get_widget (const char *name); #endif /* _OL_GUI_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_gussian_blur.c000066400000000000000000000133061345713535600215010ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include #include #include #include "ol_gussian_blur.h" struct _pixel { guint64 alpha; guint64 red; guint64 green; guint64 blue; }; static int *_calc_kernel (double sigma, int *size); static void _apply_kernel (cairo_surface_t *surface, const int *kernel, int kernel_size); static inline int _pos_to_index (int x, int y, int width, int height); static inline struct _pixel _num_to_pixel_with_factor (guint32 value, int factor); static inline guint32 _pixel_to_num_with_divisor (struct _pixel *pixel, int divisor); static inline void _pixel_plus (struct _pixel *adder_sum, const struct _pixel *adder2); static inline int _pos_to_index (int x, int y, int width, int height) { if (x >= width || y >= height || x < 0 || y < 0) return -1; return y * width + x; } static inline struct _pixel _num_to_pixel_with_factor (guint32 value, int factor) { /* This only works for type CAIRO_FORMAT_ARGB32 */ struct _pixel pixel; pixel.alpha = ((value >> 24) & 0xff) * factor; pixel.red = ((value >> 16) & 0xff) * factor; pixel.green = ((value >> 8) & 0xff) * factor; pixel.blue = (value & 0xff) * factor; return pixel; } static inline guint32 _pixel_to_num_with_divisor (struct _pixel *pixel, int divisor) { guint32 alpha = pixel->alpha / divisor; if (alpha > 0xff) alpha = 0xff; guint32 red = pixel->red / divisor; if (red > 0xff) red = 0xff; guint32 green = pixel->green / divisor; if (green > 0xff) green = 0xff; guint32 blue = pixel->blue / divisor; if (blue > 0xff) blue = 0xff; return (alpha << 24) | (red << 16) | (green << 8) | blue; } static inline void _pixel_plus (struct _pixel *adder_sum, const struct _pixel *adder2) { adder_sum->alpha += adder2->alpha; adder_sum->red += adder2->red; adder_sum->green += adder2->green; adder_sum->blue += adder2->blue; } static int * _calc_kernel (double sigma, int *size) { int kernel_size = ceil (sigma * 6); if (kernel_size % 2 == 0) kernel_size++; int orig = kernel_size / 2; if (size) *size = kernel_size; double *kernel_double = g_new (double, kernel_size); double sum = 0.0; int *kernel = g_new (int, kernel_size); int i; double factor = 1.0 / sqrt (2.0 * M_PI * sigma * sigma); double denom = 1.0 / (2.0 * sigma * sigma); for (i = 0; i < kernel_size; i++) { kernel_double[i] = factor * exp (- (i - orig) * (i - orig) * denom); sum += kernel_double[i]; } /* convert to pixed point number */ for (i = 0; i < kernel_size; i++) { kernel[i] = kernel_double[i] / sum * (1 << (sizeof (int) / 2 * 8)); } g_free (kernel_double); return kernel; } static void _apply_kernel (cairo_surface_t *surface, const int *kernel, int kernel_size) { ol_assert (kernel_size > 0 && kernel_size % 2 == 1); ol_assert (kernel != NULL); static const int DIR[2][2] = {{0, 1}, {1, 0}}; guint32 *pixels = (guint32*) cairo_image_surface_get_data (surface); int width = cairo_image_surface_get_width (surface); int height = cairo_image_surface_get_height (surface); if (pixels == NULL || width <= 0 || height <= 0) { ol_errorf ("Invalid image surface"); return; } int kernel_orig = kernel_size / 2; int d, i, x, y; for (d = 0; d < 2; d++) { guint32 *old_pixels = g_new (guint32, width * height); memcpy (old_pixels, pixels, sizeof (guint32) * width * height); for (x = 0; x < width; x++) for (y = 0; y < height; y++) { struct _pixel final_value = {0}; int sum = 0; for (i = 0; i < kernel_size; i++) { int x1 = x + (i - kernel_orig) * DIR[d][0]; int y1 = y + (i - kernel_orig) * DIR[d][1]; int index1 = _pos_to_index (x1, y1, width, height); if (index1 > 0) { sum += kernel[i]; struct _pixel value = _num_to_pixel_with_factor (old_pixels[index1], kernel[i]); _pixel_plus (&final_value, &value); } } int index = _pos_to_index (x, y, width, height); pixels[index] = _pixel_to_num_with_divisor (&final_value, sum); } g_free (old_pixels); } } void ol_gussian_blur (cairo_surface_t *surface, double sigma) { ol_assert (surface != NULL); ol_assert (sigma > 0); cairo_format_t format = cairo_image_surface_get_format (surface); if (format != CAIRO_FORMAT_ARGB32) { ol_errorf ("The surface format is %d, only ARGB32 is supported\n", format); return; } int kernel_size; int *kernel = _calc_kernel (sigma, &kernel_size); _apply_kernel (surface, kernel, kernel_size); g_free (kernel); } osdlyrics-0.5.5~rc1+dfsg1/src/ol_gussian_blur.h000066400000000000000000000022111345713535600214770ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include /** * Apply Gussian blur to a cairo image surface * * @param surface A cairo image surface. Currently only an image surface in * CAIRO_FORMAT_ARGB32 format is supported. * @param sigma The variance of Gussian function. */ void ol_gussian_blur (cairo_surface_t *surface, double sigma); osdlyrics-0.5.5~rc1+dfsg1/src/ol_image_button.c000066400000000000000000000142601345713535600214610ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include "ol_image_button.h" #include "ol_debug.h" #define DEFAULT_SIZE 16 enum ImageIndex { STATE_NORMAL = 0, STATE_ACTIVE, STATE_PRESSED, STATE_DISABLED, SLICE_NUM, }; #define OL_IMAGE_BUTTON_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), \ ol_image_button_get_type (), \ OlImageButtonPriv)) typedef struct _OlImageButtonPriv OlImageButtonPriv; struct _OlImageButtonPriv { GdkPixbuf *image; }; G_DEFINE_TYPE (OlImageButton, ol_image_button, GTK_TYPE_BUTTON); static void ol_image_button_destroy (GtkObject *object); static void ol_image_button_size_request (GtkWidget *widget, GtkRequisition *requisition); static void ol_image_button_size_allocate (GtkWidget *widget, GtkAllocation *allocation); static gboolean ol_image_button_expose (GtkWidget *widget, GdkEventExpose *event); static void ol_image_button_size_request (GtkWidget *widget, GtkRequisition *requisition) { ol_log_func (); OlImageButton *btn = OL_IMAGE_BUTTON (widget); OlImageButtonPriv *priv = OL_IMAGE_BUTTON_GET_PRIVATE (btn); if (priv->image == NULL) { requisition->width = DEFAULT_SIZE; requisition->height = DEFAULT_SIZE; } else { requisition->width = gdk_pixbuf_get_width (priv->image) / SLICE_NUM; requisition->height = gdk_pixbuf_get_height (priv->image); } ol_debugf ("request: %d x %d\n", requisition->width, requisition->height); } static void ol_image_button_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { ol_log_func (); ol_debugf ("allocation: (%d, %d) %d x %d\n", allocation->x, allocation->y, allocation->width, allocation->height); GtkButton *button = GTK_BUTTON (widget); widget->allocation = *allocation; if (GTK_WIDGET_REALIZED (widget)) gdk_window_move_resize (button->event_window, widget->allocation.x, widget->allocation.y, widget->allocation.width, widget->allocation.height); } static gboolean ol_image_button_expose (GtkWidget *widget, GdkEventExpose *event) { OlImageButton *btn = OL_IMAGE_BUTTON (widget); OlImageButtonPriv *priv = OL_IMAGE_BUTTON_GET_PRIVATE (btn); if (priv->image == NULL) { GTK_WIDGET_CLASS (ol_image_button_parent_class)->expose_event (widget, event); } else { cairo_t *cr = gdk_cairo_create (event->window); int w, h, sw, sh, mx, my, x, y; w = event->area.width; h = event->area.height; mx = event->area.x + event->area.width / 2; my = event->area.y + event->area.height / 2; sw = gdk_pixbuf_get_width (priv->image) / SLICE_NUM; sh = gdk_pixbuf_get_height (priv->image); x = mx - sw / 2; y = my - sh / 2; /* ol_debugf ("cut: (%d, %d) %d x %d\n", */ /* event->area.x, event->area.y, */ /* w, h); */ cairo_rectangle (cr, event->area.x, event->area.y, w, h); cairo_clip (cr); /* ol_debugf ("cut: (%d, %d) %d x %d\n", */ /* x, y, */ /* sw, sh); */ cairo_rectangle (cr, x, y, sw, sh); cairo_clip (cr); int img_index = STATE_NORMAL; GtkStateType state = GTK_WIDGET_STATE (widget); if (state == GTK_STATE_ACTIVE) img_index = STATE_PRESSED; else if (state == GTK_STATE_PRELIGHT || state == GTK_STATE_SELECTED) img_index = STATE_ACTIVE; else if (state == GTK_STATE_INSENSITIVE) img_index = STATE_DISABLED; gdk_cairo_set_source_pixbuf (cr, priv->image, x - img_index * sw, y); cairo_paint (cr); cairo_destroy (cr); } return FALSE; } static void ol_image_button_class_init (OlImageButtonClass *klass) { g_type_class_add_private (klass, sizeof (OlImageButtonPriv)); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkObjectClass *object_class = GTK_OBJECT_CLASS (klass); object_class->destroy = ol_image_button_destroy; widget_class->size_request = ol_image_button_size_request; widget_class->size_allocate = ol_image_button_size_allocate; widget_class->expose_event = ol_image_button_expose; } static void ol_image_button_init (OlImageButton *btn) { OlImageButtonPriv *priv = OL_IMAGE_BUTTON_GET_PRIVATE (btn); priv->image = NULL; } static void ol_image_button_destroy (GtkObject *object) { OlImageButton *btn = OL_IMAGE_BUTTON (object); OlImageButtonPriv *priv = OL_IMAGE_BUTTON_GET_PRIVATE (btn); if (priv->image != NULL) { g_object_unref (priv->image); priv->image = NULL; } GTK_OBJECT_CLASS (ol_image_button_parent_class)->destroy (object); } GtkWidget * ol_image_button_new (void) { OlImageButton *button; button = g_object_new (ol_image_button_get_type (), NULL); return GTK_WIDGET (button); } void ol_image_button_set_pixbuf (OlImageButton *btn, GdkPixbuf *image) { ol_log_func (); ol_assert (OL_IS_IMAGE_BUTTON (btn)); ol_assert (image == NULL || GDK_IS_PIXBUF (image)); OlImageButtonPriv *priv = OL_IMAGE_BUTTON_GET_PRIVATE (btn); if (priv->image != NULL) { g_object_unref (priv->image); } priv->image = image; gtk_widget_queue_resize (GTK_WIDGET (btn)); gtk_widget_queue_draw (GTK_WIDGET (btn)); } osdlyrics-0.5.5~rc1+dfsg1/src/ol_image_button.h000066400000000000000000000040741345713535600214700ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_IMAGE_BUTTON_H_ #define _OL_IMAGE_BUTTON_H_ #include #define OL_IMAGE_BUTTON(obj) GTK_CHECK_CAST (obj, ol_image_button_get_type (), OlImageButton) #define OL_IMAGE_BUTTON_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, ol_image_button_get_type (), OlImageButtonClass) #define OL_IS_IMAGE_BUTTON(obj) GTK_CHECK_TYPE (obj, ol_image_button_get_type ()) #define OL_IMAGE_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ol_image_button_get_type (), OlImageButtonClass)) typedef struct _OlImageButton OlImageButton; typedef struct _OlImageButtonClass OlImageButtonClass; struct _OlImageButton { GtkButton button; }; struct _OlImageButtonClass { GtkButtonClass button_class; }; GtkType ol_image_button_get_type (void); /** * @brief Create a new image button * * * @return A new instance of image button */ GtkWidget *ol_image_button_new (void); /** * @brief Sets the image of the button * * The image should contains 4 frames: normal, hover, pressed, disabled, from * left to right. The width of each frame must be equal. * * @param btn * @param image The image of the button */ void ol_image_button_set_pixbuf (OlImageButton *btn, GdkPixbuf *image); #endif /* _OL_IMAGE_BUTTON_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_intl.h000066400000000000000000000020101345713535600177450ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_INTL_H_ #define _OL_INTL_H_ /* Internationalization. */ #include "config.h" #include "gettext.h" #define _(String) dgettext(PACKAGE,String) #define N_(String) (String) #endif /* _OL_INTL_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_keybinder.c000066400000000000000000000212551345713535600207620ustar00rootroot00000000000000// // This file comes from the Tomboy project. // http://www.gnome.org/projects/tomboy/ // /* * Copyright (C) 2004-2007 Alex Graveley * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include "ol_eggaccelerators.h" #include "ol_keybinder.h" /* Uncomment the next line to print a debug trace. */ /* #define DEBUG */ #ifdef DEBUG # define TRACE(x) x #else # define TRACE(x) do {} while (FALSE); #endif typedef struct _Binding { OlBindkeyHandler handler; gpointer user_data; char *keystring; uint keycode; uint modifiers; } Binding; static GSList *bindings = NULL; static guint32 last_event_time = 0; static gboolean processing_event = FALSE; static guint num_lock_mask, caps_lock_mask, scroll_lock_mask; static void lookup_ignorable_modifiers (GdkKeymap *keymap) { egg_keymap_resolve_virtual_modifiers (keymap, EGG_VIRTUAL_LOCK_MASK, &caps_lock_mask); egg_keymap_resolve_virtual_modifiers (keymap, EGG_VIRTUAL_NUM_LOCK_MASK, &num_lock_mask); egg_keymap_resolve_virtual_modifiers (keymap, EGG_VIRTUAL_SCROLL_LOCK_MASK, &scroll_lock_mask); } static void grab_ungrab_with_ignorable_modifiers (GdkWindow *rootwin, Binding *binding, gboolean grab) { guint mod_masks [] = { 0, /* modifier only */ num_lock_mask, caps_lock_mask, scroll_lock_mask, num_lock_mask | caps_lock_mask, num_lock_mask | scroll_lock_mask, caps_lock_mask | scroll_lock_mask, num_lock_mask | caps_lock_mask | scroll_lock_mask, }; int i; for (i = 0; i < G_N_ELEMENTS (mod_masks); i++) { if (grab) { XGrabKey (GDK_WINDOW_XDISPLAY (rootwin), binding->keycode, binding->modifiers | mod_masks [i], GDK_WINDOW_XWINDOW (rootwin), False, GrabModeAsync, GrabModeAsync); } else { XUngrabKey (GDK_WINDOW_XDISPLAY (rootwin), binding->keycode, binding->modifiers | mod_masks [i], GDK_WINDOW_XWINDOW (rootwin)); } } } static gboolean do_grab_key (Binding *binding) { GdkKeymap *keymap = gdk_keymap_get_default (); GdkWindow *rootwin = gdk_get_default_root_window (); EggVirtualModifierType virtual_mods = 0; guint keysym = 0; if (keymap == NULL || rootwin == NULL) return FALSE; if (!egg_accelerator_parse_virtual (binding->keystring, &keysym, &virtual_mods)) return FALSE; TRACE (g_print ("Got accel %d, %d\n", keysym, virtual_mods)); binding->keycode = XKeysymToKeycode (GDK_WINDOW_XDISPLAY (rootwin), keysym); if (binding->keycode == 0) return FALSE; TRACE (g_print ("Got keycode %d\n", binding->keycode)); egg_keymap_resolve_virtual_modifiers (keymap, virtual_mods, &binding->modifiers); TRACE (g_print ("Got modmask %d\n", binding->modifiers)); gdk_error_trap_push (); grab_ungrab_with_ignorable_modifiers (rootwin, binding, TRUE /* grab */); gdk_flush (); if (gdk_error_trap_pop ()) { g_warning ("Binding '%s' failed!\n", binding->keystring); return FALSE; } return TRUE; } static gboolean do_ungrab_key (Binding *binding) { GdkWindow *rootwin = gdk_get_default_root_window (); TRACE (g_print ("Removing grab for '%s'\n", binding->keystring)); grab_ungrab_with_ignorable_modifiers (rootwin, binding, FALSE /* ungrab */); return TRUE; } static GdkFilterReturn filter_func (GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data) { GdkFilterReturn return_val = GDK_FILTER_CONTINUE; XEvent *xevent = (XEvent *) gdk_xevent; guint event_mods; GSList *iter; TRACE (g_print ("Got Event! %d, %d\n", xevent->type, event->type)); switch (xevent->type) { case KeyPress: TRACE (g_print ("Got KeyPress! keycode: %d, modifiers: %d\n", xevent->xkey.keycode, xevent->xkey.state)); /* * Set the last event time for use when showing * windows to avoid anti-focus-stealing code. */ processing_event = TRUE; last_event_time = xevent->xkey.time; event_mods = xevent->xkey.state & ~(num_lock_mask | caps_lock_mask | scroll_lock_mask); for (iter = bindings; iter != NULL; iter = iter->next) { Binding *binding = (Binding *) iter->data; if (binding->keycode == xevent->xkey.keycode && binding->modifiers == event_mods) { TRACE (g_print ("Calling handler for '%s'...\n", binding->keystring)); (binding->handler) (binding->keystring, binding->user_data); } } processing_event = FALSE; break; case KeyRelease: TRACE (g_print ("Got KeyRelease! \n")); break; } return return_val; } static void keymap_changed (GdkKeymap *map) { GdkKeymap *keymap = gdk_keymap_get_default (); GSList *iter; TRACE (g_print ("Keymap changed! Regrabbing keys...")); for (iter = bindings; iter != NULL; iter = iter->next) { Binding *binding = (Binding *) iter->data; do_ungrab_key (binding); } lookup_ignorable_modifiers (keymap); for (iter = bindings; iter != NULL; iter = iter->next) { Binding *binding = (Binding *) iter->data; do_grab_key (binding); } } void ol_keybinder_init (void) { GdkKeymap *keymap = gdk_keymap_get_default (); GdkWindow *rootwin = gdk_get_default_root_window (); lookup_ignorable_modifiers (keymap); gdk_window_add_filter (rootwin, filter_func, NULL); g_signal_connect (keymap, "keys_changed", G_CALLBACK (keymap_changed), NULL); } void ol_keybinder_bind (const char *keystring, OlBindkeyHandler handler, gpointer user_data) { Binding *binding; gboolean success; binding = g_new0 (Binding, 1); binding->keystring = g_strdup (keystring); binding->handler = handler; binding->user_data = user_data; /* Sets the binding's keycode and modifiers */ success = do_grab_key (binding); if (success) { bindings = g_slist_prepend (bindings, binding); } else { g_free (binding->keystring); g_free (binding); } } void ol_keybinder_unbind (const char *keystring, OlBindkeyHandler handler) { GSList *iter; for (iter = bindings; iter != NULL; iter = iter->next) { Binding *binding = (Binding *) iter->data; if (strcmp (keystring, binding->keystring) != 0 || handler != binding->handler) continue; do_ungrab_key (binding); bindings = g_slist_remove (bindings, binding); g_free (binding->keystring); g_free (binding); break; } } /* * From eggcellrenderkeys.c. */ gboolean ol_keybinder_is_modifier (guint keycode) { gint i; gint map_size; XModifierKeymap *mod_keymap; gboolean retval = FALSE; mod_keymap = XGetModifierMapping (gdk_display); map_size = 8 * mod_keymap->max_keypermod; i = 0; while (i < map_size) { if (keycode == mod_keymap->modifiermap[i]) { retval = TRUE; break; } ++i; } XFreeModifiermap (mod_keymap); return retval; } guint32 ol_keybinder_get_current_event_time (void) { if (processing_event) return last_event_time; else return GDK_CURRENT_TIME; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_keybinder.h000066400000000000000000000027631345713535600207720ustar00rootroot00000000000000// // This file comes from the Tomboy project. // http://www.gnome.org/projects/tomboy/ // /* * Copyright (C) 2004-2007 Alex Graveley * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifndef __DO_KEY_BINDER_H__ #define __DO_KEY_BINDER_H__ #include G_BEGIN_DECLS typedef void (* OlBindkeyHandler) (char *keystring, gpointer user_data); void ol_keybinder_init (void); void ol_keybinder_bind (const char *keystring, OlBindkeyHandler handler, gpointer user_data); void ol_keybinder_unbind (const char *keystring, OlBindkeyHandler handler); gboolean ol_keybinder_is_modifier (guint keycode); guint32 ol_keybinder_get_current_event_time (void); G_END_DECLS #endif /* __DO_KEY_BINDER_H__ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_keybinding_settings.h000066400000000000000000000016461345713535600230600ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_KEYBINDING_SETTING_H_ #define _OL_KEYBINDING_SETTING_H_ #endif /* _OL_KEYBINDING_SETTING_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_keybindings.c000066400000000000000000000042541345713535600213140ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include "ol_keybindings.h" #include "ol_keybinding_settings.h" #include "ol_keybinder.h" #include "ol_commands.h" static GtkAccelGroup *accel = NULL; static void ol_hide_accel (gpointer userdata); static void ol_hide_accel (gpointer userdata) { } void ol_keybinding_init () { ol_keybinder_init (); GtkAccelGroup *accel = ol_keybinding_get_accel_group (); GClosure *hide_closure = g_cclosure_new ((GCallback)ol_hide_accel, NULL, NULL); gtk_accel_map_add_entry ("/Hide", gdk_keyval_from_name ("h"), GDK_CONTROL_MASK | GDK_SHIFT_MASK); gtk_accel_group_connect_by_path (accel, "/Hide", hide_closure); gtk_accel_map_add_entry ("/Lock", gdk_keyval_from_name ("l"), GDK_CONTROL_MASK | GDK_SHIFT_MASK); gtk_accel_group_connect_by_path (accel, "/Hide", hide_closure); ol_keybinder_bind ("H", ol_show_hide, NULL); ol_keybinder_bind ("L", ol_osd_lock_unlock, NULL); } GtkAccelGroup* ol_keybinding_get_accel_group () { if (accel == NULL) { accel = gtk_accel_group_new (); } return accel; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_keybindings.h000066400000000000000000000022211345713535600213110ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ /** * @file ol_keybinding.h * @author Tiger Soldier * @date Sun Aug 16 15:58:56 2009 * * @brief Global Keybinding settings */ #ifndef _OL_KEYBINDING_H_ #define _OL_KEYBINDING_H_ #include void ol_keybinding_init (); GtkAccelGroup* ol_keybinding_get_accel_group (); #endif /* _OL_KEYBINDING_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_lrc.c000066400000000000000000000325361345713535600175720ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include #include #include #include "ol_lrc.h" #include "ol_debug.h" static const int DEFAULT_LAST_DURATION = 5000; struct _OlLrcIter { guint id; OlLrc *lrc; }; struct OlLrcItem { int timestamp; char *text; }; typedef struct _OlLrcPrivate OlLrcPrivate; struct _OlLrcPrivate { char *uri; int offset; GHashTable *metadata; GPtrArray *items; guint64 duration; OlLyrics *lyric_proxy; guint save_offset_timer; }; struct SaveOffsetData { OlLrc *lrc; int offset; }; #define OL_LRC_GET_PRIVATE(object) \ (G_TYPE_INSTANCE_GET_PRIVATE ((object), OL_TYPE_LRC, OlLrcPrivate)) /* -------------- OlLrc private methods ------------------*/ static void ol_lrc_finalize (GObject *object); /* -------------- OlLrcItem private methods --------------*/ static struct OlLrcItem *ol_lrc_item_new (gint64 timestamp, const gchar *text); static void ol_lrc_item_free (struct OlLrcItem *item); /* -------------- OlLrcIter private methods --------------*/ static OlLrcIter *ol_lrc_iter_new (OlLrc *lrc, guint index); static struct OlLrcItem *ol_lrc_iter_get_item (OlLrcIter *iter); /* ---------------Save offset functions ----------------- */ static gboolean _save_offset_timeout (OlLrc *lrc); G_DEFINE_TYPE (OlLrc, ol_lrc, G_TYPE_OBJECT); static void ol_lrc_class_init (OlLrcClass *klass) { GObjectClass *gklass = G_OBJECT_CLASS (klass); gklass->finalize = ol_lrc_finalize; g_type_class_add_private (klass, sizeof (OlLrcPrivate)); } static void ol_lrc_init (OlLrc *lrc) { OlLrcPrivate *priv = OL_LRC_GET_PRIVATE (lrc); priv->items = g_ptr_array_new_with_free_func ((GDestroyNotify) ol_lrc_item_free); /* ensure there is at lease one line */ g_ptr_array_add (priv->items, ol_lrc_item_new (0, "")); priv->metadata = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) g_free); priv->save_offset_timer = 0; priv->offset = 0; }; OlLrc * ol_lrc_new (OlLyrics *lyric_proxy, const gchar *uri) { OlLrc *lrc = OL_LRC (g_object_new (OL_TYPE_LRC, NULL)); OlLrcPrivate *priv = OL_LRC_GET_PRIVATE (lrc); priv->uri = g_strdup (uri); if (lyric_proxy) priv->lyric_proxy = g_object_ref (lyric_proxy); else priv->lyric_proxy = NULL; return lrc; } static void ol_lrc_finalize (GObject *object) { OlLrcPrivate *priv = OL_LRC_GET_PRIVATE (object); if (priv->save_offset_timer > 0) { g_source_remove (priv->save_offset_timer); priv->save_offset_timer = 0; ol_lyrics_set_offset (priv->lyric_proxy, priv->uri, priv->offset); } g_ptr_array_free (priv->items, TRUE); g_hash_table_destroy (priv->metadata); g_free (priv->uri); if (priv->lyric_proxy) { g_object_unref (priv->lyric_proxy); priv->lyric_proxy = NULL; } priv->items = NULL; priv->metadata = NULL; priv->uri = NULL; G_OBJECT_CLASS (ol_lrc_parent_class)->finalize (object); } void ol_lrc_set_attributes_from_variant (OlLrc *lrc, GVariant *attributes) { ol_assert (OL_IS_LRC (lrc)); ol_assert (attributes != NULL); OlLrcPrivate *priv = OL_LRC_GET_PRIVATE (lrc); g_hash_table_remove_all (priv->metadata); GVariantIter *iter = NULL; g_variant_get (attributes, "a{ss}", &iter); gchar *key, *value; while (g_variant_iter_loop (iter, "{ss}", &key, &value)) { g_hash_table_insert (priv->metadata, g_strdup (key), g_strdup (value)); ol_debugf ("LRC attribute: %s -> %s\n", key, value); } g_variant_iter_free (iter); const char *offset = NULL; if ((offset = g_hash_table_lookup (priv->metadata, "offset")) != NULL) priv->offset = atoi (offset); else priv->offset = 0; } void ol_lrc_set_content_from_variant (OlLrc *lrc, GVariant *content) { ol_assert (OL_IS_LRC (lrc)); ol_assert (content != NULL); OlLrcPrivate *priv = OL_LRC_GET_PRIVATE (lrc); g_ptr_array_remove_range (priv->items, 0, priv->items->len); GVariantIter *iter = NULL; g_variant_get (content, "aa{sv}", &iter); GVariantIter *dict_iter = NULL; while (g_variant_iter_loop (iter, "a{sv}", &dict_iter)) { gchar *key = NULL; GVariant *value = NULL; if (g_variant_iter_n_children (dict_iter) < 3) { ol_errorf ("The attributes of a lyric line is not enough, expect 3 attributes " "(id, timestame, text) but there are only %d attributes.\n", g_variant_iter_n_children (dict_iter)); continue; } guint id = 0; gint64 timestamp = 0; const gchar *text = NULL; gboolean has_id = FALSE, has_timestamp = FALSE; while (g_variant_iter_loop (dict_iter, "{sv}", &key, &value)) { if (strcmp (key, "id") == 0) { id = g_variant_get_uint32 (value); has_id = TRUE; } else if (strcmp (key, "timestamp") == 0) { timestamp = g_variant_get_int64 (value); has_timestamp = TRUE; } else if (strcmp (key, "text") == 0) { text = g_variant_get_string (value, NULL); } else { ol_errorf ("Unknown line attribute: %s\n", key); } } /* for dict_iter */ if (has_id && has_timestamp && text != NULL) { g_ptr_array_add (priv->items, ol_lrc_item_new (timestamp, text)); ol_infof ("new lyric item: %d, %d: %s\n", (int)id, (int)timestamp, text); } else { if (!has_id) ol_errorf ("missing id in lyric line\n"); if (!has_timestamp) ol_errorf ("missing timestamp in lyric line\n"); if (!text) ol_errorf ("missing text in lyric line\n"); } /* if */ } /* for iter */ g_variant_iter_free (iter); /* Ensure there are at least one item */ if (priv->items->len == 0) g_ptr_array_add (priv->items, ol_lrc_item_new (0, "")); } const char * ol_lrc_get_attribute (OlLrc *lrc, const char *key) { ol_assert_ret (OL_IS_LRC (lrc), NULL); ol_assert_ret (key != NULL, NULL); OlLrcPrivate *priv = OL_LRC_GET_PRIVATE (lrc); return g_hash_table_lookup (priv->metadata, key); } guint ol_lrc_get_item_count (OlLrc *lrc) { ol_assert_ret (OL_IS_LRC (lrc), 0); OlLrcPrivate *priv = OL_LRC_GET_PRIVATE (lrc); return priv->items->len; } static gboolean _save_offset_timeout (OlLrc *lrc) { ol_assert_ret (OL_IS_LRC (lrc), FALSE); OlLrcPrivate *priv = OL_LRC_GET_PRIVATE (lrc); ol_lyrics_set_offset (priv->lyric_proxy, priv->uri, priv->offset); priv->save_offset_timer = 0; return FALSE; } void ol_lrc_set_offset (OlLrc *lrc, int offset) { ol_assert (OL_IS_LRC (lrc)); OlLrcPrivate *priv = OL_LRC_GET_PRIVATE (lrc); priv->offset = offset; g_hash_table_replace (priv->metadata, g_strdup ("offset"), g_strdup_printf ("%d", priv->offset)); if (priv->lyric_proxy) { if (priv->save_offset_timer > 0) { g_source_remove (priv->save_offset_timer); priv->save_offset_timer = 0; } priv->save_offset_timer = g_timeout_add (1000, (GSourceFunc) _save_offset_timeout, lrc); } } int ol_lrc_get_offset (OlLrc *lrc) { ol_assert_ret (OL_IS_LRC (lrc), 0); return OL_LRC_GET_PRIVATE (lrc)->offset; } OlLrcIter * ol_lrc_iter_from_id (OlLrc *lrc, guint id) { ol_assert_ret (OL_IS_LRC (lrc), NULL); if (id < ol_lrc_get_item_count (lrc)) return ol_lrc_iter_new (lrc, id); else return NULL; } OlLrcIter * ol_lrc_iter_from_timestamp (OlLrc *lrc, gint64 timestamp) { ol_assert_ret (OL_IS_LRC (lrc), NULL); int low = 0; int high = ol_lrc_get_item_count (lrc) - 1; OlLrcIter *iter = ol_lrc_iter_from_id (lrc, 0); /* Binary search */ while (low < high) { int mid = (low + high + 1) / 2; ol_lrc_iter_move_to (iter, mid); if (ol_lrc_iter_get_timestamp (iter) <= timestamp) low = mid; else high = mid - 1; } if (low == high) /* found */ { ol_lrc_iter_move_to (iter, low); } else { ol_errorf ("low(%d) != high(%d), this should not happen\n", low, high); } return iter; } const char * ol_lrc_get_uri (OlLrc *lrc) { ol_assert_ret (OL_IS_LRC (lrc), NULL); return OL_LRC_GET_PRIVATE (lrc)->uri; } void ol_lrc_set_duration (OlLrc *lrc, guint64 duration) { ol_assert (OL_IS_LRC (lrc)); OlLrcPrivate *priv = OL_LRC_GET_PRIVATE (lrc); priv->duration = duration; } guint64 ol_lrc_get_duration (OlLrc *lrc) { ol_assert_ret (OL_IS_LRC (lrc), 0); OlLrcPrivate *priv = OL_LRC_GET_PRIVATE (lrc); return priv->duration; } static struct OlLrcItem * ol_lrc_item_new (gint64 timestamp, const gchar *text) { if (text == NULL) text = ""; struct OlLrcItem *item = g_new (struct OlLrcItem, 1); item->timestamp = timestamp; item->text = g_strdup (text); return item; } static void ol_lrc_item_free (struct OlLrcItem *item) { if (item == NULL) return; g_free (item->text); g_free (item); } static OlLrcIter * ol_lrc_iter_new (OlLrc *lrc, guint index) { ol_assert_ret (OL_IS_LRC (lrc), NULL); ol_assert_ret (index < ol_lrc_get_item_count (lrc), NULL); OlLrcIter *iter = g_new (OlLrcIter, 1); iter->lrc = g_object_ref (lrc); iter->id = index; return iter; } void ol_lrc_iter_free (OlLrcIter *iter) { if (iter == NULL) return; g_object_unref (iter->lrc); g_free (iter); } gboolean ol_lrc_iter_prev (OlLrcIter *iter) { ol_assert_ret (iter != NULL, FALSE); if (iter->id == 0) return FALSE; iter->id--; return TRUE; } gboolean ol_lrc_iter_next (OlLrcIter *iter) { ol_assert_ret (iter != NULL, FALSE); iter->id++; if (iter->id >= ol_lrc_get_item_count (iter->lrc)) return FALSE; return TRUE; } gboolean ol_lrc_iter_move_to (OlLrcIter *iter, guint id) { ol_assert_ret (iter != NULL, FALSE); if (id >= ol_lrc_get_item_count (iter->lrc)) return FALSE; iter->id = id; return TRUE; } gboolean ol_lrc_iter_loop (OlLrcIter *iter, guint *id, gint64 *timestamp, const char **text) { ol_assert_ret (iter != NULL, FALSE); if (!ol_lrc_iter_is_valid (iter)) return FALSE; if (id) *id = ol_lrc_iter_get_id (iter); if (timestamp) *timestamp = ol_lrc_iter_get_timestamp (iter); if (text) *text = ol_lrc_iter_get_text (iter); ol_lrc_iter_next (iter); return TRUE; } guint ol_lrc_iter_get_id (OlLrcIter *iter) { ol_assert_ret (iter != NULL, 0); return iter->id; } static struct OlLrcItem * ol_lrc_iter_get_item (OlLrcIter *iter) { ol_assert_ret (iter != NULL, NULL); if (iter->id >= ol_lrc_get_item_count (iter->lrc)) { ol_errorf ("LRC Iter is out of range. Don't use the iter after resetting the content or reaching the end.\n"); return NULL; } OlLrcPrivate *priv = OL_LRC_GET_PRIVATE (iter->lrc); struct OlLrcItem *item = g_ptr_array_index (priv->items, iter->id); return item; } gint64 ol_lrc_iter_get_timestamp (OlLrcIter *iter) { struct OlLrcItem *item = ol_lrc_iter_get_item (iter); if (!item) return 0; return item->timestamp - ol_lrc_get_offset (iter->lrc); } const char * ol_lrc_iter_get_text(OlLrcIter *iter) { struct OlLrcItem *item = ol_lrc_iter_get_item (iter); if (!item) return NULL; return item->text; } gboolean ol_lrc_iter_is_valid (OlLrcIter *iter) { ol_assert_ret (iter != NULL, FALSE); return iter->id < ol_lrc_get_item_count (iter->lrc); } guint64 ol_lrc_iter_get_duration (OlLrcIter *iter) { struct OlLrcItem *curr = ol_lrc_iter_get_item (iter); if (!curr) return 0; OlLrcPrivate *priv = OL_LRC_GET_PRIVATE (iter->lrc); if (iter->id < ol_lrc_get_item_count (iter->lrc) - 1) { /* Not the last one */ struct OlLrcItem *next = g_ptr_array_index (priv->items, iter->id + 1); return next->timestamp - curr->timestamp; } else { gint64 duration = ol_lrc_get_duration (iter->lrc); gint64 timestamp = ol_lrc_iter_get_timestamp (iter); if (duration <= timestamp) return DEFAULT_LAST_DURATION; else return duration - timestamp; } } gdouble ol_lrc_iter_compute_percentage (OlLrcIter *iter, gint64 time_ms) { ol_assert_ret (ol_lrc_iter_is_valid (iter), 0.0); gint64 timestamp = ol_lrc_iter_get_timestamp (iter); if (time_ms <= timestamp) return 0.0; /* use int64 instead of uint64 to avoid negative sum problem */ gint64 duration = ol_lrc_iter_get_duration (iter); if (time_ms >= timestamp + duration) return 1.0; return (gdouble) (time_ms - timestamp) / (gdouble) duration; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_lrc.h000066400000000000000000000233221345713535600175700ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_LRC_H_ #define _OL_LRC_H_ #include #define OL_TYPE_LRC \ (ol_lrc_get_type ()) #define OL_LRC(obj) \ (G_TYPE_CHECK_INSTANCE_CAST (obj, OL_TYPE_LRC, OlLrc)) #define OL_LRC_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST (klass, OL_TYPE_LRC, OlLrcClass)) #define OL_IS_LRC(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE (obj, OL_TYPE_LRC)) #define OL_IS_LRC_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE (klass, OL_TYPE_LRC)) #define OL_LRC_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), OL_TYPE_LRC, OlLrcClass)) typedef struct _OlLrc OlLrc; struct _OlLrc { GObject parent; }; typedef struct _OlLrcClass OlLrcClass; struct _OlLrcClass { GObjectClass parent_class; }; #include "ol_lyrics.h" GType ol_lrc_get_type (void); /** * @brief An iterator to iterate the content of the LRC file * * The iterator points to a line of the content. From the iterator you can get the * ID, timestamp, and lyric text of the line. It is guanranteed that you can visit * all the content in the lyrics in ascending order of timestamp by iterating it. */ typedef struct _OlLrcIter OlLrcIter; struct _OlLrcIter; /** * @brief Create a new OlLrc instance * * @return The OlLrc instance of the file, or NULL if filename doesn't exist. * You should free it with g_object_unref. */ OlLrc *ol_lrc_new (OlLyrics *lyric_proxy, const gchar *uri); /** * Sets the LRC attributes from GVariant * * This function is intented for D-Bus communication. The returned attributes of * GetLyrics or GetCurrentLyrics call should be able to pass as the attribute * parameter directly. * * The GVariant contains a dictionary with strings as keys and strings as values. * * @param lrc * @param attribute GVariant of type a{ss} */ void ol_lrc_set_attributes_from_variant (OlLrc *lrc, GVariant *attribute); /** * Sets the LRC content from GVariant * * This function is intented for D-Bus communication. The returned content of * GetLyrics or GetCurrentLyrics call should be able to pass as the content * parameter directly. * * The GVariant contains an array of a dictionary with strings as keys and * variants as values. * * It is guaranteed that there is at least one line in lrc after setting the * content * * @param lrc * @param content GVariant of type a{ss} */ void ol_lrc_set_content_from_variant (OlLrc *lrc, GVariant *content); /** * Gets the value of an attribute of an LRC file * * @param lrc * @param key * * @return NULL if the attribute doesn't exist. Otherwise return the value * of the key. */ const char *ol_lrc_get_attribute (OlLrc *lrc, const char *key); /** * @brief Get the number of lyric items * * @param lrc * * @return The number of lyric items */ guint ol_lrc_get_item_count (OlLrc *lrc); /** * Update the offset of the lrc file * * If the #lyrics_proxy is set in ol_lrc_new(), the ol_lyrics_set_offset() will be * invoked automatically. To avoid performance issue, the ol_lyrics_set_offset() will * not be called right after ol_lrc_set_offset() is called. The #OlLrc object will * sync the offset change 1 second after last ol_lrc_set_offset() call. * * @param lrc * @param offset The offset of which should be ajusted */ void ol_lrc_set_offset (OlLrc *lrc, int offset); /** * Get the offset of the LRC file * * @param lrc * * @return the current offset time */ int ol_lrc_get_offset (OlLrc *lrc); /** * Sets the duration of the track. * * The duration is used to calculate the duration and percentage of the last * line of lyrics. If the duration is less than the start time of the last line, * it is invalid and will not be used. * * @param lrc * @param duration */ void ol_lrc_set_duration (OlLrc *lrc, guint64 duration); /** * Gets the duration of the track. * * @param lrc * * @return The duration of the track. */ guint64 ol_lrc_get_duration (OlLrc *lrc); /** * @brief Get the URI of the LRC file * * @param lrc * * @return The URI or NULL. The lrc owns the the uri, so don't modify or free it. */ const char *ol_lrc_get_uri (OlLrc *lrc); /** * Frees an iterator. * * @param iter */ void ol_lrc_iter_free (OlLrcIter *iter); /** * @brief Gets an iterator by the ID of the line * * @param lrc * @param id * * @return The lyric iterator with the id, or NULL if not found. Should be freed * with ol_lrc_iter_free */ OlLrcIter *ol_lrc_iter_from_id (OlLrc *lrc, guint id); /** * @brief Gets the lyric iterator that fits the given timestamp * * The returned lyric's start time should be less or equal than the given time, * while it's end time should be larger than the given time. * * If the given timestamp is less than the first line, the first line will be * returned. If the given timestamp is greater than the duration of the track, * the last line will be returned. * * This function returns the upperbound, which means that if there are more than * one line starts with the given timestamp, this function return returns the last * on. * * @param lrc The LRC file * @param timestamp The timestamp * * @return The iterator of the timestamp. Should be freed with ol_lrc_iter_free */ OlLrcIter *ol_lrc_iter_from_timestamp (OlLrc *lrc, gint64 timestamp); /** * @brief Move to the previous line of lyrics * * @param iter * * @return FALSE if the given iter is the first one. TRUE if succeed. */ gboolean ol_lrc_iter_prev (OlLrcIter *iter); /** * @brief Move to the next line of lyrics * * @param iter The iterator * * @return FALSE if the given iter is the last one. TRUE if succeed. */ gboolean ol_lrc_iter_next (OlLrcIter *iter); /** * Move the iterator to the line of the given ID * * @param iter * @param id * * @return TRUE if success, or FALSE if the id is out of range. */ gboolean ol_lrc_iter_move_to (OlLrcIter *iter, guint id); /** * Get the information of the line represented by the iterator and move the * iterator to the next. * * This function is convenient to iterate the lyrics using a while loop: * * while (ol_lrc_iter_loop (iter, &id, ×tamp, &text)) * { * do_someting (); * } * * @param iter * @param id The return location of id of the current iterator. NULL is OK. * @param timestamp The return location of id of the current timestamp. NULL is OK. * @param text The return location of id of the current lyric text. NULL is OK. * Should NOT be modified or freed. * * @return FALSE if the iterator reaches the end of the lyrics. Otherwise TRUE will * be returned and the id, timestamp and text will be set. */ gboolean ol_lrc_iter_loop (OlLrcIter *iter, guint *id, gint64 *timestamp, const char **text); /** * Get the ID of the line. * * The ID is the index of the lyric line. * @param iter * * @return */ guint ol_lrc_iter_get_id (OlLrcIter *iter); /** * @brief Get the timestamp of the line, in milliseconds * * The time is calculated with offset of LRC * * @param iter The iterator * * @return The timestamp of the line represented by the iterator. */ gint64 ol_lrc_iter_get_timestamp (OlLrcIter *iter); /** * @brief Get the text of the line * * @param item * * @return The text of the lyric item. */ const char *ol_lrc_iter_get_text(OlLrcIter *iter); /** * @brief Get the id of the line * * The id differs with different lyric item, and is in ascending order by * timestamp. Ids start from 0. * * @param item * * @return The id of the item, or 0 if the item is invalid. */ guint ol_lrc_iter_get_id (OlLrcIter *iter); /** * Gets the duration of the line * * The duration of the line is the difference between the timestamp of this and the * next line. * * If the line is the last one, the duration is the duration of the track minus the * timestamp of the line. If the duration is less than the timestamp of the last * line, the duration of the last line will be 5 seconds. * * @param iter * * @return The duration of the line. Note that this may be 0. */ guint64 ol_lrc_iter_get_duration (OlLrcIter *iter); /** * Figure out how much of an single lyric text has been played when the position * of the track reaches given value. * * The returned value is guaranteed in the range of [0.0, 1.0]. * * @param iter * @param time_ms The position of the track, in milliseconds. * * @return */ gdouble ol_lrc_iter_compute_percentage (OlLrcIter *iter, gint64 time_ms); /** * Determine if a iterator is out of range. * * @param iter * * @return */ gboolean ol_lrc_iter_is_valid (OlLrcIter *iter); #endif /* _OL_LRC_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_lyric_candidate_list.c000066400000000000000000000116771345713535600231660ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2012 Tiger Soldier * * This file is part of OSD Lyrics. * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include "ol_lyric_candidate_list.h" #include "ol_intl.h" #include "ol_debug.h" enum { TITLE_COLUMN = 0, ARTIST_COLUMN, ALBUM_COLUMN, COMMENT_COLUMN, DATA_COLUMN, COLUMN_COUNT, }; void ol_lyric_candidate_list_init (GtkTreeView *list, GCallback select_changed_callback) { ol_assert (list != NULL); GtkTreeStore *store = gtk_tree_store_new (COLUMN_COUNT, /* Total number of columns */ G_TYPE_STRING, /* Track title */ G_TYPE_STRING, /* Artist */ G_TYPE_STRING, /* Album */ G_TYPE_STRING, /* Comment */ G_TYPE_OBJECT); GtkCellRenderer *renderer; GtkTreeViewColumn *column; renderer = gtk_cell_renderer_text_new (); column = gtk_tree_view_column_new_with_attributes (_("Title"), renderer, "text", TITLE_COLUMN, NULL); gtk_tree_view_append_column (list, column); column = gtk_tree_view_column_new_with_attributes (_("Artist"), renderer, "text", ARTIST_COLUMN, NULL); gtk_tree_view_append_column (list, column); column = gtk_tree_view_column_new_with_attributes (_("Album"), renderer, "text", ALBUM_COLUMN, NULL); gtk_tree_view_append_column (list, column); gtk_tree_view_set_model (list, GTK_TREE_MODEL (store)); GtkTreeSelection *select; select = gtk_tree_view_get_selection (list); gtk_tree_selection_set_mode (select, GTK_SELECTION_SINGLE); if (select_changed_callback != NULL) { g_signal_connect (G_OBJECT (select), "changed", G_CALLBACK (select_changed_callback), NULL); } } void ol_lyric_candidate_list_set_list (GtkTreeView *list, GList *candidates) { ol_assert (list != NULL); GtkTreeIter iter; GtkTreeStore *store = GTK_TREE_STORE (gtk_tree_view_get_model (list)); gboolean first = TRUE; ol_lyric_candidate_list_clear (list); for (; candidates; candidates = g_list_next (candidates)) { OlLyricSourceCandidate *candidate = OL_LYRIC_SOURCE_CANDIDATE (candidates->data); if (candidate == NULL) continue; gtk_tree_store_append (store, &iter, NULL); /* Acquire a top-level iterator */ gtk_tree_store_set (store, &iter, TITLE_COLUMN, ol_lyric_source_candidate_get_title (candidate), ARTIST_COLUMN, ol_lyric_source_candidate_get_artist (candidate), ALBUM_COLUMN, ol_lyric_source_candidate_get_album (candidate), COMMENT_COLUMN, ol_lyric_source_candidate_get_comment (candidate), DATA_COLUMN, candidate, -1); /* ol_debugf (" url: %s\n", candidates[i].url); */ /* Select the first item */ if (first) { gtk_tree_selection_select_iter (gtk_tree_view_get_selection (list), &iter); first = FALSE; } } } OlLyricSourceCandidate * ol_lyric_candidate_list_get_selected (GtkTreeView *list) { ol_assert_ret (list != NULL, FALSE); GtkTreeSelection *selection = gtk_tree_view_get_selection (list); if (selection != NULL) { GtkTreeIter iter; GtkTreeModel *model = NULL; OlLyricSourceCandidate *candidate = NULL; if (!gtk_tree_selection_get_selected (selection, &model, &iter)) return NULL; gtk_tree_model_get (model, &iter, DATA_COLUMN, &candidate, -1); g_object_unref (candidate); return candidate; } else { return NULL; } } void ol_lyric_candidate_list_clear (GtkTreeView *list) { GtkTreeStore *store = GTK_TREE_STORE (gtk_tree_view_get_model (list)); gtk_tree_store_clear (store); } osdlyrics-0.5.5~rc1+dfsg1/src/ol_lyric_candidate_list.h000066400000000000000000000025271345713535600231650ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2012 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_LYRIC_CANDIDATE_LIST_H_ #define _OL_LYRIC_CANDIDATE_LIST_H_ #include #include "ol_lyric_source.h" void ol_lyric_candidate_list_init (GtkTreeView *list, GCallback select_change_callback); void ol_lyric_candidate_list_set_list (GtkTreeView *list, GList *candidates); OlLyricSourceCandidate *ol_lyric_candidate_list_get_selected (GtkTreeView *list); void ol_lyric_candidate_list_clear (GtkTreeView *list); #endif /* _OL_LYRIC_CANDIDATE_LIST_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_lyric_candidate_selector.c000066400000000000000000000101251345713535600240160ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2012 Tiger Soldier * * This file is part of OSD Lyrics. * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include "ol_lyric_candidate_selector.h" #include "ol_gui.h" #include "ol_config_proxy.h" #include "ol_lyric_candidate_list.h" #include "ol_debug.h" static GtkWidget *window = NULL; static GtkTreeView *list = NULL; static GtkButton *download_button = NULL; static OlMetadata *metadata = NULL; static OlLrcFetchUiDownloadFunc download_func; static void ol_lrc_fetch_select_changed (GtkTreeSelection *selection, gpointer data); static gboolean internal_init (); gboolean ol_lrc_fetch_cancel (GtkWidget *widget, gpointer data); gboolean ol_lyric_candidate_selector_cancel (GtkWidget *widget, gpointer data) { ol_log_func (); if (window != NULL) gtk_widget_hide (window); return TRUE; } gboolean ol_lyric_candidate_selector_download (GtkWidget *widget, gpointer data) { ol_log_func (); OlLyricSourceCandidate *candidate; candidate = ol_lyric_candidate_list_get_selected (list); if (candidate) { if (download_func) download_func (candidate, metadata); } OlConfigProxy *config = ol_config_proxy_get_instance (); GtkToggleButton *prompt_btn = GTK_TOGGLE_BUTTON (ol_gui_get_widget ("choose-do-not-prompt")); if (prompt_btn != NULL && config != NULL) { if (gtk_toggle_button_get_active (prompt_btn)) { ol_config_proxy_set_bool (config, "Download/download-first-lyric", TRUE); } } gtk_widget_hide (window); return TRUE; } static void ol_lrc_fetch_select_changed (GtkTreeSelection *selection, gpointer data) { ol_log_func (); if (download_button != NULL) gtk_widget_set_sensitive (GTK_WIDGET (download_button), gtk_tree_selection_get_selected (selection, NULL, NULL)); } static gboolean internal_init () { ol_log_func (); if (window == NULL) { window = ol_gui_get_widget ("downloaddialog"); if (window == NULL) return FALSE; g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL); } if (download_button == NULL) { download_button = GTK_BUTTON (ol_gui_get_widget ("lrc-download")); } if (list == NULL) { list = GTK_TREE_VIEW (ol_gui_get_widget ("candidate-list")); ol_lyric_candidate_list_init (list, G_CALLBACK (ol_lrc_fetch_select_changed)); } return TRUE; } void ol_lyric_candidate_selector_show (GList *candidates, const OlMetadata *_metadata, OlLrcFetchUiDownloadFunc _download_func) { ol_log_func (); if (window == NULL && !internal_init ()) return; if (candidates == NULL || _download_func == NULL) { gtk_widget_hide (window); return; } ol_lyric_candidate_list_set_list (list, candidates); if (metadata == NULL) metadata = ol_metadata_new (); ol_metadata_copy (metadata, _metadata); download_func = _download_func; gboolean prompt = TRUE; OlConfigProxy *config = ol_config_proxy_get_instance (); if (config != NULL) prompt = ol_config_proxy_get_bool (config, "Download/download-first-lyric"); if (prompt || g_list_next (candidates) == NULL) ol_lyric_candidate_selector_download (GTK_WIDGET (download_button), NULL); else gtk_widget_show (window); } osdlyrics-0.5.5~rc1+dfsg1/src/ol_lyric_candidate_selector.h000066400000000000000000000025201345713535600240230ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2012 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_LYRIC_CANDIDATE_SELECTOR_H_ #define _OL_LYRIC_CANDIDATE_SELECTOR_H_ #include "ol_metadata.h" #include "ol_lyric_source.h" typedef void (*OlLrcFetchUiDownloadFunc) (OlLyricSourceCandidate *candidate, const OlMetadata *metadata); void ol_lyric_candidate_selector_show (GList *candidates, const OlMetadata *metadata, OlLrcFetchUiDownloadFunc download_func); #endif /* _OL_LYRIC_CANDIDATE_SELECTOR_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_lyric_source.c000066400000000000000000001163001345713535600215040ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2012 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include #include "ol_lyric_source.h" #include "ol_consts.h" #include "ol_marshal.h" #include "ol_debug.h" G_DEFINE_TYPE (OlLyricSource, ol_lyric_source, G_TYPE_DBUS_PROXY); G_DEFINE_TYPE (OlLyricSourceTask, ol_lyric_source_task, G_TYPE_OBJECT); G_DEFINE_TYPE (OlLyricSourceSearchTask, ol_lyric_source_search_task, OL_TYPE_LYRIC_SOURCE_TASK); G_DEFINE_TYPE (OlLyricSourceDownloadTask, ol_lyric_source_download_task, OL_TYPE_LYRIC_SOURCE_TASK); G_DEFINE_TYPE (OlLyricSourceCandidate, ol_lyric_source_candidate, G_TYPE_OBJECT); #define OL_LYRIC_SOURCE_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), \ OL_TYPE_LYRIC_SOURCE, \ OlLyricSourcePrivate)) #define OL_LYRIC_SOURCE_CANDIDATE_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), \ OL_TYPE_LYRIC_SOURCE_CANDIDATE, \ OlLyricSourceCandidatePrivate)) #define OL_LYRIC_SOURCE_TASK_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), \ OL_TYPE_LYRIC_SOURCE_TASK, \ OlLyricSourceTaskPrivate)) struct _OlLyricSourceInfo { gchar *id; gchar *name; gboolean enabled; }; typedef struct _OlLyricSourcePrivate OlLyricSourcePrivate; struct _OlLyricSourcePrivate { GHashTable *search_tasks; GHashTable *download_tasks; }; typedef struct _OlLyricSourceCandidatePrivate OlLyricSourceCandidatePrivate; struct _OlLyricSourceCandidatePrivate { gchar *title; gchar *artist; gchar *album; gchar *comment; gchar *sourceid; GVariant *downloadinfo; }; typedef struct _OlLyricSourceTaskPrivate OlLyricSourceTaskPrivate; struct _OlLyricSourceTaskPrivate { OlLyricSource *source; gint taskid; }; enum { SEARCH_SIGNAL_COMPLETE = 0, SEARCH_SIGNAL_STARTED, SEARCH_SIGNAL_LAST, }; enum { DOWNLOAD_SIGNAL_COMPLETE = 0, DOWNLOAD_SIGNAL_LAST, }; enum { CANDIDATE_PROP_NONE = 0, CANDIDATE_PROP_TITLE, CANDIDATE_PROP_ARTIST, CANDIDATE_PROP_ALBUM, CANDIDATE_PROP_COMMENT, CANDIDATE_PROP_DOWNLOADINFO, CANDIDATE_PROP_SOURCEID, }; enum { TASK_PROP_NONE = 0, TASK_PROP_SOURCE, TASK_PROP_TASKID, }; static guint _search_signals[SEARCH_SIGNAL_LAST]; static guint _download_signals[DOWNLOAD_SIGNAL_LAST]; /* OlLyricSource */ static void ol_lyric_source_finalize (GObject *object); static void ol_lyric_source_g_signal (GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *parameters); static OlLyricSourceTask *ol_lyric_source_get_search_task (OlLyricSource *source, gint taskid); static OlLyricSourceTask *ol_lyric_source_get_download_task (OlLyricSource *source, gint taskid); static void ol_lyric_source_remove_search_task (OlLyricSource *source, gint taskid); static void ol_lyric_source_remove_download_task (OlLyricSource *source, gint taskid); static void ol_lyric_source_search_complete_cb (OlLyricSource *source, GVariant *parameters); static void ol_lyric_source_search_started_cb (OlLyricSource *source, GVariant *parameters); static void ol_lyric_source_download_complete_cb (OlLyricSource *source, GVariant *parameters); static OlLyricSourceInfo *ol_lyric_source_info_new (const gchar *id, const gchar *name, gboolean enabled); /* OlLyricSourceCandidate */ static OlLyricSourceCandidate *ol_lyric_source_candidate_new (const gchar *title, const gchar *artist, const gchar *album, const gchar *comment, const gchar *sourceid, GVariant *downloadinfo); static OlLyricSourceCandidate *ol_lyric_source_candidate_new_with_variant (GVariant *dict); static void ol_lyric_source_candidate_finalize (GObject *object); static void ol_lyric_source_candidate_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void ol_lyric_source_candidate_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); /* OlLyricSourceTask */ static void ol_lyric_source_task_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void ol_lyric_source_task_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void ol_lyric_source_task_finalize (GObject *object); static void ol_lyric_source_task_weak_notify (OlLyricSourceTask *task, OlLyricSource *source); static void ol_lyric_source_task_call_cancel (OlLyricSourceTask *task, const gchar *method); /* OlLyricSourceSearchTask */ static OlLyricSourceSearchTask *ol_lyric_source_search_task_new (OlLyricSource *source, gint taskid); static void ol_lyric_source_search_task_cancel (OlLyricSourceTask *task); /* OlLyricSourceDownloadTask */ static OlLyricSourceDownloadTask *ol_lyric_source_download_task_new (OlLyricSource *source, gint taskid); static void ol_lyric_source_download_task_cancel (OlLyricSourceTask *task); static void ol_lyric_source_class_init (OlLyricSourceClass *klass) { GObjectClass *object_class; GDBusProxyClass *proxy_class; object_class = G_OBJECT_CLASS (klass); proxy_class = G_DBUS_PROXY_CLASS (klass); object_class->finalize = ol_lyric_source_finalize; proxy_class->g_signal = ol_lyric_source_g_signal; g_type_class_add_private (klass, sizeof (OlLyricSourcePrivate)); } static void ol_lyric_source_init (OlLyricSource *source) { OlLyricSourcePrivate *priv = OL_LYRIC_SOURCE_GET_PRIVATE (source); priv->search_tasks = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)g_object_unref); priv->download_tasks = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)g_object_unref); } OlLyricSource * ol_lyric_source_new (void) { GInitable *ret; ret = g_initable_new (OL_TYPE_LYRIC_SOURCE, NULL, /* cancellable */ NULL, "g-flags", G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, "g-name", OL_SERVICE_DAEMON, "g-bus-type", G_BUS_TYPE_SESSION, "g-object-path", OL_OBJECT_LYRIC_SOURCE, "g-interface-name", OL_IFACE_LYRIC_SOURCE, NULL); if (ret != NULL) return OL_LYRIC_SOURCE (ret); else return NULL; } static void ol_lyric_source_finalize (GObject *object) { OlLyricSourcePrivate *priv = OL_LYRIC_SOURCE_GET_PRIVATE (object); g_hash_table_destroy (priv->search_tasks); g_hash_table_destroy (priv->download_tasks); priv->search_tasks = NULL; priv->download_tasks = NULL; G_OBJECT_CLASS (ol_lyric_source_parent_class)->finalize (object); } static OlLyricSourceTask * ol_lyric_source_get_search_task (OlLyricSource *source, gint taskid) { OlLyricSourcePrivate *priv = OL_LYRIC_SOURCE_GET_PRIVATE (source); return g_hash_table_lookup (priv->search_tasks, GINT_TO_POINTER (taskid)); } static OlLyricSourceTask * ol_lyric_source_get_download_task (OlLyricSource *source, gint taskid) { OlLyricSourcePrivate *priv = OL_LYRIC_SOURCE_GET_PRIVATE (source); return g_hash_table_lookup (priv->download_tasks, GINT_TO_POINTER (taskid)); } static void ol_lyric_source_remove_search_task (OlLyricSource *source, gint taskid) { OlLyricSourcePrivate *priv = OL_LYRIC_SOURCE_GET_PRIVATE (source); g_hash_table_remove (priv->search_tasks, GINT_TO_POINTER (taskid)); } static void ol_lyric_source_remove_download_task (OlLyricSource *source, gint taskid) { OlLyricSourcePrivate *priv = OL_LYRIC_SOURCE_GET_PRIVATE (source); g_hash_table_remove (priv->download_tasks, GINT_TO_POINTER (taskid)); } static void ol_lyric_source_search_complete_cb (OlLyricSource *source, GVariant *parameters) { GVariantIter *iter = NULL; GVariant *dict = NULL; GList *result = NULL; gint taskid; gint statusid; OlLyricSourceTask *task; g_variant_get (parameters, "(iiaa{sv})", &taskid, &statusid, &iter); task = ol_lyric_source_get_search_task (source, taskid); if (task == NULL) { ol_errorf ("Search task %d not exist\n", taskid); return; } if (statusid < 0 || statusid > OL_LYRIC_SOURCE_STATUS_FALIURE) { ol_errorf ("Invalid search status %d\n", statusid); return; } while (g_variant_iter_loop (iter, "@a{sv}", &dict)) { OlLyricSourceCandidate *candidate; candidate = ol_lyric_source_candidate_new_with_variant (dict); result = g_list_prepend (result, candidate); } g_variant_iter_free (iter); result = g_list_reverse (result); g_signal_emit (task, _search_signals[SEARCH_SIGNAL_COMPLETE], 0, statusid, result); while (result) { OlLyricSourceCandidate *candidate = result->data; g_object_unref (candidate); result = g_list_delete_link (result, result); } ol_lyric_source_remove_search_task (source, taskid); } static void ol_lyric_source_search_started_cb (OlLyricSource *source, GVariant *parameters) { OlLyricSourceTask *task; int taskid; gchar *sourceid; gchar *sourcename; g_variant_get (parameters, "(i&s&s)", &taskid, &sourceid, &sourcename); task = ol_lyric_source_get_search_task (source, taskid); if (task == NULL) { ol_errorf ("Search task %d not exist\n", taskid); return; } g_signal_emit (G_OBJECT (task), _search_signals[SEARCH_SIGNAL_STARTED], 0, sourceid, sourcename); } static void ol_lyric_source_download_complete_cb (OlLyricSource *source, GVariant *parameters) { OlLyricSourceTask *task; int taskid; int status; GVariantIter *iter; gchar *content = NULL; gsize len = 0; g_variant_get (parameters, "(iiay)", &taskid, &status, &iter); task = ol_lyric_source_get_download_task (source, taskid); if (task == NULL) { ol_errorf ("Download task %d not exist\n", taskid); } else if (status < 0 || status > OL_LYRIC_SOURCE_STATUS_FALIURE) { ol_errorf ("Invalid download status %d\n", status); } else { len = g_variant_iter_n_children (iter); content = g_new (gchar, len + 1); gsize i = 0; while (g_variant_iter_loop (iter, "y", &content[i])) i++; content[len] = '\0'; g_signal_emit (G_OBJECT (task), _download_signals[DOWNLOAD_SIGNAL_COMPLETE], 0, status, content, (guint) len); g_free (content); } g_variant_iter_free (iter); ol_lyric_source_remove_download_task (source, taskid); } static void ol_lyric_source_g_signal (GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *parameters) { OlLyricSource *source = OL_LYRIC_SOURCE (proxy); if (strcmp (signal_name, "SearchComplete") == 0) { ol_lyric_source_search_complete_cb (source, parameters); } else if (strcmp (signal_name, "SearchStarted") == 0) { ol_lyric_source_search_started_cb (source, parameters); } else if (strcmp (signal_name, "DownloadComplete") == 0) { ol_lyric_source_download_complete_cb (source, parameters); } } GList * ol_lyric_source_list_sources (OlLyricSource* source) { ol_assert_ret (OL_IS_LYRIC_SOURCE (source), NULL); GList *list = NULL; GVariant *ret; GError *error = NULL; ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (source), "ListSources", NULL, /* parameters */ G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, &error); if (ret) { GVariantIter *iter; g_variant_get (ret, "(aa{sv})", &iter); GVariantIter *dictiter; while (g_variant_iter_loop (iter, "a{sv}", &dictiter)) { GVariant *value; gchar *key; gchar *name = NULL, *id = NULL; gboolean enabled = FALSE; while (g_variant_iter_loop (dictiter, "{sv}", &key, &value)) { if (strcmp (key, "id") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) { id = g_variant_dup_string (value, NULL); } else if (strcmp (key, "name") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) { name = g_variant_dup_string (value, NULL); } else if (strcmp (key, "enabled") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN)) { enabled = g_variant_get_boolean (value); } } if (id == NULL || name == NULL) { ol_error ("Missing id or name in lyric source info"); } else { list = g_list_prepend (list, ol_lyric_source_info_new (id, name, enabled)); } g_free (id); g_free (name); } g_variant_iter_free (iter); list = g_list_reverse (list); g_variant_unref (ret); return list; } else { ol_errorf ("Fail to get lyric source list: %s\n", error->message); g_error_free (error); return NULL; } } OlLyricSourceSearchTask * ol_lyric_source_search (OlLyricSource *source, OlMetadata *metadata, GList *source_ids) { ol_assert_ret (OL_IS_LYRIC_SOURCE (source), NULL); GVariant *ret; OlLyricSourceSearchTask *task = NULL; GError *error = NULL; GList *id; GVariantBuilder *idbuilder = g_variant_builder_new (G_VARIANT_TYPE ("as")); for (id = source_ids; id; id = g_list_next (id)) { g_variant_builder_add (idbuilder, "s", id->data); } GVariant *mdvalue = ol_metadata_to_variant (metadata); ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (source), "Search", g_variant_new ("(@a{sv}as)", mdvalue, idbuilder), G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout */ NULL, /* cancellable */ &error); g_variant_builder_unref (idbuilder); if (ret) { OlLyricSourcePrivate *priv; priv = OL_LYRIC_SOURCE_GET_PRIVATE (source); int taskid; g_variant_get (ret, "(i)", &taskid); task = ol_lyric_source_search_task_new (source, taskid); g_hash_table_insert (priv->search_tasks, GINT_TO_POINTER (taskid), task); g_variant_unref (ret); } else { ol_errorf ("Fail to call search: %s\n", error->message); g_error_free (error); } return task; } OlLyricSourceSearchTask * ol_lyric_source_search_default (OlLyricSource *source, OlMetadata *metadata) { ol_assert_ret (OL_IS_LYRIC_SOURCE (source), NULL); OlLyricSourceSearchTask *task = NULL; GList *sources; GList *source_ids = NULL; sources = ol_lyric_source_list_sources (source); for (; sources; sources = g_list_delete_link (sources, sources)) { OlLyricSourceInfo *info = sources->data; source_ids = g_list_prepend (source_ids, g_strdup (ol_lyric_source_info_get_id (info))); ol_lyric_source_info_free (info); } source_ids = g_list_reverse (source_ids); task = ol_lyric_source_search (source, metadata, source_ids); for (; source_ids; source_ids = g_list_delete_link (source_ids, source_ids)) { g_free (source_ids->data); } return task; } OlLyricSourceDownloadTask * ol_lyric_source_download (OlLyricSource *source, OlLyricSourceCandidate *candidate) { ol_assert_ret (OL_IS_LYRIC_SOURCE (source), NULL); ol_assert_ret (OL_IS_LYRIC_SOURCE_CANDIDATE (candidate), NULL); OlLyricSourceDownloadTask *task = NULL; GVariant *ret = NULL; GError *error = NULL; ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (source), "Download", g_variant_new ("(sv)", ol_lyric_source_candidate_get_sourceid (candidate), ol_lyric_source_candidate_get_downloadinfo (candidate)), G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout */ NULL, /* cancellable */ &error); if (ret) { OlLyricSourcePrivate *priv; priv = OL_LYRIC_SOURCE_GET_PRIVATE (source); int taskid = 0; g_variant_get (ret, "(i)", &taskid); task = ol_lyric_source_download_task_new (source, taskid); g_hash_table_insert (priv->download_tasks, GINT_TO_POINTER (taskid), task); g_variant_unref (ret); } else { ol_errorf ("Fail to call download: %s\n", error->message); g_error_free (error); } return task; } /* OlLyricSourceInfo */ static OlLyricSourceInfo * ol_lyric_source_info_new (const gchar *id, const gchar *name, gboolean enabled) { ol_assert_ret (id != NULL, NULL); ol_assert_ret (name != NULL, NULL); OlLyricSourceInfo *info = g_new (OlLyricSourceInfo, 1); info->id = g_strdup (id); info->name = g_strdup (name); info->enabled = enabled; return info; } void ol_lyric_source_info_free (OlLyricSourceInfo *info) { if (info == NULL) return; g_free (info->id); g_free (info->name); g_free (info); } const gchar * ol_lyric_source_info_get_id (OlLyricSourceInfo *info) { ol_assert_ret (info != NULL, NULL); return info->id; } const gchar * ol_lyric_source_info_get_name (OlLyricSourceInfo *info) { ol_assert_ret (info != NULL, NULL); return info->name; } gboolean ol_lyric_source_info_get_enabled (OlLyricSourceInfo *info) { ol_assert_ret (info != NULL, FALSE); return info->enabled; } /* OlLyricSourceCandidate */ static void ol_lyric_source_candidate_class_init (OlLyricSourceCandidateClass *klass) { GObjectClass *object_class; object_class = G_OBJECT_CLASS (klass); object_class->get_property = ol_lyric_source_candidate_get_property; object_class->set_property = ol_lyric_source_candidate_set_property; object_class->finalize = ol_lyric_source_candidate_finalize; g_type_class_add_private (klass, sizeof (OlLyricSourceCandidatePrivate)); g_object_class_install_property (object_class, CANDIDATE_PROP_TITLE, g_param_spec_string ("title", ("Title"), ("Title"), "", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, CANDIDATE_PROP_ARTIST, g_param_spec_string ("artist", ("Artist"), ("Artist"), "", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, CANDIDATE_PROP_ALBUM, g_param_spec_string ("album", ("Album"), ("Album"), "", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, CANDIDATE_PROP_COMMENT, g_param_spec_string ("comment", ("Comment"), ("Comment"), "", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, CANDIDATE_PROP_SOURCEID, g_param_spec_string ("sourceid", ("SourceId"), ("Source ID"), "", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, CANDIDATE_PROP_DOWNLOADINFO, g_param_spec_variant ("downloadinfo", ("Download Info"), ("Download Info"), G_VARIANT_TYPE_ANY, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } static void ol_lyric_source_candidate_init (OlLyricSourceCandidate *source) { } static OlLyricSourceCandidate * ol_lyric_source_candidate_new (const gchar *title, const gchar *artist, const gchar *album, const gchar *comment, const gchar *sourceid, GVariant *downloadinfo) { ol_assert_ret (sourceid != NULL, NULL); ol_assert_ret (downloadinfo != NULL, NULL); return OL_LYRIC_SOURCE_CANDIDATE (g_object_new (OL_TYPE_LYRIC_SOURCE_CANDIDATE, "title", title, "artist", artist, "album", album, "comment", comment, "sourceid", sourceid, "downloadinfo", downloadinfo, NULL)); } static OlLyricSourceCandidate * ol_lyric_source_candidate_new_with_variant (GVariant *dict) { ol_log_func (); OlLyricSourceCandidate *candidate = NULL; GVariantIter *dictiter = NULL; gchar *key = NULL; gchar *title, *artist, *album, *comment, *sourceid; GVariant *downloadinfo = NULL; GVariant *value = NULL; title = artist = album = comment = sourceid = NULL; g_variant_get (dict, "a{sv}", &dictiter); while (g_variant_iter_loop (dictiter, "{sv}", &key, &value)) { if (strcmp (key, "title") == 0 && title == NULL) { title = g_variant_dup_string (value, NULL); } else if (strcmp (key, "artist") == 0 && artist == NULL) { artist = g_variant_dup_string (value, NULL); } else if (strcmp (key, "album") == 0 && album == NULL) { album = g_variant_dup_string (value, NULL); } else if (strcmp (key, "comment") == 0 && comment == NULL) { comment = g_variant_dup_string (value, NULL); } else if (strcmp (key, "sourceid") == 0 && sourceid == NULL) { sourceid = g_variant_dup_string (value, NULL); } else if (strcmp (key, "downloadinfo") == 0 && downloadinfo == NULL) { downloadinfo = g_variant_ref (value); } else { ol_errorf ("Unknown candidate key: %s\n", key); } } g_variant_iter_free (dictiter); candidate = ol_lyric_source_candidate_new (title, artist, album, comment, sourceid, downloadinfo); g_free (title); g_free (artist); g_free (album); g_free (comment); g_free (sourceid); g_variant_unref (downloadinfo); return candidate; } static void ol_lyric_source_candidate_finalize (GObject *object) { OlLyricSourceCandidatePrivate *priv; priv = OL_LYRIC_SOURCE_CANDIDATE_GET_PRIVATE (object); g_free (priv->title); g_free (priv->artist); g_free (priv->album); g_free (priv->sourceid); g_free (priv->comment); g_variant_unref (priv->downloadinfo); priv->title = NULL; priv->artist = NULL; priv->album = NULL; priv->downloadinfo = NULL; priv->comment = NULL; priv->sourceid = NULL; G_OBJECT_CLASS (ol_lyric_source_candidate_parent_class)->finalize (object); } static void ol_lyric_source_candidate_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { OlLyricSourceCandidatePrivate *priv; priv = OL_LYRIC_SOURCE_CANDIDATE_GET_PRIVATE (object); switch (property_id) { case CANDIDATE_PROP_TITLE: g_value_set_string (value, priv->title); break; case CANDIDATE_PROP_ARTIST: g_value_set_string (value, priv->artist); break; case CANDIDATE_PROP_ALBUM: g_value_set_string (value, priv->album); break; case CANDIDATE_PROP_COMMENT: g_value_set_string (value, priv->comment); break; case CANDIDATE_PROP_SOURCEID: g_value_set_string (value, priv->sourceid); break; case CANDIDATE_PROP_DOWNLOADINFO: g_value_set_variant (value, priv->downloadinfo); break; } } static void ol_lyric_source_candidate_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { OlLyricSourceCandidatePrivate *priv; priv = OL_LYRIC_SOURCE_CANDIDATE_GET_PRIVATE (object); switch (property_id) { case CANDIDATE_PROP_TITLE: g_free (priv->title); priv->title = g_value_dup_string (value); break; case CANDIDATE_PROP_ARTIST: g_free (priv->artist); priv->artist = g_value_dup_string (value); break; case CANDIDATE_PROP_ALBUM: g_free (priv->album); priv->album = g_value_dup_string (value); break; case CANDIDATE_PROP_COMMENT: g_free (priv->comment); priv->comment = g_value_dup_string (value); break; case CANDIDATE_PROP_SOURCEID: g_free (priv->sourceid); priv->sourceid = g_value_dup_string (value); break; case CANDIDATE_PROP_DOWNLOADINFO: if (priv->downloadinfo != NULL) g_variant_unref (priv->downloadinfo); priv->downloadinfo = g_value_dup_variant (value); break; } } const gchar * ol_lyric_source_candidate_get_title (OlLyricSourceCandidate *candidate) { ol_assert_ret (OL_IS_LYRIC_SOURCE_CANDIDATE (candidate), NULL); OlLyricSourceCandidatePrivate *priv; priv = OL_LYRIC_SOURCE_CANDIDATE_GET_PRIVATE (candidate); return priv->title; } const gchar * ol_lyric_source_candidate_get_artist (OlLyricSourceCandidate *candidate) { ol_assert_ret (OL_IS_LYRIC_SOURCE_CANDIDATE (candidate), NULL); OlLyricSourceCandidatePrivate *priv; priv = OL_LYRIC_SOURCE_CANDIDATE_GET_PRIVATE (candidate); return priv->artist; } const gchar * ol_lyric_source_candidate_get_album (OlLyricSourceCandidate *candidate) { ol_assert_ret (OL_IS_LYRIC_SOURCE_CANDIDATE (candidate), NULL); OlLyricSourceCandidatePrivate *priv; priv = OL_LYRIC_SOURCE_CANDIDATE_GET_PRIVATE (candidate); return priv->album; } const gchar * ol_lyric_source_candidate_get_comment (OlLyricSourceCandidate *candidate) { ol_assert_ret (OL_IS_LYRIC_SOURCE_CANDIDATE (candidate), NULL); OlLyricSourceCandidatePrivate *priv; priv = OL_LYRIC_SOURCE_CANDIDATE_GET_PRIVATE (candidate); return priv->comment; } GVariant * ol_lyric_source_candidate_get_downloadinfo (OlLyricSourceCandidate *candidate) { ol_assert_ret (OL_IS_LYRIC_SOURCE_CANDIDATE (candidate), NULL); OlLyricSourceCandidatePrivate *priv; priv = OL_LYRIC_SOURCE_CANDIDATE_GET_PRIVATE (candidate); return priv->downloadinfo; } const gchar * ol_lyric_source_candidate_get_sourceid (OlLyricSourceCandidate *candidate) { ol_assert_ret (OL_IS_LYRIC_SOURCE_CANDIDATE (candidate), NULL); OlLyricSourceCandidatePrivate *priv; priv = OL_LYRIC_SOURCE_CANDIDATE_GET_PRIVATE (candidate); return priv->sourceid; } /* OlLyricSourceTask */ static void ol_lyric_source_task_class_init (OlLyricSourceTaskClass *klass) { GObjectClass *object_class; object_class = G_OBJECT_CLASS (klass); object_class->set_property = ol_lyric_source_task_set_property; object_class->get_property = ol_lyric_source_task_get_property; object_class->finalize = ol_lyric_source_task_finalize; klass->cancel = NULL; g_type_class_add_private (klass, sizeof (OlLyricSourceTaskPrivate)); g_object_class_install_property (object_class, TASK_PROP_SOURCE, g_param_spec_object ("source", ("Source"), ("Source"), OL_TYPE_LYRIC_SOURCE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, TASK_PROP_TASKID, g_param_spec_int ("taskid", ("Task ID"), ("Task ID"), 0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } static void ol_lyric_source_task_init (OlLyricSourceTask *task) { } static void ol_lyric_source_task_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { OlLyricSourceTaskPrivate *priv; priv = OL_LYRIC_SOURCE_TASK_GET_PRIVATE (object); switch (property_id) { case TASK_PROP_SOURCE: g_value_set_object (value, priv->source); break; case TASK_PROP_TASKID: g_value_set_int (value, priv->taskid); break; } } static void ol_lyric_source_task_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { OlLyricSourceTaskPrivate *priv; priv = OL_LYRIC_SOURCE_TASK_GET_PRIVATE (object); switch (property_id) { case TASK_PROP_SOURCE: priv->source = g_value_get_object (value); if (priv->source != NULL) g_object_weak_ref (G_OBJECT (priv->source), (GWeakNotify) ol_lyric_source_task_weak_notify, object); break; case TASK_PROP_TASKID: priv->taskid = g_value_get_int (value); break; } } static void ol_lyric_source_task_finalize (GObject *object) { OlLyricSourceTaskPrivate *priv; priv = OL_LYRIC_SOURCE_TASK_GET_PRIVATE (object); if (priv->source) { g_object_weak_unref (G_OBJECT (priv->source), (GWeakNotify) ol_lyric_source_task_weak_notify, object); priv->source = NULL; } } void ol_lyric_source_task_cancel (OlLyricSourceTask *task) { OlLyricSourceTaskClass *klass; klass = OL_LYRIC_SOURCE_TASK_CLASS (G_OBJECT_GET_CLASS (task)); if (klass->cancel) klass->cancel (task); } gint ol_lyric_source_task_get_id (OlLyricSourceTask *task) { ol_assert_ret (OL_IS_LYRIC_SOURCE_TASK (task), -1); return OL_LYRIC_SOURCE_TASK_GET_PRIVATE (task)->taskid; } static void ol_lyric_source_task_weak_notify (OlLyricSourceTask *task, OlLyricSource *source) { OlLyricSourceTaskPrivate *priv; priv = OL_LYRIC_SOURCE_TASK_GET_PRIVATE (task); if (priv->source) { g_object_weak_unref (G_OBJECT (priv->source), (GWeakNotify) ol_lyric_source_task_weak_notify, source); priv->source = NULL; } } static void ol_lyric_source_task_call_cancel (OlLyricSourceTask *task, const gchar *method) { ol_assert (OL_IS_LYRIC_SOURCE_TASK (task)); ol_assert (method != NULL); OlLyricSourceTaskPrivate *priv; GVariant *ret; priv = OL_LYRIC_SOURCE_TASK_GET_PRIVATE (task); if (!priv->source) return; GDBusProxy *proxy = G_DBUS_PROXY (priv->source); ret = g_dbus_proxy_call_sync (proxy, method, g_variant_new ("(i)", priv->taskid), G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, NULL); g_variant_unref (ret); } /* OlLyricSourceSearchTask */ static void ol_lyric_source_search_task_class_init (OlLyricSourceSearchTaskClass *klass) { OlLyricSourceTaskClass *task_class; task_class = OL_LYRIC_SOURCE_TASK_CLASS (klass); task_class->cancel = ol_lyric_source_search_task_cancel; _search_signals[SEARCH_SIGNAL_COMPLETE] = g_signal_new ("complete", OL_TYPE_LYRIC_SOURCE_SEARCH_TASK, G_SIGNAL_NO_HOOKS | G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE, 0, /* offset */ NULL, /* accumulator */ NULL, /* accu_data */ ol_marshal_VOID__ENUM_POINTER, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_POINTER); _search_signals[SEARCH_SIGNAL_STARTED] = g_signal_new ("started", OL_TYPE_LYRIC_SOURCE_SEARCH_TASK, G_SIGNAL_NO_HOOKS | G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE, 0, /* offset */ NULL, /* accumulator */ NULL, /* accu_data */ ol_marshal_VOID__STRING_STRING, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); } static void ol_lyric_source_search_task_init (OlLyricSourceSearchTask *task) { } static OlLyricSourceSearchTask * ol_lyric_source_search_task_new (OlLyricSource *source, gint taskid) { GObject *obj; obj = g_object_new (OL_TYPE_LYRIC_SOURCE_SEARCH_TASK, "source", source, "taskid", taskid, NULL); return OL_LYRIC_SOURCE_SEARCH_TASK (obj); } static void ol_lyric_source_search_task_cancel (OlLyricSourceTask *task) { ol_lyric_source_task_call_cancel (task, "CancelSearch"); } /* OlLyricSourceDownloadTask */ static void ol_lyric_source_download_task_class_init (OlLyricSourceDownloadTaskClass *klass) { OlLyricSourceTaskClass *task_class; task_class = OL_LYRIC_SOURCE_TASK_CLASS (klass); task_class->cancel = ol_lyric_source_download_task_cancel; _download_signals[DOWNLOAD_SIGNAL_COMPLETE] = g_signal_new ("complete", OL_TYPE_LYRIC_SOURCE_DOWNLOAD_TASK, G_SIGNAL_NO_HOOKS | G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE, 0, /* offset */ NULL, /* accumulator */ NULL, /* accu_data */ ol_marshal_VOID__ENUM_STRING_UINT, G_TYPE_NONE, 3, G_TYPE_INT, G_TYPE_STRING, G_TYPE_UINT); } static void ol_lyric_source_download_task_init (OlLyricSourceDownloadTask *task) { } static OlLyricSourceDownloadTask * ol_lyric_source_download_task_new (OlLyricSource *source, gint taskid) { GObject *obj; obj = g_object_new (OL_TYPE_LYRIC_SOURCE_DOWNLOAD_TASK, "source", source, "taskid", taskid, NULL); return OL_LYRIC_SOURCE_DOWNLOAD_TASK (obj); } static void ol_lyric_source_download_task_cancel (OlLyricSourceTask *task) { ol_lyric_source_task_call_cancel (task, "CancelDownload"); } osdlyrics-0.5.5~rc1+dfsg1/src/ol_lyric_source.h000066400000000000000000000332531345713535600215160ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2012 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_LYRIC_SOURCE_H_ #define _OL_LYRIC_SOURCE_H_ #include #include "ol_metadata.h" /* OlLyricSource */ #define OL_TYPE_LYRIC_SOURCE \ (ol_lyric_source_get_type ()) #define OL_LYRIC_SOURCE(obj) \ (G_TYPE_CHECK_INSTANCE_CAST (obj, \ OL_TYPE_LYRIC_SOURCE, \ OlLyricSource)) #define OL_LYRIC_SOURCE_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST (klass, \ OL_TYPE_LYRIC_SOURCE, \ OlLyricSourceClass)) #define OL_IS_LYRIC_SOURCE(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE (obj, \ OL_TYPE_LYRIC_SOURCE)) #define OL_IS_LYRIC_SOURCE_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE (klass, \ OL_TYPE_LYRIC_SOURCE)) #define OL_LYRIC_SOURCE_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), \ OL_TYPE_LYRIC_SOURCE, \ OlLyricSourceClass)) /* OlLyricSourceTask */ #define OL_TYPE_LYRIC_SOURCE_TASK \ (ol_lyric_source_task_get_type ()) #define OL_LYRIC_SOURCE_TASK(obj) \ (G_TYPE_CHECK_INSTANCE_CAST (obj, \ OL_TYPE_LYRIC_SOURCE_TASK, \ OlLyricSourceTask)) #define OL_LYRIC_SOURCE_TASK_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST (klass, \ OL_TYPE_LYRIC_SOURCE_TASK, \ OlLyricSourceTaskClass)) #define OL_IS_LYRIC_SOURCE_TASK(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE (obj, \ OL_TYPE_LYRIC_SOURCE_TASK)) #define OL_IS_LYRIC_SOURCE_TASK_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE (klass, \ OL_TYPE_LYRIC_SOURCE_TASK)) #define OL_LYRIC_SOURCE_TASK_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), \ OL_TYPE_LYRIC_SOURCE_TASK, \ OlLyricSourceTaskClass)) /* OlLyricSourceSearchTask */ #define OL_TYPE_LYRIC_SOURCE_SEARCH_TASK \ (ol_lyric_source_search_task_get_type ()) #define OL_LYRIC_SOURCE_SEARCH_TASK(obj) \ (G_TYPE_CHECK_INSTANCE_CAST (obj, \ OL_TYPE_LYRIC_SOURCE_SEARCH_TASK, \ OlLyricSourceSearchTask)) #define OL_LYRIC_SOURCE_SEARCH_TASK_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST (klass, \ OL_TYPE_LYRIC_SOURCE_SEARCH_TASK, \ OlLyricSourceSearchTaskClass)) #define OL_IS_LYRIC_SOURCE_SEARCH_TASK(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE (obj, \ OL_TYPE_LYRIC_SOURCE_SEARCH_TASK)) #define OL_IS_LYRIC_SOURCE_SEARCH_TASK_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE (klass, \ OL_TYPE_LYRIC_SOURCE_SEARCH_TASK)) #define OL_LYRIC_SOURCE_SEARCH_TASK_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), \ OL_TYPE_LYRIC_SOURCE_SEARCH_TASK, \ OlLyricSourceSearchTaskClass)) /* OlLyricSourceDownloadTask */ #define OL_TYPE_LYRIC_SOURCE_DOWNLOAD_TASK \ (ol_lyric_source_download_task_get_type ()) #define OL_LYRIC_SOURCE_DOWNLOAD_TASK(obj) \ (G_TYPE_CHECK_INSTANCE_CAST (obj, \ OL_TYPE_LYRIC_SOURCE_DOWNLOAD_TASK, \ OlLyricSourceDownloadTask)) #define OL_LYRIC_SOURCE_DOWNLOAD_TASK_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST (klass, \ OL_TYPE_LYRIC_SOURCE_DOWNLOAD_TASK, \ OlLyricSourceDownloadTaskClass)) #define OL_IS_LYRIC_SOURCE_DOWNLOAD_TASK(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE (obj, \ OL_TYPE_LYRIC_SOURCE_DOWNLOAD_TASK)) #define OL_IS_LYRIC_SOURCE_DOWNLOAD_TASK_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE (klass, \ OL_TYPE_LYRIC_SOURCE_DOWNLOAD_TASK)) #define OL_LYRIC_SOURCE_DOWNLOAD_TASK_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), \ OL_TYPE_LYRIC_SOURCE_DOWNLOAD_TASK, \ OlLyricSourceDownloadTaskClass)) /* OlLyricSourceCandidate */ #define OL_TYPE_LYRIC_SOURCE_CANDIDATE \ (ol_lyric_source_candidate_get_type ()) #define OL_LYRIC_SOURCE_CANDIDATE(obj) \ (G_TYPE_CHECK_INSTANCE_CAST (obj, \ OL_TYPE_LYRIC_SOURCE_CANDIDATE, \ OlLyricSourceCandidate)) #define OL_LYRIC_SOURCE_CANDIDATE_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST (klass, \ OL_TYPE_LYRIC_SOURCE_CANDIDATE, \ OlLyricSourceCandidateClass)) #define OL_IS_LYRIC_SOURCE_CANDIDATE(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE (obj, \ OL_TYPE_LYRIC_SOURCE_CANDIDATE)) #define OL_IS_LYRIC_SOURCE_CANDIDATE_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE (klass, \ OL_TYPE_LYRIC_SOURCE_CANDIDATE)) #define OL_LYRIC_SOURCE_CANDIDATE_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), \ OL_TYPE_LYRIC_SOURCE_CANDIDATE, \ OlLyricSourceCandidateClass)) enum OlLyricSourceStatus { OL_LYRIC_SOURCE_STATUS_SUCCESS = 0, OL_LYRIC_SOURCE_STATUS_CANCELLED = 1, OL_LYRIC_SOURCE_STATUS_FALIURE = 2, }; typedef struct _OlLyricSource OlLyricSource; typedef struct _OlLyricSourceClass OlLyricSourceClass; typedef struct _OlLyricSourceTask OlLyricSourceTask; typedef struct _OlLyricSourceTaskClass OlLyricSourceTaskClass; typedef struct _OlLyricSourceSearchTask OlLyricSourceSearchTask; typedef struct _OlLyricSourceSearchTaskClass OlLyricSourceSearchTaskClass; typedef struct _OlLyricSourceDownloadTask OlLyricSourceDownloadTask; typedef struct _OlLyricSourceDownloadTaskClass OlLyricSourceDownloadTaskClass; typedef struct _OlLyricSourceCandidate OlLyricSourceCandidate; typedef struct _OlLyricSourceCandidateClass OlLyricSourceCandidateClass; typedef struct _OlLyricSourceInfo OlLyricSourceInfo; struct _OlLyricSourceInfo; /** * Prototype of callback function of 'complete' signal in * OlLyricSourceSearchTask. * * @param task The task object that emits this signal. * @param status The status of the search task. * @param results A list of OlLyricSourceCandidate* objects. * @param userdata */ typedef void (*OlSearchCompleteFunc) (OlLyricSourceSearchTask *task, enum OlLyricSourceStatus status, GList *results, gpointer userdata); typedef void (*OlSearchStartedFunc) (OlLyricSourceSearchTask *task, const gchar *sourceid, const gchar *sourcename, gpointer userdata); /** * Prototype of callback function of 'complete' signal in * OlLyricSourceDownloadTask. * * @param task The task that emits this signal. * @param status The status of the download task. * @param content If status is OL_LYRIC_SOURCE_STATUS_SUCCESS, this * value is the content of the downloaded lyric file. * @param len The length of the content. * @param userdata */ typedef void (*OlDownloadCompleteFunc) (OlLyricSourceDownloadTask *task, enum OlLyricSourceStatus status, const gchar *content, guint len, gpointer userdata); struct _OlLyricSourceTask { GObject parent; }; struct _OlLyricSourceTaskClass { GObjectClass parent_class; void (*cancel) (OlLyricSourceTask *task); }; struct _OlLyricSourceSearchTask { OlLyricSourceTask parent; }; struct _OlLyricSourceSearchTaskClass { OlLyricSourceTaskClass parent_class; }; struct _OlLyricSourceDownloadTask { OlLyricSourceTask parent; }; struct _OlLyricSourceDownloadTaskClass { OlLyricSourceTaskClass parent_class; }; struct _OlLyricSource { GDBusProxy parent; }; struct _OlLyricSourceClass { GDBusProxyClass parent_class; }; struct _OlLyricSourceCandidate { GObject parent; }; struct _OlLyricSourceCandidateClass { GObjectClass parent_class; }; /* OlLyricSource */ /** * Creates a new lyric source proxy * */ OlLyricSource *ol_lyric_source_new (void); GType ol_lyric_source_get_type (void); /** * Lists all the available sources. * * @param source The lyric source proxy. * * @return A list of `OlLyricSourceInfo*`. Caller should free this list and its * elements. */ GList *ol_lyric_source_list_sources (OlLyricSource* source); /** * Search lyrics for a given metadata. * * @param source The lyric source proxy. * @param metadata The metadata to be searched for. * @param source_ids A list of gchar *. The ID of lyric sources. * * @return An OlLyricSourceSearchTask object of the search task. The * lyric source proxy owns a reference of it. If you want to take a * reference to the returned object, use g_object_ref(). */ OlLyricSourceSearchTask *ol_lyric_source_search (OlLyricSource *source, OlMetadata *metadata, GList *source_ids); /** * Search for lyrics with default lyric sources. * * @param source The lyric source proxy. * @param metadata The metadata to be searched for. * * @return An OlLyricSourceSearchTask object of the search task. The * lyric source proxy owns a reference of it. If you want to take a * reference to the returned object, use g_object_ref(). */ OlLyricSourceSearchTask *ol_lyric_source_search_default (OlLyricSource *source, OlMetadata *metadata); /** * Download a lyric file provided by a candidate. * * @param source The lyric source proxy. * @param candidate The candidate. * * @return An OlLyricSourceDownloadTask object of the search task. If * you want to take a reference to the returned object, use * g_object_ref(). */ OlLyricSourceDownloadTask *ol_lyric_source_download (OlLyricSource *source, OlLyricSourceCandidate *candidate); /* OlLyricSourceInfo */ const gchar *ol_lyric_source_info_get_id (OlLyricSourceInfo *info); const gchar *ol_lyric_source_info_get_name (OlLyricSourceInfo *info); gboolean ol_lyric_source_info_get_enabled (OlLyricSourceInfo *info); void ol_lyric_source_info_free (OlLyricSourceInfo *info); /* OlLyricSourceTask */ GType ol_lyric_source_task_get_type (void); gint ol_lyric_source_task_get_id (OlLyricSourceTask *task); void ol_lyric_source_task_cancel (OlLyricSourceTask *task); /* OlLyricSourceSearchTask */ GType ol_lyric_source_search_task_get_type (void); /* OlLyricSourceDownloadTask */ GType ol_lyric_source_get_type (void); /* OlLyricSourceCandidate */ GType ol_lyric_source_candidate_get_type (void); const gchar *ol_lyric_source_candidate_get_title (OlLyricSourceCandidate *candidate); const gchar *ol_lyric_source_candidate_get_artist (OlLyricSourceCandidate *candidate); const gchar *ol_lyric_source_candidate_get_album (OlLyricSourceCandidate *candidate); const gchar *ol_lyric_source_candidate_get_comment (OlLyricSourceCandidate *candidate); const gchar *ol_lyric_source_candidate_get_sourceid (OlLyricSourceCandidate *candidate); GVariant *ol_lyric_source_candidate_get_downloadinfo (OlLyricSourceCandidate *candidate); #endif /* _OL_LYRIC_SOURCE_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_lyric_source_list.c000066400000000000000000000104541345713535600225420ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2012 Tiger Soldier * * This file is part of OSD Lyrics. * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include "ol_lyric_source_list.h" #include "ol_lyric_source.h" #include "ol_debug.h" static void _cell_toggled_cb (GtkCellRendererToggle *cell_renderer, gchar *path, gpointer user_data); static void _cell_toggled_cb (GtkCellRendererToggle *cell_renderer, gchar *path, gpointer user_data) { GtkListStore *liststore = GTK_LIST_STORE (user_data); gboolean active = gtk_cell_renderer_toggle_get_active (cell_renderer); GtkTreeIter iter; gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (liststore), &iter, path); gtk_list_store_set (liststore, &iter, 0, !active, -1); } void ol_lyric_source_list_init (GtkTreeView *list) { ol_assert (list != NULL); ol_assert (GTK_IS_TREE_VIEW (list)); GtkListStore *liststore = gtk_list_store_new (3, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING); gtk_tree_view_set_model (list, GTK_TREE_MODEL (liststore)); GtkCellRenderer *check_renderer = gtk_cell_renderer_toggle_new (); gtk_cell_renderer_toggle_set_radio (GTK_CELL_RENDERER_TOGGLE (check_renderer), FALSE); g_signal_connect (G_OBJECT (check_renderer), "toggled", (GCallback) _cell_toggled_cb, liststore); gtk_tree_view_insert_column_with_attributes (list, 0, NULL, check_renderer, "active", 0, NULL); gtk_tree_view_insert_column_with_attributes (list, 1, NULL, gtk_cell_renderer_text_new (), "text", 1, NULL); } GList * ol_lyric_source_list_get_active_id_list (GtkTreeView *list) { ol_assert_ret (list != NULL, NULL); ol_assert_ret (GTK_IS_TREE_VIEW (list), NULL); GtkTreeModel *liststore = gtk_tree_view_get_model (list); GtkTreeIter iter; gboolean valid; GList *id_list = NULL; for (valid = gtk_tree_model_get_iter_first (liststore, &iter); valid; valid = gtk_tree_model_iter_next (liststore, &iter)) { gboolean enabled; gchar *id; gtk_tree_model_get (liststore, &iter, 0, &enabled, 2, &id, -1); if (enabled) id_list = g_list_prepend (id_list, id); else g_free (id); } return g_list_reverse (id_list); } void ol_lyric_source_list_set_info_list (GtkTreeView *list, GList *info_list) { ol_assert (GTK_IS_TREE_VIEW (list)); ol_log_func (); GtkListStore *liststore = GTK_LIST_STORE (gtk_tree_view_get_model (list)); GList *info_iter; gtk_list_store_clear (GTK_LIST_STORE (liststore)); for (info_iter = info_list; info_iter != NULL; info_iter = g_list_next (info_iter)) { GtkTreeIter iter = {0}; OlLyricSourceInfo *source_info = info_iter->data; gtk_list_store_append (liststore, &iter); gtk_list_store_set (liststore, &iter, 0, ol_lyric_source_info_get_enabled (source_info), 1, ol_lyric_source_info_get_name (source_info), 2, ol_lyric_source_info_get_id (source_info), -1); } } osdlyrics-0.5.5~rc1+dfsg1/src/ol_lyric_source_list.h000066400000000000000000000032711345713535600225460ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2012 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_LYRIC_SOURCE_LIST_H_ #define _OL_LYRIC_SOURCE_LIST_H_ #include void ol_lyric_source_list_init (GtkTreeView *list); /** * Sets source list with source info * * The sources will be listed in the order of the position in the list. * @param list * @param info_list A list of OlLyricSourceInfo*. */ void ol_lyric_source_list_set_info_list (GtkTreeView *list, GList *info_list); /** * Gets a list of id of enabled sources * * The returned list can be passed as the `source_ids` parameter in * ol_lyric_source_search(). * * @param list The engine list widget * * @return A list of gchar*. The elements in the list are id of enabled * sources. You must free the elemenets with g_free(), and free the list. */ GList *ol_lyric_source_list_get_active_id_list (GtkTreeView *list); #endif /* _OL_LYRIC_SOURCE_LIST_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_lyrics.c000066400000000000000000000301561345713535600203130ustar00rootroot00000000000000#include #include "ol_lyrics.h" #include "ol_consts.h" #include "ol_debug.h" typedef struct _OlLyricsPrivate OlLyricsPrivate; struct _OlLyricsPrivate { int padding; }; enum { LYRICS_CHANGED_SIGNAL = 0, LAST_SIGNAL, }; static guint signals[LAST_SIGNAL] = { 0 }; #define OL_LYRICS_GET_PRIVATE(object) \ (G_TYPE_INSTANCE_GET_PRIVATE ((object), OL_TYPE_LYRICS, OlLyricsPrivate)) static void ol_lyrics_finalize (GObject *object); static void ol_lyrics_g_signal (GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *parameters); static OlLrc *ol_lyrics_get_lrc_from_variant (OlLyrics *proxy, GVariant *variant); G_DEFINE_TYPE (OlLyrics, ol_lyrics, G_TYPE_DBUS_PROXY); static void ol_lyrics_init (OlLyrics *proxy) { } static void ol_lyrics_finalize (GObject *object) { G_OBJECT_CLASS (ol_lyrics_parent_class)->finalize (object); } static void ol_lyrics_class_init (OlLyricsClass *klass) { GObjectClass *gobject_class; GDBusProxyClass *proxy_class; g_type_class_add_private (klass, sizeof (OlLyricsPrivate)); gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = ol_lyrics_finalize; /* gobject_class->get_property = org_osdlyrics_lyrics_proxy_get_property; */ /* gobject_class->set_property = org_osdlyrics_lyrics_proxy_set_property; */ proxy_class = G_DBUS_PROXY_CLASS (klass); proxy_class->g_signal = ol_lyrics_g_signal; /* proxy_class->g_properties_changed = ol_lyrics_g_properties_changed; */ signals[LYRICS_CHANGED_SIGNAL] = g_signal_new ("lyrics-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, 0, /* class_offset */ NULL, NULL, /* accumulator, accu_data */ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void ol_lyrics_g_signal (GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *parameters) { if (strcmp (signal_name, "CurrentLyricsChanged") == 0) { g_signal_emit (proxy, signals[LYRICS_CHANGED_SIGNAL], 0); } else { ol_errorf ("Unknown D-Bus signal: %s\n", signal_name); } } OlLyrics * ol_lyrics_new (GError **error) { GInitable *ret; ret = g_initable_new (OL_TYPE_LYRICS, NULL, /* cancellable */ error, "g-flags", G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, "g-name", OL_SERVICE_DAEMON, "g-bus-type", G_BUS_TYPE_SESSION, "g-object-path", OL_OBJECT_LYRICS, "g-interface-name", OL_IFACE_LYRICS, NULL); if (ret != NULL) return OL_LYRICS (ret); else return NULL; } void ol_lyrics_proxy_new_async (GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data, GError **error) { g_async_initable_new_async (OL_TYPE_LYRICS, G_PRIORITY_DEFAULT, cancellable, callback, user_data, "g-flags", G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, "g-name", OL_SERVICE_DAEMON, "g-bus-type", G_BUS_TYPE_SESSION, "g-object-path", OL_OBJECT_LYRICS, "g-interface-name", OL_IFACE_LYRICS, NULL); } OlLyrics * ol_lyrics_proxy_new_finish (GAsyncResult *res, GError **error) { GObject *ret; GObject *source_object; source_object = g_async_result_get_source_object (res); ret = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), res, error); g_object_unref (source_object); if (ret != NULL) return OL_LYRICS (ret); else return NULL; } static OlLrc * ol_lyrics_get_lrc_from_variant (OlLyrics *proxy, GVariant *variant) { OlLrc *lrc = NULL; gboolean found; gchar *uri; GVariant *metadata, *content; g_variant_get (variant, "(bs@a{ss}@aa{sv})", &found, &uri, &metadata, &content); if (found) { lrc = ol_lrc_new (proxy, uri); ol_lrc_set_attributes_from_variant (lrc, metadata); ol_lrc_set_content_from_variant (lrc, content); } g_free (uri); g_variant_unref (content); g_variant_unref (metadata); return lrc; } OlLrc * ol_lyrics_get_current_lyrics (OlLyrics *proxy) { ol_assert_ret (OL_IS_LYRICS (proxy), NULL); GError *error = NULL; OlLrc *lrc = NULL; GVariant *ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), "GetCurrentLyrics", NULL, /* parameters */ G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout_secs */ NULL, /* cancellable */ &error); if (ret) { lrc = ol_lyrics_get_lrc_from_variant (proxy, ret); g_variant_unref (ret); } else { ol_errorf ("Cannot get current lyrics from daemon: %s\n", error->message); g_error_free (error); } return lrc; } OlLrc * ol_lyrics_get_lyrics (OlLyrics *proxy, OlMetadata *metadata) { ol_assert_ret (OL_IS_LYRICS (proxy), NULL); ol_assert_ret (metadata != NULL, NULL); GError *error = NULL; OlLrc *lrc = NULL; GVariant *ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), "GetLyrics", g_variant_new ("(@a{sv})", ol_metadata_to_variant (metadata)), G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout_secs */ NULL, /* cancellable */ &error); if (ret) { lrc = ol_lyrics_get_lrc_from_variant (proxy, ret); g_variant_unref (ret); } else { ol_errorf ("Cannot get lyrics from daemon: %s\n", error->message); g_error_free (error); } return lrc; } static gboolean ol_lyrics_get_raw_lyrics_from_variant (GVariant *variant, char **uri, char **content) { gchar *vuri, *vcontent; gboolean found; g_variant_get (variant, "(bss)", &found, &vuri, &vcontent); if (found) { if (uri) *uri = vuri; else g_free (vuri); if (content) *content = vcontent; else g_free (vcontent); } else { g_free (vuri); g_free (vcontent); if (uri) *uri = NULL; if (content) *content = NULL; } return found; } gboolean ol_lyrics_get_raw_lyrics (OlLyrics *proxy, OlMetadata *metadata, char **uri, char **content) { ol_assert_ret (OL_IS_LYRICS (proxy), FALSE); ol_assert_ret (metadata != NULL, FALSE); GError *error = NULL; gboolean found = FALSE; GVariant *ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), "GetRawLyrics", g_variant_new ("(@a{sv})", ol_metadata_to_variant (metadata)), G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout_secs */ NULL, /* cancellable */ &error); if (ret) { found = ol_lyrics_get_raw_lyrics_from_variant (ret, uri, content); g_variant_unref (ret); } else { ol_errorf ("Cannot get lyrics from daemon: %s\n", error->message); g_error_free (error); } return found; } gboolean ol_lyrics_get_current_raw_lyrics (OlLyrics *proxy, char **uri, char **content) { ol_assert_ret (OL_IS_LYRICS (proxy), FALSE); GError *error = NULL; gboolean found = FALSE; GVariant *ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), "GetCurrentRawLyrics", NULL, /* parameters */ G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout_secs */ NULL, /* cancellable */ &error); if (ret) { found = ol_lyrics_get_raw_lyrics_from_variant (ret, uri, content); g_variant_unref (ret); } else { ol_errorf ("Cannot get lyrics from daemon: %s\n", error->message); g_error_free (error); } return found; } gchar * ol_lyrics_set_content (OlLyrics *proxy, OlMetadata *metadata, const char *content, GError **error) { ol_assert_ret (OL_IS_LYRICS (proxy), NULL); ol_assert_ret (metadata != NULL, NULL); ol_assert_ret (content != NULL, NULL); GVariant *ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), "SetLyricContent", g_variant_new ("(@a{sv}@ay)", ol_metadata_to_variant (metadata), g_variant_new_bytestring (content)), G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout_secs */ NULL, /* cancellable */ error); if (ret) { gchar *uri = NULL; g_variant_get (ret, "(s)", &uri); g_variant_unref (ret); return uri; } else { return NULL; } } gboolean ol_lyrics_assign (OlLyrics *proxy, OlMetadata *metadata, const char *uri, GError **error) { ol_assert_ret (OL_IS_LYRICS (proxy), FALSE); ol_assert_ret (metadata != NULL, FALSE); gboolean result = FALSE; GVariant *ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), "AssignLyricFile", g_variant_new ("(@a{sv}s)", ol_metadata_to_variant (metadata), uri), G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout_secs */ NULL, /* cancellable */ error); if (ret) { result = TRUE; g_variant_unref (ret); } return result; } gboolean ol_lyrics_set_offset (OlLyrics *proxy, const char *uri, gint offset) { ol_assert_ret (OL_IS_LYRICS (proxy), FALSE); ol_assert_ret (uri != NULL, FALSE); GError *error = NULL; GVariant *ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), "SetOffset", g_variant_new ("(si)", uri, offset), G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout_secs */ NULL, /* cancellable */ &error); if (ret) { g_variant_unref (ret); return TRUE; } else { ol_errorf ("Failed to set offset: %s\n", error->message); g_error_free (error); return FALSE; } } osdlyrics-0.5.5~rc1+dfsg1/src/ol_lyrics.h000066400000000000000000000141601345713535600203150ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef __OL_LYRICS_H__ #define __OL_LYRICS_H__ #include #include "ol_metadata.h" G_BEGIN_DECLS #define OL_TYPE_LYRICS \ (ol_lyrics_get_type ()) #define OL_LYRICS(obj) \ (G_TYPE_CHECK_INSTANCE_CAST (obj, OL_TYPE_LYRICS, OlLyrics)) #define OL_LYRICS_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST (klass, OL_TYPE_LYRICS, OlLyricsClass)) #define OL_IS_LYRICS(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE (obj, OL_TYPE_LYRICS)) #define OL_IS_LYRICS_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE (klass, OL_TYPE_LYRICS)) #define OL_LYRICS_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), OL_TYPE_LYRICS, OlLyricsClass)) typedef struct _OlLyrics OlLyrics; struct _OlLyrics { GDBusProxy parent; }; typedef struct _OlLyricsClass OlLyricsClass; struct _OlLyricsClass { GDBusProxyClass parent_class; }; #include "ol_lrc.h" GType ol_lyrics_get_type (void) G_GNUC_CONST; OlLyrics *ol_lyrics_new (GError **error); void ol_lyrics_proxy_new_async (GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data, GError **error); OlLyrics *ol_lyrics_proxy_new_finish (GAsyncResult *res, GError **error); /* D-Bus method calls: */ /** * Gets the lyrics of current playing track * * @param proxy * * @return NULL if no lyrics found. Otherwise return an #OlLrc object * representing the lyrics. You should use g_object_unref() to free * the returned #OlLrc object. */ OlLrc *ol_lyrics_get_current_lyrics (OlLyrics *proxy); /** * Gets the lyrics assigned to the given metadata * * @param proxy * @param metadata The metadata, must not be #NULL * * @return NULL if no lyrics found. Otherwise return an #OlLrc object * representing the lyrics. You should use g_object_unref() to free * the returned #OlLrc object. */ OlLrc *ol_lyrics_get_lyrics (OlLyrics *proxy, OlMetadata *metadata); /** * Gets the content of the LRC file assigned to the specified metadata in * plain text. * * @param proxy * @param metadata The metadata, must not be #NULL. * @param uri The return location of the uri of the LRC file. Should be freed with * g_free(). Can be #NULL. * @param content The return location of the content of the LRC file. Should be * freed with g_free(). Can be #NULL. * * @return #TRUE if there are LRC file assigned to #metadata. If #metadata is * assigned to not show any lyrics, #TRUE will be returned and #content will * pointed to an empty string rather than #NULL. */ gboolean ol_lyrics_get_raw_lyrics (OlLyrics *proxy, OlMetadata *metadata, char **uri, char **content); /** * Gets the content of the LRC file assigned to the current track in * plain text. * * @param proxy * @param uri The return location of the uri of the LRC file. Should be freed with * g_free(). Can be #NULL. * @param content The return location of the content of the LRC file. Should be * freed with g_free(). Can be #NULL. * * @return #TRUE if there are LRC file assigned to #metadata. If #metadata is * assigned to not show any lyrics, #TRUE will be returned and #content will * pointed to an empty string rather than #NULL. */ gboolean ol_lyrics_get_current_raw_lyrics (OlLyrics *proxy, char **uri, char **content); /** * Sets the content of LRC file for given metadata. * * @param proxy * @param metadata The metadata, must not be #NULL. * @param content The content of the LRC file, must not be #NULL * @param error * * @return If succeed, the URI of the new LRC file of the content will be returned. * If failed, #NULL will be returned and the error will be set. The returned * URI should be freed with g_free(). */ gchar *ol_lyrics_set_content (OlLyrics *proxy, OlMetadata *metadata, const char *content, GError **error); /** * Assigns an URI to given metadata. * * The URI should be valid URI format defined in the D-Bus specification of the * daemon * @param proxy * @param metadata The metadata to be assigned. Must not be #NULL. * @param uri The URI of the LRC file. Must not be #NULL. * @param error * * @return If success, return #TRUE. Otherwise return #FALSE and #error will be * set. */ gboolean ol_lyrics_assign (OlLyrics *proxy, OlMetadata *metadata, const char *uri, GError **error); /** * Sets the offset of a given LRC file. * * @param proxy * @param uri The URI of the LRC file. Must not be #NULL. * @param offset The offset. * * @return #TRUE if success. Otherwise return #FALSE and #error will be * set. */ gboolean ol_lyrics_set_offset (OlLyrics *proxy, const char *uri, gint offset); G_END_DECLS #endif /* __OL_LYRICS_H__ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_main.c000066400000000000000000000643171345713535600177400ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include #include #include #include #include #include #include "config.h" #include "ol_lrc.h" #include "ol_player.h" #include "ol_lyrics.h" #include "ol_utils.h" #include "ol_lyric_candidate_selector.h" #include "ol_lyric_source.h" #include "ol_trayicon.h" #include "ol_intl.h" #include "ol_config_updater.h" #include "ol_config_proxy.h" #include "ol_consts.h" #include "ol_display_module.h" #include "ol_keybindings.h" #include "ol_stock.h" #include "ol_app.h" #include "ol_notify.h" #include "ol_debug.h" #include "ol_player_chooser.h" #define REFRESH_INTERVAL 100 #define INFO_INTERVAL 500 #define TIMEOUT_WAIT_LAUNCH 5000 gboolean _arg_debug_cb (const gchar *option_name, const gchar *value, gpointer data, GError **error); static gboolean _arg_version; static GOptionEntry cmdargs[] = { { "debug", 'd', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, _arg_debug_cb, N_ ("The level of debug messages to log, can be 'none', 'error', 'debug', or 'info'"), "level" }, { "version", 'v', 0, G_OPTION_ARG_NONE, &_arg_version, N_ ("Show version information"), NULL}, { NULL } }; static guint name_watch_id = 0; static guint position_timer = 0; static OlLyricSource *lyric_source = NULL; static OlLyricSourceSearchTask *search_task = NULL; static OlLyricSourceDownloadTask *download_task = NULL; static OlPlayer *player = NULL; static OlMetadata *current_metadata = NULL; static OlLrc *current_lrc = NULL; static OlLyrics *lyrics_proxy = NULL; static struct OlDisplayModule *display_module_osd = NULL; static struct OlDisplayModule *display_module_scroll = NULL; static gboolean initialized = FALSE; static enum _PlayerLostAction { ACTION_NONE = 0, ACTION_LAUNCH_DEFAULT, ACTION_CHOOSE_PLAYER, ACTION_WAIT_LAUNCH, ACTION_QUIT, } player_lost_action = ACTION_LAUNCH_DEFAULT; static OlPlayerChooser *player_chooser = NULL; #define CALL_DISPLAY_MODULES(func, args...) \ do \ { \ if (display_module_osd) func(display_module_osd, ##args); \ if (display_module_scroll) func(display_module_scroll, ##args); \ } while (0) static void _initialize (int argc, char **argv); static void _wait_for_player_launch (void); static void _player_chooser_response_cb (GtkDialog *dialog, gint response_id, gpointer user_data); /* static void _search_callback (struct OlLrcFetchResult *result, */ /* void *userdata); */ /* static void _download_callback (struct OlLrcDownloadResult *result); */ static void _display_mode_changed (OlConfigProxy *config, const gchar *key, gpointer userdata); static void _init_dbus_connection (void); static void _init_dbus_connection_done (void); static void _init_player (void); static void _init_lyrics_proxy (void); static void _client_name_acquired_cb (GDBusConnection *connection, const gchar *name, gpointer user_data); static void _client_name_lost_cb (GDBusConnection *connection, const gchar *name, gpointer user_data); static void _name_appeared_cb (GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data); static void _name_vanished_cb (GDBusConnection *connection, const gchar *name, gpointer user_data); static void _ping_daemon (void); static void _ping_daemon_cb (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data); static void _start_daemon_cb (GObject *source_object, GAsyncResult *res, gpointer user_data); static void _track_changed_cb (void); static void _status_changed_cb (void); static void _player_lost_cb (void); static void _player_connected_cb (void); static void _update_position (void); static void _start_position_timer (void); static void _stop_position_timer (void); static void _change_lrc (void); static void _cancel_source_task (void); static void _search_complete_cb (OlLyricSourceSearchTask *task, enum OlLyricSourceStatus status, GList *results, gpointer userdata); static void _search_started_cb (OlLyricSourceSearchTask *task, const gchar *sourceid, const gchar *sourcename, gpointer userdata); static void _download_complete_cb (OlLyricSourceDownloadTask *task, enum OlLyricSourceStatus status, const gchar *content, guint len, gpointer userdata); static void _display_mode_changed (OlConfigProxy *config, const gchar *key, gpointer userdata) { gboolean is_osd = strcmp(key, "General/display-mode-osd") == 0; struct OlDisplayModule **display_module = (is_osd ? &display_module_osd : &display_module_scroll); gboolean display_enabled = ol_config_proxy_get_bool (config, key); if ((*display_module == NULL) != display_enabled) return; /* Nothing to do */ if (display_enabled) { *display_module = ol_display_module_new (is_osd ? "OSD" : "scroll", player); ol_display_module_set_lrc (*display_module, current_lrc); _update_position (); } else { ol_display_module_free (*display_module); *display_module = NULL; } } static void _download_complete_cb (OlLyricSourceDownloadTask *task, enum OlLyricSourceStatus status, const gchar *content, guint len, gpointer userdata) { if (task == download_task) { if (status == OL_LYRIC_SOURCE_STATUS_SUCCESS) { GError *error = NULL; gchar *uri = ol_lyrics_set_content (lyrics_proxy, current_metadata, content, &error); if (!uri) { ol_errorf ("Set content failed: %s\n", error->message); g_error_free (error); } else { ol_debugf ("Set content to %s\n", uri); g_free (uri); } } else if (status == OL_LYRIC_SOURCE_STATUS_FALIURE) { CALL_DISPLAY_MODULES (ol_display_module_download_fail_message, _("Fail to download lyric")); } download_task = NULL; } g_object_unref (task); } static void _do_download (OlLyricSourceCandidate *candidate, const OlMetadata *metadata) { if (!ol_metadata_equal (metadata, current_metadata)) return; download_task = ol_lyric_source_download (lyric_source, candidate); g_object_ref (download_task); CALL_DISPLAY_MODULES (ol_display_module_set_message, _("Downloading lyric"), -1); g_signal_connect (G_OBJECT (download_task), "complete", G_CALLBACK (_download_complete_cb), NULL); } static void _search_complete_cb (OlLyricSourceSearchTask *task, enum OlLyricSourceStatus status, GList *results, gpointer userdata) { if (task == search_task) { if (status == OL_LYRIC_SOURCE_STATUS_SUCCESS && results != NULL) { CALL_DISPLAY_MODULES (ol_display_module_clear_message); /* TODO: show lyric select ui */ ol_lyric_candidate_selector_show (results, current_metadata, _do_download); } else if ((status == OL_LYRIC_SOURCE_STATUS_SUCCESS && results == NULL) || status == OL_LYRIC_SOURCE_STATUS_FALIURE) { CALL_DISPLAY_MODULES (ol_display_module_search_fail_message, _("Lyrics not found")); } search_task = NULL; } g_object_unref (task); } static void _search_started_cb (OlLyricSourceSearchTask *task, const gchar *sourceid, const gchar *sourcename, gpointer userdata) { if (task == search_task) { char *msg = g_strdup_printf (_("Searching lyrics from %s"), sourcename); CALL_DISPLAY_MODULES (ol_display_module_search_message, msg); g_free (msg); } } gboolean ol_app_download_lyric (OlMetadata *metadata) { ol_log_func (); _cancel_source_task (); search_task = ol_lyric_source_search_default (lyric_source, metadata); g_object_ref (search_task); g_signal_connect (G_OBJECT (search_task), "complete", G_CALLBACK (_search_complete_cb), NULL); g_signal_connect (G_OBJECT (search_task), "started", G_CALLBACK (_search_started_cb), NULL); return TRUE; } OlLrc * ol_app_get_current_lyric () { ol_log_func (); return current_lrc; } gboolean ol_app_assign_lrcfile (OlMetadata *metadata, const char *filepath, gboolean update) { ol_log_func (); ol_assert_ret (metadata != NULL, FALSE); ol_assert_ret (filepath == NULL || ol_path_is_file (filepath), FALSE); if (update) { char *uri = NULL; if (filepath == NULL) uri = g_strdup ("none:"); else uri = g_filename_to_uri (filepath, NULL, NULL); GError *error = NULL; if (!ol_lyrics_assign (lyrics_proxy, metadata, uri, &error)) { ol_errorf ("Cannot assign lyric file: %s\n", error->message); g_error_free (error); } } return TRUE; } static void _track_changed_cb (void) { ol_log_func (); ol_player_get_metadata (player, current_metadata); _change_lrc (); OlConfigProxy *config = ol_config_proxy_get_instance (); if (ol_config_proxy_get_bool (config, "General/notify-music")) ol_notify_music_change (current_metadata, ol_player_get_icon_path (player)); } static void _cancel_source_task (void) { if (search_task) { g_signal_handlers_disconnect_by_func (search_task, _search_complete_cb, NULL); g_signal_handlers_disconnect_by_func (search_task, _search_started_cb, NULL); ol_lyric_source_task_cancel (OL_LYRIC_SOURCE_TASK (search_task)); g_object_unref (search_task); search_task = NULL; } if (download_task) { ol_lyric_source_task_cancel (OL_LYRIC_SOURCE_TASK (download_task)); g_signal_handlers_disconnect_by_func (download_task, _download_complete_cb, NULL); g_object_unref (download_task); download_task = NULL; } } static void _change_lrc (void) { _cancel_source_task (); if (current_lrc) g_object_unref (current_lrc); current_lrc = ol_lyrics_get_current_lyrics (lyrics_proxy); CALL_DISPLAY_MODULES (ol_display_module_set_lrc, current_lrc); _update_position (); if (current_lrc == NULL && !ol_is_string_empty (ol_metadata_get_title (current_metadata))) ol_app_download_lyric (current_metadata); } static void _status_changed_cb (void) { ol_log_func (); enum OlPlayerStatus status = ol_player_get_status (player); switch (status) { case OL_PLAYER_PLAYING: case OL_PLAYER_PAUSED: _start_position_timer (); break; case OL_PLAYER_STOPPED: _stop_position_timer (); break; default: /* In case of the daemon sent a wrong status but still playing, it's better to keep updating the position. */ _start_position_timer (); break; } ol_trayicon_status_changed (ol_player_get_status (player)); } static gboolean _player_launch_timeout (gpointer userdata) { _player_lost_cb (); return FALSE; } static void _wait_for_player_launch (void) { player_lost_action = ACTION_WAIT_LAUNCH; g_timeout_add (TIMEOUT_WAIT_LAUNCH, _player_launch_timeout, NULL); } static void _player_chooser_response_cb (GtkDialog *dialog, gint response_id, gpointer user_data) { ol_assert (GTK_IS_DIALOG (dialog)); switch (response_id) { case OL_PLAYER_CHOOSER_RESPONSE_LAUNCH: _wait_for_player_launch (); break; case GTK_RESPONSE_DELETE_EVENT: case GTK_RESPONSE_CLOSE: gtk_widget_hide (GTK_WIDGET (dialog)); if (!ol_player_is_connected (player)) gtk_main_quit (); break; default: ol_errorf ("Unknown response id: %d\n", response_id); } } static void _player_lost_cb (void) { /* try to connect to other player first */ if (ol_player_is_connected (player)) return; _stop_position_timer (); switch (player_lost_action) { case ACTION_LAUNCH_DEFAULT: case ACTION_CHOOSE_PLAYER: { if (player_lost_action == ACTION_LAUNCH_DEFAULT) { OlConfigProxy *config = ol_config_proxy_get_instance (); char *player_cmd = ol_config_proxy_get_string (config, "General/startup-player"); if (!ol_is_string_empty (player_cmd)) { ol_debugf ("Running %s\n", player_cmd); ol_launch_app (player_cmd); _wait_for_player_launch (); g_free (player_cmd); break; } else { g_free (player_cmd); } } if (!player_chooser) { /* TODO: */ /* GList *supported_players = ol_player_get_support_players (); */ GList *supported_players = NULL; player_chooser = OL_PLAYER_CHOOSER (ol_player_chooser_new (supported_players)); g_signal_connect (player_chooser, "response", G_CALLBACK (_player_chooser_response_cb), NULL); ol_player_chooser_set_info_by_state (player_chooser, OL_PLAYER_CHOOSER_STATE_NO_PLAYER); } else { ol_player_chooser_set_info_by_state (player_chooser, OL_PLAYER_CHOOSER_STATE_LAUNCH_FAIL); } gtk_widget_show (GTK_WIDGET (player_chooser)); player_lost_action = ACTION_NONE; break; } case ACTION_QUIT: printf (_("No supported player is running, exit.\n")); gtk_main_quit (); break; default: break; } } static void _player_connected_cb (void) { if (player_chooser != NULL && gtk_widget_get_visible (GTK_WIDGET (player_chooser))) ol_player_chooser_set_info_by_state (player_chooser, OL_PLAYER_CHOOSER_STATE_CONNECTED); player_lost_action = ACTION_QUIT; _start_position_timer (); } static gint _position_timer_cb (gpointer data) { _update_position (); return TRUE; } static void _update_position (void) { if (current_lrc == NULL) return; guint64 time = 0; ol_player_get_position (player, &time); CALL_DISPLAY_MODULES (ol_display_module_set_played_time, time); } OlPlayer* ol_app_get_player (void) { return player; } OlLyricSource * ol_app_get_lyric_source (void) { return lyric_source; } OlLyrics * ol_app_get_lyrics_proxy (void) { return lyrics_proxy; } OlMetadata* ol_app_get_current_music (void) { return current_metadata; } void ol_app_adjust_lyric_offset (int offset_ms) { ol_log_func (); OlLrc *lrc = ol_app_get_current_lyric (); if (lrc == NULL) return; int old_offset = ol_lrc_get_offset (lrc); int new_offset = old_offset - offset_ms; ol_lrc_set_offset (lrc, new_offset); } static void _parse_cmd_args (int *argc, char ***argv) { ol_log_func (); GError *error = NULL; GOptionContext *context; context = g_option_context_new ("- Display your lyrics"); g_option_context_add_main_entries (context, cmdargs, PACKAGE); g_option_context_add_group (context, gtk_get_option_group (TRUE)); if (!g_option_context_parse (context, argc, argv, &error)) { ol_errorf ("option parsing failed: %s\n", error->message); } if (_arg_version) { printf ("%s %s\n", PROGRAM_NAME, VERSION); exit (0); } g_option_context_free (context); } gboolean _arg_debug_cb (const gchar *option_name, const gchar *value, gpointer data, GError **error) { if (value == NULL) value = "debug"; if (strcmp (value, "none") == 0) { ol_log_set_level (OL_LOG_NONE); } else if (strcmp (value, "error") == 0) { ol_log_set_level (OL_ERROR); } else if (strcmp (value, "debug") == 0) { ol_log_set_level (OL_DEBUG); } else if (strcmp (value, "info") == 0) { ol_log_set_level (OL_INFO); } else { g_set_error_literal (error, g_quark_from_string (PACKAGE_NAME), 1, N_ ("debug level should be one of ``none'', ``error'', ``debug'', or ``info''")); return FALSE; } return TRUE; } static void _initialize (int argc, char **argv) { ol_log_func (); #if ENABLE_NLS /* Set the text message domain. */ bindtextdomain (PACKAGE, LOCALEDIR); bind_textdomain_codeset(PACKAGE, "UTF-8"); #endif #ifndef GLIB_VERSION_2_32 g_thread_init(NULL); #endif gtk_init (&argc, &argv); _parse_cmd_args (&argc, &argv); g_set_prgname (_(PROGRAM_NAME)); initialized = FALSE; g_bus_own_name (G_BUS_TYPE_SESSION, OL_CLIENT_BUS_NAME, G_BUS_NAME_OWNER_FLAGS_NONE, NULL, /* bus_acquired_handler */ _client_name_acquired_cb, _client_name_lost_cb, NULL, /* user_data */ NULL); /* user_data_free_func */ } static void _client_name_acquired_cb (GDBusConnection *connection, const gchar *name, gpointer user_data) { ol_debugf ("Client bus name acquired\n"); _init_dbus_connection (); } static void _client_name_lost_cb (GDBusConnection *connection, const gchar *name, gpointer user_data) { ol_debugf ("Client bus name lost\n"); printf ("%s\n", _("Another OSD Lyrics is running, exit.")); exit (1); } static void _name_appeared_cb (GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data) { ol_debug ("Daemon appeared"); _ping_daemon (); if (!initialized) _init_dbus_connection_done (); } static void _ping_daemon (void) { ol_debugf ("Starting to ping the daemon\n"); GError *error = NULL; GDBusProxy *daemon_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, NULL, /* interface_info */ OL_SERVICE_DAEMON, OL_OBJECT_DAEMON, OL_IFACE_DAEMON, NULL, /* cancellable */ &error); if (daemon_proxy != NULL) { g_dbus_proxy_call (daemon_proxy, "Hello", g_variant_new ("(s)", OL_CLIENT_BUS_NAME), G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, /* cancellable */ (GAsyncReadyCallback)_ping_daemon_cb, NULL); /* user_data */ } else { ol_errorf ("Cannot create daemon proxy: %s\n", error->message); g_error_free (error); } } static void _ping_daemon_cb (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data) { GError *error = NULL; GVariant *ret = g_dbus_proxy_call_finish (proxy, res, &error); if (ret) { ol_debugf ("Succeed to ping the daemon\n"); g_variant_unref (ret); } else { ol_errorf ("Fail to ping the daemon: %s\n", error->message); g_error_free (error); } g_object_unref (proxy); } static void _name_vanished_cb (GDBusConnection *connection, const gchar *name, gpointer user_data) { ol_debugf ("Daemon lost, try to activate it\n"); g_dbus_connection_call (connection, "org.freedesktop.DBus", /* bus name */ "/org/freedesktop/DBus", /* object path */ "org.freedesktop.DBus", /* interface name */ "StartServiceByName", /* method name */ g_variant_new ("(su)", OL_SERVICE_DAEMON, 0), G_VARIANT_TYPE ("(u)"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, (GAsyncReadyCallback) _start_daemon_cb, NULL); ol_debugf ("Daemon appeared\n"); } static void _start_daemon_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { GVariant *result; GError *error = NULL; result = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error); if (result) { guint32 start_service_result; g_variant_get (result, "(u)", &start_service_result); if (start_service_result != 1 && /* DBUS_START_REPLY_SUCCESS */ start_service_result != 2) /* DBUS_START_REPLY_ALREADY_RUNNING */ { ol_errorf ("Unexpected reply %u from StartServiceByName() method\n", (guint)start_service_result); } /* We should do nothing, _name_appeared_cb will be called by name watch */ } else { ol_errorf ("Unable to start daemon: %s\n", error->message); g_error_free (error); /* TODO: notify the user that there is a fatal error */ } } static void _init_dbus_connection (void) { /* Activate the daemon */ name_watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, OL_SERVICE_DAEMON, G_BUS_NAME_WATCHER_FLAGS_NONE, _name_appeared_cb, _name_vanished_cb, NULL, /* user_data */ NULL); /* user_data_free_func */ } static void _init_player (void) { player = ol_player_new (); g_object_ref_sink (player); g_signal_connect (player, "track-changed", _track_changed_cb, NULL); g_signal_connect (player, "status-changed", _status_changed_cb, NULL); g_signal_connect (player, "player-lost", _player_lost_cb, NULL); g_signal_connect (player, "player-connected", _player_connected_cb, NULL); } static void _init_lyrics_proxy (void) { lyrics_proxy = ol_lyrics_new (NULL); g_object_ref_sink (lyrics_proxy); g_signal_connect (lyrics_proxy, "lyrics-changed", G_CALLBACK (_change_lrc), NULL); } static void _init_lyric_source (void) { lyric_source = ol_lyric_source_new (); g_object_ref_sink (lyric_source); } static void _init_dbus_connection_done (void) { ol_config_update (); current_metadata = ol_metadata_new (); _init_player (); _init_lyrics_proxy (); _init_lyric_source (); ol_stock_init (); ol_trayicon_init (); ol_notify_init (); ol_keybinding_init (); ol_display_module_init (); /* Initialize display modules */ OlConfigProxy *config = ol_config_proxy_get_instance (); _display_mode_changed (config, "General/display-mode-osd", NULL); _display_mode_changed (config, "General/display-mode-scroll", NULL); g_signal_connect (config, "changed::General/display-mode-osd", G_CALLBACK (_display_mode_changed), NULL); g_signal_connect (config, "changed::General/display-mode-scroll", G_CALLBACK (_display_mode_changed), NULL); if (ol_player_is_connected (player)) { _player_connected_cb (); } else { _player_lost_cb (); } _track_changed_cb (); _status_changed_cb (); initialized = TRUE; } static void _uninitialize (void) { _stop_position_timer (); g_signal_handlers_disconnect_by_func (player, _status_changed_cb, NULL); g_signal_handlers_disconnect_by_func (player, _track_changed_cb, NULL); g_object_unref (player); g_object_unref (lyrics_proxy); player = NULL; _cancel_source_task (); g_object_unref (lyric_source); lyric_source = NULL; g_bus_unwatch_name (name_watch_id); ol_metadata_free (current_metadata); current_metadata = NULL; ol_notify_unload (); CALL_DISPLAY_MODULES (ol_display_module_free); if (player_chooser != NULL) { gtk_widget_destroy (GTK_WIDGET (player_chooser)); player_chooser = NULL; } ol_display_module_unload (); ol_trayicon_free (); ol_config_proxy_unload (); } static void _start_position_timer (void) { if (!position_timer) position_timer = g_timeout_add (REFRESH_INTERVAL, _position_timer_cb, NULL); } static void _stop_position_timer (void) { if (position_timer) { g_source_remove (position_timer); position_timer = 0; } } int main (int argc, char **argv) { _initialize (argc, argv); gtk_main (); _uninitialize (); return 0; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_marshal.c000066400000000000000000000203241345713535600204310ustar00rootroot00000000000000 #include #ifdef G_ENABLE_DEBUG #define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) #define g_marshal_value_peek_char(v) g_value_get_schar (v) #define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) #define g_marshal_value_peek_int(v) g_value_get_int (v) #define g_marshal_value_peek_uint(v) g_value_get_uint (v) #define g_marshal_value_peek_long(v) g_value_get_long (v) #define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) #define g_marshal_value_peek_int64(v) g_value_get_int64 (v) #define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) #define g_marshal_value_peek_enum(v) g_value_get_enum (v) #define g_marshal_value_peek_flags(v) g_value_get_flags (v) #define g_marshal_value_peek_float(v) g_value_get_float (v) #define g_marshal_value_peek_double(v) g_value_get_double (v) #define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) #define g_marshal_value_peek_param(v) g_value_get_param (v) #define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) #define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) #define g_marshal_value_peek_object(v) g_value_get_object (v) #define g_marshal_value_peek_variant(v) g_value_get_variant (v) #else /* !G_ENABLE_DEBUG */ /* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. * Do not access GValues directly in your code. Instead, use the * g_value_get_*() functions */ #define g_marshal_value_peek_boolean(v) (v)->data[0].v_int #define g_marshal_value_peek_char(v) (v)->data[0].v_int #define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint #define g_marshal_value_peek_int(v) (v)->data[0].v_int #define g_marshal_value_peek_uint(v) (v)->data[0].v_uint #define g_marshal_value_peek_long(v) (v)->data[0].v_long #define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong #define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 #define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 #define g_marshal_value_peek_enum(v) (v)->data[0].v_long #define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong #define g_marshal_value_peek_float(v) (v)->data[0].v_float #define g_marshal_value_peek_double(v) (v)->data[0].v_double #define g_marshal_value_peek_string(v) (v)->data[0].v_pointer #define g_marshal_value_peek_param(v) (v)->data[0].v_pointer #define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer #define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer #define g_marshal_value_peek_object(v) (v)->data[0].v_pointer #define g_marshal_value_peek_variant(v) (v)->data[0].v_pointer #endif /* !G_ENABLE_DEBUG */ /* VOID:STRING,STRING (marshal:1) */ void ol_marshal_VOID__STRING_STRING (GClosure *closure, GValue *return_value G_GNUC_UNUSED, guint n_param_values, const GValue *param_values, gpointer invocation_hint G_GNUC_UNUSED, gpointer marshal_data) { typedef void (*GMarshalFunc_VOID__STRING_STRING) (gpointer data1, gpointer arg_1, gpointer arg_2, gpointer data2); register GMarshalFunc_VOID__STRING_STRING callback; register GCClosure *cc = (GCClosure*) closure; register gpointer data1, data2; g_return_if_fail (n_param_values == 3); if (G_CCLOSURE_SWAP_DATA (closure)) { data1 = closure->data; data2 = g_value_peek_pointer (param_values + 0); } else { data1 = g_value_peek_pointer (param_values + 0); data2 = closure->data; } callback = (GMarshalFunc_VOID__STRING_STRING) (marshal_data ? marshal_data : cc->callback); callback (data1, g_marshal_value_peek_string (param_values + 1), g_marshal_value_peek_string (param_values + 2), data2); } /* VOID:UINT,DOUBLE (marshal:2) */ void ol_marshal_VOID__UINT_DOUBLE (GClosure *closure, GValue *return_value G_GNUC_UNUSED, guint n_param_values, const GValue *param_values, gpointer invocation_hint G_GNUC_UNUSED, gpointer marshal_data) { typedef void (*GMarshalFunc_VOID__UINT_DOUBLE) (gpointer data1, guint arg_1, gdouble arg_2, gpointer data2); register GMarshalFunc_VOID__UINT_DOUBLE callback; register GCClosure *cc = (GCClosure*) closure; register gpointer data1, data2; g_return_if_fail (n_param_values == 3); if (G_CCLOSURE_SWAP_DATA (closure)) { data1 = closure->data; data2 = g_value_peek_pointer (param_values + 0); } else { data1 = g_value_peek_pointer (param_values + 0); data2 = closure->data; } callback = (GMarshalFunc_VOID__UINT_DOUBLE) (marshal_data ? marshal_data : cc->callback); callback (data1, g_marshal_value_peek_uint (param_values + 1), g_marshal_value_peek_double (param_values + 2), data2); } /* VOID:ENUM,POINTER (marshal:3) */ void ol_marshal_VOID__ENUM_POINTER (GClosure *closure, GValue *return_value G_GNUC_UNUSED, guint n_param_values, const GValue *param_values, gpointer invocation_hint G_GNUC_UNUSED, gpointer marshal_data) { typedef void (*GMarshalFunc_VOID__ENUM_POINTER) (gpointer data1, gint arg_1, gpointer arg_2, gpointer data2); register GMarshalFunc_VOID__ENUM_POINTER callback; register GCClosure *cc = (GCClosure*) closure; register gpointer data1, data2; g_return_if_fail (n_param_values == 3); if (G_CCLOSURE_SWAP_DATA (closure)) { data1 = closure->data; data2 = g_value_peek_pointer (param_values + 0); } else { data1 = g_value_peek_pointer (param_values + 0); data2 = closure->data; } callback = (GMarshalFunc_VOID__ENUM_POINTER) (marshal_data ? marshal_data : cc->callback); callback (data1, g_marshal_value_peek_enum (param_values + 1), g_marshal_value_peek_pointer (param_values + 2), data2); } /* VOID:ENUM,STRING,UINT (marshal:4) */ void ol_marshal_VOID__ENUM_STRING_UINT (GClosure *closure, GValue *return_value G_GNUC_UNUSED, guint n_param_values, const GValue *param_values, gpointer invocation_hint G_GNUC_UNUSED, gpointer marshal_data) { typedef void (*GMarshalFunc_VOID__ENUM_STRING_UINT) (gpointer data1, gint arg_1, gpointer arg_2, guint arg_3, gpointer data2); register GMarshalFunc_VOID__ENUM_STRING_UINT callback; register GCClosure *cc = (GCClosure*) closure; register gpointer data1, data2; g_return_if_fail (n_param_values == 4); if (G_CCLOSURE_SWAP_DATA (closure)) { data1 = closure->data; data2 = g_value_peek_pointer (param_values + 0); } else { data1 = g_value_peek_pointer (param_values + 0); data2 = closure->data; } callback = (GMarshalFunc_VOID__ENUM_STRING_UINT) (marshal_data ? marshal_data : cc->callback); callback (data1, g_marshal_value_peek_enum (param_values + 1), g_marshal_value_peek_string (param_values + 2), g_marshal_value_peek_uint (param_values + 3), data2); } osdlyrics-0.5.5~rc1+dfsg1/src/ol_marshal.h000066400000000000000000000037751345713535600204510ustar00rootroot00000000000000 #ifndef __ol_marshal_MARSHAL_H__ #define __ol_marshal_MARSHAL_H__ #include G_BEGIN_DECLS /* VOID:STRING,STRING (marshal:1) */ extern void ol_marshal_VOID__STRING_STRING (GClosure *closure, GValue *return_value, guint n_param_values, const GValue *param_values, gpointer invocation_hint, gpointer marshal_data); /* VOID:UINT,DOUBLE (marshal:2) */ extern void ol_marshal_VOID__UINT_DOUBLE (GClosure *closure, GValue *return_value, guint n_param_values, const GValue *param_values, gpointer invocation_hint, gpointer marshal_data); /* VOID:ENUM,POINTER (marshal:3) */ extern void ol_marshal_VOID__ENUM_POINTER (GClosure *closure, GValue *return_value, guint n_param_values, const GValue *param_values, gpointer invocation_hint, gpointer marshal_data); /* VOID:ENUM,STRING,UINT (marshal:4) */ extern void ol_marshal_VOID__ENUM_STRING_UINT (GClosure *closure, GValue *return_value, guint n_param_values, const GValue *param_values, gpointer invocation_hint, gpointer marshal_data); G_END_DECLS #endif /* __ol_marshal_MARSHAL_H__ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_md5.c000066400000000000000000000221331345713535600174670ustar00rootroot00000000000000/* md5.c * * The MD5 hash function, described in RFC 1321. */ /* nettle, low-level cryptographics library * * Copyright (C) 2001 Niels Möller * * The nettle 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. * * The nettle 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 the nettle library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111-1307, USA. */ /* Based on public domain code hacked by Colin Plumb, Andrew Kuchling, and * Niels Möller. */ #if HAVE_CONFIG_H # include "config.h" #endif #include #include #include "ol_md5.h" #define LE_READ_UINT32(p) \ ( (((guint32) (p)[3]) << 24) \ | (((guint32) (p)[2]) << 16) \ | (((guint32) (p)[1]) << 8) \ | ((guint32) (p)[0])) #define LE_WRITE_UINT32(p, i) \ do { \ (p)[3] = ((i) >> 24) & 0xff; \ (p)[2] = ((i) >> 16) & 0xff; \ (p)[1] = ((i) >> 8) & 0xff; \ (p)[0] = (i) & 0xff; \ } while(0) /* A block, treated as a sequence of 32-bit words. */ #define MD5_DATA_LENGTH 16 static void md5_transform(guint32 *digest, const guint32 *data); static void md5_block(struct md5_ctx *ctx, const guint8 *block); static void md5_final(struct md5_ctx *ctx); void md5_init(struct md5_ctx *ctx) { ctx->digest[0] = 0x67452301; ctx->digest[1] = 0xefcdab89; ctx->digest[2] = 0x98badcfe; ctx->digest[3] = 0x10325476; ctx->count_l = ctx->count_h = 0; ctx->index = 0; } void md5_update(struct md5_ctx *ctx, unsigned length, const guint8 *data) { if (ctx->index) { /* Try to fill partial block */ unsigned left = MD5_DATA_SIZE - ctx->index; if (length < left) { memcpy(ctx->block + ctx->index, data, length); ctx->index += length; return; /* Finished */ } else { memcpy(ctx->block + ctx->index, data, left); md5_block(ctx, ctx->block); data += left; length -= left; } } while (length >= MD5_DATA_SIZE) { md5_block(ctx, data); data += MD5_DATA_SIZE; length -= MD5_DATA_SIZE; } if ((ctx->index = length)) /* This assignment is intended */ /* Buffer leftovers */ memcpy(ctx->block, data, length); } void md5_digest(struct md5_ctx *ctx, unsigned length, guint8 *digest) { unsigned i; unsigned words; unsigned leftover; assert(length <= MD5_DIGEST_SIZE); md5_final(ctx); words = length / 4; leftover = length % 4; /* Little endian order */ for (i = 0; i < words; i++, digest += 4) LE_WRITE_UINT32(digest, ctx->digest[i]); if (leftover) { guint32 word; unsigned j; assert(i < _MD5_DIGEST_LENGTH); /* Still least significant byte first. */ for (word = ctx->digest[i], j = 0; j < leftover; j++, word >>= 8) digest[j] = word & 0xff; } md5_init(ctx); } /* MD5 functions */ #define F1(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) #define F2(x, y, z) F1((z), (x), (y)) #define F3(x, y, z) ((x) ^ (y) ^ (z)) #define F4(x, y, z) ((y) ^ ((x) | ~(z))) #define ROUND(f, w, x, y, z, data, s) \ ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) /* Perform the MD5 transformation on one full block of 16 32-bit * words. * * Compresses 20 (_MD5_DIGEST_LENGTH + MD5_DATA_LENGTH) words into 4 * (_MD5_DIGEST_LENGTH) words. */ static void md5_transform(guint32 *digest, const guint32 *data) { guint32 a, b, c, d; a = digest[0]; b = digest[1]; c = digest[2]; d = digest[3]; ROUND(F1, a, b, c, d, data[ 0] + 0xd76aa478, 7); ROUND(F1, d, a, b, c, data[ 1] + 0xe8c7b756, 12); ROUND(F1, c, d, a, b, data[ 2] + 0x242070db, 17); ROUND(F1, b, c, d, a, data[ 3] + 0xc1bdceee, 22); ROUND(F1, a, b, c, d, data[ 4] + 0xf57c0faf, 7); ROUND(F1, d, a, b, c, data[ 5] + 0x4787c62a, 12); ROUND(F1, c, d, a, b, data[ 6] + 0xa8304613, 17); ROUND(F1, b, c, d, a, data[ 7] + 0xfd469501, 22); ROUND(F1, a, b, c, d, data[ 8] + 0x698098d8, 7); ROUND(F1, d, a, b, c, data[ 9] + 0x8b44f7af, 12); ROUND(F1, c, d, a, b, data[10] + 0xffff5bb1, 17); ROUND(F1, b, c, d, a, data[11] + 0x895cd7be, 22); ROUND(F1, a, b, c, d, data[12] + 0x6b901122, 7); ROUND(F1, d, a, b, c, data[13] + 0xfd987193, 12); ROUND(F1, c, d, a, b, data[14] + 0xa679438e, 17); ROUND(F1, b, c, d, a, data[15] + 0x49b40821, 22); ROUND(F2, a, b, c, d, data[ 1] + 0xf61e2562, 5); ROUND(F2, d, a, b, c, data[ 6] + 0xc040b340, 9); ROUND(F2, c, d, a, b, data[11] + 0x265e5a51, 14); ROUND(F2, b, c, d, a, data[ 0] + 0xe9b6c7aa, 20); ROUND(F2, a, b, c, d, data[ 5] + 0xd62f105d, 5); ROUND(F2, d, a, b, c, data[10] + 0x02441453, 9); ROUND(F2, c, d, a, b, data[15] + 0xd8a1e681, 14); ROUND(F2, b, c, d, a, data[ 4] + 0xe7d3fbc8, 20); ROUND(F2, a, b, c, d, data[ 9] + 0x21e1cde6, 5); ROUND(F2, d, a, b, c, data[14] + 0xc33707d6, 9); ROUND(F2, c, d, a, b, data[ 3] + 0xf4d50d87, 14); ROUND(F2, b, c, d, a, data[ 8] + 0x455a14ed, 20); ROUND(F2, a, b, c, d, data[13] + 0xa9e3e905, 5); ROUND(F2, d, a, b, c, data[ 2] + 0xfcefa3f8, 9); ROUND(F2, c, d, a, b, data[ 7] + 0x676f02d9, 14); ROUND(F2, b, c, d, a, data[12] + 0x8d2a4c8a, 20); ROUND(F3, a, b, c, d, data[ 5] + 0xfffa3942, 4); ROUND(F3, d, a, b, c, data[ 8] + 0x8771f681, 11); ROUND(F3, c, d, a, b, data[11] + 0x6d9d6122, 16); ROUND(F3, b, c, d, a, data[14] + 0xfde5380c, 23); ROUND(F3, a, b, c, d, data[ 1] + 0xa4beea44, 4); ROUND(F3, d, a, b, c, data[ 4] + 0x4bdecfa9, 11); ROUND(F3, c, d, a, b, data[ 7] + 0xf6bb4b60, 16); ROUND(F3, b, c, d, a, data[10] + 0xbebfbc70, 23); ROUND(F3, a, b, c, d, data[13] + 0x289b7ec6, 4); ROUND(F3, d, a, b, c, data[ 0] + 0xeaa127fa, 11); ROUND(F3, c, d, a, b, data[ 3] + 0xd4ef3085, 16); ROUND(F3, b, c, d, a, data[ 6] + 0x04881d05, 23); ROUND(F3, a, b, c, d, data[ 9] + 0xd9d4d039, 4); ROUND(F3, d, a, b, c, data[12] + 0xe6db99e5, 11); ROUND(F3, c, d, a, b, data[15] + 0x1fa27cf8, 16); ROUND(F3, b, c, d, a, data[ 2] + 0xc4ac5665, 23); ROUND(F4, a, b, c, d, data[ 0] + 0xf4292244, 6); ROUND(F4, d, a, b, c, data[ 7] + 0x432aff97, 10); ROUND(F4, c, d, a, b, data[14] + 0xab9423a7, 15); ROUND(F4, b, c, d, a, data[ 5] + 0xfc93a039, 21); ROUND(F4, a, b, c, d, data[12] + 0x655b59c3, 6); ROUND(F4, d, a, b, c, data[ 3] + 0x8f0ccc92, 10); ROUND(F4, c, d, a, b, data[10] + 0xffeff47d, 15); ROUND(F4, b, c, d, a, data[ 1] + 0x85845dd1, 21); ROUND(F4, a, b, c, d, data[ 8] + 0x6fa87e4f, 6); ROUND(F4, d, a, b, c, data[15] + 0xfe2ce6e0, 10); ROUND(F4, c, d, a, b, data[ 6] + 0xa3014314, 15); ROUND(F4, b, c, d, a, data[13] + 0x4e0811a1, 21); ROUND(F4, a, b, c, d, data[ 4] + 0xf7537e82, 6); ROUND(F4, d, a, b, c, data[11] + 0xbd3af235, 10); ROUND(F4, c, d, a, b, data[ 2] + 0x2ad7d2bb, 15); ROUND(F4, b, c, d, a, data[ 9] + 0xeb86d391, 21); digest[0] += a; digest[1] += b; digest[2] += c; digest[3] += d; } static void md5_block(struct md5_ctx *ctx, const guint8 *block) { guint32 data[MD5_DATA_LENGTH]; unsigned i; /* Update block count */ if (!++ctx->count_l) ++ctx->count_h; /* Endian independent conversion */ for (i = 0; i<16; i++, block += 4) data[i] = LE_READ_UINT32(block); md5_transform(ctx->digest, data); } /* Final wrapup - pad to MD5_DATA_SIZE-byte boundary with the bit * pattern 1 0* (64-bit count of bits processed, LSB-first) */ static void md5_final(struct md5_ctx *ctx) { guint32 data[MD5_DATA_LENGTH]; unsigned i; unsigned words; i = ctx->index; /* Set the first char of padding to 0x80. This is safe since there * is always at least one byte free */ assert(i < MD5_DATA_SIZE); ctx->block[i++] = 0x80; /* Fill rest of word */ for( ; i & 3; i++) ctx->block[i] = 0; /* i is now a multiple of the word size 4 */ words = i >> 2; for (i = 0; i < words; i++) data[i] = LE_READ_UINT32(ctx->block + 4*i); if (words > (MD5_DATA_LENGTH-2)) { /* No room for length in this block. Process it and * pad with another one */ for (i = words ; i < MD5_DATA_LENGTH; i++) data[i] = 0; md5_transform(ctx->digest, data); for (i = 0; i < (MD5_DATA_LENGTH-2); i++) data[i] = 0; } else for (i = words ; i < MD5_DATA_LENGTH - 2; i++) data[i] = 0; /* There are 512 = 2^9 bits in one block * Little-endian order => Least significant word first */ data[MD5_DATA_LENGTH-1] = (ctx->count_h << 9) | (ctx->count_l >> 23); data[MD5_DATA_LENGTH-2] = (ctx->count_l << 9) | (ctx->index << 3); md5_transform(ctx->digest, data); } osdlyrics-0.5.5~rc1+dfsg1/src/ol_md5.h000066400000000000000000000033341345713535600174760ustar00rootroot00000000000000/* md5.h * * The MD5 hash function, described in RFC 1321. */ /* nettle, low-level cryptographics library * * Copyright (C) 2001 Niels Möller * * The nettle 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. * * The nettle 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 the nettle library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111-1307, USA. */ #ifndef NETTLE_MD5_H_INCLUDED #define NETTLE_MD5_H_INCLUDED #include /* Name mangling */ #define md5_init nettle_md5_init #define md5_update nettle_md5_update #define md5_digest nettle_md5_digest #define MD5_DIGEST_SIZE 16 #define MD5_DATA_SIZE 64 /* Digest is kept internally as 4 32-bit words. */ #define _MD5_DIGEST_LENGTH 4 struct md5_ctx { guint32 digest[_MD5_DIGEST_LENGTH]; guint32 count_l, count_h; /* Block count */ guint8 block[MD5_DATA_SIZE]; /* Block buffer */ unsigned index; /* Into buffer */ }; void md5_init(struct md5_ctx *ctx); void md5_update(struct md5_ctx *ctx, unsigned length, const guint8 *data); void md5_digest(struct md5_ctx *ctx, unsigned length, guint8 *digest); #endif /* NETTLE_MD5_H_INCLUDED */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_menu.c000066400000000000000000000315431345713535600177530ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ /** * @file ol_menu.c * @author Tiger Soldier * @date Sun Aug 16 16:18:41 2009 * * @brief Popup menu for OSD lyrics * * */ #include "ol_menu.h" #include "ol_intl.h" #include "ol_config_proxy.h" #include "ol_about.h" #include "ol_option.h" #include "ol_keybindings.h" #include "ol_commands.h" #include "ol_app.h" #include "ol_gui.h" #include "ol_search_dialog.h" #include "ol_player.h" #include "ol_debug.h" static GtkWidget *popup_menu = NULL; static struct Menu { GtkWidget *switch_osd; GtkWidget *switch_scroll; GtkWidget *lock; GtkWidget *hide; GtkWidget *play; GtkWidget *pause; GtkWidget *stop; GtkWidget *prev; GtkWidget *next; GtkWidget *preference; GtkWidget *about; GtkWidget *quit; } menu = {0}; void ol_menu_lock (GtkWidget *widget, gpointer data); void ol_menu_hide (GtkWidget *widget, gpointer data); void ol_menu_quit (GtkWidget *widget, gpointer data); void ol_menu_preference (GtkWidget *widget, gpointer data); void ol_menu_play (GtkWidget *widget, gpointer data); void ol_menu_pause (GtkWidget *widget, gpointer data); void ol_menu_stop (GtkWidget *widget, gpointer data); void ol_menu_prev (GtkWidget *widget, gpointer data); void ol_menu_next (GtkWidget *widget, gpointer data); void ol_menu_download (GtkWidget *widget, gpointer data); void ol_menu_no_lyric (GtkWidget *widget, gpointer data); void ol_menu_assign_lrc (GtkWidget *widget, gpointer data); void ol_menu_advance_lrc (GtkWidget *widget, gpointer data); void ol_menu_delay_lrc (GtkWidget *widget, gpointer data); void ol_menu_toggle_osd (GtkWidget *widget, gpointer data); void ol_menu_toggle_scrolling (GtkWidget *widget, gpointer data); static void _display_mode_osd_changed_cb (OlConfigProxy *config, const gchar *key, gpointer data); static void _display_mode_scroll_changed_cb (OlConfigProxy *config, const gchar *key, gpointer data); static void _visible_changed_cb (OlConfigProxy *config, const gchar *key, gpointer data); static void _locked_changed_cb (OlConfigProxy *config, const gchar *key, gpointer data); void ol_menu_advance_lrc (GtkWidget *widget, gpointer data) { ol_app_adjust_lyric_offset (-200); } void ol_menu_delay_lrc (GtkWidget *widget, gpointer data) { ol_app_adjust_lyric_offset (200); } void ol_menu_download (GtkWidget *widget, gpointer data) { /* if (ol_app_get_controller () != NULL) */ /* { */ OlMetadata *info = ol_app_get_current_music (); if (info != NULL) ol_search_dialog_show (); /* ol_app_download_lyric (info); */ /* } */ } void ol_menu_no_lyric (GtkWidget *widget, gpointer data) { OlMetadata *info = ol_app_get_current_music (); if (info != NULL) ol_app_assign_lrcfile (info, NULL, TRUE); } void ol_menu_assign_lrc (GtkWidget *widget, gpointer data) { static char *prev_path = NULL; OlMetadata *info = ol_app_get_current_music (); GtkFileFilter *lrc_filter = NULL; lrc_filter = gtk_file_filter_new (); gtk_file_filter_set_name (lrc_filter, _("LRC files")); gtk_file_filter_add_pattern (lrc_filter, "*.lrc"); if (info != NULL) { ol_debugf ("prev_path: %s\n", prev_path); GtkWidget *dialog = NULL; dialog = gtk_file_chooser_dialog_new (_("Choose LRC file to assign"), NULL, GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), lrc_filter); if (prev_path != NULL) gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), prev_path); if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) { char *filename; filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); ol_app_assign_lrcfile (info, filename, TRUE); g_free (filename); if (prev_path != NULL) g_free (prev_path); prev_path = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dialog)); } gtk_widget_destroy (dialog); dialog = NULL; } } static void _locked_changed_cb (OlConfigProxy *config, const gchar *key, gpointer data) { gboolean locked = ol_config_proxy_get_bool (config, key); if (menu.lock != NULL && locked != gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menu.lock))) gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu.lock), locked); } static void _visible_changed_cb (OlConfigProxy *config, const gchar *key, gpointer data) { gboolean visible = ol_config_proxy_get_bool (config, key); if (menu.hide && visible == gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menu.hide))) gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu.hide), !visible); } static void _display_mode_osd_changed_cb (OlConfigProxy *config, const gchar *key, gpointer data) { gboolean osd_enabled = ol_config_proxy_get_bool (config, key); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu.switch_osd), osd_enabled); gtk_widget_set_visible(menu.lock, osd_enabled); gtk_widget_set_visible(menu.hide, osd_enabled); } static void _display_mode_scroll_changed_cb (OlConfigProxy *config, const gchar *key, gpointer data) { gboolean scroll_enabled = ol_config_proxy_get_bool (config, key); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu.switch_scroll), scroll_enabled); } void ol_menu_lock (GtkWidget *widget, gpointer data) { OlConfigProxy *config = ol_config_proxy_get_instance (); ol_assert (config != NULL); gboolean locked = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)); ol_config_proxy_set_bool (config, "OSD/locked", locked); } void ol_menu_hide (GtkWidget *widget, gpointer data) { OlConfigProxy *config = ol_config_proxy_get_instance (); ol_assert (config != NULL); gboolean hide = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)); ol_config_proxy_set_bool (config, ".visible", !hide); } void ol_menu_toggle_osd (GtkWidget *widget, gpointer data) { OlConfigProxy *config = ol_config_proxy_get_instance (); ol_assert (config != NULL); gboolean osd_enabled = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)); ol_config_proxy_set_bool (config, "General/display-mode-osd", osd_enabled); } void ol_menu_toggle_scroll (GtkWidget *widget, gpointer data) { OlConfigProxy *config = ol_config_proxy_get_instance (); ol_assert (config != NULL); gboolean scroll_enabled = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)); ol_config_proxy_set_bool (config, "General/display-mode-scroll", scroll_enabled); } void ol_menu_quit (GtkWidget *widget, gpointer data) { gtk_main_quit (); } void ol_menu_preference (GtkWidget *widget, gpointer data) { ol_option_show (); } void ol_menu_play (GtkWidget *widget, gpointer data) { OlPlayer *player = ol_app_get_player (); if (player == NULL) return; ol_log_func (); ol_player_play (player); } void ol_menu_pause (GtkWidget *widget, gpointer data) { OlPlayer *player = ol_app_get_player (); if (player == NULL) return; ol_player_pause (player); } void ol_menu_stop (GtkWidget *widget, gpointer data) { OlPlayer *player = ol_app_get_player (); if (player == NULL) return; ol_player_stop (player); } void ol_menu_prev (GtkWidget *widget, gpointer data) { OlPlayer *player = ol_app_get_player (); if (player == NULL) return; ol_player_prev (player); } void ol_menu_next (GtkWidget *widget, gpointer data) { OlPlayer *player = ol_app_get_player (); if (player == NULL) return; ol_player_next (player); } static void ol_menu_init () { /* create accelerator group */ GtkAccelGroup *accel = ol_keybinding_get_accel_group (); OlConfigProxy *config = ol_config_proxy_get_instance (); popup_menu = ol_gui_get_widget ("pop-menu"); gtk_menu_set_accel_group (GTK_MENU (popup_menu), accel); menu.lock = ol_gui_get_widget ("menu-lock"); if (menu.lock) { gtk_menu_item_set_accel_path (GTK_MENU_ITEM (menu.lock), "/Lock"); } menu.hide = ol_gui_get_widget ("menu-hide"); if (menu.hide) { gtk_menu_item_set_accel_path (GTK_MENU_ITEM (menu.hide), "/Hide"); } menu.preference = ol_gui_get_widget ("menu-prefernce"); menu.about = ol_gui_get_widget ("menu-about"); menu.quit = ol_gui_get_widget ("menu-quit"); menu.play = ol_gui_get_widget ("menu-play"); menu.pause = ol_gui_get_widget ("menu-pause"); menu.stop = ol_gui_get_widget ("menu-stop"); menu.prev = ol_gui_get_widget ("menu-prev"); menu.next = ol_gui_get_widget ("menu-next"); menu.switch_osd = ol_gui_get_widget ("menu-switch-osd"); menu.switch_scroll = ol_gui_get_widget ("menu-switch-scroll"); gtk_widget_show_all (popup_menu); _locked_changed_cb (config, "OSD/locked", NULL); _visible_changed_cb (config, ".visible", NULL); _display_mode_osd_changed_cb (config, "General/display-mode-osd", NULL); _display_mode_scroll_changed_cb (config, "General/display-mode-scroll", NULL); g_signal_connect (config, "changed::OSD/locked", G_CALLBACK (_locked_changed_cb), NULL); g_signal_connect (config, "changed::.visible", G_CALLBACK (_visible_changed_cb), NULL); g_signal_connect (config, "changed::General/display-mode-osd", G_CALLBACK (_display_mode_osd_changed_cb), NULL); g_signal_connect (config, "changed::General/display-mode-scroll", G_CALLBACK (_display_mode_scroll_changed_cb), NULL); } static void ol_menu_update_player_control () { ol_log_func (); OlPlayer *player = ol_app_get_player (); if (menu.play) { if (player != NULL) { if (ol_player_get_status (player) == OL_PLAYER_PLAYING) { gtk_widget_hide (menu.play); } else { gtk_widget_show (menu.play); gtk_widget_set_sensitive (menu.play, ol_player_get_caps (player) & OL_PLAYER_PLAY); } } else { gtk_widget_show (menu.play); gtk_widget_set_sensitive (menu.play, FALSE); } } if (menu.pause) { if (player != NULL) { if (ol_player_get_status (player) == OL_PLAYER_PLAYING) gtk_widget_show (menu.pause); else gtk_widget_hide (menu.pause); gtk_widget_set_sensitive (menu.pause, ol_player_get_caps (player) & OL_PLAYER_PAUSE); } else { gtk_widget_hide (menu.pause); } } if (menu.stop) gtk_widget_set_sensitive (menu.stop, player != NULL && (ol_player_get_caps (player) & OL_PLAYER_STOP)); if (menu.prev) gtk_widget_set_sensitive (menu.prev, player != NULL && (ol_player_get_caps (player) & OL_PLAYER_PREV)); if (menu.next) gtk_widget_set_sensitive (menu.next, player != NULL && (ol_player_get_caps (player) & OL_PLAYER_NEXT)); } GtkWidget* ol_menu_get_popup () { ol_log_func (); if (popup_menu == NULL) { ol_menu_init (); } ol_menu_update_player_control (); return popup_menu; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_menu.h000066400000000000000000000020761345713535600177570ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_MENU_H_ #define _OL_MENU_H_ #include /** * @brief Gets singleton popup menu * The popup menu will be created at the first invoke * * @return The popup menu for the app */ GtkWidget* ol_menu_get_popup (); #endif /* _OL_MENU_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_metadata.c000066400000000000000000000334011345713535600205620ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include "ol_metadata.h" #include "ol_utils.h" #include "ol_debug.h" const int DEFAULT_TRACK_NUM = -1; struct _OlMetadata { char *title; /* The title of the track */ char *artist; /* The artist of the track */ char *album; /* The album name of the track */ int track_number; /* The track number of the track */ char *uri; /* URI of the track */ char *art; /* URI of the album art */ guint64 duration; /* Length of the track */ }; static void internal_set_string (char **string, const char *val); static int internal_streq (const char *lhs, const char *rhs); static int internal_snprint (void *buffer, size_t count, char *val); static void ol_metadata_init (OlMetadata *metadata); static void internal_set_string (char **string, const char *val) { ol_assert (string != NULL); if (*string != NULL) { g_free (*string); *string = NULL; } if (val != NULL) *string = g_strdup (val); } OlMetadata * ol_metadata_new (void) { OlMetadata *metadata = g_new (OlMetadata, 1); ol_metadata_init (metadata); return metadata; } OlMetadata * ol_metadata_new_from_variant (GVariant *variant) { ol_assert_ret (g_variant_is_of_type (variant, G_VARIANT_TYPE ("a{sv}")), NULL); OlMetadata *metadata = ol_metadata_new (); GVariantIter *iter; gchar *key; GVariant *value; g_variant_get (variant, "a{sv}", &iter); while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) { if (g_str_equal (key, "xesam:title")) { ol_metadata_set_title (metadata, g_variant_get_string (value, NULL)); } else if (g_str_equal (key, "xesam:artist")) { /* In case of some poor implementations set artist as a string */ if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) { ol_metadata_set_artist (metadata, g_variant_get_string (value, NULL)); } else if (g_variant_is_of_type (value, G_VARIANT_TYPE ("as"))) { GVariantIter *subiter; gchar *art_one; gchar *artist; gchar **artist_list = g_new (gchar*, g_variant_n_children (value) + 1); gint i = 0; artist_list[g_variant_n_children (value)] = NULL; g_variant_get (value, "as", &subiter); while (g_variant_iter_loop (subiter, "s", &art_one)) { artist_list[i] = g_strdup (art_one); i++; } artist = g_strjoinv(", ", artist_list); ol_metadata_set_artist (metadata, artist); g_strfreev (artist_list); g_free (artist); g_variant_iter_free (subiter); } else { ol_errorf ("Unknown type of artist: %s\n", g_variant_get_type_string (value)); } } else if (g_str_equal (key, "xesam:album")) { ol_metadata_set_album (metadata, g_variant_get_string (value, NULL)); } else if (g_str_equal (key, "mpris:artUrl")) { ol_metadata_set_art (metadata, g_variant_get_string (value, NULL)); } else if (g_str_equal (key, "xesam:url")) { ol_metadata_set_uri (metadata, g_variant_get_string (value, NULL)); } else if (g_str_equal (key, "xesam:trackNumber")) { ol_metadata_set_track_number (metadata, g_variant_get_int32 (value)); } else if (g_str_equal (key, "mpris:length")) { ol_metadata_set_duration (metadata, g_variant_get_int64 (value)); } } g_variant_iter_free (iter); return metadata; } void ol_metadata_free (OlMetadata *metadata) { if (metadata == NULL) return; ol_metadata_clear (metadata); g_free (metadata); } static void ol_metadata_init (OlMetadata *metadata) { ol_assert (metadata != NULL); metadata->artist = NULL; metadata->title = NULL; metadata->album = NULL; metadata->uri = NULL; metadata->art = NULL; metadata->duration = 0; metadata->track_number = DEFAULT_TRACK_NUM; } void ol_metadata_clear (OlMetadata *metadata) { ol_assert (metadata != NULL); ol_metadata_set_title (metadata, NULL); ol_metadata_set_artist (metadata, NULL); ol_metadata_set_album (metadata, NULL); ol_metadata_set_track_number (metadata, DEFAULT_TRACK_NUM); ol_metadata_set_uri (metadata, NULL); ol_metadata_set_art (metadata, NULL); ol_metadata_set_duration (metadata, 0); } void ol_metadata_copy (OlMetadata *dest, const OlMetadata *src) { ol_assert (dest != NULL); ol_assert (src != NULL); if (dest == src) return; ol_metadata_set_title (dest, src->title); ol_metadata_set_artist (dest, src->artist); ol_metadata_set_album (dest, src->album); ol_metadata_set_track_number (dest, src->track_number); ol_metadata_set_uri (dest, src->uri); ol_metadata_set_art (dest, src->art); ol_metadata_set_duration (dest, src->duration); } OlMetadata * ol_metadata_dup (const OlMetadata *src) { OlMetadata *metadata = ol_metadata_new (); ol_metadata_copy (metadata, src); return metadata; } void ol_metadata_set_title (OlMetadata *metadata, const char *title) { ol_assert (metadata != NULL); internal_set_string (&(metadata->title), title); } const char * ol_metadata_get_title (const OlMetadata *metadata) { ol_assert_ret (metadata != NULL, NULL); return metadata->title; } void ol_metadata_set_artist (OlMetadata *metadata, const char *artist) { ol_assert (metadata != NULL); internal_set_string (&(metadata->artist), artist); } const char * ol_metadata_get_artist (const OlMetadata *metadata) { ol_assert_ret (metadata != NULL, NULL); return metadata->artist; } void ol_metadata_set_album (OlMetadata *metadata, const char *album) { ol_assert (metadata != NULL); internal_set_string (&(metadata->album), album); } const char * ol_metadata_get_album (const OlMetadata *metadata) { ol_assert_ret (metadata != NULL, NULL); return metadata->album; } void ol_metadata_set_track_number (OlMetadata *metadata, int track_number) { ol_assert (metadata != NULL); metadata->track_number = track_number; } void ol_metadata_set_track_number_from_string (OlMetadata *metadata, const char *track_number) { ol_assert (metadata != NULL); if (track_number == NULL) metadata->track_number = DEFAULT_TRACK_NUM; else sscanf (track_number, "%d", &metadata->track_number); } int ol_metadata_get_track_number (const OlMetadata *metadata) { ol_assert_ret (metadata != NULL, DEFAULT_TRACK_NUM); return metadata->track_number; } void ol_metadata_set_uri (OlMetadata *metadata, const char *uri) { ol_assert (metadata != NULL); internal_set_string (&(metadata->uri), uri); } const char * ol_metadata_get_uri (const OlMetadata *metadata) { ol_assert_ret (metadata != NULL, NULL); return metadata->uri; } void ol_metadata_set_art (OlMetadata *metadata, const char *art) { ol_assert (metadata != NULL); internal_set_string (&(metadata->art), art); } const char * ol_metadata_get_art (const OlMetadata *metadata) { ol_assert_ret (metadata != NULL, NULL); return metadata->art; } void ol_metadata_set_duration (OlMetadata *metadata, guint64 duration) { ol_assert (metadata != NULL); metadata->duration = duration; } guint64 ol_metadata_get_duration (const OlMetadata *metadata) { ol_assert_ret (metadata != NULL, 0); return metadata->duration; } static int internal_snprint (void *buffer, size_t count, char *val) { if (val == NULL) val = ""; return snprintf (buffer, count, "%s\n", val); } int ol_metadata_serialize (OlMetadata *metadata, char *buffer, size_t count) { ol_assert_ret (metadata != NULL, 0); int cnt = 0; if (buffer == NULL) { if (metadata->title != NULL) cnt += strlen (metadata->title); cnt++; if (metadata->artist != NULL) cnt += strlen (metadata->artist); cnt++; if (metadata->album != NULL) cnt += strlen (metadata->album); cnt++; static char tmpbuf[100]; cnt += snprintf (tmpbuf, 100, "%d\n", metadata->track_number); if (metadata->uri != NULL) cnt += strlen (metadata->uri); cnt++; } else { cnt += internal_snprint (buffer + cnt, count - cnt, metadata->title); cnt += internal_snprint (buffer + cnt, count - cnt, metadata->artist); cnt += internal_snprint (buffer + cnt, count - cnt, metadata->album); cnt += snprintf (buffer + cnt, count - cnt, "%d\n", metadata->track_number); cnt += internal_snprint (buffer + cnt, count - cnt, metadata->uri); cnt += internal_snprint (buffer + cnt, count - cnt, metadata->art); cnt += snprintf (buffer + cnt, count - cnt, "%"G_GUINT64_FORMAT"\n", metadata->duration); if (cnt < count) buffer[cnt] = '\0'; else if (count > 0) buffer[count - 1] = '\0'; } return cnt; } int ol_metadata_deserialize (OlMetadata *metadata, const char *data) { ol_assert_ret (metadata != NULL, 0); ol_assert_ret (data != NULL, 0); int ret = 1; char *buffer = g_strdup (data); char *title = NULL, *artist = NULL, *album = NULL, *track_number = NULL, *uri = NULL, *art = NULL, *duration = NULL; title = artist = album = track_number = uri = NULL; title = buffer; if ((artist = ol_split_a_line (title)) == NULL) ret = 0; else if ((album = ol_split_a_line (artist)) == NULL) ret = 0; else if ((track_number = ol_split_a_line (album)) == NULL) ret = 0; else if ((uri = ol_split_a_line (track_number)) == NULL) ret = 0; else if ((art = ol_split_a_line (track_number)) == NULL) ret = 0; else if ((duration = ol_split_a_line (track_number)) == NULL) ret = 0; if ((ol_split_a_line (duration)) == NULL) ret = 0; if (ret) { int tn = 0; sscanf (track_number, "%d", &tn); ol_metadata_set_title (metadata, title); ol_metadata_set_artist (metadata, artist); ol_metadata_set_album (metadata, album); ol_metadata_set_track_number (metadata, tn); ol_metadata_set_uri (metadata, uri); ol_metadata_set_art (metadata, art); ol_metadata_set_duration (metadata, g_ascii_strtoull (duration, NULL, 10)); } g_free (buffer); return ret; } static int internal_streq (const char *lhs, const char *rhs) { if (lhs == rhs) return 1; if (lhs == NULL || rhs == NULL) return 0; return strcmp (lhs, rhs) == 0; } int ol_metadata_equal (const OlMetadata *lhs, const OlMetadata *rhs) { if (lhs == rhs) return 1; if (lhs == NULL || rhs == NULL) return 0; if (!internal_streq (lhs->title, rhs->title)) return 0; if (!internal_streq (lhs->artist, rhs->artist)) return 0; if (!internal_streq (lhs->album, rhs->album)) return 0; if (lhs->track_number != rhs->track_number) return 0; if (!internal_streq (lhs->uri, rhs->uri)) return 0; if (!internal_streq (lhs->art, rhs->art)) return 0; if (lhs->duration != rhs->duration) return 0; return 1; } static void _add_string_to_dict_builder (GVariantBuilder *builder, const char *key, const char *value) { if (value) g_variant_builder_add (builder, "{sv}", key, g_variant_new_string (value)); } GVariant * ol_metadata_to_variant (OlMetadata *metadata) { ol_assert_ret (metadata != NULL, NULL); GVariantBuilder *builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); _add_string_to_dict_builder (builder, "title", metadata->title); _add_string_to_dict_builder (builder, "artist", metadata->artist); _add_string_to_dict_builder (builder, "album", metadata->album); _add_string_to_dict_builder (builder, "location", metadata->uri); _add_string_to_dict_builder (builder, "arturl", metadata->art); g_variant_builder_add (builder, "{sv}", "time", g_variant_new_uint32 (metadata->duration / 1000)); g_variant_builder_add (builder, "{sv}", "mtime", g_variant_new_uint32 (metadata->duration)); if (metadata->track_number > 0) { gchar *tracknum = g_strdup_printf ("%d", metadata->track_number); g_variant_builder_add (builder, "{sv}", "tracknumber", g_variant_new_string (tracknum)); g_free (tracknum); } GVariant *ret = g_variant_builder_end (builder); g_variant_builder_unref (builder); return ret; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_metadata.h000066400000000000000000000132471345713535600205750ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_METADATA_H_ #define _OL_METADATA_H_ #include #include /** * defines a music's infomation structure */ typedef struct _OlMetadata OlMetadata; struct _OlMetadata; OlMetadata *ol_metadata_new (void); /** * Create a metadata object from GVariant of MPRIS2 metadata dictionary * * @param variant * * @return */ OlMetadata *ol_metadata_new_from_variant (GVariant *variant); void ol_metadata_free (OlMetadata *metadata); /** * @brief Clears an OlMetadata * All fields will be freed if they are not empty * * @param metadata */ void ol_metadata_clear (OlMetadata *metadata); void ol_metadata_copy (OlMetadata *dest, const OlMetadata *src); OlMetadata *ol_metadata_dup (const OlMetadata *src); /** * @brief Sets the value of music title * * @param metadata An OlMetadata * @param title The value of title. If not NULL, it will be copied * inside the metadata */ void ol_metadata_set_title (OlMetadata *metadata, const char *title); const char *ol_metadata_get_title (const OlMetadata *metadata); /** * @brief Sets the artist of music * * @param metadata An OlMetadata * @param artist The value of artist. If not NULL, it will be copied * inside the metadata */ void ol_metadata_set_artist (OlMetadata *metadata, const char *artist); const char *ol_metadata_get_artist (const OlMetadata *metadata); /** * @brief Sets the name of music album * * @param metadata An OlMetadata * @param album The name of album. If not NULL, it will be copied * inside the metadata */ void ol_metadata_set_album (OlMetadata *metadata, const char *album); const char *ol_metadata_get_album (const OlMetadata *metadata); void ol_metadata_set_track_number (OlMetadata *metadata, int track_number); void ol_metadata_set_track_number_from_string (OlMetadata *metadata, const char *track_number); int ol_metadata_get_track_number (const OlMetadata *metadata); /** * @brief Sets the location of music file * * @param metadata An OlMetadata * @param uri The value of uri. If not NULL, it will be copied * inside the metadata */ void ol_metadata_set_uri (OlMetadata *metadata, const char *uri); const char *ol_metadata_get_uri (const OlMetadata *metadata); /** * @brief Sets the location of the album art * * The album art should be in file:// scheme. * * @param metadata The metadata * @param art_uri The uri if the album art. */ void ol_metadata_set_art (OlMetadata *metadata, const char *art_uri); /** * @brief Gets the location of the album art * * The album art is in URI format, which the local files will be in * file:// scheme * * @param metadata * * @return The uri of the album art, or NULL if not exists. */ const char *ol_metadata_get_art (const OlMetadata *metadata); /** * Sets the duration of the metadata. * * @param metadata * @param duration The duration in millisecond. */ void ol_metadata_set_duration (OlMetadata *metadata, guint64 duration); /** * Gets the duration of the track, in millisecond * * @param metadata * * @return The duration of the track. */ guint64 ol_metadata_get_duration (const OlMetadata *metadata); /** * @brief Check whether two Metadatas are equal * Two Metadatas are equal if and only if all their fields are equal * * @param lhs An OlMetadata, or NULL * @param rhs An OlMetadata, or NULL * * @return If lhs is equal to rhs, return 1. Otherwise return 0 */ int ol_metadata_equal (const OlMetadata *lhs, const OlMetadata *rhs); /** * @brief Converts a music info to a string * The returned buffer is NUL-terminated * @param metadata A Metadata * @param buffer Buffer of serialzed string, or NULL. * If not NULL, the serialzed string is terminated with NUL. * @param count The size of the buffer. * * @return The length of the serialized string, regardless of the size of buffer. */ int ol_metadata_serialize (OlMetadata *metadata, char *buffer, size_t count); /** * @brief Converts a string to an OlMetadata * * @param metadata A Metadata * @param data The serialized string from an OlMetadata * * @return 1 if succeeded, or 0 if failed */ int ol_metadata_deserialize (OlMetadata *metadata, const char *data); /** * Converts a metadata to a GVariant. * * This function is intended to do D-Bus communication. * * The conveted GVariant is a dict of type a{sv}. The content of the dict follows * the format of metadata defined in the Daemon's D-Bus specification. * * @param metadata * * @return A GVariant of a{sv} */ GVariant *ol_metadata_to_variant (OlMetadata *metadata); #endif /* _OL_METADATA_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_notify.c000066400000000000000000000075721345713535600203240ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include "config.h" #include "ol_notify.h" #include "ol_intl.h" #include "ol_utils.h" #include "ol_debug.h" static const char *UNKNOWN_TITLE = N_("Unknown title"); static const char *UNKNOWN_ARTIST = N_("Unknown artist"); static const char *INFO_FORMAT = "%s"; static const char *INFO_FORMAT_ALBUM = "%s\n%s"; static const int DEFAULT_TIMEOUT = -1; static NotifyNotification *notify = NULL; static gboolean _init (); static NotifyNotification *_get_notify (const char *summary, const char *body, const char *icon); static NotifyNotification * _get_notify (const char *summary, const char *body, const char *icon) { ol_debugf ("summary: %s\n" "body: %s\n" "icon: %s\n", summary, body, icon); if (notify == NULL) { #ifdef HAVE_LIBNOTIFY_0_7 notify = notify_notification_new (summary, body, icon); #else notify = notify_notification_new (summary, body, icon, NULL); #endif } else { notify_notification_update (notify, summary, body, icon); } return notify; } static gboolean _init () { if (!notify_is_initted()) notify_init (_(PROGRAM_NAME)); return TRUE; } void ol_notify_init () { _init (); } void ol_notify_unload () { if (notify != NULL) g_object_unref (notify); notify_uninit (); } void ol_notify_music_change (OlMetadata *info, const char *icon) { ol_assert (info != NULL); if (!_init ()) { return; } char *art = NULL; const char *title = ol_metadata_get_title (info); const char *artist = ol_metadata_get_artist (info); if (title == NULL && artist == NULL) return; if (title == NULL || !title[0]) title = _(UNKNOWN_TITLE); if (artist == NULL) artist = _(UNKNOWN_ARTIST); const char *album = ol_metadata_get_album (info); char *body = NULL; if (album == NULL) { body = g_markup_printf_escaped (INFO_FORMAT, artist); } else { body = g_markup_printf_escaped (INFO_FORMAT_ALBUM, artist, album); } if (ol_metadata_get_art (info) != NULL) { const char *art_uri = ol_metadata_get_art (info); if (art_uri[0] == '/') art = g_strdup (art_uri); else if (g_str_has_prefix (art_uri, "file://")) art = g_filename_from_uri (art_uri, NULL, NULL); if (art && !ol_path_is_file (art)) { g_free (art); art = NULL; } } NotifyNotification *music_notify = _get_notify (title, body, art ? art : icon); notify_notification_set_timeout (music_notify, DEFAULT_TIMEOUT); notify_notification_show (music_notify, NULL); g_free (body); g_free (art); } osdlyrics-0.5.5~rc1+dfsg1/src/ol_notify.h000066400000000000000000000020331345713535600203140ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_NOTIFY_H_ #define _OL_NOTIFY_H_ #include "ol_metadata.h" void ol_notify_init (void); void ol_notify_music_change (OlMetadata *metadata, const char *icon); void ol_notify_unload (void); #endif /* _OL_NOTIFY_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_option.c000066400000000000000000001577201345713535600203250ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include #include "ol_option.h" #include "ol_about.h" #include "ol_gui.h" #include "ol_config_proxy.h" #include "ol_osd_render.h" #include "ol_path_pattern.h" /* For getting preview for LRC filename */ #include "ol_intl.h" #include "ol_debug.h" #include "ol_cell_renderer_button.h" #include "ol_lyric_source_list.h" #include "ol_player.h" #include "ol_app.h" #include "ol_utils.h" #define BUFFER_SIZE 1024 typedef struct _OptionWidgets OptionWidgets; struct CheckButtonOptions { const char *widget_name; const char *key; }; struct RadioStringValues { const char *widget_name; const char *value; }; struct RadioStringOptions { const char *key; struct RadioStringValues *values; }; struct TogglePropertyOptions { const char *toggle_widget; const char *target_widget; const char *property; gboolean negated; }; struct WidgetConfigOptions { const char *widget_name; const char *key; }; struct ComboStringOptions { const char *widget_name; const char *key; const char **values; }; enum { CHECK_OPT_DOWNLOAD_FIRST = 0, CHECK_OPT_TRANSLUCENT_MOUSEOVER, CHECK_OPT_NOTIFY_MUSIC, }; static struct CheckButtonOptions check_button_options[] = { {"download-first-lyric", "Download/download-first-lyric"}, {"translucent-on-mouse-over", "OSD/translucent-on-mouse-over"}, {"notify-music", "General/notify-music"}, }; static struct RadioStringValues proxy_values[] = { {"proxy-no", "no"}, {"proxy-system", "system"}, {"proxy-manual", "manual"}, {NULL, NULL}, }; static struct RadioStringValues osd_window_mode_values[] = { {.widget_name = "osd-window-mode-normal", .value = "normal"}, {.widget_name = "osd-window-mode-dock", .value = "dock"}, {NULL, NULL}, }; static struct RadioStringOptions radio_str_options[] = { {.key = "Download/proxy", .values = proxy_values}, {.key = "OSD/osd-window-mode", .values = osd_window_mode_values}, }; static struct WidgetConfigOptions entry_str_options[] = { {.widget_name = "proxy-host", .key = "Download/proxy-host"}, {.widget_name = "proxy-username", .key = "Download/proxy-username"}, {.widget_name = "proxy-passwd", .key = "Download/proxy-password"}, }; static struct WidgetConfigOptions spin_int_options[] = { {.widget_name = "proxy-port", .key = "Download/proxy-port"}, {.widget_name = "outline-width", .key = "OSD/outline-width"}, }; static struct WidgetConfigOptions scale_double_options[] = { {.widget_name = "scroll-opacity", .key = "ScrollMode/opacity"}, {.widget_name = "osd-blur-radius", .key = "OSD/blur-radius"}, }; static struct WidgetConfigOptions color_str_options[] = { {.widget_name = "scroll-bg-color", .key = "ScrollMode/bg-color"}, {.widget_name = "scroll-active-lrc-color", .key = "ScrollMode/active-lrc-color"}, {.widget_name = "scroll-inactive-lrc-color", .key = "ScrollMode/inactive-lrc-color"}, }; static struct WidgetConfigOptions font_str_options[] = { {.widget_name = "scroll-font", .key = "ScrollMode/font-name"}, {.widget_name = "osd-font", .key = "OSD/font-name"}, }; static const char *proxy_types[] = {"http", "socks4", "socks5", NULL}; static const char *scroll_modes[] = {"always", "lines", NULL}; static struct ComboStringOptions combo_str_options[] = { {.widget_name = "proxy-type", .key = "Download/proxy-type", .values = proxy_types}, {.widget_name = "scroll-scroll-mode", .key = "ScrollMode/scroll-mode", .values = scroll_modes}, }; static struct _OptionWidgets { GtkWidget *close; GtkWidget *font; GtkWidget *outline_width; GtkWidget *lrc_align[2]; GtkWidget *active_lrc_color[OL_LINEAR_COLOR_COUNT]; GtkWidget *inactive_lrc_color[OL_LINEAR_COLOR_COUNT]; GtkWidget *line_count[2]; GtkWidget *download_engine; GtkWidget *lrc_path; GtkWidget *lrc_path_text; GtkWidget *lrc_filename; GtkWidget *lrc_filename_text; GtkWidget *lrc_filename_sample; GtkWidget *path_chooser; GtkWidget *filename_menu; GtkWidget *startup_player; GtkWidget *startup_player_cb; GtkWidget *display_mode_osd; GtkWidget *display_mode_scroll; } options; static struct ListExtraWidgets { GtkWidget *entry; GtkWidget *add_button; GtkWidget *extra_button; GtkWidget *list; void (*save_func) (); } lrc_path_widgets, lrc_filename_widgets; struct ListExtraButton { char *stock_name; void (*click_func) (GtkCellRenderer *cell, gchar *path, GtkTreeView *view); }; enum TreeColumns { TEXT_COLUMN = 0, REMOVE_COLUMN, N_COLUMN, }; static struct TogglePropertyOptions toggle_properties[] = { {"proxy-manual", "align-proxy-manual", "sensitive", FALSE}, {"show-proxy-passwd", "proxy-passwd", "visibility", FALSE}, }; static void save_check_button_option (struct CheckButtonOptions* opt); /* General options */ void ol_option_display_mode_osd_changed (GtkToggleButton *togglebutton, gpointer user_data); void ol_option_display_mode_scroll_changed (GtkToggleButton *togglebutton, gpointer user_data); void ol_option_notify_music_changed (GtkToggleButton *togglebutton, gpointer user_data); void ol_option_startup_player_changed (GtkComboBox *cb, gpointer user_data); gboolean ol_option_save_startup_player (); static void init_startup_player (GtkWidget *widget); /* OSD options */ void ol_option_osd_outline_changed (GtkSpinButton *spinbutton, gpointer user_data); void ol_option_osd_line_count_changed (GtkToggleButton *togglebutton, gpointer user_data); void ol_option_mouseover_changed (GtkToggleButton *togglebutton, gpointer user_data); void ol_option_osd_alignment_changed (GtkRange *range, gpointer user_data); void ol_option_osd_color_changed (GtkColorButton *widget, gpointer user_data); /* Path options */ void ol_option_save_path_pattern (); void ol_option_save_file_pattern (); /* Download options */ static void _connect_download_engine_changed (GtkTreeView *list, void (*callback) (GtkTreeModel *model)); static void _disconnect_download_engine_changed (GtkTreeView *list, void (*callback) (GtkTreeModel *model)); static void ol_option_download_engine_changed (GtkTreeModel *model); void ol_option_download_first_changed (GtkToggleButton *togglebutton, gpointer user_data); void ol_option_about_clicked (GtkWidget *widget, gpointer data); void ol_option_close_clicked (GtkWidget *widget); void ol_option_lrc_filename_changed (GtkEditable *editable, gpointer user_data); void ol_option_menu_title_activate (GtkMenuItem *menuitem, gpointer user_data); void ol_option_menu_artist_activate (GtkMenuItem *menuitem, gpointer user_data); void ol_option_menu_album_activate (GtkMenuItem *menuitem, gpointer user_data); void ol_option_menu_number_activate (GtkMenuItem *menuitem, gpointer user_data); void ol_option_menu_filename_activate (GtkMenuItem *menuitem, gpointer user_data); static void ol_option_list_add_clicked (GtkButton *button, struct ListExtraWidgets *widgets); void ol_option_filename_clicked (GtkButton *button, struct ListExtraWidgets *widgets); void ol_option_path_clicked (GtkButton *button, struct ListExtraWidgets *widgets); static void ol_option_list_select_changed (GtkTreeSelection *selection, gpointer data); static void ol_option_list_entry_changed (GtkEditable *editable, gpointer user_data); static OlColor ol_color_from_gdk_color (const GdkColor color); static GdkColor ol_color_to_gdk_color (const OlColor color); static void ol_option_update_widget (OptionWidgets *widgets); static char **get_list_content (GtkTreeView *view); static void set_list_content (GtkTreeView *view, char **list); static void list_remove_clicked (GtkCellRenderer *cell, gchar *path, GtkTreeView *view); static void load_osd (); static void load_download (); static void load_general (); static void load_check_button_options (); static void load_radio_str_options (); static void load_entry_str_options (); static void load_spin_int_options (); static void load_color_str_options (); static void load_font_str_options (); static void load_scale_double_options (); static void load_combo_str_options (); static void init_list (struct ListExtraWidgets *widgets, struct ListExtraButton *buttons); static void init_signals (); static void init_toggle_properties (); static void radio_str_changed (GtkToggleButton *button, struct RadioStringOptions *option); static void toggle_set_property (GtkToggleButton *widget, struct TogglePropertyOptions *option); static void entry_str_changed (GtkEditable *widget, struct WidgetConfigOptions *option); static void spin_int_changed (GtkSpinButton *widget, struct WidgetConfigOptions *option); static void scale_double_changed (GtkScale *widget, struct WidgetConfigOptions *option); static void color_str_changed (GtkColorButton *widget, struct WidgetConfigOptions *option); static void font_str_changed (GtkFontButton *widget, struct WidgetConfigOptions *option); static void combo_str_changed (GtkComboBox *widget, struct ComboStringOptions *option); static void _display_mode_changed (OlConfigProxy *config, const gchar *key, gpointer data); /* General Options */ void ol_option_display_mode_osd_changed (GtkToggleButton *togglebutton, gpointer user_data) { ol_config_proxy_set_bool (ol_config_proxy_get_instance (), "General/display-mode-osd", gtk_toggle_button_get_active (togglebutton)); } void ol_option_display_mode_scroll_changed (GtkToggleButton *togglebutton, gpointer user_data) { ol_config_proxy_set_bool (ol_config_proxy_get_instance (), "General/display-mode-scroll", gtk_toggle_button_get_active (togglebutton)); } void ol_option_notify_music_changed (GtkToggleButton *togglebutton, gpointer user_data) { save_check_button_option (&check_button_options[CHECK_OPT_NOTIFY_MUSIC]); } void ol_option_startup_player_changed (GtkComboBox *cb, gpointer user_data) { if (options.startup_player == NULL) return; GtkEntry *entry = GTK_ENTRY (options.startup_player); int index = gtk_combo_box_get_active (cb); gtk_widget_set_sensitive (options.startup_player, FALSE); if (index == 0) { gtk_entry_set_text (entry, ""); } else { GtkTreeModel *liststore = gtk_combo_box_get_model (cb); GtkTreeIter iter; if (gtk_combo_box_get_active_iter (cb, &iter)) { char *command = NULL; gtk_tree_model_get (liststore, &iter, 1, &command, -1); if (!ol_is_string_empty (command)) { gtk_entry_set_text (entry, command); } else /* Customize */ { gtk_widget_set_sensitive (options.startup_player, TRUE); gtk_widget_grab_focus (options.startup_player); } g_free (command); } } ol_option_save_startup_player (); } gboolean ol_option_save_startup_player () { if (options.startup_player != NULL) { ol_config_proxy_set_string (ol_config_proxy_get_instance (), "General/startup-player", gtk_entry_get_text (GTK_ENTRY (options.startup_player))); } return FALSE; } /* OSD options */ void ol_option_osd_line_count_changed (GtkToggleButton *togglebutton, gpointer user_data) { int i; if (gtk_toggle_button_get_active (togglebutton)) for (i = 0; i < 2; i++) if (GTK_WIDGET (togglebutton) == options.line_count[i]) { ol_config_proxy_set_int (ol_config_proxy_get_instance (), "OSD/line-count", i + 1); return; } } void ol_option_mouseover_changed (GtkToggleButton *togglebutton, gpointer user_data) { save_check_button_option (&check_button_options[CHECK_OPT_TRANSLUCENT_MOUSEOVER]); } void ol_option_osd_alignment_changed (GtkRange *range, gpointer user_data) { int i; for (i = 0; i < 2; i++) { if (GTK_WIDGET (range) == options.lrc_align[i]) { char buffer[24]; sprintf (buffer, "OSD/lrc-align-%d", i); ol_config_proxy_set_double (ol_config_proxy_get_instance (), buffer, gtk_range_get_value (range)); } } } void ol_option_osd_color_changed (GtkColorButton *widget, gpointer user_data) { int i; OlConfigProxy *config = ol_config_proxy_get_instance (); GtkWidget **color_widgets[] = {options.active_lrc_color, options.inactive_lrc_color}; char *color_props[] = {"OSD/active-lrc-color", "OSD/inactive-lrc-color"}; int k; OlColor colors[OL_LINEAR_COLOR_COUNT]; for (k = 0; k < 2; k++) { for (i = 0; i < OL_LINEAR_COLOR_COUNT; i++) { if (color_widgets[k][i] != NULL) { GtkColorButton *color_button = GTK_COLOR_BUTTON (color_widgets[k][i]); GdkColor color; gtk_color_button_get_color (color_button, &color); colors[i] = ol_color_from_gdk_color (color); } } char **lrc_color_str = ol_color_to_str_list (colors, OL_LINEAR_COLOR_COUNT); ol_config_proxy_set_str_list (config, color_props[k], (const char**)lrc_color_str, OL_LINEAR_COLOR_COUNT); g_strfreev (lrc_color_str); } } /* Path options */ void ol_option_save_path_pattern () { OlConfigProxy *config = ol_config_proxy_get_instance (); if (options.lrc_path != NULL) { GtkTreeView *view = GTK_TREE_VIEW (options.lrc_path); char **list = get_list_content (view); if (list) { ol_config_proxy_set_str_list (config, "General/lrc-path", (const char **)list, g_strv_length (list)); g_strfreev (list); } } } void ol_option_save_file_pattern () { OlConfigProxy *config = ol_config_proxy_get_instance (); if (options.lrc_filename != NULL) { GtkTreeView *view = GTK_TREE_VIEW (options.lrc_filename); char **list = get_list_content (view); if (list != NULL) { ol_config_proxy_set_str_list (config, "General/lrc-filename", (const char **)list, g_strv_length (list)); g_strfreev (list); } } } /* Download options */ void ol_option_download_engine_changed (GtkTreeModel *model) { OlConfigProxy *config = ol_config_proxy_get_instance (); GList *source_ids; source_ids = ol_lyric_source_list_get_active_id_list (GTK_TREE_VIEW (options.download_engine)); guint n_ids = g_list_length (source_ids); int i = 0; gchar **id_arr = g_new (gchar *, n_ids + 1); id_arr[n_ids] = NULL; for (; source_ids; source_ids = g_list_delete_link (source_ids, source_ids)) id_arr[i++] = source_ids->data; ol_config_proxy_set_str_list (config, "Download/download-engine", (const char**)id_arr, n_ids); g_strfreev (id_arr); } void ol_option_download_first_changed (GtkToggleButton *togglebutton, gpointer user_data) { save_check_button_option (&check_button_options[CHECK_OPT_DOWNLOAD_FIRST]); } void ol_option_about_clicked (GtkWidget *widget, gpointer data) { ol_about_show (); } static OlColor ol_color_from_gdk_color (const GdkColor c) { OlColor color; color.r = c.red / 65535.0; color.g = c.green / 65535.0; color.b = c.blue / 65535.0; return color; } static GdkColor ol_color_to_gdk_color (const OlColor color) { GdkColor ret = { .pixel = 0 }; ret.red = color.r * 65535; ret.green = color.g * 65535; ret.blue = color.b * 65535; return ret; } static void ol_option_list_add_clicked (GtkButton *button, struct ListExtraWidgets *widgets) { if (button == NULL || widgets == NULL) return; if (widgets->list == NULL) return; GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widgets->list)); GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widgets->list)); GtkTreeIter iter; gtk_list_store_append (GTK_LIST_STORE (model), &iter); gtk_tree_selection_select_iter (selection, &iter); if (widgets->entry) gtk_widget_grab_focus (widgets->entry); } void ol_option_filename_clicked (GtkButton *button, struct ListExtraWidgets *widgets) { ol_log_func (); ol_assert (options.filename_menu != NULL); gtk_menu_popup (GTK_MENU (options.filename_menu), NULL, /* parent_menu_shell */ NULL, /* parent_menu_item */ NULL, /* func */ NULL, /* data */ 1, gtk_get_current_event_time()); } void ol_option_path_clicked (GtkButton *button, struct ListExtraWidgets *widgets) { ol_log_func (); ol_assert (options.path_chooser != NULL); ol_assert (lrc_path_widgets.entry != NULL); const char *current_path = gtk_entry_get_text (GTK_ENTRY (lrc_path_widgets.entry)); if (strcmp (current_path, "%s") != 0) { ol_debugf (" current path:%s\n", current_path); char expanded_path[BUFFER_SIZE]; /* expand `~' to home directory*/ ol_path_expand_path_pattern (current_path, NULL, expanded_path, BUFFER_SIZE); ol_debugf (" expanded path:%s\n", expanded_path); gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (options.path_chooser), expanded_path); } if (gtk_dialog_run (GTK_DIALOG (options.path_chooser)) == GTK_RESPONSE_ACCEPT) { char *path = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (options.path_chooser)); ol_debugf (" path:%s\n", path); gtk_entry_set_text (GTK_ENTRY (lrc_path_widgets.entry), path); g_free (path); } gtk_widget_hide (options.path_chooser); } static void ol_option_list_entry_changed (GtkEditable *editable, gpointer data) { if (editable == NULL || data == NULL) return; struct ListExtraWidgets *widgets = (struct ListExtraWidgets *) data; if (widgets->list == NULL) return; GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widgets->list)); GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widgets->list)); GtkTreeIter iter; gboolean selected = gtk_tree_selection_get_selected (selection, NULL, &iter); if (!selected) return; char *text = gtk_editable_get_chars (editable, 0, -1); gtk_list_store_set (GTK_LIST_STORE (model), &iter, TEXT_COLUMN, text, -1); g_free (text); } void ol_option_lrc_filename_changed (GtkEditable *editable, gpointer user_data) { static char buffer[BUFFER_SIZE] = ""; if (options.lrc_filename_sample == NULL) return; OlMetadata *metadata = ol_metadata_new (); ol_metadata_set_title (metadata, "Title"); ol_metadata_set_artist (metadata, "Artist"); ol_metadata_set_album (metadata, "Album"); ol_metadata_set_track_number (metadata, 1); ol_metadata_set_uri (metadata, "file:///music_path/music_filename.ogg"); char *pattern = gtk_editable_get_chars (editable, 0, -1); if (ol_path_get_lrc_pathname ("", pattern, metadata, buffer, BUFFER_SIZE) >= 0) { gtk_label_set_text (GTK_LABEL (options.lrc_filename_sample), buffer + 1); } else { gtk_label_set_text (GTK_LABEL (options.lrc_filename_sample), ""); } g_free (pattern); ol_metadata_free (metadata); } static void ol_option_list_select_changed (GtkTreeSelection *selection, gpointer data) { if (selection == NULL || data == NULL) return; struct ListExtraWidgets *widgets = (struct ListExtraWidgets *) data; GtkTreeModel *model; GtkTreeIter iter; gboolean selected = gtk_tree_selection_get_selected (selection, &model, &iter); if (widgets->entry != NULL) { gtk_widget_set_sensitive (widgets->entry, selected); if (selected) { gchar *text = NULL; gtk_tree_model_get (model, &iter, TEXT_COLUMN, &text, -1); if (text != NULL) { gtk_entry_set_text (GTK_ENTRY (widgets->entry), text); g_free (text); } else { gtk_entry_set_text (GTK_ENTRY (widgets->entry), ""); } } else { gtk_entry_set_text (GTK_ENTRY (widgets->entry), ""); } } if (widgets->extra_button != NULL) { gtk_widget_set_sensitive (widgets->extra_button, selected); } } static void ol_option_update_widget (OptionWidgets *widgets) { load_osd (); load_download (); load_general (); load_check_button_options (); load_radio_str_options (); load_entry_str_options (); load_spin_int_options (); load_scale_double_options (); load_color_str_options (); load_font_str_options (); load_combo_str_options (); init_toggle_properties (); } static void load_osd () { int i; OlConfigProxy *config = ol_config_proxy_get_instance (); ol_assert (config != NULL); /* Lrc align */ for (i = 0; i < 2; i++) { GtkRange *lrc_align = GTK_RANGE (options.lrc_align[i]); if (lrc_align != NULL) { char buffer[24]; sprintf (buffer, "OSD/lrc-align-%d", i); gtk_range_set_value (lrc_align, ol_config_proxy_get_double (config, buffer)); } } /* [In]Active lrc color */ GtkWidget **color_widgets[] = {options.active_lrc_color, options.inactive_lrc_color}; char *color_props[] = {"OSD/active-lrc-color", "OSD/inactive-lrc-color"}; int k; for (k = 0; k < 2; k++) { char ** lrc_color_str = ol_config_proxy_get_str_list (config, color_props[k], NULL); for (i = 0; i < OL_LINEAR_COLOR_COUNT; i++) { if (color_widgets[k][i] != NULL) { GtkColorButton *color_button = GTK_COLOR_BUTTON (color_widgets[k][i]); GdkColor color = ol_color_to_gdk_color (ol_color_from_string (lrc_color_str[i])); gtk_color_button_set_color (color_button, &color); } } g_strfreev (lrc_color_str); } /* OSD Line count */ int line_count = ol_config_proxy_get_int (config, "OSD/line-count"); if (line_count < 1) line_count = 1; if (line_count > 2) line_count = 2; line_count--; if (options.line_count[line_count] != NULL && GTK_IS_TOGGLE_BUTTON (options.line_count[line_count])) { GtkToggleButton *line_count_widget = GTK_TOGGLE_BUTTON (options.line_count[line_count]); gtk_toggle_button_set_active (line_count_widget, TRUE); } } static void init_signals () { int i; /* Connect RadioButton signals for string options */ for (i = 0; i < G_N_ELEMENTS (radio_str_options); i++) { struct RadioStringValues *value = radio_str_options[i].values; if (value == NULL) continue; for (; value->widget_name != NULL && value->value != NULL; value++) { GtkToggleButton *radio_button = GTK_TOGGLE_BUTTON (ol_gui_get_widget (value->widget_name)); if (radio_button != NULL) { g_signal_connect (G_OBJECT (radio_button), "toggled", G_CALLBACK (radio_str_changed), &radio_str_options[i]); } } } for (i = 0; i < G_N_ELEMENTS (toggle_properties); i++) { GtkWidget *widget = ol_gui_get_widget (toggle_properties[i].toggle_widget); if (GTK_IS_TOGGLE_BUTTON (widget)) { g_signal_connect (G_OBJECT (widget), "toggled", G_CALLBACK (toggle_set_property), &toggle_properties[i]); } } for (i = 0; i < G_N_ELEMENTS (entry_str_options); i++) { GtkWidget *widget = ol_gui_get_widget (entry_str_options[i].widget_name); if (GTK_IS_EDITABLE (widget)) { g_signal_connect (G_OBJECT (widget), "changed", G_CALLBACK (entry_str_changed), &entry_str_options[i]); } } for (i = 0; i < G_N_ELEMENTS (spin_int_options); i++) { GtkWidget *widget = ol_gui_get_widget (spin_int_options[i].widget_name); if (GTK_IS_SPIN_BUTTON (widget)) { g_signal_connect (G_OBJECT (widget), "value-changed", G_CALLBACK (spin_int_changed), &spin_int_options[i]); } } for (i = 0; i < G_N_ELEMENTS (scale_double_options); i++) { GtkWidget *widget = ol_gui_get_widget (scale_double_options[i].widget_name); if (GTK_IS_RANGE (widget)) { g_signal_connect (G_OBJECT (widget), "value-changed", G_CALLBACK (scale_double_changed), &scale_double_options[i]); } } for (i = 0; i < G_N_ELEMENTS (color_str_options); i++) { GtkWidget *widget = ol_gui_get_widget (color_str_options[i].widget_name); if (GTK_IS_COLOR_BUTTON (widget)) { g_signal_connect (G_OBJECT (widget), "color-set", G_CALLBACK (color_str_changed), &color_str_options[i]); } } for (i = 0; i < G_N_ELEMENTS (font_str_options); i++) { GtkWidget *widget = ol_gui_get_widget (font_str_options[i].widget_name); if (GTK_IS_FONT_BUTTON (widget)) { g_signal_connect (G_OBJECT (widget), "font-set", G_CALLBACK (font_str_changed), &font_str_options[i]); } } for (i = 0; i < G_N_ELEMENTS (combo_str_options); i++) { GtkWidget *widget = ol_gui_get_widget (combo_str_options[i].widget_name); if (GTK_IS_COMBO_BOX (widget)) { g_signal_connect (G_OBJECT (widget), "changed", G_CALLBACK (combo_str_changed), &combo_str_options[i]); } } } static void init_toggle_properties () { int i; for (i = 0; i < G_N_ELEMENTS (toggle_properties); i++) { toggle_set_property (NULL, &toggle_properties[i]); } } static void toggle_set_property (GtkToggleButton *widget, struct TogglePropertyOptions *option) { ol_assert (option != NULL); if (widget == NULL) { widget = GTK_TOGGLE_BUTTON (ol_gui_get_widget (option->toggle_widget)); } GtkWidget *target = ol_gui_get_widget (option->target_widget); ol_assert (widget != NULL); ol_assert (target != NULL); GValue value = {0}; g_value_init (&value, G_TYPE_BOOLEAN); gboolean prop = gtk_toggle_button_get_active (widget); if (option->negated) prop = !prop; g_value_set_boolean (&value, prop); g_object_set_property (G_OBJECT (target), option->property, &value); g_value_unset (&value); } static void radio_str_changed (GtkToggleButton *button, struct RadioStringOptions *option) { ol_assert (GTK_IS_TOGGLE_BUTTON (button)); ol_assert (option != NULL); if (!gtk_toggle_button_get_active (button)) return; OlConfigProxy *config = ol_config_proxy_get_instance (); const char *config_value = NULL; struct RadioStringValues *value = option->values; for (; value->widget_name != NULL && value->value != NULL; value++) { GtkToggleButton *radio_button = GTK_TOGGLE_BUTTON (ol_gui_get_widget (value->widget_name)); if (radio_button != NULL && gtk_toggle_button_get_active (radio_button)) { config_value = value->value; break; } } if (config_value != NULL) { ol_config_proxy_set_string (config, option->key, config_value); } } static void entry_str_changed (GtkEditable *widget, struct WidgetConfigOptions *option) { ol_assert (GTK_IS_EDITABLE (widget)); ol_assert (option != NULL); OlConfigProxy *config = ol_config_proxy_get_instance (); char *value = gtk_editable_get_chars (widget, 0, -1); ol_config_proxy_set_string (config, option->key, value); g_free (value); } static void spin_int_changed (GtkSpinButton *widget, struct WidgetConfigOptions *option) { ol_assert (GTK_IS_SPIN_BUTTON (widget)); ol_assert (option != NULL); OlConfigProxy *config = ol_config_proxy_get_instance (); int value = gtk_spin_button_get_value (widget); ol_config_proxy_set_int (config, option->key, value); } static void scale_double_changed (GtkScale *widget, struct WidgetConfigOptions *option) { ol_assert (GTK_IS_RANGE (widget)); ol_assert (option != NULL); OlConfigProxy *config = ol_config_proxy_get_instance (); double value = gtk_range_get_value (GTK_RANGE (widget)); ol_config_proxy_set_double (config, option->key, value); } static void color_str_changed (GtkColorButton *widget, struct WidgetConfigOptions *option) { ol_assert (GTK_IS_COLOR_BUTTON (widget)); ol_assert (option != NULL); OlConfigProxy *config = ol_config_proxy_get_instance (); GdkColor gcolor; gtk_color_button_get_color (GTK_COLOR_BUTTON (widget), &gcolor); OlColor color = ol_color_from_gdk_color (gcolor); const char *value = ol_color_to_string (color); ol_config_proxy_set_string (config, option->key, value); } static void font_str_changed (GtkFontButton *widget, struct WidgetConfigOptions *option) { ol_assert (GTK_IS_FONT_BUTTON (widget)); ol_assert (option != NULL); OlConfigProxy *config = ol_config_proxy_get_instance (); const char *value = gtk_font_button_get_font_name (GTK_FONT_BUTTON (widget)); ol_config_proxy_set_string (config, option->key, value); } static void combo_str_changed (GtkComboBox *widget, struct ComboStringOptions *option) { ol_assert (GTK_IS_COMBO_BOX (widget)); ol_assert (option != NULL); OlConfigProxy *config = ol_config_proxy_get_instance (); int index = gtk_combo_box_get_active (widget); if (index < g_strv_length ((char**)option->values)) ol_config_proxy_set_string (config, option->key, option->values[index]); else ol_errorf ("Index of combobox %s out of range. Value is %d\n", option->widget_name, index); } static void load_check_button_options () { int i = 0; OlConfigProxy *config = ol_config_proxy_get_instance (); if (config == NULL) return; for (i = 0; i < G_N_ELEMENTS (check_button_options); i++) { GtkToggleButton *check_button = GTK_TOGGLE_BUTTON (ol_gui_get_widget (check_button_options[i].widget_name)); if (check_button_options != NULL) { gtk_toggle_button_set_active (check_button, ol_config_proxy_get_bool (config, check_button_options[i].key)); } } } static void save_check_button_option (struct CheckButtonOptions* opt) { ol_assert (opt != NULL); OlConfigProxy *config = ol_config_proxy_get_instance (); if (config == NULL) return; GtkToggleButton *check_button = GTK_TOGGLE_BUTTON (ol_gui_get_widget (opt->widget_name)); ol_config_proxy_set_bool (config, opt->key, gtk_toggle_button_get_active (check_button)); } static void load_radio_str_options () { int i = 0; OlConfigProxy *config = ol_config_proxy_get_instance (); if (config == NULL) return; for (i = 0; i < G_N_ELEMENTS (radio_str_options); i++) { char *config_value = ol_config_proxy_get_string (config, radio_str_options[i].key); if (config_value == NULL) continue; struct RadioStringValues *value = radio_str_options[i].values; for (; value->widget_name != NULL && value->value != NULL; value++) { if (strcmp (value->value, config_value) == 0) break; } if (value->widget_name == NULL || value->value == NULL) value = radio_str_options[i].values; GtkToggleButton *radio_button = GTK_TOGGLE_BUTTON (ol_gui_get_widget (value->widget_name)); if (radio_button != NULL) { gtk_toggle_button_set_active (radio_button, TRUE); } g_free (config_value); } } static void load_entry_str_options () { int i = 0; OlConfigProxy *config = ol_config_proxy_get_instance (); if (config == NULL) return; for (i = 0; i < G_N_ELEMENTS (entry_str_options); i++) { char *config_value = ol_config_proxy_get_string (config, entry_str_options[i].key); if (config_value == NULL) continue; GtkEntry *entry = GTK_ENTRY (ol_gui_get_widget (entry_str_options[i].widget_name)); if (entry != NULL) { gtk_entry_set_text (entry, config_value); } g_free (config_value); } } static void load_spin_int_options () { int i = 0; OlConfigProxy *config = ol_config_proxy_get_instance (); if (config == NULL) return; for (i = 0; i < G_N_ELEMENTS (spin_int_options); i++) { int config_value = ol_config_proxy_get_int (config, spin_int_options[i].key); GtkSpinButton *spin = GTK_SPIN_BUTTON (ol_gui_get_widget (spin_int_options[i].widget_name)); if (spin != NULL) { gtk_spin_button_set_value (spin, config_value); } } } static void load_scale_double_options () { int i = 0; OlConfigProxy *config = ol_config_proxy_get_instance (); if (config == NULL) return; for (i = 0; i < G_N_ELEMENTS (scale_double_options); i++) { double config_value = ol_config_proxy_get_double (config, scale_double_options[i].key); GtkWidget *range = ol_gui_get_widget (scale_double_options[i].widget_name); if (GTK_IS_RANGE (range)) { gtk_range_set_value (GTK_RANGE (range), config_value); } } } static void load_color_str_options () { int i = 0; OlConfigProxy *config = ol_config_proxy_get_instance (); if (config == NULL) return; for (i = 0; i < G_N_ELEMENTS (color_str_options); i++) { char* config_value = ol_config_proxy_get_string (config, color_str_options[i].key); if (config_value == NULL) continue; GtkWidget *button = ol_gui_get_widget (color_str_options[i].widget_name); if (GTK_IS_COLOR_BUTTON (button)) { OlColor color = ol_color_from_string (config_value); GdkColor gcolor = ol_color_to_gdk_color (color); gtk_color_button_set_color (GTK_COLOR_BUTTON (button), &gcolor); } g_free (config_value); } } static void load_font_str_options () { int i = 0; OlConfigProxy *config = ol_config_proxy_get_instance (); if (config == NULL) return; for (i = 0; i < G_N_ELEMENTS (font_str_options); i++) { char* config_value = ol_config_proxy_get_string (config, font_str_options[i].key); if (config_value == NULL) continue; GtkWidget *button = ol_gui_get_widget (font_str_options[i].widget_name); if (GTK_IS_FONT_BUTTON (button)) { gtk_font_button_set_font_name (GTK_FONT_BUTTON (button), config_value); } g_free (config_value); } } static void load_combo_str_options () { int i; int index; const char **combo_value; GtkComboBox *combo; OlConfigProxy *config = ol_config_proxy_get_instance (); for (i = 0; i < G_N_ELEMENTS (combo_str_options); i++) { char *config_value = ol_config_proxy_get_string (config, combo_str_options[i].key); if (config_value == NULL) continue; combo = GTK_COMBO_BOX (ol_gui_get_widget (combo_str_options[i].widget_name)); if (combo == NULL) continue; combo_value = combo_str_options[i].values; for (index = 0; *combo_value != NULL; index++, combo_value++) { if (strcmp (config_value, *combo_value) == 0) break; } if (combo_value == NULL) index = 0; gtk_combo_box_set_active (combo, index); g_free (config_value); } } static void load_download () { /* Download engine */ GList *source_infos; source_infos = ol_lyric_source_list_sources (ol_app_get_lyric_source ()); _disconnect_download_engine_changed (GTK_TREE_VIEW (options.download_engine), ol_option_download_engine_changed); ol_lyric_source_list_set_info_list (GTK_TREE_VIEW (options.download_engine), source_infos); _connect_download_engine_changed (GTK_TREE_VIEW (options.download_engine), ol_option_download_engine_changed); for (; source_infos; source_infos = g_list_delete_link (source_infos, source_infos)) { ol_lyric_source_info_free (source_infos->data); } } static char ** get_list_content (GtkTreeView *view) { if (view == NULL) return NULL; GtkTreeModel *model = gtk_tree_view_get_model (view); int cnt = gtk_tree_model_iter_n_children (model, NULL); char **list = g_new0 (char *, cnt + 1); int i = 0; GtkTreeIter iter; if (gtk_tree_model_get_iter_first (model, &iter)) { do { gtk_tree_model_get (model, &iter, TEXT_COLUMN, &list[i], -1); i++; } while (gtk_tree_model_iter_next (model, &iter)); } return list; } static void set_list_content (GtkTreeView *view, char **list) { GtkTreeModel *model = gtk_tree_view_get_model (view); GtkListStore *store = GTK_LIST_STORE (model); gtk_list_store_clear (store); int i; GtkTreeIter iter; for (i = 0; list[i] != NULL; i++) { gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, TEXT_COLUMN, list[i], REMOVE_COLUMN, GTK_STOCK_REMOVE, -1); GtkTreePath *path = gtk_tree_model_get_path (model, &iter); gtk_tree_path_free (path); } } static void load_general () { OlConfigProxy *config = ol_config_proxy_get_instance (); if (options.lrc_path != NULL) { GtkTreeView *view = GTK_TREE_VIEW (options.lrc_path); char **list = ol_config_proxy_get_str_list (config, "General/lrc-path", NULL); if (list != NULL) { set_list_content (view, list); g_strfreev (list); } } if (options.lrc_filename != NULL) { GtkTreeView *view = GTK_TREE_VIEW (options.lrc_filename); char **list = ol_config_proxy_get_str_list (config, "General/lrc-filename", NULL); if (list != NULL) { set_list_content (view, list); g_strfreev (list); } } /* Startup player */ char *player_cmd = ol_config_proxy_get_string (config, "General/startup-player"); gboolean startup_custom = TRUE; if (options.startup_player_cb != NULL) { GtkComboBox *cb = GTK_COMBO_BOX (options.startup_player_cb); if (ol_is_string_empty (player_cmd)) { gtk_combo_box_set_active (cb, 0); startup_custom = FALSE; } else { int i = 1; GtkTreeModel *liststore = gtk_combo_box_get_model (GTK_COMBO_BOX (options.startup_player_cb)); GtkTreeIter iter; gboolean valid; for (valid = gtk_tree_model_get_iter_from_string (liststore, &iter, "1"); valid && startup_custom; valid = gtk_tree_model_iter_next (liststore, &iter), i++) { char *cmd = NULL; gtk_tree_model_get (liststore, &iter, 1, &cmd, -1); if (cmd != NULL && strcmp (player_cmd, cmd) == 0) { gtk_combo_box_set_active (cb, i); startup_custom = FALSE; } g_free (cmd); } if (startup_custom) { gtk_combo_box_set_active (cb, i - 1); } } } if (options.startup_player != NULL) { gtk_widget_set_sensitive (options.startup_player, startup_custom); gtk_entry_set_text (GTK_ENTRY (options.startup_player), player_cmd); } g_free (player_cmd); _display_mode_changed (config, "General/display-mode-osd", NULL); _display_mode_changed (config, "General/display-mode-scroll", NULL); g_signal_connect (config, "changed::General/display-mode-osd", G_CALLBACK (_display_mode_changed), NULL); g_signal_connect (config, "changed::General/display-mode-scroll", G_CALLBACK (_display_mode_changed), NULL); } void ol_option_close_clicked (GtkWidget *widget) { GtkWidget *toplevel = gtk_widget_get_toplevel (widget); if (GTK_WIDGET_TOPLEVEL (toplevel)) { gtk_widget_hide (toplevel); } } static void append_button_to_column (GtkTreeViewColumn *column, GtkWidget *list, struct ListExtraButton *button) { if (column == NULL) return; if (button == NULL || button->click_func == NULL || button->stock_name == NULL) return; GtkCellRenderer *btncell; btncell = ol_cell_renderer_button_new (); GValue remove_stock = {0}; g_value_init (&remove_stock, G_TYPE_STRING); g_value_set_static_string (&remove_stock, button->stock_name); g_object_set_property (G_OBJECT (btncell), "stock", &remove_stock); g_value_unset (&remove_stock); g_signal_connect (G_OBJECT (btncell), "clicked", G_CALLBACK (button->click_func), (gpointer) list); gtk_tree_view_column_pack_start (column, btncell, FALSE); } static GtkTreeViewColumn * get_list_column (GtkWidget *list, struct ListExtraButton *buttons) { GtkTreeViewColumn *column; GtkCellRenderer *textcell; textcell = gtk_cell_renderer_text_new (); column = gtk_tree_view_column_new_with_attributes ("Pattern", textcell, "text", TEXT_COLUMN, NULL); GValue text_editable = {0}; g_value_init (&text_editable, G_TYPE_BOOLEAN); g_value_set_boolean (&text_editable, TRUE); g_object_set_property (G_OBJECT (textcell), "editable", &text_editable); g_value_unset (&text_editable); while (buttons != NULL && buttons->stock_name != NULL && buttons->click_func != NULL) { append_button_to_column (column, list, buttons); buttons++; } struct ListExtraButton remove_button = { GTK_STOCK_REMOVE, list_remove_clicked, }; append_button_to_column (column, list, &remove_button); return column; } static void init_list (struct ListExtraWidgets *widgets, struct ListExtraButton *buttons) { GtkTreeView *list = GTK_TREE_VIEW (widgets->list); if (list == NULL) return; GtkListStore *store = gtk_list_store_new (N_COLUMN, G_TYPE_STRING, G_TYPE_STRING); /* gtk_tree_view_column_add_attribute (column, */ /* btncell, */ /* "stock", REMOVE_COLUMN); */ gtk_tree_view_append_column (list, get_list_column (widgets->list, buttons)); gtk_tree_view_set_model (list, GTK_TREE_MODEL (store)); GtkTreeSelection *select; select = gtk_tree_view_get_selection (list); gtk_tree_selection_set_mode (select, GTK_SELECTION_SINGLE); g_signal_connect (G_OBJECT (select), "changed", G_CALLBACK (ol_option_list_select_changed), (gpointer) widgets); ol_option_list_select_changed (select, (gpointer) widgets); gtk_tree_view_set_reorderable (list, TRUE); /* Setup extra widgets (add, remove, text entry)*/ if (widgets->save_func != NULL) { g_signal_connect (G_OBJECT (store), "row-changed", G_CALLBACK (widgets->save_func), (gpointer) widgets); g_signal_connect (G_OBJECT (store), "row-deleted", G_CALLBACK (widgets->save_func), (gpointer) widgets); g_signal_connect (G_OBJECT (store), "row-inserted", G_CALLBACK (widgets->save_func), (gpointer) widgets); g_signal_connect (G_OBJECT (store), "rows-reordered", G_CALLBACK (widgets->save_func), (gpointer) widgets); } if (widgets->entry != NULL) { g_signal_connect (G_OBJECT (widgets->entry), "changed", G_CALLBACK (ol_option_list_entry_changed), (gpointer) widgets); } if (widgets->add_button != NULL) { g_signal_connect (G_OBJECT (widgets->add_button), "clicked", G_CALLBACK (ol_option_list_add_clicked), (gpointer) widgets); } } static void list_remove_clicked (GtkCellRenderer *cell, gchar *path, GtkTreeView *list) { ol_logf (OL_DEBUG, "%s\n" " path:%s\n", __FUNCTION__, path); if (list == NULL || path == NULL || !GTK_IS_TREE_VIEW (list)) return; ol_logf (OL_DEBUG, " test\n"); GtkTreeModel *model = gtk_tree_view_get_model (list); GtkTreeIter iter; GtkTreePath *tree_path; tree_path = gtk_tree_path_new_from_string (path); if (gtk_tree_model_get_iter (model, &iter, tree_path)) { ol_logf (OL_DEBUG, " path valid\n"); gtk_list_store_remove (GTK_LIST_STORE (model), &iter); } gtk_tree_path_free (tree_path); } static void insert_to_filename (const char *text) { ol_log_func (); ol_debugf (" text:%s\n", text); ol_assert (lrc_filename_widgets.entry != NULL); ol_assert (text != NULL); GtkEditable *editable = GTK_EDITABLE (lrc_filename_widgets.entry); gtk_editable_delete_selection (editable); gint position = gtk_editable_get_position (editable); gtk_editable_insert_text (editable, text, strlen (text), &position); ol_debugf (" position after: %d\n", position); gtk_widget_grab_focus (lrc_filename_widgets.entry); gtk_editable_set_position (editable, position); } void ol_option_menu_title_activate (GtkMenuItem *menuitem, gpointer user_data) { insert_to_filename ("%t"); } void ol_option_menu_artist_activate (GtkMenuItem *menuitem, gpointer user_data) { insert_to_filename ("%p"); } void ol_option_menu_album_activate (GtkMenuItem *menuitem, gpointer user_data) { insert_to_filename ("%a"); } void ol_option_menu_number_activate (GtkMenuItem *menuitem, gpointer user_data) { insert_to_filename ("%n"); } void ol_option_menu_filename_activate (GtkMenuItem *menuitem, gpointer user_data) { insert_to_filename ("%f"); } static void _connect_download_engine_changed (GtkTreeView *list, void (*callback) (GtkTreeModel *model)) { ol_assert (GTK_IS_TREE_VIEW (list)); ol_assert (callback != NULL); GtkTreeModel *model = gtk_tree_view_get_model (list); g_signal_connect (G_OBJECT (model), "row-changed", (GCallback) callback, NULL); g_signal_connect (G_OBJECT (model), "row-deleted", (GCallback) callback, NULL); g_signal_connect (G_OBJECT (model), "row-inserted", (GCallback) callback, NULL); g_signal_connect (G_OBJECT (model), "rows-reordered", (GCallback) callback, NULL); } static void _disconnect_download_engine_changed (GtkTreeView *list, void (*callback) (GtkTreeModel *model)) { ol_assert (GTK_IS_TREE_VIEW (list)); ol_assert (callback != NULL); GtkTreeModel *model = gtk_tree_view_get_model (list); g_signal_handlers_disconnect_by_func (model, (GCallback) callback, NULL); } static void init_startup_player (GtkWidget *widget) { GtkComboBox *cb = GTK_COMBO_BOX (widget); if (cb == NULL) return; gtk_combo_box_append_text (cb, _("Choose on startup")); GtkListStore *liststore = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (widget))); /* TODO: */ GList *players = NULL; /* GList *players = g_list_sort (ol_player_get_support_players (), */ /* (GCompareFunc) ol_app_info_cmp); */ for (; players != NULL; players = g_list_delete_link (players, players)) { GAppInfo *app_info = players->data; GtkTreeIter iter; gtk_list_store_append (liststore, &iter); gtk_list_store_set (liststore, &iter, 0, g_app_info_get_display_name (app_info), 1, g_app_info_get_commandline (app_info), -1); g_object_unref (G_OBJECT (app_info)); } /* gtk_list_store_append (liststore, &iter); */ /* gtk_list_store_set (liststore, &iter, */ /* 0, "Customize", */ /* 1, "", */ /* -1); */ gtk_combo_box_append_text (cb, _("Customize")); } static void _display_mode_changed (OlConfigProxy *config, const gchar *key, gpointer data) { gboolean is_osd = strcmp(key, "General/display-mode-osd") == 0; gboolean display_enabled = ol_config_proxy_get_bool (config, key); GtkToggleButton *display_button = GTK_TOGGLE_BUTTON (is_osd ? options.display_mode_osd : options.display_mode_scroll); gtk_toggle_button_set_active (display_button, display_enabled); } void ol_option_show () { static GtkWidget *window = NULL; if (window == NULL) { window = ol_gui_get_widget ("optiondialog"); ol_assert (window != NULL); g_signal_connect (window, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL); options.display_mode_osd = ol_gui_get_widget ("display-mode-osd"); options.display_mode_scroll = ol_gui_get_widget ("display-mode-scroll"); options.close = ol_gui_get_widget ("option-close"); options.font = ol_gui_get_widget ("osd-font"); options.outline_width = ol_gui_get_widget ("outline-width"); options.lrc_align[0] = ol_gui_get_widget ("lrc-align-0"); options.lrc_align[1] = ol_gui_get_widget ("lrc-align-1"); options.active_lrc_color[0] = ol_gui_get_widget ("active-lrc-color-0"); options.active_lrc_color[1] = ol_gui_get_widget ("active-lrc-color-1"); options.active_lrc_color[2] = ol_gui_get_widget ("active-lrc-color-2"); options.inactive_lrc_color[0] = ol_gui_get_widget ("inactive-lrc-color-0"); options.inactive_lrc_color[1] = ol_gui_get_widget ("inactive-lrc-color-1"); options.inactive_lrc_color[2] = ol_gui_get_widget ("inactive-lrc-color-2"); options.line_count[0] = ol_gui_get_widget ("line-count-1"); options.line_count[1] = ol_gui_get_widget ("line-count-2"); options.download_engine = ol_gui_get_widget ("download-engine"); options.lrc_path = ol_gui_get_widget ("lrc-path"); options.lrc_path_text = ol_gui_get_widget ("lrc-path-text"); options.lrc_filename = ol_gui_get_widget ("lrc-filename"); options.lrc_filename_text = ol_gui_get_widget ("lrc-filename-text"); options.lrc_filename_sample = ol_gui_get_widget ("lrc-filename-sample"); options.startup_player = ol_gui_get_widget ("startup-player"); options.startup_player_cb = ol_gui_get_widget ("startup-player-cb"); init_startup_player (options.startup_player_cb); /* Init download engine combobox */ ol_lyric_source_list_init (GTK_TREE_VIEW (options.download_engine)); lrc_path_widgets.entry = options.lrc_path_text; lrc_path_widgets.list = options.lrc_path; lrc_path_widgets.add_button = ol_gui_get_widget ("add-lrc-path"); lrc_path_widgets.extra_button = ol_gui_get_widget ("lrc-path-browse"); lrc_path_widgets.save_func = ol_option_save_path_pattern; struct ListExtraButton path_buttons[] = { /* {GTK_STOCK_DIRECTORY, list_browse_clicked}, */ {NULL, NULL}, }; init_list (&lrc_path_widgets, path_buttons); lrc_filename_widgets.entry = options.lrc_filename_text; lrc_filename_widgets.list = options.lrc_filename; lrc_filename_widgets.add_button = ol_gui_get_widget ("add-lrc-filename"); lrc_filename_widgets.extra_button = ol_gui_get_widget ("lrc-filename-pattern"); lrc_filename_widgets.save_func = ol_option_save_file_pattern; struct ListExtraButton file_buttons[] = { /* {GTK_STOCK_INFO, list_pattern_clicked}, */ {NULL, NULL}, }; init_list (&lrc_filename_widgets, file_buttons); options.path_chooser = gtk_file_chooser_dialog_new (_("Select a folder"), GTK_WINDOW (window), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); #if GTK_CHECK_VERSION (2, 18, 0) gtk_file_chooser_set_create_folders (GTK_FILE_CHOOSER (options.path_chooser), TRUE); #endif options.filename_menu = ol_gui_get_widget ("filename-pattern-popup"); /* display the about button at the left edge */ gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (ol_gui_get_widget ("dialog-action_area2")), ol_gui_get_widget ("option-aboug"), TRUE); init_signals (); } ol_option_update_widget (&options); gtk_widget_show (GTK_WIDGET (window)); } osdlyrics-0.5.5~rc1+dfsg1/src/ol_option.h000066400000000000000000000016311345713535600203170ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_OPTION_H_ #define _OL_OPTION_H_ void ol_option_show (); #endif /* _OL_OPTION_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_osd_module.c000066400000000000000000000621421345713535600211400ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include "ol_metadata.h" #include "ol_config_proxy.h" #include "ol_player.h" #include "ol_osd_window.h" #include "ol_osd_toolbar.h" #include "ol_osd_module.h" #include "ol_lrc.h" #include "ol_stock.h" #include "ol_menu.h" #include "ol_app.h" #include "ol_utils.h" #include "ol_debug.h" const int MESSAGE_DURATION_MS = 3000; typedef struct _OlOsdModule OlOsdModule; struct OlLrc; struct _OlOsdModule { OlPlayer *player; OlMetadata *metadata; gint lrc_id; gint lrc_next_id; gint current_line; gint line_count; gboolean force_refresh_on_set_played_time; OlLrc *lrc; OlOsdWindow *window; OlOsdToolbar *toolbar; guint message_source; GList *config_bindings; }; typedef void (*_ConfigSetFunc) (OlConfigProxy *config, const gchar *key, OlOsdModule *osd); struct _ConfigBinding { OlOsdModule *osd; guint change_handler; _ConfigSetFunc setter; }; struct _ConfigMapping { const gchar *key; _ConfigSetFunc setter; }; /** interfaces */ static OlOsdModule* ol_osd_module_new (struct OlDisplayModule *module, OlPlayer *player); static void ol_osd_module_free (struct OlDisplayModule *module); static void _metadata_changed_cb (OlPlayer *player, OlOsdModule *module); static void _update_metadata (OlOsdModule *module); static gboolean _advance_to_nonempty_lyric (OlLrcIter *iter); static void ol_osd_module_set_played_time (struct OlDisplayModule *module, guint64 played_time); static void ol_osd_module_set_lrc (struct OlDisplayModule *module, OlLrc *lrc_file); static void ol_osd_module_set_message (struct OlDisplayModule *module, const char *message, int duration_ms); static void ol_osd_module_search_message (struct OlDisplayModule *module, const char *message); static void ol_osd_module_search_fail_message (struct OlDisplayModule *module, const char *message); static void ol_osd_module_download_fail_message (struct OlDisplayModule *module, const char *message); static void ol_osd_module_clear_message (struct OlDisplayModule *module); /** internal functions */ /** * @brief Gets the real lyric of the given lyric * A REAL lyric is the nearest lyric to the given lyric, whose text is not empty * If the given lyric text is not empty, the given lyric is a real lyric * If not real lyric available, returns NULL * @param lrc An const struct OlLrcItem * * @return The real lyric of the lrc. returns NULL if not available */ static void ol_osd_module_update_next_lyric (OlOsdModule *osd, OlLrcIter *iter); static void ol_osd_module_init_osd (OlOsdModule *osd); static gboolean hide_message (OlOsdModule *osd); static gboolean is_message_displayed (OlOsdModule *osd); static void reset_lyrics_state (OlOsdModule *osd); static void hide_lyrics (OlOsdModule *osd); /* OSD Window signal handlers */ static void ol_osd_moved_handler (OlOsdWindow *osd, gpointer data); static void ol_osd_resize_handler (OlOsdWindow *osd, gpointer data); static gboolean ol_osd_button_release (OlOsdWindow *osd, GdkEventButton *event, gpointer data); static void ol_osd_scroll (OlOsdWindow *osd, GdkEventScroll *event, gpointer data); /* Config handlers */ static struct _ConfigBinding *_bind_config (const char *key, _ConfigSetFunc setter, OlOsdModule *osd); static void _unbind_config (struct _ConfigBinding *binding); static void _bind_all_config (OlOsdModule *osd); static void _config_changed_cb (OlConfigProxy *config, const char *key, struct _ConfigBinding *binding); static void _visible_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd); static void _width_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd); static void _mode_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd); static void _locked_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd); static void _line_count_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd); static void _font_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd); static void _pos_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd); static void _lrc_align_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd); static void _active_color_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd); static void _inactive_color_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd); static void _translucent_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd); static void _outline_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd); static void _blur_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd); static struct _ConfigMapping _config_mapping[] = { { ".visible", _visible_changed_cb }, { "OSD/width", _width_changed_cb }, { "OSD/osd-window-mode", _mode_changed_cb }, { "OSD/locked", _locked_changed_cb }, { "OSD/line-count", _line_count_changed_cb }, { "OSD/font-name", _font_changed_cb }, { "OSD/x", _pos_changed_cb }, { "OSD/y", _pos_changed_cb }, { "OSD/lrc-align-0", _lrc_align_changed_cb }, { "OSD/lrc-align-1", _lrc_align_changed_cb }, { "OSD/active-lrc-color", _active_color_changed_cb }, { "OSD/inactive-lrc-color", _inactive_color_changed_cb }, { "OSD/translucent-on-mouse-over", _translucent_changed_cb }, { "OSD/outline-width", _outline_changed_cb }, { "OSD/blur-radius", _blur_changed_cb }, }; static gboolean _config_is_setting = FALSE; static void ol_osd_moved_handler (OlOsdWindow *osd, gpointer data) { ol_log_func (); if (_config_is_setting) return; _config_is_setting = TRUE; OlConfigProxy *config = ol_config_proxy_get_instance (); int x, y; ol_osd_window_get_pos (osd, &x, &y); ol_config_proxy_set_int (config, "OSD/x", x); ol_config_proxy_set_int (config, "OSD/y", y); _config_is_setting = FALSE; } static void ol_osd_resize_handler (OlOsdWindow *osd, gpointer data) { ol_log_func (); if (_config_is_setting) return; OlConfigProxy *config = ol_config_proxy_get_instance (); int width = ol_osd_window_get_width (osd); ol_config_proxy_set_int (config, "OSD/width", width); } static gboolean ol_osd_button_release (OlOsdWindow *osd, GdkEventButton *event, gpointer data) { if (event->button == 3) { gtk_menu_popup (GTK_MENU (ol_menu_get_popup ()), NULL, NULL, NULL, NULL, event->button, event->time); return TRUE; } return FALSE; } static void ol_osd_scroll (OlOsdWindow *osd, GdkEventScroll *event, gpointer data) { int doffset = 0; if (event->direction == GDK_SCROLL_DOWN || event->direction == GDK_SCROLL_RIGHT) doffset = -200; else if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_LEFT) doffset = 200; ol_app_adjust_lyric_offset (doffset); } static void _config_changed_cb (OlConfigProxy *config, const char *key, struct _ConfigBinding *binding) { if (_config_is_setting) return; _config_is_setting = TRUE; binding->setter (config, key, binding->osd); _config_is_setting = FALSE; } static void _visible_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd) { gboolean visible = ol_config_proxy_get_bool (config, key); if (visible) { gtk_widget_show (GTK_WIDGET (osd->window)); } else { gtk_widget_hide (GTK_WIDGET (osd->window)); } } static void _width_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd) { ol_osd_window_set_width (osd->window, ol_config_proxy_get_int (config, key)); } static void _mode_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd) { gchar *mode = ol_config_proxy_get_string (config, key); if (strcmp (mode, "dock") == 0) ol_osd_window_set_mode (osd->window, OL_OSD_WINDOW_DOCK); else ol_osd_window_set_mode (osd->window, OL_OSD_WINDOW_NORMAL); g_free (mode); } static void _locked_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd) { ol_osd_window_set_locked (osd->window, ol_config_proxy_get_bool (config, key)); } static void _line_count_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd) { osd->line_count = ol_config_proxy_get_int (config, key); ol_osd_window_set_line_count (osd->window, osd->line_count); } static void _font_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd) { gchar *font = ol_config_proxy_get_string (config, key); ol_assert (font != NULL); ol_osd_window_set_font_name (osd->window, font); g_free (font); } static void _pos_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd) { ol_osd_window_move (osd->window, ol_config_proxy_get_int (config, "OSD/x"), ol_config_proxy_get_int (config, "OSD/y")); } static void _lrc_align_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd) { int line = 0; if (key[strlen (key) - 1] == '1') line = 1; ol_osd_window_set_line_alignment (osd->window, line, ol_config_proxy_get_double (config, key)); } static void _active_color_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd) { gsize len; char **color_str = ol_config_proxy_get_str_list (config, key, &len); ol_debugf ("len = %d\n", (int)len); if (len != OL_LINEAR_COLOR_COUNT) return; if (color_str != NULL) { OlColor *colors = ol_color_from_str_list ((const char**)color_str, NULL); ol_osd_window_set_active_colors (osd->window, colors[0], colors[1], colors[2]); g_free (colors); g_strfreev (color_str); } } static void _inactive_color_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd) { gsize len; char **color_str = ol_config_proxy_get_str_list (config, key, &len); ol_debugf ("len = %d\n", (int)len); if (len != OL_LINEAR_COLOR_COUNT) return; if (color_str != NULL) { OlColor *colors = ol_color_from_str_list ((const char**)color_str, NULL); ol_osd_window_set_inactive_colors (osd->window, colors[0], colors[1], colors[2]); g_free (colors); g_strfreev (color_str); } } static void _translucent_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd) { ol_osd_window_set_translucent_on_mouse_over (osd->window, ol_config_proxy_get_bool (config, key)); } static void _outline_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd) { ol_osd_window_set_outline_width (osd->window, ol_config_proxy_get_int (config, key)); } static void _blur_changed_cb (OlConfigProxy *config, const char *key, OlOsdModule *osd) { ol_osd_window_set_blur_radius (osd->window, ol_config_proxy_get_double (config, key)); } static void ol_osd_module_update_next_lyric (OlOsdModule *osd, OlLrcIter *iter) { if (osd->line_count == 1) { osd->lrc_next_id = -1; return; } if (ol_lrc_iter_next (iter)) _advance_to_nonempty_lyric (iter); gint id; const char *text = NULL; if (ol_lrc_iter_is_valid (iter)) { id = ol_lrc_iter_get_id (iter); text = ol_lrc_iter_get_text (iter); } else { id = -1; text = ""; } if (osd->lrc_next_id != id) { int next_line = 1 - osd->current_line; osd->lrc_next_id = id; ol_osd_window_set_lyric (osd->window, next_line, text); ol_osd_window_set_percentage (osd->window, next_line, 0.0); } } static void _bind_all_config (OlOsdModule *osd) { int i; for (i = 0; i < G_N_ELEMENTS (_config_mapping); i++) { struct _ConfigBinding *binding = _bind_config (_config_mapping[i].key, _config_mapping[i].setter, osd); osd->config_bindings = g_list_prepend (osd->config_bindings, binding); } } static struct _ConfigBinding * _bind_config (const char *key, _ConfigSetFunc setter, OlOsdModule *osd) { ol_assert_ret (key != NULL, FALSE); OlConfigProxy *config = ol_config_proxy_get_instance (); struct _ConfigBinding *binding = g_new (struct _ConfigBinding, 1); gchar *signal = g_strdup_printf ("changed::%s", key); binding->osd = osd; binding->setter = setter; binding->change_handler = g_signal_connect (config, signal, (GCallback) _config_changed_cb, binding); g_free (signal); setter (config, key, osd); return binding; } static void _unbind_config (struct _ConfigBinding *binding) { OlConfigProxy *config = ol_config_proxy_get_instance (); ol_assert (binding != NULL); g_signal_handler_disconnect (config, binding->change_handler); g_free (binding); } static void ol_osd_module_init_osd (OlOsdModule *osd) { osd->window = OL_OSD_WINDOW (ol_osd_window_new ()); if (osd->window == NULL) return; GtkIconTheme *icontheme = gtk_icon_theme_get_default (); GdkPixbuf *bg = gtk_icon_theme_load_icon (icontheme, OL_STOCK_OSD_BG, 32, 0, NULL); ol_osd_window_set_bg (osd->window, bg); g_object_unref (bg); osd->toolbar = OL_OSD_TOOLBAR (ol_osd_toolbar_new ()); if (osd->toolbar != NULL) { gtk_container_add (GTK_CONTAINER (osd->window), GTK_WIDGET (osd->toolbar)); gtk_widget_show_all (GTK_WIDGET (osd->toolbar)); g_object_ref (osd->toolbar); ol_osd_toolbar_set_player (osd->toolbar, osd->player); } OlConfigProxy *config = ol_config_proxy_get_instance (); ol_assert (config != NULL); _bind_all_config (osd); g_signal_connect (osd->window, "moved", G_CALLBACK (ol_osd_moved_handler), NULL); g_signal_connect (osd->window, "resize", G_CALLBACK (ol_osd_resize_handler), NULL); g_signal_connect (osd->window, "button-release-event", G_CALLBACK (ol_osd_button_release), NULL); g_signal_connect (osd->window, "scroll-event", G_CALLBACK (ol_osd_scroll), NULL); } static OlOsdModule* ol_osd_module_new (struct OlDisplayModule *module, OlPlayer *player) { ol_log_func (); OlOsdModule *data = g_new (OlOsdModule, 1); g_object_ref (player); data->player = player; data->window = NULL; data->lrc = NULL; reset_lyrics_state (data); data->force_refresh_on_set_played_time = FALSE; data->message_source = 0; data->metadata = ol_metadata_new (); data->config_bindings = NULL; ol_osd_module_init_osd (data); g_signal_connect (player, "track-changed", G_CALLBACK (_metadata_changed_cb), data); _update_metadata (data); return data; } static void ol_osd_module_free (struct OlDisplayModule *module) { ol_log_func (); ol_assert (module != NULL); OlOsdModule *priv = ol_display_module_get_data (module); ol_assert (priv != NULL); if (priv->lrc) { g_object_unref (priv->lrc); priv->lrc = NULL; } if (priv->toolbar) { g_object_unref (priv->toolbar); priv->toolbar = NULL; } if (priv->window != NULL) { gtk_widget_destroy (GTK_WIDGET (priv->window)); priv->window = NULL; } if (is_message_displayed (priv)) { g_source_remove (priv->message_source); priv->message_source = 0; } if (priv->metadata != NULL) { ol_metadata_free (priv->metadata); priv->metadata = NULL; } g_signal_handlers_disconnect_by_func (priv->player, _metadata_changed_cb, priv); g_object_unref (priv->player); priv->player = NULL; while (priv->config_bindings != NULL) { _unbind_config (priv->config_bindings->data); priv->config_bindings = g_list_delete_link (priv->config_bindings, priv->config_bindings); } g_free (priv); } static void _metadata_changed_cb (OlPlayer *player, OlOsdModule *module) { ol_log_func (); _update_metadata (module); } static void _update_metadata (OlOsdModule *module) { ol_log_func (); ol_assert (module != NULL); ol_player_get_metadata (module->player, module->metadata); } static void ol_osd_module_set_played_time (struct OlDisplayModule *module, guint64 played_time) { ol_assert (module != NULL); OlOsdModule *priv = ol_display_module_get_data (module); ol_assert (priv != NULL); if (priv->lrc != NULL && priv->window != NULL) { OlLrcIter *iter = ol_lrc_iter_from_timestamp (priv->lrc, played_time); if (_advance_to_nonempty_lyric (iter)) { gint id = ol_lrc_iter_get_id (iter); if (id != priv->lrc_id) { if (id == priv->lrc_next_id) { /* advance to the next line */ ol_osd_window_set_percentage (priv->window, priv->current_line, 1.0); priv->current_line = 1 - priv->current_line; priv->lrc_id = priv->lrc_next_id; priv->lrc_next_id = -1; ol_osd_window_set_current_line (priv->window, priv->current_line); } else { /* The user seeks the position or there is only 1 line in OSD window. Reset the lyrics. */ priv->lrc_id = id; priv->current_line = 0; ol_osd_window_set_current_line (priv->window, 0); ol_osd_window_set_lyric (priv->window, priv->current_line, ol_lrc_iter_get_text (iter)); ol_osd_module_update_next_lyric (priv, iter); } } gdouble percentage = ol_lrc_iter_compute_percentage (iter, played_time); ol_osd_window_set_current_percentage (priv->window, percentage); if (percentage > 0.5 && priv->lrc_next_id == -1) ol_osd_module_update_next_lyric (priv, iter); } else if (priv->lrc_id != -1 || priv->force_refresh_on_set_played_time) { hide_lyrics (priv); reset_lyrics_state (priv); } ol_lrc_iter_free (iter); priv->force_refresh_on_set_played_time = FALSE; } } static gboolean _advance_to_nonempty_lyric (OlLrcIter *iter) { for (; ol_lrc_iter_is_valid (iter); ol_lrc_iter_next (iter)) { if (!ol_is_string_empty (ol_lrc_iter_get_text (iter))) return TRUE; } return FALSE; } static void ol_osd_module_set_lrc (struct OlDisplayModule *module, OlLrc *lrc_file) { ol_log_func (); ol_assert (module != NULL); OlOsdModule *priv = ol_display_module_get_data (module); ol_assert (priv != NULL); if (priv->lrc) g_object_unref (priv->lrc); if (lrc_file) g_object_ref (lrc_file); if (is_message_displayed (priv)) { /* A message can only be displayed if no lyrics are currently assigned. */ ol_assert (priv->lrc == NULL); ol_osd_module_clear_message (module); } else if (lrc_file == NULL) { hide_lyrics (priv); } else { priv->force_refresh_on_set_played_time = TRUE; } priv->lrc = lrc_file; reset_lyrics_state (priv); } static void ol_osd_module_set_message (struct OlDisplayModule *module, const char *message, int duration_ms) { ol_log_func (); ol_assert (module != NULL); OlOsdModule *priv = ol_display_module_get_data (module); ol_assert (priv != NULL); ol_assert (message != NULL); ol_assert (priv->window != NULL); if (priv->lrc != NULL) return; ol_debugf (" message:%s\n", message); ol_osd_window_set_current_line (priv->window, 0); ol_osd_window_set_current_percentage (priv->window, 1.0); ol_osd_window_set_lyric (priv->window, 0, message); ol_osd_window_set_lyric (priv->window, 1, NULL); if (is_message_displayed (priv)) g_source_remove (priv->message_source); priv->message_source = g_timeout_add (duration_ms, (GSourceFunc) hide_message, (gpointer) priv); } static void ol_osd_module_search_message (struct OlDisplayModule *module, const char *message) { ol_osd_module_set_message (module, message, -1); } static void ol_osd_module_search_fail_message (struct OlDisplayModule *module, const char *message) { ol_osd_module_set_message (module, message, MESSAGE_DURATION_MS); } static void ol_osd_module_download_fail_message (struct OlDisplayModule *module, const char *message) { ol_osd_module_set_message (module, message, MESSAGE_DURATION_MS); } static gboolean hide_message (OlOsdModule *osd) { ol_log_func (); ol_assert_ret (osd != NULL, FALSE); ol_assert_ret (osd->lrc == NULL, FALSE); ol_osd_window_set_lyric (osd->window, 0, NULL); osd->message_source = 0; return FALSE; } static gboolean is_message_displayed (OlOsdModule *osd) { ol_assert_ret (osd != NULL, FALSE); return osd->message_source != 0; } static void reset_lyrics_state (OlOsdModule *osd) { osd->current_line = 0; osd->lrc_id = -1; osd->lrc_next_id = -1; } static void hide_lyrics (OlOsdModule *osd) { ol_log_func (); if (osd->window != NULL && !is_message_displayed (osd)) { ol_osd_window_set_lyric (osd->window, 0, NULL); ol_osd_window_set_lyric (osd->window, 1, NULL); } } static void ol_osd_module_clear_message (struct OlDisplayModule *module) { ol_log_func (); ol_assert (module != NULL); OlOsdModule *priv = ol_display_module_get_data (module); ol_assert (priv != NULL); if (is_message_displayed (priv)) { g_source_remove (priv->message_source); hide_message (priv); } ol_debug (" clear message done"); } struct OlDisplayClass* ol_osd_module_get_class () { struct OlDisplayClass *klass = ol_display_class_new ("OSD", (OlDisplayInitFunc) ol_osd_module_new, ol_osd_module_free); klass->clear_message = ol_osd_module_clear_message; klass->download_fail_message = ol_osd_module_download_fail_message; klass->search_fail_message = ol_osd_module_search_fail_message; klass->search_message = ol_osd_module_search_message; klass->set_lrc = ol_osd_module_set_lrc; klass->set_message = ol_osd_module_set_message; klass->set_played_time = ol_osd_module_set_played_time; return klass; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_osd_module.h000066400000000000000000000017401345713535600211420ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_OSD_MODULE_H_ #define _OL_OSD_MODULE_H_ #include "ol_display_module.h" struct OlDisplayClass* ol_osd_module_get_class (); #endif /* _OL_OSD_MODULE_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_osd_render.c000066400000000000000000000173701345713535600211350ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include #include "ol_osd_render.h" #include "ol_gussian_blur.h" #include "ol_debug.h" static const int DEFAULT_OUTLINE_WIDTH = 3; static const char *DEFAULT_FONT_NAME = "serif 30"; static void ol_osd_render_update_font (OlOsdRenderContext *context); static void ol_osd_render_update_font_height (OlOsdRenderContext *context); OlOsdRenderContext * ol_osd_render_context_new () { OlOsdRenderContext *context = g_new (OlOsdRenderContext, 1); context->font_name = g_strdup (DEFAULT_FONT_NAME); int i; for (i = 0; i < OL_LINEAR_COLOR_COUNT; i++) { context->linear_colors[i] = ol_color_black; } context->linear_pos[0] = 0.0; context->linear_pos[1] = 0.5; context->linear_pos[2] = 1.0; context->pango_context = gdk_pango_context_get (); context->pango_layout = pango_layout_new (context->pango_context); context->text = NULL; context->blur_radius = 0.0; ol_osd_render_update_font (context); ol_osd_render_set_outline_width (context, DEFAULT_OUTLINE_WIDTH); return context; } void ol_osd_render_context_destroy (OlOsdRenderContext *context) { ol_assert (context != NULL); if (context->font_name != NULL) g_free (context->font_name); if (context->pango_layout != NULL) g_object_unref (context->pango_layout); if (context->pango_context != NULL) g_object_unref (context->pango_context); if (context->text != NULL) g_free (context->text); g_free (context); } void ol_osd_render_paint_text (OlOsdRenderContext *context, cairo_t *cr, const char *text, double xpos, double ypos) { ol_assert (context != NULL); ol_assert (cr != NULL); ol_assert (text != NULL); ol_osd_render_set_text (context, text); int width, height; xpos += context->outline_width / 2.0 + context->blur_radius; ypos += context->outline_width / 2.0 + context->blur_radius; pango_layout_get_pixel_size (context->pango_layout, &width, &height); /* draws the outline of the text */ cairo_move_to (cr, xpos, ypos); cairo_save (cr); pango_cairo_layout_path(cr, context->pango_layout); cairo_set_source_rgb (cr, ol_color_black.r, ol_color_black.g, ol_color_black.b); if (context->outline_width > 0) { cairo_set_line_width (cr, context->outline_width); if (context->blur_radius > 1e-4) { cairo_stroke_preserve (cr); cairo_fill (cr); ol_gussian_blur (cairo_get_target (cr), context->blur_radius); } else { cairo_stroke (cr); } } cairo_restore (cr); cairo_save (cr); cairo_new_path (cr); /* creates the linear pattern */ cairo_pattern_t *pattern = cairo_pattern_create_linear (xpos, ypos, xpos, ypos + height); int i; for (i = 0; i < OL_LINEAR_COLOR_COUNT; i++) { cairo_pattern_add_color_stop_rgb(pattern, context->linear_pos[i], context->linear_colors[i].r, context->linear_colors[i].g, context->linear_colors[i].b); } cairo_set_source (cr, pattern); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); /* draws the text */ cairo_move_to (cr, xpos, ypos); pango_cairo_show_layout (cr, context->pango_layout); cairo_pattern_destroy (pattern); cairo_restore (cr); } void ol_osd_render_get_pixel_size (OlOsdRenderContext *context, const char *text, int *width, int *height) { ol_assert (context != NULL); ol_assert (text != NULL); ol_assert (width != NULL || height != NULL); ol_osd_render_set_text (context, text); int w, h; pango_layout_get_pixel_size (context->pango_layout, &w, &h); if (width != NULL) *width = round (w + context->outline_width + context->blur_radius * 2); if (height != NULL) *height = round (h + context->outline_width + context->blur_radius * 2); } void ol_osd_render_set_linear_color (OlOsdRenderContext *context, int index, OlColor color) { ol_assert (context != NULL); ol_assert (index >= 0 && index < OL_LINEAR_COLOR_COUNT); context->linear_colors[index] = color; } void ol_osd_render_set_text (OlOsdRenderContext* context, const char *text) { ol_assert (context != NULL); ol_assert (text != NULL); if (context->text != NULL) { if (strcmp (context->text, text) == 0) return; g_free (context->text); } context->text = g_strdup (text); pango_layout_set_text (context->pango_layout, text, -1); } void ol_osd_render_set_font_name (OlOsdRenderContext *context, const char *font_name) { ol_assert (context != NULL); ol_assert (font_name != NULL); char *new_name = g_strdup (font_name); if (context->font_name != NULL) { g_free (context->font_name); } context->font_name = new_name; ol_osd_render_update_font (context); } char * ol_osd_render_get_font_name (OlOsdRenderContext *context) { ol_assert_ret (context != NULL, NULL); return g_strdup (context->font_name); } int ol_osd_render_get_font_height (OlOsdRenderContext *context) { ol_assert_ret (context != NULL, 0); return context->font_height; } void ol_osd_render_set_outline_width (OlOsdRenderContext *context, const int width) { ol_assert (context != NULL); ol_assert (width >= 0); context->outline_width = width; } int ol_osd_render_get_outline_width (OlOsdRenderContext *context) { ol_assert_ret (context != NULL, 0); return context->outline_width; } static void ol_osd_render_update_font_height (OlOsdRenderContext *context) { PangoFontMetrics *metrics = pango_context_get_metrics (context->pango_context, pango_layout_get_font_description (context->pango_layout), /* font desc */ NULL); /* languague */ if (metrics == NULL) { ol_errorf ("Cannot get font metrics\n"); } context->font_height = 0; int ascent, descent; ascent = pango_font_metrics_get_ascent (metrics); descent = pango_font_metrics_get_descent (metrics); pango_font_metrics_unref (metrics); context->font_height += PANGO_PIXELS (ascent + descent); } static void ol_osd_render_update_font (OlOsdRenderContext *context) { ol_assert (context != NULL); PangoFontDescription *font_desc = pango_font_description_from_string (context->font_name); pango_layout_set_font_description (context->pango_layout, font_desc); pango_font_description_free (font_desc); ol_osd_render_update_font_height (context); } void ol_osd_render_set_blur_radius (OlOsdRenderContext *context, double radius) { ol_assert (context != NULL); if (radius < 0.0) radius = 0.0; context->blur_radius = radius; } double ol_osd_render_get_blur_radius (OlOsdRenderContext *context) { ol_assert_ret (context != NULL, 0.0); return context->blur_radius; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_osd_render.h000066400000000000000000000112271345713535600211350ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef __OL_OSD_RENDER_H__ #define __OL_OSD_RENDER_H__ #include #include "ol_color.h" enum { OL_LINEAR_COLOR_COUNT = 3, }; typedef struct { char *font_name; int outline_width; OlColor linear_colors[OL_LINEAR_COLOR_COUNT]; double linear_pos[OL_LINEAR_COLOR_COUNT]; PangoContext *pango_context; PangoLayout *pango_layout; char *text; double blur_radius; int font_height; } OlOsdRenderContext; /** * @brief Creates a new context * The new context should be destroyed by ol_osd_render_context_destroy * * @return The new context */ OlOsdRenderContext* ol_osd_render_context_new (); /** * @brief Destroys an OlOsdRenderContext * * @param context The context to be destroyed */ void ol_osd_render_context_destroy (OlOsdRenderContext *context); /** * @brief Sets the font name for a context * * @param context An OlOsdRenderContext; * @param font_family Font name, must not be NULL */ void ol_osd_render_set_font_name (OlOsdRenderContext *context, const char *font_name); /** * @brief Gets the font name for a context * * @param context An OlOsdRenderContext * @return The font name of the context, must be freed by g_free */ char* ol_osd_render_get_font_name (OlOsdRenderContext *context); /** * @brief Sets the outline width * * @param context An OlOsdRenderContext * @param width Outline width, must be positive */ void ol_osd_render_set_outline_width (OlOsdRenderContext *context, const int width); /** * @brief Gets the outline width for a context * * @param context An OlOsdRenderContext; * * @return The outline width for the context */ int ol_osd_render_get_outline_width (OlOsdRenderContext *context); /** * @brief Gets the height of the font of a context * * @param context An OlOsdRenderContext * * @return The height of the font */ int ol_osd_render_get_font_height (OlOsdRenderContext *context); /** * @brief Sets linear color * * @param context An OlOsdRenderContext * @param index The index of the color * @param color The color to be set */ void ol_osd_render_set_linear_color (OlOsdRenderContext *context, int index, OlColor color); /** * @brief Paints text to pixmap * * @param context The context of the renderer * @param canvas The GdkDrawable to be drawn * @param text The text to be painted * @param x The horizontal position * @param y The vertectical position */ void ol_osd_render_paint_text (OlOsdRenderContext *context, cairo_t *canvas, const char *text, double x, double y); /** * @brief Gets the width and height of the text * * @param context The context of the renderer, must not be NULL * @param text The text to be calculated, must not be NULL * @param width The width of the text * @param height The height of the text */ void ol_osd_render_get_pixel_size (OlOsdRenderContext *context, const char *text, int *width, int *height); /** * @brief Sets text to the context * * @param context An OlOsdRenderContext * @param text Text to be set */ void ol_osd_render_set_text (OlOsdRenderContext* context, const char *text); /** * Sets the blur radius of shadow. * * If the radius is greater than 0, the outline of text will be blurred as shadow. * * @param context * @param radius The blur radius in pixel, non-positive value to disable blurring. */ void ol_osd_render_set_blur_radius (OlOsdRenderContext *context, double radius); double ol_osd_render_get_blur_radius (OlOsdRenderContext *context); #endif /* __OL_OSD_RENDER_H__ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_osd_toolbar.c000066400000000000000000000227761345713535600213260ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include "ol_osd_toolbar.h" #include "ol_image_button.h" #include "ol_stock.h" #include "ol_debug.h" #define OL_OSD_TOOLBAR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), \ ol_osd_toolbar_get_type (), \ OlOsdToolbarPriv)) typedef struct _OlOsdToolbarPriv OlOsdToolbarPriv; struct _OlOsdToolbarPriv { OlPlayer *player; enum OlPlayerStatus status; }; G_DEFINE_TYPE (OlOsdToolbar, ol_osd_toolbar, GTK_TYPE_ALIGNMENT); enum { BTN_PLAY = 0, BTN_PAUSE, BTN_STOP, BTN_PREV, BTN_NEXT, }; struct ButtonSpec { const char *stock; void (*handler) (GtkButton *button, OlOsdToolbar *toolbar); }; static void _player_control (OlPlayer *player, enum OlPlayerCaps caps, gboolean (*cmd) (OlPlayer *player)); static void _play_clicked (GtkButton *button, OlOsdToolbar *toolbar); static void _pause_clicked (GtkButton *button, OlOsdToolbar *toolbar); static void _stop_clicked (GtkButton *button, OlOsdToolbar *toolbar); static void _prev_clicked (GtkButton *button, OlOsdToolbar *toolbar); static void _next_clicked (GtkButton *button, OlOsdToolbar *toolbar); static GtkButton *_add_button (OlOsdToolbar *toolbar, const struct ButtonSpec *btn_spec); static void _update_caps (OlOsdToolbar *toolbar); static void _update_status (OlOsdToolbar *toolbar); static void _caps_changed_cb (OlPlayer *player, OlOsdToolbar *toolbar); static void _status_changed_cb (OlPlayer *player, OlOsdToolbar *toolbar); static void ol_osd_toolbar_destroy (GtkObject *obj); const static struct ButtonSpec btn_spec[] = { {OL_STOCK_OSD_PLAY, _play_clicked}, {OL_STOCK_OSD_PAUSE, _pause_clicked}, {OL_STOCK_OSD_STOP, _stop_clicked}, {OL_STOCK_OSD_PREV, _prev_clicked}, {OL_STOCK_OSD_NEXT, _next_clicked}, }; static void _player_control (OlPlayer *player, enum OlPlayerCaps caps, gboolean (*cmd) (OlPlayer *player)) { if (player != NULL && (ol_player_get_caps (player) & caps)) { cmd (player); } } static void _play_clicked (GtkButton *button, OlOsdToolbar *toolbar) { ol_assert (toolbar != NULL); OlOsdToolbarPriv *priv = OL_OSD_TOOLBAR_GET_PRIVATE (toolbar); _player_control (priv->player, OL_PLAYER_PLAY, ol_player_play); } static void _pause_clicked (GtkButton *button, OlOsdToolbar *toolbar) { ol_assert (toolbar != NULL); OlOsdToolbarPriv *priv = OL_OSD_TOOLBAR_GET_PRIVATE (toolbar); _player_control (priv->player, OL_PLAYER_PAUSE, ol_player_pause); } static void _stop_clicked (GtkButton *button, OlOsdToolbar *toolbar) { ol_assert (toolbar != NULL); OlOsdToolbarPriv *priv = OL_OSD_TOOLBAR_GET_PRIVATE (toolbar); _player_control (priv->player, OL_PLAYER_STOP, ol_player_stop); } static void _prev_clicked (GtkButton *button, OlOsdToolbar *toolbar) { ol_assert (toolbar != NULL); OlOsdToolbarPriv *priv = OL_OSD_TOOLBAR_GET_PRIVATE (toolbar); _player_control (priv->player, OL_PLAYER_PREV, ol_player_prev); } static void _next_clicked (GtkButton *button, OlOsdToolbar *toolbar) { ol_assert (toolbar != NULL); OlOsdToolbarPriv *priv = OL_OSD_TOOLBAR_GET_PRIVATE (toolbar); _player_control (priv->player, OL_PLAYER_NEXT, ol_player_next); } static GtkButton * _add_button (OlOsdToolbar *toolbar, const struct ButtonSpec *btn_spec) { OlImageButton *btn = OL_IMAGE_BUTTON (ol_image_button_new ()); GtkIconTheme *icontheme = gtk_icon_theme_get_default (); GtkIconInfo *info = gtk_icon_theme_lookup_icon (icontheme, btn_spec->stock, 16, 0); GdkPixbuf *image = gdk_pixbuf_new_from_file (gtk_icon_info_get_filename (info), NULL); gtk_icon_info_free (info); ol_image_button_set_pixbuf (btn, image); g_signal_connect (btn, "clicked", G_CALLBACK (btn_spec->handler), toolbar); gtk_box_pack_start (GTK_BOX (toolbar->center_box), GTK_WIDGET (btn), FALSE, TRUE, 0); gtk_widget_show (GTK_WIDGET (btn)); return GTK_BUTTON (btn); } static void _caps_changed_cb (OlPlayer *player, OlOsdToolbar *toolbar) { _update_caps (toolbar); } static void _status_changed_cb (OlPlayer *player, OlOsdToolbar *toolbar) { _update_status (toolbar); } static void _update_caps (OlOsdToolbar *toolbar) { ol_assert (OL_IS_OSD_TOOLBAR (toolbar)); OlOsdToolbarPriv *priv = OL_OSD_TOOLBAR_GET_PRIVATE (toolbar); int caps; if (priv->player) caps = ol_player_get_caps (priv->player); else caps = 0; gtk_widget_set_sensitive (GTK_WIDGET (toolbar->play_button), caps & OL_PLAYER_PLAY); gtk_widget_set_sensitive (GTK_WIDGET (toolbar->pause_button), caps & OL_PLAYER_PAUSE); gtk_widget_set_sensitive (GTK_WIDGET (toolbar->stop_button), caps & OL_PLAYER_STOP); gtk_widget_set_sensitive (GTK_WIDGET (toolbar->prev_button), caps & OL_PLAYER_PREV); gtk_widget_set_sensitive (GTK_WIDGET (toolbar->next_button), caps & OL_PLAYER_NEXT); } static void _update_status (OlOsdToolbar *toolbar) { ol_log_func (); OlOsdToolbarPriv *priv = OL_OSD_TOOLBAR_GET_PRIVATE (toolbar); enum OlPlayerStatus status; if (priv->player) status = ol_player_get_status (priv->player); else status = OL_PLAYER_UNKNOWN; switch (status) { case OL_PLAYER_PLAYING: gtk_widget_show (GTK_WIDGET (toolbar->pause_button)); gtk_widget_hide (GTK_WIDGET (toolbar->play_button)); break; case OL_PLAYER_PAUSED: case OL_PLAYER_STOPPED: gtk_widget_hide (GTK_WIDGET (toolbar->pause_button)); gtk_widget_show (GTK_WIDGET (toolbar->play_button)); break; default: gtk_widget_show (GTK_WIDGET (toolbar->pause_button)); gtk_widget_show (GTK_WIDGET (toolbar->play_button)); break; } gtk_widget_queue_draw (GTK_WIDGET (toolbar)); } static void ol_osd_toolbar_class_init (OlOsdToolbarClass *klass) { GtkObjectClass *gtk_class = GTK_OBJECT_CLASS (klass); gtk_class->destroy = ol_osd_toolbar_destroy; g_type_class_add_private (klass, sizeof (OlOsdToolbarPriv)); } static void ol_osd_toolbar_init (OlOsdToolbar *toolbar) { OlOsdToolbarPriv *priv = OL_OSD_TOOLBAR_GET_PRIVATE (toolbar); gtk_alignment_set (GTK_ALIGNMENT (toolbar), 0.5, 0.5, 0.0, 0.0); toolbar->center_box = GTK_HBOX (gtk_hbox_new (FALSE, 0)); gtk_container_add (GTK_CONTAINER (toolbar), GTK_WIDGET (toolbar->center_box)); toolbar->prev_button = _add_button (toolbar, &btn_spec[BTN_PREV]); toolbar->play_button = _add_button (toolbar, &btn_spec[BTN_PLAY]); toolbar->pause_button = _add_button (toolbar, &btn_spec[BTN_PAUSE]); toolbar->stop_button = _add_button (toolbar, &btn_spec[BTN_STOP]); toolbar->next_button = _add_button (toolbar, &btn_spec[BTN_NEXT]); priv->player = NULL; _update_status (toolbar); _update_caps (toolbar); } static void ol_osd_toolbar_destroy (GtkObject *obj) { ol_osd_toolbar_set_player (OL_OSD_TOOLBAR (obj), NULL); } GtkWidget * ol_osd_toolbar_new (void) { OlOsdToolbar *toolbar; toolbar = g_object_new (ol_osd_toolbar_get_type (), NULL); return GTK_WIDGET (toolbar); } void ol_osd_toolbar_set_player (OlOsdToolbar *toolbar, OlPlayer *player) { ol_assert (OL_IS_OSD_TOOLBAR (toolbar)); OlOsdToolbarPriv *priv = OL_OSD_TOOLBAR_GET_PRIVATE (toolbar); if (player == priv->player) return; if (player != NULL) { g_object_ref (player); g_signal_connect (player, "status-changed", G_CALLBACK (_status_changed_cb), toolbar); g_signal_connect (player, "caps-changed", G_CALLBACK (_caps_changed_cb), toolbar); } if (priv->player != NULL) { g_signal_handlers_disconnect_by_func (priv->player, _status_changed_cb, toolbar); g_signal_handlers_disconnect_by_func (priv->player, _caps_changed_cb, toolbar); g_object_unref (priv->player); } priv->player = player; _update_caps (toolbar); _update_status (toolbar); } osdlyrics-0.5.5~rc1+dfsg1/src/ol_osd_toolbar.h000066400000000000000000000037401345713535600213210ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_OSD_TOOLBAR_H_ #define _OL_OSD_TOOLBAR_H_ #include #include "ol_player.h" #define OL_OSD_TOOLBAR(obj) GTK_CHECK_CAST (obj, ol_osd_toolbar_get_type (), OlOsdToolbar) #define OL_OSD_TOOLBAR_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, ol_osd_toolbar_get_type (), OlOsdToolbarClass) #define OL_IS_OSD_TOOLBAR(obj) GTK_CHECK_TYPE (obj, ol_osd_toolbar_get_type ()) #define OL_OSD_TOOLBAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ol_osd_toolbar_get_type (), OlOsdToolbarClass)) typedef struct _OlOsdToolbar OlOsdToolbar; typedef struct _OlOsdToolbarClass OlOsdToolbarClass; struct _OlOsdToolbar { GtkAlignment alignment; GtkHBox *center_box; GtkButton *play_button; GtkButton *pause_button; GtkButton *prev_button; GtkButton *next_button; GtkButton *stop_button; }; struct _OlOsdToolbarClass { GtkAlignmentClass parent_class; }; GtkType ol_osd_toolbar_get_type (void); GtkWidget *ol_osd_toolbar_new (void); void ol_osd_toolbar_set_player (OlOsdToolbar *toolbar, OlPlayer *player); void ol_osd_toolbar_set_status (OlOsdToolbar *toolbar, enum OlPlayerStatus status); #endif /* _OL_OSD_TOOLBAR_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_osd_window.c000066400000000000000000002026461345713535600211670ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include #include "ol_osd_window.h" #include "ol_utils.h" #include "ol_debug.h" #define OL_OSD_WINDOW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), \ ol_osd_window_get_type (), \ OlOsdWindowPrivate)) enum { PROP_0, PROP_LOCKED, }; static const OlColor DEFAULT_INACTIVE_COLORS[OL_LINEAR_COLOR_COUNT]= { {0.6, 1.0, 1.0}, {0.0, 0.0, 1.0}, {0.6, 1.0, 1.0}, }; static const OlColor DEFAULT_ACTIVE_COLORS[OL_LINEAR_COLOR_COUNT]= { {0.4, 0.15, 0.0}, {1.0, 1.0, 0.0}, {1.0, 0.5, 0.0}, }; static const int MOUSE_TIMER_INTERVAL = 100; static const int DEFAULT_HEIGHT = 100; static const double LINE_PADDING = 0.0; static const int BORDER_WIDTH = 5; static const int DEFAULT_WIDTH = 1024; static const int MAX_LYRIC_LEN = 256; static const int DEFAULT_FADE_IN_SIZE = 20; enum DragState { DRAG_NONE, DRAG_MOVE, DRAG_EAST, DRAG_WEST, }; typedef struct __OlOsdWindowPrivate OlOsdWindowPrivate; struct __OlOsdWindowPrivate { gint width; gint osd_height; gint raw_x; gint raw_y; gboolean locked; gboolean composited; /* whether the screen is composited */ gboolean update_shape; double lyric_xpos[OL_OSD_WINDOW_MAX_LINE_COUNT]; gulong composited_signal; gint mouse_x; gint mouse_y; gint old_x; gint old_y; gint old_width; gboolean visible; guint mouse_timer_id; gulong configure_event; gboolean mouse_over; gboolean mouse_over_lyrics; GtkRequisition child_requisition; cairo_surface_t *active_lyric_surfaces[OL_OSD_WINDOW_MAX_LINE_COUNT]; cairo_surface_t *inactive_lyric_surfaces[OL_OSD_WINDOW_MAX_LINE_COUNT]; GdkPixmap *shape_pixmap; double blur_radius; enum OlOsdWindowMode mode; enum DragState drag_state; }; struct OsdLrc { gint id; gchar *lyric; }; G_DEFINE_TYPE(OlOsdWindow, ol_osd_window, GTK_TYPE_WINDOW) static void ol_osd_window_destroy (GtkObject *object); static void ol_osd_window_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void ol_osd_window_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void ol_osd_window_size_allocate (GtkWidget *widget, GtkAllocation *allocation); static void ol_osd_window_size_request (GtkWidget *widget, GtkRequisition *requisition); static gboolean ol_osd_window_panel_visible (OlOsdWindow *osd); static gboolean ol_osd_window_expose_before (GtkWidget *widget, GdkEventExpose *event); static gboolean ol_osd_window_expose_after (GtkWidget *widget, GdkEventExpose *event); static gboolean ol_osd_window_paint_bg (OlOsdWindow *osd, cairo_t *cr); static gboolean ol_osd_window_enter_notify (GtkWidget *widget, GdkEventCrossing *event); static gboolean ol_osd_window_leave_notify (GtkWidget *widget, GdkEventCrossing *event); static gboolean ol_osd_window_button_press (GtkWidget *widget, GdkEventButton *event); static gboolean ol_osd_window_motion_notify (GtkWidget *widget, GdkEventMotion *event); static gboolean ol_osd_window_button_release (GtkWidget *widget, GdkEventButton *event); static gboolean ol_osd_window_configure_event (GtkWindow *window, GdkEventConfigure *event, gpointer user_data); static gboolean ol_osd_window_map_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data); static gboolean ol_osd_window_unmap_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data); static void ol_osd_window_realize_cb (GtkWidget *widget, gpointer user_data); static void ol_osd_window_unrealize_cb (GtkWidget *widget, gpointer user_data); static int ol_osd_window_compute_osd_height (OlOsdWindow *osd); static int ol_osd_window_compute_window_height (OlOsdWindow *osd); static double ol_osd_window_compute_lyric_xpos (OlOsdWindow *osd, int line, double percentage); static int ol_osd_window_compute_lyric_ypos (OlOsdWindow *osd); static void ol_osd_window_paint_lyrics (OlOsdWindow *osd, cairo_t *cr); static cairo_pattern_t *ol_osd_window_create_text_mask (OlOsdWindow *osd, cairo_t *cr, int line, gdouble text_xpos, gdouble alpha); static void ol_osd_window_emit_move (OlOsdWindow *osd); static void ol_osd_window_emit_resize (OlOsdWindow *osd); static GdkWindowEdge ol_osd_window_get_edge_on_point (OlOsdWindow *osd, gint x, gint y); static void ol_osd_window_update_child_size_requisition (OlOsdWindow *osd); static void ol_osd_window_update_layout (OlOsdWindow *osd); static void ol_osd_window_move_resize (OlOsdWindow *osd, int x, int y, int width, int height, enum DragState drag_type); static void ol_osd_window_paint (OlOsdWindow *osd); static void ol_osd_window_clear_cairo (cairo_t *cr); static void ol_osd_window_reset_shape_pixmap (OlOsdWindow *osd); static void ol_osd_window_update_shape (OlOsdWindow *osd); static void ol_osd_window_queue_reshape (OlOsdWindow *osd); static void ol_osd_window_set_input_shape_mask (OlOsdWindow *osd, gboolean disable_input); static void ol_osd_draw_lyric_surface (OlOsdWindow *osd, cairo_surface_t **surface, const char *lyric); static void ol_osd_window_update_lyric_surface (OlOsdWindow *osd, int line); static void ol_osd_window_update_lyric_rect (OlOsdWindow *osd, int line); static void ol_osd_window_update_colormap (OlOsdWindow *osd); static void ol_osd_window_screen_composited_changed (GdkScreen *screen, gpointer userdata); static gboolean ol_osd_window_mouse_timer (gpointer data); static void ol_osd_window_update_child_allocation (OlOsdWindow *osd); static void ol_osd_window_check_mouse_leave (OlOsdWindow *osd); static gboolean ol_osd_window_should_blur (OlOsdWindow *osd); static void ol_osd_window_update_render_blur_radius (OlOsdWindow *osd); /* static void ol_osd_window_update_child_visibility (OlOsdWindow *osd); */ static void _paint_rect (cairo_t *cr, GdkPixbuf *source, double src_x, double src_y, double src_w, double src_h, double des_x, double des_y, double dex_w, double des_h); static gboolean _point_in_rect (int x, int y, GdkRectangle *rect); static void ol_osd_window_queue_resize (OlOsdWindow *osd); static void _paint_rect (cairo_t *cr, GdkPixbuf *source, double src_x, double src_y, double src_w, double src_h, double des_x, double des_y, double des_w, double des_h) { ol_assert (cr != NULL); ol_assert (source != NULL); cairo_save (cr); double sw = des_w / src_w; double sh = des_h / src_h; cairo_translate (cr, des_x, des_y); cairo_rectangle (cr, 0, 0, des_w, des_h); cairo_scale (cr, sw, sh); cairo_clip (cr); gdk_cairo_set_source_pixbuf (cr, source, -src_x, -src_y); cairo_paint (cr); cairo_restore (cr); } static gboolean ol_osd_window_paint_bg (OlOsdWindow *osd, cairo_t *cr) { ol_assert_ret (OL_IS_OSD_WINDOW (osd), FALSE); GtkWidget *widget = GTK_WIDGET (osd); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); int w, h; gdk_drawable_get_size (widget->window, &w, &h); ol_osd_window_clear_cairo (cr); if (ol_osd_window_panel_visible(osd)) { if (osd->bg_pixbuf != NULL) { int sw, sh; if (priv->composited) { ol_osd_window_clear_cairo (cr); } else { cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgb (cr, 0.9, 0.9, 0.9); cairo_rectangle (cr, 0, 0, w, h); cairo_fill (cr); } cairo_set_operator (cr, CAIRO_OPERATOR_OVER); sw = gdk_pixbuf_get_width (osd->bg_pixbuf); sh = gdk_pixbuf_get_height (osd->bg_pixbuf); _paint_rect (cr, osd->bg_pixbuf, 0, 0, BORDER_WIDTH, BORDER_WIDTH, 0, 0, BORDER_WIDTH, BORDER_WIDTH); _paint_rect (cr, osd->bg_pixbuf, 0, sh - BORDER_WIDTH, BORDER_WIDTH, BORDER_WIDTH, 0, h - BORDER_WIDTH, BORDER_WIDTH, BORDER_WIDTH); _paint_rect (cr, osd->bg_pixbuf, sw - BORDER_WIDTH, 0, BORDER_WIDTH, BORDER_WIDTH, w - BORDER_WIDTH, 0, BORDER_WIDTH, BORDER_WIDTH); _paint_rect (cr, osd->bg_pixbuf, sw - BORDER_WIDTH, sh - BORDER_WIDTH, BORDER_WIDTH, BORDER_WIDTH, w - BORDER_WIDTH, h - BORDER_WIDTH, BORDER_WIDTH, BORDER_WIDTH); _paint_rect (cr, osd->bg_pixbuf, 0, BORDER_WIDTH, BORDER_WIDTH, sh - BORDER_WIDTH * 2, 0, BORDER_WIDTH, BORDER_WIDTH, h - BORDER_WIDTH * 2); _paint_rect (cr, osd->bg_pixbuf, sw - BORDER_WIDTH, BORDER_WIDTH, BORDER_WIDTH, sh - BORDER_WIDTH * 2, w - BORDER_WIDTH, BORDER_WIDTH, BORDER_WIDTH, h - BORDER_WIDTH * 2); _paint_rect (cr, osd->bg_pixbuf, BORDER_WIDTH, 0, sw - BORDER_WIDTH * 2, BORDER_WIDTH, BORDER_WIDTH, 0, w - BORDER_WIDTH * 2, BORDER_WIDTH); _paint_rect (cr, osd->bg_pixbuf, BORDER_WIDTH, sh - BORDER_WIDTH, sw - BORDER_WIDTH * 2, BORDER_WIDTH, BORDER_WIDTH, h - BORDER_WIDTH, w - BORDER_WIDTH * 2, BORDER_WIDTH); _paint_rect (cr, osd->bg_pixbuf, BORDER_WIDTH, BORDER_WIDTH, sw - BORDER_WIDTH * 2, sh - BORDER_WIDTH * 2, BORDER_WIDTH, BORDER_WIDTH, w - BORDER_WIDTH * 2, h - BORDER_WIDTH * 2); } else { gtk_paint_box (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL, widget, "buttondefault", 0, 0, w, h); } } return TRUE; } static void ol_osd_window_update_colormap (OlOsdWindow *osd) { ol_assert (OL_IS_OSD_WINDOW (osd)); GtkWidget *widget = GTK_WIDGET (osd); gboolean mapped = GTK_WIDGET_MAPPED (widget); gboolean realized = GTK_WIDGET_REALIZED (widget); GdkScreen *screen = gtk_widget_get_screen (widget); GdkColormap* colormap = gdk_screen_get_rgba_colormap (screen); if (colormap == NULL) colormap = gdk_screen_get_rgb_colormap (screen); if (mapped) gtk_widget_unmap (widget); if (realized) { gtk_widget_unrealize (widget); } gtk_widget_set_colormap (widget, colormap); if (realized) { gtk_widget_realize (widget); } if (mapped) gtk_widget_map (widget); } static void ol_osd_window_screen_composited_changed (GdkScreen *screen, gpointer userdata) { ol_log_func (); ol_assert (OL_IS_OSD_WINDOW (userdata)); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (userdata); GtkWidget *widget = GTK_WIDGET (userdata); OlOsdWindow *osd = OL_OSD_WINDOW (userdata); priv->composited = gdk_screen_is_composited (screen); if (priv->blur_radius > 0) { ol_osd_window_update_render_blur_radius (osd); int i; for (i = 0; i < osd->line_count; i++) ol_osd_window_update_lyric_surface (osd, i); } ol_osd_window_queue_reshape (osd); gtk_widget_queue_draw (widget); } static gboolean ol_osd_window_use_system_drag (OlOsdWindow *osd) { ol_assert_ret (OL_IS_OSD_WINDOW (osd), FALSE); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); return priv->mode == OL_OSD_WINDOW_NORMAL; } static gboolean ol_osd_window_button_press (GtkWidget *widget, GdkEventButton *event) { OlOsdWindow *osd = OL_OSD_WINDOW (widget); if (GTK_WIDGET_CLASS (ol_osd_window_parent_class)->button_press_event && GTK_WIDGET_CLASS (ol_osd_window_parent_class)->button_press_event (widget, event)) return TRUE; ol_log_func (); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (widget); if (event->button == 1 && !priv->locked) { int edge = ol_osd_window_get_edge_on_point (osd, event->x, event->y); if (ol_osd_window_use_system_drag (osd)) { switch (edge) { case GDK_WINDOW_EDGE_EAST: case GDK_WINDOW_EDGE_WEST: gtk_window_begin_resize_drag (GTK_WINDOW (widget), edge, event->button, event->x_root, event->y_root, event->time); break; default: gtk_window_begin_move_drag (GTK_WINDOW (widget), event->button, event->x_root, event->y_root, event->time); } } else { priv->old_x = widget->allocation.x; priv->old_y = widget->allocation.y; priv->old_width = widget->allocation.width; priv->mouse_x = event->x_root; priv->mouse_y = event->y_root; switch (edge) { case GDK_WINDOW_EDGE_EAST: priv->drag_state = DRAG_EAST; break; case GDK_WINDOW_EDGE_WEST: priv->drag_state = DRAG_WEST; break; default: priv->drag_state = DRAG_MOVE; break; } } } return FALSE; } static void ol_osd_window_emit_move (OlOsdWindow *osd) { GValue params[1] = {{0}}; g_value_init (¶ms[0], G_OBJECT_TYPE (osd)); g_value_set_object (¶ms[0], G_OBJECT (osd)); g_signal_emitv (params, OL_OSD_WINDOW_GET_CLASS (osd)->signals[OSD_MOVED], 0, NULL); } static void ol_osd_window_emit_resize (OlOsdWindow *osd) { GValue params[1] = {{0}}; g_value_init (¶ms[0], G_OBJECT_TYPE (osd)); g_value_set_object (¶ms[0], G_OBJECT (osd)); g_signal_emitv (params, OL_OSD_WINDOW_GET_CLASS (osd)->signals[OSD_RESIZE], 0, NULL); } static gboolean ol_osd_window_button_release (GtkWidget *widget, GdkEventButton *event) { /* ol_log_func (); */ OlOsdWindow *osd = OL_OSD_WINDOW (widget); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (widget); if (priv->drag_state != DRAG_NONE) { ol_osd_window_emit_move (osd); ol_osd_window_emit_resize (osd); } priv->drag_state = DRAG_NONE; return FALSE; } static gboolean ol_osd_window_configure_event (GtkWindow *window, GdkEventConfigure *event, gpointer user_data) { ol_assert_ret (OL_IS_OSD_WINDOW (window), FALSE); GtkWidget *widget = GTK_WIDGET (window); OlOsdWindow *osd = OL_OSD_WINDOW (window); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); gboolean size_changed = FALSE; widget->allocation.x = event->x; widget->allocation.y = event->y; if (priv->width != event->width) { priv->width = event->width; size_changed = TRUE; if (priv->drag_state == DRAG_NONE) ol_osd_window_emit_resize (osd); } if (size_changed) { ol_osd_window_update_child_allocation (osd); ol_osd_window_reset_shape_pixmap (osd); } if (priv->raw_x != event->x || priv->raw_y != event->y) { priv->raw_x = event->x; priv->raw_y = event->y; if (priv->drag_state == DRAG_NONE) ol_osd_window_emit_move (osd); } return FALSE; } static GdkWindowEdge ol_osd_window_get_edge_on_point (OlOsdWindow *osd, gint x, gint y) { ol_assert_ret (OL_IS_OSD_WINDOW (osd), GDK_WINDOW_EDGE_SOUTH); GtkWidget *widget = GTK_WIDGET (osd); gint width = widget->allocation.width; gint height = widget->allocation.height; if (y >= 0 && y <= height) { if (x >= 0 && x < BORDER_WIDTH) return GDK_WINDOW_EDGE_WEST; if (x >= width - BORDER_WIDTH && x < width) return GDK_WINDOW_EDGE_EAST; } return -1; } static gboolean ol_osd_window_motion_notify (GtkWidget *widget, GdkEventMotion *event) { ol_log_func (); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (widget); OlOsdWindow *osd = OL_OSD_WINDOW (widget); int x = priv->old_x + (event->x_root - priv->mouse_x); int y = priv->old_y + (event->y_root - priv->mouse_y); int height, width; gtk_window_get_size (GTK_WINDOW (osd), &width, &height); switch (priv->drag_state) { case DRAG_MOVE: ol_osd_window_move (osd, x, y); break; case DRAG_EAST: ol_osd_window_move_resize (osd, priv->old_x, priv->old_y, priv->old_width + (event->x_root - priv->mouse_x), height, DRAG_EAST); break; case DRAG_WEST: ol_osd_window_move_resize (osd, x, priv->old_y, priv->old_width + priv->old_x - x, height, DRAG_WEST); break; case DRAG_NONE: { int edge = ol_osd_window_get_edge_on_point (osd, event->x, event->y); GdkCursor *cursor = NULL; switch (edge) { case GDK_WINDOW_EDGE_EAST: cursor = gdk_cursor_new (GDK_RIGHT_SIDE); break; case GDK_WINDOW_EDGE_WEST: cursor = gdk_cursor_new (GDK_LEFT_SIDE); break; } gdk_window_set_cursor (widget->window, cursor); if (cursor) gdk_cursor_unref (cursor); break; } default: break; } return FALSE; } static gboolean ol_osd_window_panel_visible (OlOsdWindow *osd) { ol_assert_ret (OL_IS_OSD_WINDOW (osd), FALSE); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); return priv->mode == OL_OSD_WINDOW_NORMAL || (priv->locked == FALSE && priv->mouse_over); } static gboolean ol_osd_window_expose_before (GtkWidget *widget, GdkEventExpose *event) { ol_assert_ret (OL_IS_OSD_WINDOW (widget), FALSE); OlOsdWindow *osd = OL_OSD_WINDOW (widget); if (ol_osd_window_panel_visible (osd)) ol_osd_window_paint (osd); return FALSE; } static gboolean ol_osd_window_expose_after (GtkWidget *widget, GdkEventExpose *event) { ol_assert_ret (OL_IS_OSD_WINDOW (widget), FALSE); OlOsdWindow *osd = OL_OSD_WINDOW (widget); if (!ol_osd_window_panel_visible (osd)) ol_osd_window_paint (osd); return FALSE; } static void ol_osd_window_paint (OlOsdWindow *osd) { ol_assert (OL_IS_OSD_WINDOW (osd)); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); cairo_t *cr; cr = gdk_cairo_create (GTK_WIDGET (osd)->window); ol_osd_window_paint_bg (osd, cr); ol_osd_window_paint_lyrics (osd, cr); if (priv->update_shape) ol_osd_window_update_shape (osd); cairo_destroy (cr); } static gboolean ol_osd_window_enter_notify (GtkWidget *widget, GdkEventCrossing *event) { ol_assert_ret (OL_IS_OSD_WINDOW (widget), FALSE); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (widget); OlOsdWindow *osd = OL_OSD_WINDOW (widget); priv->mouse_over = TRUE; ol_osd_window_queue_reshape (osd); gtk_widget_queue_draw (widget); /* ol_osd_window_update_child_visibility (OL_OSD_WINDOW (widget)); */ return FALSE; } static void ol_osd_window_check_mouse_leave (OlOsdWindow *osd) { ol_assert (osd != NULL); GtkWidget *widget = GTK_WIDGET (osd); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (widget); if (!gdk_window_is_visible (widget->window)) return; gint rel_x, rel_y; gdk_window_get_pointer (widget->window, &rel_x, &rel_y, NULL); GdkRectangle rect; rect.x = 0; rect.y = 0; rect.width = widget->allocation.width; rect.height = widget->allocation.height; if ((priv->mode != OL_OSD_WINDOW_DOCK || priv->drag_state == DRAG_NONE) && !_point_in_rect (rel_x, rel_y, &rect)) { priv->mouse_over = FALSE; ol_osd_window_queue_reshape (osd); gtk_widget_queue_draw (widget); } } static gboolean ol_osd_window_leave_notify (GtkWidget *widget, GdkEventCrossing *event) { ol_assert_ret (OL_IS_OSD_WINDOW (widget), FALSE); OlOsdWindow *osd = OL_OSD_WINDOW (widget); ol_osd_window_check_mouse_leave (osd); return FALSE; } static gboolean ol_osd_window_map_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { ol_assert_ret (OL_IS_OSD_WINDOW (widget), FALSE); OlOsdWindow *osd = OL_OSD_WINDOW (widget); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (widget); if (priv->mouse_timer_id == 0) { priv->mouse_timer_id = g_timeout_add (MOUSE_TIMER_INTERVAL, (GSourceFunc) ol_osd_window_mouse_timer, widget); } ol_osd_window_queue_reshape (osd); priv->configure_event = g_signal_connect (G_OBJECT (osd), "configure-event", G_CALLBACK (ol_osd_window_configure_event), osd); return FALSE; } static gboolean ol_osd_window_unmap_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { ol_assert_ret (OL_IS_OSD_WINDOW (widget), FALSE); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (widget); if (priv->mouse_timer_id != 0) { g_source_remove (priv->mouse_timer_id); priv->mouse_timer_id = 0; } if (priv->configure_event > 0) { g_signal_handler_disconnect (widget, priv->configure_event); priv->configure_event = 0; } return FALSE; } static void ol_osd_window_realize_cb (GtkWidget *widget, gpointer user_data) { ol_assert (OL_IS_OSD_WINDOW (widget)); OlOsdWindow *osd = OL_OSD_WINDOW (widget); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); if (priv->composited_signal == 0) { GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (widget)); priv->composited_signal = g_signal_connect (screen, "composited-changed", G_CALLBACK (ol_osd_window_screen_composited_changed), osd); } ol_osd_window_reset_shape_pixmap (osd); ol_osd_window_set_input_shape_mask (osd, priv->locked); } static void ol_osd_window_unrealize_cb (GtkWidget *widget, gpointer user_data) { ol_assert (OL_IS_OSD_WINDOW (widget)); OlOsdWindow *osd = OL_OSD_WINDOW (widget); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); if (priv->composited_signal != 0) { g_signal_handler_disconnect (gtk_widget_get_screen (widget), priv->composited_signal); priv->composited_signal = 0; } } static void ol_osd_window_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { OlOsdWindow *osd = OL_OSD_WINDOW (widget); widget->allocation = *allocation; /* priv->width = allocation->width; */ if (GTK_WIDGET_REALIZED (widget)) { gdk_window_resize (widget->window, widget->allocation.width, widget->allocation.height); } ol_osd_window_update_child_allocation (osd); } static void ol_osd_window_size_request (GtkWidget *widget, GtkRequisition *requisition) { OlOsdWindow *osd = OL_OSD_WINDOW (widget); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); ol_osd_window_update_layout (osd); requisition->width = priv->width; requisition->height = ol_osd_window_compute_window_height (osd); } static void ol_osd_window_update_layout (OlOsdWindow *osd) { ol_assert (OL_IS_OSD_WINDOW (osd)); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); int old_height = priv->osd_height; priv->osd_height = ol_osd_window_compute_osd_height (osd); if (priv->osd_height != old_height) ol_osd_window_reset_shape_pixmap (osd); ol_osd_window_update_child_size_requisition (osd); } static gboolean _point_in_rect (int x, int y, GdkRectangle *rect) { ol_assert_ret (rect != NULL, FALSE); return x < rect->x + rect->width && y < rect->y + rect->height && x >= rect->x && y >= rect->y; } static gboolean ol_osd_window_mouse_timer (gpointer data) { /* ol_log_func (); */ ol_assert_ret (data != NULL, FALSE); ol_assert_ret (OL_IS_OSD_WINDOW (data), FALSE); OlOsdWindow *osd = OL_OSD_WINDOW (data); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); GtkWidget *widget = GTK_WIDGET (osd); if (GTK_WIDGET_REALIZED (osd)) { gint rel_x, rel_y; gboolean mouse_over = FALSE; gdk_window_get_pointer (widget->window, &rel_x, &rel_y, NULL); int i; for (i = 0; i < osd->line_count; i++) { if (_point_in_rect (rel_x, rel_y, &osd->lyric_rects[i])) { mouse_over = TRUE; break; } } if (priv->mouse_over_lyrics != mouse_over) { priv->mouse_over_lyrics = mouse_over; gtk_widget_queue_draw (widget); } if (ol_osd_window_get_mode (osd) == OL_OSD_WINDOW_DOCK) { GdkRectangle window_rect = { .x = 0, .y = 0, .width = widget->allocation.width, .height = widget->allocation.height, }; gboolean mouse_over = _point_in_rect (rel_x, rel_y, &window_rect); if (mouse_over != priv->mouse_over) { priv->mouse_over = mouse_over; ol_osd_window_queue_reshape (osd); gtk_widget_queue_draw (widget); } } else { ol_osd_window_check_mouse_leave (osd); } } return TRUE; } void ol_osd_window_set_width (OlOsdWindow *osd, gint width) { ol_assert (OL_IS_OSD_WINDOW (osd)); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); priv->width = width; ol_osd_window_queue_resize (osd); ol_osd_window_queue_reshape (osd); gtk_widget_queue_draw (GTK_WIDGET (osd)); } int ol_osd_window_get_width (OlOsdWindow *osd) { ol_assert_ret (OL_IS_OSD_WINDOW (osd), 0); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); return priv->width; } void ol_osd_window_get_osd_size (OlOsdWindow *osd, gint *width, gint *height) { /* ol_log_func (); */ ol_assert (OL_IS_OSD_WINDOW (osd)); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); if (width) *width = priv->width - BORDER_WIDTH * 2; if (height) *height = ol_osd_window_compute_osd_height (osd); } /* static void */ /* ol_osd_window_update_child_visibility (OlOsdWindow *osd) */ /* { */ /* ol_assert (OL_IS_OSD_WINDOW (osd)); */ /* OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); */ /* GtkWidget *child = gtk_bin_get_child (GTK_BIN (osd)); */ /* if (child == NULL) */ /* return; */ /* if (priv->locked) */ /* gtk_widget_hide (child); */ /* else */ /* gtk_widget_show (child); */ /* } */ void ol_osd_window_set_locked (OlOsdWindow *osd, gboolean locked) { ol_assert (OL_IS_OSD_WINDOW (osd)); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); if (priv->locked == locked) return; priv->locked = locked; if (GTK_WIDGET_REALIZED (GTK_WIDGET (osd))) { ol_osd_window_set_input_shape_mask (osd, locked); } ol_osd_window_queue_reshape (osd); gtk_widget_queue_draw (GTK_WIDGET (osd)); } static void ol_osd_window_update_child_size_requisition (OlOsdWindow *osd) { ol_assert (OL_IS_OSD_WINDOW (osd)); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); GtkBin *bin = GTK_BIN (osd); if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) { gtk_widget_size_request (bin->child, &priv->child_requisition); } else { priv->child_requisition.width = 0; priv->child_requisition.height = 0; } } static int ol_osd_window_compute_window_width (OlOsdWindow *osd) { ol_assert_ret (OL_IS_OSD_WINDOW (osd), 0); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); int w = priv->width; if (w < priv->child_requisition.width) w = priv->child_requisition.width; return w; } /** * Computes the height of the OSD Window * * The computation relies on the height of OSD text and child widget. It should be * called after ol_osd_window_update_child_size_requisition priv->osd_height * updated. * * Please note that the computed height does NOT consider the constrain of screen * @param osd * * @return The height of the widget */ static int ol_osd_window_compute_window_height (OlOsdWindow *osd) { ol_assert_ret (OL_IS_OSD_WINDOW (osd), 0); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); int height = BORDER_WIDTH * 2; height += priv->osd_height; height += priv->child_requisition.height; return height; } static int ol_osd_window_compute_osd_height (OlOsdWindow *osd) { ol_assert_ret (OL_IS_OSD_WINDOW (osd) && osd->render_context != NULL, DEFAULT_HEIGHT); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); int font_height = ol_osd_render_get_font_height (osd->render_context); int height = font_height * (osd->line_count + (osd->line_count - 1) * LINE_PADDING) + ol_osd_render_get_outline_width (osd->render_context); if (ol_osd_window_should_blur (osd)) height += priv->blur_radius * 2.0; return height; } static void ol_osd_window_update_child_allocation (OlOsdWindow *osd) { ol_assert (OL_IS_OSD_WINDOW (osd)); GtkWidget *widget = GTK_WIDGET (osd); GtkBin *bin = GTK_BIN (osd); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) { ol_debugf ("set child allocation\n"); GtkAllocation alloc; alloc.x = (widget->allocation.width - priv->child_requisition.width) / 2; alloc.width = priv->child_requisition.width; alloc.height = priv->child_requisition.height; alloc.y = widget->allocation.height - BORDER_WIDTH - priv->child_requisition.height; gtk_widget_size_allocate (bin->child, &alloc); } } static double ol_osd_window_compute_lyric_xpos (OlOsdWindow *osd, int line, double percentage) { /* ol_log_func (); */ OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); gint w, h; int width; gboolean smooth = TRUE; ol_osd_window_get_osd_size (osd, &w, &h); if (priv->active_lyric_surfaces[line] != NULL) { width = cairo_image_surface_get_width (priv->active_lyric_surfaces[line]); } else { width = 0; } double xpos; if (w >= width) { xpos = (w - width) * osd->line_alignment[line]; } else { OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); if (!priv->composited && ol_osd_window_get_mode (osd) == OL_OSD_WINDOW_DOCK) smooth = FALSE; if (smooth) { if (percentage * width < w / 2.0) xpos = 0; else if ((1.0 - percentage) * width < w / 2.0) xpos = w - width; else xpos = w / 2.0 - width * percentage; } else { if (percentage * width < w) { xpos = 0; } else { int half_count = (percentage * width - w) / w + 1; xpos = -half_count * w; if (xpos < w - width) xpos = w - width; } if (xpos != priv->lyric_xpos[line]) { ol_osd_window_queue_reshape (osd); priv->lyric_xpos[line] = xpos; } } } return xpos; } static int ol_osd_window_compute_lyric_ypos (OlOsdWindow *osd) { ol_assert_ret (OL_IS_OSD_WINDOW (osd), 0); int y = BORDER_WIDTH; return y; } static void ol_osd_window_paint_lyrics (OlOsdWindow *osd, cairo_t *cr) { ol_assert (OL_IS_OSD_WINDOW (osd)); GtkWidget *widget = GTK_WIDGET (osd); if (!gdk_window_is_visible (widget->window)) return; OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); double alpha = 1.0; int font_height = ol_osd_render_get_font_height (osd->render_context); if (priv->composited && priv->locked && priv->mouse_over_lyrics && osd->translucent_on_mouse_over) { alpha = 0.3; } if (!GTK_WIDGET_REALIZED (widget)) gtk_widget_realize (widget); gint w, h; int width, height; ol_osd_window_get_osd_size (osd, &w, &h); int line; gdouble ypos, xpos; ypos = ol_osd_window_compute_lyric_ypos (osd); int start, end; if (osd->line_count == 1) { start = osd->current_line; end = start + 1; } else { start = 0; end = OL_OSD_WINDOW_MAX_LINE_COUNT; } cairo_save (cr); cairo_rectangle (cr, BORDER_WIDTH, BORDER_WIDTH, w, h); cairo_clip (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); for (line = start; line < end; line++) { double percentage = osd->percentage[line]; if (priv->active_lyric_surfaces[line] != NULL && priv->inactive_lyric_surfaces[line]) { width = cairo_image_surface_get_width (priv->active_lyric_surfaces[line]); height = cairo_image_surface_get_height (priv->active_lyric_surfaces[line]); xpos = ol_osd_window_compute_lyric_xpos (osd, line, osd->percentage[line]); xpos += BORDER_WIDTH; cairo_pattern_t *text_mask = ol_osd_window_create_text_mask (osd, cr, line, xpos, alpha); cairo_save (cr); cairo_rectangle (cr, xpos, ypos, (double)width * percentage, height); cairo_clip (cr); cairo_set_source_surface (cr, priv->active_lyric_surfaces[line], xpos, ypos); if (text_mask) cairo_mask (cr, text_mask); else cairo_paint_with_alpha (cr, alpha); cairo_restore (cr); cairo_save (cr); cairo_rectangle (cr, xpos + width * percentage, ypos, (double)width * (1.0 - percentage), height); cairo_clip (cr); cairo_set_source_surface (cr, priv->inactive_lyric_surfaces[line], xpos, ypos); if (text_mask) cairo_mask (cr, text_mask); else cairo_paint_with_alpha (cr, alpha); if (text_mask) cairo_pattern_destroy (text_mask); cairo_restore (cr); } ypos += font_height * (1 + LINE_PADDING); } cairo_restore (cr); } static cairo_pattern_t * ol_osd_window_create_text_mask (OlOsdWindow *osd, cairo_t *cr, int line, gdouble text_xpos, gdouble alpha) { OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); int left, width; ol_osd_window_get_osd_size (osd, &width, NULL); left = BORDER_WIDTH; int text_width = cairo_image_surface_get_width (priv->active_lyric_surfaces[line]); int fade_in_size = DEFAULT_FADE_IN_SIZE; if (fade_in_size * 2 > width) fade_in_size = width / 2; if (priv->composited) { cairo_pattern_t *pattern = cairo_pattern_create_linear (left, 0.0, left + width, 0.0); /* Set fade in on left edge */ if (text_xpos < left) { cairo_pattern_add_color_stop_rgba (pattern, 0.0, /* offset */ 1.0, 1.0, 1.0, 0.0); /* r,g,b,a */ gdouble loffset = 0.0; if (left - text_xpos < fade_in_size) loffset = (gdouble) (left - text_xpos) / (gdouble) width; else loffset = (gdouble) fade_in_size / (gdouble) width; cairo_pattern_add_color_stop_rgba (pattern, loffset, /* offset */ 1.0, 1.0, 1.0, alpha); /* r,g,b,a */ } else { cairo_pattern_add_color_stop_rgba (pattern, 0.0, /* offset */ 1.0, 1.0, 1.0, alpha); /* r,g,b,a */ } /* Set fade out on the right edge */ if (text_xpos + text_width > left + width) { gdouble roffset = 0.0; if (text_xpos + text_width - (left + width) < fade_in_size) roffset = 1.0 - (gdouble) (text_xpos + text_width - (left + width)) / (gdouble) width; else roffset = 1.0 - (gdouble) (fade_in_size) / (gdouble) width; cairo_pattern_add_color_stop_rgba (pattern, roffset, /* offset */ 0.0, 0.0, 0.0, alpha); /* r,g,b,a */ cairo_pattern_add_color_stop_rgba (pattern, 1.0, /* offset */ 0.0, 0.0, 0.0, 0.0); /* r,g,b,a */ } else { cairo_pattern_add_color_stop_rgba (pattern, 1.0, /* offset */ 0.0, 0.0, 0.0, alpha); /* r,g,b,a */ } return pattern; } return NULL; } static void ol_osd_window_clear_cairo (cairo_t *cr) { ol_assert (cr != NULL); cairo_save (cr); cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.0); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_paint(cr); cairo_restore (cr); } void ol_osd_window_set_percentage (OlOsdWindow *osd, gint line, double percentage) { if (line < 0 || line >= OL_OSD_WINDOW_MAX_LINE_COUNT) return; ol_assert (OL_IS_OSD_WINDOW (osd)); if (percentage == osd->percentage[line]) return; double old_percentage = osd->percentage[line]; osd->percentage[line] = percentage; /* update shape if line is too long */ OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); if (!priv->composited && percentage != old_percentage) { int old_x = ol_osd_window_compute_lyric_xpos (osd, line, old_percentage); int new_x = ol_osd_window_compute_lyric_xpos (osd, line, percentage); if (old_x != new_x) priv->update_shape = TRUE; } gtk_widget_queue_draw (GTK_WIDGET (osd)); } void ol_osd_window_set_current_percentage (OlOsdWindow *osd, double percentage) { ol_osd_window_set_percentage (osd, osd->current_line, percentage); } double ol_osd_window_get_current_percentage (OlOsdWindow *osd) { ol_assert_ret (OL_IS_OSD_WINDOW (osd), 0.0); return osd->percentage[osd->current_line]; } void ol_osd_window_set_current_line (OlOsdWindow *osd, gint line) { ol_assert (OL_IS_OSD_WINDOW (osd)); ol_assert (line < ol_osd_window_get_line_count (osd)); osd->current_line = line; } gint ol_osd_window_get_current_line (OlOsdWindow *osd) { ol_assert_ret (OL_IS_OSD_WINDOW (osd), 0); return osd->current_line; } static void ol_osd_draw_lyric_surface (OlOsdWindow *osd, cairo_surface_t **surface, const char *lyric) { if (*surface != NULL) { cairo_surface_destroy (*surface); *surface = NULL; } if (!ol_is_string_empty (lyric) && *surface == NULL) { int w, h; if (!GTK_WIDGET_REALIZED (GTK_WIDGET (osd))) gtk_widget_realize (GTK_WIDGET (osd)); ol_osd_render_get_pixel_size (osd->render_context, lyric, &w, &h); *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h); cairo_t *cr = cairo_create (*surface); cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.0); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_paint (cr); ol_osd_render_paint_text (osd->render_context, cr, lyric, 0, 0); cairo_destroy (cr); } } static void ol_osd_window_update_lyric_surface (OlOsdWindow *osd, int line) { int i; if (!GTK_WIDGET_REALIZED (GTK_WIDGET (osd))) return; OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); /* draws the inactive surfaces */ for (i = 0; i < OL_LINEAR_COLOR_COUNT; i++) { ol_osd_render_set_linear_color (osd->render_context, i, osd->inactive_colors[i]); } ol_osd_draw_lyric_surface (osd, &priv->inactive_lyric_surfaces[line], osd->lyrics[line]); /* draws the active surfaces */ for (i = 0; i < OL_LINEAR_COLOR_COUNT; i++) { ol_osd_render_set_linear_color (osd->render_context, i, osd->active_colors[i]); } ol_osd_draw_lyric_surface (osd, &priv->active_lyric_surfaces[line], osd->lyrics[line]); ol_osd_window_update_lyric_rect (osd, line); } static void ol_osd_window_update_lyric_rect (OlOsdWindow *osd, int line) { ol_log_func (); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); int w, h; w = h = 0; if (priv->active_lyric_surfaces[line] != NULL) { w = cairo_image_surface_get_width (priv->active_lyric_surfaces[line]); h = cairo_image_surface_get_height (priv->active_lyric_surfaces[line]); } int font_height = ol_osd_render_get_font_height (osd->render_context); osd->lyric_rects[line].x = ol_osd_window_compute_lyric_xpos (osd, line, osd->percentage[line]); osd->lyric_rects[line].y = font_height * line * (1 + LINE_PADDING); osd->lyric_rects[line].width = w; osd->lyric_rects[line].height = h; } void ol_osd_window_set_lyric (OlOsdWindow *osd, gint line, const char *lyric) { ol_debugf (" lyric:%s\n", lyric); ol_assert (OL_IS_OSD_WINDOW (osd)); ol_assert (line >= 0 && line < OL_OSD_WINDOW_MAX_LINE_COUNT); if (osd->lyrics[line] != NULL) g_free (osd->lyrics[line]); if (lyric != NULL) { if (g_utf8_strlen (lyric, -1) > MAX_LYRIC_LEN) osd->lyrics[line] = g_strndup (lyric, g_utf8_offset_to_pointer (lyric, MAX_LYRIC_LEN) - lyric); else osd->lyrics[line] = g_strdup (lyric); } else { osd->lyrics[line] = g_strdup (""); } ol_osd_window_update_lyric_surface (osd, line); /* We have to call ol_osd_window_update_shape instead of ol_osd_window_queue_reshape here, because there might be empty shape before setting lyric, in which case expose event will not be emitted, thus the shape will not be updated at all. */ ol_osd_window_update_shape (osd); gtk_widget_queue_draw (GTK_WIDGET (osd)); } void ol_osd_window_set_line_alignment (OlOsdWindow *osd, gint line, double alignment) { ol_debugf ("%s:%d-%lf\n", __FUNCTION__, line, alignment); ol_assert (OL_IS_OSD_WINDOW (osd)); if (line < 0 || line >= OL_OSD_WINDOW_MAX_LINE_COUNT) return; if (alignment < 0.0) alignment = 0.0; else if (alignment > 1.0) alignment = 1.0; osd->line_alignment[line] = alignment; ol_osd_window_update_lyric_rect (osd, line); ol_osd_window_queue_reshape (osd); gtk_widget_queue_draw (GTK_WIDGET (osd)); } static void ol_osd_window_reset_shape_pixmap (OlOsdWindow *osd) { /* init shape pixmap */ ol_assert (OL_IS_OSD_WINDOW (osd)); if (!GTK_WIDGET_REALIZED (GTK_WIDGET (osd))) return; GtkWidget *widget = GTK_WIDGET (osd); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); gint width = priv->width; gint height = ol_osd_window_compute_window_height (osd); if (priv->shape_pixmap != NULL) { gint w, h; gdk_drawable_get_size (priv->shape_pixmap, &w, &h); if (w == width && h == height) { return; } else { g_object_unref (priv->shape_pixmap); priv->shape_pixmap = NULL; } } priv->shape_pixmap = gdk_pixmap_new (widget->window, width, height, 1); cairo_t *cr = gdk_cairo_create (priv->shape_pixmap); ol_osd_window_clear_cairo (cr); cairo_destroy (cr); ol_osd_window_queue_reshape (osd); } static void ol_osd_window_queue_reshape (OlOsdWindow *osd) { ol_assert (OL_IS_OSD_WINDOW (osd)); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); priv->update_shape = TRUE; } static void ol_osd_window_update_shape (OlOsdWindow *osd) { ol_assert (OL_IS_OSD_WINDOW (osd)); GtkWidget *widget = GTK_WIDGET (osd); static gboolean empty_mask = TRUE; if (!GTK_WIDGET_REALIZED (widget)) return; OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); if (priv->composited || ol_osd_window_panel_visible (osd)) { if (!empty_mask) { empty_mask = TRUE; gdk_window_shape_combine_mask (widget->window, NULL, 0, 0); } return; } empty_mask = FALSE; GdkPixmap *shape_mask = priv->shape_pixmap; GdkGC *fg_gc = gdk_gc_new (shape_mask); GdkColor color; color.pixel = 0; gdk_gc_set_foreground (fg_gc, &color); gdk_gc_set_background (fg_gc, &color); cairo_t *cr = gdk_cairo_create (shape_mask); ol_osd_window_clear_cairo (cr); ol_osd_window_paint_lyrics (osd, cr); cairo_destroy (cr); gdk_window_shape_combine_mask (widget->window, shape_mask, 0, 0); priv->update_shape = FALSE; g_object_unref (fg_gc); } static void ol_osd_window_set_input_shape_mask (OlOsdWindow *osd, gboolean disable_input) { ol_assert (OL_IS_OSD_WINDOW (osd)); GtkWidget *widget = GTK_WIDGET (osd); ol_assert (GTK_WIDGET_REALIZED (widget)); if (disable_input) { GdkRegion *region = gdk_region_new (); gdk_window_input_shape_combine_region (GTK_WIDGET (osd)->window, region, 0, 0); gdk_region_destroy (region); } else { gdk_window_input_shape_combine_region (GTK_WIDGET (osd)->window, NULL, 0, 0); } } /** * Computes the position and size when constraining the window to be inside a screen * * @param osd * @param x * @param y * @param width * @param height * @param drag_state The hint of drag type. If the window is outside the screen, * drag_type determines how to constrain the window: * DRAG_MOVE: constrain the window by change position * DRAG_EAST: constrain the window by shrinking the width * DRAG_WEST: constrain the window by shrinking the width and * change the x position so that the right edge of the * window will not change * DRAG_NONE: acts as DRAG_MOVE */ static void ol_osd_window_compute_constrain (OlOsdWindow *osd, int *x, int *y, int *width, int *height, enum DragState drag_state) { ol_assert (OL_IS_OSD_WINDOW (osd)); ol_assert (x != NULL); ol_assert (y != NULL); ol_assert (width != NULL); ol_assert (height != NULL); GtkWidget *widget = GTK_WIDGET (osd); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); GdkScreen *screen = gtk_widget_get_screen (widget); if (ol_osd_window_get_mode (osd) == OL_OSD_WINDOW_DOCK && screen != NULL) { int sw = gdk_screen_get_width (screen); int sh = gdk_screen_get_height (screen); switch (drag_state) { case DRAG_EAST: *x = MAX (0, *x); *width = MIN (*width, sw - *x); break; case DRAG_WEST: *width = MIN (*width, *x + *width); *x = MAX (0, MIN (*x, priv->old_x + priv->old_width - *width)); break; default: *x = MAX (0, MIN (*x, sw - *width)); } if (*y + *height > sh) { int minh = ol_osd_window_compute_osd_height (osd) + BORDER_WIDTH * 2; *height = MAX (minh, sh - *y); *y = MAX (0, sh - *height); } else { *y = MAX (0, *y); } } } static void ol_osd_window_queue_resize (OlOsdWindow *osd) { ol_assert (OL_IS_OSD_WINDOW (osd)); GtkWidget *widget = GTK_WIDGET (osd); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); if (!GTK_WIDGET_REALIZED (widget)) return; ol_osd_window_update_layout (osd); int h = ol_osd_window_compute_window_height (osd); int w = ol_osd_window_compute_window_width (osd); ol_osd_window_move_resize (osd, priv->raw_x, priv->raw_y, w, h, DRAG_NONE); } /** * Move and resize the OSD Window. * * The coordinations and size are raw values, which means does not honor the size * of the child widget. * * @param osd * @param raw_x * @param raw_y * @param width * @param drag_state See long comment of ol_osd_window_compute_constrain */ static void ol_osd_window_move_resize (OlOsdWindow *osd, int x, int y, int width, int height, enum DragState drag_state) { ol_assert (OL_IS_OSD_WINDOW (osd)); GtkWidget *widget = GTK_WIDGET (osd); gboolean size_changed = FALSE; gboolean pos_changed = FALSE; OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); int old_x, old_y, old_w, old_h; gtk_window_get_position (GTK_WINDOW (osd), &old_x, &old_y); gtk_window_get_size (GTK_WINDOW (osd), &old_w, &old_h); ol_osd_window_compute_constrain (osd, &x, &y, &width, &height, drag_state); if (height != old_h || width != old_w) size_changed = TRUE; if (priv->raw_x != x || priv->raw_y != y || old_x != x || old_y != y) pos_changed = TRUE; priv->raw_x = x; priv->raw_y = y; if (pos_changed) { gtk_window_move (GTK_WINDOW (osd), x, y); } if (size_changed || pos_changed) { /* gtk_window_resize and ol_osd_window_queue_resize don't work, so we have to specify allocation explicitly*/ /* GtkAllocation newalloc = { */ /* .x = 0, */ /* .y = 0, */ /* .width = width, */ /* .height = height, */ /* }; */ /* gtk_widget_size_allocate (widget, &newalloc); */ gtk_window_resize (GTK_WINDOW (osd), width, height); /* ol_osd_window_queue_resize (widget); */ ol_osd_window_queue_reshape (osd); gtk_widget_queue_draw (widget); } } void ol_osd_window_move (OlOsdWindow *osd, int x, int y) { ol_assert (OL_IS_OSD_WINDOW (osd)); ol_osd_window_move_resize (osd, x, y, ol_osd_window_compute_window_width (osd), ol_osd_window_compute_window_height (osd), DRAG_MOVE); } void ol_osd_window_get_pos (OlOsdWindow *osd, int *x, int *y) { ol_assert (OL_IS_OSD_WINDOW (osd)); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); if (x) *x = priv->raw_x; if (y) *y = priv->raw_y; } static void ol_osd_window_class_init (OlOsdWindowClass *klass) { ol_log_func (); GObjectClass *gobject_class; GtkObjectClass *object_class; GtkWidgetClass *widget_class; gobject_class = G_OBJECT_CLASS (klass); object_class = (GtkObjectClass*)klass; widget_class = (GtkWidgetClass*)klass; ol_osd_window_parent_class = g_type_class_peek_parent (klass); gobject_class->set_property = ol_osd_window_set_property; gobject_class->get_property = ol_osd_window_get_property; object_class->destroy = ol_osd_window_destroy; widget_class->size_allocate = ol_osd_window_size_allocate; widget_class->size_request = ol_osd_window_size_request; widget_class->enter_notify_event = ol_osd_window_enter_notify; widget_class->leave_notify_event = ol_osd_window_leave_notify; /* set up properties */ g_object_class_install_property (gobject_class, PROP_LOCKED, g_param_spec_boolean ("locked", ("Whether the it is possible to move the OSD window"), ("If TRUE, there will be a window displayed under the" "OSD indicating that it can be moved, and so it is"), FALSE, G_PARAM_READABLE | G_PARAM_WRITABLE)); /* install signals */ klass->signals[OSD_MOVED] = g_signal_newv ("moved", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, NULL, /* closure */ NULL, /* accumulator */ NULL, /* accumulator data */ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL); klass->signals[OSD_RESIZE] = g_signal_newv ("resize", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, NULL, /* closure */ NULL, /* accumulator */ NULL, /* accumulator data */ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL); g_type_class_add_private (gobject_class, sizeof (OlOsdWindowPrivate)); } static void ol_osd_window_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { OlOsdWindow *osd = OL_OSD_WINDOW (object); switch (prop_id) { case PROP_LOCKED: ol_assert (G_VALUE_HOLDS_BOOLEAN (value)); ol_osd_window_set_locked (osd, g_value_get_boolean (value)); break; } } static void ol_osd_window_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { OlOsdWindow *osd = OL_OSD_WINDOW (object); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); switch (prop_id) { case PROP_LOCKED: g_value_set_boolean (value, priv->locked); break; } } static void ol_osd_window_init (OlOsdWindow *osd) { ol_log_func (); GtkWindow *window = GTK_WINDOW (osd); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); window->type = GTK_WINDOW_TOPLEVEL; GValue allow_shrink = {0}; g_value_init (&allow_shrink, G_TYPE_BOOLEAN); g_value_set_boolean (&allow_shrink, TRUE); g_object_set_property (G_OBJECT (window), "allow-shrink", &allow_shrink); g_value_unset (&allow_shrink); gtk_window_set_decorated (window, FALSE); gtk_widget_set_app_paintable (GTK_WIDGET (osd), TRUE); osd->current_line = 0; osd->bg_pixbuf = NULL; osd->line_count = 1; int i; for (i = 0; i < OL_OSD_WINDOW_MAX_LINE_COUNT; i++) { osd->lyrics[i] = NULL; osd->line_alignment[i] = 0.5; osd->percentage[i] = 0.0; priv->active_lyric_surfaces[i] = NULL; priv->inactive_lyric_surfaces[i] = NULL; osd->lyric_rects[i].x = 0; osd->lyric_rects[i].y = 0; osd->lyric_rects[i].width = 0; osd->lyric_rects[i].height = 0; } for (i = 0; i < OL_LINEAR_COLOR_COUNT; i++) { osd->active_colors[i] = DEFAULT_ACTIVE_COLORS[i]; osd->inactive_colors[i] = DEFAULT_INACTIVE_COLORS[i]; } osd->render_context = ol_osd_render_context_new (); osd->translucent_on_mouse_over = FALSE; /* initilaize private data */ priv->shape_pixmap = NULL; priv->width = DEFAULT_WIDTH; priv->locked = TRUE; priv->composited = FALSE; priv->visible = FALSE; priv->mouse_timer_id = 0; priv->configure_event = 0; priv->child_requisition.width = 0; priv->child_requisition.height = 0; priv->mouse_over_lyrics = FALSE; priv->composited_signal = 0; priv->raw_x = 0; priv->raw_y = 0; priv->drag_state = DRAG_NONE; priv->update_shape = FALSE; priv->blur_radius = 0.0; /* ol_osd_window_set_mode (osd, OL_OSD_WINDOW_DOCK); */ ol_osd_window_set_mode (osd, OL_OSD_WINDOW_NORMAL); ol_osd_window_update_colormap (osd); ol_osd_window_screen_composited_changed (gtk_widget_get_screen (GTK_WIDGET (osd)), osd); gtk_widget_add_events(GTK_WIDGET(osd), GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK); g_signal_connect (G_OBJECT (osd), "button-press-event", G_CALLBACK (ol_osd_window_button_press), osd); g_signal_connect (G_OBJECT (osd), "button-release-event", G_CALLBACK (ol_osd_window_button_release), osd); g_signal_connect (G_OBJECT (osd), "motion-notify-event", G_CALLBACK (ol_osd_window_motion_notify), osd); g_signal_connect (G_OBJECT (osd), "expose-event", G_CALLBACK (ol_osd_window_expose_before), osd); g_signal_connect_after (G_OBJECT (osd), "expose-event", G_CALLBACK (ol_osd_window_expose_after), osd); g_signal_connect (G_OBJECT (osd), "map-event", G_CALLBACK (ol_osd_window_map_cb), osd); g_signal_connect (G_OBJECT (osd), "unmap-event", G_CALLBACK (ol_osd_window_unmap_cb), osd); g_signal_connect (G_OBJECT (osd), "realize", G_CALLBACK (ol_osd_window_realize_cb), osd); g_signal_connect (G_OBJECT (osd), "unrealize", G_CALLBACK (ol_osd_window_unrealize_cb), osd); } GtkWidget* ol_osd_window_new () { ol_log_func (); OlOsdWindow *osd; osd = g_object_new (ol_osd_window_get_type (), NULL); return GTK_WIDGET (osd); } static void ol_osd_window_destroy (GtkObject *object) { ol_log_func (); ol_assert (OL_IS_OSD_WINDOW (object)); OlOsdWindow *osd = OL_OSD_WINDOW (object); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); int i; if (priv->mouse_timer_id > 0) { g_source_remove (priv->mouse_timer_id); priv->mouse_timer_id = 0; } for (i = 0; i < OL_OSD_WINDOW_MAX_LINE_COUNT; i++) { if (osd->lyrics[i] != NULL) { g_free (osd->lyrics[i]); osd->lyrics[i] = NULL; } if (priv->active_lyric_surfaces[i] != NULL) { cairo_surface_destroy (priv->active_lyric_surfaces[i]); priv->active_lyric_surfaces[i] = NULL; } if (priv->inactive_lyric_surfaces[i] != NULL) { cairo_surface_destroy (priv->inactive_lyric_surfaces[i]); priv->inactive_lyric_surfaces[i] = NULL; } } if (priv->shape_pixmap != NULL) { g_object_unref (priv->shape_pixmap); priv->shape_pixmap = NULL; } if (osd->render_context != NULL) { ol_osd_render_context_destroy (osd->render_context); osd->render_context = NULL; } if (osd->bg_pixbuf != NULL) { g_object_unref (osd->bg_pixbuf); osd->bg_pixbuf = NULL; } GTK_OBJECT_CLASS (ol_osd_window_parent_class)->destroy (object); } void ol_osd_window_set_font_name (OlOsdWindow *osd, const char *font_name) { if (osd == NULL || osd->render_context == NULL || font_name == NULL) return; ol_osd_render_set_font_name (osd->render_context, font_name); int i; for (i = 0; i < osd->line_count; i++) ol_osd_window_update_lyric_surface (osd, i); ol_osd_window_queue_resize (osd); ol_osd_window_queue_reshape (osd); gtk_widget_queue_draw (GTK_WIDGET (osd)); } char* ol_osd_window_get_font_name (OlOsdWindow *osd) { if (osd == NULL || osd->render_context == NULL) return NULL; return ol_osd_render_get_font_name (osd->render_context); } void ol_osd_window_set_outline_width (OlOsdWindow *osd, const int width) { ol_assert (osd != NULL); ol_assert (osd->render_context != NULL); ol_osd_render_set_outline_width (osd->render_context, width); int i; for (i = 0; i < osd->line_count; i++) ol_osd_window_update_lyric_surface (osd, i); ol_osd_window_queue_resize (osd); ol_osd_window_queue_reshape (osd); gtk_widget_queue_draw (GTK_WIDGET (osd)); } int ol_osd_window_get_outline_width (OlOsdWindow *osd) { ol_assert_ret (osd != NULL, 0); ol_assert_ret (osd->render_context != NULL, 0); return ol_osd_render_get_outline_width (osd->render_context); } void ol_osd_window_set_active_colors (OlOsdWindow *osd, OlColor top_color, OlColor middle_color, OlColor bottom_color) { ol_assert (OL_IS_OSD_WINDOW (osd)); osd->active_colors[0] = top_color; osd->active_colors[1] = middle_color; osd->active_colors[2] = bottom_color; int i; for (i = 0; i < osd->line_count; i++) ol_osd_window_update_lyric_surface (osd, i); gtk_widget_queue_draw (GTK_WIDGET (osd)); } void ol_osd_window_set_inactive_colors (OlOsdWindow *osd, OlColor top_color, OlColor middle_color, OlColor bottom_color) { ol_assert (OL_IS_OSD_WINDOW (osd)); osd->inactive_colors[0] = top_color; osd->inactive_colors[1] = middle_color; osd->inactive_colors[2] = bottom_color; int i; for (i = 0; i < osd->line_count; i++) ol_osd_window_update_lyric_surface (osd, i); gtk_widget_queue_draw (GTK_WIDGET (osd)); } void ol_osd_window_set_line_count (OlOsdWindow *osd, guint line_count) { ol_assert (OL_IS_OSD_WINDOW (osd)); ol_assert (line_count >= 1 && line_count <= 2); osd->line_count = line_count; ol_osd_window_queue_resize (osd); ol_osd_window_queue_reshape (osd); } guint ol_osd_window_get_line_count (OlOsdWindow *osd) { ol_assert_ret (OL_IS_OSD_WINDOW (osd), 1); return osd->line_count; } void ol_osd_window_set_translucent_on_mouse_over (OlOsdWindow *osd, gboolean value) { ol_assert (OL_IS_OSD_WINDOW (osd)); osd->translucent_on_mouse_over = value; } gboolean ol_osd_window_get_translucent_on_mouse_over (OlOsdWindow *osd) { ol_assert_ret (OL_IS_OSD_WINDOW (osd), FALSE); return osd->translucent_on_mouse_over; } void ol_osd_window_set_bg (OlOsdWindow *osd, GdkPixbuf *bg) { ol_log_func (); ol_debugf ("bg: %p\n", bg); ol_assert (OL_IS_OSD_WINDOW (osd)); if (bg != NULL) g_object_ref (bg); if (osd->bg_pixbuf != NULL) g_object_unref (osd->bg_pixbuf); osd->bg_pixbuf = bg; gtk_widget_queue_draw (GTK_WIDGET (osd)); } void ol_osd_window_set_mode (OlOsdWindow *osd, enum OlOsdWindowMode mode) { ol_assert (OL_IS_OSD_WINDOW (osd)); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); GtkWindow *window = GTK_WINDOW (osd); GtkWidget *widget = GTK_WIDGET (osd); if (mode == priv->mode) return; gboolean realized = GTK_WIDGET_REALIZED (widget); gboolean mapped = GTK_WIDGET_MAPPED (widget); if (mapped) gtk_widget_unmap (widget); if (realized) gtk_widget_unrealize (widget); switch (mode) { case OL_OSD_WINDOW_NORMAL: gtk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_NORMAL); window->type = GTK_WINDOW_TOPLEVEL; break; case OL_OSD_WINDOW_DOCK: gtk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_DOCK); window->type = GTK_WINDOW_POPUP; break; default: ol_errorf ("Invalid OSD Window type %d\n", mode); mode = priv->mode; } priv->mode = mode; ol_osd_window_update_render_blur_radius (osd); if (realized) gtk_widget_realize (widget); if (mapped) { gtk_widget_map (widget); /* We need to specify the position so that OSD window keeps its place. */ ol_osd_window_queue_resize (osd); } ol_osd_window_queue_reshape (osd); } enum OlOsdWindowMode ol_osd_window_get_mode (OlOsdWindow *osd) { ol_assert_ret (OL_IS_OSD_WINDOW (osd), OL_OSD_WINDOW_NORMAL); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); return priv->mode; } static gboolean ol_osd_window_should_blur (OlOsdWindow *osd) { ol_assert_ret (OL_IS_OSD_WINDOW (osd), FALSE); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); return priv->composited || priv->mode == OL_OSD_WINDOW_NORMAL; } /** * Sets the blur radius of renderer if possible. */ static void ol_osd_window_update_render_blur_radius (OlOsdWindow *osd) { ol_assert (OL_IS_OSD_WINDOW (osd)); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); if (ol_osd_window_should_blur (osd)) ol_osd_render_set_blur_radius (osd->render_context, priv->blur_radius); else ol_osd_render_set_blur_radius (osd->render_context, 0.0); } void ol_osd_window_set_blur_radius (OlOsdWindow *osd, double radius) { ol_assert (OL_IS_OSD_WINDOW (osd)); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); int i; priv->blur_radius = radius; ol_osd_window_update_render_blur_radius (osd); for (i = 0; i < osd->line_count; i++) ol_osd_window_update_lyric_surface (osd, i); ol_osd_window_queue_resize (osd); gtk_widget_queue_draw (GTK_WIDGET (osd)); } double ol_osd_window_get_blur_radius (OlOsdWindow *osd) { ol_assert_ret (OL_IS_OSD_WINDOW (osd), 0.0); OlOsdWindowPrivate *priv = OL_OSD_WINDOW_GET_PRIVATE (osd); return priv->blur_radius; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_osd_window.h000066400000000000000000000247701345713535600211740ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ /** * @file ol_osd_window.h * @author Tiger Soldier * @date Mon May 11 14:16:52 2009 * * @brief The definition of an OlOsdWindow widget */ #ifndef __OSD_WINDOW_H_ #define __OSD_WINDOW_H_ #include #include "ol_osd_render.h" #define OL_OSD_WINDOW(obj) GTK_CHECK_CAST (obj, ol_osd_window_get_type (), OlOsdWindow) #define OL_OSD_WINDOW_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, ol_osd_window_get_type (), OlOsdWindowClass) #define OL_IS_OSD_WINDOW(obj) GTK_CHECK_TYPE (obj, ol_osd_window_get_type ()) #define OL_OSD_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ol_osd_window_get_type (), OlOsdWindowClass)) #define OL_OSD_WINDOW_MAX_LINE_COUNT 2 typedef struct _OlOsdWindow OlOsdWindow; typedef struct _OlOsdWindowClass OlOsdWindowClass; enum OlOsdWindowSingals { OSD_INVALID_SIGNAL = 0, OSD_MOVED, OSD_RESIZE, OSD_SINGAL_COUNT, }; enum OlOsdWindowMode { OL_OSD_WINDOW_NORMAL, OL_OSD_WINDOW_DOCK, }; struct _OlOsdWindow { GtkWindow parent; GdkPixbuf *bg_pixbuf; gchar *lyrics[OL_OSD_WINDOW_MAX_LINE_COUNT]; double line_alignment[OL_OSD_WINDOW_MAX_LINE_COUNT]; guint current_line; /* which line is playing currently */ double percentage[OL_OSD_WINDOW_MAX_LINE_COUNT]; OlColor active_colors[OL_LINEAR_COLOR_COUNT]; OlColor inactive_colors[OL_LINEAR_COLOR_COUNT]; GdkRectangle lyric_rects[OL_OSD_WINDOW_MAX_LINE_COUNT]; OlOsdRenderContext *render_context; guint line_count; gboolean translucent_on_mouse_over; }; struct _OlOsdWindowClass { GtkWindowClass parent_class; guint signals[OSD_SINGAL_COUNT]; }; GtkType ol_osd_window_get_type (void); /** * @brief Creates a new OSD Window. * To destroy the OSD Window, use g_object_unref */ GtkWidget* ol_osd_window_new (void); /** * @brief Sets witdh of an OSD window * * @param osd An OlOsdWindow * @param width The width of the window */ void ol_osd_window_set_width (OlOsdWindow *osd, gint width); int ol_osd_window_get_width (OlOsdWindow *osd); /** * @brief Gets the size of an OSD window * * @param osd An OlOsdWindow * @param width The width of the window, can be NULL * @param height The height of the window, can be NULL */ void ol_osd_window_get_osd_size (OlOsdWindow *osd, gint *width, gint *height); /** * @brief Sets whether an OSD window is locked * If an OSD window is locked, it can neither be moved by mouse, nor receive the mouse event. * Mouse events on it will be forwarded to the window below it. * If an OSD window is unlocked, it can be moved by mouse, just move your pointer to the rectangle * of the OSD window and drag it. * @param osd An OlOsdWindow * @param locked Whether the OSD window is locked or not */ void ol_osd_window_set_locked (OlOsdWindow *osd, gboolean locked); /** * @brief Gets whether an OSD window is locked * * @param osd An OldOsdWindow * * @return Whether the OSD window is locked or not */ gboolean ol_osd_window_get_locked (OlOsdWindow *osd); /** * @brief Sets the progress of the given lyric line * The color of the left part of the given lyric line will be changed, which makes the lyric KaraOK-like. * @param osd An OlOsdWindow * @param line The line of lyric * @param percentage The width percentage of the left part whose color is changed */ void ol_osd_window_set_percentage (OlOsdWindow *osd, gint line, double percentage); /** * @brief Sets the progress of the current lyric line * The color of the left part of the current lyric line will be changed, which makes the lyric KaraOK-like. * @param osd An OlOsdWindow * @param percentage The width percentage of the left part whose color is changed */ void ol_osd_window_set_current_percentage (OlOsdWindow *osd, double percentage); /** * @brief Gets the progress of the current lyric line * * @param osd An OlOsdWindow * * @return The width percentage of the left part whose color is changed */ double ol_osd_window_get_current_percentage (OlOsdWindow *osd); /** * @brief Sets the current line number * The current line is the lyric which is playing currently. The current lyric's color will be affected by * the current percentage set by ol_osd_window_set_current_percentage * @param osd An OlOsdWindow * @param line The line number of the current lyric, can be 0 or 1. 0 is the upper line and 1 is the lower */ void ol_osd_window_set_current_line (OlOsdWindow *osd, gint line); /** * @brief Gets the current line number * * @param osd An OlOsdWindow * * @return The line number of the current lyric. */ gint ol_osd_window_get_current_line (OlOsdWindow *osd); /** * @brief Set the lyric of certain line * If a line of lyric is set, it will changes to the lyric. * @param osd An OlOsdWindow * @param line The line whose lyric will be set. Can be 0 or 1. * @param lyric The lyric of the line. NULL means the line has no lyric currently. */ void ol_osd_window_set_lyric (OlOsdWindow *osd, gint line, const char *lyric); /** * @brief Set the horizontal alignment of a line * * @param osd An OlOsdWindow * @param line The line that will be set. Can be 0 or 1. * @param alignment The alignment of the line, in the range of [0, 1]. * 0 means left aligned, 0.5 means center aligned, 1.0 means right aligned. */ void ol_osd_window_set_line_alignment (OlOsdWindow *osd, gint line, double alignment); /** * @brief Sets the font name for an OSD Window * * This name includes style and size information as well. The format follows the * format in pango_font_description_from_string * * @param osd An OlOsdWindow; * @param font_family Font name, */ void ol_osd_window_set_font_name (OlOsdWindow *osd, const char *font_name); /** * @brief Gets the font name for an OSD Window * * @param osd An OlOsdWindow * @return The font name of the context, must be freed by g_free */ char* ol_osd_window_get_font_name (OlOsdWindow *osd); /** * @brief Sets the outline width * * @param osd An OSD window * @param width Outline width, must be positive */ void ol_osd_window_set_outline_width (OlOsdWindow *osd, const int width); /** * @brief Gets the outline width for an OSD Window * * @param osd An OSD Window; * * @return The outline width for the context */ int ol_osd_window_get_outline_width (OlOsdWindow *osd); /** * @brief Sets the color of active lyrics * Active lyric is the played part of the lyric * @param osd An OlOsdWindow * @param top_color The color of the top part of the lyrics * @param middle_color The color of the middle part of the lyrics * @param bottom_color The color of the bottom part of the lyrics */ void ol_osd_window_set_active_colors (OlOsdWindow *osd, OlColor top_color, OlColor middle_color, OlColor bottom_color); /** * @brief Sets the color of inactive lyrics * Inactive lyric is the lyric to be played * @param osd An OlOsdWindow * @param top_color The color of the top part of the lyrics * @param middle_color The color of the middle part of the lyrics * @param bottom_color The color of the bottom part of the lyrics */ void ol_osd_window_set_inactive_colors (OlOsdWindow *osd, OlColor top_color, OlColor middle_color, OlColor bottom_color); /** * @brief Sets the number of lyric lines to be displayed * * @param osd An OlOsdWindow * @param line_count number of lines, in the range of [1,2] */ void ol_osd_window_set_line_count (OlOsdWindow *osd, guint line_count); /** * @brief Sets the number of lyric lines to be displayed * * @param osd An OlOsdWindow * @return number of lines, in the range of [1,2] */ guint ol_osd_window_get_line_count (OlOsdWindow *osd); /** * @brief Sets whether the OSD window will be translucent when pointer is over it * * @param osd An OlOsdWindow * @param value whether the osd will be translucent */ void ol_osd_window_set_translucent_on_mouse_over (OlOsdWindow *osd, gboolean value); /** * @brief Gets whether the OSD window will be translucent when pointer is over it * * @param osd An OlOsdWindow * @return whether the osd will be translucent */ gboolean ol_osd_window_get_translucent_on_mouse_over (OlOsdWindow *osd); /** * @brief Sets the background of the OSD window * * The background will display when the OSD Window is unlocked and * with user's mouse on it. * * @param osd * @param bg The new background. The OSD window won't increase its ref * count, but unref the old one. So if you want to keep the * copy of the pixbuf, you need to ref it manually. * */ void ol_osd_window_set_bg (OlOsdWindow *osd, GdkPixbuf *bg); void ol_osd_window_set_mode (OlOsdWindow *osd, enum OlOsdWindowMode mode); enum OlOsdWindowMode ol_osd_window_get_mode (OlOsdWindow *osd); /** * Moves osd window * * If the type of OSD window is OL_OSD_WINDOW_DOCK, the position of * window will be placed inside the screen. * * @param osd * @param x * @param y */ void ol_osd_window_move (OlOsdWindow *osd, int x, int y); void ol_osd_window_get_pos (OlOsdWindow *osd, int *x, int *y); /** * Sets the blur radius of the shadow of text. * * The outline will be blurred as shadow if radius is greater than 0. * @param osd * @param radius The blur radius. */ void ol_osd_window_set_blur_radius (OlOsdWindow *osd, double radius); /** * Gets the blur radius of the shadow of text. * * @param osd * * @return */ double ol_osd_window_get_blur_radius (OlOsdWindow *osd); #endif // __OSD_WINDOW_H__ osdlyrics-0.5.5~rc1+dfsg1/src/ol_path_pattern.c000066400000000000000000000232461345713535600215010ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include #include "ol_path_pattern.h" #include "ol_utils.h" #include "ol_debug.h" #define BUFFER_SIZE 1024 #define MAX_PATH_LEN 1024 /** * @brief duplicates the string and replace all invalid characters to `_' * * @param str string needs to be replaced * * @return replaced string, should be free with g_free */ static char* replace_invalid_str (const char *str); /** * @brief Gets the filename from a uri * * @param dest The buffer to store the filename * @param dest_len The size of the buffer * @param uri The URI of a file * @param with_ext If FALSE, the returned filename will not contain extention (i.e. `.mp3') * * @return The end of the destination if succeeded, or NULL if failed. */ static char* ol_uri_get_filename (char *dest, size_t dest_len, const char *uri, gboolean with_ext); int ol_path_get_lrc_pathname (const char *path_pattern, const char *file_pattern, OlMetadata *metadata, char *pathname, size_t len) { if (path_pattern == NULL || file_pattern == NULL || metadata == NULL || pathname == NULL || len <= 0) return -1; int current = ol_path_expand_path_pattern (path_pattern, metadata, pathname, len); if (current == -1) return -1; if (pathname[current - 1] != '/') { pathname[current] = '/'; current++; pathname[current] = '\0'; } int offset = ol_path_expand_file_pattern (file_pattern, metadata, pathname + current, len - current); if (offset == -1) { ol_debugf (" Expand pattern '%s' failed\n", file_pattern); return -1; } current += offset; if (ol_stricmp (&pathname[current - 4], ".lrc", 4) != 0) { char *end = ol_strnncpy (pathname + current, len - current, ".lrc", 4); if (end == NULL) return -1; current = end - pathname; } return current; } static char* replace_invalid_str (const char *str) { if (str == NULL) return NULL; char *ret = g_strdup (str); char *rep = ret; while ((rep = strchr (rep, '/')) != NULL) { *rep = '_'; } return ret; } int ol_path_expand_file_pattern (const char *pattern, OlMetadata *metadata, char *filename, size_t len) { char buffer[BUFFER_SIZE]; if (pattern == NULL) return -1; if (metadata == NULL) return -1; if (filename == NULL || len <= 0) return -1; const char *ptr1 = pattern; const char *pat_end = pattern + strlen (pattern); char *current = filename; char *end = filename + len; while (ptr1 < pat_end) { const char *ptr2 = strchr (ptr1, '%'); /* find place holder */ if (ptr2 == NULL) /* no more place holders */ ptr2 = pat_end; current = ol_strnncpy (current, end - current, ptr1, ptr2 - ptr1); if (current == NULL) /* The buffer is not big enough */ return -1; if (ptr2 < pat_end) { const char *append = NULL; gboolean free_append = FALSE; size_t delta = 2; switch (*(ptr2 + 1)) { case 't': /* title */ append = ol_metadata_get_title (metadata); break; case 'p': /* artist */ append = ol_metadata_get_artist (metadata); break; case 'a': /* album */ append = ol_metadata_get_album (metadata); break; case 'n': /* track number */ snprintf (buffer, BUFFER_SIZE, "%d", ol_metadata_get_track_number (metadata)); append = buffer; break; case 'f': /* file name */ if (ol_metadata_get_uri (metadata) == NULL) append = NULL; else { if (ol_uri_get_filename (buffer, BUFFER_SIZE, ol_metadata_get_uri (metadata), FALSE)) { append = replace_invalid_str (buffer); free_append = TRUE; } } break; case '%': append = "%"; break; default: append = "%"; delta = 1; break; } /* switch */ if (append == NULL) { ol_debugf (" append is NULL, pattern: %c\n", *(ptr2 + 1)); return -1; } current = ol_strnncpy (current, end - current, append, strlen (append)); if (free_append) g_free ((char*)append); if (current == NULL) return -1; ptr2 += delta; } /* if (ptr2 < pat_end) */ ptr1 = ptr2; } /* while */ return current - filename; } static const char* ol_uri_get_query_start (const char* uri) { const char *end = g_utf8_strrchr (uri, strlen (uri), '?'); if (end == NULL) end = uri + strlen (uri); const char *begin = end; while (begin >= uri && *begin != '/') begin--; if (*begin == '/') begin++; if (begin >= end) return NULL; return begin; } static char* ol_uri_get_filename (char *dest, size_t dest_len, const char *uri, gboolean with_ext) { if (dest == NULL || dest_len <= 0 || uri == NULL) return NULL; const char *end = g_utf8_strrchr (uri, strlen (uri), '?'); if (end == NULL) end = uri + strlen (uri); const char *begin = ol_uri_get_query_start (uri); gchar *file_name = g_uri_unescape_segment (begin, end, NULL); if (file_name == NULL) return NULL; if (!with_ext) { gchar *ext = g_utf8_strrchr (file_name, strlen (uri), '.'); if (ext != NULL) *ext = '\0'; } char *ret = ol_strnncpy (dest, dest_len, file_name, strlen (file_name)); g_free (file_name); return ret; } static char* ol_uri_get_path (char *dest, size_t dest_len, const char *uri) { ol_assert_ret (uri != NULL, NULL); char *dirname = NULL; if (uri[0] == '/') { dirname = g_path_get_dirname (uri); } else { GError *error = NULL; char *pathname = g_filename_from_uri (uri, NULL, &error); if (pathname == NULL) { ol_errorf ("Cannot get pathname from uri %s: %s\n", uri, error->message); g_error_free (error); return NULL; } dirname = g_path_get_dirname (pathname); g_free (pathname); } char *ret = ol_strnncpy (dest, dest_len, dirname, strlen (dirname)); g_free (dirname); return ret; } int ol_path_expand_path_pattern (const char *pattern, OlMetadata *metadata, char *filename, size_t len) { if (pattern == NULL || filename == NULL || len <= 0) return -1; if (strcmp (pattern, "%") == 0) /* use music's path */ { if (metadata == NULL || ol_metadata_get_uri (metadata) == NULL) return -1; char *end = ol_uri_get_path (filename, len, ol_metadata_get_uri (metadata)); if (end == NULL) return -1; return end - filename; } if (pattern[0] == '~' && pattern[1] == '/') /* relative to home */ { const char *home_dir; home_dir = g_get_home_dir (); char *end = ol_strnncpy (filename, len, home_dir, strlen (home_dir)); if (end == NULL) return -1; end = ol_strnncpy (end, filename + len - end, pattern + 1, strlen (pattern + 1)); if (end == NULL) return -1; return end - filename; } /* Others, copy directly */ char *end = ol_strnncpy (filename, len, pattern, strlen (pattern)); if (end == NULL) return -1; else return end - filename; } gboolean ol_path_pattern_for_each (char **path_patterns, char **name_patterns, OlMetadata *info, OlPathFunc func, gpointer data) { ol_log_func (); ol_assert_ret (path_patterns != NULL, FALSE); ol_assert_ret (name_patterns != NULL, FALSE); ol_assert_ret (info != NULL, FALSE); ol_assert_ret (func != NULL, FALSE); char file_name[MAX_PATH_LEN] = ""; int i, j; for (i = 0; path_patterns[i]; i++) for (j = 0; name_patterns[j]; j++) { ol_debugf (" path:%s, name:%s\n", path_patterns[i], name_patterns[j]); if ((ol_path_get_lrc_pathname (path_patterns[i], name_patterns[j], info, file_name, MAX_PATH_LEN)) != -1) { ol_debugf (" %s\n", file_name); if (func (file_name, data)) { return TRUE; } } /* if */ } /* for j */ return FALSE; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_path_pattern.h000066400000000000000000000101021345713535600214710ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_PATH_PATTERN_H_ #define _OL_PATH_PATTERN_H_ #include #include #include "ol_metadata.h" /** * @brief Get full pathname of lyrics file according to the path pattern and file pattern * * @param path_pattern The pattern of the path * @param file_pattern The pattern of the file * @param metadata The music track * @param pathname Buffer of the pathname * @param len Length of the buffer * * @return The length of the pathname, of -1 if failed */ int ol_path_get_lrc_pathname (const char *path_pattern, const char *file_pattern, OlMetadata *metadata, char *pathname, size_t len); /** * @brief Expands the pattern to a file name according to the infomation of a music * The following are supported place holder in the pattern: * - %t: Title of the music * - %p: Performer (artist) of the music * - %a: Album of the music * - %n: Track number of the music * - %f: Filename without extension of the music * - %%: The `%' punctuation * @param pattern The pattern to be expanded * @param metadata The info of the music * @param filename The buffer of the expanded file name * @param len The size of the buffer * * @return The length of the expanded file name, or -1 if failed */ int ol_path_expand_file_pattern (const char *pattern, OlMetadata *metadata, char *filename, size_t len); /** * @brief Expands the pattern to a directory path according to the infomation of a music * The pattern can be one of the three forms: * - begin with `/': the path is an absolute path and will not be expanded * - begin with `~/': the path is an relative path and the `~' wiil be expanded to the absolute path of the user's home directory * - `%': the path will be expanded to the directory of the music file according to its URI * @param pattern The pattern to be expanded * @param metadata The info of the music, or NULL if the pattern is not `%' * @param filename The buffer of the expanded file name * @param len The size of the buffer * * @return The length of the expanded file name, or -1 if failed */ int ol_path_expand_path_pattern (const char *pattern, OlMetadata *metadata, char *filename, size_t len); typedef gboolean (*OlPathFunc) (const char *filename, gpointer userdata); /** * @brief Invoke the given function on each lrc filename which fits the patterns and music info * * @param path_patterns Array of path patterns, should be end with NULL * @param name_patterns Array of filename patterns, should be end with NULL * @param metadata The music * @param func The function to invoke. If it returns FALSE, the iteration stops * @param data * * @return TRUE if the func returns TRUE. */ gboolean ol_path_pattern_for_each (char **path_patterns, char **name_patterns, OlMetadata *metadata, OlPathFunc func, gpointer data); #endif /* _OL_PATH_PATTERN_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_player.c000066400000000000000000000747071345713535600203140ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include "ol_player.h" #include "ol_consts.h" #include "ol_timeline.h" #include "ol_debug.h" #define assert_variant_type(value, type) \ do { \ if (!g_variant_is_of_type ((value), G_VARIANT_TYPE (type))) \ { \ ol_errorf ("Except variant type of %s, but %s got\n", \ type, g_variant_get_type_string ((value))); \ } \ } while (0); #define OL_PLAYER_GET_PRIVATE(object) \ (G_TYPE_INSTANCE_GET_PRIVATE ((object), OL_TYPE_PLAYER, OlPlayerPrivate)) const int POSITION_ACCURACY_MS = 1000; enum { PLAYER_LOST, PLAYER_CONNECTED, TRACK_CHANGED, STATUS_CHANGED, CAPS_CHANGED, LAST_SIGNAL, }; enum { CAPS_INDEX_PLAY = 0, CAPS_INDEX_PREV, CAPS_INDEX_NEXT, CAPS_INDEX_SEEK, CAPS_INDEX_STOP, CAPS_INDEX_PAUSE, LAST_CAPS_INDEX, }; typedef struct _OlPlayerPrivate OlPlayerPrivate; struct _OlPlayerPrivate { GDBusProxy *proxy; GDBusProxy *mpris2_proxy; gboolean connected; OlMetadata *metadata; enum OlPlayerStatus status; enum OlPlayerCaps caps; gchar *player_name; gchar *player_icon; GCancellable *cancel_player_info; OlTimeline *timeline; gboolean initialized; }; static guint signals[LAST_SIGNAL] = { 0 }; static struct { const gchar *name; gint value; } CAPS_NAME_MAP[] = { { "CanGoNext", CAPS_INDEX_NEXT }, { "CanGoPrevious", CAPS_INDEX_PREV }, { "CanPlay", CAPS_INDEX_PLAY }, { "CanPause", CAPS_INDEX_PAUSE }, { "CanSeek", CAPS_INDEX_SEEK }, }; static const gchar * const CAPS_INDEX_MAP[] = { "CanPlay", "CanGoPrevious", "CanGoNext", "CanSeek", "", "CanPause", }; static void ol_player_class_init (OlPlayerClass *klass); static void ol_player_init (OlPlayer *player); static void ol_player_init_proxy (OlPlayer *player); static void ol_player_init_mpris2_proxy (OlPlayer *player); static void ol_player_finalize (GObject *object); /* static void ol_player_set_property (GObject *object, */ /* guint property_id, */ /* const GValue *value, */ /* GParamSpec *pspec); */ /* static void ol_player_get_property (GObject *object, */ /* guint property_id, */ /* GValue *value, */ /* GParamSpec *pspec); */ static void ol_player_proxy_signal (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, gpointer user_data); static void ol_player_mpris2_proxy_signal (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, gpointer user_data); static void ol_player_mpris2_proxy_properties_changed (GDBusProxy *proxy, GVariant *changed_properties, GStrv invalidated_properties, gpointer user_data); static void ol_player_fetch_player_info_async (OlPlayer *player); static void ol_player_fetch_player_info_sync (OlPlayer *player); static void ol_player_fetch_player_info_cb (GObject *source_object, GAsyncResult *res, gpointer user_data); static void ol_player_set_player_info (OlPlayer *player, GVariant *value); static void ol_player_update_position (OlPlayer *player, GVariant *value); static void ol_player_update_metadata (OlPlayer *player, GVariant *value); static void ol_player_update_status (OlPlayer *player, GVariant *value); static void ol_player_update_caps (OlPlayer *player, gint changed_mask, GVariant **values); static void ol_player_proxy_name_owner_changed (GObject *gobject, GParamSpec *pspec, gpointer user_data); static void _cancel_call (GCancellable **cancellable); static GVariant *ol_player_get_property (GDBusProxy *proxy, const char *name); G_DEFINE_TYPE (OlPlayer, ol_player, G_TYPE_OBJECT); static void ol_player_class_init (OlPlayerClass *klass) { GObjectClass *gklass = G_OBJECT_CLASS (klass); /* gklass->set_property = ol_player_set_property; */ /* gklass->get_property = ol_player_get_property; */ gklass->finalize = ol_player_finalize; signals[PLAYER_LOST] = g_signal_new ("player-lost", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, /* class_offset */ NULL, NULL, /* accumulator, accu_data */ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[PLAYER_CONNECTED] = g_signal_new ("player-connected", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, /* class_offset */ NULL, NULL, /* accumulator, accu_data */ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[TRACK_CHANGED] = g_signal_new ("track-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, /* class_offset */ NULL, NULL, /* accumulator, accu_data */ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[STATUS_CHANGED] = g_signal_new ("status-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, /* class_offset */ NULL, NULL, /* accumulator, accu_data */ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[CAPS_CHANGED] = g_signal_new ("caps-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, /* class_offset */ NULL, NULL, /* accumulator, accu_data */ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); g_type_class_add_private (klass, sizeof (OlPlayerPrivate)); } OlPlayer * ol_player_new (void) { return OL_PLAYER (g_object_new (OL_TYPE_PLAYER, NULL)); } static void ol_player_init (OlPlayer *player) { OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); private->metadata = ol_metadata_new (); private->status = OL_PLAYER_UNKNOWN; private->caps = 0; private->initialized = FALSE; /* TODO: Initialize player info */ private->timeline = ol_timeline_new (); ol_player_init_proxy (player); ol_player_init_mpris2_proxy (player); ol_player_fetch_player_info_async (player); } static void ol_player_init_proxy (OlPlayer *player) { OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); GError *error = NULL; private->proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, NULL, /* GDBusInterfaceInfo */ OL_SERVICE_DAEMON, OL_OBJECT_PLAYER, OL_IFACE_PLAYER, NULL, /* GCancellable */ &error); if (private->proxy == NULL && error != NULL) { ol_errorf ("Cannot connect to player object: %s", error->message); g_error_free (error); return; } g_signal_connect (private->proxy, "g-signal", G_CALLBACK (ol_player_proxy_signal), player); } static void ol_player_init_mpris2_proxy (OlPlayer *player) { OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); GError *error = NULL; private->mpris2_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, NULL, /* GDBusInterfaceInfo */ OL_SERVICE_DAEMON, OL_OBJECT_MPRIS2, OL_IFACE_MPRIS2_PLAYER, NULL, /* GCancellable */ &error); if (private->mpris2_proxy == NULL && error != NULL) { ol_errorf ("Cannot connect to MPRIS2 player object: %s", error->message); g_error_free (error); return; } g_signal_connect (private->mpris2_proxy, "g-signal", G_CALLBACK (ol_player_mpris2_proxy_signal), player); g_signal_connect (private->mpris2_proxy, "notify::g-name-owner", G_CALLBACK (ol_player_proxy_name_owner_changed), player); g_signal_connect (private->mpris2_proxy, "g-properties-changed", G_CALLBACK (ol_player_mpris2_proxy_properties_changed), player); } static void _cancel_call (GCancellable **cancellable) { if (cancellable && *cancellable) { g_cancellable_cancel (*cancellable); g_object_unref (*cancellable); *cancellable = NULL; } } static void ol_player_finalize (GObject *object) { OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (object); if (private->proxy != NULL) { g_object_unref (private->proxy); private->proxy = NULL; } if (private->mpris2_proxy != NULL) { g_signal_handlers_disconnect_by_func (private->mpris2_proxy, "notify::g-name-owner", G_CALLBACK (ol_player_proxy_name_owner_changed)); g_signal_handlers_disconnect_by_func (private->mpris2_proxy, "g-signal", G_CALLBACK (ol_player_mpris2_proxy_signal)); g_object_unref (private->mpris2_proxy); private->mpris2_proxy = NULL; } if (private->metadata != NULL) { ol_metadata_free (private->metadata); private->metadata = NULL; } if (private->player_name != NULL) { g_free (private->player_name); private->player_name = NULL; } if (private->player_icon != NULL) { g_free (private->player_icon); private->player_icon = NULL; } _cancel_call (&private->cancel_player_info); ol_timeline_free (private->timeline); private->timeline = NULL; G_OBJECT_CLASS (ol_player_parent_class)->finalize (object); } static void ol_player_proxy_signal (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, gpointer user_data) { ol_assert (OL_IS_PLAYER (user_data)); OlPlayer *player = OL_PLAYER (user_data); if (g_strcmp0 (signal_name, "PlayerLost") == 0) { ol_player_set_player_info (player, NULL); g_signal_emit (player, signals[PLAYER_LOST], 0); } else if (g_strcmp0 (signal_name, "PlayerConnected") == 0) { assert_variant_type (parameters, "(a{sv})"); GVariant *value; g_variant_get (parameters, "(@a{sv})", &value); ol_player_set_player_info (player, value); g_variant_unref (value); g_signal_emit (player, signals[PLAYER_CONNECTED], 0); } } static void ol_player_mpris2_proxy_signal (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, gpointer user_data) { ol_assert (OL_IS_PLAYER (user_data)); OlPlayer *player = OL_PLAYER (user_data); if (g_str_equal (signal_name, "Seeked")) { GVariant *value; g_variant_get (parameters, "(@x)", &value); ol_player_update_position (player, value); g_variant_unref (value); } } static void ol_player_mpris2_proxy_properties_changed (GDBusProxy *proxy, GVariant *changed_properties, GStrv invalidated_properties, gpointer user_data) { OlPlayer *player = OL_PLAYER (user_data); GVariantIter *iter = NULL; gchar *key; GVariant *value; gint caps_masks = 0; GVariant *caps_values[LAST_CAPS_INDEX] = {0}; g_variant_get (changed_properties, "a{sv}", &iter); while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) { if (g_str_equal (key, "Metadata")) { ol_player_update_metadata (player, value); } else if (g_str_equal (key, "PlaybackStatus")) { ol_player_update_status (player, value); } else { gint i; for (i = 0; i < G_N_ELEMENTS (CAPS_NAME_MAP); i++) { if (g_str_equal (key, CAPS_NAME_MAP[i].name)) { caps_masks |= (1 << CAPS_NAME_MAP[i].value); caps_values[CAPS_NAME_MAP[i].value] = g_variant_ref (value); } } } } if (caps_masks) { guint i; ol_player_update_caps (player, caps_masks, caps_values); for (i = 0; i < LAST_CAPS_INDEX; i++) if (caps_values[i]) g_variant_unref (caps_values[i]); } g_variant_iter_free (iter); } static void ol_player_proxy_name_owner_changed (GObject *gobject, GParamSpec *pspec, gpointer user_data) { ol_assert (OL_IS_PLAYER (user_data)); OlPlayer *player = OL_PLAYER (user_data); gchar *owner = NULL; if ((owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (gobject))) == NULL) { /* Daemon disconnected, treat the player as lost */ ol_debug ("Daemon lost"); ol_player_set_player_info (player, NULL); } else { /* Daemon connected, get player info */ ol_debugf ("Daemon connected: %s\n", owner); ol_player_fetch_player_info_async (player); } } static void ol_player_fetch_player_info_sync (OlPlayer *player) { OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); GError *error = NULL; GVariant *value = NULL; value = g_dbus_proxy_call_sync (private->proxy, "GetCurrentPlayer", NULL, /* parameters */ G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout_msec */ NULL, /* cancellable */ &error); if (value == NULL) { ol_debugf ("Cannot get player info: %s\n", error->message); g_error_free (error); } else { assert_variant_type (value, "(ba{sv})"); gboolean connected; GVariant *info = NULL; g_variant_get (value, "(b@a{sv})", &connected, &info); if (connected) ol_player_set_player_info (player, info); else ol_player_set_player_info (player, NULL); g_variant_unref (info); g_variant_unref (value); } } static void ol_player_fetch_player_info_async (OlPlayer *player) { OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); if (private->cancel_player_info) return; private->cancel_player_info = g_cancellable_new (); g_dbus_proxy_call (private->proxy, "GetCurrentPlayer", NULL, /* parameters */ G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout_msec */ private->cancel_player_info, ol_player_fetch_player_info_cb, player); } static void ol_player_fetch_player_info_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { ol_assert (OL_IS_PLAYER (user_data)); OlPlayer *player = OL_PLAYER (user_data); OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); GError *error = NULL; GVariant *value = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error); if (value) { assert_variant_type (value, "(ba{sv})"); gboolean connected; GVariant *info = NULL; g_variant_get (value, "(b@a{sv})", &connected, &info); if (connected) ol_player_set_player_info (player, info); else ol_player_set_player_info (player, NULL); g_variant_unref (info); g_variant_unref (value); } else { ol_errorf ("Cannot get player info: %s\n", error->message); ol_player_set_player_info (player, NULL); g_error_free (error); } if (private->cancel_player_info) { g_object_unref (private->cancel_player_info); } } static void ol_player_set_player_info (OlPlayer *player, GVariant *value) { ol_assert (OL_IS_PLAYER (player)); OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); private->initialized = TRUE; g_free (private->player_name); private->player_name = NULL; g_free (private->player_icon); private->player_icon = NULL; if (value == NULL) { /* Player lost, clear the properties */ if (private->connected) { ol_player_update_metadata (player, NULL); ol_player_update_position (player, NULL); ol_player_update_status (player, NULL); ol_player_update_caps (player, OL_PLAYER_PLAY | OL_PLAYER_NEXT | OL_PLAYER_PREV | OL_PLAYER_PAUSE | OL_PLAYER_SEEK, NULL); private->connected = FALSE; } } else { assert_variant_type (value, "a{sv}"); GVariantIter *iter = NULL; g_variant_get (value, "a{sv}", &iter); gchar *key; GVariant *dict_value; while (g_variant_iter_loop (iter, "{sv}", &key, &dict_value)) { if (g_strcmp0 (key, "name") == 0) { const gchar *name = g_variant_get_string (dict_value, NULL); if (name) { private->player_name = g_strdup (name); } else { ol_errorf ("Cannot get the name of the player. Value type: %s", g_variant_get_type_string (dict_value)); } } else if (g_strcmp0 (key, "appname") == 0) { /* TODO: get appname */ } else if (g_strcmp0 (key, "binname") == 0) { /* TODO: get binname */ } else if (g_strcmp0 (key, "cmd") == 0) { /* TODO: get cmd */ } else if (g_strcmp0 (key, "icon") == 0) { const gchar *icon = g_variant_get_string (dict_value, NULL); if (icon) { private->player_icon = g_strdup (icon); } else { ol_errorf ("Cannot get the icon of the player. Value type: %s", g_variant_get_type_string (dict_value)); } } } ol_debugf ("player name: %s, icon: %s\n", private->player_name, private->player_icon); g_variant_iter_free (iter); ol_player_update_metadata (player, NULL); ol_player_update_status (player, NULL); ol_player_update_caps (player, OL_PLAYER_NEXT | OL_PLAYER_PREV | OL_PLAYER_PLAY | OL_PLAYER_PAUSE | OL_PLAYER_SEEK, NULL); ol_player_update_position (player, NULL); private->connected = TRUE; } } static GVariant * ol_player_get_property (GDBusProxy *proxy, const char *name) { GVariant *ret; ret = g_dbus_proxy_get_cached_property (proxy, name); if (!ret) { GError *error = NULL; GVariant *result; result = g_dbus_connection_call_sync (g_dbus_proxy_get_connection (proxy), g_dbus_proxy_get_name (proxy), g_dbus_proxy_get_object_path (proxy), "org.freedesktop.DBus.Properties", "Get", g_variant_new ("(ss)", OL_IFACE_MPRIS2_PLAYER, name), G_VARIANT_TYPE ("(v)"), G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout */ NULL, &error); if (!result) { ol_errorf ("Cannot get property %s: %s\n", name, error->message); g_error_free (error); } else { g_variant_get (result, "(v)", &ret); g_variant_unref (result); } } return ret; } static void ol_player_update_position (OlPlayer *player, GVariant *value) { OlPlayerPrivate *priv = OL_PLAYER_GET_PRIVATE (player); if (!value) value = ol_player_get_property (priv->mpris2_proxy, "Position"); else g_variant_ref (value); if (!value) return; ol_timeline_maybe_set_time (priv->timeline, g_variant_get_int64 (value) / 1000); g_variant_unref (value); } static void ol_player_update_metadata (OlPlayer *player, GVariant *value) { OlPlayerPrivate *priv = OL_PLAYER_GET_PRIVATE (player); if (!value) value = ol_player_get_property (priv->mpris2_proxy, "Metadata"); else g_variant_ref (value); if (!value) return; if (priv->metadata) ol_metadata_free (priv->metadata); priv->metadata = ol_metadata_new_from_variant (value); g_signal_emit (player, signals[TRACK_CHANGED], 0); g_variant_unref (value); ol_timeline_set_time (priv->timeline, 0); ol_player_update_position (player, NULL); } static void ol_player_update_status (OlPlayer *player, GVariant *value) { OlPlayerPrivate *priv = OL_PLAYER_GET_PRIVATE (player); if (!value) value = ol_player_get_property (priv->mpris2_proxy, "PlaybackStatus"); else g_variant_ref (value); if (!value) return; const gchar *status_str = g_variant_get_string (value, NULL); if (g_str_equal (status_str, "Playing")) { priv->status = OL_PLAYER_PLAYING; ol_timeline_play (priv->timeline); } else if (g_str_equal (status_str, "Paused")) { priv->status = OL_PLAYER_PAUSED; ol_timeline_pause (priv->timeline); } else if (g_str_equal (status_str, "Stopped")) { priv->status = OL_PLAYER_STOPPED; ol_timeline_stop (priv->timeline); } else { ol_errorf ("Unknown playback status: %s\n", status_str); priv->status = OL_PLAYER_UNKNOWN; } g_signal_emit (player, signals[STATUS_CHANGED], 0); } static void ol_player_update_caps (OlPlayer *player, gint changed_mask, GVariant **values) { OlPlayerPrivate *priv = OL_PLAYER_GET_PRIVATE (player); guint i; GVariant *value; for (i = 0; i < LAST_CAPS_INDEX; i++) { guint cap_bit = 1 << i; if (changed_mask & cap_bit) { value = values ? values[i] : NULL; if (value) g_variant_ref (value); else value = ol_player_get_property (priv->mpris2_proxy, CAPS_INDEX_MAP[i]); if (!value) continue; if (g_variant_get_boolean (value)) priv->caps |= cap_bit; else priv->caps &= ~cap_bit; g_variant_unref (value); } } if (priv->caps & OL_PLAYER_PLAY) priv->caps |= OL_PLAYER_STOP; g_signal_emit (player, signals[CAPS_CHANGED], 0); } gboolean ol_player_is_connected (OlPlayer *player) { ol_assert_ret (OL_IS_PLAYER (player), FALSE); OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); if (!private->initialized) { ol_player_fetch_player_info_sync (player); } return private->connected; } const char* ol_player_get_name (OlPlayer *player) { ol_assert_ret (OL_IS_PLAYER (player), NULL); OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); if (!private->initialized) { ol_player_fetch_player_info_sync (player); } return private->player_name; } const char* ol_player_get_icon_path (OlPlayer *player) { ol_assert_ret (OL_IS_PLAYER (player), NULL); OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); if (!private->initialized) { ol_player_fetch_player_info_sync (player); } return private->player_icon; } gboolean ol_player_get_metadata (OlPlayer *player, OlMetadata *metadata) { ol_assert_ret (OL_IS_PLAYER (player), FALSE); OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); if (!private->connected) return FALSE; if (metadata != NULL) ol_metadata_copy (metadata, private->metadata); return TRUE; } gboolean ol_player_get_position (OlPlayer *player, guint64 *pos_ms) { ol_assert_ret (OL_IS_PLAYER (player), FALSE); OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); if (!private->connected) return FALSE; if (pos_ms) { *pos_ms = ol_timeline_get_time (private->timeline); } return TRUE; } enum OlPlayerStatus ol_player_get_status (OlPlayer *player) { ol_assert_ret (OL_IS_PLAYER (player), OL_PLAYER_ERROR); OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); return private->status; } int ol_player_get_caps (OlPlayer *player) { ol_assert_ret (OL_IS_PLAYER (player), -1); OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); return private->caps; } gboolean ol_player_play (OlPlayer *player) { ol_assert_ret (OL_IS_PLAYER (player), FALSE); OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); if (!private->connected || !(private->caps & OL_PLAYER_PLAY)) return FALSE; g_dbus_proxy_call (private->mpris2_proxy, "Play", NULL, /* parameters */ G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout_msec */ NULL, /* cancellable */ NULL, /* callback */ NULL); /* user_data */ return TRUE; } gboolean ol_player_pause (OlPlayer *player) { ol_assert_ret (OL_IS_PLAYER (player), FALSE); OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); if (!private->connected || !(private->caps & OL_PLAYER_PAUSE)) return FALSE; g_dbus_proxy_call (private->mpris2_proxy, "Pause", NULL, /* parameters */ G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout_msec */ NULL, /* cancellable */ NULL, /* callback */ NULL); /* user_data */ return TRUE; } gboolean ol_player_stop (OlPlayer *player) { ol_assert_ret (OL_IS_PLAYER (player), FALSE); OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); if (!private->connected || !(private->caps & OL_PLAYER_STOP)) return FALSE; g_dbus_proxy_call (private->mpris2_proxy, "Stop", NULL, /* parameters */ G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout_msec */ NULL, /* cancellable */ NULL, /* callback */ NULL); /* user_data */ return TRUE; } gboolean ol_player_prev (OlPlayer *player) { ol_assert_ret (OL_IS_PLAYER (player), FALSE); OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); if (!private->connected || !(private->caps & OL_PLAYER_PREV)) return FALSE; g_dbus_proxy_call (private->mpris2_proxy, "Previous", NULL, /* parameters */ G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout_msec */ NULL, /* cancellable */ NULL, /* callback */ NULL); /* user_data */ return TRUE; } gboolean ol_player_next (OlPlayer *player) { ol_assert_ret (OL_IS_PLAYER (player), FALSE); OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); if (!private->connected || !(private->caps & OL_PLAYER_NEXT)) return FALSE; g_dbus_proxy_call (private->mpris2_proxy, "Next", NULL, /* parameters */ G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout_msec */ NULL, /* cancellable */ NULL, /* callback */ NULL); /* user_data */ return TRUE; } gboolean ol_player_seek (OlPlayer *player, guint64 pos_ms) { ol_assert_ret (OL_IS_PLAYER (player), FALSE); OlPlayerPrivate *private = OL_PLAYER_GET_PRIVATE (player); if (!private->connected || !(private->caps & OL_PLAYER_SEEK)) return FALSE; g_dbus_proxy_call (private->mpris2_proxy, "SetPosition", g_variant_new ("(x)", (gint64)pos_ms * 1000), G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout_msec */ NULL, /* cancellable */ NULL, /* callback */ NULL); /* user_data */ return TRUE; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_player.h000066400000000000000000000140271345713535600203060ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef __OL_PLAYER_H__ #define __OL_PLAYER_H__ #include #include #include "ol_metadata.h" enum OlPlayerStatus { OL_PLAYER_PLAYING = 0, OL_PLAYER_PAUSED, OL_PLAYER_STOPPED, OL_PLAYER_UNKNOWN, OL_PLAYER_ERROR, }; enum OlPlayerCaps { OL_PLAYER_PLAY = 1 << 0, OL_PLAYER_PREV = 1 << 1, OL_PLAYER_NEXT = 1 << 2, OL_PLAYER_SEEK = 1 << 3, OL_PLAYER_STOP = 1 << 4, OL_PLAYER_PAUSE = 1 << 5, OL_PLAYER_PLAY_PAUSE = OL_PLAYER_PLAY | OL_PLAYER_PAUSE, }; #define OL_TYPE_PLAYER \ (ol_player_get_type ()) #define OL_PLAYER(obj) \ (G_TYPE_CHECK_INSTANCE_CAST (obj, OL_TYPE_PLAYER, OlPlayer)) #define OL_PLAYER_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST (klass, OL_TYPE_PLAYER, OlPlayerClass)) #define OL_IS_PLAYER(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE (obj, OL_TYPE_PLAYER)) #define OL_IS_PLAYER_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE (klass, OL_TYPE_PLAYER)) #define OL_PLAYER_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), OL_TYPE_PLAYER, OlPlayerClass)) typedef struct _OlPlayer OlPlayer; typedef struct _OlPlayerClass OlPlayerClass; struct _OlPlayer { GObject parent; }; struct _OlPlayerClass { GObjectClass parent_class; }; GType ol_player_get_type (void); /** * Creates a new instance of OlPlayer * * * @return */ OlPlayer *ol_player_new (void); /** * @brief Checks whether OSD Lyrics has connected to a supported player. * * @return TRUE if a supported player is running */ gboolean ol_player_is_connected (OlPlayer *player); /** * @brief Gets the name of player * * @return The returned string is owned by player and should not be freed. * If no supported player is running, NULL will be returned. */ const char *ol_player_get_name (OlPlayer *player); /** * @brief Gets the full path of the icon of the current player. * * @param player * * @return The path of the icon file, or NULL. The string is owned by the player * and should not be freed. */ const char *ol_player_get_icon_path (OlPlayer *player); /** * @brief Gets the metadata of the current metadata * * @param metadata Return location of music info * * @return If no support player is connected, return FALSE. In this case, * parameter metadata will not be changed. */ gboolean ol_player_get_metadata (OlPlayer *player, OlMetadata *metadata); /** * @brief Gets the elapsed time of the current music * * @param position Return location of the elasped time, in millisecond * * @return TRUE if succeeded */ gboolean ol_player_get_position (OlPlayer *player, guint64 *pos_ms); /** * @brief Gets the status of the player. * The status of a player can be playing, paused or stopped. * * @return The status of the player, or OL_PLAYER_ERROR if failed */ enum OlPlayerStatus ol_player_get_status (OlPlayer *player); /** * @brief Gets which operations are supported by the player controller * * @return A combination of OlPlayerCapacity, or -1 if failed. */ int ol_player_get_caps (OlPlayer *player); /** * @brief Starts playing music. If the player supports this operation, * OL_PLAYER_PLAY will be set in its capacity * * @return FALSE if the operation failed or the player controller dosen't support * this operation. */ gboolean ol_player_play (OlPlayer *player); /** * @brief Pauses the current music. The elasped time will not change. If the player * supports this operation, OL_PLAYER_STOP will be set in its capacity * * @param player * * @return FALSE if the operation failed or the player controller dosen't support * this operation. */ gboolean ol_player_pause (OlPlayer *player); /** * @brief Stop playing music. The elapsed time will be reset to 0. If the player * supports this operation, OL_PLAYER_STOP will be set in its capacity * * @param player * * @return FALSE if the operation failed or the player controller dosen't support * this operation. */ gboolean ol_player_stop (OlPlayer *player); /** * @brief Plays the previous music. If the player supports this operation, * OL_PLAYER_PREV will be set in its capacity * * @param player * * @return FALSE if the operation failed or the player controller dosen't support * this operation. */ gboolean ol_player_prev (OlPlayer *player); /** * @brief Plays the next music. If the player supports this operation, * OL_PLAYER_NEXT will be set in its capacity * * @param player * * @return FALSE if the operation failed or the player controller dosen't support * this operation. */ gboolean ol_player_next (OlPlayer *player); /** * @brief Seek the current music to a given position. If the player supports this * operation, OL_PLAYER_SEEK will be set in its capacity * * Note that the actuall time may not equals to the given posision. You may need to * call ol_player_get_played_time after this function is called. * * @param pos_ms The target position in millisecond. * * @return FALSE if the operation failed or the player controller dosen't support * this operation. */ gboolean ol_player_seek (OlPlayer *player, guint64 pos_ms); #endif // __OL_PLAYER_H__ osdlyrics-0.5.5~rc1+dfsg1/src/ol_player_chooser.c000066400000000000000000000533371345713535600220320ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include "ol_player_chooser.h" #include "ol_app_chooser_widget.h" #include "ol_app_info.h" #include "ol_intl.h" #include "ol_utils.h" #include "ol_config_proxy.h" #include "ol_debug.h" #define OL_PLAYER_CHOOSER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), \ ol_player_chooser_get_type (), \ OlPlayerChooserPrivate)) static const guint SUPPORT_CHOOSER_INDEX = 0; static const guint ALL_CHOOSER_INDEX = 1; static const guint MAX_CHOOSER_HEIGHT = 96 * 3; /* about 3 lines of apps */ static const guint DEFAULT_APP_COLUMNS = 4; G_DEFINE_TYPE(OlPlayerChooser, ol_player_chooser, GTK_TYPE_DIALOG) typedef struct _OlPlayerChooserPage { GtkToggleButton *page_button; GtkScrolledWindow *panel; OlAppChooserWidget *chooser; OlPlayerChooser *window; } OlPlayerChooserPage; typedef struct _OlPlayerChooserPrivate { GPtrArray *pages; GtkBox *page_button_panel; GSList *page_button_group; GtkBox *chooser_panel; GtkBox *custom_cmd_panel; GtkWidget *remember_button; GtkEntry *cmd_entry; GtkWidget *launch_button; GtkLabel *info_label; GtkImage *info_icon; GAppInfo *launch_app; } OlPlayerChooserPrivate; static void _destroy (GtkObject *object); static void _set_supported_players (OlPlayerChooser *chooser, GList *supported_players); static void _app_activate_cb (OlAppChooserWidget *chooser, GAppInfo *info, OlPlayerChooser *window); static void _launch_button_clicked_cb (GtkButton *button, OlPlayerChooser *window); static void _launch_app (OlPlayerChooser *window, GAppInfo *app_info); static void _set_launch_app (OlPlayerChooser *window, GAppInfo *app_info); static void _remember_cmd_if_needed (OlPlayerChooser *window, const char *cmd); static void _new_page (OlPlayerChooser *window, const char *page_name); static void _show_page (OlPlayerChooser *window, OlPlayerChooserPage *page); static void _show_page_by_index (OlPlayerChooser *window, guint index); static void _page_button_toggled (GtkToggleButton *widget, OlPlayerChooserPage *page); static OlAppChooserWidget *_get_chooser (OlPlayerChooser *window, guint index); static void _setup_ui (OlPlayerChooser *window); static void _set_sensitive (OlPlayerChooser *window, gboolean sensitive); static GtkEntryCompletion *_new_bin_completion (void); static gboolean _prepend_cmd_to_list (const char *path, const char *filename, gpointer userdata); static void ol_player_chooser_class_init (OlPlayerChooserClass *klass) { ol_log_func (); GObjectClass *gobject_class; GtkObjectClass *object_class; gobject_class = G_OBJECT_CLASS (klass); object_class = (GtkObjectClass*)klass; ol_player_chooser_parent_class = g_type_class_peek_parent (klass); object_class->destroy = _destroy; g_type_class_add_private (gobject_class, sizeof (OlPlayerChooserPrivate)); } static void ol_player_chooser_init (OlPlayerChooser *window) { OlPlayerChooserPrivate *priv = OL_PLAYER_CHOOSER_GET_PRIVATE (window); priv->pages = g_ptr_array_new_with_free_func (g_free); priv->page_button_group = NULL; _setup_ui (window); } static void _new_page (OlPlayerChooser *window, const char *page_name) { OlPlayerChooserPrivate *priv = OL_PLAYER_CHOOSER_GET_PRIVATE (window); OlPlayerChooserPage *page = g_new (OlPlayerChooserPage, 1); page->page_button = GTK_TOGGLE_BUTTON (gtk_radio_button_new_with_label (priv->page_button_group, page_name)); priv->page_button_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (page->page_button)); gtk_toggle_button_set_mode (page->page_button, FALSE); gtk_button_set_relief (GTK_BUTTON (page->page_button), GTK_RELIEF_NONE); page->chooser = OL_APP_CHOOSER_WIDGET (ol_app_chooser_widget_new ()); gtk_widget_show (GTK_WIDGET (page->chooser)); page->panel = GTK_SCROLLED_WINDOW (gtk_scrolled_window_new (NULL, NULL)); gtk_scrolled_window_add_with_viewport (page->panel, GTK_WIDGET (page->chooser)); GtkViewport *viewport = GTK_VIEWPORT (gtk_bin_get_child (GTK_BIN (page->panel))); gtk_viewport_set_shadow_type (viewport, GTK_SHADOW_NONE); gtk_scrolled_window_set_policy (page->panel, GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); g_signal_connect (page->page_button, "toggled", G_CALLBACK (_page_button_toggled), page); g_signal_connect (page->chooser, "app-activate", G_CALLBACK (_app_activate_cb), window); _page_button_toggled (page->page_button, page); if (priv->pages->len > 0) gtk_box_pack_start (priv->page_button_panel, gtk_vseparator_new (), FALSE, FALSE, 5); gtk_box_pack_start (priv->page_button_panel, GTK_WIDGET (page->page_button), FALSE, /* expand */ FALSE, /* fill */ 0); /* padding */ gtk_box_pack_start (priv->chooser_panel, GTK_WIDGET (page->panel), TRUE, /* expand */ TRUE, /* fill */ 0); /* padding */ g_ptr_array_add (priv->pages, page); } static void _page_button_toggled (GtkToggleButton *widget, OlPlayerChooserPage *page) { gtk_widget_set_visible (GTK_WIDGET (page->panel), gtk_toggle_button_get_active (widget)); } static void _show_page (OlPlayerChooser *window, OlPlayerChooserPage *page) { ol_assert (page != NULL); gtk_toggle_button_set_active (page->page_button, TRUE); } static void _show_page_by_index (OlPlayerChooser *window, guint index) { OlPlayerChooserPrivate *priv = OL_PLAYER_CHOOSER_GET_PRIVATE (window); _show_page (window, g_ptr_array_index (priv->pages, index)); } static OlAppChooserWidget * _get_chooser (OlPlayerChooser *window, guint index) { OlPlayerChooserPrivate *priv = OL_PLAYER_CHOOSER_GET_PRIVATE (window); OlPlayerChooserPage *page = g_ptr_array_index (priv->pages, index); ol_assert_ret (page != NULL, NULL); return page->chooser; } static void _set_apps_to_page (OlPlayerChooser *window, guint index, GList *app_list) { OlPlayerChooserPrivate *priv = OL_PLAYER_CHOOSER_GET_PRIVATE (window); OlAppChooserWidget *chooser = _get_chooser (window, index); ol_app_chooser_widget_set_app_list (chooser, app_list, DEFAULT_APP_COLUMNS); GtkRequisition chooser_req; gint box_height; gtk_widget_size_request (GTK_WIDGET (chooser), &chooser_req); gtk_widget_get_size_request (GTK_WIDGET (priv->chooser_panel), NULL, &box_height); gint height = MIN (MAX_CHOOSER_HEIGHT, MAX (chooser_req.height, box_height)); if (height != box_height) gtk_widget_set_size_request (GTK_WIDGET (priv->chooser_panel), -1, height); } static void _setup_ui (OlPlayerChooser *window) { OlPlayerChooserPrivate *priv = OL_PLAYER_CHOOSER_GET_PRIVATE (window); /* Setup info widgets */ priv->info_label = GTK_LABEL (gtk_label_new (NULL)); gtk_label_set_line_wrap (priv->info_label, TRUE); gtk_misc_set_alignment (GTK_MISC (priv->info_label), 0.0, 0.0); priv->info_icon = GTK_IMAGE (gtk_image_new_from_stock (GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG)); GtkWidget *info_box = gtk_hbox_new (FALSE, 10); gtk_box_pack_start (GTK_BOX (info_box), GTK_WIDGET (priv->info_icon), FALSE, /* expand */ FALSE, /* fill */ 0); gtk_box_pack_start (GTK_BOX (info_box), GTK_WIDGET (priv->info_label), TRUE, /* expand */ TRUE, /* fill */ 0); /* Setup app choosers */ priv->page_button_panel = GTK_BOX (gtk_hbox_new (FALSE, 0)); priv->chooser_panel = GTK_BOX (gtk_hbox_new (FALSE, 0)); _new_page (window, _("Supported players")); _new_page (window, _("All players")); _set_apps_to_page (window, ALL_CHOOSER_INDEX, g_app_info_get_all_for_type ("audio/mp3")); GtkWidget *apps_frame = gtk_frame_new (_("Choose a player to launch")); GtkBox *page_vbox = GTK_BOX (gtk_vbox_new (FALSE, 0)); gtk_widget_show (GTK_WIDGET (page_vbox)); gtk_box_pack_start (page_vbox, GTK_WIDGET (priv->page_button_panel), FALSE, /* expand */ FALSE, /* fill */ 0); /* padding */ gtk_box_pack_end (page_vbox, GTK_WIDGET (priv->chooser_panel), FALSE, /* expand */ FALSE, /* fill */ 0); /* padding */ gtk_container_add (GTK_CONTAINER (apps_frame), GTK_WIDGET (page_vbox)); /* Setup custom command */ priv->custom_cmd_panel = GTK_BOX (gtk_hbox_new (FALSE, 5)); GtkWidget *cmd_label = gtk_label_new (_("Use command:")); GtkWidget *cmd_entry = gtk_entry_new (); priv->cmd_entry = GTK_ENTRY (cmd_entry); gtk_entry_set_activates_default (priv->cmd_entry, TRUE); gtk_entry_set_completion (priv->cmd_entry, _new_bin_completion ()); GtkWidget *launch_button = gtk_button_new_with_label (_("Launch")); gtk_widget_set_can_default (launch_button, TRUE); gtk_window_set_default (GTK_WINDOW (window), launch_button); priv->launch_button = launch_button; g_signal_connect (launch_button, "clicked", G_CALLBACK (_launch_button_clicked_cb), window); gtk_box_pack_start (priv->custom_cmd_panel, cmd_label, FALSE, TRUE, 0); gtk_box_pack_start (priv->custom_cmd_panel, cmd_entry, TRUE, TRUE, 0); gtk_box_pack_start (priv->custom_cmd_panel, launch_button, FALSE, TRUE, 0); GtkWidget *final_hbox = gtk_hbox_new (FALSE, 0); GtkWidget *remember_button = gtk_check_button_new_with_label (_("Remember my choice")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (remember_button), TRUE); priv->remember_button = remember_button; gtk_box_pack_start (GTK_BOX (final_hbox), remember_button, FALSE, TRUE, 0); /* Setup the whole dialog */ GtkWidget *vbox = gtk_dialog_get_content_area (GTK_DIALOG (window)); gtk_box_set_spacing (GTK_BOX (vbox), 10); gtk_box_pack_start (GTK_BOX (vbox), info_box, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), apps_frame, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (priv->custom_cmd_panel), FALSE, TRUE, 0); gtk_box_pack_end (GTK_BOX (vbox), final_hbox, FALSE, TRUE, 0); gtk_widget_show (vbox); gtk_widget_show_all (info_box); gtk_widget_show (apps_frame); gtk_widget_show_all (GTK_WIDGET (priv->page_button_panel)); gtk_widget_show (GTK_WIDGET (priv->chooser_panel)); gtk_widget_show_all (GTK_WIDGET (priv->custom_cmd_panel)); gtk_widget_show_all (GTK_WIDGET (final_hbox)); gtk_dialog_add_button (GTK_DIALOG (window), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE); gtk_window_set_title (GTK_WINDOW (window), _("Choose a player to launch")); gtk_window_set_resizable (GTK_WINDOW (window), FALSE); gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER); } static gboolean _prepend_cmd_to_list (const char *path, const char *filename, gpointer userdata) { GList **cmds_p = userdata; gchar *filepath = g_build_path (G_DIR_SEPARATOR_S, path, filename, NULL); if (g_file_test (filepath, G_FILE_TEST_IS_EXECUTABLE) && !g_file_test (filepath, G_FILE_TEST_IS_DIR)) { *cmds_p = g_list_prepend (*cmds_p, g_strdup (filename)); } return TRUE; } static GtkEntryCompletion * _new_bin_completion (void) { const gchar *path_env = g_getenv ("PATH"); gchar **pathdirs = g_strsplit (path_env, G_SEARCHPATH_SEPARATOR_S, 0); gchar **pathiter = pathdirs; GList *cmds_p[] = {NULL}; for (; *pathiter != NULL; pathiter++) { gchar *path = *pathiter; ol_traverse_dir (path, FALSE, _prepend_cmd_to_list, cmds_p); } GList *cmds = cmds_p[0]; cmds = g_list_sort (cmds, (GCompareFunc)strcasecmp); GtkListStore *list = gtk_list_store_new (1, G_TYPE_STRING); for (; cmds != NULL; cmds = cmds->next) { GtkTreeIter iter; gtk_list_store_append (list, &iter); gtk_list_store_set (list, &iter, 0, cmds->data, -1); } for (; cmds != NULL; cmds = g_list_delete_link (cmds, cmds)) g_free (cmds->data); GtkEntryCompletion *comp = gtk_entry_completion_new (); gtk_entry_completion_set_model (comp, GTK_TREE_MODEL (list)); gtk_entry_completion_set_text_column (comp, 0); gtk_entry_completion_set_inline_completion (comp, TRUE); gtk_entry_completion_set_inline_selection (comp, TRUE); return comp; } static void _destroy (GtkObject *object) { OlPlayerChooserPrivate *priv = OL_PLAYER_CHOOSER_GET_PRIVATE (object); if (priv->pages != NULL) { g_ptr_array_free (priv->pages, TRUE); priv->pages = NULL; } if (priv->launch_app) { g_object_unref (priv->launch_app); priv->launch_app = NULL; } GTK_OBJECT_CLASS (ol_player_chooser_parent_class)->destroy (object); } GtkWidget* ol_player_chooser_new (GList *supported_players) { ol_log_func (); GtkWidget *window = g_object_new (ol_player_chooser_get_type (), NULL); _set_supported_players (OL_PLAYER_CHOOSER (window), supported_players); return window; } static void _set_supported_players (OlPlayerChooser *window, GList *supported_players) { ol_assert (OL_IS_PLAYER_CHOOSER (window)); _set_apps_to_page (window, SUPPORT_CHOOSER_INDEX, supported_players); if (supported_players != NULL) { _show_page_by_index (window, SUPPORT_CHOOSER_INDEX); } else { _show_page_by_index (window, ALL_CHOOSER_INDEX); } } static void _app_activate_cb (OlAppChooserWidget *chooser, GAppInfo *app_info, OlPlayerChooser *window) { ol_assert (G_IS_APP_INFO (app_info)); _launch_app (window, app_info); } static void _launch_app (OlPlayerChooser *window, GAppInfo *app_info) { GError *err = NULL; if (!g_app_info_launch (app_info, NULL, NULL, &err)) { ol_errorf ("Cannot launch %s: %s", g_app_info_get_commandline (app_info), err->message); gchar *title = g_strdup_printf (_("Failed to launch %s"), g_app_info_get_name (app_info)); ol_player_chooser_set_info (window, title, err->message); ol_player_chooser_set_image_by_name (window, GTK_STOCK_DIALOG_ERROR); g_free (title); g_error_free (err); } else { _set_launch_app (window, app_info); gchar *title = g_strdup_printf (_("Launching %s"), g_app_info_get_name (app_info)); gchar *desc = g_strdup_printf (_("OSD Lyrics is trying to launch and connect to %s. Please wait for a second."), g_app_info_get_name (app_info)); ol_player_chooser_set_info (window, title, desc); g_free (title); g_free (desc); ol_player_chooser_set_image_by_gicon (window, g_app_info_get_icon (app_info)); _set_sensitive (window, FALSE); if (OL_IS_PLAYER_CHOOSER (window)) { _remember_cmd_if_needed (window, g_app_info_get_commandline (app_info)); gtk_dialog_response (GTK_DIALOG (window), OL_PLAYER_CHOOSER_RESPONSE_LAUNCH); } } } static void _set_launch_app (OlPlayerChooser *window, GAppInfo *app_info) { OlPlayerChooserPrivate *priv = OL_PLAYER_CHOOSER_GET_PRIVATE (window); if (priv->launch_app) { g_object_unref (priv->launch_app); } if (app_info) g_object_ref (app_info); priv->launch_app = app_info; } static void _launch_button_clicked_cb (GtkButton *button, OlPlayerChooser *window) { ol_assert (OL_IS_PLAYER_CHOOSER (window)); OlPlayerChooserPrivate *priv = OL_PLAYER_CHOOSER_GET_PRIVATE (window); const char *cmd = gtk_entry_get_text (priv->cmd_entry); if (ol_is_string_empty (cmd)) return; GAppInfo *app_info = G_APP_INFO (ol_app_info_new (cmd, NULL, NULL, OL_APP_INFO_PREFER_DESKTOP_FILE, NULL)); _launch_app (window, app_info); g_object_unref (app_info); } static void _remember_cmd_if_needed (OlPlayerChooser *window, const char *cmd) { ol_assert (OL_IS_PLAYER_CHOOSER (window)); OlPlayerChooserPrivate *priv = OL_PLAYER_CHOOSER_GET_PRIVATE (window); if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->remember_button))) { OlConfigProxy *config = ol_config_proxy_get_instance (); ol_config_proxy_set_string (config, "General/startup-player", cmd); } } void ol_player_chooser_set_info (OlPlayerChooser *window, const char *info, const char *description) { ol_assert (OL_IS_PLAYER_CHOOSER (window)); ol_assert (info != NULL); OlPlayerChooserPrivate *priv = OL_PLAYER_CHOOSER_GET_PRIVATE (window); gchar *text; if (description != NULL) text = g_strdup_printf ("%s\n\n%s", info, description); else text = g_strdup (info); gtk_label_set_text (priv->info_label, text); g_free (text); } void ol_player_chooser_set_image_by_name (OlPlayerChooser *window, const char *icon_name) { ol_assert (OL_IS_PLAYER_CHOOSER (window)); ol_assert (icon_name != NULL); OlPlayerChooserPrivate *priv = OL_PLAYER_CHOOSER_GET_PRIVATE (window); gtk_image_set_from_icon_name (priv->info_icon, icon_name, GTK_ICON_SIZE_DIALOG); } void ol_player_chooser_set_image_by_gicon (OlPlayerChooser *window, GIcon *icon) { ol_assert (OL_IS_PLAYER_CHOOSER (window)); ol_assert (G_IS_ICON (icon)); OlPlayerChooserPrivate *priv = OL_PLAYER_CHOOSER_GET_PRIVATE (window); gtk_image_set_from_gicon (priv->info_icon, icon, GTK_ICON_SIZE_DIALOG); } void ol_player_chooser_set_info_by_state (OlPlayerChooser *window, enum OlPlayerChooserState state) { ol_assert (OL_IS_PLAYER_CHOOSER (window)); OlPlayerChooserPrivate *priv = OL_PLAYER_CHOOSER_GET_PRIVATE (window); _set_sensitive (window, TRUE); switch (state) { case OL_PLAYER_CHOOSER_STATE_NO_PLAYER: ol_player_chooser_set_info (window, _("There is no supported player running"), _("Please choose a player below to launch")); ol_player_chooser_set_image_by_name (window, GTK_STOCK_DIALOG_INFO); break; case OL_PLAYER_CHOOSER_STATE_LAUNCH_FAIL: { gchar *title = g_strdup_printf (_("Failed to connect to %s"), g_app_info_get_name (priv->launch_app)); gchar *desc = g_strdup_printf (_("%s is not supported by OSD Lyrics, or not running. Please launch another player"), g_app_info_get_name (priv->launch_app)); ol_player_chooser_set_info (window, title, desc); ol_player_chooser_set_image_by_name (window, GTK_STOCK_DIALOG_ERROR); g_free (title); g_free (desc); _set_launch_app (window, NULL); break; } case OL_PLAYER_CHOOSER_STATE_CONNECTED: gtk_widget_hide (GTK_WIDGET (window)); break; default: ol_errorf ("Unknown player chooser state %d\n", (int) state); } } static void _set_sensitive (OlPlayerChooser *window, gboolean sensitive) { OlPlayerChooserPrivate *priv = OL_PLAYER_CHOOSER_GET_PRIVATE (window); gtk_widget_set_sensitive (GTK_WIDGET (priv->chooser_panel), sensitive); gtk_widget_set_sensitive (GTK_WIDGET (priv->page_button_panel), sensitive); gtk_widget_set_sensitive (GTK_WIDGET (priv->custom_cmd_panel), sensitive); } osdlyrics-0.5.5~rc1+dfsg1/src/ol_player_chooser.h000066400000000000000000000051431345713535600220270ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_PLAYER_CHOOSER_H_ #define _OL_PLAYER_CHOOSER_H_ #include #define OL_PLAYER_CHOOSER(obj) GTK_CHECK_CAST (obj, ol_player_chooser_get_type (), OlPlayerChooser) #define OL_PLAYER_CHOOSER_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, ol_player_chooser_get_type (), OlPlayerChooserClass) #define OL_IS_PLAYER_CHOOSER(obj) GTK_CHECK_TYPE (obj, ol_player_chooser_get_type ()) #define OL_PLAYER_CHOOSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ol_player_chooser_get_type (), OlPlayerChooserClass)) enum OlPlayerChooserResponse { OL_PLAYER_CHOOSER_RESPONSE_LAUNCH, }; enum OlPlayerChooserState { OL_PLAYER_CHOOSER_STATE_NO_PLAYER, OL_PLAYER_CHOOSER_STATE_CONNECTED, OL_PLAYER_CHOOSER_STATE_LAUNCH_FAIL, }; typedef struct _OlPlayerChooser OlPlayerChooser; typedef struct _OlPlayerChooserClass OlPlayerChooserClass; struct _OlPlayerChooser { GtkDialog parent; }; struct _OlPlayerChooserClass { GtkDialogClass parent_class; }; GtkType ol_player_chooser_get_type (void); /** * Creates a new player chooser window. * * @param supported_players List of *GAppInfo. * * @return */ GtkWidget *ol_player_chooser_new (GList *supported_players); void ol_player_chooser_set_info (OlPlayerChooser *window, const char *title, const char *description); void ol_player_chooser_set_image_by_name (OlPlayerChooser *window, const char *icon_name); void ol_player_chooser_set_image_by_gicon (OlPlayerChooser *window, GIcon *icon); void ol_player_chooser_set_info_by_state (OlPlayerChooser *window, enum OlPlayerChooserState state); #endif /* _OL_PLAYER_CHOOSER_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_scroll_module.c000066400000000000000000000546071345713535600216600ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2010 Sarlmol Apple * Copyright (C) 2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include "ol_scroll_module.h" #include "ol_metadata.h" #include "ol_scroll_window.h" #include "ol_config_proxy.h" #include "ol_color.h" #include "ol_stock.h" #include "ol_image_button.h" #include "ol_menu.h" #include "ol_app.h" #include "ol_player.h" #include "ol_lrc.h" #include "config.h" typedef struct _OlScrollModule OlScrollModule; struct OlLrc; const int MESSAGE_TIMEOUT_MS = 5000; struct _OlScrollModule { OlPlayer *player; OlMetadata *metadata; guint64 duration; OlLrc *lrc; OlScrollWindow *scroll; guint message_timer; GList *config_bindings; }; typedef void (*_ConfigSetFunc) (OlConfigProxy *config, const gchar *key, OlScrollModule *module); struct _ConfigBinding { OlScrollModule *module; guint change_handler; _ConfigSetFunc setter; }; struct _ConfigMapping { const gchar *key; _ConfigSetFunc setter; }; static OlScrollModule* ol_scroll_module_new (struct OlDisplayModule *module, OlPlayer *player); static void ol_scroll_module_free (struct OlDisplayModule *module); static void _metadata_changed_cb (OlPlayer *player, OlScrollModule *module); static void _caps_changed_cb (OlPlayer *player, OlScrollModule *module); static void _update_metadata (OlScrollModule *module); static void _update_caps (OlScrollModule *module); static void ol_scroll_module_set_played_time (struct OlDisplayModule *module, guint64 played_time); static void ol_scroll_module_set_lrc (struct OlDisplayModule *module, OlLrc *lrc); static void ol_scroll_module_set_message (struct OlDisplayModule *module, const char *message); static void ol_scroll_module_set_last_message (struct OlDisplayModule *module, const char *message); static void ol_scroll_module_clear_message (struct OlDisplayModule *module); static gboolean _window_configure_cb (GtkWidget *widget, GdkEventConfigure *event, gpointer userdata); static void _set_metadata_as_text (OlScrollModule *module); static GtkWidget* _toolbar_new (OlScrollModule *module); static gboolean _close_clicked_cb (GtkButton *button, gpointer userdata); static gboolean _button_release_cb (OlScrollWindow *scroll, GdkEventButton *event, gpointer userdata); static void _seek_cb (OlScrollWindow *scroll, guint id, gdouble percentage, gpointer userdata); static void _scroll_cb (OlScrollWindow *osd, GdkEventScroll *event, gpointer data); /* config handlers */ static struct _ConfigBinding *_bind_config (const char *key, _ConfigSetFunc setter, OlScrollModule *module); static void _unbind_config (struct _ConfigBinding *binding); static void _bind_all_config (OlScrollModule *osd); static void _config_changed_cb (OlConfigProxy *config, const char *key, struct _ConfigBinding *binding); /* config value handlers */ static void _font_changed_cb (OlConfigProxy *config, const char *key, OlScrollModule *module); static void _size_changed_cb (OlConfigProxy *config, const char *key, OlScrollModule *module); static void _active_color_changed_cb (OlConfigProxy *config, const char *key, OlScrollModule *module); static void _inactive_color_changed_cb (OlConfigProxy *config, const char *key, OlScrollModule *module); static void _bg_color_changed_cb (OlConfigProxy *config, const char *key, OlScrollModule *module); static void _opacity_changed_cb (OlConfigProxy *config, const char *key, OlScrollModule *module); static void _scroll_mode_changed_cb (OlConfigProxy *config, const char *key, OlScrollModule *module); static struct _ConfigMapping _config_mapping[] = { { "ScrollMode/width", _size_changed_cb }, { "ScrollMode/height", _size_changed_cb }, { "ScrollMode/font-name", _font_changed_cb }, { "ScrollMode/active-lrc-color", _active_color_changed_cb }, { "ScrollMode/inactive-lrc-color", _inactive_color_changed_cb }, { "ScrollMode/bg-color", _bg_color_changed_cb }, { "ScrollMode/opacity", _opacity_changed_cb }, { "ScrollMode/scroll-mode", _scroll_mode_changed_cb }, }; static gboolean _config_is_setting = FALSE; static gboolean _button_release_cb (OlScrollWindow *scroll, GdkEventButton *event, gpointer data) { if (event->button == 3) { gtk_menu_popup (GTK_MENU (ol_menu_get_popup ()), NULL, NULL, NULL, NULL, event->button, event->time); return TRUE; } return FALSE; } static void _scroll_cb (OlScrollWindow *osd, GdkEventScroll *event, gpointer data) { int doffset = 0; if (event->direction == GDK_SCROLL_DOWN || event->direction == GDK_SCROLL_RIGHT) doffset = -200; else if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_LEFT) doffset = 200; ol_app_adjust_lyric_offset (doffset); } static gboolean _window_configure_cb (GtkWidget *widget, GdkEventConfigure *event, gpointer user_data) { ol_assert_ret (GTK_IS_WINDOW (widget), FALSE); OlScrollModule *module = (OlScrollModule*) user_data; if (module == NULL) return FALSE; if (_config_is_setting) return FALSE; _config_is_setting = TRUE; gint width, height; OlConfigProxy *config = ol_config_proxy_get_instance (); gtk_window_get_size (GTK_WINDOW (widget), &width, &height); ol_config_proxy_set_int (config, "ScrollMode/width", width); ol_config_proxy_set_int (config, "ScrollMode/height", height); _config_is_setting = FALSE; return FALSE; } static void _seek_cb (OlScrollWindow *scroll, guint id, gdouble percentage, gpointer userdata) { OlScrollModule *module = userdata; if (module->lrc) { OlLrcIter *iter = ol_lrc_iter_from_id (module->lrc, id); if (!ol_lrc_iter_is_valid (iter)) { ol_errorf ("Seek to an invalid ID: %u\n", id); ol_lrc_iter_free (iter); return; } gint64 iter_time = ol_lrc_iter_get_timestamp (iter); guint64 duration = ol_lrc_iter_get_duration (iter); ol_lrc_iter_free (iter); gint64 new_time = iter_time + duration * percentage; ol_player_seek (module->player, new_time); ol_scroll_window_set_progress (module->scroll, id, percentage); /* avoid players send played time before seeked. */ g_usleep (200000); } } static void _config_changed_cb (OlConfigProxy *config, const char *key, struct _ConfigBinding *binding) { if (_config_is_setting) return; _config_is_setting = TRUE; binding->setter (config, key, binding->module); _config_is_setting = FALSE; } static void _font_changed_cb (OlConfigProxy *config, const char *key, OlScrollModule *module) { gchar *font = ol_config_proxy_get_string (config, "ScrollMode/font-name"); ol_assert (font != NULL); ol_scroll_window_set_font_name (module->scroll, font); g_free (font); } static void _size_changed_cb (OlConfigProxy *config, const char *key, OlScrollModule *module) { gint width = ol_config_proxy_get_int (config, "ScrollMode/width"); gint height = ol_config_proxy_get_int (config, "ScrollMode/height"); gtk_window_resize (GTK_WINDOW (module->scroll), width, height); } static void _active_color_changed_cb (OlConfigProxy *config, const char *key, OlScrollModule *module) { char *color_str = ol_config_proxy_get_string (config, key); if (color_str != NULL) { OlColor color = ol_color_from_string (color_str); ol_scroll_window_set_active_color (module->scroll, color); g_free (color_str); } } static void _inactive_color_changed_cb (OlConfigProxy *config, const char *key, OlScrollModule *module) { char *color_str = ol_config_proxy_get_string (config, key); if (color_str != NULL) { OlColor color = ol_color_from_string (color_str); ol_scroll_window_set_inactive_color (module->scroll, color); g_free (color_str); } } static void _bg_color_changed_cb (OlConfigProxy *config, const char *key, OlScrollModule *module) { char *color_str = ol_config_proxy_get_string (config, key); if (color_str != NULL) { OlColor color = ol_color_from_string (color_str); ol_scroll_window_set_bg_color (module->scroll, color); g_free (color_str); } } static void _opacity_changed_cb (OlConfigProxy *config, const char *key, OlScrollModule *module) { double opacity = ol_config_proxy_get_double (config, key); ol_scroll_window_set_bg_opacity (module->scroll, opacity); } static void _scroll_mode_changed_cb (OlConfigProxy *config, const char *key, OlScrollModule *module) { char *scroll_mode = ol_config_proxy_get_string (config, key); if (scroll_mode != NULL) { enum OlScrollWindowScrollMode mode = OL_SCROLL_WINDOW_ALWAYS; if (g_ascii_strcasecmp (scroll_mode, "lines") == 0) { mode = OL_SCROLL_WINDOW_BY_LINES; } ol_scroll_window_set_scroll_mode (module->scroll, mode); g_free (scroll_mode); } } static gboolean _close_clicked_cb (GtkButton *button, gpointer userdata) { gtk_main_quit (); return FALSE; } static GtkWidget * _toolbar_new (OlScrollModule *module) { GtkWidget *toolbar = gtk_hbox_new (FALSE, 0); OlImageButton *button = OL_IMAGE_BUTTON (ol_image_button_new ()); GtkIconTheme *icontheme = gtk_icon_theme_get_default (); GtkIconInfo *info = gtk_icon_theme_lookup_icon (icontheme, OL_STOCK_SCROLL_CLOSE, 16, 0); GdkPixbuf *image = gdk_pixbuf_new_from_file (gtk_icon_info_get_filename (info), NULL); ol_image_button_set_pixbuf (button, image); g_signal_connect (button, "clicked", G_CALLBACK (_close_clicked_cb), module); gtk_container_add (GTK_CONTAINER (toolbar), GTK_WIDGET (button)); gtk_icon_info_free (info); gtk_widget_show_all (toolbar); return toolbar; } static void _bind_all_config (OlScrollModule *module) { int i; for (i = 0; i < G_N_ELEMENTS (_config_mapping); i++) { struct _ConfigBinding *binding = _bind_config (_config_mapping[i].key, _config_mapping[i].setter, module); module->config_bindings = g_list_prepend (module->config_bindings, binding); } } static struct _ConfigBinding * _bind_config (const char *key, _ConfigSetFunc setter, OlScrollModule *module) { ol_assert_ret (key != NULL, FALSE); OlConfigProxy *config = ol_config_proxy_get_instance (); struct _ConfigBinding *binding = g_new (struct _ConfigBinding, 1); gchar *signal = g_strdup_printf ("changed::%s", key); binding->module = module; binding->setter = setter; binding->change_handler = g_signal_connect (config, signal, (GCallback) _config_changed_cb, binding); g_free (signal); setter (config, key, module); return binding; } static void _unbind_config (struct _ConfigBinding *binding) { OlConfigProxy *config = ol_config_proxy_get_instance (); ol_assert (binding != NULL); g_signal_handler_disconnect (config, binding->change_handler); g_free (binding); } static void ol_scroll_module_init_scroll (OlScrollModule *module) { ol_assert (module != NULL); module->scroll = OL_SCROLL_WINDOW (ol_scroll_window_new ()); module->message_timer = 0; g_object_ref_sink(module->scroll); if (module->scroll == NULL) { return; } GtkWindow *window = GTK_WINDOW (module->scroll); gtk_window_set_title (window, PROGRAM_NAME); gtk_window_set_icon_name (window, PACKAGE_NAME); GtkWidget *toolbar = _toolbar_new (module); ol_scroll_window_add_toolbar (module->scroll, toolbar); _bind_all_config (module); g_signal_connect (module->scroll, "configure-event", G_CALLBACK (_window_configure_cb), module); g_signal_connect (module->scroll, "button-release-event", G_CALLBACK (_button_release_cb), module); g_signal_connect (module->scroll, "scroll-event", G_CALLBACK (_scroll_cb), module); g_signal_connect (module->scroll, "seek", G_CALLBACK (_seek_cb), module); gtk_widget_show(GTK_WIDGET (module->scroll)); } OlScrollModule* ol_scroll_module_new (struct OlDisplayModule *module, OlPlayer *player) { OlScrollModule *priv = g_new (OlScrollModule, 1); g_object_ref (player); priv->player = player; priv->scroll = NULL; priv->lrc = NULL; priv->metadata = ol_metadata_new (); priv->config_bindings = NULL; ol_scroll_module_init_scroll (priv); g_signal_connect (player, "track-changed", G_CALLBACK (_metadata_changed_cb), priv); _update_metadata (priv); g_signal_connect (player, "caps-changed", G_CALLBACK (_caps_changed_cb), priv); _update_caps (priv); return priv; } void ol_scroll_module_free (struct OlDisplayModule *module) { ol_assert (module != NULL); OlScrollModule *priv = ol_display_module_get_data (module); ol_assert (priv != NULL); if (_config_is_setting) return; if (module == NULL) return; if (priv->scroll != NULL) { gtk_widget_destroy (GTK_WIDGET (priv->scroll)); priv->scroll = NULL; } if (priv->message_timer > 0) g_source_remove (priv->message_timer); if (priv->player != NULL) { g_signal_handlers_disconnect_by_func (priv->player, _metadata_changed_cb, priv); g_signal_handlers_disconnect_by_func (priv->player, _caps_changed_cb, priv); g_object_unref (priv->player); priv->player = NULL; } if (priv->metadata != NULL) { ol_metadata_free (priv->metadata); priv->metadata = NULL; } if (priv->lrc) { g_object_unref (priv->lrc); priv->lrc = NULL; } while (priv->config_bindings != NULL) { _unbind_config (priv->config_bindings->data); priv->config_bindings = g_list_delete_link (priv->config_bindings, priv->config_bindings); } g_free (priv); } static void _set_metadata_as_text (OlScrollModule *module) { ol_assert (module != NULL); gchar *text = NULL; gchar *title = NULL; if (module->scroll != NULL) { if (ol_metadata_get_title (module->metadata) != NULL) { if (ol_metadata_get_artist (module->metadata) != NULL) { text = g_strdup_printf ("%s\n%s", ol_metadata_get_title (module->metadata), ol_metadata_get_artist (module->metadata)); title = g_strdup_printf ("%s - %s - %s", ol_metadata_get_artist (module->metadata), ol_metadata_get_title (module->metadata), PROGRAM_NAME); } else { text = g_strdup_printf ("%s", ol_metadata_get_title (module->metadata)); title = g_strdup_printf ("%s - %s", ol_metadata_get_title (module->metadata), PROGRAM_NAME); } } ol_scroll_window_set_text (module->scroll, text); if (!title) { gtk_window_set_title (GTK_WINDOW (module->scroll), PROGRAM_NAME); } else { gtk_window_set_title (GTK_WINDOW (module->scroll), title); g_free (title); } if (text != NULL) g_free (text); } } static void _metadata_changed_cb (OlPlayer *player, OlScrollModule *module) { _update_metadata (module); } static void _update_metadata (OlScrollModule *module) { ol_assert (module != NULL); ol_player_get_metadata (module->player, module->metadata); _set_metadata_as_text (module); } static void _caps_changed_cb (OlPlayer *player, OlScrollModule *module) { _update_caps (module); } static void _update_caps (OlScrollModule *module) { ol_assert (module != NULL); ol_assert (module->player != NULL); ol_assert (module->scroll != NULL); ol_scroll_window_set_can_seek (module->scroll, ol_player_get_caps (module->player) & OL_PLAYER_SEEK); } static void ol_scroll_module_set_played_time (struct OlDisplayModule *module, guint64 played_time) { ol_assert (module != NULL); OlScrollModule *priv = ol_display_module_get_data (module); ol_assert (priv != NULL); if (priv->lrc != NULL && priv->scroll != NULL) { OlLrcIter *iter = ol_lrc_iter_from_timestamp (priv->lrc, played_time); ol_scroll_window_set_progress (priv->scroll, ol_lrc_iter_get_id (iter), ol_lrc_iter_compute_percentage (iter, played_time)); ol_lrc_iter_free (iter); } } void ol_scroll_module_set_lrc (struct OlDisplayModule *module, OlLrc *lrc) { ol_log_func (); ol_assert (module != NULL); ol_assert (module != NULL); OlScrollModule *priv = ol_display_module_get_data (module); ol_assert (priv != NULL); if (priv->lrc != NULL) g_object_unref (priv->lrc); priv->lrc = lrc; if (priv->lrc == NULL) ol_scroll_window_set_whole_lyrics(priv->scroll, NULL); else { g_object_ref (priv->lrc); /*dump the whole lyrics of a song*/ GPtrArray *text_array = g_ptr_array_new_with_free_func (g_free); OlLrcIter *iter = ol_lrc_iter_from_id (lrc, 0); const char *text = NULL; while (ol_lrc_iter_loop (iter, NULL, NULL, &text)) { g_ptr_array_add (text_array, g_strdup (text)); } ol_scroll_window_set_whole_lyrics(priv->scroll, text_array); g_ptr_array_unref (text_array); ol_lrc_iter_free (iter); } } static void ol_scroll_module_set_message (struct OlDisplayModule *module, const char *message) { ol_assert (module != NULL); OlScrollModule *priv = ol_display_module_get_data (module); ol_assert (priv != NULL); if (priv->scroll != NULL && message != NULL) { ol_scroll_window_set_text (priv->scroll, message); } } static void ol_scroll_module_set_last_message (struct OlDisplayModule *module, const char *message) { ol_assert (module != NULL); OlScrollModule *priv = ol_display_module_get_data (module); ol_assert (priv != NULL); ol_scroll_module_set_message (module, message); if (priv->message_timer != 0) g_source_remove (priv->message_timer); priv->message_timer = g_timeout_add (MESSAGE_TIMEOUT_MS, (GSourceFunc) ol_scroll_module_clear_message, module); } static void ol_scroll_module_clear_message (struct OlDisplayModule *module) { ol_assert (module != NULL); OlScrollModule *priv = ol_display_module_get_data (module); ol_assert (priv != NULL); _set_metadata_as_text (priv); if (priv->message_timer > 0) { g_source_remove (priv->message_timer); priv->message_timer = 0; } } struct OlDisplayClass* ol_scroll_module_get_class () { struct OlDisplayClass *klass = ol_display_class_new ("scroll", (OlDisplayInitFunc) ol_scroll_module_new, ol_scroll_module_free); klass->clear_message = ol_scroll_module_clear_message; klass->download_fail_message = ol_scroll_module_set_last_message; klass->search_fail_message = ol_scroll_module_set_last_message; klass->search_message = ol_scroll_module_set_message; klass->set_lrc = ol_scroll_module_set_lrc; /* klass->set_message = ol_scroll_module_set_message; */ klass->set_played_time = ol_scroll_module_set_played_time; /* klass->set_player = ol_scroll_module_set_player; */ /* klass->set_status = ol_scroll_module_set_status; */ return klass; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_scroll_module.h000066400000000000000000000017141345713535600216540ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2010 Sarlmol Apple * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_SCROLL_MODULE_H_ #define _OL_SCROLL_MODULE_H_ #include "ol_display_module.h" struct OlDisplayClass* ol_scroll_module_get_class (); #endif osdlyrics-0.5.5~rc1+dfsg1/src/ol_scroll_window.c000066400000000000000000001035211345713535600216700ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2010 Sarlmol Apple * Copyright (C) 2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include #include #include "ol_scroll_window.h" #include "ol_intl.h" #include "ol_marshal.h" #include "ol_color.h" #include "ol_debug.h" #define OL_SCROLL_WINDOW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), \ ol_scroll_window_get_type (), \ OlScrollWindowPrivate)) /*************default setting******************/ static const gint DEFAULT_WIDTH = 600; static const gint DEFAULT_HEIGHT = 600; static const gint DEFAULT_LINE_COUNT = 20; static const OlColor DEFAULT_ACTIVE_COLOR = {0.89,0.81,0}; static const OlColor DEFAULT_INACTIVE_COLOR = {0.98,0.92,0.84}; static const OlColor DEFAULT_BG_COLOR = {0,0,0}; static const char *DEFAULT_FONT_NAME = "serif 13"; static const gint DEFAULT_LINE_MARGIN = 7; static const gint DEFAULT_PADDING_X = 10; static const gint DEFAULT_PADDING_Y = 5; static const gint DEFAULT_CORNER_RADIUS = 10; static const gint DEFAULT_FRAME_WIDTH = 7; static const double DEFAULT_BG_OPACITY = 0.9; static const gchar *TOOLTIP_WITH_SEEK = N_ ("Drag to move the window\nHold CTRL to seek"); static const gchar *TOOLTIP_WITHOUT_SEEK = N_ ("Drag to move the window"); /**********************************************/ typedef struct __OlScrollWindowPrivate OlScrollWindowPrivate; struct __OlScrollWindowPrivate { gint line_count; OlColor active_color; /*Color of playing lyric*/ OlColor inactive_color; /*Color of non-playing lyric*/ OlColor bg_color; gchar *font_name; /*Font string, including family, size, and style*/ gint line_margin; /*Margin between two lines*/ gint padding_x; gint padding_y; gint frame_width; gint corner_radius; double bg_opacity; char *text; gint saved_lrc_y; GtkWidget *window_container; GtkContainer *toolbar_container; enum OlScrollWindowScrollMode scroll_mode; gboolean can_seek; gboolean seeking; gint saved_lyric_id; gint saved_seek_offset; gint saved_pointer_y; gint current_pointer_y; }; enum { SEEK_SIGNAL, LAST_SIGNAL, }; static guint _signals[LAST_SIGNAL] = {0}; static cairo_t* _get_cairo (OlScrollWindow *scroll, GtkWidget *widget); static PangoLayout* _get_pango (OlScrollWindow *scroll, cairo_t *cr); static void _paint_bg (OlScrollWindow *scroll, cairo_t *cr); static void _paint_lyrics (OlScrollWindow *scroll, cairo_t *cr); static void _paint_text (OlScrollWindow *scroll, cairo_t *cr); static gint _calc_lrc_ypos (OlScrollWindow *scroll, double percentage); static void _calc_paint_pos (OlScrollWindow *scroll, gint *lyric_id, gint *lrc_y); static void ol_scroll_window_init (OlScrollWindow *self); static void ol_scroll_window_class_init (OlScrollWindowClass *klass); static void ol_scroll_window_destroy (GtkObject *object); static gboolean ol_scroll_window_expose (GtkWidget *widget, GdkEventExpose *event, gpointer userdata); static int ol_scroll_window_compute_line_count (OlScrollWindow *scroll); static int ol_scroll_window_get_font_height (OlScrollWindow *scroll); static PangoLayout* _get_pango (OlScrollWindow *scroll, cairo_t *cr); static gboolean ol_scroll_window_button_press (GtkWidget *widget, GdkEventButton *event); static gboolean ol_scroll_window_button_release (GtkWidget *widget, GdkEventButton *event); static gboolean ol_scroll_window_motion_notify (GtkWidget *widget, GdkEventMotion *event); static void ol_scroll_window_begin_move_resize (GtkWidget *widget, GdkEventButton *event); static void ol_scroll_window_begin_seek (OlScrollWindow *scroll, GdkEventButton *event); static void ol_scroll_window_update_cursor (GtkWidget *widget, GdkEventMotion *event); static void ol_scroll_window_seek (OlScrollWindow *scroll, GdkEventMotion *event); static void ol_scroll_window_end_seek (OlScrollWindow *scroll); static void ol_scroll_window_update_tooltip (OlScrollWindow *scroll); G_DEFINE_TYPE (OlScrollWindow, ol_scroll_window, GTK_TYPE_WINDOW); GtkWidget* ol_scroll_window_new () { OlScrollWindow *scroll; scroll = g_object_new (ol_scroll_window_get_type (), NULL); return GTK_WIDGET (scroll); } static void ol_scroll_window_init (OlScrollWindow *self) { /*basic*/ self->percentage = 0.0; self->whole_lyrics = NULL; self->current_lyric_id = -1; /*privat data*/ OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (self); priv->line_count = DEFAULT_LINE_COUNT; priv->active_color = DEFAULT_ACTIVE_COLOR; priv->inactive_color = DEFAULT_INACTIVE_COLOR; priv->bg_color = DEFAULT_BG_COLOR; priv->font_name = g_strdup (DEFAULT_FONT_NAME); priv->line_margin = DEFAULT_LINE_MARGIN; priv->padding_x = DEFAULT_PADDING_X; priv->padding_y = DEFAULT_PADDING_Y; priv->corner_radius = DEFAULT_CORNER_RADIUS; priv->bg_opacity = DEFAULT_BG_OPACITY; priv->frame_width = DEFAULT_FRAME_WIDTH; priv->text = NULL; priv->scroll_mode = OL_SCROLL_WINDOW_ALWAYS; priv->can_seek = FALSE; priv->seeking = FALSE; /*set allocation*/ gtk_window_resize(GTK_WINDOW(self), DEFAULT_WIDTH, DEFAULT_HEIGHT); gtk_widget_add_events (GTK_WIDGET (self), GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_DESTROY); /* Set RGBA Colormap */ GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (self)); GdkColormap* colormap = gdk_screen_get_rgba_colormap (screen); if (colormap == NULL) colormap = gdk_screen_get_rgb_colormap (screen); gtk_widget_set_colormap (GTK_WIDGET (self), colormap); gtk_window_set_decorated (GTK_WINDOW(self), FALSE); gtk_widget_set_app_paintable (GTK_WIDGET (self), TRUE); /* We need an additional widget to paint on */ priv->window_container = gtk_alignment_new (0.0, 0.0, 1.0, 1.0); gtk_widget_set_redraw_on_allocate(priv->window_container, TRUE); gtk_container_add (GTK_CONTAINER (self), priv->window_container); /* Set toolbar container */ priv->toolbar_container = GTK_CONTAINER (gtk_alignment_new (1.0, 0.0, 0.0, 0.0)); gtk_alignment_set_padding (GTK_ALIGNMENT (priv->toolbar_container), priv->padding_x, priv->padding_x, priv->padding_x, priv->padding_x); gtk_container_add (GTK_CONTAINER (priv->window_container), GTK_WIDGET (priv->toolbar_container)); gtk_widget_show_all (priv->window_container); /* Set tooltips */ ol_scroll_window_update_tooltip (self); /* Connect signals */ g_signal_connect (G_OBJECT (priv->window_container), "expose-event", G_CALLBACK (ol_scroll_window_expose), self); g_signal_connect (G_OBJECT (self), "button-press-event", G_CALLBACK (ol_scroll_window_button_press), self); g_signal_connect (G_OBJECT (self), "button-release-event", G_CALLBACK (ol_scroll_window_button_release), self); g_signal_connect (G_OBJECT (self), "motion-notify-event", G_CALLBACK (ol_scroll_window_motion_notify), self); } static void ol_scroll_window_class_init (OlScrollWindowClass *klass) { GObjectClass *gobject_class; GtkObjectClass *gtkobject_class; gobject_class = G_OBJECT_CLASS (klass); gtkobject_class = GTK_OBJECT_CLASS (klass); gtkobject_class->destroy = ol_scroll_window_destroy; /* install signals */ _signals[SEEK_SIGNAL] = g_signal_new ("seek", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, /* class_offset */ NULL, /* accumulator */ NULL, /* accumulator data */ ol_marshal_VOID__UINT_DOUBLE, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_DOUBLE); /*add private variables into OlScrollWindow*/ g_type_class_add_private (gobject_class, sizeof (OlScrollWindowPrivate)); } void ol_scroll_window_add_toolbar (OlScrollWindow *scroll, GtkWidget *widget) { ol_assert (OL_IS_SCROLL_WINDOW (scroll)); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); gtk_container_add (priv->toolbar_container, widget); } void ol_scroll_window_remove_toolbar (OlScrollWindow *scroll, GtkWidget *widget) { ol_assert (OL_IS_SCROLL_WINDOW (scroll)); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); gtk_container_remove (priv->toolbar_container, widget); } static void ol_scroll_window_destroy (GtkObject *object) { OlScrollWindow *scroll = OL_SCROLL_WINDOW (object); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (object); if (priv->font_name != NULL) g_free (priv->font_name); if (priv->text != NULL) g_free (priv->text); if (scroll->current_lyric_id!= -1) { scroll->current_lyric_id = -1; } GTK_OBJECT_CLASS (ol_scroll_window_parent_class)->destroy (object); } static gboolean ol_scroll_window_expose (GtkWidget *widget, GdkEventExpose *event, gpointer userdata) { ol_assert_ret (OL_IS_SCROLL_WINDOW (userdata), FALSE); OlScrollWindow *scroll = OL_SCROLL_WINDOW (userdata); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); cairo_t *cr = _get_cairo (scroll, widget); _paint_bg (scroll, cr); if (scroll->whole_lyrics != NULL) _paint_lyrics (scroll, cr); else if (priv->text != NULL) _paint_text (scroll, cr); cairo_destroy (cr); return FALSE; } void ol_scroll_window_set_whole_lyrics (OlScrollWindow *scroll, GPtrArray *whole_lyrics) { ol_log_func (); ol_assert (OL_IS_SCROLL_WINDOW (scroll)); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); if (scroll->whole_lyrics != NULL) g_ptr_array_unref (scroll->whole_lyrics); scroll->whole_lyrics = whole_lyrics; if (whole_lyrics != NULL) { g_ptr_array_ref (whole_lyrics); priv->saved_lrc_y = -1; } else { /* We only queue draw when the lyrics are no available. Otherwise the progress will go wrong due to out-dated progress info*/ gtk_widget_queue_draw (GTK_WIDGET (scroll)); } ol_scroll_window_update_tooltip (scroll); if (priv->seeking) ol_scroll_window_end_seek (scroll); } static PangoLayout* _get_pango (OlScrollWindow *scroll, cairo_t *cr) { ol_assert_ret (OL_IS_SCROLL_WINDOW (scroll), NULL); ol_assert_ret (cr != NULL, NULL); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); PangoLayout *layout = pango_cairo_create_layout (cr); PangoFontDescription *desc = pango_font_description_from_string (priv->font_name); pango_layout_set_font_description (layout, desc); pango_font_description_free (desc); return layout; } static cairo_t* _get_cairo (OlScrollWindow *scroll, GtkWidget *widget) { ol_assert_ret (OL_IS_SCROLL_WINDOW (scroll), NULL); ol_assert_ret (GTK_IS_WIDGET (widget), NULL); cairo_t *cr; cr = gdk_cairo_create (gtk_widget_get_window (widget)); cairo_save (cr); cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); cairo_paint(cr); cairo_restore (cr); return cr; } static double _get_active_color_ratio (OlScrollWindow *scroll, int line) { OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); int line_height = ol_scroll_window_get_font_height (scroll) + priv->line_margin; gint current_lyric_id, lrc_y; _calc_paint_pos (scroll, ¤t_lyric_id, &lrc_y); double ratio = 0.0; gdouble percentage = (gdouble) lrc_y / (gdouble) line_height; if (line == current_lyric_id) { ratio = (1.0 - percentage) / 0.1; if (ratio > 1.0) ratio = 1.0; if (ratio < 0.0) ratio = 0.0; return ratio; } else if (line == current_lyric_id + 1) { ratio = (percentage - 0.9) / 0.1; if (ratio > 1.0) ratio = 1.0; if (ratio < 0.0) ratio = 0.0; } return ratio; } static void _paint_bg (OlScrollWindow *scroll, cairo_t *cr) { ol_assert (OL_IS_SCROLL_WINDOW (scroll)); ol_assert (cr != NULL); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); gint width, height; gdk_drawable_get_size (gtk_widget_get_window (GTK_WIDGET (scroll)), &width, &height); cairo_save (cr); cairo_new_path (cr); /* Top-left */ cairo_arc (cr, priv->corner_radius, priv->corner_radius, priv->corner_radius, M_PI, M_PI * 1.5); cairo_line_to (cr, width - priv->corner_radius, 0); /* Top-right */ cairo_arc (cr, width - priv->corner_radius, priv->corner_radius, priv->corner_radius, M_PI * 1.5, M_PI * 2); cairo_line_to (cr, width, height - priv->corner_radius); /* Bottom-right */ cairo_arc (cr, width - priv->corner_radius, height - priv->corner_radius, priv->corner_radius, 0, M_PI * 0.5); cairo_line_to (cr, priv->corner_radius, height); /* Bottom-left */ cairo_arc (cr, priv->corner_radius, height - priv->corner_radius, priv->corner_radius, M_PI * 0.5, M_PI); cairo_close_path (cr); cairo_set_source_rgba (cr, priv->bg_color.r, priv->bg_color.g, priv->bg_color.b, priv->bg_opacity); cairo_fill (cr); /* cairo_stroke (cr); */ cairo_restore (cr); } static gint _calc_lrc_ypos (OlScrollWindow *scroll, double percentage) { ol_assert_ret (OL_IS_SCROLL_WINDOW (scroll), -1); GtkWidget *widget = GTK_WIDGET (scroll); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); if (!GTK_WIDGET_REALIZED (widget)) return -1; gint line_height; line_height = ol_scroll_window_get_font_height (scroll) + priv->line_margin; if (priv->scroll_mode == OL_SCROLL_WINDOW_BY_LINES) { if (percentage < 0.15) percentage = percentage / 0.15; else percentage = 1; } return line_height * percentage; } static void _calc_paint_pos (OlScrollWindow *scroll, gint *lyric_id, gint *lrc_y) { OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); if (priv->seeking) { gint line_height; line_height = ol_scroll_window_get_font_height (scroll) + priv->line_margin; gint id = priv->saved_lyric_id; gint y = priv->saved_seek_offset - priv->current_pointer_y + priv->saved_pointer_y; id += y / line_height; y %= line_height; if (y < 0) { y += line_height; id--; } if (id < 0) { id = 0; y = 0; } else if (id >= scroll->whole_lyrics->len) { id = scroll->whole_lyrics->len - 1; y = line_height; } if (lyric_id) *lyric_id = id; if (lrc_y) *lrc_y = y; } else { if (lyric_id) *lyric_id = scroll->current_lyric_id; priv->saved_lrc_y = _calc_lrc_ypos (scroll, scroll->percentage); if (lrc_y) *lrc_y = priv->saved_lrc_y; } } static void _paint_lyrics (OlScrollWindow *scroll, cairo_t *cr) { ol_assert (OL_IS_SCROLL_WINDOW (scroll)); GtkWidget *widget = GTK_WIDGET (scroll); ol_assert (GTK_WIDGET_REALIZED (widget)); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); int line_height = ol_scroll_window_get_font_height (scroll) + priv->line_margin; int count = ol_scroll_window_compute_line_count (scroll); gint width, height; gdk_drawable_get_size (gtk_widget_get_window (GTK_WIDGET (scroll)), &width, &height); /* set the font */ PangoLayout *layout = _get_pango (scroll, cr); pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT); pango_layout_set_width (layout, (width - priv->padding_x*2) * PANGO_SCALE); pango_layout_set_indent (layout, -20 * PANGO_SCALE); /* paint the lyrics*/ cairo_save (cr); cairo_new_path (cr); cairo_rectangle (cr, priv->padding_x, 0, width - priv->padding_x * 2, height - priv->padding_y * 2); cairo_close_path (cr); cairo_clip (cr); int i; gint current_lyric_id; gint lrc_y; _calc_paint_pos (scroll, ¤t_lyric_id, &lrc_y); int begin = current_lyric_id - count / 2; int end = current_lyric_id + count / 2 + 1; int ypos = height / 2 - lrc_y - (count / 2 + 1) * line_height; cairo_set_source_rgb(cr, priv->inactive_color.r, priv->inactive_color.g, priv->inactive_color.b); if (scroll->whole_lyrics != NULL) { for (i = begin; i < end; i++) { ypos += line_height; if (i < 0) continue; if (i >= scroll->whole_lyrics->len) break; pango_layout_set_text (layout, g_ptr_array_index (scroll->whole_lyrics, i), -1); cairo_save (cr); double ratio = _get_active_color_ratio (scroll, i); double alpha = 1.0; if (ypos < line_height / 2.0 + priv->padding_y) alpha = 1.0 - (line_height / 2.0 + priv->padding_y - ypos) * 1.0 / line_height * 2; else if (ypos > height - line_height * 1.5 - priv->padding_y) alpha = (height - line_height - priv->padding_y - ypos) * 1.0 / line_height * 2; if (alpha < 0.0) alpha = 0.0; cairo_set_source_rgba (cr, priv->active_color.r * ratio + priv->inactive_color.r * (1 - ratio), priv->active_color.g * ratio + priv->inactive_color.g * (1 - ratio), priv->active_color.b * ratio + priv->inactive_color.b * (1 - ratio), alpha); cairo_move_to (cr, priv->padding_x, ypos); pango_cairo_update_layout (cr, layout); pango_cairo_show_layout (cr, layout); cairo_restore (cr); if (pango_layout_is_wrapped (layout)) { // There is more than one line, offset ypos according to the number of // additional lines ypos += (pango_layout_get_line_count (layout) - 1) * ol_scroll_window_get_font_height (scroll); } } } g_object_unref (layout); cairo_reset_clip (cr); cairo_restore (cr); } static void _paint_text (OlScrollWindow *scroll, cairo_t *cr) { ol_log_func (); ol_assert (OL_IS_SCROLL_WINDOW (scroll)); ol_assert (cr != NULL); GtkWidget *widget = GTK_WIDGET (scroll); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); gint width, height; PangoRectangle extent; PangoLayout *layout; gint x, y; gdk_drawable_get_size (gtk_widget_get_window (widget), &width, &height); /* set the font */ cairo_save (cr); cairo_set_source_rgb (cr, priv->inactive_color.r, priv->inactive_color.g, priv->inactive_color.b); layout = _get_pango (scroll, cr); pango_layout_set_text (layout, priv->text, -1); pango_layout_get_pixel_extents (layout, NULL, &extent); pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER); x = (width - extent.width) / 2; y = (height - extent.height) / 2; if (x < 0) x = 0; if (y < 0) y = 0; cairo_move_to (cr, x, y); pango_cairo_update_layout (cr, layout); pango_cairo_show_layout (cr, layout); } void ol_scroll_window_set_progress (OlScrollWindow *scroll, int lyric_id, double percentage) { ol_assert (OL_IS_SCROLL_WINDOW (scroll)); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); gint saved_lyric_id = scroll->current_lyric_id; scroll->current_lyric_id = lyric_id; scroll->percentage = percentage; if (saved_lyric_id != lyric_id || priv->saved_lrc_y != _calc_lrc_ypos (scroll, percentage)) gtk_widget_queue_draw (GTK_WIDGET (scroll)); } void ol_scroll_window_set_text (OlScrollWindow *scroll, const char *text) { ol_assert (OL_IS_SCROLL_WINDOW (scroll)); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); if (priv->text != NULL) g_free (priv->text); priv->text = g_strdup (text); gtk_widget_queue_draw (GTK_WIDGET (scroll)); } static int ol_scroll_window_compute_line_count (OlScrollWindow *scroll) { ol_assert_ret (OL_IS_SCROLL_WINDOW (scroll), 0); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); int font_height = ol_scroll_window_get_font_height (scroll) + priv->line_margin; gint width, height; gtk_window_get_size (GTK_WINDOW (scroll), &width, &height); int line_count = height - priv->padding_y * 2 / font_height; return line_count; } static int ol_scroll_window_get_font_height (OlScrollWindow *scroll) { ol_assert_ret (OL_IS_SCROLL_WINDOW (scroll), 0); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); PangoContext *pango_context = gdk_pango_context_get (); PangoLayout *pango_layout = pango_layout_new (pango_context); PangoFontDescription *font_desc = pango_font_description_from_string (priv->font_name); pango_layout_set_font_description (pango_layout, font_desc); PangoFontMetrics *metrics = pango_context_get_metrics (pango_context, pango_layout_get_font_description (pango_layout), /* font desc */ NULL); /* languague */ int height = 0; int ascent, descent; ascent = pango_font_metrics_get_ascent (metrics); descent = pango_font_metrics_get_descent (metrics); pango_font_metrics_unref (metrics); height += PANGO_PIXELS (ascent + descent); pango_font_description_free (font_desc); g_object_unref (pango_layout); g_object_unref (pango_context); return height; } int ol_scroll_window_get_current_lyric_id (OlScrollWindow *scroll) { ol_assert_ret (OL_IS_SCROLL_WINDOW (scroll), -1); return scroll->current_lyric_id; } void ol_scroll_window_set_font_name (OlScrollWindow *scroll, const char *font_name) { if (scroll == NULL || font_name == NULL) return; OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); if (priv->font_name != NULL) g_free (priv->font_name); priv->font_name = g_strdup (font_name); gtk_widget_queue_draw (GTK_WIDGET (scroll)); } const char* ol_scroll_window_get_font_name (OlScrollWindow *scroll) { if (scroll == NULL) return NULL; OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); return priv->font_name; } static gboolean _get_pointer_edge (gint x, gint y, gint width, gint height, gint top, gint bottom, gint left, gint right, GdkWindowEdge *edge) { ol_assert_ret (width > 0 && height > 0, FALSE); ol_assert_ret (top >= 0, FALSE); ol_assert_ret (bottom >= 0, FALSE); ol_assert_ret (left >= 0, FALSE); ol_assert_ret (right >= 0, FALSE); gboolean ret = TRUE; GdkWindowEdge ret_edge = GDK_WINDOW_EDGE_EAST; if (x < left) { if (y < top) ret_edge = GDK_WINDOW_EDGE_NORTH_WEST; else if (y >= height - bottom) ret_edge = GDK_WINDOW_EDGE_SOUTH_WEST; else ret_edge = GDK_WINDOW_EDGE_WEST; } else if (x >= width - right) { if (y < top) ret_edge = GDK_WINDOW_EDGE_NORTH_EAST; else if (y >= height - bottom) ret_edge = GDK_WINDOW_EDGE_SOUTH_EAST; else ret_edge = GDK_WINDOW_EDGE_EAST; } else if (y < top) { ret_edge = GDK_WINDOW_EDGE_NORTH; } else if (y >= height - bottom) { ret_edge = GDK_WINDOW_EDGE_SOUTH; } else { ret = FALSE; } if (edge != NULL && ret) *edge = ret_edge; return ret; } static void ol_scroll_window_begin_move_resize (GtkWidget *widget, GdkEventButton *event) { gint width, height; GtkWindow *window = GTK_WINDOW (widget); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (widget); GdkWindowEdge edge; gtk_window_get_size (window, &width, &height); if (!_get_pointer_edge (event->x, event->y, width, height, priv->frame_width, priv->frame_width, priv->frame_width, priv->frame_width, &edge)) gtk_window_begin_move_drag (window, event->button, (gint)event->x_root, (gint)event->y_root, event->time); else gtk_window_begin_resize_drag (window, edge, event->button, (gint)event->x_root, (gint)event->y_root, event->time); } static void ol_scroll_window_begin_seek (OlScrollWindow *scroll, GdkEventButton *event) { OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); priv->seeking = TRUE; priv->saved_seek_offset = priv->saved_lrc_y; priv->saved_pointer_y = event->y; priv->saved_lyric_id = scroll->current_lyric_id; } static gboolean ol_scroll_window_button_press (GtkWidget *widget, GdkEventButton *event) { ol_assert_ret (OL_IS_SCROLL_WINDOW (widget), FALSE); OlScrollWindow *scroll = OL_SCROLL_WINDOW (widget); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); if (event->button == 1) { if (event->state & GDK_CONTROL_MASK && scroll->whole_lyrics != NULL && priv->can_seek) { ol_scroll_window_begin_seek (OL_SCROLL_WINDOW (widget), event); } else { ol_scroll_window_begin_move_resize (widget, event); } } return FALSE; } static void ol_scroll_window_end_seek (OlScrollWindow *scroll) { OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); priv->seeking = FALSE; } static gboolean ol_scroll_window_button_release (GtkWidget *widget, GdkEventButton *event) { ol_assert_ret (OL_IS_SCROLL_WINDOW (widget), FALSE); OlScrollWindow *scroll = OL_SCROLL_WINDOW (widget); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (widget); if (priv->seeking) { gint current_lyric_id, lrc_y; gint line_height = ol_scroll_window_get_font_height (scroll) + priv->line_margin; _calc_paint_pos (scroll, ¤t_lyric_id, &lrc_y); gdouble percentage = (gdouble) lrc_y / (gdouble) line_height; ol_scroll_window_end_seek (scroll); g_signal_emit (scroll, _signals[SEEK_SIGNAL], 0, /* detail */ (guint)current_lyric_id, percentage); } return FALSE; } static void ol_scroll_window_update_cursor (GtkWidget *widget, GdkEventMotion *event) { GdkWindowEdge edge; gint width, height; gtk_window_get_size (GTK_WINDOW (widget), &width, &height); GdkCursor *cursor = NULL; OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (widget); if (_get_pointer_edge (event->x, event->y, width, height, priv->frame_width, priv->frame_width, priv->frame_width, priv->frame_width, &edge)) { switch (edge) { case GDK_WINDOW_EDGE_EAST: cursor = gdk_cursor_new (GDK_RIGHT_SIDE); break; case GDK_WINDOW_EDGE_WEST: cursor = gdk_cursor_new (GDK_LEFT_SIDE); break; case GDK_WINDOW_EDGE_NORTH: cursor = gdk_cursor_new (GDK_TOP_SIDE); break; case GDK_WINDOW_EDGE_SOUTH: cursor = gdk_cursor_new (GDK_BOTTOM_SIDE); break; case GDK_WINDOW_EDGE_NORTH_EAST: cursor = gdk_cursor_new (GDK_TOP_RIGHT_CORNER); break; case GDK_WINDOW_EDGE_NORTH_WEST: cursor = gdk_cursor_new (GDK_TOP_LEFT_CORNER); break; case GDK_WINDOW_EDGE_SOUTH_EAST: cursor = gdk_cursor_new (GDK_BOTTOM_RIGHT_CORNER); break; case GDK_WINDOW_EDGE_SOUTH_WEST: cursor = gdk_cursor_new (GDK_BOTTOM_LEFT_CORNER); break; } } gdk_window_set_cursor (widget->window, cursor); if (cursor) gdk_cursor_unref (cursor); } static void ol_scroll_window_seek (OlScrollWindow *scroll, GdkEventMotion *event) { OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); priv->current_pointer_y = event->y; gtk_widget_queue_draw (GTK_WIDGET (scroll)); } static gboolean ol_scroll_window_motion_notify (GtkWidget *widget, GdkEventMotion *event) { ol_assert_ret (OL_IS_SCROLL_WINDOW (widget), FALSE); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (widget); if (priv->seeking) { ol_scroll_window_seek (OL_SCROLL_WINDOW (widget), event); } else if (event->state & GDK_CONTROL_MASK) { gdk_window_set_cursor (widget->window, NULL); } else { ol_scroll_window_update_cursor (widget, event); } return FALSE; } void ol_scroll_window_set_active_color (OlScrollWindow *scroll, OlColor color) { ol_assert (OL_IS_SCROLL_WINDOW (scroll)); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); priv->active_color = color; gtk_widget_queue_draw (GTK_WIDGET (scroll)); } OlColor ol_scroll_window_get_active_color (OlScrollWindow *scroll) { ol_assert_ret (OL_IS_SCROLL_WINDOW (scroll), ol_color_from_string ("#000000")); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); return priv->active_color; } void ol_scroll_window_set_inactive_color (OlScrollWindow *scroll, OlColor color) { ol_assert (OL_IS_SCROLL_WINDOW (scroll)); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); priv->inactive_color = color; gtk_widget_queue_draw (GTK_WIDGET (scroll)); } OlColor ol_scroll_window_get_inactive_color (OlScrollWindow *scroll) { ol_assert_ret (OL_IS_SCROLL_WINDOW (scroll), ol_color_from_string ("#000000")); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); return priv->inactive_color; } void ol_scroll_window_set_bg_color (OlScrollWindow *scroll, OlColor color) { ol_assert (OL_IS_SCROLL_WINDOW (scroll)); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); priv->bg_color = color; gtk_widget_queue_draw (GTK_WIDGET (scroll)); } OlColor ol_scroll_window_get_bg_color (OlScrollWindow *scroll) { ol_assert_ret (OL_IS_SCROLL_WINDOW (scroll), ol_color_from_string ("#000000")); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); return priv->bg_color; } void ol_scroll_window_set_bg_opacity (OlScrollWindow *scroll, double opacity) { ol_assert (OL_IS_SCROLL_WINDOW (scroll)); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); priv->bg_opacity = opacity; gtk_widget_queue_draw (GTK_WIDGET (scroll)); } double ol_scroll_window_get_bg_opacity (OlScrollWindow *scroll) { ol_assert_ret (OL_IS_SCROLL_WINDOW (scroll), 1.0); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); return priv->bg_opacity; } void ol_scroll_window_set_scroll_mode (OlScrollWindow *scroll, enum OlScrollWindowScrollMode mode) { ol_assert (OL_IS_SCROLL_WINDOW (scroll)); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); priv->scroll_mode = mode; gtk_widget_queue_draw (GTK_WIDGET (scroll)); } enum OlScrollWindowScrollMode ol_scroll_window_get_scroll_mode (OlScrollWindow *scroll) { ol_assert_ret (OL_IS_SCROLL_WINDOW (scroll), OL_SCROLL_WINDOW_ALWAYS); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); return priv->scroll_mode; } static void ol_scroll_window_update_tooltip (OlScrollWindow *scroll) { ol_assert (OL_IS_SCROLL_WINDOW (scroll)); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); if (priv->can_seek && scroll->whole_lyrics != NULL) gtk_widget_set_tooltip_text (GTK_WIDGET (scroll), _(TOOLTIP_WITH_SEEK)); else gtk_widget_set_tooltip_text (GTK_WIDGET (scroll), _(TOOLTIP_WITHOUT_SEEK)); } void ol_scroll_window_set_can_seek (OlScrollWindow *scroll, gboolean can_seek) { ol_assert (OL_IS_SCROLL_WINDOW (scroll)); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); if (can_seek != priv->can_seek) { priv->can_seek = can_seek; ol_scroll_window_update_tooltip (scroll); } } gboolean ol_scroll_window_get_can_seek (OlScrollWindow *scroll) { ol_assert_ret (OL_IS_SCROLL_WINDOW (scroll), FALSE); OlScrollWindowPrivate *priv = OL_SCROLL_WINDOW_GET_PRIVATE (scroll); return priv->can_seek; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_scroll_window.h000066400000000000000000000133521345713535600216770ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2010 Sarlmol Apple * Copyright (C) 2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef __SCROLL_WINDOW_H_ #define __SCROLL_WINDOW_H_ #include #include #include "ol_color.h" #define OL_SCROLL_WINDOW(obj) GTK_CHECK_CAST (obj, ol_scroll_window_get_type (), OlScrollWindow) #define OL_SCROLL_WINDOW_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, ol_scroll_window_get_type (), OlScrollWindowClass) #define OL_IS_SCROLL_WINDOW(obj) GTK_CHECK_TYPE (obj, ol_scroll_window_get_type ()) #define OL_SCROLL_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ol_scroll_window_get_type (), OlScrollWindowClass)) typedef struct _OlScrollWindow OlScrollWindow; typedef struct _OlScrollWindowClass OlScrollWindowClass; enum OlScrollWindowScrollMode { OL_SCROLL_WINDOW_ALWAYS, OL_SCROLL_WINDOW_BY_LINES, }; struct _OlScrollWindow { /*basic*/ GtkWindow widget; double percentage; GPtrArray *whole_lyrics; gint current_lyric_id; }; struct _OlScrollWindowClass { GtkWindowClass parent_class; }; GtkType ol_scroll_window_get_type (void); /** * @brief create a new Scroll Window. * To destroy the Scroll Window, use g_object_unref */ GtkWidget* ol_scroll_window_new (void); /** * @brief Set the whole lyric of a song * If music changes,the whole lyrics of window will be changed. * @param scroll An OlScrollWindow * @param whole_lyrics The lyrics of a song. NULL means the line has no lyric * currently. The scroll window will increase the ref count of it. */ void ol_scroll_window_set_whole_lyrics(OlScrollWindow *scroll, GPtrArray *whole_lyrics); /** * @brief Sets the progress of the lyrics * @param scroll An OlScrollWindow * @param lyric_id The lyric_line which is currenty being displayed. -1 means the line has no lyric currently. * @param percentage The width percentage of the left part whose color is changed */ void ol_scroll_window_set_progress (OlScrollWindow *scroll, int lyric_id, double percentage); /** * @brief Gets the current line number * The current line is the lyric which is playing currently. * @param scroll An OlScrollWindow * @param line The line number of the current lyric, can be 0 or 1. 0 is the upper line and 1 is the lower */ int ol_scroll_window_get_current_lyric_id (OlScrollWindow *scroll); /** * @brief Sets the font family for an SCROLL Window * * @param scroll An OlScrollWindow; * @param font_name Font family, must not be NULL. The font_name contains style and * size information. Should be able to pass the value to * pango_font_description_from_string() */ void ol_scroll_window_set_font_name (OlScrollWindow *scroll, const char *font_family); /** * @brief Gets the font family for an SCROLL Window * * @param scroll An OlScrollWindow * @return The font name, see the comment of ol_scroll_window_set_font_name */ const char* ol_scroll_window_get_font_name (OlScrollWindow *scroll); /** * Sets the text to be shown * * The text will be shown only if the lyrics are set to be NULL * @param scroll * @param text The text to be set, or NULL. */ void ol_scroll_window_set_text (OlScrollWindow *scroll, const char *text); /** * Sets the opacity of the background * * @param scroll * @param opacity The opacity of the background. 0 being fully transparent * and 1 meansfully opaque. * */ void ol_scroll_window_set_bg_opacity (OlScrollWindow *scroll, double opacity); double ol_scroll_window_get_bg_opacity (OlScrollWindow *scroll); void ol_scroll_window_set_active_color (OlScrollWindow *scroll, OlColor color); OlColor ol_scroll_window_get_active_color (OlScrollWindow *scroll); void ol_scroll_window_set_inactive_color (OlScrollWindow *scroll, OlColor color); OlColor ol_scroll_window_get_inactive_color (OlScrollWindow *scroll); void ol_scroll_window_set_bg_color (OlScrollWindow *scroll, OlColor color); OlColor ol_scroll_window_get_bg_color (OlScrollWindow *scroll); void ol_scroll_window_add_toolbar (OlScrollWindow *scroll, GtkWidget *widget); void ol_scroll_window_remove_toolbar (OlScrollWindow *scroll, GtkWidget *widget); void ol_scroll_window_set_scroll_mode (OlScrollWindow *scroll, enum OlScrollWindowScrollMode mode); enum OlScrollWindowScrollMode ol_scroll_window_get_scroll_mode (OlScrollWindow *scroll); void ol_scroll_window_set_can_seek (OlScrollWindow *scroll, gboolean can_seek); gboolean ol_scroll_window_get_can_seek (OlScrollWindow *scroll); #endif /* __OL_SCROLL_WINDOW_H__ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_search_dialog.c000066400000000000000000000252071345713535600215730ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include "ol_search_dialog.h" #include "ol_gui.h" #include "ol_app.h" #include "ol_config_proxy.h" #include "ol_lyric_candidate_list.h" #include "ol_lyric_source_list.h" #include "ol_intl.h" #include "ol_debug.h" const char *MSG_SEARCHING = N_("Searching lyrics from %s..."); const char *MSG_NOT_FOUND = N_("Ooops, no lyric found :("); const char *MSG_FOUND = N_("%d lyrics found :)"); const char *MSG_SEARCH_FAILURE = N_("Fail to search. Please check network connection"); const char *MSG_SEARCH_CANCELLED = N_("Cancelled"); struct { GtkWidget *window; GtkWidget *candidates_panel; GtkEntry *title; GtkEntry *artist; GtkTreeView *list; GtkLabel *msg; GtkWidget *download; GtkWidget *engine; } widgets = {0}; static OlLyricSourceSearchTask *search_task = NULL; static OlMetadata *global_metadata = NULL; gboolean ol_search_dialog_search_click (GtkWidget *widget, gpointer data); gboolean ol_search_dialog_download_click (GtkWidget *widget, gpointer data); gboolean ol_search_dialog_cancel_click (GtkWidget *widget, gpointer data); static void internal_select_changed (GtkTreeSelection *selection, gpointer data); static gboolean internal_init (); static void ol_search_dialog_search_complete_cb (OlLyricSourceSearchTask *task, enum OlLyricSourceStatus status, GList *results, gpointer userdata); static void ol_search_dialog_search_started_cb (OlLyricSourceSearchTask *task, const gchar *sourceid, const gchar *sourcename, gpointer userdata); static void ol_search_dialog_download_complete_cb (OlLyricSourceDownloadTask *task, enum OlLyricSourceStatus status, const gchar *content, guint len, gpointer userdata); static void internal_select_changed (GtkTreeSelection *selection, gpointer data) { if (widgets.download != NULL) gtk_widget_set_sensitive (GTK_WIDGET (widgets.download), gtk_tree_selection_get_selected (selection, NULL, NULL)); } gboolean ol_search_dialog_download_click (GtkWidget *widget, gpointer data) { ol_log_func (); ol_assert_ret (widgets.list != NULL, FALSE); OlLyricSourceCandidate *candidate; candidate = ol_lyric_candidate_list_get_selected (widgets.list); if (candidate) { OlLyricSourceDownloadTask *task; task = ol_lyric_source_download (ol_app_get_lyric_source (), candidate); gtk_label_set_text (widgets.msg, _("Downloading...")); g_signal_connect (task, "complete", G_CALLBACK (ol_search_dialog_download_complete_cb), ol_metadata_dup (global_metadata)); } return TRUE; } gboolean ol_search_dialog_cancel_click (GtkWidget *widget, gpointer data) { if (widgets.window != NULL) gtk_widget_hide (widgets.window); return TRUE; } gboolean ol_search_dialog_search_click (GtkWidget *widget, gpointer data) { ol_log_func (); OlMetadata *metadata = ol_metadata_new (); ol_metadata_copy (metadata, ol_app_get_current_music ()); if (widgets.title != NULL) ol_metadata_set_title (metadata, gtk_entry_get_text (widgets.title)); if (widgets.artist != NULL) ol_metadata_set_artist (metadata, gtk_entry_get_text (widgets.artist)); gtk_widget_set_sensitive (GTK_WIDGET (widgets.list), FALSE); gtk_widget_set_sensitive (widgets.download, FALSE); GList *sourceids = ol_lyric_source_list_get_active_id_list (GTK_TREE_VIEW(widgets.engine)); search_task = ol_lyric_source_search (ol_app_get_lyric_source (), metadata, sourceids); g_signal_connect (search_task, "complete", G_CALLBACK (ol_search_dialog_search_complete_cb), NULL); g_signal_connect (search_task, "started", G_CALLBACK (ol_search_dialog_search_started_cb), NULL); for (; sourceids; sourceids = g_list_delete_link (sourceids, sourceids)) { g_free (sourceids->data); } return TRUE; } static void ol_search_dialog_search_started_cb (OlLyricSourceSearchTask *task, const gchar *sourceid, const gchar *sourcename, gpointer userdata) { if (widgets.msg != NULL && task == search_task) { char *msg = g_strdup_printf (_(MSG_SEARCHING), sourcename); gtk_label_set_text (widgets.msg, msg); } } static void ol_search_dialog_search_complete_cb (OlLyricSourceSearchTask *task, enum OlLyricSourceStatus status, GList *results, gpointer userdata) { ol_log_func (); if (task != search_task) return; gtk_widget_set_sensitive (GTK_WIDGET (widgets.list), TRUE); ol_lyric_candidate_list_set_list (widgets.list, results); gtk_widget_show (widgets.candidates_panel); if (widgets.msg != NULL) { if (status == OL_LYRIC_SOURCE_STATUS_SUCCESS) { if (results != NULL) { int cnt = 0; GList *iter; for (iter = results; iter; iter = g_list_next (iter)) cnt++; char *msg = g_strdup_printf (_(MSG_FOUND), cnt); gtk_label_set_text (widgets.msg, msg); } else { gtk_label_set_text (widgets.msg, _(MSG_NOT_FOUND)); } } else if (status == OL_LYRIC_SOURCE_STATUS_CANCELLED) { gtk_label_set_text (widgets.msg, _(MSG_SEARCH_CANCELLED)); } else { gtk_label_set_text (widgets.msg, _(MSG_SEARCH_FAILURE)); } } } static void ol_search_dialog_download_complete_cb (OlLyricSourceDownloadTask *task, enum OlLyricSourceStatus status, const gchar *content, guint len, gpointer userdata) { OlMetadata *metadata = userdata; if (status == OL_LYRIC_SOURCE_STATUS_SUCCESS) { GError *error = NULL; gchar *uri = ol_lyrics_set_content (ol_app_get_lyrics_proxy (), metadata, content, &error); if (!uri) { ol_errorf ("Set content failed: %s\n", error->message); g_error_free (error); gtk_label_set_text (widgets.msg, _("Download complete, but fail to assign to the track")); } else { gtk_label_set_text (widgets.msg, _("Download complete")); ol_debugf ("Set content to %s\n", uri); g_free (uri); } } else if (status == OL_LYRIC_SOURCE_STATUS_FALIURE) { gtk_label_set_text (widgets.msg, _("Fail to download lyric")); } ol_metadata_free (metadata); } static gboolean internal_init () { if (widgets.window == NULL) { widgets.window = ol_gui_get_widget ("search-dialog"); if (widgets.window == NULL) { ol_error ("Search Dialog not found"); return FALSE; } g_signal_connect (G_OBJECT (widgets.window), "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL); widgets.title = GTK_ENTRY (ol_gui_get_widget ("search-title")); widgets.artist = GTK_ENTRY (ol_gui_get_widget ("search-artist")); widgets.candidates_panel = ol_gui_get_widget ("search-candidates"); widgets.list = GTK_TREE_VIEW (ol_gui_get_widget ("search-candidates-list")); widgets.msg = GTK_LABEL (ol_gui_get_widget ("search-msg")); widgets.download = ol_gui_get_widget ("search-download"); ol_lyric_candidate_list_init (widgets.list, G_CALLBACK (internal_select_changed)); widgets.engine = ol_gui_get_widget ("search-engine"); ol_lyric_source_list_init (GTK_TREE_VIEW (widgets.engine)); } return widgets.window != NULL; } void ol_search_dialog_show () { if (!internal_init ()) return; if (GTK_WIDGET_VISIBLE (widgets.window)) return; ol_lyric_candidate_list_clear (widgets.list); if (global_metadata == NULL) global_metadata = ol_metadata_new (); ol_metadata_copy (global_metadata, ol_app_get_current_music ()); gtk_entry_set_text (widgets.title, ol_metadata_get_title (global_metadata)); gtk_entry_set_text (widgets.artist, ol_metadata_get_artist (global_metadata)); gtk_widget_set_sensitive (widgets.download, FALSE); gtk_label_set_text (widgets.msg, ""); OlLyricSource *lyric_source = ol_app_get_lyric_source (); GList *info_list = ol_lyric_source_list_sources (lyric_source); ol_lyric_source_list_set_info_list (GTK_TREE_VIEW (widgets.engine), info_list); for (; info_list; info_list = g_list_delete_link (info_list, info_list)) { ol_lyric_source_info_free (info_list->data); } gtk_widget_show (widgets.window); } osdlyrics-0.5.5~rc1+dfsg1/src/ol_search_dialog.h000066400000000000000000000016651345713535600216020ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_SEARCH_DIALOG_H_ #define _OL_SEARCH_DIALOG_H_ void ol_search_dialog_show (); #endif /* _OL_SEARCH_DIALOG_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_stock.c000066400000000000000000000034111345713535600201230ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include "ol_stock.h" static GtkIconFactory *icon_factory = NULL; const char *ICON_LIST[] = { OL_STOCK_TRAYICON, OL_STOCK_LOADING, OL_STOCK_OSD_BG, OL_STOCK_OSD_PLAY, OL_STOCK_OSD_PAUSE, OL_STOCK_OSD_STOP, OL_STOCK_OSD_PREV, OL_STOCK_OSD_NEXT, OL_STOCK_SCROLL_CLOSE, }; void ol_stock_init () { if (icon_factory == NULL) { icon_factory = gtk_icon_factory_new (); int i; for (i = 0; i < G_N_ELEMENTS (ICON_LIST); i++) { GtkIconSet *icon_set = gtk_icon_set_new (); GtkIconSource *icon_source = gtk_icon_source_new (); gtk_icon_source_set_icon_name (icon_source, ICON_LIST[i]); gtk_icon_set_add_source (icon_set, icon_source); gtk_icon_source_free (icon_source); gtk_icon_factory_add (icon_factory, ICON_LIST[i], icon_set); } gtk_icon_factory_add_default (icon_factory); } } osdlyrics-0.5.5~rc1+dfsg1/src/ol_stock.h000066400000000000000000000025021345713535600201300ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_STOCK_H_ #define _OL_STOCK_H_ #define OL_STOCK_TRAYICON "osdlyrics-trayicon" #define OL_STOCK_LOADING "osdlyrics-loading" #define OL_STOCK_OSD_BG "osdlyrics-osd-bg" #define OL_STOCK_OSD_PLAY "osdlyrics-osd-play" #define OL_STOCK_OSD_PAUSE "osdlyrics-osd-pause" #define OL_STOCK_OSD_STOP "osdlyrics-osd-stop" #define OL_STOCK_OSD_PREV "osdlyrics-osd-prev" #define OL_STOCK_OSD_NEXT "osdlyrics-osd-next" #define OL_STOCK_SCROLL_CLOSE "osdlyrics-scroll-close" void ol_stock_init (); #endif /* _OL_STOCK_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_timeline.c000066400000000000000000000065731345713535600206220ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2012 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include "ol_timeline.h" #include #include #include "ol_debug.h" enum OlTimelineStatus { OL_TIMELINE_PLAYING, OL_TIMELINE_PAUSED, OL_TIMELINE_STOPPED }; struct _OlTimeline { int cached_time; struct timeval cached_time_begin; enum OlTimelineStatus status; int accuracy; }; OlTimeline * ol_timeline_new (void) { OlTimeline *timeline = g_new (OlTimeline, 1); timeline->cached_time = 0; timeline->status = OL_TIMELINE_STOPPED; timeline->accuracy = 1000; return timeline; } void ol_timeline_free (OlTimeline *timeline) { g_free (timeline); } void ol_timeline_play (OlTimeline *timeline) { ol_assert (timeline != NULL); if (timeline->status == OL_TIMELINE_PLAYING) return; timeline->status = OL_TIMELINE_PLAYING; gettimeofday (&timeline->cached_time_begin, NULL); } void ol_timeline_pause (OlTimeline *timeline) { ol_assert (timeline != NULL); if (timeline->status == OL_TIMELINE_PAUSED) return; if (timeline->status == OL_TIMELINE_PLAYING) timeline->cached_time = ol_timeline_get_time (timeline); timeline->status = OL_TIMELINE_PAUSED; } void ol_timeline_stop (OlTimeline *timeline) { ol_assert (timeline != NULL); timeline->cached_time = 0; timeline->status = OL_TIMELINE_STOPPED; } void ol_timeline_maybe_set_time (OlTimeline *timeline, gint64 time_in_ms) { ol_assert (timeline != NULL); gint64 current_time = ol_timeline_get_time (timeline); if (abs (current_time - time_in_ms) > timeline->accuracy) { ol_timeline_set_time (timeline, time_in_ms); } } void ol_timeline_set_time (OlTimeline *timeline, gint64 time_in_ms) { ol_assert (timeline != NULL); if (timeline->status == OL_TIMELINE_STOPPED) return; timeline->cached_time = time_in_ms; gettimeofday (&timeline->cached_time_begin, NULL); } gint64 ol_timeline_get_time (OlTimeline *timeline) { ol_assert_ret (timeline != NULL, 0); if (timeline->status == OL_TIMELINE_PLAYING) { struct timeval current_time; gettimeofday (¤t_time, NULL); gint64 time = timeline->cached_time + (current_time.tv_sec - timeline->cached_time_begin.tv_sec) * 1000 + (current_time.tv_usec - timeline->cached_time_begin.tv_usec) / 1000; return time; } else { return timeline->cached_time; } } void ol_timeline_set_accuracy (OlTimeline *timeline, guint accuracy) { ol_assert (timeline != NULL); timeline->accuracy = accuracy; } guint ol_timeline_get_accuracy (OlTimeline *timeline) { ol_assert_ret (timeline != NULL, 0); return timeline->accuracy; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_timeline.h000066400000000000000000000060431345713535600206170ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2012 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_TIMELINE_H_ #define _OL_TIMELINE_H_ #include typedef struct _OlTimeline OlTimeline; /** * Create a new timeline. * * @return The new timeline. Its status is stopped, time is 0, and accuracy is * 1000 milliseconds. */ OlTimeline *ol_timeline_new (void); /** * Free a timeline * * @param timeline The timeline to be freed. */ void ol_timeline_free (OlTimeline *timeline); /** * Change status of a timeline to playing. * * The time of the timeline will keep increasing. * @param timeline A timeline. */ void ol_timeline_play (OlTimeline *timeline); /** * Change status of a timeline to paused. * * The time of the timeline will not be changed. * @param timeline A timeline. */ void ol_timeline_pause (OlTimeline *timeline); /** * Change status of a timeline to stopped. * * The time of the timeline will be reset to 0. * @param timeline A timeline. */ void ol_timeline_stop (OlTimeline *timeline); /** * Set time of a timeline if neccessary. * * If the difference between time of the timeline and the specified time is greater * than accuracy of the timeline, the time will be set. Otherwise the time will not * change. * * It is suggestted to use this function if the precesion of time_in_ms is greater * than milliseconds. * * @param timeline A timeline. * @param time_in_ms The time to set, in milliseconds. */ void ol_timeline_maybe_set_time (OlTimeline *timeline, gint64 time_in_ms); /** * Set time of a timeline. * * @param timeline A timeline. * @param time_in_ms The time to set, in milliseconds. */ void ol_timeline_set_time (OlTimeline *timeline, gint64 time_in_ms); /** * Get current time of a timeline. * * @param timeline A timeline. * * @return */ gint64 ol_timeline_get_time (OlTimeline *timeline); /** * Set accuracy of a timeline. * * Accuracy only affects ol_timeline_maybe_set_time(). * @param timeline * @param accuracy */ void ol_timeline_set_accuracy (OlTimeline *timeline, guint accuracy); /** * Get accuracy of a timeline. * * @param timeline A timeline. * * @return */ guint ol_timeline_get_accuracy (OlTimeline *timeline); #endif /* _OL_TIMELINE_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_trayicon.c000066400000000000000000000143541345713535600206400ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include "config.h" #if HAVE_APP_INDICATOR #include #endif /* HAVE_APP_INDICATOR */ #include "ol_trayicon.h" #include "ol_app.h" #include "ol_intl.h" #include "ol_menu.h" #include "ol_stock.h" #include "ol_player.h" #include "ol_config_proxy.h" #include "ol_debug.h" #if HAVE_APP_INDICATOR static AppIndicator *indicator = NULL; #else /* HAVE_APP_INDICATOR */ static const char *UNKNOWN_TITLE = N_("Unknown title"); static const char *UNKNOWN_ARTIST = N_("Unknown artist"); static const char *INFO_FORMAT = "%s\n" " %s"; static const char *INFO_FORMAT_ALBUM = "%s\n" " %s - %s"; static GtkStatusIcon *status_icon = NULL; static gboolean internal_query_tooltip (GtkStatusIcon *status_icon, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data); static void activate (GtkStatusIcon* status_icon, gpointer user_data) { OlConfigProxy *config = ol_config_proxy_get_instance (); ol_config_proxy_set_bool (config, ".visible", !ol_config_proxy_get_bool (config, ".visible")); } static gboolean internal_query_tooltip (GtkStatusIcon *status_icon, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data) { OlMetadata *metadata = ol_app_get_current_music (); if (metadata == NULL || (ol_metadata_get_title (metadata) == NULL && ol_metadata_get_artist (metadata) == NULL)) { gtk_tooltip_set_text (tooltip, _("OSD Lyrics")); } else { const char *title = ol_metadata_get_title (metadata); if (title == NULL) title = _(UNKNOWN_TITLE); const char *artist = ol_metadata_get_artist (metadata); if (artist == NULL) artist = _(UNKNOWN_ARTIST); const char *album = ol_metadata_get_album (metadata); char *markup = NULL; if (album == NULL) { markup = g_markup_printf_escaped (INFO_FORMAT, title, artist); } else { markup = g_markup_printf_escaped (INFO_FORMAT_ALBUM, title, artist, album); } gtk_tooltip_set_markup (tooltip, markup); g_free (markup); OlPlayer *player = ol_app_get_player (); const char *icon_path = ol_player_get_icon_path (player); GdkPixbuf *icon = NULL; if (icon_path != NULL) { icon = gdk_pixbuf_new_from_file_at_scale (icon_path, 48, 48, FALSE, NULL); } gtk_tooltip_set_icon (tooltip, icon); if (icon != NULL) g_object_unref (icon); } return TRUE; } static void popup (GtkStatusIcon *status_icon, guint button, guint activate_time, gpointer data) { GtkWidget *popup_menu = ol_menu_get_popup (); gtk_menu_popup (GTK_MENU(popup_menu), NULL, NULL, gtk_status_icon_position_menu, status_icon, button, activate_time); } static void scroll (GtkStatusIcon *icon, GdkEventScroll *event, gpointer userdata) { int sign = event->direction == GDK_SCROLL_UP ? 1 : -1; ol_app_adjust_lyric_offset(sign * 200); } #endif /* HAVE_APP_INDICATOR */ void ol_trayicon_init () { #if HAVE_APP_INDICATOR if (indicator == NULL) { indicator = app_indicator_new (PACKAGE, OL_STOCK_TRAYICON, APP_INDICATOR_CATEGORY_APPLICATION_STATUS); app_indicator_set_status (indicator, APP_INDICATOR_STATUS_ACTIVE); app_indicator_set_attention_icon (indicator, OL_STOCK_TRAYICON); app_indicator_set_menu (indicator, GTK_MENU (ol_menu_get_popup ())); } #else if (status_icon == NULL) { status_icon = gtk_status_icon_new_from_icon_name (OL_STOCK_TRAYICON); gtk_status_icon_set_tooltip_text (status_icon, _(PROGRAM_NAME)); gtk_status_icon_set_name (status_icon, PACKAGE_NAME); gtk_status_icon_set_title (status_icon, _(PROGRAM_NAME)); gtk_status_icon_set_visible (status_icon, TRUE); /* Connect signals */ g_signal_connect (G_OBJECT (status_icon), "popup-menu", G_CALLBACK (popup), NULL); g_signal_connect (G_OBJECT (status_icon), "query-tooltip", G_CALLBACK (internal_query_tooltip), NULL); g_signal_connect (G_OBJECT (status_icon), "activate", G_CALLBACK (activate), NULL); g_signal_connect (G_OBJECT (status_icon), "scroll-event", G_CALLBACK (scroll), NULL); } #endif /* HAVE_APP_INDICATOR */ } void ol_trayicon_free () { #if HAVE_APP_INDICATOR if (indicator != NULL) { g_object_unref (indicator); indicator = NULL; } #else if (status_icon != NULL) { g_object_unref (status_icon); status_icon = NULL; } #endif } void ol_trayicon_status_changed (enum OlPlayerStatus status) { #if HAVE_APP_INDICATOR if (indicator != NULL) app_indicator_set_menu (indicator, GTK_MENU (ol_menu_get_popup ())); #else /* Do nothing */ #endif } osdlyrics-0.5.5~rc1+dfsg1/src/ol_trayicon.h000066400000000000000000000022101345713535600206310ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef __OL_TRAYICON_H__ #define __OL_TRAYICON_H__ enum OlPlayerStatus; /** * @brief give a Entrance to do trayicon job * */ void ol_trayicon_init (); /** * @brief Notifiy the trayicon that the playing status has changed * */ void ol_trayicon_status_changed (enum OlPlayerStatus status); void ol_trayicon_free (); #endif osdlyrics-0.5.5~rc1+dfsg1/src/ol_utils.c000066400000000000000000000230341345713535600201430ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include #include #include #include #include #include #include #include #include "ol_utils.h" #include "ol_debug.h" const gchar* ol_get_string_from_hash_table (GHashTable *hash_table, const gchar *key) { if (!hash_table) return NULL; GValue *value; value = (GValue *) g_hash_table_lookup(hash_table, key); if (value != NULL && G_VALUE_HOLDS_STRING (value)) { return (const gchar*) g_value_get_string (value); } else { ol_debugf ("Type of %s is %s, not string\n", key, value != NULL ? G_VALUE_TYPE_NAME (value) : "NULL"); return NULL; } } gchar** ol_get_str_list_from_hash_table (GHashTable *hash_table, const gchar *key) { if (!hash_table) return NULL; GValue *value; value = (GValue *) g_hash_table_lookup(hash_table, key); if (value != NULL && (G_VALUE_TYPE (value) == G_TYPE_STRV || G_VALUE_HOLDS_BOXED (value))) { return (gchar**) g_value_get_boxed (value); } else { ol_debugf ("Type of %s is %s, not string list\n", key, value != NULL ? G_VALUE_TYPE_NAME (value) : "NULL"); return NULL; } } gint ol_get_int_from_hash_table (GHashTable *hash_table, const gchar *key) { if (!hash_table) return -1; GValue *value; value = (GValue *) g_hash_table_lookup(hash_table, key); if (value != NULL && G_VALUE_HOLDS_INT(value)) return g_value_get_int (value); else return -1; } guint ol_get_uint_from_hash_table (GHashTable *hash_table, const gchar *key) { if (!hash_table) return 0; GValue *value; value = (GValue *) g_hash_table_lookup(hash_table, key); if (value != NULL && G_VALUE_HOLDS_UINT(value)) return g_value_get_uint (value); else return 0; } gint64 ol_get_int64_from_hash_table (GHashTable *hash_table, const gchar *key) { if (!hash_table) return -1; GValue *value; value = (GValue *) g_hash_table_lookup(hash_table, key); if (value != NULL && G_VALUE_HOLDS_INT64(value)) return g_value_get_int64 (value); else return -1; } gboolean ol_is_string_empty (const char *str) { if (str == NULL) return TRUE; int len = strlen (str); int i; for (i = 0; i < len; i++) { if (str[i] != ' ') return FALSE; } return TRUE; } #ifdef PATH_MAX static int pathmax = PATH_MAX; #else static int pathmax = 0; #endif #define PATH_MAX_GUESS 1024 char * ol_path_alloc(void) { char *ptr; if(pathmax == 0) { errno = 0; if((pathmax = pathconf("/", _PC_PATH_MAX)) < 0) { if(errno == 0) { pathmax = PATH_MAX_GUESS; } else { ol_debugf ("pathconf error for _PC_PATH_MAX\n"); return NULL; } } else { pathmax++; } } if((ptr = calloc(pathmax, sizeof(char))) == NULL) { ol_debugf ("malloc error for pathname"); return NULL; } return ptr; } int ol_stricmp (const char *str1, const char *str2, const ssize_t count) { const char *ptr1 = str1; const char *ptr2 = str2; int len1 = strlen(str1); int len2 = strlen(str2); int min = len1 > len2 ? len2 : len1; if (count >= 0 && count < min) min = count; while((ptr1 < str1+min) && (ptr2 < str2+min)) { if (isalpha (*ptr1) && isalpha (*ptr2)) { if (tolower (*ptr1) != tolower (*ptr2)) return *ptr1 > *ptr2 ? 1 : -1; } else { if (*ptr1 != *ptr2) return *ptr1 > *ptr2 ? 1 : -1; } ptr1++; ptr2++; } return 0; } size_t ol_lcs (const char *str1, const char *str2) { if (str1 == NULL || str2 == NULL) return 0; size_t len[2]; len[0] = strlen (str1); len[1] = strlen (str2); if (len[0] == 0 || len[1] == 0) return 0; size_t i, j; int **data; data = g_new (int*, len[0]); for (i = 0; i < len[0]; i++) { data[i] = g_new (int, len[1]); for (j = 0; j < len[1]; j++) { data[i][j] = 0; if (i > 0 && j > 0) { data[i][j] = data[i - 1][j - 1]; } if ((tolower (str1[i]) == tolower (str2[j]))) data[i][j]++; if (i > 0 && data[i - 1][j] > data[i][j]) data[i][j] = data[i - 1][j]; if (j > 0 && data[i][j - 1] > data[i][j]) data[i][j] = data[i][j - 1]; } } int ret = data[len[0] - 1][len[1] - 1]; for (i = 0; i < len[0]; i++) g_free (data[i]); g_free (data); return ret; } char* ol_strnncpy (char *dest, size_t dest_len, const char *src, size_t src_len) { if (dest == NULL || dest_len <= 0 || src == NULL || src_len < 0) return NULL; size_t src_real_len = strlen (src); if (src_len > src_real_len) src_len = src_real_len; if (dest_len < src_len + 1) /* The space in dest is not enough */ { dest[0] = '\0'; return NULL; } strncpy (dest, src, src_len); dest[src_len] = '\0'; return dest + src_len; } gboolean ol_streq (const char *str1, const char *str2) { if (str1 == str2) return TRUE; if (str1 == NULL || str2 == NULL) return FALSE; return (strcmp (str1, str2) == 0); } char * ol_strptrcpy (char **dest, const char *src) { ol_assert_ret (dest != NULL, NULL); if (*dest != NULL) g_free (*dest); if (src == NULL) *dest = NULL; else *dest = g_strdup (src); return *dest; } char * ol_split_a_line (char *str) { ol_assert_ret (str != NULL, NULL); while (*str != '\n' && *str != '\0') str++; if (*str == '\n') { *str = '\0'; return str + 1; } else return NULL; } char * ol_trim_string (char *str) { if (str == NULL) return NULL; while (isspace (*str)) { *str = '\0'; str++; } if (*str == '\0') /* The whole string is space */ return NULL; size_t len = strlen (str) - 1; while (isspace (str[len])) { str[len] = '\0'; len--; } return str; } gboolean ol_path_is_file (const char *filename) { /* ol_log_func (); */ if (filename == NULL) return FALSE; struct stat buf; /* ol_debugf (" stat:%d mode:%d\n", stat (filename, &buf), (int)buf.st_mode); */ return stat (filename, &buf) == 0 && S_ISREG (buf.st_mode); } ssize_t ol_file_len (const char *filename) { ol_assert_ret (filename != NULL, -1); struct stat buf; if (stat (filename, &buf) != 0) return -1; return buf.st_size; } char* ol_encode_hex (const char *data, ssize_t len) { ol_assert_ret (data != NULL, NULL); if (len < 0) len = strlen (data); size_t hex_len = len * 2 + 1; gchar *hex = g_new (gchar, hex_len); gchar *current = hex; for (; len > 0; len--, current += 2, data++) { sprintf (current, "%02x", (unsigned char)*data); } *current = '\0'; return hex; } void ol_path_splitext (const char *path, char **root, char **ext) { if (path != NULL) { char *period = strrchr (path, '.'); if (period != NULL && (period - path == 0 || *(period - 1) == G_DIR_SEPARATOR)) period = NULL; if (period == NULL) { if (root != NULL) *root = g_strdup (path); if (ext != NULL) *ext = NULL; } else { if (root != NULL) *root = g_strndup (path, period - path); if (ext != NULL) *ext = g_strdup (period); } } else { if (root != NULL) *root = NULL; if (ext != NULL) *ext = NULL; } } gint ol_app_info_cmp (GAppInfo *a, GAppInfo *b) { return strcasecmp (g_app_info_get_display_name (a), g_app_info_get_display_name (b)); } gboolean ol_launch_app (const char *cmdline) { ol_assert_ret (cmdline != NULL, FALSE); GAppInfo* app = g_app_info_create_from_commandline (cmdline, "", 0, NULL); gboolean ret = g_app_info_launch (app, NULL, NULL, NULL); g_object_unref (G_OBJECT (app)); return ret; } gboolean ol_traverse_dir (const char *dirname, gboolean recursive, gboolean (*traverse_func) (const char *path, const char *filename, gpointer userdata), gpointer userdata) { ol_assert_ret (dirname != NULL, FALSE); ol_assert_ret (traverse_func != NULL, FALSE); GError *error = NULL; GDir *dir = g_dir_open (dirname, 0, &error); if (!dir) { ol_error ("Cannot open directory %s: %s\n", dirname, error->message); return FALSE; } const gchar *filename = NULL; while ((filename = g_dir_read_name (dir)) != NULL) { if (!traverse_func (dirname, filename, userdata)) return FALSE; if (recursive) { gchar *filepath = g_build_path (G_DIR_SEPARATOR_S, dirname, filename, NULL); if (g_file_test (filepath, G_FILE_TEST_IS_DIR)) if (!ol_traverse_dir (filepath, recursive, traverse_func, userdata)) { g_free (filepath); return FALSE; } g_free (filepath); } } return TRUE; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_utils.h000066400000000000000000000162211345713535600201500ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef __OL_UTILS_H__ #define __OL_UTILS_H__ #include #include #include #define ol_get_array_len(arr) (sizeof (arr) / sizeof (arr[0])) const gchar* ol_get_string_from_hash_table (GHashTable *hash_table, const gchar *name); gchar** ol_get_str_list_from_hash_table (GHashTable *hash_table, const gchar *name); gint ol_get_int_from_hash_table (GHashTable *hash_table, const gchar *name); guint ol_get_uint_from_hash_table (GHashTable *hash_table, const gchar *name); gint64 ol_get_int64_from_hash_table (GHashTable *hash_table, const gchar *name); /** * @brief Checks if a string is empty * A string is empty if it is NULL or contains only white spaces * @param str The string to be checked * * @return If the string is empty, returns true */ gboolean ol_is_string_empty (const char *str); /** * @brief allocate memory for pathname, from APUE * * @return pointer to this memory if success, or NULL. Should be free with free() */ char* ol_path_alloc(void); /** * @brief comparing str1 with str2 case insensitive * * @param str1 * @param str2 * @param count The number of characters to be compared with, or -1 to compare * the whole strings * * @return the same with the function: strcmp in */ int ol_stricmp(const char *str1, const char *str2, const ssize_t count); /** * @brief Copy the source string to the destination string * If the length to be coyped is larger than that of the destination string, * the copying will fail, and returns NULL. * @param dest Destination string * @param dest_len The length of destination string, including '\0' * @param src Source string * @param src_len The length of source string to be copyed * * @return The end of the destination if suceeded, or NULL if failed. */ char* ol_strnncpy (char *dest, size_t dest_len, const char *src, size_t src_len); /** * @Calculates the largest common substring for two strings * * @param str1 * @param str2 * * @return the length of the largest common substring */ size_t ol_lcs (const char *str1, const char *str2); /** * @Checks whether two strings are equale * Strings are different if one of them is NULL but another not, * or their content are different * * @param str1 The first string or NULL * @param str2 The second string or NULL * * @return TRUE if they are equal */ int ol_streq (const char *str1, const char *str2); /** * @Copy the content of a string pointer to another * * @param dest The target string pointer. If it doesn't point to NULL, \ * it will be free with g_free * @param src The source string. If it is not NULL, \ * it will be copyed with g_strdup, the dupped string will be assigned to \ * dest. Otherwise the dest will be NULL after copied. * * @return The string that dest points to after copying */ char *ol_strptrcpy (char **dest, const char *src); /** * @brief Find the first '\n', change it into '\0' and return the pointer to \ * the next line * * @param str the string to be splited, cannot be NULL * * @return The pointer to the next line. If no '\n' exists, return NULL */ char *ol_split_a_line (char *str); /** * @brief Fill the heading and tailing white spaces of str with '\0', \ * and returns the pointer to the first non-space character. \ * If the string is empty, simply return NULL * * @param str The string to be trimed. If it is NULL, returns NULL. * * @return The pointer to the first non-space character, \ * or NULL if str is empty or NULL. */ char *ol_trim_string (char *str); /** * @brief Check whether a file exists and is a regular file. * * @param filename The path and fullname of the file * * @return TRUE if the file in the path exists and is a regular file */ gboolean ol_path_is_file (const char *filename); /** * @brief Gets the length of a file * * @param filename The full path of the file * * @return The length of a file, or negative if error occurs */ ssize_t ol_file_len (const char *filename); /** * Converts a string to hex representation. * * @param data The string to encode * @param len The length to encode, or -1 if the string is NUL-terminated * * @return The encoded string, should be freed with g_free. */ char* ol_encode_hex (const char *data, ssize_t len); /** * Split a path into two parts (root, ext), the ext is the extension of a file * name, which begins with a period and contains at most period. root + ext = path. * * If the filename begins with a period and has no extension, the filename will not * be stored in the ext part. * * @param path The path to be splited. * @param root The path without ext. Should be freed with g_free. * @param ext The extension of filename, or NULL if no extension found. * Should be freed with g_free. */ void ol_path_splitext (const char *path, char **root, char **ext); /** * Compares two GAppInfo according to its name. * * The comparison is case-insensitive * * @param a * @param b * * @return 0 if the name of a equals to be. * Negative if a is less than b. * Otherwise a is greater than b. */ gint ol_app_info_cmp (GAppInfo *a, GAppInfo *b); /** * Launch a commandline from GAppInfo * * @param cmdline The commandline to launch. %f and %U will be ignored * * @return TRUE on successful launch, FALSE otherwise. */ gboolean ol_launch_app (const char *cmdline); /** * Pass names of all files and directories in the specified directory to given * function. * * @param dir The path of the directroy * @param recursive Whether to visit subdirectories in the directory * @param traverse_func The function to receive the file name. traverse_func will be * called on each file once. If it returns FALSE, traversal will * be ended. * @param userdata * * @return TRUE if the traversal is no interrupted because traverse_func returns * FALSE. The return value is intend to be used in recursive traversal. */ gboolean ol_traverse_dir (const char *dir, gboolean recursive, gboolean (*traverse_func) (const char *path, const char *filename, gpointer userdata), gpointer userdata); #endif // __OL_UTILS_H__ osdlyrics-0.5.5~rc1+dfsg1/src/ol_utils_cmdline.c000066400000000000000000000034641345713535600216430ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include /* For WEXITSTATUS */ #include "ol_utils_cmdline.h" #include "ol_debug.h" gboolean ol_cmd_get_string (const char *cmd, char **retval) { /* ol_log_func (); */ /* ol_debugf (" cmd: %s\n", cmd); */ ol_assert_ret (cmd != NULL, FALSE); int flags = 0; if (retval == NULL) flags |= G_SPAWN_STDOUT_TO_DEV_NULL; flags |= G_SPAWN_STDERR_TO_DEV_NULL; int exit_status; if (!g_spawn_command_line_sync (cmd, retval, NULL, &exit_status, NULL)) return FALSE; if (WEXITSTATUS(exit_status) != 0) return FALSE; return TRUE; } gboolean ol_cmd_get_int (const char *cmd, int *retval) { char *output; gboolean ret = ol_cmd_get_string (cmd, &output); if ((output != NULL) && (retval != NULL)) { sscanf (output, "%d", retval); } if (output != NULL) g_free (output); return ret; } gboolean ol_cmd_exec (const char *cmd) { char *output = NULL; gboolean ret = ol_cmd_get_string (cmd, &output); if (output != NULL) g_free (output); return ret; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_utils_cmdline.h000066400000000000000000000042671345713535600216520ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ /** * @file ol_utils_cmdline.h * @author Tiger Soldier * @date Sat Jan 9 11:58:23 2010 * * @brief Utilities for running applications from command line. * * */ #ifndef _OL_UTILS_CMDLINE_H_ #define _OL_UTILS_CMDLINE_H_ #include /** * @brief Execute a command and get its output as a string * * @param cmd The command to execute * @param retval The return location of the output string, or NULL. \ * Note: The passed string will NOT be freed. \ * The return string should be freed with g_free. * * @return If execute failed or the exit code of command is not 0, return FALSE */ gboolean ol_cmd_get_string (const char *cmd, char **retval); /** * @brief Execute a command and get its output as an integer * * @param cmd The command to execute * @param retval The return location of the output integer, or NULL. \ * If the output cannot be parsed as a number, it will remain what it was. * * @return If execute failed or the exit code of command is not 0, return FALSE */ gboolean ol_cmd_get_int (const char *cmd, int *retval); /** * @brief Execute a command and get its output as a string * * @param cmd The command to execute * * @return If execute failed or the exit code of command is not 0, return FALSE */ gboolean ol_cmd_exec (const char *cmd); #endif /* _OL_UTILS_CMDLINE_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_utils_dbus.c000066400000000000000000000371261345713535600211670ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include "ol_utils_dbus.h" #include "ol_debug.h" static const char *DBUS_NAME = "org.freedesktop.DBus"; static const char *DBUS_IFACE = "org.freedesktop.DBus"; static const char *DBUS_PATH = "/"; static const char *DBUS_LIST_NAMES = "ListNames"; static DBusGConnection *connection = NULL; static GError *error = NULL; DBusGConnection *ol_dbus_get_connection () { if (connection == NULL) { connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); if (connection == NULL) { ol_debugf ("get connection failed: %s\n", error->message); g_error_free(error); error = NULL; } } return connection; } gboolean ol_dbus_get_string (DBusGProxy *proxy, const gchar *method, gchar **returnval) { ol_assert_ret (proxy != NULL, FALSE); ol_assert_ret (method != NULL, FALSE); ol_assert_ret (returnval != NULL, FALSE); if (dbus_g_proxy_call (proxy, method, NULL, G_TYPE_INVALID, G_TYPE_STRING, returnval, G_TYPE_INVALID)) { return TRUE; } else { return FALSE; } } gboolean ol_dbus_get_string_with_str_arg (DBusGProxy *proxy, const gchar *method, const gchar *arg, gchar **returnval) { ol_assert_ret (proxy != NULL, FALSE); ol_assert_ret (method != NULL, FALSE); ol_assert_ret (returnval != NULL, FALSE); ol_assert_ret (arg != NULL, FALSE); if (dbus_g_proxy_call (proxy, method, NULL, G_TYPE_STRING, arg, G_TYPE_INVALID, G_TYPE_STRING, returnval, G_TYPE_INVALID)) { return TRUE; } else { return FALSE; } } gboolean ol_dbus_set_string (DBusGProxy *proxy, const gchar *method, const gchar *value) { ol_assert_ret (proxy != NULL, FALSE); ol_assert_ret (method != NULL, FALSE); ol_assert_ret (value != NULL, FALSE); return dbus_g_proxy_call (proxy, method, NULL, G_TYPE_STRING, value, G_TYPE_INVALID, G_TYPE_INVALID); } gboolean ol_dbus_get_uint (DBusGProxy *proxy, const gchar *method, guint *returnval) { ol_assert_ret (proxy != NULL, FALSE); ol_assert_ret (method != NULL, FALSE); ol_assert_ret (returnval != NULL, FALSE); GError *error = NULL; if (dbus_g_proxy_call (proxy, method, &error, G_TYPE_INVALID, G_TYPE_UINT, returnval, G_TYPE_INVALID)) { return TRUE; } else { ol_errorf ("call %s failed: %s\n", method, error->message); g_error_free (error); return FALSE; } } gboolean ol_dbus_set_uint (DBusGProxy *proxy, const gchar *method, guint value) { ol_assert_ret (proxy != NULL, FALSE); ol_assert_ret (method != NULL, FALSE); GError *error = NULL; if (dbus_g_proxy_call (proxy, method, &error, G_TYPE_UINT, value, G_TYPE_INVALID, G_TYPE_INVALID)) { return TRUE; } else { ol_errorf ("call %s failed: %s\n", method, error->message); g_error_free (error); return FALSE; } } gboolean ol_dbus_get_int64 (DBusGProxy *proxy, const gchar *method, gint64 *returnval) { ol_assert_ret (proxy != NULL, FALSE); ol_assert_ret (method != NULL, FALSE); ol_assert_ret (returnval != NULL, FALSE); GError *error = NULL; if (dbus_g_proxy_call (proxy, method, &error, G_TYPE_INVALID, G_TYPE_INT64, returnval, G_TYPE_INVALID)) { return TRUE; } else { ol_errorf ("call %s failed: %s\n", method, error->message); g_error_free (error); return FALSE; } } gboolean ol_dbus_set_int64 (DBusGProxy *proxy, const gchar *method, gint64 value) { ol_assert_ret (proxy != NULL, FALSE); ol_assert_ret (method != NULL, FALSE); GError *error = NULL; if (dbus_g_proxy_call (proxy, method, &error, G_TYPE_INT64, value, G_TYPE_INVALID, G_TYPE_INVALID)) { return TRUE; } else { ol_errorf ("call %s failed: %s\n", method, error->message); g_error_free (error); return FALSE; } } gboolean ol_dbus_get_int (DBusGProxy *proxy, const gchar *method, gint *returnval) { ol_assert_ret (proxy != NULL, FALSE); ol_assert_ret (method != NULL, FALSE); ol_assert_ret (returnval != NULL, FALSE); GError *error = NULL; if (dbus_g_proxy_call (proxy, method, &error, G_TYPE_INVALID, G_TYPE_INT, returnval, G_TYPE_INVALID)) { return TRUE; } else { ol_errorf ("call %s failed: %s\n", method, error->message); g_error_free (error); return FALSE; } } gboolean ol_dbus_set_int (DBusGProxy *proxy, const gchar *method, gint value) { ol_assert_ret (proxy != NULL, FALSE); ol_assert_ret (method != NULL, FALSE); GError *error = NULL; if (dbus_g_proxy_call (proxy, method, &error, G_TYPE_INT, value, G_TYPE_INVALID, G_TYPE_INVALID)) { return TRUE; } else { ol_errorf ("call %s failed: %s\n", method, error->message); g_error_free (error); return FALSE; } } gboolean ol_dbus_get_uint8 (DBusGProxy *proxy, const gchar *method, guint8 *returnval) { ol_assert_ret (proxy != NULL, FALSE); ol_assert_ret (method != NULL, FALSE); ol_assert_ret (returnval != NULL, FALSE); return dbus_g_proxy_call (proxy, method, NULL, G_TYPE_INVALID, G_TYPE_UCHAR, returnval, G_TYPE_INVALID); } gboolean ol_dbus_set_uint8 (DBusGProxy *proxy, const gchar *method, guint8 value) { ol_assert_ret (proxy != NULL, FALSE); ol_assert_ret (method != NULL, FALSE); return dbus_g_proxy_call (proxy, method, NULL, G_TYPE_UCHAR, value, G_TYPE_INVALID, G_TYPE_INVALID); } gboolean ol_dbus_get_double (DBusGProxy *proxy, const gchar *method, gdouble *returnval) { ol_assert_ret (proxy != NULL, FALSE); ol_assert_ret (method != NULL, FALSE); ol_assert_ret (returnval != NULL, FALSE); return dbus_g_proxy_call (proxy, method, NULL, G_TYPE_INVALID, G_TYPE_DOUBLE, returnval, G_TYPE_INVALID); } gboolean ol_dbus_get_bool (DBusGProxy *proxy, const gchar *method, gboolean *returnval) { ol_assert_ret (proxy != NULL, FALSE); ol_assert_ret (method != NULL, FALSE); ol_assert_ret (returnval != NULL, FALSE); return dbus_g_proxy_call (proxy, method, NULL, G_TYPE_INVALID, G_TYPE_BOOLEAN, returnval, G_TYPE_INVALID); } gboolean ol_dbus_set_bool (DBusGProxy *proxy, const gchar *method, gboolean value) { ol_assert_ret (proxy != NULL, FALSE); ol_assert_ret (method != NULL, FALSE); return dbus_g_proxy_call (proxy, method, NULL, G_TYPE_BOOLEAN, value, G_TYPE_INVALID, G_TYPE_INVALID); } gboolean ol_dbus_invoke (DBusGProxy *proxy, const gchar *method) { ol_assert_ret (proxy != NULL, FALSE); ol_assert_ret (method != NULL, FALSE); return dbus_g_proxy_call (proxy, method, NULL, G_TYPE_INVALID, G_TYPE_INVALID); } gboolean ol_dbus_invoke_with_str_arg (DBusGProxy *proxy, const gchar *method, const gchar *arg) { ol_assert_ret (proxy != NULL, FALSE); ol_assert_ret (method != NULL, FALSE); return dbus_g_proxy_call (proxy, method, NULL, G_TYPE_STRING, arg, G_TYPE_INVALID, G_TYPE_INVALID); } gboolean ol_dbus_connect (const gchar *service, const gchar *path, const gchar *interface, GCallback disconnect_handler, gpointer disconnect_data, DBusGProxy **proxy) { ol_log_func (); GError *error = NULL; ol_assert_ret (proxy != NULL, FALSE); ol_assert_ret (service != NULL, FALSE); ol_assert_ret (path != NULL, FALSE); ol_assert_ret (interface != NULL, FALSE); DBusGConnection *connection = ol_dbus_get_connection (); if (connection == NULL) return FALSE; *proxy = dbus_g_proxy_new_for_name_owner (connection, service, path, interface, &error); if (*proxy == NULL) { ol_debugf ("get proxy failed: %s\n", error->message); g_error_free (error); error = NULL; return FALSE; } if (disconnect_handler != NULL) { if (disconnect_data == NULL) disconnect_data = (gpointer) proxy; g_signal_connect (*proxy, "destroy", G_CALLBACK (disconnect_handler), disconnect_data); } return TRUE; } void ol_dbus_unref_proxy (GObject *object, DBusGProxy **proxy) { ol_log_func (); if (*proxy != NULL) { g_object_unref (*proxy); *proxy = NULL; } } char** ol_dbus_list_names () { DBusGConnection *connection = ol_dbus_get_connection (); ol_assert_ret (connection != NULL, NULL); GError *error = NULL; DBusGProxy *proxy = dbus_g_proxy_new_for_name (connection, DBUS_NAME, DBUS_PATH, DBUS_IFACE); ol_assert_ret (proxy != NULL, NULL); char **names = NULL; if (!dbus_g_proxy_call (proxy, DBUS_LIST_NAMES, &error, G_TYPE_INVALID, G_TYPE_STRV, &names, G_TYPE_INVALID)) { ol_errorf ("Cannot list names of session bus: %s\n", error->message); g_error_free (error); } g_object_unref (proxy); return names; } gboolean ol_dbus_get_property (DBusGProxy *proxy, const char *name, GValue *returnval) { ol_assert_ret (proxy != NULL, FALSE); ol_assert_ret (name != NULL, FALSE); ol_assert_ret (returnval != NULL, FALSE); const char *iface = dbus_g_proxy_get_interface (proxy); GError *error = NULL; gboolean ret = TRUE; DBusGProxy *dbus_proxy = dbus_g_proxy_new_from_proxy (proxy, "org.freedesktop.DBus.Properties", NULL); if (!dbus_g_proxy_call (dbus_proxy, "Get", &error, G_TYPE_STRING, iface, G_TYPE_STRING, name, G_TYPE_INVALID, G_TYPE_VALUE, returnval, G_TYPE_INVALID)) { ret = FALSE; ol_debugf ("Get dbus property %s.%s failed: %s\n", iface, name, error->message); g_error_free (error); } g_object_unref (dbus_proxy); return ret; } gboolean ol_dbus_get_bool_property (DBusGProxy *proxy, const char *name, gboolean *returnval) { ol_assert_ret (returnval != NULL, FALSE); GValue value = {0}; gboolean ret = TRUE; if (!ol_dbus_get_property (proxy, name, &value)) { ret = FALSE; } else { if (!G_VALUE_HOLDS_BOOLEAN (&value)) { ol_errorf ("Property type mismatch, %s got\n", G_VALUE_TYPE_NAME (&value)); ret = FALSE; } else { *returnval = g_value_get_boolean (&value); } g_value_unset (&value); } return ret; } gboolean ol_dbus_get_int64_property (DBusGProxy *proxy, const char *name, gint64 *returnval) { ol_assert_ret (returnval != NULL, FALSE); GValue value = {0}; gboolean ret = TRUE; if (!ol_dbus_get_property (proxy, name, &value)) { ret = FALSE; } else { if (!G_VALUE_HOLDS_INT64 (&value)) { ol_errorf ("Property type mismatch, %s got\n", G_VALUE_TYPE_NAME (&value)); ret = FALSE; } else { *returnval = g_value_get_int64 (&value); } g_value_unset (&value); } return ret; } gboolean ol_dbus_get_string_property (DBusGProxy *proxy, const char *name, char **returnval) { ol_assert_ret (returnval != NULL, FALSE); GValue value = {0}; gboolean ret = TRUE; if (!ol_dbus_get_property (proxy, name, &value)) { ret = FALSE; } else { if (!G_VALUE_HOLDS_STRING (&value)) { ol_errorf ("Property type mismatch, %s got\n", G_VALUE_TYPE_NAME (&value)); ret = FALSE; } else { *returnval = g_strdup (g_value_get_string (&value)); } g_value_unset (&value); } return ret; } gboolean ol_dbus_get_dict_property (DBusGProxy *proxy, const char *name, GHashTable **returnval) { ol_assert_ret (returnval != NULL, FALSE); GValue value = {0}; gboolean ret = TRUE; if (!ol_dbus_get_property (proxy, name, &value)) { ret = FALSE; } else { if (!G_VALUE_HOLDS_BOXED (&value)) { ol_errorf ("Property type mismatch, %s got\n", G_VALUE_TYPE_NAME (&value)); ret = FALSE; } else { *returnval = g_value_get_boxed (&value); g_hash_table_ref (*returnval); } g_value_unset (&value); } return ret; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_utils_dbus.h000066400000000000000000000153711345713535600211720ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef __OL_UTILS_DBUS_H__ #define __OL_UTILS_DBUS_H__ #include #include /** * @brief Gets the connection of dbus * * * @return A singleton connection, shouldn't be freed */ DBusGConnection *ol_dbus_get_connection (); /** * @brief Invokes a dbus method without parameter and returns a string * * @param proxy A DBusGProxy * @param method The name of method to invoke * @param returnval The point to the returned string, should be freed by g_free * * @return If succeeded, return TRUE */ gboolean ol_dbus_get_string (DBusGProxy *proxy, const gchar *method, gchar **returnval); gboolean ol_dbus_get_string_with_str_arg (DBusGProxy *proxy, const gchar *method, const gchar *arg, gchar **returnval); gboolean ol_dbus_set_string (DBusGProxy *proxy, const gchar *method, const gchar *value); gboolean ol_dbus_get_uint (DBusGProxy *proxy, const gchar *method, guint *returnval); gboolean ol_dbus_set_uint (DBusGProxy *proxy, const gchar *method, guint value); gboolean ol_dbus_get_int (DBusGProxy *proxy, const gchar *method, gint *returnval); gboolean ol_dbus_set_int (DBusGProxy *proxy, const gchar *method, gint value); gboolean ol_dbus_get_int64 (DBusGProxy *proxy, const gchar *method, gint64 *returnval); gboolean ol_dbus_set_int64 (DBusGProxy *proxy, const gchar *method, gint64 value); gboolean ol_dbus_get_uint8 (DBusGProxy *proxy, const gchar *method, guint8 *returnval); gboolean ol_dbus_set_uint8 (DBusGProxy *proxy, const gchar *method, guint8 value); gboolean ol_dbus_get_bool (DBusGProxy *proxy, const gchar *method, gboolean *returnval); gboolean ol_dbus_set_bool (DBusGProxy *proxy, const gchar *method, gboolean value); gboolean ol_dbus_get_double (DBusGProxy *proxy, const gchar *method, gdouble *returnval); gboolean ol_dbus_invoke (DBusGProxy *proxy, const gchar *method); gboolean ol_dbus_invoke_with_str_arg (DBusGProxy *proxy, const gchar *method, const gchar *arg); /** * Connect to a dbus service, get a proxy to invoke methods * * @param service The service to connect. * @param path The path of object to connect. * @param interface The interface. * @param disconnect_handler The handler when the proxy is disconnected. * You can use ol_dbus_unref_proxy, which unreferences * the proxy object on disconnected. * The first parameter of the handler is the DBusGProxy * object that emits this signal, the second parameter is * disconnect_data, or the pointer to the proxy if * disconnect_data is NULL. * @param disconnect_data The value of second parameter of disconnect_handler. * If it is set to NULL, it will be treated as the proxy * parameter. Keep it NULL if you use ol_dbus_unref_proxy * as disconnect_handler. * @param proxy The return location of the proxy. If it doesn't point to NULL, it * WON'T free the old proxy. If connect failed, it'll be set to NULL. * * @return TRUE if connected. */ gboolean ol_dbus_connect (const gchar *service, const gchar *path, const gchar *interface, GCallback disconnect_handler, gpointer disconnect_data, DBusGProxy **proxy); /** * Free a DBusGProxy pointer and set the pointer to NULL. * * This function is intend to be used as default disconnect handler in * ol_dbus_connect * * @param object Not used, just to fit the signal handler prototype * @param proxy The proxy to be freed. It will be set to NULL afterwards */ void ol_dbus_unref_proxy (GObject *object, DBusGProxy **proxy); /** * Lists owned-names on session bus. * * @return A NULL-terminated string array of names. If failed, NULL is returned. * Should be freed with g_strfreev * * @return */ char** ol_dbus_list_names (); gboolean ol_dbus_get_property (DBusGProxy *proxy, const char *name, GValue *value); gboolean ol_dbus_get_bool_property (DBusGProxy *proxy, const char *name, gboolean *returnval); gboolean ol_dbus_get_int64_property (DBusGProxy *proxy, const char *name, gint64 *returnval); /** * * * @param proxy * @param name * @param returnval The caller must use g_free to free the string * * @return */ gboolean ol_dbus_get_string_property (DBusGProxy *proxy, const char *name, char **returnval); /** * * * @param proxy * @param name * @param returnval The caller must use g_hash_table_unref to free the dict. * * @return */ gboolean ol_dbus_get_dict_property (DBusGProxy *proxy, const char *name, GHashTable **returnval); #endif // __OL_UTILS_DBUS_H__ osdlyrics-0.5.5~rc1+dfsg1/src/ol_utils_dcop.c000066400000000000000000000041071345713535600211500ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include #include #include #include #include "ol_utils_dcop.h" #include "ol_debug.h" enum { BUFFER_SIZE = 512, }; gboolean ol_dcop_get_string (const gchar *cmd, gchar **returnval) { ol_assert_ret (cmd != NULL, FALSE); ol_assert_ret (returnval != NULL, FALSE); FILE *pPipe = popen (cmd, "r"); if (!pPipe) return FALSE; gchar buffer[BUFFER_SIZE] = ""; if (!fgets (buffer, BUFFER_SIZE, pPipe)) { pclose(pPipe); return FALSE; } pclose (pPipe); strtok (buffer,"\n"); if (*returnval != NULL) { g_free (*returnval); } *returnval = g_strdup (buffer); return TRUE; } gboolean ol_dcop_get_uint (const gchar *cmd, guint *returnval) { ol_assert_ret (cmd != NULL, FALSE); ol_assert_ret (returnval != NULL, FALSE); gchar *ret = NULL; if (!ol_dcop_get_string (cmd, &ret)) return FALSE; *returnval = atoi (ret); g_free (ret); return TRUE; } gboolean ol_dcop_get_boolean (const gchar *cmd, gboolean *returnval) { ol_log_func (); ol_assert_ret (cmd != NULL, FALSE); ol_assert_ret (returnval != NULL, FALSE); gchar *ret = NULL; if (!ol_dcop_get_string (cmd, &ret)) return FALSE; *returnval = (strcmp (ret, "true") == 0); ol_debugf ("returns %s\n", ret); g_free (ret); return TRUE; } osdlyrics-0.5.5~rc1+dfsg1/src/ol_utils_dcop.h000066400000000000000000000027731345713535600211640ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ /** * @file ol_utils_dcop.h * @author Tiger Soldier * @date Mon May 18 14:15:26 2009 * * @brief Utilities for DCOP operation * * */ #ifndef _OL_UTILS_DCOP_H_ #define _OL_UTILS_DCOP_H_ /** * @brief Executes the command in cmd, fetch the output as a string and return it . * * @param cmd The DCOP command * @param returnval The point to the returned string, should be freed by g_free * * @return If succeeded, return TRUE */ gboolean ol_dcop_get_string (const gchar *cmd, gchar **returnval); gboolean ol_dcop_get_uint (const gchar *cmd, guint *returnval); gboolean ol_dcop_get_boolean (const gchar *cmd, gboolean *returnval); #endif /* _OL_UTILS_DCOP_H_ */ osdlyrics-0.5.5~rc1+dfsg1/src/ol_utils_network.c000066400000000000000000000053641345713535600217220ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #include "ol_utils_network.h" #include #include "ol_config_proxy.h" #include "ol_debug.h" static enum OlProxyType _get_proxy_type_from_system (); static enum OlProxyType _get_proxy_type_from_system () { /* TODO: We need to detect something like GNOME or KDE */ return OL_PROXY_ENVAR; } enum OlProxyType ol_get_proxy_type () { OlConfigProxy *config = ol_config_proxy_get_instance (); char *proxy_setting = ol_config_proxy_get_string (config, "Download/proxy"); enum OlProxyType type = OL_PROXY_NONE; if (proxy_setting != NULL && strcmp (proxy_setting, "system") == 0) { type = _get_proxy_type_from_system (); } else if (proxy_setting != NULL && strcmp (proxy_setting, "manual") == 0) { char *proxy_type = ol_config_proxy_get_string (config, "Download/proxy-type"); if (proxy_type != NULL && strcmp (proxy_type, "socks4") == 0) type = OL_PROXY_SOCKS4; else if (proxy_type != NULL && strcmp (proxy_type, "socks5") == 0) type = OL_PROXY_SOCKS5; else type = OL_PROXY_HTTP; if (proxy_type != NULL) g_free (proxy_type); } if (proxy_setting != NULL) g_free (proxy_setting); return type; } char * ol_get_proxy_host () { /* TODO: detect GNOME or KDE */ OlConfigProxy *config = ol_config_proxy_get_instance (); return ol_config_proxy_get_string (config, "Download/proxy-host"); } int ol_get_proxy_port () { /* TODO: detect GNOME or KDE */ OlConfigProxy *config = ol_config_proxy_get_instance (); return ol_config_proxy_get_int (config, "Download/proxy-port"); } char * ol_get_proxy_username () { /* TODO: detect GNOME or KDE */ OlConfigProxy *config = ol_config_proxy_get_instance (); return ol_config_proxy_get_string (config, "Download/proxy-username"); } char * ol_get_proxy_password () { /* TODO: detect GNOME or KDE */ OlConfigProxy *config = ol_config_proxy_get_instance (); return ol_config_proxy_get_string (config, "Download/proxy-password"); } osdlyrics-0.5.5~rc1+dfsg1/src/ol_utils_network.h000066400000000000000000000022441345713535600217210ustar00rootroot00000000000000/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2009-2011 Tiger Soldier * * This file is part of OSD Lyrics. * * OSD Lyrics is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSD Lyrics 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSD Lyrics. If not, see . */ #ifndef _OL_UTILS_NETWORK_H_ #define _OL_UTILS_NETWORK_H_ enum OlProxyType { OL_PROXY_NONE, OL_PROXY_ENVAR, OL_PROXY_HTTP, OL_PROXY_SOCKS4, OL_PROXY_SOCKS5, }; enum OlProxyType ol_get_proxy_type (); char *ol_get_proxy_host (); int ol_get_proxy_port (); char *ol_get_proxy_username (); char *ol_get_proxy_password (); #endif /* _OL_UTILS_NETWORK_H_ */ osdlyrics-0.5.5~rc1+dfsg1/tools/000077500000000000000000000000001345713535600165145ustar00rootroot00000000000000osdlyrics-0.5.5~rc1+dfsg1/tools/.gitignore000066400000000000000000000000351345713535600205020ustar00rootroot00000000000000osdlyrics-create-lyricsource osdlyrics-0.5.5~rc1+dfsg1/tools/Makefile.am000066400000000000000000000005251345713535600205520ustar00rootroot00000000000000bin_SCRIPTS = osdlyrics-create-lyricsource osdlyricstoolsdir = $(pkglibdir)/tools osdlyricstools_PYTHON = \ create-lyricsource.py \ $(NULL) osdlyrics-create-lyricsource: osdlyrics-create-lyricsource.in @sed -e "s|\@pkglibdir\@|$(pkglibdir)|" -e "s|\@PYTHON\@|$(PYTHON)|" $< > $@ CLEANFILES = \ osdlyrics-create-lyricsource \ $(NULL) osdlyrics-0.5.5~rc1+dfsg1/tools/create-lyricsource.py000066400000000000000000000113671345713535600227020ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2012 Tiger Soldier # # This file is part of OSD Lyrics. # # OSD Lyrics is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # OSD Lyrics 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with OSD Lyrics. If not, see . # from __future__ import print_function from builtins import input import os import os.path import re import string ROOTMAKEFILEAM = r"""SUBDIRS = src """ MAKEFILEAM = r"""${name}_PYTHON = ${name}.py ${name}dir = $$(pkglibdir)/lyricsources/${name} servicedir = $$(datadir)/dbus-1/services service_in_files = org.osdlyrics.LyricSourcePlugin.${name}.service.in service_DATA = $$(service_in_files:.service.in=.service) EXTRA_DIST = \ $$(service_in_files) \ $$(NULL) $$(service_DATA): $$(service_in_files) @sed -e "s|\@pkglibdir\@|$$(pkglibdir)|" -e "s|\@PYTHON\@|$$(PYTHON)|" $$< > $$@ CLEANFILES = \ org.osdlyrics.LyricSourcePlugin.${name}.service \ $$(NULL) """ # noqa: W191 SERVICE = r"""[D-BUS Service] Name=org.osdlyrics.LyricSourcePlugin.${name} Exec=@PYTHON@ @pkglibdir@/lyricsources/${name}/${name}.py """ PYTHON = r"""# -*- coding: utf-8 -*- from __future__ import unicode_literals from builtins import super from future import standard_library standard_library.install_aliases() import http.client class ${capsname}Source(BaseLyricSourcePlugin): def __init__(self): super().__init__(id='${name}', name='${name}') def do_search(self, metadata): # type: (osdlyrics.metadata.Metadata) -> List[SearchResult] # you can make use of utils.http_download # # example: status, content = http_download(url='http://foo.bar/foobar', params={'param1': 'foo', 'param2': 'bar'}, proxy=get_proxy_settings(config=self.config_proxy)) if status < 200 or status >= 400: raise http.client.HTTPException(status, '') # now do something with content return [SearchResult(title='title', artist='artist', album='album', sourceid=self.id, downloadinfo='http://foo.bar/download?id=1')] def do_download(self, downloadinfo): # type: (Any) -> bytes # `downloadinfo` is what you set in SearchResult status, content = http_download(url=downloadinfo, proxy=get_proxy_settings(self.config_proxy)) if status < 200 or status >= 400: raise http.client.HTTPException(status, '') return content if __name__ == '__main__': ${name} = ${capsname}Source() ${name}._app.run() """ # noqa: E101 def input_name(): prompt = 'Input the lyric source name with only lower-case alphabets and numbers:\n' while True: name = input(prompt).strip().lower() if not re.match(r'[a-z][a-z0-9]*$', name): prompt = 'Invalid name. Name must contain only lower-case alphabets and numbers.\nName:' else: break return name def input_boolean(prompt, default_value): prompt += ' [Y/n]?' if default_value is True else ' [y/N]?' value = input(prompt) if value.lower() == 'y': return True elif value.lower() == 'n': return False else: return default_value is True def create_file(template, path, name, params): content = string.Template(template).substitute(params) f = open(os.path.join(path, name), 'w') f.write(content) f.close() def main(): name = input_name() have_am = input_boolean('Generate Makefile.am', True) have_subdir = input_boolean('Create source files in src subdirectory', False) rootpath = name srcpath = name if not have_subdir else name + '/src' if not os.path.isdir(srcpath): os.makedirs(srcpath) params = { 'name': name, 'capsname': name.capitalize() } create_file(PYTHON, srcpath, name + '.py', params) create_file(SERVICE, srcpath, 'org.osdlyrics.LyricSourcePlugin.' + name + '.service.in', params) if have_am: create_file(MAKEFILEAM, srcpath, 'Makefile.am', params) if have_subdir: create_file(ROOTMAKEFILEAM, rootpath, 'Makefile.am', params) print('Done') if __name__ == '__main__': main() osdlyrics-0.5.5~rc1+dfsg1/tools/osdlyrics-create-lyricsource.in000066400000000000000000000000741345713535600246620ustar00rootroot00000000000000#!/bin/sh @PYTHON@ @pkglibdir@/tools/create-lyricsource.py