summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore18
-rw-r--r--COPYING510
-rw-r--r--Makefile.am9
-rw-r--r--NEWS84
-rwxr-xr-xautogen.sh31
-rw-r--r--bin/.gitignore3
-rw-r--r--bin/Makefile.am9
-rw-r--r--bin/dconf-update.vala169
-rw-r--r--bin/dconf.vala103
-rw-r--r--bin/fixes.vapi4
-rw-r--r--bin/gvdb.vapi21
-rw-r--r--client/.gitignore7
-rw-r--r--client/Makefile.am46
-rw-r--r--client/dconf-client.h145
-rw-r--r--client/dconf-client.vala411
-rw-r--r--client/dconf.h28
-rw-r--r--client/dconf.pc.in11
-rw-r--r--client/engine.vapi36
-rw-r--r--client/extra-docs.c89
-rw-r--r--common/Makefile.am7
-rw-r--r--common/dconf-paths.c240
-rw-r--r--common/dconf-paths.h42
-rw-r--r--common/dconf-shmdir.c83
-rw-r--r--common/dconf-shmdir.h30
-rw-r--r--configure.ac67
-rw-r--r--dbus-1/.gitignore4
-rw-r--r--dbus-1/Makefile.am34
-rw-r--r--dbus-1/dconf-dbus-1.c728
-rw-r--r--dbus-1/dconf-dbus-1.h56
-rw-r--r--dbus-1/dconf-dbus-1.pc.in11
-rw-r--r--dconf.doap19
-rw-r--r--docs/.gitignore17
-rw-r--r--docs/Makefile.am24
-rw-r--r--docs/dconf-docs.xml29
-rw-r--r--docs/dconf-sections.txt40
-rw-r--r--docs/dconf.types1
-rw-r--r--editor/.gitignore4
-rw-r--r--editor/Makefile.am15
-rw-r--r--editor/config.vapi7
-rw-r--r--editor/dconf-editor.desktop.in14
-rw-r--r--editor/dconf-editor.ui267
-rw-r--r--editor/dconf-editor.vala172
-rw-r--r--editor/dconf-model.vala646
-rw-r--r--editor/dconf-schema.vala280
-rw-r--r--editor/dconf-view.vala286
-rw-r--r--engine/Makefile.am8
-rw-r--r--engine/dconf-engine.c662
-rw-r--r--engine/dconf-engine.h159
-rw-r--r--engine/dconf-readtype.h11
-rw-r--r--engine/dconf-resetlist.c1
-rw-r--r--engine/dconf-resetlist.h20
-rw-r--r--gsettings/.gitignore1
-rw-r--r--gsettings/Makefile.am24
-rw-r--r--gsettings/dconfcontext.c35
-rw-r--r--gsettings/dconfcontext.h9
-rw-r--r--gsettings/dconfsettingsbackend.c670
-rw-r--r--gvdb/Makefile.am6
-rw-r--r--gvdb/gvdb-builder.c (renamed from gvdb-builder.c)0
-rw-r--r--gvdb/gvdb-builder.h (renamed from gvdb-builder.h)0
-rw-r--r--gvdb/gvdb-format.h (renamed from gvdb-format.h)0
-rw-r--r--gvdb/gvdb-reader.c (renamed from gvdb-reader.c)0
-rw-r--r--gvdb/gvdb-reader.h (renamed from gvdb-reader.h)0
-rw-r--r--gvdb/gvdb.doap (renamed from gvdb.doap)0
-rw-r--r--service/.gitignore2
-rw-r--r--service/Makefile.am26
-rw-r--r--service/dconf-interfaces.c91
-rw-r--r--service/dconf-interfaces.h30
-rw-r--r--service/dconf-rebuilder.c214
-rw-r--r--service/dconf-rebuilder.h34
-rw-r--r--service/dconf-state.c98
-rw-r--r--service/dconf-state.h22
-rw-r--r--service/dconf-writer.c165
-rw-r--r--service/dconf-writer.h49
-rw-r--r--service/service.c433
-rw-r--r--tests/.gitignore3
-rw-r--r--tests/Makefile.am11
-rw-r--r--tests/dbus1.c349
-rw-r--r--tests/gsettings.c409
-rw-r--r--tests/paths.c100
79 files changed, 8499 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..beccb04
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,18 @@
+# for all subdirectories
+Makefile.in
+Makefile
+.deps
+*.o
+*.pc
+*.gir
+*.typelib
+
+# autofoo stuff here
+/m4/
+/aux/
+/libtool
+/config.*
+/configure
+/autom4te.cache
+/aclocal.m4
+/stamp-h1
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..2d2d780
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,510 @@
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations
+below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it
+becomes a de-facto standard. To achieve this, non-free programs must
+be allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control
+compilation and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at least
+ three years, to give the same user the materials specified in
+ Subsection 6a, above, for a charge no more than the cost of
+ performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply, and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License
+may add an explicit geographical distribution limitation excluding those
+countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ 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 St, 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/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..6f23b0a
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,9 @@
+ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
+
+SUBDIRS = gvdb service gsettings dbus-1 tests client bin engine common docs
+
+if ENABLE_EDITOR
+SUBDIRS += editor
+endif
+
+DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..c109f13
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,84 @@
+Changes in dconf 0.7.1
+======================
+
+The last release contained a few problems that caused build failures on
+some strict linkers. Those should be fixed now.
+
+Changes in dconf 0.7
+====================
+
+ - new library to use dconf with libdbus-1
+ - quite a lot of improvements and bug-fixes in dconf-editor, thanks to
+ Robert Ancell
+ - some bug fixes in the GSettings backend (crashers caused by use if
+ custom dconf profiles)
+ - some FreeBSD build fixes
+ - increased Vala dependency to 0.11.4 (required for dconf-editor fixes)
+
+Changes in dconf 0.6
+====================
+
+ - Rewrite a lot of the GSettings backend to reduce GDBus abuse. We use
+ our own worker thread now instead of trying to hijack GDBus's.
+ - disable gobject-introspection support for now
+ - drop support for GTK2 in dconf-editor
+ - Add a new torture-test case
+ - Increase dbus timeout to 2 minutes (in case the service is heavily loaded)
+ - Fix several memory leaks and other bugs
+
+Changes in dconf 0.5.1
+======================
+
+ - Adjust to GDBus API changes
+ - Send correct object path in Notify on WriteMany
+ - Use printf() and exit() instead of g_error() to avoid too many crash
+ reports for now
+ - Require gobject-introspection 0.9.5
+ - Require vala 0.9.5
+ - Make dconf-editor optional
+ - Drop libgee requirement for dconf-editor
+ - Tweak shared library installation to make ldconfig happy
+ - Bump .gir version to dconf-1.0
+ - Fix introspection build with recent gobject-introspection
+ - Minor bug fixes
+
+Changes in dconf 0.5
+=====================
+
+ - Include a dconf-editor
+ - drop libtool
+ - allow compiling without gobject-introspection
+ - autotools/build fixups
+ - repair some broken use of tags
+ - many updates for glib API changes
+ - fix a crasher in the service
+ - prefer 'automake-1.11' if it is in the path
+ - add support for layering (ie: for system defaults)
+ - add support for multiple writers in one service
+ - add a shared memory status region to indicate if the gvdb is stale
+ this prevents dconf from having to reopen the file each time
+ - support keyfile-maintained system settings (via 'dconf update')
+ - port client library and commandline tool to vala
+ - client library no longer has unimplemented calls
+ (except for write_many_async, due to bugs in valac)
+ - gtk-doc is now complete for the client library
+ - install our own vapi
+ - support 'reset' in the GSettingsBackend
+
+Changes in dconf 0.4
+=====================
+
+ - fix crashes when the dconf database doesn't yet exist
+ - add some incomplete gtk-doc
+ - use new GVDB (note: dconf file format has incompatibly changed)
+ - implement GSettings sync()
+ - use string tags instead of sequence numbers since it was impossible
+ to have universally unique sequence numbers
+ - theoretical support for sharing dconf databases between machines with
+ different byte orders
+ - fix bug where first write was not successful when auto-starting
+ service
+ - FreeBSD build fixes
+ - client API cleanups
+ - GObject introspection support
+ - enable automake silent rules by default for tarball builds
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..aaf6544
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+set -e
+
+if [ "$1" = "clean" ]; then
+ rm -f aclocal.m4 configure config.* `find . -name Makefile.in` libtool
+ rm -rf autom4te.cache m4 aux
+ exit
+fi
+
+if automake-1.11 --version &> /dev/null; then
+ automake_suffix='-1.11'
+else
+ automake_suffix=''
+fi
+
+mkdir -p m4 aux
+gtkdocize --docdir docs --flavour no-tmpl
+aclocal${automake_suffix} ${ACLOCAL_FLAGS}
+autoheader
+automake${automake_suffix} --add-missing --foreign
+autoconf
+
+CFLAGS=${CFLAGS=-ggdb -Werror}
+LDFLAGS=${LDFLAGS=-Wl,-O1}
+export CFLAGS LDFLAGS
+
+if test -z "$NOCONFIGURE"; then
+ ./configure "$@"
+fi
+
diff --git a/bin/.gitignore b/bin/.gitignore
new file mode 100644
index 0000000..d657c26
--- /dev/null
+++ b/bin/.gitignore
@@ -0,0 +1,3 @@
+dconf
+*.c
+*.stamp
diff --git a/bin/Makefile.am b/bin/Makefile.am
new file mode 100644
index 0000000..f830052
--- /dev/null
+++ b/bin/Makefile.am
@@ -0,0 +1,9 @@
+AM_CFLAGS = -std=c89 -Wall -Wmissing-prototypes -Wwrite-strings
+CFLAGS += -Wno-error
+INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/engine -I$(top_srcdir)/client -I$(top_srcdir)/gvdb $(gio_CFLAGS)
+
+bin_PROGRAMS = dconf
+
+dconf_VALAFLAGS = --vapidir ../client --pkg=gio-2.0 --pkg=posix --pkg=dconf
+dconf_LDADD = $(gio_LIBS) ../client/libdconf.so.0
+dconf_SOURCES = dconf.vala dconf-update.vala ../gvdb/gvdb-builder.c gvdb.vapi fixes.vapi
diff --git a/bin/dconf-update.vala b/bin/dconf-update.vala
new file mode 100644
index 0000000..2dd9b2e
--- /dev/null
+++ b/bin/dconf-update.vala
@@ -0,0 +1,169 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+unowned Gvdb.Item get_parent (Gvdb.HashTable table, string name) {
+ unowned Gvdb.Item parent;
+
+ int end = 0;
+
+ for (int i = 0; name[i] != '\0'; i++) {
+ if (name[i - 1] == '/') {
+ end = i;
+ }
+ }
+
+ var parent_name = name.ndup (end);
+ parent = table.lookup (parent_name);
+
+ if (parent == null) {
+ parent = table.insert (parent_name);
+ parent.set_parent (get_parent (table, parent_name));
+ }
+
+ return parent;
+}
+
+Gvdb.HashTable read_directory (string dirname) throws GLib.Error {
+ var table = new Gvdb.HashTable ();
+ unowned string? name;
+
+ table.insert ("/");
+
+ var dir = Dir.open (dirname);
+ while ((name = dir.read_name ()) != null) {
+ var filename = Path.build_filename (dirname, name);
+
+ var kf = new KeyFile ();
+
+ try {
+ kf.load_from_file (filename, KeyFileFlags.NONE);
+ } catch (GLib.Error e) {
+ stderr.printf ("%s: %s\n", filename, e.message);
+ continue;
+ }
+
+ foreach (var group in kf.get_groups ()) {
+ if (group.has_prefix ("/") || group.has_suffix ("/") || group.str ("//") != null) {
+ stderr.printf ("%s: ignoring invalid group name: %s\n", filename, group);
+ continue;
+ }
+
+ foreach (var key in kf.get_keys (group)) {
+ if (key.str ("/") != null) {
+ stderr.printf ("%s: [%s]: ignoring invalid key name: %s\n", filename, group, key);
+ continue;
+ }
+
+ var path = "/" + group + "/" + key;
+
+ if (table.lookup (path) != null) {
+ stderr.printf ("%s: [%s]: %s: ignoring duplicate definition of key %s\n",
+ filename, group, key, path);
+ continue;
+ }
+
+ var text = kf.get_value (group, key);
+
+ try {
+ var value = Variant.parse (null, text);
+ unowned Gvdb.Item item = table.insert (path);
+ item.set_parent (get_parent (table, path));
+ item.set_value (value);
+ } catch (VariantParseError e) {
+ stderr.printf ("%s: [%s]: %s: skipping invalid value: %s (%s)\n",
+ filename, group, key, text, e.message);
+ }
+ }
+ }
+ }
+
+ return table;
+}
+
+void maybe_update_from_directory (string dirname) throws GLib.Error {
+ Posix.Stat dir_buf;
+
+ if (Posix.stat (dirname, out dir_buf) == 0 && Posix.S_ISDIR (dir_buf.st_mode)) {
+ Posix.Stat file_buf;
+
+ var filename = dirname.ndup (dirname.length - 2);
+
+ if (Posix.stat (filename, out file_buf) == 0 && file_buf.st_mtime > dir_buf.st_mtime) {
+ return;
+ }
+
+ var table = read_directory (dirname);
+
+ var fd = Posix.open (filename, Posix.O_WRONLY);
+
+ if (fd < 0 && errno != Posix.ENOENT) {
+ var saved_error = errno;
+ throw new FileError.FAILED ("Can not open '%s' for replacement: %s", filename, strerror (saved_error));
+ }
+
+ try {
+ table.write_contents (filename);
+
+ if (fd >= 0) {
+ Posix.write (fd, "\0\0\0\0\0\0\0\0", 8);
+ }
+ } finally {
+ if (fd >= 0) {
+ Posix.close (fd);
+ }
+ }
+
+ try {
+ var system_bus = Bus.get_sync (BusType.SYSTEM);
+ system_bus.emit_signal (null, "/" + Path.get_basename (filename), "ca.desrt.dconf.Writer", "Notify",
+ new Variant ("(tsas)", (uint64) 0, "/", new VariantBuilder (STRING_ARRAY)));
+ flush_the_bus (system_bus);
+ } catch {
+ /* if we can't, ... don't. */
+ }
+ }
+}
+
+void update_all (string dirname) throws GLib.Error {
+ unowned string? name;
+
+ var dir = Dir.open (dirname);
+
+ while ((name = dir.read_name ()) != null) {
+ if (name.has_suffix (".d")) {
+ try {
+ maybe_update_from_directory (Path.build_filename (dirname, name));
+ } catch (GLib.Error e) {
+ stderr.printf ("%s\n", e.message);
+ }
+ }
+ }
+}
+
+void do_update () {
+ try {
+ update_all ("/etc/dconf/db");
+ } catch (GLib.Error e) {
+ stderr.printf ("fatal: %s\n", e.message);
+ }
+}
+
+// vim:noet ts=4 sw=4
diff --git a/bin/dconf.vala b/bin/dconf.vala
new file mode 100644
index 0000000..dd3b5e5
--- /dev/null
+++ b/bin/dconf.vala
@@ -0,0 +1,103 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+void do_read (DConf.Client client, string key) throws Error {
+ DConf.verify_key (key);
+
+ var result = client.read (key);
+ if (result != null) {
+ stdout.puts (result.print (true));
+ stdout.putc ('\n');
+ }
+}
+
+void do_list (DConf.Client client, string dir) throws Error {
+ DConf.verify_dir (dir);
+
+ foreach (var item in client.list (dir)) {
+ stdout.puts (item);
+ stdout.putc ('\n');
+ }
+}
+
+void do_write (DConf.Client client, string key, string val) throws Error {
+ DConf.verify_key (key);
+
+ client.write (key, Variant.parse (null, val));
+}
+
+void do_lock (DConf.Client client, string key, bool locked) throws Error {
+ DConf.verify_key (key);
+
+ client.set_locked (key, locked);
+}
+
+void do_watch (DConf.Client client, string name) throws Error {
+ DConf.verify_path (name);
+
+ client.watch (name);
+ new MainLoop (null, false).run ();
+}
+
+void main (string[] args) {
+ try {
+ var client = new DConf.Client ();
+
+ Environment.set_prgname (args[0]);
+
+ switch (args[1]) {
+ case "read":
+ do_read (client, args[2]);
+ break;
+
+ case "list":
+ do_list (client, args[2]);
+ break;
+
+ case "write":
+ do_write (client, args[2], args[3]);
+ break;
+
+ case "update":
+ do_update ();
+ break;
+
+ case "lock":
+ do_lock (client, args[2], true);
+ break;
+
+ case "unlock":
+ do_lock (client, args[2], false);
+ break;
+
+ case "watch":
+ do_watch (client, args[2]);
+ break;
+
+ default:
+ error ("unknown command");
+ }
+ } catch (Error e) {
+ stderr.printf ("error: %s\n", e.message);
+ }
+}
+
+// vim:noet sw=4 ts=4
diff --git a/bin/fixes.vapi b/bin/fixes.vapi
new file mode 100644
index 0000000..ada6ab6
--- /dev/null
+++ b/bin/fixes.vapi
@@ -0,0 +1,4 @@
+[CCode (cname = "g_dbus_connection_flush_sync")]
+void flush_the_bus (GLib.DBusConnection connection, GLib.Cancellable? cancellable = null) throws GLib.Error;
+[CCode (cname = "G_VARIANT_TYPE_STRING_ARRAY")]
+public static GLib.VariantType STRING_ARRAY;
diff --git a/bin/gvdb.vapi b/bin/gvdb.vapi
new file mode 100644
index 0000000..1848c1d
--- /dev/null
+++ b/bin/gvdb.vapi
@@ -0,0 +1,21 @@
+[CCode (cheader_filename = "gvdb-builder.h")]
+namespace Gvdb {
+ [Compact]
+ [CCode (cname = "GHashTable")]
+ class HashTable : GLib.HashTable<string, Item> {
+ public HashTable (HashTable? parent = null, string? key = null);
+ public unowned Item insert (string key);
+ public void insert_string (string key, string value);
+ [CCode (cname = "gvdb_table_write_contents")]
+ public void write_contents (string filename, bool byteswap = false) throws GLib.Error;
+ }
+
+ [Compact]
+ class Item {
+ public void set_value (GLib.Variant value);
+ public void set_hash_table (HashTable table);
+ public void set_parent (Item parent);
+ }
+}
+
+// vim:noet ts=4 sw=4
diff --git a/client/.gitignore b/client/.gitignore
new file mode 100644
index 0000000..6b4b55c
--- /dev/null
+++ b/client/.gitignore
@@ -0,0 +1,7 @@
+dconf.vapi
+dconf-client.c
+*.stamp
+*.deps
+libdconf.so
+libdconf.so.0
+libdconf.so.0.0.0
diff --git a/client/Makefile.am b/client/Makefile.am
new file mode 100644
index 0000000..a6c9212
--- /dev/null
+++ b/client/Makefile.am
@@ -0,0 +1,46 @@
+AM_CFLAGS = -std=c89 -Wall -Wmissing-prototypes -Wwrite-strings -D__dconf_h__ -fPIC -DPIC
+CFLAGS += -Wno-error
+INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/gvdb -I$(top_srcdir)/engine $(gio_CFLAGS)
+
+shlibdir=$(libdir)
+shlib_PROGRAMS = libdconf.so.0.0.0
+nodist_noinst_DATA = libdconf.so.0 libdconf.so
+
+libdconf.so.0 libdconf.so: libdconf.so.0.0.0
+ $(AM_V_GEN) ln -fs libdconf.so.0.0.0 $@
+
+install-data-hook:
+ ln -fs libdconf.so.0.0.0 $(DESTDIR)$(shlibdir)/libdconf.so.0
+ ln -fs libdconf.so.0.0.0 $(DESTDIR)$(shlibdir)/libdconf.so
+
+uninstall-hook:
+ rm -f $(DESTDIR)$(shlibdir)/libdconf.so.0
+ rm -f $(DESTDIR)$(shlibdir)/libdconf.so
+
+dconfinclude_HEADERS = \
+ dconf-client.h \
+ dconf.h
+
+libdconf_so_0_0_0_LDADD = $(gio_LIBS)
+libdconf_so_0_0_0_LDFLAGS = -shared -Wl,-soname=libdconf.so.0
+libdconf_so_0_0_0_SOURCES = \
+ ../common/dconf-shmdir.c \
+ ../common/dconf-paths.c \
+ ../engine/dconf-engine.c \
+ ../gvdb/gvdb-reader.c \
+ dconf-client.vala engine.vapi
+libdconf_so_0_0_0_VALAFLAGS = --library dconf --pkg=gio-2.0
+
+EXTRA_DIST = dconf.vapi extra-docs.c
+dconf.vapi: libdconf.so.0
+
+dconf.deps:
+ $(AM_V_GEN) echo gio-2.0 > dconf.deps
+
+vapi_DATA = dconf.vapi dconf.deps
+vapidir = $(datadir)/vala/vapi
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = dconf.pc
+
+CLEANFILES = dconf.deps libdconf.so.0 libdconf.so
diff --git a/client/dconf-client.h b/client/dconf-client.h
new file mode 100644
index 0000000..019bb38
--- /dev/null
+++ b/client/dconf-client.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __dconf_client_h__
+#define __dconf_client_h__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define DCONF_TYPE_CLIENT (dconf_client_get_type ())
+#define DCONF_CLIENT(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), DCONF_TYPE_CLIENT, DConfClient))
+#define DCONF_IS_CLIENT(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), DCONF_TYPE_CLIENT))
+
+typedef GObjectClass DConfClientClass;
+typedef struct _DConfClient DConfClient;
+
+typedef void (*DConfWatchFunc) (DConfClient *client,
+ const gchar *path,
+ const gchar * const *items,
+ gint n_items,
+ const gchar *tag,
+ gpointer user_data);
+
+GType dconf_client_get_type (void);
+
+DConfClient * dconf_client_new (const gchar *profile,
+ DConfWatchFunc watch_func,
+ gpointer user_data,
+ GDestroyNotify notify);
+
+GVariant * dconf_client_read (DConfClient *client,
+ const gchar *key);
+GVariant * dconf_client_read_default (DConfClient *client,
+ const gchar *key);
+GVariant * dconf_client_read_no_default (DConfClient *client,
+ const gchar *key);
+
+gchar ** dconf_client_list (DConfClient *client,
+ const gchar *dir,
+ gint *length);
+
+gboolean dconf_client_is_writable (DConfClient *client,
+ const gchar *key);
+
+gboolean dconf_client_write (DConfClient *client,
+ const gchar *key,
+ GVariant *value,
+ gchar **tag,
+ GCancellable *cancellable,
+ GError **error);
+void dconf_client_write_async (DConfClient *client,
+ const gchar *key,
+ GVariant *value,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean dconf_client_write_finish (DConfClient *client,
+ GAsyncResult *result,
+ gchar **tag,
+ GError **error);
+
+gboolean dconf_client_set_locked (DConfClient *client,
+ const gchar *path,
+ gboolean locked,
+ GCancellable *cancellable,
+ GError **error);
+void dconf_client_set_locked_async (DConfClient *client,
+ const gchar *path,
+ gboolean locked,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean dconf_client_set_locked_finish (DConfClient *client,
+ GAsyncResult *result,
+ GError **error);
+
+gboolean dconf_client_write_many (DConfClient *client,
+ const gchar *dir,
+ const gchar * const *rels,
+ GVariant **values,
+ gint n_values,
+ gchar **tag,
+ GCancellable *cancellable,
+ GError **error);
+
+/* write_many_async currently disabled due to missing Vala functionality
+void dconf_client_write_many_async (DConfClient *client,
+ const gchar *dir,
+ const gchar * const *rels,
+ GVariant **values,
+ gint n_values,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean dconf_client_write_many_finish (DConfClient *client,
+ GAsyncResult *result,
+ gchar **tag,
+ GError **error);*/
+
+gboolean dconf_client_watch (DConfClient *client,
+ const gchar *path,
+ GCancellable *cancellable,
+ GError **error);
+void dconf_client_watch_async (DConfClient *client,
+ const gchar *path,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean dconf_client_watch_finish (DConfClient *client,
+ GAsyncResult *result,
+ GError **error);
+gboolean dconf_client_unwatch (DConfClient *client,
+ const gchar *path,
+ GCancellable *cancellable,
+ GError **error);
+void dconf_client_unwatch_async (DConfClient *client,
+ const gchar *path,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean dconf_client_unwatch_finish (DConfClient *client,
+ GAsyncResult *result,
+ GError **error);
+G_END_DECLS
+
+#endif /* __dconf_client_h__ */
diff --git a/client/dconf-client.vala b/client/dconf-client.vala
new file mode 100644
index 0000000..810348e
--- /dev/null
+++ b/client/dconf-client.vala
@@ -0,0 +1,411 @@
+[CCode (cheader_filename = "dconf.h")]
+namespace DConf {
+ public delegate void WatchFunc (DConf.Client client, string path, string[] items, string tag);
+
+ public class Client : Object {
+ DBusConnection? session;
+ DBusConnection? system;
+ WatchFunc watch_func;
+ Engine engine;
+
+ void call_sync (EngineMessage dcem, out string tag, Cancellable? cancellable) throws Error {
+ DBusConnection connection;
+
+ if (dcem.bus_types[0] == 'e') {
+ if (session == null) {
+ session = Bus.get_sync (BusType.SESSION, cancellable);
+ }
+ connection = session;
+ } else {
+ assert (dcem.bus_types[0] == 'y');
+ if (system == null) {
+ system = Bus.get_sync (BusType.SYSTEM, cancellable);
+ }
+ connection = system;
+ }
+
+ foreach (var message in dcem.parameters) {
+ var reply = connection.call_sync (dcem.bus_name, dcem.object_path, dcem.interface_name, dcem.method_name,
+ message, dcem.reply_type, DBusCallFlags.NONE, -1, cancellable);
+ if (dcem.reply_type != VariantType.UNIT) {
+ reply.get ("(s)", out tag);
+ }
+ }
+ }
+
+ async void call_async (EngineMessage dcem, out string tag, Cancellable? cancellable) throws Error {
+ DBusConnection connection;
+
+ if (dcem.bus_types[0] == 'e') {
+ if (session == null) {
+ session = yield Bus.get (BusType.SESSION, cancellable);
+ }
+ connection = session;
+ } else {
+ assert (dcem.bus_types[0] == 'y');
+ if (system == null) {
+ system = yield Bus.get (BusType.SYSTEM, cancellable);
+ }
+ connection = system;
+ }
+
+ foreach (var message in dcem.parameters) {
+ var reply = yield connection.call (dcem.bus_name, dcem.object_path, dcem.interface_name, dcem.method_name,
+ message, dcem.reply_type, DBusCallFlags.NONE, -1, cancellable);
+ if (dcem.reply_type != VariantType.UNIT) {
+ reply.get ("(s)", out tag);
+ }
+ }
+ }
+
+ /**
+ * dconf_client_is_writable:
+ * @client: a #DConfClient
+ * @key: a dconf key
+ * Returns: %TRUE is @key is writable
+ *
+ * Checks if @key is writable (ie: the key has no mandatory setting).
+ *
+ * This call does not verify that writing to the key will actually be successful. It only checks for
+ * the existence of mandatory keys/locks that might affect writing to @key. Other issues (such as a
+ * full disk or an inability to connect to the bus and start the service) may cause the write to fail.
+ **/
+ public bool is_writable (string key) {
+ return engine.is_writable (key);
+ }
+
+ /**
+ * dconf_client_write:
+ * @client: a #DConfClient
+ * @key: a dconf key
+ * @value: (allow-none): a #GVariant, or %NULL
+ * @tag: (out) (allow-none): the tag from this write
+ * @cancellable: a #GCancellable, or %NULL
+ * @error: a pointer to a #GError, or %NULL
+ * Returns: %TRUE if the write is successful
+ *
+ * Write a value to the given @key, or reset @key to its default value.
+ *
+ * If @value is %NULL then @key is reset to its default value (which may
+ * be completely unset), otherwise @value becomes the new value.
+ *
+ * If @tag is non-%NULL then it is set to the unique tag associated with this write. This is the same
+ * tag that appears in change notifications.
+ **/
+ public bool write (string key, Variant? value, out string tag = null, Cancellable? cancellable = null) throws Error {
+ if (&tag == null) { /* bgo #591673 */
+ string junk;
+ call_sync (engine.write (key, value), out junk, cancellable);
+ } else {
+ call_sync (engine.write (key, value), out tag, cancellable);
+ }
+ return true;
+ }
+
+ /**
+ * dconf_client_write_async:
+ * @client: a #DConfClient
+ * @key: a dconf key
+ * @value: (allow-none): a #GVariant, or %NULL
+ * @cancellable: a #GCancellable, or %NULL
+ * @callback: the function to call when complete
+ * @user_data: the user data for @callback
+ *
+ * Write a value to the given @key, or reset @key to its default value.
+ *
+ * This is the asynchronous version of dconf_client_write(). You should call
+ * dconf_client_write_finish() from @callback to collect the result.
+ **/
+ public async bool write_async (string key, Variant? value, out string tag = null, Cancellable? cancellable = null) throws Error {
+ yield call_async (engine.write (key, value), out tag, cancellable);
+ return true;
+ }
+
+ /**
+ * dconf_client_write_many:
+ * @client: a #DConfClient
+ * @dir: the dconf directory under which to make the writes
+ * @rels: a %NULL-terminated array of relative keys
+ * @values: an array of possibly-%NULL #GVariant pointers
+ * @n_values: the length of @values, which must be equal to the length of @rels
+ * @tag: (out) (allow-none): the tag from this write
+ * @cancellable: a #GCancellable, or %NULL
+ * @error: a pointer to a #GError, or %NULL
+ * Returns: %TRUE if the write is successful
+ *
+ * Write multiple values at once.
+ *
+ * For each pair of items from @rels and @values, the value is written to the result of concatenating
+ * @dir with the relative path. As with dconf_client_write(), if a given value is %NULL then the effect
+ * is that the specified key is reset.
+ *
+ * If @tag is non-%NULL then it is set to the unique tag associated with this write. This is the same
+ * tag that appears in change notifications.
+ **/
+ public bool write_many (string dir, [CCode (array_length = false, array_null_terminated = true)] string[] rels, Variant?[] values, out string? tag = null, Cancellable? cancellable = null) throws Error {
+ if (&tag == null) { /* bgo #591673 */
+ string junk;
+ call_sync (engine.write_many (dir, rels, values), out junk, cancellable);
+ } else {
+ call_sync (engine.write_many (dir, rels, values), out tag, cancellable);
+ }
+ return true;
+ }
+
+ /*< disabled due to Vala compiler bugs >
+ * dconf_client_write_many_async:
+ * @client: a #DConfClient
+ * @dir: the dconf directory under which to make the writes
+ * @rels: a %NULL-terminated array of relative keys
+ * @values: an array of possibly-%NULL #GVariant pointers
+ * @n_values: the length of @values, which must be equal to the length of @rels
+ * @cancellable: a #GCancellable, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when finished
+ * @user_data: a pointer to pass as the last argument to @callback
+ *
+ * Write multiple values at once.
+ *
+ * This is the asynchronous version of dconf_client_write_many(). You should call
+ * dconf_client_write_many_finish() from @callback to collect the result.
+ *
+ public async bool write_many_async (string dir, [CCode (array_length = false, array_null_terminated = true)] string[] rels, Variant?[] values, out string? tag = null, Cancellable? cancellable = null) throws Error {
+ yield call_async (engine.write_many (dir, rels, values), out tag, cancellable);
+ return true;
+ }*/
+
+ /**
+ * dconf_client_set_locked:
+ * @client: a #DConfClient
+ * @path: a dconf path
+ * @locked: %TRUE to lock, %FALSE to unlock
+ * @cancellable: a #GCancellable, or %NULL
+ * @error: a pointer to a #GError, or %NULL
+ * Returns: %TRUE if setting the lock was successful
+ *
+ * Marks a dconf path as being locked.
+ *
+ * Locks do not affect writes to this #DConfClient. You can still write to a key that is marked as
+ * being locked without problems.
+ *
+ * Locks are only effective when they are set on a database that is being used as the source of
+ * default/mandatory values. In that case, the lock will prevent writes from occuring to the database
+ * that has this database as its defaults.
+ **/
+ public bool set_locked (string path, bool locked, Cancellable? cancellable = null) throws Error {
+ call_sync (engine.set_locked (path, locked), null, cancellable);
+ return true;
+ }
+
+ /**
+ * dconf_client_set_locked_async:
+ * @client: a #DConfClient
+ * @path: a dconf path
+ * @locked: %TRUE to lock, %FALSE to unlock
+ * @cancellable: a #GCancellable, or %NULL
+ * @callback: the function to call when complete
+ * @user_data: the user data for @callback
+ *
+ * Marks a dconf path as being locked.
+ *
+ * This is the asynchronous version of dconf_client_set_locked(). You should call
+ * dconf_client_set_locked_finish() from @callback to collect the result.
+ **/
+ public async bool set_locked_async (string key, bool locked, Cancellable? cancellable = null) throws Error {
+ yield call_async (engine.set_locked (key, locked), null, cancellable);
+ return true;
+ }
+
+ /**
+ * dconf_client_read:
+ * @client: a #DConfClient
+ * @key: a valid dconf key
+ * Returns: the value corresponding to @key, or %NULL if there is none
+ *
+ * Reads the value named by @key from dconf. If no such value exists, %NULL is returned.
+ **/
+ public Variant? read (string key) {
+ return engine.read (key);
+ }
+
+ /**
+ * dconf_client_read_default:
+ * @client: a #DConfClient
+ * @key: a valid dconf key
+ * Returns: the default value corresponding to @key, or %NULL if there is none
+ *
+ * Reads the value named by @key from any existing default/mandatory databases but ignoring any value
+ * set by the user. The result is as if the named key had just been reset.
+ **/
+ public Variant? read_default (string key) {
+ return engine.read_default (key);
+ }
+
+ /**
+ * dconf_client_read_no_default:
+ * @client: a #DConfClient
+ * @key: a valid dconf key
+ * Returns: the user value corresponding to @key, or %NULL if there is none
+ *
+ * Reads the value named by @key as set by the user, ignoring any default/mandatory databases. Normal
+ * applications will never want to do this, but it may be useful for administrative or configuration
+ * tweaking utilities to have access to this information.
+ *
+ * Note that in the case of mandatory keys, the result of dconf_client_read_no_default() with a fallback
+ * to dconf_client_read_default() is not necessarily the same as the result of a dconf_client_read().
+ * This is because the user may have set a value before the key became marked as mandatory, in which
+ * case this call will see the user's (otherwise inaccessible) key.
+ **/
+ public Variant? read_no_default (string key) {
+ return engine.read_no_default (key);
+ }
+
+ /**
+ * dconf_client_list:
+ * @client: a #DConfClient
+ * @dir: a dconf dir
+ * @length: the number of items that were returned
+ * Returns: (array length=length): the paths located directly below @dir
+ *
+ * Lists the keys and dirs located directly below @dir.
+ *
+ * You should free the return result with g_strfreev() when it is no longer needed.
+ **/
+ public string[] list (string dir) {
+ return engine.list (dir);
+ }
+
+ /**
+ * dconf_client_watch:
+ * @client: a #DConfClient
+ * @path: a dconf path
+ * @cancellable: a #GCancellable, or %NULL
+ * @error: a pointer to a %NULL #GError, or %NULL
+ * Returns: %TRUE on success, else %FALSE with @error set
+ *
+ * Requests monitoring of a portion of the dconf database.
+ *
+ * If @path is a key (ie: doesn't end with a slash) then a single key is monitored for changes. If
+ * @path is a dir (ie: sending with a slash) then all keys that have @path as a prefix are monitored.
+ *
+ * This function blocks until the watch has definitely been established with the bus daemon. If you
+ * would like a non-blocking version of this call, see dconf_client_watch_async().
+ **/
+ public bool watch (string path, Cancellable? cancellable = null) throws GLib.Error {
+ call_sync (engine.watch (path), null, cancellable);
+ return true;
+ }
+
+ /**
+ * dconf_client_watch_async:
+ * @client: a #DConfClient
+ * @path: a dconf path
+ * @cancellable: a #GCancellable, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when finished
+ * @user_data: a pointer to pass as the last argument to @callback
+ *
+ * Requests monitoring of a portion of the dconf database.
+ *
+ * This is the asynchronous version of dconf_client_watch(). You should call
+ * dconf_client_watch_finish() from @callback to collect the result.
+ **/
+ public async bool watch_async (string name, Cancellable? cancellable = null) throws GLib.Error {
+ yield call_async (engine.watch (name), null, cancellable);
+ return true;
+ }
+
+ /**
+ * dconf_client_unwatch:
+ * @client: a #DConfClient
+ * @path: a dconf path
+ * @cancellable: a #GCancellable, or %NULL
+ * @error: a pointer to a %NULL #GError, or %NULL
+ * Returns: %TRUE on success, else %FALSE with @error set
+ *
+ * Cancels the effect of a previous call to dconf_client_watch().
+ *
+ * If the same path has been watched multiple times then only one of the watches is cancelled and the
+ * net effect is that the path is still watched.
+ *
+ * This function blocks until the watch has definitely been removed from the bus daemon. It is possible
+ * that notifications in transit will arrive after this call returns. For an asynchronous version of
+ * this call, see dconf_client_unwatch_async().
+ **/
+ public bool unwatch (string name, Cancellable? cancellable = null) throws GLib.Error {
+ call_sync (engine.unwatch (name), null, cancellable);
+ return true;
+ }
+
+ /**
+ * dconf_client_unwatch_async:
+ * @client: a #DConfClient
+ * @path: a dconf path
+ * @cancellable: a #GCancellable, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when finished
+ * @user_data: a pointer to pass as the last argument to @callback
+ *
+ * Cancels the effect of a previous call to dconf_client_watch().
+ *
+ * This is the asynchronous version of dconf_client_unwatch(). You should call
+ * dconf_client_unwatch_finish() from @callback to collect the result. No additional notifications will
+ * be delivered for this watch after @callback is called.
+ **/
+ public async bool unwatch_async (string name, Cancellable? cancellable = null) throws GLib.Error {
+ yield call_async (engine.unwatch (name), null, cancellable);
+ return true;
+ }
+
+ static Variant? service_func (EngineMessage dcem) {
+ try {
+ assert (dcem.bus_types[0] == 'e');
+ var connection = Bus.get_sync (BusType.SESSION, null);
+ return connection.call_sync (dcem.bus_name, dcem.object_path, dcem.interface_name, dcem.method_name,
+ dcem.parameters[0], dcem.reply_type, DBusCallFlags.NONE, -1, null);
+ } catch {
+ return null;
+ }
+ }
+
+ /**
+ * dconf_client_new:
+ * @profile: the dconf profile to use, or %NULL
+ * @watch_func: the function to call when changes occur
+ * @user_data: the user_data to pass to @watch_func
+ * @notify: the function to free @user_data when no longer needed
+ * Returns: a new #DConfClient
+ *
+ * Creates a new #DConfClient for the given context.
+ *
+ * If @profile is non-%NULL then it specifies the name of the profile to use. If @profile is %NULL then
+ * the DCONF_PROFILE environment variable is consulted. If that is unset then the default profile of
+ * "user" is used. If a profile named "user" is not installed then the dconf client is setup to access
+ * ~/.config/dconf/user.
+ **/
+ public Client (string? profile = null, owned WatchFunc? watch_func = null) {
+ Engine.set_service_func (service_func);
+
+ engine = new Engine (profile);
+ this.watch_func = watch_func;
+ }
+ }
+
+ public extern bool is_path (string str, Error *error = null);
+ public extern bool is_key (string str, Error *error = null);
+ public extern bool is_dir (string str, Error *error = null);
+ public extern bool is_rel_path (string str, Error *error = null);
+ public extern bool is_rel_key (string str, Error *error = null);
+ public extern bool is_rel_dir (string str, Error *error = null);
+ [CCode (cname = "dconf_is_path")]
+ public extern bool verify_path (string str) throws Error;
+ [CCode (cname = "dconf_is_key")]
+ public extern bool verify_key (string str) throws Error;
+ [CCode (cname = "dconf_is_dir")]
+ public extern bool verify_dir (string str) throws Error;
+ [CCode (cname = "dconf_is_rel_path")]
+ public extern bool verify_rel_path (string str) throws Error;
+ [CCode (cname = "dconf_is_rel_key")]
+ public extern bool verify_rel_key (string str) throws Error;
+ [CCode (cname = "dconf_is_rel_dir")]
+ public extern bool verify_rel_dir (string str) throws Error;
+}
+
+// vim:noet sw=4 ts=4
diff --git a/client/dconf.h b/client/dconf.h
new file mode 100644
index 0000000..77f1154
--- /dev/null
+++ b/client/dconf.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __dconf_h__
+#define __dconf_h__
+
+#include <dconf-paths.h>
+#include <dconf-client.h>
+
+#endif /* __dconf_h__ */
diff --git a/client/dconf.pc.in b/client/dconf.pc.in
new file mode 100644
index 0000000..652c6aa
--- /dev/null
+++ b/client/dconf.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: dconf
+Description: dconf client library
+Version: @VERSION@
+Requires: gio-2.0 >= 2.25.7
+Libs: -L${libdir} -ldconf
+Cflags: -I${includedir}/dconf
diff --git a/client/engine.vapi b/client/engine.vapi
new file mode 100644
index 0000000..8ed9c92
--- /dev/null
+++ b/client/engine.vapi
@@ -0,0 +1,36 @@
+namespace DConf {
+ [Compact]
+ [CCode (cheader_filename = "dconf-engine.h")]
+ class Engine {
+ internal Engine (string? profile);
+ internal bool is_writable (string key);
+ internal EngineMessage write (string key, GLib.Variant? value) throws GLib.Error;
+ internal EngineMessage write_many (string dir, [CCode (array_length = false, array_null_terminated = true, type = "const gchar * const *")] string[] keys, [CCode (array_length = false)] GLib.Variant?[] values) throws GLib.Error;
+ internal GLib.Variant? read (string key);
+ internal GLib.Variant? read_default (string key);
+ internal GLib.Variant? read_no_default (string key);
+ internal EngineMessage set_locked (string key, bool locked);
+ internal string[] list (string dir, void*junk = null);
+ internal static void set_service_func (ServiceFunc func);
+ internal EngineMessage watch (string name);
+ internal EngineMessage unwatch (string name);
+ }
+
+ struct EngineMessage {
+ string bus_name;
+ string object_path;
+ string interface_name;
+ string method_name;
+ int n_messages;
+ [CCode (array_length_cname = "n_messages")]
+ GLib.Variant[] parameters;
+ [CCode (array_length_cname = "n_messages")]
+ char[] bus_types;
+ GLib.VariantType reply_type;
+ }
+
+ [CCode (has_target = false)]
+ delegate GLib.Variant? ServiceFunc (EngineMessage dcem);
+}
+
+// vim:noet sw=4 ts=4
diff --git a/client/extra-docs.c b/client/extra-docs.c
new file mode 100644
index 0000000..2f12951
--- /dev/null
+++ b/client/extra-docs.c
@@ -0,0 +1,89 @@
+/* extra docs here until we can emit them with Vala */
+
+/**
+ * SECTION:client
+ * @title: DConfClient
+ * @short_description: Direct read and write access to DConf, based on GDBus
+ *
+ * This is a simple class that allows an application to directly read
+ * from and write to the dconf database. There is also a very simple
+ * mechanism for requesting and receiving notification of changes but
+ * not robust mechanism for dispatching change notifications to multiple
+ * listeners.
+ *
+ * Most applications probably don't want to access dconf directly and
+ * would be better off using something like #GSettings.
+ **/
+
+/**
+ * DConfWatchFunc:
+ * @client: the #DConfClient emitting the notification
+ * @path: the path at which the change occured
+ * @items: the items that were changed, given as relative paths
+ * @n_items: the length of @items
+ * @tag: the tag associated with the change
+ * @user_data: the user data given to dconf_client_new()
+ *
+ * This is the type of the callback given to dconf_client_new().
+ *
+ * This function is called in response to changes occuring to the dconf
+ * database that @client is associated with.
+ *
+ * @path can either be a key or a dir. If @path is a key then @items
+ * will be empty and the notification should be taken to mean that one
+ * key -- the key named by @path -- may have changed.
+ *
+ * If @path is a dir and @items is empty then it is an indication that
+ * any key under @path may have changed.
+ *
+ * Otherwise (if @items is non-empty) then the set of affected keys is
+ * the same as if the watch function had been called multiple times for
+ * each item in the array appended to @path. This includes the
+ * possibility of the resulting path being a dir.
+ **/
+
+/**
+ * DConfClient:
+ *
+ * An opaque structure type. May only be used with the following
+ * functions.
+ **/
+
+/**
+ * dconf_client_write_finish:
+ * @client: a #DConfClient
+ * @result: the #GAsyncResult passed to the #GAsyncReadyCallback
+ * @tag: (out) (allow-none): the tag from this write
+ * @error: a pointer to a #GError, or %NULL
+ *
+ * Collects the result from a prior call to dconf_client_write_async().
+ **/
+
+/**
+ * dconf_client_set_locked_finish:
+ * @client: a #DConfClient
+ * @result: the #GAsyncResult passed to the #GAsyncReadyCallback
+ * @error: a pointer to a #GError, or %NULL
+ *
+ * Collects the result from a prior call to
+ * dconf_client_set_locked_async().
+ **/
+
+/**
+ * dconf_client_watch_finish:
+ * @client: a #DConfClient
+ * @result: the #GAsyncResult passed to the #GAsyncReadyCallback
+ * @error: a pointer to a #GError, or %NULL
+ *
+ * Collects the result from a prior call to dconf_client_watch_async().
+ **/
+
+/**
+ * dconf_client_unwatch_finish:
+ * @client: a #DConfClient
+ * @result: the #GAsyncResult passed to the #GAsyncReadyCallback
+ * @error: a pointer to a #GError, or %NULL
+ *
+ * Collects the result from a prior call to
+ * dconf_client_unwatch_async().
+ **/
diff --git a/common/Makefile.am b/common/Makefile.am
new file mode 100644
index 0000000..0238cd1
--- /dev/null
+++ b/common/Makefile.am
@@ -0,0 +1,7 @@
+dconfinclude_HEADERS = \
+ dconf-paths.h
+
+EXTRA_DIST = \
+ dconf-shmdir.h \
+ dconf-paths.h \
+ dconf-paths.c
diff --git a/common/dconf-paths.c b/common/dconf-paths.c
new file mode 100644
index 0000000..1278d64
--- /dev/null
+++ b/common/dconf-paths.c
@@ -0,0 +1,240 @@
+/*
+ * Copyright © 2008-2009 Ryan Lortie
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "dconf-paths.h"
+
+/**
+ * SECTION:paths
+ * @title: DConf Paths
+ * @short_description: utility functions to validate dconf paths
+ *
+ * Various places in the dconf API speak of "paths", "keys", "dirs" and
+ * relative versions of each of these. This file contains functions to
+ * check if a given string is a valid member of each of these classes
+ * and to report errors when a string is not.
+ *
+ * See each function in this section for a precise description of what
+ * makes a string a valid member of a given class.
+ **/
+
+#define vars gchar c, l
+
+#define DCONF_ERROR 0
+#define DCONF_ERROR_PATH 0
+
+#define absolute \
+ if ((l = *string++) != '/') \
+ { \
+ g_set_error (error, DCONF_ERROR, DCONF_ERROR_PATH, \
+ "dconf %s must begin with a slash", type); \
+ return FALSE; \
+ }
+
+#define relative \
+ if (*string == '/') \
+ { \
+ g_set_error (error, DCONF_ERROR, DCONF_ERROR_PATH, \
+ "dconf %s must not begin with a slash", type); \
+ return FALSE; \
+ } \
+ l = '/'
+
+#define no_double_slash \
+ while ((c = *string++)) \
+ { \
+ if (c == '/' && l == '/') \
+ { \
+ g_set_error (error, DCONF_ERROR, DCONF_ERROR_PATH, \
+ "dconf %s must not contain two " \
+ "consecutive slashes", type); \
+ return FALSE; \
+ } \
+ l = c; \
+ } \
+
+#define path \
+ return TRUE
+
+#define key \
+ if (l == '/') \
+ { \
+ g_set_error (error, DCONF_ERROR, DCONF_ERROR_PATH, \
+ "dconf %s must not end with a slash", type); \
+ return FALSE; \
+ } \
+ return TRUE
+
+#define dir \
+ if (l != '/') \
+ { \
+ g_set_error (error, DCONF_ERROR, DCONF_ERROR_PATH, \
+ "dconf %s must end with a slash", type); \
+ return FALSE; \
+ } \
+ return TRUE
+
+
+
+/**
+ * dconf_is_path:
+ * @string: a string
+ * @error: a pointer to a #GError, or %NULL, set when %FALSE is returned
+ * Returns: %TRUE if @string is a path
+ *
+ * Checks if @string is a valid dconf path. dconf keys must start with
+ * '/' and not contain '//'.
+ *
+ * A dconf path may be either a key or a dir. See dconf_is_key() and
+ * dconf_is_dir() for examples of each.
+ **/
+gboolean
+dconf_is_path (const gchar *string,
+ GError **error)
+{
+#define type "path"
+ vars; absolute; no_double_slash; path;
+#undef type
+}
+
+/**
+ * dconf_is_key:
+ * @string: a string
+ * @error: a pointer to a #GError, or %NULL, set when %FALSE is returned
+ * Returns: %TRUE if @string is a key
+ *
+ * Checks if @string is a valid dconf key. dconf keys must start with
+ * '/', not contain '//' and not end with '/'.
+ *
+ * A dconf key is the potential location of a single value within the
+ * database.
+ *
+ * "/a", "/a/b" and "/a/b/c" are examples of keys. "", "/", "a", "a/b",
+ * "//a/b", "/a//b", and "/a/" are examples of strings that are not
+ * keys.
+ **/
+gboolean
+dconf_is_key (const gchar *string,
+ GError **error)
+{
+#define type "key"
+ vars; absolute; no_double_slash; key;
+#undef type
+}
+
+/**
+ * dconf_is_dir:
+ * @string: a string
+ * @error: a pointer to a #GError, or %NULL, set when %FALSE is returned
+ * Returns: %TRUE if @string is a dir
+ *
+ * Checks if @string is a valid dconf dir. dconf dirs must start and
+ * end with '/' and not contain '//'.
+ *
+ * A dconf dir refers to a subtree of the database that can contain
+ * other dirs or keys. If @string is a dir, then it will be a prefix of
+ * any key or dir contained within it.
+ *
+ * "/", "/a/" and "/a/b/" are examples of dirs. "", "a/", "a/b/",
+ * "//a/b/", "/a//b/" and "/a" are examples of strings that are not
+ * dirs.
+ **/
+gboolean
+dconf_is_dir (const gchar *string,
+ GError **error)
+{
+#define type "dir"
+ vars; absolute; no_double_slash; dir;
+#undef type
+}
+
+/**
+ * dconf_is_rel:
+ * @string: a string
+ * @error: a pointer to a #GError, or %NULL, set when %FALSE is returned
+ * Returns: %TRUE if @string is a relative path
+ *
+ * Checks if @string is a valid dconf relative path. A relative path is
+ * a string that, when concatenated to a dir, forms a valid dconf path.
+ * This means that a rel must not start with a '/' or contain '//'.
+ *
+ * A dconf rel may be either a relative key or a relative dir. See
+ * dconf_is_rel_key() and dconf_is_rel_dir() for examples of each.
+ **/
+gboolean
+dconf_is_rel (const gchar *string,
+ GError **error)
+{
+#define type "relative path"
+ vars; relative; no_double_slash; path;
+#undef type
+}
+
+
+/**
+ * dconf_is_rel_key:
+ * @string: a string
+ * @error: a pointer to a #GError, or %NULL, set when %FALSE is returned
+ * Returns: %TRUE if @string is a relative key
+ *
+ * Checks if @string is a valid dconf relative key. A relative key is a
+ * string that, when concatenated to a dir, forms a valid dconf key.
+ * This means that a relative key must not start or end with a '/' or
+ * contain '//'.
+ *
+ * "a", "a/b" and "a/b/c" are examples of relative keys. "", "/", "/a",
+ * "/a/b", "//a/b", "/a//b", and "a/" are examples of strings that are
+ * not relative keys.
+ **/
+gboolean
+dconf_is_rel_key (const gchar *string,
+ GError **error)
+{
+#define type "relative key"
+ vars; relative; no_double_slash; key;
+#undef type
+}
+
+/**
+ * dconf_is_rel_dir:
+ * @string: a string
+ * @error: a pointer to a #GError, or %NULL, set when %FALSE is returned
+ * Returns: %TRUE if @string is a relative dir
+ *
+ * Checks if @string is a valid dconf relative dir. A relative dir is a
+ * string that, when appended to a dir, forms a valid dconf dir. This
+ * means that a relative dir must not start with a '/' or contain '//'
+ * and must end with a '/' except in the case that it is the empty
+ * string (in which case the path specified by appending the rel to a
+ * directory is the original directory).
+ *
+ * "", "a/" and "a/b/" are examples of relative dirs. "/", "/a/",
+ * "/a/b/", "//a/b/", "a//b/" and "a" are examples of strings that are
+ * not relative dirs.
+ **/
+gboolean
+dconf_is_rel_dir (const gchar *string,
+ GError **error)
+{
+#define type "relative dir"
+ vars; relative; no_double_slash; dir;
+#undef type
+}
diff --git a/common/dconf-paths.h b/common/dconf-paths.h
new file mode 100644
index 0000000..fa4d1e9
--- /dev/null
+++ b/common/dconf-paths.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright © 2008-2009 Ryan Lortie
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __dconf_common_h__
+#define __dconf_common_h__
+
+#include <glib.h>
+
+gboolean dconf_is_path (const gchar *string,
+ GError **error);
+gboolean dconf_is_key (const gchar *string,
+ GError **error);
+gboolean dconf_is_dir (const gchar *string,
+ GError **error);
+
+gboolean dconf_is_rel (const gchar *string,
+ GError **error);
+gboolean dconf_is_rel_key (const gchar *string,
+ GError **error);
+gboolean dconf_is_rel_dir (const gchar *string,
+ GError **error);
+
+#endif /* __dconf_common_h__ */
diff --git a/common/dconf-shmdir.c b/common/dconf-shmdir.c
new file mode 100644
index 0000000..d87d6d7
--- /dev/null
+++ b/common/dconf-shmdir.c
@@ -0,0 +1,83 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "dconf-shmdir.h"
+
+#ifndef __FreeBSD__
+#include <sys/statfs.h>
+#include <sys/vfs.h>
+#endif
+
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <errno.h>
+
+#ifndef NFS_SUPER_MAGIC
+#define NFS_SUPER_MAGIC 0x6969
+#endif
+
+static gboolean
+is_local (const gchar *filename)
+{
+ struct statfs buf;
+ gint s;
+
+ do
+ s = statfs (filename, &buf);
+ while (s < 0 && errno == EINTR);
+
+ if (s < 0 && errno == ENOENT)
+ {
+ g_mkdir_with_parents (filename, 0700);
+
+ do
+ s = statfs (filename, &buf);
+ while (s < 0 && errno == EINTR);
+ }
+
+ return s == 0 && buf.f_type != NFS_SUPER_MAGIC;
+}
+
+gchar *
+dconf_shmdir_from_environment (void)
+{
+ gchar *result;
+
+ result = g_strdup (g_getenv ("DCONF_SESSION_DIR"));
+
+ if (result == NULL)
+ {
+ const gchar *cache = g_get_user_cache_dir ();
+
+ if (is_local (cache))
+ {
+ result = g_build_filename (cache, "dconf", NULL);
+
+ if (g_mkdir_with_parents (result, 0700) != 0)
+ {
+ g_free (result);
+ result = NULL;
+ }
+ }
+ }
+
+ return result;
+}
diff --git a/common/dconf-shmdir.h b/common/dconf-shmdir.h
new file mode 100644
index 0000000..3b08de6
--- /dev/null
+++ b/common/dconf-shmdir.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __dconf_shmdir_h__
+#define __dconf_shmdir_h__
+
+#include <glib.h>
+
+G_GNUC_INTERNAL
+gchar *dconf_shmdir_from_environment (void);
+
+#endif /* __dconf_shmdir_h__ */
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..27c6777
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,67 @@
+AC_INIT([dconf],
+ [0.7.1],
+ [https://bugzilla.gnome.org/enter_bug.cgi?product=dconf],
+ [dconf])
+
+AC_CONFIG_SRCDIR([configure.ac])
+AC_CONFIG_MACRO_DIR([m4])
+AC_CONFIG_AUX_DIR([aux])
+AC_CONFIG_HEADERS([config.h])
+
+AM_INIT_AUTOMAKE([1.11 -Wno-portability])
+AM_SILENT_RULES([yes])
+
+# Check for programs
+AC_PROG_CC
+AM_PROG_VALAC([0.11.4])
+
+# Gtk-doc support
+GTK_DOC_CHECK([1.15])
+
+# Dependencies
+PKG_CHECK_MODULES(glib, glib-2.0 >= 2.25.16)
+PKG_CHECK_MODULES(gio, gio-2.0)
+PKG_CHECK_MODULES(dbus, dbus-1)
+
+AC_ARG_ENABLE(editor,
+ AC_HELP_STRING([--disable-editor],
+ [Disable the dconf editor]))
+AM_CONDITIONAL(ENABLE_EDITOR, test "x$enable_editor" != "xno")
+
+if test "x$enable_editor" != "xno"; then
+ PKG_CHECK_MODULES(gtk, gtk+-3.0)
+ PKG_CHECK_MODULES(gmodule, gmodule-2.0)
+ PKG_CHECK_MODULES(libxml, libxml-2.0)
+fi
+
+AC_ARG_WITH(gio_modules_dir, [ --with-gio-modules-dir=PATH choose directory for the GIO module, [default=LIBDIR/gio/modules]], giomodulesdir="$withval", giomodulesdir=${libdir}/gio/modules)
+AC_SUBST(giomodulesdir)
+
+AC_ARG_WITH(dbus_service_dir, [ --with-dbus-service-dir=PATH choose directory for dbus service files, [default=PREFIX/share/dbus-1/services]], dbusservicedir="$withval", dbusservicedir=${datadir}/dbus-1/services)
+AC_SUBST(dbusservicedir)
+
+AC_ARG_WITH(dbus_system_service_dir, [ --with-dbus-system-service-dir=PATH choose directory for dbus system service files, [default=PREFIX/share/dbus-1/system-services]], dbussystemservicedir="$withval", dbussystemservicedir=${datadir}/dbus-1/system-services)
+AC_SUBST(dbussystemservicedir)
+
+AC_SUBST(dconfincludedir, ${includedir}/dconf)
+
+AC_PATH_PROG(gio_QUERYMODULES, gio-querymodules, no)
+
+AC_CONFIG_FILES([
+ common/Makefile
+ gvdb/Makefile
+ engine/Makefile
+ gsettings/Makefile
+ dbus-1/dconf-dbus-1.pc
+ client/dconf.pc
+ client/Makefile
+ service/Makefile
+ dbus-1/Makefile
+ bin/Makefile
+ editor/Makefile
+ editor/dconf-editor.desktop
+ tests/Makefile
+ docs/Makefile
+ Makefile
+])
+AC_OUTPUT
diff --git a/dbus-1/.gitignore b/dbus-1/.gitignore
new file mode 100644
index 0000000..32072d0
--- /dev/null
+++ b/dbus-1/.gitignore
@@ -0,0 +1,4 @@
+libdconf-dbus-1.so
+libdconf-dbus-1.so.0
+libdconf-dbus-1.so.0.0.0
+dconf-dbus-1.pc
diff --git a/dbus-1/Makefile.am b/dbus-1/Makefile.am
new file mode 100644
index 0000000..6e740f1
--- /dev/null
+++ b/dbus-1/Makefile.am
@@ -0,0 +1,34 @@
+AM_CFLAGS = -std=c89 -Wall -Wmissing-prototypes -Wwrite-strings -fPIC -DPIC
+INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/gvdb -I$(top_srcdir)/engine $(dbus_CFLAGS) $(glib_CFLAGS)
+
+dconf_dbus_1includedir = $(includedir)/dconf-dbus-1
+dconf_dbus_1include_HEADERS = dconf-dbus-1.h
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = dconf-dbus-1.pc
+
+shlibdir = $(libdir)
+shlib_PROGRAMS = libdconf-dbus-1.so.0.0.0
+
+libdconf_dbus_1_so_0_0_0_LDADD = $(glib_LIBS) $(dbus_LIBS)
+libdconf_dbus_1_so_0_0_0_LDFLAGS = -shared -Wl,-soname=libdconf-dbus-1.so.0
+libdconf_dbus_1_so_0_0_0_SOURCES = \
+ ../engine/dconf-engine.c \
+ ../common/dconf-shmdir.c \
+ ../gvdb/gvdb-reader.c \
+ dconf-dbus-1.c
+
+noinst_DATA = libdconf-dbus-1.so libdconf-dbus-1.so.0
+
+libdconf-dbus-1.so.0 libdconf-dbus-1.so: libdconf-dbus-1.so.0.0.0
+ $(AM_V_GEN) ln -fs libdconf-dbus-1.so.0.0.0 $@
+
+install-data-hook:
+ ln -fs libdconf-dbus-1.so.0.0.0 $(DESTDIR)$(shlibdir)/libdconf-dbus-1.so.0
+ ln -fs libdconf-dbus-1.so.0.0.0 $(DESTDIR)$(shlibdir)/libdconf-dbus-1.so
+
+uninstall-hook:
+ rm -f $(DESTDIR)$(shlibdir)/libdconf-dbus-1.so.0
+ rm -f $(DESTDIR)$(shlibdir)/libdconf-dbus-1.so
+
+CLEANFILES = libdconf-dbus-1.so.0 libdconf-dbus-1.so
diff --git a/dbus-1/dconf-dbus-1.c b/dbus-1/dconf-dbus-1.c
new file mode 100644
index 0000000..fefdbab
--- /dev/null
+++ b/dbus-1/dconf-dbus-1.c
@@ -0,0 +1,728 @@
+/**
+ * Copyright © 2010 Canonical Limited
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the licence, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ **/
+
+#include "dconf-dbus-1.h"
+
+#include <dconf-engine.h>
+
+#include <string.h>
+
+typedef struct _Outstanding Outstanding;
+
+struct _DConfDBusClient
+{
+ DBusConnection *session_bus;
+ DBusConnection *system_bus;
+ GSList *watches;
+ gint ref_count;
+
+ Outstanding *outstanding;
+ gchar *anti_expose_tag;
+
+ DConfEngine *engine;
+};
+
+static void
+dconf_dbus_client_add_value_to_iter (DBusMessageIter *iter,
+ GVariant *value)
+{
+ GVariantClass class;
+
+ class = g_variant_classify (value);
+
+ switch (class)
+ {
+ case G_VARIANT_CLASS_BOOLEAN:
+ {
+ dbus_bool_t boolean;
+
+ boolean = g_variant_get_boolean (value);
+ dbus_message_iter_append_basic (iter, 'b', &boolean);
+ }
+ break;
+
+ case G_VARIANT_CLASS_BYTE:
+ case G_VARIANT_CLASS_INT16:
+ case G_VARIANT_CLASS_UINT16:
+ case G_VARIANT_CLASS_INT32:
+ case G_VARIANT_CLASS_UINT32:
+ case G_VARIANT_CLASS_INT64:
+ case G_VARIANT_CLASS_UINT64:
+ case G_VARIANT_CLASS_DOUBLE:
+ dbus_message_iter_append_basic (iter, class,
+ g_variant_get_data (value));
+ break;
+
+ case G_VARIANT_CLASS_STRING:
+ case G_VARIANT_CLASS_OBJECT_PATH:
+ case G_VARIANT_CLASS_SIGNATURE:
+ {
+ const gchar *str;
+
+ str = g_variant_get_string (value, NULL);
+ dbus_message_iter_append_basic (iter, class, &str);
+ }
+ break;
+
+ case G_VARIANT_CLASS_ARRAY:
+ {
+ const gchar *contained;
+ DBusMessageIter sub;
+ gint i, n;
+
+ contained = g_variant_get_type_string (value) + 1;
+ n = g_variant_n_children (value);
+ dbus_message_iter_open_container (iter, 'a', contained, &sub);
+ for (i = 0; i < n; i++)
+ {
+ GVariant *child;
+
+ child = g_variant_get_child_value (value, i);
+ dconf_dbus_client_add_value_to_iter (&sub, child);
+ g_variant_unref (child);
+ }
+
+ dbus_message_iter_close_container (iter, &sub);
+ }
+ break;
+
+ case G_VARIANT_CLASS_TUPLE:
+ {
+ DBusMessageIter sub;
+ gint i, n;
+
+ n = g_variant_n_children (value);
+ dbus_message_iter_open_container (iter, 'r', NULL, &sub);
+ for (i = 0; i < n; i++)
+ {
+ GVariant *child;
+
+ child = g_variant_get_child_value (value, i);
+ dconf_dbus_client_add_value_to_iter (&sub, child);
+ g_variant_unref (child);
+ }
+
+ dbus_message_iter_close_container (iter, &sub);
+ }
+ break;
+
+ case G_VARIANT_CLASS_DICT_ENTRY:
+ {
+ DBusMessageIter sub;
+ gint i;
+
+ dbus_message_iter_open_container (iter, 'e', NULL, &sub);
+ for (i = 0; i < 2; i++)
+ {
+ GVariant *child;
+
+ child = g_variant_get_child_value (value, i);
+ dconf_dbus_client_add_value_to_iter (&sub, child);
+ g_variant_unref (child);
+ }
+
+ dbus_message_iter_close_container (iter, &sub);
+ }
+ break;
+
+ case G_VARIANT_CLASS_VARIANT:
+ {
+ DBusMessageIter sub;
+ GVariant *child;
+
+ child = g_variant_get_variant (value);
+ dbus_message_iter_open_container (iter, 'v',
+ g_variant_get_type_string (child),
+ &sub);
+ dconf_dbus_client_add_value_to_iter (&sub, child);
+ dbus_message_iter_close_container (iter, &sub);
+ g_variant_unref (child);
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+dconf_dbus_client_send (DConfDBusClient *dcdbc,
+ DConfEngineMessage *dcem,
+ DBusPendingCallNotifyFunction callback,
+ gpointer user_data)
+{
+ DBusConnection *connection;
+ gint i;
+
+ for (i = 0; i < dcem->n_messages; i++)
+ {
+ switch (dcem->bus_types[i])
+ {
+ case 'e':
+ connection = dcdbc->session_bus;
+ break;
+
+ case 'y':
+ connection = dcdbc->system_bus;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (connection == NULL && callback != NULL)
+ callback (NULL, user_data);
+
+ if (connection != NULL)
+ {
+ DBusPendingCall *pending;
+ DBusMessageIter diter;
+ DBusMessage *message;
+ GVariantIter giter;
+ GVariant *child;
+
+ message = dbus_message_new_method_call (dcem->bus_name,
+ dcem->object_path,
+ dcem->interface_name,
+ dcem->method_name);
+
+ dbus_message_iter_init_append (message, &diter);
+ g_variant_iter_init (&giter, dcem->parameters[i]);
+
+ while ((child = g_variant_iter_next_value (&giter)))
+ {
+ dconf_dbus_client_add_value_to_iter (&diter, child);
+ g_variant_unref (child);
+ }
+
+ dbus_connection_send_with_reply (connection, message,
+ &pending, 120000);
+ dbus_pending_call_set_notify (pending, callback, user_data, NULL);
+ dbus_message_unref (message);
+ }
+ }
+}
+
+static GVariant *
+dconf_dbus_client_send_finish (DBusPendingCall *pending)
+{
+ DBusMessage *message;
+ GVariant *result;
+
+ if (pending == NULL)
+ return NULL;
+
+ message = dbus_pending_call_steal_reply (pending);
+ dbus_pending_call_unref (pending);
+
+ /* We only have to deal with two types of replies: () and (s) */
+ if (dbus_message_has_signature (message, "s"))
+ {
+ dbus_message_get_args (message, NULL,
+ DBUS_TYPE_STRING, &result,
+ DBUS_TYPE_INVALID);
+ result = g_variant_new ("(s)", result);
+ }
+ else
+ result = g_variant_new ("()");
+
+ dbus_message_unref (message);
+
+ return result;
+}
+
+struct _Outstanding
+{
+ Outstanding *next;
+
+ DConfDBusClient *dcdbc;
+ DConfEngineMessage dcem;
+
+ gchar *set_key;
+ GVariant *set_value;
+ GTree *tree;
+};
+
+static void
+dconf_dbus_client_outstanding_returned (DBusPendingCall *pending,
+ gpointer user_data)
+{
+ Outstanding *outstanding = user_data;
+ DConfDBusClient *dcdbc;
+ GVariant *reply;
+
+ dcdbc = outstanding->dcdbc;
+
+ /* One way or another we no longer need this hooked into the list.
+ */
+ {
+ Outstanding **tmp;
+
+ for (tmp = &dcdbc->outstanding; tmp; tmp = &(*tmp)->next)
+ if (*tmp == outstanding)
+ {
+ *tmp = outstanding->next;
+ break;
+ }
+ }
+
+ reply = dconf_dbus_client_send_finish (pending);
+
+ if (reply)
+ {
+ /* Success.
+ *
+ * We want to ensure that we don't emit an extra change
+ * notification signal when we see the signal that the service is
+ * about to send, so store the tag so we know to ignore it when
+ * the signal comes.
+ *
+ * No thread safety issue here since this variable is only
+ * accessed from the worker thread.
+ */
+ g_free (dcdbc->anti_expose_tag);
+
+ if (g_variant_is_of_type (reply, G_VARIANT_TYPE ("(s)")))
+ g_variant_get_child (reply, 0, "s", dcdbc->anti_expose_tag);
+
+ g_variant_unref (reply);
+ }
+ else
+ {
+ /* An error of some kind.
+ *
+ * We already removed the outstanding entry from the list, so the
+ * unmodified database is now visible to the client. Change
+ * notify so that they see it.
+ */
+ if (outstanding->set_key)
+ /* XXX emit */;
+ else
+ /* XXX emit */;
+ }
+
+ dconf_engine_message_destroy (&outstanding->dcem);
+ dconf_dbus_client_unref (outstanding->dcdbc);
+ g_free (outstanding->set_key);
+
+ if (outstanding->set_value)
+ g_variant_unref (outstanding->set_value);
+
+ if (outstanding->tree)
+ g_tree_unref (outstanding->tree);
+
+ g_slice_free (Outstanding, outstanding);
+}
+
+static void
+dconf_dbus_client_queue (DConfDBusClient *dcdbc,
+ DConfEngineMessage *dcem,
+ const gchar *set_key,
+ GVariant *set_value,
+ GTree *tree)
+{
+ Outstanding *outstanding;
+
+ outstanding = g_slice_new (Outstanding);
+ outstanding->dcdbc = dconf_dbus_client_ref (dcdbc);
+ outstanding->dcem = *dcem;
+
+ outstanding->set_key = g_strdup (set_key);
+ outstanding->set_value = set_value ? g_variant_ref_sink (set_value) : NULL;
+ outstanding->tree = tree ? g_tree_ref (tree) : NULL;
+
+ outstanding->next = dcdbc->outstanding;
+ dcdbc->outstanding = outstanding;
+
+ dconf_dbus_client_send (outstanding->dcdbc,
+ &outstanding->dcem,
+ dconf_dbus_client_outstanding_returned,
+ outstanding);
+}
+
+static gboolean
+dconf_dbus_client_scan_outstanding_tree (GTree *tree,
+ const gchar *key,
+ gsize key_length,
+ gpointer *value)
+{
+ gchar *mykey;
+
+ mykey = g_alloca (key_length + 1);
+ memcpy (mykey, key, key_length + 1);
+
+ while (!g_tree_lookup_extended (tree, mykey, NULL, value) &&
+ --key_length)
+ {
+ while (mykey[key_length - 1] != '/')
+ key_length--;
+
+ mykey[key_length] = '\0';
+ }
+
+ return key_length != 0;
+}
+
+static gboolean
+dconf_dbus_client_scan_outstanding (DConfDBusClient *dcdbc,
+ const gchar *key,
+ GVariant **value)
+{
+ gboolean found = FALSE;
+ Outstanding *node;
+ gsize length;
+
+ length = strlen (key);
+
+ if G_LIKELY (dcdbc->outstanding == NULL)
+ return FALSE;
+
+ for (node = dcdbc->outstanding; node; node = node->next)
+ {
+ if (node->set_key)
+ {
+ if (strcmp (key, node->set_key) == 0)
+ {
+ if (node->set_value != NULL)
+ *value = g_variant_ref (node->set_value);
+ else
+ *value = NULL;
+
+ found = TRUE;
+ break;
+ }
+ }
+
+ else
+ {
+ gpointer result;
+
+ if (dconf_dbus_client_scan_outstanding_tree (node->tree, key,
+ length, &result))
+ {
+ if (result)
+ *value = g_variant_ref (result);
+ else
+ *value = NULL;
+
+ found = TRUE;
+ break;
+ }
+ }
+ }
+
+ return found;
+}
+
+/* Watches are reference counted because they can be held both by the
+ * list of watches and by the pending watch registration. In the normal
+ * case, the registration completes before the watch is unsubscribed
+ * from but it might be the case that the watch is unsubscribed from
+ * before the AddMatch completes. For that reason, either thing could
+ * be responsible for freeing the watch structure; we solve that
+ * ambiguity using a reference count.
+ *
+ * We just initially set it to 2, since these are the only two users.
+ * That way we can skip having the ref() function.
+ */
+typedef struct
+{
+ DConfDBusClient *dcdbc;
+ gchar *name;
+ DConfDBusNotify notify;
+ gpointer user_data;
+ guint64 initial_state;
+ gint ref_count;
+} Watch;
+
+
+
+static void
+dconf_dbus_emit_change (DConfDBusClient *dcdbc,
+ const gchar *key)
+{
+ GSList *iter;
+
+ for (iter = dcdbc->watches; iter; iter = iter->next)
+ {
+ Watch *watch = iter->data;
+
+ if (g_str_has_prefix (key, watch->name))
+ watch->notify (dcdbc, key, watch->user_data);
+ }
+}
+
+GVariant *
+dconf_dbus_client_read (DConfDBusClient *dcdbc,
+ const gchar *key)
+{
+ GVariant *value;
+
+ if (dconf_dbus_client_scan_outstanding (dcdbc, key, &value))
+ return value;
+
+ return dconf_engine_read (dcdbc->engine, key);
+}
+
+gboolean
+dconf_dbus_client_write (DConfDBusClient *dcdbc,
+ const gchar *key,
+ GVariant *value)
+{
+ DConfEngineMessage dcem;
+
+ if (!dconf_engine_write (dcdbc->engine, key, value, &dcem, NULL))
+ return FALSE;
+
+ dconf_dbus_client_queue (dcdbc, &dcem, key, value, NULL);
+ dconf_dbus_emit_change (dcdbc, key);
+
+ return TRUE;
+}
+
+static Watch *
+watch_new (DConfDBusClient *dcdbc,
+ const gchar *name,
+ DConfDBusNotify notify,
+ gpointer user_data)
+{
+ Watch *watch;
+
+ watch = g_slice_new (Watch);
+ watch->dcdbc = dconf_dbus_client_ref (dcdbc);
+ watch->user_data = user_data;
+ watch->name = g_strdup (name);
+ watch->notify = notify;
+
+ watch->initial_state = dconf_engine_get_state (dcdbc->engine);
+ watch->ref_count = 2;
+
+ dcdbc->watches = g_slist_prepend (dcdbc->watches, watch);
+
+ return watch;
+}
+
+static void
+watch_unref (Watch *watch)
+{
+ if (--watch->ref_count == 0)
+ {
+ dconf_dbus_client_unref (watch->dcdbc);
+ g_free (watch->name);
+ g_slice_free (Watch, watch);
+ }
+}
+
+static void
+add_match_done (DBusPendingCall *pending,
+ gpointer user_data)
+{
+ Watch *watch = user_data;
+ GError *error = NULL;
+ GVariant *reply;
+
+ reply = dconf_dbus_client_send_finish (pending);
+
+ if (reply == NULL)
+ {
+ /* This is extremely unlikely to happen and it happens
+ * asynchronous to the user's call. Since the user doesn't know
+ * that it happened, we pretend that it didn't (ie: we leave the
+ * watch structure in the list).
+ */
+
+ g_critical ("DBus AddMatch for dconf path '%s': %s",
+ watch->name, error->message);
+ g_error_free (error);
+ watch_unref (watch);
+
+ return;
+ }
+
+ else
+ g_variant_unref (reply); /* it is just an empty tuple */
+
+ /* In the normal case we're done.
+ *
+ * There is a fleeting chance, however, that the database has changed
+ * in the meantime. In that case we can only assume that the subject
+ * of this watch changed in that time period and emit a signal to that
+ * effect.
+ */
+ if (dconf_engine_get_state (watch->dcdbc->engine) != watch->initial_state)
+ watch->notify (watch->dcdbc, watch->name, watch->user_data);
+
+ watch_unref (watch);
+}
+
+void
+dconf_dbus_client_subscribe (DConfDBusClient *dcdbc,
+ const gchar *name,
+ DConfDBusNotify notify,
+ gpointer user_data)
+{
+ DConfEngineMessage dcem;
+ Watch *watch;
+
+ watch = watch_new (dcdbc, name, notify, user_data);
+ dconf_engine_watch (dcdbc->engine, name, &dcem);
+ dconf_dbus_client_send (dcdbc, &dcem, add_match_done, watch);
+ dconf_engine_message_destroy (&dcem);
+}
+
+void
+dconf_dbus_client_unsubscribe (DConfDBusClient *dcdbc,
+ DConfDBusNotify notify,
+ gpointer user_data)
+{
+ DConfEngineMessage dcem;
+ GSList **ptr;
+
+ for (ptr = &dcdbc->watches; *ptr; ptr = &(*ptr)->next)
+ {
+ Watch *watch = (*ptr)->data;
+
+ if (watch->notify == notify && watch->user_data == user_data)
+ {
+ *ptr = g_slist_remove (*ptr, *ptr);
+
+ dconf_engine_unwatch (dcdbc->engine, watch->name, &dcem);
+ dconf_dbus_client_send (dcdbc, &dcem, NULL, NULL);
+ dconf_engine_message_destroy (&dcem);
+ watch_unref (watch);
+
+ return;
+ }
+ }
+
+ g_warning ("No matching watch found to unsubscribe");
+}
+
+gboolean
+dconf_dbus_client_has_pending (DConfDBusClient *dcdbc)
+{
+ return dcdbc->outstanding != NULL;
+}
+
+static GVariant *
+dconf_dbus_client_service_func (DConfEngineMessage *dcem)
+{
+ g_assert_not_reached ();
+}
+
+static DBusHandlerResult
+dconf_dbus_client_filter (DBusConnection *connection,
+ DBusMessage *message,
+ void *user_data)
+{
+ DConfDBusClient *dcdbc = user_data;
+
+ if (dbus_message_is_signal (message, "ca.desrt.dconf.Writer", "Notify") &&
+ dbus_message_has_signature (message, "sass"))
+ {
+ DBusMessageIter iter, sub;
+ const gchar *path, *tag;
+
+ dbus_message_iter_init (message, &iter);
+ dbus_message_iter_get_basic (&iter, &path);
+ dbus_message_iter_next (&iter);
+ dbus_message_iter_recurse (&iter, &sub);
+ dbus_message_iter_next (&iter);
+ dbus_message_iter_get_basic (&iter, &tag);
+ dbus_message_iter_next (&iter);
+
+ /* Only emit the event if it hasn't been anti-exposed */
+ if (dcdbc->anti_expose_tag == NULL ||
+ strcmp (tag, dcdbc->anti_expose_tag) != 0)
+ {
+ /* Empty list means that only one key changed */
+ if (!dbus_message_iter_get_arg_type (&sub))
+ dconf_dbus_emit_change (dcdbc, path);
+
+ while (dbus_message_iter_get_arg_type (&sub) == 's')
+ {
+ const gchar *item;
+ gchar *full;
+
+ dbus_message_iter_get_basic (&iter, &item);
+ full = g_strconcat (path, item, NULL);
+ dconf_dbus_emit_change (dcdbc, full);
+ g_free (full);
+
+ dbus_message_iter_next (&sub);
+ }
+ }
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+DConfDBusClient *
+dconf_dbus_client_new (const gchar *profile,
+ DBusConnection *session,
+ DBusConnection *system)
+{
+ DConfDBusClient *dcdbc;
+
+ if (session == NULL)
+ session = dbus_bus_get (DBUS_BUS_SESSION, NULL);
+
+ if (system == NULL)
+ system = dbus_bus_get (DBUS_BUS_SYSTEM, NULL);
+
+ dconf_engine_set_service_func (dconf_dbus_client_service_func);
+
+ dcdbc = g_slice_new (DConfDBusClient);
+ dcdbc->engine = dconf_engine_new (profile);
+ dcdbc->system_bus = dbus_connection_ref (system);
+ dcdbc->session_bus = dbus_connection_ref (session);
+ dcdbc->anti_expose_tag = NULL;
+ dcdbc->outstanding = NULL;
+ dcdbc->watches = NULL;
+ dcdbc->ref_count = 1;
+
+ dbus_connection_add_filter (system, dconf_dbus_client_filter, dcdbc, NULL);
+ dbus_connection_add_filter (session, dconf_dbus_client_filter, dcdbc, NULL);
+
+ return dcdbc;
+}
+
+void
+dconf_dbus_client_unref (DConfDBusClient *dcdbc)
+{
+ if (--dcdbc->ref_count == 0)
+ {
+ dbus_connection_remove_filter (dcdbc->session_bus,
+ dconf_dbus_client_filter, dcdbc);
+ dbus_connection_remove_filter (dcdbc->system_bus,
+ dconf_dbus_client_filter, dcdbc);
+ dbus_connection_unref (dcdbc->session_bus);
+ dbus_connection_unref (dcdbc->system_bus);
+
+ g_slice_free (DConfDBusClient, dcdbc);
+ }
+}
+
+DConfDBusClient *
+dconf_dbus_client_ref (DConfDBusClient *dcdbc)
+{
+ dcdbc->ref_count++;
+
+ return dcdbc;
+}
diff --git a/dbus-1/dconf-dbus-1.h b/dbus-1/dconf-dbus-1.h
new file mode 100644
index 0000000..bd204be
--- /dev/null
+++ b/dbus-1/dconf-dbus-1.h
@@ -0,0 +1,56 @@
+/**
+ * Copyright © 2010 Canonical Limited
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the licence, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ **/
+
+#ifndef _dconf_dbus_1_h_
+#define _dconf_dbus_1_h_
+
+#include <dbus/dbus.h>
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _DConfDBusClient DConfDBusClient;
+
+typedef void (* DConfDBusNotify) (DConfDBusClient *dcdbc,
+ const gchar *key,
+ gpointer user_data);
+
+DConfDBusClient * dconf_dbus_client_new (const gchar *profile,
+ DBusConnection *session,
+ DBusConnection *system);
+void dconf_dbus_client_unref (DConfDBusClient *dcdbc);
+DConfDBusClient * dconf_dbus_client_ref (DConfDBusClient *dcdbc);
+
+GVariant * dconf_dbus_client_read (DConfDBusClient *dcdbc,
+ const gchar *key);
+gboolean dconf_dbus_client_write (DConfDBusClient *dcdbc,
+ const gchar *key,
+ GVariant *value);
+void dconf_dbus_client_subscribe (DConfDBusClient *dcdbc,
+ const gchar *name,
+ DConfDBusNotify notify,
+ gpointer user_data);
+void dconf_dbus_client_unsubscribe (DConfDBusClient *dcdbc,
+ DConfDBusNotify notify,
+ gpointer user_data);
+gboolean dconf_dbus_client_has_pending (DConfDBusClient *dcdbc);
+
+G_END_DECLS
+
+#endif /* _dconf_dbus_1_h_ */
diff --git a/dbus-1/dconf-dbus-1.pc.in b/dbus-1/dconf-dbus-1.pc.in
new file mode 100644
index 0000000..c4c3380
--- /dev/null
+++ b/dbus-1/dconf-dbus-1.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: dconf-dbus-1
+Description: dconf client library for libdbus-1
+Version: @VERSION@
+Requires: dbus-1, glib-2.0
+Libs: -L${libdir} -ldconf-dbus-1
+Cflags: -I${includedir}/dconf-dbus-1
diff --git a/dconf.doap b/dconf.doap
new file mode 100644
index 0000000..69a2178
--- /dev/null
+++ b/dconf.doap
@@ -0,0 +1,19 @@
+<?xml version='1.0' encoding='utf-8'?>
+
+<Project xmlns='http://usefulinc.com/ns/doap#'
+ xmlns:foaf='http://xmlns.com/foaf/0.1/'
+ xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
+ xmlns:gnome='http://api.gnome.org/doap-extensions#'>
+
+ <name xml:lang='en'>dconf</name>
+ <shortdesc xml:lang='en'>configuation database system</shortdesc>
+
+ <maintainer>
+ <foaf:Person>
+ <foaf:name>Ryan Lortie</foaf:name>
+ <foaf:mbox rdf:resource='mailto:desrt@desrt.ca'/>
+ <gnome:userid>ryanl</gnome:userid>
+ </foaf:Person>
+ </maintainer>
+
+</Project>
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 0000000..c5b7af4
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,17 @@
+*-decl-list.txt
+*-decl.txt
+*-overrides.txt
+*-unused.txt
+*-undocumented.txt
+*-undeclared.txt
+*.args
+*.hierarchy
+*.interfaces
+*.prerequisites
+*.signals
+*.stamp
+html
+xml
+*.bak
+version.xml
+gtk-doc.make
diff --git a/docs/Makefile.am b/docs/Makefile.am
new file mode 100644
index 0000000..4e11f65
--- /dev/null
+++ b/docs/Makefile.am
@@ -0,0 +1,24 @@
+include gtk-doc.make
+
+DOC_MODULE = dconf
+
+DOC_MAIN_SGML_FILE = dconf-docs.xml
+
+DOC_SOURCE_DIR = ../client ../common
+
+MKDB_OPTIONS = --output-format=xml
+
+INCLUDES = $(gio_CFLAGS)
+GTKDOC_LIBS = $(gio_LIBS) -L../client -ldconf -Wl,-rpath=../client
+
+IGNORE_HFILES = \
+ dconf-engine.h \
+ dconf-shmdir.h \
+ dconf-resetlist.h \
+ dconf-readtype.h \
+ dconf-rebuilder.h \
+ gvdb-builder.h \
+ gvdb-reader.h \
+ gvdb-format.h
+
+
diff --git a/docs/dconf-docs.xml b/docs/dconf-docs.xml
new file mode 100644
index 0000000..31df0b3
--- /dev/null
+++ b/docs/dconf-docs.xml
@@ -0,0 +1,29 @@
+<?xml version='1.0'?>
+
+<book id='index' xmlns:xi='http://www.w3.org/2001/XInclude'>
+ <bookinfo>
+ <title>dconf Reference Manual</title>
+ <releaseinfo>
+ The latest version of this documentation can be found on-line at
+ <ulink role='online-location' url='http://library.gnome.org/devel/dconf/unstable/'>http://library.gnome.org/devel/dconf/unstable/</ulink>.
+ </releaseinfo>
+ </bookinfo>
+
+ <chapter>
+ <title>DConf Client API</title>
+ <xi:include href='xml/paths.xml'/>
+ <xi:include href='xml/client.xml'/>
+ </chapter>
+
+ <chapter id='object-tree'>
+ <title>Object Hierarchy</title>
+ <xi:include href='xml/tree_index.sgml'/>
+ </chapter>
+
+ <index id='api-index-full'>
+ <title>API Index</title>
+ <xi:include href='xml/api-index-full.xml'><xi:fallback /></xi:include>
+ </index>
+
+ <xi:include href='xml/annotation-glossary.xml'><xi:fallback /></xi:include>
+</book>
diff --git a/docs/dconf-sections.txt b/docs/dconf-sections.txt
new file mode 100644
index 0000000..475522f
--- /dev/null
+++ b/docs/dconf-sections.txt
@@ -0,0 +1,40 @@
+<SECTION>
+<FILE>client</FILE>
+DConfClient
+DConfWatchFunc
+dconf_client_new
+dconf_client_read
+dconf_client_read_default
+dconf_client_read_no_default
+dconf_client_list
+dconf_client_is_writable
+dconf_client_write
+dconf_client_write_async
+dconf_client_write_finish
+dconf_client_set_locked
+dconf_client_set_locked_async
+dconf_client_set_locked_finish
+dconf_client_write_many
+dconf_client_watch
+dconf_client_watch_async
+dconf_client_watch_finish
+dconf_client_unwatch
+dconf_client_unwatch_async
+dconf_client_unwatch_finish
+<SUBSECTION Standard>
+DConfClientClass
+DCONF_CLIENT
+DCONF_IS_CLIENT
+DCONF_TYPE_CLIENT
+dconf_client_get_type
+</SECTION>
+
+<SECTION>
+<FILE>paths</FILE>
+dconf_is_dir
+dconf_is_key
+dconf_is_path
+dconf_is_rel
+dconf_is_rel_dir
+dconf_is_rel_key
+</SECTION>
diff --git a/docs/dconf.types b/docs/dconf.types
new file mode 100644
index 0000000..36554ba
--- /dev/null
+++ b/docs/dconf.types
@@ -0,0 +1 @@
+dconf_client_get_type
diff --git a/editor/.gitignore b/editor/.gitignore
new file mode 100644
index 0000000..22bf57e
--- /dev/null
+++ b/editor/.gitignore
@@ -0,0 +1,4 @@
+*.c
+*.stamp
+dconf-editor
+dconf-editor.desktop
diff --git a/editor/Makefile.am b/editor/Makefile.am
new file mode 100644
index 0000000..df2ad1a
--- /dev/null
+++ b/editor/Makefile.am
@@ -0,0 +1,15 @@
+bin_PROGRAMS = dconf-editor
+
+AM_CFLAGS = $(gtk_CFLAGS) $(gmodule_CFLAGS) $(libxml_CFLAGS) -I$(top_srcdir)/common -I$(top_srcdir)/client -DPKGDATADIR=\"@datadir@/dconf-editor\"
+AM_VALAFLAGS = --vapidir ../client --pkg gtk+-3.0 --pkg gmodule-2.0 --pkg libxml-2.0 --pkg dconf
+CFLAGS += -Wno-error
+dconf_editor_LDADD = ../client/libdconf.so.0 $(gtk_LIBS) $(gmodule_LIBS) $(gee_LIBS) $(libxml_LIBS)
+dconf_editor_SOURCES = config.vapi dconf-editor.vala dconf-model.vala dconf-schema.vala dconf-view.vala
+
+desktopdir = $(datadir)/applications
+desktop_in_files = dconf-editor.desktop.in.in
+desktop_DATA = $(desktop_in_files:.desktop.in.in=.desktop)
+
+uidir = $(datadir)/dconf-editor
+ui_DATA = dconf-editor.ui
+EXTRA_DIST = dconf-editor.ui
diff --git a/editor/config.vapi b/editor/config.vapi
new file mode 100644
index 0000000..f697a31
--- /dev/null
+++ b/editor/config.vapi
@@ -0,0 +1,7 @@
+[CCode (cprefix = "", lower_case_cprefix = "", cheader_filename = "config.h")]
+namespace Config
+{
+ public const string PKGDATADIR;
+ public const string GETTEXT_PACKAGE;
+ public const string VERSION;
+}
diff --git a/editor/dconf-editor.desktop.in b/editor/dconf-editor.desktop.in
new file mode 100644
index 0000000..435f30a
--- /dev/null
+++ b/editor/dconf-editor.desktop.in
@@ -0,0 +1,14 @@
+[Desktop Entry]
+Name=Configuration Editor
+Comment=Directly edit your entire configuration database
+Exec=dconf-editor
+Terminal=false
+Type=Application
+Icon=dconf-editor
+StartupNotify=true
+NoDisplay=true
+Categories=GNOME;GTK;System;
+X-GNOME-Bugzilla-Bugzilla=GNOME
+X-GNOME-Bugzilla-Product=dconf
+X-GNOME-Bugzilla-Component=editor
+X-GNOME-Bugzilla-Version=@VERSION@
diff --git a/editor/dconf-editor.ui b/editor/dconf-editor.ui
new file mode 100644
index 0000000..84ea5c5
--- /dev/null
+++ b/editor/dconf-editor.ui
@@ -0,0 +1,267 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="2.24"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkAction" id="set_default_action">
+ <property name="label" translatable="yes">Set to Default</property>
+ <property name="sensitive">False</property>
+ <signal name="activate" handler="set_default_cb" swapped="no"/>
+ </object>
+ <object class="GtkWindow" id="main_window">
+ <property name="can_focus">False</property>
+ <property name="border_width">6</property>
+ <property name="title" translatable="yes">Configuration Editor</property>
+ <property name="default_width">600</property>
+ <property name="default_height">300</property>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkScrolledWindow" id="directory_scrolledwindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkTable" id="key_info_table">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="n_rows">5</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">Schema:</property>
+ </object>
+ <packing>
+ <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+ <property name="y_options">GTK_SHRINK | GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">Summary:</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+ <property name="y_options">GTK_SHRINK | GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">Description:</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+ <property name="y_options">GTK_SHRINK | GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">Type:</property>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+ <property name="y_options">GTK_SHRINK | GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">Default:</property>
+ </object>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+ <property name="y_options">GTK_SHRINK | GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="schema_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property>
+ <property name="y_options">GTK_SHRINK | GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property>
+ <property name="y_options">GTK_SHRINK | GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="description_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="selectable">True</property>
+ <property name="single_line_mode">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property>
+ <property name="y_options">GTK_SHRINK | GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="type_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property>
+ <property name="y_options">GTK_SHRINK | GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="default_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="related_action">set_default_action</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property>
+ <property name="y_options">GTK_SHRINK | GTK_FILL</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="key_scrolledwindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/editor/dconf-editor.vala b/editor/dconf-editor.vala
new file mode 100644
index 0000000..d3b1e63
--- /dev/null
+++ b/editor/dconf-editor.vala
@@ -0,0 +1,172 @@
+class ConfigurationEditor
+{
+ private SettingsModel model;
+
+ private Gtk.Builder ui;
+ private Gtk.Window window;
+ private Gtk.TreeView dir_tree_view;
+ private Gtk.TreeView key_tree_view;
+ private Gtk.Table key_info_table;
+ private Gtk.Label schema_label;
+ private Gtk.Label summary_label;
+ private Gtk.Label description_label;
+ private Gtk.Label type_label;
+ private Gtk.Label default_label;
+ private Gtk.Action set_default_action;
+
+ private Key? selected_key;
+
+ public ConfigurationEditor()
+ {
+ model = new SettingsModel();
+
+ ui = new Gtk.Builder();
+ try
+ {
+ ui.add_from_file(Path.build_filename(Config.PKGDATADIR, "dconf-editor.ui"));
+ }
+ catch (Error e)
+ {
+ critical("Failed to load UI: %s", e.message);
+ }
+ ui.connect_signals(this);
+ window = (Gtk.Window)ui.get_object("main_window");
+ window.destroy.connect(Gtk.main_quit);
+
+ dir_tree_view = new DConfDirView();
+ dir_tree_view.set_model(model);
+ dir_tree_view.get_selection().changed.connect(dir_selected_cb); // FIXME: Put in view
+ dir_tree_view.show();
+ var scroll = (Gtk.ScrolledWindow)ui.get_object("directory_scrolledwindow");
+ scroll.add(dir_tree_view);
+
+ key_tree_view = new DConfKeyView();
+ key_tree_view.show();
+ key_tree_view.get_selection().changed.connect(key_selected_cb);
+ scroll = (Gtk.ScrolledWindow)ui.get_object("key_scrolledwindow");
+ scroll.add(key_tree_view);
+
+ key_info_table = (Gtk.Table)ui.get_object("key_info_table");
+ schema_label = (Gtk.Label)ui.get_object("schema_label");
+ summary_label = (Gtk.Label)ui.get_object("summary_label");
+ description_label = (Gtk.Label)ui.get_object("description_label");
+ type_label = (Gtk.Label)ui.get_object("type_label");
+ default_label = (Gtk.Label)ui.get_object("default_label");
+ set_default_action = (Gtk.Action)ui.get_object("set_default_action");
+
+ /* Always select something */
+ Gtk.TreeIter iter;
+ if (model.get_iter_first(out iter))
+ dir_tree_view.get_selection().select_iter(iter);
+ }
+
+ public void show()
+ {
+ window.show();
+ }
+
+ private void dir_selected_cb()
+ {
+ KeyModel? key_model = null;
+
+ Gtk.TreeIter iter;
+ if (dir_tree_view.get_selection().get_selected(null, out iter))
+ key_model = model.get_directory(iter).key_model;
+
+ key_tree_view.set_model(key_model);
+
+ /* Always select something */
+ if (key_model != null && key_model.get_iter_first(out iter))
+ key_tree_view.get_selection().select_iter(iter);
+ }
+
+ private string type_to_description(string type)
+ {
+ switch(type)
+ {
+ case "i":
+ return "Integer";
+ case "b":
+ return "Boolean";
+ case "s":
+ return "String";
+ case "enum":
+ return "Enumeration";
+ default:
+ return type;
+ }
+ }
+
+ private void key_selected_cb()
+ {
+ if(selected_key != null)
+ selected_key.value_changed.disconnect(key_changed_cb);
+
+ Gtk.TreeIter iter;
+ Gtk.TreeModel model;
+ if (key_tree_view.get_selection().get_selected(out model, out iter))
+ {
+ var key_model = (KeyModel) model;
+ selected_key = key_model.get_key(iter);
+ }
+ else
+ selected_key = null;
+
+ if(selected_key != null)
+ selected_key.value_changed.connect(key_changed_cb);
+
+ key_info_table.sensitive = selected_key != null;
+ set_default_action.sensitive = selected_key != null && !selected_key.is_default;
+
+ string schema_name = "", summary = "", description = "", type = "", default_value = "";
+
+ if (selected_key != null)
+ {
+ if (selected_key.schema != null)
+ {
+ schema_name = selected_key.schema.schema.id;
+ if (selected_key.schema.summary != null)
+ summary = selected_key.schema.summary;
+ if (selected_key.schema.description != null)
+ description = selected_key.schema.description;
+ type = type_to_description(selected_key.schema.type);
+ default_value = selected_key.schema.default_value.print(false);
+ }
+ else
+ {
+ schema_name = "No schema";
+ }
+ }
+
+ schema_label.set_text(schema_name);
+ summary_label.set_text(summary);
+ description_label.set_text(description.strip());
+ type_label.set_text(type);
+ default_label.set_text(default_value);
+ }
+
+ private void key_changed_cb(Key key)
+ {
+ set_default_action.sensitive = selected_key != null && !selected_key.is_default;
+ }
+
+ [CCode (cname = "G_MODULE_EXPORT set_default_cb", instance_pos = -1)]
+ public void set_default_cb (Gtk.Action action)
+ {
+ if (selected_key == null)
+ return;
+ selected_key.set_to_default();
+ }
+
+ public static int main(string[] args)
+ {
+ Gtk.init(ref args);
+
+ var editor = new ConfigurationEditor();
+ editor.show ();
+
+ Gtk.main();
+
+ return 0;
+ }
+}
diff --git a/editor/dconf-model.vala b/editor/dconf-model.vala
new file mode 100644
index 0000000..d731fdd
--- /dev/null
+++ b/editor/dconf-model.vala
@@ -0,0 +1,646 @@
+public class Key : GLib.Object
+{
+ private SettingsModel model;
+
+ public Directory? parent;
+
+ public string name;
+ public string full_name;
+
+ public SchemaKey? schema;
+
+ public bool has_schema
+ {
+ get { return schema != null; }
+ }
+
+ public int index
+ {
+ get { return parent.keys.index (this); }
+ }
+
+ public string type_string
+ {
+ private set {}
+ public get
+ {
+ if (_value != null)
+ {
+ if (_value.is_of_type(VariantType.STRING) && has_schema && schema.enum_name != null)
+ return "<enum>";
+ else
+ return _value.get_type_string();
+ }
+ else
+ return schema.type;
+ }
+ }
+
+ private Variant? _value;
+ public Variant value
+ {
+ get
+ {
+ update_value();
+ if (_value != null)
+ return _value;
+ else
+ return schema.default_value;
+ }
+ set
+ {
+ _value = value;
+ try
+ {
+ model.client.write(full_name, value);
+ }
+ catch (GLib.Error e)
+ {
+ }
+ value_changed();
+ }
+ }
+
+ public bool is_default
+ {
+ get { update_value(); return _value == null; }
+ }
+
+ public signal void value_changed();
+
+ public Key(SettingsModel model, Directory parent, string name, string full_name)
+ {
+ this.model = model;
+ this.parent = parent;
+ this.name = name;
+ this.full_name = full_name;
+ this.schema = model.schemas.keys.lookup(full_name);
+ }
+
+ public void set_to_default()
+ {
+ if (!has_schema)
+ return;
+
+ _value = null;
+ try
+ {
+ model.client.write(full_name, null);
+ }
+ catch (GLib.Error e)
+ {
+ }
+ value_changed();
+ }
+
+ private void update_value()
+ {
+ _value = model.client.read(full_name);
+ }
+}
+
+public class Directory : GLib.Object
+{
+ private SettingsModel model;
+
+ public string name;
+ public string full_name;
+
+ public Directory? parent;
+
+ private KeyModel _key_model;
+ public KeyModel key_model
+ {
+ get { update_children(); if (_key_model == null) _key_model = new KeyModel(this); return _key_model; }
+ private set {}
+ }
+
+ public int index
+ {
+ get { return parent.children.index (this); }
+ }
+
+ public GLib.HashTable<string, Directory> _child_map = new GLib.HashTable<string, Directory>(str_hash, str_equal);
+ public GLib.List<Directory> _children = new GLib.List<Directory>();
+ public GLib.List<Directory> children
+ {
+ get { update_children(); return _children; }
+ private set { }
+ }
+
+ public GLib.HashTable<string, Key> _key_map = new GLib.HashTable<string, Key>(str_hash, str_equal);
+ private GLib.List<Key> _keys = new GLib.List<Key>();
+ public GLib.List<Key> keys
+ {
+ get { update_children(); return _keys; }
+ private set { }
+ }
+
+ private bool have_children;
+
+ public Directory(SettingsModel model, Directory? parent, string name, string full_name)
+ {
+ this.model = model;
+ this.parent = parent;
+ this.name = name;
+ this.full_name = full_name;
+ }
+
+ public Directory get_child(string name)
+ {
+ Directory? directory = _child_map.lookup(name);
+
+ if (directory == null)
+ {
+ directory = new Directory(model, this, name, full_name + name + "/");
+ _children.insert_sorted(directory, compare_directories);
+ _child_map.insert(name, directory);
+ }
+
+ return directory;
+ }
+
+ private static int compare_directories(Directory a, Directory b)
+ {
+ return strcmp(a.name, b.name);
+ }
+
+ public Key get_key(string name)
+ {
+ Key? key = _key_map.lookup(name);
+
+ if (key == null)
+ {
+ key = new Key(model, this, name, full_name + name);
+ _keys.insert_sorted(key, compare_keys);
+ _key_map.insert(name, key);
+ }
+
+ return key;
+ }
+
+ public static int compare_keys(Key a, Key b)
+ {
+ return strcmp(a.name, b.name);
+ }
+
+ public void load_schema(Schema schema, string path)
+ {
+ if (path == "")
+ {
+ foreach (var schema_key in schema.keys.get_values())
+ get_key(schema_key.name);
+ }
+ else
+ {
+ string[] tokens = path.split("/", 2);
+ string name = tokens[0];
+
+ Directory directory = get_child(name);
+ directory.load_schema(schema, tokens[1]);
+ }
+ }
+
+ private void update_children()
+ {
+ if (have_children)
+ return;
+ have_children = true;
+
+ string[] items = model.client.list(full_name);
+ for (int i = 0; i < items.length; i++)
+ {
+ string item_name = full_name + items[i];
+
+ if (DConf.is_dir(item_name))
+ {
+ string dir_name = items[i][0:-1];
+ get_child(dir_name);
+ }
+ else
+ {
+ get_key(items[i]);
+ }
+ }
+ }
+}
+
+public class KeyModel: GLib.Object, Gtk.TreeModel
+{
+ private Directory directory;
+
+ public KeyModel(Directory directory)
+ {
+ this.directory = directory;
+ foreach (var key in directory.keys)
+ key.value_changed.connect(key_changed_cb); // FIXME: Need to delete this callbacks
+ }
+
+ private void key_changed_cb(Key key)
+ {
+ Gtk.TreeIter iter;
+ if (!get_iter_first(out iter))
+ return;
+
+ do
+ {
+ if(get_key(iter) == key)
+ {
+ row_changed(get_path(iter), iter);
+ return;
+ }
+ } while(iter_next(ref iter));
+ }
+
+ public Gtk.TreeModelFlags get_flags()
+ {
+ return Gtk.TreeModelFlags.LIST_ONLY;
+ }
+
+ public int get_n_columns()
+ {
+ return 3;
+ }
+
+ public Type get_column_type(int index)
+ {
+ if (index == 0)
+ return typeof(Key);
+ else
+ return typeof(string);
+ }
+
+ private void set_iter(out Gtk.TreeIter iter, Key key)
+ {
+ iter.stamp = 0;
+ iter.user_data = key;
+ iter.user_data2 = key;
+ iter.user_data3 = key;
+ }
+
+ public Key get_key(Gtk.TreeIter iter)
+ {
+ return (Key)iter.user_data;
+ }
+
+ public bool get_iter(out Gtk.TreeIter iter, Gtk.TreePath path)
+ {
+ if (path.get_depth() != 1)
+ return false;
+
+ return iter_nth_child(out iter, null, path.get_indices()[0]);
+ }
+
+ public Gtk.TreePath get_path(Gtk.TreeIter iter)
+ {
+ var path = new Gtk.TreePath();
+ path.append_index(get_key(iter).index);
+ return path;
+ }
+
+ public void get_value(Gtk.TreeIter iter, int column, out Value value)
+ {
+ Key key = get_key(iter);
+
+ if (column == 0)
+ value = key;
+ else if (column == 1)
+ value = key.name;
+ else if (column == 2)
+ {
+ if (key.value != null)
+ value = key.value.print(false);
+ else
+ value = "";
+ }
+ else if (column == 4)
+ {
+ if (key.is_default)
+ value = Pango.Weight.NORMAL;
+ else
+ value = Pango.Weight.BOLD;
+ }
+ }
+
+ public bool iter_next(ref Gtk.TreeIter iter)
+ {
+ int index = get_key(iter).index;
+ if (index >= directory.keys.length() - 1)
+ return false;
+ set_iter(out iter, directory.keys.nth_data(index+1));
+ return true;
+ }
+
+ public bool iter_children(out Gtk.TreeIter iter, Gtk.TreeIter? parent)
+ {
+ if (parent != null || directory.keys.length() == 0)
+ return false;
+ set_iter(out iter, directory.keys.nth_data(0));
+ return true;
+ }
+
+ public bool iter_has_child(Gtk.TreeIter iter)
+ {
+ return false;
+ }
+
+ public int iter_n_children(Gtk.TreeIter? iter)
+ {
+ if (iter == null)
+ return (int)directory.keys.length();
+ else
+ return 0;
+ }
+
+ public bool iter_nth_child(out Gtk.TreeIter iter, Gtk.TreeIter? parent, int n)
+ {
+ if (parent != null)
+ return false;
+
+ if (n >= directory.keys.length())
+ return false;
+ set_iter(out iter, directory.keys.nth_data(n));
+ return true;
+ }
+
+ public bool iter_parent(out Gtk.TreeIter iter, Gtk.TreeIter child)
+ {
+ return false;
+ }
+
+ public void ref_node(Gtk.TreeIter iter)
+ {
+ get_key(iter).ref();
+ }
+
+ public void unref_node(Gtk.TreeIter iter)
+ {
+ get_key(iter).unref();
+ }
+}
+
+public class EnumModel: GLib.Object, Gtk.TreeModel
+{
+ private SchemaEnum schema_enum;
+
+ public EnumModel(SchemaEnum schema_enum)
+ {
+ this.schema_enum = schema_enum;
+ }
+
+ public Gtk.TreeModelFlags get_flags()
+ {
+ return Gtk.TreeModelFlags.LIST_ONLY;
+ }
+
+ public int get_n_columns()
+ {
+ return 2;
+ }
+
+ public Type get_column_type(int index)
+ {
+ if (index == 0)
+ return typeof(string);
+ else
+ return typeof(int);
+ }
+
+ private void set_iter(out Gtk.TreeIter iter, SchemaEnumValue value)
+ {
+ iter.stamp = 0;
+ iter.user_data = value;
+ iter.user_data2 = value;
+ iter.user_data3 = value;
+ }
+
+ public SchemaEnumValue get_enum_value(Gtk.TreeIter iter)
+ {
+ return (SchemaEnumValue)iter.user_data;
+ }
+
+ public bool get_iter(out Gtk.TreeIter iter, Gtk.TreePath path)
+ {
+ if (path.get_depth() != 1)
+ return false;
+
+ return iter_nth_child(out iter, null, path.get_indices()[0]);
+ }
+
+ public Gtk.TreePath get_path(Gtk.TreeIter iter)
+ {
+ var path = new Gtk.TreePath();
+ path.append_index((int)get_enum_value(iter).index);
+ return path;
+ }
+
+ public void get_value(Gtk.TreeIter iter, int column, out Value value)
+ {
+ if (column == 0)
+ value = get_enum_value(iter).nick;
+ else if (column == 1)
+ value = get_enum_value(iter).value;
+ }
+
+ public bool iter_next(ref Gtk.TreeIter iter)
+ {
+ uint index = get_enum_value(iter).index;
+ if (index >= schema_enum.values.length () - 1)
+ return false;
+ set_iter(out iter, schema_enum.values.nth_data(index + 1));
+ return true;
+ }
+
+ public bool iter_children(out Gtk.TreeIter iter, Gtk.TreeIter? parent)
+ {
+ if (parent != null || schema_enum.values.length() == 0)
+ return false;
+ set_iter(out iter, schema_enum.values.nth_data(0));
+ return true;
+ }
+
+ public bool iter_has_child(Gtk.TreeIter iter)
+ {
+ return false;
+ }
+
+ public int iter_n_children(Gtk.TreeIter? iter)
+ {
+ if (iter == null)
+ return (int) schema_enum.values.length();
+ else
+ return 0;
+ }
+
+ public bool iter_nth_child(out Gtk.TreeIter iter, Gtk.TreeIter? parent, int n)
+ {
+ if (parent != null)
+ return false;
+
+ if (n >= schema_enum.values.length())
+ return false;
+ set_iter(out iter, schema_enum.values.nth_data(n));
+ return true;
+ }
+
+ public bool iter_parent(out Gtk.TreeIter iter, Gtk.TreeIter child)
+ {
+ return false;
+ }
+
+ public void ref_node(Gtk.TreeIter iter)
+ {
+ get_enum_value(iter).ref();
+ }
+
+ public void unref_node(Gtk.TreeIter iter)
+ {
+ get_enum_value(iter).unref();
+ }
+}
+
+public class SettingsModel: GLib.Object, Gtk.TreeModel
+{
+ public SchemaList schemas;
+
+ public DConf.Client client;
+ private Directory root;
+
+ public SettingsModel()
+ {
+ client = new DConf.Client ();
+ root = new Directory(this, null, "/", "/");
+
+ schemas = new SchemaList();
+ try
+ {
+ schemas.load_directory("/usr/share/glib-2.0/schemas");
+ } catch (Error e) {
+ warning("Failed to parse schemas: %s", e.message);
+ }
+
+ /* Add keys for the values in the schemas */
+ foreach (var schema in schemas.schemas)
+ root.load_schema(schema, schema.path[1:schema.path.length]);
+ }
+
+ public Gtk.TreeModelFlags get_flags()
+ {
+ return 0;
+ }
+
+ public int get_n_columns()
+ {
+ return 2;
+ }
+
+ public Type get_column_type(int index)
+ {
+ if (index == 0)
+ return typeof(Directory);
+ else
+ return typeof(string);
+ }
+
+ private void set_iter(out Gtk.TreeIter iter, Directory directory)
+ {
+ iter.stamp = 0;
+ iter.user_data = directory;
+ iter.user_data2 = directory;
+ iter.user_data3 = directory;
+ }
+
+ public Directory get_directory(Gtk.TreeIter? iter)
+ {
+ if (iter == null)
+ return root;
+ else
+ return (Directory)iter.user_data;
+ }
+
+ public bool get_iter(out Gtk.TreeIter iter, Gtk.TreePath path)
+ {
+ if (!iter_nth_child(out iter, null, path.get_indices()[0]))
+ return false;
+
+ for (int i = 1; i < path.get_depth(); i++)
+ {
+ Gtk.TreeIter parent = iter;
+ if (!iter_nth_child(out iter, parent, path.get_indices()[i]))
+ return false;
+ }
+
+ return true;
+ }
+
+ public Gtk.TreePath get_path(Gtk.TreeIter iter)
+ {
+ var path = new Gtk.TreePath();
+ path.append_index((int)get_directory(iter).index);
+ return path;
+ }
+
+ public void get_value(Gtk.TreeIter iter, int column, out Value value)
+ {
+ if (column == 0)
+ value = get_directory(iter);
+ else
+ value = get_directory(iter).name;
+ }
+
+ public bool iter_next(ref Gtk.TreeIter iter)
+ {
+ Directory directory = get_directory(iter);
+ if (directory.index >= directory.parent.children.length() - 1)
+ return false;
+ set_iter(out iter, directory.parent.children.nth_data(directory.index+1));
+ return true;
+ }
+
+ public bool iter_children(out Gtk.TreeIter iter, Gtk.TreeIter? parent)
+ {
+ Directory directory = get_directory(parent);
+ if (directory.children.length() == 0)
+ return false;
+ set_iter(out iter, directory.children.nth_data(0));
+ return true;
+ }
+
+ public bool iter_has_child(Gtk.TreeIter iter)
+ {
+ return get_directory(iter).children.length() > 0;
+ }
+
+ public int iter_n_children(Gtk.TreeIter? iter)
+ {
+ return (int) get_directory(iter).children.length();
+ }
+
+ public bool iter_nth_child(out Gtk.TreeIter iter, Gtk.TreeIter? parent, int n)
+ {
+ Directory directory = get_directory(parent);
+ if (n >= directory.children.length())
+ return false;
+ set_iter(out iter, directory.children.nth_data(n));
+ return true;
+ }
+
+ public bool iter_parent(out Gtk.TreeIter iter, Gtk.TreeIter child)
+ {
+ Directory directory = get_directory(child);
+ if (directory.parent == root)
+ return false;
+ set_iter(out iter, directory.parent);
+ return true;
+ }
+
+ public void ref_node(Gtk.TreeIter iter)
+ {
+ get_directory(iter).ref();
+ }
+
+ public void unref_node(Gtk.TreeIter iter)
+ {
+ get_directory(iter).unref();
+ }
+}
diff --git a/editor/dconf-schema.vala b/editor/dconf-schema.vala
new file mode 100644
index 0000000..7bb538f
--- /dev/null
+++ b/editor/dconf-schema.vala
@@ -0,0 +1,280 @@
+public class SchemaKey
+{
+ public Schema schema;
+ public string name;
+ public string type;
+ public Variant default_value;
+ public SchemaValueRange? range;
+ public string? enum_name;
+ public string? summary;
+ public string? description;
+ public string? gettext_domain;
+
+ public SchemaKey(Xml.Node* node, Schema schema, string? gettext_domain)
+ {
+ this.schema = schema;
+ this.gettext_domain = gettext_domain;
+
+ for (var prop = node->properties; prop != null; prop = prop->next)
+ {
+ if (prop->name == "name")
+ name = prop->children->content;
+ else if (prop->name == "type")
+ type = prop->children->content;
+ else if (prop->name == "enum")
+ {
+ type = "s";
+ enum_name = prop->children->content;
+ }
+ else
+ warning ("Unknown property on <key>, %s", prop->name);
+ }
+
+ //if (name == null || type == null)
+ // ?
+
+ for (var child = node->children; child != null; child = child->next)
+ {
+ if (child->name == "default")
+ {
+ try
+ {
+ default_value = Variant.parse(new VariantType(type), child->children->content);
+ }
+ catch (VariantParseError e)
+ {
+ // ...
+ }
+ }
+ else if (child->name == "summary")
+ summary = child->children->content;
+ else if (child->name == "description")
+ description = child->children->content;
+ else if (child->name == "range")
+ range = new SchemaValueRange (type, child);
+ else if (child->type != Xml.ElementType.TEXT_NODE && child->type != Xml.ElementType.COMMENT_NODE)
+ warning ("Unknown child tag in <key>, <%s>", child->name);
+ }
+
+ //if (default_value == null)
+ // ?
+ }
+}
+
+public class SchemaEnumValue : GLib.Object
+{
+ public SchemaEnum schema_enum;
+ public uint index;
+ public string nick;
+ public int value;
+
+ public SchemaEnumValue(SchemaEnum schema_enum, uint index, string nick, int value)
+ {
+ this.schema_enum = schema_enum;
+ this.index = index;
+ this.nick = nick;
+ this.value = value;
+ }
+}
+
+public class SchemaValueRange
+{
+ public Variant min;
+ public Variant max;
+
+ public SchemaValueRange(string type, Xml.Node* node)
+ {
+ for (var prop = node->properties; prop != null; prop = prop->next)
+ {
+ if (prop->name == "min")
+ {
+ try
+ {
+ min = Variant.parse(new VariantType(type), prop->children->content);
+ }
+ catch (VariantParseError e)
+ {
+ // ...
+ }
+ }
+ else if (prop->name == "max")
+ {
+ try
+ {
+ max = Variant.parse(new VariantType(type), prop->children->content);
+ }
+ catch (VariantParseError e)
+ {
+ // ...
+ }
+ }
+ else
+ warning ("Unknown property in <range>, %s", prop->name);
+ }
+
+ //if (min == null || max == null)
+ // ?
+ }
+}
+
+public class SchemaEnum
+{
+ public SchemaList list;
+ public string id;
+ public GLib.List<SchemaEnumValue> values = new GLib.List<SchemaEnumValue>();
+
+ public SchemaEnum(SchemaList list, Xml.Node* node)
+ {
+ this.list = list;
+
+ for (var prop = node->properties; prop != null; prop = prop->next)
+ {
+ if (prop->name == "id")
+ id = prop->children->content;
+ else
+ warning ("Unknown property in <enum>, %s", prop->name);
+ }
+
+ //if (id = null)
+ // ?
+
+ for (var child = node->children; child != null; child = child->next)
+ {
+ if (child->name == "value")
+ {
+ string? nick = null;
+ int value = -1;
+
+ for (var prop = child->properties; prop != null; prop = prop->next)
+ {
+ if (prop->name == "value")
+ value = prop->children->content.to_int();
+ else if (prop->name == "nick")
+ nick = prop->children->content;
+ else
+ warning ("Unknown property in enum <value>, %s", prop->name);
+ }
+
+ //if (value < 0 || nick == null)
+ // ?
+
+ SchemaEnumValue schema_value = new SchemaEnumValue(this, values.length(), nick, value);
+ values.append(schema_value);
+ }
+ else if (child->type != Xml.ElementType.TEXT_NODE && child->type != Xml.ElementType.COMMENT_NODE)
+ warning ("Unknown tag in <enum>, <%s>", child->name);
+ }
+
+ //if (default_value == null)
+ // ?
+ }
+}
+
+public class Schema
+{
+ public SchemaList list;
+ public string id;
+ public string? path;
+ public GLib.HashTable<string, SchemaKey> keys = new GLib.HashTable<string, SchemaKey>(str_hash, str_equal);
+
+ public Schema(SchemaList list, Xml.Node* node, string? gettext_domain)
+ {
+ this.list = list;
+
+ for (var prop = node->properties; prop != null; prop = prop->next)
+ {
+ if (prop->name == "id")
+ id = prop->children->content;
+ else if (prop->name == "path")
+ path = prop->children->content; // FIXME: Does the path have to end with '/'?
+ else if (prop->name == "gettext-domain")
+ gettext_domain = prop->children->content;
+ else
+ warning ("Unknown property on <schema>, %s", prop->name);
+ }
+
+ //if (id == null)
+ //?
+
+ for (var child = node->children; child != null; child = child->next)
+ {
+ if (child->name != "key")
+ continue;
+ var key = new SchemaKey(child, this, gettext_domain);
+ keys.insert(key.name, key);
+ }
+ }
+}
+
+public class SchemaList
+{
+ public GLib.List<Schema> schemas = new GLib.List<Schema>();
+ public GLib.HashTable<string, SchemaKey> keys = new GLib.HashTable<string, SchemaKey>(str_hash, str_equal);
+ public GLib.HashTable<string, SchemaEnum> enums = new GLib.HashTable<string, SchemaEnum>(str_hash, str_equal);
+
+ public void parse_file(string path)
+ {
+ var doc = Xml.Parser.parse_file(path);
+ if (doc == null)
+ return;
+
+ var root = doc->get_root_element();
+ if (root == null)
+ return;
+ if (root->name != "schemalist")
+ return;
+
+ string? gettext_domain = null;
+ for (var prop = root->properties; prop != null; prop = prop->next)
+ {
+ if (prop->name == "gettext-domain")
+ gettext_domain = prop->children->content;
+ }
+
+ for (var node = root->children; node != null; node = node->next)
+ {
+ if (node->name == "schema")
+ {
+ Schema schema = new Schema(this, node, gettext_domain);
+ if (schema.path == null)
+ {
+ // FIXME: What to do here?
+ continue;
+ }
+
+ foreach (var key in schema.keys.get_values())
+ {
+ string full_name = schema.path + key.name;
+ keys.insert(full_name, key);
+ }
+ schemas.append(schema);
+ }
+ else if (node->name == "enum")
+ {
+ SchemaEnum enum = new SchemaEnum(this, node);
+ enums.insert(enum.id, enum);
+ }
+ else if (node->type != Xml.ElementType.TEXT_NODE)
+ warning ("Unknown tag <%s>", node->name);
+ }
+
+ delete doc;
+ }
+
+ public void load_directory(string dir) throws Error
+ {
+ File directory = File.new_for_path(dir);
+ var i = directory.enumerate_children (FILE_ATTRIBUTE_STANDARD_NAME, 0, null);
+ FileInfo info;
+ while ((info = i.next_file (null)) != null) {
+ string name = info.get_name();
+
+ if (!name.has_suffix(".gschema.xml"))
+ continue;
+
+ string path = Path.build_filename(dir, name, null);
+ debug("Loading schema: %s", path);
+ parse_file(path);
+ }
+ }
+}
diff --git a/editor/dconf-view.vala b/editor/dconf-view.vala
new file mode 100644
index 0000000..65bf441
--- /dev/null
+++ b/editor/dconf-view.vala
@@ -0,0 +1,286 @@
+private class KeyValueRenderer: Gtk.CellRenderer
+{
+ private DConfKeyView view;
+ private Gtk.CellRendererText text_renderer;
+ private Gtk.CellRendererSpin spin_renderer;
+ private Gtk.CellRendererToggle toggle_renderer;
+ private Gtk.CellRendererCombo combo_renderer;
+
+ private Key _key;
+ public Key key
+ {
+ get { return _key; }
+ set
+ {
+ _key = value;
+ switch (key.type_string)
+ {
+ case "<enum>":
+ combo_renderer.text = key.value.get_string();
+ combo_renderer.model = new EnumModel(key.schema.schema.list.enums.lookup(key.schema.enum_name));
+ mode = Gtk.CellRendererMode.EDITABLE;
+ break;
+ case "b":
+ toggle_renderer.active = key.value.get_boolean();
+ mode = Gtk.CellRendererMode.ACTIVATABLE;
+ break;
+ case "s":
+ text_renderer.text = key.value.get_string();
+ mode = Gtk.CellRendererMode.EDITABLE;
+ break;
+ case "y":
+ case "n":
+ case "q":
+ case "i":
+ case "u":
+ case "x":
+ case "t":
+ case "d":
+ spin_renderer.text = key.value.print(false);
+ double min, max;
+ var v = get_variant_as_double(key.value, out min, out max);
+ if (key.schema.range != null)
+ {
+ double t;
+ min = get_variant_as_double(key.schema.range.min, out t, out t);
+ max = get_variant_as_double(key.schema.range.max, out t, out t);
+ }
+ spin_renderer.adjustment = new Gtk.Adjustment(v, min, max, 1, 0, 0);
+ spin_renderer.digits = 0;
+ mode = Gtk.CellRendererMode.EDITABLE;
+ break;
+ default:
+ text_renderer.text = key.value.print(false);
+ mode = Gtk.CellRendererMode.INERT;
+ break;
+ }
+ }
+ }
+
+ private static double get_variant_as_double(Variant value, out double min, out double max)
+ {
+ switch (value.classify ())
+ {
+ case Variant.Class.BYTE:
+ min = 0.0;
+ max = 255.0;
+ return (double)value.get_byte();
+ case Variant.Class.INT16:
+ min = int16.MIN;
+ max = int16.MAX;
+ return (double)value.get_int16();
+ case Variant.Class.UINT16:
+ min = uint16.MIN;
+ max = uint16.MAX;
+ return (double)value.get_uint16();
+ case Variant.Class.INT32:
+ min = int32.MIN;
+ max = int32.MAX;
+ return (double)value.get_int32();
+ case Variant.Class.UINT32:
+ min = uint32.MAX;
+ max = uint32.MIN;
+ return (double)value.get_uint32();
+ case Variant.Class.INT64:
+ min = int64.MIN;
+ max = int64.MAX;
+ return (double)value.get_int64();
+ case Variant.Class.UINT64:
+ min = uint64.MIN;
+ max = uint64.MAX;
+ return (double)value.get_uint64();
+ case Variant.Class.DOUBLE:
+ min = double.MIN;
+ max = double.MAX;
+ return value.get_double();
+ default:
+ min = 0.0;
+ max = 0.0;
+ return 0.0;
+ }
+ }
+
+ public KeyValueRenderer(DConfKeyView view)
+ {
+ this.view = view;
+
+ text_renderer = new Gtk.CellRendererText();
+ text_renderer.editable = true;
+ text_renderer.edited.connect(text_edited_cb);
+
+ spin_renderer = new Gtk.CellRendererSpin();
+ spin_renderer.editable = true;
+ spin_renderer.edited.connect(spin_edited_cb);
+
+ toggle_renderer = new Gtk.CellRendererToggle();
+ toggle_renderer.xalign = 0f;
+ toggle_renderer.activatable = true;
+ toggle_renderer.toggled.connect(toggle_cb);
+
+ combo_renderer = new Gtk.CellRendererCombo();
+ combo_renderer.has_entry = false;
+ combo_renderer.text_column = 0;
+ combo_renderer.editable = true;
+ combo_renderer.edited.connect(text_edited_cb);
+ }
+
+ private Gtk.CellRenderer renderer
+ {
+ set {}
+ get
+ {
+ switch (key.type_string)
+ {
+ case "<enum>":
+ return combo_renderer;
+ case "b":
+ return toggle_renderer;
+ case "y":
+ case "n":
+ case "q":
+ case "i":
+ case "u":
+ case "x":
+ case "t":
+ case "d":
+ return spin_renderer;
+ default:
+ case "s":
+ return text_renderer;
+ }
+ }
+ }
+
+ public override void get_size(Gtk.Widget widget,
+ Gdk.Rectangle? cell_area,
+ out int x_offset,
+ out int y_offset,
+ out int width,
+ out int height)
+ {
+ renderer.get_size(widget, cell_area, out x_offset, out y_offset, out width, out height);
+ }
+
+ public override void get_preferred_width(Gtk.Widget widget,
+ out int minimum_size,
+ out int natural_size)
+ {
+ renderer.get_preferred_width(widget, out minimum_size, out natural_size);
+ }
+
+ public override void get_preferred_height_for_width(Gtk.Widget widget,
+ int width,
+ out int minimum_height,
+ out int natural_height)
+ {
+ renderer.get_preferred_height_for_width(widget, width, out minimum_height, out natural_height);
+ }
+
+ public override void get_preferred_height(Gtk.Widget widget,
+ out int minimum_size,
+ out int natural_size)
+ {
+ renderer.get_preferred_height(widget, out minimum_size, out natural_size);
+ }
+
+ public override void get_preferred_width_for_height(Gtk.Widget widget,
+ int height,
+ out int minimum_width,
+ out int natural_width)
+ {
+ renderer.get_preferred_width_for_height(widget, height, out minimum_width, out natural_width);
+ }
+
+ public override void render(Cairo.Context context,
+ Gtk.Widget widget,
+ Gdk.Rectangle background_area,
+ Gdk.Rectangle cell_area,
+ Gtk.CellRendererState flags)
+ {
+ renderer.render(context, widget, background_area, cell_area, flags);
+ }
+
+ public override bool activate(Gdk.Event event,
+ Gtk.Widget widget,
+ string path,
+ Gdk.Rectangle background_area,
+ Gdk.Rectangle cell_area,
+ Gtk.CellRendererState flags)
+ {
+ return renderer.activate(event, widget, path, background_area, cell_area, flags);
+ }
+
+ public override unowned Gtk.CellEditable start_editing(Gdk.Event event,
+ Gtk.Widget widget,
+ string path,
+ Gdk.Rectangle background_area,
+ Gdk.Rectangle cell_area,
+ Gtk.CellRendererState flags)
+ {
+ return renderer.start_editing(event, widget, path, background_area, cell_area, flags);
+ }
+
+ private Key get_key_from_path(string path)
+ {
+ Gtk.TreeIter iter;
+ view.model.get_iter_from_string(out iter, path);
+
+ Key key;
+ view.model.get(iter, 0, out key, -1);
+
+ return key;
+ }
+
+ private void toggle_cb(Gtk.CellRendererToggle renderer, string path)
+ {
+ var key = get_key_from_path(path);
+ key.value = new Variant.boolean(!key.value.get_boolean());
+ }
+
+ private void text_edited_cb(Gtk.CellRendererText renderer, string path, string text)
+ {
+ var key = get_key_from_path(path);
+ key.value = new Variant.string(text);
+ }
+
+ private void spin_edited_cb(Gtk.CellRendererText renderer, string path, string text)
+ {
+ Key key = get_key_from_path(path);
+ if (key.type_string == "y")
+ key.value = new Variant.byte((uchar)text.to_int());
+ else if (key.type_string == "n")
+ key.value = new Variant.int16((int16)text.to_int());
+ else if (key.type_string == "q")
+ key.value = new Variant.uint16((uint16)text.to_int());
+ else if (key.type_string == "i")
+ key.value = new Variant.int32(text.to_int());
+ else if (key.type_string == "u")
+ key.value = new Variant.uint32(text.to_int());
+ else if (key.type_string == "x")
+ key.value = new Variant.int64(text.to_int());
+ else if (key.type_string == "t")
+ key.value = new Variant.uint64(text.to_int());
+ else if (key.type_string == "d")
+ key.value = new Variant.double(text.to_double());
+ }
+}
+
+public class DConfDirView : Gtk.TreeView
+{
+ public DConfDirView()
+ {
+ set_headers_visible(false);
+ insert_column_with_attributes(-1, "Key", new Gtk.CellRendererText(), "text", 1, null);
+ }
+}
+
+public class DConfKeyView : Gtk.TreeView
+{
+ public DConfKeyView()
+ {
+ var column = new Gtk.TreeViewColumn.with_attributes("Name", new Gtk.CellRendererText(), "text", 1, "weight", 4, null);
+ /*column.set_sort_column_id(1);*/
+ append_column(column);
+ insert_column_with_attributes(-1, "Value", new KeyValueRenderer(this), "key", 0, null);
+ }
+}
diff --git a/engine/Makefile.am b/engine/Makefile.am
new file mode 100644
index 0000000..c67c91f
--- /dev/null
+++ b/engine/Makefile.am
@@ -0,0 +1,8 @@
+dconfinclude_HEADERS = \
+ dconf-readtype.h \
+ dconf-resetlist.h \
+ dconf-engine.h
+
+EXTRA_DIST = \
+ dconf-resetlist.c \
+ dconf-engine.c
diff --git a/engine/dconf-engine.c b/engine/dconf-engine.c
new file mode 100644
index 0000000..86cc9cc
--- /dev/null
+++ b/engine/dconf-engine.c
@@ -0,0 +1,662 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#define _XOPEN_SOURCE 600
+#include "dconf-shmdir.h"
+#include "dconf-engine.h"
+#include <gvdb-reader.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+static DConfEngineServiceFunc dconf_engine_service_func;
+
+void
+dconf_engine_message_destroy (DConfEngineMessage *dcem)
+{
+ gint i;
+
+ for (i = 0; dcem->parameters[i]; i++)
+ g_variant_unref (dcem->parameters[i]);
+ g_free (dcem->parameters);
+}
+
+void
+dconf_engine_message_copy (DConfEngineMessage *orig,
+ DConfEngineMessage *copy)
+{
+ gint i, n;
+
+ *copy = *orig;
+
+ for (n = 0; orig->parameters[n]; n++);
+ copy->parameters = g_new (GVariant *, n + 1);
+ for (i = 0; i < n; i++)
+ copy->parameters[i] = g_variant_ref (orig->parameters[i]);
+ copy->parameters[i] = NULL;
+}
+
+void
+dconf_engine_set_service_func (DConfEngineServiceFunc func)
+{
+ dconf_engine_service_func = func;
+}
+
+static const gchar *
+dconf_engine_get_session_dir (void)
+{
+ static const gchar *session_dir;
+ static gsize initialised;
+
+ if (g_once_init_enter (&initialised))
+ {
+ session_dir = dconf_shmdir_from_environment ();
+
+ if (session_dir == NULL)
+ {
+ DConfEngineMessage dcem;
+ GVariant *parameters[2];
+ GVariant *result;
+
+ dcem.bus_types = "e";
+ dcem.bus_name = "ca.desrt.dconf";
+ dcem.object_path = "/ca/desrt/dconf/Writer";
+ dcem.interface_name = "org.freedesktop.DBus.Properties";
+ dcem.method_name = "Get";
+ dcem.reply_type = G_VARIANT_TYPE ("(v)");
+ parameters[0] = g_variant_new ("(ss)",
+ "ca.desrt.dconf.WriterInfo",
+ "ShmDirectory");
+ parameters[1] = NULL;
+ dcem.parameters = parameters;
+
+ result = dconf_engine_service_func (&dcem);
+
+ g_variant_unref (parameters[0]);
+
+ if (result != NULL)
+ {
+ GVariant *str;
+
+ g_variant_get (result, "(v)", &str);
+
+ if (g_variant_is_of_type (str, G_VARIANT_TYPE_STRING))
+ session_dir = g_variant_dup_string (str, NULL);
+ else
+ g_critical ("dconf service sent invalid reply");
+
+ g_variant_unref (result);
+ g_variant_unref (str);
+ }
+ else
+ g_critical ("Unable to contact dconf service");
+ }
+
+ g_once_init_leave (&initialised, 1);
+ }
+
+ return session_dir;
+}
+
+struct _DConfEngine
+{
+ guint64 state;
+
+ guint8 *shm;
+
+ GvdbTable **gvdbs;
+ gchar **object_paths;
+ gchar *bus_types;
+ gchar **names;
+ gint n_dbs;
+};
+
+static void
+dconf_engine_setup_user (DConfEngine *engine)
+{
+ /* invariant: we never have user gvdb without shm */
+ g_assert ((engine->gvdbs[0] == NULL) >= (engine->shm == NULL));
+
+ if (engine->names[0])
+ {
+ const gchar *session_dir = dconf_engine_get_session_dir ();
+
+ if (session_dir)
+ {
+ gchar *filename;
+ gint fd;
+
+ filename = g_build_filename (session_dir,
+ engine->names[0],
+ NULL);
+ fd = open (filename, O_RDWR | O_CREAT, 0600);
+ g_free (filename);
+
+ if (fd >= 0)
+ {
+ if (ftruncate (fd, 1) == 0)
+ {
+ engine->shm = mmap (NULL, 1, PROT_READ, MAP_SHARED, fd, 0);
+
+ if (engine->shm == MAP_FAILED)
+ engine->shm = NULL;
+ }
+
+ close (fd);
+ }
+ }
+
+ if (engine->shm)
+ {
+ gchar *filename;
+
+ filename = g_build_filename (g_get_user_config_dir (),
+ "dconf",
+ engine->names[0],
+ NULL);
+ engine->gvdbs[0] = gvdb_table_new (filename, FALSE, NULL);
+ g_free (filename);
+ }
+ }
+
+ g_assert ((engine->gvdbs[0] == NULL) >= (engine->shm == NULL));
+}
+
+static void
+dconf_engine_refresh_user (DConfEngine *engine)
+{
+ g_assert ((engine->gvdbs[0] == NULL) >= (engine->shm == NULL));
+
+ /* if we failed the first time, fail forever */
+ if (engine->shm && *engine->shm == 1)
+ {
+ if (engine->gvdbs[0])
+ {
+ gvdb_table_unref (engine->gvdbs[0]);
+ engine->gvdbs[0] = NULL;
+ }
+
+ munmap (engine->shm, 1);
+ engine->shm = NULL;
+
+ dconf_engine_setup_user (engine);
+ engine->state++;
+ }
+
+ g_assert ((engine->gvdbs[0] == NULL) >= (engine->shm == NULL));
+}
+
+static void
+dconf_engine_refresh_system (DConfEngine *engine)
+{
+ gint i;
+
+ for (i = 1; i < engine->n_dbs; i++)
+ {
+ if (engine->gvdbs[i] && !gvdb_table_is_valid (engine->gvdbs[i]))
+ {
+ gvdb_table_unref (engine->gvdbs[i]);
+ engine->gvdbs[i] = NULL;
+ }
+
+ if (engine->gvdbs[i] == NULL)
+ {
+ gchar *filename = g_build_filename ("/etc/dconf/db",
+ engine->names[i], NULL);
+ engine->gvdbs[i] = gvdb_table_new (filename, TRUE, NULL);
+ if (engine->gvdbs[i] == NULL)
+ g_error ("Unable to open '%s', specified in dconf profile\n",
+ filename);
+ g_free (filename);
+ engine->state++;
+ }
+ }
+}
+
+static void
+dconf_engine_refresh (DConfEngine *engine)
+{
+ static GStaticMutex lock;
+
+ g_static_mutex_lock (&lock);
+
+ dconf_engine_refresh_system (engine);
+ dconf_engine_refresh_user (engine);
+
+ g_static_mutex_unlock (&lock);
+}
+
+guint64
+dconf_engine_get_state (DConfEngine *engine)
+{
+ dconf_engine_refresh (engine);
+
+ return engine->state;
+}
+
+static gboolean
+dconf_engine_load_profile (const gchar *profile,
+ gchar ***dbs,
+ gint *n_dbs,
+ GError **error)
+{
+ gchar *filename;
+ gint allocated;
+ char line[80];
+ FILE *f;
+
+ filename = g_build_filename ("/etc/dconf/profile", profile, NULL);
+ f = fopen (filename, "r");
+
+ if (f == NULL)
+ {
+ gint saved_errno = errno;
+
+ g_set_error (error, G_FILE_ERROR,
+ g_file_error_from_errno (saved_errno),
+ "open '%s': %s", filename, g_strerror (saved_errno));
+ g_free (filename);
+ return FALSE;
+ }
+
+ allocated = 4;
+ *dbs = g_new (gchar *, allocated);
+ *n_dbs = 0;
+
+ /* quick and dirty is good enough for now */
+ while (fgets (line, sizeof line, f))
+ {
+ const gchar *end;
+
+ end = strchr (line, '\n');
+
+ if (end == NULL)
+ g_error ("long line in %s", filename);
+
+ if (end == line)
+ continue;
+
+ if (line[0] == '#')
+ continue;
+
+ if (*n_dbs == allocated)
+ {
+ allocated *= 2;
+ *dbs = g_renew (gchar *, *dbs, allocated);
+ }
+
+ (*dbs)[(*n_dbs)++] = g_strndup (line, end - line);
+ }
+
+ *dbs = g_renew (gchar *, *dbs, *n_dbs);
+ g_free (filename);
+ fclose (f);
+
+ return TRUE;
+}
+
+DConfEngine *
+dconf_engine_new (const gchar *profile)
+{
+ DConfEngine *engine;
+ gint i;
+
+ engine = g_slice_new (DConfEngine);
+ engine->shm = NULL;
+
+ if (profile == NULL)
+ profile = getenv ("DCONF_PROFILE");
+
+ if (profile)
+ {
+ GError *error = NULL;
+
+ if (!dconf_engine_load_profile (profile, &engine->names,
+ &engine->n_dbs, &error))
+ g_error ("Error loading dconf profile '%s': %s\n",
+ profile, error->message);
+ }
+ else
+ {
+ if (!dconf_engine_load_profile ("user", &engine->names,
+ &engine->n_dbs, NULL))
+ {
+ engine->names = g_new (gchar *, 1);
+ engine->names[0] = g_strdup ("user");
+ engine->n_dbs = 1;
+ }
+ }
+
+ if (strcmp (engine->names[0], "-") == 0)
+ {
+ g_free (engine->names[0]);
+ engine->names[0] = NULL;
+ }
+
+ engine->object_paths = g_new (gchar *, engine->n_dbs);
+ engine->gvdbs = g_new0 (GvdbTable *, engine->n_dbs);
+ engine->bus_types = g_strdup ("eyyyyyyyyyyyyy");
+ engine->state = 0;
+
+ for (i = 0; i < engine->n_dbs; i++)
+ if (engine->names[i])
+ engine->object_paths[i] = g_strjoin (NULL,
+ "/ca/desrt/dconf/Writer/",
+ engine->names[i],
+ NULL);
+ else
+ engine->object_paths[i] = NULL;
+
+ dconf_engine_refresh_system (engine);
+ dconf_engine_setup_user (engine);
+
+ return engine;
+}
+
+void
+dconf_engine_free (DConfEngine *engine)
+{
+ gint i;
+
+ for (i = 0; i < engine->n_dbs; i++)
+ {
+ g_free (engine->object_paths[i]);
+ g_free (engine->names[i]);
+
+ if (engine->gvdbs[i])
+ gvdb_table_unref (engine->gvdbs[i]);
+ }
+
+ if (engine->shm)
+ {
+ munmap (engine->shm, 1);
+ }
+
+ g_free (engine->object_paths);
+ g_free (engine->bus_types);
+ g_free (engine->names);
+ g_free (engine->gvdbs);
+
+ g_slice_free (DConfEngine, engine);
+}
+
+GVariant *
+dconf_engine_read (DConfEngine *engine,
+ const gchar *key)
+{
+ GVariant *value = NULL;
+ gint i;
+
+ dconf_engine_refresh (engine);
+
+ if (engine->gvdbs[0])
+ value = gvdb_table_get_value (engine->gvdbs[0], key);
+
+ for (i = 1; i < engine->n_dbs && value == NULL; i++)
+ value = gvdb_table_get_value (engine->gvdbs[i], key);
+
+ return value;
+}
+
+GVariant *
+dconf_engine_read_default (DConfEngine *engine,
+ const gchar *key)
+{
+ GVariant *value = NULL;
+ gint i;
+
+ dconf_engine_refresh (engine);
+
+ for (i = 1; i < engine->n_dbs && value == NULL; i++)
+ value = gvdb_table_get_value (engine->gvdbs[i], key);
+
+ return value;
+}
+
+GVariant *
+dconf_engine_read_no_default (DConfEngine *engine,
+ const gchar *key)
+{
+ GVariant *value = NULL;
+
+ dconf_engine_refresh (engine);
+
+ if (engine->gvdbs[0])
+ value = gvdb_table_get_value (engine->gvdbs[0], key);
+
+ return value;
+}
+
+static void
+dconf_engine_make_match_rule (DConfEngine *engine,
+ DConfEngineMessage *dcem,
+ const gchar *name,
+ const gchar *method_name)
+{
+ gint i;
+
+ dcem->bus_name = "org.freedesktop.DBus";
+ dcem->object_path = "/org/freedesktop/DBus";
+ dcem->interface_name = "org.freedesktop.DBus";
+ dcem->method_name = method_name;
+
+ dcem->parameters = g_new (GVariant *, engine->n_dbs + 1);
+ for (i = 0; i < engine->n_dbs; i++)
+ {
+ gchar *rule;
+
+ rule = g_strdup_printf ("type='signal',"
+ "interface='ca.desrt.dconf.Writer',"
+ "path='%s',"
+ "arg0path='%s'",
+ engine->object_paths[i],
+ name);
+ dcem->parameters[i] = g_variant_new ("(s)", rule);
+ g_variant_ref_sink (dcem->parameters[i]);
+ g_free (rule);
+ }
+ dcem->parameters[i] = NULL;
+
+ dcem->bus_types = engine->bus_types;
+ dcem->n_messages = engine->n_dbs;
+ dcem->reply_type = G_VARIANT_TYPE_UNIT;
+}
+
+void
+dconf_engine_watch (DConfEngine *engine,
+ const gchar *name,
+ DConfEngineMessage *dcem)
+{
+ dconf_engine_make_match_rule (engine, dcem, name, "AddMatch");
+}
+
+void
+dconf_engine_unwatch (DConfEngine *engine,
+ const gchar *name,
+ DConfEngineMessage *dcem)
+{
+ dconf_engine_make_match_rule (engine, dcem, name, "RemoveMatch");
+}
+
+gboolean
+dconf_engine_is_writable (DConfEngine *engine,
+ const gchar *name)
+{
+ return TRUE;
+}
+
+static GVariant *
+fake_maybe (GVariant *value)
+{
+ GVariantBuilder builder;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
+
+ if (value != NULL)
+ g_variant_builder_add (&builder, "v", value);
+
+ return g_variant_builder_end (&builder);
+}
+
+static void
+dconf_engine_dcem (DConfEngine *engine,
+ DConfEngineMessage *dcem,
+ const gchar *method_name,
+ const gchar *format_string,
+ ...)
+{
+ va_list ap;
+
+ dcem->bus_name = "ca.desrt.dconf";
+ dcem->object_path = engine->object_paths[0];
+ dcem->interface_name = "ca.desrt.dconf.Writer";
+ dcem->method_name = method_name;
+ dcem->parameters = g_new (GVariant *, 2);
+ dcem->n_messages = 1;
+
+ va_start (ap, format_string);
+ dcem->parameters[0] = g_variant_new_va (format_string, NULL, &ap);
+ g_variant_ref_sink (dcem->parameters[0]);
+ dcem->parameters[1] = NULL;
+ va_end (ap);
+
+ dcem->bus_types = engine->bus_types;
+ dcem->reply_type = G_VARIANT_TYPE ("(s)");
+}
+
+gboolean
+dconf_engine_write (DConfEngine *engine,
+ const gchar *name,
+ GVariant *value,
+ DConfEngineMessage *dcem,
+ GError **error)
+{
+ dconf_engine_dcem (engine, dcem,
+ "Write", "(s@av)",
+ name, fake_maybe (value));
+
+ return TRUE;
+}
+
+gboolean
+dconf_engine_write_many (DConfEngine *engine,
+ const gchar *prefix,
+ const gchar * const *keys,
+ GVariant **values,
+ DConfEngineMessage *dcem,
+ GError **error)
+{
+ GVariantBuilder builder;
+ gsize i;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sav)"));
+
+ for (i = 0; keys[i]; i++)
+ g_variant_builder_add (&builder, "(s@av)",
+ keys[i], fake_maybe (values[i]));
+
+ dconf_engine_dcem (engine, dcem, "WriteMany", "(sa(sav))", prefix, &builder);
+
+ return TRUE;
+}
+
+void
+dconf_engine_set_locked (DConfEngine *engine,
+ const gchar *path,
+ gboolean locked,
+ DConfEngineMessage *dcem)
+{
+ dconf_engine_dcem (engine, dcem, "SetLocked", "(sb)", path, locked);
+}
+
+gchar **
+dconf_engine_list (DConfEngine *engine,
+ const gchar *dir,
+ DConfResetList *resets,
+ gint *length)
+{
+ gchar **list;
+
+ /* not yet supported */
+ g_assert (resets == NULL);
+
+ dconf_engine_refresh (engine);
+
+ if (engine->gvdbs[0])
+ list = gvdb_table_list (engine->gvdbs[0], dir);
+ else
+ list = NULL;
+
+ if (list == NULL)
+ list = g_new0 (char *, 1);
+
+ if (length)
+ *length = g_strv_length (list);
+
+ return list;
+}
+
+gboolean
+dconf_engine_decode_notify (DConfEngine *engine,
+ const gchar *anti_expose,
+ const gchar **path,
+ const gchar ***rels,
+ guint bus_type,
+ const gchar *sender,
+ const gchar *iface,
+ const gchar *method,
+ GVariant *body)
+{
+ if (strcmp (iface, "ca.desrt.dconf.Writer") || strcmp (method, "Notify"))
+ return FALSE;
+
+ if (!g_variant_is_of_type (body, G_VARIANT_TYPE ("(sass)")))
+ return FALSE;
+
+ if (anti_expose)
+ {
+ const gchar *ae;
+
+ g_variant_get_child (body, 2, "&s", &ae);
+
+ if (strcmp (ae, anti_expose) == 0)
+ return FALSE;
+ }
+
+ g_variant_get (body, "(&s^a&ss)", path, rels, NULL);
+
+ return TRUE;
+}
+
+gboolean
+dconf_engine_interpret_reply (DConfEngineMessage *dcem,
+ const gchar *sender,
+ GVariant *body,
+ gchar **tag,
+ GError **error)
+{
+ g_variant_get_child (body, 0, "s", tag);
+ return TRUE;
+}
diff --git a/engine/dconf-engine.h b/engine/dconf-engine.h
new file mode 100644
index 0000000..d2058d5
--- /dev/null
+++ b/engine/dconf-engine.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __dconf_engine_h__
+#define __dconf_engine_h__
+
+#include <dconf-readtype.h>
+#include <dconf-resetlist.h>
+#include <glib.h>
+
+typedef struct _DConfEngine DConfEngine;
+
+/**
+ * DConfEngineMessage:
+ *
+ * This structure represents a number of DBus method call messages that #DConfEngine would like to send.
+ *
+ * #DConfEngine itself is unaware of a particular DBus or main loop implementation. As such, all requests are
+ * synchronous and non-blocking, but most of them produce a #DConfEngineMessage describing messages that must be
+ * sent in order for the operation to be completed.
+ *
+ * @bus_name, @object_path, @interface_name, @method_name specify the respective header fields of the method
+ * call. These are always equal for all of the calls contained within a single #DConfEngineMessage.
+ *
+ * @reply_type is the expected reply type of the method call. This is also the same for all calls contained
+ * within a single #DConfEngineMessage.
+ *
+ * @n_messages is the number of messages to send.
+ *
+ * @bus_types and @parameters are both arrays, of length @n_messages. Each element of @bus_type is the bus type
+ * to send each method call on and each of @parameters is the body of that call. The reason that there may be
+ * several messages is that a single dconf "watch" operation may need to send multiple DBus "AddMatch" calls
+ * (and usually to multiple busses).
+ *
+ * Each element in @bus_types is either 'y' for system bus or 'e' for session bus.
+ *
+ * A #DConfEngineMessage is always stack-allocated by the caller. It must be cleared using
+ * dconf_engine_message_destroy() when done. It may be copied using dconf_engine_message_copy().
+ */
+typedef struct
+{
+ const gchar *bus_name;
+ const gchar *object_path;
+ const gchar *interface_name;
+ const gchar *method_name;
+
+ gint n_messages;
+ GVariant **parameters;
+ const gchar *bus_types;
+
+ const GVariantType *reply_type;
+} DConfEngineMessage;
+
+typedef GVariant * (*DConfEngineServiceFunc) (DConfEngineMessage *message);
+G_GNUC_INTERNAL
+void dconf_engine_message_copy (DConfEngineMessage *orig,
+ DConfEngineMessage *copy);
+G_GNUC_INTERNAL
+void dconf_engine_message_destroy (DConfEngineMessage *message);
+
+G_GNUC_INTERNAL
+void dconf_engine_set_service_func (DConfEngineServiceFunc func);
+G_GNUC_INTERNAL
+DConfEngine * dconf_engine_new (const gchar *profile);
+G_GNUC_INTERNAL
+DConfEngine * dconf_engine_new_for_db (DConfEngineServiceFunc *service,
+ const gchar *db_name);
+G_GNUC_INTERNAL
+guint64 dconf_engine_get_state (DConfEngine *engine);
+
+G_GNUC_INTERNAL
+void dconf_engine_free (DConfEngine *engine);
+
+G_GNUC_INTERNAL
+GVariant * dconf_engine_read (DConfEngine *engine,
+ const gchar *key);
+G_GNUC_INTERNAL
+GVariant * dconf_engine_read_default (DConfEngine *engine,
+ const gchar *key);
+G_GNUC_INTERNAL
+GVariant * dconf_engine_read_no_default (DConfEngine *engine,
+ const gchar *key);
+G_GNUC_INTERNAL
+gchar ** dconf_engine_list (DConfEngine *engine,
+ const gchar *path,
+ DConfResetList *resets,
+ gint *length);
+
+G_GNUC_INTERNAL
+void dconf_engine_get_service_info (DConfEngine *engine,
+ const gchar **bus_type,
+ const gchar **destination,
+ const gchar **object_path);
+G_GNUC_INTERNAL
+gboolean dconf_engine_is_writable (DConfEngine *engine,
+ const gchar *name);
+G_GNUC_INTERNAL
+gboolean dconf_engine_write (DConfEngine *engine,
+ const gchar *key,
+ GVariant *value,
+ DConfEngineMessage *message,
+ GError **error);
+G_GNUC_INTERNAL
+gboolean dconf_engine_write_many (DConfEngine *engine,
+ const gchar *prefix,
+ const gchar * const *keys,
+ GVariant **values,
+ DConfEngineMessage *message,
+ GError **error);
+G_GNUC_INTERNAL
+void dconf_engine_watch (DConfEngine *engine,
+ const gchar *name,
+ DConfEngineMessage *message);
+G_GNUC_INTERNAL
+void dconf_engine_unwatch (DConfEngine *engine,
+ const gchar *name,
+ DConfEngineMessage *message);
+G_GNUC_INTERNAL
+gboolean dconf_engine_decode_notify (DConfEngine *engine,
+ const gchar *anti_expose,
+ const gchar **prefix,
+ const gchar ***keys,
+ guint bus_type,
+ const gchar *sender,
+ const gchar *interface,
+ const gchar *member,
+ GVariant *body);
+G_GNUC_INTERNAL
+void dconf_engine_set_locked (DConfEngine *engine,
+ const gchar *path,
+ gboolean locked,
+ DConfEngineMessage *message);
+
+G_GNUC_INTERNAL
+gboolean dconf_engine_interpret_reply (DConfEngineMessage *message,
+ const gchar *sender,
+ GVariant *body,
+ gchar **tag,
+ GError **error);
+
+#endif /* __dconf_engine_h__ */
diff --git a/engine/dconf-readtype.h b/engine/dconf-readtype.h
new file mode 100644
index 0000000..68c4f13
--- /dev/null
+++ b/engine/dconf-readtype.h
@@ -0,0 +1,11 @@
+#ifndef __dconf_readtype_h__
+#define __dconf_readtype_h__
+
+typedef enum
+{
+ DCONF_READ_NORMAL,
+ DCONF_READ_SET,
+ DCONF_READ_RESET
+} DConfReadType;
+
+#endif /* __dconf_readtype_h__ */
diff --git a/engine/dconf-resetlist.c b/engine/dconf-resetlist.c
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/engine/dconf-resetlist.c
@@ -0,0 +1 @@
+
diff --git a/engine/dconf-resetlist.h b/engine/dconf-resetlist.h
new file mode 100644
index 0000000..ee9d13b
--- /dev/null
+++ b/engine/dconf-resetlist.h
@@ -0,0 +1,20 @@
+#ifndef __dconf_resetlist_h__
+#define __dconf_resetlist_h__
+
+#include <glib.h>
+
+typedef struct
+{
+ gpointer opaque[8];
+} DConfResetList;
+
+void dconf_reset_list_init (DConfResetList *list,
+ const gchar *prefix,
+ const gchar * const *rels,
+ gsize n_rels);
+void dconf_reset_list_add (DConfResetList *list,
+ const gchar *path);
+void dconf_reset_list_clear (DConfResetList *list);
+
+
+#endif /* __dconf_resetlist_h__ */
diff --git a/gsettings/.gitignore b/gsettings/.gitignore
new file mode 100644
index 0000000..621458d
--- /dev/null
+++ b/gsettings/.gitignore
@@ -0,0 +1 @@
+libdconfsettings.so
diff --git a/gsettings/Makefile.am b/gsettings/Makefile.am
new file mode 100644
index 0000000..ace7da0
--- /dev/null
+++ b/gsettings/Makefile.am
@@ -0,0 +1,24 @@
+AM_CFLAGS = -std=c89 -Wall -Wmissing-prototypes -Wwrite-strings -fPIC -DPIC
+INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/gvdb -I$(top_srcdir)/engine $(gio_CFLAGS)
+
+giomodules_PROGRAMS = libdconfsettings.so
+
+libdconfsettings_so_LDADD = $(gio_LIBS)
+libdconfsettings_so_LDFLAGS = -shared
+libdconfsettings_so_SOURCES = \
+ ../engine/dconf-engine.c \
+ ../common/dconf-shmdir.c \
+ ../gvdb/gvdb-reader.c \
+ dconfcontext.h \
+ dconfcontext.c \
+ dconfsettingsbackend.c
+
+uninstall-hook:
+ if test -z "$(DESTDIR)" -a "$(gio_QUERYMODULES)" != "no" ; then \
+ $(gio_QUERYMODULES) $(giomodulesdir) ; \
+ fi
+
+install-data-hook:
+ if test -z "$(DESTDIR)" -a "$(gio_QUERYMODULES)" != "no" ; then \
+ $(gio_QUERYMODULES) $(giomodulesdir) ; \
+ fi
diff --git a/gsettings/dconfcontext.c b/gsettings/dconfcontext.c
new file mode 100644
index 0000000..339507e
--- /dev/null
+++ b/gsettings/dconfcontext.c
@@ -0,0 +1,35 @@
+#include "dconfcontext.h"
+
+static gpointer
+dconf_context_thread (gpointer data)
+{
+ GMainContext *context = data;
+ GMainLoop *loop;
+
+ g_main_context_push_thread_default (context);
+ loop = g_main_loop_new (context, FALSE);
+ g_main_loop_run (loop);
+
+ g_assert_not_reached ();
+}
+
+GMainContext *
+dconf_context_get (void)
+{
+ static GMainContext *context;
+ static gsize initialised;
+
+ if (g_once_init_enter (&initialised))
+ {
+ GThread *thread;
+
+ context = g_main_context_new ();
+ thread = g_thread_create (dconf_context_thread,
+ context, FALSE, NULL);
+ g_assert (thread != NULL);
+
+ g_once_init_leave (&initialised, 1);
+ }
+
+ return context;
+}
diff --git a/gsettings/dconfcontext.h b/gsettings/dconfcontext.h
new file mode 100644
index 0000000..42d40a6
--- /dev/null
+++ b/gsettings/dconfcontext.h
@@ -0,0 +1,9 @@
+#ifndef _dconfcontext_h_
+#define _dconfcontext_h_
+
+#include <glib.h>
+
+G_GNUC_INTERNAL
+GMainContext * dconf_context_get (void);
+
+#endif
diff --git a/gsettings/dconfsettingsbackend.c b/gsettings/dconfsettingsbackend.c
new file mode 100644
index 0000000..d978646
--- /dev/null
+++ b/gsettings/dconfsettingsbackend.c
@@ -0,0 +1,670 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#define G_SETTINGS_ENABLE_BACKEND
+#include <gio/gsettingsbackend.h>
+#include <dconf-engine.h>
+#include <gio/gio.h>
+
+#include <string.h>
+
+#include "dconfcontext.h"
+
+typedef GSettingsBackendClass DConfSettingsBackendClass;
+typedef struct _Outstanding Outstanding;
+
+typedef struct
+{
+ GSettingsBackend backend;
+
+ GDBusConnection *session_bus;
+ GDBusConnection *system_bus;
+ guint session_subscription;
+ guint system_subscription;
+
+ Outstanding *outstanding;
+ gchar *anti_expose_tag;
+
+ DConfEngine *engine;
+ GStaticMutex lock;
+ GCond *sync_cond;
+} DConfSettingsBackend;
+
+static GType dconf_settings_backend_get_type (void);
+G_DEFINE_TYPE (DConfSettingsBackend,
+ dconf_settings_backend,
+ G_TYPE_SETTINGS_BACKEND)
+
+static void
+dconf_settings_backend_signal (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ DConfSettingsBackend *dcsb = user_data;
+ const gchar *anti_expose;
+ const gchar **rels;
+ const gchar *path;
+ gchar bus_type;
+
+ if (connection == dcsb->session_bus)
+ {
+ anti_expose = dcsb->anti_expose_tag;
+ bus_type = 'e';
+ }
+
+ else if (connection == dcsb->system_bus)
+ {
+ anti_expose = NULL;
+ bus_type = 'y';
+ }
+
+ else
+ g_assert_not_reached ();
+
+ if (dconf_engine_decode_notify (dcsb->engine, anti_expose,
+ &path, &rels, bus_type,
+ sender_name, interface_name,
+ signal_name, parameters))
+ {
+ GSettingsBackend *backend = G_SETTINGS_BACKEND (dcsb);
+
+ if (!g_str_has_suffix (path, "/"))
+ g_settings_backend_changed (backend, path, NULL);
+
+ else if (rels[0] == NULL)
+ g_settings_backend_path_changed (backend, path, NULL);
+
+ else
+ g_settings_backend_keys_changed (backend, path, rels, NULL);
+
+ g_free (rels);
+ }
+}
+
+static void
+dconf_settings_backend_send (DConfSettingsBackend *dcsb,
+ DConfEngineMessage *dcem,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GDBusConnection *connection;
+ GError *error = NULL;
+ gint i;
+
+ for (i = 0; i < dcem->n_messages; i++)
+ {
+ switch (dcem->bus_types[i])
+ {
+ case 'e':
+ if (dcsb->session_bus == NULL && callback)
+ {
+ dcsb->session_bus =
+ g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+
+ if (dcsb->session_bus != NULL)
+ dcsb->session_subscription =
+ g_dbus_connection_signal_subscribe (dcsb->session_bus, NULL,
+ "ca.desrt.dconf.Writer",
+ NULL, NULL, NULL,
+ G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
+ dconf_settings_backend_signal,
+ dcsb, NULL);
+ }
+ connection = dcsb->session_bus;
+ break;
+
+ case 'y':
+ if (dcsb->system_bus == NULL && callback)
+ {
+ dcsb->system_bus =
+ g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+
+ if (dcsb->system_bus != NULL)
+ dcsb->system_subscription =
+ g_dbus_connection_signal_subscribe (dcsb->system_bus, NULL,
+ "ca.desrt.dconf.Writer",
+ NULL, NULL, NULL,
+ G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
+ dconf_settings_backend_signal,
+ dcsb, NULL);
+ }
+ connection = dcsb->system_bus;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (connection == NULL && callback != NULL)
+ callback (NULL, NULL, user_data);
+
+ if (connection != NULL)
+ g_dbus_connection_call (connection,
+ dcem->bus_name,
+ dcem->object_path,
+ dcem->interface_name,
+ dcem->method_name,
+ dcem->parameters[i],
+ dcem->reply_type,
+ 0, 120000, NULL, callback, user_data);
+ }
+}
+
+static GVariant *
+dconf_settings_backend_send_finish (GObject *source,
+ GAsyncResult *result)
+{
+ if (source == NULL)
+ return NULL;
+
+ return g_dbus_connection_call_finish (G_DBUS_CONNECTION (source),
+ result, NULL);
+}
+
+struct _Outstanding
+{
+ Outstanding *next;
+
+ DConfSettingsBackend *dcsb;
+ DConfEngineMessage dcem;
+
+ gchar *set_key;
+ GVariant *set_value;
+ GTree *tree;
+};
+
+static void
+dconf_settings_backend_outstanding_returned (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ Outstanding *outstanding = user_data;
+ DConfSettingsBackend *dcsb;
+ GVariant *reply;
+
+ dcsb = outstanding->dcsb;
+
+ /* One way or another we no longer need this hooked into the list.
+ */
+ g_static_mutex_lock (&dcsb->lock);
+ {
+ Outstanding **tmp;
+
+ for (tmp = &dcsb->outstanding; tmp; tmp = &(*tmp)->next)
+ if (*tmp == outstanding)
+ {
+ *tmp = outstanding->next;
+ break;
+ }
+
+ if (dcsb->outstanding == NULL && dcsb->sync_cond)
+ g_cond_broadcast (dcsb->sync_cond);
+ }
+ g_static_mutex_unlock (&dcsb->lock);
+
+ reply = dconf_settings_backend_send_finish (source, result);
+
+ if (reply)
+ {
+ /* Success.
+ *
+ * We want to ensure that we don't emit an extra change
+ * notification signal when we see the signal that the service is
+ * about to send, so store the tag so we know to ignore it when
+ * the signal comes.
+ *
+ * No thread safety issue here since this variable is only
+ * accessed from the worker thread.
+ */
+ g_free (dcsb->anti_expose_tag);
+ g_variant_get_child (reply, 0, "s", &dcsb->anti_expose_tag);
+ g_variant_unref (reply);
+ }
+ else
+ {
+ /* An error of some kind.
+ *
+ * We already removed the outstanding entry from the list, so the
+ * unmodified database is now visible to the client. Change
+ * notify so that they see it.
+ */
+ if (outstanding->set_key)
+ g_settings_backend_changed (G_SETTINGS_BACKEND (dcsb),
+ outstanding->set_key, NULL);
+
+ else
+ g_settings_backend_changed_tree (G_SETTINGS_BACKEND (dcsb),
+ outstanding->tree, NULL);
+ }
+
+ dconf_engine_message_destroy (&outstanding->dcem);
+ g_object_unref (outstanding->dcsb);
+ g_free (outstanding->set_key);
+
+ if (outstanding->set_value)
+ g_variant_unref (outstanding->set_value);
+
+ if (outstanding->tree)
+ g_tree_unref (outstanding->tree);
+
+ g_slice_free (Outstanding, outstanding);
+}
+
+static gboolean
+dconf_settings_backend_send_outstanding (gpointer data)
+{
+ Outstanding *outstanding = data;
+
+ dconf_settings_backend_send (outstanding->dcsb,
+ &outstanding->dcem,
+ dconf_settings_backend_outstanding_returned,
+ outstanding);
+
+ return FALSE;
+}
+
+static void
+dconf_settings_backend_queue (DConfSettingsBackend *dcsb,
+ DConfEngineMessage *dcem,
+ const gchar *set_key,
+ GVariant *set_value,
+ GTree *tree)
+{
+ Outstanding *outstanding;
+
+ outstanding = g_slice_new (Outstanding);
+ outstanding->dcsb = g_object_ref (dcsb);
+ outstanding->dcem = *dcem;
+
+ outstanding->set_key = g_strdup (set_key);
+ outstanding->set_value = set_value ? g_variant_ref_sink (set_value) : NULL;
+ outstanding->tree = tree ? g_tree_ref (tree) : NULL;
+
+ g_static_mutex_lock (&dcsb->lock);
+ outstanding->next = dcsb->outstanding;
+ dcsb->outstanding = outstanding;
+ g_static_mutex_unlock (&dcsb->lock);
+
+ g_main_context_invoke (dconf_context_get (),
+ dconf_settings_backend_send_outstanding,
+ outstanding);
+}
+
+static gboolean
+dconf_settings_backend_scan_outstanding_tree (GTree *tree,
+ const gchar *key,
+ gsize key_length,
+ gpointer *value)
+{
+ gchar *mykey;
+
+ mykey = g_alloca (key_length + 1);
+ memcpy (mykey, key, key_length + 1);
+
+ while (!g_tree_lookup_extended (tree, mykey, NULL, value) &&
+ --key_length)
+ {
+ while (mykey[key_length - 1] != '/')
+ key_length--;
+
+ mykey[key_length] = '\0';
+ }
+
+ return key_length != 0;
+}
+
+static gboolean
+dconf_settings_backend_scan_outstanding (DConfSettingsBackend *backend,
+ const gchar *key,
+ GVariant **value)
+{
+ gboolean found = FALSE;
+ Outstanding *node;
+ gsize length;
+
+ length = strlen (key);
+
+ if G_LIKELY (backend->outstanding == NULL)
+ return FALSE;
+
+ g_static_mutex_lock (&backend->lock);
+
+ for (node = backend->outstanding; node; node = node->next)
+ {
+ if (node->set_key)
+ {
+ if (strcmp (key, node->set_key) == 0)
+ {
+ if (node->set_value != NULL)
+ *value = g_variant_ref (node->set_value);
+ else
+ *value = NULL;
+
+ found = TRUE;
+ break;
+ }
+ }
+
+ else
+ {
+ gpointer result;
+
+ if (dconf_settings_backend_scan_outstanding_tree (node->tree, key,
+ length, &result))
+ {
+ if (result)
+ *value = g_variant_ref (result);
+ else
+ *value = NULL;
+
+ found = TRUE;
+ break;
+ }
+ }
+ }
+
+ g_static_mutex_unlock (&backend->lock);
+
+ return found;
+}
+
+static GVariant *
+dconf_settings_backend_read (GSettingsBackend *backend,
+ const gchar *key,
+ const GVariantType *expected_type,
+ gboolean default_value)
+{
+ DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend;
+
+ if (!default_value)
+ {
+ GVariant *value;
+
+ if (dconf_settings_backend_scan_outstanding (dcsb, key, &value))
+ return value;
+
+ return dconf_engine_read (dcsb->engine, key);
+ }
+ else
+ return dconf_engine_read_default (dcsb->engine, key);
+}
+
+static gboolean
+dconf_settings_backend_write (GSettingsBackend *backend,
+ const gchar *key,
+ GVariant *value,
+ gpointer origin_tag)
+{
+ DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend;
+ DConfEngineMessage dcem;
+
+ if (!dconf_engine_write (dcsb->engine, key, value, &dcem, NULL))
+ return FALSE;
+
+ dconf_settings_backend_queue (dcsb, &dcem, key, value, NULL);
+ g_settings_backend_changed (backend, key, origin_tag);
+
+ return TRUE;
+}
+
+static gboolean
+dconf_settings_backend_write_tree (GSettingsBackend *backend,
+ GTree *tree,
+ gpointer origin_tag)
+{
+ DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend;
+ DConfEngineMessage dcem;
+ const gchar **keys;
+ GVariant **values;
+ gboolean success;
+ gchar *prefix;
+
+ g_settings_backend_flatten_tree (tree, &prefix, &keys, &values);
+
+ if ((success = dconf_engine_write_many (dcsb->engine, prefix,
+ keys, values, &dcem, NULL)))
+ {
+ dconf_settings_backend_queue (dcsb, &dcem, NULL, NULL, tree);
+ g_settings_backend_keys_changed (backend, prefix, keys, origin_tag);
+ }
+
+ g_free (prefix);
+ g_free (values);
+ g_free (keys);
+
+ return success;
+}
+
+static void
+dconf_settings_backend_reset (GSettingsBackend *backend,
+ const gchar *key,
+ gpointer origin_tag)
+{
+ dconf_settings_backend_write (backend, key, NULL, origin_tag);
+}
+
+static gboolean
+dconf_settings_backend_get_writable (GSettingsBackend *backend,
+ const gchar *name)
+{
+ DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend;
+
+ return dconf_engine_is_writable (dcsb->engine, name);
+}
+
+typedef struct
+{
+ DConfSettingsBackend *dcsb;
+ guint64 state;
+ gchar *name;
+ gint outstanding;
+} OutstandingWatch;
+
+static OutstandingWatch *
+outstanding_watch_new (DConfSettingsBackend *dcsb,
+ const gchar *name)
+{
+ OutstandingWatch *watch;
+
+ watch = g_slice_new (OutstandingWatch);
+ watch->dcsb = g_object_ref (dcsb);
+ watch->state = dconf_engine_get_state (dcsb->engine);
+ watch->outstanding = 0;
+ watch->name = g_strdup (name);
+
+ return watch;
+}
+
+static void
+outstanding_watch_free (OutstandingWatch *watch)
+{
+ if (--watch->outstanding == 0)
+ {
+ g_object_unref (watch->dcsb);
+ g_free (watch->name);
+
+ g_slice_free (OutstandingWatch, watch);
+ }
+}
+
+static void
+add_match_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ OutstandingWatch *watch = user_data;
+ GError *error = NULL;
+ GVariant *reply;
+
+ /* couldn't connect to DBus */
+ if (source == NULL)
+ {
+ outstanding_watch_free (watch);
+ return;
+ }
+
+ reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source),
+ result, &error);
+
+ if (reply == NULL)
+ {
+ g_critical ("DBus AddMatch for dconf path '%s': %s",
+ watch->name, error->message);
+ outstanding_watch_free (watch);
+ g_error_free (error);
+
+ return;
+ }
+
+ else
+ g_variant_unref (reply); /* it is just an empty tuple */
+
+ /* In the normal case we can just free everything and be done.
+ *
+ * There is a fleeting chance, however, that the database has changed
+ * in the meantime. In that case we can only assume that the subject
+ * of this watch changed in that time period and emit a signal to that
+ * effect.
+ */
+ if (dconf_engine_get_state (watch->dcsb->engine) != watch->state)
+ g_settings_backend_path_changed (G_SETTINGS_BACKEND (watch->dcsb),
+ watch->name, NULL);
+
+ outstanding_watch_free (watch);
+}
+
+static gboolean
+dconf_settings_backend_subscribe_context_func (gpointer data)
+{
+ OutstandingWatch *watch = data;
+ DConfEngineMessage dcem;
+
+ dconf_engine_watch (watch->dcsb->engine, watch->name, &dcem);
+ watch->outstanding = dcem.n_messages;
+
+ dconf_settings_backend_send (watch->dcsb, &dcem, add_match_done, watch);
+ dconf_engine_message_destroy (&dcem);
+
+ return FALSE;
+}
+
+static void
+dconf_settings_backend_subscribe (GSettingsBackend *backend,
+ const gchar *name)
+{
+ DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend;
+
+ g_main_context_invoke (dconf_context_get (),
+ dconf_settings_backend_subscribe_context_func,
+ outstanding_watch_new (dcsb, name));
+}
+
+static void
+dconf_settings_backend_unsubscribe (GSettingsBackend *backend,
+ const gchar *name)
+{
+ DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend;
+ DConfEngineMessage dcem;
+
+ dconf_engine_unwatch (dcsb->engine, name, &dcem);
+ dconf_settings_backend_send (dcsb, &dcem, NULL, NULL);
+ dconf_engine_message_destroy (&dcem);
+}
+
+static void
+dconf_settings_backend_sync (GSettingsBackend *backend)
+{
+ DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend;
+
+ if (!dcsb->outstanding)
+ return;
+
+ g_static_mutex_lock (&dcsb->lock);
+
+ g_assert (dcsb->sync_cond == NULL);
+ dcsb->sync_cond = g_cond_new ();
+
+ while (dcsb->outstanding)
+ g_cond_wait (dcsb->sync_cond, g_static_mutex_get_mutex (&dcsb->lock));
+
+ g_cond_free (dcsb->sync_cond);
+ dcsb->sync_cond = NULL;
+
+ g_static_mutex_unlock (&dcsb->lock);
+}
+
+static GVariant *
+dconf_settings_backend_service_func (DConfEngineMessage *dcem)
+{
+ g_assert (dcem->bus_types[0] == 'e');
+
+ return g_dbus_connection_call_sync (g_bus_get_sync (G_BUS_TYPE_SESSION,
+ NULL, NULL),
+ dcem->bus_name, dcem->object_path,
+ dcem->interface_name, dcem->method_name,
+ dcem->parameters[0], dcem->reply_type,
+ 0, -1, NULL, NULL);
+}
+
+static void
+dconf_settings_backend_init (DConfSettingsBackend *dcsb)
+{
+ dconf_engine_set_service_func (dconf_settings_backend_service_func);
+ dcsb->engine = dconf_engine_new (NULL);
+}
+
+static void
+dconf_settings_backend_class_init (GSettingsBackendClass *class)
+{
+ class->read = dconf_settings_backend_read;
+ class->write = dconf_settings_backend_write;
+ class->write_tree = dconf_settings_backend_write_tree;
+ class->reset = dconf_settings_backend_reset;
+ class->get_writable = dconf_settings_backend_get_writable;
+ class->subscribe = dconf_settings_backend_subscribe;
+ class->unsubscribe = dconf_settings_backend_unsubscribe;
+ class->sync = dconf_settings_backend_sync;
+}
+
+void
+g_io_module_load (GIOModule *module)
+{
+ g_type_module_use (G_TYPE_MODULE (module));
+ g_io_extension_point_implement (G_SETTINGS_BACKEND_EXTENSION_POINT_NAME,
+ dconf_settings_backend_get_type (),
+ "dconf", 100);
+}
+
+void
+g_io_module_unload (GIOModule *module)
+{
+ g_assert_not_reached ();
+}
+
+gchar **
+g_io_module_query (void)
+{
+ return g_strsplit (G_SETTINGS_BACKEND_EXTENSION_POINT_NAME, "!", 0);
+}
diff --git a/gvdb/Makefile.am b/gvdb/Makefile.am
new file mode 100644
index 0000000..47e77c8
--- /dev/null
+++ b/gvdb/Makefile.am
@@ -0,0 +1,6 @@
+EXTRA_DIST = \
+ gvdb-format.h \
+ gvdb-reader.h \
+ gvdb-reader.c \
+ gvdb-builder.h \
+ gvdb-builder.c
diff --git a/gvdb-builder.c b/gvdb/gvdb-builder.c
index 4b48d80..4b48d80 100644
--- a/gvdb-builder.c
+++ b/gvdb/gvdb-builder.c
diff --git a/gvdb-builder.h b/gvdb/gvdb-builder.h
index 797626e..797626e 100644
--- a/gvdb-builder.h
+++ b/gvdb/gvdb-builder.h
diff --git a/gvdb-format.h b/gvdb/gvdb-format.h
index 886aa56..886aa56 100644
--- a/gvdb-format.h
+++ b/gvdb/gvdb-format.h
diff --git a/gvdb-reader.c b/gvdb/gvdb-reader.c
index 73f4f13..73f4f13 100644
--- a/gvdb-reader.c
+++ b/gvdb/gvdb-reader.c
diff --git a/gvdb-reader.h b/gvdb/gvdb-reader.h
index e6921e9..e6921e9 100644
--- a/gvdb-reader.h
+++ b/gvdb/gvdb-reader.h
diff --git a/gvdb.doap b/gvdb/gvdb.doap
index b4ae60c..b4ae60c 100644
--- a/gvdb.doap
+++ b/gvdb/gvdb.doap
diff --git a/service/.gitignore b/service/.gitignore
new file mode 100644
index 0000000..aefa930
--- /dev/null
+++ b/service/.gitignore
@@ -0,0 +1,2 @@
+dconf-service
+ca.desrt.dconf.service
diff --git a/service/Makefile.am b/service/Makefile.am
new file mode 100644
index 0000000..d844e2a
--- /dev/null
+++ b/service/Makefile.am
@@ -0,0 +1,26 @@
+AM_CFLAGS = $(gio_CFLAGS) -I$(top_srcdir)/gvdb -I$(top_srcdir)/common -Wall -Wmissing-prototypes -Wwrite-strings
+
+libexec_PROGRAMS = dconf-service
+
+dbussystemservice_DATA = ca.desrt.dconf.service
+dbusservice_DATA = ca.desrt.dconf.service
+
+dconf_service_LDADD = $(gio_LIBS)
+dconf_service_SOURCES = \
+ ../gvdb/gvdb-builder.c \
+ ../gvdb/gvdb-reader.c \
+ ../common/dconf-shmdir.c \
+ dconf-interfaces.h \
+ dconf-interfaces.c \
+ dconf-rebuilder.h \
+ dconf-rebuilder.c \
+ dconf-writer.h \
+ dconf-writer.c \
+ dconf-state.h \
+ dconf-state.c \
+ service.c
+
+DISTCLEANFILES = ca.desrt.dconf.service
+
+ca.desrt.dconf.service: Makefile
+ $(AM_V_GEN) echo -e '[D-BUS Service]\nName=ca.desrt.dconf\nExec=${libexecdir}/dconf-service' > $@
diff --git a/service/dconf-interfaces.c b/service/dconf-interfaces.c
new file mode 100644
index 0000000..cac039c
--- /dev/null
+++ b/service/dconf-interfaces.c
@@ -0,0 +1,91 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "dconf-interfaces.h"
+
+static const GDBusArgInfo name_arg = { -1, (gchar *) "name", (gchar *) "s" };
+static const GDBusArgInfo path_arg = { -1, (gchar *) "path", (gchar *) "s" };
+static const GDBusArgInfo names_arg = { -1, (gchar *) "names", (gchar *) "as" };
+static const GDBusArgInfo tag_arg = { -1, (gchar *) "tag", (gchar *) "s" };
+static const GDBusArgInfo value_arg = { -1, (gchar *) "value", (gchar *) "av" };
+static const GDBusArgInfo values_arg = { -1, (gchar *) "values", (gchar *) "a(sav)" };
+static const GDBusArgInfo locked_arg = { -1, (gchar *) "locked", (gchar *) "b" };
+
+static const GDBusArgInfo *write_in[] = { &name_arg, &value_arg, NULL };
+static const GDBusArgInfo *write_out[] = { &tag_arg, NULL };
+static const GDBusArgInfo *many_in[] = { &path_arg, &values_arg, NULL };
+static const GDBusArgInfo *many_out[] = { &tag_arg, NULL };
+static const GDBusArgInfo *notify_args[] = { &path_arg, &names_arg, &tag_arg, NULL };
+static const GDBusArgInfo *setlocked_in[] = { &name_arg, &locked_arg, NULL };
+static const GDBusArgInfo *setlocked_out[] = { NULL };
+
+static const GDBusMethodInfo write_method = {
+ -1, (gchar *) "Write",
+ (GDBusArgInfo **) write_in,
+ (GDBusArgInfo **) write_out
+};
+
+static const GDBusMethodInfo writemany_method = {
+ -1, (gchar *) "WriteMany",
+ (GDBusArgInfo **) many_in,
+ (GDBusArgInfo **) many_out
+};
+
+static const GDBusMethodInfo setlocked_method = {
+ -1, (gchar *) "SetLocked",
+ (GDBusArgInfo **) setlocked_in,
+ (GDBusArgInfo **) setlocked_out
+};
+
+static const GDBusSignalInfo notify_signal = {
+ -1, (gchar *) "Notify",
+ (GDBusArgInfo **) notify_args
+};
+
+static const GDBusPropertyInfo shmdir_property = {
+ -1, (gchar *) "ShmDirectory", (gchar *) "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE
+};
+
+static const GDBusMethodInfo *writer_methods[] = {
+ &write_method, &writemany_method, &setlocked_method, NULL
+};
+
+static const GDBusSignalInfo *writer_signals[] = {
+ &notify_signal, NULL
+};
+
+static const GDBusPropertyInfo *writer_info_properties[] = {
+ &shmdir_property, NULL
+};
+
+const GDBusInterfaceInfo ca_desrt_dconf_Writer = {
+ -1, (gchar *) "ca.desrt.dconf.Writer",
+ (GDBusMethodInfo **) writer_methods,
+ (GDBusSignalInfo **) writer_signals,
+ (GDBusPropertyInfo **) NULL
+};
+
+const GDBusInterfaceInfo ca_desrt_dconf_WriterInfo = {
+ -1, (gchar *) "ca.desrt.dconf.WriterInfo",
+ (GDBusMethodInfo **) NULL,
+ (GDBusSignalInfo **) NULL,
+ (GDBusPropertyInfo **) writer_info_properties
+};
diff --git a/service/dconf-interfaces.h b/service/dconf-interfaces.h
new file mode 100644
index 0000000..9f159b1
--- /dev/null
+++ b/service/dconf-interfaces.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __dconf_interfaces_h__
+#define __dconf_interfaces_h__
+
+#include <gio/gio.h>
+
+extern const GDBusInterfaceInfo ca_desrt_dconf_Writer;
+extern const GDBusInterfaceInfo ca_desrt_dconf_WriterInfo;
+
+#endif /* __dconf_interfaces_h__ */
diff --git a/service/dconf-rebuilder.c b/service/dconf-rebuilder.c
new file mode 100644
index 0000000..d7f4cb3
--- /dev/null
+++ b/service/dconf-rebuilder.c
@@ -0,0 +1,214 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "dconf-rebuilder.h"
+
+#include <string.h>
+
+#include "gvdb-reader.h"
+#include "gvdb-builder.h"
+
+typedef struct
+{
+ const gchar *prefix;
+ gint prefix_len;
+
+ GHashTable *table;
+ const gchar *const*keys;
+ GVariant *const*values;
+ gint n_items;
+ gint index;
+
+ gchar name[4096];
+ gint name_len;
+} DConfRebuilderState;
+
+static GvdbItem *
+dconf_rebuilder_get_parent (GHashTable *table,
+ gchar *key,
+ gint length)
+{
+ GvdbItem *grandparent, *parent;
+
+ if (length == 1)
+ return NULL;
+
+ while (key[--length - 1] != '/');
+ key[length] = '\0';
+
+ parent = g_hash_table_lookup (table, key);
+
+ if (parent == NULL)
+ {
+ parent = gvdb_hash_table_insert (table, key);
+
+ grandparent = dconf_rebuilder_get_parent (table, key, length);
+
+ if (grandparent != NULL)
+ gvdb_item_set_parent (parent, grandparent);
+ }
+
+ return parent;
+}
+
+static void
+dconf_rebuilder_insert (GHashTable *table,
+ const gchar *key,
+ GVariant *value)
+{
+ GvdbItem *item;
+ gchar *mykey;
+ gint length;
+
+ length = strlen (key);
+ mykey = g_alloca (length);
+ memcpy (mykey, key, length);
+
+ g_assert (g_hash_table_lookup (table, key) == NULL);
+ item = gvdb_hash_table_insert (table, key);
+
+ gvdb_item_set_parent (item,
+ dconf_rebuilder_get_parent (table, mykey, length));
+
+ gvdb_item_set_value (item, value);
+}
+
+static void
+dconf_rebuilder_put_item (DConfRebuilderState *state)
+{
+ if (state->values[state->index] != NULL)
+ {
+ gchar *fullname;
+ GVariant *ouch;
+
+ fullname = g_strconcat (state->prefix, state->keys[state->index], NULL);
+ ouch = g_variant_get_variant (state->values[state->index]);
+ dconf_rebuilder_insert (state->table, fullname, ouch);
+ g_variant_unref (ouch);
+ g_free (fullname);
+ }
+
+ state->index++;
+}
+
+static gboolean
+dconf_rebuilder_walk_name (DConfRebuilderState *state,
+ const gchar *name,
+ gsize name_len)
+{
+ gint cmp;
+
+ g_assert (state->name_len + name_len < sizeof state->name - 1);
+ memcpy (state->name + state->name_len, name, name_len);
+ state->name[state->name_len + name_len] = '\0';
+
+ if (state->index == state->n_items)
+ return TRUE;
+
+ if (state->name_len + name_len < state->prefix_len ||
+ memcmp (state->name, state->prefix, state->prefix_len) != 0)
+ return TRUE;
+
+ while ((cmp = strcmp (state->name + state->prefix_len,
+ state->keys[state->index])) > 0)
+ {
+ dconf_rebuilder_put_item (state);
+
+ if (state->index == state->n_items)
+ return TRUE;
+ }
+
+ return cmp != 0;
+}
+
+static void
+dconf_rebuilder_walk_value (const gchar *name,
+ gsize name_len,
+ GVariant *value,
+ gpointer user_data)
+{
+ DConfRebuilderState *state = user_data;
+
+ if (dconf_rebuilder_walk_name (state, name, name_len))
+ dconf_rebuilder_insert (state->table, state->name, value);
+
+ else
+ dconf_rebuilder_put_item (state);
+}
+
+static gboolean
+dconf_rebuilder_walk_open (const gchar *name,
+ gsize name_len,
+ gpointer user_data)
+{
+ DConfRebuilderState *state = user_data;
+
+ if (dconf_rebuilder_walk_name (state, name, name_len))
+ {
+ state->name_len += name_len;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+dconf_rebuilder_walk_close (gsize name_len,
+ gpointer user_data)
+{
+ DConfRebuilderState *state = user_data;
+
+ state->name_len -= name_len;
+}
+
+gboolean
+dconf_rebuilder_rebuild (const gchar *filename,
+ const gchar *prefix,
+ const gchar *const*keys,
+ GVariant *const*values,
+ int n_items,
+ GError **error)
+{
+ DConfRebuilderState state = { prefix, strlen (prefix),
+ 0, keys, values, n_items };
+ gboolean success;
+ GvdbTable *old;
+
+ state.table = gvdb_hash_table_new (NULL, NULL);
+
+ if ((old = gvdb_table_new (filename, FALSE, NULL)))
+ {
+ gvdb_table_walk (old, "/",
+ dconf_rebuilder_walk_open,
+ dconf_rebuilder_walk_value,
+ dconf_rebuilder_walk_close,
+ &state);
+ gvdb_table_unref (old);
+ }
+
+ while (state.index != state.n_items)
+ dconf_rebuilder_put_item (&state);
+
+ success = gvdb_table_write_contents (state.table, filename, FALSE, error);
+ g_hash_table_unref (state.table);
+
+ return success;
+}
diff --git a/service/dconf-rebuilder.h b/service/dconf-rebuilder.h
new file mode 100644
index 0000000..1aa06de
--- /dev/null
+++ b/service/dconf-rebuilder.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __dconf_rebuilder_h__
+#define __dconf_rebuilder_h__
+
+#include <glib.h>
+
+gboolean dconf_rebuilder_rebuild (const gchar *filename,
+ const gchar *prefix,
+ const gchar *const*keys,
+ GVariant *const*values,
+ gint n_items,
+ GError **error);
+
+#endif /* __dconf_rebuilder_h__ */
diff --git a/service/dconf-state.c b/service/dconf-state.c
new file mode 100644
index 0000000..0ed156e
--- /dev/null
+++ b/service/dconf-state.c
@@ -0,0 +1,98 @@
+#include "dconf-state.h"
+
+#include "dconf-shmdir.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+static void
+dconf_state_init_session (DConfState *state)
+{
+ const gchar *config_dir = g_get_user_config_dir ();
+
+ state->db_dir = g_build_filename (config_dir, "dconf", NULL);
+
+ if (g_mkdir_with_parents (state->db_dir, 0700))
+ {
+ /* XXX remove this after a while... */
+ if (errno == ENOTDIR)
+ {
+ gchar *tmp, *final;
+
+ g_message ("Attempting to migrate ~/.config/dconf "
+ "to ~/.config/dconf/user");
+
+ tmp = g_build_filename (config_dir, "dconf-user.db", NULL);
+
+ if (rename (state->db_dir, tmp))
+ g_error ("Can not rename '%s' to '%s': %s",
+ state->db_dir, tmp, g_strerror (errno));
+
+ if (g_mkdir_with_parents (state->db_dir, 0700))
+ g_error ("Can not create directory '%s': %s",
+ state->db_dir, g_strerror (errno));
+
+ final = g_build_filename (state->db_dir, "user", NULL);
+
+ if (rename (tmp, final))
+ g_error ("Can not rename '%s' to '%s': %s",
+ tmp, final, g_strerror (errno));
+
+ g_message ("Successful.");
+
+ g_free (final);
+ g_free (tmp);
+ }
+ else
+ g_error ("Can not create directory '%s': %s",
+ state->db_dir, g_strerror (errno));
+ }
+
+ state->shm_dir = dconf_shmdir_from_environment ();
+
+ if (state->shm_dir == NULL)
+ {
+ const gchar *tmpdir = g_get_tmp_dir ();
+ gchar *shmdir;
+
+ shmdir = g_build_filename (tmpdir, "dconf.XXXXXX", NULL);
+
+ if ((state->shm_dir = mkdtemp (shmdir)) == NULL)
+ g_error ("Can not create reasonable shm directory");
+ }
+}
+
+void
+dconf_state_init (DConfState *state)
+{
+ state->is_session = strcmp (g_get_user_name (), "dconf") != 0;
+ state->main_loop = g_main_loop_new (NULL, FALSE);
+ state->serial = 0;
+ state->id = NULL;
+
+ if (state->is_session)
+ dconf_state_init_session (state);
+}
+
+void
+dconf_state_destroy (DConfState *state)
+{
+ g_main_loop_unref (state->main_loop);
+}
+
+void
+dconf_state_set_id (DConfState *state,
+ const gchar *id)
+{
+ g_assert (state->id == NULL);
+ state->id = g_strdup (id);
+}
+
+gchar *
+dconf_state_get_tag (DConfState *state)
+{
+ return g_strdup_printf ("%"G_GUINT64_FORMAT"%s",
+ state->serial++, state->id);
+}
diff --git a/service/dconf-state.h b/service/dconf-state.h
new file mode 100644
index 0000000..8bbe612
--- /dev/null
+++ b/service/dconf-state.h
@@ -0,0 +1,22 @@
+#ifndef __dconf_state_h__
+#define __dconf_state_h__
+
+#include <glib.h>
+
+typedef struct
+{
+ gboolean is_session;
+ GMainLoop *main_loop;
+ guint64 serial;
+ gchar *db_dir;
+ gchar *shm_dir;
+ gchar *id;
+} DConfState;
+
+void dconf_state_init (DConfState *state);
+void dconf_state_set_id (DConfState *state,
+ const gchar *id);
+void dconf_state_destroy (DConfState *state);
+gchar * dconf_state_get_tag (DConfState *state);
+
+#endif /* __dconf_state_h__ */
diff --git a/service/dconf-writer.c b/service/dconf-writer.c
new file mode 100644
index 0000000..3b98efd
--- /dev/null
+++ b/service/dconf-writer.c
@@ -0,0 +1,165 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "dconf-writer.h"
+
+#include "dconf-rebuilder.h"
+#include "dconf-state.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+
+struct OPAQUE_TYPE__DConfWriter
+{
+ DConfState *state;
+ gchar *name;
+ gchar *path;
+ gchar *shm;
+};
+
+/* Each element must only contain the ASCII characters "[A-Z][a-z][0-9]_"
+ */
+static gboolean
+is_valid_dbus_path_element (const gchar *string)
+{
+ gint i;
+
+ for (i = 0; string[i]; i++)
+ if (!g_ascii_isalnum (string[i]) && string[i] != '_')
+ return FALSE;
+
+ return TRUE;
+}
+
+gchar **
+dconf_writer_list_existing (void)
+{
+ GPtrArray *array;
+ gchar *path;
+ GDir *dir;
+
+ path = g_build_filename (g_get_user_config_dir (), "dconf", NULL);
+ array = g_ptr_array_new ();
+
+ if ((dir = g_dir_open (path, 0, NULL)))
+ {
+ const gchar *name;
+
+ while ((name = g_dir_read_name (dir)))
+ if (is_valid_dbus_path_element (name))
+ g_ptr_array_add (array, g_strdup (name));
+ }
+
+ g_ptr_array_add (array, NULL);
+ g_free (path);
+
+ return (gchar **) g_ptr_array_free (array, FALSE);
+}
+
+static void
+dconf_writer_touch_shm (DConfWriter *writer)
+{
+ gchar one = 1;
+ gint fd;
+
+ fd = open (writer->shm, O_WRONLY);
+
+ if (fd >= 0)
+ {
+ write (fd, &one, sizeof one);
+ close (fd);
+
+ unlink (writer->shm);
+ }
+
+ else if (errno != ENOENT)
+ unlink (writer->shm);
+}
+
+gboolean
+dconf_writer_write (DConfWriter *writer,
+ const gchar *name,
+ GVariant *value,
+ GError **error)
+{
+ if (!dconf_rebuilder_rebuild (writer->path, "", &name, &value, 1, error))
+ return FALSE;
+
+ dconf_writer_touch_shm (writer);
+
+ return TRUE;
+}
+
+gboolean
+dconf_writer_write_many (DConfWriter *writer,
+ const gchar *prefix,
+ const gchar * const *keys,
+ GVariant * const *values,
+ gsize n_items,
+ GError **error)
+{
+ if (!dconf_rebuilder_rebuild (writer->path, prefix, keys,
+ values, n_items, error))
+ return FALSE;
+
+ dconf_writer_touch_shm (writer);
+
+ return TRUE;
+}
+
+gboolean
+dconf_writer_set_lock (DConfWriter *writer,
+ const gchar *name,
+ gboolean locked,
+ GError **error)
+{
+ return TRUE;
+}
+
+const gchar *
+dconf_writer_get_name (DConfWriter *writer)
+{
+ return writer->name;
+}
+
+DConfState *
+dconf_writer_get_state (DConfWriter *writer)
+{
+ return writer->state;
+}
+
+DConfWriter *
+dconf_writer_new (DConfState *state,
+ const gchar *name)
+{
+ DConfWriter *writer;
+
+ writer = g_slice_new (DConfWriter);
+ writer->state = state;
+ writer->path = g_build_filename (state->db_dir, name, NULL);
+ writer->shm = g_build_filename (state->shm_dir, name, NULL);
+ writer->name = g_strdup (name);
+
+ return writer;
+}
diff --git a/service/dconf-writer.h b/service/dconf-writer.h
new file mode 100644
index 0000000..4c2e6b3
--- /dev/null
+++ b/service/dconf-writer.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __dconf_writer_h__
+#define __dconf_writer_h__
+
+#include "dconf-state.h"
+
+typedef struct OPAQUE_TYPE__DConfWriter DConfWriter;
+
+gchar ** dconf_writer_list_existing (void);
+DConfWriter * dconf_writer_new (DConfState *state,
+ const gchar *name);
+DConfState * dconf_writer_get_state (DConfWriter *writer);
+const gchar * dconf_writer_get_name (DConfWriter *writer);
+gboolean dconf_writer_write (DConfWriter *writer,
+ const gchar *name,
+ GVariant *value,
+ GError **error);
+gboolean dconf_writer_write_many (DConfWriter *writer,
+ const gchar *prefix,
+ const gchar * const *keys,
+ GVariant * const *values,
+ gsize n_items,
+ GError **error);
+gboolean dconf_writer_set_lock (DConfWriter *writer,
+ const gchar *name,
+ gboolean locked,
+ GError **error);
+
+#endif /* __dconf_writer_h__ */
diff --git a/service/service.c b/service/service.c
new file mode 100644
index 0000000..54418d1
--- /dev/null
+++ b/service/service.c
@@ -0,0 +1,433 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include <gio/gio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "dconf-interfaces.h"
+#include "dconf-writer.h"
+#include "dconf-state.h"
+
+static void
+emit_notify_signal (GDBusConnection *connection,
+ DConfWriter *writer,
+ const gchar *tag,
+ const gchar *prefix,
+ const gchar **keys,
+ guint n_keys)
+{
+ GVariantBuilder builder;
+ GVariant *items;
+ gchar *path;
+ gchar *obj;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
+
+ if (n_keys > 1)
+ {
+ const gchar *last_reset = NULL;
+ gint last_reset_len;
+ gint i;
+
+ for (i = 0; i < n_keys; i++)
+ {
+ gint length = strlen (keys[i]);
+
+ if (last_reset && length > last_reset_len &&
+ memcmp (last_reset, keys[i], last_reset_len) == 0)
+ continue;
+
+ if (length == 0 || keys[i][length - 1] == '/')
+ {
+ last_reset_len = length;
+ last_reset = keys[i];
+ }
+ else
+ {
+ if (last_reset != NULL)
+ {
+ g_variant_builder_add (&builder, "s", last_reset);
+ last_reset = NULL;
+ }
+
+ g_variant_builder_add (&builder, "s", keys[i]);
+ }
+ }
+ }
+
+ items = g_variant_builder_end (&builder);
+
+ if (g_variant_n_children (items) == 0)
+ path = g_strconcat (prefix, keys[0], NULL);
+ else
+ path = g_strdup (prefix);
+
+ obj = g_strjoin (NULL, "/ca/desrt/dconf/Writer/",
+ dconf_writer_get_name (writer), NULL);
+ g_dbus_connection_emit_signal (connection, NULL, obj,
+ "ca.desrt.dconf.Writer", "Notify",
+ g_variant_new ("(s@ass)",
+ path, items, tag),
+ NULL);
+ g_free (path);
+ g_free (obj);
+}
+
+static void
+unwrap_maybe (GVariant **ptr)
+{
+ GVariant *array, *child;
+
+ array = *ptr;
+
+ if (g_variant_n_children (array))
+ child = g_variant_get_child_value (array, 0);
+ else
+ child = NULL;
+
+ g_variant_unref (array);
+ *ptr = child;
+}
+
+static void
+method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ DConfWriter *writer = user_data;
+ DConfState *state;
+
+ state = dconf_writer_get_state (writer);
+
+ if (strcmp (method_name, "Write") == 0)
+ {
+ GError *error = NULL;
+ GVariant *keyvalue;
+ const gchar *key;
+ gsize key_length;
+ GVariant *value;
+ GVariant *none;
+ gchar *path;
+ gchar *tag;
+
+ g_variant_get (parameters, "(@s@av)", &keyvalue, &value);
+ key = g_variant_get_string (keyvalue, &key_length);
+ g_variant_unref (keyvalue);
+ unwrap_maybe (&value);
+
+ if (key[0] != '/' || strstr (key, "//"))
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "invalid key: %s", key);
+ if (value != NULL)
+ g_variant_unref (value);
+
+ return;
+ }
+
+ if (key[key_length - 1] == '/' && value != NULL)
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "can not set value to path");
+ if (value != NULL)
+ g_variant_unref (value);
+ return;
+ }
+
+ if (!dconf_writer_write (writer, key, value, &error))
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ if (value != NULL)
+ g_variant_unref (value);
+ g_error_free (error);
+ return;
+ }
+
+ tag = dconf_state_get_tag (state);
+ g_dbus_method_invocation_return_value (invocation,
+ g_variant_new ("(s)", tag));
+ none = g_variant_new_array (G_VARIANT_TYPE_STRING, NULL, 0);
+ path = g_strjoin (NULL, "/ca/desrt/dconf/Writer/",
+ dconf_writer_get_name (writer), NULL);
+ g_dbus_connection_emit_signal (connection, NULL, path,
+ "ca.desrt.dconf.Writer", "Notify",
+ g_variant_new ("(s@ass)",
+ key, none, tag),
+ NULL);
+ if (value != NULL)
+ g_variant_unref (value);
+ g_free (path);
+ g_free (tag);
+ }
+
+ else if (strcmp (method_name, "WriteMany") == 0)
+ {
+ GError *error = NULL;
+ const gchar *prefix;
+ GVariantIter *iter;
+ const gchar **keys;
+ GVariant **values;
+ gsize length;
+ gchar *tag;
+ gint i = 0;
+ gint j;
+
+ g_variant_get (parameters, "(&sa(sav))", &prefix, &iter);
+ length = g_variant_iter_n_children (iter);
+
+ keys = g_new (const gchar *, length + 1);
+ values = g_new (GVariant *, length);
+ while (g_variant_iter_next (iter, "(&s@av)", &keys[i], &values[i]))
+ {
+ unwrap_maybe (&values[i]);
+
+ if (keys[i][0] == '/' || strstr (keys[i], "//") ||
+ (i > 0 && !(strcmp (keys[i - 1], keys[i]) < 0)))
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "invalid key list");
+
+ for (j = 0; j <= i; j++)
+ if (values[j] != NULL)
+ g_variant_unref (values[j]);
+
+ g_free (values);
+ g_free (keys);
+
+ return;
+ }
+
+ i++;
+ }
+ g_variant_iter_free (iter);
+ keys[i] = NULL;
+
+ if (!dconf_writer_write_many (writer, prefix, keys, values, i, &error))
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ g_error_free (error);
+ return;
+ }
+
+ tag = dconf_state_get_tag (state);
+ g_dbus_method_invocation_return_value (invocation,
+ g_variant_new ("(s)", tag));
+ emit_notify_signal (connection, writer, tag, prefix, keys, i);
+
+ for (j = 0; j < i; j++)
+ if (values[j] != NULL)
+ g_variant_unref (values[j]);
+
+ g_free (values);
+ g_free (keys);
+ g_free (tag);
+ }
+
+ else if (strcmp (method_name, "SetLocked") == 0)
+ {
+ GError *error = NULL;
+ const gchar *name;
+ gboolean locked;
+
+ g_variant_get (parameters, "(&sb)", &name, &locked);
+
+ if (!dconf_writer_set_lock (writer, name, locked, &error))
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ g_error_free (error);
+ return;
+ }
+
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ }
+
+ else
+ g_assert_not_reached ();
+}
+
+static GVariant *
+writer_info_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ DConfState *state = user_data;
+
+ return g_variant_new_string (state->shm_dir);
+}
+
+static const GDBusInterfaceVTable *
+subtree_dispatch (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *node,
+ gpointer *out_user_data,
+ gpointer user_data)
+{
+ DConfState *state = user_data;
+
+ if (strcmp (interface_name, "ca.desrt.dconf.Writer") == 0)
+ {
+ static const GDBusInterfaceVTable vtable = {
+ method_call, NULL, NULL
+ };
+ static GHashTable *writer_table;
+ DConfWriter *writer;
+
+ if (node == NULL)
+ return NULL;
+
+ if G_UNLIKELY (writer_table == NULL)
+ writer_table = g_hash_table_new (g_str_hash, g_str_equal);
+
+ writer = g_hash_table_lookup (writer_table, node);
+
+ if G_UNLIKELY (writer == NULL)
+ {
+ writer = dconf_writer_new (state, node);
+ g_hash_table_insert (writer_table, g_strdup (node), writer);
+ }
+
+ *out_user_data = writer;
+
+ return &vtable;
+ }
+
+ else if (strcmp (interface_name, "ca.desrt.dconf.WriterInfo") == 0)
+ {
+ static const GDBusInterfaceVTable vtable = {
+ NULL, writer_info_get_property, NULL
+ };
+
+ *out_user_data = state;
+ return &vtable;
+ }
+
+ else
+ return NULL;
+}
+
+static gchar **
+subtree_enumerate (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ gpointer user_data)
+{
+ return dconf_writer_list_existing ();
+}
+
+static GDBusInterfaceInfo **
+subtree_introspect (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *node,
+ gpointer user_data)
+{
+ /* The root node supports only the info iface */
+ if (node == NULL)
+ {
+ const GDBusInterfaceInfo *interfaces[] = {
+ &ca_desrt_dconf_WriterInfo, NULL
+ };
+
+ return g_memdup (interfaces, sizeof interfaces);
+ }
+ else
+ {
+ const GDBusInterfaceInfo *interfaces[] = {
+ &ca_desrt_dconf_WriterInfo, &ca_desrt_dconf_Writer, NULL
+ };
+
+ return g_memdup (interfaces, sizeof interfaces);
+ }
+}
+
+static void
+bus_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ static GDBusSubtreeVTable vtable = {
+ subtree_enumerate, subtree_introspect, subtree_dispatch
+ };
+ DConfState *state = user_data;
+ GDBusSubtreeFlags flags;
+
+ dconf_state_set_id (state, g_dbus_connection_get_unique_name (connection));
+
+ flags = G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES;
+ g_dbus_connection_register_subtree (connection, "/ca/desrt/dconf/Writer",
+ &vtable, flags, state, NULL, NULL);
+}
+
+static void
+name_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+}
+
+static void
+name_lost (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ fprintf (stderr, "unable to acquire name: '%s'", name);
+ exit (1);
+}
+
+int
+main (void)
+{
+ DConfState state;
+ GBusType type;
+
+ g_type_init ();
+ dconf_state_init (&state);
+
+ if (state.is_session)
+ type = G_BUS_TYPE_SESSION;
+ else
+ type = G_BUS_TYPE_SYSTEM;
+
+ g_bus_own_name (type, "ca.desrt.dconf", G_BUS_NAME_OWNER_FLAGS_NONE,
+ bus_acquired, name_acquired, name_lost, &state, NULL);
+
+ g_main_loop_run (state.main_loop);
+
+ dconf_state_destroy (&state);
+
+ return 0;
+}
diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644
index 0000000..e4f23e5
--- /dev/null
+++ b/tests/.gitignore
@@ -0,0 +1,3 @@
+paths
+gsettings
+dbus1
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..3b66beb
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,11 @@
+AM_CFLAGS = -std=c89 -Wall -Wmissing-prototypes -Wwrite-strings
+INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/engine -I$(top_srcdir)/client $(gio_CFLAGS) -I$(top_srcdir)/dbus-1 $(dbus_CFLAGS)
+
+noinst_PROGRAMS = paths gsettings dbus1
+
+gsettings_LDADD = $(gio_LIBS)
+dbus1_LDADD = -L../dbus-1 -ldconf-dbus-1 $(glib_LIBS)
+paths_LDADD = $(gio_LIBS)
+paths_SOURCES = \
+ ../common/dconf-paths.c \
+ paths.c
diff --git a/tests/dbus1.c b/tests/dbus1.c
new file mode 100644
index 0000000..1220160
--- /dev/null
+++ b/tests/dbus1.c
@@ -0,0 +1,349 @@
+/**
+ * Copyright © 2010 Canonical Limited
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the licence, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ **/
+
+#include <dconf-dbus-1.h>
+
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+
+static DConfDBusClient *backend;
+
+static void
+free_variant (gpointer data)
+{
+ if (data != NULL)
+ g_variant_unref (data);
+}
+
+static GVariant *
+do_read (const gchar *key)
+{
+ return dconf_dbus_client_read (backend, key);
+}
+
+static gboolean
+do_write (const gchar *key,
+ GVariant *value)
+{
+ return dconf_dbus_client_write (backend, key, value);
+}
+
+static gboolean
+do_write_tree (GTree *tree)
+{
+ g_assert_not_reached ();
+}
+
+static void
+do_sync (void)
+{
+/* g_assert_not_reached (); */
+}
+
+#define RANDOM_ELEMENT(array) \
+ array[g_test_rand_int_range(0, G_N_ELEMENTS(array))]
+
+static gchar *
+random_key (void)
+{
+ const gchar * const words[] = {
+ "alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf",
+ "hotel", "india", "juliet", "kilo", "lima", "mike", "november",
+ "oscar", "papa", "quebec", "romeo", "sierra", "tango", "uniform",
+ "victor", "whiskey", "xray", "yankee", "zulu"
+ };
+ const gchar *parts[8];
+ gint n, i;
+
+ n = g_test_rand_int_range (2, 8);
+ parts[0] = "";
+ for (i = 1; i < n; i++)
+ parts[i] = RANDOM_ELEMENT (words);
+ parts[n] = NULL;
+
+ return g_strjoinv ("/", (gchar **) parts);
+}
+
+static GVariant *
+random_value (void)
+{
+ switch (g_test_rand_int_range (0, 3))
+ {
+ case 0:
+ return g_variant_new_int32 (g_test_rand_int ());
+
+ case 1:
+ return g_variant_new_boolean (g_test_rand_bit ());
+
+ case 2:
+ {
+ gint length = g_test_rand_int_range (0, 24);
+ gchar buffer[24];
+ gint i;
+
+ for (i = 0; i < length; i++)
+ buffer[i] = 'a' + g_test_rand_int_range (0, 26);
+ buffer[i] = '\0';
+
+ return g_variant_new_string (buffer);
+ }
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static GTree *
+random_tree (void)
+{
+ GTree *tree;
+ gint n;
+
+ tree = g_tree_new_full ((GCompareDataFunc) strcmp, NULL,
+ g_free, free_variant);
+ n = g_test_rand_int_range (1, 20);
+
+ while (n--)
+ g_tree_insert (tree, random_key (), g_variant_ref_sink (random_value ()));
+
+ return tree;
+}
+
+static void
+apply_change (GHashTable *table,
+ const gchar *key,
+ GVariant *value)
+{
+ if (value)
+ g_hash_table_insert (table, g_strdup (key), g_variant_ref_sink (value));
+ else
+ g_hash_table_insert (table, g_strdup (key), NULL);
+}
+
+static gboolean
+apply_one_change (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ apply_change (user_data, key, value);
+ return FALSE;
+}
+
+static void
+apply_change_tree (GHashTable *table,
+ GTree *tree)
+{
+ g_tree_foreach (tree, apply_one_change, table);
+}
+
+static GHashTable *implicit;
+static GHashTable *explicit;
+
+static void
+watch_func (DConfDBusClient *client,
+ const gchar *key,
+ gpointer user_data)
+{
+ GVariant *value;
+
+ /* ensure that we see no dupes from the bus */
+/* g_assert (origin_tag == do_write); */
+ g_assert (client == backend);
+
+ value = do_read (key);
+ apply_change (implicit, key, value);
+ g_variant_unref (value);
+}
+
+static void
+setup (void)
+{
+ gchar *file;
+
+ file = g_build_filename (g_get_user_config_dir (),
+ "dconf/test", NULL);
+ unlink (file);
+ g_free (file);
+
+ g_setenv ("DCONF_PROFILE", "test", false);
+
+ backend = dconf_dbus_client_new ("test", NULL, NULL);
+ dconf_dbus_client_subscribe (backend, "/", watch_func, NULL);
+
+ implicit = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, free_variant);
+ explicit = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, free_variant);
+
+ sleep(1);
+}
+
+static void
+make_random_change (void)
+{
+ if (1)
+ {
+ GVariant *value;
+ gchar *key;
+
+ key = random_key ();
+ value = random_value ();
+ apply_change (explicit, key, value);
+ do_write (key, value);
+
+ g_free (key);
+ }
+ else
+ {
+ GTree *tree;
+
+ tree = random_tree ();
+ apply_change_tree (explicit, tree);
+ do_write_tree (tree);
+
+ g_tree_unref (tree);
+ }
+}
+
+guint64 dconf_time;
+guint64 ghash_time;
+guint64 lookups;
+gboolean dots;
+
+static void
+verify_consistency (void)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+
+ if (dots)
+ g_print (".");
+ else
+ g_print ("(%d)", g_hash_table_size (explicit));
+
+ g_assert (g_hash_table_size (explicit) == g_hash_table_size (implicit));
+ g_hash_table_iter_init (&iter, explicit);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ if (value)
+ {
+ GVariant *other;
+
+ ghash_time -= g_get_monotonic_time ();
+ other = g_hash_table_lookup (implicit, key);
+ ghash_time += g_get_monotonic_time ();
+ g_assert (g_variant_equal (value, other));
+
+
+ dconf_time -= g_get_monotonic_time ();
+ other = do_read (key);
+ dconf_time += g_get_monotonic_time ();
+ g_assert (g_variant_equal (value, other));
+ g_variant_unref (other);
+ }
+ else
+ {
+ g_assert (g_hash_table_lookup (implicit, key) == NULL);
+ g_assert (do_read (key) == NULL);
+ }
+
+ lookups++;
+ }
+}
+
+#if 0
+static void
+dump_table (void)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_print ("{");
+ g_hash_table_iter_init (&iter, explicit);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ if (value)
+ {
+ gchar *printed;
+
+ if (value)
+ printed = g_variant_print (value, FALSE);
+ else
+ printed = g_strdup ("None");
+
+ g_print ("'%s': %s, ", (gchar *) key, printed);
+ g_free (printed);
+ }
+ g_print ("}");
+}
+#endif
+
+static void
+test (void)
+{
+ int i;
+
+ g_print ("Testing dconf...");
+ for (i = 0; i < 1000; i++)
+ {
+ g_print (" %d", i);
+ make_random_change ();
+ verify_consistency ();
+ }
+
+ g_print ("\n");
+ g_print ("GSettings lookup time: %f µs/lookup\n",
+ ((double) dconf_time / lookups));
+ g_print ("GHashTable lookup time: %f µs/lookup\n",
+ ((double) ghash_time / lookups));
+
+ dconf_time = 0;
+ ghash_time = 0;
+ lookups = 0;
+
+ g_print ("\nWaiting for dconf-service to catch up...");
+ do_sync ();
+ g_print (" done.\n");
+
+ g_print ("Measuring dconf read performance...");
+ dots = TRUE;
+ for (i = 0; i < 1000; i++)
+ verify_consistency ();
+ g_print ("\n");
+
+ g_print ("dconf lookup time: %f µs/lookup\n",
+ ((double) dconf_time / lookups));
+ g_print ("GHashTable lookup time: %f µs/lookup\n",
+ ((double) ghash_time / lookups));
+
+ g_hash_table_unref (explicit);
+ g_hash_table_unref (implicit);
+}
+
+int
+main (int argc, char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+
+ setup ();
+
+ test ();
+
+ return g_test_run ();
+}
diff --git a/tests/gsettings.c b/tests/gsettings.c
new file mode 100644
index 0000000..5695fa7
--- /dev/null
+++ b/tests/gsettings.c
@@ -0,0 +1,409 @@
+/**
+ * Copyright © 2010 Canonical Limited
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the licence, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ **/
+
+#define G_SETTINGS_ENABLE_BACKEND
+#include <gio/gsettingsbackend.h>
+#include <gio/gio.h>
+
+#include <stdbool.h>
+#include <string.h>
+
+static GSettingsBackend *backend;
+
+static void
+free_variant (gpointer data)
+{
+ if (data != NULL)
+ g_variant_unref (data);
+}
+
+static GVariant *
+do_read (const gchar *key)
+{
+ return G_SETTINGS_BACKEND_GET_CLASS (backend)
+ ->read (backend, key, NULL, FALSE);
+}
+
+static gboolean
+do_write (const gchar *key,
+ GVariant *value)
+{
+ return G_SETTINGS_BACKEND_GET_CLASS (backend)
+ ->write (backend, key, value, do_write);
+}
+
+static gboolean
+do_write_tree (GTree *tree)
+{
+ return G_SETTINGS_BACKEND_GET_CLASS (backend)
+ ->write_tree (backend, tree, do_write);
+}
+
+static void
+do_sync (void)
+{
+ return G_SETTINGS_BACKEND_GET_CLASS (backend)
+ ->sync (backend);
+}
+
+#define RANDOM_ELEMENT(array) \
+ array[g_test_rand_int_range(0, G_N_ELEMENTS(array))]
+
+static gchar *
+random_key (void)
+{
+ const gchar * const words[] = {
+ "alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf",
+ "hotel", "india", "juliet", "kilo", "lima", "mike", "november",
+ "oscar", "papa", "quebec", "romeo", "sierra", "tango", "uniform",
+ "victor", "whiskey", "xray", "yankee", "zulu"
+ };
+ const gchar *parts[8];
+ gint n, i;
+
+ n = g_test_rand_int_range (2, 8);
+ parts[0] = "";
+ for (i = 1; i < n; i++)
+ parts[i] = RANDOM_ELEMENT (words);
+ parts[n] = NULL;
+
+ return g_strjoinv ("/", (gchar **) parts);
+}
+
+static GVariant *
+random_value (void)
+{
+ switch (g_test_rand_int_range (0, 3))
+ {
+ case 0:
+ return g_variant_new_int32 (g_test_rand_int ());
+
+ case 1:
+ return g_variant_new_boolean (g_test_rand_bit ());
+
+ case 2:
+ {
+ gint length = g_test_rand_int_range (0, 24);
+ gchar buffer[24];
+ gint i;
+
+ for (i = 0; i < length; i++)
+ buffer[i] = 'a' + g_test_rand_int_range (0, 26);
+ buffer[i] = '\0';
+
+ return g_variant_new_string (buffer);
+ }
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static GTree *
+random_tree (void)
+{
+ GTree *tree;
+ gint n;
+
+ tree = g_tree_new_full ((GCompareDataFunc) strcmp, NULL,
+ g_free, free_variant);
+ n = g_test_rand_int_range (1, 20);
+
+ while (n--)
+ g_tree_insert (tree, random_key (), g_variant_ref_sink (random_value ()));
+
+ return tree;
+}
+
+static void
+apply_change (GHashTable *table,
+ const gchar *key,
+ GVariant *value)
+{
+ if (value)
+ g_hash_table_insert (table, g_strdup (key), g_variant_ref_sink (value));
+ else
+ g_hash_table_insert (table, g_strdup (key), NULL);
+}
+
+static gboolean
+apply_one_change (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ apply_change (user_data, key, value);
+ return FALSE;
+}
+
+static void
+apply_change_tree (GHashTable *table,
+ GTree *tree)
+{
+ g_tree_foreach (tree, apply_one_change, table);
+}
+
+static GHashTable *implicit;
+static GHashTable *explicit;
+
+/* interpose */
+void
+g_settings_backend_changed (GSettingsBackend *backend_,
+ const gchar *key,
+ gpointer origin_tag)
+{
+ GVariant *value;
+
+ /* ensure that we see no dupes from the bus */
+ g_assert (origin_tag == do_write);
+ g_assert (backend == backend_);
+
+ value = do_read (key);
+ apply_change (implicit, key, value);
+ g_variant_unref (value);
+}
+
+/* interpose */
+void
+g_settings_backend_keys_changed (GSettingsBackend *backend_,
+ const gchar *path,
+ const gchar * const *items,
+ gpointer origin_tag)
+{
+ gint i;
+
+ /* ensure that we see no dupes from the bus */
+ g_assert (origin_tag == do_write);
+ g_assert (backend == backend_);
+
+ for (i = 0; items[i]; i++)
+ {
+ GVariant *value;
+ gchar *key;
+
+ key = g_strconcat (path, items[i], NULL);
+ value = do_read (key);
+
+ apply_change (implicit, key, value);
+
+ g_variant_unref (value);
+ g_free (key);
+ }
+}
+
+/* interpose */
+void
+g_settings_backend_changed_tree (GSettingsBackend *backend_,
+ GTree *tree,
+ gpointer origin_tag)
+{
+ const gchar **keys;
+ gchar *path;
+
+ g_settings_backend_flatten_tree (tree, &path, &keys, NULL);
+ g_settings_backend_keys_changed (backend_, path, keys, origin_tag);
+}
+
+static void
+setup (void)
+{
+ extern void _g_io_modules_ensure_loaded (void);
+ GIOExtensionPoint *point;
+ GIOExtension *extension;
+ GType extension_type;
+ gchar *file;
+
+ file = g_build_filename (g_get_user_config_dir (),
+ "dconf/test", NULL);
+ unlink (file);
+ g_free (file);
+
+ g_setenv ("DCONF_PROFILE", "test", false);
+
+ g_type_init ();
+
+ /* Cause GIO modules to be loaded... */
+ g_object_unref (g_file_new_for_path ("."));
+
+ point = g_io_extension_point_lookup ("gsettings-backend");
+ extension = g_io_extension_point_get_extension_by_name (point, "dconf");
+ extension_type = g_io_extension_get_type (extension);
+ backend = g_object_new (extension_type, NULL);
+
+ G_SETTINGS_BACKEND_GET_CLASS (backend)
+ ->subscribe (backend, "/");
+
+ implicit = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, free_variant);
+ explicit = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, free_variant);
+
+ sleep(1);
+}
+
+static void
+make_random_change (void)
+{
+ if (g_test_rand_bit ())
+ {
+ GVariant *value;
+ gchar *key;
+
+ key = random_key ();
+ value = random_value ();
+ apply_change (explicit, key, value);
+ do_write (key, value);
+
+ g_free (key);
+ }
+ else
+ {
+ GTree *tree;
+
+ tree = random_tree ();
+ apply_change_tree (explicit, tree);
+ do_write_tree (tree);
+
+ g_tree_unref (tree);
+ }
+}
+
+guint64 dconf_time;
+guint64 ghash_time;
+guint64 lookups;
+gboolean dots;
+
+static void
+verify_consistency (void)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+
+ if (dots)
+ g_print (".");
+ else
+ g_print ("(%d)", g_hash_table_size (implicit));
+
+ g_assert (g_hash_table_size (explicit) == g_hash_table_size (implicit));
+ g_hash_table_iter_init (&iter, explicit);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ if (value)
+ {
+ GVariant *other;
+
+ ghash_time -= g_get_monotonic_time ();
+ other = g_hash_table_lookup (implicit, key);
+ ghash_time += g_get_monotonic_time ();
+ g_assert (g_variant_equal (value, other));
+
+ dconf_time -= g_get_monotonic_time ();
+ other = do_read (key);
+ dconf_time += g_get_monotonic_time ();
+ g_assert (g_variant_equal (value, other));
+ g_variant_unref (other);
+ }
+ else
+ {
+ g_assert (g_hash_table_lookup (implicit, key) == NULL);
+ g_assert (do_read (key) == NULL);
+ }
+
+ lookups++;
+ }
+}
+
+#if 0
+static void
+dump_table (void)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_print ("{");
+ g_hash_table_iter_init (&iter, explicit);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ if (value)
+ {
+ gchar *printed;
+
+ if (value)
+ printed = g_variant_print (value, FALSE);
+ else
+ printed = g_strdup ("None");
+
+ g_print ("'%s': %s, ", (gchar *) key, printed);
+ g_free (printed);
+ }
+ g_print ("}");
+}
+#endif
+
+static void
+test (void)
+{
+ int i;
+
+ g_print ("Testing dconf...");
+ for (i = 0; i < 1000; i++)
+ {
+ g_print (" %d", i);
+ make_random_change ();
+ verify_consistency ();
+ }
+
+ g_print ("\n");
+ g_print ("GSettings lookup time: %f µs/lookup\n",
+ ((double) dconf_time / lookups));
+ g_print ("GHashTable lookup time: %f µs/lookup\n",
+ ((double) ghash_time / lookups));
+
+ dconf_time = 0;
+ ghash_time = 0;
+ lookups = 0;
+
+ g_print ("\nWaiting for dconf-service to catch up...");
+ do_sync ();
+ g_print (" done.\n");
+
+ g_print ("Measuring dconf read performance...");
+ dots = TRUE;
+ for (i = 0; i < 1000; i++)
+ verify_consistency ();
+ g_print ("\n");
+
+ g_print ("dconf lookup time: %f µs/lookup\n",
+ ((double) dconf_time / lookups));
+ g_print ("GHashTable lookup time: %f µs/lookup\n",
+ ((double) ghash_time / lookups));
+
+ g_hash_table_unref (explicit);
+ g_hash_table_unref (implicit);
+}
+
+int
+main (int argc, char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+
+ setup ();
+
+ test ();
+
+ return g_test_run ();
+}
diff --git a/tests/paths.c b/tests/paths.c
new file mode 100644
index 0000000..a95530e
--- /dev/null
+++ b/tests/paths.c
@@ -0,0 +1,100 @@
+#include <dconf-paths.h>
+
+static void
+test_paths (void)
+{
+ struct test_case {
+ const gchar *string;
+ guint flags;
+ } cases[] = {
+
+#define invalid 0
+#define path 001
+#define key 002 | path
+#define dir 004 | path
+#define rel 010
+#define relkey 020 | rel
+#define reldir 040 | rel
+
+ { "", reldir },
+ { "/", dir },
+
+ { "/key", key },
+ { "/path/", dir },
+ { "/path/key", key },
+ { "/path/path/", dir },
+ { "/a/b", key },
+ { "/a/b/", dir },
+
+ { "//key", invalid },
+ { "//path/", invalid },
+ { "//path/key", invalid },
+ { "//path/path/", invalid },
+ { "//a/b", invalid },
+ { "//a/b/", invalid },
+
+ { "/key", key },
+ { "/path//", invalid },
+ { "/path/key", key },
+ { "/path/path//", invalid },
+ { "/a/b", key },
+ { "/a/b//", invalid },
+
+ { "/key", key },
+ { "/path/", dir },
+ { "/path//key", invalid },
+ { "/path//path/", invalid },
+ { "/a//b", invalid },
+ { "/a//b/", invalid },
+
+ { "key", relkey },
+ { "path/", reldir },
+ { "path/key", relkey },
+ { "path/path/", reldir },
+ { "a/b", relkey },
+ { "a/b/", reldir },
+
+ { "key", relkey },
+ { "path//", invalid },
+ { "path/key", relkey },
+ { "path/path//", invalid },
+ { "a/b", relkey },
+ { "a/b//", invalid },
+
+ { "key", relkey },
+ { "path/", reldir },
+ { "path//key", invalid },
+ { "path//path/", invalid },
+ { "a//b", invalid },
+ { "a//b/", invalid }
+ };
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (cases); i++)
+ {
+ const gchar *string = cases[i].string;
+ guint flags;
+
+ flags = (dconf_is_path (string, NULL) ? 001 : 000) |
+ (dconf_is_key (string, NULL) ? 002 : 000) |
+ (dconf_is_dir (string, NULL) ? 004 : 000) |
+ (dconf_is_rel (string, NULL) ? 010 : 000) |
+ (dconf_is_rel_key (string, NULL) ? 020 : 000) |
+ (dconf_is_rel_dir (string, NULL) ? 040 : 000);
+
+ if (flags != cases[i].flags)
+ {
+ g_print ("case %i: string '%s' should be %o but is %o",
+ i, string, cases[i].flags, flags);
+ g_assert_not_reached ();
+ }
+ }
+}
+
+int
+main (void)
+{
+ test_paths ();
+
+ return 0;
+}