diff options
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 @@ -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 @@ -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[] = { + ¬ify_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; +} |