├── .gitignore ├── AUTHORS ├── COPYING ├── ChangeLog ├── INSTALL ├── Makefile.am ├── NEWS ├── README ├── autogen.sh ├── configure.ac ├── docs ├── Makefile.am └── reference │ ├── Makefile.am │ └── libmenu-cache │ └── Makefile.am ├── libmenu-cache ├── Makefile.am ├── libmenu-cache.pc.in ├── menu-cache.c ├── menu-cache.h.in ├── test.c └── version.h ├── menu-cache-daemon ├── Makefile.am └── menu-cached.c ├── menu-cache-gen ├── Makefile.am ├── main.c ├── menu-compose.c ├── menu-merge.c └── menu-tags.h └── vapi └── libmenu-cache.vapi /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile.in 2 | aclocal.m4 3 | compile 4 | config.h.in 5 | configure 6 | depcomp 7 | install-sh 8 | ltmain.sh 9 | */Makefile.in 10 | */Makefile 11 | */*.o 12 | */*/*.o 13 | */*/*/*.o 14 | */.deps/ 15 | */*/.deps/ 16 | */*/*/.deps/ 17 | */.libs/ 18 | */*/.libs/ 19 | */*/*/.libs/ 20 | */*.lo 21 | */*/*.lo 22 | */*/*/*.lo 23 | */*.la 24 | */*/*.la 25 | */*/*/*.la 26 | */*.a 27 | */*/*.a 28 | */*/*/*.a 29 | m4/ 30 | missing 31 | po/Makefile.in.in 32 | po/.intltool-merge-cache 33 | config.guess 34 | config.h 35 | config.h.in~ 36 | config.log 37 | config.status 38 | config.sub 39 | libtool 40 | gtk-doc.make 41 | man/*.1 42 | po/LINGUAS 43 | po/Makefile 44 | po/POTFILES 45 | po/*.gmo 46 | po/stamp-it 47 | stamp-h1 48 | *.stamp 49 | Makefile 50 | intltool-extract.in 51 | intltool-merge.in 52 | intltool-update.in 53 | src/xml-purge 54 | libmenu-cache/libmenu-cache.pc 55 | libmenu-cache/menu-cache.h 56 | menu-cache-daemon/menu-cached 57 | menu-cache-gen/menu-cache-gen 58 | docs/reference/libmenu-cache/libmenu-cache* 59 | docs/reference/libmenu-cache/*/* 60 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Upstream Authors: 2 | Hong Jen Yee (PCMan) 3 | Jürgen Hötzel 4 | Andriy Grytsenko (LStranger) 5 | 6 | Copyright: 7 | LXDE team: http://lxde.org 8 | 9 | License: LGPL-2.1+ 10 | The full text of the license can be found in the 'COPYING' file. 11 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | 2 | GNU LESSER GENERAL PUBLIC LICENSE 3 | Version 2.1, February 1999 4 | 5 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 6 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 7 | Everyone is permitted to copy and distribute verbatim copies 8 | of this license document, but changing it is not allowed. 9 | 10 | [This is the first released version of the Lesser GPL. It also counts 11 | as the successor of the GNU Library Public License, version 2, hence 12 | the version number 2.1.] 13 | 14 | Preamble 15 | 16 | The licenses for most software are designed to take away your 17 | freedom to share and change it. By contrast, the GNU General Public 18 | Licenses are intended to guarantee your freedom to share and change 19 | free software--to make sure the software is free for all its users. 20 | 21 | This license, the Lesser General Public License, applies to some 22 | specially designated software packages--typically libraries--of the 23 | Free Software Foundation and other authors who decide to use it. You 24 | can use it too, but we suggest you first think carefully about whether 25 | this license or the ordinary General Public License is the better 26 | strategy to use in any particular case, based on the explanations 27 | below. 28 | 29 | When we speak of free software, we are referring to freedom of use, 30 | not price. Our General Public Licenses are designed to make sure that 31 | you have the freedom to distribute copies of free software (and charge 32 | for this service if you wish); that you receive source code or can get 33 | it if you want it; that you can change the software and use pieces of 34 | it in new free programs; and that you are informed that you can do 35 | these things. 36 | 37 | To protect your rights, we need to make restrictions that forbid 38 | distributors to deny you these rights or to ask you to surrender these 39 | rights. These restrictions translate to certain responsibilities for 40 | you if you distribute copies of the library or if you modify it. 41 | 42 | For example, if you distribute copies of the library, whether gratis 43 | or for a fee, you must give the recipients all the rights that we gave 44 | you. You must make sure that they, too, receive or can get the source 45 | code. If you link other code with the library, you must provide 46 | complete object files to the recipients, so that they can relink them 47 | with the library after making changes to the library and recompiling 48 | it. And you must show them these terms so they know their rights. 49 | 50 | We protect your rights with a two-step method: (1) we copyright the 51 | library, and (2) we offer you this license, which gives you legal 52 | permission to copy, distribute and/or modify the library. 53 | 54 | To protect each distributor, we want to make it very clear that 55 | there is no warranty for the free library. Also, if the library is 56 | modified by someone else and passed on, the recipients should know 57 | that what they have is not the original version, so that the original 58 | author's reputation will not be affected by problems that might be 59 | introduced by others. 60 | 61 | Finally, software patents pose a constant threat to the existence of 62 | any free program. We wish to make sure that a company cannot 63 | effectively restrict the users of a free program by obtaining a 64 | restrictive license from a patent holder. Therefore, we insist that 65 | any patent license obtained for a version of the library must be 66 | consistent with the full freedom of use specified in this license. 67 | 68 | Most GNU software, including some libraries, is covered by the 69 | ordinary GNU General Public License. This license, the GNU Lesser 70 | General Public License, applies to certain designated libraries, and 71 | is quite different from the ordinary General Public License. We use 72 | this license for certain libraries in order to permit linking those 73 | libraries into non-free programs. 74 | 75 | When a program is linked with a library, whether statically or using 76 | a shared library, the combination of the two is legally speaking a 77 | combined work, a derivative of the original library. The ordinary 78 | General Public License therefore permits such linking only if the 79 | entire combination fits its criteria of freedom. The Lesser General 80 | Public License permits more lax criteria for linking other code with 81 | the library. 82 | 83 | We call this license the "Lesser" General Public License because it 84 | does Less to protect the user's freedom than the ordinary General 85 | Public License. It also provides other free software developers Less 86 | of an advantage over competing non-free programs. These disadvantages 87 | are the reason we use the ordinary General Public License for many 88 | libraries. However, the Lesser license provides advantages in certain 89 | special circumstances. 90 | 91 | For example, on rare occasions, there may be a special need to 92 | encourage the widest possible use of a certain library, so that it 93 | becomes a de-facto standard. To achieve this, non-free programs must 94 | be allowed to use the library. A more frequent case is that a free 95 | library does the same job as widely used non-free libraries. In this 96 | case, there is little to gain by limiting the free library to free 97 | software only, so we use the Lesser General Public License. 98 | 99 | In other cases, permission to use a particular library in non-free 100 | programs enables a greater number of people to use a large body of 101 | free software. For example, permission to use the GNU C Library in 102 | non-free programs enables many more people to use the whole GNU 103 | operating system, as well as its variant, the GNU/Linux operating 104 | system. 105 | 106 | Although the Lesser General Public License is Less protective of the 107 | users' freedom, it does ensure that the user of a program that is 108 | linked with the Library has the freedom and the wherewithal to run 109 | that program using a modified version of the Library. 110 | 111 | The precise terms and conditions for copying, distribution and 112 | modification follow. Pay close attention to the difference between a 113 | "work based on the library" and a "work that uses the library". The 114 | former contains code derived from the library, whereas the latter must 115 | be combined with the library in order to run. 116 | 117 | GNU LESSER GENERAL PUBLIC LICENSE 118 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 119 | 120 | 0. This License Agreement applies to any software library or other 121 | program which contains a notice placed by the copyright holder or 122 | other authorized party saying it may be distributed under the terms of 123 | this Lesser General Public License (also called "this License"). 124 | Each licensee is addressed as "you". 125 | 126 | A "library" means a collection of software functions and/or data 127 | prepared so as to be conveniently linked with application programs 128 | (which use some of those functions and data) to form executables. 129 | 130 | The "Library", below, refers to any such software library or work 131 | which has been distributed under these terms. A "work based on the 132 | Library" means either the Library or any derivative work under 133 | copyright law: that is to say, a work containing the Library or a 134 | portion of it, either verbatim or with modifications and/or translated 135 | straightforwardly into another language. (Hereinafter, translation is 136 | included without limitation in the term "modification".) 137 | 138 | "Source code" for a work means the preferred form of the work for 139 | making modifications to it. For a library, complete source code means 140 | all the source code for all modules it contains, plus any associated 141 | interface definition files, plus the scripts used to control 142 | compilation and installation of the library. 143 | 144 | Activities other than copying, distribution and modification are not 145 | covered by this License; they are outside its scope. The act of 146 | running a program using the Library is not restricted, and output from 147 | such a program is covered only if its contents constitute a work based 148 | on the Library (independent of the use of the Library in a tool for 149 | writing it). Whether that is true depends on what the Library does 150 | and what the program that uses the Library does. 151 | 152 | 1. You may copy and distribute verbatim copies of the Library's 153 | complete source code as you receive it, in any medium, provided that 154 | you conspicuously and appropriately publish on each copy an 155 | appropriate copyright notice and disclaimer of warranty; keep intact 156 | all the notices that refer to this License and to the absence of any 157 | warranty; and distribute a copy of this License along with the 158 | Library. 159 | 160 | You may charge a fee for the physical act of transferring a copy, 161 | and you may at your option offer warranty protection in exchange for a 162 | fee. 163 | 164 | 2. You may modify your copy or copies of the Library or any portion 165 | of it, thus forming a work based on the Library, and copy and 166 | distribute such modifications or work under the terms of Section 1 167 | above, provided that you also meet all of these conditions: 168 | 169 | a) The modified work must itself be a software library. 170 | 171 | b) You must cause the files modified to carry prominent notices 172 | stating that you changed the files and the date of any change. 173 | 174 | c) You must cause the whole of the work to be licensed at no 175 | charge to all third parties under the terms of this License. 176 | 177 | d) If a facility in the modified Library refers to a function or a 178 | table of data to be supplied by an application program that uses 179 | the facility, other than as an argument passed when the facility 180 | is invoked, then you must make a good faith effort to ensure that, 181 | in the event an application does not supply such function or 182 | table, the facility still operates, and performs whatever part of 183 | its purpose remains meaningful. 184 | 185 | (For example, a function in a library to compute square roots has 186 | a purpose that is entirely well-defined independent of the 187 | application. Therefore, Subsection 2d requires that any 188 | application-supplied function or table used by this function must 189 | be optional: if the application does not supply it, the square 190 | root function must still compute square roots.) 191 | 192 | These requirements apply to the modified work as a whole. If 193 | identifiable sections of that work are not derived from the Library, 194 | and can be reasonably considered independent and separate works in 195 | themselves, then this License, and its terms, do not apply to those 196 | sections when you distribute them as separate works. But when you 197 | distribute the same sections as part of a whole which is a work based 198 | on the Library, the distribution of the whole must be on the terms of 199 | this License, whose permissions for other licensees extend to the 200 | entire whole, and thus to each and every part regardless of who wrote 201 | it. 202 | 203 | Thus, it is not the intent of this section to claim rights or contest 204 | your rights to work written entirely by you; rather, the intent is to 205 | exercise the right to control the distribution of derivative or 206 | collective works based on the Library. 207 | 208 | In addition, mere aggregation of another work not based on the Library 209 | with the Library (or with a work based on the Library) on a volume of 210 | a storage or distribution medium does not bring the other work under 211 | the scope of this License. 212 | 213 | 3. You may opt to apply the terms of the ordinary GNU General Public 214 | License instead of this License to a given copy of the Library. To do 215 | this, you must alter all the notices that refer to this License, so 216 | that they refer to the ordinary GNU General Public License, version 2, 217 | instead of to this License. (If a newer version than version 2 of the 218 | ordinary GNU General Public License has appeared, then you can specify 219 | that version instead if you wish.) Do not make any other change in 220 | these notices. 221 | 222 | Once this change is made in a given copy, it is irreversible for 223 | that copy, so the ordinary GNU General Public License applies to all 224 | subsequent copies and derivative works made from that copy. 225 | 226 | This option is useful when you wish to copy part of the code of 227 | the Library into a program that is not a library. 228 | 229 | 4. You may copy and distribute the Library (or a portion or 230 | derivative of it, under Section 2) in object code or executable form 231 | under the terms of Sections 1 and 2 above provided that you accompany 232 | it with the complete corresponding machine-readable source code, which 233 | must be distributed under the terms of Sections 1 and 2 above on a 234 | medium customarily used for software interchange. 235 | 236 | If distribution of object code is made by offering access to copy 237 | from a designated place, then offering equivalent access to copy the 238 | source code from the same place satisfies the requirement to 239 | distribute the source code, even though third parties are not 240 | compelled to copy the source along with the object code. 241 | 242 | 5. A program that contains no derivative of any portion of the 243 | Library, but is designed to work with the Library by being compiled or 244 | linked with it, is called a "work that uses the Library". Such a 245 | work, in isolation, is not a derivative work of the Library, and 246 | therefore falls outside the scope of this License. 247 | 248 | However, linking a "work that uses the Library" with the Library 249 | creates an executable that is a derivative of the Library (because it 250 | contains portions of the Library), rather than a "work that uses the 251 | library". The executable is therefore covered by this License. 252 | Section 6 states terms for distribution of such executables. 253 | 254 | When a "work that uses the Library" uses material from a header file 255 | that is part of the Library, the object code for the work may be a 256 | derivative work of the Library even though the source code is not. 257 | Whether this is true is especially significant if the work can be 258 | linked without the Library, or if the work is itself a library. The 259 | threshold for this to be true is not precisely defined by law. 260 | 261 | If such an object file uses only numerical parameters, data 262 | structure layouts and accessors, and small macros and small inline 263 | functions (ten lines or less in length), then the use of the object 264 | file is unrestricted, regardless of whether it is legally a derivative 265 | work. (Executables containing this object code plus portions of the 266 | Library will still fall under Section 6.) 267 | 268 | Otherwise, if the work is a derivative of the Library, you may 269 | distribute the object code for the work under the terms of Section 6. 270 | Any executables containing that work also fall under Section 6, 271 | whether or not they are linked directly with the Library itself. 272 | 273 | 6. As an exception to the Sections above, you may also combine or 274 | link a "work that uses the Library" with the Library to produce a 275 | work containing portions of the Library, and distribute that work 276 | under terms of your choice, provided that the terms permit 277 | modification of the work for the customer's own use and reverse 278 | engineering for debugging such modifications. 279 | 280 | You must give prominent notice with each copy of the work that the 281 | Library is used in it and that the Library and its use are covered by 282 | this License. You must supply a copy of this License. If the work 283 | during execution displays copyright notices, you must include the 284 | copyright notice for the Library among them, as well as a reference 285 | directing the user to the copy of this License. Also, you must do one 286 | of these things: 287 | 288 | a) Accompany the work with the complete corresponding 289 | machine-readable source code for the Library including whatever 290 | changes were used in the work (which must be distributed under 291 | Sections 1 and 2 above); and, if the work is an executable linked 292 | with the Library, with the complete machine-readable "work that 293 | uses the Library", as object code and/or source code, so that the 294 | user can modify the Library and then relink to produce a modified 295 | executable containing the modified Library. (It is understood 296 | that the user who changes the contents of definitions files in the 297 | Library will not necessarily be able to recompile the application 298 | to use the modified definitions.) 299 | 300 | b) Use a suitable shared library mechanism for linking with the 301 | Library. A suitable mechanism is one that (1) uses at run time a 302 | copy of the library already present on the user's computer system, 303 | rather than copying library functions into the executable, and (2) 304 | will operate properly with a modified version of the library, if 305 | the user installs one, as long as the modified version is 306 | interface-compatible with the version that the work was made with. 307 | 308 | c) Accompany the work with a written offer, valid for at least 309 | three years, to give the same user the materials specified in 310 | Subsection 6a, above, for a charge no more than the cost of 311 | performing this distribution. 312 | 313 | d) If distribution of the work is made by offering access to copy 314 | from a designated place, offer equivalent access to copy the above 315 | specified materials from the same place. 316 | 317 | e) Verify that the user has already received a copy of these 318 | materials or that you have already sent this user a copy. 319 | 320 | For an executable, the required form of the "work that uses the 321 | Library" must include any data and utility programs needed for 322 | reproducing the executable from it. However, as a special exception, 323 | the materials to be distributed need not include anything that is 324 | normally distributed (in either source or binary form) with the major 325 | components (compiler, kernel, and so on) of the operating system on 326 | which the executable runs, unless that component itself accompanies 327 | the executable. 328 | 329 | It may happen that this requirement contradicts the license 330 | restrictions of other proprietary libraries that do not normally 331 | accompany the operating system. Such a contradiction means you cannot 332 | use both them and the Library together in an executable that you 333 | distribute. 334 | 335 | 7. You may place library facilities that are a work based on the 336 | Library side-by-side in a single library together with other library 337 | facilities not covered by this License, and distribute such a combined 338 | library, provided that the separate distribution of the work based on 339 | the Library and of the other library facilities is otherwise 340 | permitted, and provided that you do these two things: 341 | 342 | a) Accompany the combined library with a copy of the same work 343 | based on the Library, uncombined with any other library 344 | facilities. This must be distributed under the terms of the 345 | Sections above. 346 | 347 | b) Give prominent notice with the combined library of the fact 348 | that part of it is a work based on the Library, and explaining 349 | where to find the accompanying uncombined form of the same work. 350 | 351 | 8. You may not copy, modify, sublicense, link with, or distribute 352 | the Library except as expressly provided under this License. Any 353 | attempt otherwise to copy, modify, sublicense, link with, or 354 | distribute the Library is void, and will automatically terminate your 355 | rights under this License. However, parties who have received copies, 356 | or rights, from you under this License will not have their licenses 357 | terminated so long as such parties remain in full compliance. 358 | 359 | 9. You are not required to accept this License, since you have not 360 | signed it. However, nothing else grants you permission to modify or 361 | distribute the Library or its derivative works. These actions are 362 | prohibited by law if you do not accept this License. Therefore, by 363 | modifying or distributing the Library (or any work based on the 364 | Library), you indicate your acceptance of this License to do so, and 365 | all its terms and conditions for copying, distributing or modifying 366 | the Library or works based on it. 367 | 368 | 10. Each time you redistribute the Library (or any work based on the 369 | Library), the recipient automatically receives a license from the 370 | original licensor to copy, distribute, link with or modify the Library 371 | subject to these terms and conditions. You may not impose any further 372 | restrictions on the recipients' exercise of the rights granted herein. 373 | You are not responsible for enforcing compliance by third parties with 374 | this License. 375 | 376 | 11. If, as a consequence of a court judgment or allegation of patent 377 | infringement or for any other reason (not limited to patent issues), 378 | conditions are imposed on you (whether by court order, agreement or 379 | otherwise) that contradict the conditions of this License, they do not 380 | excuse you from the conditions of this License. If you cannot 381 | distribute so as to satisfy simultaneously your obligations under this 382 | License and any other pertinent obligations, then as a consequence you 383 | may not distribute the Library at all. For example, if a patent 384 | license would not permit royalty-free redistribution of the Library by 385 | all those who receive copies directly or indirectly through you, then 386 | the only way you could satisfy both it and this License would be to 387 | refrain entirely from distribution of the Library. 388 | 389 | If any portion of this section is held invalid or unenforceable under 390 | any particular circumstance, the balance of the section is intended to 391 | apply, and the section as a whole is intended to apply in other 392 | circumstances. 393 | 394 | It is not the purpose of this section to induce you to infringe any 395 | patents or other property right claims or to contest validity of any 396 | such claims; this section has the sole purpose of protecting the 397 | integrity of the free software distribution system which is 398 | implemented by public license practices. Many people have made 399 | generous contributions to the wide range of software distributed 400 | through that system in reliance on consistent application of that 401 | system; it is up to the author/donor to decide if he or she is willing 402 | to distribute software through any other system and a licensee cannot 403 | impose that choice. 404 | 405 | This section is intended to make thoroughly clear what is believed to 406 | be a consequence of the rest of this License. 407 | 408 | 12. If the distribution and/or use of the Library is restricted in 409 | certain countries either by patents or by copyrighted interfaces, the 410 | original copyright holder who places the Library under this License 411 | may add an explicit geographical distribution limitation excluding those 412 | countries, so that distribution is permitted only in or among 413 | countries not thus excluded. In such case, this License incorporates 414 | the limitation as if written in the body of this License. 415 | 416 | 13. The Free Software Foundation may publish revised and/or new 417 | versions of the Lesser General Public License from time to time. 418 | Such new versions will be similar in spirit to the present version, 419 | but may differ in detail to address new problems or concerns. 420 | 421 | Each version is given a distinguishing version number. If the Library 422 | specifies a version number of this License which applies to it and 423 | "any later version", you have the option of following the terms and 424 | conditions either of that version or of any later version published by 425 | the Free Software Foundation. If the Library does not specify a 426 | license version number, you may choose any version ever published by 427 | the Free Software Foundation. 428 | 429 | 14. If you wish to incorporate parts of the Library into other free 430 | programs whose distribution conditions are incompatible with these, 431 | write to the author to ask for permission. For software which is 432 | copyrighted by the Free Software Foundation, write to the Free 433 | Software Foundation; we sometimes make exceptions for this. Our 434 | decision will be guided by the two goals of preserving the free status 435 | of all derivatives of our free software and of promoting the sharing 436 | and reuse of software generally. 437 | 438 | NO WARRANTY 439 | 440 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 441 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 442 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 443 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 444 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 445 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 446 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 447 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 448 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 449 | 450 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 451 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 452 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 453 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 454 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 455 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 456 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 457 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 458 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 459 | DAMAGES. 460 | 461 | END OF TERMS AND CONDITIONS 462 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxde/menu-cache/014ed4ce881fc3154c08e98e8d59c1b13c3f2f06/ChangeLog -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | Installation Instructions 2 | ************************* 3 | 4 | Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, 5 | 2006, 2007 Free Software Foundation, Inc. 6 | 7 | This file is free documentation; the Free Software Foundation gives 8 | unlimited permission to copy, distribute and modify it. 9 | 10 | Basic Installation 11 | ================== 12 | 13 | Briefly, the shell commands `./configure; make; make install' should 14 | configure, build, and install this package. The following 15 | more-detailed instructions are generic; see the `README' file for 16 | instructions specific to this package. 17 | 18 | The `configure' shell script attempts to guess correct values for 19 | various system-dependent variables used during compilation. It uses 20 | those values to create a `Makefile' in each directory of the package. 21 | It may also create one or more `.h' files containing system-dependent 22 | definitions. Finally, it creates a shell script `config.status' that 23 | you can run in the future to recreate the current configuration, and a 24 | file `config.log' containing compiler output (useful mainly for 25 | debugging `configure'). 26 | 27 | It can also use an optional file (typically called `config.cache' 28 | and enabled with `--cache-file=config.cache' or simply `-C') that saves 29 | the results of its tests to speed up reconfiguring. Caching is 30 | disabled by default to prevent problems with accidental use of stale 31 | cache files. 32 | 33 | If you need to do unusual things to compile the package, please try 34 | to figure out how `configure' could check whether to do them, and mail 35 | diffs or instructions to the address given in the `README' so they can 36 | be considered for the next release. If you are using the cache, and at 37 | some point `config.cache' contains results you don't want to keep, you 38 | may remove or edit it. 39 | 40 | The file `configure.ac' (or `configure.in') is used to create 41 | `configure' by a program called `autoconf'. You need `configure.ac' if 42 | you want to change it or regenerate `configure' using a newer version 43 | of `autoconf'. 44 | 45 | The simplest way to compile this package is: 46 | 47 | 1. `cd' to the directory containing the package's source code and type 48 | `./configure' to configure the package for your system. 49 | 50 | Running `configure' might take a while. While running, it prints 51 | some messages telling which features it is checking for. 52 | 53 | 2. Type `make' to compile the package. 54 | 55 | 3. Optionally, type `make check' to run any self-tests that come with 56 | the package. 57 | 58 | 4. Type `make install' to install the programs and any data files and 59 | documentation. 60 | 61 | 5. You can remove the program binaries and object files from the 62 | source code directory by typing `make clean'. To also remove the 63 | files that `configure' created (so you can compile the package for 64 | a different kind of computer), type `make distclean'. There is 65 | also a `make maintainer-clean' target, but that is intended mainly 66 | for the package's developers. If you use it, you may have to get 67 | all sorts of other programs in order to regenerate files that came 68 | with the distribution. 69 | 70 | 6. Often, you can also type `make uninstall' to remove the installed 71 | files again. 72 | 73 | Compilers and Options 74 | ===================== 75 | 76 | Some systems require unusual options for compilation or linking that the 77 | `configure' script does not know about. Run `./configure --help' for 78 | details on some of the pertinent environment variables. 79 | 80 | You can give `configure' initial values for configuration parameters 81 | by setting variables in the command line or in the environment. Here 82 | is an example: 83 | 84 | ./configure CC=c99 CFLAGS=-g LIBS=-lposix 85 | 86 | *Note Defining Variables::, for more details. 87 | 88 | Compiling For Multiple Architectures 89 | ==================================== 90 | 91 | You can compile the package for more than one kind of computer at the 92 | same time, by placing the object files for each architecture in their 93 | own directory. To do this, you can use GNU `make'. `cd' to the 94 | directory where you want the object files and executables to go and run 95 | the `configure' script. `configure' automatically checks for the 96 | source code in the directory that `configure' is in and in `..'. 97 | 98 | With a non-GNU `make', it is safer to compile the package for one 99 | architecture at a time in the source code directory. After you have 100 | installed the package for one architecture, use `make distclean' before 101 | reconfiguring for another architecture. 102 | 103 | Installation Names 104 | ================== 105 | 106 | By default, `make install' installs the package's commands under 107 | `/usr/local/bin', include files under `/usr/local/include', etc. You 108 | can specify an installation prefix other than `/usr/local' by giving 109 | `configure' the option `--prefix=PREFIX'. 110 | 111 | You can specify separate installation prefixes for 112 | architecture-specific files and architecture-independent files. If you 113 | pass the option `--exec-prefix=PREFIX' to `configure', the package uses 114 | PREFIX as the prefix for installing programs and libraries. 115 | Documentation and other data files still use the regular prefix. 116 | 117 | In addition, if you use an unusual directory layout you can give 118 | options like `--bindir=DIR' to specify different values for particular 119 | kinds of files. Run `configure --help' for a list of the directories 120 | you can set and what kinds of files go in them. 121 | 122 | If the package supports it, you can cause programs to be installed 123 | with an extra prefix or suffix on their names by giving `configure' the 124 | option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. 125 | 126 | Optional Features 127 | ================= 128 | 129 | Some packages pay attention to `--enable-FEATURE' options to 130 | `configure', where FEATURE indicates an optional part of the package. 131 | They may also pay attention to `--with-PACKAGE' options, where PACKAGE 132 | is something like `gnu-as' or `x' (for the X Window System). The 133 | `README' should mention any `--enable-' and `--with-' options that the 134 | package recognizes. 135 | 136 | For packages that use the X Window System, `configure' can usually 137 | find the X include and library files automatically, but if it doesn't, 138 | you can use the `configure' options `--x-includes=DIR' and 139 | `--x-libraries=DIR' to specify their locations. 140 | 141 | Specifying the System Type 142 | ========================== 143 | 144 | There may be some features `configure' cannot figure out automatically, 145 | but needs to determine by the type of machine the package will run on. 146 | Usually, assuming the package is built to be run on the _same_ 147 | architectures, `configure' can figure that out, but if it prints a 148 | message saying it cannot guess the machine type, give it the 149 | `--build=TYPE' option. TYPE can either be a short name for the system 150 | type, such as `sun4', or a canonical name which has the form: 151 | 152 | CPU-COMPANY-SYSTEM 153 | 154 | where SYSTEM can have one of these forms: 155 | 156 | OS KERNEL-OS 157 | 158 | See the file `config.sub' for the possible values of each field. If 159 | `config.sub' isn't included in this package, then this package doesn't 160 | need to know the machine type. 161 | 162 | If you are _building_ compiler tools for cross-compiling, you should 163 | use the option `--target=TYPE' to select the type of system they will 164 | produce code for. 165 | 166 | If you want to _use_ a cross compiler, that generates code for a 167 | platform different from the build platform, you should specify the 168 | "host" platform (i.e., that on which the generated programs will 169 | eventually be run) with `--host=TYPE'. 170 | 171 | Sharing Defaults 172 | ================ 173 | 174 | If you want to set default values for `configure' scripts to share, you 175 | can create a site shell script called `config.site' that gives default 176 | values for variables like `CC', `cache_file', and `prefix'. 177 | `configure' looks for `PREFIX/share/config.site' if it exists, then 178 | `PREFIX/etc/config.site' if it exists. Or, you can set the 179 | `CONFIG_SITE' environment variable to the location of the site script. 180 | A warning: not all `configure' scripts look for a site script. 181 | 182 | Defining Variables 183 | ================== 184 | 185 | Variables not defined in a site shell script can be set in the 186 | environment passed to `configure'. However, some packages may run 187 | configure again during the build, and the customized values of these 188 | variables may be lost. In order to avoid this problem, you should set 189 | them in the `configure' command line, using `VAR=value'. For example: 190 | 191 | ./configure CC=/usr/local2/bin/gcc 192 | 193 | causes the specified `gcc' to be used as the C compiler (unless it is 194 | overridden in the site shell script). 195 | 196 | Unfortunately, this technique does not work for `CONFIG_SHELL' due to 197 | an Autoconf bug. Until the bug is fixed you can use this workaround: 198 | 199 | CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash 200 | 201 | `configure' Invocation 202 | ====================== 203 | 204 | `configure' recognizes the following options to control how it operates. 205 | 206 | `--help' 207 | `-h' 208 | Print a summary of the options to `configure', and exit. 209 | 210 | `--version' 211 | `-V' 212 | Print the version of Autoconf used to generate the `configure' 213 | script, and exit. 214 | 215 | `--cache-file=FILE' 216 | Enable the cache: use and save the results of the tests in FILE, 217 | traditionally `config.cache'. FILE defaults to `/dev/null' to 218 | disable caching. 219 | 220 | `--config-cache' 221 | `-C' 222 | Alias for `--cache-file=config.cache'. 223 | 224 | `--quiet' 225 | `--silent' 226 | `-q' 227 | Do not print messages saying which checks are being made. To 228 | suppress all normal output, redirect it to `/dev/null' (any error 229 | messages will still be shown). 230 | 231 | `--srcdir=DIR' 232 | Look for the package's source code in directory DIR. Usually 233 | `configure' can determine that directory automatically. 234 | 235 | `configure' also accepts some other, not widely useful, options. Run 236 | `configure --help' for more details. 237 | 238 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | ## Process this file with automake to produce Makefile.in 2 | 3 | ACLOCAL_AMFLAGS= -I m4 4 | 5 | NULL = 6 | 7 | SUBDIRS_DOCS = docs 8 | 9 | EXTRA_DIST_DOCS = \ 10 | gtk-doc.make \ 11 | docs/Makefile.am \ 12 | docs/Makefile.in \ 13 | docs/reference/Makefile.am \ 14 | docs/reference/Makefile.in \ 15 | docs/reference/libmenu-cache/Makefile.am \ 16 | docs/reference/libmenu-cache/Makefile.in \ 17 | $(NULL) 18 | 19 | ALL_SUBDIRS = \ 20 | libmenu-cache \ 21 | menu-cache-gen \ 22 | menu-cache-daemon \ 23 | $(NULL) 24 | 25 | SUBDIRS = $(ALL_SUBDIRS) 26 | DIST_SUBDIRS = $(ALL_SUBDIRS) 27 | 28 | EXTRA_DIST = \ 29 | $(NULL) 30 | 31 | if ENABLE_GTK_DOC 32 | DIST_SUBDIRS += $(SUBDIRS_DOCS) 33 | SUBDIRS += $(SUBDIRS_DOCS) 34 | else 35 | EXTRA_DIST += $(EXTRA_DIST_DOCS) 36 | endif 37 | 38 | DISTCHECK_CONFIGURE_FLAGS=--enable-gtk-doc 39 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | Changes in 1.1.1 since 1.1.0: 2 | 3 | * Fixed memory leaks. 4 | 5 | * GCC 10 compilation support. 6 | 7 | * Removed unused libmenu-cache-uninstalled.pc.in. 8 | 9 | 10 | Changes in 1.1.0 since 1.0.2: 11 | 12 | * Fixed crash with invalid tag in a menu. 13 | 14 | * Added new API menu_cache_app_get_generic_name() to get generic 15 | name for application. 16 | 17 | * Fixed potential access violation, use runtime user dir instead of tmp dir. 18 | It limits libmenu-cache compatibility to menu-cached >= 0.7.0. 19 | 20 | * Directory $XDG_DATA_HOME/applications will be created if it does not 21 | exist so it will be monitored in any case. 22 | 23 | * Fixed issue when subdirectories added would be skipped in monitoring. 24 | 25 | * Fixed potential file descriptors leak. 26 | 27 | * Reduced inactivity timer to 6 seconds (from 600 seconds). 28 | 29 | * Fixed an issue with multiple daemons started: test if daemon is already 30 | running on socket before killing old socket file. 31 | 32 | * Fixed 100% CPU load by menu-cached due to invalid dup2() call. 33 | 34 | 35 | Changes in 1.0.2 since 1.0.1: 36 | 37 | * Fixed crash in menu-cached if cache regeneration fails. 38 | 39 | * Fixed 100% CPU load by menu-cached in some rare conditions. 40 | 41 | * Invalid empty should be ignored, see specification. 42 | 43 | * Fixed crash in menu-cache-gen on if no file 44 | to merge found. 45 | 46 | * Fixed showing empty Other menu in some cases. 47 | 48 | * The option --disable-debug is now default, instead of --enable-debug. 49 | 50 | 51 | Changes in 1.0.1 since 1.0.0: 52 | 53 | * Fixed crash on generating menu with both tags and 54 | present. 55 | 56 | * Fixed crash when menu-cache-gen ran manually without CACHE_GEN_VERSION. 57 | 58 | * Fixed crash in menu-cache-gen on in menu layout with 59 | tag present. 60 | 61 | * Fixed incorrect processing of in menu-cache-gen. 62 | 63 | * Added safeguards against environment variables containing newlines. 64 | 65 | * Fixed case when cache was not updated while it should, it was claimed 66 | to be fixed in 1.0.0 but apparently fix was incomplete. 67 | 68 | * Fixed ignored tags inside of tag. 69 | 70 | * Fixed memory corruption in scanning addressed directory. 71 | 72 | 73 | Changes in 1.0.0 since 0.7.0: 74 | 75 | * Added new cache file format generation support with changes: 76 | - invisible directories (NoDisplay=true or empty) can be put into the 77 | cache too but with flag (not displayed) set; 78 | - content of TryExec field is added to contents of cache file; 79 | - the working dir to execute application is added to cache file; 80 | - list of categories is added to contents of cache file; 81 | - list of keywords is added to contents of cache file. 82 | 83 | * Fixed crash in menu_cache_item_get_file_dirname() for a non-existent 84 | file (might happen for directories without .directory file). 85 | 86 | * Made menu_cache_app_get_working_dir() actually work. 87 | 88 | * Made menu_cache_lookup() faster (do not load cache immediately but on 89 | idle instead). 90 | 91 | * Eliminated secondary cache reload in menu_cache_lookup_sync() - server 92 | response in such case will be ahead of idle reload (since main thread 93 | is in wait ATM) and therefore idle call will be supressed. 94 | 95 | * Added new API menu_cache_app_get_categories() to get list of categories 96 | for the application. 97 | 98 | * Added new APIs for applications: menu_cache_list_all_for_category() and 99 | menu_cache_list_all_for_keyword() that return list of applications 100 | matching criteria. 101 | 102 | * Fixed problem if some string in the desktop entry file contained a new 103 | line character. That broke cache file format, now it's replaced with 104 | a "\n" string and converted back into new line in the library. 105 | 106 | * Fixed bug when cache was not updated while it should: check if the last 107 | modification time for directory is more recent than modification time 108 | for cache may not always be valid - some .desktop entry might be just 109 | changed by update and that will lead to falsed cache contents. 110 | 111 | * Added Log Domain "Menu-Cache" for better library logging messages. 112 | 113 | 114 | Changes in 0.7.0 since 0.6.1: 115 | 116 | * Added bit of support for multiple supported cache file versions, using 117 | CACHE_GEN_VERSION environment variable to the generator. This may be 118 | useful in future when 1.2 cache file version will be implemented. 119 | 120 | * The menu-cache-gen libexec binary is rewritten from scratch. No that 121 | Red Hat / GNOME code anymore. New menu-cache-gen uses libfm-extra XML 122 | manipulation functions therefore it is required now for build. 123 | 124 | * Added a parameter for menu-cached to specify socket path instead of 125 | calculating one, that is definitely more safe. 126 | 127 | * Libmenu-cache handles menu-cached failure more gracefully now, don't 128 | tries to restart it so fast that it clones many times. 129 | 130 | * Fixed menu-cached crash after menu-cache-gen failure. 131 | 132 | 133 | Changes in 0.6.1 since 0.6.0: 134 | 135 | * Fixed invalid memory access after cache reload. 136 | 137 | * A little cleanup of dist tarball: removed unused files menu-cache.h and 138 | libmenu-cache-uninstalled.pc.in. 139 | 140 | 141 | Changes in 0.6.0 since 0.5.1: 142 | 143 | * Fixed few GLIB compatibility issues. 144 | 145 | * Changed default tarball format to XZ instead of GZIP. 146 | 147 | * Fixed build without --enable-gtk-doc. 148 | 149 | * Fixed crash on access root_dir with empty cache (no menu). 150 | 151 | * Allowed menu_cache_get_desktop_env_flag() accept colon-separated list 152 | in accordance to freedesktop.org specification for the environment 153 | variable XDG_CURRENT_DESKTOP. 154 | 155 | * The case if user deleted cache file is handled: it will be regenerated. 156 | 157 | 158 | Changes in 0.5.1 since 0.5.0: 159 | 160 | * Fixed build on systems where MAXSYMLINKS isn't defined. 161 | 162 | * Fixed menu-cached crash in some rare cases. 163 | 164 | 165 | Changes in 0.5.0 since 0.4.1: 166 | 167 | * Added a possibility to include NoDisplay files into cache file. This 168 | can be achieved by adding suffix '+hidden' to requested name in call 169 | to menu_cache_lookup(). The hidden items will be returned along with 170 | visible ones by any API that returns listing of cache directory. The 171 | menu_cache_app_get_is_visible() API will return FALSE for hidden item 172 | with any DE mask passed to the API. 173 | 174 | * New macro MENU_CACHE_CHECK_VERSION() to test version of library. 175 | 176 | * Added new APIs: menu_cache_find_item_by_id, menu_cache_find_child_by_id, 177 | menu_cache_find_child_by_name. 178 | 179 | * Two bugfixes for crashes, and for some another bugs. 180 | 181 | 182 | Changes in 0.4.1 since 0.4.0: 183 | 184 | * Minor bugfix, the resulting tar file was not complete. 185 | 186 | Changes in 0.4.0 since 0.3.3: 187 | 188 | * The libmenu-cache is made thread-safe. Thread-unsafe APIs are marked 189 | as deprecated now and should be never used in any multithreaded 190 | application. 191 | 192 | * Added creation of HTML developers documentation. It is triggered by 193 | configure script option --enable-gtk-doc. 194 | 195 | * The libmenu-cache is made more responsible by moving some time-critical 196 | operations into thread. Also it preloads saved cache file if it 197 | exists so it is available near instantly after menu_cache_lookup(). 198 | Caller still will get updates by adding notifier to the cache. 199 | 200 | * Added automatic shutdown of menu-cached server after some inactivity 201 | timeout (i.e. all clients were unregistered). 202 | 203 | * Fixed few memory problems (referencing errors and memory leaks). 204 | 205 | * Fix for bug #3501347: use g_get_tmp_dir() instead of hardcoded "/tmp". 206 | 207 | * The libmenu-cache ABI bumped to 2. 208 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Libmenu-cache is a library creating and utilizing caches to speed up 2 | the manipulation for freedesktop.org defined application menus. 3 | It can be used as a replacement of libgnome-menu of gnome-menus. 4 | 5 | Advantages: 6 | 1. Shorten time for loading menu entries. 7 | 2. Ease of use. (API is very similar to that of libgnome-menu) 8 | 3. Lightweight runtime library. (Parsing of the menu definition files 9 | are done by menu-cache-gen when the menus are really changed.) 10 | 4. Less unnecessary and complicated file monitoring. 11 | 5. Heavily reduced disk I/O. 12 | 13 | Installing: 14 | 15 | Since version 0.7.0 the Libmenu-cache requires Libfm-extra for the 16 | menu-cache-gen menu cache generation binary. Since Libfm depends on 17 | Libmenu-cache, there is some hint for bootstrapers how to build those 18 | libraries together: you need create Libfm-extra first, you can easily 19 | do this by passing '--with-extra-only' option to configure script and 20 | installing Libfm-extra. Then you can succesfully build Libmenu-cache 21 | and therefore build full version of Libfm. 22 | 23 | Diagnostics: 24 | 25 | Libmenu-cache uses cache generation in "fail-proof" mode when it will 26 | ignore improper tags in XML menu file but fail only if that menu file 27 | doesn't exist at all, or has invalid XML structure, or has no root menu 28 | with name 'Applications'. If you want to explore how it is generated 29 | then you can start menu-cache-gen manually in verbose mode, for example: 30 | 31 | G_MESSAGES_DEBUG=all \ 32 | /usr/lib/menu-cache/menu-cache-gen -v -i applications.menu -o /dev/null 33 | 34 | so you can see all the processing of your XML file and it will fail on 35 | any broken tag or at least show you a warning and you can inspect that 36 | log. 37 | 38 | Spec: 39 | 40 | Cached menus are localized and stored in ~/.cache/menus/file_name. 41 | file_name is a md5 hash of the name of the original menu + 42 | some environment variable + 43 | locale name. 44 | 45 | Since most data in a menu are plain text (names, description comments, 46 | icon names,...etc.), the cached file is in plain text rather than binary 47 | to prevent byte order problems. 48 | 49 | Format of the cached menu file, line by line. 50 | (Spaces before the lines are for easier demostration of hierarchy. 51 | There are no spaces in the real cached file. 52 | Fields marked with [**] added only in the file format 1.2): 53 | 54 | version number (major.minor) 55 | menu name> 56 | number of files to monitor 57 | list of files/dirs which require monitor (prefix D or F indicate whether it 58 | is a dir or file) 59 | list of DE names used in this menu other than the known DEs (LXDE, GNOME, 60 | XFCE, KDE, ROX), 61 | seperated by ; 62 | +id of top menu dir (+ means dir entry, and - means an application entry) 63 | title 64 | comment 65 | icon name 66 | menu directory file basename 67 | dir in which menu directory file locates (use this number as index to get 68 | the string from array of preceding 69 | list of monitored files) 70 | [**] bitmask flags: (--)|(--)|(not displayed) 71 | -application desktop id (NOTE: desktop id is not necessarily the basename 72 | of desktop file) 73 | title 74 | comment 75 | icon name 76 | app desktop file basename, empty means the same as desktop id. 77 | dir in which the desktop file localtes (use this number as index to get 78 | the string from array of 79 | preceding list of monitored files) 80 | generic name 81 | exec command line 82 | bitmask flags: (use terminal)|(use startup notification)|(not displayed) 83 | show_in_flags: bitwise or of masks for various DEs. 84 | [**] executable name to test for availability 85 | [**] working dir for exec 86 | [**] categories: list divided by semicolon 87 | [**] keywords: list divided by comma 88 | +sub dir id 89 | title 90 | comment 91 | icon name 92 | ... 93 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | AC_VERSION= 3 | 4 | test -n "$SRC_DIR" || SRC_DIR=$(dirname "$0") 5 | test -n "$SRC_DIR" || SRC_DIR=. 6 | 7 | OLD_DIR=$(pwd) 8 | cd "$SRC_DIR" 9 | 10 | AUTOMAKE=${AUTOMAKE:-automake} 11 | AM_INSTALLED_VERSION=$($AUTOMAKE --version | sed -e '2,$ d' -e 's/.* \([0-9]*\.[0-9]*\).*/\1/') 12 | 13 | # FIXME: we need a better way for version check later. 14 | case "$AM_INSTALLED_VERSION" in 15 | 1.1[0-9]) 16 | ;; 17 | *) 18 | echo 19 | echo "You must have automake 1.10 or newer installed." 20 | echo "Install the appropriate package for your distribution," 21 | echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/" 22 | exit 1 23 | ;; 24 | esac 25 | 26 | if [ "x${ACLOCAL_DIR}" != "x" ]; then 27 | ACLOCAL_ARG="-I ${ACLOCAL_DIR}" 28 | fi 29 | 30 | test -d m4 || mkdir m4 31 | 32 | if gtkdocize --copy; then 33 | echo "Files needed by gtk-doc are generated." 34 | else 35 | echo "You need gtk-doc to build this package." 36 | echo "http://www.gtk.org/gtk-doc/" 37 | exit 1 38 | fi 39 | 40 | set -x 41 | 42 | ${ACLOCAL:-aclocal$AM_VERSION} ${ACLOCAL_ARG} 43 | ${AUTOHEADER:-autoheader$AC_VERSION} --force 44 | AUTOMAKE=$AUTOMAKE libtoolize -c --automake --force 45 | $AUTOMAKE --add-missing --copy --include-deps 46 | ${AUTOCONF:-autoconf$AC_VERSION} 47 | 48 | rm -rf autom4te.cache 49 | 50 | if test -n "$DOCONFIGURE"; then 51 | ./configure $@ 52 | fi 53 | 54 | cd "$OLD_DIR" 55 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | dnl Process this file with autoconf to produce a configure script. 2 | 3 | AC_INIT([menu-cache], [1.1.1], 4 | [http://lxde.org/]) 5 | 6 | AC_CONFIG_MACRO_DIR([m4]) 7 | 8 | AM_INIT_AUTOMAKE([no-dist-gzip dist-xz]) 9 | AM_CONFIG_HEADER(config.h) 10 | 11 | # Support silent build rules. Disable by either passing --disable-silent-rules 12 | # to configure or passing V=1 to make 13 | AM_SILENT_RULES([yes]) 14 | 15 | AM_MAINTAINER_MODE([enable]) 16 | 17 | AC_ISC_POSIX 18 | AC_PROG_CC 19 | AM_PROG_CC_C_O 20 | AC_STDC_HEADERS 21 | dnl AC_ARG_PROGRAM 22 | AM_PROG_LIBTOOL 23 | 24 | dnl make sure we keep ACLOCAL_FLAGS around for maintainer builds to work 25 | AC_SUBST(ACLOCAL_AMFLAGS, "$ACLOCAL_FLAGS") 26 | 27 | PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.18.0 gio-2.0) 28 | AC_SUBST(GLIB_CFLAGS) 29 | AC_SUBST(GLIB_LIBS) 30 | 31 | PKG_CHECK_MODULES(LIBFM_EXTRA, libfm-extra) 32 | AC_SUBST(LIBFM_EXTRA_CFLAGS) 33 | AC_SUBST(LIBFM_EXTRA_LIBS) 34 | 35 | AC_ARG_ENABLE(more_warnings, 36 | [AC_HELP_STRING([--enable-more-warnings], 37 | [Add more warnings @<:@default=no@:>@])], 38 | [enable_more_warnings="${enableval}"], 39 | [enable_more_warnings=no] 40 | ) 41 | 42 | if test x"$enable_more_warnings" = x"yes"; then 43 | 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" 44 | fi 45 | AC_SUBST(ADDITIONAL_FLAGS) 46 | 47 | dnl --enable-debug=(yes|minimum|no) 48 | AC_ARG_ENABLE(debug, [ --enable-debug=[no/yes] turn on debugging [[default=no]]],,enable_debug=no) 49 | if test "$enable_debug" = "yes"; then 50 | DEBUG_CFLAGS="-DG_ENABLE_DEBUG" 51 | else 52 | if test "x$enable_debug" = "xno"; then 53 | DEBUG_CFLAGS="-DG_DISABLE_ASSERT -DG_DISABLE_CHECKS" 54 | else 55 | DEBUG_CFLAGS="" 56 | fi 57 | fi 58 | AC_SUBST(DEBUG_CFLAGS) 59 | 60 | dnl check for gtk-doc 61 | m4_ifdef([GTK_DOC_CHECK], [ 62 | GTK_DOC_CHECK([1.14],[--flavour no-tmpl]) 63 | ],[ 64 | AM_CONDITIONAL([ENABLE_GTK_DOC], false) 65 | ]) 66 | 67 | dnl Make version subfields for MENU_CACHE_CHECK_VERSION macro 68 | ac_version_subst_str=`echo $VERSION | awk -F '.' '/.*/ { printf "MC_VERSION_MAJOR=%d MC_VERSION_MINOR=%d MC_VERSION_MICRO=%d", $1, $2, $3 }'` 69 | eval ${ac_version_subst_str} 70 | AC_SUBST(MC_VERSION_MAJOR) 71 | AC_SUBST(MC_VERSION_MINOR) 72 | AC_SUBST(MC_VERSION_MICRO) 73 | 74 | exec_prefix_save="$exec_prefix" 75 | test "x$exec_prefix" = xNONE && exec_prefix="${prefix}" 76 | if test `eval echo "$libdir"` = '/usr/lib' 77 | then 78 | PCFILE_LIBDIR= 79 | else 80 | PCFILE_LIBDIR=' -L${libdir}' 81 | fi 82 | exec_prefix="$exec_prefix_save" 83 | AC_SUBST(PCFILE_LIBDIR) 84 | 85 | AC_OUTPUT([ 86 | Makefile 87 | libmenu-cache/Makefile 88 | libmenu-cache/libmenu-cache.pc 89 | libmenu-cache/menu-cache.h 90 | menu-cache-gen/Makefile 91 | menu-cache-daemon/Makefile 92 | docs/Makefile 93 | docs/reference/Makefile 94 | docs/reference/libmenu-cache/Makefile 95 | ]) 96 | -------------------------------------------------------------------------------- /docs/Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = reference 2 | -------------------------------------------------------------------------------- /docs/reference/Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = libmenu-cache 2 | -------------------------------------------------------------------------------- /docs/reference/libmenu-cache/Makefile.am: -------------------------------------------------------------------------------- 1 | ## Process this file with automake to produce Makefile.in 2 | 3 | # We require automake 1.6 at least. 4 | AUTOMAKE_OPTIONS = 1.6 5 | 6 | # This is a blank Makefile.am for using gtk-doc. 7 | # Copy this to your project's API docs directory and modify the variables to 8 | # suit your project. See the GTK+ Makefiles in gtk+/docs/reference for examples 9 | # of using the various options. 10 | 11 | # The name of the module, e.g. 'glib'. 12 | DOC_MODULE=libmenu-cache 13 | 14 | # Uncomment for versioned docs and specify the version of the module, e.g. '2'. 15 | #DOC_MODULE_VERSION=2 16 | 17 | 18 | # The top-level SGML file. You can change this if you want to. 19 | DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.sgml 20 | 21 | # Directories containing the source code. 22 | # gtk-doc will search all .c and .h files beneath these paths 23 | # for inline comments documenting functions and macros. 24 | # e.g. DOC_SOURCE_DIR=$(top_srcdir)/gtk $(top_srcdir)/gdk 25 | DOC_SOURCE_DIR=$(top_srcdir)/libmenu-cache 26 | 27 | # Extra options to pass to gtkdoc-scangobj. Not normally needed. 28 | SCANGOBJ_OPTIONS= 29 | 30 | # Extra options to supply to gtkdoc-scan. 31 | # e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED" 32 | SCAN_OPTIONS=--rebuild-sections --rebuild-types --deprecated-guards="G_DISABLE_DEPRECATED" 33 | 34 | # Extra options to supply to gtkdoc-mkdb. 35 | # e.g. MKDB_OPTIONS=--xml-mode --output-format=xml 36 | MKDB_OPTIONS=--xml-mode --output-format=xml 37 | 38 | # Extra options to supply to gtkdoc-mktmpl 39 | # e.g. MKTMPL_OPTIONS=--only-section-tmpl 40 | MKTMPL_OPTIONS= 41 | 42 | # Extra options to supply to gtkdoc-mkhtml 43 | MKHTML_OPTIONS= 44 | 45 | # Extra options to supply to gtkdoc-fixref. Not normally needed. 46 | # e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html 47 | FIXXREF_OPTIONS= 48 | 49 | # Used for dependencies. The docs will be rebuilt if any of these change. 50 | # e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h 51 | # e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c 52 | HFILE_GLOB=$(top_srcdir)/libmenu-cache/*.h 53 | CFILE_GLOB=$(top_srcdir)/libmenu-cache/*.c 54 | 55 | # Extra header to include when scanning, which are not under DOC_SOURCE_DIR 56 | # e.g. EXTRA_HFILES=$(top_srcdir}/contrib/extra.h 57 | EXTRA_HFILES= 58 | 59 | # Header files or dirs to ignore when scanning. Use base file/dir names 60 | # e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h private_code 61 | IGNORE_HFILES=version.h 62 | 63 | # Images to copy into HTML directory. 64 | # e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png 65 | HTML_IMAGES= 66 | 67 | # Extra SGML files that are included by $(DOC_MAIN_SGML_FILE). 68 | # e.g. content_files=running.sgml building.sgml changes-2.0.sgml 69 | content_files= 70 | 71 | # SGML files where gtk-doc abbrevations (#GtkWidget) are expanded 72 | # These files must be listed here *and* in content_files 73 | # e.g. expand_content_files=running.sgml 74 | expand_content_files= 75 | 76 | # CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library. 77 | # Only needed if you are using gtkdoc-scangobj to dynamically query widget 78 | # signals and properties. 79 | # e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS) 80 | # e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib) 81 | GTKDOC_CFLAGS= 82 | GTKDOC_LIBS=$(top_builddir)/libmenu-cache/libmenu-cache.la 83 | 84 | # This includes the standard gtk-doc make rules, copied by gtkdocize. 85 | include $(top_srcdir)/gtk-doc.make 86 | 87 | # Other files to distribute 88 | # e.g. EXTRA_DIST += version.xml.in 89 | EXTRA_DIST += 90 | 91 | # Files not to distribute 92 | # for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types 93 | # for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt 94 | DISTCLEANFILES = $(DOC_MODULE).types $(DOC_MODULE)-sections.txt 95 | 96 | # Fix parallel build - we need this file built before continuing 97 | $(DOC_MAIN_SGML_FILE): sgml-build.stamp 98 | 99 | # Comment this out if you want 'make check' to test you doc status 100 | # and run some sanity checks 101 | if ENABLE_GTK_DOC 102 | TESTS_ENVIRONMENT = cd $(srcdir) && \ 103 | DOC_MODULE=$(DOC_MODULE) DOC_MAIN_SGML_FILE=$(DOC_MAIN_SGML_FILE) \ 104 | SRCDIR=$(abs_srcdir) BUILDDIR=$(abs_builddir) 105 | #TESTS = $(GTKDOC_CHECK) 106 | endif 107 | 108 | -include $(top_srcdir)/git.mk 109 | -------------------------------------------------------------------------------- /libmenu-cache/Makefile.am: -------------------------------------------------------------------------------- 1 | NULL = 2 | 3 | AM_CPPFLAGS = \ 4 | $(GLIB_CFLAGS) \ 5 | $(DEBUG_CFLAGS) \ 6 | $(ADDITIONAL_FLAGS) \ 7 | -Werror-implicit-function-declaration \ 8 | -DMENUCACHE_LIBEXECDIR="\"$(pkglibexecdir)\"" \ 9 | -DG_LOG_DOMAIN=\"Menu-Cache\" \ 10 | $(NULL) 11 | 12 | lib_LTLIBRARIES = libmenu-cache.la 13 | 14 | libmenu_cache_la_SOURCES = \ 15 | menu-cache.c \ 16 | $(NULL) 17 | 18 | libmenu_cache_la_LIBADD = \ 19 | $(GLIB_LIBS) \ 20 | $(NULL) 21 | 22 | libmenu_cache_la_LDFLAGS = \ 23 | -no-undefined \ 24 | -export-symbols-regex menu_cache \ 25 | -version-info 5:1:2 \ 26 | $(NULL) 27 | 28 | lib_menu_cache_includedir = $(includedir)/menu-cache 29 | 30 | nodist_lib_menu_cache_include_HEADERS = \ 31 | menu-cache.h \ 32 | $(NULL) 33 | 34 | EXTRA_DIST = \ 35 | version.h \ 36 | libmenu-cache.pc.in \ 37 | $(NULL) 38 | 39 | pkgconfigdir = $(libdir)/pkgconfig 40 | pkgconfig_DATA = libmenu-cache.pc 41 | -------------------------------------------------------------------------------- /libmenu-cache/libmenu-cache.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@prefix@ 2 | exec_prefix=@exec_prefix@ 3 | libdir=@libdir@ 4 | includedir=@includedir@ 5 | 6 | Name: libmenu-cache 7 | Description: Cache for freedesktop.org menu spec 8 | Requires: glib-2.0 9 | Version: @VERSION@ 10 | Libs:@PCFILE_LIBDIR@ -lmenu-cache 11 | Cflags: -I${includedir}/menu-cache 12 | -------------------------------------------------------------------------------- /libmenu-cache/menu-cache.h.in: -------------------------------------------------------------------------------- 1 | /* 2 | * menu-cache.h 3 | * 4 | * libmenu-cache is a small convinient library used to access 5 | * caches of freedesktop.org menus generated by menu-cache-gen. 6 | * 7 | * Copyright 2008 PCMan 8 | * Copyright 2012-2014 Andriy Grytsenko (LStranger) 9 | * 10 | * This library is free software; you can redistribute it and/or 11 | * modify it under the terms of the GNU Lesser General Public 12 | * License as published by the Free Software Foundation; either 13 | * version 2.1 of the License, or (at your option) any later version. 14 | * 15 | * This library is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * Lesser General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU Lesser General Public 21 | * License along with this library; if not, write to the Free Software 22 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 23 | */ 24 | 25 | #ifndef __MENU_CACHE_H__ 26 | #define __MENU_CACHE_H__ 27 | 28 | #include 29 | 30 | G_BEGIN_DECLS 31 | 32 | #define __VERSION_MAJOR @MC_VERSION_MAJOR@ 33 | #define __VERSION_MINOR @MC_VERSION_MINOR@ 34 | #define __VERSION_MICRO @MC_VERSION_MICRO@ 35 | 36 | #define MENU_CACHE_CHECK_VERSION(_a,_b,_c) \ 37 | (__VERSION_MAJOR > _a || \ 38 | (__VERSION_MAJOR == _a && __VERSION_MINOR > _b) || \ 39 | (__VERSION_MAJOR == _a && __VERSION_MINOR == _b && __VERSION_MICRO >= _c)) 40 | 41 | #define MENU_CACHE_ITEM(x) ((MenuCacheItem*)x) 42 | #define MENU_CACHE_DIR(x) ((MenuCacheDir*)x) 43 | #define MENU_CACHE_APP(x) ((MenuCacheApp*)x) 44 | 45 | typedef struct _MenuCacheItem MenuCacheItem; 46 | typedef struct _MenuCacheDir MenuCacheDir; 47 | typedef struct _MenuCacheApp MenuCacheApp; 48 | typedef struct _MenuCache MenuCache; 49 | 50 | /** 51 | * MenuCacheType: 52 | * @MENU_CACHE_TYPE_NONE: invalid type 53 | * @MENU_CACHE_TYPE_DIR: item #MenuCacheDir 54 | * @MENU_CACHE_TYPE_APP: item #MenuCacheApp 55 | * @MENU_CACHE_TYPE_SEP: menu separator 56 | * 57 | * type of #MenuCacheItem. 58 | */ 59 | typedef enum 60 | { 61 | MENU_CACHE_TYPE_NONE, 62 | MENU_CACHE_TYPE_DIR, 63 | MENU_CACHE_TYPE_APP, 64 | MENU_CACHE_TYPE_SEP 65 | }MenuCacheType; 66 | 67 | /** 68 | * MenuCacheShowFlag: 69 | * @SHOW_IN_LXDE: show in LXDE 70 | * @SHOW_IN_GNOME: show in GNOME 71 | * @SHOW_IN_KDE: show in KDE 72 | * @SHOW_IN_XFCE: show in XFCE 73 | * @SHOW_IN_ROX: show in ROX 74 | * 75 | * bitmask of desktop environments where the item should be visible. 76 | */ 77 | typedef enum 78 | { 79 | SHOW_IN_LXDE = 1 << 0, 80 | SHOW_IN_GNOME = 1 << 1, 81 | SHOW_IN_KDE = 1 << 2, 82 | SHOW_IN_XFCE = 1 << 3, 83 | SHOW_IN_ROX = 1 << 4, 84 | /*< private >*/ 85 | N_KNOWN_DESKTOPS = 5 86 | }MenuCacheShowFlag; 87 | 88 | /** 89 | * MenuCacheItemFlag: 90 | * @FLAG_USE_TERMINAL: run this application in terminal 91 | * @FLAG_USE_SN: use Startup Notify for this application 92 | * @FLAG_IS_NODISPLAY: application is hidden from menu 93 | * 94 | * flags for application run. 95 | */ 96 | typedef enum 97 | { 98 | FLAG_USE_TERMINAL = 1 << 0, 99 | FLAG_USE_SN = 1 << 1, 100 | FLAG_IS_NODISPLAY = 1 << 2 101 | }MenuCacheItemFlag; 102 | 103 | void menu_cache_init(int flags); 104 | 105 | MenuCache* menu_cache_lookup( const char* menu_name ); 106 | MenuCache* menu_cache_lookup_sync( const char* menu_name ); 107 | 108 | MenuCache* menu_cache_ref(MenuCache* cache); 109 | void menu_cache_unref(MenuCache* cache); 110 | 111 | gboolean menu_cache_reload( MenuCache* cache ); 112 | 113 | #ifndef G_DISABLE_DEPRECATED 114 | MenuCacheDir* menu_cache_get_root_dir( MenuCache* cache ); 115 | MenuCacheDir* menu_cache_get_dir_from_path( MenuCache* cache, const char* path ); 116 | #endif 117 | MenuCacheDir* menu_cache_dup_root_dir( MenuCache* cache ); 118 | MenuCacheItem* menu_cache_item_from_path( MenuCache* cache, const char* path ); 119 | 120 | typedef struct _MenuCacheNotifyId* MenuCacheNotifyId; 121 | typedef void (*MenuCacheReloadNotify)(MenuCache* cache, gpointer user_data); 122 | 123 | MenuCacheNotifyId menu_cache_add_reload_notify(MenuCache* cache, 124 | MenuCacheReloadNotify func, 125 | gpointer user_data); 126 | void menu_cache_remove_reload_notify(MenuCache* cache, MenuCacheNotifyId notify_id); 127 | 128 | guint32 menu_cache_get_desktop_env_flag( MenuCache* cache, const char* desktop_env ); 129 | 130 | MenuCacheItem* menu_cache_item_ref(MenuCacheItem* item); 131 | gboolean menu_cache_item_unref(MenuCacheItem* item); 132 | 133 | MenuCacheType menu_cache_item_get_type( MenuCacheItem* item ); 134 | const char* menu_cache_item_get_id( MenuCacheItem* item ); 135 | const char* menu_cache_item_get_name( MenuCacheItem* item ); 136 | const char* menu_cache_item_get_comment( MenuCacheItem* item ); 137 | const char* menu_cache_item_get_icon( MenuCacheItem* item ); 138 | 139 | const char* menu_cache_item_get_file_basename( MenuCacheItem* item ); 140 | const char* menu_cache_item_get_file_dirname( MenuCacheItem* item ); 141 | char* menu_cache_item_get_file_path( MenuCacheItem* item ); 142 | 143 | #ifndef G_DISABLE_DEPRECATED 144 | MenuCacheDir* menu_cache_item_get_parent( MenuCacheItem* item ); 145 | GSList* menu_cache_dir_get_children( MenuCacheDir* dir ); 146 | #endif 147 | MenuCacheDir* menu_cache_item_dup_parent( MenuCacheItem* item ); 148 | GSList* menu_cache_dir_list_children( MenuCacheDir* dir ); 149 | MenuCacheItem *menu_cache_find_child_by_id(MenuCacheDir *dir, const char *id); 150 | MenuCacheItem *menu_cache_find_child_by_name(MenuCacheDir *dir, const char *name); 151 | 152 | char* menu_cache_dir_make_path( MenuCacheDir* dir ); 153 | 154 | const char* menu_cache_app_get_generic_name( MenuCacheApp* app ); 155 | const char* menu_cache_app_get_exec( MenuCacheApp* app ); 156 | const char* menu_cache_app_get_working_dir( MenuCacheApp* app ); 157 | const char* const *menu_cache_app_get_categories(MenuCacheApp* app); 158 | 159 | guint32 menu_cache_app_get_show_flags( MenuCacheApp* app ); 160 | gboolean menu_cache_app_get_is_visible( MenuCacheApp* app, guint32 de_flags ); 161 | gboolean menu_cache_dir_is_visible(MenuCacheDir *dir); 162 | 163 | gboolean menu_cache_app_get_use_terminal( MenuCacheApp* app ); 164 | gboolean menu_cache_app_get_use_sn( MenuCacheApp* app ); 165 | 166 | GSList* menu_cache_list_all_apps(MenuCache* cache); 167 | GSList *menu_cache_list_all_for_category(MenuCache* cache, const char *category); 168 | GSList *menu_cache_list_all_for_keyword(MenuCache* cache, const char *keyword); 169 | 170 | MenuCacheItem *menu_cache_find_item_by_id(MenuCache *cache, const char *id); 171 | /* 172 | MenuCacheApp* menu_cache_find_app_by_exec( const char* exec ); 173 | */ 174 | G_END_DECLS 175 | 176 | #endif 177 | -------------------------------------------------------------------------------- /libmenu-cache/test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * test.c 3 | * 4 | * Copyright 2008 PCMan 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301, USA. 20 | */ 21 | 22 | #include 23 | #include "menu-cache.h" 24 | 25 | static void on_menu_item( GtkMenuItem* mi, MenuCacheItem* item ) 26 | { 27 | g_debug( "Exec = %s", menu_cache_app_get_exec(MENU_CACHE_APP(item)) ); 28 | g_debug( "Terminal = %d", menu_cache_app_get_use_terminal(MENU_CACHE_APP(item)) ); 29 | } 30 | 31 | static GtkWidget* create_item( MenuCacheItem* item ) 32 | { 33 | GtkWidget* mi; 34 | if( menu_cache_item_get_type(item) == MENU_CACHE_TYPE_SEP ) 35 | mi = gtk_separator_menu_item_new(); 36 | else 37 | { 38 | GtkWidget* img; 39 | mi = gtk_image_menu_item_new_with_label( menu_cache_item_get_name(item) ); 40 | gtk_widget_set_tooltip_text( mi, menu_cache_item_get_comment(item) ); 41 | /* 42 | if( g_file_test( menu_cache_item_get_icon(item), G_FILE_TEST_IS_REGULAR ) ) 43 | { 44 | img = gtk_image_new_from_file(menu_cache_item_get_icon(item)); 45 | } 46 | else 47 | */ 48 | img = gtk_image_new_from_icon_name(menu_cache_item_get_icon(item), GTK_ICON_SIZE_MENU ); 49 | gtk_image_menu_item_set_image( mi, img ); 50 | if( menu_cache_item_get_type(item) == MENU_CACHE_TYPE_APP ) 51 | { 52 | g_object_set_data_full( mi, "mcitem", menu_cache_item_ref(item), menu_cache_item_unref ); 53 | g_signal_connect( mi, "activate", on_menu_item, item ); 54 | } 55 | } 56 | gtk_widget_show( mi ); 57 | return mi; 58 | } 59 | 60 | static GtkWidget* create_menu(MenuCacheDir* dir, GtkWidget* parent) 61 | { 62 | GSList* l; 63 | GtkWidget* menu = gtk_menu_new(); 64 | GtkWidget* mi; 65 | 66 | if( parent ) 67 | { 68 | mi = create_item( dir ); 69 | gtk_menu_item_set_submenu( mi, menu ); 70 | gtk_menu_append( parent, mi ); 71 | gtk_widget_show( mi ); 72 | // printf( "comment:%s\n", menu_cache_item_get_comment(dir) ); 73 | } 74 | 75 | for( l = menu_cache_dir_get_children(dir); l; l = l->next ) 76 | { 77 | MenuCacheItem* item = MENU_CACHE_ITEM(l->data); 78 | 79 | if( menu_cache_item_get_type(item) == MENU_CACHE_TYPE_DIR ) 80 | create_menu( item, menu ); 81 | else 82 | { 83 | mi = create_item(item); 84 | gtk_widget_show( mi ); 85 | // printf( "comment:%s\n", menu_cache_item_get_comment(item) ); 86 | // printf( "icon:%s\n", menu_cache_item_get_icon(item) ); 87 | // printf( "exec:%s\n", menu_cache_app_get_exec(item) ); 88 | gtk_menu_append( menu, mi ); 89 | } 90 | } 91 | return menu; 92 | } 93 | 94 | static void on_clicked( GtkButton* btn, GtkWidget* pop ) 95 | { 96 | gtk_menu_popup( pop, NULL, NULL, NULL, NULL, 0, GDK_CURRENT_TIME ); 97 | } 98 | 99 | int main(int argc, char** argv) 100 | { 101 | GtkWidget* win, *btn; 102 | gtk_init( &argc, &argv ); 103 | 104 | MenuCache* menu_cache = menu_cache_new( argv[1] ? argv[1] : "/etc/xdg/menus/applications.menu", NULL, NULL ); 105 | MenuCacheDir* menu = menu_cache_get_root_dir( menu_cache ); 106 | GtkWidget* pop = create_menu( menu, NULL ); 107 | 108 | // g_debug( "update: %d", menu_cache_is_updated( menu_cache ) ); 109 | g_debug( "update: %d", menu_cache_file_is_updated( argv[1] ) ); 110 | 111 | win = gtk_window_new( GTK_WINDOW_TOPLEVEL ); 112 | gtk_window_set_title( win, "MenuCache Test" ); 113 | btn = gtk_button_new_with_label( menu_cache_item_get_name(menu) ); 114 | gtk_widget_set_tooltip_text( btn, menu_cache_item_get_comment(menu) ); 115 | gtk_container_add( win, btn ); 116 | g_signal_connect( btn, "clicked", G_CALLBACK(on_clicked), pop ); 117 | g_signal_connect( win, "delete-event", G_CALLBACK(gtk_main_quit), NULL ); 118 | 119 | gtk_widget_show_all( win ); 120 | 121 | menu_cache_unref( menu ); 122 | 123 | gtk_main(); 124 | return 0; 125 | } 126 | -------------------------------------------------------------------------------- /libmenu-cache/version.h: -------------------------------------------------------------------------------- 1 | #ifndef __MENU_CACHE_VER_H__ 2 | #define __MENU_CACHE_VER_H__ 3 | 4 | /* change these numbers if you change enums in menu-cache.h 5 | note that will break compatibility for generated cache */ 6 | #define VER_MAJOR 1 7 | #define VER_MINOR 2 8 | /* This is for backward compatibility on transitions */ 9 | #define VER_MINOR_SUPPORTED 1 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /menu-cache-daemon/Makefile.am: -------------------------------------------------------------------------------- 1 | NULL = 2 | 3 | AM_CPPFLAGS = \ 4 | -I$(top_srcdir)/libmenu-cache \ 5 | -I$(top_builddir)/libmenu-cache \ 6 | $(GLIB_CFLAGS) \ 7 | $(DEBUG_CFLAGS) \ 8 | $(ADDITIONAL_FLAGS) \ 9 | -DMENUCACHE_LIBEXECDIR="\"$(pkglibexecdir)\"" \ 10 | -Werror-implicit-function-declaration \ 11 | $(NULL) 12 | 13 | pkglibexec_PROGRAMS = menu-cached 14 | 15 | menu_cached_SOURCES = \ 16 | menu-cached.c \ 17 | $(NULL) 18 | 19 | menu_cached_LDADD = \ 20 | $(GLIB_LIBS) \ 21 | $(NULL) 22 | menu_cached_LDFLAGS = \ 23 | -no-undefined \ 24 | $(NULL) 25 | -------------------------------------------------------------------------------- /menu-cache-daemon/menu-cached.c: -------------------------------------------------------------------------------- 1 | /* 2 | * menu-cached.c 3 | * 4 | * Copyright 2008 - 2010 PCMan 5 | * Copyright 2009 Jürgen Hötzel 6 | * Copyright 2012-2017 Andriy Grytsenko (LStranger) 7 | * Copyright 2016 Mamoru TASAKA 8 | * 9 | * This file is a part of libmenu-cache package and created program 10 | * should be not used without the library. 11 | * 12 | * This library is free software; you can redistribute it and/or 13 | * modify it under the terms of the GNU Lesser General Public 14 | * License as published by the Free Software Foundation; either 15 | * version 2.1 of the License, or (at your option) any later version. 16 | * 17 | * This library is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | * Lesser General Public License for more details. 21 | * 22 | * You should have received a copy of the GNU Lesser General Public 23 | * License along with this library; if not, write to the Free Software 24 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 25 | */ 26 | 27 | #ifdef HAVE_CONFIG_H 28 | #include 29 | #endif 30 | 31 | #include "menu-cache.h" 32 | #include "version.h" 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | #ifdef G_ENABLE_DEBUG 52 | #define DEBUG(...) g_debug(__VA_ARGS__) 53 | #else 54 | #define DEBUG(...) 55 | #endif 56 | 57 | typedef struct _Cache 58 | { 59 | char md5[33]; /* cache id */ 60 | /* environment */ 61 | char* menu_name; 62 | char* lang_name; 63 | char** env; /* XDG- env variables */ 64 | 65 | /* files involved, and their monitors */ 66 | int n_files; 67 | char** files; 68 | GFileMonitor** mons; 69 | /* GFileMonitor* cache_mon; */ 70 | 71 | gboolean need_reload; 72 | guint delayed_reload_handler; 73 | guint delayed_free_handler; 74 | 75 | /* clients requesting this menu cache */ 76 | GSList* clients; 77 | 78 | /* cache file name for reference */ 79 | char *cache_file; 80 | }Cache; 81 | 82 | typedef struct ClientIO_ { 83 | guint source_id; 84 | GIOChannel *channel; 85 | } ClientIO; 86 | 87 | static GMainLoop* main_loop = NULL; 88 | 89 | static GHashTable* hash = NULL; 90 | 91 | static char *socket_file = NULL; 92 | 93 | static void on_file_changed( GFileMonitor* mon, GFile* gf, GFile* other, 94 | GFileMonitorEvent evt, Cache* cache ); 95 | 96 | static void on_client_closed(gpointer user_data); 97 | 98 | static gboolean delayed_cache_free(gpointer data) 99 | { 100 | Cache* cache = data; 101 | int i; 102 | 103 | if(g_source_is_destroyed(g_main_current_source())) 104 | return FALSE; 105 | 106 | g_hash_table_remove( hash, cache->md5 ); 107 | /* DEBUG("menu cache freed"); */ 108 | for(i = 0; i < cache->n_files; ++i) 109 | { 110 | g_file_monitor_cancel( cache->mons[i] ); 111 | g_object_unref( cache->mons[i] ); 112 | } 113 | /* 114 | g_file_monitor_cancel(cache->cache_mon); 115 | g_object_unref(cache->cache_mon); 116 | */ 117 | g_free( cache->mons ); 118 | g_free(cache->menu_name); 119 | g_free(cache->lang_name); 120 | g_free(cache->cache_file); 121 | g_strfreev( cache->env ); 122 | g_strfreev( cache->files ); 123 | 124 | if( cache->delayed_reload_handler ) 125 | g_source_remove( cache->delayed_reload_handler ); 126 | 127 | g_slice_free( Cache, cache ); 128 | 129 | if(g_hash_table_size(hash) == 0) 130 | g_main_loop_quit(main_loop); 131 | return FALSE; 132 | } 133 | 134 | static void cache_free(Cache* cache) 135 | { 136 | /* shutdown cache in 6 seconds of inactivity */ 137 | if(!cache->delayed_free_handler) 138 | cache->delayed_free_handler = g_timeout_add_seconds(6, 139 | delayed_cache_free, 140 | cache); 141 | DEBUG("menu %p cache unused, removing in 6s", cache); 142 | } 143 | 144 | static gboolean read_all_used_files( FILE* f, int* n_files, char*** used_files ) 145 | { 146 | char line[ 4096 ]; 147 | int i, n, x; 148 | char** files; 149 | int ver_maj, ver_min; 150 | 151 | /* the first line of the cache file is version number */ 152 | if( !fgets(line, G_N_ELEMENTS(line),f) ) 153 | return FALSE; 154 | if( sscanf(line, "%d.%d", &ver_maj, &ver_min)< 2 ) 155 | return FALSE; 156 | if (ver_maj != VER_MAJOR || 157 | ver_min > VER_MINOR || ver_min < VER_MINOR_SUPPORTED) 158 | return FALSE; 159 | 160 | /* skip the second line containing menu name */ 161 | if( ! fgets( line, G_N_ELEMENTS(line), f ) ) 162 | return FALSE; 163 | 164 | /* num of files used */ 165 | if( ! fgets( line, G_N_ELEMENTS(line), f ) ) 166 | return FALSE; 167 | 168 | n = atoi( line ); 169 | files = g_new0( char*, n + 1 ); 170 | 171 | for( i = 0, x = 0; i < n; ++i ) 172 | { 173 | int len; 174 | 175 | if( ! fgets( line, G_N_ELEMENTS(line), f ) ) 176 | return FALSE; 177 | 178 | len = strlen( line ); 179 | if( len <= 1 ) 180 | return FALSE; 181 | files[ x ] = g_strndup( line, len - 1 ); /* don't include \n */ 182 | if (g_file_test(files[x]+1, G_FILE_TEST_EXISTS)) 183 | x++; 184 | else 185 | { 186 | DEBUG("ignoring not existent file from menu-cache-gen: %s", files[x]); 187 | g_free(files[x]); 188 | files[x] = NULL; 189 | } 190 | } 191 | *n_files = x; 192 | *used_files = files; 193 | return TRUE; 194 | } 195 | 196 | static void set_env( char* penv, const char* name ) 197 | { 198 | if( penv && *penv ) 199 | g_setenv( name, penv, TRUE); 200 | else 201 | g_unsetenv( name ); 202 | } 203 | 204 | static void pre_exec( gpointer user_data ) 205 | { 206 | char** env = (char**)user_data; 207 | set_env(*env, "XDG_CACHE_HOME"); 208 | set_env(*++env, "XDG_CONFIG_DIRS"); 209 | set_env(*++env, "XDG_MENU_PREFIX"); 210 | set_env(*++env, "XDG_DATA_DIRS"); 211 | set_env(*++env, "XDG_CONFIG_HOME"); 212 | set_env(*++env, "XDG_DATA_HOME"); 213 | set_env(*++env, "CACHE_GEN_VERSION"); 214 | } 215 | 216 | static gboolean regenerate_cache( const char* menu_name, 217 | const char* lang_name, 218 | const char* cache_file, 219 | char** env, 220 | int* n_used_files, 221 | char*** used_files ) 222 | { 223 | FILE* f; 224 | int n_files, status = 0; 225 | char** files; 226 | const char *user_data_dir = env[5]; 227 | const char* argv[] = { 228 | MENUCACHE_LIBEXECDIR "/menu-cache-gen", 229 | "-l", NULL, 230 | "-i", NULL, 231 | "-o", NULL, 232 | NULL}; 233 | argv[2] = lang_name; 234 | argv[4] = menu_name; 235 | argv[6] = cache_file; 236 | 237 | /* DEBUG("cmd: %s", g_strjoinv(" ", argv)); */ 238 | 239 | /* create $XDG_DATA_HOME/applications if it does not exist yet */ 240 | if (!user_data_dir || !user_data_dir[0]) 241 | user_data_dir = g_get_user_data_dir(); 242 | if (g_file_test(user_data_dir, G_FILE_TEST_IS_DIR) || 243 | g_mkdir(user_data_dir, 0700) == 0) 244 | { 245 | char *local_app_path = g_build_filename(user_data_dir, "applications", NULL); 246 | if (!g_file_test(local_app_path, G_FILE_TEST_IS_DIR)) 247 | g_mkdir(local_app_path, 0700); 248 | g_free(local_app_path); 249 | } 250 | 251 | /* run menu-cache-gen */ 252 | /* FIXME: is cast to (char**) valid here? */ 253 | if( !g_spawn_sync(NULL, (char **)argv, NULL, 0, 254 | pre_exec, env, NULL, NULL, &status, NULL )) 255 | { 256 | DEBUG("error executing menu-cache-gen"); 257 | } 258 | /* DEBUG("exit status of menu-cache-gen: %d", status); */ 259 | if( status != 0 ) 260 | return FALSE; 261 | 262 | f = fopen( cache_file, "r" ); 263 | if( f ) 264 | { 265 | if( !read_all_used_files( f, &n_files, &files ) ) 266 | { 267 | n_files = 0; 268 | files = NULL; 269 | /* DEBUG("error: read_all_used_files"); */ 270 | } 271 | fclose(f); 272 | } 273 | else 274 | return FALSE; 275 | *n_used_files = n_files; 276 | *used_files = files; 277 | return TRUE; 278 | } 279 | 280 | static void do_reload(Cache* cache) 281 | { 282 | GSList* l; 283 | char buf[38]; 284 | int i; 285 | GFile* gf; 286 | 287 | int new_n_files; 288 | char **new_files = NULL; 289 | 290 | /* DEBUG("Re-generation of cache is needed!"); */ 291 | /* DEBUG("call menu-cache-gen to re-generate the cache"); */ 292 | memcpy( buf, "REL:", 4 ); 293 | memcpy( buf + 4, cache->md5, 32 ); 294 | buf[36] = '\n'; 295 | buf[37] = '\0'; 296 | 297 | if( ! regenerate_cache( cache->menu_name, cache->lang_name, cache->cache_file, 298 | cache->env, &new_n_files, &new_files ) ) 299 | { 300 | DEBUG("regeneration of cache failed."); 301 | return; 302 | } 303 | 304 | /* cancel old file monitors */ 305 | g_strfreev(cache->files); 306 | for( i = 0; i < cache->n_files; ++i ) 307 | { 308 | g_file_monitor_cancel( cache->mons[i] ); 309 | g_signal_handlers_disconnect_by_func( cache->mons[i], on_file_changed, cache ); 310 | g_object_unref( cache->mons[i] ); 311 | } 312 | /* 313 | g_file_monitor_cancel(cache->cache_mon); 314 | g_object_unref(cache->cache_mon); 315 | */ 316 | 317 | cache->n_files = new_n_files; 318 | cache->files = new_files; 319 | 320 | cache->mons = g_realloc( cache->mons, sizeof(GFileMonitor*)*(cache->n_files+1) ); 321 | /* create required file monitors */ 322 | for( i = 0; i < cache->n_files; ++i ) 323 | { 324 | gf = g_file_new_for_path( cache->files[i] + 1 ); 325 | if( cache->files[i][0] == 'D' ) 326 | cache->mons[i] = g_file_monitor_directory( gf, 0, NULL, NULL ); 327 | else 328 | cache->mons[i] = g_file_monitor_file( gf, 0, NULL, NULL ); 329 | g_signal_connect(cache->mons[i], "changed", 330 | G_CALLBACK(on_file_changed), cache); 331 | g_object_unref(gf); 332 | } 333 | /* 334 | gf = g_file_new_for_path( cache_file ); 335 | cache->cache_mon = g_file_monitor_file( gf, 0, NULL, NULL ); 336 | g_signal_connect( cache->cache_mon, "changed", on_file_changed, cache); 337 | g_object_unref(gf); 338 | */ 339 | 340 | /* notify the clients that reload is needed. */ 341 | for( l = cache->clients; l; ) 342 | { 343 | ClientIO *channel_io = (ClientIO *)l->data; 344 | GIOChannel* ch = channel_io->channel; 345 | l = l->next; /* do it beforehand, as client may be removed below */ 346 | if(write(g_io_channel_unix_get_fd(ch), buf, 37) < 37) 347 | { 348 | on_client_closed(channel_io); 349 | } 350 | } 351 | cache->need_reload = FALSE; 352 | } 353 | 354 | static gboolean delayed_reload( Cache* cache ) 355 | { 356 | if(g_source_is_destroyed(g_main_current_source())) 357 | return FALSE; 358 | 359 | if(cache->need_reload) 360 | do_reload(cache); 361 | 362 | if (cache->need_reload) 363 | return TRUE; 364 | 365 | cache->delayed_reload_handler = 0; 366 | return FALSE; 367 | } 368 | 369 | void on_file_changed( GFileMonitor* mon, GFile* gf, GFile* other, 370 | GFileMonitorEvent evt, Cache* cache ) 371 | { 372 | #ifdef G_ENABLE_DEBUG 373 | char *path = g_file_get_path(gf); 374 | g_debug("file %s is changed (%d).", path, evt); 375 | g_free(path); 376 | #endif 377 | /* if( mon != cache->cache_mon ) */ 378 | { 379 | /* Optimization: Some files in the dir are changed, but it 380 | * won't affect the content of the menu. So, just omit them, 381 | * and update the mtime of the cached file with utime. 382 | */ 383 | int idx; 384 | /* dirty hack: get index of the monitor in array */ 385 | for(idx = 0; idx < cache->n_files; ++idx) 386 | { 387 | if(mon == cache->mons[idx]) 388 | break; 389 | } 390 | /* if the monitored file is a directory */ 391 | if( G_LIKELY(idx < cache->n_files) && cache->files[idx][0] == 'D' ) 392 | { 393 | char* changed_file = g_file_get_path(gf); 394 | /* Regenerate the cache if the changed file is a directory. 395 | * 396 | * The file monitor isn't recursive, so imagine we add a 397 | * subdirectory to /usr/share/applications, and subsequently 398 | * add a desktop entry to that. If we ignore the new 399 | * subdirectory, we won't notice when the desktop entry is 400 | * added. By regenerating the cache, the subdirectory will 401 | * be mentioned there, picked up by read_all_used_files(), 402 | * and monitored for subsequent changes. 403 | */ 404 | if (!g_file_test(changed_file, G_FILE_TEST_IS_DIR)) 405 | { 406 | char* dir_path = cache->files[idx]+1; 407 | int len = strlen(dir_path); 408 | /* if the changed file is a file in the monitored dir */ 409 | if( strncmp(changed_file, dir_path, len) == 0 && changed_file[len] == '/' ) 410 | { 411 | char* base_name = changed_file + len + 1; 412 | gboolean skip = TRUE; 413 | /* only *.desktop and *.directory files can affect the content of the menu. */ 414 | if( g_str_has_suffix(base_name, ".desktop") ) 415 | { 416 | skip = FALSE; 417 | } 418 | else if( g_str_has_suffix(base_name, ".directory") ) 419 | skip = FALSE; 420 | 421 | if( skip ) 422 | { 423 | DEBUG("files are changed, but no re-generation is needed."); 424 | g_free(changed_file); 425 | return; 426 | } 427 | } 428 | } 429 | g_free(changed_file); 430 | } 431 | } 432 | 433 | if( cache->delayed_reload_handler ) 434 | { 435 | /* we got some change in last 3 seconds... not reload again */ 436 | cache->need_reload = TRUE; 437 | g_source_remove(cache->delayed_reload_handler); 438 | } 439 | else 440 | { 441 | /* no reload in last 3 seconds... good, do it immediately */ 442 | /* mark need_reload anyway. If do_reload succeeds, it is cleaned up */ 443 | cache->need_reload = TRUE; 444 | do_reload(cache); 445 | } 446 | 447 | cache->delayed_reload_handler = g_timeout_add_seconds_full( G_PRIORITY_LOW, 3, (GSourceFunc)delayed_reload, cache, NULL ); 448 | } 449 | 450 | static gboolean cache_file_is_updated( const char* cache_file, int* n_used_files, char*** used_files ) 451 | { 452 | gboolean ret = FALSE; 453 | struct stat st; 454 | #if 0 455 | time_t cache_mtime; 456 | char** files; 457 | int n, i; 458 | #endif 459 | FILE* f; 460 | 461 | f = fopen( cache_file, "r" ); 462 | if( f ) 463 | { 464 | if( fstat( fileno(f), &st) == 0 ) 465 | { 466 | #if 0 467 | cache_mtime = st.st_mtime; 468 | if( read_all_used_files(f, &n, &files) ) 469 | { 470 | for( i =0; i < n; ++i ) 471 | { 472 | /* files[i][0] is 'D' or 'F' indicating file type. */ 473 | if( stat( files[i] + 1, &st ) == -1 ) 474 | continue; 475 | if( st.st_mtime > cache_mtime ) 476 | break; 477 | } 478 | if( i >= n ) 479 | { 480 | ret = TRUE; 481 | *n_used_files = n; 482 | *used_files = files; 483 | } 484 | } 485 | #else 486 | ret = read_all_used_files(f, n_used_files, used_files); 487 | #endif 488 | } 489 | fclose( f ); 490 | } 491 | return ret; 492 | } 493 | 494 | static void get_socket_name( char* buf, int len ) 495 | { 496 | char* dpy = g_strdup(g_getenv("DISPLAY")); 497 | if(dpy && *dpy) 498 | { 499 | char* p = strchr(dpy, ':'); 500 | for(++p; *p && *p != '.' && *p != '\n';) 501 | ++p; 502 | if(*p) 503 | *p = '\0'; 504 | } 505 | /* NOTE: this socket name is incompatible with versions > 1.0.2, 506 | although this function is never used since 0.7.0 but 507 | libmenu-cache always requests exact socket name instead */ 508 | g_snprintf( buf, len, "%s/.menu-cached-%s-%s", g_get_tmp_dir(), 509 | dpy ? dpy : ":0", g_get_user_name() ); 510 | g_free(dpy); 511 | } 512 | 513 | static gboolean socket_is_alive(struct sockaddr_un *addr) 514 | { 515 | int fd = socket(PF_UNIX, SOCK_STREAM, 0); 516 | socklen_t len = sizeof(sa_family_t) + strlen(addr->sun_path) + 1; 517 | 518 | if (fd < 0) 519 | { 520 | DEBUG("Failed to create socket"); 521 | /* still return TRUE to drop attempt */ 522 | return TRUE; 523 | } 524 | if (connect(fd, (struct sockaddr*)addr, len) >= 0) 525 | { 526 | /* there is a listener on the socket */ 527 | close(fd); 528 | DEBUG("Another menu-cached seems to reside on the socket"); 529 | return TRUE; 530 | } 531 | close(fd); 532 | 533 | /* remove previous socket file */ 534 | if (unlink(addr->sun_path) < 0) { 535 | if (errno != ENOENT) 536 | g_error("Couldn't remove previous socket file %s", addr->sun_path); 537 | } 538 | /* remove of previous socket file successful */ 539 | else 540 | g_warning("removed previous socket file %s", addr->sun_path); 541 | return FALSE; 542 | } 543 | 544 | static int create_socket(struct sockaddr_un *addr) 545 | { 546 | int fd = -1; 547 | char *lockfile; 548 | 549 | lockfile = g_strconcat(addr->sun_path, ".lock", NULL); 550 | fd = open(lockfile, O_CREAT | O_EXCL | O_WRONLY, 0700); 551 | if (fd < 0) 552 | { 553 | DEBUG("Cannot create lock file %s: %s", lockfile, strerror(errno)); 554 | g_free(lockfile); 555 | return -1; 556 | } 557 | close(fd); 558 | 559 | fd = socket(PF_UNIX, SOCK_STREAM, 0); 560 | if (fd < 0) 561 | { 562 | DEBUG("Failed to create socket"); 563 | } 564 | 565 | /* remove previous socket file */ 566 | else if (g_file_test(addr->sun_path, G_FILE_TEST_EXISTS) && 567 | socket_is_alive(addr)) 568 | { 569 | close(fd); 570 | fd = -1; 571 | } 572 | 573 | else if(bind(fd, (struct sockaddr *)addr, sizeof(*addr)) < 0) 574 | { 575 | DEBUG("Failed to bind to socket"); 576 | close(fd); 577 | fd = -1; 578 | } 579 | 580 | else if(listen(fd, 30) < 0) 581 | { 582 | DEBUG( "Failed to listen to socket" ); 583 | close(fd); 584 | fd = -1; 585 | } 586 | 587 | else 588 | fcntl(fd, F_SETFD, FD_CLOEXEC); 589 | 590 | unlink(lockfile); 591 | g_free(lockfile); 592 | return fd; 593 | } 594 | 595 | static void on_client_closed(gpointer user_data) 596 | { 597 | ClientIO* client_io = user_data; 598 | GHashTableIter it; 599 | char* md5; 600 | Cache* cache; 601 | GSList *l; 602 | GIOChannel *ch = client_io->channel; 603 | 604 | DEBUG("client closed: %p", ch); 605 | g_hash_table_iter_init (&it, hash); 606 | while( g_hash_table_iter_next (&it, (gpointer*)&md5, (gpointer*)&cache) ) 607 | { 608 | while((l = g_slist_find( cache->clients, client_io )) != NULL) 609 | { 610 | /* FIXME: some clients are closed accidentally without 611 | * unregister the menu first due to crashes. 612 | * We need to do unref for them to prevent memory leaks. 613 | * 614 | * The behavior is not currently well-defined. 615 | * if a client do unregister first, and then was closed, 616 | * unref will be called twice and incorrect ref. counting 617 | * will happen. 618 | */ 619 | cache->clients = g_slist_delete_link( cache->clients, l ); 620 | DEBUG("remove channel from cache %p", cache); 621 | if(cache->clients == NULL) 622 | cache_free(cache); 623 | } 624 | } 625 | /* DEBUG("client closed"); */ 626 | 627 | g_source_remove(client_io->source_id); 628 | g_free(client_io); 629 | } 630 | 631 | static gboolean on_client_data_in(GIOChannel* ch, GIOCondition cond, gpointer user_data) 632 | { 633 | char *line; 634 | gsize len; 635 | GIOStatus st; 636 | const char* md5; 637 | Cache* cache; 638 | GFile* gf; 639 | gboolean ret = TRUE; 640 | 641 | if(cond & (G_IO_HUP|G_IO_ERR) ) 642 | { 643 | on_client_closed(user_data); 644 | return FALSE; /* remove the watch */ 645 | } 646 | 647 | retry: 648 | st = g_io_channel_read_line( ch, &line, &len, NULL, NULL ); 649 | if( st == G_IO_STATUS_AGAIN ) 650 | goto retry; 651 | if( st != G_IO_STATUS_NORMAL ) { 652 | on_client_closed(user_data); 653 | return FALSE; 654 | } 655 | 656 | --len; 657 | line[len] = 0; 658 | 659 | DEBUG("line(%d) = %s", (int)len, line); 660 | 661 | if( memcmp(line, "REG:", 4) == 0 ) 662 | { 663 | int n_files = 0, i; 664 | char *pline = line + 4; 665 | char *sep, *menu_name, *lang_name, *cache_dir; 666 | char **files = NULL; 667 | char **env; 668 | char reload_cmd[38] = "REL:"; 669 | 670 | len -= 4; 671 | /* Format of received string, separated by '\t'. 672 | * Menu Name 673 | * Language Name 674 | * XDG_CACHE_HOME 675 | * XDG_CONFIG_DIRS 676 | * XDG_MENU_PREFIX 677 | * XDG_DATA_DIRS 678 | * XDG_CONFIG_HOME 679 | * XDG_DATA_HOME 680 | * (optional) CACHE_GEN_VERSION 681 | * md5 hash */ 682 | 683 | md5 = pline + len - 32; /* the md5 hash of this menu */ 684 | 685 | cache = (Cache*)g_hash_table_lookup(hash, md5); 686 | if( !cache ) 687 | { 688 | sep = strchr(pline, '\t'); 689 | *sep = '\0'; 690 | menu_name = pline; 691 | pline = sep + 1; 692 | 693 | sep = strchr(pline, '\t'); 694 | *sep = '\0'; 695 | lang_name = pline; 696 | pline = sep + 1; 697 | 698 | ((char *)md5)[-1] = '\0'; 699 | env = g_strsplit(pline, "\t", 0); 700 | 701 | cache_dir = env[0]; /* XDG_CACHE_HOME */ 702 | /* obtain cache dir from client's env */ 703 | 704 | cache = g_slice_new0( Cache ); 705 | cache->cache_file = g_build_filename(*cache_dir ? cache_dir : g_get_user_cache_dir(), "menus", md5, NULL ); 706 | if( ! cache_file_is_updated(cache->cache_file, &n_files, &files) ) 707 | { 708 | /* run menu-cache-gen */ 709 | if(! regenerate_cache( menu_name, lang_name, cache->cache_file, env, &n_files, &files ) ) 710 | { 711 | DEBUG("regeneration of cache failed!!"); 712 | } 713 | } 714 | else 715 | { 716 | /* file loaded, schedule update anyway */ 717 | cache->need_reload = TRUE; 718 | cache->delayed_reload_handler = g_timeout_add_seconds_full(G_PRIORITY_LOW, 3, 719 | (GSourceFunc)delayed_reload, cache, NULL); 720 | } 721 | memcpy( cache->md5, md5, 33 ); 722 | cache->n_files = n_files; 723 | cache->files = files; 724 | cache->menu_name = g_strdup(menu_name); 725 | cache->lang_name = g_strdup(lang_name); 726 | cache->env = env; 727 | cache->mons = g_new0(GFileMonitor*, n_files+1); 728 | /* create required file monitors */ 729 | DEBUG("%d files/dirs are monitored.", n_files); 730 | for( i = 0; i < n_files; ++i ) 731 | { 732 | gf = g_file_new_for_path( files[i] + 1 ); 733 | if( files[i][0] == 'D' ) 734 | cache->mons[i] = g_file_monitor_directory( gf, 0, NULL, NULL ); 735 | else 736 | cache->mons[i] = g_file_monitor_file( gf, 0, NULL, NULL ); 737 | DEBUG("monitor: %s", g_file_get_path(gf)); 738 | g_signal_connect(cache->mons[i], "changed", 739 | G_CALLBACK(on_file_changed), cache); 740 | g_object_unref(gf); 741 | } 742 | /* 743 | gf = g_file_new_for_path( cache_file ); 744 | cache->cache_mon = g_file_monitor_file( gf, 0, NULL, NULL ); 745 | g_signal_connect( cache->cache_mon, "changed", on_file_changed, cache); 746 | g_object_unref(gf); 747 | */ 748 | g_hash_table_insert(hash, cache->md5, cache); 749 | DEBUG("new menu cache %p added to hash", cache); 750 | } 751 | else if (access(cache->cache_file, R_OK) != 0) 752 | { 753 | /* bug SF#657: if user deleted cache file we have to regenerate it */ 754 | if (!regenerate_cache(cache->menu_name, cache->lang_name, cache->cache_file, 755 | cache->env, &cache->n_files, &cache->files)) 756 | { 757 | DEBUG("regeneration of cache failed."); 758 | } 759 | } 760 | /* DEBUG("menu %s requested by client %d", md5, g_io_channel_unix_get_fd(ch)); */ 761 | cache->clients = g_slist_prepend(cache->clients, user_data); 762 | if(cache->delayed_free_handler) 763 | { 764 | g_source_remove(cache->delayed_free_handler); 765 | cache->delayed_free_handler = 0; 766 | } 767 | DEBUG("client %p added to cache %p", ch, cache); 768 | 769 | /* generate a fake reload notification */ 770 | DEBUG("fake reload!"); 771 | memcpy(reload_cmd + 4, md5, 32); 772 | 773 | reload_cmd[36] = '\n'; 774 | reload_cmd[37] = '\0'; 775 | 776 | DEBUG("reload command: %s", reload_cmd); 777 | ret = write(g_io_channel_unix_get_fd(ch), reload_cmd, 37) > 0; 778 | } 779 | else if( memcmp(line, "UNR:", 4) == 0 ) 780 | { 781 | md5 = line + 4; 782 | DEBUG("unregister: %s", md5); 783 | cache = (Cache*)g_hash_table_lookup(hash, md5); 784 | if(cache && cache->clients) 785 | { 786 | /* remove the IO channel from the cache */ 787 | cache->clients = g_slist_remove(cache->clients, user_data); 788 | if(cache->clients == NULL) 789 | cache_free(cache); 790 | } 791 | else 792 | DEBUG("bug! client is not found."); 793 | } 794 | g_free( line ); 795 | 796 | return ret; 797 | } 798 | 799 | static void terminate(int sig) 800 | { 801 | /* #ifndef HAVE_ABSTRACT_SOCKETS */ 802 | unlink(socket_file); 803 | exit(0); 804 | /* #endif */ 805 | } 806 | 807 | static gboolean on_new_conn_incoming(GIOChannel* ch, GIOCondition cond, gpointer user_data) 808 | { 809 | int server, client; 810 | socklen_t client_addrlen; 811 | struct sockaddr client_addr; 812 | GIOChannel* child; 813 | ClientIO* client_io; 814 | 815 | server = g_io_channel_unix_get_fd(ch); 816 | 817 | client_addrlen = sizeof(client_addr); 818 | client = accept(server, &client_addr, &client_addrlen); 819 | if( client == -1 ) 820 | { 821 | DEBUG("accept error"); 822 | if (errno == ECONNABORTED) 823 | /* client failed, just continue */ 824 | return TRUE; 825 | /* else it's socket error, terminate server */ 826 | terminate(SIGTERM); 827 | return FALSE; 828 | } 829 | 830 | fcntl (client, F_SETFD, FD_CLOEXEC); 831 | 832 | child = g_io_channel_unix_new(client); 833 | g_io_channel_set_close_on_unref( child, TRUE ); 834 | 835 | client_io = g_new0 (ClientIO, 1); 836 | client_io->channel = child; 837 | client_io->source_id = 838 | g_io_add_watch(child, G_IO_PRI|G_IO_IN|G_IO_HUP|G_IO_ERR, 839 | on_client_data_in, client_io); 840 | g_io_channel_unref(child); 841 | 842 | DEBUG("new client accepted: %p", child); 843 | return TRUE; 844 | } 845 | 846 | static gboolean on_server_conn_close(GIOChannel* ch, GIOCondition cond, gpointer user_data) 847 | { 848 | /* FIXME: is this possible? */ 849 | /* the server socket is accidentally closed. terminate the server. */ 850 | terminate(SIGTERM); 851 | return TRUE; 852 | } 853 | 854 | int main(int argc, char** argv) 855 | { 856 | GIOChannel* ch; 857 | int server_fd; 858 | struct sockaddr_un addr; 859 | #ifndef DISABLE_DAEMONIZE 860 | pid_t pid; 861 | int status; 862 | 863 | long open_max; 864 | long i; 865 | #endif 866 | 867 | #ifndef DISABLE_DAEMONIZE 868 | /* Become a daemon */ 869 | if ((pid = fork()) < 0) { 870 | g_error("can't fork"); 871 | return 2; 872 | } 873 | else if (pid != 0) { 874 | /* wait for result of child */ 875 | while ((i = waitpid(pid, &status, 0)) < 0 && errno == EINTR); 876 | if (i < 0 || !WIFEXITED(status)) /* system error or child crashed */ 877 | return 127; 878 | /* exit parent */ 879 | return WEXITSTATUS(status); 880 | } 881 | 882 | /* reset session to forget about parent process completely */ 883 | setsid(); 884 | 885 | /* change working directory to root, so previous working directory 886 | * can be unmounted */ 887 | if (chdir("/") < 0) { 888 | g_error("can't change directory to /"); 889 | } 890 | 891 | /* don't hold open fd opened besides server socket and std{in,out,err} */ 892 | open_max = sysconf (_SC_OPEN_MAX); 893 | for (i = 3; i < open_max; i++) 894 | close (i); 895 | 896 | /* /dev/null for stdin, stdout, stderr */ 897 | if (freopen("/dev/null", "r", stdin)) i = i; 898 | if (freopen("/dev/null", "w", stdout)) i = i; 899 | if (freopen("/dev/null", "w", stderr)) i = i; 900 | #endif 901 | 902 | memset(&addr, 0, sizeof(addr)); 903 | addr.sun_family = AF_UNIX; 904 | if (argc < 2) 905 | /* old way, not recommended! */ 906 | get_socket_name( addr.sun_path, sizeof( addr.sun_path ) ); 907 | else if (strlen(argv[1]) >= sizeof(addr.sun_path)) 908 | /* ouch, it's too big! */ 909 | return 1; 910 | else 911 | /* this is a good way! */ 912 | strncpy(addr.sun_path, argv[1], sizeof(addr.sun_path) - 1); 913 | 914 | socket_file = addr.sun_path; 915 | 916 | server_fd = create_socket(&addr); 917 | 918 | if( server_fd < 0 ) 919 | return 1; 920 | 921 | #ifndef DISABLE_DAEMONIZE 922 | /* Second fork to let parent get a result */ 923 | if ((pid = fork()) < 0) { 924 | g_error("can't fork"); 925 | close(server_fd); 926 | unlink(socket_file); 927 | return 2; 928 | } else if (pid != 0) { 929 | /* exit child */ 930 | return 0; 931 | } 932 | /* We are in grandchild now, daemonized */ 933 | #endif 934 | 935 | signal(SIGHUP, terminate); 936 | signal(SIGINT, terminate); 937 | signal(SIGQUIT, terminate); 938 | signal(SIGTERM, terminate); 939 | signal(SIGPIPE, SIG_IGN); 940 | 941 | ch = g_io_channel_unix_new(server_fd); 942 | if(!ch) 943 | return 1; 944 | g_io_add_watch(ch, G_IO_IN|G_IO_PRI, on_new_conn_incoming, NULL); 945 | g_io_add_watch(ch, G_IO_ERR, on_server_conn_close, NULL); 946 | g_io_channel_unref(ch); 947 | 948 | #if !GLIB_CHECK_VERSION(2, 36, 0) 949 | g_type_init(); 950 | #endif 951 | 952 | hash = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); 953 | 954 | main_loop = g_main_loop_new( NULL, TRUE ); 955 | g_main_loop_run( main_loop ); 956 | g_main_loop_unref( main_loop ); 957 | 958 | unlink(addr.sun_path); 959 | 960 | g_hash_table_destroy(hash); 961 | return 0; 962 | } 963 | -------------------------------------------------------------------------------- /menu-cache-gen/Makefile.am: -------------------------------------------------------------------------------- 1 | NULL = 2 | 3 | AM_CPPFLAGS = \ 4 | -I$(top_builddir)/libmenu-cache \ 5 | -I$(top_srcdir)/libmenu-cache \ 6 | $(GLIB_CFLAGS) \ 7 | $(LIBFM_EXTRA_CFLAGS) \ 8 | $(DEBUG_CFLAGS) \ 9 | $(ADDITIONAL_FLAGS) \ 10 | -Werror-implicit-function-declaration \ 11 | $(NULL) 12 | 13 | pkglibexec_PROGRAMS = menu-cache-gen 14 | 15 | menu_cache_gen_SOURCES = \ 16 | main.c \ 17 | menu-merge.c \ 18 | menu-compose.c \ 19 | $(NULL) 20 | 21 | menu_cache_gen_LDADD = \ 22 | $(GLIB_LIBS) \ 23 | $(LIBFM_EXTRA_LIBS) \ 24 | $(NULL) 25 | 26 | EXTRA_DIST = \ 27 | menu-tags.h \ 28 | $(NULL) 29 | -------------------------------------------------------------------------------- /menu-cache-gen/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * main.c : the main() function for menu-cache-gen binary. 3 | * 4 | * Copyright 2014 Andriy Grytsenko (LStranger) 5 | * 6 | * This file is a part of libmenu-cache package and created program 7 | * should be not used without the library. 8 | * 9 | * This library is free software; you can redistribute it and/or 10 | * modify it under the terms of the GNU Lesser General Public 11 | * License as published by the Free Software Foundation; either 12 | * version 2.1 of the License, or (at your option) any later version. 13 | * 14 | * This library is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | * Lesser General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU Lesser General Public 20 | * License along with this library; if not, write to the Free Software 21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 22 | */ 23 | 24 | #ifdef HAVE_CONFIG_H 25 | #include 26 | #endif 27 | 28 | #include "menu-tags.h" 29 | 30 | #include 31 | #include 32 | 33 | char **languages = NULL; 34 | GSList *MenuFiles = NULL; 35 | 36 | gint verbose = 0; 37 | 38 | static gboolean option_verbose (const gchar *option_name, const gchar *value, 39 | gpointer data, GError **error) 40 | { 41 | verbose++; 42 | return TRUE; 43 | } 44 | 45 | /* GLib options parser data is taken from previous menu-cache-gen code 46 | * 47 | * Copyright 2008 PCMan 48 | */ 49 | static char* ifile = NULL; 50 | static char* ofile = NULL; 51 | static char* lang = NULL; 52 | 53 | GOptionEntry opt_entries[] = 54 | { 55 | /* 56 | {"force", 'f', 0, G_OPTION_ARG_NONE, &force, "Force regeneration of cache even if it's up-to-dat 57 | e.", NULL }, 58 | */ 59 | {"input", 'i', 0, G_OPTION_ARG_FILENAME, &ifile, "Source *.menu file to read", "FILENAME" }, 60 | {"output", 'o', 0, G_OPTION_ARG_FILENAME, &ofile, "Output file to write cache to", "FILENAME" }, 61 | {"lang", 'l', 0, G_OPTION_ARG_STRING, &lang, "Language", "LANG_LIST" }, 62 | {"verbose", 'v', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, &option_verbose, "Send debug messages to terminal", NULL }, 63 | { NULL } 64 | }; 65 | 66 | int main(int argc, char **argv) 67 | { 68 | FmXmlFile *xmlfile = NULL; 69 | GOptionContext *opt_ctx; 70 | GError *err = NULL; 71 | MenuMenu *menu; 72 | int rc = 1; 73 | gboolean with_hidden = FALSE; 74 | 75 | /* wish we could use some POSIX parser but there isn't one for long options */ 76 | opt_ctx = g_option_context_new("Generate cache for freedesktop.org compliant menus."); 77 | g_option_context_add_main_entries(opt_ctx, opt_entries, NULL); 78 | if (!g_option_context_parse(opt_ctx, &argc, &argv, &err)) 79 | { 80 | g_printerr("menu-cache-gen: %s\n", err->message); 81 | g_error_free(err); 82 | return 1; 83 | } 84 | 85 | /* do with -l: if language is NULL then query it from environment */ 86 | if (lang == NULL || lang[0] == '\0') 87 | languages = (char **)g_get_language_names(); 88 | else 89 | languages = g_strsplit(lang, ":", 0); 90 | setlocale(LC_ALL, ""); 91 | 92 | /* do with files: both ifile and ofile should be set correctly */ 93 | if (ifile == NULL || ofile == NULL) 94 | { 95 | g_printerr("menu-cache-gen: failed: both input and output files must be defined.\n"); 96 | return 1; 97 | } 98 | with_hidden = g_str_has_suffix(ifile, "+hidden"); 99 | if (with_hidden) 100 | ifile[strlen(ifile)-7] = '\0'; 101 | if (G_LIKELY(!g_path_is_absolute(ifile))) 102 | { 103 | /* resolv the path */ 104 | char *path; 105 | gboolean found; 106 | const gchar * const *dirs = g_get_system_config_dirs(); 107 | 108 | const char *menu_prefix = g_getenv("XDG_MENU_PREFIX"); 109 | if (menu_prefix != NULL && menu_prefix[0] != '\0') 110 | { 111 | char *path = g_strconcat(menu_prefix, ifile, NULL); 112 | g_free(ifile); 113 | ifile = path; 114 | } 115 | path = g_build_filename(g_get_user_config_dir(), "menus", ifile, NULL); 116 | found = g_file_test(path, G_FILE_TEST_IS_REGULAR); 117 | while (!found && dirs[0] != NULL) 118 | { 119 | MenuFiles = g_slist_append(MenuFiles, (gpointer)g_intern_string(path)); 120 | g_free(path); 121 | path = g_build_filename(dirs[0], "menus", ifile, NULL); 122 | found = g_file_test(path, G_FILE_TEST_IS_REGULAR); 123 | dirs++; 124 | } 125 | if (!found) 126 | { 127 | g_printerr("menu-cache-gen: failed: cannot find file '%s'\n", ifile); 128 | return 1; 129 | } 130 | g_free(ifile); 131 | ifile = path; 132 | } 133 | MenuFiles = g_slist_append(MenuFiles, (gpointer)g_intern_string(ifile)); 134 | 135 | #if !GLIB_CHECK_VERSION(2, 36, 0) 136 | g_type_init(); 137 | #endif 138 | 139 | /* load, merge menu file, and create menu */ 140 | menu = get_merged_menu(ifile, &xmlfile, &err); 141 | if (menu == NULL) 142 | { 143 | g_printerr("menu-cache-gen: %s\n", err->message); 144 | g_error_free(err); 145 | return 1; 146 | } 147 | 148 | /* save the layout */ 149 | rc = !save_menu_cache(menu, ifile, ofile, with_hidden); 150 | if (xmlfile != NULL) 151 | g_object_unref(xmlfile); 152 | return rc; 153 | } 154 | -------------------------------------------------------------------------------- /menu-cache-gen/menu-compose.c: -------------------------------------------------------------------------------- 1 | /* 2 | * menu-compose.c : scans appropriate .desktop files and composes cache file. 3 | * 4 | * Copyright 2014-2015 Andriy Grytsenko (LStranger) 5 | * 6 | * This file is a part of libmenu-cache package and created program 7 | * should be not used without the library. 8 | * 9 | * This library is free software; you can redistribute it and/or 10 | * modify it under the terms of the GNU Lesser General Public 11 | * License as published by the Free Software Foundation; either 12 | * version 2.1 of the License, or (at your option) any later version. 13 | * 14 | * This library is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | * Lesser General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU Lesser General Public 20 | * License along with this library; if not, write to the Free Software 21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 22 | */ 23 | 24 | #ifdef HAVE_CONFIG_H 25 | #include 26 | #endif 27 | 28 | #include "menu-tags.h" 29 | #include "version.h" 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | #define NONULL(a) (a == NULL) ? "" : a 36 | 37 | static GSList *DEs = NULL; 38 | 39 | static guint req_version = 1; /* old compatibility default */ 40 | 41 | static void menu_app_reset(MenuApp *app) 42 | { 43 | g_free(app->filename); 44 | g_free(app->title); 45 | g_free(app->key); 46 | app->key = NULL; 47 | g_free(app->comment); 48 | g_free(app->icon); 49 | g_free(app->generic_name); 50 | g_free(app->exec); 51 | g_free(app->try_exec); 52 | g_free(app->wd); 53 | g_free(app->categories); 54 | g_free(app->keywords); 55 | g_free(app->show_in); 56 | g_free(app->hide_in); 57 | } 58 | 59 | static void menu_app_free(gpointer data) 60 | { 61 | MenuApp *app = data; 62 | 63 | menu_app_reset(app); 64 | g_free(app->id); 65 | g_list_free(app->dirs); 66 | g_list_free(app->menus); 67 | g_slice_free(MenuApp, app); 68 | } 69 | 70 | static char *_escape_lf(char *str) 71 | { 72 | char *c; 73 | 74 | if (str != NULL && (c = strchr(str, '\n')) != NULL) 75 | { 76 | GString *s = g_string_new_len(str, c - str); 77 | 78 | while (*c) 79 | { 80 | if (*c == '\n') 81 | g_string_append(s, "\\n"); 82 | else 83 | g_string_append_c(s, *c); 84 | c++; 85 | } 86 | g_free(str); 87 | str = g_string_free(s, FALSE); 88 | } 89 | return str; 90 | } 91 | 92 | static char *_get_string(GKeyFile *kf, const char *key) 93 | { 94 | return _escape_lf(g_key_file_get_string(kf, G_KEY_FILE_DESKTOP_GROUP, key, NULL)); 95 | } 96 | 97 | /* g_key_file_get_locale_string is too much limited so implement replacement */ 98 | static char *_get_language_string(GKeyFile *kf, const char *key) 99 | { 100 | char **lang; 101 | char *try_key, *str; 102 | 103 | for (lang = languages; lang[0] != NULL; lang++) 104 | { 105 | try_key = g_strdup_printf("%s[%s]", key, lang[0]); 106 | str = _get_string(kf, try_key); 107 | g_free(try_key); 108 | if (str != NULL) 109 | return str; 110 | } 111 | return _escape_lf(g_key_file_get_locale_string(kf, G_KEY_FILE_DESKTOP_GROUP, 112 | key, languages[0], NULL)); 113 | } 114 | 115 | static char **_get_string_list(GKeyFile *kf, const char *key, gsize *lp) 116 | { 117 | char **str, **p; 118 | 119 | str = g_key_file_get_string_list(kf, G_KEY_FILE_DESKTOP_GROUP, key, lp, NULL); 120 | if (str != NULL) 121 | for (p = str; p[0] != NULL; p++) 122 | p[0] = _escape_lf(p[0]); 123 | return str; 124 | } 125 | 126 | static char **_get_language_string_list(GKeyFile *kf, const char *key, gsize *lp) 127 | { 128 | char **lang; 129 | char *try_key, **str; 130 | 131 | for (lang = languages; lang[0] != NULL; lang++) 132 | { 133 | try_key = g_strdup_printf("%s[%s]", key, lang[0]); 134 | str = _get_string_list(kf, try_key, lp); 135 | g_free(try_key); 136 | if (str != NULL) 137 | return str; 138 | } 139 | str = g_key_file_get_locale_string_list(kf, G_KEY_FILE_DESKTOP_GROUP, key, 140 | languages[0], lp, NULL); 141 | if (str != NULL) 142 | for (lang = str; lang[0] != NULL; lang++) 143 | lang[0] = _escape_lf(lang[0]); 144 | return str; 145 | } 146 | 147 | static void _fill_menu_from_file(MenuMenu *menu, const char *path) 148 | { 149 | GKeyFile *kf; 150 | 151 | if (!g_str_has_suffix(path, ".directory")) /* ignore random names */ 152 | return; 153 | kf = g_key_file_new(); 154 | if (!g_key_file_load_from_file(kf, path, G_KEY_FILE_KEEP_TRANSLATIONS, NULL)) 155 | goto exit; 156 | menu->title = _get_language_string(kf, G_KEY_FILE_DESKTOP_KEY_NAME); 157 | menu->comment = _get_language_string(kf, G_KEY_FILE_DESKTOP_KEY_COMMENT); 158 | menu->icon = _get_string(kf, G_KEY_FILE_DESKTOP_KEY_ICON); 159 | menu->layout.nodisplay = g_key_file_get_boolean(kf, G_KEY_FILE_DESKTOP_GROUP, 160 | G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, NULL); 161 | menu->layout.is_set = TRUE; 162 | exit: 163 | g_key_file_free(kf); 164 | } 165 | 166 | static const char **menu_app_intern_key_file_list(GKeyFile *kf, const char *key, 167 | gboolean localized, 168 | gboolean add_to_des) 169 | { 170 | gsize len, i; 171 | char **val; 172 | const char **res; 173 | 174 | if (localized) 175 | val = _get_language_string_list(kf, key, &len); 176 | else 177 | val = _get_string_list(kf, key, &len); 178 | if (val == NULL) 179 | return NULL; 180 | res = (const char **)g_new(char *, len + 1); 181 | for (i = 0; i < len; i++) 182 | { 183 | res[i] = g_intern_string(val[i]); 184 | if (add_to_des && g_slist_find(DEs, res[i]) == NULL) 185 | DEs = g_slist_append(DEs, (gpointer)res[i]); 186 | } 187 | res[i] = NULL; 188 | g_strfreev(val); 189 | return res; 190 | } 191 | 192 | static void _fill_app_from_key_file(MenuApp *app, GKeyFile *kf) 193 | { 194 | app->title = _get_language_string(kf, G_KEY_FILE_DESKTOP_KEY_NAME); 195 | app->comment = _get_language_string(kf, G_KEY_FILE_DESKTOP_KEY_COMMENT); 196 | app->icon = _get_string(kf, G_KEY_FILE_DESKTOP_KEY_ICON); 197 | app->generic_name = _get_language_string(kf, G_KEY_FILE_DESKTOP_KEY_GENERIC_NAME); 198 | app->exec = _get_string(kf, G_KEY_FILE_DESKTOP_KEY_EXEC); 199 | app->try_exec = _get_string(kf, G_KEY_FILE_DESKTOP_KEY_TRY_EXEC); 200 | app->wd = _get_string(kf, G_KEY_FILE_DESKTOP_KEY_PATH); 201 | app->categories = menu_app_intern_key_file_list(kf, G_KEY_FILE_DESKTOP_KEY_CATEGORIES, 202 | FALSE, FALSE); 203 | app->keywords = menu_app_intern_key_file_list(kf, "Keywords", TRUE, FALSE); 204 | app->show_in = menu_app_intern_key_file_list(kf, G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN, 205 | FALSE, TRUE); 206 | app->hide_in = menu_app_intern_key_file_list(kf, G_KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN, 207 | FALSE, TRUE); 208 | app->use_terminal = g_key_file_get_boolean(kf, G_KEY_FILE_DESKTOP_GROUP, 209 | G_KEY_FILE_DESKTOP_KEY_TERMINAL, NULL); 210 | app->use_notification = g_key_file_get_boolean(kf, G_KEY_FILE_DESKTOP_GROUP, 211 | G_KEY_FILE_DESKTOP_KEY_STARTUP_NOTIFY, NULL); 212 | app->hidden = g_key_file_get_boolean(kf, G_KEY_FILE_DESKTOP_GROUP, 213 | G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, NULL); 214 | } 215 | 216 | static GHashTable *all_apps = NULL; 217 | 218 | static GSList *loaded_dirs = NULL; 219 | 220 | static GList *_make_def_layout(void) 221 | { 222 | MenuMerge *mm; 223 | GList *layout; 224 | 225 | mm = g_slice_new(MenuMerge); 226 | mm->type = MENU_CACHE_TYPE_NONE; 227 | mm->merge_type = MERGE_FILES; 228 | layout = g_list_prepend(NULL, mm); 229 | mm = g_slice_new(MenuMerge); 230 | mm->type = MENU_CACHE_TYPE_NONE; 231 | mm->merge_type = MERGE_MENUS; 232 | return g_list_prepend(layout, mm); 233 | } 234 | 235 | static void _fill_apps_from_dir(MenuMenu *menu, GList *lptr, GString *prefix, 236 | gboolean is_legacy) 237 | { 238 | const char *dir = lptr->data; 239 | GDir *gd; 240 | const char *name; 241 | char *filename, *id; 242 | gsize prefix_len = prefix->len; 243 | MenuApp *app; 244 | GKeyFile *kf; 245 | 246 | if (g_slist_find(loaded_dirs, dir) == NULL) 247 | loaded_dirs = g_slist_prepend(loaded_dirs, (gpointer)dir); 248 | /* the directory might be scanned with different prefix already */ 249 | else if (prefix->str[0] == '\0') 250 | return; 251 | gd = g_dir_open(dir, 0, NULL); 252 | if (gd == NULL) 253 | return; 254 | kf = g_key_file_new(); 255 | DBG("fill apps from dir [%s]%s", prefix->str, dir); 256 | /* Scan the directory with subdirs, 257 | ignore not .desktop files, 258 | ignore already present files that are allocated */ 259 | while ((name = g_dir_read_name(gd)) != NULL) 260 | { 261 | filename = g_build_filename(dir, name, NULL); 262 | if (g_file_test(filename, G_FILE_TEST_IS_DIR)) 263 | { 264 | /* recursion */ 265 | if (is_legacy) 266 | { 267 | MenuMenu *submenu = g_slice_new0(MenuMenu); 268 | submenu->layout = menu->layout; /* copy all */ 269 | submenu->layout.items = _make_def_layout(); /* default layout */ 270 | submenu->layout.inline_limit_is_set = TRUE; /* marker */ 271 | submenu->name = g_strdup(name); 272 | submenu->dir = g_intern_string(filename); 273 | menu->children = g_list_append(menu->children, submenu); 274 | } 275 | else 276 | { 277 | g_string_append(prefix, name); 278 | g_string_append_c(prefix, '-'); 279 | name = g_intern_string(filename); 280 | /* a little trick here - we insert new node after this one */ 281 | lptr = g_list_insert_before(lptr, lptr->next, (gpointer)name); 282 | _fill_apps_from_dir(menu, lptr->next, prefix, FALSE); 283 | g_string_truncate(prefix, prefix_len); 284 | } 285 | } 286 | else if (!g_str_has_suffix(name, ".desktop") || 287 | !g_file_test(filename, G_FILE_TEST_IS_REGULAR) || 288 | !g_key_file_load_from_file(kf, filename, 289 | G_KEY_FILE_KEEP_TRANSLATIONS, NULL)) 290 | ; /* ignore not key files */ 291 | else if ((id = g_key_file_get_string(kf, G_KEY_FILE_DESKTOP_GROUP, 292 | G_KEY_FILE_DESKTOP_KEY_TYPE, NULL)) == NULL || 293 | strcmp(id, G_KEY_FILE_DESKTOP_TYPE_APPLICATION) != 0) 294 | /* ignore non-applications */ 295 | g_free(id); 296 | else 297 | { 298 | g_free(id); 299 | if (prefix_len > 0) 300 | { 301 | g_string_append(prefix, name); 302 | app = g_hash_table_lookup(all_apps, prefix->str); 303 | } 304 | else 305 | app = g_hash_table_lookup(all_apps, name); 306 | if (app == NULL) 307 | { 308 | if (!g_key_file_get_boolean(kf, G_KEY_FILE_DESKTOP_GROUP, 309 | G_KEY_FILE_DESKTOP_KEY_HIDDEN, NULL)) 310 | { 311 | /* deleted file should be ignored */ 312 | app = g_slice_new0(MenuApp); 313 | app->type = MENU_CACHE_TYPE_APP; 314 | app->filename = (prefix_len > 0) ? g_strdup(name) : NULL; 315 | app->id = g_strdup((prefix_len > 0) ? prefix->str : name); 316 | VDBG("found app id=%s", app->id); 317 | g_hash_table_insert(all_apps, app->id, app); 318 | app->dirs = g_list_prepend(NULL, (gpointer)dir); 319 | _fill_app_from_key_file(app, kf); 320 | } 321 | } 322 | else if (app->allocated) 323 | g_warning("id '%s' already allocated for %s and requested to" 324 | " change to %s, ignoring request", name, 325 | (const char *)app->dirs->data, dir); 326 | else if (g_key_file_get_boolean(kf, G_KEY_FILE_DESKTOP_GROUP, 327 | G_KEY_FILE_DESKTOP_KEY_HIDDEN, NULL)) 328 | { 329 | VDBG("removing app id=%s", app->id), 330 | g_hash_table_remove(all_apps, name); 331 | } 332 | else 333 | { 334 | /* reset the data */ 335 | menu_app_reset(app); 336 | app->filename = (prefix_len > 0) ? g_strdup(name) : NULL; 337 | /* reorder dirs list */ 338 | app->dirs = g_list_remove(app->dirs, dir); 339 | app->dirs = g_list_prepend(app->dirs, (gpointer)dir); 340 | _fill_app_from_key_file(app, kf); 341 | /* FIXME: conform to spec about Legacy in Categories field */ 342 | } 343 | if (prefix_len > 0) 344 | g_string_truncate(prefix, prefix_len); 345 | } 346 | g_free(filename); 347 | } 348 | g_dir_close(gd); 349 | g_key_file_free(kf); 350 | } 351 | 352 | static int _compare_items(gconstpointer a, gconstpointer b) 353 | { 354 | /* return negative value to reverse sort list */ 355 | return -strcmp(((MenuApp*)a)->type == MENU_CACHE_TYPE_APP ? ((MenuApp*)a)->key 356 | : ((MenuMenu*)a)->key, 357 | ((MenuApp*)b)->type == MENU_CACHE_TYPE_APP ? ((MenuApp*)b)->key 358 | : ((MenuMenu*)b)->key); 359 | } 360 | 361 | static gboolean menu_app_match_tag(MenuApp *app, FmXmlFileItem *it) 362 | { 363 | FmXmlFileTag tag = fm_xml_file_item_get_tag(it); 364 | GList *children, *child; 365 | gboolean ok = FALSE; 366 | 367 | VVDBG("menu_app_match_tag: entering <%s>", fm_xml_file_item_get_tag_name(it)); 368 | children = fm_xml_file_item_get_children(it); 369 | if (tag == menuTag_Or) 370 | { 371 | for (child = children; child; child = child->next) 372 | if (menu_app_match_tag(app, child->data)) 373 | break; 374 | ok = (child != NULL); 375 | } 376 | else if (tag == menuTag_And) 377 | { 378 | for (child = children; child; child = child->next) 379 | if (!menu_app_match_tag(app, child->data)) 380 | break; 381 | ok = (child == NULL); 382 | } 383 | else if (tag == menuTag_Not) 384 | { 385 | for (child = children; child; child = child->next) 386 | if (menu_app_match_tag(app, child->data)) 387 | break; 388 | ok = (child == NULL); 389 | } 390 | else if (tag == menuTag_All) 391 | ok = TRUE; 392 | else if (tag == menuTag_Filename) 393 | { 394 | register const char *id = fm_xml_file_item_get_data(children->data, NULL); 395 | ok = (g_strcmp0(id, app->id) == 0); 396 | } 397 | else if (tag == menuTag_Category) 398 | { 399 | if (app->categories != NULL) 400 | { 401 | const char *cat = g_intern_string(fm_xml_file_item_get_data(children->data, NULL)); 402 | const char **cats = app->categories; 403 | while (*cats) 404 | if (*cats == cat) 405 | break; 406 | else 407 | cats++; 408 | ok = (*cats != NULL); 409 | } 410 | } 411 | g_list_free(children); 412 | VVDBG("menu_app_match_tag %s: leaving <%s>: %d", app->id, fm_xml_file_item_get_tag_name(it), ok); 413 | return ok; 414 | } 415 | 416 | static gboolean menu_app_match_excludes(MenuApp *app, GList *rules); 417 | 418 | static gboolean menu_app_match(MenuApp *app, GList *rules, gboolean do_all) 419 | { 420 | MenuRule *rule; 421 | GList *children, *child; 422 | 423 | for (; rules != NULL; rules = rules->next) 424 | { 425 | rule = rules->data; 426 | if (rule->type != MENU_CACHE_TYPE_NONE || 427 | fm_xml_file_item_get_tag(rule->rule) != menuTag_Include) 428 | continue; 429 | children = fm_xml_file_item_get_children(rule->rule); 430 | for (child = children; child; child = child->next) 431 | if (menu_app_match_tag(app, child->data)) 432 | break; 433 | g_list_free(children); 434 | if (child != NULL) 435 | return (!do_all || !menu_app_match_excludes(app, rules->next)); 436 | } 437 | return FALSE; 438 | } 439 | 440 | static gboolean menu_app_match_excludes(MenuApp *app, GList *rules) 441 | { 442 | MenuRule *rule; 443 | GList *children, *child; 444 | 445 | for (; rules != NULL; rules = rules->next) 446 | { 447 | rule = rules->data; 448 | if (rule->type != MENU_CACHE_TYPE_NONE || 449 | fm_xml_file_item_get_tag(rule->rule) != menuTag_Exclude) 450 | continue; 451 | children = fm_xml_file_item_get_children(rule->rule); 452 | for (child = children; child; child = child->next) 453 | if (menu_app_match_tag(app, child->data)) 454 | break; 455 | g_list_free(children); 456 | if (child != NULL) 457 | /* application might be included again later so check for it */ 458 | return !menu_app_match(app, rules->next, TRUE); 459 | } 460 | return FALSE; 461 | } 462 | 463 | static void _free_leftovers(GList *item); 464 | 465 | static void menu_menu_free(MenuMenu *menu) 466 | { 467 | g_free(menu->name); 468 | g_free(menu->key); 469 | g_list_foreach(menu->id, (GFunc)g_free, NULL); 470 | g_list_free(menu->id); 471 | _free_leftovers(menu->children); 472 | _free_layout_items(menu->layout.items); 473 | g_free(menu->title); 474 | g_free(menu->comment); 475 | g_free(menu->icon); 476 | g_slice_free(MenuMenu, menu); 477 | } 478 | 479 | static void _free_leftovers(GList *item) 480 | { 481 | union { 482 | MenuMenu *menu; 483 | MenuRule *rule; 484 | } a = { NULL }; 485 | 486 | while (item) 487 | { 488 | a.menu = item->data; 489 | if (a.rule->type == MENU_CACHE_TYPE_NONE) 490 | g_slice_free(MenuRule, a.rule); 491 | else if (a.rule->type == MENU_CACHE_TYPE_DIR) 492 | menu_menu_free(a.menu); 493 | /* MenuApp and MenuSep are not allocated in menu->children */ 494 | item = g_list_delete_link(item, item); 495 | } 496 | } 497 | 498 | /* dirs are in order "first is more relevant" */ 499 | static void _stage1(MenuMenu *menu, GList *dirs, GList *apps, GList *legacy, GList *p) 500 | { 501 | GList *child, *_dirs = NULL, *_apps = NULL, *_legs = NULL, *_lprefs = NULL; 502 | GList *l, *available = NULL, *result; 503 | const char *id; 504 | char *filename; 505 | GString *prefix; 506 | MenuApp *app; 507 | FmXmlFileTag tag; 508 | GHashTableIter iter; 509 | 510 | DBG("... entering %s (%d dirs %d apps)", menu->name, g_list_length(dirs), g_list_length(apps)); 511 | /* Gather our dirs : DirectoryDir AppDir LegacyDir KDELegacyDirs */ 512 | for (child = menu->children; child; child = child->next) 513 | { 514 | MenuRule *rule = child->data; 515 | if (rule->type != MENU_CACHE_TYPE_NONE) 516 | continue; 517 | tag = fm_xml_file_item_get_tag(rule->rule); 518 | if (tag == menuTag_DirectoryDir) { 519 | id = g_intern_string(fm_xml_file_item_get_data(fm_xml_file_item_find_child(rule->rule, 520 | FM_XML_FILE_TEXT), NULL)); 521 | DBG("new DirectoryDir %s", id); 522 | if (_dirs == NULL) 523 | _dirs = g_list_copy(dirs); 524 | /* replace and reorder the list */ 525 | _dirs = g_list_remove(_dirs, id); 526 | _dirs = g_list_prepend(_dirs, (gpointer)id); 527 | } else if (tag == menuTag_AppDir) { 528 | id = g_intern_string(fm_xml_file_item_get_data(fm_xml_file_item_find_child(rule->rule, 529 | FM_XML_FILE_TEXT), NULL)); 530 | DBG("new AppDir %s", id); 531 | _apps = g_list_prepend(_apps, (gpointer)id); 532 | } else if (tag == menuTag_LegacyDir) { 533 | id = g_intern_string(fm_xml_file_item_get_data(fm_xml_file_item_find_child(rule->rule, 534 | FM_XML_FILE_TEXT), NULL)); 535 | DBG("new LegacyDir %s", id); 536 | _legs = g_list_prepend(_legs, (gpointer)id); 537 | _lprefs = g_list_prepend(_lprefs, (gpointer)fm_xml_file_item_get_comment(rule->rule)); 538 | } 539 | } 540 | /* Gather data from files in the dirs */ 541 | if (_dirs != NULL) dirs = _dirs; 542 | for (l = menu->id; l; l = l->next) 543 | { 544 | /* scan dirs now for availability of any of ids */ 545 | filename = NULL; 546 | for (child = dirs; child; child = child->next) 547 | { 548 | filename = g_build_filename(child->data, l->data, NULL); 549 | if (g_file_test(filename, G_FILE_TEST_IS_REGULAR)) 550 | break; 551 | g_free(filename); 552 | filename = NULL; 553 | } 554 | if (filename == NULL) for (child = _legs; child; child = child->next) 555 | { 556 | filename = g_build_filename(child->data, l->data, NULL); 557 | if (g_file_test(filename, G_FILE_TEST_IS_REGULAR)) 558 | break; 559 | g_free(filename); 560 | filename = NULL; 561 | } 562 | if (filename == NULL) for (child = legacy; child; child = child->next) 563 | { 564 | filename = g_build_filename(child->data, l->data, NULL); 565 | if (g_file_test(filename, G_FILE_TEST_IS_REGULAR)) 566 | break; 567 | g_free(filename); 568 | filename = NULL; 569 | } 570 | if (filename != NULL) 571 | { 572 | VVDBG("found dir file %s", filename); 573 | _fill_menu_from_file(menu, filename); 574 | g_free(filename); 575 | if (!menu->layout.is_set) 576 | continue; 577 | menu->dir = child->data; 578 | if (l != menu->id) /* relocate matched id to top if required */ 579 | { 580 | menu->id = g_list_remove_link(menu->id, l); 581 | menu->id = g_list_concat(menu->id, l); 582 | } 583 | break; 584 | } 585 | } 586 | if (menu->layout.inline_limit_is_set && !menu->layout.is_set) 587 | { 588 | filename = g_build_filename(menu->dir, ".directory", NULL); 589 | _fill_menu_from_file(menu, filename); 590 | g_free(filename); 591 | filename = NULL; 592 | } 593 | prefix = g_string_new(""); 594 | _apps = g_list_reverse(_apps); 595 | _legs = g_list_reverse(_legs); 596 | /* the same directory being scanned with different prefix should be denied */ 597 | for (l = _apps; l; l = l->next) 598 | { 599 | for (child = _apps; child; child = result) 600 | { 601 | int len; 602 | 603 | result = child->next; /* it is not used yet so we can use it now */ 604 | if (child == l) 605 | continue; 606 | len = strlen(l->data); 607 | if (strncmp(l->data, child->data, len) == 0 && 608 | ((const char *)child->data)[len] == G_DIR_SEPARATOR) 609 | _apps = g_list_delete_link(_apps, child); 610 | } 611 | for (child = _legs; child; child = result) 612 | { 613 | int len; 614 | 615 | result = child->next; 616 | len = strlen(l->data); 617 | if (strncmp(l->data, child->data, len) == 0 && 618 | ((const char *)child->data)[len] == G_DIR_SEPARATOR) 619 | { 620 | len = g_list_position(_legs, child); 621 | _legs = g_list_delete_link(_legs, child); 622 | child = g_list_nth(_lprefs, len); 623 | _lprefs = g_list_delete_link(_lprefs, child); 624 | } 625 | } 626 | } 627 | if (!menu->layout.inline_limit_is_set) for (l = _apps; l; l = l->next) 628 | { 629 | /* scan and fill the list */ 630 | _fill_apps_from_dir(menu, l, prefix, FALSE); 631 | } 632 | if (_apps != NULL) 633 | apps = _apps = g_list_concat(g_list_copy(apps), _apps); 634 | for (l = _legs, child = _lprefs; l; l = l->next, child = child->next) 635 | { 636 | /* use prefix from attribute */ 637 | g_string_assign(prefix, NONULL(child->data)); 638 | VDBG("got legacy prefix %s", (char*)child->data); 639 | _fill_apps_from_dir(menu, l, prefix, TRUE); 640 | g_string_truncate(prefix, 0); 641 | } 642 | if (_legs != NULL) 643 | legacy = _legs = g_list_concat(g_list_copy(legacy), _legs); 644 | if (_lprefs != NULL) 645 | p = _lprefs = g_list_concat(g_list_copy(p), _lprefs); 646 | /* Gather all available files (some in $all_apps may be not in $apps) */ 647 | VDBG("... do matching"); 648 | g_hash_table_iter_init(&iter, all_apps); 649 | while (g_hash_table_iter_next(&iter, NULL, (gpointer *)&app)) 650 | { 651 | app->matched = FALSE; 652 | /* check every dir if it is in $apps */ 653 | if (menu->layout.inline_limit_is_set) 654 | { 655 | for (child = app->dirs; child; child = child->next) 656 | if (menu->dir == child->data) 657 | break; 658 | } 659 | else for (child = app->dirs; child; child = child->next) 660 | { 661 | for (l = apps; l; l = l->next) 662 | { 663 | if (l->data == child->data) 664 | break; 665 | } 666 | if (l != NULL) /* found one */ 667 | break; 668 | } 669 | VVDBG("check %s in %s: %d", app->id, app->dirs ? (const char *)app->dirs->data : "(nil)", child != NULL); 670 | if (child == NULL) /* not matched */ 671 | continue; 672 | /* Check matching : Include And Or Not All */ 673 | if (menu->layout.inline_limit_is_set) 674 | app->matched = (app->categories == NULL); /* see the spec */ 675 | else 676 | app->matched = menu_app_match(app, menu->children, FALSE); 677 | if (!app->matched) 678 | continue; 679 | app->allocated = TRUE; 680 | /* Mark it by Exclude And Or Not All */ 681 | app->excluded = menu_app_match_excludes(app, menu->children); 682 | VVDBG("found match: %s excluded:%d", app->id, app->excluded); 683 | if (!app->excluded) 684 | available = g_list_prepend(available, app); 685 | } 686 | /* Compose layout using available list and replace menu->children */ 687 | VDBG("... compose (available=%d)", g_list_length(available)); 688 | result = NULL; 689 | for (child = menu->layout.items; child; child = child->next) 690 | { 691 | GList *next; 692 | app = child->data; /* either: MenuMenuname, MemuFilename, MenuSep, MenuMerge */ 693 | switch (app->type) { 694 | case MENU_CACHE_TYPE_DIR: /* MenuMenuname */ 695 | VDBG("composing Menuname %s", ((MenuMenuname *)app)->name); 696 | for (l = menu->children; l; l = l->next) 697 | if (((MenuMenu *)l->data)->layout.type == MENU_CACHE_TYPE_DIR && 698 | strcmp(((MenuMenuname *)app)->name, ((MenuMenu *)l->data)->name) == 0) 699 | break; 700 | if (l != NULL) /* found such menu */ 701 | { 702 | /* apply custom settings */ 703 | if (((MenuMenuname *)app)->layout.only_unallocated) 704 | ((MenuMenu *)l->data)->layout.show_empty = ((MenuMenuname *)app)->layout.show_empty; 705 | if (((MenuMenuname *)app)->layout.is_set) 706 | ((MenuMenu *)l->data)->layout.allow_inline = ((MenuMenuname *)app)->layout.allow_inline; 707 | if (((MenuMenuname *)app)->layout.inline_header_is_set) 708 | ((MenuMenu *)l->data)->layout.inline_header = ((MenuMenuname *)app)->layout.inline_header; 709 | if (((MenuMenuname *)app)->layout.inline_alias_is_set) 710 | ((MenuMenu *)l->data)->layout.inline_alias = ((MenuMenuname *)app)->layout.inline_alias; 711 | if (((MenuMenuname *)app)->layout.inline_limit_is_set) 712 | ((MenuMenu *)l->data)->layout.inline_limit = ((MenuMenuname *)app)->layout.inline_limit; 713 | /* remove from menu->children */ 714 | menu->children = g_list_remove_link(menu->children, l); 715 | /* prepend to result */ 716 | result = g_list_concat(l, result); 717 | /* ready for recursion now */ 718 | _stage1(l->data, dirs, apps, legacy, p); 719 | } 720 | break; 721 | case MENU_CACHE_TYPE_APP: /* MemuFilename */ 722 | VDBG("composing Filename %s", ((MenuFilename *)app)->id); 723 | app = g_hash_table_lookup(all_apps, ((MenuFilename *)app)->id); 724 | if (app == NULL) 725 | /* not available, ignoring it */ 726 | break; 727 | l = g_list_find(result, app); /* this might be slow but we have 728 | to do this because app might be 729 | already added into result */ 730 | if (l != NULL) 731 | { 732 | /* move it out to this place */ 733 | result = g_list_remove_link(result, l); 734 | VVDBG("+++ composing app %s (move)", app->id); 735 | } 736 | else 737 | { 738 | l = g_list_find(available, app); 739 | VVDBG("+++ composing app %s%s", app->id, (l == NULL) ? " (add)" : ""); 740 | if (l != NULL) 741 | available = g_list_remove_link(available, l); 742 | else 743 | l = g_list_prepend(NULL, app); 744 | } 745 | if (l != NULL) 746 | app->menus = g_list_prepend(app->menus, menu); 747 | result = g_list_concat(l, result); 748 | break; 749 | case MENU_CACHE_TYPE_SEP: /* MenuSep */ 750 | VDBG("composing Separator"); 751 | result = g_list_prepend(result, app); 752 | break; 753 | case MENU_CACHE_TYPE_NONE: /* MenuMerge */ 754 | VDBG("composing Merge type %d", ((MenuMerge *)app)->merge_type); 755 | next = NULL; 756 | tag = 0; 757 | switch (((MenuMerge *)app)->merge_type) { 758 | case MERGE_FILES: 759 | tag = 1; /* use it as mark to not add dirs */ 760 | case MERGE_ALL: 761 | for (l = available; l; l = l->next) 762 | { 763 | app = l->data; 764 | VVDBG("+++ composing app %s", app->id); 765 | if (app->key == NULL) 766 | { 767 | if (app->title != NULL) 768 | app->key = g_utf8_collate_key(app->title, -1); 769 | else 770 | g_warning("id %s has no Name", app->id), 771 | app->key = g_utf8_collate_key(app->id, -1); 772 | } 773 | app->menus = g_list_prepend(app->menus, menu); 774 | } 775 | next = available; 776 | available = NULL; 777 | /* continue with menus */ 778 | case MERGE_MENUS: 779 | if (tag != 1) for (l = menu->children; l; ) 780 | { 781 | if (((MenuMenu *)l->data)->layout.type == MENU_CACHE_TYPE_DIR) 782 | { 783 | GList *this = l; 784 | 785 | /* find it in the rest of layout and skip if it's found */ 786 | for (l = child->next; l; l = l->next) 787 | if (((MenuMenuname *)l->data)->layout.type == MENU_CACHE_TYPE_DIR && 788 | strcmp(((MenuMenuname *)l->data)->name, ((MenuMenu *)this->data)->name) == 0) 789 | break; 790 | if (l != NULL) 791 | { 792 | /* it will be added later by MenuMenuname handler */ 793 | l = this->next; 794 | continue; 795 | } 796 | _stage1(this->data, dirs, apps, legacy, p); /* it's time for recursion */ 797 | VVDBG("+++ composing menu %s (%s)", ((MenuMenu *)this->data)->name, ((MenuMenu *)this->data)->title); 798 | if (((MenuMenu *)this->data)->key == NULL) 799 | { 800 | if (((MenuMenu *)this->data)->title != NULL) 801 | ((MenuMenu *)this->data)->key = g_utf8_collate_key(((MenuMenu *)this->data)->title, -1); 802 | else 803 | ((MenuMenu *)this->data)->key = g_utf8_collate_key(((MenuMenu *)this->data)->name, -1); 804 | } 805 | l = this->next; 806 | /* move out from menu->children into result */ 807 | menu->children = g_list_remove_link(menu->children, this); 808 | next = g_list_concat(this, next); 809 | } 810 | else 811 | l = l->next; 812 | } 813 | result = g_list_concat(g_list_sort(next, _compare_items), result); 814 | break; 815 | default: ; 816 | } 817 | } 818 | } 819 | VDBG("... cleanup"); 820 | _free_leftovers(menu->children); 821 | menu->children = g_list_reverse(result); 822 | for (child = menu->children; child; ) 823 | { 824 | MenuMenu *submenu = child->data; 825 | 826 | child = child->next; 827 | if (submenu->layout.type == MENU_CACHE_TYPE_DIR && 828 | submenu->layout.allow_inline && 829 | (submenu->layout.inline_limit == 0 || 830 | (int)g_list_length(submenu->children) <= submenu->layout.inline_limit)) 831 | { 832 | DBG("*** got some inline!"); 833 | if (submenu->layout.inline_alias && g_list_length(submenu->children) == 1) 834 | { 835 | /* replace name of single child with name of submenu */ 836 | VDBG("replacing title of single child of %s due to inline_alias", 837 | submenu->name); 838 | app = submenu->children->data; 839 | if (app->type == MENU_CACHE_TYPE_DIR) 840 | { 841 | g_free(((MenuMenu *)app)->title); 842 | ((MenuMenu *)app)->title = g_strdup(submenu->title ? submenu->title : submenu->name); 843 | } 844 | else if (app->type == MENU_CACHE_TYPE_APP) 845 | { 846 | g_free(app->title); 847 | app->title = g_strdup(submenu->title ? submenu->title : submenu->name); 848 | } 849 | } 850 | /* FIXME: inline the submenu... how to use inline_header? */ 851 | submenu->children = g_list_reverse(submenu->children); 852 | while (submenu->children != NULL) 853 | { 854 | menu->children = g_list_insert_before(menu->children, child, 855 | submenu->children->data); 856 | submenu->children = g_list_delete_link(submenu->children, submenu->children); 857 | } 858 | menu->children = g_list_remove(menu->children, submenu); 859 | menu_menu_free(submenu); 860 | } 861 | } 862 | /* NOTE: now only menus are allocated in menu->children */ 863 | DBG("... done %s", menu->name); 864 | /* Do cleanup */ 865 | g_list_free(available); 866 | g_list_free(_dirs); 867 | g_list_free(_apps); 868 | g_list_free(_legs); 869 | g_list_free(_lprefs); 870 | g_string_free(prefix, TRUE); 871 | } 872 | 873 | static gint _stage2(MenuMenu *menu, gboolean with_hidden) 874 | { 875 | GList *child = menu->children, *next, *to_delete = NULL; 876 | MenuApp *app; 877 | gint count = 0; 878 | 879 | VVDBG("stage 2: entered '%s'", menu->name); 880 | while (child) 881 | { 882 | app = child->data; 883 | next = child->next; 884 | switch (app->type) { 885 | case MENU_CACHE_TYPE_APP: /* Menu App */ 886 | if (menu->layout.only_unallocated && app->menus->next != NULL) 887 | { 888 | VDBG("removing from %s as only_unallocated %s",menu->name,app->id); 889 | /* it is more than in one menu */ 890 | menu->children = g_list_delete_link(menu->children, child); 891 | app->menus = g_list_remove(app->menus, menu); 892 | } 893 | else if (app->hidden && !with_hidden) 894 | /* should be not displayed */ 895 | menu->children = g_list_delete_link(menu->children, child); 896 | else 897 | count++; 898 | break; 899 | case MENU_CACHE_TYPE_DIR: /* MenuMenu */ 900 | /* do recursion */ 901 | if (_stage2(child->data, with_hidden) > 0) 902 | count++; 903 | else if (!with_hidden || req_version < 2) 904 | to_delete = g_list_prepend(to_delete, child); 905 | break; 906 | default: 907 | /* separator */ 908 | if (child == menu->children || next == NULL || 909 | (app = next->data)->type == MENU_CACHE_TYPE_SEP) 910 | menu->children = g_list_delete_link(menu->children, child); 911 | } 912 | child = next; 913 | } 914 | VVDBG("stage 2: counted '%s': %d", menu->name, count); 915 | if (count > 0 && with_hidden) 916 | g_list_free(to_delete); 917 | else while (to_delete) /* if no apps here then don't keep dirs as well */ 918 | { 919 | child = to_delete->data; 920 | VVDBG("stage 2: deleting empty '%s'", ((MenuMenu *)child->data)->name); 921 | menu_menu_free(child->data); 922 | menu->children = g_list_delete_link(menu->children, child); 923 | to_delete = g_list_delete_link(to_delete, to_delete); 924 | } 925 | if (count == 0) 926 | { 927 | if (menu->layout.show_empty) 928 | count++; 929 | else 930 | menu->layout.nodisplay = TRUE; 931 | } 932 | return count; 933 | } 934 | 935 | static inline int _compose_flags(const char **f) 936 | { 937 | int x = 0, i; 938 | 939 | while (*f) 940 | { 941 | i = g_slist_index(DEs, *f++); 942 | if (i >= 0) 943 | x |= 1 << i; 944 | } 945 | return x; 946 | } 947 | 948 | static gboolean write_app_extra(FILE *f, MenuApp *app) 949 | { 950 | gboolean ret; 951 | char *cats, *keywords; 952 | char *null_list[] = { NULL }; 953 | 954 | if (req_version < 2) 955 | return TRUE; 956 | cats = g_strjoinv(";", app->categories ? (char **)app->categories : null_list); 957 | keywords = g_strjoinv(",", app->keywords ? (char **)app->keywords : null_list); 958 | ret = fprintf(f, "%s\n%s\n%s\n%s\n", NONULL(app->try_exec), NONULL(app->wd), 959 | cats, keywords) > 0; 960 | g_free(cats); 961 | g_free(keywords); 962 | return ret; 963 | } 964 | 965 | static gboolean write_app(FILE *f, MenuApp *app, gboolean with_hidden) 966 | { 967 | int index; 968 | MenuCacheItemFlag flags = 0; 969 | int show = 0; 970 | 971 | if (app->hidden && !with_hidden) 972 | return TRUE; 973 | index = MAX(g_slist_index(AppDirs, app->dirs->data), 0) + g_slist_length(DirDirs); 974 | if (app->use_terminal) 975 | flags |= FLAG_USE_TERMINAL; 976 | if (app->hidden) 977 | flags |= FLAG_IS_NODISPLAY; 978 | if (app->use_notification) 979 | flags |= FLAG_USE_SN; 980 | if (app->show_in) 981 | show = _compose_flags(app->show_in); 982 | else if (app->hide_in) 983 | show = ~_compose_flags(app->hide_in); 984 | 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, 985 | NONULL(app->title), NONULL(app->comment), NONULL(app->icon), 986 | NONULL(app->filename), index, NONULL(app->generic_name), 987 | NONULL(app->exec), flags, show) > 0 && write_app_extra(f, app); 988 | } 989 | 990 | static gboolean write_menu(FILE *f, MenuMenu *menu, gboolean with_hidden) 991 | { 992 | int index; 993 | GList *child; 994 | gboolean ok = TRUE; 995 | 996 | if (!with_hidden && !menu->layout.show_empty && menu->children == NULL) 997 | return TRUE; 998 | if (menu->layout.nodisplay && (!with_hidden || req_version < 2)) 999 | return TRUE; 1000 | index = g_slist_index(DirDirs, menu->dir); 1001 | if (fprintf(f, "+%s\n%s\n%s\n%s\n%s\n%d\n", menu->name, NONULL(menu->title), 1002 | NONULL(menu->comment), NONULL(menu->icon), 1003 | menu->id ? (const char *)menu->id->data : "", index) < 0) 1004 | return FALSE; 1005 | /* pass show_empty into file if format is v.1.2 */ 1006 | if (req_version >= 2 && 1007 | fprintf(f, "%d\n", menu->layout.nodisplay ? FLAG_IS_NODISPLAY : 0) < 0) 1008 | return FALSE; 1009 | for (child = menu->children; ok && child != NULL; child = child->next) 1010 | { 1011 | index = ((MenuApp *)child->data)->type; 1012 | if (index == MENU_CACHE_TYPE_DIR) 1013 | ok = write_menu(f, child->data, with_hidden); 1014 | else if (index == MENU_CACHE_TYPE_APP) 1015 | ok = write_app(f, child->data, with_hidden); 1016 | else if (child->next != NULL && child != menu->children && 1017 | ((MenuApp *)child->next->data)->type != MENU_CACHE_TYPE_SEP) 1018 | /* separator - not add duplicates nor at start nor at end */ 1019 | fprintf(f, "-\n"); 1020 | } 1021 | fputc('\n', f); 1022 | return ok; 1023 | } 1024 | 1025 | 1026 | /* 1027 | * we handle here only: 1028 | * - menuTag_DirectoryDir : for directory files list 1029 | * - menuTag_AppDir menuTag_LegacyDir menuTag_KDELegacyDirs : for app files list 1030 | * - menuTag_Include menuTag_Exclude menuTag_And menuTag_Or menuTag_Not menuTag_All : 1031 | * as matching rules 1032 | */ 1033 | gboolean save_menu_cache(MenuMenu *layout, const char *menuname, const char *file, 1034 | gboolean with_hidden) 1035 | { 1036 | const char *de_names[N_KNOWN_DESKTOPS] = { "LXDE", 1037 | "GNOME", 1038 | "KDE", 1039 | "XFCE", 1040 | "ROX" }; 1041 | char *tmp; 1042 | FILE *f = NULL; 1043 | GSList *l; 1044 | int i; 1045 | gboolean ok = FALSE; 1046 | 1047 | tmp = (char *)g_getenv("CACHE_GEN_VERSION"); 1048 | if (tmp && sscanf(tmp, "%d.%u", &i, &req_version) == 2) 1049 | { 1050 | if (i != VER_MAJOR) /* unsupported format requested */ 1051 | return FALSE; 1052 | } 1053 | if (req_version < VER_MINOR_SUPPORTED) /* unsupported format requested */ 1054 | return FALSE; 1055 | if (req_version > VER_MINOR) /* fallback to maximal supported format */ 1056 | req_version = VER_MINOR; 1057 | all_apps = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, menu_app_free); 1058 | for (i = 0; i < N_KNOWN_DESKTOPS; i++) 1059 | DEs = g_slist_append(DEs, (gpointer)g_intern_static_string(de_names[i])); 1060 | /* Recursively add files into layout, don't take OnlyUnallocated into account */ 1061 | _stage1(layout, NULL, NULL, NULL, NULL); 1062 | /* Recursively remove non-matched files by OnlyUnallocated flag */ 1063 | _stage2(layout, with_hidden); 1064 | /* Prepare temporary file for safe creation */ 1065 | tmp = strrchr(menuname, G_DIR_SEPARATOR); 1066 | if (tmp) 1067 | menuname = &tmp[1]; 1068 | tmp = g_path_get_dirname(file); 1069 | if (tmp != NULL && !g_file_test(tmp, G_FILE_TEST_EXISTS)) 1070 | g_mkdir_with_parents(tmp, 0700); 1071 | g_free(tmp); 1072 | tmp = g_strdup_printf("%sXXXXXX", file); 1073 | i = g_mkstemp(tmp); 1074 | if (i < 0) 1075 | goto failed; 1076 | /* Compose created layout into output file */ 1077 | f = fdopen(i, "w"); 1078 | if (f == NULL) 1079 | goto failed; 1080 | /* Write common data */ 1081 | fprintf(f, "1.%d\n%s%s\n%d\n", req_version, /* use CACHE_GEN_VERSION */ 1082 | menuname, with_hidden ? "+hidden" : "", 1083 | g_slist_length(DirDirs) + g_slist_length(AppDirs) 1084 | + g_slist_length(MenuDirs) + g_slist_length(MenuFiles)); 1085 | VDBG("%d %d %d %d",g_slist_length(DirDirs),g_slist_length(AppDirs),g_slist_length(MenuDirs),g_slist_length(MenuFiles)); 1086 | for (l = DirDirs; l; l = l->next) 1087 | if (fprintf(f, "D%s\n", (const char *)l->data) < 0) 1088 | goto failed; 1089 | for (l = AppDirs; l; l = l->next) 1090 | if (fprintf(f, "D%s\n", (const char *)l->data) < 0) 1091 | goto failed; 1092 | for (l = MenuDirs; l; l = l->next) 1093 | if (fprintf(f, "D%s\n", (const char *)l->data) < 0) 1094 | goto failed; 1095 | for (l = MenuFiles; l; l = l->next) 1096 | if (fprintf(f, "F%s\n", (const char *)l->data) < 0) 1097 | goto failed; 1098 | for (l = g_slist_nth(DEs, 5); l; l = l->next) 1099 | if (fprintf(f, "%s;", (const char *)l->data) < 0) 1100 | goto failed; 1101 | fputc('\n', f); 1102 | /* Write the menu tree */ 1103 | ok = write_menu(f, layout, with_hidden); 1104 | failed: 1105 | if (f != NULL) 1106 | fclose(f); 1107 | if (ok) 1108 | ok = g_rename(tmp, file) == 0; 1109 | else if (tmp) 1110 | g_unlink(tmp); 1111 | /* Free all the data */ 1112 | menu_menu_free(layout); 1113 | g_free(tmp); 1114 | g_hash_table_destroy(all_apps); 1115 | g_slist_free(DEs); 1116 | g_slist_free(loaded_dirs); 1117 | return ok; 1118 | } 1119 | -------------------------------------------------------------------------------- /menu-cache-gen/menu-tags.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Andriy Grytsenko (LStranger) 3 | * 4 | * This file is a part of libmenu-cache package and created program 5 | * should be not used without the library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this library; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | 22 | #include 23 | #include 24 | 25 | extern FmXmlFileTag menuTag_AppDir; 26 | extern FmXmlFileTag menuTag_DirectoryDir; 27 | extern FmXmlFileTag menuTag_Include; 28 | extern FmXmlFileTag menuTag_Exclude; 29 | extern FmXmlFileTag menuTag_Filename; 30 | extern FmXmlFileTag menuTag_Or; 31 | extern FmXmlFileTag menuTag_And; 32 | extern FmXmlFileTag menuTag_Not; 33 | extern FmXmlFileTag menuTag_Category; 34 | extern FmXmlFileTag menuTag_All; 35 | extern FmXmlFileTag menuTag_LegacyDir; 36 | 37 | typedef enum { 38 | MERGE_NONE, /* starting value */ 39 | MERGE_FILES, /* first set */ 40 | MERGE_MENUS, 41 | MERGE_ALL, /* only set */ 42 | MERGE_FILES_MENUS, /* second set */ 43 | MERGE_MENUS_FILES 44 | } MenuMergeType; 45 | 46 | typedef struct { 47 | MenuCacheType type : 2; /* used by MenuMenu, MENU_CACHE_TYPE_DIR */ 48 | gboolean only_unallocated : 1; /* for Menuname: TRUE if show_empty is set */ 49 | gboolean is_set : 1; /* used by MenuMenu, for Menuname: TRUE if allow_inline is set */ 50 | gboolean show_empty : 1; 51 | gboolean allow_inline : 1; 52 | gboolean inline_header : 1; 53 | gboolean inline_alias : 1; 54 | gboolean inline_header_is_set : 1; /* for Menuname */ 55 | gboolean inline_alias_is_set : 1; /* for Menuname */ 56 | gboolean inline_limit_is_set : 1; /* for Menuname; for MenuMenu is Legacy mark */ 57 | gboolean nodisplay : 1; 58 | GList *items; /* items are MenuItem : Menuname or Filename or Separator or Merge */ 59 | int inline_limit; 60 | } MenuLayout; 61 | 62 | /* Menuname item */ 63 | typedef struct { 64 | MenuLayout layout; 65 | char *name; 66 | } MenuMenuname; 67 | 68 | /* Filename item in layout */ 69 | typedef struct { 70 | MenuCacheType type : 2; /* MENU_CACHE_TYPE_APP */ 71 | char *id; 72 | } MenuFilename; 73 | 74 | /* Separator item */ 75 | typedef struct { 76 | MenuCacheType type : 2; /* MENU_CACHE_TYPE_SEP */ 77 | } MenuSep; 78 | 79 | /* Merge item */ 80 | typedef struct { 81 | MenuCacheType type : 2; /* MENU_CACHE_TYPE_NONE */ 82 | MenuMergeType merge_type; 83 | } MenuMerge; 84 | 85 | /* Menu item */ 86 | typedef struct { 87 | MenuLayout layout; /* copied from hash on */ 88 | char *name; 89 | /* next fields are only for Menu */ 90 | char *key; /* for sorting */ 91 | GList *id; /* for , may be NULL, first is most relevant */ 92 | /* next fields are only for composer */ 93 | GList *children; /* items are MenuItem : MenuApp, MenuMenu, MenuSep, MenuRule */ 94 | char *title; 95 | char *comment; 96 | char *icon; 97 | const char *dir; 98 | } MenuMenu; 99 | 100 | /* File item in menu */ 101 | typedef struct { 102 | MenuCacheType type : 2; /* MENU_CACHE_TYPE_APP */ 103 | gboolean excluded : 1; 104 | gboolean allocated : 1; 105 | gboolean matched : 1; 106 | gboolean use_terminal : 1; 107 | gboolean use_notification : 1; 108 | gboolean hidden : 1; 109 | GList *dirs; /* can be reordered until allocated */ 110 | GList *menus; 111 | char *filename; /* if NULL then is equal to id */ 112 | char *key; /* for sorting */ 113 | char *id; 114 | char *title; 115 | char *comment; 116 | char *icon; 117 | char *generic_name; 118 | char *exec; 119 | char *try_exec; 120 | char *wd; 121 | const char **categories; /* all char ** keep interned values */ 122 | const char **keywords; 123 | const char **show_in; 124 | const char **hide_in; 125 | } MenuApp; 126 | 127 | /* a placeholder for matching */ 128 | typedef struct { 129 | MenuCacheType type : 2; /* MENU_CACHE_TYPE_NONE */ 130 | FmXmlFileItem *rule; 131 | } MenuRule; 132 | 133 | /* requested language(s) */ 134 | extern char **languages; 135 | 136 | /* list of menu files to monitor */ 137 | extern GSList *MenuFiles; 138 | 139 | /* list of menu dirs to monitor */ 140 | extern GSList *MenuDirs; 141 | 142 | /* list of available app dirs */ 143 | extern GSList *AppDirs; 144 | 145 | /* list of available dir dirs */ 146 | extern GSList *DirDirs; 147 | 148 | /* parse and merge menu files */ 149 | MenuMenu *get_merged_menu(const char *file, FmXmlFile **xmlfile, GError **error); 150 | 151 | /* parse all files into layout and save cache file */ 152 | gboolean save_menu_cache(MenuMenu *layout, const char *menuname, const char *file, 153 | gboolean with_hidden); 154 | 155 | /* free MenuLayout data */ 156 | void _free_layout_items(GList *data); 157 | 158 | /* verbosity level */ 159 | extern gint verbose; 160 | 161 | #define DBG if (verbose) g_debug 162 | #define VDBG if (verbose > 1) g_debug 163 | #define VVDBG if (verbose > 2) g_debug 164 | -------------------------------------------------------------------------------- /vapi/libmenu-cache.vapi: -------------------------------------------------------------------------------- 1 | /* libmenu-cache.vapi generated by vapigen, do not modify. */ 2 | 3 | namespace Mc { 4 | [CCode (cname = "MenuCache", cprefix="menu_cache_", cheader_filename = "menu-cache.h", ref_function = "menu_cache_ref", unref_function = "menu_cache_unref")] 5 | [Compact] 6 | public class Cache { 7 | public void* add_reload_notify (GLib.Func func); 8 | public uint32 get_desktop_env_flag (string desktop_env); 9 | public unowned CacheDir get_dir_from_path (string path); 10 | public unowned CacheDir get_root_dir (); 11 | public static void init (int flags); 12 | public unowned GLib.SList list_all_apps (); 13 | public static unowned Cache lookup (string menu_name); 14 | public static unowned Cache lookup_sync (string menu_name); 15 | public bool reload (); 16 | public void remove_reload_notify (void* notify_id); 17 | } 18 | [CCode (cname = "MenuCacheApp", cprefix="menu_cache_app_", cheader_filename = "menu-cache.h")] 19 | [Compact] 20 | public class CacheApp { 21 | public unowned string get_exec (); 22 | public bool get_is_visible (uint32 de_flags); 23 | public uint32 get_show_flags (); 24 | public bool get_use_sn (); 25 | public bool get_use_terminal (); 26 | public unowned string get_working_dir (); 27 | } 28 | [CCode (cname = "MenuCacheDir", cprefix="menu_cache_dir_", cheader_filename = "menu-cache.h")] 29 | [Compact] 30 | public class CacheDir { 31 | public unowned GLib.SList get_children (); 32 | public unowned string make_path (); 33 | } 34 | [CCode (cname = "MenuCacheItem", cprefix="menu_cache_item_", cheader_filename = "menu-cache.h", ref_function = "menu_cache_item_ref", unref_function = "menu_cache_item_unref")] 35 | [Compact] 36 | public class CacheItem { 37 | public unowned Type get_type (); 38 | public unowned string get_comment (); 39 | public unowned string get_file_basename (); 40 | public unowned string get_file_dirname (); 41 | public unowned string get_file_path (); 42 | public unowned string get_icon (); 43 | public unowned string get_id (); 44 | public unowned string get_name (); 45 | public unowned CacheDir get_parent (); 46 | } 47 | [CCode (cname="MenuCacheItemFlag", cheader_filename = "menu-cache.h", cprefix = "FLAG_", has_type_id = false)] 48 | public enum Item { 49 | USE_TERMINAL, 50 | USE_SN 51 | } 52 | [CCode (cname="MenuCacheShowFlag", cheader_filename = "menu-cache.h", cprefix = "SHOW_", has_type_id = false)] 53 | public enum Show { 54 | IN_LXDE, 55 | IN_GNOME, 56 | IN_KDE, 57 | IN_XFCE, 58 | IN_ROX, 59 | N_KNOWN_DESKTOPS 60 | } 61 | [CCode (cname="MenuCacheType", cheader_filename = "menu-cache.h", cprefix = "MENU_CACHE_TYPE_", has_type_id = false)] 62 | public enum Type { 63 | NONE, 64 | DIR, 65 | APP, 66 | SEP 67 | } 68 | } 69 | --------------------------------------------------------------------------------