]> xenbits.xen.org Git - xenclient/ruby-dbus.git/commitdiff
Import Revion 160.
authorJean Guyader <jean.guyader@eu.citrix.com>
Fri, 19 Jun 2009 13:22:33 +0000 (14:22 +0100)
committerJean Guyader <jean.guyader@eu.citrix.com>
Fri, 19 Jun 2009 13:22:33 +0000 (14:22 +0100)
https://svn.luon.net/svn/ruby-dbus/trunk/

31 files changed:
COPYING [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
NEWS [new file with mode: 0644]
README [new file with mode: 0644]
doc/tutorial/src/00.index.page [new file with mode: 0644]
doc/tutorial/src/10.intro.page [new file with mode: 0644]
doc/tutorial/src/20.basic_client.page [new file with mode: 0644]
doc/tutorial/src/30.service.page [new file with mode: 0644]
doc/tutorial/src/default.css [new file with mode: 0644]
doc/tutorial/src/default.template [new file with mode: 0644]
examples/gdbus/gdbus [new file with mode: 0644]
examples/gdbus/gdbus.glade [new file with mode: 0644]
examples/gdbus/launch.sh [new file with mode: 0755]
examples/no-introspect/nm-test.rb [new file with mode: 0644]
examples/no-introspect/tracker-test.rb [new file with mode: 0644]
examples/rhythmbox/playpause.rb [new file with mode: 0644]
examples/service/call_service.rb [new file with mode: 0644]
examples/service/service_newapi.rb [new file with mode: 0644]
examples/simple/call_introspect.rb [new file with mode: 0644]
examples/utils/listnames.rb [new file with mode: 0644]
examples/utils/notify.rb [new file with mode: 0644]
lib/dbus.rb [new file with mode: 0644]
lib/dbus/auth.rb [new file with mode: 0644]
lib/dbus/bus.rb [new file with mode: 0644]
lib/dbus/export.rb [new file with mode: 0644]
lib/dbus/introspect.rb [new file with mode: 0644]
lib/dbus/marshall.rb [new file with mode: 0644]
lib/dbus/matchrule.rb [new file with mode: 0644]
lib/dbus/message.rb [new file with mode: 0644]
lib/dbus/type.rb [new file with mode: 0644]
setup.rb [new file with mode: 0644]

diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..5ab7695
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,504 @@
+                 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!
+
+
diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..7976b1d
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,782 @@
+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.
+
diff --git a/NEWS b/NEWS
new file mode 100644 (file)
index 0000000..130cd7a
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,31 @@
+= 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.
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..8dd160c
--- /dev/null
+++ b/README
@@ -0,0 +1,53 @@
+= 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.
diff --git a/doc/tutorial/src/00.index.page b/doc/tutorial/src/00.index.page
new file mode 100644 (file)
index 0000000..88c12d6
--- /dev/null
@@ -0,0 +1,12 @@
+---
+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.
diff --git a/doc/tutorial/src/10.intro.page b/doc/tutorial/src/10.intro.page
new file mode 100644 (file)
index 0000000..dac42ea
--- /dev/null
@@ -0,0 +1,127 @@
+---
+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.
diff --git a/doc/tutorial/src/20.basic_client.page b/doc/tutorial/src/20.basic_client.page
new file mode 100644 (file)
index 0000000..953f878
--- /dev/null
@@ -0,0 +1,174 @@
+---
+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.
diff --git a/doc/tutorial/src/30.service.page b/doc/tutorial/src/30.service.page
new file mode 100644 (file)
index 0000000..0aa5a52
--- /dev/null
@@ -0,0 +1,121 @@
+---
+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.
diff --git a/doc/tutorial/src/default.css b/doc/tutorial/src/default.css
new file mode 100644 (file)
index 0000000..db95f63
--- /dev/null
@@ -0,0 +1,129 @@
+  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;       
+  }
diff --git a/doc/tutorial/src/default.template b/doc/tutorial/src/default.template
new file mode 100644 (file)
index 0000000..cf02887
--- /dev/null
@@ -0,0 +1,46 @@
+--- 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/>
+        &copy; 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>
diff --git a/examples/gdbus/gdbus b/examples/gdbus/gdbus
new file mode 100644 (file)
index 0000000..4f7b46d
--- /dev/null
@@ -0,0 +1,255 @@
+#!/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
diff --git a/examples/gdbus/gdbus.glade b/examples/gdbus/gdbus.glade
new file mode 100644 (file)
index 0000000..109e386
--- /dev/null
@@ -0,0 +1,184 @@
+<?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>
diff --git a/examples/gdbus/launch.sh b/examples/gdbus/launch.sh
new file mode 100755 (executable)
index 0000000..f6c61f7
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+set -e
+# for the lazy typer
+ruby -w -I ../../lib gdbus
diff --git a/examples/no-introspect/nm-test.rb b/examples/no-introspect/nm-test.rb
new file mode 100644 (file)
index 0000000..c83e0fe
--- /dev/null
@@ -0,0 +1,16 @@
+#!/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
+
+
diff --git a/examples/no-introspect/tracker-test.rb b/examples/no-introspect/tracker-test.rb
new file mode 100644 (file)
index 0000000..b9b1b62
--- /dev/null
@@ -0,0 +1,16 @@
+#!/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"])
+
+
diff --git a/examples/rhythmbox/playpause.rb b/examples/rhythmbox/playpause.rb
new file mode 100644 (file)
index 0000000..8368ae1
--- /dev/null
@@ -0,0 +1,25 @@
+#!/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
+
diff --git a/examples/service/call_service.rb b/examples/service/call_service.rb
new file mode 100644 (file)
index 0000000..4907bf8
--- /dev/null
@@ -0,0 +1,25 @@
+#!/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
+
diff --git a/examples/service/service_newapi.rb b/examples/service/service_newapi.rb
new file mode 100644 (file)
index 0000000..9c49735
--- /dev/null
@@ -0,0 +1,51 @@
+#!/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
+
diff --git a/examples/simple/call_introspect.rb b/examples/simple/call_introspect.rb
new file mode 100644 (file)
index 0000000..375ed8b
--- /dev/null
@@ -0,0 +1,34 @@
+#!/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
+
diff --git a/examples/utils/listnames.rb b/examples/utils/listnames.rb
new file mode 100644 (file)
index 0000000..6b46f9a
--- /dev/null
@@ -0,0 +1,11 @@
+#!/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}" }
+
diff --git a/examples/utils/notify.rb b/examples/utils/notify.rb
new file mode 100644 (file)
index 0000000..81391b9
--- /dev/null
@@ -0,0 +1,19 @@
+#!/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
+
diff --git a/lib/dbus.rb b/lib/dbus.rb
new file mode 100644 (file)
index 0000000..f1b8de4
--- /dev/null
@@ -0,0 +1,82 @@
+# 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
diff --git a/lib/dbus/auth.rb b/lib/dbus/auth.rb
new file mode 100644 (file)
index 0000000..42a3243
--- /dev/null
@@ -0,0 +1,156 @@
+# 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
diff --git a/lib/dbus/bus.rb b/lib/dbus/bus.rb
new file mode 100644 (file)
index 0000000..c528d7a
--- /dev/null
@@ -0,0 +1,690 @@
+# 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
diff --git a/lib/dbus/export.rb b/lib/dbus/export.rb
new file mode 100644 (file)
index 0000000..f51716e
--- /dev/null
@@ -0,0 +1,123 @@
+# 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
diff --git a/lib/dbus/introspect.rb b/lib/dbus/introspect.rb
new file mode 100644 (file)
index 0000000..bbb09a4
--- /dev/null
@@ -0,0 +1,509 @@
+# 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
+
diff --git a/lib/dbus/marshall.rb b/lib/dbus/marshall.rb
new file mode 100644 (file)
index 0000000..23a0f3b
--- /dev/null
@@ -0,0 +1,391 @@
+# 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
diff --git a/lib/dbus/matchrule.rb b/lib/dbus/matchrule.rb
new file mode 100644 (file)
index 0000000..b6f79f5
--- /dev/null
@@ -0,0 +1,98 @@
+# 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
diff --git a/lib/dbus/message.rb b/lib/dbus/message.rb
new file mode 100644 (file)
index 0000000..a54796e
--- /dev/null
@@ -0,0 +1,280 @@
+# 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
diff --git a/lib/dbus/type.rb b/lib/dbus/type.rb
new file mode 100644 (file)
index 0000000..83423f0
--- /dev/null
@@ -0,0 +1,206 @@
+# 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
diff --git a/setup.rb b/setup.rb
new file mode 100644 (file)
index 0000000..424a5f3
--- /dev/null
+++ b/setup.rb
@@ -0,0 +1,1585 @@
+#
+# 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