--- /dev/null
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+\f
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+\f
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+\f
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+\f
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+\f
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+\f
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+\f
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+\f
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+\f
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
--- /dev/null
+2007-12-28 09:37 paul
+
+ * ChangeLog, NEWS: Another NEWS and ChangeLog for the 0.2.1
+ release.
+
+2007-12-27 22:26 acornet
+
+ * examples/gdbus/gdbus: gdbus: Don't fail on error.
+
+2007-12-27 21:57 acornet
+
+ * examples/service/call_service.rb,
+ examples/service/service_newapi.rb: Variant usage example.
+
+2007-12-27 21:57 acornet
+
+ * lib/dbus/introspect.rb, lib/dbus/marshall.rb,
+ lib/dbus/message.rb, lib/dbus/type.rb: Working VARIANT
+ marshalling (closes: #10).
+
+2007-12-27 20:51 acornet
+
+ * lib/dbus/marshall.rb: Totally untested VARIANT marshalling code
+
+2007-12-27 20:11 paul
+
+ * ChangeLog, NEWS: Prepare for the 0.2.1 release; updated the NEWS
+ and ChangeLog files.
+
+2007-12-27 20:00 paul
+
+ * examples/gdbus/launch.sh, lib/dbus/matchrule.rb,
+ lib/dbus/message.rb: Make sure instance variables are
+ initialized. This surpresses the reported warnings (closes #6).
+
+2007-12-27 19:50 acornet
+
+ * lib/dbus/marshall.rb: Complete double support.
+
+2007-12-27 19:47 acornet
+
+ * lib/dbus/marshall.rb: Double marshalling support. Thanks to
+ patricksissons@gmail.com (closes: #8).
+
+2007-12-27 19:43 paul
+
+ * lib/dbus/bus.rb, lib/dbus/introspect.rb: Only print stuff if is
+ set (closes: #3).
+
+2007-12-27 19:28 acornet
+
+ * lib/dbus/bus.rb, lib/dbus/introspect.rb: Pass errors to receiving
+ classes.
+
+2007-12-16 21:34 acornet
+
+ * lib/dbus/marshall.rb, lib/dbus/message.rb: Drake Wilson's fix for
+ a bug as old as ruby-dbus.
+
+2007-12-16 21:30 acornet
+
+ * lib/dbus/marshall.rb: Merge Drake Wilson's excellent dict type
+ support.
+
+2007-11-19 22:43 acornet
+
+ * examples/gdbus/gdbus: drop old commented-out code.
+
+2007-11-17 16:40 acornet
+
+ * examples/gdbus/gdbus, lib/dbus/bus.rb, lib/dbus/message.rb: Raise
+ exception when marshalling a message to
+ /org/freedesktop/DBus/Local (new in spec). Thanks to sjoerd for
+ this one.
+
+2007-11-11 12:39 acornet
+
+ * lib/dbus/marshall.rb: Don't forget to commit the fix.
+
+2007-11-11 05:17 acornet
+
+ * lib/dbus/marshall.rb: Blank character cosmetics.
+
+2007-09-22 21:31 acornet
+
+ * examples/gdbus/gdbus, examples/no-introspect/tracker-test.rb: Add
+ tracker example.
+
+2007-09-22 21:28 acornet
+
+ * lib/dbus/bus.rb, lib/dbus/introspect.rb, lib/dbus/message.rb: Fix
+ converting DBUS error to ruby Exception on synchronous calls.
+
+2007-09-22 20:46 acornet
+
+ * doc/tutorial/src/20.basic_client.page: Fix example in tutorial.
+
+2007-07-30 08:18 paul
+
+ * lib/dbus/marshall.rb: Fixed unknown variable use in the struct
+ part of the packet marshaller.
+
+2007-07-21 11:58 acornet
+
+ * examples/no-introspect, examples/no-introspect/nm-test.rb: Add
+ example without introspection.
+
+2007-07-02 19:21 paul
+
+ * NEWS: Prepare for 0.2.0 release.
+
+2007-06-17 09:42 acornet
+
+ * lib/dbus/introspect.rb: Trivial doc fixes.
+
+2007-06-17 09:38 acornet
+
+ * lib/dbus/introspect.rb: Trivial doc fixes.
+
+2007-06-17 09:36 acornet
+
+ * lib/dbus/bus.rb: Trivial doc fixes.
+
+2007-06-15 16:34 paul
+
+ * lib/dbus.rb, lib/dbus/auth.rb, lib/dbus/bus.rb,
+ lib/dbus/export.rb, lib/dbus/introspect.rb,
+ lib/dbus/matchrule.rb, lib/dbus/type.rb: Added documentation for
+ many classes, modules and methods.
+
+2007-05-30 18:41 paul
+
+ * NEWS, lib/dbus.rb: Prepare the 0.2.0 release.
+
+2007-05-26 11:43 acornet
+
+ * lib/dbus/introspect.rb, lib/dbus/marshall.rb,
+ lib/dbus/matchrule.rb: Parse INT64. Lose a check in on_signal.
+
+2007-05-26 11:37 acornet
+
+ * examples/gdbus/gdbus: Small error prevention fix.
+
+2007-05-21 08:26 paul
+
+ * examples/rhythmbox/playpause.rb,
+ examples/service/call_service.rb,
+ examples/service/service_newapi.rb,
+ examples/simple/call_introspect.rb, examples/utils/notify.rb:
+ Synchronise the syntax in the examples a bit. Small misc fixes.
+
+2007-05-21 08:17 paul
+
+ * ChangeLog, NEWS: Merged old ChangeLog into NEWS; new ChangeLog
+ now generated by svn2cl.
+
+2007-05-19 15:42 acornet
+
+ * examples/gdbus/gdbus: Add a warning in gdbus header.
+
+2007-05-19 10:04 acornet
+
+ * lib/dbus/marshall.rb: Allow to add two byte integer in packet.
+
+2007-05-19 09:53 acornet
+
+ * lib/dbus/marshall.rb: Implement integer type parser. Fix typo.
+
+2007-05-12 19:51 paul
+
+ * NEWS, README: Preparing the 0.1.2 releases: added release to the
+ NEWS file and updated the README.
+
+2007-05-12 19:41 paul
+
+ * doc/tutorial/src/20.basic_client.page,
+ doc/tutorial/src/30.service.page: Tutorial fixes based on review
+ comments:
+ * Added a note about the manual introspection requirement.
+ * Added a remark that explains that when a service name is
+ requested,
+ which is denied, an exception is thrown.
+
+2007-05-12 09:32 acornet
+
+ * doc/tutorial/src/20.basic_client.page: Word the ListNames method
+ correctly.
+
+2007-05-10 22:08 acornet
+
+ * doc/tutorial/src/20.basic_client.page: Fix doc mistake.
+
+2007-05-09 21:46 paul
+
+ * doc/tutorial/src/10.intro.page,
+ doc/tutorial/src/20.basic_client.page: Applied tutorial fixes
+ supplied by Bram. Thanks!
+
+2007-05-06 18:57 paul
+
+ * README: Fixed typo in the README.
+
+2007-05-06 18:07 paul
+
+ * doc/tutorial/src/00.index.page, doc/tutorial/src/10.intro.page,
+ doc/tutorial/src/default.css, doc/tutorial/src/index.page: Small
+ CSS style tweaks and renamed index.page to 00.index.page to
+ follow the filename convention of the others.
+
+2007-05-06 16:45 paul
+
+ * doc/tutorial/src/10.intro.page,
+ doc/tutorial/src/20.basic_client.page,
+ doc/tutorial/src/30.service.page: Reviewed the tutorial and
+ reworked some parts.
+
+2007-05-05 21:27 paul
+
+ * doc/tutorial/src/10.intro.page,
+ doc/tutorial/src/20.basic_client.page,
+ doc/tutorial/src/30.service.page, doc/tutorial/src/default.css,
+ doc/tutorial/src/default.template, doc/tutorial/src/index.page:
+ Pimped the tutorial:
+ * Added a new palette to the CSS.
+ * Add "Ruby D-Bus tutorial" to the title.
+ * Restructured the pages: added section navigation and more
+ sections.
+ * Added some textile style stuff to things such as object names,
+ object
+ paths and service names.
+ * More consistent spelling for: Ruby, D-Bus, buses
+ * General spellcheck!
+ * Misc fixes.
+
+2007-05-05 21:00 acornet
+
+ * doc/tutorial/src/10.intro.page,
+ doc/tutorial/src/20.basic_client.page,
+ doc/tutorial/src/30.service.page: Look! I now have super aspell
+ powers!
+
+2007-04-25 21:42 paul
+
+ * doc/tutorial/src/default.template: Mention the license in the
+ tutorial footer.
+
+2007-04-25 21:32 paul
+
+ * NEWS: Added NEWS file. Should correspond to trac's milestones.
+
+2007-04-25 21:29 acornet
+
+ * COPYING, lib/dbus.rb, lib/dbus/auth.rb, lib/dbus/bus.rb,
+ lib/dbus/export.rb, lib/dbus/introspect.rb, lib/dbus/marshall.rb,
+ lib/dbus/matchrule.rb, lib/dbus/message.rb, lib/dbus/type.rb:
+ Switch license to LGPL.
+
+2007-04-25 21:09 paul
+
+ * doc/tutorial/src/10.intro.page,
+ doc/tutorial/src/20.basic_client.page,
+ doc/tutorial/src/30.service.page, doc/tutorial/src/default.css,
+ doc/tutorial/src/default.template, doc/tutorial/src/index.page:
+ Tutorial style tweaks: template changes, imported old CSS, fixed
+ some titles and the directoryName.
+
+2007-04-25 20:44 acornet
+
+ * doc/tutorial/src/30.service.page: Small tutorial refactoring.
+
+2007-04-25 20:41 paul
+
+ * README: Added feature list.
+
+2007-04-25 20:36 acornet
+
+ * doc/tutorial/src/30.service.page: Fix code rendering in tutorial.
+
+2007-04-23 17:53 acornet
+
+ * ChangeLog, lib/dbus/bus.rb: Fix stupid hardcoded string, bug
+ found by Rudi Cilibrasi.
+
+2007-04-17 16:15 acornet
+
+ * ChangeLog: Here, have a changelog.
+
+2007-04-17 16:13 acornet
+
+ * lib/dbus.rb, lib/dbus/auth.rb, lib/dbus/bus.rb,
+ lib/dbus/export.rb, lib/dbus/introspect.rb, lib/dbus/marshall.rb,
+ lib/dbus/matchrule.rb, lib/dbus/message.rb, lib/dbus/type.rb: Add
+ proper licensing terms.
+
+2007-04-14 08:03 acornet
+
+ * examples/service, examples/simple/service: Move service example
+ around.
+
+2007-04-14 08:02 acornet
+
+ * examples/gdbus/gdbus, examples/rhythmbox/playpause.rb,
+ examples/service, examples/simple/call_introspect.rb,
+ examples/simple/service/service_newapi.rb,
+ examples/utils/listnames.rb, examples/utils/notify.rb,
+ lib/dbus/bus.rb, lib/dbus/introspect.rb, lib/dbus/matchrule.rb,
+ tests: Move more examples to newer api. Tutorial update.
+
+2007-04-12 18:16 acornet
+
+ * doc/tutorial/src/10.intro.page,
+ doc/tutorial/src/20.basic_client.page,
+ doc/tutorial/src/30.service.page: Tutorial fixes.
+
+2007-04-11 17:44 acornet
+
+ * doc/tutorial/src/10.intro.page, doc/tutorial/src/10_intro.page,
+ doc/tutorial/src/20.basic_client.page,
+ doc/tutorial/src/20_basic_client.page,
+ doc/tutorial/src/30.service.page,
+ doc/tutorial/src/30_service.page: Fix names for correct order in
+ menu.
+
+2007-04-11 17:43 acornet
+
+ * doc/tutorial/00_intro, doc/tutorial/10_basic_client,
+ doc/tutorial/20_service, doc/tutorial/src,
+ doc/tutorial/src/10_intro.page,
+ doc/tutorial/src/20_basic_client.page,
+ doc/tutorial/src/30_service.page, doc/tutorial/src/default.css,
+ doc/tutorial/src/default.template, doc/tutorial/src/index.page:
+ More tutorial.
+
+2007-04-10 17:59 acornet
+
+ * doc/tutorial/10_basic_client, doc/tutorial/20_service,
+ doc/tutorial/30_annoying_client, doc/tutorial/70_signatures,
+ examples/simple/service/call_service.rb,
+ examples/simple/service/service_newapi.rb, lib/dbus/bus.rb,
+ lib/dbus/export.rb, lib/dbus/introspect.rb: More tutorial and a
+ main loop.
+
+2007-04-06 16:58 acornet
+
+ * BRAINSTORM, doc/tutorial/10_basic_client,
+ examples/simple/call_introspect.rb, lib/dbus/bus.rb,
+ lib/dbus/introspect.rb, lib/dbus/message.rb: More tutorial error
+ management and fixes
+
+2007-04-05 17:42 acornet
+
+ * doc, doc/tutorial, doc/tutorial/00_intro,
+ doc/tutorial/10_basic_client, doc/tutorial/20_service,
+ doc/tutorial/30_annoying_client, doc/tutorial/70_signatures:
+ Start of a tutorial
+
+2007-04-04 18:57 acornet
+
+ * lib/dbus.rb, lib/dbus/auth.rb, lib/dbus/bus.rb: Authenticator !
+
+2007-04-03 18:08 acornet
+
+ * examples/gdbus/gdbus, examples/utils/notify.rb, lib/dbus/bus.rb:
+ Fixes
+
+2007-04-02 18:28 acornet
+
+ * examples/gdbus/gdbus, examples/simple/call_introspect.rb,
+ examples/simple/service/call_service.rb,
+ examples/simple/service/service_newapi.rb, lib/dbus/bus.rb,
+ lib/dbus/export.rb, lib/dbus/introspect.rb: Pack things up with
+ new API.
+
+2007-03-31 10:50 acornet
+
+ * examples/service/service.rb, examples/simple/service,
+ examples/simple/service.rb,
+ examples/simple/service/call_service.rb,
+ examples/simple/service/service_newapi.rb, lib/dbus.rb,
+ lib/dbus/bus.rb, lib/dbus/export.rb, lib/dbus/introspect.rb:
+ Implement extremely sexy object definition interface.
+ See examples/simple/service/*
+
+2007-03-29 17:02 acornet
+
+ * examples/simple, examples/simple/call_introspect.rb,
+ examples/simple/service.rb, lib/dbus.rb, lib/dbus/bus.rb,
+ lib/dbus/export.rb, lib/dbus/introspect.rb, lib/dbus/message.rb:
+ Now that's a sexy api
+
+2007-03-28 18:24 acornet
+
+ * examples/gdbus/gdbus, examples/rhythmbox,
+ examples/rhythmbox/playpause.rb, lib/dbus/bus.rb,
+ lib/dbus/matchrule.rb: Signals work client side.
+
+2007-03-28 17:42 acornet
+
+ * examples/gdbus/gdbus, lib/dbus/bus.rb, lib/dbus/introspect.rb,
+ lib/dbus/matchrule.rb, lib/dbus/message.rb: Some cleanups and
+ more signal code
+
+2007-03-27 18:34 acornet
+
+ * examples/gdbus/gdbus, examples/service/call.rb,
+ examples/service/call_intro.rb,
+ examples/service/call_intro_async.rb,
+ examples/service/service.rb, examples/utils/notify.rb,
+ lib/dbus.rb, lib/dbus/bus.rb, lib/dbus/introspect.rb,
+ lib/dbus/marshall.rb, lib/dbus/matchrule.rb, lib/dbus/message.rb:
+ Fairly complete introspection support.
+ Some signal management. Add funny notify example.
+ Add matchrule object
+
+2007-03-26 22:18 acornet
+
+ * examples/service/service.rb, lib/dbus/bus.rb,
+ lib/dbus/introspect.rb, lib/dbus/message.rb: Now you can register
+ a service. Service can be introspected. One last word: YAY.
+
+2007-03-26 18:27 acornet
+
+ * examples/service/call.rb, examples/service/service.rb,
+ lib/dbus/bus.rb, lib/dbus/introspect.rb: Some more cleanups.
+ getting closer to have a service running
+
+2007-03-26 18:01 acornet
+
+ * examples/service/service.rb, lib/dbus.rb, lib/dbus/bus.rb:
+ Cosmetic fixes.
+
+2007-03-26 17:55 acornet
+
+ * lib/dbus.rb, lib/dbus/bus.rb, lib/dbus/introspect.rb,
+ lib/dbus/marshall.rb, lib/dbus/message.rb, lib/dbus/type.rb:
+ Answer paul's questions. Implement his resquests. Split things
+ up.
+
+2007-03-25 00:40 paul
+
+ * lib/dbus.rb: Small style and typo fixes.
+
+2007-03-25 00:34 paul
+
+ * lib/dbus.rb, lib/dbus/introspect.rb, lib/dbus/type.rb: Added
+ documentation (mainly to dbus.rb).
+
+2007-03-24 22:45 acornet
+
+ * lib/dbus.rb: move exception at top of fil
+
+2007-03-24 14:07 acornet
+
+ * examples/service, examples/service/service.rb, lib/dbus.rb,
+ lib/dbus/introspect.rb: Some work to allow service creation.
+ Nothing that actually works yet.
+
+2007-03-24 10:11 acornet
+
+ * lib/dbus.rb, lib/dbus/introspect.rb: Look! I just learned the
+ kind_of? method!
+
+2007-03-23 18:09 acornet
+
+ * lib/dbus/introspect.rb: Quick parsing fix.
+
+2007-03-23 18:07 acornet
+
+ * BRAINSTORM, lib/dbus/introspect.rb: Start some object exporting
+ work.
+
+2007-03-22 23:30 acornet
+
+ * examples/gdbus/gdbus, examples/gdbus/gdbus.glade, lib/dbus.rb,
+ lib/dbus/introspect.rb, lib/dbus/type.rb: Preliminary ability do
+ call methods with arguments in gdbus.
+ Better alignment management.
+ Missing check in array unmarshalling.
+
+2007-03-21 22:14 acornet
+
+ * lib/dbus.rb, tests/rhythmboxplaypause.rb: param type handling
+ fixed. Make rhythmboxplaypause work again.
+
+2007-03-21 21:42 acornet
+
+ * lib/dbus.rb, tests/rhythmboxplaypause.rb: Small fix in send_sync.
+
+2007-03-21 20:16 acornet
+
+ * examples/gdbus/gdbus: Slightly more responsive through a
+ idle_add_priority. Still not satisfying
+
+2007-03-21 19:53 acornet
+
+ * lib/dbus.rb: Blah, renaming methods...
+
+2007-03-21 19:53 acornet
+
+ * examples/gdbus/gdbus, lib/dbus.rb: Be more flexible when
+ extracting messages from the wire.
+
+2007-03-21 15:21 acornet
+
+ * examples/gdbus/gdbus, lib/dbus.rb: Bugfix in msg parsing and
+ polling.
+
+ Attempt at making gdbus more responsive on init.
+
+2007-03-20 16:12 acornet
+
+ * BRAINSTORM: Add some ideas in a plain text file.
+
+2007-03-19 23:31 acornet
+
+ * examples/gdbus/gdbus, lib/dbus.rb: I think I spotted the problem
+ that makes gdbus UI not responsive. I have no solution though.
+
+ When there is a lot of dbus messages that stackup, the
+ while msg = bus.poll_message
+ is a loop that does not return. Hence we don't go back to the
+ glib main loop
+ while there are still buffer on the stack.
+
+2007-03-19 21:51 paul
+
+ * examples/gdbus/gdbus, tests/rhythmboxplaypause.rb: * Fixed a
+ small typo.
+ * Keep the GIO channels in an instance variable otherwise it will
+ be GC'ed while using it (fixes #2, courtesy of Sjoerd Simons).
+
+2007-03-19 19:08 acornet
+
+ * examples/gdbus/gdbus, lib/dbus.rb, lib/dbus/introspect.rb: And
+ commit the API breakage.
+
+2007-03-19 19:02 acornet
+
+ * examples/utils, examples/utils/listnames.rb: A listnames with a
+ sexy api.
+
+2007-03-19 18:43 acornet
+
+ * examples/gdbus/gdbus, lib/dbus/introspect.rb: More beautiful
+ method defs in gdbus
+
+2007-03-18 21:22 acornet
+
+ * examples/gdbus/gdbus: Sort the tree a bit.
+
+2007-03-18 21:15 acornet
+
+ * examples/gdbus/gdbus, examples/gdbus/gdbus.glade, lib/dbus.rb,
+ lib/dbus/introspect.rb: More gdbus hacking and api fixes.
+ Fix connect with bigendian architecture support
+
+2007-03-18 13:34 acornet
+
+ * examples/gdbus/gdbus, examples/gdbus/gdbus.glade,
+ examples/gdbus/gdbus.glade.old, lib/dbus.rb,
+ lib/dbus/introspect.rb: Some API polishing.
+ gDBus is now officially sexy.
+
+2007-03-18 00:17 acornet
+
+ * examples/gdbus/gdbus, examples/gdbus/gdbus.glade,
+ examples/gdbus/gdbus.glade.old: Simplify GDbus. Don't make
+ everything too complex at once.
+
+2007-03-18 00:13 acornet
+
+ * examples/gdbus/gdbus, lib/dbus.rb, lib/dbus/introspect.rb: Mess
+ things up a bit :p
+
+2007-03-17 15:46 acornet
+
+ * examples/gdbus/gdbus, lib/dbus.rb, lib/dbus/introspect.rb:
+ Introspector fixes.
+
+2007-03-17 14:51 acornet
+
+ * examples/gdbus/gdbus, examples/gdbus/gdbus.glade: Add window
+ delete handler.
+
+2007-03-17 14:40 acornet
+
+ * examples/gdbus/gdbus, examples/gdbus/gdbus.glade, lib/dbus.rb,
+ lib/dbus/introspect.rb: Some api fixes
+
+2007-03-17 10:20 acornet
+
+ * examples/gdbus, examples/gdbus/gdbus, examples/gdbus/gdbus.glade,
+ examples/gdbus/launch.sh, lib/dbus.rb, tests/gdbus: Finally, Glib
+ IOChannels are easy to cope with. Start a real life example.
+
+2007-03-17 09:16 acornet
+
+ * lib/dbus/introspect.rb: Add xml validation checks.
+
+2007-03-17 09:08 acornet
+
+ * lib/dbus.rb, tests/gdbus: Method rename.
+
+2007-03-17 09:05 acornet
+
+ * contrib, lib/dbus.rb: Use Kristoffer Lundén's hack instead of the
+ ruby interpreter patch, until the interpreter is fixed.
+
+2007-03-15 18:31 acornet
+
+ * lib/dbus.rb, lib/dbus/introspect.rb, tests/gdbus: Synchronous
+ calls.
+ Use XML and a proxy object for org.freedesktop.DBus methods.
+ Implement lame dbus stuff viewer.
+ XML parser fixes.
+
+2007-03-14 20:47 acornet
+
+ * lib/dbus.rb, lib/dbus/type.rb, tests/listnames.rb: More types.
+ Few fixes.
+
+2007-03-13 18:49 acornet
+
+ * lib/dbus.rb, lib/dbus/introspect.rb, lib/dbus/type.rb,
+ tests/intro.rb, tests/listnames.rb: Lots of stuff:
+ A ProxyObjectFactory that creates proxy object from XML
+ definitions.
+ A signature genrerator (Type#to_s).
+ Marshaller support for arrays and structs.
+
+2007-03-12 19:17 acornet
+
+ * lib/dbus.rb, lib/dbus/introspect.rb, tests/intro.rb: Add a basic,
+ REXML based Introspect data parser.
+
+2007-03-12 18:39 acornet
+
+ * lib/dbus.rb, tests/listnames.rb: on_return implementation.
+
+2007-03-11 21:17 acornet
+
+ * lib/dbus.rb, tests/listnames.rb: Fix some alignments. Add
+ listname.rb example.
+
+2007-03-11 14:45 acornet
+
+ * lib/dbus.rb, tests/rhythmboxplaypause.rb, tests/unmarshall.rb:
+ Packet size computation fixes.
+
+ This also comes with another example that actually talks to a
+ dbus-application.
+ It asks rhythmbox to play.
+
+2007-03-10 19:47 paul
+
+ * lib/dbus/type.rb: Fixed typo.
+
+2007-03-08 11:38 paul
+
+ * README: Fixed the README because it mention 'bindings'.
+
+2007-03-07 20:53 acornet
+
+ * lib/dbus.rb, tests/connection.rb, tests/ping.rb,
+ tests/sendsignal.rb: Add message poller.
+
+2007-03-06 19:09 acornet
+
+ * lib/dbus.rb, tests/connection.rb: Alignment and other fixes.
+
+2007-03-05 18:37 acornet
+
+ * lib/dbus.rb, tests/conn3.rb, tests/connection.rb: Dirty
+ request_name implementation.
+
+2007-03-04 11:07 acornet
+
+ * contrib, contrib/903_abstract_unix_socket.patch, contrib/README:
+ Add the (hopefully) temporary hack to connect to dbus from ruby.
+
+2007-03-04 10:19 acornet
+
+ * lib/dbus.rb, tests/unmarshall.rb: Message::unmarshall
+ implemented.
+
+2007-03-03 23:00 acornet
+
+ * lib/dbus.rb, tests/conn2.rb, tests/conn3.rb: Basic message
+ creation.
+
+2007-03-02 18:27 acornet
+
+ * lib/dbus, lib/dbus.rb, lib/dbus/type.rb, tests/connection.rb,
+ tests/unmarshall.rb: Some basic stuff:
+ - Signature parsing
+ - Packet unmarshalling
+ - A braindead connection routine without auth
+
+2007-03-02 13:02 paul
+
+ * README, ext, tests/test000.rb: Removed dbusglue stuff from trunk.
+
+2007-02-23 10:44 acornet
+
+ * tests/test000.rb: Fix test.
+
+2007-02-23 10:41 acornet
+
+ * ext/dbus/dbusglue.c: Should be working if it builds.
+
+2007-02-22 23:28 acornet
+
+ * ext/dbus/bus.c, ext/dbus/dbusglue.c, ext/dbus/message.c,
+ tests/test000.rb: More stuff, I need to implement
+ bus.request_name to get a signal working.
+
+2007-02-22 19:06 acornet
+
+ * ext/dbus/bus.c, ext/dbus/dbusglue.c, tests/test000.rb: More
+ functions.
+
+2007-02-22 18:56 acornet
+
+ * ext/dbus/bus.c, ext/dbus/dbusglue.c: Make module names sexier.
+
+2007-02-22 16:09 acornet
+
+ * ext/dbus/bus.c: bus.c new file for bus methods.
+
+2007-02-22 15:28 paul
+
+ * ext/dbus/dbus-glue.c, ext/dbus/dbusglue.c, ext/dbus/extconf.rb:
+ Fixed the source filename (dash not allowed).
+
+2007-02-22 15:25 acornet
+
+ * ext/dbus/dbus-glue.c: Add some static keywords.
+
+2007-02-22 15:23 acornet
+
+ * ext/dbus/dbus-glue.c: New messages constructor for signals,
+ method return and errors.
+
+2007-02-22 15:15 paul
+
+ * ext/dbus/dbus-glue.c: Fixed some errors to fix building:
+ * Corrected message types const names.
+ * Removed unused variables.
+
+2007-02-22 14:59 acornet
+
+ * ext/dbus/dbus-glue.c: More basic stuff. DBusMessage constructor.
+
+2007-02-22 14:49 paul
+
+ * COPYING, README: Added README and COPYING files.
+
+2007-02-22 14:38 paul
+
+ * ext/dbus, ext/dbusglue: Renamed the ext directory.
+
+2007-02-22 14:37 paul
+
+ * ext/dbusglue, ext/dbusglue/dbus-glue.c, ext/dbusglue/extconf.rb,
+ ext/dbusglue/pkg-config.rb: Added some preliminary code and some
+ build stuff.
+
+2007-02-22 13:22 paul
+
+ * ruby-dbus: Remove redundant directory.
+
+2007-02-22 13:21 paul
+
+ * examples, ext, lib, ruby-dbus/examples, ruby-dbus/src,
+ ruby-dbus/tests, setup.rb, tests: Restructure to fit
+ setup.rb-compatible setup.
+
+2007-02-22 13:17 paul
+
+ * tags, ., ruby-dbus, ruby-dbus/examples, ruby-dbus/src,
+ ruby-dbus/tests: Created initial structure in Subversion.
+
--- /dev/null
+= Ruby D-Bus NEWS
+
+== Ruby D-Bus "Thanks for all the fish" 0.2.1 - 2007-12-29
+
+More bugfixes, mostly supplied by users supplying us with patches. Thanks!
+
+ * Support for new types added:
+ - dict (courtesy of Drake Wilson);
+ - double (courtesy of Patrick Sissons);
+ - variant.
+ * Improved exception raise support (courtesy of Sjoerd Simons,
+ Patrick Sissons).
+ * Some polish (removed debug output, solved unnecessary warnings).
+ * Documentation updates, example fixes and updates.
+
+== Ruby D-Bus "Almost live from DebConf 7" 0.2.0 - 2007-06-02
+
+Again a bugfix release, also meant to be the public release
+for exploratory purposes. New in 0.2.0:
+
+ * Complete tutorial revamp.
+ * Relicensed to the LGPL.
+
+== Ruby D-Bus "Release Often" 0.1.1 - 2007-04-23
+
+Bugfix release. Fixes hardcoded string for requesting bus names,
+found by Rudi Cilibrasi.
+
+== Ruby D-Bus "Happy Birthday Paul" 0.1.0 - 2007-04-17
+
+First release. Supports most of D-Bus' features.
--- /dev/null
+= Ruby D-Bus README
+
+Ruby D-Bus provides an implementation of the D-Bus protocol such that the
+D-Bus system can be used in the Ruby programming language.
+
+== Requirements
+
+ * Ruby 1.8 (>= 1.8.6?)
+
+ Optionally, for generating the tutorial:
+ * Webgen (>= 0.4)
+
+== Installation
+
+ 1. Decompress the Ruby D-Bus tarball (ruby-dbus-<version>.tar.gz).
+ 2. Move to top-level directory and type:
+
+ $ ruby setup.rb config
+ $ ruby setup.rb setup
+ ($ su)
+ # ruby setup.rb install
+
+ You can also install files in your favorite directory by
+ supplying setup.rb some options. Try "ruby setup.rb --help".
+
+== Feature
+
+Ruby D-Bus currently supports the following features:
+
+ * Connecting to local buses.
+ * Accessing remote services, objects and interfaces.
+ * Invoking methods on remote objects synchronously and asynchronously.
+ * Catch signals on remote objects and handle them via callbacks.
+ * Remote object introspection.
+ * Walking object trees.
+ * Creating services and registering them on the bus.
+ * Exporting objects with interfaces on a bus for remote use.
+ * Rubyish D-Bus object and interface syntax support that automatically
+ allows for introspection.
+ * Emitting signals on exported objects.
+
+== Usage
+
+ See some of the examples in the examples/ subdirectory of the tarball.
+ Also, check out the included tutorial (in Webgen format) in doc/tutorial/
+ or view it online on http://trac.luon.net/data/ruby-dbus/tutorial/.
+
+== License
+
+ Ruby D-Bus 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.
--- /dev/null
+---
+title: Welcome
+inMenu: true
+directoryName: Start
+---
+
+h2. Welcome!
+
+This is the Ruby D-Bus tutorial. It aims to show you the features of Ruby
+D-Bus and as you read through the tutorial also how to use them.
+
+Please proceed to the "Introduction":intro.html.
--- /dev/null
+---
+title: Introduction
+inMenu: true
+---
+
+This is a tutorial for Ruby D-Bus, a library to access D-Bus facilities of your
+system. This chapter has the following sections:
+
+# "What is D-Bus?":#what-is
+# "Definitions":#def
+## "Client":#def-client
+## "Service":#def-service
+## "Object path":#def-obj-path
+## "Interface":#def-iface
+## "Method":#def-method
+## "Signal":#def-signal
+## "Message":#def-message
+## "Signature":#def-sig
+
+h2(#what-is). What is D-Bus?
+
+D-Bus is an RPC(Remote Procedure Call) protocol. A common setup can have
+multiple D-Bus daemons running that route procedure calls and signals in
+the form of messages. Each of these daemons supports a bus. A bus that
+is often used by modern desktop environments, and is available per session, is
+called the _session bus_. Another bus that can be available, but in a
+system-wide manner, is called the _system bus_. It is used for example by
+the "Hardware Abstraction Layer":http://hal.freedesktop.org/ daemon. Note
+that theoretically the D-Bus RPC protocol can be used without a system or
+session bus. I never came across any actual use of this though.
+
+At the desktop level, D-Bus allows some components to interact. Typically
+if you are writing an application or a personal script that wants to
+interact with your web browser, your music player, or that simply wants to
+pop-up a desktop notification, D-Bus comes into play.
+
+At the system level, the Hardware Abstraction Layer is a privileged daemon
+that notifies other software of hardware activities. Typically, if you
+want to be notified if a CD-ROM has been loaded in, of if you want to
+explore hardware, the system daemon comes into play.
+
+The D-Bus RPC system is as we will see _object oriented_.
+
+Buses provide access to _services_ provided in turn by running or ready to
+run processes. Let me introduce some D-Bus terminology before we discuss
+the API of Ruby D-Bus.
+
+h2(#def). Definitions
+
+h3(#def-client). Client
+
+A D-Bus client is a process that connects to a D-Bus. They issue method
+calls and register to the bus for signals and events.
+
+h3(#def-service). Service
+
+A connected client can export some of its objects and let other clients
+call some of its methods. Such clients typically register a special name
+like @org.freedesktop.Notifications@, the service name.
+
+There is slightly different type of service. They are provided by
+processes that can be launched by a D-Bus daemon on demand. Once they are
+started by D-Bus they register a service name and behave like another
+client.
+
+Note that the buses themselves provide the @org.freedesktop.DBus@ service,
+and provide some features through it.
+
+h3(#def-obj-path). Object path
+
+An object path is the D-Bus way to specify an object _instance_ address. A
+service can provide different object instances to the outside world, so
+that external processes can call methods on each of them. An object path
+is an address of an instance in a very similar way that the path is an
+address of a file on a file system. For example:
+@/org/freedesktop/Notification@ is an object path of an object provided by
+the @org.freedesktop.Notification@ service
+
+*Beware*: service names and object paths can, but do _not_ have to be
+related! You'll probably encounter a lot of cases though, where the
+object path is a slashed version of the dotted service name.
+
+h3(#def-iface). Interface
+
+Classically in an object model, classes can implement interfaces. That is,
+some method definitions grouped in an interface. This is exactly what a
+D-Bus interface is as well. In D-Bus interfaces have names. These names must be
+specified on method calls.
+
+The @org.freedesktop.Notification@ service provides an object instance
+called @/org/freedesktop/Notification@. This instance object implements an
+interface called @org.freedesktop.Notifications@. It also provides two
+special D-Bus specific interfaces: @org.freedesktop.DBus.Introspect@ and
+@org.freedesktop.DBus.Properties@. Again, object paths, service names,
+and interface names can be related but do not have to be.
+
+Basically the @org.freedesktop.DBus.Introspect@ has an @Introspect@ method,
+that returns XML data describing the @/org/freedesktop/Notification@ object
+interfaces. This is used heavily internally by Ruby D-Bus.
+
+h3(#def-method). Method
+
+A method is, well, a method in the classical meaning. It's a function that
+is called in the context of an object instance. Methods have typed
+parameters and return typed return values.
+
+h3(#def-signal). Signal
+
+Signals are simplified method calls that do not have a return value. They
+do have typed parameters though.
+
+h3(#def-message). Message
+
+Method calls, method returns, signals, errors: all are encoded as D-Bus
+messages sent over a bus. They are made of a packet header with source and
+destination address, a type (method call, method reply, signal) and the
+body containing the parameters (for signals and method calls) or the return
+values (for a method return message).
+
+h3(#def-sig). Signature
+
+Because D-Bus is typed and dynamic, each message comes with a signature that
+describes the types of the data that is contained within the message. The
+signature is a string with an extremely basic language that only describes
+a data type. You will need to have some knowledge of what a signature
+looks like if you are setting up a service. If you are just programming a
+D-Bus client, you can live without knowing about them.
--- /dev/null
+---
+title: Client Usage
+inMenu: true
+---
+
+This chapter discusses basic client usage and has the following topics:
+
+# "Using the library":#loading
+# "Connecting to a bus":#connecting
+# "Performing method calls":#method-call
+## "Introspection":#method-call--introspection
+# "Calling a method asynchronously":#method-call-async
+# "Waiting for a signal":#signal-wait
+# "More about introspection":#introspection
+## "Walking the object tree":#introspection--tree
+
+h2(#loading). Using the library
+
+If you want to use the library, you have to make Ruby load it by issuing:
+
+ require 'dbus'
+
+That's all! Now we can move on to really using it...
+
+h2(#connecting). Connecting to a bus
+
+On a typical system, two buses are running, the system bus and the session
+bus. The system bus can be accessed by:
+
+ bus = DBus::SystemBus.instance
+
+Probably you already have guessed how to access the session bus. This
+can be done by:
+
+ bus = DBus::SessionBus.instance
+
+h2(#method-call). Performing method calls
+
+Let me continue this example using the session bus. Let's say that I want
+to access an object of some client on the session bus. This particular
+D-Bus client provides a service called @org.gnome.Rhythmbox@. Let me
+access this service:
+
+ rb_service = bus.service("org.gnome.Rhythmbox")
+
+In this example I access the @org.gnome.Rhythmbox@ service, which is
+provided by the application
+"Rhythmbox":http://www.gnome.org/projects/rhythmbox/.
+OK, I have a service handle now, and I know that it exports the object
+"/org/gnome/Rhythmbox/Player". I will trivially access this remote object
+using:
+
+ rb_player = rb_service.object("/org/gnome/Rhythmbox/Player")
+
+h3(#method-call--introspection). Introspection
+
+Well, that was easy. Let's say that I know that this particular object is
+introspectable. In real life most of them are. The @rb_object@ object we
+have here is just a handle of a remote object, in general they are called
+_proxy objects_, because they are the local handle of a remote object. It
+would be nice to be able to make it have methods, and that its methods send
+a D-Bus call to remotely execute the actual method in another process.
+Well, instating these methods for a _introspectable_ object is trivial:
+
+ rb_player.introspect
+
+And there you go. Note that not all services or objects can be
+introspected, therefore you have to do this manually! Let me remind you
+that objects in D-Bus have interfaces and interfaces have methods. Let's
+now access these methods:
+
+ rb_player_iface = rb_player["org.gnome.Rhythmbox.Player"]
+ puts rb_player_iface.getPlayingUri
+
+As you can see, when you want to call a method on an instance object, you have
+to get the correct interface. It is a bit tedious, so we have the following
+shortcut that does the same thing as before:
+
+ rb_player.default_iface = "org.gnome.Rhythmbox.Player"
+ puts rb_player.getPlayingUri
+
+The @default_iface=@ call specifies the default interface that should be
+used when non existing methods are called directly on a proxy object, and
+not on one of its interfaces.
+
+Note that the bus itself has a corresponding introspectable object. You can
+access it with @bus.proxy@ method. For example, you can retrieve an array of
+exported service names of a bus like this:
+
+ bus.proxy.ListNames[0]
+
+h2(#method-call-async). Calling a method asynchronously
+
+D-Bus is _asynchronous_. This means that you do not have to wait for a
+reply when you send a message. When you call a remote method that takes a
+lot of time to process remotely, you don't want your application to hang,
+right? Well the asychronousness exists for this reason. What if you dont'
+want to wait for the return value of a method, but still you want to take
+some action when you receive it?
+
+There is a classical method to program this event-driven mechanism. You do
+some computation, perform some method call, and at the same time you setup
+a callback that will be triggered once you receive a reply. Then you run a
+main loop that is responsible to call the callbacks properly. Here is how
+you do it:
+
+ rb_player.getPlayingUri do |resp|
+ puts "The playing URI is #{resp}"
+ end
+ puts "See, I'm not waiting!"
+ loop = DBus::Main.new
+ loop << bus
+ loop.run
+
+This code will print the following:
+
+ See, I'm not waiting!
+ The playing URI is file:///music/papapingoin.mp3
+
+h2(#signal-wait). Waiting for a signal
+
+Signals are calls from the remote object to your program. As a client, you
+set yourself up to receive a signal and handle it with a callback. Then running
+the main loop triggers the callback. You can register a callback handler
+as allows:
+
+ rb_player.on_signal("elapsedChanged") do |u|
+ puts u
+ end
+
+h2(#introspection). More about introspection
+
+There are various ways to inspect a remote service. You can simply call
+@Introspect()@ and read the XML output. However, in this tutorial I assume
+that you want to do it using the Ruby D-Bus API.
+
+Notice that you can introspect a service, and not only objects:
+
+ rb_service = bus.service("org.gnome.Rhythmbox")
+ rb_service.introspect
+ p rb_service.root
+
+This dumps a tree-like structure that represents multiple object paths. In
+this particular case the output is:
+
+ </: {org => {gnome => {Rhythmbox => {Player => ..fdbe625de {},Shell => ..fdbe6852e {},PlaylistManager => ..fdbe4e340 {}}></code></pre>
+
+Read this left to right: the root node is "/", it has one child node "org",
+"org" has one child node "gnome", and "gnome" has one child node "Rhythmbox".
+Rhythmbox has Tree child nodes "Player", "Shell" and "PlaylistManager".
+These three last child nodes have a weird digit that means it has an object
+instance. Such object instances are already introspected.
+
+If the prose wasn't clear, maybe the following ASCII art will help you:
+
+ /
+ org
+ gnome
+ Rhythmbox
+ Shell (with object)
+ Player (with object)
+ PlaylistManager (with object)
+
+h3(#introspection--tree). Walking the object tree
+
+You can have an object on any node, i.e. it is not limited to leaves.
+You can access a specific node like this:
+
+ rb_player = rb_service.root["org"]["gnome"]["Rhythmbox"]["Player"]
+ rb_player = rb_service.object("/org/gnome/Rhythmbox/Player")
+
+The difference between the two is that for the first one, @rb_service@
+needs to have been introspected. Also the obtained @rb_player@ is already
+introspected whereas the second @rb_player@ isn't yet.
--- /dev/null
+---
+title: Creating a Service
+inMenu: true
+---
+
+This chapter deals with the opposite side of the basic client usage, namely
+the creation of a D-Bus service. It contains the following sections:
+
+# "Registering a service":#service-reg
+# "Exporting an object":#obj-export
+## "Using the exported object":#obj-export-use
+# "Emitting a signal":#signal-emit
+
+h2(#service-reg). Registering a service
+
+Now that you know how to perform D-Bus calls, and how to wait for and
+handle signals, you might want to learn how to publish some object and
+interface to provide them to the D-Bus world. Here is how you do that.
+
+As you should already know, D-Bus clients that provide some object to be
+called remotely are services. Here is how to allocate a name on a bus:
+
+ bus = DBus.session_bus
+ service = bus.request_service("org.ruby.service")
+
+Now this client is know to the outside world as @org.ruby.service@.
+Note that this is a request and it _can_ be denied! When it
+is denied, an exception (@DBus::NameRequestError@) is thrown.
+
+h2(#obj-export). Exporting an object
+
+Now, let's define a class that we want to export:
+
+ class Test < DBus::Object
+ # Create an interface.
+ dbus_interface "org.ruby.SampleInterface" do
+ # Create a hello method in that interface.
+ dbus_method :hello, "in name:s, in name2:s" do |name, name2|
+ puts "hello(#{name}, #{name2})"
+ end
+ end
+ end
+
+As you can see, we define a @Test@ class in which we define a
+@org.ruby.SampleInterface@ interface. In this interface, we define a
+method. The given code block is the method's implementation. This will be
+executed when remote programs performs a D-Bus call. Now the annoying part:
+the actual method definition. As you can guess the call
+
+ dbus_method :hello, "in name:s, in name2:s" do ...
+
+creates a @hello@ method that takes two parameters both of type string.
+The _:s_ means "of type string". Let's have a look at some other common
+parameter types:
+
+* _u_ means unsigned integer
+* _i_ means integer
+* _y_ means byte
+* _(ui)_ means a structure having a unsigned integer and a signed one.
+* _a_ means array, so that "ai" means array of integers
+** _as_ means array of string
+** _a(is)_ means array of structures, each having an integer and a string.
+
+For a full description of the available D-Bus types, please refer to the
+"D-Bus specification":http://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-signatures.
+
+Now that the class has been defined, we can instantiate an object
+and export it as follows:
+
+ exported_obj = Test.new("/org/ruby/MyInstance")
+ service.export(exported_obj)
+
+This piece of code above instantiates a @Test@ object with a D-Bus object
+path. This object is reachable from the outside world after
+@service.export(exported_obj)@ is called.
+
+h3(#obj-export-use). Using the exported object
+
+Now, let's consider another program that will access our newly created service:
+
+ ruby_service = bus.service("org.ruby.service")
+ obj = ruby_service.object("/org/ruby/MyInstance")
+ obj.introspect
+ obj.default_iface = "org.ruby.SampleInterface"
+ obj.hello("giligiligiligili", "haaaaaaa")
+
+As you can see, the object we defined earlier is automatically introspectable.
+See also "Basic Client Usage":basic_client.html.
+
+h2(#signal-emit). Emitting a signal
+
+Let's add some example method so you can see how to return a value to the
+caller and let's also define another example interface that has a signal.
+
+ class Test2 < DBus::Object
+ # Create an interface
+ dbus_interface "org.ruby.SampleInterface" do
+ # Create a hello method in the interface:
+ dbus_method :hello, "in name:s, in name2:s" do |name, name2|
+ puts "hello(#{name}, #{name2})"
+ end
+ # Define a signal in the interface:
+ dbus_signal :SomethingJustHappened, "toto:s, tutu:u"
+ end
+
+ dbus_interface "org.ruby.AnotherInterface" do
+ dbus_method :ThatsALongMethodNameIThink, "in name:s, out ret:s" do |name|
+ ["So your name is #{name}"]
+ end
+ end
+ end
+
+Triggering the signal is a easy as calling a method, but then this time on
+a local (exported) object and not on a remote/proxy object:
+
+ exported_obj.SomethingJustHappened("blah", 1)
+
+Note that the @ThatsALongMethodNameIThink@ method is returning a single
+value to the caller. Notice that you always have to return an array. If
+you want to return multiple values, just have an array with multiple
+values.
--- /dev/null
+ a { text-decoration: none; }
+ a:link { color: #2E5F84; background-color: inherit; }
+ a:visited { color: #4084B8; background-color: inherit; }
+ a:hover,
+ a:active {
+ color: #4084B8;
+ background-color: inherit;
+ text-decoration: underline;
+ }
+
+ #content {
+ width: 50em;
+ margin: 0pt auto;
+ }
+
+ #header {
+ padding: 2pt;
+ color: inherit;
+ background-color: #C61C18;
+ border-top: thin solid #891D1B;
+ border-left: thin solid #891D1B;
+ border-right: thin solid #891D1B;
+ }
+
+ #header h1 {
+ margin: 5pt;
+ color: #FFFFFF;
+ background-color: inherit;
+ }
+
+ #footer {
+ margin-top: 5pt;
+ border-top: thin solid #891D1B;
+ }
+
+ #body { text-align: justify; }
+
+ .bar {
+ clear: both;
+ padding: 2pt;
+ text-align: center;
+ font-size: 83%;
+ border: thin solid #891D1B;
+ color: inherit;
+ background-color: #F07F7D;
+ }
+ .bar a:link { color: #FFFFFF; background-color: inherit; }
+ .bar a:visited { color: #FFFFFF; background-color: inherit; }
+
+ .left, .right {
+ padding: 0pt 1em;
+ }
+
+ .left {
+ float: left;
+ text-align: left;
+ }
+
+ .right {
+ float: right;
+ text-align: right;
+ }
+
+ .log { table-layout: fixed; width: 100%; }
+ .log tr.header .day { width: 6em; }
+
+ code { color: inherit; background-color: #F0E7E7; }
+
+ pre {
+ font-size: 90%;
+ overflow: hidden;
+ padding-left: 10pt;
+ border: thin solid #F0B4B4;
+ color: inherit;
+ background-color: #F0DDDD;
+ }
+
+ pre code { color: inherit; background-color: #F0DDDD; }
+
+ h2, h3 {
+ color: #1D3B52;
+ background-color: inherit;
+ border-bottom: thin dashed #1D3B52;
+ }
+
+ table th { color: inherit; background-color: #B4D2E8; }
+
+ /* styling the menu */
+
+ #menu {
+ float: right;
+ width: 15em;
+ margin: 10pt 0pt 5pt 10pt;
+ font-size: 83%;
+ border: thin solid #2E5F84;
+ color: inherit;
+ background-color: #B4D2E8;
+ }
+
+ #menu a {
+ text-decoration: none;
+ }
+
+ #menu a:hover {
+ text-decoration: underline;
+ }
+
+ #menu li.webgen-menu-item-selected {
+ font-weight: bold;
+ }
+
+ #menu ul {
+ list-style-type: none;
+ padding-left: 15pt; /* first ul has some padding left & right */
+ padding-right: 5pt;
+ }
+
+ #menu li {
+ padding: 3pt 0pt; /* inter-item padding */
+ color: #2E5F84;
+ background-color: inherit;
+ }
+
+ #menu li > ul {
+ font-weight: normal;
+ padding-top: 3pt; /* padding in front of first item */
+ padding-left: 7pt; /* indentation */
+ padding-right: 0pt;
+ }
--- /dev/null
+--- content, html
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="{lang:}">
+ <head>
+ <title>The Ruby D-Bus Tutorial - {title: }</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="description" content="The Ruby D-Bus tutorial" />
+ <meta name="keywords" content="ruby,d-bus,dbus,tutorial,object,interface,service,proxy" />
+ <meta name="author" content="Arnaud Cornet" />
+ <meta name="generator" content="Webgen - http://webgen.rubyforge.com/" />
+ <link href="{relocatable: default.css}" rel="stylesheet" type="text/css" />
+ <link href="{resource: webgen-css}" rel="stylesheet" type="text/css" />
+ </head>
+ <body>
+ <div id="content">
+ <div id="header">
+ <h1>Ruby D-Bus Tutorial - {title: }</h1>
+ </div>
+
+ <div id="headerbar" class="bar">
+ <span class="left">Location: {breadcrumbTrail: }</span>
+ <span class="right">Language: {langbar: }</span>
+ <div style="clear:both"></div>
+ </div>
+
+ <div id="menu">
+ {menu: vertical}
+ </div>
+
+ <div id="body">
+ {block: }
+ </div>
+
+ <div id="footer" class="bar">
+ Generated with <a href="http://webgen.rubyforge.org">webgen</a> on <b>{date: }</b><br/>
+ © Arnaud Cornet and Paul van Tilburg; this tutorial is part of
+ free software; you can redistribute it and/or modify it under the
+ terms of the GNU
+ <a href="http://www.gnu.org/licenses/lgpl.html">Lesser General Public
+ License, version 2.1</a> as published by the
+ <a href="http://www.fsf.org/">Free Software Foundation</a>.
+ </div>
+ </div>
+ </body>
+</html>
--- /dev/null
+#!/usr/bin/ruby
+#
+# This is a quite complex example using internal lower level API.
+# Not a good starting point, but might be usefull if you want to do tricky
+# stuff.
+# -- Arnaud
+
+require 'dbus'
+require 'libglade2'
+
+$enable_system = false
+
+class MethodCallWindow
+ def initialize(pwindow, intf, meth)
+ @intf, @meth = intf, meth
+ @entries = Array.new
+ @dialog = Gtk::Dialog.new(meth.name, pwindow,
+ Gtk::Dialog::MODAL | Gtk::Dialog::NO_SEPARATOR,
+ [Gtk::Stock::OK, Gtk::Dialog::RESPONSE_OK],
+ [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL])
+
+ @meth.params.each do |param|
+ shbox = Gtk::HBox.new(true, 0)
+ label = Gtk::Label.new("#{param[0]} (#{param[1]})")
+ input = Gtk::Entry.new
+ @entries << input
+ shbox.pack_start(label, true, true, 0)
+ shbox.pack_start(input, true, true, 0)
+ @dialog.vbox.pack_start(shbox, true, true, 0)
+ @dialog.vbox.show_all
+ end
+ end
+
+ def run
+ on_ok if @dialog.run == Gtk::Dialog::RESPONSE_OK
+ @dialog.destroy
+ end
+
+ def on_ok
+ bus = @intf.object.bus
+ m = DBus::Message.new(DBus::Message::METHOD_CALL)
+ m.path = @intf.object.path
+ m.interface = @intf.name
+ m.destination = @intf.object.destination
+ m.member = @meth.name
+ m.sender = bus.unique_name
+ @meth.params.each_with_index do |param, idx|
+ entry = @entries[idx]
+ data = nil
+ case param[1]
+ when "u", "i"
+ data = entry.text.to_i
+ when "s"
+ data = entry.text
+ when /^a/
+ begin
+ data = eval(entry.text)
+ rescue
+ puts "Incorrect data: #{data}"
+ end
+ end
+ m.add_param(param[1], data)
+ end
+ bus.on_return(m) do |retm|
+ if retm.is_a?(DBus::Error)
+ puts "Error: #{retm.inspect}"
+ else
+ puts "Method #{m.member} returns: #{retm.params.inspect}"
+ end
+ end
+ bus.send(m.marshall)
+ end
+end
+
+class DBusUI
+ def initialize
+ @glade = GladeXML.new("gdbus.glade") { |h| method(h) } # This block is like
+ # black magic :)
+ @sessiontreeview = @glade.get_widget("sessiontreeview")
+ setup_treeview_renderer(@sessiontreeview, 'D-Bus Objects')
+ @sessiontreeview.selection.signal_connect("changed") do |selection|
+ on_treeview_selection_changed(selection)
+ end
+
+ @systemtreeview = @glade.get_widget("systemtreeview")
+ setup_treeview_renderer(@systemtreeview, 'D-Bus Objects')
+ @systemtreeview.selection.signal_connect("changed") do |selection|
+ on_treeview_selection_changed(selection)
+ end
+
+ @methsigtreeview = @glade.get_widget("methsigtreeview")
+ # ierk
+ setup_methodview_renderer(@methsigtreeview)
+
+ @window = @glade.get_widget("window1")
+ @window.show_all
+ start_buses
+ end
+
+ def beautify_method(meth)
+ # Damn, this need to be rewritten :p
+ s = meth.name + "("
+ if meth.kind_of?(DBus::Method)
+ s += (meth.params.collect { |a| "in #{a[0]}:#{a[1]}" } +
+ meth.rets.collect { |a| "out #{a[0]}:#{a[1]}" }).join(", ")
+ elsif meth.kind_of?(DBus::Signal)
+ s += (meth.params.collect { |a| "in #{a[0]}:#{a[1]}" }).join(", ")
+ end
+ s += ")"
+ s
+ end
+
+ def on_treeview_selection_changed(selection)
+ selected = selection.selected
+ model = Gtk::ListStore.new(String, String, DBus::Method,
+ DBus::ProxyObjectInterface)
+ @methsigtreeview.model = model
+ if selected
+ if intf = selected[1]
+ intf.methods.keys.sort.each do |mi|
+ m = intf.methods[mi]
+ subiter = model.append
+ subiter[0] = beautify_method(m)
+ subiter[1] = "M"
+ subiter[2] = m
+ subiter[3] = intf
+ end
+ intf.signals.keys.sort.each do |mi|
+ m = intf.signals[mi]
+ subiter = model.append
+ subiter[0] = beautify_method(m)
+ subiter[1] = "S"
+ subiter[2] = m
+ subiter[3] = intf
+ end
+ end
+ end
+ end
+
+ def on_method_activated(view, path, column)
+ name = view.model.get_iter(path)[0]
+ puts "Clicked on: #{name.inspect}"
+ type = view.model.get_iter(path)[1]
+ intf = view.model.get_iter(path)[2]
+ if type == "M"
+ method = view.model.get_iter(path)[2]
+ intf = view.model.get_iter(path)[3]
+ MethodCallWindow.new(@window, intf, method).run
+ elsif type == "S"
+ signal = view.model.get_iter(path)[2]
+ intf = view.model.get_iter(path)[3]
+ mr = DBus::MatchRule.new.from_signal(intf, signal)
+ puts "*** Registering matchrule: #{mr.to_s} ***"
+ intf.object.bus.add_match(mr) do |sig|
+ puts "Got #{sig.member}(#{sig.params.join(',')})"
+ end
+ end
+ end
+
+ def on_sessiontreeview_row_activated(view, path, column)
+ name = view.model.get_iter(path)[0]
+ puts "Clicked on: #{name.inspect}"
+ intf = view.model.get_iter(path)[1]
+ end
+
+ def on_window_delete_event(window, event)
+ Gtk.main_quit
+ end
+
+ def setup_methodview_renderer(treeview)
+ renderer = Gtk::CellRendererText.new
+ col_offset = treeview.insert_column(-1, "T", renderer, 'text' => 1)
+ col_offset = treeview.insert_column(-1, "Name", renderer, 'text' => 0)
+ column = treeview.get_column(col_offset - 1)
+ column.clickable = true
+ end
+
+ def setup_treeview_renderer(treeview, str)
+ renderer = Gtk::CellRendererText.new
+ col_offset = treeview.insert_column(-1, str, renderer, 'text' => 0)
+ column = treeview.get_column(col_offset - 1)
+ column.clickable = true
+ end
+
+ def process_input(bus)
+ # THIS is the bad ass loop
+ # we should return to the glib main loop from time to time. Anyone with a
+ # proper way to handle it ?
+ bus.update_buffer
+ bus.messages.each do |msg|
+ bus.process(msg)
+ end
+ end
+
+ def start_buses
+ # call glibize to get dbus messages from the glib mainloop
+ DBus::SessionBus.instance.glibize
+ DBus::SystemBus.instance.glibize if $enable_system
+
+ DBus::SessionBus.instance.proxy.ListNames do |msg, names|
+ fill_treeview(DBus::SessionBus.instance, @sessiontreeview, names)
+ end
+ if $enable_system
+ DBus::SystemBus.instance.proxy.ListNames do |msg, names|
+ fill_treeview(DBus::SystemBus.instance, @systemtreeview, names)
+ end
+ end
+ end
+
+ def walk_node(model, iter, node)
+ node.each_pair do |key, val|
+ subiter = model.append(iter)
+ subiter[0] = key
+ walk_node(model, subiter, val)
+ end
+ unless node.object.nil?
+ node.object.interfaces.sort.each do |ifname|
+ subiter = model.append(iter)
+ subiter[0] = ifname
+ subiter[1] = node.object[ifname]
+ end
+ end
+ end
+
+ def introspect_services(model, bus)
+ el = @introspect_array.shift
+ if not el =~ /^:/
+ iter = model.append(nil)
+ iter[0] = el
+ puts "introspecting: #{el}"
+ begin
+ service = bus.service(el).introspect
+ walk_node(model, iter, service.root)
+ rescue Exception => e
+ puts "DBus Error:"
+ puts e.backtrace.join("\n")
+ end
+ end
+
+ not @introspect_array.empty?
+ end
+
+ def fill_treeview(bus, treeview, array)
+ model = Gtk::TreeStore.new(String, DBus::ProxyObjectInterface)
+ treeview.model = model
+ @introspect_array = array.sort
+ Gtk::idle_add { introspect_services(model, bus) }
+ end
+
+ def main
+ Gtk.main
+ end
+end
+
+DBusUI.new.main
--- /dev/null
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkWindow" id="window1">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">GD-Bus</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="default_width">500</property>
+ <property name="default_height">400</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <signal name="delete_event" handler="on_window_delete_event" last_modification_time="Sat, 17 Mar 2007 14:49:13 GMT"/>
+
+ <child>
+ <widget class="GtkHPaned" id="hpaned1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+
+ <child>
+ <widget class="GtkNotebook" id="notebook1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="show_tabs">True</property>
+ <property name="show_border">True</property>
+ <property name="tab_pos">GTK_POS_TOP</property>
+ <property name="scrollable">False</property>
+ <property name="enable_popup">False</property>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow3">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_ALWAYS</property>
+ <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTreeView" id="sessiontreeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">True</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ <signal name="row_activated" handler="on_sessiontreeview_row_activated" last_modification_time="Sat, 17 Mar 2007 10:17:11 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Session</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow5">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_ALWAYS</property>
+ <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTreeView" id="systemtreeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">True</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">System</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="shrink">True</property>
+ <property name="resize">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow4">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_ALWAYS</property>
+ <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTreeView" id="methsigtreeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">True</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ <signal name="row_activated" handler="on_method_activated" last_modification_time="Sat, 17 Mar 2007 14:49:13 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="shrink">True</property>
+ <property name="resize">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
--- /dev/null
+#!/bin/sh
+set -e
+# for the lazy typer
+ruby -w -I ../../lib gdbus
--- /dev/null
+#!/usr/bin/ruby
+#
+# Trivial network interface lister using NetworkManager.
+# NetworkManager does not support introspection, so the api is not that sexy.
+
+require 'dbus'
+
+bus = DBus::SystemBus.instance
+
+nm_service = bus.service("org.freedesktop.NetworkManager")
+nm_manager = nm_service.object("/org/freedesktop/NetworkManager")
+poi = DBus::ProxyObjectInterface.new(nm_manager, "org.freedesktop.NetworkManager")
+poi.define_method("getDevices", "")
+p poi.getDevices
+
+
--- /dev/null
+#!/usr/bin/ruby
+#
+# Trivial network interface lister using NetworkManager.
+# NetworkManager does not support introspection, so the api is not that sexy.
+
+require 'dbus'
+
+bus = DBus::SessionBus.instance
+
+tracker_service = bus.service("org.freedesktop.Tracker")
+tracker_manager = tracker_service.object("/org/freedesktop/tracker")
+poi = DBus::ProxyObjectInterface.new(tracker_manager, "org.freedesktop.Tracker.Files")
+poi.define_method("GetMetadataForFilesInFolder", "in live_query_id:i, in uri:s, in fields:as, out values:aas")
+p poi.GetMetadataForFilesInFolder(-1, ENV['HOME'] + "/Desktop", ["File:Name", "File:Size"])
+
+
--- /dev/null
+#!/usr/bin/ruby
+
+require 'dbus'
+bus = DBus::SessionBus.instance
+# get a rb object
+proxy = bus.introspect("org.gnome.Rhythmbox", "/org/gnome/Rhythmbox/Player")
+proxyi = proxy["org.gnome.Rhythmbox.Player"]
+
+# register for signals
+
+mr = DBus::MatchRule.new
+mr.type = "signal"
+mr.interface = "org.gnome.Rhythmbox.Player"
+mr.path = "/org/gnome/Rhythmbox/Player"
+bus.add_match(mr) do |msg, first_param|
+ print msg.member + " "
+ puts first_param
+end
+
+proxyi.playPause(true)
+
+main = DBus::Main.new
+main << bus
+main.run
+
--- /dev/null
+#!/usr/bin/ruby
+
+require "dbus"
+
+session_bus = DBus::SessionBus.instance
+
+ruby_srv = session_bus.service("org.ruby.service")
+
+# Get the object from this service
+player = ruby_srv.object("/org/ruby/MyInstance")
+
+# Introspect it
+puts player.introspect
+player.default_iface = "org.ruby.SampleInterface"
+player.test_variant(["s", "coucou"])
+player.on_signal("SomethingJustHappened") do |u, v|
+ puts "SomethingJustHappened: #{u} #{v}"
+end
+player.hello("8=======D", "(_._)")
+p player["org.ruby.AnotherInterface"].Reverse("Hello world!")
+
+main = DBus::Main.new
+main << session_bus
+main.run
+
--- /dev/null
+#!/usr/bin/ruby
+
+require 'dbus'
+require 'thread'
+Thread.abort_on_exception = true
+
+class Test < DBus::Object
+ # Create an interface aggregating all upcoming dbus_method defines.
+ dbus_interface "org.ruby.SampleInterface" do
+ dbus_method :hello, "in name:s, in name2:s" do |name, name2|
+ puts "hello(#{name}, #{name2})"
+ end
+
+ dbus_method :test_variant, "in stuff:v" do |variant|
+ p variant
+ end
+
+ dbus_signal :SomethingJustHappened, "toto:s, tutu:u"
+ end
+
+ dbus_interface "org.ruby.AnotherInterface" do
+ dbus_method :ThatsALongMethodNameIThink do
+ puts "ThatsALongMethodNameIThink"
+ end
+ dbus_method :Reverse, "in instr:s, out outstr:s" do |instr|
+ outstr = instr.split(//).reverse.join
+ puts "got: #{instr}, replying: #{outstr}"
+ [outstr]
+ end
+ end
+end
+
+bus = DBus::SessionBus.instance
+service = bus.request_service("org.ruby.service")
+myobj = Test.new("/org/ruby/MyInstance")
+service.export(myobj)
+
+Thread.new do
+ i = 0
+ loop do
+ # Signal emission
+ myobj.SomethingJustHappened("hey", i += 1)
+ sleep(0.5)
+ end
+end
+
+puts "listening"
+main = DBus::Main.new
+main << bus
+main.run
+
--- /dev/null
+#!/usr/bin/ruby
+
+require "dbus"
+
+session_bus = DBus::SessionBus.instance
+
+# Get the Rhythmbox service
+rhythmbox = session_bus.service("org.gnome.Rhythmbox")
+
+# Get the object from this service
+player = rhythmbox.object("/org/gnome/Rhythmbox/Player")
+
+# Introspect it
+player.introspect
+if player.has_iface? "org.gnome.Rhythmbox.Player"
+ puts "We have Rhythmbox Player interface"
+end
+
+player_with_iface = player["org.gnome.Rhythmbox.Player"]
+p player_with_iface.getPlayingUri
+
+# Maybe support default_iface=(iface_str) on an ProxyObject, so
+# that this is possible?
+player.default_iface = "org.gnome.Rhythmbox.Player"
+puts "default_iface test:"
+p player.getPlayingUri
+player.on_signal("elapsedChanged") do |u|
+ puts "elapsedChanged: #{u}"
+end
+
+main = DBus::Main.new
+main << session_bus
+main.run
+
--- /dev/null
+#!/usr/bin/ruby
+
+require 'dbus'
+
+d = if ARGV.member?("--system")
+ DBus::SystemBus.instance
+else
+ DBus::SessionBus.instance
+end
+d.proxy.ListNames[0].each{ |n| puts "\t#{n}" }
+
--- /dev/null
+#!/usr/bin/ruby
+
+require 'dbus'
+
+if ARGV.size < 2
+ puts "Usage:"
+ puts "notify.rb \"title\" \"body\""
+ exit
+end
+
+d = DBus::SessionBus.instance
+o = d.service("org.freedesktop.Notifications").object("/org/freedesktop/Notifications")
+o.introspect
+
+i = o["org.freedesktop.Notifications"]
+
+i.Notify('notify.rb', 0, 'info', ARGV[0], ARGV[1], [], {}, 2000) do |ret, param|
+end
+
--- /dev/null
+# dbus.rb - Module containing the low-level D-Bus implementation
+#
+# This file is part of the ruby-dbus project
+# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License, version 2.1 as published by the Free Software Foundation.
+# See the file "COPYING" for the exact licensing terms.
+
+require 'dbus/type'
+require 'dbus/introspect'
+require 'dbus/export'
+require 'dbus/bus.rb'
+require 'dbus/marshall'
+require 'dbus/message'
+require 'dbus/matchrule'
+require 'dbus/auth'
+
+require 'socket'
+require 'thread'
+
+# = D-Bus main module
+#
+# Module containing all the D-Bus modules and classes.
+module DBus
+ # Default socket name for the system bus.
+ SystemSocketName = "unix:path=/var/run/dbus/system_bus_socket"
+
+ # Byte signifying big endianness.
+ BIG_END = ?B
+ # Byte signifying little endianness.
+ LIL_END = ?l
+
+ # Byte signifying the host's endianness.
+ HOST_END = if [0x01020304].pack("L").unpack("V")[0] == 0x01020304
+ LIL_END
+ else
+ BIG_END
+ end
+
+ # General exceptions.
+
+ # Exception raised when an invalid packet is encountered.
+ class InvalidPacketException < Exception
+ end
+
+ # Exception raised when there is a problem with a type (may be unknown or
+ # mismatch).
+ class TypeException < Exception
+ end
+
+ # Exception raised when an unmarshalled buffer is truncated and
+ # incomplete.
+ class IncompleteBufferException < Exception
+ end
+
+ # Exception raised when an interface is not implemented.
+ class InterfaceNotImplemented < Exception
+ end
+
+ # Exception raised when an method is not found in the interface.
+ class MethodNotInInterface < Exception
+ end
+
+ # Exception raised when a method has not been implemented (yet).
+ class MethodNotImplemented < Exception
+ end
+
+ # Exception raised when a method is invoked with invalid
+ # parameters (wrong number or type).
+ class InvalidParameters < Exception
+ end
+
+ # Exception raised when an invalid method name is used.
+ class InvalidMethodName < Exception
+ end
+
+ # Exception raised when invalid introspection data is parsed/used.
+ class InvalidIntrospectionData < Exception
+ end
+end # module DBus
--- /dev/null
+# This file is part of the ruby-dbus project
+# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License, version 2.1 as published by the Free Software Foundation.
+# See the file "COPYING" for the exact licensing terms.
+
+module DBus
+ # Exception raised when authentication fails somehow.
+ class AuthenticationFailed < Exception
+ end
+
+ # = General class for authentication.
+ class Authenticator
+ # Returns the name of the authenticator.
+ def name
+ self.class.to_s.upcase.sub(/.*::/, "")
+ end
+ end
+
+ # = External authentication class
+ #
+ # Class for 'external' type authentication.
+ class External < Authenticator
+ # Performs the authentication.
+ def authenticate
+ # Take the user id (eg integer 1000) make a string out of it "1000", take
+ # each character and determin hex value "1" => 0x31, "0" => 0x30. You
+ # obtain for "1000" => 31303030 This is what the server is expecting.
+ # Why? I dunno. How did I come to that conclusion? by looking at rbus
+ # code. I have no idea how he found that out.
+ return Process.uid.to_s.split(//).collect { |a| "%x" % a[0] }.join
+ end
+ end
+
+ # Note: this following stuff is tested with External authenticator only!
+
+ # = Authentication client class.
+ #
+ # Class tha performs the actional authentication.
+ class Client
+ # Create a new authentication client.
+ def initialize(socket)
+ @socket = socket
+ @state = nil
+ @auth_list = [External]
+ end
+
+ # Start the authentication process.
+ def authenticate
+ @socket.write(0.chr)
+ next_authenticator
+ @state = :Starting
+ while @state != :Authenticated
+ r = next_state
+ return r if not r
+ end
+ true
+ end
+
+ ##########
+ private
+ ##########
+
+ # Send an authentication method _meth_ with arguments _args_ to the
+ # server.
+ def send(meth, *args)
+ o = ([meth] + args).join(" ")
+ @socket.write(o + "\r\n")
+ end
+
+ # Try authentication using the next authenticator.
+ def next_authenticator
+ raise AuthenticationFailed if @auth_list.size == 0
+ @authenticator = @auth_list.shift.new
+ send("AUTH", @authenticator.name, @authenticator.authenticate)
+ end
+
+
+ # Read data (a buffer) from the bus until CR LF is encountered.
+ # Return the buffer without the CR LF characters.
+ def next_msg
+ @socket.readline.chomp.split(" ")
+ end
+
+ # Try to reach the next state based on the current state.
+ def next_state
+ msg = next_msg
+ if @state == :Starting
+ case msg[0]
+ when "CONTINUE"
+ @state = :WaitingForData
+ when "OK"
+ @state = :WaitingForOk
+ end
+ end
+ case @state
+ when :WaitingForData
+ case msg[0]
+ when "DATA"
+ chall = msg[1]
+ resp, chall = @authenticator.data(chall)
+ case resp
+ when :AuthContinue
+ send("DATA", chall)
+ @state = :WaitingForData
+ when :AuthOk
+ send("DATA", chall)
+ @state = :WaitingForOk
+ when :AuthError
+ send("ERROR")
+ @state = :WaitingForData
+ end
+ when "REJECTED"
+ next_authenticator
+ @state = :WaitingForData
+ when "ERROR"
+ send("CANCEL")
+ @state = :WaitingForReject
+ when "OK"
+ send("BEGIN")
+ @state = :Authenticated
+ else
+ send("ERROR")
+ @state = :WaitingForData
+ end
+ when :WaitingForOk
+ case msg[0]
+ when "OK"
+ send("BEGIN")
+ @state = :Authenticated
+ when "REJECT"
+ next_authenticator
+ @state = :WaitingForData
+ when "DATA", "ERROR"
+ send("CANCEL")
+ @state = :WaitingForReject
+ else
+ send("ERROR")
+ @state = :WaitingForOk
+ end
+ when :WaitingForReject
+ case msg[0]
+ when "REJECT"
+ next_authenticator
+ @state = :WaitingForOk
+ else
+ @socket.close
+ return false
+ end
+ end
+ return true
+ end # def next_state
+ end # class Client
+end # module D-Bus
--- /dev/null
+# dbus.rb - Module containing the low-level D-Bus implementation
+#
+# This file is part of the ruby-dbus project
+# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License, version 2.1 as published by the Free Software Foundation.
+# See the file "COPYING" for the exact licensing terms.
+
+require 'socket'
+require 'thread'
+require 'singleton'
+
+# = D-Bus main module
+#
+# Module containing all the D-Bus modules and classes.
+module DBus
+ # This represents a remote service. It should not be instancied directly
+ # Use Bus::service()
+ class Service
+ # The service name.
+ attr_reader :name
+ # The bus the service is running on.
+ attr_reader :bus
+ # The service root (FIXME).
+ attr_reader :root
+
+ # Create a new service with a given _name_ on a given _bus_.
+ def initialize(name, bus)
+ @name, @bus = name, bus
+ @root = Node.new("/")
+ end
+
+ # Determine whether the serice name already exists.
+ def exists?
+ bus.proxy.ListName.member?(@name)
+ end
+
+ # Perform an introspection on all the objects on the service
+ # (starting recursively from the root).
+ def introspect
+ if block_given?
+ raise NotImplementedError
+ else
+ rec_introspect(@root, "/")
+ end
+ self
+ end
+
+ # Retrieves an object at the given _path_.
+ def object(path)
+ node = get_node(path, true)
+ if node.object.nil?
+ node.object = ProxyObject.new(@bus, @name, path)
+ end
+ node.object
+ end
+
+ # Export an object _obj_ (an DBus::Object subclass instance).
+ def export(obj)
+ obj.service = self
+ get_node(obj.path, true).object = obj
+ end
+
+ # Get the object node corresponding to the given _path_. if _create_ is
+ # true, the the nodes in the path are created if they do not already exist.
+ def get_node(path, create = false)
+ n = @root
+ path.sub(/^\//, "").split("/").each do |elem|
+ if not n[elem]
+ if not create
+ return nil
+ else
+ n[elem] = Node.new(elem)
+ end
+ end
+ n = n[elem]
+ end
+ if n.nil?
+ puts "Warning, unknown object #{path}" if $DEBUG
+ end
+ n
+ end
+
+ #########
+ private
+ #########
+
+ # Perform a recursive retrospection on the given current _node_
+ # on the given _path_.
+ def rec_introspect(node, path)
+ xml = bus.introspect_data(@name, path)
+ intfs, subnodes = IntrospectXMLParser.new(xml).parse
+ subnodes.each do |nodename|
+ subnode = node[nodename] = Node.new(nodename)
+ if path == "/"
+ subpath = "/" + nodename
+ else
+ subpath = path + "/" + nodename
+ end
+ rec_introspect(subnode, subpath)
+ end
+ if intfs.size > 0
+ node.object = ProxyObjectFactory.new(xml, @bus, @name, path).build
+ end
+ end
+ end
+
+ # = Object path node class
+ #
+ # Class representing a node on an object path.
+ class Node < Hash
+ # The D-Bus object contained by the node.
+ attr_accessor :object
+ # The name of the node.
+ attr_reader :name
+
+ # Create a new node with a given _name_.
+ def initialize(name)
+ @name = name
+ @object = nil
+ end
+
+ # Return an XML string representation of the node.
+ def to_xml
+ xml = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+'
+ self.each_pair do |k, v|
+ xml += "<node name=\"#{k}\" />"
+ end
+ if @object
+ @object.intfs.each_pair do |k, v|
+ xml += %{<interface name="#{v.name}">\n}
+ v.methods.each_value { |m| xml += m.to_xml }
+ v.signals.each_value { |m| xml += m.to_xml }
+ xml +="</interface>\n"
+ end
+ end
+ xml += '</node>'
+ xml
+ end
+
+ # Return inspect information of the node.
+ def inspect
+ # Need something here
+ "<DBus::Node #{sub_inspect}>"
+ end
+
+ # Return instance inspect information, used by Node#inspect.
+ def sub_inspect
+ s = ""
+ if not @object.nil?
+ s += "%x " % @object.object_id
+ end
+ s + "{" + keys.collect { |k| "#{k} => #{self[k].sub_inspect}" }.join(",") + "}"
+ end
+ end # class Inspect
+
+ # FIXME: rename Connection to Bus?
+
+ # D-Bus main connection class
+ #
+ # Main class that maintains a connection to a bus and can handle incoming
+ # and outgoing messages.
+ class Connection
+ # The unique name (by specification) of the message.
+ attr_reader :unique_name
+ # The socket that is used to connect with the bus.
+ attr_reader :socket
+
+ # Create a new connection to the bus for a given connect _path_. _path_
+ # format is described in the D-Bus specification:
+ # http://dbus.freedesktop.org/doc/dbus-specification.html#addresses
+ # and is something like:
+ # "transport1:key1=value1,key2=value2;transport2:key1=value1,key2=value2"
+ # e.g. "unix:path=/tmp/dbus-test"
+ #
+ # Current implementation of ruby-dbus supports only a single server
+ # address and only "unix:path=...,guid=..." and
+ # "unix:abstract=...,guid=..." forms
+ def initialize(path)
+ @path = path
+ @unique_name = nil
+ @buffer = ""
+ @method_call_replies = Hash.new
+ @method_call_msgs = Hash.new
+ @signal_matchrules = Array.new
+ @proxy = nil
+ # FIXME: can be TCP or any stream
+ @socket = Socket.new(Socket::Constants::PF_UNIX,
+ Socket::Constants::SOCK_STREAM, 0)
+ @object_root = Node.new("/")
+ end
+
+ # Connect to the bus and initialize the connection.
+ def connect
+ parse_session_string
+ if @transport == "unix" and @type == "abstract"
+ if HOST_END == LIL_END
+ sockaddr = "\1\0\0#{@unix_abstract}"
+ else
+ sockaddr = "\0\1\0#{@unix_abstract}"
+ end
+ elsif @transport == "unix" and @type == "path"
+ sockaddr = Socket.pack_sockaddr_un(@unix)
+ end
+ @socket.connect(sockaddr)
+ init_connection
+ end
+
+ # Send the buffer _buf_ to the bus using Connection#writel.
+ def send(buf)
+ @socket.write(buf)
+ end
+
+ # Tell a bus to register itself on the glib main loop
+ def glibize
+ require 'glib2'
+ # Circumvent a ruby-glib bug
+ @channels ||= Array.new
+
+ gio = GLib::IOChannel.new(@socket.fileno)
+ @channels << gio
+ gio.add_watch(GLib::IOChannel::IN) do |c, ch|
+ update_buffer
+ messages.each do |msg|
+ process(msg)
+ end
+ true
+ end
+ end
+
+ # FIXME: describe the following names, flags and constants.
+ # See DBus spec for definition
+ NAME_FLAG_ALLOW_REPLACEMENT = 0x1
+ NAME_FLAG_REPLACE_EXISTING = 0x2
+ NAME_FLAG_DO_NOT_QUEUE = 0x4
+
+ REQUEST_NAME_REPLY_PRIMARY_OWNER = 0x1
+ REQUEST_NAME_REPLY_IN_QUEUE = 0x2
+ REQUEST_NAME_REPLY_EXISTS = 0x3
+ REQUEST_NAME_REPLY_ALREADY_OWNER = 0x4
+
+ DBUSXMLINTRO = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <interface name="org.freedesktop.DBus.Introspectable">
+ <method name="Introspect">
+ <arg name="data" direction="out" type="s"/>
+ </method>
+ </interface>
+ <interface name="org.freedesktop.DBus">
+ <method name="RequestName">
+ <arg direction="in" type="s"/>
+ <arg direction="in" type="u"/>
+ <arg direction="out" type="u"/>
+ </method>
+ <method name="ReleaseName">
+ <arg direction="in" type="s"/>
+ <arg direction="out" type="u"/>
+ </method>
+ <method name="StartServiceByName">
+ <arg direction="in" type="s"/>
+ <arg direction="in" type="u"/>
+ <arg direction="out" type="u"/>
+ </method>
+ <method name="Hello">
+ <arg direction="out" type="s"/>
+ </method>
+ <method name="NameHasOwner">
+ <arg direction="in" type="s"/>
+ <arg direction="out" type="b"/>
+ </method>
+ <method name="ListNames">
+ <arg direction="out" type="as"/>
+ </method>
+ <method name="ListActivatableNames">
+ <arg direction="out" type="as"/>
+ </method>
+ <method name="AddMatch">
+ <arg direction="in" type="s"/>
+ </method>
+ <method name="RemoveMatch">
+ <arg direction="in" type="s"/>
+ </method>
+ <method name="GetNameOwner">
+ <arg direction="in" type="s"/>
+ <arg direction="out" type="s"/>
+ </method>
+ <method name="ListQueuedOwners">
+ <arg direction="in" type="s"/>
+ <arg direction="out" type="as"/>
+ </method>
+ <method name="GetConnectionUnixUser">
+ <arg direction="in" type="s"/>
+ <arg direction="out" type="u"/>
+ </method>
+ <method name="GetConnectionUnixProcessID">
+ <arg direction="in" type="s"/>
+ <arg direction="out" type="u"/>
+ </method>
+ <method name="GetConnectionSELinuxSecurityContext">
+ <arg direction="in" type="s"/>
+ <arg direction="out" type="ay"/>
+ </method>
+ <method name="ReloadConfig">
+ </method>
+ <signal name="NameOwnerChanged">
+ <arg type="s"/>
+ <arg type="s"/>
+ <arg type="s"/>
+ </signal>
+ <signal name="NameLost">
+ <arg type="s"/>
+ </signal>
+ <signal name="NameAcquired">
+ <arg type="s"/>
+ </signal>
+ </interface>
+</node>
+'
+
+ def introspect_data(dest, path)
+ m = DBus::Message.new(DBus::Message::METHOD_CALL)
+ m.path = path
+ m.interface = "org.freedesktop.DBus.Introspectable"
+ m.destination = dest
+ m.member = "Introspect"
+ m.sender = unique_name
+ if not block_given?
+ # introspect in synchronous !
+ send_sync(m) do |rmsg|
+ if rmsg.is_a?(Error)
+ raise rmsg
+ else
+ return rmsg.params[0]
+ end
+ end
+ else
+ send(m.marshall)
+ on_return(m) do |rmsg|
+ if rmsg.is_a?(Error)
+ yield rmsg
+ else
+ yield rmsg.params[0]
+ end
+ end
+ end
+ nil
+ end
+
+ # Issues a call to the org.freedesktop.DBus.Introspectable.Introspect method
+ # _dest_ is the service and _path_ the object path you want to introspect
+ # If a code block is given, the introspect call in asynchronous. If not
+ # data is returned
+ #
+ # FIXME: link to ProxyObject data definition
+ # The returned object is a ProxyObject that has methods you can call to
+ # issue somme METHOD_CALL messages, and to setup to receive METHOD_RETURN
+ def introspect(dest, path)
+ if not block_given?
+ # introspect in synchronous !
+ data = introspect_data(dest, path)
+ pof = DBus::ProxyObjectFactory.new(data, self, dest, path)
+ return pof.build
+ else
+ introspect_data(dest, path) do |data|
+ yield(DBus::ProxyObjectFactory.new(data, self, dest, path).build)
+ end
+ end
+ end
+
+ # Exception raised when a service name is requested that is not available.
+ class NameRequestError < Exception
+ end
+
+ # Attempt to request a service _name_.
+ def request_service(name)
+ r = proxy.RequestName(name, NAME_FLAG_REPLACE_EXISTING)
+ raise NameRequestError if r[0] != REQUEST_NAME_REPLY_PRIMARY_OWNER
+ @service = Service.new(name, self)
+ @service
+ end
+
+ # Set up a ProxyObject for the bus itself, since the bus is introspectable.
+ # Returns the object.
+ def proxy
+ if @proxy == nil
+ path = "/org/freedesktop/DBus"
+ dest = "org.freedesktop.DBus"
+ pof = DBus::ProxyObjectFactory.new(DBUSXMLINTRO, self, dest, path)
+ @proxy = pof.build["org.freedesktop.DBus"]
+ end
+ @proxy
+ end
+
+ # Fill (append) the buffer from data that might be available on the
+ # socket.
+ def update_buffer
+ @buffer += @socket.read_nonblock(MSG_BUF_SIZE)
+ end
+
+ # Get one message from the bus and remove it from the buffer.
+ # Return the message.
+ def pop_message
+ ret = nil
+ begin
+ ret, size = Message.new.unmarshall_buffer(@buffer)
+ @buffer.slice!(0, size)
+ rescue IncompleteBufferException => e
+ # fall through, let ret be null
+ end
+ ret
+ end
+
+ # Retrieve all the messages that are currently in the buffer.
+ def messages
+ ret = Array.new
+ while msg = pop_message
+ ret << msg
+ end
+ ret
+ end
+
+ # The buffer size for messages.
+ MSG_BUF_SIZE = 4096
+
+ # Update the buffer and retrieve all messages using Connection#messages.
+ # Return the messages.
+ def poll_messages
+ ret = nil
+ r, d, d = IO.select([@socket], nil, nil, 0)
+ if r and r.size > 0
+ update_buffer
+ end
+ messages
+ end
+
+ # Wait for a message to arrive. Return it once it is available.
+ def wait_for_message
+ ret = pop_message
+ while ret == nil
+ r, d, d = IO.select([@socket])
+ if r and r[0] == @socket
+ update_buffer
+ ret = pop_message
+ end
+ end
+ ret
+ end
+
+ # Send a message _m_ on to the bus. This is done synchronously, thus
+ # the call will block until a reply message arrives.
+ def send_sync(m, &retc) # :yields: reply/return message
+ send(m.marshall)
+ @method_call_msgs[m.serial] = m
+ @method_call_replies[m.serial] = retc
+
+ retm = wait_for_message
+ process(retm)
+ until [DBus::Message::ERROR,
+ DBus::Message::METHOD_RETURN].include?(retm.message_type) and
+ retm.reply_serial == m.serial
+ retm = wait_for_message
+ process(retm)
+ end
+ end
+
+ # Specify a code block that has to be executed when a reply for
+ # message _m_ is received.
+ def on_return(m, &retc)
+ # Have a better exception here
+ if m.message_type != Message::METHOD_CALL
+ raise "on_return should only get method_calls"
+ end
+ @method_call_msgs[m.serial] = m
+ @method_call_replies[m.serial] = retc
+ end
+
+ # Asks bus to send us messages matching mr, and execute slot when
+ # received
+ def add_match(mr, &slot)
+ # check this is a signal.
+ @signal_matchrules << [mr, slot]
+ self.proxy.AddMatch(mr.to_s)
+ end
+
+ # Process a message _m_ based on its type.
+ # method call:: FIXME...
+ # method call return value:: FIXME...
+ # signal:: FIXME...
+ # error:: FIXME...
+ def process(m)
+ case m.message_type
+ when Message::ERROR, Message::METHOD_RETURN
+ raise InvalidPacketException if m.reply_serial == nil
+ mcs = @method_call_replies[m.reply_serial]
+ if not mcs
+ puts "no return code for #{mcs.inspect} (#{m.inspect})" if $DEBUG
+ else
+ if m.message_type == Message::ERROR
+ mcs.call(Error.new(m))
+ else
+ mcs.call(m)
+ end
+ @method_call_replies.delete(m.reply_serial)
+ @method_call_msgs.delete(m.reply_serial)
+ end
+ when DBus::Message::METHOD_CALL
+ if m.path == "/org/freedesktop/DBus"
+ puts "Got method call on /org/freedesktop/DBus" if $DEBUG
+ end
+ # handle introspectable as an exception:
+ if m.interface == "org.freedesktop.DBus.Introspectable" and
+ m.member == "Introspect"
+ reply = Message.new(Message::METHOD_RETURN).reply_to(m)
+ reply.sender = @unique_name
+ node = @service.get_node(m.path)
+ raise NotImplementedError if not node
+ reply.sender = @unique_name
+ reply.add_param(Type::STRING, @service.get_node(m.path).to_xml)
+ send(reply.marshall)
+ else
+ node = @service.get_node(m.path)
+ return if node.nil?
+ obj = node.object
+ return if obj.nil?
+ obj.dispatch(m) if obj
+ end
+ when DBus::Message::SIGNAL
+ @signal_matchrules.each do |elem|
+ mr, slot = elem
+ if mr.match(m)
+ slot.call(m)
+ return
+ end
+ end
+ else
+ puts "Unknown message type: #{m.message_type}" if $DEBUG
+ end
+ end
+
+ # Retrieves the service with the given _name_.
+ def service(name)
+ # The service might not exist at this time so we cannot really check
+ # anything
+ Service.new(name, self)
+ end
+ alias :[] :service
+
+ # Emit a signal event for the given _service_, object _obj_, interface
+ # _intf_ and signal _sig_ with arguments _args_.
+ def emit(service, obj, intf, sig, *args)
+ m = Message.new(DBus::Message::SIGNAL)
+ m.path = obj.path
+ m.interface = intf.name
+ m.member = sig.name
+ m.sender = service.name
+ i = 0
+ sig.params.each do |par|
+ m.add_param(par[1], args[i])
+ i += 1
+ end
+ send(m.marshall)
+ end
+
+ ###########################################################################
+ private
+
+ # Send a hello messages to the bus to let it know we are here.
+ def send_hello
+ m = Message.new(DBus::Message::METHOD_CALL)
+ m.path = "/org/freedesktop/DBus"
+ m.destination = "org.freedesktop.DBus"
+ m.interface = "org.freedesktop.DBus"
+ m.member = "Hello"
+ send_sync(m) do |rmsg|
+ @unique_name = rmsg.destination
+ puts "Got hello reply. Our unique_name is #{@unique_name}" if $DEBUG
+ end
+ end
+
+ # Parse the session string (socket address).
+ def parse_session_string
+ path_parsed = /^([^:]*):([^;]*)$/.match(@path)
+ @transport = path_parsed[1]
+ adr = path_parsed[2]
+ if @transport == "unix"
+ adr.split(",").each do |eqstr|
+ idx, val = eqstr.split("=")
+ case idx
+ when "path"
+ @type = idx
+ @unix = val
+ when "abstract"
+ @type = idx
+ @unix_abstract = val
+ when "guid"
+ @guid = val
+ end
+ end
+ end
+ end
+
+ # Initialize the connection to the bus.
+ def init_connection
+ @client = Client.new(@socket)
+ @client.authenticate
+ # TODO: code some real stuff here
+ #writel("AUTH EXTERNAL 31303030")
+ #s = readl
+ # parse OK ?
+ #writel("BEGIN")
+ end
+ end # class Connection
+
+ # = D-Bus session bus class
+ #
+ # The session bus is a session specific bus (mostly for desktop use).
+ # This is a singleton class.
+ class SessionBus < Connection
+ include Singleton
+
+ # Get the the default session bus.
+ def initialize
+ super(ENV["DBUS_SESSION_BUS_ADDRESS"])
+ connect
+ send_hello
+ end
+ end
+
+ # = D-Bus system bus class
+ #
+ # The system bus is a system-wide bus mostly used for global or
+ # system usages. This is a singleton class.
+ class SystemBus < Connection
+ include Singleton
+
+ # Get the default system bus.
+ def initialize
+ super(SystemSocketName)
+ connect
+ send_hello
+ end
+ end
+
+ # FIXME: we should get rid of these
+
+ def DBus.system_bus
+ SystemBus.instance
+ end
+
+ def DBus.session_bus
+ SessionBus.instance
+ end
+
+ # = Main event loop class.
+ #
+ # Class that takes care of handling message and signal events
+ # asynchronously. *Note:* This is a native implement and therefore does
+ # not integrate with a graphical widget set main loop.
+ class Main
+ # Create a new main event loop.
+ def initialize
+ @buses = Hash.new
+ end
+
+ # Add a _bus_ to the list of buses to watch for events.
+ def <<(bus)
+ @buses[bus.socket] = bus
+ end
+
+ # Run the main loop. This is a blocking call!
+ def run
+ loop do
+ ready, dum, dum = IO.select(@buses.keys)
+ ready.each do |socket|
+ b = @buses[socket]
+ b.update_buffer
+ while m = b.pop_message
+ b.process(m)
+ end
+ end
+ end
+ end
+ end # class Main
+end # module DBus
--- /dev/null
+# dbus/introspection.rb - module containing a low-level D-Bus introspection implementation
+#
+# This file is part of the ruby-dbus project
+# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License, version 2.1 as published by the Free Software Foundation.
+# See the file "COPYING" for the exact licensing terms.
+
+require 'thread'
+
+module DBus
+ # Exception raised when an interface cannot be found in an object.
+ class InterfaceNotInObject < Exception
+ end
+
+ # Exception raised when a method cannot be found in an inferface.
+ class MethodNotInInterface < Exception
+ end
+
+ # Method raised when a method returns an invalid return type.
+ class InvalidReturnType < Exception
+ end
+
+ # Exported object type
+ # = Exportable D-Bus object class
+ #
+ # Objects that are going to be exported by a D-Bus service
+ # should inherit from this class.
+ class Object
+ # The path of the object.
+ attr_reader :path
+ # The interfaces that the object supports.
+ attr_reader :intfs
+ # The service that the object is exported by.
+ attr_writer :service
+
+ @@intfs = Hash.new
+ @@cur_intf = nil
+ @@intfs_mutex = Mutex.new
+
+ # Create a new object with a given _path_.
+ def initialize(path)
+ @path = path
+ @intfs = @@intfs.dup
+ @service = nil
+ end
+
+ # State that the object implements the given _intf_.
+ def implements(intf)
+ @intfs[intf.name] = intf
+ end
+
+ # Dispatch a message _msg_.
+ def dispatch(msg)
+ case msg.message_type
+ when Message::METHOD_CALL
+ if not @intfs[msg.interface]
+ raise InterfaceNotInObject, msg.interface
+ end
+ meth = @intfs[msg.interface].methods[msg.member.to_sym]
+ raise MethodNotInInterface if not meth
+ methname = Object.make_method_name(msg.interface, msg.member)
+ retdata = method(methname).call(*msg.params).to_a
+
+ reply = Message.new.reply_to(msg)
+ meth.rets.zip(retdata).each do |rsig, rdata|
+ reply.add_param(rsig[1], rdata)
+ end
+ @service.bus.send(reply.marshall)
+ end
+ end
+
+ # Select (and create) the interface that the following defined methods
+ # belong to.
+ def self.dbus_interface(s)
+ @@intfs_mutex.synchronize do
+ @@cur_intf = @@intfs[s] = Interface.new(s)
+ yield
+ @@cur_intf = nil
+ end
+ end
+
+ # Dummy undefined interface class.
+ class UndefinedInterface
+ end
+
+ # Defines an exportable method on the object with the given name _sym_,
+ # _prototype_ and the code in a block.
+ def self.dbus_method(sym, protoype = "", &block)
+ raise UndefinedInterface if @@cur_intf.nil?
+ @@cur_intf.define(Method.new(sym.to_s).from_prototype(protoype))
+ define_method(Object.make_method_name(@@cur_intf.name, sym.to_s), &block)
+ end
+
+ # Emits a signal from the object with the given _interface_, signal
+ # _sig_ and arguments _args_.
+ def emit(intf, sig, *args)
+ @service.bus.emit(@service, self, intf, sig, *args)
+ end
+
+ # Defines a signal for the object with a given name _sym_ and _prototype_.
+ def self.dbus_signal(sym, protoype = "")
+ raise UndefinedInterface if @@cur_intf.nil?
+ cur_intf = @@cur_intf
+ signal = Signal.new(sym.to_s).from_prototype(protoype)
+ cur_intf.define(Signal.new(sym.to_s).from_prototype(protoype))
+ define_method(sym.to_s) do |*args|
+ emit(cur_intf, signal, *args)
+ end
+ end
+
+ ####################################################################
+ private
+
+ # Helper method that returns a method name generated from the interface
+ # name _intfname_ and method name _methname_.
+ def self.make_method_name(intfname, methname)
+ "#{intfname}%%#{methname}"
+ end
+ end # class Object
+end # module DBus
--- /dev/null
+# dbus/introspection.rb - module containing a low-level D-Bus introspection implementation
+#
+# This file is part of the ruby-dbus project
+# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License, version 2.1 as published by the Free Software Foundation.
+# See the file "COPYING" for the exact licensing terms.
+
+require 'rexml/document'
+
+module DBus
+ # Regular expressions that should match all method names.
+ MethodSignalRE = /^[A-Za-z][A-Za-z0-9_]*$/
+ # Regular expressions that should match all interface names.
+ InterfaceElementRE = /^[A-Za-z][A-Za-z0-9_]*$/
+
+ # Exception raised when an unknown signal is used.
+ class UnknownSignal < Exception
+ end
+
+ # Exception raised when an invalid class definition is encountered.
+ class InvalidClassDefinition < Exception
+ end
+
+ # = D-Bus interface class
+ #
+ # This class is the interface descriptor. In most cases, the Introspect()
+ # method call instanciates and configures this class for us.
+ #
+ # It also is the local definition of interface exported by the program.
+ class Interface
+ # The name of the interface.
+ attr_reader :name
+ # The methods that are part of the interface.
+ attr_reader :methods
+ # The signals that are part of the interface.
+ attr_reader :signals
+
+ # Creates a new interface with a given _name_.
+ def initialize(name)
+ validate_name(name)
+ @name = name
+ @methods, @signals = Hash.new, Hash.new
+ end
+
+ # Validates a service _name_.
+ def validate_name(name)
+ raise InvalidIntrospectionData if name.size > 255
+ raise InvalidIntrospectionData if name =~ /^\./ or name =~ /\.$/
+ raise InvalidIntrospectionData if name =~ /\.\./
+ raise InvalidIntrospectionData if not name =~ /\./
+ name.split(".").each do |element|
+ raise InvalidIntrospectionData if not element =~ InterfaceElementRE
+ end
+ end
+
+ # Helper method for defining a method _m_.
+ def define(m)
+ if m.kind_of?(Method)
+ @methods[m.name.to_sym] = m
+ elsif m.kind_of?(Signal)
+ @signals[m.name.to_sym] = m
+ end
+ end
+ alias :<< :define
+
+ # Defines a method with name _id_ and a given _prototype_ in the
+ # interface.
+ def define_method(id, prototype)
+ m = Method.new(id)
+ m.from_prototype(prototype)
+ define(m)
+ end
+ end # class Interface
+
+ # = D-Bus interface element class
+ #
+ # This is a generic class for entities that are part of the interface
+ # such as methods and signals.
+ class InterfaceElement
+ # The name of the interface element.
+ attr_reader :name
+ # The parameters of the interface element
+ attr_reader :params
+
+ # Validates element _name_.
+ def validate_name(name)
+ if (not name =~ MethodSignalRE) or (name.size > 255)
+ raise InvalidMethodName
+ end
+ end
+
+ # Creates a new element with the given _name_.
+ def initialize(name)
+ validate_name(name.to_s)
+ @name = name
+ @params = Array.new
+ end
+
+ # Adds a parameter _param_.
+ def add_param(param)
+ @params << param
+ end
+ end # class InterfaceElement
+
+ # = D-Bus interface method class
+ #
+ # This is a class representing methods that are part of an interface.
+ class Method < InterfaceElement
+ # The list of return values for the method.
+ attr_reader :rets
+
+ # Creates a new method interface element with the given _name_.
+ def initialize(name)
+ super(name)
+ @rets = Array.new
+ end
+
+ # Add a return value _ret_.
+ def add_return(ret)
+ @rets << ret
+ end
+
+ # Add parameter types by parsing the given _prototype_.
+ def from_prototype(prototype)
+ prototype.split(/, */).each do |arg|
+ arg = arg.split(" ")
+ raise InvalidClassDefinition if arg.size != 2
+ dir, arg = arg
+ if arg =~ /:/
+ arg = arg.split(":")
+ name, sig = arg
+ else
+ sig = arg
+ end
+ case dir
+ when "in"
+ add_param([name, sig])
+ when "out"
+ add_return([name, sig])
+ end
+ end
+ self
+ end
+
+ # Return an XML string representation of the method interface elment.
+ def to_xml
+ xml = %{<method name="#{@name}">\n}
+ @params.each do |param|
+ name = param[0] ? %{name="#{param[0]}" } : ""
+ xml += %{<arg #{name}direction="in" type="#{param[1]}"/>\n}
+ end
+ @rets.each do |param|
+ name = param[0] ? %{name="#{param[0]}" } : ""
+ xml += %{<arg #{name}direction="out" type="#{param[1]}"/>\n}
+ end
+ xml += %{</method>\n}
+ xml
+ end
+ end # class Method
+
+ # = D-Bus interface signal class
+ #
+ # This is a class representing signals that are part of an interface.
+ class Signal < InterfaceElement
+ # Add parameter types based on the given _prototype_.
+ def from_prototype(prototype)
+ prototype.split(/, */).each do |arg|
+ if arg =~ /:/
+ arg = arg.split(":")
+ name, sig = arg
+ else
+ sig = arg
+ end
+ add_param([name, sig])
+ end
+ self
+ end
+
+ # Return an XML string representation of the signal interface elment.
+ def to_xml
+ xml = %{<signal name="#{@name}">\n}
+ @params.each do |param|
+ name = param[0] ? %{name="#{param[0]}" } : ""
+ xml += %{<arg #{name}type="#{param[1]}"/>\n}
+ end
+ xml += %{</signal>\n}
+ xml
+ end
+ end # class Signal
+
+ # = D-Bus introspect XML parser class
+ #
+ # This class parses introspection XML of an object and constructs a tree
+ # of Node, Interface, Method, Signal instances.
+ class IntrospectXMLParser
+ # Creates a new parser for XML data in string _xml_.
+ def initialize(xml)
+ @xml = xml
+ end
+
+ # Recursively parses the subnodes, constructing the tree.
+ def parse_subnodes
+ subnodes = Array.new
+ t = Time.now
+ d = REXML::Document.new(@xml)
+ d.elements.each("node/node") do |e|
+ subnodes << e.attributes["name"]
+ end
+ subnodes
+ end
+
+ # Parses the XML, constructing the tree.
+ def parse
+ ret = Array.new
+ subnodes = Array.new
+ t = Time.now
+ d = REXML::Document.new(@xml)
+ d.elements.each("node/node") do |e|
+ subnodes << e.attributes["name"]
+ end
+ d.elements.each("node/interface") do |e|
+ i = Interface.new(e.attributes["name"])
+ e.elements.each("method") do |me|
+ m = Method.new(me.attributes["name"])
+ parse_methsig(me, m)
+ i << m
+ end
+ e.elements.each("signal") do |se|
+ s = Signal.new(se.attributes["name"])
+ parse_methsig(se, s)
+ i << s
+ end
+ ret << i
+ end
+ d = Time.now - t
+ if d > 2
+ puts "Some XML took more that two secs to parse. Optimize me!" if $DEBUG
+ end
+ [ret, subnodes]
+ end
+
+ ######################################################################
+ private
+
+ # Parses a method signature XML element _e_ and initialises
+ # method/signal _m_.
+ def parse_methsig(e, m)
+ e.elements.each("arg") do |ae|
+ name = ae.attributes["name"]
+ dir = ae.attributes["direction"]
+ sig = ae.attributes["type"]
+ if m.is_a?(DBus::Signal)
+ m.add_param([name, sig])
+ elsif m.is_a?(DBus::Method)
+ case dir
+ when "in"
+ m.add_param([name, sig])
+ when "out"
+ m.add_return([name, sig])
+ end
+ else
+ raise NotImplementedError, dir
+ end
+ end
+ end
+ end # class IntrospectXMLParser
+
+ # = D-Bus proxy object interface class
+ #
+ # A class similar to the normal Interface used as a proxy for remote
+ # object interfaces.
+ class ProxyObjectInterface
+ # The proxied methods contained in the interface.
+ attr_accessor :methods
+ # The proxied signals contained in the interface.
+ attr_accessor :signals
+ # The proxy object to which this interface belongs.
+ attr_reader :object
+ # The name of the interface.
+ attr_reader :name
+
+ # Creates a new proxy interface for the given proxy _object_
+ # and the given _name_.
+ def initialize(object, name)
+ @object, @name = object, name
+ @methods, @signals = Hash.new, Hash.new
+ end
+
+ # Returns the string representation of the interface (the name).
+ def to_str
+ @name
+ end
+
+ # Returns the singleton class of the interface.
+ def singleton_class
+ (class << self ; self ; end)
+ end
+
+ # FIXME
+ def check_for_eval(s)
+ raise RuntimeException, "invalid internal data" if not s.to_s =~ /^[A-Za-z0-9_]*$/
+ end
+
+ # FIXME
+ def check_for_quoted_eval(s)
+ raise RuntimeException, "invalid internal data" if not s.to_s =~ /^[^"]+$/
+ end
+
+ # Defines a method on the interface from the descriptor _m_.
+ def define_method_from_descriptor(m)
+ check_for_eval(m.name)
+ check_for_quoted_eval(@name)
+ methdef = "def #{m.name}("
+ methdef += (0..(m.params.size - 1)).to_a.collect { |n|
+ "arg#{n}"
+ }.join(", ")
+ methdef += %{)
+ msg = Message.new(Message::METHOD_CALL)
+ msg.path = @object.path
+ msg.interface = "#{@name}"
+ msg.destination = @object.destination
+ msg.member = "#{m.name}"
+ msg.sender = @object.bus.unique_name
+ }
+ idx = 0
+ m.params.each do |npar|
+ paramname, par = npar
+ check_for_quoted_eval(par)
+
+ # This is the signature validity check
+ Type::Parser.new(par).parse
+
+ methdef += %{
+ msg.add_param("#{par}", arg#{idx})
+ }
+ idx += 1
+ end
+ methdef += "
+ ret = nil
+ if block_given?
+ @object.bus.on_return(msg) do |rmsg|
+ if rmsg.is_a?(Error)
+ yield(rmsg)
+ else
+ yield(rmsg, *rmsg.params)
+ end
+ end
+ @object.bus.send(msg.marshall)
+ else
+ @object.bus.send_sync(msg) do |rmsg|
+ if rmsg.is_a?(Error)
+ raise rmsg
+ else
+ ret = rmsg.params
+ end
+ end
+ end
+ ret
+ end
+ "
+ singleton_class.class_eval(methdef)
+ @methods[m.name] = m
+ end
+
+ # Defines a signal from the descriptor _s_.
+ def define_signal_from_descriptor(s)
+ @signals[s.name] = s
+ end
+
+ # Defines a signal or method based on the descriptor _m_.
+ def define(m)
+ if m.kind_of?(Method)
+ define_method_from_descriptor(m)
+ elsif m.kind_of?(Signal)
+ define_signal_from_descriptor(m)
+ end
+ end
+
+ # Defines a proxied method on the interface.
+ def define_method(methodname, prototype)
+ m = Method.new(methodname)
+ m.from_prototype(prototype)
+ define(m)
+ end
+
+ # Registers a handler (code block) for a signal with _name_ arriving
+ # over the given _bus_.
+ def on_signal(bus, name, &block)
+ mr = DBus::MatchRule.new.from_signal(self, name)
+ bus.add_match(mr) { |msg| block.call(*msg.params) }
+ end
+ end # class ProxyObjectInterface
+
+ # D-Bus proxy object class
+ #
+ # Class representing a remote object in an external application.
+ # Typically, calling a method on an instance of a ProxyObject sends a message
+ # over the bus so that the method is executed remotely on the correctponding
+ # object.
+ class ProxyObject
+ # The subnodes of the object in the tree.
+ attr_accessor :subnodes
+ # Flag determining whether the object has been introspected.
+ attr_accessor :introspected
+ # The (remote) destination of the object.
+ attr_reader :destination
+ # The path to the object.
+ attr_reader :path
+ # The bus the object is reachable via.
+ attr_reader :bus
+ # The default interface of the object.
+ attr_accessor :default_iface
+
+ # Creates a new proxy object living on the given _bus_ at destination _dest_
+ # on the given _path_.
+ def initialize(bus, dest, path)
+ @bus, @destination, @path = bus, dest, path
+ @interfaces = Hash.new
+ @subnodes = Array.new
+ end
+
+ # Returns the interfaces of the object.
+ def interfaces
+ @interfaces.keys
+ end
+
+ # Retrieves an interface of the proxy object (ProxyObjectInterface instance).
+ def [](intfname)
+ @interfaces[intfname]
+ end
+
+ # Maps the given interface name _intfname_ to the given interface _intf.
+ def []=(intfname, intf)
+ @interfaces[intfname] = intf
+ end
+
+ # Introspects the remote object. Allows you to find and select
+ # interfaces on the object.
+ def introspect
+ # Synchronous call here.
+ xml = @bus.introspect_data(@destination, @path)
+ ProxyObjectFactory.introspect_into(self, xml)
+ xml
+ end
+
+ # Returns whether the object has an interface with the given _name_.
+ def has_iface?(name)
+ raise "Cannot call has_iface? is not introspected" if not @introspected
+ @interfaces.member?(name)
+ end
+
+ # Registers a handler, the code block, for a signal with the given _name_.
+ def on_signal(name, &block)
+ if @default_iface and has_iface?(@default_iface)
+ @interfaces[@default_iface].on_signal(@bus, name, &block)
+ else
+ raise NoMethodError
+ end
+ end
+
+ ####################################################
+ private
+
+ # Handles all unkown methods, mostly to route method calls to the
+ # default interface.
+ def method_missing(name, *args)
+ if @default_iface and has_iface?(@default_iface)
+ @interfaces[@default_iface].method(name).call(*args)
+ else
+ raise NoMethodError
+ end
+ end
+ end # class ProxyObject
+
+ # = D-Bus proxy object factory class
+ #
+ # Class that generates and sets up a proxy object based on introspection data.
+ class ProxyObjectFactory
+ # Creates a new proxy object factory for the given introspection XML _xml_,
+ # _bus_, destination _dest_, and _path_.
+ def initialize(xml, bus, dest, path)
+ @xml, @bus, @path, @dest = xml, bus, path, dest
+ end
+
+ # Investigates the sub-nodes of the proxy object _po_ based on the
+ # introspection XML data _xml_ and sets them up recursively.
+ def ProxyObjectFactory.introspect_into(po, xml)
+ intfs, po.subnodes = IntrospectXMLParser.new(xml).parse
+ intfs.each do |i|
+ poi = ProxyObjectInterface.new(po, i.name)
+ i.methods.each_value { |m| poi.define(m) }
+ i.signals.each_value { |s| poi.define(s) }
+ po[i.name] = poi
+ end
+ po.introspected = true
+ end
+
+ # Generates, sets up and returns the proxy object.
+ def build
+ po = ProxyObject.new(@bus, @dest, @path)
+ ProxyObjectFactory.introspect_into(po, @xml)
+ po
+ end
+ end # class ProxyObjectFactory
+end # module DBus
+
--- /dev/null
+# dbus.rb - Module containing the low-level D-Bus implementation
+#
+# This file is part of the ruby-dbus project
+# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License, version 2.1 as published by the Free Software Foundation.
+# See the file "COPYING" for the exact licensing terms.
+
+require 'socket'
+
+# = D-Bus main module
+#
+# Module containing all the D-Bus modules and classes.
+module DBus
+ # Exception raised when an invalid packet is encountered.
+ class InvalidPacketException < Exception
+ end
+
+ # = D-Bus packet unmarshaller class
+ #
+ # Class that handles the conversion (unmarshalling) of payload data
+ # to Array.
+ class PacketUnmarshaller
+ # Index pointer that points to the byte in the data that is
+ # currently being processed.
+ #
+ # Used to kown what part of the buffer has been consumed by unmarshalling.
+ # FIXME: Maybe should be accessed with a "consumed_size" method.
+ attr_reader :idx
+
+ # Create a new unmarshaller for the given data _buffer_ and _endianness_.
+ def initialize(buffer, endianness)
+ @buffy, @endianness = buffer.dup, endianness
+ if @endianness == BIG_END
+ @uint32 = "N"
+ @uint16 = "n"
+ @double = "G"
+ elsif @endianness == LIL_END
+ @uint32 = "V"
+ @uint16 = "v"
+ @double = "E"
+ else
+ # FIXME: shouldn't a more special exception be raised here?
+ # yes, idea for a good name ? :)
+ raise Exception, "Incorrect endianness"
+ end
+ @idx = 0
+ end
+
+ # Unmarshall the buffer for a given _signature_ and length _len_.
+ # Return an array of unmarshalled objects
+ def unmarshall(signature, len = nil)
+ if len != nil
+ if @buffy.size < @idx + len
+ raise IncompleteBufferException
+ end
+ end
+ sigtree = Type::Parser.new(signature).parse
+ ret = Array.new
+ sigtree.each do |elem|
+ ret << do_parse(elem)
+ end
+ ret
+ end
+
+ # Align the pointer index on a byte index of _a_, where a
+ # must be 1, 2, 4 or 8.
+ def align(a)
+ case a
+ when 1
+ when 2, 4, 8
+ bits = a - 1
+ @idx = @idx + bits & ~bits
+ raise IncompleteBufferException if @idx > @buffy.size
+ else
+ raise "Unsupported alignment #{a}"
+ end
+ end
+
+ ###############################################################
+ # FIXME: does anyone except the object itself call the above methods?
+ # Yes : Message marshalling code needs to align "body" to 8 byte boundary
+ private
+
+ # Retrieve the next _nbytes_ number of bytes from the buffer.
+ def get(nbytes)
+ raise IncompleteBufferException if @idx + nbytes > @buffy.size
+ ret = @buffy.slice(@idx, nbytes)
+ @idx += nbytes
+ ret
+ end
+
+ # Retrieve the series of bytes until the next NULL (\0) byte.
+ def get_nul_terminated
+ raise IncompleteBufferException if not @buffy[@idx..-1] =~ /^([^\0]*)\0/
+ str = $1
+ raise IncompleteBufferException if @idx + str.size + 1 > @buffy.size
+ @idx += str.size + 1
+ str
+ end
+
+ # Get the string length and string itself from the buffer.
+ # Return the string.
+ def get_string
+ align(4)
+ str_sz = get(4).unpack(@uint32)[0]
+ ret = @buffy.slice(@idx, str_sz)
+ raise IncompleteBufferException if @idx + str_sz + 1 > @buffy.size
+ @idx += str_sz
+ if @buffy[@idx] != 0
+ raise InvalidPacketException, "String is not nul-terminated"
+ end
+ @idx += 1
+ # no exception, see check above
+ ret
+ end
+
+ # Get the signature length and signature itself from the buffer.
+ # Return the signature.
+ def get_signature
+ str_sz = get(1).unpack('C')[0]
+ ret = @buffy.slice(@idx, str_sz)
+ raise IncompleteBufferException if @idx + str_sz + 1 >= @buffy.size
+ @idx += str_sz
+ if @buffy[@idx] != 0
+ raise InvalidPacketException, "Type is not nul-terminated"
+ end
+ @idx += 1
+ # no exception, see check above
+ ret
+ end
+
+ # Based on the _signature_ type, retrieve a packet from the buffer
+ # and return it.
+ def do_parse(signature)
+ packet = nil
+ case signature.sigtype
+ when Type::BYTE
+ packet = get(1).unpack("C")[0]
+ when Type::UINT16
+ align(2)
+ packet = get(2).unpack(@uint16)[0]
+ when Type::INT16
+ align(4)
+ packet = get(4).unpack(@uint16)[0]
+ if (packet & 0x8000) != 0
+ packet -= 0x10000
+ end
+ when Type::UINT32
+ align(4)
+ packet = get(4).unpack(@uint32)[0]
+ when Type::INT32
+ align(4)
+ packet = get(4).unpack(@uint32)[0]
+ if (packet & 0x80000000) != 0
+ packet -= 0x100000000
+ end
+ when Type::UINT64
+ align(8)
+ packet_l = get(4).unpack(@uint32)[0]
+ packet_h = get(4).unpack(@uint32)[0]
+ if @endianness == LIL_END
+ packet = packet_l + packet_h * 2**32
+ else
+ packet = packet_l * 2**32 + packet_h
+ end
+ when Type::INT64
+ align(8)
+ packet_l = get(4).unpack(@uint32)[0]
+ packet_h = get(4).unpack(@uint32)[0]
+ if @endianness == LIL_END
+ packet = packet_l + packet_h * 2**32
+ else
+ packet = packet_l * 2**32 + packet_h
+ end
+ if (packet & 0x8000000000000000) != 0
+ packet -= 0x10000000000000000
+ end
+ when Type::DOUBLE
+ align(8)
+ packet = get(8).unpack(@double)[0]
+ when Type::BOOLEAN
+ align(4)
+ v = get(4).unpack(@uint32)[0]
+ raise InvalidPacketException if not [0, 1].member?(v)
+ packet = (v == 1)
+ when Type::ARRAY
+ align(4)
+ # checks please
+ array_sz = get(4).unpack(@uint32)[0]
+ raise InvalidPacketException if array_sz > 67108864
+
+ align(signature.child.alignment)
+ raise IncompleteBufferException if @idx + array_sz > @buffy.size
+
+ packet = Array.new
+ start_idx = @idx
+ while @idx - start_idx < array_sz
+ packet << do_parse(signature.child)
+ end
+
+ if signature.child.sigtype == Type::DICT_ENTRY then
+ packet = packet.inject(Hash.new) do |hash, pair|
+ hash[pair[0]] = pair[1]
+ hash
+ end
+ end
+ when Type::STRUCT
+ align(8)
+ packet = Array.new
+ signature.members.each do |elem|
+ packet << do_parse(elem)
+ end
+ when Type::VARIANT
+ string = get_signature
+ # error checking please
+ sig = Type::Parser.new(string).parse[0]
+ align(sig.alignment)
+ packet = do_parse(sig)
+ when Type::OBJECT_PATH
+ packet = get_string
+ when Type::STRING
+ packet = get_string
+ when Type::SIGNATURE
+ packet = get_signature
+ when Type::DICT_ENTRY
+ align(8)
+ key = do_parse(signature.members[0])
+ value = do_parse(signature.members[1])
+ packet = [key, value]
+ else
+ raise NotImplementedError,
+ "sigtype: #{signature.sigtype} (#{signature.sigtype.chr})"
+ end
+ packet
+ end # def do_parse
+ end # class PacketUnmarshaller
+
+ # D-Bus packet marshaller class
+ #
+ # Class that handles the conversion (unmarshalling) of Ruby objects to
+ # (binary) payload data.
+ class PacketMarshaller
+ # The current or result packet.
+ # FIXME: allow access only when marshalling is finished
+ attr_reader :packet
+
+ # Create a new marshaller, setting the current packet to the
+ # empty packet.
+ def initialize
+ @packet = ""
+ end
+
+ # Align the buffer with NULL (\0) bytes on a byte length of _a_.
+ def align(a)
+ case a
+ when 1
+ when 2, 4, 8
+ bits = a - 1
+ @packet = @packet.ljust(@packet.length + bits & ~bits, 0.chr)
+ else
+ raise "Unsupported alignment"
+ end
+ end
+
+ # Append the the string _str_ itself to the packet.
+ def append_string(str)
+ align(4)
+ @packet += [str.length].pack("L") + str + "\0"
+ end
+
+ # Append the the signature _signature_ itself to the packet.
+ def append_signature(str)
+ @packet += str.length.chr + str + "\0"
+ end
+
+ # Append the array type _type_ to the packet and allow for appending
+ # the child elements.
+ def array(type)
+ # Thanks to Peter Rullmann for this line
+ align(4)
+ sizeidx = @packet.size
+ @packet += "ABCD"
+ align(type.alignment)
+ contentidx = @packet.size
+ yield
+ sz = @packet.size - contentidx
+ raise InvalidPacketException if sz > 67108864
+ @packet[sizeidx...sizeidx + 4] = [sz].pack("L")
+ end
+
+ # Align and allow for appending struct fields.
+ def struct
+ align(8)
+ yield
+ end
+
+ # Append a string of bytes without type.
+ def append_simple_string(s)
+ @packet += s + "\0"
+ end
+
+ # Append a value _val_ to the packet based on its _type_.
+ def append(type, val)
+ type = type.chr if type.kind_of?(Fixnum)
+ type = Type::Parser.new(type).parse[0] if type.kind_of?(String)
+ case type.sigtype
+ when Type::BYTE
+ @packet += val.chr
+ when Type::UINT32
+ align(4)
+ @packet += [val].pack("L")
+ when Type::UINT64
+ align(8)
+ @packet += [val].pack("Q")
+ when Type::INT64
+ align(8)
+ @packet += [val].pack("q")
+ when Type::INT32
+ align(4)
+ @packet += [val].pack("l")
+ when Type::UINT16
+ align(2)
+ @packet += [val].pack("S")
+ when Type::INT16
+ align(2)
+ @packet += [val].pack("s")
+ when Type::DOUBLE
+ align(8)
+ @packet += [val].pack("d")
+ when Type::BOOLEAN
+ align(4)
+ if val
+ @packet += [1].pack("L")
+ else
+ @packet += [0].pack("L")
+ end
+ when Type::OBJECT_PATH
+ append_string(val)
+ when Type::STRING
+ append_string(val)
+ when Type::SIGNATURE
+ append_signature(val)
+ when Type::VARIANT
+ if not val.kind_of?(Array)
+ raise TypeException
+ end
+ vartype, vardata = val
+ vartype = Type::Parser.new(vartype).parse[0] if vartype.kind_of?(String)
+ append_signature(vartype.to_s)
+ align(vartype.alignment)
+ sub = PacketMarshaller.new
+ sub.append(vartype, vardata)
+ @packet += sub.packet
+ when Type::ARRAY
+ if val.kind_of?(Hash)
+ raise TypeException if type.child.sigtype != Type::DICT_ENTRY
+ # Damn ruby rocks here
+ val = val.to_a
+ end
+ if not val.kind_of?(Array)
+ raise TypeException
+ end
+ array(type.child) do
+ val.each do |elem|
+ append(type.child, elem)
+ end
+ end
+ when Type::STRUCT, Type::DICT_ENTRY
+ raise TypeException if not val.kind_of?(Array)
+ if type.sigtype == Type::DICT_ENTRY and val.size != 2
+ raise TypeException
+ end
+ struct do
+ idx = 0
+ while val[idx] != nil
+ type.members.each do |subtype|
+ raise TypeException if val[idx] == nil
+ append(subtype, val[idx])
+ idx += 1
+ end
+ end
+ end
+ else
+ raise NotImplementedError
+ end
+ end # def append
+ end # class PacketMarshaller
+end # module DBus
--- /dev/null
+# This file is part of the ruby-dbus project
+# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License, version 2.1 as published by the Free Software Foundation.
+# See the file "COPYING" for the exact licensing terms.
+
+module DBus
+ # Exception raised when an erroneous match rule type is encountered.
+ class MatchRuleException < Exception
+ end
+
+ # = D-Bus match rule class
+ #
+ # FIXME
+ class MatchRule
+ # The list of possible match filters.
+ FILTERS = [:sender, :interface, :member, :path, :destination, :type]
+ # The sender filter.
+ attr_accessor :sender
+ # The interface filter.
+ attr_accessor :interface
+ # The member filter.
+ attr_accessor :member
+ # The path filter.
+ attr_accessor :path
+ # The destination filter.
+ attr_accessor :destination
+ # The type type that is matched.
+ attr_reader :type
+
+ # Create a new match rule
+ def initialize
+ @sender = @interface = @member = @path = @destination = @type = nil
+ end
+
+ # Set the message types to filter to type _t_.
+ # Possible message types are: signal, method_call, method_return, and error.
+ def type=(t)
+ if not ['signal', 'method_call', 'method_return', 'error'].member?(t)
+ raise MatchRuleException
+ end
+ @type = t
+ end
+
+ # Returns a match rule string version of the object.
+ # E.g.: "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='Foo',path='/bar/foo',destination=':452345.34',arg2='bar'"
+ def to_s
+ FILTERS.select do |sym|
+ not method(sym).call.nil?
+ end.collect do |sym|
+ "#{sym.to_s}='#{method(sym).call}'"
+ end.join(",")
+ end
+
+ # Parses a match rule string _s_ and sets the filters on the object.
+ def from_s(str)
+ s.split(",").each do |eq|
+ if eq =~ /^(.*)='([^']*)'$/
+ name = $1
+ val = $1
+ if FILTERS.member?(name.to_sym)
+ method(name + "=").call(val)
+ else
+ raise MatchRuleException
+ end
+ end
+ end
+ end
+
+ # Sets the match rule to filter for the given _signal_ and the
+ # given interface _intf_.
+ def from_signal(intf, signal)
+ signal = signal.name unless signal.is_a?(String)
+ self.type = "signal"
+ self.interface = intf.name
+ self.member = signal
+ self.path = intf.object.path
+ self
+ end
+
+ # Determines whether a message _msg_ matches the match rule.
+ def match(msg)
+ if @type
+ if {Message::SIGNAL => "signal", Message::METHOD_CALL => "method_call",
+ Message::METHOD_RETURN => "method_return",
+ Message::ERROR => "error"}[msg.message_type] != @type
+ return false
+ end
+ end
+ return false if @interface and @interface != msg.interface
+ return false if @member and @member != msg.member
+ return false if @path and @path != msg.path
+ true
+ end
+ end # class MatchRule
+end # module D-Bus
--- /dev/null
+# dbus.rb - Module containing the low-level D-Bus implementation
+#
+# This file is part of the ruby-dbus project
+# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License, version 2.1 as published by the Free Software Foundation.
+# See the file "COPYING" for the exact licensing terms.
+
+# = D-Bus main module
+#
+# Module containing all the D-Bus modules and classes.
+module DBus
+ # = InvalidDestinationName class
+ # Thrown when you try do send a message to /org/freedesktop/DBus/Local, that
+ # is reserved.
+ class InvalidDestinationName < Exception
+ end
+
+ # = D-Bus message class
+ #
+ # Class that holds any type of message that travels over the bus.
+ class Message
+ # The serial number of the message.
+ @@serial = 1
+ # Mutex that protects updates on the serial number.
+ @@serial_mutex = Mutex.new
+ # Type of a message (by specification).
+ MESSAGE_SIGNATURE = "yyyyuua(yv)"
+
+ # FIXME: following message type constants should be under Message::Type IMO
+ # well, yeah sure
+ #
+ # Invalid message type.
+ INVALID = 0
+ # Method call message type.
+ METHOD_CALL = 1
+ # Method call return value message type.
+ METHOD_RETURN = 2
+ # Error message type.
+ ERROR = 3
+ # Signal message type.
+ SIGNAL = 4
+
+ # Message flag signyfing that no reply is expected.
+ NO_REPLY_EXPECTED = 0x1
+ # Message flag signifying that no automatic start is required/must be
+ # performed.
+ NO_AUTO_START = 0x2
+
+ # The type of the message.
+ attr_reader :message_type
+ # The path of the object instance the message must be sent to/is sent from.
+ attr_accessor :path
+ # The interface of the object that must be used/was used.
+ attr_accessor :interface
+ # The interface member (method/signal name) of the object that must be
+ # used/was used.
+ attr_accessor :member
+ # The name of the error (in case of an error message type).
+ attr_accessor :error_name
+ # The destination connection of the object that must be used/was used.
+ attr_accessor :destination
+ # The sender of the message.
+ attr_accessor :sender
+ # The signature of the message contents.
+ attr_accessor :signature
+ # The serial number of the message this message is a reply for.
+ attr_accessor :reply_serial
+ # The protocol.
+ attr_reader :protocol
+ # The serial of the message.
+ attr_reader :serial
+ # The parameters of the message.
+ attr_reader :params
+
+ # Create a message with message type _mtype_ with default values and a
+ # unique serial number.
+ def initialize(mtype = INVALID)
+ @message_type = mtype
+
+ @flags = 0
+ @protocol = 1
+ @body_length = 0
+ @signature = String.new
+ @@serial_mutex.synchronize do
+ @serial = @@serial
+ @@serial += 1
+ end
+ @params = Array.new
+ @destination = nil
+ @error_name = nil
+ @member = nil
+ @path = nil
+ @reply_serial = nil
+
+ if mtype == METHOD_RETURN
+ @flags = NO_REPLY_EXPECTED
+ end
+ end
+
+ # Mark this message as a reply to a another message _m_, taking
+ # the serial number of _m_ as reply serial and the sender of _m_ as
+ # destination.
+ def reply_to(m)
+ @message_type = METHOD_RETURN
+ @reply_serial = m.serial
+ @destination = m.sender
+ self
+ end
+
+ # Add a parameter _val_ of type _type_ to the message.
+ def add_param(type, val)
+ type = type.chr if type.kind_of?(Fixnum)
+ @signature += type.to_s
+ @params << [type, val]
+ end
+
+ # FIXME: what are these? a message element constant enumeration?
+ # See method below, in a message, you have and array of optional parameters
+ # that come with an index, to determine their meaning. The values are in
+ # spec, more a definition than an enumeration.
+
+ PATH = 1
+ INTERFACE = 2
+ MEMBER = 3
+ ERROR_NAME = 4
+ REPLY_SERIAL = 5
+ DESTINATION = 6
+ SENDER = 7
+ SIGNATURE = 8
+
+ # Marshall the message with its current set parameters and return
+ # it in a packet form.
+ def marshall
+ if @path == "/org/freedesktop/DBus/Local"
+ raise InvalidDestinationName
+ end
+
+ params = PacketMarshaller.new
+ @params.each do |param|
+ params.append(param[0], param[1])
+ end
+ @body_length = params.packet.length
+
+ marshaller = PacketMarshaller.new
+ marshaller.append(Type::BYTE, HOST_END)
+ marshaller.append(Type::BYTE, @message_type)
+ marshaller.append(Type::BYTE, @flags)
+ marshaller.append(Type::BYTE, @protocol)
+ marshaller.append(Type::UINT32, @body_length)
+ marshaller.append(Type::UINT32, @serial)
+ marshaller.array(Type::Parser.new("y").parse[0]) do
+ if @path
+ marshaller.struct do
+ marshaller.append(Type::BYTE, PATH)
+ marshaller.append(Type::BYTE, 1)
+ marshaller.append_simple_string("o")
+ marshaller.append(Type::OBJECT_PATH, @path)
+ end
+ end
+ if @interface
+ marshaller.struct do
+ marshaller.append(Type::BYTE, INTERFACE)
+ marshaller.append(Type::BYTE, 1)
+ marshaller.append_simple_string("s")
+ marshaller.append(Type::STRING, @interface)
+ end
+ end
+ if @member
+ marshaller.struct do
+ marshaller.append(Type::BYTE, MEMBER)
+ marshaller.append(Type::BYTE, 1)
+ marshaller.append_simple_string("s")
+ marshaller.append(Type::STRING, @member)
+ end
+ end
+ if @error_name
+ marshaller.struct do
+ marshaller.append(Type::BYTE, ERROR_NAME)
+ marshaller.append(Type::BYTE, 1)
+ marshaller.append_simple_string("s")
+ marshaller.append(Type::STRING, @error_name)
+ end
+ end
+ if @reply_serial
+ marshaller.struct do
+ marshaller.append(Type::BYTE, REPLY_SERIAL)
+ marshaller.append(Type::BYTE, 1)
+ marshaller.append_simple_string("u")
+ marshaller.append(Type::UINT32, @reply_serial)
+ end
+ end
+ if @destination
+ marshaller.struct do
+ marshaller.append(Type::BYTE, DESTINATION)
+ marshaller.append(Type::BYTE, 1)
+ marshaller.append_simple_string("s")
+ marshaller.append(Type::STRING, @destination)
+ end
+ end
+ if @signature != ""
+ marshaller.struct do
+ marshaller.append(Type::BYTE, SIGNATURE)
+ marshaller.append(Type::BYTE, 1)
+ marshaller.append_simple_string("g")
+ marshaller.append(Type::SIGNATURE, @signature)
+ end
+ end
+ end
+ marshaller.align(8)
+ @params.each do |param|
+ marshaller.append(param[0], param[1])
+ end
+ marshaller.packet
+ end
+
+ # Unmarshall a packet contained in the buffer _buf_ and set the
+ # parameters of the message object according the data found in the
+ # buffer.
+ # Return the detected message and the index pointer of the buffer where
+ # the message data ended.
+ def unmarshall_buffer(buf)
+ buf = buf.dup
+ if buf[0] == ?l
+ endianness = LIL_END
+ else
+ endianness = BIG_END
+ end
+ pu = PacketUnmarshaller.new(buf, endianness)
+ mdata = pu.unmarshall(MESSAGE_SIGNATURE)
+ dummy, @message_type, @flags, @protocol, @body_length, @serial,
+ headers = mdata
+
+ headers.each do |struct|
+ case struct[0]
+ when PATH
+ @path = struct[1]
+ when INTERFACE
+ @interface = struct[1]
+ when MEMBER
+ @member = struct[1]
+ when ERROR_NAME
+ @error_name = struct[1]
+ when REPLY_SERIAL
+ @reply_serial = struct[1]
+ when DESTINATION
+ @destination = struct[1]
+ when SENDER
+ @sender = struct[1]
+ when SIGNATURE
+ @signature = struct[1]
+ end
+ end
+ pu.align(8)
+ if @body_length > 0 and @signature
+ @params = pu.unmarshall(@signature, @body_length)
+ end
+ [self, pu.idx]
+ end # def unmarshall_buf
+
+ # Unmarshall the data of a message found in the buffer _buf_ using
+ # Message#unmarshall_buf.
+ # Return the message.
+ def unmarshall(buf)
+ ret, size = unmarshall_buffer(buf)
+ ret
+ end
+ end # class Message
+
+ # A helper exception on errors
+ class Error < Exception
+ attr_reader :dbus_message
+ def initialize(msg)
+ super(msg.error_name + ": " + msg.params.join(", "))
+ @dbus_message = msg
+ end
+ end
+end # module DBus
--- /dev/null
+# dbus/type.rb - module containing low-level D-Bus data type information
+#
+# This file is part of the ruby-dbus project
+# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License, version 2.1 as published by the Free Software Foundation.
+# See the file "COPYING" for the exact licensing terms.
+
+module DBus
+
+# = D-Bus type module
+#
+# This module containts the constants of the types specified in the D-Bus
+# protocol.
+module Type
+ # The types.
+ INVALID = 0
+ BYTE = ?y
+ BOOLEAN = ?b
+ INT16 = ?n
+ UINT16 = ?q
+ INT32 = ?i
+ UINT32 = ?u
+ INT64 = ?x
+ UINT64 = ?t
+ DOUBLE = ?d
+ STRUCT = ?r
+ ARRAY = ?a
+ VARIANT = ?v
+ OBJECT_PATH = ?o
+ STRING = ?s
+ SIGNATURE = ?g
+ DICT_ENTRY = ?e
+
+ # Mapping from type number to name.
+ TypeName = {
+ INVALID => "INVALID",
+ BYTE => "BYTE",
+ BOOLEAN => "BOOLEAN",
+ INT16 => "INT16",
+ UINT16 => "UINT16",
+ INT32 => "INT32",
+ UINT32 => "UINT32",
+ INT64 => "INT64",
+ UINT64 => "UINT64",
+ DOUBLE => "DOUBLE",
+ STRUCT => "STRUCT",
+ ARRAY => "ARRAY",
+ VARIANT => "VARIANT",
+ OBJECT_PATH => "OBJECT_PATH",
+ STRING => "STRING",
+ SIGNATURE => "SIGNATURE",
+ DICT_ENTRY => "DICT_ENTRY"
+ }
+
+ # Exception raised when an unknown/incorrect type is encountered.
+ class SignatureException < Exception
+ end
+
+ # = D-Bus type conversion class
+ #
+ # Helper class for representing a D-Bus type.
+ class Type
+ # Returns the signature type number.
+ attr_reader :sigtype
+ # Return contained member types.
+ attr_reader :members
+
+ # Create a new type instance for type number _sigtype_.
+ def initialize(sigtype)
+ if not TypeName.keys.member?(sigtype)
+ raise SignatureException, "Unknown key in signature: #{sigtype.chr}"
+ end
+ @sigtype = sigtype
+ @members = Array.new
+ end
+
+ # Return the required alignment for the type.
+ def alignment
+ {
+ BYTE => 1,
+ BOOLEAN => 4,
+ INT16 => 2,
+ UINT16 => 2,
+ INT32 => 4,
+ UINT32 => 4,
+ INT64 => 8,
+ UINT64 => 8,
+ STRUCT => 8,
+ DICT_ENTRY => 8,
+ DOUBLE => 8,
+ ARRAY => 4,
+ OBJECT_PATH => 4,
+ STRING => 4,
+ SIGNATURE => 1,
+ }[@sigtype]
+ end
+
+ # Return a string representation of the type according to the
+ # D-Bus specification.
+ def to_s
+ case @sigtype
+ when STRUCT
+ "(" + @members.collect { |t| t.to_s }.join + ")"
+ when ARRAY
+ "a" + @members.collect { |t| t.to_s }
+ when DICT_ENTRY
+ "{" + @members.collect { |t| t.to_s }.join + "}"
+ else
+ if not TypeName.keys.member?(@sigtype)
+ raise NotImplementedError
+ end
+ @sigtype.chr
+ end
+ end
+
+ # Add a new member type _a_.
+ def <<(a)
+ if not [STRUCT, ARRAY, DICT_ENTRY].member?(@sigtype)
+ raise SignatureException
+ end
+ raise SignatureException if @sigtype == ARRAY and @members.size > 0
+ if @sigtype == DICT_ENTRY
+ if @members.size == 2
+ raise SignatureException, "Dict entries have exactly two members"
+ end
+ if @members.size == 0
+ if [STRUCT, ARRAY, DICT_ENTRY].member?(a.sigtype)
+ raise SignatureException, "Dict entry keys must be basic types"
+ end
+ end
+ end
+ @members << a
+ end
+
+ # Return the first contained member type.
+ def child
+ @members[0]
+ end
+
+ def inspect
+ s = TypeName[@sigtype]
+ if [STRUCT, ARRAY].member?(@sigtype)
+ s += ": " + @members.inspect
+ end
+ s
+ end
+ end # class Type
+
+ # = D-Bus type parser class
+ #
+ # Helper class to parse a type signature in the protocol.
+ class Parser
+ # Create a new parser for the given _signature_.
+ def initialize(signature)
+ @signature = signature
+ @idx = 0
+ end
+
+ # Returns the next character from the signature.
+ def nextchar
+ c = @signature[@idx]
+ @idx += 1
+ c
+ end
+
+ # Parse one character _c_ of the signature.
+ def parse_one(c)
+ res = nil
+ case c
+ when ?a
+ res = Type.new(ARRAY)
+ child = parse_one(nextchar)
+ res << child
+ when ?(
+ res = Type.new(STRUCT)
+ while (c = nextchar) != nil and c != ?)
+ res << parse_one(c)
+ end
+ raise SignatureException, "Parse error in #{@signature}" if c == nil
+ when ?{
+ res = Type.new(DICT_ENTRY)
+ while (c = nextchar) != nil and c != ?}
+ res << parse_one(c)
+ end
+ raise SignatureException, "Parse error in #{@signature}" if c == nil
+ else
+ res = Type.new(c)
+ end
+ res
+ end
+
+ # Parse the entire signature, return a DBus::Type object.
+ def parse
+ @idx = 0
+ ret = Array.new
+ while (c = nextchar)
+ ret << parse_one(c)
+ end
+ ret
+ end
+ end # class Parser
+end # module Type
+end # module DBus
--- /dev/null
+#
+# setup.rb
+#
+# Copyright (c) 2000-2005 Minero Aoki
+#
+# This program is free software.
+# You can distribute/modify this program under the terms of
+# the GNU LGPL, Lesser General Public License version 2.1.
+#
+
+unless Enumerable.method_defined?(:map) # Ruby 1.4.6
+ module Enumerable
+ alias map collect
+ end
+end
+
+unless File.respond_to?(:read) # Ruby 1.6
+ def File.read(fname)
+ open(fname) {|f|
+ return f.read
+ }
+ end
+end
+
+unless Errno.const_defined?(:ENOTEMPTY) # Windows?
+ module Errno
+ class ENOTEMPTY
+ # We do not raise this exception, implementation is not needed.
+ end
+ end
+end
+
+def File.binread(fname)
+ open(fname, 'rb') {|f|
+ return f.read
+ }
+end
+
+# for corrupted Windows' stat(2)
+def File.dir?(path)
+ File.directory?((path[-1,1] == '/') ? path : path + '/')
+end
+
+
+class ConfigTable
+
+ include Enumerable
+
+ def initialize(rbconfig)
+ @rbconfig = rbconfig
+ @items = []
+ @table = {}
+ # options
+ @install_prefix = nil
+ @config_opt = nil
+ @verbose = true
+ @no_harm = false
+ end
+
+ attr_accessor :install_prefix
+ attr_accessor :config_opt
+
+ attr_writer :verbose
+
+ def verbose?
+ @verbose
+ end
+
+ attr_writer :no_harm
+
+ def no_harm?
+ @no_harm
+ end
+
+ def [](key)
+ lookup(key).resolve(self)
+ end
+
+ def []=(key, val)
+ lookup(key).set val
+ end
+
+ def names
+ @items.map {|i| i.name }
+ end
+
+ def each(&block)
+ @items.each(&block)
+ end
+
+ def key?(name)
+ @table.key?(name)
+ end
+
+ def lookup(name)
+ @table[name] or setup_rb_error "no such config item: #{name}"
+ end
+
+ def add(item)
+ @items.push item
+ @table[item.name] = item
+ end
+
+ def remove(name)
+ item = lookup(name)
+ @items.delete_if {|i| i.name == name }
+ @table.delete_if {|name, i| i.name == name }
+ item
+ end
+
+ def load_script(path, inst = nil)
+ if File.file?(path)
+ MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path
+ end
+ end
+
+ def savefile
+ '.config'
+ end
+
+ def load_savefile
+ begin
+ File.foreach(savefile()) do |line|
+ k, v = *line.split(/=/, 2)
+ self[k] = v.strip
+ end
+ rescue Errno::ENOENT
+ setup_rb_error $!.message + "\n#{File.basename($0)} config first"
+ end
+ end
+
+ def save
+ @items.each {|i| i.value }
+ File.open(savefile(), 'w') {|f|
+ @items.each do |i|
+ f.printf "%s=%s\n", i.name, i.value if i.value? and i.value
+ end
+ }
+ end
+
+ def load_standard_entries
+ standard_entries(@rbconfig).each do |ent|
+ add ent
+ end
+ end
+
+ def standard_entries(rbconfig)
+ c = rbconfig
+
+ rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT'])
+
+ major = c['MAJOR'].to_i
+ minor = c['MINOR'].to_i
+ teeny = c['TEENY'].to_i
+ version = "#{major}.#{minor}"
+
+ # ruby ver. >= 1.4.4?
+ newpath_p = ((major >= 2) or
+ ((major == 1) and
+ ((minor >= 5) or
+ ((minor == 4) and (teeny >= 4)))))
+
+ if c['rubylibdir']
+ # V > 1.6.3
+ libruby = "#{c['prefix']}/lib/ruby"
+ librubyver = c['rubylibdir']
+ librubyverarch = c['archdir']
+ siteruby = c['sitedir']
+ siterubyver = c['sitelibdir']
+ siterubyverarch = c['sitearchdir']
+ elsif newpath_p
+ # 1.4.4 <= V <= 1.6.3
+ libruby = "#{c['prefix']}/lib/ruby"
+ librubyver = "#{c['prefix']}/lib/ruby/#{version}"
+ librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
+ siteruby = c['sitedir']
+ siterubyver = "$siteruby/#{version}"
+ siterubyverarch = "$siterubyver/#{c['arch']}"
+ else
+ # V < 1.4.4
+ libruby = "#{c['prefix']}/lib/ruby"
+ librubyver = "#{c['prefix']}/lib/ruby/#{version}"
+ librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
+ siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby"
+ siterubyver = siteruby
+ siterubyverarch = "$siterubyver/#{c['arch']}"
+ end
+ parameterize = lambda {|path|
+ path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')
+ }
+
+ if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
+ makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
+ else
+ makeprog = 'make'
+ end
+
+ [
+ ExecItem.new('installdirs', 'std/site/home',
+ 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\
+ {|val, table|
+ case val
+ when 'std'
+ table['rbdir'] = '$librubyver'
+ table['sodir'] = '$librubyverarch'
+ when 'site'
+ table['rbdir'] = '$siterubyver'
+ table['sodir'] = '$siterubyverarch'
+ when 'home'
+ setup_rb_error '$HOME was not set' unless ENV['HOME']
+ table['prefix'] = ENV['HOME']
+ table['rbdir'] = '$libdir/ruby'
+ table['sodir'] = '$libdir/ruby'
+ end
+ },
+ PathItem.new('prefix', 'path', c['prefix'],
+ 'path prefix of target environment'),
+ PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
+ 'the directory for commands'),
+ PathItem.new('libdir', 'path', parameterize.call(c['libdir']),
+ 'the directory for libraries'),
+ PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
+ 'the directory for shared data'),
+ PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
+ 'the directory for man pages'),
+ PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
+ 'the directory for system configuration files'),
+ PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']),
+ 'the directory for local state data'),
+ PathItem.new('libruby', 'path', libruby,
+ 'the directory for ruby libraries'),
+ PathItem.new('librubyver', 'path', librubyver,
+ 'the directory for standard ruby libraries'),
+ PathItem.new('librubyverarch', 'path', librubyverarch,
+ 'the directory for standard ruby extensions'),
+ PathItem.new('siteruby', 'path', siteruby,
+ 'the directory for version-independent aux ruby libraries'),
+ PathItem.new('siterubyver', 'path', siterubyver,
+ 'the directory for aux ruby libraries'),
+ PathItem.new('siterubyverarch', 'path', siterubyverarch,
+ 'the directory for aux ruby binaries'),
+ PathItem.new('rbdir', 'path', '$siterubyver',
+ 'the directory for ruby scripts'),
+ PathItem.new('sodir', 'path', '$siterubyverarch',
+ 'the directory for ruby extentions'),
+ PathItem.new('rubypath', 'path', rubypath,
+ 'the path to set to #! line'),
+ ProgramItem.new('rubyprog', 'name', rubypath,
+ 'the ruby program using for installation'),
+ ProgramItem.new('makeprog', 'name', makeprog,
+ 'the make program to compile ruby extentions'),
+ SelectItem.new('shebang', 'all/ruby/never', 'ruby',
+ 'shebang line (#!) editing mode'),
+ BoolItem.new('without-ext', 'yes/no', 'no',
+ 'does not compile/install ruby extentions')
+ ]
+ end
+ private :standard_entries
+
+ def load_multipackage_entries
+ multipackage_entries().each do |ent|
+ add ent
+ end
+ end
+
+ def multipackage_entries
+ [
+ PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
+ 'package names that you want to install'),
+ PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
+ 'package names that you do not want to install')
+ ]
+ end
+ private :multipackage_entries
+
+ ALIASES = {
+ 'std-ruby' => 'librubyver',
+ 'stdruby' => 'librubyver',
+ 'rubylibdir' => 'librubyver',
+ 'archdir' => 'librubyverarch',
+ 'site-ruby-common' => 'siteruby', # For backward compatibility
+ 'site-ruby' => 'siterubyver', # For backward compatibility
+ 'bin-dir' => 'bindir',
+ 'bin-dir' => 'bindir',
+ 'rb-dir' => 'rbdir',
+ 'so-dir' => 'sodir',
+ 'data-dir' => 'datadir',
+ 'ruby-path' => 'rubypath',
+ 'ruby-prog' => 'rubyprog',
+ 'ruby' => 'rubyprog',
+ 'make-prog' => 'makeprog',
+ 'make' => 'makeprog'
+ }
+
+ def fixup
+ ALIASES.each do |ali, name|
+ @table[ali] = @table[name]
+ end
+ @items.freeze
+ @table.freeze
+ @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/
+ end
+
+ def parse_opt(opt)
+ m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}"
+ m.to_a[1,2]
+ end
+
+ def dllext
+ @rbconfig['DLEXT']
+ end
+
+ def value_config?(name)
+ lookup(name).value?
+ end
+
+ class Item
+ def initialize(name, template, default, desc)
+ @name = name.freeze
+ @template = template
+ @value = default
+ @default = default
+ @description = desc
+ end
+
+ attr_reader :name
+ attr_reader :description
+
+ attr_accessor :default
+ alias help_default default
+
+ def help_opt
+ "--#{@name}=#{@template}"
+ end
+
+ def value?
+ true
+ end
+
+ def value
+ @value
+ end
+
+ def resolve(table)
+ @value.gsub(%r<\$([^/]+)>) { table[$1] }
+ end
+
+ def set(val)
+ @value = check(val)
+ end
+
+ private
+
+ def check(val)
+ setup_rb_error "config: --#{name} requires argument" unless val
+ val
+ end
+ end
+
+ class BoolItem < Item
+ def config_type
+ 'bool'
+ end
+
+ def help_opt
+ "--#{@name}"
+ end
+
+ private
+
+ def check(val)
+ return 'yes' unless val
+ case val
+ when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes'
+ when /\An(o)?\z/i, /\Af(alse)\z/i then 'no'
+ else
+ setup_rb_error "config: --#{@name} accepts only yes/no for argument"
+ end
+ end
+ end
+
+ class PathItem < Item
+ def config_type
+ 'path'
+ end
+
+ private
+
+ def check(path)
+ setup_rb_error "config: --#{@name} requires argument" unless path
+ path[0,1] == '$' ? path : File.expand_path(path)
+ end
+ end
+
+ class ProgramItem < Item
+ def config_type
+ 'program'
+ end
+ end
+
+ class SelectItem < Item
+ def initialize(name, selection, default, desc)
+ super
+ @ok = selection.split('/')
+ end
+
+ def config_type
+ 'select'
+ end
+
+ private
+
+ def check(val)
+ unless @ok.include?(val.strip)
+ setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
+ end
+ val.strip
+ end
+ end
+
+ class ExecItem < Item
+ def initialize(name, selection, desc, &block)
+ super name, selection, nil, desc
+ @ok = selection.split('/')
+ @action = block
+ end
+
+ def config_type
+ 'exec'
+ end
+
+ def value?
+ false
+ end
+
+ def resolve(table)
+ setup_rb_error "$#{name()} wrongly used as option value"
+ end
+
+ undef set
+
+ def evaluate(val, table)
+ v = val.strip.downcase
+ unless @ok.include?(v)
+ setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})"
+ end
+ @action.call v, table
+ end
+ end
+
+ class PackageSelectionItem < Item
+ def initialize(name, template, default, help_default, desc)
+ super name, template, default, desc
+ @help_default = help_default
+ end
+
+ attr_reader :help_default
+
+ def config_type
+ 'package'
+ end
+
+ private
+
+ def check(val)
+ unless File.dir?("packages/#{val}")
+ setup_rb_error "config: no such package: #{val}"
+ end
+ val
+ end
+ end
+
+ class MetaConfigEnvironment
+ def initialize(config, installer)
+ @config = config
+ @installer = installer
+ end
+
+ def config_names
+ @config.names
+ end
+
+ def config?(name)
+ @config.key?(name)
+ end
+
+ def bool_config?(name)
+ @config.lookup(name).config_type == 'bool'
+ end
+
+ def path_config?(name)
+ @config.lookup(name).config_type == 'path'
+ end
+
+ def value_config?(name)
+ @config.lookup(name).config_type != 'exec'
+ end
+
+ def add_config(item)
+ @config.add item
+ end
+
+ def add_bool_config(name, default, desc)
+ @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
+ end
+
+ def add_path_config(name, default, desc)
+ @config.add PathItem.new(name, 'path', default, desc)
+ end
+
+ def set_config_default(name, default)
+ @config.lookup(name).default = default
+ end
+
+ def remove_config(name)
+ @config.remove(name)
+ end
+
+ # For only multipackage
+ def packages
+ raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer
+ @installer.packages
+ end
+
+ # For only multipackage
+ def declare_packages(list)
+ raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer
+ @installer.packages = list
+ end
+ end
+
+end # class ConfigTable
+
+
+# This module requires: #verbose?, #no_harm?
+module FileOperations
+
+ def mkdir_p(dirname, prefix = nil)
+ dirname = prefix + File.expand_path(dirname) if prefix
+ $stderr.puts "mkdir -p #{dirname}" if verbose?
+ return if no_harm?
+
+ # Does not check '/', it's too abnormal.
+ dirs = File.expand_path(dirname).split(%r<(?=/)>)
+ if /\A[a-z]:\z/i =~ dirs[0]
+ disk = dirs.shift
+ dirs[0] = disk + dirs[0]
+ end
+ dirs.each_index do |idx|
+ path = dirs[0..idx].join('')
+ Dir.mkdir path unless File.dir?(path)
+ end
+ end
+
+ def rm_f(path)
+ $stderr.puts "rm -f #{path}" if verbose?
+ return if no_harm?
+ force_remove_file path
+ end
+
+ def rm_rf(path)
+ $stderr.puts "rm -rf #{path}" if verbose?
+ return if no_harm?
+ remove_tree path
+ end
+
+ def remove_tree(path)
+ if File.symlink?(path)
+ remove_file path
+ elsif File.dir?(path)
+ remove_tree0 path
+ else
+ force_remove_file path
+ end
+ end
+
+ def remove_tree0(path)
+ Dir.foreach(path) do |ent|
+ next if ent == '.'
+ next if ent == '..'
+ entpath = "#{path}/#{ent}"
+ if File.symlink?(entpath)
+ remove_file entpath
+ elsif File.dir?(entpath)
+ remove_tree0 entpath
+ else
+ force_remove_file entpath
+ end
+ end
+ begin
+ Dir.rmdir path
+ rescue Errno::ENOTEMPTY
+ # directory may not be empty
+ end
+ end
+
+ def move_file(src, dest)
+ force_remove_file dest
+ begin
+ File.rename src, dest
+ rescue
+ File.open(dest, 'wb') {|f|
+ f.write File.binread(src)
+ }
+ File.chmod File.stat(src).mode, dest
+ File.unlink src
+ end
+ end
+
+ def force_remove_file(path)
+ begin
+ remove_file path
+ rescue
+ end
+ end
+
+ def remove_file(path)
+ File.chmod 0777, path
+ File.unlink path
+ end
+
+ def install(from, dest, mode, prefix = nil)
+ $stderr.puts "install #{from} #{dest}" if verbose?
+ return if no_harm?
+
+ realdest = prefix ? prefix + File.expand_path(dest) : dest
+ realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
+ str = File.binread(from)
+ if diff?(str, realdest)
+ verbose_off {
+ rm_f realdest if File.exist?(realdest)
+ }
+ File.open(realdest, 'wb') {|f|
+ f.write str
+ }
+ File.chmod mode, realdest
+
+ File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
+ if prefix
+ f.puts realdest.sub(prefix, '')
+ else
+ f.puts realdest
+ end
+ }
+ end
+ end
+
+ def diff?(new_content, path)
+ return true unless File.exist?(path)
+ new_content != File.binread(path)
+ end
+
+ def command(*args)
+ $stderr.puts args.join(' ') if verbose?
+ system(*args) or raise RuntimeError,
+ "system(#{args.map{|a| a.inspect }.join(' ')}) failed"
+ end
+
+ def ruby(*args)
+ command config('rubyprog'), *args
+ end
+
+ def make(task = nil)
+ command(*[config('makeprog'), task].compact)
+ end
+
+ def extdir?(dir)
+ File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb")
+ end
+
+ def files_of(dir)
+ Dir.open(dir) {|d|
+ return d.select {|ent| File.file?("#{dir}/#{ent}") }
+ }
+ end
+
+ DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn )
+
+ def directories_of(dir)
+ Dir.open(dir) {|d|
+ return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT
+ }
+ end
+
+end
+
+
+# This module requires: #srcdir_root, #objdir_root, #relpath
+module HookScriptAPI
+
+ def get_config(key)
+ @config[key]
+ end
+
+ alias config get_config
+
+ # obsolete: use metaconfig to change configuration
+ def set_config(key, val)
+ @config[key] = val
+ end
+
+ #
+ # srcdir/objdir (works only in the package directory)
+ #
+
+ def curr_srcdir
+ "#{srcdir_root()}/#{relpath()}"
+ end
+
+ def curr_objdir
+ "#{objdir_root()}/#{relpath()}"
+ end
+
+ def srcfile(path)
+ "#{curr_srcdir()}/#{path}"
+ end
+
+ def srcexist?(path)
+ File.exist?(srcfile(path))
+ end
+
+ def srcdirectory?(path)
+ File.dir?(srcfile(path))
+ end
+
+ def srcfile?(path)
+ File.file?(srcfile(path))
+ end
+
+ def srcentries(path = '.')
+ Dir.open("#{curr_srcdir()}/#{path}") {|d|
+ return d.to_a - %w(. ..)
+ }
+ end
+
+ def srcfiles(path = '.')
+ srcentries(path).select {|fname|
+ File.file?(File.join(curr_srcdir(), path, fname))
+ }
+ end
+
+ def srcdirectories(path = '.')
+ srcentries(path).select {|fname|
+ File.dir?(File.join(curr_srcdir(), path, fname))
+ }
+ end
+
+end
+
+
+class ToplevelInstaller
+
+ Version = '3.4.1'
+ Copyright = 'Copyright (c) 2000-2005 Minero Aoki'
+
+ TASKS = [
+ [ 'all', 'do config, setup, then install' ],
+ [ 'config', 'saves your configurations' ],
+ [ 'show', 'shows current configuration' ],
+ [ 'setup', 'compiles ruby extentions and others' ],
+ [ 'install', 'installs files' ],
+ [ 'test', 'run all tests in test/' ],
+ [ 'clean', "does `make clean' for each extention" ],
+ [ 'distclean',"does `make distclean' for each extention" ]
+ ]
+
+ def ToplevelInstaller.invoke
+ config = ConfigTable.new(load_rbconfig())
+ config.load_standard_entries
+ config.load_multipackage_entries if multipackage?
+ config.fixup
+ klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller)
+ klass.new(File.dirname($0), config).invoke
+ end
+
+ def ToplevelInstaller.multipackage?
+ File.dir?(File.dirname($0) + '/packages')
+ end
+
+ def ToplevelInstaller.load_rbconfig
+ if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
+ ARGV.delete(arg)
+ load File.expand_path(arg.split(/=/, 2)[1])
+ $".push 'rbconfig.rb'
+ else
+ require 'rbconfig'
+ end
+ ::Config::CONFIG
+ end
+
+ def initialize(ardir_root, config)
+ @ardir = File.expand_path(ardir_root)
+ @config = config
+ # cache
+ @valid_task_re = nil
+ end
+
+ def config(key)
+ @config[key]
+ end
+
+ def inspect
+ "#<#{self.class} #{__id__()}>"
+ end
+
+ def invoke
+ run_metaconfigs
+ case task = parsearg_global()
+ when nil, 'all'
+ parsearg_config
+ init_installers
+ exec_config
+ exec_setup
+ exec_install
+ else
+ case task
+ when 'config', 'test'
+ ;
+ when 'clean', 'distclean'
+ @config.load_savefile if File.exist?(@config.savefile)
+ else
+ @config.load_savefile
+ end
+ __send__ "parsearg_#{task}"
+ init_installers
+ __send__ "exec_#{task}"
+ end
+ end
+
+ def run_metaconfigs
+ @config.load_script "#{@ardir}/metaconfig"
+ end
+
+ def init_installers
+ @installer = Installer.new(@config, @ardir, File.expand_path('.'))
+ end
+
+ #
+ # Hook Script API bases
+ #
+
+ def srcdir_root
+ @ardir
+ end
+
+ def objdir_root
+ '.'
+ end
+
+ def relpath
+ '.'
+ end
+
+ #
+ # Option Parsing
+ #
+
+ def parsearg_global
+ while arg = ARGV.shift
+ case arg
+ when /\A\w+\z/
+ setup_rb_error "invalid task: #{arg}" unless valid_task?(arg)
+ return arg
+ when '-q', '--quiet'
+ @config.verbose = false
+ when '--verbose'
+ @config.verbose = true
+ when '--help'
+ print_usage $stdout
+ exit 0
+ when '--version'
+ puts "#{File.basename($0)} version #{Version}"
+ exit 0
+ when '--copyright'
+ puts Copyright
+ exit 0
+ else
+ setup_rb_error "unknown global option '#{arg}'"
+ end
+ end
+ nil
+ end
+
+ def valid_task?(t)
+ valid_task_re() =~ t
+ end
+
+ def valid_task_re
+ @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/
+ end
+
+ def parsearg_no_options
+ unless ARGV.empty?
+ task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1)
+ setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}"
+ end
+ end
+
+ alias parsearg_show parsearg_no_options
+ alias parsearg_setup parsearg_no_options
+ alias parsearg_test parsearg_no_options
+ alias parsearg_clean parsearg_no_options
+ alias parsearg_distclean parsearg_no_options
+
+ def parsearg_config
+ evalopt = []
+ set = []
+ @config.config_opt = []
+ while i = ARGV.shift
+ if /\A--?\z/ =~ i
+ @config.config_opt = ARGV.dup
+ break
+ end
+ name, value = *@config.parse_opt(i)
+ if @config.value_config?(name)
+ @config[name] = value
+ else
+ evalopt.push [name, value]
+ end
+ set.push name
+ end
+ evalopt.each do |name, value|
+ @config.lookup(name).evaluate value, @config
+ end
+ # Check if configuration is valid
+ set.each do |n|
+ @config[n] if @config.value_config?(n)
+ end
+ end
+
+ def parsearg_install
+ @config.no_harm = false
+ @config.install_prefix = ''
+ while a = ARGV.shift
+ case a
+ when '--no-harm'
+ @config.no_harm = true
+ when /\A--prefix=/
+ path = a.split(/=/, 2)[1]
+ path = File.expand_path(path) unless path[0,1] == '/'
+ @config.install_prefix = path
+ else
+ setup_rb_error "install: unknown option #{a}"
+ end
+ end
+ end
+
+ def print_usage(out)
+ out.puts 'Typical Installation Procedure:'
+ out.puts " $ ruby #{File.basename $0} config"
+ out.puts " $ ruby #{File.basename $0} setup"
+ out.puts " # ruby #{File.basename $0} install (may require root privilege)"
+ out.puts
+ out.puts 'Detailed Usage:'
+ out.puts " ruby #{File.basename $0} <global option>"
+ out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]"
+
+ fmt = " %-24s %s\n"
+ out.puts
+ out.puts 'Global options:'
+ out.printf fmt, '-q,--quiet', 'suppress message outputs'
+ out.printf fmt, ' --verbose', 'output messages verbosely'
+ out.printf fmt, ' --help', 'print this message'
+ out.printf fmt, ' --version', 'print version and quit'
+ out.printf fmt, ' --copyright', 'print copyright and quit'
+ out.puts
+ out.puts 'Tasks:'
+ TASKS.each do |name, desc|
+ out.printf fmt, name, desc
+ end
+
+ fmt = " %-24s %s [%s]\n"
+ out.puts
+ out.puts 'Options for CONFIG or ALL:'
+ @config.each do |item|
+ out.printf fmt, item.help_opt, item.description, item.help_default
+ end
+ out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
+ out.puts
+ out.puts 'Options for INSTALL:'
+ out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
+ out.printf fmt, '--prefix=path', 'install path prefix', ''
+ out.puts
+ end
+
+ #
+ # Task Handlers
+ #
+
+ def exec_config
+ @installer.exec_config
+ @config.save # must be final
+ end
+
+ def exec_setup
+ @installer.exec_setup
+ end
+
+ def exec_install
+ @installer.exec_install
+ end
+
+ def exec_test
+ @installer.exec_test
+ end
+
+ def exec_show
+ @config.each do |i|
+ printf "%-20s %s\n", i.name, i.value if i.value?
+ end
+ end
+
+ def exec_clean
+ @installer.exec_clean
+ end
+
+ def exec_distclean
+ @installer.exec_distclean
+ end
+
+end # class ToplevelInstaller
+
+
+class ToplevelInstallerMulti < ToplevelInstaller
+
+ include FileOperations
+
+ def initialize(ardir_root, config)
+ super
+ @packages = directories_of("#{@ardir}/packages")
+ raise 'no package exists' if @packages.empty?
+ @root_installer = Installer.new(@config, @ardir, File.expand_path('.'))
+ end
+
+ def run_metaconfigs
+ @config.load_script "#{@ardir}/metaconfig", self
+ @packages.each do |name|
+ @config.load_script "#{@ardir}/packages/#{name}/metaconfig"
+ end
+ end
+
+ attr_reader :packages
+
+ def packages=(list)
+ raise 'package list is empty' if list.empty?
+ list.each do |name|
+ raise "directory packages/#{name} does not exist"\
+ unless File.dir?("#{@ardir}/packages/#{name}")
+ end
+ @packages = list
+ end
+
+ def init_installers
+ @installers = {}
+ @packages.each do |pack|
+ @installers[pack] = Installer.new(@config,
+ "#{@ardir}/packages/#{pack}",
+ "packages/#{pack}")
+ end
+ with = extract_selection(config('with'))
+ without = extract_selection(config('without'))
+ @selected = @installers.keys.select {|name|
+ (with.empty? or with.include?(name)) \
+ and not without.include?(name)
+ }
+ end
+
+ def extract_selection(list)
+ a = list.split(/,/)
+ a.each do |name|
+ setup_rb_error "no such package: #{name}" unless @installers.key?(name)
+ end
+ a
+ end
+
+ def print_usage(f)
+ super
+ f.puts 'Inluded packages:'
+ f.puts ' ' + @packages.sort.join(' ')
+ f.puts
+ end
+
+ #
+ # Task Handlers
+ #
+
+ def exec_config
+ run_hook 'pre-config'
+ each_selected_installers {|inst| inst.exec_config }
+ run_hook 'post-config'
+ @config.save # must be final
+ end
+
+ def exec_setup
+ run_hook 'pre-setup'
+ each_selected_installers {|inst| inst.exec_setup }
+ run_hook 'post-setup'
+ end
+
+ def exec_install
+ run_hook 'pre-install'
+ each_selected_installers {|inst| inst.exec_install }
+ run_hook 'post-install'
+ end
+
+ def exec_test
+ run_hook 'pre-test'
+ each_selected_installers {|inst| inst.exec_test }
+ run_hook 'post-test'
+ end
+
+ def exec_clean
+ rm_f @config.savefile
+ run_hook 'pre-clean'
+ each_selected_installers {|inst| inst.exec_clean }
+ run_hook 'post-clean'
+ end
+
+ def exec_distclean
+ rm_f @config.savefile
+ run_hook 'pre-distclean'
+ each_selected_installers {|inst| inst.exec_distclean }
+ run_hook 'post-distclean'
+ end
+
+ #
+ # lib
+ #
+
+ def each_selected_installers
+ Dir.mkdir 'packages' unless File.dir?('packages')
+ @selected.each do |pack|
+ $stderr.puts "Processing the package `#{pack}' ..." if verbose?
+ Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
+ Dir.chdir "packages/#{pack}"
+ yield @installers[pack]
+ Dir.chdir '../..'
+ end
+ end
+
+ def run_hook(id)
+ @root_installer.run_hook id
+ end
+
+ # module FileOperations requires this
+ def verbose?
+ @config.verbose?
+ end
+
+ # module FileOperations requires this
+ def no_harm?
+ @config.no_harm?
+ end
+
+end # class ToplevelInstallerMulti
+
+
+class Installer
+
+ FILETYPES = %w( bin lib ext data conf man )
+
+ include FileOperations
+ include HookScriptAPI
+
+ def initialize(config, srcroot, objroot)
+ @config = config
+ @srcdir = File.expand_path(srcroot)
+ @objdir = File.expand_path(objroot)
+ @currdir = '.'
+ end
+
+ def inspect
+ "#<#{self.class} #{File.basename(@srcdir)}>"
+ end
+
+ def noop(rel)
+ end
+
+ #
+ # Hook Script API base methods
+ #
+
+ def srcdir_root
+ @srcdir
+ end
+
+ def objdir_root
+ @objdir
+ end
+
+ def relpath
+ @currdir
+ end
+
+ #
+ # Config Access
+ #
+
+ # module FileOperations requires this
+ def verbose?
+ @config.verbose?
+ end
+
+ # module FileOperations requires this
+ def no_harm?
+ @config.no_harm?
+ end
+
+ def verbose_off
+ begin
+ save, @config.verbose = @config.verbose?, false
+ yield
+ ensure
+ @config.verbose = save
+ end
+ end
+
+ #
+ # TASK config
+ #
+
+ def exec_config
+ exec_task_traverse 'config'
+ end
+
+ alias config_dir_bin noop
+ alias config_dir_lib noop
+
+ def config_dir_ext(rel)
+ extconf if extdir?(curr_srcdir())
+ end
+
+ alias config_dir_data noop
+ alias config_dir_conf noop
+ alias config_dir_man noop
+
+ def extconf
+ ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt
+ end
+
+ #
+ # TASK setup
+ #
+
+ def exec_setup
+ exec_task_traverse 'setup'
+ end
+
+ def setup_dir_bin(rel)
+ files_of(curr_srcdir()).each do |fname|
+ update_shebang_line "#{curr_srcdir()}/#{fname}"
+ end
+ end
+
+ alias setup_dir_lib noop
+
+ def setup_dir_ext(rel)
+ make if extdir?(curr_srcdir())
+ end
+
+ alias setup_dir_data noop
+ alias setup_dir_conf noop
+ alias setup_dir_man noop
+
+ def update_shebang_line(path)
+ return if no_harm?
+ return if config('shebang') == 'never'
+ old = Shebang.load(path)
+ if old
+ $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1
+ new = new_shebang(old)
+ return if new.to_s == old.to_s
+ else
+ return unless config('shebang') == 'all'
+ new = Shebang.new(config('rubypath'))
+ end
+ $stderr.puts "updating shebang: #{File.basename(path)}" if verbose?
+ open_atomic_writer(path) {|output|
+ File.open(path, 'rb') {|f|
+ f.gets if old # discard
+ output.puts new.to_s
+ output.print f.read
+ }
+ }
+ end
+
+ def new_shebang(old)
+ if /\Aruby/ =~ File.basename(old.cmd)
+ Shebang.new(config('rubypath'), old.args)
+ elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby'
+ Shebang.new(config('rubypath'), old.args[1..-1])
+ else
+ return old unless config('shebang') == 'all'
+ Shebang.new(config('rubypath'))
+ end
+ end
+
+ def open_atomic_writer(path, &block)
+ tmpfile = File.basename(path) + '.tmp'
+ begin
+ File.open(tmpfile, 'wb', &block)
+ File.rename tmpfile, File.basename(path)
+ ensure
+ File.unlink tmpfile if File.exist?(tmpfile)
+ end
+ end
+
+ class Shebang
+ def Shebang.load(path)
+ line = nil
+ File.open(path) {|f|
+ line = f.gets
+ }
+ return nil unless /\A#!/ =~ line
+ parse(line)
+ end
+
+ def Shebang.parse(line)
+ cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ')
+ new(cmd, args)
+ end
+
+ def initialize(cmd, args = [])
+ @cmd = cmd
+ @args = args
+ end
+
+ attr_reader :cmd
+ attr_reader :args
+
+ def to_s
+ "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}")
+ end
+ end
+
+ #
+ # TASK install
+ #
+
+ def exec_install
+ rm_f 'InstalledFiles'
+ exec_task_traverse 'install'
+ end
+
+ def install_dir_bin(rel)
+ install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755
+ end
+
+ def install_dir_lib(rel)
+ install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644
+ end
+
+ def install_dir_ext(rel)
+ return unless extdir?(curr_srcdir())
+ install_files rubyextentions('.'),
+ "#{config('sodir')}/#{File.dirname(rel)}",
+ 0555
+ end
+
+ def install_dir_data(rel)
+ install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644
+ end
+
+ def install_dir_conf(rel)
+ # FIXME: should not remove current config files
+ # (rename previous file to .old/.org)
+ install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644
+ end
+
+ def install_dir_man(rel)
+ install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644
+ end
+
+ def install_files(list, dest, mode)
+ mkdir_p dest, @config.install_prefix
+ list.each do |fname|
+ install fname, dest, mode, @config.install_prefix
+ end
+ end
+
+ def libfiles
+ glob_reject(%w(*.y *.output), targetfiles())
+ end
+
+ def rubyextentions(dir)
+ ents = glob_select("*.#{@config.dllext}", targetfiles())
+ if ents.empty?
+ setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
+ end
+ ents
+ end
+
+ def targetfiles
+ mapdir(existfiles() - hookfiles())
+ end
+
+ def mapdir(ents)
+ ents.map {|ent|
+ if File.exist?(ent)
+ then ent # objdir
+ else "#{curr_srcdir()}/#{ent}" # srcdir
+ end
+ }
+ end
+
+ # picked up many entries from cvs-1.11.1/src/ignore.c
+ JUNK_FILES = %w(
+ core RCSLOG tags TAGS .make.state
+ .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
+ *~ *.old *.bak *.BAK *.orig *.rej _$* *$
+
+ *.org *.in .*
+ )
+
+ def existfiles
+ glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.')))
+ end
+
+ def hookfiles
+ %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
+ %w( config setup install clean ).map {|t| sprintf(fmt, t) }
+ }.flatten
+ end
+
+ def glob_select(pat, ents)
+ re = globs2re([pat])
+ ents.select {|ent| re =~ ent }
+ end
+
+ def glob_reject(pats, ents)
+ re = globs2re(pats)
+ ents.reject {|ent| re =~ ent }
+ end
+
+ GLOB2REGEX = {
+ '.' => '\.',
+ '$' => '\$',
+ '#' => '\#',
+ '*' => '.*'
+ }
+
+ def globs2re(pats)
+ /\A(?:#{
+ pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|')
+ })\z/
+ end
+
+ #
+ # TASK test
+ #
+
+ TESTDIR = 'test'
+
+ def exec_test
+ unless File.directory?('test')
+ $stderr.puts 'no test in this package' if verbose?
+ return
+ end
+ $stderr.puts 'Running tests...' if verbose?
+ begin
+ require 'test/unit'
+ rescue LoadError
+ setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.'
+ end
+ runner = Test::Unit::AutoRunner.new(true)
+ runner.to_run << TESTDIR
+ runner.run
+ end
+
+ #
+ # TASK clean
+ #
+
+ def exec_clean
+ exec_task_traverse 'clean'
+ rm_f @config.savefile
+ rm_f 'InstalledFiles'
+ end
+
+ alias clean_dir_bin noop
+ alias clean_dir_lib noop
+ alias clean_dir_data noop
+ alias clean_dir_conf noop
+ alias clean_dir_man noop
+
+ def clean_dir_ext(rel)
+ return unless extdir?(curr_srcdir())
+ make 'clean' if File.file?('Makefile')
+ end
+
+ #
+ # TASK distclean
+ #
+
+ def exec_distclean
+ exec_task_traverse 'distclean'
+ rm_f @config.savefile
+ rm_f 'InstalledFiles'
+ end
+
+ alias distclean_dir_bin noop
+ alias distclean_dir_lib noop
+
+ def distclean_dir_ext(rel)
+ return unless extdir?(curr_srcdir())
+ make 'distclean' if File.file?('Makefile')
+ end
+
+ alias distclean_dir_data noop
+ alias distclean_dir_conf noop
+ alias distclean_dir_man noop
+
+ #
+ # Traversing
+ #
+
+ def exec_task_traverse(task)
+ run_hook "pre-#{task}"
+ FILETYPES.each do |type|
+ if type == 'ext' and config('without-ext') == 'yes'
+ $stderr.puts 'skipping ext/* by user option' if verbose?
+ next
+ end
+ traverse task, type, "#{task}_dir_#{type}"
+ end
+ run_hook "post-#{task}"
+ end
+
+ def traverse(task, rel, mid)
+ dive_into(rel) {
+ run_hook "pre-#{task}"
+ __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
+ directories_of(curr_srcdir()).each do |d|
+ traverse task, "#{rel}/#{d}", mid
+ end
+ run_hook "post-#{task}"
+ }
+ end
+
+ def dive_into(rel)
+ return unless File.dir?("#{@srcdir}/#{rel}")
+
+ dir = File.basename(rel)
+ Dir.mkdir dir unless File.dir?(dir)
+ prevdir = Dir.pwd
+ Dir.chdir dir
+ $stderr.puts '---> ' + rel if verbose?
+ @currdir = rel
+ yield
+ Dir.chdir prevdir
+ $stderr.puts '<--- ' + rel if verbose?
+ @currdir = File.dirname(rel)
+ end
+
+ def run_hook(id)
+ path = [ "#{curr_srcdir()}/#{id}",
+ "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) }
+ return unless path
+ begin
+ instance_eval File.read(path), path, 1
+ rescue
+ raise if $DEBUG
+ setup_rb_error "hook #{path} failed:\n" + $!.message
+ end
+ end
+
+end # class Installer
+
+
+class SetupError < StandardError; end
+
+def setup_rb_error(msg)
+ raise SetupError, msg
+end
+
+if $0 == __FILE__
+ begin
+ ToplevelInstaller.invoke
+ rescue SetupError
+ raise if $DEBUG
+ $stderr.puts $!.message
+ $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
+ exit 1
+ end
+end