pax_global_header00006660000000000000000000000064147536162710014526gustar00rootroot0000000000000052 comment=014ed4ce881fc3154c08e98e8d59c1b13c3f2f06 menu-cache-1.1.1/000077500000000000000000000000001475361627100135335ustar00rootroot00000000000000menu-cache-1.1.1/.gitignore000066400000000000000000000014121475361627100155210ustar00rootroot00000000000000Makefile.in aclocal.m4 compile config.h.in configure depcomp install-sh ltmain.sh */Makefile.in */Makefile */*.o */*/*.o */*/*/*.o */.deps/ */*/.deps/ */*/*/.deps/ */.libs/ */*/.libs/ */*/*/.libs/ */*.lo */*/*.lo */*/*/*.lo */*.la */*/*.la */*/*/*.la */*.a */*/*.a */*/*/*.a m4/ missing po/Makefile.in.in po/.intltool-merge-cache config.guess config.h config.h.in~ config.log config.status config.sub libtool gtk-doc.make man/*.1 po/LINGUAS po/Makefile po/POTFILES po/*.gmo po/stamp-it stamp-h1 *.stamp Makefile intltool-extract.in intltool-merge.in intltool-update.in src/xml-purge libmenu-cache/libmenu-cache.pc libmenu-cache/menu-cache.h menu-cache-daemon/menu-cached menu-cache-gen/menu-cache-gen docs/reference/libmenu-cache/libmenu-cache* docs/reference/libmenu-cache/*/* menu-cache-1.1.1/AUTHORS000066400000000000000000000004421475361627100146030ustar00rootroot00000000000000Upstream Authors: Hong Jen Yee (PCMan) Jürgen Hötzel Andriy Grytsenko (LStranger) Copyright: LXDE team: http://lxde.org License: LGPL-2.1+ The full text of the license can be found in the 'COPYING' file. menu-cache-1.1.1/COPYING000066400000000000000000000576331475361627100146040ustar00rootroot00000000000000 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 menu-cache-1.1.1/ChangeLog000066400000000000000000000000001475361627100152730ustar00rootroot00000000000000menu-cache-1.1.1/INSTALL000066400000000000000000000224501475361627100145670ustar00rootroot00000000000000Installation Instructions ************************* Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, 2006, 2007 Free Software Foundation, Inc. This file is free documentation; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. Basic Installation ================== Briefly, the shell commands `./configure; make; make install' should configure, build, and install this package. The following more-detailed instructions are generic; see the `README' file for instructions specific to this package. The `configure' shell script attempts to guess correct values for various system-dependent variables used during compilation. It uses those values to create a `Makefile' in each directory of the package. It may also create one or more `.h' files containing system-dependent definitions. Finally, it creates a shell script `config.status' that you can run in the future to recreate the current configuration, and a file `config.log' containing compiler output (useful mainly for debugging `configure'). It can also use an optional file (typically called `config.cache' and enabled with `--cache-file=config.cache' or simply `-C') that saves the results of its tests to speed up reconfiguring. Caching is disabled by default to prevent problems with accidental use of stale cache files. If you need to do unusual things to compile the package, please try to figure out how `configure' could check whether to do them, and mail diffs or instructions to the address given in the `README' so they can be considered for the next release. If you are using the cache, and at some point `config.cache' contains results you don't want to keep, you may remove or edit it. The file `configure.ac' (or `configure.in') is used to create `configure' by a program called `autoconf'. You need `configure.ac' if you want to change it or regenerate `configure' using a newer version of `autoconf'. The simplest way to compile this package is: 1. `cd' to the directory containing the package's source code and type `./configure' to configure the package for your system. Running `configure' might take a while. While running, it prints some messages telling which features it is checking for. 2. Type `make' to compile the package. 3. Optionally, type `make check' to run any self-tests that come with the package. 4. Type `make install' to install the programs and any data files and documentation. 5. You can remove the program binaries and object files from the source code directory by typing `make clean'. To also remove the files that `configure' created (so you can compile the package for a different kind of computer), type `make distclean'. There is also a `make maintainer-clean' target, but that is intended mainly for the package's developers. If you use it, you may have to get all sorts of other programs in order to regenerate files that came with the distribution. 6. Often, you can also type `make uninstall' to remove the installed files again. Compilers and Options ===================== Some systems require unusual options for compilation or linking that the `configure' script does not know about. Run `./configure --help' for details on some of the pertinent environment variables. You can give `configure' initial values for configuration parameters by setting variables in the command line or in the environment. Here is an example: ./configure CC=c99 CFLAGS=-g LIBS=-lposix *Note Defining Variables::, for more details. Compiling For Multiple Architectures ==================================== You can compile the package for more than one kind of computer at the same time, by placing the object files for each architecture in their own directory. To do this, you can use GNU `make'. `cd' to the directory where you want the object files and executables to go and run the `configure' script. `configure' automatically checks for the source code in the directory that `configure' is in and in `..'. With a non-GNU `make', it is safer to compile the package for one architecture at a time in the source code directory. After you have installed the package for one architecture, use `make distclean' before reconfiguring for another architecture. Installation Names ================== By default, `make install' installs the package's commands under `/usr/local/bin', include files under `/usr/local/include', etc. You can specify an installation prefix other than `/usr/local' by giving `configure' the option `--prefix=PREFIX'. You can specify separate installation prefixes for architecture-specific files and architecture-independent files. If you pass the option `--exec-prefix=PREFIX' to `configure', the package uses PREFIX as the prefix for installing programs and libraries. Documentation and other data files still use the regular prefix. In addition, if you use an unusual directory layout you can give options like `--bindir=DIR' to specify different values for particular kinds of files. Run `configure --help' for a list of the directories you can set and what kinds of files go in them. If the package supports it, you can cause programs to be installed with an extra prefix or suffix on their names by giving `configure' the option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. Optional Features ================= Some packages pay attention to `--enable-FEATURE' options to `configure', where FEATURE indicates an optional part of the package. They may also pay attention to `--with-PACKAGE' options, where PACKAGE is something like `gnu-as' or `x' (for the X Window System). The `README' should mention any `--enable-' and `--with-' options that the package recognizes. For packages that use the X Window System, `configure' can usually find the X include and library files automatically, but if it doesn't, you can use the `configure' options `--x-includes=DIR' and `--x-libraries=DIR' to specify their locations. Specifying the System Type ========================== There may be some features `configure' cannot figure out automatically, but needs to determine by the type of machine the package will run on. Usually, assuming the package is built to be run on the _same_ architectures, `configure' can figure that out, but if it prints a message saying it cannot guess the machine type, give it the `--build=TYPE' option. TYPE can either be a short name for the system type, such as `sun4', or a canonical name which has the form: CPU-COMPANY-SYSTEM where SYSTEM can have one of these forms: OS KERNEL-OS See the file `config.sub' for the possible values of each field. If `config.sub' isn't included in this package, then this package doesn't need to know the machine type. If you are _building_ compiler tools for cross-compiling, you should use the option `--target=TYPE' to select the type of system they will produce code for. If you want to _use_ a cross compiler, that generates code for a platform different from the build platform, you should specify the "host" platform (i.e., that on which the generated programs will eventually be run) with `--host=TYPE'. Sharing Defaults ================ If you want to set default values for `configure' scripts to share, you can create a site shell script called `config.site' that gives default values for variables like `CC', `cache_file', and `prefix'. `configure' looks for `PREFIX/share/config.site' if it exists, then `PREFIX/etc/config.site' if it exists. Or, you can set the `CONFIG_SITE' environment variable to the location of the site script. A warning: not all `configure' scripts look for a site script. Defining Variables ================== Variables not defined in a site shell script can be set in the environment passed to `configure'. However, some packages may run configure again during the build, and the customized values of these variables may be lost. In order to avoid this problem, you should set them in the `configure' command line, using `VAR=value'. For example: ./configure CC=/usr/local2/bin/gcc causes the specified `gcc' to be used as the C compiler (unless it is overridden in the site shell script). Unfortunately, this technique does not work for `CONFIG_SHELL' due to an Autoconf bug. Until the bug is fixed you can use this workaround: CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash `configure' Invocation ====================== `configure' recognizes the following options to control how it operates. `--help' `-h' Print a summary of the options to `configure', and exit. `--version' `-V' Print the version of Autoconf used to generate the `configure' script, and exit. `--cache-file=FILE' Enable the cache: use and save the results of the tests in FILE, traditionally `config.cache'. FILE defaults to `/dev/null' to disable caching. `--config-cache' `-C' Alias for `--cache-file=config.cache'. `--quiet' `--silent' `-q' Do not print messages saying which checks are being made. To suppress all normal output, redirect it to `/dev/null' (any error messages will still be shown). `--srcdir=DIR' Look for the package's source code in directory DIR. Usually `configure' can determine that directory automatically. `configure' also accepts some other, not widely useful, options. Run `configure --help' for more details. menu-cache-1.1.1/Makefile.am000066400000000000000000000012521475361627100155670ustar00rootroot00000000000000## Process this file with automake to produce Makefile.in ACLOCAL_AMFLAGS= -I m4 NULL = SUBDIRS_DOCS = docs EXTRA_DIST_DOCS = \ gtk-doc.make \ docs/Makefile.am \ docs/Makefile.in \ docs/reference/Makefile.am \ docs/reference/Makefile.in \ docs/reference/libmenu-cache/Makefile.am \ docs/reference/libmenu-cache/Makefile.in \ $(NULL) ALL_SUBDIRS = \ libmenu-cache \ menu-cache-gen \ menu-cache-daemon \ $(NULL) SUBDIRS = $(ALL_SUBDIRS) DIST_SUBDIRS = $(ALL_SUBDIRS) EXTRA_DIST = \ $(NULL) if ENABLE_GTK_DOC DIST_SUBDIRS += $(SUBDIRS_DOCS) SUBDIRS += $(SUBDIRS_DOCS) else EXTRA_DIST += $(EXTRA_DIST_DOCS) endif DISTCHECK_CONFIGURE_FLAGS=--enable-gtk-doc menu-cache-1.1.1/NEWS000066400000000000000000000157721475361627100142460ustar00rootroot00000000000000Changes in 1.1.1 since 1.1.0: * Fixed memory leaks. * GCC 10 compilation support. * Removed unused libmenu-cache-uninstalled.pc.in. Changes in 1.1.0 since 1.0.2: * Fixed crash with invalid tag in a menu. * Added new API menu_cache_app_get_generic_name() to get generic name for application. * Fixed potential access violation, use runtime user dir instead of tmp dir. It limits libmenu-cache compatibility to menu-cached >= 0.7.0. * Directory $XDG_DATA_HOME/applications will be created if it does not exist so it will be monitored in any case. * Fixed issue when subdirectories added would be skipped in monitoring. * Fixed potential file descriptors leak. * Reduced inactivity timer to 6 seconds (from 600 seconds). * Fixed an issue with multiple daemons started: test if daemon is already running on socket before killing old socket file. * Fixed 100% CPU load by menu-cached due to invalid dup2() call. Changes in 1.0.2 since 1.0.1: * Fixed crash in menu-cached if cache regeneration fails. * Fixed 100% CPU load by menu-cached in some rare conditions. * Invalid empty should be ignored, see specification. * Fixed crash in menu-cache-gen on if no file to merge found. * Fixed showing empty Other menu in some cases. * The option --disable-debug is now default, instead of --enable-debug. Changes in 1.0.1 since 1.0.0: * Fixed crash on generating menu with both tags and present. * Fixed crash when menu-cache-gen ran manually without CACHE_GEN_VERSION. * Fixed crash in menu-cache-gen on in menu layout with tag present. * Fixed incorrect processing of in menu-cache-gen. * Added safeguards against environment variables containing newlines. * Fixed case when cache was not updated while it should, it was claimed to be fixed in 1.0.0 but apparently fix was incomplete. * Fixed ignored tags inside of tag. * Fixed memory corruption in scanning addressed directory. Changes in 1.0.0 since 0.7.0: * Added new cache file format generation support with changes: - invisible directories (NoDisplay=true or empty) can be put into the cache too but with flag (not displayed) set; - content of TryExec field is added to contents of cache file; - the working dir to execute application is added to cache file; - list of categories is added to contents of cache file; - list of keywords is added to contents of cache file. * Fixed crash in menu_cache_item_get_file_dirname() for a non-existent file (might happen for directories without .directory file). * Made menu_cache_app_get_working_dir() actually work. * Made menu_cache_lookup() faster (do not load cache immediately but on idle instead). * Eliminated secondary cache reload in menu_cache_lookup_sync() - server response in such case will be ahead of idle reload (since main thread is in wait ATM) and therefore idle call will be supressed. * Added new API menu_cache_app_get_categories() to get list of categories for the application. * Added new APIs for applications: menu_cache_list_all_for_category() and menu_cache_list_all_for_keyword() that return list of applications matching criteria. * Fixed problem if some string in the desktop entry file contained a new line character. That broke cache file format, now it's replaced with a "\n" string and converted back into new line in the library. * Fixed bug when cache was not updated while it should: check if the last modification time for directory is more recent than modification time for cache may not always be valid - some .desktop entry might be just changed by update and that will lead to falsed cache contents. * Added Log Domain "Menu-Cache" for better library logging messages. Changes in 0.7.0 since 0.6.1: * Added bit of support for multiple supported cache file versions, using CACHE_GEN_VERSION environment variable to the generator. This may be useful in future when 1.2 cache file version will be implemented. * The menu-cache-gen libexec binary is rewritten from scratch. No that Red Hat / GNOME code anymore. New menu-cache-gen uses libfm-extra XML manipulation functions therefore it is required now for build. * Added a parameter for menu-cached to specify socket path instead of calculating one, that is definitely more safe. * Libmenu-cache handles menu-cached failure more gracefully now, don't tries to restart it so fast that it clones many times. * Fixed menu-cached crash after menu-cache-gen failure. Changes in 0.6.1 since 0.6.0: * Fixed invalid memory access after cache reload. * A little cleanup of dist tarball: removed unused files menu-cache.h and libmenu-cache-uninstalled.pc.in. Changes in 0.6.0 since 0.5.1: * Fixed few GLIB compatibility issues. * Changed default tarball format to XZ instead of GZIP. * Fixed build without --enable-gtk-doc. * Fixed crash on access root_dir with empty cache (no menu). * Allowed menu_cache_get_desktop_env_flag() accept colon-separated list in accordance to freedesktop.org specification for the environment variable XDG_CURRENT_DESKTOP. * The case if user deleted cache file is handled: it will be regenerated. Changes in 0.5.1 since 0.5.0: * Fixed build on systems where MAXSYMLINKS isn't defined. * Fixed menu-cached crash in some rare cases. Changes in 0.5.0 since 0.4.1: * Added a possibility to include NoDisplay files into cache file. This can be achieved by adding suffix '+hidden' to requested name in call to menu_cache_lookup(). The hidden items will be returned along with visible ones by any API that returns listing of cache directory. The menu_cache_app_get_is_visible() API will return FALSE for hidden item with any DE mask passed to the API. * New macro MENU_CACHE_CHECK_VERSION() to test version of library. * Added new APIs: menu_cache_find_item_by_id, menu_cache_find_child_by_id, menu_cache_find_child_by_name. * Two bugfixes for crashes, and for some another bugs. Changes in 0.4.1 since 0.4.0: * Minor bugfix, the resulting tar file was not complete. Changes in 0.4.0 since 0.3.3: * The libmenu-cache is made thread-safe. Thread-unsafe APIs are marked as deprecated now and should be never used in any multithreaded application. * Added creation of HTML developers documentation. It is triggered by configure script option --enable-gtk-doc. * The libmenu-cache is made more responsible by moving some time-critical operations into thread. Also it preloads saved cache file if it exists so it is available near instantly after menu_cache_lookup(). Caller still will get updates by adding notifier to the cache. * Added automatic shutdown of menu-cached server after some inactivity timeout (i.e. all clients were unregistered). * Fixed few memory problems (referencing errors and memory leaks). * Fix for bug #3501347: use g_get_tmp_dir() instead of hardcoded "/tmp". * The libmenu-cache ABI bumped to 2. menu-cache-1.1.1/README000066400000000000000000000075201475361627100144170ustar00rootroot00000000000000Libmenu-cache is a library creating and utilizing caches to speed up the manipulation for freedesktop.org defined application menus. It can be used as a replacement of libgnome-menu of gnome-menus. Advantages: 1. Shorten time for loading menu entries. 2. Ease of use. (API is very similar to that of libgnome-menu) 3. Lightweight runtime library. (Parsing of the menu definition files are done by menu-cache-gen when the menus are really changed.) 4. Less unnecessary and complicated file monitoring. 5. Heavily reduced disk I/O. Installing: Since version 0.7.0 the Libmenu-cache requires Libfm-extra for the menu-cache-gen menu cache generation binary. Since Libfm depends on Libmenu-cache, there is some hint for bootstrapers how to build those libraries together: you need create Libfm-extra first, you can easily do this by passing '--with-extra-only' option to configure script and installing Libfm-extra. Then you can succesfully build Libmenu-cache and therefore build full version of Libfm. Diagnostics: Libmenu-cache uses cache generation in "fail-proof" mode when it will ignore improper tags in XML menu file but fail only if that menu file doesn't exist at all, or has invalid XML structure, or has no root menu with name 'Applications'. If you want to explore how it is generated then you can start menu-cache-gen manually in verbose mode, for example: G_MESSAGES_DEBUG=all \ /usr/lib/menu-cache/menu-cache-gen -v -i applications.menu -o /dev/null so you can see all the processing of your XML file and it will fail on any broken tag or at least show you a warning and you can inspect that log. Spec: Cached menus are localized and stored in ~/.cache/menus/file_name. file_name is a md5 hash of the name of the original menu + some environment variable + locale name. Since most data in a menu are plain text (names, description comments, icon names,...etc.), the cached file is in plain text rather than binary to prevent byte order problems. Format of the cached menu file, line by line. (Spaces before the lines are for easier demostration of hierarchy. There are no spaces in the real cached file. Fields marked with [**] added only in the file format 1.2): version number (major.minor) menu name> number of files to monitor list of files/dirs which require monitor (prefix D or F indicate whether it is a dir or file) list of DE names used in this menu other than the known DEs (LXDE, GNOME, XFCE, KDE, ROX), seperated by ; +id of top menu dir (+ means dir entry, and - means an application entry) title comment icon name menu directory file basename dir in which menu directory file locates (use this number as index to get the string from array of preceding list of monitored files) [**] bitmask flags: (--)|(--)|(not displayed) -application desktop id (NOTE: desktop id is not necessarily the basename of desktop file) title comment icon name app desktop file basename, empty means the same as desktop id. dir in which the desktop file localtes (use this number as index to get the string from array of preceding list of monitored files) generic name exec command line bitmask flags: (use terminal)|(use startup notification)|(not displayed) show_in_flags: bitwise or of masks for various DEs. [**] executable name to test for availability [**] working dir for exec [**] categories: list divided by semicolon [**] keywords: list divided by comma +sub dir id title comment icon name ... menu-cache-1.1.1/autogen.sh000077500000000000000000000022531475361627100155360ustar00rootroot00000000000000#! /bin/sh AC_VERSION= test -n "$SRC_DIR" || SRC_DIR=$(dirname "$0") test -n "$SRC_DIR" || SRC_DIR=. OLD_DIR=$(pwd) cd "$SRC_DIR" AUTOMAKE=${AUTOMAKE:-automake} AM_INSTALLED_VERSION=$($AUTOMAKE --version | sed -e '2,$ d' -e 's/.* \([0-9]*\.[0-9]*\).*/\1/') # FIXME: we need a better way for version check later. case "$AM_INSTALLED_VERSION" in 1.1[0-9]) ;; *) echo echo "You must have automake 1.10 or newer installed." echo "Install the appropriate package for your distribution," echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/" exit 1 ;; esac if [ "x${ACLOCAL_DIR}" != "x" ]; then ACLOCAL_ARG="-I ${ACLOCAL_DIR}" fi test -d m4 || mkdir m4 if gtkdocize --copy; then echo "Files needed by gtk-doc are generated." else echo "You need gtk-doc to build this package." echo "http://www.gtk.org/gtk-doc/" exit 1 fi set -x ${ACLOCAL:-aclocal$AM_VERSION} ${ACLOCAL_ARG} ${AUTOHEADER:-autoheader$AC_VERSION} --force AUTOMAKE=$AUTOMAKE libtoolize -c --automake --force $AUTOMAKE --add-missing --copy --include-deps ${AUTOCONF:-autoconf$AC_VERSION} rm -rf autom4te.cache if test -n "$DOCONFIGURE"; then ./configure $@ fi cd "$OLD_DIR" menu-cache-1.1.1/configure.ac000066400000000000000000000057311475361627100160270ustar00rootroot00000000000000dnl Process this file with autoconf to produce a configure script. AC_INIT([menu-cache], [1.1.1], [http://lxde.org/]) AC_CONFIG_MACRO_DIR([m4]) AM_INIT_AUTOMAKE([no-dist-gzip dist-xz]) AM_CONFIG_HEADER(config.h) # Support silent build rules. Disable by either passing --disable-silent-rules # to configure or passing V=1 to make AM_SILENT_RULES([yes]) AM_MAINTAINER_MODE([enable]) AC_ISC_POSIX AC_PROG_CC AM_PROG_CC_C_O AC_STDC_HEADERS dnl AC_ARG_PROGRAM AM_PROG_LIBTOOL dnl make sure we keep ACLOCAL_FLAGS around for maintainer builds to work AC_SUBST(ACLOCAL_AMFLAGS, "$ACLOCAL_FLAGS") PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.18.0 gio-2.0) AC_SUBST(GLIB_CFLAGS) AC_SUBST(GLIB_LIBS) PKG_CHECK_MODULES(LIBFM_EXTRA, libfm-extra) AC_SUBST(LIBFM_EXTRA_CFLAGS) AC_SUBST(LIBFM_EXTRA_LIBS) AC_ARG_ENABLE(more_warnings, [AC_HELP_STRING([--enable-more-warnings], [Add more warnings @<:@default=no@:>@])], [enable_more_warnings="${enableval}"], [enable_more_warnings=no] ) if test x"$enable_more_warnings" = x"yes"; then ADDITIONAL_FLAGS="-Wall -Wextra -Werror -Wno-missing-field-initializers -Wno-unused-parameter -Wmissing-declarations -Wredundant-decls -Wmissing-noreturn -Wpointer-arith -Wcast-align -Wwrite-strings -Werror=inline -Wformat-nonliteral -Wformat-security -Winit-self -Wmissing-include-dirs -Wundef -Waggregate-return -Wmissing-format-attribute -Wnested-externs -fno-strict-aliasing -fmessage-length=0 -Wp,-D_FORTIFY_SOURCE=2 -DG_DISABLE_DEPRECATED -DG_DISABLE_SINGLE_INCLUDES -DGDK_DISABLE_DEPRECATED -DGDK_PIXBUF_DISABLE_DEPRECATED -DGDK_PIXBUF_DISABLE_SINGLE_INCLUDES -DGTK_DISABLE_DEPRECATED -DGTK_DISABLE_SINGLE_INCLUDES" fi AC_SUBST(ADDITIONAL_FLAGS) dnl --enable-debug=(yes|minimum|no) AC_ARG_ENABLE(debug, [ --enable-debug=[no/yes] turn on debugging [[default=no]]],,enable_debug=no) if test "$enable_debug" = "yes"; then DEBUG_CFLAGS="-DG_ENABLE_DEBUG" else if test "x$enable_debug" = "xno"; then DEBUG_CFLAGS="-DG_DISABLE_ASSERT -DG_DISABLE_CHECKS" else DEBUG_CFLAGS="" fi fi AC_SUBST(DEBUG_CFLAGS) dnl check for gtk-doc m4_ifdef([GTK_DOC_CHECK], [ GTK_DOC_CHECK([1.14],[--flavour no-tmpl]) ],[ AM_CONDITIONAL([ENABLE_GTK_DOC], false) ]) dnl Make version subfields for MENU_CACHE_CHECK_VERSION macro ac_version_subst_str=`echo $VERSION | awk -F '.' '/.*/ { printf "MC_VERSION_MAJOR=%d MC_VERSION_MINOR=%d MC_VERSION_MICRO=%d", $1, $2, $3 }'` eval ${ac_version_subst_str} AC_SUBST(MC_VERSION_MAJOR) AC_SUBST(MC_VERSION_MINOR) AC_SUBST(MC_VERSION_MICRO) exec_prefix_save="$exec_prefix" test "x$exec_prefix" = xNONE && exec_prefix="${prefix}" if test `eval echo "$libdir"` = '/usr/lib' then PCFILE_LIBDIR= else PCFILE_LIBDIR=' -L${libdir}' fi exec_prefix="$exec_prefix_save" AC_SUBST(PCFILE_LIBDIR) AC_OUTPUT([ Makefile libmenu-cache/Makefile libmenu-cache/libmenu-cache.pc libmenu-cache/menu-cache.h menu-cache-gen/Makefile menu-cache-daemon/Makefile docs/Makefile docs/reference/Makefile docs/reference/libmenu-cache/Makefile ]) menu-cache-1.1.1/docs/000077500000000000000000000000001475361627100144635ustar00rootroot00000000000000menu-cache-1.1.1/docs/Makefile.am000066400000000000000000000000241475361627100165130ustar00rootroot00000000000000SUBDIRS = reference menu-cache-1.1.1/docs/reference/000077500000000000000000000000001475361627100164215ustar00rootroot00000000000000menu-cache-1.1.1/docs/reference/Makefile.am000066400000000000000000000000301475361627100204460ustar00rootroot00000000000000SUBDIRS = libmenu-cache menu-cache-1.1.1/docs/reference/libmenu-cache/000077500000000000000000000000001475361627100211155ustar00rootroot00000000000000menu-cache-1.1.1/docs/reference/libmenu-cache/Makefile.am000066400000000000000000000074211475361627100231550ustar00rootroot00000000000000## Process this file with automake to produce Makefile.in # We require automake 1.6 at least. AUTOMAKE_OPTIONS = 1.6 # This is a blank Makefile.am for using gtk-doc. # Copy this to your project's API docs directory and modify the variables to # suit your project. See the GTK+ Makefiles in gtk+/docs/reference for examples # of using the various options. # The name of the module, e.g. 'glib'. DOC_MODULE=libmenu-cache # Uncomment for versioned docs and specify the version of the module, e.g. '2'. #DOC_MODULE_VERSION=2 # The top-level SGML file. You can change this if you want to. DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.sgml # Directories containing the source code. # gtk-doc will search all .c and .h files beneath these paths # for inline comments documenting functions and macros. # e.g. DOC_SOURCE_DIR=$(top_srcdir)/gtk $(top_srcdir)/gdk DOC_SOURCE_DIR=$(top_srcdir)/libmenu-cache # Extra options to pass to gtkdoc-scangobj. Not normally needed. SCANGOBJ_OPTIONS= # Extra options to supply to gtkdoc-scan. # e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED" SCAN_OPTIONS=--rebuild-sections --rebuild-types --deprecated-guards="G_DISABLE_DEPRECATED" # Extra options to supply to gtkdoc-mkdb. # e.g. MKDB_OPTIONS=--xml-mode --output-format=xml MKDB_OPTIONS=--xml-mode --output-format=xml # Extra options to supply to gtkdoc-mktmpl # e.g. MKTMPL_OPTIONS=--only-section-tmpl MKTMPL_OPTIONS= # Extra options to supply to gtkdoc-mkhtml MKHTML_OPTIONS= # Extra options to supply to gtkdoc-fixref. Not normally needed. # e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html FIXXREF_OPTIONS= # Used for dependencies. The docs will be rebuilt if any of these change. # e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h # e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c HFILE_GLOB=$(top_srcdir)/libmenu-cache/*.h CFILE_GLOB=$(top_srcdir)/libmenu-cache/*.c # Extra header to include when scanning, which are not under DOC_SOURCE_DIR # e.g. EXTRA_HFILES=$(top_srcdir}/contrib/extra.h EXTRA_HFILES= # Header files or dirs to ignore when scanning. Use base file/dir names # e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h private_code IGNORE_HFILES=version.h # Images to copy into HTML directory. # e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png HTML_IMAGES= # Extra SGML files that are included by $(DOC_MAIN_SGML_FILE). # e.g. content_files=running.sgml building.sgml changes-2.0.sgml content_files= # SGML files where gtk-doc abbrevations (#GtkWidget) are expanded # These files must be listed here *and* in content_files # e.g. expand_content_files=running.sgml expand_content_files= # CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library. # Only needed if you are using gtkdoc-scangobj to dynamically query widget # signals and properties. # e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS) # e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib) GTKDOC_CFLAGS= GTKDOC_LIBS=$(top_builddir)/libmenu-cache/libmenu-cache.la # This includes the standard gtk-doc make rules, copied by gtkdocize. include $(top_srcdir)/gtk-doc.make # Other files to distribute # e.g. EXTRA_DIST += version.xml.in EXTRA_DIST += # Files not to distribute # for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types # for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt DISTCLEANFILES = $(DOC_MODULE).types $(DOC_MODULE)-sections.txt # Fix parallel build - we need this file built before continuing $(DOC_MAIN_SGML_FILE): sgml-build.stamp # Comment this out if you want 'make check' to test you doc status # and run some sanity checks if ENABLE_GTK_DOC TESTS_ENVIRONMENT = cd $(srcdir) && \ DOC_MODULE=$(DOC_MODULE) DOC_MAIN_SGML_FILE=$(DOC_MAIN_SGML_FILE) \ SRCDIR=$(abs_srcdir) BUILDDIR=$(abs_builddir) #TESTS = $(GTKDOC_CHECK) endif -include $(top_srcdir)/git.mk menu-cache-1.1.1/libmenu-cache/000077500000000000000000000000001475361627100162275ustar00rootroot00000000000000menu-cache-1.1.1/libmenu-cache/Makefile.am000066400000000000000000000013601475361627100202630ustar00rootroot00000000000000NULL = AM_CPPFLAGS = \ $(GLIB_CFLAGS) \ $(DEBUG_CFLAGS) \ $(ADDITIONAL_FLAGS) \ -Werror-implicit-function-declaration \ -DMENUCACHE_LIBEXECDIR="\"$(pkglibexecdir)\"" \ -DG_LOG_DOMAIN=\"Menu-Cache\" \ $(NULL) lib_LTLIBRARIES = libmenu-cache.la libmenu_cache_la_SOURCES = \ menu-cache.c \ $(NULL) libmenu_cache_la_LIBADD = \ $(GLIB_LIBS) \ $(NULL) libmenu_cache_la_LDFLAGS = \ -no-undefined \ -export-symbols-regex menu_cache \ -version-info 5:1:2 \ $(NULL) lib_menu_cache_includedir = $(includedir)/menu-cache nodist_lib_menu_cache_include_HEADERS = \ menu-cache.h \ $(NULL) EXTRA_DIST = \ version.h \ libmenu-cache.pc.in \ $(NULL) pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libmenu-cache.pc menu-cache-1.1.1/libmenu-cache/libmenu-cache.pc.in000066400000000000000000000004031475361627100216510ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: libmenu-cache Description: Cache for freedesktop.org menu spec Requires: glib-2.0 Version: @VERSION@ Libs:@PCFILE_LIBDIR@ -lmenu-cache Cflags: -I${includedir}/menu-cache menu-cache-1.1.1/libmenu-cache/menu-cache.c000066400000000000000000001717151475361627100204140ustar00rootroot00000000000000/* * menu-cache.c * * Copyright 2008 PCMan * Copyright 2009 Jürgen Hötzel * Copyright 2012-2017 Andriy Grytsenko (LStranger) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include #endif #include "version.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "menu-cache.h" #ifdef G_ENABLE_DEBUG #define DEBUG(...) g_debug(__VA_ARGS__) #else #define DEBUG(...) #endif #if GLIB_CHECK_VERSION(2, 32, 0) static GRecMutex _cache_lock; # define MENU_CACHE_LOCK g_rec_mutex_lock(&_cache_lock) # define MENU_CACHE_UNLOCK g_rec_mutex_unlock(&_cache_lock) /* for sync lookup */ static GMutex sync_run_mutex; static GCond sync_run_cond; #define SET_CACHE_READY(_cache_) do { \ g_mutex_lock(&sync_run_mutex); \ _cache_->ready = TRUE; \ g_cond_broadcast(&sync_run_cond); \ g_mutex_unlock(&sync_run_mutex); } while(0) #else /* before 2.32 GLib had another entity for statically allocated mutexes */ static GStaticRecMutex _cache_lock = G_STATIC_REC_MUTEX_INIT; # define MENU_CACHE_LOCK g_static_rec_mutex_lock(&_cache_lock) # define MENU_CACHE_UNLOCK g_static_rec_mutex_unlock(&_cache_lock) /* for sync lookup */ static GMutex *sync_run_mutex = NULL; static GCond *sync_run_cond = NULL; #define SET_CACHE_READY(_cache_) do { \ g_mutex_lock(sync_run_mutex); \ _cache_->ready = TRUE; \ if(sync_run_cond) g_cond_broadcast(sync_run_cond); \ g_mutex_unlock(sync_run_mutex); } while(0) #endif typedef struct { char *dir; gint n_ref; } MenuCacheFileDir; struct _MenuCacheItem { guint n_ref; MenuCacheType type; char* id; char* name; char* comment; char* icon; MenuCacheFileDir* file_dir; char* file_name; MenuCacheDir* parent; }; struct _MenuCacheDir { MenuCacheItem item; GSList* children; guint32 flags; }; struct _MenuCacheApp { MenuCacheItem item; char* generic_name; char* exec; char* working_dir; guint32 show_in_flags; guint32 flags; char* try_exec; const char **categories; char* keywords; }; struct _MenuCache { guint n_ref; MenuCacheDir* root_dir; char* menu_name; char* reg; /* includes md5 sum */ char* md5; /* link inside of reg */ char* cache_file; char** known_des; GSList* notifiers; GThread* thr; GCancellable* cancellable; guint version; guint reload_id; gboolean ready : 1; /* used for sync access */ }; static int server_fd = -1; G_LOCK_DEFINE(connect); /* for server_fd */ static GHashTable* hash = NULL; /* Don't call this API directly. Use menu_cache_lookup instead. */ static MenuCache* menu_cache_new( const char* cache_file ); static gboolean connect_server(GCancellable* cancellable); static gboolean register_menu_to_server(MenuCache* cache); static void unregister_menu_from_server( MenuCache* cache ); /* keep them for backward compatibility */ #ifdef G_DISABLE_DEPRECATED MenuCacheDir* menu_cache_get_root_dir( MenuCache* cache ); MenuCacheDir* menu_cache_item_get_parent( MenuCacheItem* item ); MenuCacheDir* menu_cache_get_dir_from_path( MenuCache* cache, const char* path ); GSList* menu_cache_dir_get_children( MenuCacheDir* dir ); #endif void menu_cache_init(int flags) { #if !GLIB_CHECK_VERSION(2, 36, 0) g_type_init(); #endif } static MenuCacheItem* read_item(GDataInputStream* f, MenuCache* cache, MenuCacheFileDir** all_used_files, int n_all_used_files); /* functions read_dir(), read_app(), and read_item() should be called for items that aren't accessible yet, therefore no lock is required */ static void read_dir(GDataInputStream* f, MenuCacheDir* dir, MenuCache* cache, MenuCacheFileDir** all_used_files, int n_all_used_files) { MenuCacheItem* item; char *line; gsize len; /* nodisplay flag */ if (cache->version >= 2) { line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if (G_UNLIKELY(line == NULL)) return; dir->flags = (guint32)atoi(line); g_free(line); } /* load child items in the dir */ while( (item = read_item( f, cache, all_used_files, n_all_used_files )) ) { /* menu_cache_ref shouldn't be called here for dir. * Otherwise, circular reference will happen. */ item->parent = dir; dir->children = g_slist_prepend( dir->children, item ); } dir->children = g_slist_reverse( dir->children ); /* set flag by children if working with old cache generator */ if (cache->version == 1) { if (dir->children == NULL) dir->flags = FLAG_IS_NODISPLAY; else if ((line = menu_cache_item_get_file_path(MENU_CACHE_ITEM(dir))) != NULL) { GKeyFile *kf = g_key_file_new(); if (g_key_file_load_from_file(kf, line, G_KEY_FILE_NONE, NULL) && g_key_file_get_boolean(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, NULL)) dir->flags = FLAG_IS_NODISPLAY; g_key_file_free(kf); g_free(line); } } } static char *_unescape_lf(char *str) { char *c, *p = str; gsize len = 0; while ((c = strchr(p, '\\')) != NULL) { if (p != &str[len]) memmove(&str[len], p, c - p); len += (c - p); if (c[1] == 'n') { str[len++] = '\n'; c++; } else if (c != &str[len]) str[len++] = *c; p = &c[1]; } if (p != &str[len]) memmove(&str[len], p, strlen(p) + 1); return str; } static void read_app(GDataInputStream* f, MenuCacheApp* app, MenuCache* cache) { char *line; gsize len; GString *str; /* generic name */ line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if(G_UNLIKELY(line == NULL)) return; if(G_LIKELY(len > 0)) app->generic_name = _unescape_lf(line); else g_free(line); /* exec */ line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if(G_UNLIKELY(line == NULL)) return; if(G_LIKELY(len > 0)) app->exec = _unescape_lf(line); else g_free(line); /* terminal / startup notify */ line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if(G_UNLIKELY(line == NULL)) return; app->flags = (guint32)atoi(line); g_free(line); /* ShowIn flags */ line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if(G_UNLIKELY(line == NULL)) return; app->show_in_flags = (guint32)atol(line); g_free(line); if (cache->version < 2) return; /* TryExec */ line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if (G_UNLIKELY(line == NULL)) return; if (G_LIKELY(len > 0)) app->try_exec = g_strchomp(line); else g_free(line); /* Path */ line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if (G_UNLIKELY(line == NULL)) return; if (G_LIKELY(len > 0)) app->working_dir = line; else g_free(line); /* Categories */ line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if (G_UNLIKELY(line == NULL)) return; if (G_LIKELY(len > 0)) { const char **x; /* split and intern all the strings so categories can be processed later for search doing g_quark_try_string()+g_quark_to_string() */ app->categories = x = (const char **)g_strsplit(line, ";", 0); while (*x != NULL) { char *cat = (char *)*x; *x = g_intern_string(cat); g_free(cat); x++; } } g_free(line); /* Keywords */ str = g_string_new(MENU_CACHE_ITEM(app)->name); if (G_LIKELY(app->exec != NULL)) { char *sp = strchr(app->exec, ' '); char *bn = strrchr(app->exec, G_DIR_SEPARATOR); g_string_append_c(str, ','); if (bn == NULL && sp == NULL) g_string_append(str, app->exec); else if (bn == NULL || (sp != NULL && sp < bn)) g_string_append_len(str, app->exec, sp - app->exec); else if (sp == NULL) g_string_append(str, &bn[1]); else g_string_append_len(str, &bn[1], sp - &bn[1]); } if (app->generic_name != NULL) { g_string_append_c(str, ','); g_string_append(str, app->generic_name); } line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if (G_UNLIKELY(line == NULL)) return; if (len > 0) { g_string_append_c(str, ','); g_string_append(str, line); } app->keywords = g_utf8_casefold(str->str, str->len); g_string_free(str, TRUE); g_free(line); } static MenuCacheItem* read_item(GDataInputStream* f, MenuCache* cache, MenuCacheFileDir** all_used_files, int n_all_used_files) { MenuCacheItem* item; char *line; gsize len; gint idx; /* desktop/menu id */ line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if(G_UNLIKELY(line == NULL)) return NULL; if( G_LIKELY(len >= 1) ) { if( line[0] == '+' ) /* menu dir */ { item = (MenuCacheItem*)g_slice_new0( MenuCacheDir ); item->n_ref = 1; item->type = MENU_CACHE_TYPE_DIR; } else if( line[0] == '-' ) /* menu item */ { item = (MenuCacheItem*)g_slice_new0( MenuCacheApp ); item->n_ref = 1; if( G_LIKELY( len > 1 ) ) /* application item */ item->type = MENU_CACHE_TYPE_APP; else /* separator */ { item->type = MENU_CACHE_TYPE_SEP; g_free(line); return item; } } else { g_free(line); return NULL; } item->id = g_strndup( line + 1, len - 1 ); g_free(line); } else { g_free(line); return NULL; } /* name */ line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if(G_UNLIKELY(line == NULL)) goto _fail; if(G_LIKELY(len > 0)) item->name = _unescape_lf(line); else g_free(line); /* comment */ line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if(G_UNLIKELY(line == NULL)) goto _fail; if(G_LIKELY(len > 0)) item->comment = _unescape_lf(line); else g_free(line); /* icon */ line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if(G_UNLIKELY(line == NULL)) goto _fail; if(G_LIKELY(len > 0)) item->icon = line; else g_free(line); /* file dir/basename */ /* file name */ line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if(G_UNLIKELY(line == NULL)) goto _fail; if(G_LIKELY(len > 0)) item->file_name = line; else if( item->type == MENU_CACHE_TYPE_APP ) { /* When file name is the same as desktop_id, which is * quite common in desktop files, we use this trick to * save memory usage. */ item->file_name = item->id; g_free(line); } else g_free(line); /* desktop file dir */ line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if(G_UNLIKELY(line == NULL)) { _fail: g_free(item->id); g_free(item->name); g_free(item->comment); g_free(item->icon); if(item->file_name && item->file_name != item->id) g_free(item->file_name); if(item->type == MENU_CACHE_TYPE_DIR) g_slice_free(MenuCacheDir, MENU_CACHE_DIR(item)); else g_slice_free(MenuCacheApp, MENU_CACHE_APP(item)); return NULL; } idx = atoi( line ); g_free(line); if( G_LIKELY( idx >=0 && idx < n_all_used_files ) ) { item->file_dir = all_used_files[ idx ]; g_atomic_int_inc(&item->file_dir->n_ref); } if( item->type == MENU_CACHE_TYPE_DIR ) read_dir( f, MENU_CACHE_DIR(item), cache, all_used_files, n_all_used_files ); else if( item->type == MENU_CACHE_TYPE_APP ) read_app( f, MENU_CACHE_APP(item), cache ); return item; } static void menu_cache_file_dir_unref(MenuCacheFileDir *file_dir) { if (file_dir && g_atomic_int_dec_and_test(&file_dir->n_ref)) { g_free(file_dir->dir); g_free(file_dir); } } static gint read_all_used_files(GDataInputStream* f, MenuCache* cache, MenuCacheFileDir*** all_used_files) { char *line; gsize len; int i, n; MenuCacheFileDir** dirs; line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if(G_UNLIKELY(line == NULL)) return -1; n = atoi( line ); g_free(line); if (G_UNLIKELY(n <= 0)) return n; dirs = g_new0( MenuCacheFileDir *, n ); for( i = 0; i < n; ++i ) { line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if(G_UNLIKELY(line == NULL)) { while (i-- > 0) menu_cache_file_dir_unref(dirs[i]); g_free(dirs); return -1; } dirs[i] = g_new(MenuCacheFileDir, 1); dirs[i]->n_ref = 1; dirs[i]->dir = line; /* don't include \n */ } *all_used_files = dirs; return n; } static gboolean read_all_known_des(GDataInputStream* f, MenuCache* cache) { char *line; gsize len; line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if(G_UNLIKELY(line == NULL)) return FALSE; cache->known_des = g_strsplit_set( line, ";\n", 0 ); g_free(line); return TRUE; } static MenuCache* menu_cache_new( const char* cache_file ) { MenuCache* cache; cache = g_slice_new0( MenuCache ); cache->cache_file = g_strdup( cache_file ); cache->n_ref = 1; return cache; } /** * menu_cache_ref * @cache: a menu cache descriptor * * Increases reference counter on @cache. * * Returns: @cache. * * Since: 0.1.0 */ MenuCache* menu_cache_ref(MenuCache* cache) { g_atomic_int_inc( &cache->n_ref ); return cache; } /** * menu_cache_unref * @cache: a menu cache descriptor * * Descreases reference counter on @cache. When reference count becomes 0 * then resources associated with @cache will be freed. * * Since: 0.1.0 */ void menu_cache_unref(MenuCache* cache) { /* DEBUG("cache_unref: %d", cache->n_ref); */ /* we need a lock here unfortunately because item in hash isn't protected by reference therefore another thread may get access to it right now */ MENU_CACHE_LOCK; if( g_atomic_int_dec_and_test(&cache->n_ref) ) { /* g_assert(cache->reload_id != 0); */ unregister_menu_from_server( cache ); /* DEBUG("unregister to server"); */ g_hash_table_remove( hash, cache->menu_name ); if( g_hash_table_size(hash) == 0 ) { /* DEBUG("destroy hash"); */ g_hash_table_destroy(hash); /* DEBUG("disconnect from server"); */ G_LOCK(connect); shutdown(server_fd, SHUT_RDWR); /* the IO thread will terminate itself */ server_fd = -1; G_UNLOCK(connect); hash = NULL; } MENU_CACHE_UNLOCK; if(G_LIKELY(cache->thr)) { g_cancellable_cancel(cache->cancellable); g_thread_join(cache->thr); } g_object_unref(cache->cancellable); if( G_LIKELY(cache->root_dir) ) { /* DEBUG("unref root dir"); */ menu_cache_item_unref( MENU_CACHE_ITEM(cache->root_dir) ); /* DEBUG("unref root dir finished"); */ } g_free( cache->cache_file ); g_free( cache->menu_name ); g_free(cache->reg); /* g_free( cache->menu_file_path ); */ g_strfreev(cache->known_des); g_slist_free(cache->notifiers); g_slice_free( MenuCache, cache ); } else MENU_CACHE_UNLOCK; } /** * menu_cache_get_root_dir * @cache: a menu cache instance * * Since: 0.1.0 * * Deprecated: 0.3.4: Use menu_cache_dup_root_dir() instead. */ MenuCacheDir* menu_cache_get_root_dir( MenuCache* cache ) { MenuCacheDir* dir = menu_cache_dup_root_dir(cache); /* NOTE: this is very ugly hack but cache->root_dir may be changed by cache reload in server-io thread, so we should keep it alive :( */ if(dir) g_timeout_add_seconds(10, (GSourceFunc)menu_cache_item_unref, dir); return dir; } /** * menu_cache_dup_root_dir * @cache: a menu cache instance * * Retrieves root directory for @cache. Returned data should be freed * with menu_cache_item_unref() after usage. * * Returns: (transfer full): root item or %NULL in case of error. * * Since: 0.3.4 */ MenuCacheDir* menu_cache_dup_root_dir( MenuCache* cache ) { MenuCacheDir* dir; MENU_CACHE_LOCK; dir = cache->root_dir; if(G_LIKELY(dir)) menu_cache_item_ref(MENU_CACHE_ITEM(dir)); MENU_CACHE_UNLOCK; return dir; } /** * menu_cache_item_ref * @item: a menu cache item * * Increases reference counter on @item. * * Returns: @item. * * Since: 0.1.0 */ MenuCacheItem* menu_cache_item_ref(MenuCacheItem* item) { g_atomic_int_inc( &item->n_ref ); /* DEBUG("item_ref %s: %d -> %d", item->id, item->n_ref-1, item->n_ref); */ return item; } static gboolean menu_cache_reload_idle(gpointer cache) { /* do reload once */ if (!g_source_is_destroyed(g_main_current_source())) menu_cache_reload(cache); return FALSE; } typedef struct _CacheReloadNotifier { MenuCacheReloadNotify func; gpointer user_data; }CacheReloadNotifier; struct _MenuCacheNotifyId { GSList l; }; /** * menu_cache_add_reload_notify * @cache: a menu cache instance * @func: callback to call when menu cache is reloaded * @user_data: user data provided for @func * * Adds a @func to list of callbacks that are called each time menu cache * is loaded. * * Returns: an ID of added callback. * * Since: 0.1.0 */ MenuCacheNotifyId menu_cache_add_reload_notify(MenuCache* cache, MenuCacheReloadNotify func, gpointer user_data) { GSList* l = g_slist_alloc(); CacheReloadNotifier* n = g_slice_new(CacheReloadNotifier); gboolean is_first; n->func = func; n->user_data = user_data; l->data = n; MENU_CACHE_LOCK; is_first = (cache->root_dir == NULL && cache->notifiers == NULL); cache->notifiers = g_slist_concat( cache->notifiers, l ); /* reload existing file first so it will be ready right away */ if(is_first && cache->reload_id == 0) cache->reload_id = g_idle_add_full(G_PRIORITY_HIGH_IDLE, menu_cache_reload_idle, menu_cache_ref(cache), (GDestroyNotify)menu_cache_unref); MENU_CACHE_UNLOCK; return (MenuCacheNotifyId)l; } /** * menu_cache_remove_reload_notify * @cache: a menu cache instance * @notify_id: an ID of callback * * Removes @notify_id from list of callbacks added for @cache by previous * call to menu_cache_add_reload_notify(). * * Since: 0.1.0 */ void menu_cache_remove_reload_notify(MenuCache* cache, MenuCacheNotifyId notify_id) { MENU_CACHE_LOCK; g_slice_free( CacheReloadNotifier, ((GSList*)notify_id)->data ); cache->notifiers = g_slist_delete_link( cache->notifiers, (GSList*)notify_id ); MENU_CACHE_UNLOCK; } static gboolean reload_notify(gpointer data) { MenuCache* cache = (MenuCache*)data; GSList* l; MENU_CACHE_LOCK; /* we have it referenced and there is no source removal so no check */ for( l = cache->notifiers; l; l = l->next ) { CacheReloadNotifier* n = (CacheReloadNotifier*)l->data; if(n->func) n->func( cache, n->user_data ); } MENU_CACHE_UNLOCK; return FALSE; } /** * menu_cache_reload * @cache: a menu cache instance * * Reloads menu cache from file generated by menu-cached. * * Returns: %TRUE if reload was successful. * * Since: 0.1.0 */ gboolean menu_cache_reload( MenuCache* cache ) { char* line; gsize len; GFile* file; GFileInputStream* istr = NULL; GDataInputStream* f; MenuCacheFileDir** all_used_files; int i, n; int ver_maj, ver_min; MENU_CACHE_LOCK; if (cache->reload_id) g_source_remove(cache->reload_id); cache->reload_id = 0; MENU_CACHE_UNLOCK; file = g_file_new_for_path(cache->cache_file); if(!file) return FALSE; istr = g_file_read(file, cache->cancellable, NULL); g_object_unref(file); if(!istr) return FALSE; f = g_data_input_stream_new(G_INPUT_STREAM(istr)); g_object_unref(istr); if( ! f ) return FALSE; /* the first line is version number */ line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if(G_LIKELY(line)) { len = sscanf(line, "%d.%d", &ver_maj, &ver_min); g_free(line); if(len < 2) goto _fail; if( ver_maj != VER_MAJOR || ver_min > VER_MINOR || ver_min < VER_MINOR_SUPPORTED ) goto _fail; } else goto _fail; g_debug("menu cache: got file version 1.%d", ver_min); /* the second line is menu name */ line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL); if(G_UNLIKELY(line == NULL)) goto _fail; g_free(line); /* FIXME: this may lock other threads for some time */ MENU_CACHE_LOCK; if(cache->notifiers == NULL) { /* nobody aware of reloads, stupid clients may think root is forever */ MENU_CACHE_UNLOCK; goto _fail; } /* get all used files */ n = read_all_used_files( f, cache, &all_used_files ); if (n <= 0) { MENU_CACHE_UNLOCK; goto _fail; } /* read known DEs */ g_strfreev( cache->known_des ); if( ! read_all_known_des( f, cache ) ) { cache->known_des = NULL; MENU_CACHE_UNLOCK; for (i = 0; i < n; i++) menu_cache_file_dir_unref(all_used_files[i]); g_free(all_used_files); _fail: g_object_unref(f); return FALSE; } cache->version = ver_min; if(cache->root_dir) menu_cache_item_unref( MENU_CACHE_ITEM(cache->root_dir) ); cache->root_dir = (MenuCacheDir*)read_item( f, cache, all_used_files, n ); g_object_unref(f); g_idle_add_full(G_PRIORITY_HIGH_IDLE, reload_notify, menu_cache_ref(cache), (GDestroyNotify)menu_cache_unref); MENU_CACHE_UNLOCK; for (i = 0; i < n; i++) menu_cache_file_dir_unref(all_used_files[i]); g_free(all_used_files); return TRUE; } /** * menu_cache_item_unref * @item: a menu cache item * * Decreases reference counter on @item. When reference count becomes 0 * then resources associated with @item will be freed. * * Returns: %FALSE (since 0.5.0) * * Since: 0.1.0 */ gboolean menu_cache_item_unref(MenuCacheItem* item) { /* DEBUG("item_unref(%s): %d", item->id, item->n_ref); */ /* We need a lock here unfortunately since another thread may have access to it via some child->parent which isn't protected by reference */ MENU_CACHE_LOCK; /* lock may be recursive here */ if( g_atomic_int_dec_and_test( &item->n_ref ) ) { /* DEBUG("free item: %s", item->id); */ g_free( item->id ); g_free( item->name ); g_free( item->comment ); g_free( item->icon ); menu_cache_file_dir_unref(item->file_dir); if( item->file_name && item->file_name != item->id ) g_free( item->file_name ); if( item->parent ) { /* DEBUG("remove %s from parent %s", item->id, MENU_CACHE_ITEM(item->parent)->id); */ /* remove ourselve from the parent node. */ item->parent->children = g_slist_remove(item->parent->children, item); } if( item->type == MENU_CACHE_TYPE_DIR ) { MenuCacheDir* dir = MENU_CACHE_DIR(item); GSList* l; for(l = dir->children; l; ) { MenuCacheItem* child = MENU_CACHE_ITEM(l->data); /* remove ourselve from the children. */ child->parent = NULL; l = l->next; menu_cache_item_unref(child); } g_slist_free( dir->children ); g_slice_free( MenuCacheDir, dir ); } else { MenuCacheApp* app = MENU_CACHE_APP(item); g_free(app->generic_name); g_free( app->exec ); g_free(app->try_exec); g_free(app->working_dir); g_free(app->categories); g_free(app->keywords); g_slice_free( MenuCacheApp, app ); } } MENU_CACHE_UNLOCK; return FALSE; } /** * menu_cache_item_get_type * @item: a menu cache item * * Checks type of @item. * * Returns: type of @item. * * Since: 0.1.0 */ MenuCacheType menu_cache_item_get_type( MenuCacheItem* item ) { return item->type; } /** * menu_cache_item_get_id * @item: a menu cache item * * Retrieves ID (short name such as 'application.desktop') of @item. * Returned data are owned by menu cache and should be not freed by caller. * * Returns: (transfer none): item ID. * * Since: 0.1.0 */ const char* menu_cache_item_get_id( MenuCacheItem* item ) { return item->id; } /** * menu_cache_item_get_name * @item: a menu cache item * * Retrieves display name of @item. Returned data are owned by menu * cache and should be not freed by caller. * * Returns: (transfer none): @item display name or %NULL. * * Since: 0.1.0 */ const char* menu_cache_item_get_name( MenuCacheItem* item ) { return item->name; } /** * menu_cache_item_get_comment * @item: a menu cache item * * Retrieves comment of @item. The comment can be used to show tooltip * on @item. Returned data are owned by menu cache and should be not * freed by caller. * * Returns: (transfer none): @item comment or %NULL. * * Since: 0.1.0 */ const char* menu_cache_item_get_comment( MenuCacheItem* item ) { return item->comment; } /** * menu_cache_item_get_icon * @item: a menu cache item * * Retrieves name of icon of @item. Returned data are owned by menu * cache and should be not freed by caller. * * Returns: (transfer none): @item icon name or %NULL. * * Since: 0.1.0 */ const char* menu_cache_item_get_icon( MenuCacheItem* item ) { return item->icon; } /** * menu_cache_item_get_file_basename * @item: a menu cache item * * Retrieves basename of @item. This API can return %NULL if @item is a * directory and have no directory desktop entry file. Returned data are * owned by menu cache and should be not freed by caller. * * Returns: (transfer none): @item file basename or %NULL. * * Since: 0.2.0 */ const char* menu_cache_item_get_file_basename( MenuCacheItem* item ) { return item->file_name; } /** * menu_cache_item_get_file_dirname * @item: a menu cache item * * Retrieves path to directory where @item desktop enrty file is located. * This API can return %NULL if @item is a directory and have no * desktop entry file. Returned data are owned by menu cache and should * be not freed by caller. * * Returns: (transfer none): @item file parent directory path or %NULL. * * Since: 0.2.0 */ const char* menu_cache_item_get_file_dirname( MenuCacheItem* item ) { return item->file_dir ? item->file_dir->dir + 1 : NULL; } /** * menu_cache_item_get_file_path * @item: a menu cache item * * Retrieves path to @item desktop enrty file. This API can return %NULL * if @item is a directory and have no desktop entry file. Returned data * should be freed with g_free() after usage. * * Returns: (transfer full): @item file path or %NULL. * * Since: 0.2.0 */ char* menu_cache_item_get_file_path( MenuCacheItem* item ) { if( ! item->file_name || ! item->file_dir ) return NULL; return g_build_filename( item->file_dir->dir + 1, item->file_name, NULL ); } /** * menu_cache_item_get_parent * @item: a menu cache item * * Since: 0.1.0 * * Deprecated: 0.3.4: Use menu_cache_item_dup_parent() instead. */ MenuCacheDir* menu_cache_item_get_parent( MenuCacheItem* item ) { MenuCacheDir* dir = menu_cache_item_dup_parent(item); /* NOTE: this is very ugly hack but parent may be changed by item freeing so we should keep it alive :( */ if(dir) g_timeout_add_seconds(10, (GSourceFunc)menu_cache_item_unref, dir); return dir; } /** * menu_cache_item_dup_parent * @item: a menu item * * Retrieves parent (directory) for @item. Returned data should be freed * with menu_cache_item_unref() after usage. * * Returns: (transfer full): parent item or %NULL in case of error. * * Since: 0.3.4 */ MenuCacheDir* menu_cache_item_dup_parent( MenuCacheItem* item ) { MenuCacheDir* dir; MENU_CACHE_LOCK; dir = item->parent; if(G_LIKELY(dir)) menu_cache_item_ref(MENU_CACHE_ITEM(dir)); MENU_CACHE_UNLOCK; return dir; } /** * menu_cache_dir_get_children * @dir: a menu cache item * * Retrieves list of items contained in @dir. Returned data are owned by * menu cache and should be not freed by caller. * This API is thread unsafe and should be never called from outside of * default main loop. * * Returns: (transfer none) (element-type MenuCacheItem): list of items. * * Since: 0.1.0 * * Deprecated: 0.4.0: Use menu_cache_dir_list_children() instead. */ GSList* menu_cache_dir_get_children( MenuCacheDir* dir ) { /* NOTE: this is very ugly hack but dir may be freed by cache reload in server-io thread, so we should keep it alive :( */ g_timeout_add_seconds(10, (GSourceFunc)menu_cache_item_unref, menu_cache_item_ref(MENU_CACHE_ITEM(dir))); return dir->children; } /** * menu_cache_dir_list_children * @dir: a menu cache item * * Retrieves list of items contained in @dir. Returned data should be * freed with g_slist_free_full(list, menu_cache_item_unref) after usage. * * Returns: (transfer full) (element-type MenuCacheItem): list of items. * * Since: 0.4.0 */ GSList* menu_cache_dir_list_children(MenuCacheDir* dir) { GSList *children, *l; if(MENU_CACHE_ITEM(dir)->type != MENU_CACHE_TYPE_DIR) return NULL; MENU_CACHE_LOCK; children = g_slist_copy(dir->children); for(l = children; l; l = l->next) menu_cache_item_ref(l->data); MENU_CACHE_UNLOCK; return children; } /** * menu_cache_find_child_by_id * @dir: a menu cache item * @id: a string to find * * Checks if @dir has a child with given @id. Returned data should be * freed with menu_cache_item_unref() when no longer needed. * * Returns: (transfer full): found item or %NULL. * * Since: 0.5.0 */ MenuCacheItem *menu_cache_find_child_by_id(MenuCacheDir *dir, const char *id) { GSList *child; MenuCacheItem *item = NULL; if (MENU_CACHE_ITEM(dir)->type != MENU_CACHE_TYPE_DIR || id == NULL) return NULL; MENU_CACHE_LOCK; for (child = dir->children; child; child = child->next) if (g_strcmp0(MENU_CACHE_ITEM(child->data)->id, id) == 0) { item = menu_cache_item_ref(child->data); break; } MENU_CACHE_UNLOCK; return item; } /** * menu_cache_find_child_by_name * @dir: a menu cache item * @name: a string to find * * Checks if @dir has a child with given @name. Returned data should be * freed with menu_cache_item_unref() when no longer needed. * * Returns: (transfer full): found item or %NULL. * * Since: 0.5.0 */ MenuCacheItem *menu_cache_find_child_by_name(MenuCacheDir *dir, const char *name) { GSList *child; MenuCacheItem *item = NULL; if (MENU_CACHE_ITEM(dir)->type != MENU_CACHE_TYPE_DIR || name == NULL) return NULL; MENU_CACHE_LOCK; for (child = dir->children; child; child = child->next) if (g_strcmp0(MENU_CACHE_ITEM(child->data)->name, name) == 0) { item = menu_cache_item_ref(child->data); break; } MENU_CACHE_UNLOCK; return item; } /** * menu_cache_dir_is_visible * @dir: a menu cache item * * Checks if @dir should be visible. * * Returns: %TRUE if @dir is visible. * * Since: 0.5.0 */ gboolean menu_cache_dir_is_visible(MenuCacheDir *dir) { return ((dir->flags & FLAG_IS_NODISPLAY) == 0); } /** * menu_cache_app_get_generic_name * @app: a menu cache item * * Retrieves generic name for @app. Returned data are owned by menu * cache and should not be freed by caller. * * Returns: (transfer none): app's generic name or %NULL. * * Since: 1.0.3 */ const char* menu_cache_app_get_generic_name( MenuCacheApp* app ) { return app->generic_name; } /** * menu_cache_app_get_exec * @app: a menu cache item * * Retrieves execution string for @app. Returned data are owned by menu * cache and should be not freed by caller. * * Returns: (transfer none): item execution string or %NULL. * * Since: 0.1.0 */ const char* menu_cache_app_get_exec( MenuCacheApp* app ) { return app->exec; } /** * menu_cache_app_get_working_dir * @app: a menu cache item * * Retrieves working directory for @app. Returned data are owned by menu * cache and should be not freed by caller. * * Returns: (transfer none): item working directory or %NULL. * * Since: 0.1.0 */ const char* menu_cache_app_get_working_dir( MenuCacheApp* app ) { return app->working_dir; } /** * menu_cache_app_get_categories * @app: a menu cache item * * Retrieves list of categories for @app. Returned data are owned by menu * cache and should be not freed by caller. * * Returns: (transfer none): list of categories or %NULL. * * Since: 1.0.0 */ const char * const * menu_cache_app_get_categories(MenuCacheApp* app) { return app->categories; } /** * menu_cache_app_get_use_terminal * @app: a menu cache item * * Checks if @app should be ran in terminal. * * Returns: %TRUE if @app requires terminal to run. * * Since: 0.1.0 */ gboolean menu_cache_app_get_use_terminal( MenuCacheApp* app ) { return ( (app->flags & FLAG_USE_TERMINAL) != 0 ); } /** * menu_cache_app_get_use_sn * @app: a menu cache item * * Checks if @app wants startup notification. * * Returns: %TRUE if @app wants startup notification. * * Since: 0.1.0 */ gboolean menu_cache_app_get_use_sn( MenuCacheApp* app ) { return ( (app->flags & FLAG_USE_SN) != 0 ); } /** * menu_cache_app_get_show_flags * @app: a menu cache item * * Retrieves list of desktop environments where @app should be visible. * * Returns: bit mask of DE. * * Since: 0.2.0 */ guint32 menu_cache_app_get_show_flags( MenuCacheApp* app ) { return app->show_in_flags; } static gboolean _can_be_exec(MenuCacheApp *app) { char *path; if (app->try_exec == NULL) return TRUE; path = g_find_program_in_path(app->try_exec); g_free(path); return (path != NULL); } /** * menu_cache_app_get_is_visible * @app: a menu cache item * @de_flags: bit mask of DE to test * * Checks if @app should be visible in any of desktop environments * @de_flags. * * Returns: %TRUE if @app is visible. * * Since: 0.2.0 */ gboolean menu_cache_app_get_is_visible( MenuCacheApp* app, guint32 de_flags ) { if(app->flags & FLAG_IS_NODISPLAY) return FALSE; return (!app->show_in_flags || (app->show_in_flags & de_flags)) && _can_be_exec(app); } /* MenuCacheApp* menu_cache_find_app_by_exec( const char* exec ) { return NULL; } */ /** * menu_cache_get_dir_from_path * @cache: a menu cache instance * @path: item path * * Since: 0.1.0 * * Deprecated: 0.3.4: Use menu_cache_item_from_path() instead. */ MenuCacheDir* menu_cache_get_dir_from_path( MenuCache* cache, const char* path ) { char** names = g_strsplit( path + 1, "/", -1 ); int i = 0; MenuCacheDir* dir = NULL; if( !names ) return NULL; if( G_UNLIKELY(!names[0]) ) { g_strfreev(names); return NULL; } /* the topmost dir of the path should be the root menu dir. */ MENU_CACHE_LOCK; dir = cache->root_dir; if (G_UNLIKELY(dir == NULL) || strcmp(names[0], MENU_CACHE_ITEM(dir)->id)) { MENU_CACHE_UNLOCK; return NULL; } for( ++i; names[i]; ++i ) { GSList* l; for( l = dir->children; l; l = l->next ) { MenuCacheItem* item = MENU_CACHE_ITEM(l->data); if( item->type == MENU_CACHE_TYPE_DIR && 0 == strcmp( item->id, names[i] ) ) dir = MENU_CACHE_DIR(item); } /* FIXME: we really should ref it on return since other thread may destroy the parent at this time and returned data become invalid. Therefore this call isn't thread-safe! */ if( ! dir ) { MENU_CACHE_UNLOCK; return NULL; } } MENU_CACHE_UNLOCK; return dir; } /** * menu_cache_item_from_path * @cache: cache to inspect * @path: item path * * Searches item @path in the @cache. The @path consists of item IDs * separated by slash ('/'). Returned data should be freed with * menu_cache_item_unref() after usage. * * Returns: (transfer full): found item or %NULL if no item found. * * Since: 0.3.4 */ MenuCacheItem* menu_cache_item_from_path( MenuCache* cache, const char* path ) { char** names = g_strsplit( path + 1, "/", -1 ); int i; MenuCacheDir* dir; MenuCacheItem* item = NULL; if( !names ) return NULL; if( G_UNLIKELY(!names[0]) ) { g_strfreev(names); return NULL; } /* the topmost dir of the path should be the root menu dir. */ MENU_CACHE_LOCK; dir = cache->root_dir; if( G_UNLIKELY(!dir) || strcmp(names[0], MENU_CACHE_ITEM(dir)->id) != 0 ) goto _end; for( i = 1; names[i]; ++i ) { GSList* l; item = NULL; if( !dir ) break; l = dir->children; dir = NULL; for( ; l; l = l->next ) { item = MENU_CACHE_ITEM(l->data); if( g_strcmp0( item->id, names[i] ) == 0 ) { if( item->type == MENU_CACHE_TYPE_DIR ) dir = MENU_CACHE_DIR(item); break; } item = NULL; } if( !item ) break; } if(item) menu_cache_item_ref(item); _end: MENU_CACHE_UNLOCK; g_strfreev(names); return item; } /** * menu_cache_dir_make_path * @dir: a menu cache item * * Retrieves path of @dir. The path consists of item IDs separated by * slash ('/'). Returned data should be freed with g_free() after usage. * * Returns: (transfer full): item path. * * Since: 0.1.0 */ char* menu_cache_dir_make_path( MenuCacheDir* dir ) { GString* path = g_string_sized_new(1024); MenuCacheItem* it; MENU_CACHE_LOCK; while( (it = MENU_CACHE_ITEM(dir)) ) /* this is not top dir */ { g_string_prepend( path, menu_cache_item_get_id(it) ); g_string_prepend_c( path, '/' ); /* FIXME: if parent is already unref'd by another thread then path being made will be broken. Is there any way to avoid that? */ dir = it->parent; } MENU_CACHE_UNLOCK; return g_string_free( path, FALSE ); } static void get_socket_name( char* buf, int len ) { char* dpy = g_strdup(g_getenv("DISPLAY")); if(dpy && *dpy) { char* p = strchr(dpy, ':'); for(++p; *p && *p != '.' && *p != '\n';) ++p; if(*p) *p = '\0'; } #if GLIB_CHECK_VERSION(2, 28, 0) g_snprintf( buf, len, "%s/menu-cached-%s", g_get_user_runtime_dir(), dpy ? dpy : ":0" ); #else g_snprintf( buf, len, "%s/.menu-cached-%s-%s", g_get_tmp_dir(), dpy ? dpy : ":0", g_get_user_name() ); #endif g_free(dpy); } #define MAX_RETRIES 25 static gboolean fork_server(const char *path) { int ret, pid, status; if (!g_file_test (MENUCACHE_LIBEXECDIR "/menu-cached", G_FILE_TEST_IS_EXECUTABLE)) { g_error("failed to find menu-cached"); } /* Start daemon */ pid = fork(); if (pid == 0) { execl(MENUCACHE_LIBEXECDIR "/menu-cached", MENUCACHE_LIBEXECDIR "/menu-cached", path, NULL); g_print("failed to exec %s %s\n", MENUCACHE_LIBEXECDIR "/menu-cached", path); } /* * do a waitpid on the intermediate process to avoid zombies. */ retry_wait: ret = waitpid(pid, &status, 0); if (ret < 0) { if (errno == EINTR) goto retry_wait; } return TRUE; } /* this thread is started by connect_server() */ static gpointer server_io_thread(gpointer data) { char buf[1024]; /* protocol has a lot shorter strings */ ssize_t sz; size_t ptr = 0; int fd = GPOINTER_TO_INT(data); GHashTableIter it; char* menu_name; MenuCache* cache; while(fd >= 0) { sz = read(fd, &buf[ptr], sizeof(buf) - ptr); if(sz <= 0) /* socket error or EOF */ { MENU_CACHE_LOCK; ptr = hash ? g_hash_table_size(hash) : 0; MENU_CACHE_UNLOCK; if (ptr == 0) /* don't need it anymore */ break; G_LOCK(connect); if(fd != server_fd) /* someone replaced us?! go out immediately! */ { G_UNLOCK(connect); break; } server_fd = -1; G_UNLOCK(connect); DEBUG("connect failed, trying reconnect"); sleep(1); if( ! connect_server(NULL) ) { g_critical("fail to re-connect to the server."); MENU_CACHE_LOCK; if(hash) { g_hash_table_iter_init(&it, hash); while(g_hash_table_iter_next(&it, (gpointer*)&menu_name, (gpointer*)&cache)) SET_CACHE_READY(cache); } MENU_CACHE_UNLOCK; break; } DEBUG("successfully reconnected server, re-register menus."); /* re-register all menu caches */ MENU_CACHE_LOCK; if(hash) { g_hash_table_iter_init(&it, hash); while(g_hash_table_iter_next(&it, (gpointer*)&menu_name, (gpointer*)&cache)) register_menu_to_server(cache); /* FIXME: need we remove it from hash if failed? */ } MENU_CACHE_UNLOCK; break; /* next thread will do it */ } while(sz > 0) { while(sz > 0) { if(buf[ptr] == '\n') break; sz--; ptr++; } if(ptr == sizeof(buf)) /* EOB reached, seems we got garbage */ { g_warning("menu cache: got garbage from server, break connect"); shutdown(fd, SHUT_RDWR); /* drop connection */ break; /* we handle it above */ } else if(sz == 0) /* incomplete line, wait for data again */ break; /* we got a line, let check what we got */ buf[ptr] = '\0'; if(memcmp(buf, "REL:", 4) == 0) /* reload */ { DEBUG("server ask us to reload cache: %s", &buf[4]); MENU_CACHE_LOCK; if(hash) { g_hash_table_iter_init(&it, hash); while(g_hash_table_iter_next(&it, (gpointer*)&menu_name, (gpointer*)&cache)) { if(memcmp(cache->md5, &buf[4], 32) == 0) { DEBUG("RELOAD!"); menu_cache_reload(cache); SET_CACHE_READY(cache); break; } } } MENU_CACHE_UNLOCK; /* DEBUG("cache reloaded"); */ } else g_warning("menu cache: unrecognized input: %s", buf); /* go to next line */ sz--; if(sz > 0) memmove(buf, &buf[ptr+1], sz); ptr = 0; } } G_LOCK(connect); if (fd == server_fd) server_fd = -1; G_UNLOCK(connect); close(fd); /* DEBUG("server io thread terminated"); */ #if GLIB_CHECK_VERSION(2, 32, 0) g_thread_unref(g_thread_self()); #endif return NULL; } static gboolean connect_server(GCancellable* cancellable) { int fd, rc; struct sockaddr_un addr; int retries = 0; G_LOCK(connect); if(server_fd != -1 || (cancellable && g_cancellable_is_cancelled(cancellable))) { G_UNLOCK(connect); return TRUE; } retry: fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) { g_print("Failed to create socket\n"); G_UNLOCK(connect); return FALSE; } fcntl (fd, F_SETFD, FD_CLOEXEC); memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; get_socket_name( addr.sun_path, sizeof( addr.sun_path ) ); if( connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { rc = errno; close(fd); if(cancellable && g_cancellable_is_cancelled(cancellable)) { G_UNLOCK(connect); return TRUE; } if((rc == ECONNREFUSED || rc == ENOENT) && retries == 0) { DEBUG("no running server found, starting it"); fork_server(addr.sun_path); ++retries; goto retry; } if(retries < MAX_RETRIES) { usleep(50000); ++retries; goto retry; } g_print("Unable to connect\n"); G_UNLOCK(connect); return FALSE; } server_fd = fd; G_UNLOCK(connect); #if GLIB_CHECK_VERSION(2, 32, 0) g_thread_new("menu-cache-io", server_io_thread, GINT_TO_POINTER(fd)); #else g_thread_create(server_io_thread, GINT_TO_POINTER(fd), FALSE, NULL); #endif return TRUE; } #define CACHE_VERSION __num2str(VER_MAJOR) "." __num2str(VER_MINOR) #define __num2str(s) __def2str(s) #define __def2str(s) #s static inline char *_validate_env(const char *env) { char *res, *c; if (env) res = g_strdup(env); else res = g_strdup(""); for (c = res; *c; c++) if (*c == '\n' || *c == '\t') *c = ' '; return res; } static MenuCache* menu_cache_create(const char* menu_name) { MenuCache* cache; const gchar * const * langs = g_get_language_names(); const char* xdg_cfg_env = g_getenv("XDG_CONFIG_DIRS"); const char* xdg_prefix_env = g_getenv("XDG_MENU_PREFIX"); const char* xdg_data_env = g_getenv("XDG_DATA_DIRS"); const char* xdg_cfg_home_env = g_getenv("XDG_CONFIG_HOME"); const char* xdg_data_home_env = g_getenv("XDG_DATA_HOME"); const char* xdg_cache_home_env = g_getenv("XDG_CACHE_HOME"); char *xdg_cfg, *xdg_prefix, *xdg_data, *xdg_cfg_home, *xdg_data_home, *xdg_cache_home; char* buf; const char* md5; char* file_name; int len = 0; GChecksum *sum; char *langs_list; xdg_cfg = _validate_env(xdg_cfg_env); xdg_prefix = _validate_env(xdg_prefix_env); xdg_data = _validate_env(xdg_data_env); xdg_cfg_home = _validate_env(xdg_cfg_home_env); xdg_data_home = _validate_env(xdg_data_home_env); xdg_cache_home = _validate_env(xdg_cache_home_env); /* reconstruct languages list in form as it should be in $LANGUAGES */ langs_list = g_strjoinv(":", (char **)langs); for (buf = langs_list; *buf; buf++) /* reusing buf var as char pointer */ if (*buf == '\n' || *buf == '\t') *buf = ' '; buf = g_strdup_printf( "REG:%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t" CACHE_VERSION "\t00000000000000000000000000000000\n", menu_name, langs_list, xdg_cache_home, xdg_cfg, xdg_prefix, xdg_data, xdg_cfg_home, xdg_data_home ); /* calculate the md5 sum of menu name + lang + all environment variables */ sum = g_checksum_new(G_CHECKSUM_MD5); len = strlen(buf); g_checksum_update(sum, (guchar*)buf + 4, len - 38); md5 = g_checksum_get_string(sum); file_name = g_build_filename( g_get_user_cache_dir(), "menus", md5, NULL ); DEBUG("cache file_name = %s", file_name); cache = menu_cache_new( file_name ); cache->reg = buf; cache->md5 = buf + len - 33; memcpy( cache->md5, md5, 32 ); cache->menu_name = g_strdup(menu_name); g_free( file_name ); g_free(langs_list); g_free(xdg_cfg); g_free(xdg_prefix); g_free(xdg_data); g_free(xdg_cfg_home); g_free(xdg_data_home); g_free(xdg_cache_home); g_checksum_free(sum); /* md5 is also freed here */ MENU_CACHE_LOCK; g_hash_table_insert( hash, g_strdup(menu_name), cache ); MENU_CACHE_UNLOCK; return cache; } static gboolean register_menu_to_server(MenuCache* cache) { ssize_t len = strlen(cache->reg); /* FIXME: do unblocking I/O */ if(write(server_fd, cache->reg, len) < len) { DEBUG("register_menu_to_server: sending failed"); return FALSE; /* socket write failed */ } return TRUE; } static void unregister_menu_from_server( MenuCache* cache ) { char buf[38]; g_snprintf( buf, 38, "UNR:%s\n", cache->md5 ); /* FIXME: do unblocking I/O */ if(write( server_fd, buf, 37 ) <= 0) { DEBUG("unregister_menu_from_server: sending failed"); } } static gpointer menu_cache_loader_thread(gpointer data) { MenuCache* cache = (MenuCache*)data; /* try to connect server now */ if(!connect_server(cache->cancellable)) { g_print("unable to connect to menu-cached.\n"); SET_CACHE_READY(cache); return NULL; } /* and request update from server */ if ((cache->cancellable && g_cancellable_is_cancelled(cache->cancellable)) || !register_menu_to_server(cache)) SET_CACHE_READY(cache); return NULL; } /** * menu_cache_lookup * @menu_name: a menu name * * Searches for connection to menu-cached for @menu_name. If there is no * such connection exist then creates new one. Caller can be notified * when cache is (re)loaded by adding callback. Caller should check if * the cache is already loaded trying to retrieve its root. * * See also: menu_cache_add_reload_notify(), menu_cache_item_dup_parent(). * * Returns: (transfer full): menu cache descriptor. * * Since: 0.1.0 */ MenuCache* menu_cache_lookup( const char* menu_name ) { MenuCache* cache; /* lookup in a hash table for already loaded menus */ MENU_CACHE_LOCK; #if !GLIB_CHECK_VERSION(2, 32, 0) /* FIXME: destroy them on application exit? */ if(!sync_run_mutex) sync_run_mutex = g_mutex_new(); if(!sync_run_cond) sync_run_cond = g_cond_new(); #endif if( G_UNLIKELY( ! hash ) ) hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL ); else { cache = (MenuCache*)g_hash_table_lookup(hash, menu_name); if( cache ) { menu_cache_ref(cache); MENU_CACHE_UNLOCK; return cache; } } MENU_CACHE_UNLOCK; cache = menu_cache_create(menu_name); cache->cancellable = g_cancellable_new(); #if GLIB_CHECK_VERSION(2, 32, 0) cache->thr = g_thread_new(menu_name, menu_cache_loader_thread, cache); #else cache->thr = g_thread_create(menu_cache_loader_thread, cache, TRUE, NULL); #endif return cache; } /** * menu_cache_lookup_sync * @menu_name: a menu name * * Searches for data from menu-cached for @menu_name. If no connection * exists yet then creates new one and retrieves all data. * * Returns: (transfer full): menu cache descriptor. * * Since: 0.3.1 */ MenuCache* menu_cache_lookup_sync( const char* menu_name ) { MenuCache* mc = menu_cache_lookup(menu_name); MenuCacheDir* root_dir = menu_cache_dup_root_dir(mc); /* ensure that the menu cache is loaded */ if(root_dir) menu_cache_item_unref(MENU_CACHE_ITEM(root_dir)); else /* if it's not yet loaded */ { MenuCacheNotifyId notify_id; /* add stub */ notify_id = menu_cache_add_reload_notify(mc, NULL, NULL); #if GLIB_CHECK_VERSION(2, 32, 0) g_mutex_lock(&sync_run_mutex); while(!mc->ready) g_cond_wait(&sync_run_cond, &sync_run_mutex); g_mutex_unlock(&sync_run_mutex); #else g_mutex_lock(sync_run_mutex); g_debug("menu_cache_lookup_sync: enter wait %p", mc); while(!mc->ready) g_cond_wait(sync_run_cond, sync_run_mutex); g_debug("menu_cache_lookup_sync: leave wait"); g_mutex_unlock(sync_run_mutex); #endif menu_cache_remove_reload_notify(mc, notify_id); } return mc; } static GSList* list_app_in_dir(MenuCacheDir* dir, GSList* list) { GSList* l; for( l = dir->children; l; l = l->next ) { MenuCacheItem* item = MENU_CACHE_ITEM(l->data); switch( menu_cache_item_get_type(item) ) { case MENU_CACHE_TYPE_DIR: list = list_app_in_dir( MENU_CACHE_DIR(item), list ); break; case MENU_CACHE_TYPE_APP: list = g_slist_prepend(list, menu_cache_item_ref(item)); break; case MENU_CACHE_TYPE_NONE: case MENU_CACHE_TYPE_SEP: break; } } return list; } /** * menu_cache_list_all_apps * @cache: a menu cache descriptor * * Retrieves full list of applications in menu cache. Returned list * should be freed with g_slist_free_full(list, menu_cache_item_unref) * after usage. * * Returns: (transfer full) (element-type MenuCacheItem): list of items. * * Since: 0.1.2 */ GSList* menu_cache_list_all_apps(MenuCache* cache) { GSList* list; MENU_CACHE_LOCK; if (G_UNLIKELY(!cache->root_dir)) /* empty cache */ list = NULL; else list = list_app_in_dir(cache->root_dir, NULL); MENU_CACHE_UNLOCK; return list; } /** * menu_cache_get_desktop_env_flag * @cache: a menu cache descriptor * @desktop_env: desktop environment name * * Makes bit mask of desktop environment from its name. The @desktop_env * may be simple string or colon separated list of compatible session * names according to XDG_CURRENT_DESKTOP freedesktop.org specification. * * Returns: DE bit mask. * * Since: 0.2.0 */ guint32 menu_cache_get_desktop_env_flag( MenuCache* cache, const char* desktop_env ) { char** de; char **envs; guint32 flags = 0; int j; if (desktop_env == NULL || desktop_env[0] == '\0') return flags; envs = g_strsplit(desktop_env, ":", -1); MENU_CACHE_LOCK; de = cache->known_des; for (j = 0; envs[j]; j++) { if( de ) { int i; for( i = 0; de[i]; ++i ) if (strcmp(envs[j], de[i]) == 0) break; if (de[i]) { flags |= 1 << (i + N_KNOWN_DESKTOPS); continue; } } if (strcmp(envs[j], "GNOME") == 0) flags |= SHOW_IN_GNOME; else if (strcmp(envs[j], "KDE") == 0) flags |= SHOW_IN_KDE; else if (strcmp(envs[j], "XFCE") == 0) flags |= SHOW_IN_XFCE; else if (strcmp(envs[j], "LXDE") == 0) flags |= SHOW_IN_LXDE; else if (strcmp(envs[j], "ROX") == 0) flags |= SHOW_IN_ROX; } MENU_CACHE_UNLOCK; g_strfreev(envs); return flags; } static MenuCacheItem *_scan_by_id(MenuCacheItem *item, const char *id) { GSList *l; if (item) switch (menu_cache_item_get_type(item)) { case MENU_CACHE_TYPE_DIR: for (l = MENU_CACHE_DIR(item)->children; l; l = l->next) { item = _scan_by_id(MENU_CACHE_ITEM(l->data), id); if (item) return item; } break; case MENU_CACHE_TYPE_APP: if (g_strcmp0(menu_cache_item_get_id(item), id) == 0) return item; break; default: ; } return NULL; } /** * menu_cache_find_item_by_id * @cache: a menu cache descriptor * @id: item ID (name such as 'application.desktop') * * Searches if @id already exists within @cache and returns found item. * Returned data should be freed with menu_cache_item_unref() after usage. * * Returns: (transfer full): found item or %NULL. * * Since: 0.5.0 */ MenuCacheItem *menu_cache_find_item_by_id(MenuCache *cache, const char *id) { MenuCacheItem *item = NULL; MENU_CACHE_LOCK; if (cache && id) item = _scan_by_id(MENU_CACHE_ITEM(cache->root_dir), id); if (item) menu_cache_item_ref(item); MENU_CACHE_UNLOCK; return item; } static GSList* list_app_in_dir_for_cat(MenuCacheDir *dir, GSList *list, const char *id) { const char **cat; GSList *l; for (l = dir->children; l; l = l->next) { MenuCacheItem *item = MENU_CACHE_ITEM(l->data); switch (item->type) { case MENU_CACHE_TYPE_DIR: list = list_app_in_dir_for_cat(MENU_CACHE_DIR(item), list, id); break; case MENU_CACHE_TYPE_APP: cat = MENU_CACHE_APP(item)->categories; if (cat) while (*cat) if (*cat++ == id) { list = g_slist_prepend(list, menu_cache_item_ref(item)); break; } break; case MENU_CACHE_TYPE_NONE: case MENU_CACHE_TYPE_SEP: break; } } return list; } /** * menu_cache_list_all_for_category * @cache: a menu cache descriptor * @category: category to list items * * Retrieves list of applications in menu cache which have @category in * their list of categories. The search is case-sensitive. Returned list * should be freed with g_slist_free_full(list, menu_cache_item_unref) * after usage. * * Returns: (transfer full) (element-type MenuCacheItem): list of items. * * Since: 1.0.0 */ GSList *menu_cache_list_all_for_category(MenuCache* cache, const char *category) { GQuark q; GSList *list; g_return_val_if_fail(cache != NULL && category != NULL, NULL); q = g_quark_try_string(category); if (q == 0) return NULL; MENU_CACHE_LOCK; if (G_UNLIKELY(cache->root_dir == NULL)) list = NULL; else list = list_app_in_dir_for_cat(cache->root_dir, NULL, g_quark_to_string(q)); MENU_CACHE_UNLOCK; return list; } static GSList* list_app_in_dir_for_kw(MenuCacheDir *dir, GSList *list, const char *kw) { GSList *l; for (l = dir->children; l; l = l->next) { MenuCacheItem *item = MENU_CACHE_ITEM(l->data); switch (item->type) { case MENU_CACHE_TYPE_DIR: list = list_app_in_dir_for_kw(MENU_CACHE_DIR(item), list, kw); break; case MENU_CACHE_TYPE_APP: if (strstr(MENU_CACHE_APP(item)->keywords, kw) != NULL) list = g_slist_prepend(list, menu_cache_item_ref(item)); break; case MENU_CACHE_TYPE_NONE: case MENU_CACHE_TYPE_SEP: break; } } return list; } /** * menu_cache_list_all_for_keyword * @cache: a menu cache descriptor * @keyword: a keyword to search * * Retrieves list of applications in menu cache which have a @keyword * as either a word or part of word in exec command, name, generic name * or defined keywords. The search is case-insensitive. Returned list * should be freed with g_slist_free_full(list, menu_cache_item_unref) * after usage. * * Returns: (transfer full) (element-type MenuCacheItem): list of items. * * Since: 1.0.0 */ GSList *menu_cache_list_all_for_keyword(MenuCache* cache, const char *keyword) { char *casefolded = g_utf8_casefold(keyword, -1); GSList *list; g_return_val_if_fail(cache != NULL && keyword != NULL, NULL); MENU_CACHE_LOCK; if (G_UNLIKELY(cache->root_dir == NULL)) list = NULL; else list = list_app_in_dir_for_kw(cache->root_dir, NULL, casefolded); MENU_CACHE_UNLOCK; g_free(casefolded); return list; } menu-cache-1.1.1/libmenu-cache/menu-cache.h.in000066400000000000000000000141201475361627100210100ustar00rootroot00000000000000/* * menu-cache.h * * libmenu-cache is a small convinient library used to access * caches of freedesktop.org menus generated by menu-cache-gen. * * Copyright 2008 PCMan * Copyright 2012-2014 Andriy Grytsenko (LStranger) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __MENU_CACHE_H__ #define __MENU_CACHE_H__ #include G_BEGIN_DECLS #define __VERSION_MAJOR @MC_VERSION_MAJOR@ #define __VERSION_MINOR @MC_VERSION_MINOR@ #define __VERSION_MICRO @MC_VERSION_MICRO@ #define MENU_CACHE_CHECK_VERSION(_a,_b,_c) \ (__VERSION_MAJOR > _a || \ (__VERSION_MAJOR == _a && __VERSION_MINOR > _b) || \ (__VERSION_MAJOR == _a && __VERSION_MINOR == _b && __VERSION_MICRO >= _c)) #define MENU_CACHE_ITEM(x) ((MenuCacheItem*)x) #define MENU_CACHE_DIR(x) ((MenuCacheDir*)x) #define MENU_CACHE_APP(x) ((MenuCacheApp*)x) typedef struct _MenuCacheItem MenuCacheItem; typedef struct _MenuCacheDir MenuCacheDir; typedef struct _MenuCacheApp MenuCacheApp; typedef struct _MenuCache MenuCache; /** * MenuCacheType: * @MENU_CACHE_TYPE_NONE: invalid type * @MENU_CACHE_TYPE_DIR: item #MenuCacheDir * @MENU_CACHE_TYPE_APP: item #MenuCacheApp * @MENU_CACHE_TYPE_SEP: menu separator * * type of #MenuCacheItem. */ typedef enum { MENU_CACHE_TYPE_NONE, MENU_CACHE_TYPE_DIR, MENU_CACHE_TYPE_APP, MENU_CACHE_TYPE_SEP }MenuCacheType; /** * MenuCacheShowFlag: * @SHOW_IN_LXDE: show in LXDE * @SHOW_IN_GNOME: show in GNOME * @SHOW_IN_KDE: show in KDE * @SHOW_IN_XFCE: show in XFCE * @SHOW_IN_ROX: show in ROX * * bitmask of desktop environments where the item should be visible. */ typedef enum { SHOW_IN_LXDE = 1 << 0, SHOW_IN_GNOME = 1 << 1, SHOW_IN_KDE = 1 << 2, SHOW_IN_XFCE = 1 << 3, SHOW_IN_ROX = 1 << 4, /*< private >*/ N_KNOWN_DESKTOPS = 5 }MenuCacheShowFlag; /** * MenuCacheItemFlag: * @FLAG_USE_TERMINAL: run this application in terminal * @FLAG_USE_SN: use Startup Notify for this application * @FLAG_IS_NODISPLAY: application is hidden from menu * * flags for application run. */ typedef enum { FLAG_USE_TERMINAL = 1 << 0, FLAG_USE_SN = 1 << 1, FLAG_IS_NODISPLAY = 1 << 2 }MenuCacheItemFlag; void menu_cache_init(int flags); MenuCache* menu_cache_lookup( const char* menu_name ); MenuCache* menu_cache_lookup_sync( const char* menu_name ); MenuCache* menu_cache_ref(MenuCache* cache); void menu_cache_unref(MenuCache* cache); gboolean menu_cache_reload( MenuCache* cache ); #ifndef G_DISABLE_DEPRECATED MenuCacheDir* menu_cache_get_root_dir( MenuCache* cache ); MenuCacheDir* menu_cache_get_dir_from_path( MenuCache* cache, const char* path ); #endif MenuCacheDir* menu_cache_dup_root_dir( MenuCache* cache ); MenuCacheItem* menu_cache_item_from_path( MenuCache* cache, const char* path ); typedef struct _MenuCacheNotifyId* MenuCacheNotifyId; typedef void (*MenuCacheReloadNotify)(MenuCache* cache, gpointer user_data); MenuCacheNotifyId menu_cache_add_reload_notify(MenuCache* cache, MenuCacheReloadNotify func, gpointer user_data); void menu_cache_remove_reload_notify(MenuCache* cache, MenuCacheNotifyId notify_id); guint32 menu_cache_get_desktop_env_flag( MenuCache* cache, const char* desktop_env ); MenuCacheItem* menu_cache_item_ref(MenuCacheItem* item); gboolean menu_cache_item_unref(MenuCacheItem* item); MenuCacheType menu_cache_item_get_type( MenuCacheItem* item ); const char* menu_cache_item_get_id( MenuCacheItem* item ); const char* menu_cache_item_get_name( MenuCacheItem* item ); const char* menu_cache_item_get_comment( MenuCacheItem* item ); const char* menu_cache_item_get_icon( MenuCacheItem* item ); const char* menu_cache_item_get_file_basename( MenuCacheItem* item ); const char* menu_cache_item_get_file_dirname( MenuCacheItem* item ); char* menu_cache_item_get_file_path( MenuCacheItem* item ); #ifndef G_DISABLE_DEPRECATED MenuCacheDir* menu_cache_item_get_parent( MenuCacheItem* item ); GSList* menu_cache_dir_get_children( MenuCacheDir* dir ); #endif MenuCacheDir* menu_cache_item_dup_parent( MenuCacheItem* item ); GSList* menu_cache_dir_list_children( MenuCacheDir* dir ); MenuCacheItem *menu_cache_find_child_by_id(MenuCacheDir *dir, const char *id); MenuCacheItem *menu_cache_find_child_by_name(MenuCacheDir *dir, const char *name); char* menu_cache_dir_make_path( MenuCacheDir* dir ); const char* menu_cache_app_get_generic_name( MenuCacheApp* app ); const char* menu_cache_app_get_exec( MenuCacheApp* app ); const char* menu_cache_app_get_working_dir( MenuCacheApp* app ); const char* const *menu_cache_app_get_categories(MenuCacheApp* app); guint32 menu_cache_app_get_show_flags( MenuCacheApp* app ); gboolean menu_cache_app_get_is_visible( MenuCacheApp* app, guint32 de_flags ); gboolean menu_cache_dir_is_visible(MenuCacheDir *dir); gboolean menu_cache_app_get_use_terminal( MenuCacheApp* app ); gboolean menu_cache_app_get_use_sn( MenuCacheApp* app ); GSList* menu_cache_list_all_apps(MenuCache* cache); GSList *menu_cache_list_all_for_category(MenuCache* cache, const char *category); GSList *menu_cache_list_all_for_keyword(MenuCache* cache, const char *keyword); MenuCacheItem *menu_cache_find_item_by_id(MenuCache *cache, const char *id); /* MenuCacheApp* menu_cache_find_app_by_exec( const char* exec ); */ G_END_DECLS #endif menu-cache-1.1.1/libmenu-cache/test.c000066400000000000000000000076221475361627100173610ustar00rootroot00000000000000/* * test.c * * Copyright 2008 PCMan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include #include "menu-cache.h" static void on_menu_item( GtkMenuItem* mi, MenuCacheItem* item ) { g_debug( "Exec = %s", menu_cache_app_get_exec(MENU_CACHE_APP(item)) ); g_debug( "Terminal = %d", menu_cache_app_get_use_terminal(MENU_CACHE_APP(item)) ); } static GtkWidget* create_item( MenuCacheItem* item ) { GtkWidget* mi; if( menu_cache_item_get_type(item) == MENU_CACHE_TYPE_SEP ) mi = gtk_separator_menu_item_new(); else { GtkWidget* img; mi = gtk_image_menu_item_new_with_label( menu_cache_item_get_name(item) ); gtk_widget_set_tooltip_text( mi, menu_cache_item_get_comment(item) ); /* if( g_file_test( menu_cache_item_get_icon(item), G_FILE_TEST_IS_REGULAR ) ) { img = gtk_image_new_from_file(menu_cache_item_get_icon(item)); } else */ img = gtk_image_new_from_icon_name(menu_cache_item_get_icon(item), GTK_ICON_SIZE_MENU ); gtk_image_menu_item_set_image( mi, img ); if( menu_cache_item_get_type(item) == MENU_CACHE_TYPE_APP ) { g_object_set_data_full( mi, "mcitem", menu_cache_item_ref(item), menu_cache_item_unref ); g_signal_connect( mi, "activate", on_menu_item, item ); } } gtk_widget_show( mi ); return mi; } static GtkWidget* create_menu(MenuCacheDir* dir, GtkWidget* parent) { GSList* l; GtkWidget* menu = gtk_menu_new(); GtkWidget* mi; if( parent ) { mi = create_item( dir ); gtk_menu_item_set_submenu( mi, menu ); gtk_menu_append( parent, mi ); gtk_widget_show( mi ); // printf( "comment:%s\n", menu_cache_item_get_comment(dir) ); } for( l = menu_cache_dir_get_children(dir); l; l = l->next ) { MenuCacheItem* item = MENU_CACHE_ITEM(l->data); if( menu_cache_item_get_type(item) == MENU_CACHE_TYPE_DIR ) create_menu( item, menu ); else { mi = create_item(item); gtk_widget_show( mi ); // printf( "comment:%s\n", menu_cache_item_get_comment(item) ); // printf( "icon:%s\n", menu_cache_item_get_icon(item) ); // printf( "exec:%s\n", menu_cache_app_get_exec(item) ); gtk_menu_append( menu, mi ); } } return menu; } static void on_clicked( GtkButton* btn, GtkWidget* pop ) { gtk_menu_popup( pop, NULL, NULL, NULL, NULL, 0, GDK_CURRENT_TIME ); } int main(int argc, char** argv) { GtkWidget* win, *btn; gtk_init( &argc, &argv ); MenuCache* menu_cache = menu_cache_new( argv[1] ? argv[1] : "/etc/xdg/menus/applications.menu", NULL, NULL ); MenuCacheDir* menu = menu_cache_get_root_dir( menu_cache ); GtkWidget* pop = create_menu( menu, NULL ); // g_debug( "update: %d", menu_cache_is_updated( menu_cache ) ); g_debug( "update: %d", menu_cache_file_is_updated( argv[1] ) ); win = gtk_window_new( GTK_WINDOW_TOPLEVEL ); gtk_window_set_title( win, "MenuCache Test" ); btn = gtk_button_new_with_label( menu_cache_item_get_name(menu) ); gtk_widget_set_tooltip_text( btn, menu_cache_item_get_comment(menu) ); gtk_container_add( win, btn ); g_signal_connect( btn, "clicked", G_CALLBACK(on_clicked), pop ); g_signal_connect( win, "delete-event", G_CALLBACK(gtk_main_quit), NULL ); gtk_widget_show_all( win ); menu_cache_unref( menu ); gtk_main(); return 0; } menu-cache-1.1.1/libmenu-cache/version.h000066400000000000000000000004721475361627100200700ustar00rootroot00000000000000#ifndef __MENU_CACHE_VER_H__ #define __MENU_CACHE_VER_H__ /* change these numbers if you change enums in menu-cache.h note that will break compatibility for generated cache */ #define VER_MAJOR 1 #define VER_MINOR 2 /* This is for backward compatibility on transitions */ #define VER_MINOR_SUPPORTED 1 #endif menu-cache-1.1.1/menu-cache-daemon/000077500000000000000000000000001475361627100170015ustar00rootroot00000000000000menu-cache-1.1.1/menu-cache-daemon/Makefile.am000066400000000000000000000007011475361627100210330ustar00rootroot00000000000000NULL = AM_CPPFLAGS = \ -I$(top_srcdir)/libmenu-cache \ -I$(top_builddir)/libmenu-cache \ $(GLIB_CFLAGS) \ $(DEBUG_CFLAGS) \ $(ADDITIONAL_FLAGS) \ -DMENUCACHE_LIBEXECDIR="\"$(pkglibexecdir)\"" \ -Werror-implicit-function-declaration \ $(NULL) pkglibexec_PROGRAMS = menu-cached menu_cached_SOURCES = \ menu-cached.c \ $(NULL) menu_cached_LDADD = \ $(GLIB_LIBS) \ $(NULL) menu_cached_LDFLAGS = \ -no-undefined \ $(NULL) menu-cache-1.1.1/menu-cache-daemon/menu-cached.c000066400000000000000000000676001475361627100213270ustar00rootroot00000000000000/* * menu-cached.c * * Copyright 2008 - 2010 PCMan * Copyright 2009 Jürgen Hötzel * Copyright 2012-2017 Andriy Grytsenko (LStranger) * Copyright 2016 Mamoru TASAKA * * This file is a part of libmenu-cache package and created program * should be not used without the library. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include #endif #include "menu-cache.h" #include "version.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef G_ENABLE_DEBUG #define DEBUG(...) g_debug(__VA_ARGS__) #else #define DEBUG(...) #endif typedef struct _Cache { char md5[33]; /* cache id */ /* environment */ char* menu_name; char* lang_name; char** env; /* XDG- env variables */ /* files involved, and their monitors */ int n_files; char** files; GFileMonitor** mons; /* GFileMonitor* cache_mon; */ gboolean need_reload; guint delayed_reload_handler; guint delayed_free_handler; /* clients requesting this menu cache */ GSList* clients; /* cache file name for reference */ char *cache_file; }Cache; typedef struct ClientIO_ { guint source_id; GIOChannel *channel; } ClientIO; static GMainLoop* main_loop = NULL; static GHashTable* hash = NULL; static char *socket_file = NULL; static void on_file_changed( GFileMonitor* mon, GFile* gf, GFile* other, GFileMonitorEvent evt, Cache* cache ); static void on_client_closed(gpointer user_data); static gboolean delayed_cache_free(gpointer data) { Cache* cache = data; int i; if(g_source_is_destroyed(g_main_current_source())) return FALSE; g_hash_table_remove( hash, cache->md5 ); /* DEBUG("menu cache freed"); */ for(i = 0; i < cache->n_files; ++i) { g_file_monitor_cancel( cache->mons[i] ); g_object_unref( cache->mons[i] ); } /* g_file_monitor_cancel(cache->cache_mon); g_object_unref(cache->cache_mon); */ g_free( cache->mons ); g_free(cache->menu_name); g_free(cache->lang_name); g_free(cache->cache_file); g_strfreev( cache->env ); g_strfreev( cache->files ); if( cache->delayed_reload_handler ) g_source_remove( cache->delayed_reload_handler ); g_slice_free( Cache, cache ); if(g_hash_table_size(hash) == 0) g_main_loop_quit(main_loop); return FALSE; } static void cache_free(Cache* cache) { /* shutdown cache in 6 seconds of inactivity */ if(!cache->delayed_free_handler) cache->delayed_free_handler = g_timeout_add_seconds(6, delayed_cache_free, cache); DEBUG("menu %p cache unused, removing in 6s", cache); } static gboolean read_all_used_files( FILE* f, int* n_files, char*** used_files ) { char line[ 4096 ]; int i, n, x; char** files; int ver_maj, ver_min; /* the first line of the cache file is version number */ if( !fgets(line, G_N_ELEMENTS(line),f) ) return FALSE; if( sscanf(line, "%d.%d", &ver_maj, &ver_min)< 2 ) return FALSE; if (ver_maj != VER_MAJOR || ver_min > VER_MINOR || ver_min < VER_MINOR_SUPPORTED) return FALSE; /* skip the second line containing menu name */ if( ! fgets( line, G_N_ELEMENTS(line), f ) ) return FALSE; /* num of files used */ if( ! fgets( line, G_N_ELEMENTS(line), f ) ) return FALSE; n = atoi( line ); files = g_new0( char*, n + 1 ); for( i = 0, x = 0; i < n; ++i ) { int len; if( ! fgets( line, G_N_ELEMENTS(line), f ) ) return FALSE; len = strlen( line ); if( len <= 1 ) return FALSE; files[ x ] = g_strndup( line, len - 1 ); /* don't include \n */ if (g_file_test(files[x]+1, G_FILE_TEST_EXISTS)) x++; else { DEBUG("ignoring not existent file from menu-cache-gen: %s", files[x]); g_free(files[x]); files[x] = NULL; } } *n_files = x; *used_files = files; return TRUE; } static void set_env( char* penv, const char* name ) { if( penv && *penv ) g_setenv( name, penv, TRUE); else g_unsetenv( name ); } static void pre_exec( gpointer user_data ) { char** env = (char**)user_data; set_env(*env, "XDG_CACHE_HOME"); set_env(*++env, "XDG_CONFIG_DIRS"); set_env(*++env, "XDG_MENU_PREFIX"); set_env(*++env, "XDG_DATA_DIRS"); set_env(*++env, "XDG_CONFIG_HOME"); set_env(*++env, "XDG_DATA_HOME"); set_env(*++env, "CACHE_GEN_VERSION"); } static gboolean regenerate_cache( const char* menu_name, const char* lang_name, const char* cache_file, char** env, int* n_used_files, char*** used_files ) { FILE* f; int n_files, status = 0; char** files; const char *user_data_dir = env[5]; const char* argv[] = { MENUCACHE_LIBEXECDIR "/menu-cache-gen", "-l", NULL, "-i", NULL, "-o", NULL, NULL}; argv[2] = lang_name; argv[4] = menu_name; argv[6] = cache_file; /* DEBUG("cmd: %s", g_strjoinv(" ", argv)); */ /* create $XDG_DATA_HOME/applications if it does not exist yet */ if (!user_data_dir || !user_data_dir[0]) user_data_dir = g_get_user_data_dir(); if (g_file_test(user_data_dir, G_FILE_TEST_IS_DIR) || g_mkdir(user_data_dir, 0700) == 0) { char *local_app_path = g_build_filename(user_data_dir, "applications", NULL); if (!g_file_test(local_app_path, G_FILE_TEST_IS_DIR)) g_mkdir(local_app_path, 0700); g_free(local_app_path); } /* run menu-cache-gen */ /* FIXME: is cast to (char**) valid here? */ if( !g_spawn_sync(NULL, (char **)argv, NULL, 0, pre_exec, env, NULL, NULL, &status, NULL )) { DEBUG("error executing menu-cache-gen"); } /* DEBUG("exit status of menu-cache-gen: %d", status); */ if( status != 0 ) return FALSE; f = fopen( cache_file, "r" ); if( f ) { if( !read_all_used_files( f, &n_files, &files ) ) { n_files = 0; files = NULL; /* DEBUG("error: read_all_used_files"); */ } fclose(f); } else return FALSE; *n_used_files = n_files; *used_files = files; return TRUE; } static void do_reload(Cache* cache) { GSList* l; char buf[38]; int i; GFile* gf; int new_n_files; char **new_files = NULL; /* DEBUG("Re-generation of cache is needed!"); */ /* DEBUG("call menu-cache-gen to re-generate the cache"); */ memcpy( buf, "REL:", 4 ); memcpy( buf + 4, cache->md5, 32 ); buf[36] = '\n'; buf[37] = '\0'; if( ! regenerate_cache( cache->menu_name, cache->lang_name, cache->cache_file, cache->env, &new_n_files, &new_files ) ) { DEBUG("regeneration of cache failed."); return; } /* cancel old file monitors */ g_strfreev(cache->files); for( i = 0; i < cache->n_files; ++i ) { g_file_monitor_cancel( cache->mons[i] ); g_signal_handlers_disconnect_by_func( cache->mons[i], on_file_changed, cache ); g_object_unref( cache->mons[i] ); } /* g_file_monitor_cancel(cache->cache_mon); g_object_unref(cache->cache_mon); */ cache->n_files = new_n_files; cache->files = new_files; cache->mons = g_realloc( cache->mons, sizeof(GFileMonitor*)*(cache->n_files+1) ); /* create required file monitors */ for( i = 0; i < cache->n_files; ++i ) { gf = g_file_new_for_path( cache->files[i] + 1 ); if( cache->files[i][0] == 'D' ) cache->mons[i] = g_file_monitor_directory( gf, 0, NULL, NULL ); else cache->mons[i] = g_file_monitor_file( gf, 0, NULL, NULL ); g_signal_connect(cache->mons[i], "changed", G_CALLBACK(on_file_changed), cache); g_object_unref(gf); } /* gf = g_file_new_for_path( cache_file ); cache->cache_mon = g_file_monitor_file( gf, 0, NULL, NULL ); g_signal_connect( cache->cache_mon, "changed", on_file_changed, cache); g_object_unref(gf); */ /* notify the clients that reload is needed. */ for( l = cache->clients; l; ) { ClientIO *channel_io = (ClientIO *)l->data; GIOChannel* ch = channel_io->channel; l = l->next; /* do it beforehand, as client may be removed below */ if(write(g_io_channel_unix_get_fd(ch), buf, 37) < 37) { on_client_closed(channel_io); } } cache->need_reload = FALSE; } static gboolean delayed_reload( Cache* cache ) { if(g_source_is_destroyed(g_main_current_source())) return FALSE; if(cache->need_reload) do_reload(cache); if (cache->need_reload) return TRUE; cache->delayed_reload_handler = 0; return FALSE; } void on_file_changed( GFileMonitor* mon, GFile* gf, GFile* other, GFileMonitorEvent evt, Cache* cache ) { #ifdef G_ENABLE_DEBUG char *path = g_file_get_path(gf); g_debug("file %s is changed (%d).", path, evt); g_free(path); #endif /* if( mon != cache->cache_mon ) */ { /* Optimization: Some files in the dir are changed, but it * won't affect the content of the menu. So, just omit them, * and update the mtime of the cached file with utime. */ int idx; /* dirty hack: get index of the monitor in array */ for(idx = 0; idx < cache->n_files; ++idx) { if(mon == cache->mons[idx]) break; } /* if the monitored file is a directory */ if( G_LIKELY(idx < cache->n_files) && cache->files[idx][0] == 'D' ) { char* changed_file = g_file_get_path(gf); /* Regenerate the cache if the changed file is a directory. * * The file monitor isn't recursive, so imagine we add a * subdirectory to /usr/share/applications, and subsequently * add a desktop entry to that. If we ignore the new * subdirectory, we won't notice when the desktop entry is * added. By regenerating the cache, the subdirectory will * be mentioned there, picked up by read_all_used_files(), * and monitored for subsequent changes. */ if (!g_file_test(changed_file, G_FILE_TEST_IS_DIR)) { char* dir_path = cache->files[idx]+1; int len = strlen(dir_path); /* if the changed file is a file in the monitored dir */ if( strncmp(changed_file, dir_path, len) == 0 && changed_file[len] == '/' ) { char* base_name = changed_file + len + 1; gboolean skip = TRUE; /* only *.desktop and *.directory files can affect the content of the menu. */ if( g_str_has_suffix(base_name, ".desktop") ) { skip = FALSE; } else if( g_str_has_suffix(base_name, ".directory") ) skip = FALSE; if( skip ) { DEBUG("files are changed, but no re-generation is needed."); g_free(changed_file); return; } } } g_free(changed_file); } } if( cache->delayed_reload_handler ) { /* we got some change in last 3 seconds... not reload again */ cache->need_reload = TRUE; g_source_remove(cache->delayed_reload_handler); } else { /* no reload in last 3 seconds... good, do it immediately */ /* mark need_reload anyway. If do_reload succeeds, it is cleaned up */ cache->need_reload = TRUE; do_reload(cache); } cache->delayed_reload_handler = g_timeout_add_seconds_full( G_PRIORITY_LOW, 3, (GSourceFunc)delayed_reload, cache, NULL ); } static gboolean cache_file_is_updated( const char* cache_file, int* n_used_files, char*** used_files ) { gboolean ret = FALSE; struct stat st; #if 0 time_t cache_mtime; char** files; int n, i; #endif FILE* f; f = fopen( cache_file, "r" ); if( f ) { if( fstat( fileno(f), &st) == 0 ) { #if 0 cache_mtime = st.st_mtime; if( read_all_used_files(f, &n, &files) ) { for( i =0; i < n; ++i ) { /* files[i][0] is 'D' or 'F' indicating file type. */ if( stat( files[i] + 1, &st ) == -1 ) continue; if( st.st_mtime > cache_mtime ) break; } if( i >= n ) { ret = TRUE; *n_used_files = n; *used_files = files; } } #else ret = read_all_used_files(f, n_used_files, used_files); #endif } fclose( f ); } return ret; } static void get_socket_name( char* buf, int len ) { char* dpy = g_strdup(g_getenv("DISPLAY")); if(dpy && *dpy) { char* p = strchr(dpy, ':'); for(++p; *p && *p != '.' && *p != '\n';) ++p; if(*p) *p = '\0'; } /* NOTE: this socket name is incompatible with versions > 1.0.2, although this function is never used since 0.7.0 but libmenu-cache always requests exact socket name instead */ g_snprintf( buf, len, "%s/.menu-cached-%s-%s", g_get_tmp_dir(), dpy ? dpy : ":0", g_get_user_name() ); g_free(dpy); } static gboolean socket_is_alive(struct sockaddr_un *addr) { int fd = socket(PF_UNIX, SOCK_STREAM, 0); socklen_t len = sizeof(sa_family_t) + strlen(addr->sun_path) + 1; if (fd < 0) { DEBUG("Failed to create socket"); /* still return TRUE to drop attempt */ return TRUE; } if (connect(fd, (struct sockaddr*)addr, len) >= 0) { /* there is a listener on the socket */ close(fd); DEBUG("Another menu-cached seems to reside on the socket"); return TRUE; } close(fd); /* remove previous socket file */ if (unlink(addr->sun_path) < 0) { if (errno != ENOENT) g_error("Couldn't remove previous socket file %s", addr->sun_path); } /* remove of previous socket file successful */ else g_warning("removed previous socket file %s", addr->sun_path); return FALSE; } static int create_socket(struct sockaddr_un *addr) { int fd = -1; char *lockfile; lockfile = g_strconcat(addr->sun_path, ".lock", NULL); fd = open(lockfile, O_CREAT | O_EXCL | O_WRONLY, 0700); if (fd < 0) { DEBUG("Cannot create lock file %s: %s", lockfile, strerror(errno)); g_free(lockfile); return -1; } close(fd); fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) { DEBUG("Failed to create socket"); } /* remove previous socket file */ else if (g_file_test(addr->sun_path, G_FILE_TEST_EXISTS) && socket_is_alive(addr)) { close(fd); fd = -1; } else if(bind(fd, (struct sockaddr *)addr, sizeof(*addr)) < 0) { DEBUG("Failed to bind to socket"); close(fd); fd = -1; } else if(listen(fd, 30) < 0) { DEBUG( "Failed to listen to socket" ); close(fd); fd = -1; } else fcntl(fd, F_SETFD, FD_CLOEXEC); unlink(lockfile); g_free(lockfile); return fd; } static void on_client_closed(gpointer user_data) { ClientIO* client_io = user_data; GHashTableIter it; char* md5; Cache* cache; GSList *l; GIOChannel *ch = client_io->channel; DEBUG("client closed: %p", ch); g_hash_table_iter_init (&it, hash); while( g_hash_table_iter_next (&it, (gpointer*)&md5, (gpointer*)&cache) ) { while((l = g_slist_find( cache->clients, client_io )) != NULL) { /* FIXME: some clients are closed accidentally without * unregister the menu first due to crashes. * We need to do unref for them to prevent memory leaks. * * The behavior is not currently well-defined. * if a client do unregister first, and then was closed, * unref will be called twice and incorrect ref. counting * will happen. */ cache->clients = g_slist_delete_link( cache->clients, l ); DEBUG("remove channel from cache %p", cache); if(cache->clients == NULL) cache_free(cache); } } /* DEBUG("client closed"); */ g_source_remove(client_io->source_id); g_free(client_io); } static gboolean on_client_data_in(GIOChannel* ch, GIOCondition cond, gpointer user_data) { char *line; gsize len; GIOStatus st; const char* md5; Cache* cache; GFile* gf; gboolean ret = TRUE; if(cond & (G_IO_HUP|G_IO_ERR) ) { on_client_closed(user_data); return FALSE; /* remove the watch */ } retry: st = g_io_channel_read_line( ch, &line, &len, NULL, NULL ); if( st == G_IO_STATUS_AGAIN ) goto retry; if( st != G_IO_STATUS_NORMAL ) { on_client_closed(user_data); return FALSE; } --len; line[len] = 0; DEBUG("line(%d) = %s", (int)len, line); if( memcmp(line, "REG:", 4) == 0 ) { int n_files = 0, i; char *pline = line + 4; char *sep, *menu_name, *lang_name, *cache_dir; char **files = NULL; char **env; char reload_cmd[38] = "REL:"; len -= 4; /* Format of received string, separated by '\t'. * Menu Name * Language Name * XDG_CACHE_HOME * XDG_CONFIG_DIRS * XDG_MENU_PREFIX * XDG_DATA_DIRS * XDG_CONFIG_HOME * XDG_DATA_HOME * (optional) CACHE_GEN_VERSION * md5 hash */ md5 = pline + len - 32; /* the md5 hash of this menu */ cache = (Cache*)g_hash_table_lookup(hash, md5); if( !cache ) { sep = strchr(pline, '\t'); *sep = '\0'; menu_name = pline; pline = sep + 1; sep = strchr(pline, '\t'); *sep = '\0'; lang_name = pline; pline = sep + 1; ((char *)md5)[-1] = '\0'; env = g_strsplit(pline, "\t", 0); cache_dir = env[0]; /* XDG_CACHE_HOME */ /* obtain cache dir from client's env */ cache = g_slice_new0( Cache ); cache->cache_file = g_build_filename(*cache_dir ? cache_dir : g_get_user_cache_dir(), "menus", md5, NULL ); if( ! cache_file_is_updated(cache->cache_file, &n_files, &files) ) { /* run menu-cache-gen */ if(! regenerate_cache( menu_name, lang_name, cache->cache_file, env, &n_files, &files ) ) { DEBUG("regeneration of cache failed!!"); } } else { /* file loaded, schedule update anyway */ cache->need_reload = TRUE; cache->delayed_reload_handler = g_timeout_add_seconds_full(G_PRIORITY_LOW, 3, (GSourceFunc)delayed_reload, cache, NULL); } memcpy( cache->md5, md5, 33 ); cache->n_files = n_files; cache->files = files; cache->menu_name = g_strdup(menu_name); cache->lang_name = g_strdup(lang_name); cache->env = env; cache->mons = g_new0(GFileMonitor*, n_files+1); /* create required file monitors */ DEBUG("%d files/dirs are monitored.", n_files); for( i = 0; i < n_files; ++i ) { gf = g_file_new_for_path( files[i] + 1 ); if( files[i][0] == 'D' ) cache->mons[i] = g_file_monitor_directory( gf, 0, NULL, NULL ); else cache->mons[i] = g_file_monitor_file( gf, 0, NULL, NULL ); DEBUG("monitor: %s", g_file_get_path(gf)); g_signal_connect(cache->mons[i], "changed", G_CALLBACK(on_file_changed), cache); g_object_unref(gf); } /* gf = g_file_new_for_path( cache_file ); cache->cache_mon = g_file_monitor_file( gf, 0, NULL, NULL ); g_signal_connect( cache->cache_mon, "changed", on_file_changed, cache); g_object_unref(gf); */ g_hash_table_insert(hash, cache->md5, cache); DEBUG("new menu cache %p added to hash", cache); } else if (access(cache->cache_file, R_OK) != 0) { /* bug SF#657: if user deleted cache file we have to regenerate it */ if (!regenerate_cache(cache->menu_name, cache->lang_name, cache->cache_file, cache->env, &cache->n_files, &cache->files)) { DEBUG("regeneration of cache failed."); } } /* DEBUG("menu %s requested by client %d", md5, g_io_channel_unix_get_fd(ch)); */ cache->clients = g_slist_prepend(cache->clients, user_data); if(cache->delayed_free_handler) { g_source_remove(cache->delayed_free_handler); cache->delayed_free_handler = 0; } DEBUG("client %p added to cache %p", ch, cache); /* generate a fake reload notification */ DEBUG("fake reload!"); memcpy(reload_cmd + 4, md5, 32); reload_cmd[36] = '\n'; reload_cmd[37] = '\0'; DEBUG("reload command: %s", reload_cmd); ret = write(g_io_channel_unix_get_fd(ch), reload_cmd, 37) > 0; } else if( memcmp(line, "UNR:", 4) == 0 ) { md5 = line + 4; DEBUG("unregister: %s", md5); cache = (Cache*)g_hash_table_lookup(hash, md5); if(cache && cache->clients) { /* remove the IO channel from the cache */ cache->clients = g_slist_remove(cache->clients, user_data); if(cache->clients == NULL) cache_free(cache); } else DEBUG("bug! client is not found."); } g_free( line ); return ret; } static void terminate(int sig) { /* #ifndef HAVE_ABSTRACT_SOCKETS */ unlink(socket_file); exit(0); /* #endif */ } static gboolean on_new_conn_incoming(GIOChannel* ch, GIOCondition cond, gpointer user_data) { int server, client; socklen_t client_addrlen; struct sockaddr client_addr; GIOChannel* child; ClientIO* client_io; server = g_io_channel_unix_get_fd(ch); client_addrlen = sizeof(client_addr); client = accept(server, &client_addr, &client_addrlen); if( client == -1 ) { DEBUG("accept error"); if (errno == ECONNABORTED) /* client failed, just continue */ return TRUE; /* else it's socket error, terminate server */ terminate(SIGTERM); return FALSE; } fcntl (client, F_SETFD, FD_CLOEXEC); child = g_io_channel_unix_new(client); g_io_channel_set_close_on_unref( child, TRUE ); client_io = g_new0 (ClientIO, 1); client_io->channel = child; client_io->source_id = g_io_add_watch(child, G_IO_PRI|G_IO_IN|G_IO_HUP|G_IO_ERR, on_client_data_in, client_io); g_io_channel_unref(child); DEBUG("new client accepted: %p", child); return TRUE; } static gboolean on_server_conn_close(GIOChannel* ch, GIOCondition cond, gpointer user_data) { /* FIXME: is this possible? */ /* the server socket is accidentally closed. terminate the server. */ terminate(SIGTERM); return TRUE; } int main(int argc, char** argv) { GIOChannel* ch; int server_fd; struct sockaddr_un addr; #ifndef DISABLE_DAEMONIZE pid_t pid; int status; long open_max; long i; #endif #ifndef DISABLE_DAEMONIZE /* Become a daemon */ if ((pid = fork()) < 0) { g_error("can't fork"); return 2; } else if (pid != 0) { /* wait for result of child */ while ((i = waitpid(pid, &status, 0)) < 0 && errno == EINTR); if (i < 0 || !WIFEXITED(status)) /* system error or child crashed */ return 127; /* exit parent */ return WEXITSTATUS(status); } /* reset session to forget about parent process completely */ setsid(); /* change working directory to root, so previous working directory * can be unmounted */ if (chdir("/") < 0) { g_error("can't change directory to /"); } /* don't hold open fd opened besides server socket and std{in,out,err} */ open_max = sysconf (_SC_OPEN_MAX); for (i = 3; i < open_max; i++) close (i); /* /dev/null for stdin, stdout, stderr */ if (freopen("/dev/null", "r", stdin)) i = i; if (freopen("/dev/null", "w", stdout)) i = i; if (freopen("/dev/null", "w", stderr)) i = i; #endif memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; if (argc < 2) /* old way, not recommended! */ get_socket_name( addr.sun_path, sizeof( addr.sun_path ) ); else if (strlen(argv[1]) >= sizeof(addr.sun_path)) /* ouch, it's too big! */ return 1; else /* this is a good way! */ strncpy(addr.sun_path, argv[1], sizeof(addr.sun_path) - 1); socket_file = addr.sun_path; server_fd = create_socket(&addr); if( server_fd < 0 ) return 1; #ifndef DISABLE_DAEMONIZE /* Second fork to let parent get a result */ if ((pid = fork()) < 0) { g_error("can't fork"); close(server_fd); unlink(socket_file); return 2; } else if (pid != 0) { /* exit child */ return 0; } /* We are in grandchild now, daemonized */ #endif signal(SIGHUP, terminate); signal(SIGINT, terminate); signal(SIGQUIT, terminate); signal(SIGTERM, terminate); signal(SIGPIPE, SIG_IGN); ch = g_io_channel_unix_new(server_fd); if(!ch) return 1; g_io_add_watch(ch, G_IO_IN|G_IO_PRI, on_new_conn_incoming, NULL); g_io_add_watch(ch, G_IO_ERR, on_server_conn_close, NULL); g_io_channel_unref(ch); #if !GLIB_CHECK_VERSION(2, 36, 0) g_type_init(); #endif hash = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); main_loop = g_main_loop_new( NULL, TRUE ); g_main_loop_run( main_loop ); g_main_loop_unref( main_loop ); unlink(addr.sun_path); g_hash_table_destroy(hash); return 0; } menu-cache-1.1.1/menu-cache-gen/000077500000000000000000000000001475361627100163075ustar00rootroot00000000000000menu-cache-1.1.1/menu-cache-gen/Makefile.am000066400000000000000000000007141475361627100203450ustar00rootroot00000000000000NULL = AM_CPPFLAGS = \ -I$(top_builddir)/libmenu-cache \ -I$(top_srcdir)/libmenu-cache \ $(GLIB_CFLAGS) \ $(LIBFM_EXTRA_CFLAGS) \ $(DEBUG_CFLAGS) \ $(ADDITIONAL_FLAGS) \ -Werror-implicit-function-declaration \ $(NULL) pkglibexec_PROGRAMS = menu-cache-gen menu_cache_gen_SOURCES = \ main.c \ menu-merge.c \ menu-compose.c \ $(NULL) menu_cache_gen_LDADD = \ $(GLIB_LIBS) \ $(LIBFM_EXTRA_LIBS) \ $(NULL) EXTRA_DIST = \ menu-tags.h \ $(NULL) menu-cache-1.1.1/menu-cache-gen/main.c000066400000000000000000000117071475361627100174050ustar00rootroot00000000000000/* * main.c : the main() function for menu-cache-gen binary. * * Copyright 2014 Andriy Grytsenko (LStranger) * * This file is a part of libmenu-cache package and created program * should be not used without the library. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include #endif #include "menu-tags.h" #include #include char **languages = NULL; GSList *MenuFiles = NULL; gint verbose = 0; static gboolean option_verbose (const gchar *option_name, const gchar *value, gpointer data, GError **error) { verbose++; return TRUE; } /* GLib options parser data is taken from previous menu-cache-gen code * * Copyright 2008 PCMan */ static char* ifile = NULL; static char* ofile = NULL; static char* lang = NULL; GOptionEntry opt_entries[] = { /* {"force", 'f', 0, G_OPTION_ARG_NONE, &force, "Force regeneration of cache even if it's up-to-dat e.", NULL }, */ {"input", 'i', 0, G_OPTION_ARG_FILENAME, &ifile, "Source *.menu file to read", "FILENAME" }, {"output", 'o', 0, G_OPTION_ARG_FILENAME, &ofile, "Output file to write cache to", "FILENAME" }, {"lang", 'l', 0, G_OPTION_ARG_STRING, &lang, "Language", "LANG_LIST" }, {"verbose", 'v', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, &option_verbose, "Send debug messages to terminal", NULL }, { NULL } }; int main(int argc, char **argv) { FmXmlFile *xmlfile = NULL; GOptionContext *opt_ctx; GError *err = NULL; MenuMenu *menu; int rc = 1; gboolean with_hidden = FALSE; /* wish we could use some POSIX parser but there isn't one for long options */ opt_ctx = g_option_context_new("Generate cache for freedesktop.org compliant menus."); g_option_context_add_main_entries(opt_ctx, opt_entries, NULL); if (!g_option_context_parse(opt_ctx, &argc, &argv, &err)) { g_printerr("menu-cache-gen: %s\n", err->message); g_error_free(err); return 1; } /* do with -l: if language is NULL then query it from environment */ if (lang == NULL || lang[0] == '\0') languages = (char **)g_get_language_names(); else languages = g_strsplit(lang, ":", 0); setlocale(LC_ALL, ""); /* do with files: both ifile and ofile should be set correctly */ if (ifile == NULL || ofile == NULL) { g_printerr("menu-cache-gen: failed: both input and output files must be defined.\n"); return 1; } with_hidden = g_str_has_suffix(ifile, "+hidden"); if (with_hidden) ifile[strlen(ifile)-7] = '\0'; if (G_LIKELY(!g_path_is_absolute(ifile))) { /* resolv the path */ char *path; gboolean found; const gchar * const *dirs = g_get_system_config_dirs(); const char *menu_prefix = g_getenv("XDG_MENU_PREFIX"); if (menu_prefix != NULL && menu_prefix[0] != '\0') { char *path = g_strconcat(menu_prefix, ifile, NULL); g_free(ifile); ifile = path; } path = g_build_filename(g_get_user_config_dir(), "menus", ifile, NULL); found = g_file_test(path, G_FILE_TEST_IS_REGULAR); while (!found && dirs[0] != NULL) { MenuFiles = g_slist_append(MenuFiles, (gpointer)g_intern_string(path)); g_free(path); path = g_build_filename(dirs[0], "menus", ifile, NULL); found = g_file_test(path, G_FILE_TEST_IS_REGULAR); dirs++; } if (!found) { g_printerr("menu-cache-gen: failed: cannot find file '%s'\n", ifile); return 1; } g_free(ifile); ifile = path; } MenuFiles = g_slist_append(MenuFiles, (gpointer)g_intern_string(ifile)); #if !GLIB_CHECK_VERSION(2, 36, 0) g_type_init(); #endif /* load, merge menu file, and create menu */ menu = get_merged_menu(ifile, &xmlfile, &err); if (menu == NULL) { g_printerr("menu-cache-gen: %s\n", err->message); g_error_free(err); return 1; } /* save the layout */ rc = !save_menu_cache(menu, ifile, ofile, with_hidden); if (xmlfile != NULL) g_object_unref(xmlfile); return rc; } menu-cache-1.1.1/menu-cache-gen/menu-compose.c000066400000000000000000001231251475361627100210660ustar00rootroot00000000000000/* * menu-compose.c : scans appropriate .desktop files and composes cache file. * * Copyright 2014-2015 Andriy Grytsenko (LStranger) * * This file is a part of libmenu-cache package and created program * should be not used without the library. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include #endif #include "menu-tags.h" #include "version.h" #include #include #include #define NONULL(a) (a == NULL) ? "" : a static GSList *DEs = NULL; static guint req_version = 1; /* old compatibility default */ static void menu_app_reset(MenuApp *app) { g_free(app->filename); g_free(app->title); g_free(app->key); app->key = NULL; g_free(app->comment); g_free(app->icon); g_free(app->generic_name); g_free(app->exec); g_free(app->try_exec); g_free(app->wd); g_free(app->categories); g_free(app->keywords); g_free(app->show_in); g_free(app->hide_in); } static void menu_app_free(gpointer data) { MenuApp *app = data; menu_app_reset(app); g_free(app->id); g_list_free(app->dirs); g_list_free(app->menus); g_slice_free(MenuApp, app); } static char *_escape_lf(char *str) { char *c; if (str != NULL && (c = strchr(str, '\n')) != NULL) { GString *s = g_string_new_len(str, c - str); while (*c) { if (*c == '\n') g_string_append(s, "\\n"); else g_string_append_c(s, *c); c++; } g_free(str); str = g_string_free(s, FALSE); } return str; } static char *_get_string(GKeyFile *kf, const char *key) { return _escape_lf(g_key_file_get_string(kf, G_KEY_FILE_DESKTOP_GROUP, key, NULL)); } /* g_key_file_get_locale_string is too much limited so implement replacement */ static char *_get_language_string(GKeyFile *kf, const char *key) { char **lang; char *try_key, *str; for (lang = languages; lang[0] != NULL; lang++) { try_key = g_strdup_printf("%s[%s]", key, lang[0]); str = _get_string(kf, try_key); g_free(try_key); if (str != NULL) return str; } return _escape_lf(g_key_file_get_locale_string(kf, G_KEY_FILE_DESKTOP_GROUP, key, languages[0], NULL)); } static char **_get_string_list(GKeyFile *kf, const char *key, gsize *lp) { char **str, **p; str = g_key_file_get_string_list(kf, G_KEY_FILE_DESKTOP_GROUP, key, lp, NULL); if (str != NULL) for (p = str; p[0] != NULL; p++) p[0] = _escape_lf(p[0]); return str; } static char **_get_language_string_list(GKeyFile *kf, const char *key, gsize *lp) { char **lang; char *try_key, **str; for (lang = languages; lang[0] != NULL; lang++) { try_key = g_strdup_printf("%s[%s]", key, lang[0]); str = _get_string_list(kf, try_key, lp); g_free(try_key); if (str != NULL) return str; } str = g_key_file_get_locale_string_list(kf, G_KEY_FILE_DESKTOP_GROUP, key, languages[0], lp, NULL); if (str != NULL) for (lang = str; lang[0] != NULL; lang++) lang[0] = _escape_lf(lang[0]); return str; } static void _fill_menu_from_file(MenuMenu *menu, const char *path) { GKeyFile *kf; if (!g_str_has_suffix(path, ".directory")) /* ignore random names */ return; kf = g_key_file_new(); if (!g_key_file_load_from_file(kf, path, G_KEY_FILE_KEEP_TRANSLATIONS, NULL)) goto exit; menu->title = _get_language_string(kf, G_KEY_FILE_DESKTOP_KEY_NAME); menu->comment = _get_language_string(kf, G_KEY_FILE_DESKTOP_KEY_COMMENT); menu->icon = _get_string(kf, G_KEY_FILE_DESKTOP_KEY_ICON); menu->layout.nodisplay = g_key_file_get_boolean(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, NULL); menu->layout.is_set = TRUE; exit: g_key_file_free(kf); } static const char **menu_app_intern_key_file_list(GKeyFile *kf, const char *key, gboolean localized, gboolean add_to_des) { gsize len, i; char **val; const char **res; if (localized) val = _get_language_string_list(kf, key, &len); else val = _get_string_list(kf, key, &len); if (val == NULL) return NULL; res = (const char **)g_new(char *, len + 1); for (i = 0; i < len; i++) { res[i] = g_intern_string(val[i]); if (add_to_des && g_slist_find(DEs, res[i]) == NULL) DEs = g_slist_append(DEs, (gpointer)res[i]); } res[i] = NULL; g_strfreev(val); return res; } static void _fill_app_from_key_file(MenuApp *app, GKeyFile *kf) { app->title = _get_language_string(kf, G_KEY_FILE_DESKTOP_KEY_NAME); app->comment = _get_language_string(kf, G_KEY_FILE_DESKTOP_KEY_COMMENT); app->icon = _get_string(kf, G_KEY_FILE_DESKTOP_KEY_ICON); app->generic_name = _get_language_string(kf, G_KEY_FILE_DESKTOP_KEY_GENERIC_NAME); app->exec = _get_string(kf, G_KEY_FILE_DESKTOP_KEY_EXEC); app->try_exec = _get_string(kf, G_KEY_FILE_DESKTOP_KEY_TRY_EXEC); app->wd = _get_string(kf, G_KEY_FILE_DESKTOP_KEY_PATH); app->categories = menu_app_intern_key_file_list(kf, G_KEY_FILE_DESKTOP_KEY_CATEGORIES, FALSE, FALSE); app->keywords = menu_app_intern_key_file_list(kf, "Keywords", TRUE, FALSE); app->show_in = menu_app_intern_key_file_list(kf, G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN, FALSE, TRUE); app->hide_in = menu_app_intern_key_file_list(kf, G_KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN, FALSE, TRUE); app->use_terminal = g_key_file_get_boolean(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TERMINAL, NULL); app->use_notification = g_key_file_get_boolean(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_STARTUP_NOTIFY, NULL); app->hidden = g_key_file_get_boolean(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, NULL); } static GHashTable *all_apps = NULL; static GSList *loaded_dirs = NULL; static GList *_make_def_layout(void) { MenuMerge *mm; GList *layout; mm = g_slice_new(MenuMerge); mm->type = MENU_CACHE_TYPE_NONE; mm->merge_type = MERGE_FILES; layout = g_list_prepend(NULL, mm); mm = g_slice_new(MenuMerge); mm->type = MENU_CACHE_TYPE_NONE; mm->merge_type = MERGE_MENUS; return g_list_prepend(layout, mm); } static void _fill_apps_from_dir(MenuMenu *menu, GList *lptr, GString *prefix, gboolean is_legacy) { const char *dir = lptr->data; GDir *gd; const char *name; char *filename, *id; gsize prefix_len = prefix->len; MenuApp *app; GKeyFile *kf; if (g_slist_find(loaded_dirs, dir) == NULL) loaded_dirs = g_slist_prepend(loaded_dirs, (gpointer)dir); /* the directory might be scanned with different prefix already */ else if (prefix->str[0] == '\0') return; gd = g_dir_open(dir, 0, NULL); if (gd == NULL) return; kf = g_key_file_new(); DBG("fill apps from dir [%s]%s", prefix->str, dir); /* Scan the directory with subdirs, ignore not .desktop files, ignore already present files that are allocated */ while ((name = g_dir_read_name(gd)) != NULL) { filename = g_build_filename(dir, name, NULL); if (g_file_test(filename, G_FILE_TEST_IS_DIR)) { /* recursion */ if (is_legacy) { MenuMenu *submenu = g_slice_new0(MenuMenu); submenu->layout = menu->layout; /* copy all */ submenu->layout.items = _make_def_layout(); /* default layout */ submenu->layout.inline_limit_is_set = TRUE; /* marker */ submenu->name = g_strdup(name); submenu->dir = g_intern_string(filename); menu->children = g_list_append(menu->children, submenu); } else { g_string_append(prefix, name); g_string_append_c(prefix, '-'); name = g_intern_string(filename); /* a little trick here - we insert new node after this one */ lptr = g_list_insert_before(lptr, lptr->next, (gpointer)name); _fill_apps_from_dir(menu, lptr->next, prefix, FALSE); g_string_truncate(prefix, prefix_len); } } else if (!g_str_has_suffix(name, ".desktop") || !g_file_test(filename, G_FILE_TEST_IS_REGULAR) || !g_key_file_load_from_file(kf, filename, G_KEY_FILE_KEEP_TRANSLATIONS, NULL)) ; /* ignore not key files */ else if ((id = g_key_file_get_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TYPE, NULL)) == NULL || strcmp(id, G_KEY_FILE_DESKTOP_TYPE_APPLICATION) != 0) /* ignore non-applications */ g_free(id); else { g_free(id); if (prefix_len > 0) { g_string_append(prefix, name); app = g_hash_table_lookup(all_apps, prefix->str); } else app = g_hash_table_lookup(all_apps, name); if (app == NULL) { if (!g_key_file_get_boolean(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_HIDDEN, NULL)) { /* deleted file should be ignored */ app = g_slice_new0(MenuApp); app->type = MENU_CACHE_TYPE_APP; app->filename = (prefix_len > 0) ? g_strdup(name) : NULL; app->id = g_strdup((prefix_len > 0) ? prefix->str : name); VDBG("found app id=%s", app->id); g_hash_table_insert(all_apps, app->id, app); app->dirs = g_list_prepend(NULL, (gpointer)dir); _fill_app_from_key_file(app, kf); } } else if (app->allocated) g_warning("id '%s' already allocated for %s and requested to" " change to %s, ignoring request", name, (const char *)app->dirs->data, dir); else if (g_key_file_get_boolean(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_HIDDEN, NULL)) { VDBG("removing app id=%s", app->id), g_hash_table_remove(all_apps, name); } else { /* reset the data */ menu_app_reset(app); app->filename = (prefix_len > 0) ? g_strdup(name) : NULL; /* reorder dirs list */ app->dirs = g_list_remove(app->dirs, dir); app->dirs = g_list_prepend(app->dirs, (gpointer)dir); _fill_app_from_key_file(app, kf); /* FIXME: conform to spec about Legacy in Categories field */ } if (prefix_len > 0) g_string_truncate(prefix, prefix_len); } g_free(filename); } g_dir_close(gd); g_key_file_free(kf); } static int _compare_items(gconstpointer a, gconstpointer b) { /* return negative value to reverse sort list */ return -strcmp(((MenuApp*)a)->type == MENU_CACHE_TYPE_APP ? ((MenuApp*)a)->key : ((MenuMenu*)a)->key, ((MenuApp*)b)->type == MENU_CACHE_TYPE_APP ? ((MenuApp*)b)->key : ((MenuMenu*)b)->key); } static gboolean menu_app_match_tag(MenuApp *app, FmXmlFileItem *it) { FmXmlFileTag tag = fm_xml_file_item_get_tag(it); GList *children, *child; gboolean ok = FALSE; VVDBG("menu_app_match_tag: entering <%s>", fm_xml_file_item_get_tag_name(it)); children = fm_xml_file_item_get_children(it); if (tag == menuTag_Or) { for (child = children; child; child = child->next) if (menu_app_match_tag(app, child->data)) break; ok = (child != NULL); } else if (tag == menuTag_And) { for (child = children; child; child = child->next) if (!menu_app_match_tag(app, child->data)) break; ok = (child == NULL); } else if (tag == menuTag_Not) { for (child = children; child; child = child->next) if (menu_app_match_tag(app, child->data)) break; ok = (child == NULL); } else if (tag == menuTag_All) ok = TRUE; else if (tag == menuTag_Filename) { register const char *id = fm_xml_file_item_get_data(children->data, NULL); ok = (g_strcmp0(id, app->id) == 0); } else if (tag == menuTag_Category) { if (app->categories != NULL) { const char *cat = g_intern_string(fm_xml_file_item_get_data(children->data, NULL)); const char **cats = app->categories; while (*cats) if (*cats == cat) break; else cats++; ok = (*cats != NULL); } } g_list_free(children); VVDBG("menu_app_match_tag %s: leaving <%s>: %d", app->id, fm_xml_file_item_get_tag_name(it), ok); return ok; } static gboolean menu_app_match_excludes(MenuApp *app, GList *rules); static gboolean menu_app_match(MenuApp *app, GList *rules, gboolean do_all) { MenuRule *rule; GList *children, *child; for (; rules != NULL; rules = rules->next) { rule = rules->data; if (rule->type != MENU_CACHE_TYPE_NONE || fm_xml_file_item_get_tag(rule->rule) != menuTag_Include) continue; children = fm_xml_file_item_get_children(rule->rule); for (child = children; child; child = child->next) if (menu_app_match_tag(app, child->data)) break; g_list_free(children); if (child != NULL) return (!do_all || !menu_app_match_excludes(app, rules->next)); } return FALSE; } static gboolean menu_app_match_excludes(MenuApp *app, GList *rules) { MenuRule *rule; GList *children, *child; for (; rules != NULL; rules = rules->next) { rule = rules->data; if (rule->type != MENU_CACHE_TYPE_NONE || fm_xml_file_item_get_tag(rule->rule) != menuTag_Exclude) continue; children = fm_xml_file_item_get_children(rule->rule); for (child = children; child; child = child->next) if (menu_app_match_tag(app, child->data)) break; g_list_free(children); if (child != NULL) /* application might be included again later so check for it */ return !menu_app_match(app, rules->next, TRUE); } return FALSE; } static void _free_leftovers(GList *item); static void menu_menu_free(MenuMenu *menu) { g_free(menu->name); g_free(menu->key); g_list_foreach(menu->id, (GFunc)g_free, NULL); g_list_free(menu->id); _free_leftovers(menu->children); _free_layout_items(menu->layout.items); g_free(menu->title); g_free(menu->comment); g_free(menu->icon); g_slice_free(MenuMenu, menu); } static void _free_leftovers(GList *item) { union { MenuMenu *menu; MenuRule *rule; } a = { NULL }; while (item) { a.menu = item->data; if (a.rule->type == MENU_CACHE_TYPE_NONE) g_slice_free(MenuRule, a.rule); else if (a.rule->type == MENU_CACHE_TYPE_DIR) menu_menu_free(a.menu); /* MenuApp and MenuSep are not allocated in menu->children */ item = g_list_delete_link(item, item); } } /* dirs are in order "first is more relevant" */ static void _stage1(MenuMenu *menu, GList *dirs, GList *apps, GList *legacy, GList *p) { GList *child, *_dirs = NULL, *_apps = NULL, *_legs = NULL, *_lprefs = NULL; GList *l, *available = NULL, *result; const char *id; char *filename; GString *prefix; MenuApp *app; FmXmlFileTag tag; GHashTableIter iter; DBG("... entering %s (%d dirs %d apps)", menu->name, g_list_length(dirs), g_list_length(apps)); /* Gather our dirs : DirectoryDir AppDir LegacyDir KDELegacyDirs */ for (child = menu->children; child; child = child->next) { MenuRule *rule = child->data; if (rule->type != MENU_CACHE_TYPE_NONE) continue; tag = fm_xml_file_item_get_tag(rule->rule); if (tag == menuTag_DirectoryDir) { id = g_intern_string(fm_xml_file_item_get_data(fm_xml_file_item_find_child(rule->rule, FM_XML_FILE_TEXT), NULL)); DBG("new DirectoryDir %s", id); if (_dirs == NULL) _dirs = g_list_copy(dirs); /* replace and reorder the list */ _dirs = g_list_remove(_dirs, id); _dirs = g_list_prepend(_dirs, (gpointer)id); } else if (tag == menuTag_AppDir) { id = g_intern_string(fm_xml_file_item_get_data(fm_xml_file_item_find_child(rule->rule, FM_XML_FILE_TEXT), NULL)); DBG("new AppDir %s", id); _apps = g_list_prepend(_apps, (gpointer)id); } else if (tag == menuTag_LegacyDir) { id = g_intern_string(fm_xml_file_item_get_data(fm_xml_file_item_find_child(rule->rule, FM_XML_FILE_TEXT), NULL)); DBG("new LegacyDir %s", id); _legs = g_list_prepend(_legs, (gpointer)id); _lprefs = g_list_prepend(_lprefs, (gpointer)fm_xml_file_item_get_comment(rule->rule)); } } /* Gather data from files in the dirs */ if (_dirs != NULL) dirs = _dirs; for (l = menu->id; l; l = l->next) { /* scan dirs now for availability of any of ids */ filename = NULL; for (child = dirs; child; child = child->next) { filename = g_build_filename(child->data, l->data, NULL); if (g_file_test(filename, G_FILE_TEST_IS_REGULAR)) break; g_free(filename); filename = NULL; } if (filename == NULL) for (child = _legs; child; child = child->next) { filename = g_build_filename(child->data, l->data, NULL); if (g_file_test(filename, G_FILE_TEST_IS_REGULAR)) break; g_free(filename); filename = NULL; } if (filename == NULL) for (child = legacy; child; child = child->next) { filename = g_build_filename(child->data, l->data, NULL); if (g_file_test(filename, G_FILE_TEST_IS_REGULAR)) break; g_free(filename); filename = NULL; } if (filename != NULL) { VVDBG("found dir file %s", filename); _fill_menu_from_file(menu, filename); g_free(filename); if (!menu->layout.is_set) continue; menu->dir = child->data; if (l != menu->id) /* relocate matched id to top if required */ { menu->id = g_list_remove_link(menu->id, l); menu->id = g_list_concat(menu->id, l); } break; } } if (menu->layout.inline_limit_is_set && !menu->layout.is_set) { filename = g_build_filename(menu->dir, ".directory", NULL); _fill_menu_from_file(menu, filename); g_free(filename); filename = NULL; } prefix = g_string_new(""); _apps = g_list_reverse(_apps); _legs = g_list_reverse(_legs); /* the same directory being scanned with different prefix should be denied */ for (l = _apps; l; l = l->next) { for (child = _apps; child; child = result) { int len; result = child->next; /* it is not used yet so we can use it now */ if (child == l) continue; len = strlen(l->data); if (strncmp(l->data, child->data, len) == 0 && ((const char *)child->data)[len] == G_DIR_SEPARATOR) _apps = g_list_delete_link(_apps, child); } for (child = _legs; child; child = result) { int len; result = child->next; len = strlen(l->data); if (strncmp(l->data, child->data, len) == 0 && ((const char *)child->data)[len] == G_DIR_SEPARATOR) { len = g_list_position(_legs, child); _legs = g_list_delete_link(_legs, child); child = g_list_nth(_lprefs, len); _lprefs = g_list_delete_link(_lprefs, child); } } } if (!menu->layout.inline_limit_is_set) for (l = _apps; l; l = l->next) { /* scan and fill the list */ _fill_apps_from_dir(menu, l, prefix, FALSE); } if (_apps != NULL) apps = _apps = g_list_concat(g_list_copy(apps), _apps); for (l = _legs, child = _lprefs; l; l = l->next, child = child->next) { /* use prefix from attribute */ g_string_assign(prefix, NONULL(child->data)); VDBG("got legacy prefix %s", (char*)child->data); _fill_apps_from_dir(menu, l, prefix, TRUE); g_string_truncate(prefix, 0); } if (_legs != NULL) legacy = _legs = g_list_concat(g_list_copy(legacy), _legs); if (_lprefs != NULL) p = _lprefs = g_list_concat(g_list_copy(p), _lprefs); /* Gather all available files (some in $all_apps may be not in $apps) */ VDBG("... do matching"); g_hash_table_iter_init(&iter, all_apps); while (g_hash_table_iter_next(&iter, NULL, (gpointer *)&app)) { app->matched = FALSE; /* check every dir if it is in $apps */ if (menu->layout.inline_limit_is_set) { for (child = app->dirs; child; child = child->next) if (menu->dir == child->data) break; } else for (child = app->dirs; child; child = child->next) { for (l = apps; l; l = l->next) { if (l->data == child->data) break; } if (l != NULL) /* found one */ break; } VVDBG("check %s in %s: %d", app->id, app->dirs ? (const char *)app->dirs->data : "(nil)", child != NULL); if (child == NULL) /* not matched */ continue; /* Check matching : Include And Or Not All */ if (menu->layout.inline_limit_is_set) app->matched = (app->categories == NULL); /* see the spec */ else app->matched = menu_app_match(app, menu->children, FALSE); if (!app->matched) continue; app->allocated = TRUE; /* Mark it by Exclude And Or Not All */ app->excluded = menu_app_match_excludes(app, menu->children); VVDBG("found match: %s excluded:%d", app->id, app->excluded); if (!app->excluded) available = g_list_prepend(available, app); } /* Compose layout using available list and replace menu->children */ VDBG("... compose (available=%d)", g_list_length(available)); result = NULL; for (child = menu->layout.items; child; child = child->next) { GList *next; app = child->data; /* either: MenuMenuname, MemuFilename, MenuSep, MenuMerge */ switch (app->type) { case MENU_CACHE_TYPE_DIR: /* MenuMenuname */ VDBG("composing Menuname %s", ((MenuMenuname *)app)->name); for (l = menu->children; l; l = l->next) if (((MenuMenu *)l->data)->layout.type == MENU_CACHE_TYPE_DIR && strcmp(((MenuMenuname *)app)->name, ((MenuMenu *)l->data)->name) == 0) break; if (l != NULL) /* found such menu */ { /* apply custom settings */ if (((MenuMenuname *)app)->layout.only_unallocated) ((MenuMenu *)l->data)->layout.show_empty = ((MenuMenuname *)app)->layout.show_empty; if (((MenuMenuname *)app)->layout.is_set) ((MenuMenu *)l->data)->layout.allow_inline = ((MenuMenuname *)app)->layout.allow_inline; if (((MenuMenuname *)app)->layout.inline_header_is_set) ((MenuMenu *)l->data)->layout.inline_header = ((MenuMenuname *)app)->layout.inline_header; if (((MenuMenuname *)app)->layout.inline_alias_is_set) ((MenuMenu *)l->data)->layout.inline_alias = ((MenuMenuname *)app)->layout.inline_alias; if (((MenuMenuname *)app)->layout.inline_limit_is_set) ((MenuMenu *)l->data)->layout.inline_limit = ((MenuMenuname *)app)->layout.inline_limit; /* remove from menu->children */ menu->children = g_list_remove_link(menu->children, l); /* prepend to result */ result = g_list_concat(l, result); /* ready for recursion now */ _stage1(l->data, dirs, apps, legacy, p); } break; case MENU_CACHE_TYPE_APP: /* MemuFilename */ VDBG("composing Filename %s", ((MenuFilename *)app)->id); app = g_hash_table_lookup(all_apps, ((MenuFilename *)app)->id); if (app == NULL) /* not available, ignoring it */ break; l = g_list_find(result, app); /* this might be slow but we have to do this because app might be already added into result */ if (l != NULL) { /* move it out to this place */ result = g_list_remove_link(result, l); VVDBG("+++ composing app %s (move)", app->id); } else { l = g_list_find(available, app); VVDBG("+++ composing app %s%s", app->id, (l == NULL) ? " (add)" : ""); if (l != NULL) available = g_list_remove_link(available, l); else l = g_list_prepend(NULL, app); } if (l != NULL) app->menus = g_list_prepend(app->menus, menu); result = g_list_concat(l, result); break; case MENU_CACHE_TYPE_SEP: /* MenuSep */ VDBG("composing Separator"); result = g_list_prepend(result, app); break; case MENU_CACHE_TYPE_NONE: /* MenuMerge */ VDBG("composing Merge type %d", ((MenuMerge *)app)->merge_type); next = NULL; tag = 0; switch (((MenuMerge *)app)->merge_type) { case MERGE_FILES: tag = 1; /* use it as mark to not add dirs */ case MERGE_ALL: for (l = available; l; l = l->next) { app = l->data; VVDBG("+++ composing app %s", app->id); if (app->key == NULL) { if (app->title != NULL) app->key = g_utf8_collate_key(app->title, -1); else g_warning("id %s has no Name", app->id), app->key = g_utf8_collate_key(app->id, -1); } app->menus = g_list_prepend(app->menus, menu); } next = available; available = NULL; /* continue with menus */ case MERGE_MENUS: if (tag != 1) for (l = menu->children; l; ) { if (((MenuMenu *)l->data)->layout.type == MENU_CACHE_TYPE_DIR) { GList *this = l; /* find it in the rest of layout and skip if it's found */ for (l = child->next; l; l = l->next) if (((MenuMenuname *)l->data)->layout.type == MENU_CACHE_TYPE_DIR && strcmp(((MenuMenuname *)l->data)->name, ((MenuMenu *)this->data)->name) == 0) break; if (l != NULL) { /* it will be added later by MenuMenuname handler */ l = this->next; continue; } _stage1(this->data, dirs, apps, legacy, p); /* it's time for recursion */ VVDBG("+++ composing menu %s (%s)", ((MenuMenu *)this->data)->name, ((MenuMenu *)this->data)->title); if (((MenuMenu *)this->data)->key == NULL) { if (((MenuMenu *)this->data)->title != NULL) ((MenuMenu *)this->data)->key = g_utf8_collate_key(((MenuMenu *)this->data)->title, -1); else ((MenuMenu *)this->data)->key = g_utf8_collate_key(((MenuMenu *)this->data)->name, -1); } l = this->next; /* move out from menu->children into result */ menu->children = g_list_remove_link(menu->children, this); next = g_list_concat(this, next); } else l = l->next; } result = g_list_concat(g_list_sort(next, _compare_items), result); break; default: ; } } } VDBG("... cleanup"); _free_leftovers(menu->children); menu->children = g_list_reverse(result); for (child = menu->children; child; ) { MenuMenu *submenu = child->data; child = child->next; if (submenu->layout.type == MENU_CACHE_TYPE_DIR && submenu->layout.allow_inline && (submenu->layout.inline_limit == 0 || (int)g_list_length(submenu->children) <= submenu->layout.inline_limit)) { DBG("*** got some inline!"); if (submenu->layout.inline_alias && g_list_length(submenu->children) == 1) { /* replace name of single child with name of submenu */ VDBG("replacing title of single child of %s due to inline_alias", submenu->name); app = submenu->children->data; if (app->type == MENU_CACHE_TYPE_DIR) { g_free(((MenuMenu *)app)->title); ((MenuMenu *)app)->title = g_strdup(submenu->title ? submenu->title : submenu->name); } else if (app->type == MENU_CACHE_TYPE_APP) { g_free(app->title); app->title = g_strdup(submenu->title ? submenu->title : submenu->name); } } /* FIXME: inline the submenu... how to use inline_header? */ submenu->children = g_list_reverse(submenu->children); while (submenu->children != NULL) { menu->children = g_list_insert_before(menu->children, child, submenu->children->data); submenu->children = g_list_delete_link(submenu->children, submenu->children); } menu->children = g_list_remove(menu->children, submenu); menu_menu_free(submenu); } } /* NOTE: now only menus are allocated in menu->children */ DBG("... done %s", menu->name); /* Do cleanup */ g_list_free(available); g_list_free(_dirs); g_list_free(_apps); g_list_free(_legs); g_list_free(_lprefs); g_string_free(prefix, TRUE); } static gint _stage2(MenuMenu *menu, gboolean with_hidden) { GList *child = menu->children, *next, *to_delete = NULL; MenuApp *app; gint count = 0; VVDBG("stage 2: entered '%s'", menu->name); while (child) { app = child->data; next = child->next; switch (app->type) { case MENU_CACHE_TYPE_APP: /* Menu App */ if (menu->layout.only_unallocated && app->menus->next != NULL) { VDBG("removing from %s as only_unallocated %s",menu->name,app->id); /* it is more than in one menu */ menu->children = g_list_delete_link(menu->children, child); app->menus = g_list_remove(app->menus, menu); } else if (app->hidden && !with_hidden) /* should be not displayed */ menu->children = g_list_delete_link(menu->children, child); else count++; break; case MENU_CACHE_TYPE_DIR: /* MenuMenu */ /* do recursion */ if (_stage2(child->data, with_hidden) > 0) count++; else if (!with_hidden || req_version < 2) to_delete = g_list_prepend(to_delete, child); break; default: /* separator */ if (child == menu->children || next == NULL || (app = next->data)->type == MENU_CACHE_TYPE_SEP) menu->children = g_list_delete_link(menu->children, child); } child = next; } VVDBG("stage 2: counted '%s': %d", menu->name, count); if (count > 0 && with_hidden) g_list_free(to_delete); else while (to_delete) /* if no apps here then don't keep dirs as well */ { child = to_delete->data; VVDBG("stage 2: deleting empty '%s'", ((MenuMenu *)child->data)->name); menu_menu_free(child->data); menu->children = g_list_delete_link(menu->children, child); to_delete = g_list_delete_link(to_delete, to_delete); } if (count == 0) { if (menu->layout.show_empty) count++; else menu->layout.nodisplay = TRUE; } return count; } static inline int _compose_flags(const char **f) { int x = 0, i; while (*f) { i = g_slist_index(DEs, *f++); if (i >= 0) x |= 1 << i; } return x; } static gboolean write_app_extra(FILE *f, MenuApp *app) { gboolean ret; char *cats, *keywords; char *null_list[] = { NULL }; if (req_version < 2) return TRUE; cats = g_strjoinv(";", app->categories ? (char **)app->categories : null_list); keywords = g_strjoinv(",", app->keywords ? (char **)app->keywords : null_list); ret = fprintf(f, "%s\n%s\n%s\n%s\n", NONULL(app->try_exec), NONULL(app->wd), cats, keywords) > 0; g_free(cats); g_free(keywords); return ret; } static gboolean write_app(FILE *f, MenuApp *app, gboolean with_hidden) { int index; MenuCacheItemFlag flags = 0; int show = 0; if (app->hidden && !with_hidden) return TRUE; index = MAX(g_slist_index(AppDirs, app->dirs->data), 0) + g_slist_length(DirDirs); if (app->use_terminal) flags |= FLAG_USE_TERMINAL; if (app->hidden) flags |= FLAG_IS_NODISPLAY; if (app->use_notification) flags |= FLAG_USE_SN; if (app->show_in) show = _compose_flags(app->show_in); else if (app->hide_in) show = ~_compose_flags(app->hide_in); return fprintf(f, "-%s\n%s\n%s\n%s\n%s\n%d\n%s\n%s\n%u\n%d\n", app->id, NONULL(app->title), NONULL(app->comment), NONULL(app->icon), NONULL(app->filename), index, NONULL(app->generic_name), NONULL(app->exec), flags, show) > 0 && write_app_extra(f, app); } static gboolean write_menu(FILE *f, MenuMenu *menu, gboolean with_hidden) { int index; GList *child; gboolean ok = TRUE; if (!with_hidden && !menu->layout.show_empty && menu->children == NULL) return TRUE; if (menu->layout.nodisplay && (!with_hidden || req_version < 2)) return TRUE; index = g_slist_index(DirDirs, menu->dir); if (fprintf(f, "+%s\n%s\n%s\n%s\n%s\n%d\n", menu->name, NONULL(menu->title), NONULL(menu->comment), NONULL(menu->icon), menu->id ? (const char *)menu->id->data : "", index) < 0) return FALSE; /* pass show_empty into file if format is v.1.2 */ if (req_version >= 2 && fprintf(f, "%d\n", menu->layout.nodisplay ? FLAG_IS_NODISPLAY : 0) < 0) return FALSE; for (child = menu->children; ok && child != NULL; child = child->next) { index = ((MenuApp *)child->data)->type; if (index == MENU_CACHE_TYPE_DIR) ok = write_menu(f, child->data, with_hidden); else if (index == MENU_CACHE_TYPE_APP) ok = write_app(f, child->data, with_hidden); else if (child->next != NULL && child != menu->children && ((MenuApp *)child->next->data)->type != MENU_CACHE_TYPE_SEP) /* separator - not add duplicates nor at start nor at end */ fprintf(f, "-\n"); } fputc('\n', f); return ok; } /* * we handle here only: * - menuTag_DirectoryDir : for directory files list * - menuTag_AppDir menuTag_LegacyDir menuTag_KDELegacyDirs : for app files list * - menuTag_Include menuTag_Exclude menuTag_And menuTag_Or menuTag_Not menuTag_All : * as matching rules */ gboolean save_menu_cache(MenuMenu *layout, const char *menuname, const char *file, gboolean with_hidden) { const char *de_names[N_KNOWN_DESKTOPS] = { "LXDE", "GNOME", "KDE", "XFCE", "ROX" }; char *tmp; FILE *f = NULL; GSList *l; int i; gboolean ok = FALSE; tmp = (char *)g_getenv("CACHE_GEN_VERSION"); if (tmp && sscanf(tmp, "%d.%u", &i, &req_version) == 2) { if (i != VER_MAJOR) /* unsupported format requested */ return FALSE; } if (req_version < VER_MINOR_SUPPORTED) /* unsupported format requested */ return FALSE; if (req_version > VER_MINOR) /* fallback to maximal supported format */ req_version = VER_MINOR; all_apps = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, menu_app_free); for (i = 0; i < N_KNOWN_DESKTOPS; i++) DEs = g_slist_append(DEs, (gpointer)g_intern_static_string(de_names[i])); /* Recursively add files into layout, don't take OnlyUnallocated into account */ _stage1(layout, NULL, NULL, NULL, NULL); /* Recursively remove non-matched files by OnlyUnallocated flag */ _stage2(layout, with_hidden); /* Prepare temporary file for safe creation */ tmp = strrchr(menuname, G_DIR_SEPARATOR); if (tmp) menuname = &tmp[1]; tmp = g_path_get_dirname(file); if (tmp != NULL && !g_file_test(tmp, G_FILE_TEST_EXISTS)) g_mkdir_with_parents(tmp, 0700); g_free(tmp); tmp = g_strdup_printf("%sXXXXXX", file); i = g_mkstemp(tmp); if (i < 0) goto failed; /* Compose created layout into output file */ f = fdopen(i, "w"); if (f == NULL) goto failed; /* Write common data */ fprintf(f, "1.%d\n%s%s\n%d\n", req_version, /* use CACHE_GEN_VERSION */ menuname, with_hidden ? "+hidden" : "", g_slist_length(DirDirs) + g_slist_length(AppDirs) + g_slist_length(MenuDirs) + g_slist_length(MenuFiles)); VDBG("%d %d %d %d",g_slist_length(DirDirs),g_slist_length(AppDirs),g_slist_length(MenuDirs),g_slist_length(MenuFiles)); for (l = DirDirs; l; l = l->next) if (fprintf(f, "D%s\n", (const char *)l->data) < 0) goto failed; for (l = AppDirs; l; l = l->next) if (fprintf(f, "D%s\n", (const char *)l->data) < 0) goto failed; for (l = MenuDirs; l; l = l->next) if (fprintf(f, "D%s\n", (const char *)l->data) < 0) goto failed; for (l = MenuFiles; l; l = l->next) if (fprintf(f, "F%s\n", (const char *)l->data) < 0) goto failed; for (l = g_slist_nth(DEs, 5); l; l = l->next) if (fprintf(f, "%s;", (const char *)l->data) < 0) goto failed; fputc('\n', f); /* Write the menu tree */ ok = write_menu(f, layout, with_hidden); failed: if (f != NULL) fclose(f); if (ok) ok = g_rename(tmp, file) == 0; else if (tmp) g_unlink(tmp); /* Free all the data */ menu_menu_free(layout); g_free(tmp); g_hash_table_destroy(all_apps); g_slist_free(DEs); g_slist_free(loaded_dirs); return ok; } menu-cache-1.1.1/menu-cache-gen/menu-merge.c000066400000000000000000002147021475361627100205220ustar00rootroot00000000000000/* * menu-file.c : parses .menu file and merges all XML tags. * * Copyright 2013-2017 Andriy Grytsenko (LStranger) * * This file is a part of libmenu-cache package and created program * should be not used without the library. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include #endif #include "menu-tags.h" #include #include #include #define _(...) __VA_ARGS__ /* ---- applications.menu manipulations ---- */ typedef struct _MenuTreeData MenuTreeData; struct _MenuTreeData { FmXmlFile *menu; /* composite tree to analyze */ const char *file_path; /* current file */ gint line, pos; /* we remember position in deepest file */ }; FmXmlFileTag menuTag_Menu = 0; /* tags that are supported */ FmXmlFileTag menuTag_Include = 0; FmXmlFileTag menuTag_Exclude = 0; FmXmlFileTag menuTag_Filename = 0; FmXmlFileTag menuTag_Or = 0; FmXmlFileTag menuTag_And = 0; FmXmlFileTag menuTag_Not = 0; FmXmlFileTag menuTag_Category = 0; FmXmlFileTag menuTag_MergeFile = 0; FmXmlFileTag menuTag_MergeDir = 0; FmXmlFileTag menuTag_DefaultMergeDirs = 0; FmXmlFileTag menuTag_Directory = 0; FmXmlFileTag menuTag_Name = 0; FmXmlFileTag menuTag_Deleted = 0; FmXmlFileTag menuTag_NotDeleted = 0; FmXmlFileTag menuTag_AppDir = 0; FmXmlFileTag menuTag_DefaultAppDirs = 0; FmXmlFileTag menuTag_DirectoryDir = 0; FmXmlFileTag menuTag_DefaultDirectoryDirs = 0; FmXmlFileTag menuTag_OnlyUnallocated = 0; FmXmlFileTag menuTag_NotOnlyUnallocated = 0; FmXmlFileTag menuTag_All = 0; FmXmlFileTag menuTag_LegacyDir = 0; FmXmlFileTag menuTag_KDELegacyDirs = 0; FmXmlFileTag menuTag_Move = 0; FmXmlFileTag menuTag_Old = 0; FmXmlFileTag menuTag_New = 0; FmXmlFileTag menuTag_Layout = 0; FmXmlFileTag menuTag_DefaultLayout = 0; FmXmlFileTag menuTag_Menuname = 0; FmXmlFileTag menuTag_Separator = 0; FmXmlFileTag menuTag_Merge = 0; /* list of available app dirs */ GSList *AppDirs = NULL; /* list of available dir dirs */ GSList *DirDirs = NULL; /* list of menu dirs to monitor */ GSList *MenuDirs = NULL; /* we keep all the unfinished items in the hash */ static GHashTable *layout_hash = NULL; #define RETURN_TRUE_AND_DESTROY_IF_QUIET(a) do { \ if (verbose == 0) { \ fm_xml_file_item_destroy(a); \ return TRUE; \ } } while (0) static gboolean _fail_if_in_layout(FmXmlFileItem *item, GError **error) { FmXmlFileItem *parent; parent = fm_xml_file_item_get_parent(item); if (parent && (fm_xml_file_item_get_tag(parent) == menuTag_Layout || fm_xml_file_item_get_tag(parent) == menuTag_DefaultLayout)) { g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Tag <%s> is invalid below <%s>"), fm_xml_file_item_get_tag_name(item), fm_xml_file_item_get_tag_name(parent)); return TRUE; } return FALSE; } #define RETURN_IF_IN_LAYOUT(i,e) do { \ if (_fail_if_in_layout(i, e)) { \ if (verbose > 0) \ return FALSE; \ fm_xml_file_item_destroy(i); \ g_clear_error(e); \ return TRUE; \ } } while (0) /* this handler does nothing, used just to remember its id */ static gboolean _menu_xml_handler_pass(FmXmlFileItem *item, GList *children, char * const *attribute_names, char * const *attribute_values, guint n_attributes, gint line, gint pos, GError **error, gpointer user_data) { RETURN_IF_IN_LAYOUT(item, error); return TRUE; } /* checks the tag */ static gboolean _menu_xml_handler_Name(FmXmlFileItem *item, GList *children, char * const *attribute_names, char * const *attribute_values, guint n_attributes, gint line, gint pos, GError **error, gpointer user_data) { FmXmlFileItem *name_item; const char *name; RETURN_IF_IN_LAYOUT(item, error); name_item = fm_xml_file_item_find_child(item, FM_XML_FILE_TEXT); if (name_item == NULL || (name = fm_xml_file_item_get_data(name_item, NULL)) == NULL || strchr(name, '/') != NULL) /* empty or invalid tag */ { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Invalid tag")); return FALSE; } return TRUE; } static gboolean _menu_xml_handler_Not(FmXmlFileItem *item, GList *children, char * const *attribute_names, char * const *attribute_values, guint n_attributes, gint line, gint pos, GError **error, gpointer user_data) { FmXmlFileTag tag; GList *child; RETURN_IF_IN_LAYOUT(item, error); if (verbose > 0) for (child = children; child; child = child->next) { tag = fm_xml_file_item_get_tag(child->data); if (tag != menuTag_And && tag == menuTag_Or && tag == menuTag_Filename && tag != menuTag_Category && tag != menuTag_All) { g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Tag may contain only , ," " , or child")); return FALSE; } } return TRUE; } static void _add_app_dir(const char *app_dir) { const char *str = g_intern_string(app_dir); GSList *l; GDir *dir; char *path; for (l = AppDirs; l; l = l->next) if (l->data == str) break; if (l == NULL) AppDirs = g_slist_append(AppDirs, (gpointer)str); /* recursively scan the directory now */ dir = g_dir_open(app_dir, 0, NULL); if (dir) { while ((str = g_dir_read_name(dir)) != NULL) /* reuse pointer */ { path = g_build_filename(app_dir, str, NULL); if (g_file_test(path, G_FILE_TEST_IS_DIR)) _add_app_dir(path); g_free(path); } g_dir_close(dir); } } static gboolean _menu_xml_handler_AppDir(FmXmlFileItem *item, GList *children, char * const *attribute_names, char * const *attribute_values, guint n_attributes, gint line, gint pos, GError **error, gpointer user_data) { MenuTreeData *data = user_data; FmXmlFileItem *parent, *name; const char *path; char *_path; parent = fm_xml_file_item_get_parent(item); if (parent == NULL || fm_xml_file_item_get_tag(parent) != menuTag_Menu) { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Tag can appear only below ")); return FALSE; } if (children == NULL || fm_xml_file_item_get_tag((name = children->data)) != FM_XML_FILE_TEXT || (path = fm_xml_file_item_get_data(name, NULL)) == NULL) { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Invalid tag")); return FALSE; } if (g_path_is_absolute(path)) _path = NULL; else { char *_dir = g_path_get_dirname(data->file_path); path = _path = g_build_filename(_dir, path, NULL); g_free(_dir); /* FIXME: canonicalize path */ fm_xml_file_item_destroy(name); fm_xml_file_item_append_text(item, _path, -1, FALSE); } _add_app_dir(path); g_free(_path); /* contents of the directory will be parsed later */ return TRUE; } static gboolean _menu_xml_handler_DefaultAppDirs(FmXmlFileItem *item, GList *children, char * const *attribute_names, char * const *attribute_values, guint n_attributes, gint line, gint pos, GError **error, gpointer user_data) { FmXmlFileItem *parent; static gboolean added = FALSE; parent = fm_xml_file_item_get_parent(item); if (parent == NULL || fm_xml_file_item_get_tag(parent) != menuTag_Menu) { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Tag can appear only below ")); return FALSE; } if (!added) { const gchar* const * dirs = g_get_system_data_dirs(); char *dir = g_build_filename(g_get_user_data_dir(), "applications", NULL); _add_app_dir(dir); g_free(dir); if (dirs) while (dirs[0] != NULL) { dir = g_build_filename(*dirs++, "applications", NULL); _add_app_dir(dir); g_free(dir); } added = TRUE; } /* contents of the directories will be parsed later */ return TRUE; } static void _add_dir_dir(const char *dir_dir) { const char *str = g_intern_string(dir_dir); GSList *l; for (l = DirDirs; l; l = l->next) if (l->data == str) return; DirDirs = g_slist_append(DirDirs, (gpointer)str); } static gboolean _menu_xml_handler_DirectoryDir(FmXmlFileItem *item, GList *children, char * const *attribute_names, char * const *attribute_values, guint n_attributes, gint line, gint pos, GError **error, gpointer user_data) { MenuTreeData *data = user_data; FmXmlFileItem *parent, *name; const char *path; parent = fm_xml_file_item_get_parent(item); if (parent == NULL || fm_xml_file_item_get_tag(parent) != menuTag_Menu) { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Tag can appear only below ")); return FALSE; } if (children == NULL || fm_xml_file_item_get_tag((name = children->data)) != FM_XML_FILE_TEXT || (path = fm_xml_file_item_get_data(name, NULL)) == NULL) { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Invalid tag")); return FALSE; } if (g_path_is_absolute(path)) _add_dir_dir(path); else { char *_dir = g_path_get_dirname(data->file_path); char *_path = g_build_filename(_dir, path, NULL); g_free(_dir); fm_xml_file_item_destroy(name); fm_xml_file_item_append_text(item, _path, -1, FALSE); _add_dir_dir(_path); g_free(_path); } /* contents of the directory will be parsed later */ return TRUE; } static gboolean _menu_xml_handler_DefaultDirectoryDirs(FmXmlFileItem *item, GList *children, char * const *attribute_names, char * const *attribute_values, guint n_attributes, gint line, gint pos, GError **error, gpointer user_data) { FmXmlFileItem *parent; static gboolean added = FALSE; parent = fm_xml_file_item_get_parent(item); if (parent == NULL || fm_xml_file_item_get_tag(parent) != menuTag_Menu) { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Tag can appear only below ")); return FALSE; } if (!added) { const gchar* const * dirs = g_get_system_data_dirs(); char *dir = g_build_filename(g_get_user_data_dir(), "desktop-directories", NULL); _add_dir_dir(dir); g_free(dir); if (dirs) while (dirs[0] != NULL) { dir = g_build_filename(*dirs++, "desktop-directories", NULL); _add_dir_dir(dir); g_free(dir); } added = TRUE; } /* contents of the directories will be parsed later */ return TRUE; } /* adds .menu file contents next to current item */ static gboolean _menu_xml_handler_MergeFile(FmXmlFileItem *item, GList *children, char * const *attribute_names, char * const *attribute_values, guint n_attributes, gint line, gint pos, GError **error, gpointer user_data) { MenuTreeData *data = user_data; FmXmlFileItem *name; const char *path; name = fm_xml_file_item_get_parent(item); if (name == NULL || fm_xml_file_item_get_tag(name) != menuTag_Menu) { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Tag can appear only below ")); return FALSE; } if (children == NULL || fm_xml_file_item_get_tag((name = children->data)) != FM_XML_FILE_TEXT || (path = fm_xml_file_item_get_data(name, NULL)) == NULL) { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Invalid tag")); return FALSE; } if (attribute_names) while (attribute_names[0]) { if (strcmp(attribute_names[0], "type") == 0) { if (strcmp(attribute_values[0], "parent") == 0) { const gchar* const *dirs = g_get_system_config_dirs(); const gchar* const *dir; const char *rel_path; char *file; /* scan whole config dirs for matching, ignoring current path */ for (dir = dirs; dir[0]; dir++) if (g_str_has_prefix(data->file_path, dir[0])) { rel_path = data->file_path + strlen(dir[0]); goto replace_from_system_config_dirs; } /* not found in XDG_CONFIG_DIRS, test for XDG_CONFIG_HOME */ if (g_str_has_prefix(data->file_path, g_get_user_config_dir())) { rel_path = data->file_path + strlen(g_get_user_config_dir()); replace_from_system_config_dirs: fm_xml_file_item_destroy(name); while (*rel_path == G_DIR_SEPARATOR) rel_path++; while (dirs[0] != NULL) { if (dirs[0] == dir[0]) continue; file = g_build_filename(dirs[0], rel_path, NULL); if (g_file_test(file, G_FILE_TEST_IS_REGULAR)) { fm_xml_file_item_append_text(item, file, -1, FALSE); g_free(file); break; } g_free(file); dirs++; } if (dirs[0] != NULL) /* a file for merge was found */ return TRUE; } /* FIXME: what to do if parsed file is not in some config dirs? */ VDBG("No file for found, ignoring"); fm_xml_file_item_destroy(item); return TRUE; } break; } attribute_names++; attribute_values++; } if (!g_path_is_absolute(path)) { char *_dir = g_path_get_dirname(data->file_path); char *_path = g_build_filename(_dir, path, NULL); g_free(_dir); fm_xml_file_item_destroy(name); fm_xml_file_item_append_text(item, _path, -1, FALSE); g_free(_path); } /* actual merge will be done in next stage */ return TRUE; } /* adds all .menu files in directory */ static gboolean _menu_xml_handler_MergeDir(FmXmlFileItem *item, GList *children, char * const *attribute_names, char * const *attribute_values, guint n_attributes, gint line, gint pos, GError **error, gpointer user_data) { MenuTreeData *data = user_data; FmXmlFileItem *name; const char *path; name = fm_xml_file_item_get_parent(item); if (name == NULL || fm_xml_file_item_get_tag(name) != menuTag_Menu) { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Tag can appear only below ")); return FALSE; } if (children == NULL || fm_xml_file_item_get_tag((name = children->data)) != FM_XML_FILE_TEXT || (path = fm_xml_file_item_get_data(name, NULL)) == NULL) { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Invalid tag")); return FALSE; } if (!g_path_is_absolute(path)) { char *_dir = g_path_get_dirname(data->file_path); char *_path = g_build_filename(_dir, path, NULL); g_free(_dir); fm_xml_file_item_destroy(name); fm_xml_file_item_append_text(item, _path, -1, FALSE); g_free(_path); } /* actual merge will be done in next stage */ return TRUE; } /* used for validating DefaultMergeDirs and KDELegacyDirs */ static gboolean _menu_xml_handler_DefaultMergeDirs(FmXmlFileItem *item, GList *children, char * const *attribute_names, char * const *attribute_values, guint n_attributes, gint line, gint pos, GError **error, gpointer user_data) { FmXmlFileItem *parent = fm_xml_file_item_get_parent(item); if (parent == NULL || fm_xml_file_item_get_tag(parent) != menuTag_Menu) { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Tag <%s> can appear only below "), fm_xml_file_item_get_tag_name(item)); return FALSE; } /* actual merge will be done in next stage */ return TRUE; } static gboolean _menu_xml_handler_LegacyDir(FmXmlFileItem *item, GList *children, char * const *attribute_names, char * const *attribute_values, guint n_attributes, gint line, gint pos, GError **error, gpointer user_data) { MenuTreeData *data = user_data; FmXmlFileItem *name; const char *path; name = fm_xml_file_item_get_parent(item); if (name == NULL || fm_xml_file_item_get_tag(name) != menuTag_Menu) { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Tag can appear only below ")); return FALSE; } if (children == NULL || fm_xml_file_item_get_tag((name = children->data)) != FM_XML_FILE_TEXT || (path = fm_xml_file_item_get_data(name, NULL)) == NULL) { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Invalid tag")); return FALSE; } if (!g_path_is_absolute(path)) { char *_dir = g_path_get_dirname(data->file_path); char *_path = g_build_filename(_dir, path, NULL); g_free(_dir); fm_xml_file_item_destroy(name); fm_xml_file_item_append_text(item, _path, -1, FALSE); g_free(_path); } /* handle "prefix" attribute! */ path = 0; if (attribute_names) while (attribute_names[0]) { if (strcmp(attribute_names[0], "prefix") == 0) path = attribute_values[0]; attribute_names++; attribute_values++; } fm_xml_file_item_set_comment(item, path); /* actual merge will be done in next stage */ return TRUE; } static MenuLayout *_find_layout(FmXmlFileItem *item, gboolean create) { MenuLayout *layout = g_hash_table_lookup(layout_hash, item); if (layout == NULL && create) { layout = g_slice_new0(MenuLayout); /* set defaults */ layout->inline_header = TRUE; layout->inline_limit = 4; g_hash_table_insert(layout_hash, item, layout); } return layout; } static gboolean _menu_xml_handler_Filename(FmXmlFileItem *item, GList *children, char * const *attribute_names, char * const *attribute_values, guint n_attributes, gint line, gint pos, GError **error, gpointer user_data) { FmXmlFileItem *parent; const char *id; MenuLayout *layout; MenuFilename *app; FmXmlFileTag tag = 0; if (children == NULL || fm_xml_file_item_get_tag(children->data) != FM_XML_FILE_TEXT || (id = fm_xml_file_item_get_data(children->data, NULL)) == NULL) { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Empty tag")); return FALSE; } parent = fm_xml_file_item_get_parent(item); if (parent) tag = fm_xml_file_item_get_tag(parent); if (tag == menuTag_Layout || tag == menuTag_DefaultLayout) { layout = _find_layout(parent, TRUE); app = g_slice_new0(MenuFilename); app->type = MENU_CACHE_TYPE_APP; app->id = g_strdup(id); layout->items = g_list_append(layout->items, app); } return TRUE; } static gboolean _menu_xml_handler_Menuname(FmXmlFileItem *item, GList *children, char * const *attribute_names, char * const *attribute_values, guint n_attributes, gint line, gint pos, GError **error, gpointer user_data) { FmXmlFileItem *parent; const char *name; MenuLayout *layout; MenuMenuname *menu; FmXmlFileTag tag = 0; if (children == NULL || fm_xml_file_item_get_tag(children->data) != FM_XML_FILE_TEXT || (name = fm_xml_file_item_get_data(children->data, NULL)) == NULL) { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Empty tag")); return FALSE; } parent = fm_xml_file_item_get_parent(item); if (parent) tag = fm_xml_file_item_get_tag(parent); if (tag != menuTag_Layout && tag != menuTag_DefaultLayout) { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Tag may only appear below or" " ")); return FALSE; } layout = _find_layout(parent, TRUE); menu = g_slice_new0(MenuMenuname); menu->layout.type = MENU_CACHE_TYPE_DIR; menu->name = g_strdup(name); if (attribute_names) while (attribute_names[0]) { if (strcmp(attribute_names[0], "show_empty") == 0) { menu->layout.show_empty = (g_ascii_strcasecmp(attribute_values[0], "true") == 0); menu->layout.only_unallocated = TRUE; } else if (strcmp(attribute_names[0], "inline") == 0) { menu->layout.allow_inline = (g_ascii_strcasecmp(attribute_values[0], "true") == 0); menu->layout.is_set = TRUE; } else if (strcmp(attribute_names[0], "inline_header") == 0) { menu->layout.inline_header = (g_ascii_strcasecmp(attribute_values[0], "true") == 0); menu->layout.inline_header_is_set = TRUE; } else if (strcmp(attribute_names[0], "inline_alias") == 0) { menu->layout.inline_alias = (g_ascii_strcasecmp(attribute_values[0], "true") == 0); menu->layout.inline_alias_is_set = TRUE; } else if (strcmp(attribute_names[0], "inline_limit") == 0) { menu->layout.inline_limit = atoi(attribute_values[0]); menu->layout.inline_limit_is_set = TRUE; } attribute_names++; attribute_values++; } layout->items = g_list_append(layout->items, menu); return TRUE; } static gboolean _menu_xml_handler_Separator(FmXmlFileItem *item, GList *children, char * const *attribute_names, char * const *attribute_values, guint n_attributes, gint line, gint pos, GError **error, gpointer user_data) { FmXmlFileItem *parent; MenuLayout *layout; MenuSep *sep; FmXmlFileTag tag = 0; parent = fm_xml_file_item_get_parent(item); if (parent) tag = fm_xml_file_item_get_tag(parent); if (tag != menuTag_Layout && tag != menuTag_DefaultLayout) { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Tag may only appear below or" " ")); return FALSE; } layout = _find_layout(parent, TRUE); sep = g_slice_new0(MenuSep); sep->type = MENU_CACHE_TYPE_SEP; layout->items = g_list_append(layout->items, sep); return TRUE; } static gboolean _menu_xml_handler_Merge(FmXmlFileItem *item, GList *children, char * const *attribute_names, char * const *attribute_values, guint n_attributes, gint line, gint pos, GError **error, gpointer user_data) { FmXmlFileItem *parent; MenuLayout *layout; MenuMerge *mm; FmXmlFileTag tag = 0; MenuMergeType type = MERGE_NONE; parent = fm_xml_file_item_get_parent(item); if (parent) tag = fm_xml_file_item_get_tag(parent); if (tag != menuTag_Layout && tag != menuTag_DefaultLayout) { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Tag may only appear below or" " ")); return FALSE; } if (attribute_names) while (attribute_names[0]) { if (strcmp(attribute_names[0], "type") == 0) { if (strcmp(attribute_values[0], "menus") == 0) type = MERGE_MENUS; else if (strcmp(attribute_values[0], "files") == 0) type = MERGE_FILES; else if (strcmp(attribute_values[0], "all") == 0) type = MERGE_ALL; break; } attribute_names++; attribute_values++; } if (type == MERGE_NONE) { RETURN_TRUE_AND_DESTROY_IF_QUIET(item); g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Tag should have attribute 'type' as" " \"menus\", \"files\", or \"all\"")); return FALSE; } layout = _find_layout(parent, TRUE); mm = g_slice_new0(MenuMerge); mm->type = MENU_CACHE_TYPE_NONE; mm->merge_type = type; layout->items = g_list_append(layout->items, mm); return TRUE; } static gboolean _menu_xml_handler_Layout(FmXmlFileItem *item, GList *children, char * const *attribute_names, char * const *attribute_values, guint n_attributes, gint line, gint pos, GError **error, gpointer user_data) { /* ignore empty layout */ return TRUE; } static gboolean _menu_xml_handler_DefaultLayout(FmXmlFileItem *item, GList *children, char * const *attribute_names, char * const *attribute_values, guint n_attributes, gint line, gint pos, GError **error, gpointer user_data) { MenuLayout *layout; layout = _find_layout(item, TRUE); if (attribute_names) while (attribute_names[0]) { if (strcmp(attribute_names[0], "show_empty") == 0) layout->show_empty = (g_ascii_strcasecmp(attribute_values[0], "true") == 0); else if (strcmp(attribute_names[0], "inline") == 0) layout->allow_inline = (g_ascii_strcasecmp(attribute_values[0], "true") == 0); else if (strcmp(attribute_names[0], "inline_header") == 0) layout->inline_header = (g_ascii_strcasecmp(attribute_values[0], "true") == 0); else if (strcmp(attribute_names[0], "inline_alias") == 0) layout->inline_alias = (g_ascii_strcasecmp(attribute_values[0], "true") == 0); else if (strcmp(attribute_names[0], "inline_limit") == 0) layout->inline_limit = atoi(attribute_values[0]); attribute_names++; attribute_values++; } return TRUE; } static gboolean _merge_xml_file(MenuTreeData *data, FmXmlFileItem *item, const char *path, GList **m, GError **error, gboolean add_to_list) { FmXmlFile *menu = NULL; GList *xml = NULL, *it; /* loaded list */ GFile *gf; GError *err = NULL; const char *save_path; char *contents; gsize len; gboolean ok; /* check for loops! */ path = g_intern_string(path); it = *m; if (g_list_find(it, path) != NULL) { g_critical("merging loop detected for file '%s'", path); return TRUE; } *m = g_list_prepend(it, (gpointer)path); if (add_to_list && g_slist_find(MenuFiles, path) == NULL) MenuFiles = g_slist_append(MenuFiles, (gpointer)path); save_path = data->file_path; data->file_path = path; DBG("merging the XML file '%s'", data->file_path); gf = g_file_new_for_path(data->file_path); ok = g_file_load_contents(gf, NULL, &contents, &len, NULL, error); g_object_unref(gf); if (!ok) { /* replace the path with failed one */ return FALSE; } menu = fm_xml_file_new(data->menu); /* g_debug("merging FmXmlFile %p into %p", menu, data->menu); */ ok = fm_xml_file_parse_data(menu, contents, len, error, data); g_free(contents); if (ok) { xml = fm_xml_file_finish_parse(menu, &err); if (err && err->domain == G_MARKUP_ERROR && err->code == G_MARKUP_ERROR_EMPTY) { /* NOTE: it should be legal case to have empty menu file. it may be not generally this but let it be */ g_error_free(err); data->file_path = save_path; g_object_unref(menu); return TRUE; } if (err) g_propagate_error(error, err); } if (xml == NULL) /* error is set by failed function */ { /* g_debug("freeing FmXmlFile %p (failed)", menu); */ /* only this handler does recursion, therefore it is safe to set and and do check of data->line here */ if (data->line == -1) data->line = fm_xml_file_get_current_line(menu, &data->pos); /* we do a little trick here - we don't restore previous fule but leave data->file_path for diagnostics in _update_categories() */ g_object_unref(menu); return FALSE; } data->file_path = save_path; /* insert all children but Name before item */ for (it = xml; it; it = it->next) { GList *xml_sub, *it_sub; if (fm_xml_file_item_get_tag(it->data) != menuTag_Menu) { if (verbose == 0) continue; /* just skip it in quiet mode */ g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, _("Merging file may contain only top level tag," " got <%s>"), fm_xml_file_item_get_tag_name(it->data)); /* FIXME: it will show error not for merged file but current */ break; } xml_sub = fm_xml_file_item_get_children(it->data); for (it_sub = xml_sub; it_sub; it_sub = it_sub->next) { /* g_debug("merge: trying to insert %p into %p", it_sub->data, fm_xml_file_item_get_parent(item)); */ if (fm_xml_file_item_get_tag(it_sub->data) != menuTag_Name && !fm_xml_file_insert_before(item, it_sub->data)) { g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, _("Failed to insert tag <%s> from merging file"), fm_xml_file_item_get_tag_name(it_sub->data)); /* FIXME: it will show error not for merged file but current */ break; } } g_list_free(xml_sub); if (it_sub) /* failed above */ break; } g_list_free(xml); ok = (it == NULL); /* g_debug("freeing FmXmlFile %p (success=%d)", menu, (int)ok); */ g_object_unref(menu); return ok; } static gboolean _merge_menu_directory(MenuTreeData *data, FmXmlFileItem *item, const char *path, GList **m, GError **error, gboolean ignore_not_exist) { char *child; GDir *dir; GError *err = NULL; const char *name; gboolean ok = TRUE; DBG("merging the XML directory '%s'", path); path = g_intern_string(path); if (g_slist_find(MenuDirs, path) == NULL) MenuDirs = g_slist_append(MenuDirs, (gpointer)path); dir = g_dir_open(path, 0, &err); if (dir) { while ((name = g_dir_read_name(dir))) { if (strlen(name) <= 5 || !g_str_has_suffix(name, ".menu")) { /* skip files that aren't *.menu */ continue; } child = g_build_filename(path, name, NULL); ok = _merge_xml_file(data, item, child, m, &err, FALSE); if (!ok) { /* if (err->domain == G_IO_ERROR && err->code == G_IO_ERROR_PERMISSION_DENIED) { g_warning("cannot merge XML file %s: no access", child); g_clear_error(&err); g_free(child); ok = TRUE; continue; } */ g_free(child); if (ignore_not_exist && err->domain == G_IO_ERROR && (err->code == G_IO_ERROR_NOT_FOUND)) { g_clear_error(&err); continue; } g_propagate_error(error, err); err = NULL; break; } g_free(child); } g_dir_close(dir); } else if (ignore_not_exist && err->domain == G_FILE_ERROR && (err->code == G_FILE_ERROR_NOENT)) { VDBG("_merge_menu_directory: dir %s does not exist", path); g_error_free(err); } else { g_propagate_error(error, err); ok = FALSE; } return ok; } static inline const char *_get_menu_name(FmXmlFileItem *item) { if (fm_xml_file_item_get_tag(item) != menuTag_Menu) /* skip not menu */ return NULL; item = fm_xml_file_item_find_child(item, menuTag_Name); if (item == NULL) /* no Name tag? */ return NULL; item = fm_xml_file_item_find_child(item, FM_XML_FILE_TEXT); if (item == NULL) /* empty Name tag? */ return NULL; return fm_xml_file_item_get_data(item, NULL); } /* merges subitems - consumes list */ /* NOTE: it will not delete duplicate elements other than Menu or Name */ static void _merge_level(GList *first) { while (first) { if (first->data) /* we might merge this one already */ { GList *next; if (first->next) { /* merge this item with identical ones */ const char *name = _get_menu_name(first->data); if (name) /* not a menu tag */ { for (next = first->next; next; next = next->next) { if (next->data == NULL) /* already merged */ continue; if (g_strcmp0(name, _get_menu_name(next->data)) == 0) { GList *children = fm_xml_file_item_get_children(next->data); GList *l; DBG("found two identical Menu '%s', merge them", name); for (l = children; l; l = l->next) /* merge all but Name */ if (fm_xml_file_item_get_tag(l->data) != menuTag_Name) fm_xml_file_item_append_child(first->data, l->data); g_list_free(children); fm_xml_file_item_destroy(next->data); next->data = NULL; /* we merged it so no data */ } } } } } /* go to next item */ first = g_list_delete_link(first, first); } } static FmXmlFileItem *_walk_path(GList *child, const char *path, FmXmlFileItem *parent, gboolean create) { FmXmlFileItem *item = NULL; char *subpath = strchr(path, '/'); if (subpath) { char *next = &subpath[1]; subpath = g_strndup(path, subpath - path); path = next; } for (; child != NULL; child = child->next) { item = child->data; if (item == NULL) continue; if (g_strcmp0(subpath ? subpath : path, _get_menu_name(item)) == 0) break; item = NULL; } g_free(subpath); /* free but still use as marker */ if (subpath != NULL && item != NULL) { child = fm_xml_file_item_get_children(item); item = _walk_path(child, path, item, create); g_list_free(child); } else if (subpath == NULL && item == NULL && create) { /* create new path and append it to parent */ item = fm_xml_file_item_new(menuTag_Menu); if (!fm_xml_file_item_append_child(parent, item)) fm_xml_file_item_destroy(item); /* FIXME: is it possible? */ else { parent = fm_xml_file_item_new(menuTag_Name); /* reuse pointer */ fm_xml_file_item_append_text(parent, path, -1, FALSE); fm_xml_file_item_append_child(item, parent); } } return item; } static FmXmlFileItem *_walk_children(GList *children, FmXmlFileItem *list, FmXmlFileTag tag, gboolean create) { GList *sub, *l; FmXmlFileItem *item = NULL; sub = fm_xml_file_item_get_children(list); for (l = sub; l; l = l->next) if (fm_xml_file_item_get_tag((item = l->data)) == tag) break; else item = NULL; g_list_free(sub); if (item == NULL) /* no tag found */ return NULL; item = fm_xml_file_item_find_child(item, FM_XML_FILE_TEXT); list = fm_xml_file_item_get_parent(list); /* it contains parent of now */ if (item == NULL) /* empty tag, assume we are here */ return list; return _walk_path(children, fm_xml_file_item_get_data(item, NULL), list, create); } static gboolean _activate_merges(MenuTreeData *data, FmXmlFileItem *item, GError **error) { GList *children, *l, *l2, *merged = NULL; const char *path, *path2; FmXmlFileItem *sub; FmXmlFileTag tag; gboolean ok; restart: children = fm_xml_file_item_get_children(item); /* expand DefaultMergeDirs first */ for (l = children, sub = NULL; l; l = l->next) { if (fm_xml_file_item_get_tag(l->data) == menuTag_DefaultMergeDirs) sub = l->data; } if (sub != NULL) { const gchar * const *dirs = g_get_system_config_dirs(); char *merged; FmXmlFileItem *it_sub; int i = g_strv_length((char **)dirs); /* insert in reverse order - see XDG menu specification */ while (i > 0) { merged = g_build_filename(dirs[--i], "menus", "applications-merged", NULL); it_sub = fm_xml_file_item_new(menuTag_MergeDir); fm_xml_file_item_append_text(it_sub, merged, -1, FALSE); if (!fm_xml_file_insert_before(sub, it_sub) && verbose > 0) { g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, _("Failed to insert tag %s"), merged); g_free(merged); goto failed; /* failed to merge */ } g_free(merged); } merged = g_build_filename(g_get_user_config_dir(), "menus", "applications-merged", NULL); it_sub = fm_xml_file_item_new(menuTag_MergeDir); fm_xml_file_item_append_text(it_sub, merged, -1, FALSE); if (!fm_xml_file_insert_before(sub, it_sub) && verbose > 0) { g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, _("Failed to insert tag %s"), merged); g_free(merged); goto failed; /* failed to merge */ } g_free(merged); /* destroy all DefaultMergeDirs -- we replaced it already */ for (l = children; l; l = l->next) if (fm_xml_file_item_get_tag(l->data) == menuTag_DefaultMergeDirs) fm_xml_file_item_destroy(l->data); /* restart merge again, we changed the list */ g_list_free(children); goto restart; } /* do with MergeFile and MergeDir now */ for (l = children; l; l = l->next) { tag = fm_xml_file_item_get_tag(l->data); if (tag == menuTag_MergeFile || tag == menuTag_MergeDir) { path = fm_xml_file_item_get_data(fm_xml_file_item_find_child(l->data, FM_XML_FILE_TEXT), NULL); /* find duplicate - only last one should be used */ for (l2 = l->next; l2; l2 = l2->next) { if (fm_xml_file_item_get_tag(l2->data) == tag) { path2 = fm_xml_file_item_get_data(fm_xml_file_item_find_child(l2->data, FM_XML_FILE_TEXT), NULL); if (strcmp(path2, path) == 0) break; } } if (l2 == NULL) { if (tag == menuTag_MergeFile) { GError *err = NULL; ok = _merge_xml_file(data, l->data, path, &merged, &err, TRUE); if (ok) ; else if (err->domain == G_IO_ERROR && err->code == G_IO_ERROR_NOT_FOUND) { g_error_free(err); ok = TRUE; } else g_propagate_error(error, err); } else ok = _merge_menu_directory(data, l->data, path, &merged, error, TRUE); if (!ok) { if (verbose > 0) { g_prefix_error(error, "failed on '%s': ", path); goto failed; /* failed to merge */ } g_clear_error(error); } } /* destroy item -- we replaced it already */ fm_xml_file_item_destroy(l->data); if (l2 != NULL) /* it was a duplicate */ continue; /* restart merge again, we could get new merges */ g_list_free(children); goto restart; } } g_list_free(merged); /* we don't need it anymore */ /* merge this level */ _merge_level(children); children = fm_xml_file_item_get_children(item); /* expand DefaultAppDirs then supress duplicates on AppDir */ for (l = children, sub = NULL; l; l = l->next) { if (fm_xml_file_item_get_tag(l->data) == menuTag_DefaultAppDirs) sub = l->data; } if (sub != NULL) { const gchar * const *dirs = g_get_system_data_dirs(); char *merged; FmXmlFileItem *it_sub; int i = g_strv_length((char **)dirs); /* insert in reverse order - see XDG menu specification */ while (i > 0) { merged = g_build_filename(dirs[--i], "applications", NULL); it_sub = fm_xml_file_item_new(menuTag_AppDir); fm_xml_file_item_append_text(it_sub, merged, -1, FALSE); if (!fm_xml_file_insert_before(sub, it_sub) && verbose > 0) { g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, _("Failed to insert tag %s"), merged); g_free(merged); goto failed; /* failed to merge */ } g_free(merged); } merged = g_build_filename(g_get_user_data_dir(), "applications", NULL); it_sub = fm_xml_file_item_new(menuTag_AppDir); fm_xml_file_item_append_text(it_sub, merged, -1, FALSE); if (!fm_xml_file_insert_before(sub, it_sub) && verbose > 0) { g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, _("Failed to insert tag %s"), merged); g_free(merged); goto failed; /* failed to merge */ } g_free(merged); /* destroy all DefaultAppDirs -- we replaced it already */ for (l = children; l; l = l->next) if (fm_xml_file_item_get_tag(l->data) == menuTag_DefaultAppDirs) { fm_xml_file_item_destroy(l->data); l->data = NULL; } } for (l = children; l; l = l->next) { sub = l->data; if (sub == NULL || fm_xml_file_item_get_tag(sub) != menuTag_AppDir) continue; for (l2 = l->next; l2; l2 = l2->next) if (l2->data != NULL && fm_xml_file_item_get_tag(l2->data) == menuTag_AppDir) if (strcmp(fm_xml_file_item_get_data(fm_xml_file_item_find_child(l2->data, FM_XML_FILE_TEXT), NULL), fm_xml_file_item_get_data(fm_xml_file_item_find_child(l->data, FM_XML_FILE_TEXT), NULL)) == 0) break; if (l2 == NULL) /* no duplicates */ continue; fm_xml_file_item_destroy(sub); l->data = NULL; } /* expand KDELegacyDirs then supress duplicates on LegacyDir */ for (l = children, sub = NULL; l; l = l->next) { if (l->data == NULL) continue; if (fm_xml_file_item_get_tag(l->data) == menuTag_KDELegacyDirs) sub = l->data; } if (sub != NULL) { const gchar * const *dirs = g_get_system_data_dirs(); char *merged; FmXmlFileItem *it_sub; int i = g_strv_length((char **)dirs); /* insert in reverse order - see XDG menu specification */ while (i > 0) { merged = g_build_filename(dirs[--i], "applnk", NULL); it_sub = fm_xml_file_item_new(menuTag_LegacyDir); fm_xml_file_item_set_comment(it_sub, "kde-"); fm_xml_file_item_append_text(it_sub, merged, -1, FALSE); if (!fm_xml_file_insert_before(sub, it_sub) && verbose > 0) { g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, _("Failed to insert tag %s"), merged); g_free(merged); goto failed; /* failed to merge */ } g_free(merged); } merged = g_build_filename(g_get_user_data_dir(), "applnk", NULL); it_sub = fm_xml_file_item_new(menuTag_LegacyDir); fm_xml_file_item_set_comment(it_sub, "kde-"); fm_xml_file_item_append_text(it_sub, merged, -1, FALSE); if (!fm_xml_file_insert_before(sub, it_sub) && verbose > 0) { g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, _("Failed to insert tag %s"), merged); g_free(merged); goto failed; /* failed to merge */ } g_free(merged); /* destroy all KDELegacyDirs */ for (l = children; l; l = l->next) if (l->data && fm_xml_file_item_get_tag(l->data) == menuTag_KDELegacyDirs) { fm_xml_file_item_destroy(l->data); l->data = NULL; } } for (l = children; l; l = l->next) { sub = l->data; if (sub == NULL || fm_xml_file_item_get_tag(sub) != menuTag_LegacyDir) continue; VDBG("check LegacyDir %s", fm_xml_file_item_get_data(fm_xml_file_item_find_child(l->data, FM_XML_FILE_TEXT), NULL)); for (l2 = l->next; l2; l2 = l2->next) if (l2->data != NULL && fm_xml_file_item_get_tag(l2->data) == menuTag_LegacyDir) if (strcmp(fm_xml_file_item_get_data(fm_xml_file_item_find_child(l2->data, FM_XML_FILE_TEXT), NULL), fm_xml_file_item_get_data(fm_xml_file_item_find_child(l->data, FM_XML_FILE_TEXT), NULL)) == 0) break; if (l2 == NULL) /* no duplicates */ continue; fm_xml_file_item_destroy(sub); l->data = NULL; } /* expand DefaultDirectoryDirs then supress duplicates on DirectoryDir */ for (l = children, sub = NULL; l; l = l->next) { if (l->data == NULL) continue; if (fm_xml_file_item_get_tag(l->data) == menuTag_DefaultDirectoryDirs) sub = l->data; } if (sub != NULL) { const gchar * const *dirs = g_get_system_data_dirs(); char *merged; FmXmlFileItem *it_sub; int i = g_strv_length((char **)dirs); /* insert in reverse order - see XDG menu specification */ while (i > 0) { merged = g_build_filename(dirs[--i], "desktop-directories", NULL); it_sub = fm_xml_file_item_new(menuTag_DirectoryDir); fm_xml_file_item_append_text(it_sub, merged, -1, FALSE); if (!fm_xml_file_insert_before(sub, it_sub) && verbose > 0) { g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, _("Failed to insert tag %s"), merged); g_free(merged); goto failed; /* failed to merge */ } g_free(merged); } merged = g_build_filename(g_get_user_data_dir(), "desktop-directories", NULL); it_sub = fm_xml_file_item_new(menuTag_DirectoryDir); fm_xml_file_item_append_text(it_sub, merged, -1, FALSE); if (!fm_xml_file_insert_before(sub, it_sub) && verbose > 0) { g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, _("Failed to insert tag %s"), merged); g_free(merged); goto failed; /* failed to merge */ } g_free(merged); /* destroy all DefaultDirectoryDirs -- we replaced it already */ for (l = children; l; l = l->next) if (l->data && fm_xml_file_item_get_tag(l->data) == menuTag_DefaultDirectoryDirs) { fm_xml_file_item_destroy(l->data); l->data = NULL; } } for (l = children; l; l = l->next) { sub = l->data; if (sub == NULL || fm_xml_file_item_get_tag(sub) != menuTag_DirectoryDir) continue; for (l2 = l->next; l2; l2 = l2->next) if (l2->data != NULL && fm_xml_file_item_get_tag(l2->data) == menuTag_DirectoryDir) if (strcmp(fm_xml_file_item_get_data(fm_xml_file_item_find_child(l2->data, FM_XML_FILE_TEXT), NULL), fm_xml_file_item_get_data(fm_xml_file_item_find_child(l->data, FM_XML_FILE_TEXT), NULL)) == 0) break; if (l2 == NULL) /* no duplicates */ continue; fm_xml_file_item_destroy(sub); l->data = NULL; } /* support ...... for menus */ for (l = children; l; l = l2) { l2 = l->next; sub = l->data; if (sub && fm_xml_file_item_get_tag(sub) == menuTag_Move) { FmXmlFileItem *old = _walk_children(children, sub, menuTag_Old, FALSE); sub = _walk_children(children, sub, menuTag_New, TRUE); if (old != NULL && sub != NULL) { GList *child = fm_xml_file_item_get_children(old); while (child != NULL) { if (fm_xml_file_item_get_tag(child->data) != menuTag_Name) fm_xml_file_item_append_child(sub, child->data); child = g_list_delete_link(child, child); } fm_xml_file_item_destroy(old); } else DBG("invalid tag ignored"); } } /* reload children, they might be changed after movements */ g_list_free(children); children = fm_xml_file_item_get_children(item); /* do recursion for children Menu now */ for (l = children; l; l = l->next) { sub = l->data; if (fm_xml_file_item_get_tag(sub) == menuTag_Menu && !_activate_merges(data, sub, error)) goto failed; /* failed to merge */ } g_list_free(children); return TRUE; failed: g_list_free(children); return FALSE; } static GList *_layout_items_copy(GList *orig) { GList *copy = NULL; gpointer item; while (orig) { MenuSep *sep = orig->data; switch (sep->type) { case MENU_CACHE_TYPE_NONE: item = g_slice_new(MenuMerge); memcpy(item, sep, sizeof(MenuMerge)); VVDBG("*** new menu layout: MenuMerge"); break; case MENU_CACHE_TYPE_SEP: item = g_slice_new(MenuSep); memcpy(item, sep, sizeof(MenuSep)); VVDBG("*** new menu layout: MenuSeparator"); break; case MENU_CACHE_TYPE_APP: item = g_slice_new(MenuFilename); memcpy(item, sep, sizeof(MenuFilename)); ((MenuFilename *)item)->id = g_strdup(((MenuFilename *)sep)->id); VVDBG("*** new menu layout: MenuFilename %s", ((MenuFilename *)item)->id); break; case MENU_CACHE_TYPE_DIR: item = g_slice_new(MenuMenuname); memcpy(item, sep, sizeof(MenuMenuname)); ((MenuMenuname *)item)->name = g_strdup(((MenuMenuname *)sep)->name); VVDBG("*** new menu layout: MenuMenuname %s", ((MenuMenuname *)item)->name); } copy = g_list_prepend(copy, item); orig = orig->next; } return g_list_reverse(copy); } static MenuMenu *_make_menu_node(FmXmlFileItem *node, MenuLayout *def) { FmXmlFileItem *item = NULL; MenuLayout *layout = NULL; GList *children, *l; MenuMenu *menu; FmXmlFileTag tag; gboolean ok = TRUE; if (fm_xml_file_item_find_child(node, menuTag_Name) == NULL) { g_warning("got a without , ignored"); return NULL; } children = fm_xml_file_item_get_children(node); /* check if it's deleted first */ for (l = children; l; l = l->next) { tag = fm_xml_file_item_get_tag(l->data); if (tag == menuTag_Layout) item = l->data; else if (tag == menuTag_DefaultLayout) layout = _find_layout(l->data, FALSE); else if (tag == menuTag_Deleted) ok = FALSE; else if (tag == menuTag_NotDeleted) ok = TRUE; } if (!ok) { /* menu was disabled, ignore it */ g_list_free(children); return NULL; } /* find default layout if any and subst */ if (layout != NULL) { /* new DefaultLayout might be empty, ignore it then */ if (layout->items == NULL) layout = NULL; else def = layout; } /* find layout, if not found then fill from default */ if (item != NULL) layout = _find_layout(item, FALSE); if (layout == NULL) layout = def; menu = g_slice_new0(MenuMenu); menu->layout.type = MENU_CACHE_TYPE_DIR; menu->layout.show_empty = layout->show_empty; menu->layout.allow_inline = layout->allow_inline; menu->layout.inline_header = layout->inline_header; menu->layout.inline_alias = layout->inline_alias; menu->layout.inline_limit = layout->inline_limit; menu->layout.items = _layout_items_copy(layout->items); VDBG("*** starting new menu"); /* gather all explicit data from XML */ for (l = children; l; l = l->next) { tag = fm_xml_file_item_get_tag(l->data); /* we don't do any matching now, i.e. those tags will be processed later * Include Exclude Filename Category All And Not Or directory scannings will be processed later as well: * AppDir LegacyDir KDELegacyDirs */ if (tag == menuTag_Menu) { MenuMenu *child = _make_menu_node(l->data, def); if (child != NULL) { VDBG("*** added submenu %s", child->name); menu->children = g_list_prepend(menu->children, child); } } else if (tag == menuTag_Directory) { item = fm_xml_file_item_find_child(l->data, FM_XML_FILE_TEXT); if (item != NULL) menu->id = g_list_prepend(menu->id, g_strdup(fm_xml_file_item_get_data(item, NULL))); } else if (tag == menuTag_Name) { if (menu->name == NULL) menu->name = g_strdup(fm_xml_file_item_get_data(fm_xml_file_item_find_child(l->data, FM_XML_FILE_TEXT), NULL)); } else if (tag == menuTag_OnlyUnallocated) menu->layout.only_unallocated = TRUE; else if (tag == menuTag_NotOnlyUnallocated) menu->layout.only_unallocated = FALSE; else if (tag == menuTag_Include || tag == menuTag_Exclude || tag == menuTag_DirectoryDir || tag == menuTag_AppDir || tag == menuTag_LegacyDir) /* FIXME: can those be here? Filename Category All And Not Or */ { MenuRule *child = g_slice_new0(MenuRule); child->type = MENU_CACHE_TYPE_NONE; child->rule = l->data; VDBG("*** adding rule %s", fm_xml_file_item_get_tag_name(l->data)); menu->children = g_list_prepend(menu->children, child); } } g_list_free(children); menu->children = g_list_reverse(menu->children); VDBG("*** done menu %s",menu->name); return menu; } static FmXmlFileItem *_find_in_children(GList *list, const char *name) { while (list) { const char *elem_name = _get_menu_name(list->data); /* g_debug("got child %d: %s", fm_xml_file_item_get_tag(list->data), elem_name); */ if (g_strcmp0(elem_name, name) == 0) return list->data; else list = list->next; } return NULL; } void _free_layout_items(GList *data) { union { MenuMenuname *menu; MenuFilename *file; MenuSep *sep; MenuMerge *merge; } a = { NULL }; while (data != NULL) { a.menu = data->data; switch (a.menu->layout.type) { case MENU_CACHE_TYPE_DIR: g_free(a.menu->name); g_slice_free(MenuMenuname, a.menu); break; case MENU_CACHE_TYPE_APP: g_free(a.file->id); g_slice_free(MenuFilename, a.file); break; case MENU_CACHE_TYPE_SEP: g_slice_free(MenuSep, a.sep); break; case MENU_CACHE_TYPE_NONE: g_slice_free(MenuMerge, a.merge); } data = g_list_delete_link(data, data); } } static void _free_layout(gpointer data) { MenuLayout *layout = data; _free_layout_items(layout->items); g_slice_free(MenuLayout, data); } MenuMenu *get_merged_menu(const char *file, FmXmlFile **xmlfile, GError **error) { GFile *gf; char *contents; gsize len; MenuTreeData data; GList *xml = NULL; FmXmlFileItem *apps; MenuMenu *menu = NULL; MenuLayout default_layout; MenuMerge def_files = { .type = MENU_CACHE_TYPE_NONE, .merge_type = MERGE_FILES }; MenuMerge def_menus = { .type = MENU_CACHE_TYPE_NONE, .merge_type = MERGE_MENUS }; gboolean ok; /* Load the file */ data.file_path = file; gf = g_file_new_for_path(file); contents = NULL; ok = g_file_load_contents(gf, NULL, &contents, &len, NULL, error); g_object_unref(gf); if (!ok) return NULL; /* Init layouts hash and all the data */ layout_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, _free_layout); data.menu = fm_xml_file_new(NULL); data.line = data.pos = -1; /* g_debug("new FmXmlFile %p", data.menu); */ menuTag_Menu = fm_xml_file_set_handler(data.menu, "Menu", &_menu_xml_handler_pass, FALSE, NULL); menuTag_Include = fm_xml_file_set_handler(data.menu, "Include", &_menu_xml_handler_pass, FALSE, NULL); menuTag_Exclude = fm_xml_file_set_handler(data.menu, "Exclude", &_menu_xml_handler_pass, FALSE, NULL); menuTag_Filename = fm_xml_file_set_handler(data.menu, "Filename", &_menu_xml_handler_Filename, FALSE, NULL); menuTag_Or = fm_xml_file_set_handler(data.menu, "Or", &_menu_xml_handler_pass, FALSE, NULL); menuTag_And = fm_xml_file_set_handler(data.menu, "And", &_menu_xml_handler_pass, FALSE, NULL); menuTag_Not = fm_xml_file_set_handler(data.menu, "Not", &_menu_xml_handler_Not, FALSE, NULL); menuTag_Category = fm_xml_file_set_handler(data.menu, "Category", &_menu_xml_handler_pass, FALSE, NULL); menuTag_MergeFile = fm_xml_file_set_handler(data.menu, "MergeFile", &_menu_xml_handler_MergeFile, FALSE, NULL); menuTag_MergeDir = fm_xml_file_set_handler(data.menu, "MergeDir", &_menu_xml_handler_MergeDir, FALSE, NULL); menuTag_DefaultMergeDirs = fm_xml_file_set_handler(data.menu, "DefaultMergeDirs", &_menu_xml_handler_DefaultMergeDirs, FALSE, NULL); menuTag_KDELegacyDirs = fm_xml_file_set_handler(data.menu, "KDELegacyDirs", &_menu_xml_handler_DefaultMergeDirs, FALSE, NULL); menuTag_Name = fm_xml_file_set_handler(data.menu, "Name", &_menu_xml_handler_Name, FALSE, NULL); menuTag_Deleted = fm_xml_file_set_handler(data.menu, "Deleted", &_menu_xml_handler_pass, FALSE, NULL); menuTag_NotDeleted = fm_xml_file_set_handler(data.menu, "NotDeleted", &_menu_xml_handler_pass, FALSE, NULL); menuTag_Directory = fm_xml_file_set_handler(data.menu, "Directory", &_menu_xml_handler_pass, FALSE, NULL); menuTag_AppDir = fm_xml_file_set_handler(data.menu, "AppDir", &_menu_xml_handler_AppDir, FALSE, NULL); menuTag_DefaultAppDirs = fm_xml_file_set_handler(data.menu, "DefaultAppDirs", &_menu_xml_handler_DefaultAppDirs, FALSE, NULL); menuTag_DirectoryDir = fm_xml_file_set_handler(data.menu, "DirectoryDir", &_menu_xml_handler_DirectoryDir, FALSE, NULL); menuTag_DefaultDirectoryDirs = fm_xml_file_set_handler(data.menu, "DefaultDirectoryDirs", &_menu_xml_handler_DefaultDirectoryDirs, FALSE, NULL); menuTag_OnlyUnallocated = fm_xml_file_set_handler(data.menu, "OnlyUnallocated", &_menu_xml_handler_pass, FALSE, NULL); menuTag_NotOnlyUnallocated = fm_xml_file_set_handler(data.menu, "NotOnlyUnallocated", &_menu_xml_handler_pass, FALSE, NULL); menuTag_All = fm_xml_file_set_handler(data.menu, "All", &_menu_xml_handler_pass, FALSE, NULL); menuTag_LegacyDir = fm_xml_file_set_handler(data.menu, "LegacyDir", &_menu_xml_handler_LegacyDir, FALSE, NULL); menuTag_Move = fm_xml_file_set_handler(data.menu, "Move", &_menu_xml_handler_pass, FALSE, NULL); menuTag_Old = fm_xml_file_set_handler(data.menu, "Old", &_menu_xml_handler_pass, FALSE, NULL); menuTag_New = fm_xml_file_set_handler(data.menu, "New", &_menu_xml_handler_pass, FALSE, NULL); menuTag_Layout = fm_xml_file_set_handler(data.menu, "Layout", &_menu_xml_handler_Layout, FALSE, NULL); menuTag_DefaultLayout = fm_xml_file_set_handler(data.menu, "DefaultLayout", &_menu_xml_handler_DefaultLayout, FALSE, NULL); menuTag_Menuname = fm_xml_file_set_handler(data.menu, "Menuname", &_menu_xml_handler_Menuname, FALSE, NULL); menuTag_Separator = fm_xml_file_set_handler(data.menu, "Separator", &_menu_xml_handler_Separator, FALSE, NULL); menuTag_Merge = fm_xml_file_set_handler(data.menu, "Merge", &_menu_xml_handler_Merge, FALSE, NULL); /* Do parsing */ ok = fm_xml_file_parse_data(data.menu, contents, len, error, &data); g_free(contents); if (ok) xml = fm_xml_file_finish_parse(data.menu, error); if (xml == NULL) /* error is set by failed function */ { if (data.line == -1) data.line = fm_xml_file_get_current_line(data.menu, &data.pos); g_prefix_error(error, _("XML file '%s' error (%d:%d): "), data.file_path, data.line, data.pos); goto _return_error; } /* Merge other files */ apps = _find_in_children(xml, "Applications"); g_list_free(xml); if (apps == NULL) { g_set_error_literal(error, G_FILE_ERROR, G_FILE_ERROR_NOENT, _("XML file doesn't contain Applications root")); goto _return_error; } if (!_activate_merges(&data, apps, error)) goto _return_error; /* FIXME: validate tags */ /* Create our menu tree -- no failures anymore! */ memset(&default_layout, 0, sizeof(default_layout)); default_layout.inline_header = TRUE; default_layout.inline_limit = 4; default_layout.items = g_list_prepend(g_list_prepend(NULL, &def_files), &def_menus); menu = _make_menu_node(apps, &default_layout); g_list_free(default_layout.items); if (verbose > 2) { char *dump = fm_xml_file_to_data(data.menu, NULL, NULL); g_print("%s", dump); g_free(dump); } /* Free layouts hash */ _return_error: if (menu == NULL) g_object_unref(data.menu); else /* keep XML file still since MenuRule elements use items in it */ *xmlfile = data.menu; g_hash_table_destroy(layout_hash); return menu; } menu-cache-1.1.1/menu-cache-gen/menu-tags.h000066400000000000000000000116451475361627100203670ustar00rootroot00000000000000/* * Copyright 2014 Andriy Grytsenko (LStranger) * * This file is a part of libmenu-cache package and created program * should be not used without the library. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include extern FmXmlFileTag menuTag_AppDir; extern FmXmlFileTag menuTag_DirectoryDir; extern FmXmlFileTag menuTag_Include; extern FmXmlFileTag menuTag_Exclude; extern FmXmlFileTag menuTag_Filename; extern FmXmlFileTag menuTag_Or; extern FmXmlFileTag menuTag_And; extern FmXmlFileTag menuTag_Not; extern FmXmlFileTag menuTag_Category; extern FmXmlFileTag menuTag_All; extern FmXmlFileTag menuTag_LegacyDir; typedef enum { MERGE_NONE, /* starting value */ MERGE_FILES, /* first set */ MERGE_MENUS, MERGE_ALL, /* only set */ MERGE_FILES_MENUS, /* second set */ MERGE_MENUS_FILES } MenuMergeType; typedef struct { MenuCacheType type : 2; /* used by MenuMenu, MENU_CACHE_TYPE_DIR */ gboolean only_unallocated : 1; /* for Menuname: TRUE if show_empty is set */ gboolean is_set : 1; /* used by MenuMenu, for Menuname: TRUE if allow_inline is set */ gboolean show_empty : 1; gboolean allow_inline : 1; gboolean inline_header : 1; gboolean inline_alias : 1; gboolean inline_header_is_set : 1; /* for Menuname */ gboolean inline_alias_is_set : 1; /* for Menuname */ gboolean inline_limit_is_set : 1; /* for Menuname; for MenuMenu is Legacy mark */ gboolean nodisplay : 1; GList *items; /* items are MenuItem : Menuname or Filename or Separator or Merge */ int inline_limit; } MenuLayout; /* Menuname item */ typedef struct { MenuLayout layout; char *name; } MenuMenuname; /* Filename item in layout */ typedef struct { MenuCacheType type : 2; /* MENU_CACHE_TYPE_APP */ char *id; } MenuFilename; /* Separator item */ typedef struct { MenuCacheType type : 2; /* MENU_CACHE_TYPE_SEP */ } MenuSep; /* Merge item */ typedef struct { MenuCacheType type : 2; /* MENU_CACHE_TYPE_NONE */ MenuMergeType merge_type; } MenuMerge; /* Menu item */ typedef struct { MenuLayout layout; /* copied from hash on */ char *name; /* next fields are only for Menu */ char *key; /* for sorting */ GList *id; /* for , may be NULL, first is most relevant */ /* next fields are only for composer */ GList *children; /* items are MenuItem : MenuApp, MenuMenu, MenuSep, MenuRule */ char *title; char *comment; char *icon; const char *dir; } MenuMenu; /* File item in menu */ typedef struct { MenuCacheType type : 2; /* MENU_CACHE_TYPE_APP */ gboolean excluded : 1; gboolean allocated : 1; gboolean matched : 1; gboolean use_terminal : 1; gboolean use_notification : 1; gboolean hidden : 1; GList *dirs; /* can be reordered until allocated */ GList *menus; char *filename; /* if NULL then is equal to id */ char *key; /* for sorting */ char *id; char *title; char *comment; char *icon; char *generic_name; char *exec; char *try_exec; char *wd; const char **categories; /* all char ** keep interned values */ const char **keywords; const char **show_in; const char **hide_in; } MenuApp; /* a placeholder for matching */ typedef struct { MenuCacheType type : 2; /* MENU_CACHE_TYPE_NONE */ FmXmlFileItem *rule; } MenuRule; /* requested language(s) */ extern char **languages; /* list of menu files to monitor */ extern GSList *MenuFiles; /* list of menu dirs to monitor */ extern GSList *MenuDirs; /* list of available app dirs */ extern GSList *AppDirs; /* list of available dir dirs */ extern GSList *DirDirs; /* parse and merge menu files */ MenuMenu *get_merged_menu(const char *file, FmXmlFile **xmlfile, GError **error); /* parse all files into layout and save cache file */ gboolean save_menu_cache(MenuMenu *layout, const char *menuname, const char *file, gboolean with_hidden); /* free MenuLayout data */ void _free_layout_items(GList *data); /* verbosity level */ extern gint verbose; #define DBG if (verbose) g_debug #define VDBG if (verbose > 1) g_debug #define VVDBG if (verbose > 2) g_debug menu-cache-1.1.1/vapi/000077500000000000000000000000001475361627100144725ustar00rootroot00000000000000menu-cache-1.1.1/vapi/libmenu-cache.vapi000066400000000000000000000046411475361627100200540ustar00rootroot00000000000000/* libmenu-cache.vapi generated by vapigen, do not modify. */ namespace Mc { [CCode (cname = "MenuCache", cprefix="menu_cache_", cheader_filename = "menu-cache.h", ref_function = "menu_cache_ref", unref_function = "menu_cache_unref")] [Compact] public class Cache { public void* add_reload_notify (GLib.Func func); public uint32 get_desktop_env_flag (string desktop_env); public unowned CacheDir get_dir_from_path (string path); public unowned CacheDir get_root_dir (); public static void init (int flags); public unowned GLib.SList list_all_apps (); public static unowned Cache lookup (string menu_name); public static unowned Cache lookup_sync (string menu_name); public bool reload (); public void remove_reload_notify (void* notify_id); } [CCode (cname = "MenuCacheApp", cprefix="menu_cache_app_", cheader_filename = "menu-cache.h")] [Compact] public class CacheApp { public unowned string get_exec (); public bool get_is_visible (uint32 de_flags); public uint32 get_show_flags (); public bool get_use_sn (); public bool get_use_terminal (); public unowned string get_working_dir (); } [CCode (cname = "MenuCacheDir", cprefix="menu_cache_dir_", cheader_filename = "menu-cache.h")] [Compact] public class CacheDir { public unowned GLib.SList get_children (); public unowned string make_path (); } [CCode (cname = "MenuCacheItem", cprefix="menu_cache_item_", cheader_filename = "menu-cache.h", ref_function = "menu_cache_item_ref", unref_function = "menu_cache_item_unref")] [Compact] public class CacheItem { public unowned Type get_type (); public unowned string get_comment (); public unowned string get_file_basename (); public unowned string get_file_dirname (); public unowned string get_file_path (); public unowned string get_icon (); public unowned string get_id (); public unowned string get_name (); public unowned CacheDir get_parent (); } [CCode (cname="MenuCacheItemFlag", cheader_filename = "menu-cache.h", cprefix = "FLAG_", has_type_id = false)] public enum Item { USE_TERMINAL, USE_SN } [CCode (cname="MenuCacheShowFlag", cheader_filename = "menu-cache.h", cprefix = "SHOW_", has_type_id = false)] public enum Show { IN_LXDE, IN_GNOME, IN_KDE, IN_XFCE, IN_ROX, N_KNOWN_DESKTOPS } [CCode (cname="MenuCacheType", cheader_filename = "menu-cache.h", cprefix = "MENU_CACHE_TYPE_", has_type_id = false)] public enum Type { NONE, DIR, APP, SEP } }