diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..e0deab2 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,3 @@ +Mark McLoughlin +Havoc Pennington +Vincent Untz diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/COPYING.LIB b/COPYING.LIB new file mode 100644 index 0000000..5bc8fb2 --- /dev/null +++ b/COPYING.LIB @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..0c3c776 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,4869 @@ +# Generated by Makefile. Do not edit. + +commit 246a46e6f240915c7ba58c455f8d45ea59cebb2e +Author: Matthias Clasen +Date: Mon Mar 25 22:31:16 2013 -0400 + + 3.8.0 + +M NEWS +M configure.ac + +commit 0a37bcb06b0094a677178bd9777758cc16441274 +Author: Ani Peter +Date: Mon Mar 25 19:19:17 2013 +0530 + + Completed for Malayalam + +M po/ml.po + +commit e28da785eccf196ef0b987b1f4dbe74a5102aff5 +Author: Krishnababu Krothapalli +Date: Mon Mar 25 15:49:39 2013 +0530 + + Updated Telugu Translations + +M po/te.po + +commit 4343c5db75e0b306b835212a98b3edee8ea66db5 +Author: Reşat SABIQ +Date: Sun Mar 24 13:26:35 2013 -0500 + + Minor update for Crimean Tatar/Turkish translation + +M po/crh.po + +commit 292ecdb7084cd52c5c8016c2e4b25a78f1debdef +Author: Reşat SABIQ +Date: Sun Mar 24 13:24:38 2013 -0500 + + Updated Crimean Tatar (Crimean Turkish) translation + +M po/crh.po + +commit 47378bcc11d754c3490c7ce06b6a258b8c8afa8c +Author: Jiro Matsuzawa +Date: Mon Mar 25 02:30:49 2013 +0900 + + l10n: Update Japanese translation + +M po/ja.po + +commit 5e5b387cfd590cd028df6dc323a547e66c76c23b +Author: Wouter Bolsterlee +Date: Sat Mar 23 23:39:30 2013 +0100 + + Updated Dutch translation + +M po/nl.po + +commit 5a6cb52314c39f577ff6faaa6e94bb9620d96f00 +Author: Daniel Korostil +Date: Sat Mar 23 12:42:16 2013 +0200 + + Uploaded Ukranian + +M po/uk.po + +commit 370d50f85550cd5e2398198f7148e52c69e2d38f +Author: Shantha kumar +Date: Fri Mar 22 15:45:10 2013 +0530 + + Tamil Translations Updated + +M po/ta.po + +commit 8770020280a690de1f047aa55d360968d187f94b +Author: Rajesh Ranjan +Date: Fri Mar 22 12:40:18 2013 +0530 + + hindi translation + +M po/hi.po + +commit c03b16747e734de180c3dfd678f9657f1f5f163d +Author: Sandeep Sheshrao Shedmake +Date: Thu Mar 21 18:46:00 2013 +0530 + + Updated Marathi Translations + +M po/mr.po + +commit 5c50a43768e2eff9f68f94b3a1207b0c1d8cd905 +Author: ManojKumar Giri +Date: Wed Mar 20 18:04:23 2013 +0530 + + Updated Odia Translation. + +M po/or.po + +commit fb40f7702b8d443e5c6a4983da133b1eafad300c +Author: Danial Behzadi +Date: Wed Mar 20 09:26:27 2013 +0330 + + l10n: Updated Persian translation + +M po/fa.po + +commit 107172dc7b4c8b7936113d74f3c53fe53b24bdeb +Author: Victor Ibragimov +Date: Tue Mar 19 20:40:30 2013 +0100 + + [l10n] Added Tadjik translation + +M po/LINGUAS +A po/tg.po + +commit c573fa0e08616a908e6f1c335641ec6c1b9105f4 +Author: Shankar Prasad +Date: Mon Mar 18 16:02:23 2013 +0530 + + Updated kn translations + +M po/kn.po + +commit e26a4cd263ffc78db3104a9aca6e85034d48a67b +Author: Marek Černocký +Date: Sun Mar 17 22:46:15 2013 +0100 + + Updated Czech translation + +M po/cs.po + +commit 3fa134531fa6a7f1ed2ac4b572ea455a55eb1dd4 +Author: Carles Ferrando +Date: Sun Mar 17 19:51:46 2013 +0100 + + [l10n] Updated Catalan (Valencian) translation + +M po/ca@valencia.po + +commit 94a7ab3216a5d64c38272f616a20715ea6028b5f +Author: Ask H. Larsen +Date: Sun Mar 17 14:44:39 2013 +0100 + + Updated Danish translation + +M po/da.po + +commit f4adf5966ff0a6ebc86cdad7b22126dd88859804 +Author: Ihar Hrachyshka +Date: Sun Mar 17 16:24:34 2013 +0300 + + Updated Belarusian translation. + +M po/be.po + +commit b717f6e4f5d9a792f441f626bb769ec150e4bdd9 +Author: Changwoo Ryu +Date: Sat Mar 16 22:26:34 2013 +0900 + + Updated Korean translation + +M po/ko.po + +commit 8da5f3028860bfa6f5d8b38f6071285b501bb800 +Author: Balázs Úr +Date: Thu Mar 14 22:14:33 2013 +0100 + + Updated Hungarian translation + +M po/hu.po + +commit ca3a6029128122ee4f4b4b991686529174f25d02 +Author: Duarte Loreto +Date: Tue Mar 12 13:05:37 2013 +0000 + + Updated Portuguese translation and converted to New Spelling (Novo AO) + +M po/pt.po + +commit 758c22ec25528869dc96a3a77b76f378e4339d1e +Author: Gil Forcada +Date: Mon Mar 11 23:01:17 2013 +0100 + + [l10n] Updated Catalan translation + +M po/ca.po + +commit ed0dbd6eba3bcff96ce03dc7ba03c1d632e9e14d +Author: Mattias Põldaru +Date: Mon Mar 11 15:31:45 2013 +0200 + + [l10n] Updated Estonian translation + +M po/et.po + +commit 2be1f73f29a753a243f8dfcdf540916c8c9f20ad +Author: Andika Triwidada +Date: Mon Mar 11 12:39:01 2013 +0700 + + Updated Indonesian translation + +M po/id.po + +commit 8d0d9190809b44cdd94ae293d47df2abc4b1fe8d +Author: Khaled Hosny +Date: Sun Mar 10 00:06:18 2013 +0200 + + Update Arabic translation + +M po/ar.po + +commit a6c9fb7eaa9c3df19dbe6d555795f563fcdfa7c8 +Author: Baurzhan Muftakhidinov +Date: Sat Mar 9 21:28:34 2013 +0600 + + Updated Kazakh translation. + +M po/kk.po + +commit eebcb71254cc92334da975505e07446d1a00bf09 +Author: Ville-Pekka Vainio +Date: Thu Mar 7 20:57:21 2013 +0200 + + Finnish translation update by Jiri Grönroos + +M po/fi.po + +commit 47aec8856134fb8960679260a92c499578228b26 +Author: Rafael Ferreira +Date: Tue Mar 5 10:16:37 2013 -0300 + + Updated Brazilian Portuguese translation + +M po/pt_BR.po + +commit e63b9b880787e06c945a525048a7a7d92f8e19c8 +Author: Nilamdyuti Goswami +Date: Tue Mar 5 15:34:11 2013 +0530 + + Assamese translation updated for gnome 3.8 + +M po/as.po + +commit 192c6566cc281c52a2ca438da965da32c5e752f2 +Author: Inaki Larranaga Murgoitio +Date: Mon Mar 4 23:08:53 2013 +0100 + + Updated Basque language + +M po/eu.po + +commit 0f929355820faafef8712b17be906e834c3c97cc +Author: Jeremy Bicha +Date: Sun Mar 3 01:49:02 2013 -0500 + + sundry: Add Power Statistics & Personal File Sharing + + These seem to sort of duplicate what we have in Settings. + Perhaps the extra graphs and info from Power Statistics should find + a new home in System Monitor? + + https://bugzilla.gnome.org/show_bug.cgi?id=695047 + +M layout/gnome-applications.menu + +commit af5d71c573cda0e3527ea935516454dc3cd35e28 +Author: William Jon McCann +Date: Sun Mar 3 15:33:51 2013 -0500 + + Revert "Remove the special casing for Fedora desktop vendor renames" + + This reverts commit 5cc05790a308bbf7a5905254e377d3ca47df603b. + + We should really wait a bit before pushing this so the changes have + time to propagate. + +M layout/gnome-applications.menu + +commit a5382598d7f982e49b6267869db69ac7cf562b26 +Author: William Jon McCann +Date: Sun Mar 3 15:19:45 2013 -0500 + + layout: put im-chooser into Sundry + +M layout/gnome-applications.menu + +commit 28ab24749488986519a21a51ca8060e5d9002256 +Author: Benjamin Steinwender +Date: Sun Mar 3 19:52:08 2013 +0100 + + [l10n] Updated German translation + +M po/de.po + +commit 6f27ae37ae12ee34b15479bb2a76d8a83ad5be81 +Author: Luca Ferretti +Date: Sun Mar 3 01:19:56 2013 +0100 + + l10n: Updated Italian translation + +M po/it.po + +commit 5cc05790a308bbf7a5905254e377d3ca47df603b +Author: Kalev Lember +Date: Fri Feb 22 13:15:18 2013 +0100 + + Remove the special casing for Fedora desktop vendor renames + + The desktop file vendor prefixes are gone from eog.desktop, + file-roller.desktop, gucharmap.desktop, and yelp.desktop in Fedora 19+, + so we no longer need to special case them in gnome-applications.menu. + + https://bugzilla.gnome.org/show_bug.cgi?id=694444 + +M layout/gnome-applications.menu + +commit e85f2419023afef93e25d2058560fc833c22d310 +Author: Мирослав Николић +Date: Fri Mar 1 09:26:42 2013 +0100 + + Updated Serbian translation + +M po/sr.po +M po/sr@latin.po + +commit aab9d5ed860175bb86063a58a1fb82b3083d3431 +Author: Chao-Hsiung Liao +Date: Wed Feb 27 22:51:08 2013 +0800 + + Updated Traditional Chinese translation(Hong Kong and Taiwan) + +M po/zh_HK.po +M po/zh_TW.po + +commit 7ba60014ce6bcc74996b519540ad58940ecd7205 +Author: Sweta Kothari +Date: Wed Feb 27 13:58:35 2013 +0530 + + Updated gujarati file + +M po/gu.po + +commit 4a4d04d822e226eaf2c13a24a79ec639b620eb8a +Author: Pavol Klačanský +Date: Mon Feb 25 20:57:24 2013 +0100 + + Updated slovak translation + +M po/sk.po + +commit 3b28046273ca53e7a4f2d78f26fad10ea7c06424 +Author: Kjartan Maraas +Date: Mon Feb 25 19:18:08 2013 +0100 + + Updated Norwegian bokmål translation + +M po/nb.po + +commit 7c94f1773354a31f9db1f140bd164f9bb5a2f872 +Author: Jasper St. Pierre +Date: Mon Feb 25 11:13:14 2013 -0500 + + configure: Remove AM_PATH_PYTHON + + This was a leftover from removing the simple-editor. + +M configure.ac + +commit fe172ebde5ff50e55784611bc6048ecf56580699 +Author: Aurimas Černius +Date: Sun Feb 24 21:59:22 2013 +0200 + + Updated Lithuanian translation + +M po/lt.po + +commit 4338482231743c5e8bab7dd565e269e3a0cbe2cd +Author: Dimitris Spingos +Date: Sun Feb 24 10:23:11 2013 +0200 + + Updated Greek translation + +M po/el.po + +commit 3e5fe2ec16b5683ffdf225f1b2721ac4e4f902c6 +Author: Matej Urbančič +Date: Fri Feb 22 22:28:14 2013 +0100 + + Updated Slovenian translation + +M po/sl.po + +commit 57b724f42917bcde9cf6b52d885bea4662b18f89 +Author: A S Alam +Date: Fri Feb 22 14:55:42 2013 +0000 + + Punjabi: Translation updated (aalam) + +M po/pa.po + +commit 0cfe9c374d664492a77dbda436da5fe3bed7da4c +Author: Gheyret Kenji +Date: Fri Feb 22 22:58:01 2013 +0900 + + Updated Uyghur translation + + Signed-off-by: Gheyret Kenji + +M po/ug.po + +commit e1849964ad1a91d2a017f0bcc743bb936c52fb0d +Author: Yaron Shahrabani +Date: Thu Feb 21 19:41:32 2013 +0200 + + Updated Hebrew translation. + +M po/he.po + +commit ecc8ace4d8d7603f3f1c182de3ae107c20701302 +Author: Daniel Mustieles +Date: Thu Feb 21 16:55:32 2013 +0100 + + Updated Spanish translation + +M po/es.po + +commit eb5f8823904a79787d0e6057f36fc9d08d44c332 +Author: Yuri Myasoedov +Date: Thu Feb 21 15:54:56 2013 +0400 + + Updated Russian translation + +M po/ru.po + +commit 207fa196dd04124ea3919cc7e66b44be75f982f9 +Author: Cosimo Cecchi +Date: Wed Feb 20 18:48:01 2013 -0500 + + sundry: add more Java stuff + + I get these indecipherable desktop files when I install Eclipse. + + https://bugzilla.gnome.org/show_bug.cgi?id=694324 + +M layout/gnome-applications.menu + +commit 31b32c027426007639369832e4d6bb48f4f6c0fc +Author: Daniel Martinez +Date: Wed Feb 20 23:11:23 2013 +0100 + + Updated Aragonese translation + +M po/an.po + +commit 48307035146fb3b1e6fea66f5b73a4bfe001a064 +Author: Alexandre Franke +Date: Wed Feb 20 21:15:02 2013 +0100 + + Update French translation + +M po/fr.po + +commit e775ffd58b91ec437c4a17d3fea2f5bc45b2423a +Author: Fran Diéguez +Date: Wed Feb 20 10:38:36 2013 +0100 + + Updated Galician translations + +M po/gl.po + +commit 96ae298b28c458bfd3f735a6ed8dac5d79b3d0b1 +Author: Rūdolfs Mazurs +Date: Wed Feb 20 11:33:40 2013 +0200 + + Updated Latvian translation + +M po/lv.po + +commit a2f3bacaba079df016cf53b8331fb4e1c77f2eab +Author: Jasper St. Pierre +Date: Tue Feb 19 18:55:17 2013 -0500 + + Post-release version bump + +M configure.ac + +commit 005605f79c8fb7f13542c497ccc1f1fe52907902 +Author: Jasper St. Pierre +Date: Tue Feb 19 18:53:34 2013 -0500 + + Release 3.7.90 + +M NEWS +M configure.ac + +commit 64e189fedf5a04956cb96c54560f54736a012f4b +Author: Piotr Drąg +Date: Tue Feb 19 23:32:28 2013 +0100 + + Updated Polish translation + +M po/pl.po + +commit f3c636fbcf38846698c657d6aeb920cdabb0ea17 +Author: Piotr Drąg +Date: Tue Feb 19 23:31:14 2013 +0100 + + Updated POTFILES.in + +M po/POTFILES.in + +commit ac21fd7730b0c7f84b06468f862592d9666dfac6 +Author: Florian Müllner +Date: Tue Feb 19 12:47:18 2013 +0100 + + Add .directory file for Sundry + + https://bugzilla.gnome.org/show_bug.cgi?id=694131 + +M desktop-directories/Makefile.am +A desktop-directories/X-GNOME-Sundry.directory.in +M layout/gnome-applications.menu + +commit 95858c40461f6a2c53e2d4427d0851a08e0181c7 +Author: Alexandre Franke +Date: Tue Feb 19 21:56:15 2013 +0100 + + Update French translation + +M po/fr.po + +commit acb41bfe71a4e4717707eb21aef6c2b19b331e76 +Author: Jasper St. Pierre +Date: Tue Feb 19 15:14:48 2013 -0500 + + layout: Add some more items to Sundry + +M layout/gnome-applications.menu + +commit 1f4c3b6891637dfd2b8f42bae139166cd0e1fd8e +Author: Piotr Drąg +Date: Tue Feb 19 20:56:29 2013 +0100 + + Updated Polish translation + +M po/pl.po + +commit b1f831d2894df2501086f7c4bd523fe0e67f3676 +Author: Piotr Drąg +Date: Tue Feb 19 20:44:51 2013 +0100 + + Updated POTFILES.in + +M po/POTFILES.in + +commit 36d5d699d7d4193a1b3d84777566466326f78b19 +Author: Florian Müllner +Date: Sun Feb 17 03:40:22 2013 +0100 + + layout: Add Sundry category + + Creates a Sundry directory to contain things that either aren't really apps + or are scheduled for removal but that are hard to remove at the moment for + various reasons. We just need a temporary way to segregate these items so they + don't pollute the app view. + + https://bugzilla.gnome.org/show_bug.cgi?id=694131 + +M layout/gnome-applications.menu + +commit 8cb236fbf31dee1a533582f1f8ef72b7b96a33e0 +Author: Florian Müllner +Date: Sun Feb 17 02:51:00 2013 +0100 + + layout: Account for gcalctool => gnome-calculator name change + + https://bugzilla.gnome.org/show_bug.cgi?id=694131 + +M layout/gnome-applications.menu + +commit a06c0348c1a129ffd996e00615d5d6d25b17dcdc +Author: Florian Müllner +Date: Sun Feb 17 02:25:42 2013 +0100 + + layout: Deal with some vendor renames + + The menu spec doesn't offer very specific guidance for how + to use vendor prefixes. And it isn't even clear about what + a vendor is. Or how it helps avoid collisions with a naming scheme + that isn't fully qualified in the first place. + + Longer term we should move to something that uses fully qualified + names like the dbus spec. In the meantime we have to deal with + "aliases" for app names. + + https://bugzilla.gnome.org/show_bug.cgi?id=694131 + +M layout/gnome-applications.menu + +commit e644a541b5bab530fe6649c3b8f212faca46eb66 +Author: Florian Müllner +Date: Sun Feb 17 02:18:51 2013 +0100 + + layout: Add a new X-GNOME-Utilities directory + + Rather than using a developer-defined category, this directory + contains an explicit whitelist of known-good GNOME tools. This + more or less follows the definition set in the GNOME jhbuild + moduleset. + + https://bugzilla.gnome.org/show_bug.cgi?id=694131 + +M desktop-directories/Makefile.am +A desktop-directories/X-GNOME-Utilities.directory.in +M layout/gnome-applications.menu + +commit a5e2e6e4912c2dfcf6559828186063ecd6d09e57 +Author: Jasper St. Pierre +Date: Mon Feb 18 14:36:03 2013 -0500 + + desktop-directories: Remove system settings desktop entries + + They're unused by gnome-control-center. + +D desktop-directories/Hardware.directory.in +M desktop-directories/Makefile.am +D desktop-directories/Personal.directory.in +D desktop-directories/System.directory.in + +commit b53758987b4d04bf9c869bfd02c0ec1d252638bf +Author: Jasper St. Pierre +Date: Fri Feb 15 19:23:22 2013 -0500 + + layout: Install as gnome-applications.menu + + gnome-session now sets XDG_MENU_PREFIX by default, so we should + install it where applications that use gnome-menus look. + +M layout/Makefile.am +R100 layout/applications.menu layout/gnome-applications.menu + +commit cfaa9d460ef9fb502585d0fcdfcf3bb4e25b19aa +Author: Jasper St. Pierre +Date: Fri Feb 15 17:33:00 2013 -0500 + + layout: Put control center apps in the applications menu + + This allows gnome-shell to do app tracking for them. It should + not display them, as the desktop directory itself is marked as + NoDisplay=true. + +M desktop-directories/Makefile.am +A desktop-directories/X-GNOME-SystemSettings.directory.in +M layout/applications.menu + +commit a1c6559dcb1f9c9d12d9ebb6bd226a078a02d19a +Author: Jasper St. Pierre +Date: Fri Feb 15 20:44:48 2013 -0500 + + gmenu-tree: Fix error reporting + + No idea why this was here.. + +M libmenu/gmenu-tree.c + +commit 1e12d0f9f309c977702dad1363e56cd9989b7375 +Author: Jasper St. Pierre +Date: Fri Feb 15 20:29:18 2013 -0500 + + configure: Have better debugging on by default + +M configure.ac + +commit 589ba554d79366dacf3592b3670459003e45ace4 +Author: William Jon McCann +Date: Fri Sep 28 09:53:22 2012 -0400 + + Remove the simple editor + + gnome-menus is a dependency component and we don't really want it + to be installing applications as a side effect. There are other + better and maintained options for menu editing now anyway. + + https://bugzilla.gnome.org/show_bug.cgi?id=684900 + +M Makefile.am +M configure.ac +M po/POTFILES.in +D simple-editor/GMenuSimpleEditor/Makefile.am +D simple-editor/GMenuSimpleEditor/__init__.py +D simple-editor/GMenuSimpleEditor/config.py.in +D simple-editor/GMenuSimpleEditor/main.py +D simple-editor/GMenuSimpleEditor/maindialog.py +D simple-editor/GMenuSimpleEditor/menufilewriter.py +D simple-editor/GMenuSimpleEditor/menutreemodel.py +D simple-editor/Makefile.am +D simple-editor/gmenu-simple-editor.desktop.in +D simple-editor/gmenu-simple-editor.in +D simple-editor/gmenu-simple-editor.ui + +commit 7d90221e27fcc72de497f45db4f3ef3c2d623fd6 +Author: Jasper St. Pierre +Date: Fri Feb 1 21:46:13 2013 -0500 + + Memory leak fixes + + Based on a patch by William Jon McCann + + https://bugzilla.gnome.org/show_bug.cgi?id=349695 + +M libmenu/desktop-entries.c +M libmenu/entry-directories.c +M libmenu/gmenu-tree.c + +commit db75a478f53f2619b1d96cd09d3a4f9abe682fc9 +Author: Inaki Larranaga Murgoitio +Date: Wed Jan 30 21:26:01 2013 +0100 + + Updated Basque language + +M po/eu.po + +commit 2b4eb5f9db8411e62da6ec8d80ada1895772d05b +Author: Nuno Araujo +Date: Tue Jan 15 22:36:11 2013 +0100 + + Fix the build with automake 1.13 + + In Automake 1.13, the long-deprecated macro AM_CONFIG_HEADER (deprecated + since 2002) has been removed in favour of AC_CONFIG_HEADERS. + + https://bugzilla.gnome.org/show_bug.cgi?id=692108 + +M configure.ac + +commit 86241e29cd1101fe638a5665b8c52e8d3c84444d +Author: Fabio Tomat +Date: Sat Jan 19 02:34:16 2013 +0100 + + Updated Friulian translation + +M po/fur.po + +commit 8d161da704766d9f4a42f1213ca4dc6b8d0e7873 +Author: Matthias Clasen +Date: Tue Jan 15 07:42:02 2013 -0500 + + Bump version + +M configure.ac + +commit d4a11d28df389f00550848a078c40a2438908df5 +Author: Matthias Clasen +Date: Tue Jan 15 07:35:47 2013 -0500 + + 3.6.2 + +M NEWS + +commit 234940bf89aa82012c3622674cec151dcf4ff128 +Author: Gheyret Kenji +Date: Sat Jan 12 14:00:36 2013 +0900 + + Updated Uyghur translation + + Signed-off-by: Gheyret Kenji + +M po/ug.po + +commit 0594875c19b1bf981f1bbde416a1ca440c7caeb5 +Author: Vincent Untz +Date: Mon Jan 7 16:36:24 2013 +0100 + + misc: Remove myself from maintainers + + I'm not really doing anything on gnome-menus anymore, and Jasper is + happy being the sole maintainer. + +M MAINTAINERS +M gnome-menus.doap + +commit 9c1c50146b8d1a92ac556ba0a0ca174426bc6ffa +Author: Gheyret Kenji +Date: Fri Jan 4 21:38:40 2013 +0900 + + Uyghur translation + + Signed-off-by: Gheyret Kenji + +M po/ug.po + +commit fd4a321b9eaf6ee11b80d0c377b8ff8873e6c777 +Author: Khaled Hosny +Date: Mon Dec 24 13:38:26 2012 +0200 + + Update Arabic translation + +M po/ar.po + +commit 9d45c5369f4a486d2ba5c3a8c3262c84172b095a +Author: Alexandre Rostovtsev +Date: Sat Nov 24 15:24:50 2012 -0500 + + libmenu: always call menu_layout_load() with non_prefixed_name parameter + + We must ensure that when loading "${XDG_MENU_PREFIX}applications.menu" + or "applications.menu", the root layout node's name is set to "applications", + not "${XDG_MENU_PREFIX}applications", because the menu spec states that the + default merge directory for "${XDG_MENU_PREFIX}applications.menu" is + "applications-merged", not "${XDG_MENU_PREFIX}applications-merged". + + https://bugzilla.gnome.org/show_bug.cgi?id=688972 + +M libmenu/gmenu-tree.c + +commit dfae41dd0f0df6a72c1ef47dfe1e9efb1bfee746 +Author: Shankar Prasad +Date: Thu Nov 29 14:24:44 2012 +0530 + + Updated kn translation + +M po/kn.po + +commit aad880ce305edc2fe61fb3070ec8df1d9d29f28a +Author: Shankar Prasad +Date: Thu Nov 29 14:21:37 2012 +0530 + + Updated kn translation + +M po/kn.po + +commit df21eb893653e0b3c0e0f43cd0bc0de00c28d54a +Author: Shankar Prasad +Date: Thu Nov 29 14:13:13 2012 +0530 + + Updated kn translation + +M po/kn.po + +commit 09ea3c32275547f8de8799bb47ef3f9441abf12f +Author: Shankar Prasad +Date: Tue Nov 27 16:00:31 2012 +0530 + + Updated kn translation + +M po/kn.po + +commit 6a113f67b993702417d9db189d36e63fd9e4806d +Author: Matthias Clasen +Date: Tue Nov 13 22:56:06 2012 -0500 + + Post-release version bump + +M configure.ac + +commit 0ebd911ccbd177b856d34f43a6ef680275ab0871 +Author: Matthias Clasen +Date: Tue Nov 13 22:54:01 2012 -0500 + + 3.6.1 + +M NEWS + +commit fff8296aa0be3e257b2147e590da38cf470f4054 +Author: Bahodir Mansurov <6ahodir@gmail.com> +Date: Sun Oct 14 13:42:00 2012 -0400 + + updating Uzbek@cyrillic translation + +M po/uz@cyrillic.po + +commit d9dce393a242ddc4799fdc776f0af305bf935ff5 +Author: Bahodir Mansurov <6ahodir@gmail.com> +Date: Sun Oct 14 13:04:49 2012 -0400 + + updating Uzbek@cyrillic translation + +M po/uz@cyrillic.po + +commit 7acb8e2e5bff34c049c150d53190843996aa3f3d +Author: Pavol Klačanský +Date: Tue Oct 9 10:44:23 2012 +0100 + + Updated Slovak translation + +M po/sk.po + +commit 78b37e6142300931604566e2d8eb4123c70cfeb4 +Author: Daniel Martinez Cucalon +Date: Sat Sep 29 00:54:41 2012 +0200 + + Updated Aragonese translation + +M po/an.po + +commit 206da7e2fb133923f5898b42fbdc26db7f46d437 +Author: Daniel Korostil +Date: Wed Sep 26 09:15:57 2012 +0300 + + Added uk translation + +M po/uk.po + +commit 20f8d7169da967c39a92fb9bd97c74f5e1bb798d +Author: Carles Ferrando +Date: Wed Sep 26 01:31:05 2012 +0200 + + [l10n] Updated Catalan (Valencian) translation + +M po/ca@valencia.po + +commit e8cda485fec0fb2480f8e232b790387c1f41c77f +Author: Gil Forcada +Date: Wed Sep 26 01:30:42 2012 +0200 + + [l10n] Updated Catalan translation + +M po/ca.po + +commit e2ad6f559992e253ee7b10db7358c709d3a54328 +Author: Matthias Clasen +Date: Tue Sep 25 08:01:46 2012 -0400 + + post-release version bump + +M configure.ac + +commit ff379b515cdb3728dfd2f49c233797603b9658b8 +Author: Matthias Clasen +Date: Tue Sep 25 07:54:10 2012 -0400 + + 3.6.0 + +M NEWS + +commit 1f596f13f5b793515dcb07cc7dd0834581f9f22e +Author: Rūdolfs Mazurs +Date: Mon Sep 24 13:22:34 2012 +0300 + + Updated Latvian translation + +M po/lv.po + +commit 3839fb3f2784f3575c454a7128fb7ff4fed12233 +Author: Mattias Põldaru +Date: Mon Sep 24 11:15:01 2012 +0300 + + [l10n] Updated Estonian translation + +M po/et.po + +commit b2199a2b2972f37d85deab11818a94ce636a2d0a +Author: Timur Zhamakeev +Date: Mon Sep 24 09:21:40 2012 +0600 + + Updated Kyrgyz translation + +M po/ky.po + +commit d1ea18a03b123039f63e54360b5aac02d58e07fd +Author: Runa Bhattacharjee +Date: Fri Sep 21 19:23:07 2012 +0530 + + Updated Bengali India Translation + +M po/bn_IN.po + +commit 4883507b4742178707f227922f421e130ae0e612 +Author: Rajesh Ranjan +Date: Fri Sep 21 14:21:36 2012 +0530 + + hindi update + +M po/hi.po + +commit 319bc9001ec45d837da239a4dbd1cf30546ba4ce +Author: Praveen Illa +Date: Wed Sep 19 22:08:18 2012 +0530 + + Updated Telugu Translation + +M po/te.po + +commit dd8991b15488f384122120ea31e8c6043e0144ea +Author: Ani Peter +Date: Tue Sep 18 23:04:10 2012 +0530 + + Updated Malayalam file + +M po/ml.po + +commit e24c669b5331a6deab6730c0bb7f582ad98c138c +Author: Noriko Mizumoto +Date: Tue Sep 18 12:57:52 2012 +0900 + + [l10n] Update Japanese translation + +M po/ja.po + +commit d529600f891fdd32d111e281a65677c9468d8f5c +Author: Djavan Fagundes +Date: Mon Sep 17 21:28:13 2012 -0300 + + Revert "Updated Brazilian Portuguese translation" + + This reverts commit 0b8278f3dc7f834b5856a98762cda69b0d713707. + +A po/pt_BR.po + +commit 0b8278f3dc7f834b5856a98762cda69b0d713707 +Author: Djavan Fagundes +Date: Mon Sep 17 20:40:04 2012 -0300 + + Updated Brazilian Portuguese translation + +D po/pt_BR.po + +commit 0619ec60ce602e023beb9d14df34d4958d326a31 +Author: Og B. Maciel +Date: Mon Sep 17 16:51:48 2012 -0400 + + Updated translation for Brazilian Portuguese. + +M po/pt_BR.po + +commit 4ea3f1de69fec378262bc966f5d3e355f82c2b20 +Author: Vincent Untz +Date: Mon Sep 17 13:10:55 2012 +0200 + + release: post-release bump to 3.6.0 + +M configure.ac + +commit a4e3e70fb8150461af79a5314cdd3b9a8fe5956a +Author: Vincent Untz +Date: Mon Sep 17 13:10:14 2012 +0200 + + release: 3.5.92 + +M NEWS +M configure.ac + +commit c21dc9a2fbc2fbf288de0d688afd519e2f25da4b +Author: Ask H. Larsen +Date: Sun Sep 16 13:25:58 2012 +0200 + + Updated Danish translation + +M po/da.po + +commit fe09bdbae726dbefa92f22d2e851b7ec5b30dfef +Author: Changwoo Ryu +Date: Sat Sep 15 16:55:29 2012 +0900 + + Updated Korean translation + +M po/ko.po + +commit aceac14e0b728626c966d3ccfab51bb99a396ba6 +Author: Rico Tzschichholz +Date: Wed Sep 12 08:40:37 2012 +0200 + + libmenu: Add proper header reference to GMenu-3.0.gir + +M libmenu/Makefile.am + +commit 491c7eccff4e5fedc3f68f2d9d33399d49eed9a8 +Author: Marek Černocký +Date: Mon Sep 10 00:47:09 2012 +0200 + + Czech translation + +M po/cs.po + +commit 2c2c4f24c60952ef3e014ce0fd24c9db83c7f3fb +Author: Milo Casagrande +Date: Thu Sep 6 22:51:48 2012 +0200 + + [l10n] Updated Italian translation. + +M po/it.po + +commit f33d2b42226ae7c0d2fe871b55563b544dfcd252 +Author: Arash Mousavi +Date: Thu Sep 6 21:02:51 2012 +0430 + + l10n: Updated Persian translation + +M po/fa.po + +commit db8230cb5375eb951fc3385fa56e0fbd20222030 +Author: Theppitak Karoonboonyanan +Date: Thu Sep 6 20:23:58 2012 +0700 + + Updated Thai translation. + +M po/th.po + +commit b2cd372cf3ef3d5e10ca7e724366c72322a44849 +Author: Timo Jyrinki +Date: Thu Sep 6 08:59:09 2012 +0300 + + Finnish translation update by Jiri Grönroos + +M po/fi.po + +commit 103fb06fe86990bc7ec95e7cd8f61b71b69f9d9a +Author: Bruce Cowan +Date: Wed Sep 5 19:50:45 2012 +0100 + + Updated British English translation + +M po/en_GB.po + +commit a8f755a4b967738261540d2efd4c3ab45a9b283b +Author: Alexandre Franke +Date: Tue Sep 4 20:52:48 2012 +0200 + + Update French translation + +M po/fr.po + +commit b4c27cda6d161dbffb3878bfb1fd9164bb292de7 +Author: Gabor Kelemen +Date: Tue Sep 4 13:49:16 2012 +0200 + + Updated Hungarian translation + +M po/hu.po + +commit c66d0da3965cb3157d08b33278220c2d8d52c5f6 +Author: Muhammet Kara +Date: Tue Sep 4 06:48:19 2012 +0300 + + [l10n]Updated Turkish translation + +M po/tr.po + +commit 691760dbea68b46aa6b895f0d6c739302c372806 +Author: Piotr Drąg +Date: Mon Sep 3 19:09:33 2012 +0200 + + Updated Polish translation + +M po/pl.po + +commit a797159ac6b20b5e608290731ff0c9ccc1792848 +Author: Dr.T.Vasudevan +Date: Sun Sep 2 18:02:37 2012 +0530 + + updated Tamil translation + +M po/ta.po + +commit a44c335288e7c50db6d95bb080329bb1151d40a7 +Author: Daniel Nylander +Date: Sun Sep 2 11:59:21 2012 +0200 + + Updated Swedish translation + +M po/sv.po + +commit 8a09d2aad657036cb91c6d26fc370fd3a7be3d80 +Author: Nguyễn Thái Ngọc Duy +Date: Sat Sep 1 22:04:28 2012 +0700 + + Updated Vietnamese translation + +M po/vi.po + +commit c126f269866ed323926841157c72c48df5753a99 +Author: Nguyễn Thái Ngọc Duy +Date: Sat Sep 1 22:01:50 2012 +0700 + + po/vi: import from Damned Lies + +M po/vi.po + +commit d2b6d0a7e02d36125a455b99f2a43fb2598fc7cc +Author: Duarte Loreto +Date: Sat Sep 1 01:17:02 2012 +0100 + + Updated Portuguese translation + +M po/pt.po + +commit 6cbb7189d43207cc7abb5709255ddf8a6d5e4562 +Author: Aurimas Černius +Date: Wed Aug 29 22:57:58 2012 +0300 + + Updated Lithuanian translation + +M po/lt.po + +commit 55149c45571e54ed59599429e5c131fd64789d80 +Author: Chrovex Fan +Date: Tue Aug 28 22:32:44 2012 +0800 + + update Simplified Chinese (zh_CN) translation + +M po/zh_CN.po + +commit 2bfb0ab9c702b811d26c5adbd276eebdf0e04592 +Author: Nilamdyuti Goswami +Date: Tue Aug 28 00:03:50 2012 +0530 + + Implemented FUEL entries to Assamese translation + +M po/as.po + +commit 387804f05c1c6a8648f3930b45df939bf1b684f2 +Author: A S Alam +Date: Mon Aug 27 19:58:58 2012 +0530 + + update Punjabi Translation + +M po/pa.po + +commit 3be4bc499b3df622c149f89780ad8ffc8c67319a +Author: Piotr Drąg +Date: Thu Aug 23 03:24:55 2012 +0200 + + Updated Polish translation + +M po/pl.po + +commit 1682e39a113f5ea19e1fd8d0da5fb27db6a95274 +Author: Dirgita +Date: Tue Aug 21 22:20:32 2012 +0700 + + Updated Indonesian translation + +M po/id.po + +commit 0ed913bb8276b06e3f305b5e1907d87cc23d906a +Author: Fran Diéguez +Date: Fri Aug 17 23:37:20 2012 +0200 + + Updated Galician translations + +M po/gl.po + +commit d0e752350b2a9d8f85845614fda54571e143cef5 +Author: Sandeep Sheshrao Shedmake +Date: Thu Aug 16 12:19:50 2012 +0530 + + Updated Marathi Translations + +M po/mr.po + +commit 37f8b405844aedc87169ee902a8ed150978ffb1a +Author: Jasper St. Pierre +Date: Mon Aug 6 18:42:20 2012 -0300 + + Post-release version bump + +M configure.ac + +commit 42ace8382ab15914f55c7dc6dffab418b38e0e50 +Author: Jasper St. Pierre +Date: Mon Aug 6 18:36:56 2012 -0300 + + Release 3.5.5 + +M NEWS + +commit 685323a7cae56b1ced25c10f26af8eda7c9c893a +Author: Мирослав Николић +Date: Mon Aug 6 11:30:07 2012 +0200 + + Updated Serbian translation + +M po/sr.po +M po/sr@latin.po + +commit b0c0c0afbc6a563f987df95e508c66c881bb5c27 +Author: Sweta Kothari +Date: Mon Jul 30 13:54:26 2012 +0530 + + Updated gujarati file + +M po/gu.po + +commit 70f681351441c9146e1e4eb1c48154c5db1d2a5b +Author: Przemysław Buczkowski +Date: Thu Jul 26 19:41:14 2012 +0200 + + Added Silesian translation + +M po/LINGUAS +A po/szl.po + +commit 09053a26df6431e32d706eda69c0b7bf9100994b +Author: Baurzhan Muftakhidinov +Date: Thu Jul 26 09:20:18 2012 +0600 + + Updated Kazakh translation + +M po/kk.po + +commit 73b36491c3a472c67a15f1684893d080c743c601 +Author: Tobias Endrigkeit +Date: Mon Jul 23 22:43:15 2012 +0200 + + Updated German translation + +M po/de.po + +commit 985728566f914aa8a3a3c571dd203ee4b50827f3 +Author: Chao-Hsiung Liao +Date: Tue Jul 17 15:01:09 2012 +0800 + + Updated Traditional Chinese translation(Hong Kong and Taiwan) + +M po/zh_HK.po +M po/zh_TW.po + +commit 50f09c92d3418043cbead8ccc014235ab3eb9bd3 +Author: Jasper St. Pierre +Date: Mon Jul 16 20:24:57 2012 -0400 + + Post-release version bump + +M configure.ac + +commit f8e4cb42e10b7c2666f3771da158646a145abd34 +Author: Jasper St. Pierre +Date: Mon Jul 16 20:24:15 2012 -0400 + + Release 3.5.4 + +M NEWS + +commit 1e62d7d67525ea5628296435bd95ef325954786d +Author: chingiz +Date: Mon Jul 16 11:36:35 2012 +0400 + + Updated Kirghiz translation + +M po/ky.po + +commit b3f0c47b46b5456d220b7cb8b384a52e71018fa3 +Author: Alexander Shopov +Date: Wed Jul 4 07:17:08 2012 +0300 + + Updated Bulgarian translation + +M po/bg.po + +commit 2c31301300f13a4ced24a0b116a010f9e20b0756 +Author: Jasper St. Pierre +Date: Tue Jun 26 13:19:56 2012 -0400 + + Post-release version bump + +M configure.ac + +commit 78ac358fcb3135574377cea00781edd19bad6065 +Author: Jasper St. Pierre +Date: Tue Jun 26 13:18:33 2012 -0400 + + Release 3.5.3 + +M NEWS + +commit 1fba5a23c30af1a8cd07a6bd2f85e3c805129813 +Author: Sasi Bhushan Boddepalli +Date: Thu Jun 21 14:27:51 2012 +0530 + + Updated Telugu Translation + +M po/te.po + +commit 4a00f1eb30bc33afc14e647efb6ec1450ca7ff51 +Author: Nilamdyuti Goswami +Date: Thu Jun 21 14:01:14 2012 +0530 + + Assamese translation reviewed + +M po/as.po + +commit d1beaf075f02281e28bb730f3e43dd15dfea2ef3 +Author: Jasper St. Pierre +Date: Wed Jun 20 14:57:58 2012 -0400 + + libmenu: Allow grabbing the parent from any GMenuTreeItem + + Oh how I wish we had real inheritance. + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h + +commit ade49e7f07ef6cb0168002f6c12a4be6c1bdbee8 +Author: Jasper St. Pierre +Date: Mon Jun 18 18:08:26 2012 -0400 + + libmenu: Add a way to grab NoDisplay on an item and all of its parents + + Some applications may want a way to show or hide a tree entry based on + its visibility in the actual tree. + + https://bugzilla.gnome.org/show_bug.cgi?id=678419 + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h + +commit fef1440c7b440d0360992c2178ef636b02ef777a +Author: Jasper St. Pierre +Date: Fri Jun 1 09:16:01 2012 -0400 + + libmenu: Allow grabbing the GMenuTree from any GMenuTreeItem + + https://bugzilla.gnome.org/show_bug.cgi?id=677270 + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h + +commit eec6221af6a8ee04dddb11af9dbac45819a296db +Author: Vincent Untz +Date: Wed Jun 20 19:56:20 2012 +0200 + + misc: Add Jasper as co-maintainer + + Jasper is completely fixing alacarte, so he's the menu guy now :-) + +M MAINTAINERS +M gnome-menus.doap + +commit 7a87b12a86d7f8dfebca2a33791c04d5249de692 +Author: Tom Tryfonidis +Date: Tue Jun 19 19:26:24 2012 +0300 + + Updated Greek translation + +M po/el.po + +commit 8cd6efdf03a7056ed4c7373e00a2eb7657e5c6cc +Author: Jasper St. Pierre +Date: Sat Jun 2 13:27:24 2012 -0400 + + libmenu: Add the forgotten gmenu_tree_iter_get_separator + + https://bugzilla.gnome.org/show_bug.cgi?id=677344 + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h + +commit 2a83843d381cebf95a775a9ae2c176c5ae816fc8 +Author: Colin Walters +Date: Fri Jun 8 16:34:12 2012 -0400 + + gnome-menus: Fix g-ir-scanner warnings + +M libmenu/gmenu-tree.c + +commit 4973cf303cb9d5e8b160fb1ac0649ac2f864b8b4 +Author: Ihar Hrachyshka +Date: Fri Jun 8 18:55:01 2012 +0300 + + Updated Belarusian translation. + +M po/be.po + +commit 175f03aeef932bf9bf0527e11624e9a8e126e769 +Author: Matthias Clasen +Date: Tue Jun 5 09:21:45 2012 -0400 + + Post-release version bump + +M configure.ac + +commit c43bc6bc305b156be066fba6ae385b46e0063409 +Author: Matthias Clasen +Date: Tue Jun 5 09:17:34 2012 -0400 + + 3.5.2 + +M NEWS +M configure.ac + +commit 326c6ca76a4a739bb3d669ed4ce2d36d9b89eabe +Author: Kjartan Maraas +Date: Wed May 30 14:06:31 2012 +0200 + + Updated Norwegian bokmål translation + +M po/nb.po + +commit efc14ec052a53b706d8d2132ec62426cd46f54ea +Author: Aleksej Kabanov +Date: Sun May 27 00:56:26 2012 +0400 + + Updated Russian translation + +M po/ru.po + +commit 4693fdd844b623a69e02ec853f90888aee82a728 +Author: Yaron Shahrabani +Date: Fri May 25 15:29:40 2012 +0300 + + Updated Hebrew translation. + +M po/he.po + +commit ef10032c3c9d20cb46482829f7bbf5e09c311dee +Author: Matej Urbančič +Date: Wed May 23 20:02:27 2012 +0200 + + Updated Slovenian translation + +M po/sl.po + +commit 64606ea5a3c0f3faa5f406a63c53a51a1eb8823a +Author: Fran Diéguez +Date: Sun May 20 17:57:35 2012 +0200 + + Updated Galician translations + +M po/gl.po + +commit 068bfbdbcb328c0f7eb29faecaf41c82e5633bb5 +Author: Daniel Mustieles +Date: Mon May 14 16:49:12 2012 +0200 + + Updated Spanish translation + +M po/es.po + +commit 9452548505add051e3ca099332edad2dc7764181 +Author: Giovanni Campagna +Date: Tue May 1 00:23:39 2012 +0200 + + layout: Add a separate category for Web Applications + + For technical reasons, it is not possible to guess an appropriate + category for web applications created with Epiphany, so they + would all end up in Others. Instead, make up a category and submenu + just for them. + + https://bugzilla.gnome.org/show_bug.cgi?id=675198 + +M desktop-directories/Makefile.am +A desktop-directories/X-GNOME-WebApplications.directory.in +M layout/applications.menu +M po/POTFILES.in + +commit b93cc6818c7ee7be1339f0f7bf8d3ec24790df59 +Author: Vincent Untz +Date: Thu Feb 2 10:19:02 2012 +0100 + + util: Add --include-unallocated option to gnome-menu-spec-test + + We want easy testing for GMENU_TREE_FLAGS_INCLUDE_UNALLOCATED. + + https://bugzilla.gnome.org/show_bug.cgi?id=668512 + +M util/test-menu-spec.c + +commit 159bfe766f024598e967765a418430dfa46d2197 +Author: Vincent Untz +Date: Thu Feb 2 10:16:40 2012 +0100 + + libmenu: Add GMENU_TREE_FLAGS_INCLUDE_UNALLOCATED flag + + Add a new GMENU_TREE_FLAGS_INCLUDE_UNALLOCATED flag to add in the root + directory entries that are not allocated anywhere else. This is useful + if the user really wants to get absolutely all entries (in addition to + using INCLUDE_EXCLUDED, which is a bit different, and + INCLUDE_NODISPLAY). + + Add gmenu_tree_entry_get_is_unallocated() API matching this flag. + + https://bugzilla.gnome.org/show_bug.cgi?id=668512 + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h + +commit 6b6956b75de4566772c7144a01b39634671b364f +Author: Piotr Drąg +Date: Mon Apr 16 19:15:35 2012 +0200 + + Added Kashubian translation + +M po/LINGUAS +A po/csb.po + +commit 9d68b854b35573d5567fe79cc58d011ae23ed23e +Author: Alexander Shopov +Date: Sun Apr 1 18:11:32 2012 +0300 + + Updated Bulgarian translation + +M po/bg.po + +commit da8b0ff0c8733d8f94d7e29f1ba67ff2e9bace98 +Author: Vincent Untz +Date: Mon Mar 26 10:37:18 2012 +0200 + + release: post-release bump to 3.4.1 + +M configure.ac + +commit e1b3578220871cd5e607a93ab1b251da9e56d4d6 +Author: Vincent Untz +Date: Mon Mar 26 10:36:57 2012 +0200 + + release: 3.4.0 + +M NEWS +M configure.ac + +commit f553195fee927f128963702fc9443ce6d84fb20b +Author: Khoem Sokhem +Date: Sat Mar 10 13:24:57 2012 +0100 + + Add initial Khmer translation + +M po/LINGUAS +A po/km.po + +commit f289d086d2f3162bef06eada7276cd473f73fbfe +Author: Bahodir Mansurov <6ahodir@gmail.com> +Date: Fri Feb 24 21:24:22 2012 -0500 + + Updated Uzbek@cyrillic translation + +M po/uz@cyrillic.po + +commit 17b1dc3a3c850794bfe3df028ed72483fb21489e +Author: Vincent Untz +Date: Wed Feb 22 15:10:35 2012 +0100 + + build: Generate ChangeLog on make dist + +M Makefile.am + +commit ac467f5484a8c32ccb1484147003dbea8ee51d99 +Author: Vincent Untz +Date: Wed Feb 22 15:03:08 2012 +0100 + + build: Update git.mk and ignore generated tarballs + +M Makefile.am +M git.mk + +commit 82f575edbd949f682bc9242263e318f7e1d4d57c +Author: Vincent Untz +Date: Mon Feb 6 12:27:00 2012 +0100 + + release: post-release bump to 3.3.90 + +M configure.ac + +commit 44030054b1752ea353c010ec0588fbc804ffc662 +Author: Vincent Untz +Date: Mon Feb 6 12:26:54 2012 +0100 + + release: 3.3.5 + +M NEWS +M configure.ac + +commit 2adf8dc80b99459450e891161b3b7f254317d9b5 +Author: Danishka Navin +Date: Wed Feb 1 17:29:09 2012 +0530 + + fixed few typos in si.po file + +M po/si.po + +commit 17d5a7f7942f9045acdb8d5cbe18c967169d054e +Author: Danishka Navin +Date: Wed Feb 1 16:08:30 2012 +0530 + + fixed few typos in si.po file + +M po/si.po + +commit 7b895320a73ef066508d0d411c9ce1e725188a96 +Author: Kjartan Maraas +Date: Wed Jan 25 14:47:48 2012 +0100 + + Updated Norwegian bokmål translation + +M po/nb.po + +commit a84c7d360b4cb22a10cac76c95eff4654effd730 +Author: Andiswa Mvanyashe +Date: Tue Jan 17 08:09:02 2012 +0200 + + Updated translation for Xhosa (xh) + +M po/xh.po + +commit 91a67746970ca4327c03c2f048e48c666a1f3776 +Author: Vincent Untz +Date: Tue Dec 20 09:45:45 2011 +0100 + + build: Create xz tarballs + +M configure.ac + +commit 581bdf9920d18024413fa7afcacd83e733a26089 +Author: Jiro Matsuzawa +Date: Fri Nov 11 02:51:39 2011 +0900 + + Updated Japanese translation + +M po/ja.po + +commit 8d46c3a20c4718384b66180990258c0ad6c12f49 +Author: Vincent Untz +Date: Mon Oct 24 13:58:21 2011 +0200 + + release: post-release bump to 3.3.2 + +M configure.ac + +commit f258c9c144c569772a1ea7ea81179c98110fc58f +Author: Vincent Untz +Date: Mon Oct 24 13:58:14 2011 +0200 + + release: 3.3.1 + +M NEWS +M configure.ac + +commit fbabc41cb6f2c6520f9f117137fa296a7f8340f3 +Author: Florian Müllner +Date: Fri Oct 21 19:57:55 2011 +0200 + + libmenu: Ignore invalid desktop entries + + Both "Name" and "Exec" are mandatory keys according to the desktop + entry spec; some .desktop files missing one or the other have been + spotted in the while, so ignore them explicitly. + + https://bugzilla.gnome.org/show_bug.cgi?id=662409 + +M libmenu/desktop-entries.c + +commit 2179d84e1d7ce5a5b79f2736bf64a13e7bca1b4d +Author: Vincent Untz +Date: Fri Oct 7 10:43:02 2011 +0200 + + layout: Put the Other menu at the end + + It's really a special menu, which should not be handled with + alphabetical order. + +M layout/applications.menu + +commit 480edac95baba553b3cfc471817779e50a367871 +Author: Vincent Untz +Date: Wed Sep 28 09:24:19 2011 +0200 + + release: post-release bump to 3.2.1 + +M configure.ac + +commit 2d5915b925791d2d3bd736704a8706553455bd26 +Author: Vincent Untz +Date: Wed Sep 28 09:24:11 2011 +0200 + + release: 3.2.0.1 + +M NEWS +M configure.ac + +commit f1c76629d33a616089a15bd034708d21e8bd9a87 +Author: Vincent Untz +Date: Tue Sep 27 12:09:35 2011 +0200 + + editor: Fix to work with latest pygi + + https://bugzilla.gnome.org/show_bug.cgi?id=660112 + +M simple-editor/GMenuSimpleEditor/main.py +M simple-editor/GMenuSimpleEditor/menufilewriter.py +M simple-editor/GMenuSimpleEditor/menutreemodel.py + +commit b87a8c15e95fa64c94343b0bfdc6523e8f6c1bbc +Author: Vincent Untz +Date: Mon Sep 26 11:09:13 2011 +0200 + + release: post-release bump to 3.2.1 + +M configure.ac + +commit 4b0e41e9b45087402e64d90c2760c8d2101cd53e +Author: Vincent Untz +Date: Mon Sep 26 11:09:08 2011 +0200 + + release: 3.2.0 + +M NEWS + +commit 36f7db2473f1d6a0c7a1b1e05e056210ac87e80b +Author: Nilamdyuti Goswami +Date: Sat Sep 24 15:27:52 2011 +0530 + + Updated Assamese Translations:bugzilla#659595 + +M po/as.po + +commit 1412cf2222ef8e0f33e9bfa234cf7fdbf6aee035 +Author: ipraveen +Date: Fri Sep 23 16:32:28 2011 +0530 + + Updated Telugu Translation + +M po/te.po + +commit 3ecf6975346ab77b111a99c45b0f7a79228b202f +Author: ipraveen +Date: Fri Sep 23 16:26:27 2011 +0530 + + Updated Telugu Translation + +M po/te.po + +commit 26e1dd8864b18c5dee038a929bc23cfcadc0f6e1 +Author: Nilamdyuti Goswami +Date: Thu Sep 22 20:17:41 2011 +0200 + + Updated Assamese translation + +M po/as.po + +commit 612011be0953e15f349946ba48df5e6e8f036282 +Author: Vincent Untz +Date: Mon Sep 19 19:34:28 2011 +0200 + + release: post-release bump to 3.2.0 + +M configure.ac + +commit 5d7dd75fbf694cfd31f04a3f81c80786b1b5841d +Author: Vincent Untz +Date: Mon Sep 19 19:34:21 2011 +0200 + + release: 3.1.92 + +M NEWS +M configure.ac + +commit 49406208c5b1bfe449ed46c20d0bf16c7a363518 +Author: Ihar Hrachyshka +Date: Sun Sep 18 18:22:42 2011 +0300 + + Updated Belarusian translation (some fixes after manual testing). + +M po/be.po + +commit c3cc2e1d2dfa4f219c01c8dfee4d14e8aebd3a1e +Author: Jiro Matsuzawa +Date: Sat Sep 17 12:08:06 2011 +0900 + + Updated Japanese translation + +M po/ja.po + +commit 8c4aa57ca8ff56f1941899eb3c67babd2b7a3839 +Author: dmustieles +Date: Wed Sep 14 17:37:05 2011 +0200 + + Updated Spanish translation + +M po/es.po + +commit ecb47adb12c8ec9a3998efa96656dfedfe002525 +Author: Vincent Untz +Date: Mon Aug 29 08:43:26 2011 +0200 + + libmenu: Fix build failure with --enable-debug + + gmenu_tree_entry_get_name() was still being used. + + https://bugzilla.gnome.org/show_bug.cgi?id=656714 + +M libmenu/gmenu-tree.c + +commit 20d76fb2f1664aadf0b33c7c91603b47dfe04ef0 +Author: Matthias Clasen +Date: Sun Aug 28 19:22:26 2011 -0400 + + Post-release version bump + +M configure.ac + +commit 002662a50ab7fbce7398596a53e7270fc6f168e3 +Author: Matthias Clasen +Date: Sun Aug 28 19:22:00 2011 -0400 + + 3.1.90 + +M NEWS + +commit 739d3f6ef7d533544f1ec2b4d6e237bfaca99e85 +Author: Jasper St. Pierre +Date: Sun Aug 21 17:27:50 2011 -0400 + + libmenu: Don't try to unref potentially NULL pointers + + When a DIRECTORY desktop entry fails to load, we'll try to unref + some NULL pointers. This was causing some warnings. + + https://bugzilla.gnome.org/show_bug.cgi?id=657042 + +M libmenu/desktop-entries.c + +commit 5fade40dc3ebff3b4c00cdf37e6d7198e39e9bce +Author: Vincent Untz +Date: Fri Aug 12 11:50:59 2011 +0200 + + release: post-release bump to 3.1.90 + +M configure.ac + +commit 8e0ecffe8a6d4434dab086525f52d05e00edf0e9 +Author: Vincent Untz +Date: Fri Aug 12 11:50:52 2011 +0200 + + release: 3.1.5 + +M NEWS +M configure.ac + +commit 61c87a8e66bfa1d7a823a588b073e3922edc78c3 +Author: Vincent Untz +Date: Fri Aug 12 10:44:32 2011 +0200 + + libmenu: Support XDG_CURRENT_DESKTOP + + https://bugzilla.gnome.org/show_bug.cgi?id=653440 + +M libmenu/desktop-entries.c + +commit f0e41152be32a3e07772b53a6c3a0d4dbb0545d9 +Author: Colin Walters +Date: Tue Aug 2 23:13:30 2011 -0400 + + gmenu_tree_get_entry_by_id: New API + + It's useful for gnome-shell if we have an index by desktop file ID to + the tree entry. The cost is very small. + + https://bugzilla.gnome.org/show_bug.cgi?id=655868 + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h + +commit 2a17c07927cfc23c9837969ac43f2fbcf9e433d7 +Author: Vincent Untz +Date: Sat Jul 23 10:06:35 2011 +0200 + + build: Require gio-unix-2.0 >= 2.29.15 + + g_desktop_app_info_get_show_in() was pushed, but after 2.29.14. + +M configure.ac + +commit 3fae17cc6e86b3e988df481a05a386e005b7637c +Author: Vincent Untz +Date: Fri Jul 22 13:21:17 2011 +0200 + + editor: Stop editing settings.menu + + We dropped that file a while ago. We could replace this with + gnomecc.menu, but gnome-control-center loads the file from the system + configuration and explicitly ignore the user changes there... + +M simple-editor/GMenuSimpleEditor/menutreemodel.py + +commit 1cb6fe8f97cc5a12933bd77d278a16827e763c13 +Author: Vincent Untz +Date: Fri Jul 22 08:55:19 2011 +0200 + + libmenu: Unset GMenuTree:menu-basename if GMenuTree:menu-path is set + + Since GMenuTree:menu-basename has a non-NULL default value, we really + want to unset it if the user sets GMenuTree:menu-path, to avoid any + potential confusion. + + We need a constructor to do this magic, as the properties are + construct-only, and doing this anywhere else would be too late. + +M libmenu/gmenu-tree.c + +commit 02d0f749a1c4f406fa773254c2095e6c4f3d5979 +Author: Vincent Untz +Date: Thu Jul 21 18:28:56 2011 +0200 + + libmenu: Group GMenuTreeFlags flags in a pseudo-consistent way + + Since we're breaking ABI, let's go crazy and change the value of flags. + Grouping flags by some sort of meaning (include ones, show ones, misc. + ones) makes the header a bit nicer to read. + +M libmenu/gmenu-tree.h + +commit 9e2b4abdc8ec63e20f4c2a52b3047f1d767ebd8d +Author: Vincent Untz +Date: Thu Jul 21 18:19:24 2011 +0200 + + editor: Port to introspection-based gmenu bindings + +M simple-editor/GMenuSimpleEditor/maindialog.py +M simple-editor/GMenuSimpleEditor/menutreemodel.py + +commit 6f4cf16ec2633ebc62cb91759dbfd01923cd3c73 +Author: Vincent Untz +Date: Thu Jul 21 18:09:25 2011 +0200 + + libmenu, util: Rename gmenu_tree_get_menu_path() + + Renamed to gmenu_tree_get_canonical_menu_path(), to avoid any potential + confusion with the menu-path property. + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h +M util/test-menu-spec.c + +commit 8bc90706c9bfbf0d55c2eaddc859afb4a52a3281 +Author: Vincent Untz +Date: Thu Jul 21 18:07:54 2011 +0200 + + libmenu: Add API to load menu file from full path + + Add gmenu_tree_new_for_path() and GMenuTree:menu-path. + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h + +commit c653bbe8904acca4340f70574357d843e549035c +Author: Vincent Untz +Date: Thu Jul 21 17:22:20 2011 +0200 + + libmenu, util: Rename GMenuTree:name to GMenuTree:menu-basename + + This is a much clearer name for the property, else people might assume + they can use a full path, or something that is not a basename. + + Note that a relative path will work, but that really should not be of + much use, hence the choice of basename. + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h +M util/gnome-menus-ls.js + +commit c45596c3d87fa3e344b9e9a32c51190e12d67d93 +Author: Vincent Untz +Date: Thu Jul 21 17:13:18 2011 +0200 + + libmenu: Fix critical warning when trying to load "" menu + + Yes, this is a weird use of the API, but still. + +M libmenu/gmenu-tree.c + +commit 4c85e0ee58a4e8b11f1e5527909eefd02133a72a +Author: Vincent Untz +Date: Thu Jul 21 17:00:25 2011 +0200 + + libmenu: Default GMenuTree::name property to "applications.menu" + + This is useful for introspection bindings, since users will not + necessarily think of setting the menu name property at construct time. + And it's a saner default than NULL. + +M libmenu/gmenu-tree.c + +commit d8be0079a48bb7b486cc2b1355ee0cea2596fecb +Author: Vincent Untz +Date: Thu Jul 21 16:55:57 2011 +0200 + + libmenu: Do not keep internal load_error in GMenuTree + + This is not used, and it actually gets corrupted at some point (since + g_propagate_error means we lose the ownership of the GError). + +M libmenu/gmenu-tree.c + +commit bb3e154f60f81e97178b0cb8457827b29ee20692 +Author: Vincent Untz +Date: Thu Jul 21 16:01:46 2011 +0200 + + build: Bump version to 3.1.4 + + This is needed so users can know which version to require for the new + API. + +M configure.ac + +commit 70e9aaacbe0783fe11b7c69310873e0dc638b274 +Author: Vincent Untz +Date: Thu Jul 21 15:57:18 2011 +0200 + + libmenu: Drop GMenuTreeDirectoryRoot + + This was only used internally, but with no reason. + +M libmenu/gmenu-tree.c + +commit 17961198a182d5f68e4773427b1c8f735e0a01cd +Author: Vincent Untz +Date: Thu Jul 21 15:48:35 2011 +0200 + + libmenu: Make gmenu_tree_directory_get_icon() return a GIcon + + It's much better to use GIcon than icon name strings. + +M libmenu/desktop-entries.c +M libmenu/desktop-entries.h +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h + +commit 3c6ad47de8cfc3581fee66107a41ba4b39dd0de0 +Author: Vincent Untz +Date: Thu Jul 21 15:26:11 2011 +0200 + + libmenu: Correctly deal with OnlyShowIn/NotShowIn + + We're using new glib API for this. + + Also, we do not remove entries because of OnlyShowIn/NotShowIn before + processing the layout, as we might want to add a GMenuTreeFlags value in + the future, to include .desktop files that are excluded because of + OnlyShowIn/NotShowIn. + +M configure.ac +M libmenu/desktop-entries.c +M libmenu/desktop-entries.h +M libmenu/gmenu-tree.c + +commit 66557ef586adff091c8ce69a2f472da00dcd89ba +Author: Vincent Untz +Date: Mon Jul 4 17:23:32 2011 +0200 + + build: Drop check for -fno-strict-aliasing support + + This was only needed for our previous python bindings. + +M configure.ac + +commit fc5492ab992fde2b8d378ed91f0b4c6c62cbe69b +Author: Colin Walters +Date: Tue Jun 14 13:52:06 2011 -0400 + + libmenu: Dispose of source before context to avoid possible double unref + +M libmenu/menu-layout.c + +commit 21062503fff272872fdfc5fb468288b1faa85127 +Author: Colin Walters +Date: Tue Jun 14 13:25:47 2011 -0400 + + libmenu: Make refcounts volatile as g_atomic_* expect + +M libmenu/gmenu-tree.c + +commit b8b2471e8a3bd0f2d469dea41067721919359c22 +Author: Vincent Untz +Date: Tue Jun 14 09:04:31 2011 +0200 + + libmenu: Correctly look up at NoDisplay for .desktop files + + We were using Hidden instead. This requires glib 2.29.9. + +M configure.ac +M libmenu/desktop-entries.c + +commit 6ba57847943cd74839a306ed6c8926401e370b85 +Author: Vincent Untz +Date: Sun Jun 12 12:50:05 2011 +0200 + + libmenu: Simplify some code + +M libmenu/desktop-entries.c + +commit 2c47cdbebb15715d91d95aa4babc2de7a993dbd4 +Author: Vincent Untz +Date: Sun Jun 12 12:38:30 2011 +0200 + + libmenu: Fix loading of .directory files + + We were always returning FALSE, leaking some data and not displaying a + debug message in case of error (like for .desktop files). + +M libmenu/desktop-entries.c + +commit 6aff671fbeccb5ff7ec3290d0fd6345fbbb632c8 +Author: Vincent Untz +Date: Sun Jun 12 12:34:26 2011 +0200 + + util: Fix build after gmenu_tree_alias_get_item_type() renaming + +M util/test-menu-spec.c + +commit 398fd5c145524b7961c9d1dbc105c1cd49d98103 +Author: Vincent Untz +Date: Sun Jun 12 12:12:55 2011 +0200 + + doc: Do not reference non-existing gmenu_tree_iter_get_next_type() + + This is gmenu_tree_iter_next(). + +M libmenu/gmenu-tree.c + +commit e71520d5d2ccc4392b046e5feb84349d71a2c69f +Author: Vincent Untz +Date: Sun Jun 12 12:11:31 2011 +0200 + + libmenu: Handle GenericName for .directory files + + While we do not strictly need them right now, this is allowed by the + specification and might be used later. + +M libmenu/desktop-entries.c +M libmenu/desktop-entries.h +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h + +commit f29590a8c3771af47debf75c929170fe59877dce +Author: Vincent Untz +Date: Sun Jun 12 11:53:59 2011 +0200 + + libmenu: Fix desktop_entry_copy() for .desktop file + + We were not dealing with the appinfo field at all. + +M libmenu/desktop-entries.c + +commit 0d5f1909518f86d7283cde404e9470e6f55a42d2 +Author: Vincent Untz +Date: Sun Jun 12 11:41:20 2011 +0200 + + libmenu: Rename gmenu_tree_alias_get_item_type + + Renamed to gmenu_tree_alias_get_aliased_item_type, to avoid any + potential confusion about the typoe of what item this is. + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h + +commit 4d96af04a6d1d2e211c2005cc9d03d2b0e1a85c0 +Author: Vincent Untz +Date: Sun Jun 12 11:33:16 2011 +0200 + + libmenu: Drop gmenu_tree_entry_get_is_nodisplay() from header + + The function got dropped, and having a similar function in + GDesktopAppInfo would be the right thing to do. + +M libmenu/gmenu-tree.h + +commit f587ca8642070892fd4172ab01735c4a0789dc19 +Author: Vincent Untz +Date: Sun Jun 12 11:12:42 2011 +0200 + + libmenu: Remove unneeded cast + +M libmenu/gmenu-tree.c + +commit 2167c042bc089ba1e570ac9ba60dcdddb1d1d3c3 +Author: Vincent Untz +Date: Sun Jun 12 11:05:52 2011 +0200 + + libmenu: Rename desktop_entry_desktop_get_icon to desktop_entry_get_icon + + The additional desktop in the name was confusing. + +M libmenu/desktop-entries.c +M libmenu/desktop-entries.h +M libmenu/gmenu-tree.c + +commit 0f7d374006f32559893629fba47fecfaa031d4f8 +Author: Vincent Untz +Date: Sun Jun 12 10:53:05 2011 +0200 + + libmenu: Fix getting the flags property + + This was not handled at all. + +M libmenu/gmenu-tree.c + +commit 0eaed2f94d52a8b03263e5976a4d16104546929c +Author: Vincent Untz +Date: Sun Jun 12 10:39:54 2011 +0200 + + libmenu: Drop support for "KDE Desktop Entry" group + + This was deprecated for years, and with the move to GDesktopAppInfo, we + don't support this group for .desktop files anyway, so it makes no sense + to keep supporting it for .directory files. + +M libmenu/desktop-entries.c + +commit 536c6d510f53bbf76ff0c4134e935c8e7cf552da +Author: Vincent Untz +Date: Sun Jun 12 10:31:37 2011 +0200 + + libmenu: Use explicit GMENU_TREE_FLAGS_NONE instead of 0 + +M libmenu/gmenu-tree.c + +commit d5da147d09ca09e593e4a88c11b782ee7479268f +Author: Vincent Untz +Date: Sun Jun 12 09:58:20 2011 +0200 + + Spaces/tabs fixes + +M libmenu/desktop-entries.c +M libmenu/desktop-entries.h +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h +M libmenu/menu-layout.c + +commit 6920649082e3c0be363094054eecc9f1185a8185 +Author: Vincent Untz +Date: Wed Jun 8 17:55:43 2011 +0200 + + util: Do not call exit() in test tool + + A simple return statement is enough. + +M util/test-menu-spec.c + +commit f88d8c4dd17055932a6b335c008b496327d536b3 +Author: Vincent Untz +Date: Wed Jun 8 17:48:58 2011 +0200 + + build: Fix pkg-config to require gio-unix-2.0 and not glib-2.0 + + We reference gio/gdesktopappinfo.h in our public header. + +M libmenu/libgnome-menu-3.0-uninstalled.pc.in +M libmenu/libgnome-menu-3.0.pc.in + +commit cfb20fb779eeed93a0c9571a8340251c8964ef69 +Author: Vincent Untz +Date: Wed Jun 8 17:44:16 2011 +0200 + + gi: Add/Fix annotations to remove introspection warnings + +M libmenu/gmenu-tree.c + +commit 91a698f7860b3d8d5f29ffcdfcdbadf0cc347bad +Author: Vincent Untz +Date: Wed Jun 8 17:39:42 2011 +0200 + + build: Version the library name and the pkg-config file + + The library is now libgnome-menu-3, the pkg-config file is + libgnome-menu-3.0, the header directory is gnome-menus-3.0 and the + gettext package is gnome-menus-3.0. + + This way, it's possible to keep the old libgnome-menu around if some + applications still need it. + +M configure.ac +M libmenu/Makefile.am +R080 libmenu/libgnome-menu-uninstalled.pc.in libmenu/libgnome-menu-3.0-uninstalled.pc.in +R072 libmenu/libgnome-menu.pc.in libmenu/libgnome-menu-3.0.pc.in +M util/Makefile.am + +commit 669f03a403ca83facf83020e9c37b894c67f1b9b +Author: Vincent Untz +Date: Wed Jun 8 16:19:49 2011 +0200 + + build: Remove old python-related build stuff + +M configure.ac +D m4/python.m4 + +commit 1cf4482fa3416b98916653db23b40edb26d8646d +Author: Colin Walters +Date: Thu Apr 21 13:21:57 2011 -0400 + + Bump to GMenu-4.0.gir; this matches the package version. + +M libmenu/Makefile.am + +commit 5aa2c53ef25f684a848312455dad396f28c96a90 +Author: Colin Walters +Date: Wed Apr 20 17:48:07 2011 -0400 + + Replace Python example with JS + +M util/Makefile.am +A util/gnome-menus-ls.js +D util/gnome-menus-ls.py + +commit f0101da1c3df623f5a0023d9c787535e89476291 +Author: Colin Walters +Date: Wed Apr 20 17:42:39 2011 -0400 + + Remove GMenuTreeItem from public API + + gmenu_tree_directory_get_contents() wasn't actually bindable because + there's no way to express to introspection the inheritance hierarchy + (and to do the runtime type checking necessary via + gmenu_tree_item_get_item_type()). + + Therefore, drop it, and instead add explicit functions where each type + is needed. So for gmenu_tree_directory_get_contents(), we instead + add an explicit typesafe iterator object. + + The only other instance was gmenu_tree_alias. + + Note that gmenu_tree_item_ref() and gmenu_tree_item_unref() remain; + they're C only, and already just took "gpointer". + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h +M util/test-menu-spec.c + +commit 76895ad01d450696519425d03beab8845230397b +Author: Colin Walters +Date: Wed Apr 20 17:16:25 2011 -0400 + + introspection: scan gmenu-tree.c + + Now that we're actually adding gtk-doc. + +M libmenu/Makefile.am + +commit 0877f894a6c74f0dc7522e9cb93cc223097d1494 +Author: Colin Walters +Date: Mon Apr 18 16:17:04 2011 -0400 + + Document a few functions + +M libmenu/gmenu-tree.c + +commit bd78be7ad777f70d2390309507162f5dc267ea2f +Author: Colin Walters +Date: Mon Apr 18 15:36:01 2011 -0400 + + gmenu_tree_entry_get_parent: New function + + Earlier we moved this down to GMenuTreeDirectory, but it turns + out gnome-shell does expect to be able to get the parent of a + GMenuTreeEntry. In the future I want to nuke that code, but + for now just readd this functionality. + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h + +commit 94b2ea48a5221bd6dc82b8b468bb85ea6d420e57 +Author: Colin Walters +Date: Mon Apr 18 10:10:53 2011 -0400 + + layout: Use thread-default main context for callbacks + + Rather than hardcoding g_idle_add(); this gives us future + flexibility for thread support. + + https://bugzilla.gnome.org/show_bug.cgi?id=647968 + +M libmenu/menu-layout.c + +commit 52f9f7017779b6691e2b3f46f6c24e37f219abbf +Author: Colin Walters +Date: Sun Apr 17 11:22:32 2011 -0400 + + Remove gmenu_tree_directory_get_tree() + + This causes a circular reference internally, and API consumers + can just keep track of it easily enough externally. + + https://bugzilla.gnome.org/show_bug.cgi?id=647968 + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h + +commit 5d1e03adc206f52dec82f2b5e8831022321cb1f8 +Author: Colin Walters +Date: Sun Apr 17 10:17:20 2011 -0400 + + Switch to gslice for most data + + This is more efficient for the small items we have here. + + https://bugzilla.gnome.org/show_bug.cgi?id=647968 + +M libmenu/gmenu-tree.c + +commit 4f47a654d91cc3b08d46ef518a42c7f484f01be3 +Author: Colin Walters +Date: Sun Apr 17 09:57:47 2011 -0400 + + Rename gmenu_tree_get_menu_file() to gmenu_tree_get_menu_path() + + Document it and clean it up. + + https://bugzilla.gnome.org/show_bug.cgi?id=647968 + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h +M util/test-menu-spec.c + +commit 13920c91868f77a964b4498b00072563608f583e +Author: Colin Walters +Date: Sun Apr 17 09:51:58 2011 -0400 + + Further propagate GError for gmenu_tree_load_sync() + + We had some GError use internally; clean things up so we propagate + it more consistently to the top of gmenu_tree_load_sync(). + + https://bugzilla.gnome.org/show_bug.cgi?id=647968 + +M libmenu/gmenu-tree.c + +commit 8ecb1aa5d762d25f787002dd001e92e404efabe9 +Author: Colin Walters +Date: Sun Apr 17 09:31:34 2011 -0400 + + Add explicit gmenu_tree_load_sync() + + Rather than having _get_root_directory() be lazy, require users + to explicitly load via this function (or in the future, an + async variant). + + https://bugzilla.gnome.org/show_bug.cgi?id=647968 + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h +M util/test-menu-spec.c + +commit a2bda1462b6b46b1c8e47a19977f15c294226ef9 +Author: Colin Walters +Date: Sun Apr 17 08:58:19 2011 -0400 + + Lower gmenu_tree_item_get_parent to gmenu_tree_directory_get_parent + + Introspection doesn't know about the GMenuTreeItem "subclassing", + so we would have to duplicate the _get_parent method on all + subclasses - but in practice it only seems to be used on directories, + so just lower it there. + + https://bugzilla.gnome.org/show_bug.cgi?id=647968 + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h +M util/test-menu-spec.c + +commit 14c163c8a8d35fe2daa9c0973495fb4462a3ddec +Author: Colin Walters +Date: Sun Apr 17 08:46:14 2011 -0400 + + GMenuTreeItem: Register boxed types, drop user data + + This was a hack for the static Python bindings, no longer necessary + after we register proper boxed types for the structures. + + Convert the refcount to an atomic integer too; the _unref may be + called from a garbage collector thread in bindings. + + https://bugzilla.gnome.org/show_bug.cgi?id=647968 + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h + +commit 8eaf04da66be9437c518ac21e27cbbb67abcd3f5 +Author: Colin Walters +Date: Sun Apr 17 08:44:02 2011 -0400 + + Rename gmenu_tree_item_get_type() to _get_item_type() + + The _get_type() namespace suffix is reserved for GType. + + https://bugzilla.gnome.org/show_bug.cgi?id=647968 + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h +M util/test-menu-spec.c + +commit 474b86aac925d7b45d3bbbaba9111719ca0d9b8a +Author: Colin Walters +Date: Sun Apr 17 08:40:47 2011 -0400 + + Replace monitor API with a simple "changed" signal + + So much simpler... + + https://bugzilla.gnome.org/show_bug.cgi?id=647968 + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h +M util/test-menu-spec.c + +commit 21672a2d0ee94a9588fc15cf0cd4c5ffdfc818c1 +Author: Colin Walters +Date: Sun Apr 17 07:50:19 2011 -0400 + + Convert to GObject, drop static Python bindings + + GMenuTree is now a GObject. Drop the static Python bindings, since + introspection gives us coverage of most of the API now. + + https://bugzilla.gnome.org/show_bug.cgi?id=647968 + +M Makefile.am +M configure.ac +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h +M libmenu/menu-monitor.c +D python/Makefile.am +D python/gmenu.c +M util/gnome-menus-ls.py +M util/test-menu-spec.c + +commit 0a7e4736d221ada2167a064c8b4aa9710a2e04bf +Author: Colin Walters +Date: Sun Apr 17 07:32:07 2011 -0400 + + Drop GMenuTree caching and support for absolute paths + + This is work towards converting to GObject; the API to create a tree + is now just gmenu_tree_new(). + + First, the internal caching makes things very complex for little gain + - gnome-shell only creates one GMenuTree, and gnome-panel could pretty + easily be converted to do so. + + In a Google Code Search, I couldn't find anyone using absolute paths + for menus, and looking through the revision history, I don't see + a rationale for it. So, just drop it too. + + https://bugzilla.gnome.org/show_bug.cgi?id=647968 + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h +M python/gmenu.c +M util/test-menu-spec.c + +commit 903c144be9527f048fa1389c39872d0f22740b62 +Author: Colin Walters +Date: Sun Apr 17 07:17:22 2011 -0400 + + GMenuTreeFlags: Register with GType + + https://bugzilla.gnome.org/show_bug.cgi?id=647968 + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h + +commit d660a95756c99fe053ffd4fbaf523008868f5a67 +Author: Colin Walters +Date: Sun Apr 17 07:10:01 2011 -0400 + + Fold sorting into GMenuTreeFlags + + There's only two sorts right now, and so we can make one the default + and select the other with the flags. + + Drop the ability to set the sort at runtime; this never was compatible + with the current GMenuTree caching, and also I'm trying to move + GMenuTree towards being immutable. + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h +M python/gmenu.c + +commit 4d00dcf9ce31b248c61dc66430b881ba32e2d611 +Author: Colin Walters +Date: Sat Apr 16 14:09:57 2011 -0400 + + Rebase DesktopEntry on GDesktopAppInfo + + The main motivation for this work is to avoid gnome-shell having + to read all .desktop files *twice* - once from gnome-menus, and + once from gio (when doing MIME assocation etc.) + + This patch replaces almost all of the accessors for GMenuTreeEntry + with the simple gmenu_tree_entry_get_app_info(). + + Note this patch depends on patches from (see bug 647967). + + https://bugzilla.gnome.org/show_bug.cgi?id=647968 + +M configure.ac +M libmenu/Makefile.am +M libmenu/desktop-entries.c +M libmenu/desktop-entries.h +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h +M python/Makefile.am +M python/gmenu.c +M util/Makefile.am + +commit 8a01f3c268a1515fb4e9ee8c924dae20a0880bb1 +Author: Colin Walters +Date: Sat Apr 16 13:31:21 2011 -0400 + + desktop-entries.c: Split structure explicitly between .desktop and .directory + + This is code cleaup preparatory work for rebasing DesktopEntry on + GDesktopAppInfo. + + These two cases are different; make this explicit via structure subclassing + of a common base structure. + + https://bugzilla.gnome.org/show_bug.cgi?id=647968 + +M libmenu/desktop-entries.c + +commit 5a936c77ebb7ffddbc4e282662bb2d5f62a8a814 +Author: Colin Walters +Date: Sat Apr 16 11:54:17 2011 -0400 + + DesktopEntry: Clean up structure, make TryExec evaluation lazy + + This is preparatory work for rebasing DesktopEntry on top of + GDesktopAppInfo. + + First, it doesn't make sense to represet a random subset of booleans + as flags; just flatten these into a bitfield. Move the refcount to be + a plain guint at top. + + Second, previously we were calling g_find_program_in_path when + creating a .desktop file, even if the caller wasn't interested in + whether the TryExec succeeded. Make this lazy. + + https://bugzilla.gnome.org/show_bug.cgi?id=647968 + +M libmenu/desktop-entries.c + +commit ea008b417c02e8238f58d2d419d01129c2de9226 +Author: Colin Walters +Date: Sat Apr 16 11:04:26 2011 -0400 + + DesktopEntry: Make basename const reference + + Avoids another malloc block. + + https://bugzilla.gnome.org/show_bug.cgi?id=647968 + +M libmenu/desktop-entries.c + +commit 6cf363f9eaf4ba8ea736ca0a9e8d427226f381f6 +Author: Friedel Wolff +Date: Thu Jul 7 22:30:06 2011 +0200 + + Add Zulu (zu) to LINGUAS + +M po/LINGUAS + +commit 756d19762d1793ebe80df661328a1c7a510b9773 +Author: Priscilla Mahlangu +Date: Thu Jul 7 22:28:26 2011 +0200 + + New translation for Zulu (zu) + +A po/zu.po + +commit 5c896c6418cbc6111c823cca5401204bd62c16dc +Author: Ihar Hrachyshka +Date: Sat Jun 25 20:33:13 2011 +0300 + + Updated Belarusian translation. + +M po/be.po + +commit 9841d10ecf88b816ea18f186b64d4f1798f0c21a +Author: Carles Ferrando +Date: Sun May 29 18:26:56 2011 +0200 + + [l10n]Updated Catalan (Valencian) translation + +M po/ca@valencia.po + +commit 7336104da09a01db012c6d7e4d11abc3bac2f4e8 +Author: Vincent Untz +Date: Tue Apr 26 09:12:06 2011 +0200 + + release: post-release bump to 3.0.2 + +M configure.ac + +commit 582b78f2d085130fb63537c8e7b1120ff77c9979 +Author: Vincent Untz +Date: Tue Apr 26 09:11:59 2011 +0200 + + release: 3.0.1 + +M NEWS +M configure.ac + +commit 1febd5e57900e931b9aa53774a8c3caaff4f6a2a +Author: Theppitak Karoonboonyanan +Date: Mon Apr 25 08:41:18 2011 +0700 + + Updated Thai translation. + +M po/th.po + +commit df2f20a03a78d23665148c608b4955bb4702c2d9 +Author: Anousak Souphavanh +Date: Tue Apr 19 12:47:55 2011 +0300 + + l10n: Added Lao translation for gnome-menus + +M po/LINGUAS +A po/lo.po + +commit 2a436ad5101f3a05a029ffaded8d60c412686540 +Author: Vincent Untz +Date: Mon Apr 4 23:04:28 2011 +0200 + + release: post-release bump to 3.0.1 + +M configure.ac + +commit 8f288a1114e95adf98f8186cb309e5cacad66105 +Author: Vincent Untz +Date: Mon Apr 4 23:04:21 2011 +0200 + + release: 3.0.0 + +M NEWS +M configure.ac + +commit a153239bdf7126154ada0b7e22bf827860cad330 +Author: Jamil Ahmed +Date: Mon Apr 4 15:25:07 2011 +0600 + + Updated Bengali translation + +M po/bn.po + +commit 381752ab7db377bdabcfda08ab90353ce78770f7 +Author: Jordi Serratosa +Date: Mon Apr 4 00:14:46 2011 +0200 + + [l10n]Fixes on Catalan translation + +M po/ca.po + +commit 61593b848a8f03694a7963a0cb796aa06b82330c +Author: Nguyễn Thái Ngọc Duy +Date: Sat Apr 2 10:31:35 2011 +0700 + + Updated Vietnamese translation + +M po/vi.po + +commit 5d9e2b6e441b907ca9724ab3fdc02fc32fa12205 +Author: Nguyễn Thái Ngọc Duy +Date: Sat Apr 2 10:31:18 2011 +0700 + + po/vi.po: import from Damned Lies + +M po/vi.po + +commit e8d7b8fb9d128ac39e3e0f1dd6a8e96c7ce18e75 +Author: krishnababu k +Date: Fri Apr 1 13:21:53 2011 +0530 + + Updated Telugu translations done by praveen + +M po/te.po + +commit 4fb0ed4d0e7a4fb40e56af6d462467bd16d4c512 +Author: Vincent Untz +Date: Wed Mar 30 12:53:39 2011 +0530 + + layout: Show administration tools and old capplets in Other + + Right now, administration tools and capplets that used to be in the + Control Center are not accessible in any way. With this patch, they + appear in Other. While this is not perfect, this will make them + accessible until we find the right solution. + + To do this, we just exclude .desktop files that match panels from the + Control Center instead of excludings the Settings category, which is too + broad. + + https://bugzilla.gnome.org/show_bug.cgi?id=645061 + +M layout/applications.menu + +commit 7c73245fcbd5feaf66df50ee6ca5e8c9132a11aa +Author: Abduxukur Abdurixit +Date: Sun Mar 27 19:50:01 2011 +0200 + + Added UG translation + +M po/ug.po + +commit d2bd20e2e09fa4791da9a9881d77d58f31328ec9 +Author: Sense Hofstede +Date: Fri Mar 18 19:52:08 2011 +0100 + + Updated Frisian translation + +M po/fy.po + +commit de31041b34a03b1438407a5604a0d416ea791e31 +Author: Khaled Hosny +Date: Mon Mar 7 08:07:25 2011 +0200 + + Updated Arabic translation + +M po/ar.po + +commit 819557fd56cd9a867bc1ed66955b70c823292b71 +Author: Vincent Untz +Date: Sun Mar 6 23:38:58 2011 +0100 + + release: post-release bump to 2.91.92 + +M configure.ac + +commit 764cf265fac8b7ee574d045b44431fe7cfbd4852 +Author: Vincent Untz +Date: Sun Mar 6 23:38:51 2011 +0100 + + release: 2.91.91 + +M NEWS +M configure.ac + +commit 8e256fa080eadccecea4823f688974da34ed3790 +Author: Daniel Korostil +Date: Tue Mar 1 17:43:54 2011 +0200 + + Uploaded Ukranian + +M po/uk.po + +commit 715a80efbe9d2be0f329979e13073b0ece9241af +Author: Changwoo Ryu +Date: Sun Feb 27 17:17:37 2011 +0900 + + Updated Korean translation + +M po/ko.po + +commit 2966b4886b300a0bba438242fa5f44ac9f16f1c0 +Author: Vincent Untz +Date: Wed Feb 23 02:55:18 2011 +0100 + + editor: Fix to work with latest pygi + + https://bugzilla.gnome.org/show_bug.cgi?id=643019 + +M simple-editor/GMenuSimpleEditor/maindialog.py +M simple-editor/GMenuSimpleEditor/menufilewriter.py +M simple-editor/GMenuSimpleEditor/menutreemodel.py + +commit 8c66c8ad72ab512bc194cf16eadb7bf76696d817 +Author: Friedel Wolff +Date: Mon Feb 7 07:59:47 2011 +0200 + + Updated translation for Afrikaans (af) + +M po/af.po + +commit 9792ac69297dc003f5ba4d9461e15874ac1a6908 +Author: Vincent Untz +Date: Fri Feb 4 12:01:19 2011 +0100 + + build: Add ${ACLOCAL_FLAGS} to ACLOCAL_AMFLAGS + + This is used by gnome-autogen.sh, and we need it when aclocal.m4 is + to be rebuilt by make, to avoid losing some aclocal paths. + + Also, don't set ACLOCAL_AMFLAGS in configure. + +M Makefile.am +M configure.ac + +commit 6223b261d5a46ac170567ca4d5c0a00a07f64d43 +Author: Pavol Klačanský +Date: Wed Feb 2 09:39:00 2011 +0100 + + Updated Slovak translation + +M po/sk.po + +commit 668835a8d12d1dd5c88e3bc0fdd44eb6a63272e3 +Author: Vincent Untz +Date: Wed Feb 2 01:21:58 2011 +0100 + + release: post-release bump to 2.91.90 + +M configure.ac + +commit c24f0b2ada3123141049ca579d080245a923b88a +Author: Vincent Untz +Date: Wed Feb 2 01:21:07 2011 +0100 + + release: 2.91.6 + +M NEWS +M configure.ac + +commit 8c2c9b554fdbbd10769c6028a51e230e362b9b4c +Author: Vincent Untz +Date: Wed Feb 2 01:19:36 2011 +0100 + + build: Fix distcheck (missing file in POTFILES.in) + +M po/POTFILES.in + +commit b68bcd27f44ce2c494f6e3cd9695890b9c02af04 +Author: Vincent Untz +Date: Tue Feb 1 15:18:49 2011 +0100 + + layout: Drop settings.menu + + It doesn't fit in the GNOME 3 world, where we use gnomecc.menu. + +D desktop-directories/InternetAndNetwork.directory.in +D desktop-directories/LookAndFeel.directory.in +M desktop-directories/Makefile.am +D desktop-directories/Settings-System.directory.in +D desktop-directories/Settings.directory.in +D desktop-directories/X-GNOME-Menu-System.directory.in +M layout/Makefile.am +D layout/settings.menu +M po/POTFILES.in + +commit 9ff2b267f4ae08ec38a2eb49c9de7f6778708d22 +Author: Fran Diéguez +Date: Thu Jan 27 03:38:30 2011 +0100 + + QA of Galician translations + +M po/gl.po + +commit 81dcfb9938e49083eddf545ec4ad919885a041c8 +Author: Gheyret T.Kenji +Date: Thu Dec 23 19:02:04 2010 +0100 + + Added UG translation + +M po/ug.po + +commit 340f2aa06c57705e9c3c7dcc62abccfa583ed2c8 +Author: Mattias Põldaru +Date: Mon Dec 20 14:06:53 2010 +0200 + + [l10n] Updated Estonian translation + +M po/et.po + +commit 31c1309040172956c84a6123460e7254221ac2eb +Author: Nguyễn Thái Ngọc Duy +Date: Sun Dec 19 12:56:53 2010 +0700 + + po/vi.po: import some translations from Ubuntu/Maverick + +M po/vi.po + +commit faf02643fb23eb2ca0ef08b75cd77e1cae0237ab +Author: Gheyret T.Kenji +Date: Sat Nov 20 11:41:03 2010 +0100 + + Added UG translation + +M po/ug.po + +commit f921bd15a02ee98d8ad2a5301135f096e0a0621f +Author: Mahyar Moghimi +Date: Fri Nov 19 16:16:58 2010 +0330 + + Updating Persian Translation + +M po/fa.po + +commit eb15998cee9614522a4a6018249aa449a5b9ead0 +Author: Gheyret T.Kenji +Date: Sat Nov 13 22:20:28 2010 +0100 + + Added UG translation + +M po/ug.po + +commit 5062b41c9ede06f1dfaa030a72201d7cb24e28d4 +Author: Carles Ferrando +Date: Fri Oct 29 01:09:57 2010 +0100 + + Updated Catalan (Valencian) translation + +M po/ca@valencia.po + +commit cfaef75741125ab692e4154dcf9ceda8b554c26d +Author: Vincent Untz +Date: Sat Oct 9 11:48:56 2010 +0200 + + build: Update git.mk from pango + +M git.mk + +commit 9cbd519531eaacb61cc686c13dce94cc5efb2fb2 +Author: Vincent Untz +Date: Wed Oct 6 16:44:29 2010 +0200 + + editor: Remove useless import + +M simple-editor/GMenuSimpleEditor/maindialog.py + +commit 6a776912dd1c963644b0291dbf8726ef1ec116ee +Author: Vincent Untz +Date: Wed Oct 6 16:42:06 2010 +0200 + + introspection: Tell g-ir-scanner what are the prefixes + + This means we now require gobject-introspection 0.9.5. + +M configure.ac +M libmenu/Makefile.am + +commit 5092245c4fe1449bee93b8923e370d7db339f40f +Author: Vincent Untz +Date: Wed Oct 6 15:45:31 2010 +0200 + + libmenu: Do not send multiple notifications for one file change + + We emit notifications in the idle loop, which enables us to compress + multiple notifications: this way, we can check if there is already a + notification for a directory. + + We do this at the CachedDir level because it enables us to get one + notification for a file rename, for example. But we also need it at the + MenuLayout level to be sure to compress notifications accross multiple + directories. It could be argued that we only need the latter, but I like + it this way :-) + + https://bugzilla.gnome.org/show_bug.cgi?id=172046 + +M libmenu/entry-directories.c +M libmenu/menu-layout.c + +commit 2e394806107ccbf0a3b822ab299042ea441c721e +Author: Vincent Untz +Date: Wed Oct 6 12:01:53 2010 +0200 + + editor: Forgot to remove "import pygtk" :-) + +M simple-editor/GMenuSimpleEditor/main.py + +commit adb08fee8c3677e539b9d1ca8fab9be1c4bf14b9 +Author: Vincent Untz +Date: Wed Oct 6 11:58:31 2010 +0200 + + editor: Remove has_separator property + + It's gone in new GTK+. + +M simple-editor/gmenu-simple-editor.ui + +commit cd550ce70f391ba3511800a1e100213bc3ff6c45 +Author: Vincent Untz +Date: Wed Oct 6 11:56:48 2010 +0200 + + editor: port to pygobject-based introspection bindings + + https://bugzilla.gnome.org/show_bug.cgi?id=626256 + +M simple-editor/GMenuSimpleEditor/main.py +M simple-editor/GMenuSimpleEditor/maindialog.py +M simple-editor/GMenuSimpleEditor/menufilewriter.py +M simple-editor/GMenuSimpleEditor/menutreemodel.py + +commit ce014ee2884f134bcd9764043a58eb32294062ed +Author: Kikongo Translation Team +Date: Mon Sep 27 13:27:43 2010 +0200 + + Added Kikongo translation + +M po/LINGUAS +A po/kg.po + +commit 15056451271dc5faf31ee2161cdb5696773275c5 +Author: Vincent Untz +Date: Mon Sep 27 12:58:48 2010 +0200 + + release: post-release bump to 2.30.5 + +M configure.ac + +commit 9c269a6850ca8c97edc1e41419d8f7778574c84f +Author: Vincent Untz +Date: Mon Sep 27 12:58:40 2010 +0200 + + release: 2.30.4 + +M NEWS +M configure.ac + +commit 90a58de1f3a7381b26206be39624a3d40d8ffb07 +Author: Vincent Untz +Date: Mon Sep 27 12:29:45 2010 +0200 + + misc: Rename --enable-deprecations to --enable-deprecation-flags + + This is a better name for this configure option, since it's really about + enabling the use of the deprecation flags, not allowing the use of + deprecated API. + +M configure.ac + +commit f1da5abb7c1783134cfa485b323aedb4f5bc2465 +Author: Vincent Untz +Date: Mon Sep 27 12:04:53 2010 +0200 + + introspection: Associate .gir with pkg-config file + +M libmenu/Makefile.am + +commit fbd3e2c9f8fb8de4c975367f30b84121478da7d2 +Author: Vincent Untz +Date: Thu Sep 23 19:40:47 2010 +0200 + + libmenu: Clear cache of desktop entries set when files are added/removed + + When installing or removing an application, we get a notification of + change. However, the menu tree still contained the same content. + + We were simply not clearing the cache containing the list of desktop + entries when a file got added or removed, meaning that we always got the + same list of desktop entries. + + (Note that it didn't affect gnome-panel, it's unclear why) + + https://bugzilla.gnome.org/show_bug.cgi?id=630410 + +M libmenu/entry-directories.c + +commit 54af0ebf8863955c0641670343ac57900092f104 +Author: Torstein Winterseth +Date: Wed Sep 22 14:32:00 2010 +0200 + + Updated Norwegian Nynorsk translation. + +M po/nn.po + +commit b8874d1d36c9b7d448b4958af5b71f2fa3606984 +Author: Vincent Untz +Date: Mon Sep 20 17:39:28 2010 +0200 + + build: Update all Makefile.am to more recent standards + +M Makefile.am +M desktop-directories/Makefile.am +M layout/Makefile.am +M libmenu/Makefile.am +M python/Makefile.am +M simple-editor/GMenuSimpleEditor/Makefile.am +M simple-editor/Makefile.am +M util/Makefile.am + +commit 7060e3c48a391c0fa96e57879575e333c3a54e67 +Author: Vincent Untz +Date: Mon Sep 20 17:37:50 2010 +0200 + + misc: Update instructions for commit messages + + We're switching to "tag:" instead of "[tag]". + +M ChangeLog + +commit 22642577f7324349b3681bc58e32004820236066 +Author: Takayuki KUSANO +Date: Mon Sep 20 18:06:25 2010 +0900 + + Updated Japanese translation + +M po/ja.po + +commit feeb6b4d64f2d3e5e0f284aa3e1bfd625a627b1f +Author: Daniel Martinez +Date: Sat Sep 18 20:45:19 2010 +0200 + + Updated Aragonese translation + +M po/an.po + +commit 6010094c40eb65965622141cb09fec3a0c8b31d3 +Author: Baurzhan Muftakhidinov +Date: Wed Sep 15 12:55:46 2010 +0600 + + Updated Kazakh translation + +M po/kk.po + +commit fe36b5b5371404ea96a46ac6d6b4a003f54d9828 +Author: Vincent Untz +Date: Tue Sep 14 14:40:07 2010 +0200 + + release: post-release bump to 2.30.4 + +M configure.ac + +commit 721b241e2c5449003238d333d03063fdfe7b3aa1 +Author: Vincent Untz +Date: Tue Sep 14 14:39:56 2010 +0200 + + release: 2.30.3 + +M NEWS +M configure.ac + +commit 328cff2e2951e817a6e999b3a3a8b563f07cd778 +Author: Vincent Untz +Date: Wed Sep 8 19:23:00 2010 +0200 + + [misc] Update AUTHORS, HACKING, MAINTAINERS, README + + Nothing new, but make sure this is up-to-date. + + Also tweak a bit autogen.sh and Makefile.am to make them look similar in + all my modules. + +M AUTHORS +M HACKING +M MAINTAINERS +M Makefile.am +M README +M autogen.sh +M gnome-menus.doap + +commit 4cfd7db5d1ac221bc1397854a04dc40a654f1188 +Author: Vincent Untz +Date: Wed Sep 8 17:48:37 2010 +0200 + + [misc] Update license files to latest text + + Note that this doesn't change the license. The license text was updated + for the latest FSF address, for example. + +M COPYING +M COPYING.LIB + +commit bd02ce500383aa04914fda89900af93cdb33809d +Author: Vincent Untz +Date: Wed Sep 8 17:09:44 2010 +0200 + + [build] Rename configure.in to configure.ac + +M autogen.sh +R100 configure.in configure.ac + +commit 8b14b78f279bad26580387b69a3274ba1197f66b +Author: Dirgita +Date: Wed Aug 4 10:36:10 2010 +0700 + + Updated Indonesian translation + +M po/id.po + +commit d524140a28c92719bdcc81df207c38c3c96052c0 +Author: Pablo Castellano +Date: Tue Aug 3 19:57:18 2010 +0200 + + Update git.mk from pango + +M git.mk + +commit d0cbd37e93980bae0781704765c56009913b0204 +Author: Gheyret Tohti +Date: Tue Aug 3 13:54:06 2010 +0200 + + Updated Uyghur translation + +M po/ug.po + +commit 784b664c760d655ae306d2d5d718231a1d09333f +Author: Fran Diéguez +Date: Tue Jul 20 12:10:56 2010 +0200 + + Updated Galician translations + +M po/gl.po + +commit 1100aed1ec5863ebfa80e51ca7f07e2b964459dd +Author: Sense Hofstede +Date: Sun Jul 18 00:34:23 2010 +0200 + + Fix forgotten copyright notices in the Frisian translations. + +M po/fy.po + +commit e29c70ada73e7f029423361da318685d517cbd2b +Author: Sense Hofstede +Date: Sun Jul 18 00:31:14 2010 +0200 + + Updated Frisian translation and added it to the LINGUAS file + +M po/LINGUAS +A po/fy.po + +commit ce67a8b1603bb878131c23b625cf48b44ea8c477 +Author: Reuben Potts +Date: Wed Jul 7 13:51:56 2010 +0200 + + Added Manx translation + +M po/LINGUAS +A po/gv.po + +commit 5e30e13fd5923befda0372473f64a81b98b35f92 +Author: Baurzhan Muftakhidinov +Date: Fri Jul 2 14:40:12 2010 +0300 + + Updated Kazakh translation for gdm + +M po/kk.po + +commit 2054996af842e6df1726e58d81a569f03e30d75f +Author: Vincent Untz +Date: Wed Jun 30 12:36:26 2010 +0200 + + [editor] Better fix for XDG_MENU_PREFIX support in editor + + The previous fix was partly wrong, because the menu file referenced in + the created user menu file was not using the prefix. + + So instead of letting the library handle XDG_MENU_PREFIX, we handle it + ourselves everywhere. + +M simple-editor/GMenuSimpleEditor/menufilewriter.py +M simple-editor/GMenuSimpleEditor/menutreemodel.py + +commit ac425c5ac9e386fd171801496503d591d5c5ade2 +Author: Vincent Untz +Date: Wed Jun 30 12:31:33 2010 +0200 + + [editor] Respect XDG_MENU_PREFIX when writing user menu file + + https://bugzilla.gnome.org/show_bug.cgi?id=623197 + +M simple-editor/GMenuSimpleEditor/menufilewriter.py + +commit e8a7198465e090f3e35fc1ac328800909574801f +Author: Kristjan Schmidt +Date: Wed Jun 23 17:15:51 2010 +0200 + + Updated Esperanto translation + +M po/eo.po + +commit 0e25cc961e1814cb2b6e6e2b9172a649f5542a50 +Author: Vincent Untz +Date: Tue Jun 22 03:57:14 2010 +0200 + + [release] post-release bump to 2.30.3 + +M configure.in + +commit 3ec065b74712c99d1cdde955e864f203afccb41b +Author: Vincent Untz +Date: Tue Jun 22 03:57:06 2010 +0200 + + [release] 2.30.2 + +M NEWS +M README +M configure.in + +commit 320453668ea791f1d7c9f11d84363bfbae63fb25 +Author: Yaakov Selkowitz +Date: Tue Jun 15 23:00:19 2010 -0500 + + [build] Do not dist gir_DATA + + GIR files contain a shared-library attribute which varies per platform, + and therefore must not be disted; see bug 621611 for rationale. + + https://bugzilla.gnome.org/show_bug.cgi?id=621724 + +M libmenu/Makefile.am + +commit 612aeb7cd40f57757a733d0805a52f6a6a000152 +Author: Fran Diéguez +Date: Sun Jun 13 19:32:21 2010 +0200 + + Updated Galician translations + +M po/gl.po + +commit 7e1332e718118f16ab451a622d40ec3afde79c2b +Author: Matej Urbančič +Date: Fri May 21 22:24:04 2010 +0200 + + Updated Slovenian translation + +M po/sl.po + +commit f93cca66736b876b8739fb900b51c82e22c7aead +Author: Thomas Thurman +Date: Sun May 16 18:04:30 2010 -0400 + + Updated Shavian translation + +M po/en@shaw.po + +commit 0928101b34ac6bc6e7da5c8bd52d39af43a9f38f +Author: Thomas Thurman +Date: Wed May 12 18:41:06 2010 -0400 + + Updated Shavian transliteration + +M po/en@shaw.po + +commit a66c03d1771a15ffe054e9e64ff5183bda951bcf +Author: Peteris Krisjanis +Date: Sat Apr 24 22:25:22 2010 +0300 + + Updated Latvian translation. + +M po/lv.po + +commit 498f225b6f9a4a146a534bacdc8b8b665cf71d95 +Author: Daniel Martinez +Date: Sat Apr 24 11:21:36 2010 +0200 + + Added Aragonese translation + +M po/LINGUAS +A po/an.po + +commit 5b8ae6e132b31a0a0f14021ef6cf2fc2794bc87e +Author: Carles Ferrando +Date: Wed Apr 21 23:31:15 2010 +0200 + + Updated Catalan (Valencian) translation + +M po/ca@valencia.po + +commit 7408013978ba989af175f2ca17526fec4eea49c6 +Author: Shankar Prasad +Date: Wed Apr 21 11:50:02 2010 +0530 + + Updated Kannada translations + +M po/kn.po + +commit 20d4732caf167f54c376d34184ca7fdcf7d834b7 +Author: Jordi Serratosa +Date: Fri Apr 2 17:41:22 2010 +0200 + + Fixes to Catalan translation + +M po/ca.po + +commit a08bbc660cd320245ea4f3ccab4c33261e83dc7b +Author: Christian Kirbach +Date: Wed Mar 31 20:11:39 2010 +0200 + + Updated German translation + +M po/de.po + +commit ffbddbf28289a4ac72b284e3cc6f4db04cf0b43e +Author: Wouter Bolsterlee +Date: Tue Mar 30 18:01:20 2010 +0200 + + Dutch translation updated by Wouter Bolsterlee + +M po/nl.po + +commit 8d9f3db359acde4395645a0ea95c48567e5d79f4 +Author: Reşat SABIQ +Date: Tue Mar 30 00:46:58 2010 -0500 + + Updated Crimean Tatar (Crimean Turkish) translation + +M po/crh.po + +commit fe6b717216cb93b05abc547fd11c550d9d4f4f4e +Author: Vincent Untz +Date: Tue Mar 30 02:02:15 2010 +0200 + + [release] post-release bump to 2.30.1 + +M configure.in + +commit ada97a6e81d2f282796938269da7280dc5a8c83f +Author: Vincent Untz +Date: Tue Mar 30 02:02:06 2010 +0200 + + [release] 2.30.0 + +M NEWS +M README +M configure.in + +commit 2d0068dc1859b3adf21cfcc47a23aa00fc7d4502 +Author: Kostas Papadimas +Date: Sun Mar 28 10:01:00 2010 +0300 + + Updated Greek translation + +M po/el.po + +commit b7d6ee4f3f252b57a85787043fa3506d1640fa05 +Author: Inaki Larranaga Murgoitio +Date: Tue Mar 23 15:10:22 2010 +0100 + + Updated Basque language + +M po/eu.po + +commit e74ebd36d267033054e6bf15851da0f88d12eb3a +Author: Badral Sanligiin +Date: Tue Mar 23 02:57:26 2010 +0100 + + Updated Mongolian translation + +M po/mn.po + +commit 1bec0581f480c3e8b485be1453de389083feff5c +Author: David Planella +Date: Wed Mar 17 08:24:02 2010 +0100 + + Updated Catalan translation as per fixes discussed on the translation mailing list + +M po/ca.po + +commit 1253c82ed7ba774cbad7794c06d31249bf591c37 +Author: Vincent Untz +Date: Mon Mar 15 09:55:46 2010 +0100 + + [libmenu] Never ignore Menuname nodes from DefaultLayout + + If a Menuname node applies to a subdirectory that appears because of + inlining, we generally want to ignore it. However, if this Menuname node + comes from a DefaultLayout, then it should really be applied. + +M libmenu/gmenu-tree.c + +commit 7dba2d5302b539e24131e669e2db26f3c16e1547 +Author: Vincent Untz +Date: Mon Mar 15 09:27:44 2010 +0100 + + [libmenu] Fix layout processing for Menuname nodes + + Because of the scope of a variable, no submenu were matching Menuname + nodes in the layouts, making all the Menuname nodes non-working. + + https://bugzilla.gnome.org/show_bug.cgi?id=612585 + +M libmenu/gmenu-tree.c + +commit c48304179db938f7336223f99ead95dda6044f7a +Author: Changwoo Ryu +Date: Sat Mar 13 23:55:09 2010 +0900 + + Updated Korean translation + +M po/ko.po + +commit 87f03667154f5c77548506218c57020e37d04c81 +Author: Vincent Untz +Date: Tue Mar 9 14:06:21 2010 +0100 + + [build] Add configure summary + +M configure.in + +commit 9fbaa3d7f76ffb4076216d231acf39da0331e0bc +Author: Vincent Untz +Date: Mon Mar 8 14:49:09 2010 +0100 + + [release] post-release bump to 2.30.0 + +M configure.in + +commit 076f8c42ce3e46c3b5cb761a3cd65c6f1e0a55a2 +Author: Vincent Untz +Date: Mon Mar 8 14:49:01 2010 +0100 + + [release] 2.29.92 + +M NEWS +M README +M configure.in + +commit d50ff15f43bc2311fbd06bd9664ae9571fd550ea +Author: Vincent Untz +Date: Mon Mar 8 14:43:18 2010 +0100 + + [libmenu] Add gobject-introspection support + + https://bugzilla.gnome.org/show_bug.cgi?id=598406 + +M Makefile.am +M autogen.sh +M configure.in +M libmenu/Makefile.am + +commit d3135a15585afcb8d597fae05beadfaf1c743238 +Author: Vincent Untz +Date: Thu Mar 4 17:55:40 2010 +0100 + + [misc] Do not call bindtextdomain() and friends in test-menu-spec.c + + We decided to not make this application translatable in commit + 25c10ed7, so we don't need those calls. + +M util/test-menu-spec.c + +commit 57e825050cf8eafb970c8ddd2dc9c66885d25ce3 +Author: Nikos Charonitakis +Date: Thu Feb 25 00:52:48 2010 +0200 + + Updated Greek translation + +M po/el.po + +commit 274a633ddb053a70e26278308a6fb52ef933e608 +Author: Pavol Klačanský +Date: Tue Feb 23 17:55:32 2010 +0100 + + Updated Slovak translation + +M po/sk.po + +commit 41d1ce2320c26dce1677e0624c36119ec88e6823 +Author: Vincent Untz +Date: Mon Feb 22 20:18:20 2010 +0100 + + [release] post-release bump to 2.29.92 + +M configure.in + +commit 5eaf95e2c9e7755332a7c586eef34726244290dd +Author: Vincent Untz +Date: Mon Feb 22 20:18:05 2010 +0100 + + [release] 2.29.91 + +M NEWS +M README +M configure.in + +commit ca24481c06829c7382d20ac7ee2283e22b908451 +Author: Vincent Untz +Date: Mon Feb 22 12:11:57 2010 +0100 + + [misc] Add translator comment for Personal + + https://bugzilla.gnome.org/show_bug.cgi?id=610661 + +M desktop-directories/Personal.directory.in + +commit 25c10ed70c8125516537b3b731dff1c559bcab94 +Author: Vincent Untz +Date: Mon Feb 22 12:06:48 2010 +0100 + + [misc] Do not make the string test-menu-spec.c translatable + + This is only a test program that doesn't get installed, so there's no + point in making translators work on the strings there. + + https://bugzilla.gnome.org/show_bug.cgi?id=609441 + +M po/POTFILES.in +A po/POTFILES.skip +M util/test-menu-spec.c + +commit 8f52b3c312da587fb29590559cb50370787f6116 +Author: Fran Diéguez +Date: Mon Feb 15 18:44:37 2010 +0100 + + Updated Galician Translation + +M po/gl.po + +commit ef1383eda9480c96ac2e5d958342c98ab1be41cb +Author: Torstein Adolf Winterseth +Date: Thu Jan 28 12:10:06 2010 +0100 + + Updated Norwegian Nynorsk translation + +M po/nn.po + +commit a0a398d77aa2ea28d267f106880e0df1417d8fab +Author: Vincent Untz +Date: Wed Jan 27 16:47:53 2010 +0100 + + [release] post-release bump to 2.29.90 + +M configure.in + +commit 2c37a7cbdd9d6f7e9f1ccfb0d2979da897e9668e +Author: Vincent Untz +Date: Wed Jan 27 16:47:41 2010 +0100 + + [release] 2.29.6 + +M NEWS +M README +M configure.in + +commit a462be862680ee3051eef3ca238f5bc405640e16 +Author: Jamil Ahmed +Date: Sun Jan 24 23:15:18 2010 +0600 + + Updated Bengali translation + +M po/bn.po + +commit fdafd8c07a65e65fdf6f2e7d358b7eb2b14d01eb +Author: Aron Xu +Date: Sun Jan 24 15:34:41 2010 +0800 + + Update Simplified Chinese translation. + +M po/zh_CN.po + +commit 89309a8bb1e76209e147180a3fe2fd15307d6012 +Author: Vincent Untz +Date: Fri Jan 15 00:02:07 2010 +0100 + + [libmenu] Do not count non-inlining submenus as inlining with header + + When computing if a submenu should be inlined, we use the number of + items in the subsubmenus. It was always assuming that inlining with a + header would be used, because of misuse of the will_inline_header field. + +M libmenu/gmenu-tree.c + +commit 4b51a77fcbc1a2c3e94945c4b92a65559f7e5996 +Author: Vincent Untz +Date: Thu Jan 14 23:30:47 2010 +0100 + + [libmenu] Support inline alias of an inline alias + +M libmenu/gmenu-tree.c + +commit cd1d0a199110ca94266c3e8b928e61d12cd2aac3 +Author: Vincent Untz +Date: Thu Jan 14 23:26:49 2010 +0100 + + [libmenu] Add real support for inline aliases during layout processing + + It looks like the inline aliases were not really tested before... + + All the processing of the layout info (merge_subdir_by_name, + merge_entry_by_id, merge_subdirs, merge_entries, + merge_subdirs_and_entries) was completely ignoring the fact that some + subdirs or entries might be aliases, and therefore need a different + processing. + + This is now done correctly, and we have a merge_alias() function to do + the right thing when an alias is detected. + + Also, this simplifies quite a bit the sorting of subdirs or entries + since we always need to check the types of the items we compare, so we + can get rid of gmenu_tree_directory_compare and + gmenu_tree_entry_compare. + +M libmenu/gmenu-tree.c + +commit cda451cec61345d357cbc26e5a5dfd102f476ea5 +Author: Vincent Untz +Date: Thu Jan 14 21:29:42 2010 +0100 + + [libmenu] Fix miscalulation for inlining when inline_header = true + + When inline_header = true for a subdirectory, the length of the content + of the parent directory was miscomputed: instead of adding the length of + the subdirectory, it was just replacing the current length with the + length of the subdirectory. + + Also fix a typo in a comment + +M libmenu/gmenu-tree.c + +commit b6dc7a2443ef3d8fef1a1175532124882e95d03b +Author: krishnababu k +Date: Wed Dec 2 14:36:36 2009 +0530 + + Updated telugu translation + +M po/te.po + +commit 696867378e99b410c1803412c7f894c99b386f64 +Author: Thomas Thurman +Date: Sat Oct 31 22:26:02 2009 -0400 + + Shavian translation + +M po/LINGUAS +A po/en@shaw.po + +commit 4c76410de345e06b879c85fa015659286f42c240 +Author: Nils-Christoph Fiedler +Date: Thu Oct 22 19:47:54 2009 +0200 + + Updated Low German translation + +M po/nds.po + +commit 5f2c53bd3fbd4b39707609955f445c96eb9b5fb3 +Author: Reşat SABIQ +Date: Wed Oct 21 00:29:46 2009 -0500 + + Update for Crimean Tatar (Crimean Turkish) translation + +M po/crh.po + +commit bde6cabd440da28d25983e762ed6b71849da918f +Author: Leonid Kanter +Date: Tue Oct 20 12:06:06 2009 +0300 + + Updated Russian translation + +M po/ru.po + +commit 72f4ede1ab9bbcfa29affda98e1fcfecdfcf87ef +Author: Gil Forcada +Date: Tue Oct 6 17:36:50 2009 +0200 + + Updated Catalan translation + +M po/ca.po + +commit e1c00f83d903581233e7e15655916b4079b3e500 +Author: Sveinn í Felli +Date: Fri Oct 2 06:17:30 2009 +0000 + + Updated Icelandic translation + +M po/is.po + +commit 5aff8025f08164699721084857a5321ed2a45abe +Author: Vincent Untz +Date: Thu Oct 1 13:25:58 2009 +0200 + + [release] post-release bump to 2.28.1 + +M configure.in + +commit 1d899c5b7bafb9ea01e0f407df7acb2b0d6432f4 +Author: Vincent Untz +Date: Thu Oct 1 13:25:50 2009 +0200 + + [release] 2.28.0.1 + +M NEWS +M README +M configure.in + +commit 9d8e52a9a3e495f1e8e50a61f89510a03a4d31c9 +Author: Vincent Untz +Date: Thu Oct 1 13:21:45 2009 +0200 + + [build] Link the python binding to libpython + + Thanks to Frederic Crozat for spotting this. + +D acinclude.m4 +A m4/python.m4 +M python/Makefile.am + +commit 117273842932a6fb24143926513474c28e3595b4 +Author: Frederic Crozat +Date: Tue Sep 29 15:35:10 2009 +0200 + + [libmenu] Make sure to use a value when sorting items during a merge + + The name variable was not assigned any value... + +M libmenu/gmenu-tree.c + +commit 5713f8e66b0fe3791a57555ebd12ddb076176cd9 +Author: Vincent Untz +Date: Mon Sep 21 18:18:27 2009 +0200 + + [release] post-release bump to 2.28.1 + +M configure.in + +commit ddc11bf8f9d279cc2578c074ccb2c85a837e4ba7 +Author: Vincent Untz +Date: Mon Sep 21 18:18:18 2009 +0200 + + [release] 2.28.0 + +M NEWS +M README +M configure.in + +commit 87d43c2dd053ae3cb16e53341db2cb60ae348cf9 +Author: Amitakhya Phukan +Date: Mon Sep 21 11:30:34 2009 +0530 + + Updating Assamese translations + +M po/as.po + +commit a44cc5ae04856e05300dc189cab1a9e0ea0d7697 +Author: Amitakhya Phukan +Date: Mon Sep 21 11:29:14 2009 +0530 + + Updating Assamese translations + +M po/as.po + +commit e6a5afde9de461ccbc45556ca7720d0b9e1c63a8 +Author: Shankar Prasad +Date: Mon Sep 21 10:18:37 2009 +0530 + + Updated Kannada(kn) translation + +M po/kn.po + +commit dc324804a5105c78f5fded62232d7f83976dec2d +Author: Petr Kovar +Date: Mon Sep 21 04:22:31 2009 +0200 + + Updated Czech translation + +M po/cs.po + +commit c7045e1d8937e7730cde99e87dce31ccb3474d76 +Author: Rajesh Ranjan +Date: Sun Sep 20 12:35:22 2009 +0530 + + maithili update, translated by Sangeeta Kumari + +M po/mai.po + +commit 3e5f8caa3f659f4a3ea7730ea79af98c639379ac +Author: Daniel Nylander +Date: Sat Sep 19 10:25:52 2009 +0200 + + Updated Swedish translation + +M po/sv.po + +commit 8af7dc29b7ccf039422d70c38f3927d1e493e726 +Author: Luca Ferretti +Date: Fri Sep 18 15:08:11 2009 +0200 + + Added Italian translation + +M po/it.po + +commit b46ad5eb16a78883c09ebddcf09634368052ac72 +Author: krishnababu k +Date: Thu Sep 17 21:43:06 2009 +0530 + + Updated Telugu Translation + +M po/te.po + +commit 9f35303a5dbb43c2f024d001de95dd58402e4519 +Author: Maxim V. Dziumanenko +Date: Thu Sep 17 13:56:16 2009 +0300 + + Added Ukrainian translation + +M po/uk.po + +commit 9f656630180c4328bfe95e325c4e2afbf61a22d4 +Author: Niels-Christoph Fiedler +Date: Wed Sep 16 18:33:04 2009 +0200 + + Updated German translation + +M po/nds.po + +commit 3989f3da21c0b72c2161c4476620e992ca533a02 +Author: Adi Roiban +Date: Wed Sep 16 16:01:54 2009 +0300 + + Updated Romanian translation + +M po/ro.po + +commit a9f05d49e0e31e5cd7ec6ca6e835b3de438f3033 +Author: Rajesh Ranjan +Date: Wed Sep 16 13:47:43 2009 +0530 + + hindi update by Rajesh Ranjan + +M po/hi.po + +commit 19d5f458acc6001cee1446caa39163f3369af273 +Author: Gintautas Miliauskas +Date: Tue Sep 15 02:11:17 2009 +0300 + + Updated Lithuanian translation. + +M po/lt.po + +commit 1102f0542b4df782708eb857e638bfd5966aa7dc +Author: Ask H. Larsen +Date: Sun Sep 13 03:23:56 2009 +0200 + + Updated Danish translation + +M po/da.po + +commit da99034888d5191a27e3029cbad6c73c7e52e10a +Author: Philip Withnall +Date: Sat Sep 12 23:35:46 2009 +0100 + + Updated British English translation + +M po/en_GB.po + +commit 67c6af706c287319a7d5c73cf86d5209fd89f44f +Author: Chao-Hsiung Liao +Date: Sun Sep 13 06:32:22 2009 +0800 + + Updated Traditional Chinese translation(Hong Kong and Taiwan) + +M po/zh_HK.po +M po/zh_TW.po + +commit ebd7a9aaa45fb4b43ac7e5b35e45412ddd55dc9d +Author: Ani +Date: Sat Sep 12 18:32:13 2009 +0530 + + Updaeted Malayalam Translations + +M po/ml.po + +commit 37d761d18917ba9167c2be84c8fd5c6948ff3139 +Author: Matej Urbančič +Date: Sat Sep 12 10:31:29 2009 +0200 + + Updated Slovenian translation + +M po/sl.po + +commit 333ce6b4509b8137c085171b730b92f0d2870c7b +Author: Sandeep Shedmake +Date: Fri Sep 11 19:35:53 2009 +0530 + + Updated Marathi Translations + +M po/mr.po + +commit 068006c9639123b7c7fe47855f5e1801c78c7dd0 +Author: A S Alam +Date: Thu Sep 10 06:46:10 2009 +0530 + + updating for Punjabi + +M po/pa.po + +commit 7411701b1e806aa8f283ea9868d165ab150ac2c7 +Author: Baris Cicek +Date: Thu Sep 10 00:25:54 2009 +0300 + + Updated Turkish translation. + +M po/tr.po + +commit eb43a84b5a1b55f1743e9d0b9aee036a72962b17 +Author: Vincent Untz +Date: Wed Sep 9 02:10:28 2009 +0200 + + [build] Generate bzip2 tarballs and use foreign automake option + +M configure.in + +commit 4d35bde7e67f6b59c108ebc8615a342cb93cbd17 +Author: Vincent Untz +Date: Wed Sep 9 02:08:49 2009 +0200 + + [release] post-release bump to 2.28.0 + +M configure.in + +commit 908771796bad88ce49158f951bd7ca40c51b256b +Author: Vincent Untz +Date: Wed Sep 9 02:08:40 2009 +0200 + + [release] 2.27.92 + +M NEWS +M README +M configure.in + +commit f1a8145230fc24f47f4818f39839aafe5b911c37 +Author: Takayuki KUSANO +Date: Tue Sep 8 22:14:00 2009 +0900 + + Updated Japanese translation + +M po/ja.po + +commit 0178e1acc5aeca2e05ddfcf67e8f03e8ee82eff6 +Author: Vincent Untz +Date: Tue Sep 8 00:20:56 2009 +0200 + + [misc] Bump version to 2.27.92 + +M configure.in + +commit db81a7f1194e435b538ab6710d752276fb1c7135 +Author: Vincent Untz +Date: Mon Sep 7 23:28:56 2009 +0200 + + [editor] Use display name instead of name + +M simple-editor/GMenuSimpleEditor/menutreemodel.py + +commit d3fdd0ba4133c66e670561bca5b1bf4dd1a8feea +Author: Vincent Untz +Date: Mon Sep 7 23:13:22 2009 +0200 + + [libmenu,python] Add gmenu_tree_get_sort_key()/gmenu_tree_set_sort_key() + + This is needed for users of gnome-menus that display applications by + strings obtained via get_display_name(), since the order of entry + changes if strings are different. + + The default is to keep the sort order by name. At the moment, the only + other option is to use a sort by display name. + + Bindings for python are added too. + +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h +M python/gmenu.c + +commit 4b007ffddf4cfd58d508a8c24d49ca8724753bb0 +Author: Vincent Untz +Date: Mon Sep 7 23:19:49 2009 +0200 + + [python] Fix accidental swap between display_name and generic_name + +M python/gmenu.c + +commit 22b78e8b3cbc5d68638a1729728b682a4ce1d10b +Author: Vincent Untz +Date: Mon Sep 7 19:21:12 2009 +0200 + + [python] Add bindings for get_generic_name() and get_display_name() + +M python/gmenu.c + +commit e539c64e99a000b3a89a53bb5a6233a6b16ed96a +Author: Vincent Untz +Date: Mon Sep 7 19:19:41 2009 +0200 + + [libmenu] Add gmenu_tree_entry_get_display_name() API + + This API returns the content of the X-GNOME-FullName key if available, + and fallbacks to Name. + +M libmenu/desktop-entries.c +M libmenu/desktop-entries.h +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h + +commit 9767b9094ef22034be948f289473726e922e1d28 +Author: Gabor Kelemen +Date: Mon Sep 7 02:30:34 2009 +0200 + + Hungarian translation updated + +M po/hu.po + +commit c2e0a3cedf6ceded675e6d6b8368d98dc8e64716 +Author: Rodrigo L. M. Flores +Date: Sun Aug 30 19:56:20 2009 -0300 + + Updated Brazilian Portuguese mailing list address. + +M po/pt_BR.po + +commit e5c016f25d380332029e4b4d78ebc795fcc06e27 +Author: Mario Blättermann +Date: Sun Aug 30 14:54:33 2009 +0200 + + Updated German translation + +M po/de.po + +commit aadd320b39ccd5a713b2fc9f5fb85d894141c61f +Author: Miloš Popović +Date: Sat Aug 29 14:43:42 2009 +0200 + + Updated Serbian translation + +M po/sr.po +M po/sr@latin.po + +commit 900767c9056455409c0ad674d17959b50b1a0f49 +Author: Runa Bhattacharjee +Date: Fri Aug 28 12:30:47 2009 +0530 + + Updated Bengali India Translations + +M po/bn_IN.po + +commit 66913548e742cc1a8c138fccce9302b88fe5eae9 +Author: Duarte Loreto +Date: Thu Aug 27 08:58:37 2009 +0100 + + Updated Portuguese translation + +M po/pt.po + +commit 4dbf30628a5f29f8eb0b85c65ffd5b9b0caa5177 +Author: Claude Paroz +Date: Wed Aug 26 21:33:09 2009 +0200 + + Updated French translation + +M po/fr.po + +commit bcf479be8e2b92c26becafe004a0f1b1c62af679 +Author: Inaki Larranaga Murgoitio +Date: Wed Aug 26 18:27:07 2009 +0200 + + Updated Basque language + +M po/eu.po + +commit 5ee416dd4c7b0ddb11c7c38581bf293b3b9352dd +Author: Luca Ferretti +Date: Wed Aug 26 09:31:11 2009 +0200 + + Updated Italian translation + +M po/it.po + +commit a02a132ed3b18f156bf2c7e965c3075685f72759 +Author: Tomasz Dominikowski +Date: Tue Aug 25 19:26:18 2009 +0200 + + Updated Polish translation + +M po/pl.po + +commit 6f55d2289a2a30d03db2bf40c31f79474ff952c0 +Author: Frédéric Péters +Date: Sat Aug 22 13:44:25 2009 +0200 + + Remove deprecated Encoding key from desktop file + +M simple-editor/gmenu-simple-editor.desktop.in + +commit 4771bdb6af1b29f10ba39706cb2b63433d1f0480 +Author: Andre Klapper +Date: Mon Aug 24 23:01:03 2009 +0200 + + Change nds_NFE to nds as discussed on irc + +M po/LINGUAS +R100 po/nds_NFE.po po/nds.po + +commit 539360b4cfa0c26b884c9811acd15d943a7574a0 +Author: Sweta Kothari +Date: Mon Aug 24 15:37:44 2009 +0530 + + Updated Gujarati Translations + +M po/gu.po + +commit 28501f2afe901298d74760362f47bb7f67107699 +Author: Changwoo Ryu +Date: Sun Aug 23 07:27:42 2009 +0900 + + Update Korean translation + +M po/ko.po + +commit b2b842dbd5fd8687562d0961ad62391d5f72004f +Author: Mario Blättermann +Date: Sat Aug 22 15:09:52 2009 +0200 + + Added Low German to LINGUAS + +M po/LINGUAS + +commit 4694660b8ebac75d9c72ef1de5654bd031a9062e +Author: Mario Blättermann +Date: Sat Aug 22 15:07:10 2009 +0200 + + Added Low German translation + +A po/nds_NFE.po + +commit 9dc36cf2b049dbca649c92911f29644b073f031b +Author: Khaled Hosny +Date: Thu Aug 20 21:19:07 2009 +0300 + + Updated Arabic translation + +M po/ar.po + +commit 895ba3af6f107e61cc6a4414746d94ba3df38a8f +Author: Alexander Shopov +Date: Fri Aug 14 07:48:02 2009 +0300 + + Updated Bulgarian translation + +M po/bg.po + +commit 761e44b26fd2315f8823d3b5f2347346f1ef9eb0 +Author: Manoj Kumar Giri +Date: Thu Aug 13 20:09:24 2009 +0530 + + Updated Oriya Translation + +M po/or.po + +commit c49f4bb691fd51039add261b9a19c5b45bbbd0d5 +Author: Antón Méixome +Date: Wed Aug 12 13:25:27 2009 +0200 + + Updated Galician translation + +M po/gl.po + +commit 3ed6cedb4154fa399f48b036088fa703291d28d9 +Author: Krix Apolinário +Date: Wed Aug 12 00:01:59 2009 -0300 + + Updated Brazilian Portuguese translation. + +M po/pt_BR.po + +commit 3f85e31f5fbb7e3e77ed8459af8b18f071ed3767 +Author: Kjartan Maraas +Date: Tue Aug 4 18:15:34 2009 +0200 + + Updated Norwegian bokmål translation. + +M po/nb.po + +commit ed5f86151fd103c446535bc8d1ed3d80604e2899 +Author: Seán de Búrca +Date: Wed Jul 29 17:45:34 2009 -0600 + + Updated Irish translation + +M po/ga.po + +commit 6b5cd5918a318712237b5fc511fe122a1229a29b +Author: Denis Arnaud +Date: Wed Jul 29 12:13:13 2009 +0200 + + Updated breton translation + +M po/br.po + +commit c6f657e60f70bb848b70b9462591a38c973b4010 +Author: drtvasudevan +Date: Wed Jul 29 12:12:26 2009 +0530 + + Updated Tamil translation + +M po/ta.po + +commit 458924440a7f69c64272ddeb9fbe1999ac70bbcd +Author: drtvasudevan +Date: Wed Jul 29 12:10:37 2009 +0530 + + Updated Tamil translation + +M po/ta.po + +commit 3d9f5ce3d34839e2342692a849838c9e7503c588 +Author: Denis Arnaud +Date: Wed Jul 29 07:09:51 2009 +0200 + + Updated breton translation + +M po/br.po + +commit ea7245e45238b380a8ba11ee8dabc67775e49c6f +Author: Vincent Untz +Date: Wed Jul 29 00:08:38 2009 +0200 + + [misc] Update commit guidelines for referencing bugs + +M ChangeLog + +commit 374cdd09e33122d6a7f06eec9748130b3c85f9fe +Author: Vincent Untz +Date: Tue Jul 28 18:07:17 2009 +0200 + + [release] post-release bump to 2.27.90 + +M configure.in + +commit e399908c171a0add6ab6febd405b5121b3772fd2 +Author: Vincent Untz +Date: Tue Jul 28 18:07:07 2009 +0200 + + [release] 2.27.5 + +M NEWS +M README +M configure.in + +commit 2bb16693175f59d28c8a798d50ffb9b462c916f3 +Author: Denis Arnaud +Date: Tue Jul 28 14:10:04 2009 +0200 + + Updated breton translation + +M po/br.po + +commit b9cbf41d7c93ad8f18a5b9162dbd85a9e2774efa +Author: Aron Xu +Date: Sun Jul 26 18:25:28 2009 +0800 + + Updated Simplified Chinese translation. + +M po/zh_CN.po + +commit 0aed3c9a813e79ad57ec335e2aa410beb5d23991 +Author: Theppitak Karoonboonyanan +Date: Thu Jul 23 15:13:40 2009 +0700 + + Updated Thai translation. + +M po/th.po + +commit 83e66d9b0487b0be48ea935801d500e9857a4ae5 +Author: Vincent Untz +Date: Tue Jul 21 19:35:18 2009 +0200 + + [build] Set m4/ as macro dir + +M Makefile.am +M configure.in + +commit 34c1fcb2306df9ca22634a82e75865eda1031769 +Author: Vincent Untz +Date: Tue Jul 21 18:53:35 2009 +0200 + + [build] Use silent-rules instead of shave for quiet build + +M configure.in +D m4/shave.m4 +D shave-libtool.in +D shave.in +M simple-editor/GMenuSimpleEditor/Makefile.am +M simple-editor/Makefile.am + +commit 8ca7e0223b9f0e935889c146bfb5378bfeadf0ea +Author: Ilkka Tuohela +Date: Sun Jul 19 10:39:57 2009 +0300 + + Updated Finnish translation + +M po/fi.po + +commit b8fa21a37017f3fe5e333a616434c5b489e7f333 +Author: Vincent Untz +Date: Wed Jul 15 16:52:26 2009 +0200 + + [release] post-release bump to 2.27.5 + +M configure.in + +commit c17916822f13accf78dfc4d1f3ef8bc398e398f5 +Author: Vincent Untz +Date: Wed Jul 15 16:52:15 2009 +0200 + + [release] 2.27.4 + +M NEWS +M README +M configure.in + +commit d49ae45dc30c3fbafded146e5fbccbe23215316b +Author: Daniel Nylander +Date: Tue Jul 7 19:37:51 2009 +0200 + + Updated Swedish translation + +M po/sv.po + +commit 0b7478a5de36192e3bc31576c7e8114a3a6dbed3 +Author: Yaron Shahrabani +Date: Sat Jul 4 22:39:12 2009 +0300 + + Updated Hebrew translation + +M po/he.po + +commit 8bddaf2076e094043189a20c36bf66b8c3813921 +Author: Vincent Untz +Date: Sat Jun 27 04:18:02 2009 +0200 + + [libmenu] Sort inlined items unless inline_header is used + + We also strip duplicate entries in a menu when we do such a sort. + + This is actually not a trivial task since it means we have to correctly + preprocess all inline data before using the layout -- else, we'd try to + sort items while a layout has been used, which is too late (since you + can specify that you want mixed entries and directories, eg; or even + entries or directories in a specified order). + + We make sure to evaluate the inline data at the deepest first, so we can + correctly propagate as much inline items as possible to the top. + + http://bugzilla.gnome.org/show_bug.cgi?id=490483 + +M libmenu/gmenu-tree.c + +commit 0b1f053e07d1d4c081a58303cfe7ed6b583ed966 +Author: Jorge Gonzalez +Date: Sat Jun 27 14:58:46 2009 +0200 + + Updated Spanish translation + +M po/es.po + +commit 7a6b5b5a292357357f4458820b6e8f851472c592 +Author: Vincent Untz +Date: Fri Jun 26 23:42:59 2009 +0200 + + [libmenu] Do not always inherit parent DefaultLayout attributes + + We should not use the parent DefaultLayout attributes when there no + DefaultLayout children for the child but there are DefaultLayout + attributes for the child. + + Ie, if the child has , then it used to + keep inheriting the parent DefaultLayout attributes, and then possibly + lose the inline="true". + +M libmenu/gmenu-tree.c + +commit 89ab14f5fd34d18ef1591baf1000f5197344cd92 +Author: Vincent Untz +Date: Fri Jun 26 23:19:47 2009 +0200 + + [libmenu] Fix DefaultLayout attributes not being inherited + + The submenus were not inheriting the DefaultLayout attributes (eg, + all the inline parameters) of their parent when there was no child to + the DefaultLayout of their parent. + + Concretely, this was ignored for submenus: + + while this should have behaved like: + + +M libmenu/gmenu-tree.c + +commit 6f948049c368cad02b713332430b98c5daa11b5e +Author: Mattias Põldaru +Date: Thu Jun 25 14:29:03 2009 +0300 + + Updating Estonian translation + +M po/et.po + +commit 80f5477a91e11630403d60b701658c7ae242c7d7 +Author: Claude Paroz +Date: Sun Jun 21 19:35:00 2009 +0200 + + Add translator comment + +M simple-editor/GMenuSimpleEditor/main.py + +commit fc7e3ebf3266a47de4dc96a2fc938e01848eb53d +Author: Claude Paroz +Date: Sun Jun 21 19:28:21 2009 +0200 + + Fix POTFILES.in + +M po/POTFILES.in + +commit dca731f53b3686436cc94cf2704035419c03b7e8 +Author: Robert Staudinger +Date: Thu May 7 17:28:08 2009 +0200 + + [libmenu] Add API to access GenericName + + http://bugzilla.gnome.org/show_bug.cgi?id=581887 + +M libmenu/desktop-entries.c +M libmenu/desktop-entries.h +M libmenu/gmenu-tree.c +M libmenu/gmenu-tree.h + +commit d504fabf22e5303abd631e5a868fb5f45f4c7b05 +Author: Pedro Fragoso +Date: Sun Jun 21 01:17:26 2009 +0200 + + [editor] Port to GtkBuilder + + (with a few tweaks by me to make it work) + + http://bugzilla.gnome.org/show_bug.cgi?id=580158 + +M po/POTFILES.in +M simple-editor/GMenuSimpleEditor/config.py.in +M simple-editor/GMenuSimpleEditor/main.py +M simple-editor/GMenuSimpleEditor/maindialog.py +M simple-editor/Makefile.am +D simple-editor/gmenu-simple-editor.glade +A simple-editor/gmenu-simple-editor.ui + +commit fecc2847cf75048a6fe41c7423ac1e705a32b12b +Author: Vincent Untz +Date: Sun Jun 21 01:09:40 2009 +0200 + + [editor] Add --help and --version arguments + + http://bugzilla.gnome.org/show_bug.cgi?id=552989 + +M simple-editor/GMenuSimpleEditor/main.py + +commit db6d999b0ada473a3124a14dd83ed7ccb2375474 +Author: Seán de Búrca +Date: Sun May 24 21:21:58 2009 +0100 + + Updated Irish translation + +M po/ga.po + +commit 0aa50a649aa76ea95f2cda225a87448e8a16b69c +Author: Inash Zubair +Date: Sat May 23 13:32:29 2009 +0200 + + Added Divehi translation + +M po/LINGUAS +A po/dv.po + +commit 569e4835258906ca51b6b32e062cb3f860e5aeac +Author: Michael Meeks +Date: Thu May 21 03:55:59 2009 +0200 + + [libmenu] Add a cache for listing the desktop files + + When processing a layout, we get the list of desktop files from a set of + directories. It turns out that we nearly always use the same set of + directories, so adding a one-entry cache makes it possible to avoid + computing things again and again. + + I changed Michael's patch a bit, mainly to empty the cache when the last + GMenuTree is unref'ed. + + Closes: bgo#498749 + +M libmenu/entry-directories.c +M libmenu/entry-directories.h +M libmenu/gmenu-tree.c + +commit 020085ed0a4086509ede57eb4fb480bbdbb5c79a +Author: Vincent Untz +Date: Sat May 2 02:24:07 2009 +0200 + + Update commit messages guidelines + +M ChangeLog + +commit 4900c5713c5caf69d4d476023fb5638fb9d60a17 +Author: Vincent Untz +Date: Mon Apr 27 00:20:04 2009 +0200 + + Use git.mk from pango to autogenerate .gitignore files + +M Makefile.am +M desktop-directories/Makefile.am +A git.mk +M layout/Makefile.am +M libmenu/Makefile.am +M python/Makefile.am +M simple-editor/GMenuSimpleEditor/Makefile.am +M simple-editor/Makefile.am +M util/Makefile.am + +commit c11452a831ab1ecf15ccb516492740980bda7c18 +Author: Vincent Untz +Date: Mon Apr 27 01:10:53 2009 +0200 + + Remove .cvsignore files + +D .cvsignore +D desktop-directories/.cvsignore +D layout/.cvsignore +D libmenu/.cvsignore +D po/.cvsignore +D python/.cvsignore +D simple-editor/.cvsignore +D simple-editor/GMenuSimpleEditor/.cvsignore +D util/.cvsignore + +commit 5fa8af519c166de0a0abd4d9cf0e396540270b4f +Author: Vincent Untz +Date: Mon Apr 27 00:13:56 2009 +0200 + + Use shave to improve build log readability + + See http://git.lespiau.name/cgit/shave/tree/README for more details. + +M configure.in +A m4/shave.m4 +A shave-libtool.in +A shave.in +M simple-editor/GMenuSimpleEditor/Makefile.am +M simple-editor/Makefile.am + +commit 22e86da4dfd79d8f2084bce3537607fa2b9bd40e +Author: Vincent Untz +Date: Sun Apr 26 12:26:11 2009 +0200 + + Fix doap file to have mail address as URL (mailto:) + +M gnome-menus.doap + +commit 1e3078b3ef1f58c7f6e8878ff0836d312c3e57dc +Author: Vincent Untz +Date: Fri Apr 24 03:25:12 2009 +0200 + + Make autogen.sh more modern. + +M autogen.sh + +commit 2f4ef97ac8153a1460b2a1404c4f622712b3db81 +Author: Vincent Untz +Date: Wed Apr 22 17:28:55 2009 +0200 + + Add doap file + +A gnome-menus.doap + +commit e5c0da200efb4a82d8478d45c62959b848085d77 +Author: Vincent Untz +Date: Wed Apr 22 17:27:40 2009 +0200 + + ename ChangeLog files to ChangeLog.pre-git + +M ChangeLog +C100 ChangeLog ChangeLog.pre-git +R100 po/ChangeLog po/ChangeLog.pre-git + +commit 71593a96a06add451182f6b2bf848daaae3d37a0 +Author: Miquel Esplà +Date: Tue Apr 21 15:05:10 2009 +0200 + + Added Valencian-Catalan translation + +M po/LINGUAS +A po/ca@valencia.po + +commit 44190fe0edccfe82eeed523af572bcda3cb55dab +Author: Vincent Untz +Date: Mon Apr 13 22:05:39 2009 +0000 + + post-release bump to 2.26.2 + + 2009-04-14 Vincent Untz + + * configure.in: post-release bump to 2.26.2 + + svn path=/trunk/; revision=1017 + +M ChangeLog +M configure.in +M po/ChangeLog diff --git a/HACKING b/HACKING new file mode 100644 index 0000000..88bd2d2 --- /dev/null +++ b/HACKING @@ -0,0 +1,31 @@ +Hacking on gnome-menus +====================== + + + The development occurs in git: + + http://git.gnome.org/browse/gnome-menus + + For information on how to access GNOME git please read: + + http://live.gnome.org/Git + + + Please send patches as bug reports in GNOME Bugzilla: + + https://bugzilla.gnome.org/ (product gnome-menus) + + Your patch should be in unified diff form (the -u option to GNU + diff). See also: + + http://live.gnome.org/GnomeLove/SubmittingPatches + + + Please try and send a patch against a recent version of this package. + Patches against git master are most preferable. + + + Don't commit any but the most trivial patches without approval. + + + Exceptions to this are: + + - Translators may commit basic i18n related patches to the build + setup. + - Build sheriff are welcome - in accordance with the relevant build + sheriff constraints. diff --git a/MAINTAINERS b/MAINTAINERS new file mode 100644 index 0000000..0404041 --- /dev/null +++ b/MAINTAINERS @@ -0,0 +1,16 @@ +Currently active maintainers +---------------------------- + +Jasper St. Pierre +E-mail: jstpierre@mecheye.net +Userid: jstpierre + +Non-active maintainers, who have a good understanding of the code +----------------------------------------------------------------- + +#Mark McLoughlin +#E-mail: mark@skynet.ie + +#Vincent Untz +#E-mail: vuntz@gnome.org +#Userid: vuntz diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..b50e63c --- /dev/null +++ b/Makefile.am @@ -0,0 +1,31 @@ +SUBDIRS = libmenu + +ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} + +DISTCHECK_CONFIGURE_FLAGS = --enable-introspection + +EXTRA_DIST = \ + HACKING \ + MAINTAINERS + +MAINTAINERCLEANFILES = \ + $(srcdir)/INSTALL \ + $(srcdir)/aclocal.m4 \ + $(srcdir)/config.guess \ + $(srcdir)/config.h.in \ + $(srcdir)/config.sub \ + $(srcdir)/depcomp \ + $(srcdir)/install-sh \ + $(srcdir)/ltmain.sh \ + $(srcdir)/missing \ + $(srcdir)/mkinstalldirs \ + $(srcdir)/py-compile \ + `find "$(srcdir)" -type f -name Makefile.in -print` \ + $(srcdir)/configure \ + $(srcdir)/m4/intltool.m4 \ + $(srcdir)/m4/libtool.m4 \ + $(srcdir)/m4/ltoptions.m4 \ + $(srcdir)/m4/ltsugar.m4 \ + $(srcdir)/m4/ltversion.m4 \ + $(srcdir)/m4/lt~obsolete.m4 + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..a2a1c75 --- /dev/null +++ b/NEWS @@ -0,0 +1,1790 @@ +============= +Version 3.8.0 +============= + + * Add more java things to sundry + * Add im-chooser, power statistics and personal file + sharing to sundry + * Remove special-casing for Fedora vendor prefixdd + * Translation updates + +============== +Version 3.7.90 +============== + +Bugs Fixed: + * Fix error reporting + * Memory leak fixes (William John McCann) + * Remove the simple editor (William John McCann) + * New menu layout for gnome-shell tweaks (William John McCann, + Florian Muellner, Jasper St. Pierre) + * Remove old gnome-control-center files (Jasper St. Pierre) + +Translation updates: + * Alexandre Franke + * Fabio Tomat + * Inaki Larranaga Murgoitio + * Piotr Drąg + * William Jon McCann + +============= +Version 3.6.2 +============= + +* 688972 call menu_layout_load() with non_prefixed_name parameter +* Translation updates (Arabic, Kannada, Uyghur) + +============= +Version 3.6.1 +============= + +* Translation updates (Catalan, Catalan (Valencian), Ukrainian, + Aragonese, Slovak, Uzbek) + +============= +Version 3.6.0 +============= + +* Translation updates + +============== +Version 3.5.92 +============== + + libmenu + + * Add proper header reference to GMenu-3.0.gir (Rico Tzschichholz) + + Translators + + * Nilamdyuti Goswami (as) + * Petr Kovar (cs) + * Peter Bach (da) + * Bruce Cowan (en_GB) + * Arash Mousavi (fa) + * Jiri Grönroos (fi) + * Claude Paroz (fr) + * Leandro Regueiro (gl) + * Gabor Kelemen (hu) + * Dirgita (id) + * Milo Casagrande (it) + * Changwoo Ryu (ko) + * Aurimas Černius (lt) + * Sandeep Shedmake (mr) + * A S Alam (pa) + * Piotr Drąg (pl) + * Duarte Loreto (pt) + * Daniel Nylander (sv) + * Dr.T.Vasudevan (ta) + * Theppitak Karoonboonyanan (th) + * Muhammet Kara (tr) + * Nguyễn Thái Ngọc Duy (vi) + * Chrovex Fan (zh_CN) + +============= +Version 3.5.5 +============= + +Translations Updates: + + * Traditional Chinese (Chao-Hsiung Liao) + * German (Tobias Endrigkeit) + * Kazakh (Baurzhan Muftakhidinov) + * Silesian (Przemysław Buczkowski) + * Gujarati (Sweta Kothari) + * Serbian (Мирослав Николић) + +============= +Version 3.5.4 +============= + +Translations Updates + + * Belarusian (Alexander Shopov) + +============= +Version 3.5.3 +============= + +libmenu + + * Fix scanner warnings (Colin Walters) + * Add gmenu_tree_iter_get_separator (Jasper St. Pierre) + * Add a recursive NoDisplay getter (Jasper St. Pierre) + * Add a way to get a GMenuTree from a GMenuTreeItem (Jasper St. Pierre) + * Add a way to get the parent item for a GMenuTreeItem (Jasper St. Pierre) + +Translations Updates + + * Belarusian (Ihar Hrachyshka) + * Greek (Tom Tryfonidis) + * Assamese (Nilamdyuti Goswami) + * Telugu (Sasi Bhushan Boddepalli) + +============= +Version 3.5.2 +============= + +Layout + + * Add a separate category for Web Applications (Giovanni Campagna) + +libmenu + + * Add a new GMENU_TREE_FLAGS_INCLUDE_UNALLOCATED flag (Vincent Untz) + +Translations + + * Bulgarian + * Kashubian + * Spanish + * Galician + * Slovenian + * Hebrew + * Russian + * Norwegian bokmål + +============= +Version 3.4.0 +============= + + Translators + + * Morn Met (km) + * Bahodir Mansurov (uz@cyrillic) + +============= +Version 3.3.5 +============= + + Translators + + * Jiro Matsuzawa (ja) + * Kjartan Maraas (nb) + * Danishka Navin (si) + * Andiswa Mvanyashe (xh) + +============= +Version 3.3.1 +============= + + libmenu + + * Ignore desktop entries with no Name or Exec key (Florian Müllner) + + Layout + + * Put the Other menu at the end (Vincent) + +=============== +Version 3.2.0.1 +=============== + + Menu Editor + + * Work with latest pygobject (Vincent) + +============= +Version 3.2.0 +============= + + Translators + + * Nilamdyuti Goswami (as) + * Praveen Illa (te) + +============== +Version 3.1.92 +============== + + libmenu + + * Fix build failure with --enable-debug (Vincent) + + Translators + + * Ігар Грачышка (be) + * Daniel Mustieles (es) + * Jiro Matsuzawa (ja) + +============== +Version 3.1.90 +============== + + libmenu + + * Don't try to unref potentially NULL pointers (Jasper St. Pierre) + +============= +Version 3.1.5 +============= + +This version of gnome-menus comes with a significant API and ABI break, to +modernize the code. As a result, the name of the library and the pkg-config +filename have changed, so that this new version is parallel-installable with +previous ones. + +As of now, there is no guide to migrate to the new API, but it is rather +straight-forward as changes were mostly done to improve the experience for +introspection-based bindings. The examples shipped in util/ are a good basis. + + libmenu + + * Rebase internal representation of .desktop files on top of + GDesktopAppInfo (Colin Walters) + * Make GMenuTree a GObject (Colin Walters) + * Use GSlice for various data (Colin Walters) + * Use thread-default main context for callbacks for future flexibility + for thread support (Colin Walters) + * Many API changes, see new headers for changes. The most visible one + is that gmenu_tree_load_sync() should explicitly be used to load the + menu now. (Colin Walters, Vincent) + * Drop support for "KDE Desktop Entry" group (Vincent) + * Return GIcon instead of char * for icon-related API (Vincent) + * Various fixes and cleanups to merge Colin's branch (Vincent, Colin + Walters) + * Add gmenu_tree_get_entry_by_id() API (Colin Walters) + * Support XDG_CURRENT_DESKTOP (Vincent) + + Menu Editor + + * Port to introspection-based bindings (Vincent) + * Stop editing settings.menu (Vincent) + + Python + + * Drop static python bindings; introspection-based ones should be used + now (Colin Walters) + + Misc + + * Replace example of python code with javascript code (Colin Walters) + * Change library name, header directory, pkg-config filename (Vincent) + * Require glib 2.29.15 for new API (Vincent) + + Translators + + * Ігар Грачышка (be) + * Gil Forcada (ca@valencia) + * Priscilla Mahlangu (zu) + +============= +Version 3.0.1 +============= + + Translators + + * Anousak Souphavanh (lo) + * Theppitak Karoonboonyanan (th) + +============= +Version 3.0.0 +============= + + Layout + + * Show administration tools and old capplets in Other (Vincent) + + Translators + + * Khaled Hosny (ar) + * Zenat Rahnuma (bn) + * Gil Forcada (ca) + * Sense Hofstede (fy) + * Praveen Illa (te) + * Sahran (ug) + * Clytie Siddall (vi) + +=============== +Version 2.91.91 +=============== + + Menu Editor + + * Fix to work with latest pygi (Vincent) + + Misc + + * Build fix (Vincent) + + Translators + + * F Wolff (af) + * Changwoo Ryu (ko) + * Pavol Klačanský (sk) + * Korostil Daniel (uk) + +============== +Version 2.91.6 +============== + + libmenu + + * Do not send multiple notifications for one file change (Vincent) + + Menu Editor + + * Port to pygobject-based introspection bindings (Vincent) + * Make editor GTK+ 3 ready (Vincent) + + Misc + + * Improve introspection build (Vincent) + * Drop settings.menu (Vincent) + + Translators + + * Gil Forcada (ca@valencia) + * Mattias Põldaru (et) + * Mahyar Moghimi (fa) + * Fran Diéguez (gl) + * Kikongo Translation Team (kg) + * Sahran (ug) + * Clytie Siddall (vi) + +============== +Version 2.30.4 +============== + + libmenu + + * Clear cache of desktop entries set when files are added/removed + (Vincent) + + Misc + + * Associate .gir with pkg-config file (Vincent) + * Rename --enable-deprecations configure option to + --enable-deprecation-flags (Vincent) + + Translators + + * Daniel Martinez (an) + * Takayuki KUSANO (ja) + * Baurzhan Muftakhidinov (kk) + * Torstein Winterseth (nn) + +============== +Version 2.30.3 +============== + + Menu Editor + + * Respect XDG_MENU_PREFIX when writing user menu file (Vincent) + + Misc + + * Update information in README and other files (Vincent) + + Translators + + * Kristjan SCHMIDT (eo) + * Sense Hofstede (fy) + * Fran Diéguez (gl) + * Reuben Potts (gv) + * Dirgita (id) + * Baurzhan M. (kk) + * Sahran (ug) + +============== +Version 2.30.2 +============== + + Misc + + * Do not ship gir files in the tarball (Yaakov Selkowitz) + + Translators + + * Daniel Martinez (an) + * Gil Forcada (ca) + * Gil Forcada (ca@valencia) + * Reşat SABIQ (crh) + * Christian Kirbach (de) + * Thomas Thurman (en@shaw) + * Fran Diéguez (gl) + * Shankar Prasad (kn) + * Peteris Krisjanis (lv) + * Wouter Bolsterlee (nl) + * Matej Urbančič (sl) + +============== +Version 2.30.0 +============== + + libmenu + + * Fix layout processing for Menuname nodes (Vincent) + * Never ignore Menuname nodes from DefaultLayout (Vincent) + + Misc + + * Add configure summary (Vincent) + + Translators + + * Gil Forcada (ca) + * Kostas Papadimas (el) + * Iñaki Larrañaga Murgoitio (eu) + * Changwoo Ryu (ko) + * Badral (mn) + +=============== +Version 2.29.92 +=============== + + libmenu + + * Add gobject-introspection support (Vincent) + + Misc + + * Build fix (Vincent) + + Translators + + * Nikos Charonitakis (el) + * Pavol Klačanský (sk) + +=============== +Version 2.29.91 +=============== + + Misc + + * Make some non-visible strings non-translatable (Vincent) + * Add translator comment (Vincent) + + Translators + + * Fran Diéguez (gl) + * Torstein Adolf Winterseth (nn) + +============== +Version 2.29.6 +============== + + libmenu + + * Fix miscalculation for inlining when inline_header = true (Vincent) + * Add real support for inline aliases during layout processing + (Vincent) + * Support inline alias of an inline alias (Vincent) + * Do not count non-inlining submenus as inlining with header (Vincent) + + Translators + + * Sadia Afroz (bn) + * Gil Forcada (ca) + * Reşat SABIQ (crh) + * Thomas Thurman (en@shaw) + * Sveinn í Felli (is) + * Nils-Christoph Fiedler (nds) + * Dmitry Yacenko (ru) + * Krishna Babu K (te) + * 甘露(Gan Lu) (zh_CN) + +================ +Version 2.28.0.1 +================ + + libmenu + + * Fix sorting of menu items during merge to actually work (and not + crash) (Frédéric Crozat) + + Python + + * Link the python module to libpython (Frédéric Crozat, Vincent) + +============== +Version 2.28.0 +============== + + Translators + + * Amitakhya Phukan (as) + * Petr Kovar (cs) + * Peter Bach (da) + * Philip Withnall (en_GB) + * Rajesh Ranjan (hi) + * Luca Ferretti (it) + * Shankar Prasad (kn) + * Gintautas Miliauskas (lt) + * Rajesh Ranjan (mai) + * Peter Ani + * Sandeep Shedmake (mr) + * Nils-Christoph Fiedler (nds) + * A S Alam (pa) + * Adi Roiban (ro) + * Matej Urbančič (sl) + * Daniel Nylander (sv) + * Krishna Babu K (te) + * Baris Cicek (tr) + * Maxim Dziumanenko (uk) + * Chao-Hsiung Liao (zh_HK) + * Chao-Hsiung Liao (zh_TW) + +=============== +Version 2.27.92 +=============== + +This releases features new API that applications can use to display the full +name contained in .desktop files that is now in the X-GNOME-FullName key. If an +application chooses to use this, then it should set the sort key so that +.desktop files are correctly sorted. + + libmenu + + * Add gmenu_tree_entry_get_display_name() API (Vincent) + This will return X-GNOME-FullName if available, and fallback to Name. + * Add gmenu_tree_get_sort_key()/gmenu_tree_set_sort_key() (Vincent) + The default sort key is still Name. Users of + gmenu_tree_entry_get_display_name() should use + gmenu_tree_set_sort_key(). + + Python + + * Bind new API (Vincent) + * Add missing bindings for gmenu_tree_entry_get_generic_name() (Vincent) + + Menu Editor + + * Remove deprecated Encoding key from desktop file (Frédéric Péters) + * Use display name instead of name (Vincent) + + Translators + + * Khaled Hosny (ar) + * Alexander Shopov (bg) + * Runa Bhattacharjee (bn_IN) + * Denis (br) + * Mario Blättermann (de) + * Iñaki Larrañaga Murgoitio (eu) + * Claude Paroz (fr) + * Seán de Búrca (ga) + * Anton Meixome (gl) + * Sweta Kothari (gu) + * Gabor Kelemen (hu) + * Francesco Marletta (it) + * Takayuki KUSANO (ja) + * Changwoo Ryu (ko) + * Kjartan Maraas (nb) + * Mario Blättermann (nds) + * Manoj Kumar Giri (or) + * Tomasz Dominikowski (pl) + * Duarte Loreto (pt) + * Krix Apolinário (pt_BR) + * Горан Ракић (sr) + * Goran Rakić (sr@latin) + * Dr.T.Vasudevan (ta) + * Theppitak Karoonboonyanan (th) + * 甘露 (Lu Gan) (zh_CN) + +============== +Version 2.27.5 +============== + + Misc + + * Use silent-rules with automake 1.11 (Vincent) + + Translators + + * Ilkka Tuohela (fi) + +============== +Version 2.27.4 +============== + + libmenu + + * Improve performance by using a cache to not compute the same thing + again and again (Michael Meeks, Vincent) + * Add API to access GenericName (Robert Staudinger) + * Fix DefaultLayout attributes not being inherited (Vincent) + * Do not always inherit parent DefaultLayout attributes (Vincent) + * Sort inlined items unless inline_header is used (Vincent) + + Menu Editor + + * Add --help and --version arguments (Vincent) + * Port to GtkBuilder (Pedro Fragoso, Vincent) + + Misc + + * Use shave to improve build log readability (Vincent) + + Translators + + * Jordi Mallach (ca@valencia) + * Huxain (dv) + * Jorge González (es) + * Mattias Põldaru (et) + * Seán de Búrca (ga) + * Yaron Shahrabani (he) + * Daniel Nylander (sv) + +============== +Version 2.26.1 +============== + + Translators + + * Khaled Hosny (ar) + * Daniel Nylander (sv) + +============== +Version 2.26.0 +============== + + Translators + + * Reşat SABIQ (crh) + * Suso Baleato (gl) + * Rajesh Ranjan (hi) + * Francesco Marletta (it) + * Manoj Kumar Giri (or) + +=============== +Version 2.25.91 +=============== + + Translators + + * Changwoo Ryu (ko) + * Raivis Dejus (lv) + * Sandeep Shedmake (mr) + * Горан Ракић (sr) + * Daniel Nylander (sv) + * Woodman Tuen (zh_HK) + * Woodman Tuen (zh_TW) + +============== +Version 2.25.5 +============== + + Misc + + * Use gnome-common macro to define DEPRECATED build variables (Vincent) + + Translators + + * Reşat SABIQ (crh) + * Saudat Mohammed (ha) + * Sylvester Onye (ig) + * Fajuyitan, Sunday Ayo (yo) + +============== +Version 2.25.2 +============== + + Fixes + + * Fix a critical warning in the python binding for monitoring a file + (Vincent) + + Misc + + * Ship a gnome-menus-ls.py script that is an example of python bindings + and that can be used as a replacement for gnome-menu-spec-test + (Vincent) + +============== +Version 2.24.2 +============== + + Translators + + * Mikel González (ast) + +============== +Version 2.24.1 +============== + + Translators + + * Khaled Hosny (ar) + * Rostislav "zbrox" Raykov (bg) + * Margulan Moldabekov (kk) + +============== +Version 2.24.0 +============== + + Translators + + * F Wolff (af) + * Khaled Hosny (ar) + * Roozbeh Pournader (fa) + * Rajesh Ranjan (hi) + * Gabor Kelemen (hu) + * Francesco Marletta (it) + * Shankar Prasad (kn) + * Leonardo Ferreira Fontenelle (pt_BR) + * Mișu Moldovan (ro) + +=============== +Version 2.23.92 +=============== + + Translators + + * Seán de Búrca (ga) + * Krešo Kunjas (hr) + * Praveen Arimbrathodiyil (ml) + * Leonardo Ferreira Fontenelle (pt_BR) + +=============== +Version 2.23.91 +=============== + + Translators + + * Khaled Hosny (ar) + * Reuven Gonen (he) + * Shankar Prasad (kn) + +============== +Version 2.23.6 +============== + + Layout + + * Fix the icon for the accessibility menu (Matthias Clasen) + + Translators + + * Khaled Hosny (ar) + * Fabrício Godoy (pt_BR) + +============== +Version 2.23.5 +============== + + Translators + + * Yannig Marchegay (Kokoyaya) (oc) + * Wadim Dziedzic (pl) + * Zabeeh Khan (ps) + * Laurent Dhima (sq) + +============== +Version 2.23.4 +============== + + Misc + + * Require intltool 0.40.0 (Vincent) + + Translators + + * 甘露(Lu Gan) (zh_CN) + +============== +Version 2.23.3 +============== + + Features + + * Implement handling of $XDG_MENU_PREFIX from the xdg menu + specification (Vincent) + + Fixes + + * Fix the values of (ie, show_empty, inline, + inline_limit, etc.) not being inherited by submenus when the + node is after the node in the .menu file + (Vincent) + * Fix a bug where the fallback on the filename in couldn't + be used (Vincent) + + Translators + + * Djihed Afifi (ar) + * Anna Ármansdóttir (is) + * Manoj Kumar Giri (or) + +============== +Version 2.23.1 +============== + + Features + + * Do not show separators at the beginning/end of a menu, or after + another separator, but add an option to show them (Vincent) + + Fixes + + * Call g_type_init() because GIO needs it and we use GIO for file + monitoring (Vincent) + * Fix a crash when a file notification is emitted with a non-ascii + filename (Vincent) + * Remove entries from the excluded set if they are included after they + were excluded (eg: + somethingsomething) (Vincent) + * Implicitly add nodes to and that are + missing some (Vincent) + * Correctly order the move operations to execute so that moving + something and moving it back again works as undo (Vincent) + * Simplify some code (William Jon McCann, Vincent) + * Plug leak (Vincent) + + Layout + + * Do not show accessibility items in the accessories submenu + (Josselin Mouette) + * Merge menus and files at the end of the layout of settings.menu + * Explicitly do not include gnomecc.menu in the preferences menu + instead of explicitly excluding it, so that alacarte doesn't know it + was excluded (Vincent) + * Rename many .directory files so they use fd.o Categories as name + (Vincent) + * Remove preferences.menu and directly include things in settings.menu + (Vincent) + * Update a few icons used in .directory files according to the icon + naming spec (Vincent) + * Remove the accessibility submenu from Preferences, since it's empty + now anyway (Vincent) + + Misc + + * Remove shebangs from non-executable Python scripts. + + Translators + + * Žygimantas Beručka (lt) + * Kjartan Maraas (nb) + +============== +Version 2.22.1 +============== + + Misc + + * Remove shebangs from non-executable Python scripts. + + Translators + + * David Lodge (en_GB) + * Massimo Furlani (fur) + * Eskild Hustvedt (nn) + +============== +Version 2.22.0 +============== + + Translators + + * Petr Kovar (cs) + * nikosCharonitakis (el) + * Changwoo Ryu (ko) + * Žygimantas Beručka (lt) + * Sandeep Shedmake (mr) + * Woodman Tuen (zh_HK) + * Woodman Tuen (zh_TW) + +=============== +Version 2.21.92 +=============== + + Translators + + * Massimo Furlani (fur) + * Sangeeta Kumari (mai) + * Nabin Gautam (ne) + + +=============== +Version 2.21.91 +=============== + + Fixes + + * Remove the various monitor backends, and unconditionnaly use gio + (Vincent) + + Misc + + * Do not install gnome-menu-spec-test, it's useless for the user + (Vincent) + * Add a hard dependency on gio (Vincent) + + Translators + + + * Woodman Tuen (zh_HK) + * Woodman Tuen (zh_TW) + +=============== +Version 2.21.90 +=============== + + Misc + + * When using gio, require version 2.15.2 (Saleem Abdulrasool) + + Translators + + + * Djihed Afifi (ar) + * Alexander Nyakhaychyk (be) + * Michael Terry (io) + +============== +Version 2.21.5 +============== + + Fixes + + * Fix for API changes in gio (Sebastian Bacher, Wouter Bolsterlee, + Vincent) + + Translators + + + * F Wolff (af) + * Iñaki Larrañaga Murgoitio (eu) + * Erdal Ronahi (ku) + * Kjartan Maraas (nn) + * Yannig Marchegay (Kokoyaya) (oc) + +============== +Version 2.21.3 +============== + + Features + + * Fix for api change in gio (Kjartan Maraas) + * Prevent a major memory leak (Sebastian Droge) + + Translators + + + * Petr Kovar (cs) + * Seán de Búrca (ga) + * Danishka Navin (si) + +============== +Version 2.21.2 +============== + + Features + + * Add a new GIO-based monitor backend. It is now the preferred one. + The --with-monitor-backend configure flag can be used to select the + backend to build. (Sebastian Dröge) + + Translators + + * Anas Husseini (ar) + * Peter Bach (da) + * Jorge González (es) + * Seán de Búrca (ga) + * Ignacio Casal Quinteiro (gl) + * Huda Toriq (id) + * Matej Urbančič (sl) + +============== +Version 2.20.1 +============== + + Translators + + * Peter Bach (da) + +============== +Version 2.20.0 +============== + + Translators + + * Anas Husseini (ar) + * Amitakhya Phukan (as) + * Jordi Mallach (ca) + * Peter Bach (da) + * Simos Xenitellis (el) + * Francesco Marletta (it) + * Shankar Prasad (kn) + * Žygimantas Beručka (lt) + * Alexandru Szasz (ro) + * Nickolay V. Shmyrev (ru) + * Peter Tuhársky (sk) + * Горан Ракић (sr) + * Maxim Dziumanenko (uk) + +=============== +Version 2.19.92 +=============== + + Fixes + + * Fix potential crash (Rob Bradford) + + Translators + + * Rostislav "zbrox" Raykov (bg) + * Stéphane Raimbault (fr) + * Arangel Angov (mk) + * Tomasz Dominikowski (pl) + * Leonardo Ferreira Fontenelle (pt_BR) + * Duarte Loreto (pt) + * Funda Wang (zh_CN) + +=============== +Version 2.19.90 +=============== + + Translators + + * Ani Peter (ml) + * Inaki Larranaga Murgoitio (eu) + * Ankit Patel (gu) + * Ilkka Tuohela (fi) + * Yair Hershkovitz (he) + * Sean Burke (ga) + +============== +Version 2.19.6 +============== + + Translators + + * Ihar Hrachyshka (be@latin) + * Runa Bhattacharjee (bn_IN) + * Hendrik Richter (de) + * Ilkka Tuohela (fi) + * Gabor Kelemen (hu) + * Eunju Kim (ko) + * Raivis Dejus (lv) + * Wouter Bolsterlee (nl) + * Bharat Kumar (te) + * Nurali Abdurahmonov (uz@cyrillic) + +============== +Version 2.19.5 +============== + + Fixes + + * Use python-config to get python includes (Sebastien Bacher) + * Don't show screensavers in the menus (Ray Strode) + + Translators + + * Tshewang Norbu (dz) + * Takeshi AIHANA (ja) + * Tomasz Dominikowski (pl) + * Danishka Navin (si) + * Daniel Nylander (sv) + * Dr.T.Vasudevan (ta) + * Bharat Kumar (te) + * Nurali Abdurahmonov (uz) + * Clytie Siddall (vi) + +============== +Version 2.19.4 +============== + + Fixes + + * Fix crashes in python bindings (Colin Walters) + * Fix crash in inotify backend when ~/.config/menus is created + (Vincent) + + Translators + + * Alexander Nyakhaychyk (be) + +============== +Version 2.19.3 +============== + + Fixes + + * Use G_DIR_SEPARATOR instead of '/' (Vincent) + * Fix small leak (William KJon McCann) + + Translators + + * David Lodge (en_GB) + * Jorge González (es) + * Ivar Smolin (et) + * Theppitak Karoonboonyanan (th) + +============== +Version 2.19.2 +============== + + Menu Layout + + * Fix "system-wide" typo (Vincent) + * Put Preferences before Administration in the System menu (Vincent) + * Use icons from the icon naming spec (Luca Ferreti, Matthias Clasen, + Vincent) + * Use Universal Access instead of Accessibility (Calum Benson, Vincent) + * Use System instead of Desktop since the menu got renamed + (Sébastien Bacher) + * Do not require the Application category in the Other submenu (Vincent) + + Menu Editor + + * Fix a crash when unselecting the current menu (Vincent) + * Require pygtk at runtime (Vincent) + * Use the python executable found by configure (Vincent) + + Misc + + * Require automake 1.9 + + Translators + + * Ihar Hrachyshka (be@latin) + * norbu (dz) + * Jorge González (es) + * Iñaki Larrañaga Murgoitio (eu) + * Ignacio Casal Quinteiro (gl) + * Francesco Marletta (it) + * Raivis Dejus (lv) + * Kjartan Maraas (nb) + * Yannig MARCHEGAY (Kokoyaya) (oc) + * Matej Urbančič (sl) + * Daniel Nylander (sv) + +============== +Version 2.18.0 +============== + + Translators + + * Alaksandar Navicki (be@latin) + * Peter Bach (da) + * Simos Xenitellis (el) + * Ankit Patel (gu) + * Žygimantas Beručka (lt) + * wadim dziedzic (pl) + * Elian Myftiu (sq) + * Горан Ракић (sr) + +=============== +Version 2.17.92 +=============== + + Fixes + + * Show the system menu directories by default (as it was in 2.16) + (Denis Washington) + + Translators + + * Peter Bach (da) + * Takeshi AIHANA (ja) + * Duarte Loreto (pt) + * Clytie Siddall (vi) + * Funda Wang (zh_CN) + * Woodman Tuen (zh_HK) + * Woodman Tuen (zh_TW) + +=============== +Version 2.17.91 +=============== + + Features + + * Rework the layout so that it's easy to have the old layout. The + control center will ship its own menu file to have the layout for the + shell. (Denis Washington) + + Translators + + * Khaled Hosny (ar) + * Ihar Hrachyshka (be) + * Alaksandar Navicki (be@latin) + * Rostislav "zbrox" Raykov (bg) + * Runa Bhattacharjee (bn_IN) + * Mahay Alam Khan (bn) + * Jordi Mallach (ca) + * Jakub Friedl (cs) + * Rhys Jones (cy) + * Hendrik Richter (de) + * David Lodge (en_GB) + * Ivar Smolin (et) + * Ilkka Tuohela (fi) + * Robert-André Mauchin (fr) + * Reuven Gonen (he) + * Rajesh Ranjan (hi) + * Gabor Kelemen (hu) + * Young-Ho Cha (ko) + * Žygimantas Beručka (lt) + * Jovan Naumovski (mk) + * Badral (mn) + * Kjartan Maraas (nb) + * Wouter Bolsterlee (nl) + * Og Maciel (pt_BR) + * Leonid Kanter (ru) + * Steve Murphy (rw) + * Daniel Nylander (sv) + * Theppitak Karoonboonyanan (th) + * Clytie Siddall (vi) + +============== +Version 2.17.5 +============== + + Features + + * New menu layout for the control center capplets (Denis Washington) + + Translators + + * Khaled Hosny (ar) + * Ihar Hrachyshka (be) + +============== +Version 2.17.2 +============== + + Features + + * Flesh out inotify support (use --enable-inotify) (Mark) + + Fixes + + * Don't load incorrectly encoded .desktop files (Mark) + * Fix compile warning (Mark) + + Translators + + * Khaled Hosny (ar) + * Guillaume Savaton (eo) + +============== +Version 2.16.1 +============== + + Translators + + * David Lodge (en_GB) + * Francesco Marletta (it) + +============== +Version 2.16.0 +============== + + Translators + + * Gabor Kelemen (hu) + * Jovan Naumovski (mk) + * Badral (mn) + * Rahul Bhalerao (mr) + * Matic Žgur (sl) + * Onur Can Çakmak (tr) + +=============== +Version 2.15.91 +=============== + + Translators + + * Runa Bhattacharjee (bn_IN) + * Francisco Javier F. Serrador (es) + * Arangel Angov (mk) + * Matic Žgur (sl) + +=============== +Version 2.15.90 +=============== + + Translators + + * Ani Peter (ml) + * Subhransu Behera (or) + * Theppitak Karoonboonyanan (th) + +================ +Version 2.15.4.1 +================ + + Fixes + + * Correctly update LT_VERSION (Vincent) + +============== +Version 2.15.4 +============== + + Features + + * Add new API to know if an application should be launched in a + terminal and to know the path to the desktop file (Travis Watkins) + * Complete python bindings for the "No Display" flag (Travis Watkins) + + Menu Editor + + * Allow specifying alternate menu files as command line arguments + (William Jon McCann) + + Misc + + * Use po/LINGUAS (Wouter Bolsterlee) + * Require intltool 0.35.0 (Vincent Untz) + + Translators + + * Runa Bhattacharjee (bn_IN) + * Matheus Grandi (gn) + * Swapnil Hajare (mr) + +============== +Version 2.14.0 +============== + + Features + + * Start inotify support (not compiled by default) (Mark McLoughlin) + + Fixes + + * Small fix for the python bindings (Mark McLoughlin) + * Fix infinite loop (Mark McLoughlin) + + Translators + + * Ales Nyakhaychyk (be) + * Jérémy Le Floc'h (br) + * Petr Tomeš (cs) + * Rhys Jones (cy) + * Ole Laursen (da) + * Hendrik Richter (de) + * Pema Geyleg (dz) + * Kostas Papadimas (el) + * Reuven Gonen (he) + * Rajesh Ranjan (hi) + * Takeshi AIHANA (ja) + * Vladimer Sichinava (ka) + * Erdal Ronahi (ku) + * Žygimantas Beručka (lt) + * Raivis Dejus (lv) + * Thierry Randrianiriana (mg) + * Wouter Bolsterlee (nl) + * Kjartan Maraas (nn) + * GNOME PL Team (pl) + * Evandro Fernandes Giovanini (pt_BR) + * Duarte Loreto (pt) + * Sebastian Ivan (ro) + * Leonid Kanter (ru) + * Elian Myftiu (sq) + * Слободан Д. Средојевић (sr) + * Maxim Dziumanenko (uk) + * Funda Wang (zh_CN) + * Woodman Tuen (zh_HK) + * Woodman Tuen (zh_TW) + +============== +Version 2.13.5 +============== + + Features + + * Add "include NoDisplay" flag (Mark McLoughlin) + + Fixes + + * Fix issue where menu wouldn't fully reload after lots of + file change events (Mark McLoughlin, Frederic Crozat) + * Remove some unused code (Kjartan Maraas) + * Fix incorrect escaping of C format string (The Written Word) + + Translators + + * Rostislav Raykov (bg) + * Mahay Alam Khan (bn) + * Jordi Mallach (ca) + * Miloslav Trmac (cs) + * Adam Weinberger (en_CA) + * Francisco Javier F. Serrador (es) + * Priit Laes (et) + * Iñaki Larrañaga (eu) + * Ilkka Tuohela (fi) + * Christophe Merlet (RedFox) (fr) + * Ignacio Casal Quinteiro (gl) + * Ankit Patel (gu) + * Gabor Kelemen (hu) + * Francesco Marletta (it) + * Erdal Ronahî (ku) + * Timur Jamakeev (ky) + * Kjartan Maraas (nb, no) + * Tino Meinen (nl) + * Marcel Telka (sk) + * Christian Rose (sv) + * Y.Kiran Chandra (te) + * Theppitak Karoonboonyanan (th) + * Abduxukur Abdurixit (ug) + * Clytie Siddall (vi) + * Woodman Tuen (zh_HK, zh_TW) + +============== +Version 2.12.0 +============== + + Fixes + + * Fix FAM crasher in gmenu-simple-editor (Ed Catmur) + + Translators + + * Rhys Jones (cy) + * Vincent Untz (fr) + * Ignacio Casal Quinteiro (gl) + * Norayr Chilingaryan (hy) + * Žygimantas Beručka (lt) + * Duarte Loreto (pt) + * Leonid Kanter (ru) + * Elian Myftiu (sq) + * Данило Шеган (sr) + * Onur Can Cakmak (tr) + * Clytie Siddall (vi) + +=============== +Version 2.11.92 +=============== + + Fixes + + * Fix memory corruption crasher handling notifies (Mark) + * Fix python syntax warning (Mark) + * Fix build when FAM isn't found (Elijah Newren) + * Fix crasher when a references a subdir of another (Mark) + * Fix duplicate entries after updating (Mark) + * Fix infinite loop (Frederic Crozat) + * Make with prefix work again (Chris Lahey, Mark) + + Translators + + * Rostislav "zbrox" Raykov (bg) + * Jordi Mallach (ca) + * Hendrik Brandt (de) + * Nikos Charonitakis (el) + * Roozbeh Pournader (fa) + * ahmad riza h nst (id) + * Takeshi AIHANA (ja) + * Young-Ho Cha (ko) + * GNOME PL Team (pl) + * Sebastian Ivan (ro) + * Maxim Dziumanenko (uk) + * Clytie Siddall (vi) + +=============== +Version 2.11.91 +=============== + + Fixes + + * Install .desktop file for editor (Dennis Cranston, Mark) + * Fix the window icon in the editor (Jaap A. Haitsma, Mark) + * Allow running editor in different prefix from python (Mark) + + Translators + + * Miloslav Trmac (cs) + * Hendrik Brandt (de) + * Adam Weinberger (en_CA) + * Francisco Javier F. Serrador (es) + * Priit Laes (et) + * Ilkka Tuohela (fi) + * Ankit Patel (gu) + * Reuven Gonen (he) + * Gabor Kelemen (hu) + * Takeshi AIHANA (ja) + * Kjartan Maraas (nb) + * Tino Meinen (nl) + * Kjartan Maraas (no) + * Afonso Celso Medina (pt_BR) + * Marcel Telka (sk) + * Theppitak Karoonboonyanan (th) + * Clytie Siddall (vi) + * Funda Wang (zh_CN) + * Woodman Tuen (zh_TW) + +=============== +Version 2.11.90 +=============== + + Fixes + + * Fix issue with handling of filename encodings (Mark) + * Only try to include ".directory" for if it exists (Mark) + * Re-name the Edutainment sub-menu to Education (Mark) + * Fix spec compliance issue with tag handling (Mark) + * Remove some unused code (Mark) + * Plug some leaks (Mark) + + Menu Editor + + * HIGify menu editor (Dennis Cranston) + * Make "Desktop" menu appear correctly (Mark) + + Misc + + * Allow building against uninstalled library (Brian Cameron) + + Translators + + * Ales Nyakhaychyk (be) + * Rostislav "zbrox" Raykov (bg) + * Miloslav Trmac (cs) + * Martin Willemoes Hansen (da) + * Hendrik Brandt (de) + * Nikos Charonitakis (el) + * Adam Weinberger (en_CA) + * Francisco Javier F. Serrador (es) + * Priit Laes (et) + * Iñaki Larrañaga (eu) + * Ilkka Tuohela (fi) + * Ignacio Casal Quinteiro (gl) + * Ankit Patel (gu) + * Yuval Tanny (he) + * Swapnil Hajare (mr) + * Terance Edward Sola (nb) + * Ganesh Ghimire (ne) + * Tino Meinen (nl) + * Terance Edward Sola (no) + * Marcel Telka (sk) + * Elian Myftiu (sq) + * Данило Шеган (sr) + * Theppitak Karoonboonyanan (th) + * Onur Can Cakmak (tr) + * Clytie Siddall (vi) + * Funda Wang (zh_CN) + * Woodman Tuen (zh_TW) + +================ +Version 2.11.1.1 +================ + + Fixes + + * Fix crasher bug in libgnome-menu triggered by editor (Mark) + * Make the editor create $XDG_CONFIG_HOME/menus if it doesn't exist (Mark) + +============== +Version 2.11.1 +============== + + Features + + * Simple menu editor (Mark) + * Python bindings (Mark) + * Support for and (Mark, Frederic Crozat) + * Use FAM directly for monitoring rather than gnome-vfs (Mark) + * Add API for retaining empty sub-menus and excluded items in + the GMenuTree (Mark, Christian Neumair) + * Add gmenu_tree_directory_get_menu_id() API (Mark) + * Add gmenu_tree_directory_get_tree() and gmenu_tree_get_menu_file() + API (Mark) + * Namespace the API - i.e. MenuTree -> GMenuTree (Mark) + + Fixes + + * Plug major memory leak when the menu is reloaded (Mark) + * Fix "recursive inclusion" crash (Mark) + * Fix problem where you could end up with identical items in + the same menu (Mark) + * Fix issue where you could end up with more than one menu + with the same name (Mark) + * Update for changes to behaviour in spec (Mark) + * Fix off-by-one errors shown up in valgrind (Mark) + * Remove s from default menu (Mark) + + Translators + + * Vladimir "Kaladan" Petkov (bg) + * Miloslav Trmac (cs) + * Hendrik Brandt (de) + * Adam Weinberger (en_CA) + * David Lodge (en_GB) + * Francisco Javier F. Serrador (es) + * Priit Laes (et) + * Iñaki Larrañaga (eu) + * Takeshi AIHANA (ja) + * Steve Murphy (rw) + * Canonical Ltd (xh) + +============== +Version 2.10.1 +============== + + Fixes + + * Add support for new "type" argument to (Mark) + * Monitor s for changes (Mark) + * Make user desktop entries override system ones (Mark) + * Make .directory files in s be pulled in (Mark) + * Fix weirdess with [KDE Desktop Entry] files (Mark) + * Fix s which don't contain any entries in the toplevel (Mark) + * Make sure items in s as allocated (Mark) + * Make s with a prefix work correctly (Mark) + + Translators + + * Adam Weinberger (en_CA) + * Daniel van Eeden (nl) + +============== +Version 2.10.0 +============== + + Fixes + + * Fix 64-bit crasher (Jeremy Katz) + + Translators + + * Dafydd Harries (cy) + * Farzaneh Sarafraz (fa) + * Rajesh Ranjan (hi) + * Žygimantas Beručka (lt) + * Данило Шеган (sr) + * Woodman Tuen (zh_TW) + +============== +Version 2.9.92 +============== + + Fixes + + * Fix issue with file monitoring and subdirs of (Mark) + * Fix bug with the directive (Mark) + * Make gnome-menu-spec-test work with menu-spec test framework again (Mark) + + Translators + + * Arafat Medini (ar) + * Jordi Mallach (ca) + * Martin Willemoes Hansen (da) + * Nikos Charonitakis (el) + * David Lodge (en_GB) + * Ankit Patel (gu) + * Laszlo Dvornik (hu) + * ahmad riza h nst (id) + * Francesco Marletta (it) + * Takeshi AIHANA (ja) + * Sang-Gju Kim (ko) + * Rajeev Shrestha (ne) + * Daniel van Eeden (nl) + * GNOME PL Team (pl) + * Duarte Loreto (pt) + * Dan Damian (ro) + * Leonid Kanter (ru) + * Elian Myftiu (sq) + +============== +Version 2.9.90 +============== + + Fixes + + * Do not include the Core category in the Other menu (Vincent Untz) + + Translators + + * Vladimir "Kaladan" Petkov (bg) + * Francisco Javier F. Serrador (es) + * Priit Laes (et) + * Tommi Vainikainen (fi) + * Baptiste Mille-Mathias (fr) + * Žygimantas Beručka (lt) + * Kjartan Maraas (nb) + * Kjartan Maraas (nn) + * Raphael Higino (pt_BR) + * Marcel Telka (sk) + * Christian Rose (sv) + * Theppitak Karoonboonyanan (th) + * Maxim Dziumanenko (uk) + +=============== +Version 2.9.4.1 +=============== + + Features + + * Add menu_tree_entry_get_exec() (Richard Hult) + + Translators + + * Miloslav Trmac (cs) + * Kjartan Maraas (nb) + +============= +Version 2.9.4 +============= + + Fixes + + * New menus layout (Vincent Untz) + * Reload menus correctly when they are deleted/updated (Frederic Crozat) + * Ref the return value from menu_tree_entry_get_parent() (Mark) + + Translators + + * Hendrik Brandt (de) + * Adam Weinberger (en_CA) + * Baptiste Mille-Mathias (fr) + * Arangel Angov (mk) + +============= +Version 2.9.3 +============= + + Fixes + + * Find the right icon path in desktop files (Frederic Crozat) + * Handle root path correctly (Mark) + * Always remove file monitors (Mark) + * Plug leak (Vincent Untz) + * Implement behaviour defined in version 0.9 of the spec: entries that + match an rule and an rule are marked as + "allocated" (Mark) + + Translators + + * Vladimir "Kaladan" Petkov (bg) + * David Nielsen (da) + * Hendrik Brandt (de) + * Simos Xenitellis (el) + * Iñaki Larrañaga (eu) + * Tommi Vainikainen (fi) + * Gabor Kelemen (hu) + * Žygimantas Beručka (lt) + * Duarte Loreto (pt) + * Dmitry G. Mastrukov (ru) + * Marcel Telka (sk) + * Данило Шеган (sr) + * Christian Rose (sv) + * Theppitak Karoonboonyanan (th) + +============= +Version 2.9.2 +============= + + Fixes + + * Fix a bunch of leaks (Frederic Crozat) + * Fix problem where menu entries appear in random places (Mark) + * Don't go into an infinite loop if $XDG_CONFIG_DIRS is set wrong (Mark) + * Put the user config/data dirs before the system dirs (Mark) + * Allow removing monitors from handlers (Mark) + + Translators + + * Miloslav Trmac (cs) + * Hendrik Brandt (de) + * Adam Weinberger (en_CA) + * Francisco Javier F. Serrador (es) + * Satoru SATOH (ja) + * Hasbullah Bin Pit (ms) + * Kjartan Maraas (nb) + * Daniel van Eeden (nl) + * Raphael Higino (pt_BR) + * Funda Wang (zh_CN) + diff --git a/README b/README new file mode 100644 index 0000000..489e17f --- /dev/null +++ b/README @@ -0,0 +1,44 @@ +gnome-menus +=========== + +gnome-menus contains the libgnome-menu library, the layout configuration +files for the GNOME menu, as well as a simple menu editor. + +The libgnome-menu library implements the "Desktop Menu Specification" +from freedesktop.org: + + http://freedesktop.org/wiki/Specifications/menu-spec + http://specifications.freedesktop.org/menu-spec/menu-spec-latest.html + +You may download updates to the package from: + + http://download.gnome.org/sources/gnome-menus/ + +To discuss gnome-menus, you may use the desktop-devel-list mailing list: + + http://mail.gnome.org/mailman/listinfo/desktop-devel-list + + +Installation +============ + +See the file 'INSTALL'. If you are not using a released version of +gnome-menus (for example, if you checked out the code from git), you +first need to run './autogen.sh'. + + +How to report bugs +================== + +Bugs should be reported to the GNOME bug tracking system: + + https://bugzilla.gnome.org/ (product gnome-menus) + +You will need to create an account for yourself. + +Please read the following page on how to prepare a useful bug report: + + https://bugzilla.gnome.org/page.cgi?id=bug-writing.html + +Please read the HACKING file for information on where to send changes or +bugfixes for this package. diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..d8f139a --- /dev/null +++ b/autogen.sh @@ -0,0 +1,40 @@ +#!/bin/sh +# Run this to generate all the initial makefiles, etc. +test -n "$srcdir" || srcdir=$(dirname "$0") +test -n "$srcdir" || srcdir=. + +olddir=$(pwd) + +cd $srcdir + +(test -f configure.ac) || { + echo "*** ERROR: Directory '$srcdir' does not look like the top-level project directory ***" + exit 1 +} + +# shellcheck disable=SC2016 +PKG_NAME=$(autoconf --trace 'AC_INIT:$1' configure.ac) + +if [ "$#" = 0 -a "x$NOCONFIGURE" = "x" ]; then + echo "*** WARNING: I am going to run 'configure' with no arguments." >&2 + echo "*** If you wish to pass any to it, please specify them on the" >&2 + echo "*** '$0' command line." >&2 + echo "" >&2 +fi + +mkdir -p m4 + +glib-gettextize --force --copy || exit 1 +intltoolize --force --copy --automake || exit 1 +autoreconf --verbose --force --install || exit 1 + +cd "$olddir" +if [ "$NOCONFIGURE" = "" ]; then + $srcdir/configure "$@" || exit 1 + + if [ "$1" = "--help" ]; then exit 0 else + echo "Now type 'make' to compile $PKG_NAME" || exit 1 + fi +else + echo "Skipping configure process." +fi diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..1fdd930 --- /dev/null +++ b/configure.ac @@ -0,0 +1,99 @@ +AC_PREREQ(2.62) + +AC_INIT([cinnamon-menus], [3.4.0]) +AC_CONFIG_SRCDIR(libmenu/gmenu-tree.h) + +m4_ifdef([AX_IS_RELEASE], [AX_IS_RELEASE([always])]) + +AM_INIT_AUTOMAKE([1.11 foreign no-dist-gzip dist-xz]) +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) +AC_CONFIG_MACRO_DIR([m4]) +AC_SUBST([ACLOCAL_AMFLAGS], ["-I $ac_macro_dir \${ACLOCAL_FLAGS}"]) +AC_CONFIG_HEADERS(config.h) + +AM_MAINTAINER_MODE + +# Before making a release, the LT_VERSION string should be modified. +# The string is of the form C:R:A. +# - If interfaces have been changed or added, but binary compatibility has +# been preserved, change to C+1:0:A+1 +# - If binary compatibility has been broken (eg removed or changed interfaces) +# change to C+1:0:0 +# - If the interface is the same as the previous version, change to C:R+1:A + +LIB_MENU_LT_VERSION=0:1:0 +AC_SUBST(LIB_MENU_LT_VERSION) + +AC_PROG_CC +AC_STDC_HEADERS +AC_ARG_PROGRAM +AC_LIBTOOL_WIN32_DLL +AM_PROG_LIBTOOL + +PKG_CHECK_MODULES(GIO_UNIX, gio-unix-2.0 >= 2.29.15) +AC_SUBST(GIO_UNIX_CFLAGS) +AC_SUBST(GIO_UNIX_LIBS) + +m4_ifdef([AX_COMPILER_FLAGS], + [AX_COMPILER_FLAGS([WARN_CFLAGS],[WARN_LDFLAGS])]) + +AC_ARG_ENABLE(deprecation_flags, + [AC_HELP_STRING([--enable-deprecation-flags], + [use *_DISABLE_DEPRECATED flags @<:@default=no@:>@])],, + [enable_deprecation_flags=no]) + +if test "x$enable_deprecation_flags" = "xyes"; then + DISABLE_DEPRECATED_CFLAGS=$DISABLE_DEPRECATED + AC_SUBST(DISABLE_DEPRECATED_CFLAGS) +fi + +AC_ARG_ENABLE(debug, + [AS_HELP_STRING([--enable-debug=@<:@no/minimum/yes@:>@], + [turn on debugging @<:@default=debug_default@:>@])],, + [enable_debug=debug_default]) +if test "x$enable_debug" = "xyes"; then + DEBUG_CFLAGS="-DG_ENABLE_DEBUG" +else + if test "x$enable_debug" = "xno"; then + DEBUG_CFLAGS="-DG_DISABLE_ASSERT -DG_DISABLE_CHECKS -DG_DISABLE_CAST_CHECKS" + else + DEBUG_CFLAGS="-DG_ENABLE_DEBUG -DG_DISABLE_CAST_CHECKS" + fi +fi +AC_SUBST(DEBUG_CFLAGS) + +GOBJECT_INTROSPECTION_CHECK([0.9.5]) + +AC_OUTPUT([ +Makefile +libmenu/Makefile +libmenu/libcinnamon-menu-3.0.pc +libmenu/libcinnamon-menu-3.0-uninstalled.pc +]) + +dnl --------------------------------------------------------------------------- +dnl - Show summary +dnl --------------------------------------------------------------------------- + +echo " + cinnamon-menus $VERSION + `echo cinnamon-menus $VERSION | sed "s/./=/g"` + + prefix: ${prefix} + exec_prefix: ${exec_prefix} + libdir: ${libdir} + bindir: ${bindir} + sbindir: ${sbindir} + sysconfdir: ${sysconfdir} + localstatedir: ${localstatedir} + datadir: ${datadir} + source code location: ${srcdir} + compiler: ${CC} + cflags: ${CFLAGS} + Maintainer mode: ${USE_MAINTAINER_MODE} + Use *_DISABLE_DEPRECATED: ${enable_deprecation_flags} + + Turn on debugging: ${enable_debug} + Build introspection support: ${found_introspection} + +" diff --git a/libmenu/Makefile.am b/libmenu/Makefile.am new file mode 100644 index 0000000..e92766b --- /dev/null +++ b/libmenu/Makefile.am @@ -0,0 +1,79 @@ +lib_LTLIBRARIES = libcinnamon-menu-3.la + +AM_CPPFLAGS = \ + $(GIO_UNIX_CFLAGS) \ + $(WARN_CFLAGS) \ + -DGMENU_I_KNOW_THIS_IS_UNSTABLE \ + $(DISABLE_DEPRECATED_CFLAGS) \ + $(DEBUG_CFLAGS) + +AM_CFLAGS = $(WARN_CFLAGS) + +libcinnamon_menu_3_includedir = $(includedir)/cinnamon-menus-3.0 +libcinnamon_menu_3_include_HEADERS = \ + gmenu-tree.h + +libcinnamon_menu_3_sources = \ + canonicalize.c \ + desktop-entries.c \ + entry-directories.c \ + gmenu-tree.c \ + menu-layout.c \ + menu-monitor.c \ + menu-util.c + +libcinnamon_menu_3_la_SOURCES = \ + $(libcinnamon_menu_3_sources) \ + canonicalize.h \ + desktop-entries.h \ + entry-directories.h \ + gmenu-tree.h \ + menu-layout.h \ + menu-monitor.h \ + menu-util.h + +libcinnamon_menu_3_la_LIBADD = \ + $(GIO_UNIX_LIBS) + +libcinnamon_menu_3_la_LDFLAGS = \ + $(WARN_LDFLAGS) \ + -version-info $(LIB_MENU_LT_VERSION) \ + -no-undefined \ + -export-symbols-regex gmenu_tree + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libcinnamon-menu-3.0.pc + +EXTRA_DIST = \ + libcinnamon-menu-3.0.pc.in \ + libcinnamon-menu-3.0-uninstalled.pc.in + +CLEANFILES = + +# Introspection +-include $(INTROSPECTION_MAKEFILE) +INTROSPECTION_GIRS = +INTROSPECTION_SCANNER_ARGS = --warn-all --add-include-path=$(srcdir) +INTROSPECTION_COMPILER_ARGS = --includedir=$(srcdir) + +if HAVE_INTROSPECTION +introspection_sources = $(libcinnamon_menu_3_include_HEADERS) gmenu-tree.c + +CMenu-3.0.gir: libcinnamon-menu-3.la +CMenu_3_0_gir_INCLUDES = Gio-2.0 +CMenu_3_0_gir_CFLAGS = $(AM_CPPFLAGS) +CMenu_3_0_gir_LIBS = libcinnamon-menu-3.la +CMenu_3_0_gir_SCANNERFLAGS = $(WARN_SCANNERFLAGS) --identifier-prefix=GMenu --symbol-prefix=gmenu --pkg-export=libcinnamon-menu-3.0 --c-include=gmenu-tree.h +CMenu_3_0_gir_FILES = $(addprefix $(srcdir)/,$(introspection_sources)) +INTROSPECTION_GIRS += CMenu-3.0.gir + +girdir = $(INTROSPECTION_GIRDIR) +gir_DATA = $(INTROSPECTION_GIRS) + +typelibdir = $(INTROSPECTION_TYPELIBDIR) +typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib) + +CLEANFILES += $(gir_DATA) $(typelib_DATA) +endif + +-include $(top_srcdir)/git.mk diff --git a/libmenu/canonicalize.c b/libmenu/canonicalize.c new file mode 100644 index 0000000..f601c4f --- /dev/null +++ b/libmenu/canonicalize.c @@ -0,0 +1,256 @@ +/* Return the canonical absolute name of a given file. + Copyright (C) 1996-2001, 2002 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + Copyright (C) 2002 Red Hat, Inc. (trivial port to GLib) + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#include + +#include "canonicalize.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Return the canonical absolute name of file NAME. A canonical name + does not contain any `.', `..' components nor any repeated path + separators ('/') or symlinks. All path components must exist. If + RESOLVED is null, the result is malloc'd; otherwise, if the + canonical name is PATH_MAX chars or more, returns null with `errno' + set to ENAMETOOLONG; if the name fits in fewer than PATH_MAX chars, + returns the name in RESOLVED. If the name cannot be resolved and + RESOLVED is non-NULL, it contains the path of the first component + that cannot be resolved. If the path can be resolved, RESOLVED + holds the same value as the value returned. */ + +static char* +menu_realpath (const char *name, char *resolved) +{ + char *rpath, *dest, *extra_buf = NULL; + const char *start, *end, *rpath_limit; + long int path_max; + int num_links = 0; + + if (name == NULL) + { + /* As per Single Unix Specification V2 we must return an error if + either parameter is a null pointer. We extend this to allow + the RESOLVED parameter to be NULL in case the we are expected to + allocate the room for the return value. */ + errno = EINVAL; + return NULL; + } + + if (name[0] == '\0') + { + /* As per Single Unix Specification V2 we must return an error if + the name argument points to an empty string. */ + errno = ENOENT; + return NULL; + } + +#ifdef PATH_MAX + path_max = PATH_MAX; +#else + path_max = pathconf (name, _PC_PATH_MAX); + if (path_max <= 0) + path_max = 1024; +#endif + + rpath = resolved ? g_alloca (path_max) : g_malloc (path_max); + rpath_limit = rpath + path_max; + + if (name[0] != G_DIR_SEPARATOR) + { + if (!getcwd (rpath, path_max)) + { + rpath[0] = '\0'; + goto error; + } + dest = strchr (rpath, '\0'); + } + else + { + rpath[0] = G_DIR_SEPARATOR; + dest = rpath + 1; + } + + for (start = end = name; *start; start = end) + { + struct stat st; + int n; + + /* Skip sequence of multiple path-separators. */ + while (*start == G_DIR_SEPARATOR) + ++start; + + /* Find end of path component. */ + for (end = start; *end && *end != G_DIR_SEPARATOR; ++end) + /* Nothing. */; + + if (end - start == 0) + break; + else if (end - start == 1 && start[0] == '.') + /* nothing */; + else if (end - start == 2 && start[0] == '.' && start[1] == '.') + { + /* Back up to previous component, ignore if at root already. */ + if (dest > rpath + 1) + while ((--dest)[-1] != G_DIR_SEPARATOR); + } + else + { + size_t new_size; + + if (dest[-1] != G_DIR_SEPARATOR) + *dest++ = G_DIR_SEPARATOR; + + if (dest + (end - start) >= rpath_limit) + { + ptrdiff_t dest_offset = dest - rpath; + char *new_rpath; + + if (resolved) + { +#ifdef ENAMETOOLONG + errno = ENAMETOOLONG; +#else + /* Uh... just pick something */ + errno = EINVAL; +#endif + if (dest > rpath + 1) + dest--; + *dest = '\0'; + goto error; + } + new_size = rpath_limit - rpath; + if (end - start + 1 > path_max) + new_size += end - start + 1; + else + new_size += path_max; + new_rpath = (char *) realloc (rpath, new_size); + if (new_rpath == NULL) + goto error; + rpath = new_rpath; + rpath_limit = rpath + new_size; + + dest = rpath + dest_offset; + } + + memcpy (dest, start, end - start); + dest = dest + (end - start); + *dest = '\0'; + + if (stat (rpath, &st) < 0) + goto error; + + if (S_ISLNK (st.st_mode)) + { + char *buf = alloca (path_max); + size_t len; + + if (++num_links > MAXSYMLINKS) + { + errno = ELOOP; + goto error; + } + + n = readlink (rpath, buf, path_max); + if (n < 0) + goto error; + buf[n] = '\0'; + + if (!extra_buf) + extra_buf = g_alloca (path_max); + + len = strlen (end); + if ((long int) (n + len) >= path_max) + { +#ifdef ENAMETOOLONG + errno = ENAMETOOLONG; +#else + /* Uh... just pick something */ + errno = EINVAL; +#endif + goto error; + } + + /* Careful here, end may be a pointer into extra_buf... */ + g_memmove (&extra_buf[n], end, len + 1); + name = end = memcpy (extra_buf, buf, n); + + if (buf[0] == G_DIR_SEPARATOR) + dest = rpath + 1; /* It's an absolute symlink */ + else + /* Back up to previous component, ignore if at root already: */ + if (dest > rpath + 1) + while ((--dest)[-1] != G_DIR_SEPARATOR); + } + } + } + if (dest > rpath + 1 && dest[-1] == G_DIR_SEPARATOR) + --dest; + *dest = '\0'; + + return resolved ? memcpy (resolved, rpath, dest - rpath + 1) : rpath; + +error: + if (resolved) + strcpy (resolved, rpath); + else + g_free (rpath); + return NULL; +} + +char * +menu_canonicalize_file_name (const char *name, + gboolean allow_missing_basename) +{ + char *retval; + + retval = menu_realpath (name, NULL); + + /* We could avoid some system calls by using the second + * argument to realpath() instead of doing realpath + * all over again, but who cares really. we'll see if + * it's ever in a profile. + */ + if (allow_missing_basename && retval == NULL) + { + char *dirname; + char *canonical_dirname; + dirname = g_path_get_dirname (name); + canonical_dirname = menu_realpath (dirname, NULL); + g_free (dirname); + if (canonical_dirname) + { + char *basename; + basename = g_path_get_basename (name); + retval = g_build_filename (canonical_dirname, basename, NULL); + g_free (basename); + g_free (canonical_dirname); + } + } + + return retval; +} diff --git a/libmenu/canonicalize.h b/libmenu/canonicalize.h new file mode 100644 index 0000000..e3ef502 --- /dev/null +++ b/libmenu/canonicalize.h @@ -0,0 +1,34 @@ +/* Return the canonical absolute name of a given file. + Copyright (C) 1996-2001, 2002 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + Copyright (C) 2002 Red Hat, Inc. (trivial port to GLib) + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#ifndef G_CANONICALIZE_H +#define G_CANONICALIZE_H + +#include + +G_BEGIN_DECLS + +char* menu_canonicalize_file_name (const char *name, + gboolean allow_missing_basename); + +G_END_DECLS + +#endif /* G_CANONICALIZE_H */ diff --git a/libmenu/desktop-entries.c b/libmenu/desktop-entries.c new file mode 100644 index 0000000..3a9e96c --- /dev/null +++ b/libmenu/desktop-entries.c @@ -0,0 +1,979 @@ +/* + * Copyright (C) 2002 - 2004 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#include "desktop-entries.h" +#include + +#include + +#include "menu-util.h" + +#define DESKTOP_ENTRY_GROUP "Desktop Entry" + +struct DesktopEntry +{ + guint refcount; + + char *path; + const char *basename; + + guint type : 2; + guint reserved : 30; +}; + +typedef struct +{ + DesktopEntry base; + + GDesktopAppInfo *appinfo; + GQuark *categories; + guint showin : 1; +} DesktopEntryDesktop; + +typedef struct +{ + DesktopEntry base; + + char *name; + char *generic_name; + char *comment; + GIcon *icon; + + guint nodisplay : 1; + guint hidden : 1; + guint showin : 1; +} DesktopEntryDirectory; + +struct DesktopEntrySet +{ + int refcount; + GHashTable *hash; +}; + +/* + * Desktop entries + */ + +/** + * unix_basename_from_path: + * @path: Path string + * + * Returns: A constant pointer into the basename of @path + */ +static const char * +unix_basename_from_path (const char *path) +{ + const char *basename = g_strrstr (path, "/"); + if (basename) + return basename + 1; + else + return path; +} + +static const char * +get_current_desktop (void) +{ + static char *current_desktop = NULL; + + /* Support XDG_CURRENT_DESKTOP environment variable; this can be used + * to abuse gnome-menus in non-GNOME desktops. */ + if (!current_desktop) + { + const char *desktop; + + desktop = g_getenv ("XDG_CURRENT_DESKTOP"); + + /* Note: if XDG_CURRENT_DESKTOP is set but empty, do as if it + * was not set */ + if (!desktop || desktop[0] == '\0') + current_desktop = g_strdup ("GNOME"); + else + current_desktop = g_strdup (desktop); + } + + /* Using "*" means skipping desktop-related checks */ + if (g_strcmp0 (current_desktop, "*") == 0) + return NULL; + + return current_desktop; +} + +static GIcon * +key_file_get_icon (GKeyFile *key_file) +{ + GIcon *icon = NULL; + gchar *icon_name; + + icon_name = g_key_file_get_locale_string (key_file, DESKTOP_ENTRY_GROUP, + "Icon", NULL, NULL); + if (!icon_name) + return NULL; + + if (g_path_is_absolute (icon_name)) { + GFile *file; + + file = g_file_new_for_path (icon_name); + icon = g_file_icon_new (file); + g_object_unref (file); + } else { + char *p; + + /* Work around a common mistake in desktop files */ + if ((p = strrchr (icon_name, '.')) != NULL && + (strcmp (p, ".png") == 0 || + strcmp (p, ".xpm") == 0 || + strcmp (p, ".svg") == 0)) + *p = 0; + + icon = g_themed_icon_new (icon_name); + } + + g_free (icon_name); + + return icon; +} + +static gboolean +key_file_get_show_in (GKeyFile *key_file) +{ + const gchar *current_desktop; + gchar **strv; + gboolean show_in = TRUE; + int i; + gchar *exec; + + current_desktop = get_current_desktop (); + if (!current_desktop) + return TRUE; + + exec = g_key_file_get_string (key_file, + DESKTOP_ENTRY_GROUP, + "Exec", + NULL); + + if (exec) { + if (g_str_has_prefix (exec, "gnome-control-center")) { + g_free (exec); + return FALSE; + } + g_free (exec); + } + + strv = g_key_file_get_string_list (key_file, + DESKTOP_ENTRY_GROUP, + "OnlyShowIn", + NULL, + NULL); + + if (strv) + { + show_in = FALSE; + for (i = 0; strv[i]; i++) + { + if (!strcmp (strv[i], "GNOME") || !strcmp (strv[i], "X-Cinnamon")) + { + show_in = TRUE; + break; + } + } + } + else + { + strv = g_key_file_get_string_list (key_file, + DESKTOP_ENTRY_GROUP, + "NotShowIn", + NULL, + NULL); + if (strv) + { + show_in = TRUE; + for (i = 0; strv[i]; i++) + { + if (!strcmp (strv[i], current_desktop)) + { + show_in = FALSE; + } + } + } + } + g_strfreev (strv); + + return show_in; +} + +static gboolean +desktop_entry_load_directory (DesktopEntry *entry, + GKeyFile *key_file, + GError **error) +{ + DesktopEntryDirectory *entry_directory = (DesktopEntryDirectory*)entry; + char *type_str; + + type_str = g_key_file_get_string (key_file, DESKTOP_ENTRY_GROUP, "Type", error); + if (!type_str) + return FALSE; + + if (strcmp (type_str, "Directory") != 0) + { + g_set_error (error, + G_KEY_FILE_ERROR, + G_KEY_FILE_ERROR_INVALID_VALUE, + "\"%s\" does not contain the correct \"Type\" value\n", entry->path); + g_free (type_str); + return FALSE; + } + + g_free (type_str); + + entry_directory->name = g_key_file_get_locale_string (key_file, DESKTOP_ENTRY_GROUP, "Name", NULL, error); + if (entry_directory->name == NULL) + return FALSE; + + entry_directory->generic_name = g_key_file_get_locale_string (key_file, DESKTOP_ENTRY_GROUP, "GenericName", NULL, NULL); + entry_directory->comment = g_key_file_get_locale_string (key_file, DESKTOP_ENTRY_GROUP, "Comment", NULL, NULL); + entry_directory->icon = key_file_get_icon (key_file); + entry_directory->nodisplay = g_key_file_get_boolean (key_file, + DESKTOP_ENTRY_GROUP, + "NoDisplay", + NULL); + entry_directory->hidden = g_key_file_get_boolean (key_file, + DESKTOP_ENTRY_GROUP, + "Hidden", + NULL); + entry_directory->showin = key_file_get_show_in (key_file); + + return TRUE; +} + +static DesktopEntryResultCode +desktop_entry_load (DesktopEntry *entry) +{ + if (strstr (entry->path, "/menu-xdg/")) + return DESKTOP_ENTRY_LOAD_FAIL_OTHER; + if (entry->type == DESKTOP_ENTRY_DESKTOP) + { + GKeyFile *key_file = NULL; + DesktopEntryDesktop *entry_desktop = (DesktopEntryDesktop*)entry; + const char *categories_str; + + entry_desktop->appinfo = g_desktop_app_info_new_from_filename (entry->path); + if (!entry_desktop->appinfo || + !g_app_info_get_name (G_APP_INFO (entry_desktop->appinfo)) || + !g_app_info_get_executable (G_APP_INFO (entry_desktop->appinfo))) + { + menu_verbose ("Failed to load \"%s\"\n", entry->path); + return DESKTOP_ENTRY_LOAD_FAIL_APPINFO; + } + + categories_str = g_desktop_app_info_get_categories (entry_desktop->appinfo); + if (categories_str) + { + char **categories; + int i; + + categories = g_strsplit (categories_str, ";", -1); + entry_desktop->categories = g_new0 (GQuark, g_strv_length (categories) + 1); + + for (i = 0; categories[i]; i++) + entry_desktop->categories[i] = g_quark_from_string (categories[i]); + + g_strfreev (categories); + } + + key_file = g_key_file_new (); + + if (!g_key_file_load_from_file (key_file, entry->path, 0, NULL)) + entry_desktop->showin = TRUE; + else + entry_desktop->showin = key_file_get_show_in (key_file); + + g_key_file_free (key_file); + + return DESKTOP_ENTRY_LOAD_SUCCESS; + } + else if (entry->type == DESKTOP_ENTRY_DIRECTORY) + { + GKeyFile *key_file = NULL; + GError *error = NULL; + DesktopEntryResultCode rescode = DESKTOP_ENTRY_LOAD_SUCCESS; + + key_file = g_key_file_new (); + + if (!g_key_file_load_from_file (key_file, entry->path, 0, &error)) + { + rescode = DESKTOP_ENTRY_LOAD_FAIL_OTHER; + goto out; + } + + if (!desktop_entry_load_directory (entry, key_file, &error)) + { + rescode = DESKTOP_ENTRY_LOAD_FAIL_OTHER; + goto out; + } + + rescode = DESKTOP_ENTRY_LOAD_SUCCESS; + + out: + g_key_file_free (key_file); + + if (rescode == DESKTOP_ENTRY_LOAD_FAIL_OTHER) + { + if (error) + { + menu_verbose ("Failed to load \"%s\": %s\n", entry->path, error->message); + g_error_free (error); + } + else + menu_verbose ("Failed to load \"%s\"\n", entry->path); + } + + return rescode; + } + else + g_assert_not_reached (); + + return DESKTOP_ENTRY_LOAD_FAIL_OTHER; +} + +DesktopEntry * +desktop_entry_new (const char *path, + DesktopEntryResultCode *res_code) +{ + DesktopEntryType type; + DesktopEntry *retval; + DesktopEntryResultCode code; + + menu_verbose ("Loading desktop entry \"%s\"\n", path); + + if (g_str_has_suffix (path, ".desktop")) + { + type = DESKTOP_ENTRY_DESKTOP; + retval = (DesktopEntry*)g_new0 (DesktopEntryDesktop, 1); + } + else if (g_str_has_suffix (path, ".directory")) + { + type = DESKTOP_ENTRY_DIRECTORY; + retval = (DesktopEntry*)g_new0 (DesktopEntryDirectory, 1); + } + else + { + menu_verbose ("Unknown desktop entry suffix in \"%s\"\n", + path); + *res_code = DESKTOP_ENTRY_LOAD_FAIL_OTHER; + return NULL; + } + + retval->refcount = 1; + retval->type = type; + retval->path = g_strdup (path); + retval->basename = unix_basename_from_path (retval->path); + + code = desktop_entry_load (retval); + *res_code = code; + + if (code < DESKTOP_ENTRY_LOAD_SUCCESS) + { + desktop_entry_unref (retval); + return NULL; + } + + return retval; +} + +DesktopEntry * +desktop_entry_reload (DesktopEntry *entry) +{ + g_return_val_if_fail (entry != NULL, NULL); + + menu_verbose ("Re-loading desktop entry \"%s\"\n", entry->path); + + if (entry->type == DESKTOP_ENTRY_DESKTOP) + { + DesktopEntryDesktop *entry_desktop = (DesktopEntryDesktop *) entry; + + g_object_unref (entry_desktop->appinfo); + entry_desktop->appinfo = NULL; + + g_free (entry_desktop->categories); + entry_desktop->categories = NULL; + } + else if (entry->type == DESKTOP_ENTRY_DIRECTORY) + { + DesktopEntryDirectory *entry_directory = (DesktopEntryDirectory*) entry; + + g_free (entry_directory->name); + entry_directory->name = NULL; + + g_free (entry_directory->comment); + entry_directory->comment = NULL; + + g_object_unref (entry_directory->icon); + entry_directory->icon = NULL; + } + else + g_assert_not_reached (); + + if (desktop_entry_load (entry) < DESKTOP_ENTRY_LOAD_SUCCESS) + { + desktop_entry_unref (entry); + return NULL; + } + + return entry; +} + +DesktopEntry * +desktop_entry_ref (DesktopEntry *entry) +{ + g_return_val_if_fail (entry != NULL, NULL); + g_return_val_if_fail (entry->refcount > 0, NULL); + + g_atomic_int_inc (&entry->refcount); + + return entry; +} + +DesktopEntry * +desktop_entry_copy (DesktopEntry *entry) +{ + DesktopEntry *retval; + + menu_verbose ("Copying desktop entry \"%s\"\n", + entry->basename); + + if (entry->type == DESKTOP_ENTRY_DESKTOP) + retval = (DesktopEntry*)g_new0 (DesktopEntryDesktop, 1); + else if (entry->type == DESKTOP_ENTRY_DIRECTORY) + retval = (DesktopEntry*)g_new0 (DesktopEntryDirectory, 1); + else + g_assert_not_reached (); + + retval->refcount = 1; + retval->type = entry->type; + retval->path = g_strdup (entry->path); + retval->basename = unix_basename_from_path (retval->path); + + if (retval->type == DESKTOP_ENTRY_DESKTOP) + { + DesktopEntryDesktop *desktop_entry = (DesktopEntryDesktop*) entry; + DesktopEntryDesktop *retval_desktop_entry = (DesktopEntryDesktop*) retval; + int i; + + retval_desktop_entry->appinfo = g_object_ref (desktop_entry->appinfo); + + if (desktop_entry->categories != NULL) + { + i = 0; + for (; desktop_entry->categories[i]; i++); + + retval_desktop_entry->categories = g_new0 (GQuark, i + 1); + + i = 0; + for (; desktop_entry->categories[i]; i++) + retval_desktop_entry->categories[i] = desktop_entry->categories[i]; + } + else + retval_desktop_entry->categories = NULL; + } + else if (entry->type == DESKTOP_ENTRY_DIRECTORY) + { + DesktopEntryDirectory *entry_directory = (DesktopEntryDirectory*)entry; + DesktopEntryDirectory *retval_directory = (DesktopEntryDirectory*)retval; + + retval_directory->name = g_strdup (entry_directory->name); + retval_directory->comment = g_strdup (entry_directory->comment); + retval_directory->icon = g_object_ref (entry_directory->icon); + retval_directory->nodisplay = entry_directory->nodisplay; + retval_directory->hidden = entry_directory->hidden; + retval_directory->showin = entry_directory->showin; + } + + return retval; +} + +void +desktop_entry_unref (DesktopEntry *entry) +{ + g_return_if_fail (entry != NULL); + g_return_if_fail (entry->refcount > 0); + + entry->refcount -= 1; + if (entry->refcount != 0) + return; + + g_free (entry->path); + entry->path = NULL; + + if (entry->type == DESKTOP_ENTRY_DESKTOP) + { + DesktopEntryDesktop *desktop_entry = (DesktopEntryDesktop*) entry; + g_free (desktop_entry->categories); + if (desktop_entry->appinfo) + g_object_unref (desktop_entry->appinfo); + } + else if (entry->type == DESKTOP_ENTRY_DIRECTORY) + { + DesktopEntryDirectory *entry_directory = (DesktopEntryDirectory*) entry; + + g_free (entry_directory->name); + entry_directory->name = NULL; + + g_free (entry_directory->comment); + entry_directory->comment = NULL; + + if (entry_directory->icon != NULL) + { + g_object_unref (entry_directory->icon); + entry_directory->icon = NULL; + } + } + else + g_assert_not_reached (); + + g_free (entry); +} + +DesktopEntryType +desktop_entry_get_type (DesktopEntry *entry) +{ + return entry->type; +} + +const char * +desktop_entry_get_path (DesktopEntry *entry) +{ + return entry->path; +} + +const char * +desktop_entry_get_basename (DesktopEntry *entry) +{ + return entry->basename; +} + +const char * +desktop_entry_get_name (DesktopEntry *entry) +{ + if (entry->type == DESKTOP_ENTRY_DESKTOP) + return g_app_info_get_name (G_APP_INFO (((DesktopEntryDesktop*)entry)->appinfo)); + return ((DesktopEntryDirectory*)entry)->name; +} + +const char * +desktop_entry_get_generic_name (DesktopEntry *entry) +{ + if (entry->type == DESKTOP_ENTRY_DESKTOP) + return g_desktop_app_info_get_generic_name (((DesktopEntryDesktop*)entry)->appinfo); + return ((DesktopEntryDirectory*)entry)->generic_name; +} + +const char * +desktop_entry_get_comment (DesktopEntry *entry) +{ + if (entry->type == DESKTOP_ENTRY_DESKTOP) + return g_app_info_get_description (G_APP_INFO (((DesktopEntryDesktop*)entry)->appinfo)); + return ((DesktopEntryDirectory*)entry)->comment; +} + +GIcon * +desktop_entry_get_icon (DesktopEntry *entry) +{ + if (entry->type == DESKTOP_ENTRY_DESKTOP) + return g_app_info_get_icon (G_APP_INFO (((DesktopEntryDesktop*)entry)->appinfo)); + return ((DesktopEntryDirectory*)entry)->icon; +} + +gboolean +desktop_entry_get_no_display (DesktopEntry *entry) +{ + if (entry->type == DESKTOP_ENTRY_DESKTOP) + return g_desktop_app_info_get_nodisplay (((DesktopEntryDesktop*)entry)->appinfo); + return ((DesktopEntryDirectory*)entry)->nodisplay; +} + +gboolean +desktop_entry_get_hidden (DesktopEntry *entry) +{ + if (entry->type == DESKTOP_ENTRY_DESKTOP) + return g_desktop_app_info_get_is_hidden (((DesktopEntryDesktop*)entry)->appinfo); + return ((DesktopEntryDirectory*)entry)->hidden; +} + +gboolean +desktop_entry_get_show_in (DesktopEntry *entry) +{ + if (entry->type == DESKTOP_ENTRY_DESKTOP) + { + const char *current_desktop = get_current_desktop (); + + if (current_desktop == NULL) + return TRUE; + else { + return ((DesktopEntryDesktop *)entry)->showin; + } + } + return ((DesktopEntryDirectory*)entry)->showin; +} + + +GDesktopAppInfo * +desktop_entry_get_app_info (DesktopEntry *entry) +{ + g_return_val_if_fail (entry->type == DESKTOP_ENTRY_DESKTOP, NULL); + return ((DesktopEntryDesktop*)entry)->appinfo; +} + +gboolean +desktop_entry_has_categories (DesktopEntry *entry) +{ + DesktopEntryDesktop *desktop_entry; + if (entry->type != DESKTOP_ENTRY_DESKTOP) + return FALSE; + + desktop_entry = (DesktopEntryDesktop*) entry; + return (desktop_entry->categories != NULL && desktop_entry->categories[0] != 0); +} + +gboolean +desktop_entry_has_category (DesktopEntry *entry, + const char *category) +{ + GQuark quark; + int i; + DesktopEntryDesktop *desktop_entry; + + if (entry->type != DESKTOP_ENTRY_DESKTOP) + return FALSE; + + desktop_entry = (DesktopEntryDesktop*) entry; + + if (desktop_entry->categories == NULL) + return FALSE; + + if (!(quark = g_quark_try_string (category))) + return FALSE; + + for (i = 0; desktop_entry->categories[i]; i++) + { + if (quark == desktop_entry->categories[i]) + return TRUE; + } + + return FALSE; +} + +void +desktop_entry_add_legacy_category (DesktopEntry *entry) +{ + GQuark *categories; + int i; + DesktopEntryDesktop *desktop_entry; + + g_return_if_fail (entry->type == DESKTOP_ENTRY_DESKTOP); + + desktop_entry = (DesktopEntryDesktop*) entry; + + menu_verbose ("Adding Legacy category to \"%s\"\n", + entry->basename); + + if (desktop_entry->categories != NULL) + { + i = 0; + for (; desktop_entry->categories[i]; i++); + + categories = g_new0 (GQuark, i + 2); + + i = 0; + for (; desktop_entry->categories[i]; i++) + categories[i] = desktop_entry->categories[i]; + } + else + { + categories = g_new0 (GQuark, 2); + i = 0; + } + + categories[i] = g_quark_from_string ("Legacy"); + + g_free (desktop_entry->categories); + desktop_entry->categories = categories; +} + +/* + * Entry sets + */ + +DesktopEntrySet * +desktop_entry_set_new (void) +{ + DesktopEntrySet *set; + + set = g_new0 (DesktopEntrySet, 1); + set->refcount = 1; + + menu_verbose (" New entry set %p\n", set); + + return set; +} + +DesktopEntrySet * +desktop_entry_set_ref (DesktopEntrySet *set) +{ + g_return_val_if_fail (set != NULL, NULL); + g_return_val_if_fail (set->refcount > 0, NULL); + + g_atomic_int_inc (&set->refcount); + + return set; +} + +void +desktop_entry_set_unref (DesktopEntrySet *set) +{ + gboolean is_zero; + + g_return_if_fail (set != NULL); + g_return_if_fail (set->refcount > 0); + + is_zero = g_atomic_int_dec_and_test (&set->refcount); + if (is_zero) + { + menu_verbose (" Deleting entry set %p\n", set); + + if (set->hash) + g_hash_table_destroy (set->hash); + set->hash = NULL; + + g_free (set); + } +} + +void +desktop_entry_set_add_entry (DesktopEntrySet *set, + DesktopEntry *entry, + const char *file_id) +{ + menu_verbose (" Adding to set %p entry %s\n", set, file_id); + + if (set->hash == NULL) + { + set->hash = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) desktop_entry_unref); + } + + g_hash_table_replace (set->hash, + g_strdup (file_id), + desktop_entry_ref (entry)); +} + +DesktopEntry * +desktop_entry_set_lookup (DesktopEntrySet *set, + const char *file_id) +{ + if (set->hash == NULL) + return NULL; + + return g_hash_table_lookup (set->hash, file_id); +} + +typedef struct +{ + DesktopEntrySetForeachFunc func; + gpointer user_data; +} EntryHashForeachData; + +static void +entry_hash_foreach (const char *file_id, + DesktopEntry *entry, + EntryHashForeachData *fd) +{ + fd->func (file_id, entry, fd->user_data); +} + +void +desktop_entry_set_foreach (DesktopEntrySet *set, + DesktopEntrySetForeachFunc func, + gpointer user_data) +{ + g_return_if_fail (set != NULL); + g_return_if_fail (func != NULL); + + if (set->hash != NULL) + { + EntryHashForeachData fd; + + fd.func = func; + fd.user_data = user_data; + + g_hash_table_foreach (set->hash, + (GHFunc) entry_hash_foreach, + &fd); + } +} + +static void +desktop_entry_set_clear (DesktopEntrySet *set) +{ + menu_verbose (" Clearing set %p\n", set); + + if (set->hash != NULL) + { + g_hash_table_destroy (set->hash); + set->hash = NULL; + } +} + +int +desktop_entry_set_get_count (DesktopEntrySet *set) +{ + if (set->hash == NULL) + return 0; + + return g_hash_table_size (set->hash); +} + +static void +union_foreach (const char *file_id, + DesktopEntry *entry, + DesktopEntrySet *set) +{ + /* we are iterating over "with" adding anything not + * already in "set". We unconditionally overwrite + * the stuff in "set" because we can assume + * two entries with the same name are equivalent. + */ + desktop_entry_set_add_entry (set, entry, file_id); +} + +void +desktop_entry_set_union (DesktopEntrySet *set, + DesktopEntrySet *with) +{ + menu_verbose (" Union of %p and %p\n", set, with); + + if (desktop_entry_set_get_count (with) == 0) + return; /* A fast simple case */ + + g_hash_table_foreach (with->hash, + (GHFunc) union_foreach, + set); +} + +typedef struct +{ + DesktopEntrySet *set; + DesktopEntrySet *with; +} IntersectData; + +static gboolean +intersect_foreach_remove (const char *file_id, + DesktopEntry *entry, + IntersectData *id) +{ + /* Remove everything in "set" which is not in "with" */ + + if (g_hash_table_lookup (id->with->hash, file_id) != NULL) + return FALSE; + + menu_verbose (" Removing from %p entry %s\n", id->set, file_id); + + return TRUE; /* return TRUE to remove */ +} + +void +desktop_entry_set_intersection (DesktopEntrySet *set, + DesktopEntrySet *with) +{ + IntersectData id; + + menu_verbose (" Intersection of %p and %p\n", set, with); + + if (desktop_entry_set_get_count (set) == 0 || + desktop_entry_set_get_count (with) == 0) + { + /* A fast simple case */ + desktop_entry_set_clear (set); + return; + } + + id.set = set; + id.with = with; + + g_hash_table_foreach_remove (set->hash, + (GHRFunc) intersect_foreach_remove, + &id); +} + +typedef struct +{ + DesktopEntrySet *set; + DesktopEntrySet *other; +} SubtractData; + +static gboolean +subtract_foreach_remove (const char *file_id, + DesktopEntry *entry, + SubtractData *sd) +{ + /* Remove everything in "set" which is not in "other" */ + + if (g_hash_table_lookup (sd->other->hash, file_id) == NULL) + return FALSE; + + menu_verbose (" Removing from %p entry %s\n", sd->set, file_id); + + return TRUE; /* return TRUE to remove */ +} + +void +desktop_entry_set_subtract (DesktopEntrySet *set, + DesktopEntrySet *other) +{ + SubtractData sd; + + menu_verbose (" Subtract from %p set %p\n", set, other); + + if (desktop_entry_set_get_count (set) == 0 || + desktop_entry_set_get_count (other) == 0) + return; /* A fast simple case */ + + sd.set = set; + sd.other = other; + + g_hash_table_foreach_remove (set->hash, + (GHRFunc) subtract_foreach_remove, + &sd); +} + +void +desktop_entry_set_swap_contents (DesktopEntrySet *a, + DesktopEntrySet *b) +{ + GHashTable *tmp; + + menu_verbose (" Swap contents of %p and %p\n", a, b); + + tmp = a->hash; + a->hash = b->hash; + b->hash = tmp; +} diff --git a/libmenu/desktop-entries.h b/libmenu/desktop-entries.h new file mode 100644 index 0000000..5a113ae --- /dev/null +++ b/libmenu/desktop-entries.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2002 - 2004 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __DESKTOP_ENTRIES_H__ +#define __DESKTOP_ENTRIES_H__ + +#include + +G_BEGIN_DECLS + +typedef enum +{ + DESKTOP_ENTRY_INVALID = 0, + DESKTOP_ENTRY_DESKTOP, + DESKTOP_ENTRY_DIRECTORY +} DesktopEntryType; + +typedef enum +{ + DESKTOP_ENTRY_LOAD_FAIL_OTHER = 0, + DESKTOP_ENTRY_LOAD_FAIL_APPINFO, + DESKTOP_ENTRY_LOAD_SUCCESS +} DesktopEntryResultCode; + +typedef struct DesktopEntry DesktopEntry; + +DesktopEntry *desktop_entry_new (const char *path, + DesktopEntryResultCode *res_code); + +DesktopEntry *desktop_entry_ref (DesktopEntry *entry); +DesktopEntry *desktop_entry_copy (DesktopEntry *entry); +DesktopEntry *desktop_entry_reload (DesktopEntry *entry); +void desktop_entry_unref (DesktopEntry *entry); + +DesktopEntryType desktop_entry_get_type (DesktopEntry *entry); +const char *desktop_entry_get_path (DesktopEntry *entry); +const char *desktop_entry_get_basename (DesktopEntry *entry); +const char *desktop_entry_get_name (DesktopEntry *entry); +const char *desktop_entry_get_generic_name (DesktopEntry *entry); +const char *desktop_entry_get_comment (DesktopEntry *entry); +GIcon *desktop_entry_get_icon (DesktopEntry *entry); +gboolean desktop_entry_get_hidden (DesktopEntry *entry); +gboolean desktop_entry_get_no_display (DesktopEntry *entry); +gboolean desktop_entry_get_show_in (DesktopEntry *entry); + +/* Only valid for DESKTOP_ENTRY_DESKTOP */ +GDesktopAppInfo *desktop_entry_get_app_info (DesktopEntry *entry); +gboolean desktop_entry_has_categories (DesktopEntry *entry); +gboolean desktop_entry_has_category (DesktopEntry *entry, + const char *category); + +void desktop_entry_add_legacy_category (DesktopEntry *src); + + +typedef struct DesktopEntrySet DesktopEntrySet; + +DesktopEntrySet *desktop_entry_set_new (void); +DesktopEntrySet *desktop_entry_set_ref (DesktopEntrySet *set); +void desktop_entry_set_unref (DesktopEntrySet *set); + +void desktop_entry_set_add_entry (DesktopEntrySet *set, + DesktopEntry *entry, + const char *file_id); +DesktopEntry* desktop_entry_set_lookup (DesktopEntrySet *set, + const char *file_id); +int desktop_entry_set_get_count (DesktopEntrySet *set); + +void desktop_entry_set_union (DesktopEntrySet *set, + DesktopEntrySet *with); +void desktop_entry_set_intersection (DesktopEntrySet *set, + DesktopEntrySet *with); +void desktop_entry_set_subtract (DesktopEntrySet *set, + DesktopEntrySet *other); +void desktop_entry_set_swap_contents (DesktopEntrySet *a, + DesktopEntrySet *b); + +typedef void (*DesktopEntrySetForeachFunc) (const char *file_id, + DesktopEntry *entry, + gpointer user_data); + +void desktop_entry_set_foreach (DesktopEntrySet *set, + DesktopEntrySetForeachFunc func, + gpointer user_data); + +G_END_DECLS + +#endif /* __DESKTOP_ENTRIES_H__ */ diff --git a/libmenu/entry-directories.c b/libmenu/entry-directories.c new file mode 100644 index 0000000..3dba079 --- /dev/null +++ b/libmenu/entry-directories.c @@ -0,0 +1,1404 @@ +/* + * Copyright (C) 2002 - 2004 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#include "entry-directories.h" + +#include +#include +#include +#include + +#include "menu-util.h" +#include "menu-monitor.h" +#include "canonicalize.h" + +typedef struct CachedDir CachedDir; +typedef struct CachedDirMonitor CachedDirMonitor; + +struct EntryDirectory +{ + CachedDir *dir; + char *legacy_prefix; + + guint entry_type : 2; + guint is_legacy : 1; + volatile gint refcount; +}; + +struct EntryDirectoryList +{ + volatile int refcount; + int length; + GList *dirs; +}; + +struct CachedDir +{ + CachedDir *parent; + char *name; + + GSList *entries; + GSList *subdirs; + GSList *retry_later_desktop_entries; + + MenuMonitor *dir_monitor; + GSList *monitors; + + guint have_read_entries : 1; + guint deleted : 1; + + GFunc notify; + gpointer notify_data; + + volatile gint references; +}; + +struct CachedDirMonitor +{ + EntryDirectory *ed; + EntryDirectoryChangedFunc callback; + gpointer user_data; +}; + +static void cached_dir_add_reference (CachedDir *dir); +static void cached_dir_remove_reference (CachedDir *dir); +static void cached_dir_free (CachedDir *dir); +static gboolean cached_dir_load_entries_recursive (CachedDir *dir, + const char *dirname); +static void cached_dir_unref (CachedDir *dir); +static CachedDir * cached_dir_add_subdir (CachedDir *dir, + const char *basename, + const char *path); +static gboolean cached_dir_remove_subdir (CachedDir *dir, + const char *basename); + +static void handle_cached_dir_changed (MenuMonitor *monitor, + MenuMonitorEvent event, + const char *path, + CachedDir *dir); + +/* + * Entry directory cache + */ + +static CachedDir *dir_cache = NULL; + +static void +clear_cache (CachedDir *dir, + gpointer *cache) +{ + *cache = NULL; +} + +static CachedDir * +cached_dir_new (const char *name) +{ + CachedDir *dir; + + dir = g_new0 (CachedDir, 1); + dir->name = g_strdup (name); + + return dir; +} + +static CachedDir * +cached_dir_new_full (const char *name, + GFunc notify, + gpointer notify_data) +{ + CachedDir *dir; + + dir = cached_dir_new (name); + + dir->notify = notify; + dir->notify_data = notify_data; + + return dir; +} + +static void +cached_dir_free (CachedDir *dir) +{ + if (dir->dir_monitor) + { + menu_monitor_remove_notify (dir->dir_monitor, + (MenuMonitorNotifyFunc) handle_cached_dir_changed, + dir); + menu_monitor_unref (dir->dir_monitor); + dir->dir_monitor = NULL; + } + + g_slist_foreach (dir->monitors, (GFunc) g_free, NULL); + g_slist_free (dir->monitors); + dir->monitors = NULL; + + g_slist_foreach (dir->entries, + (GFunc) desktop_entry_unref, + NULL); + g_slist_free (dir->entries); + dir->entries = NULL; + + g_slist_foreach (dir->subdirs, + (GFunc) cached_dir_unref, + NULL); + g_slist_free (dir->subdirs); + dir->subdirs = NULL; + + g_slist_free_full (dir->retry_later_desktop_entries, g_free); + dir->retry_later_desktop_entries = NULL; + + g_free (dir->name); + g_free (dir); +} + +static CachedDir * +cached_dir_ref (CachedDir *dir) +{ + g_atomic_int_inc (&dir->references); + + return dir; +} + +static void +cached_dir_unref (CachedDir *dir) +{ + gboolean is_zero; + + is_zero = g_atomic_int_dec_and_test (&dir->references); + if (is_zero) + { + CachedDir *parent; + + parent = dir->parent; + + if (parent != NULL) + cached_dir_remove_subdir (parent, dir->name); + + if (dir->notify) + dir->notify (dir, dir->notify_data); + + cached_dir_free (dir); + } +} + +static inline CachedDir * +find_subdir (CachedDir *dir, + const char *subdir) +{ + GSList *tmp; + + tmp = dir->subdirs; + while (tmp != NULL) + { + CachedDir *sub = tmp->data; + + if (strcmp (sub->name, subdir) == 0) + return sub; + + tmp = tmp->next; + } + + return NULL; +} + +static DesktopEntry * +find_entry (CachedDir *dir, + const char *basename) +{ + GSList *tmp; + + tmp = dir->entries; + while (tmp != NULL) + { + const char *entry_basename; + + entry_basename = desktop_entry_get_basename (tmp->data); + if (strcmp (entry_basename, basename) == 0) + { + return tmp->data; + } + + tmp = tmp->next; + } + + return NULL; +} + +static DesktopEntry * +cached_dir_find_relative_path (CachedDir *dir, + const char *relative_path) +{ + DesktopEntry *retval = NULL; + char **split; + int i; + + split = g_strsplit (relative_path, "/", -1); + + i = 0; + while (split[i] != NULL) + { + if (split[i + 1] != NULL) + { + if ((dir = find_subdir (dir, split[i])) == NULL) + break; + } + else + { + retval = find_entry (dir, split[i]); + break; + } + + ++i; + } + + g_strfreev (split); + + return retval; +} + +static CachedDir * +cached_dir_lookup (const char *canonical) +{ + CachedDir *dir; + char **split; + int i; + + if (dir_cache == NULL) + dir_cache = cached_dir_new_full ("/", + (GFunc) clear_cache, + &dir_cache); + dir = dir_cache; + + g_assert (canonical != NULL && canonical[0] == G_DIR_SEPARATOR); + + menu_verbose ("Looking up cached dir \"%s\"\n", canonical); + + split = g_strsplit (canonical + 1, "/", -1); + + i = 0; + while (split[i] != NULL) + { + CachedDir *subdir; + + subdir = cached_dir_add_subdir (dir, split[i], NULL); + + dir = subdir; + + ++i; + } + + g_strfreev (split); + + g_assert (dir != NULL); + + return dir; +} + +static gboolean +cached_dir_add_entry (CachedDir *dir, + const char *basename, + const char *path) +{ + DesktopEntry *entry; + DesktopEntryResultCode code; + + entry = desktop_entry_new (path, &code); + if (entry == NULL) + { + if (code == DESKTOP_ENTRY_LOAD_FAIL_APPINFO) + { + menu_verbose ("Adding %s to the retry list (mimeinfo.cache maybe isn't done getting updated yet\n", path); + + dir->retry_later_desktop_entries = g_slist_prepend (dir->retry_later_desktop_entries, g_strdup (path)); + } + + return FALSE; + } + + dir->entries = g_slist_prepend (dir->entries, entry); + + return TRUE; +} + +static gboolean +cached_dir_update_entry (CachedDir *dir, + const char *basename, + const char *path) +{ + GSList *tmp; + + tmp = dir->entries; + while (tmp != NULL) + { + const char *entry_basename; + entry_basename = desktop_entry_get_basename (tmp->data); + if (strcmp (entry_basename, basename) == 0) + { + if (!desktop_entry_reload (tmp->data)) + { + dir->entries = g_slist_delete_link (dir->entries, tmp); + } + + return TRUE; + } + + tmp = tmp->next; + } + + return cached_dir_add_entry (dir, basename, path); +} + +static gboolean +cached_dir_remove_entry (CachedDir *dir, + const char *basename) +{ + GSList *tmp; + + tmp = dir->entries; + while (tmp != NULL) + { + const char *entry_basename; + entry_basename = desktop_entry_get_basename (tmp->data); + + if (strcmp (entry_basename, basename) == 0) + { + desktop_entry_unref (tmp->data); + dir->entries = g_slist_delete_link (dir->entries, tmp); + return TRUE; + } + + tmp = tmp->next; + } + + return FALSE; +} + +static CachedDir * +cached_dir_add_subdir (CachedDir *dir, + const char *basename, + const char *path) +{ + CachedDir *subdir; + + subdir = find_subdir (dir, basename); + + if (subdir != NULL) + { + subdir->deleted = FALSE; + return subdir; + } + + subdir = cached_dir_new (basename); + + if (path != NULL && !cached_dir_load_entries_recursive (subdir, path)) + { + cached_dir_free (subdir); + return NULL; + } + + menu_verbose ("Caching dir \"%s\"\n", basename); + + subdir->parent = dir; + dir->subdirs = g_slist_prepend (dir->subdirs, cached_dir_ref (subdir)); + + return subdir; +} + +static gboolean +cached_dir_remove_subdir (CachedDir *dir, + const char *basename) +{ + CachedDir *subdir; + + subdir = find_subdir (dir, basename); + + if (subdir != NULL) + { + subdir->deleted = TRUE; + + if (subdir->references == 0) + { + cached_dir_unref (subdir); + dir->subdirs = g_slist_remove (dir->subdirs, subdir); + } + + return TRUE; + } + + return FALSE; +} + +static guint monitors_idle_handler = 0; +static GSList *pending_monitors_dirs = NULL; + +static void +cached_dir_invoke_monitors (CachedDir *dir) +{ + GSList *tmp; + + tmp = dir->monitors; + while (tmp != NULL) + { + CachedDirMonitor *monitor = tmp->data; + GSList *next = tmp->next; + + monitor->callback (monitor->ed, monitor->user_data); + + tmp = next; + } + + /* we explicitly don't invoke monitors of the parent since an + * event has been queued for it too */ +} + +static gboolean +emit_monitors_in_idle (void) +{ + GSList *monitors_to_emit; + GSList *tmp; + + monitors_to_emit = pending_monitors_dirs; + + pending_monitors_dirs = NULL; + monitors_idle_handler = 0; + + tmp = monitors_to_emit; + while (tmp != NULL) + { + CachedDir *dir = tmp->data; + + cached_dir_invoke_monitors (dir); + cached_dir_remove_reference (dir); + + tmp = tmp->next; + } + + g_slist_free (monitors_to_emit); + + return FALSE; +} + +static void +cached_dir_queue_monitor_event (CachedDir *dir) +{ + GSList *tmp; + + tmp = pending_monitors_dirs; + while (tmp != NULL) + { + CachedDir *d = tmp->data; + GSList *next = tmp->next; + + if (dir->parent == d->parent && + g_strcmp0 (dir->name, d->name) == 0) + break; + + tmp = next; + } + + /* not found, so let's queue it */ + if (tmp == NULL) + { + cached_dir_add_reference (dir); + pending_monitors_dirs = g_slist_append (pending_monitors_dirs, dir); + } + + if (dir->parent) + { + cached_dir_queue_monitor_event (dir->parent); + } + + if (monitors_idle_handler == 0) + { + monitors_idle_handler = g_idle_add ((GSourceFunc) emit_monitors_in_idle, NULL); + } +} + +static void +handle_cached_dir_changed (MenuMonitor *monitor, + MenuMonitorEvent event, + const char *path, + CachedDir *dir) +{ + gboolean handled = FALSE; + gboolean retry_changes = FALSE; + + char *basename; + char *dirname; + + menu_verbose ("'%s' notified of '%s' %s - invalidating cache\n", + dir->name, + path, + event == MENU_MONITOR_EVENT_CREATED ? ("created") : + event == MENU_MONITOR_EVENT_DELETED ? ("deleted") : ("changed")); + + dirname = g_path_get_dirname (path); + basename = g_path_get_basename (path); + + dir = cached_dir_lookup (dirname); + + if (g_str_has_suffix (basename, ".desktop") || + g_str_has_suffix (basename, ".directory")) + { + switch (event) + { + case MENU_MONITOR_EVENT_CREATED: + case MENU_MONITOR_EVENT_CHANGED: + handled = cached_dir_update_entry (dir, basename, path); + break; + + case MENU_MONITOR_EVENT_DELETED: + handled = cached_dir_remove_entry (dir, basename); + break; + + default: + g_assert_not_reached (); + break; + } + } + else if (g_strcmp0 (basename, "mimeinfo.cache") == 0) + { + /* The observed file notifies when a new desktop file is added + * (but fails to load) go something like: + * + * NOTIFY: foo.desktop + * NOTIFY: mimeinfo.cache.tempfile + * NOTIFY: mimeinfo.cache.tempfile + * NOTIFY: mimeinfo.cache + * + * Additionally, the failure is not upon trying to read the file, + * but attempting to get its GAppInfo (g_desktop_app_info_new_from_filename() + * in desktop-entries.c ln 277). If you jigger desktop_entry_load() around + * and read the file as a keyfile *first*, it succeeds. If you then try + * to run g_desktop_app_info_new_from_keyfile(), *then* it fails. + * + * The theory here is there is a race condition where app info (which includes + * mimetype stuff) is unavailable because mimeinfo.cache is updated immediately + * after the app is installed. + * + * What we do here is, when a desktop fails to load, we add it to a temporary + * list. We wait until mimeinfo.cache changes, then retry that desktop file, + * which succeeds this second time. + * + * Note: An alternative fix (presented more as a proof than a suggestion) is to + * change line 151 in menu-monitor.c to use g_timeout_add_seconds, and delay + * for one second. This also avoids the issue (but it remains a race condition). + */ + + GSList *iter; + + menu_verbose ("mimeinfo changed, checking for failed entries\n"); + + for (iter = dir->retry_later_desktop_entries; iter != NULL; iter = iter->next) + { + const gchar *retry_path = iter->data; + + menu_verbose ("retrying %s\n", retry_path); + + char *retry_basename = g_path_get_basename (retry_path); + + if (cached_dir_update_entry (dir, retry_basename, retry_path)) + retry_changes = TRUE; + + g_free (retry_basename); + } + + g_slist_free_full (dir->retry_later_desktop_entries, g_free); + dir->retry_later_desktop_entries = NULL; + + handled = retry_changes; + } + else /* Try recursing */ + { + switch (event) + { + case MENU_MONITOR_EVENT_CREATED: + handled = cached_dir_add_subdir (dir, basename, path) != NULL; + break; + + case MENU_MONITOR_EVENT_CHANGED: + break; + + case MENU_MONITOR_EVENT_DELETED: + handled = cached_dir_remove_subdir (dir, basename); + break; + + default: + g_assert_not_reached (); + break; + } + } + + g_free (basename); + g_free (dirname); + + if (handled) + { + /* CHANGED events don't change the set of desktop entries, unless it's the mimeinfo.cache file changing */ + if (retry_changes || (event == MENU_MONITOR_EVENT_CREATED || event == MENU_MONITOR_EVENT_DELETED)) + { + _entry_directory_list_empty_desktop_cache (); + } + + cached_dir_queue_monitor_event (dir); + } +} + +static void +cached_dir_ensure_monitor (CachedDir *dir, + const char *dirname) +{ + if (dir->dir_monitor == NULL) + { + dir->dir_monitor = menu_get_directory_monitor (dirname); + menu_monitor_add_notify (dir->dir_monitor, + (MenuMonitorNotifyFunc) handle_cached_dir_changed, + dir); + } +} + +static gboolean +cached_dir_load_entries_recursive (CachedDir *dir, + const char *dirname) +{ + DIR *dp; + struct dirent *dent; + GString *fullpath; + gsize fullpath_len; + + g_assert (dir != NULL); + + if (dir->have_read_entries) + return TRUE; + + menu_verbose ("Attempting to read entries from %s (full path %s)\n", + dir->name, dirname); + + dp = opendir (dirname); + if (dp == NULL) + { + menu_verbose ("Unable to list directory \"%s\"\n", + dirname); + return FALSE; + } + + cached_dir_ensure_monitor (dir, dirname); + + fullpath = g_string_new (dirname); + if (fullpath->str[fullpath->len - 1] != G_DIR_SEPARATOR) + g_string_append_c (fullpath, G_DIR_SEPARATOR); + + fullpath_len = fullpath->len; + + while ((dent = readdir (dp)) != NULL) + { + /* ignore . and .. */ + if (dent->d_name[0] == '.' && + (dent->d_name[1] == '\0' || + (dent->d_name[1] == '.' && + dent->d_name[2] == '\0'))) + continue; + + g_string_append (fullpath, dent->d_name); + + if (g_str_has_suffix (dent->d_name, ".desktop") || + g_str_has_suffix (dent->d_name, ".directory")) + { + cached_dir_add_entry (dir, dent->d_name, fullpath->str); + } + else /* Try recursing */ + { + cached_dir_add_subdir (dir, dent->d_name, fullpath->str); + } + + g_string_truncate (fullpath, fullpath_len); + } + + closedir (dp); + + g_string_free (fullpath, TRUE); + + dir->have_read_entries = TRUE; + + return TRUE; +} + +static void +cached_dir_add_monitor (CachedDir *dir, + EntryDirectory *ed, + EntryDirectoryChangedFunc callback, + gpointer user_data) +{ + CachedDirMonitor *monitor; + GSList *tmp; + + tmp = dir->monitors; + while (tmp != NULL) + { + monitor = tmp->data; + + if (monitor->ed == ed && + monitor->callback == callback && + monitor->user_data == user_data) + break; + + tmp = tmp->next; + } + + if (tmp == NULL) + { + monitor = g_new0 (CachedDirMonitor, 1); + monitor->ed = ed; + monitor->callback = callback; + monitor->user_data = user_data; + + dir->monitors = g_slist_append (dir->monitors, monitor); + } +} + +static void +cached_dir_remove_monitor (CachedDir *dir, + EntryDirectory *ed, + EntryDirectoryChangedFunc callback, + gpointer user_data) +{ + GSList *tmp; + + tmp = dir->monitors; + while (tmp != NULL) + { + CachedDirMonitor *monitor = tmp->data; + GSList *next = tmp->next; + + if (monitor->ed == ed && + monitor->callback == callback && + monitor->user_data == user_data) + { + dir->monitors = g_slist_delete_link (dir->monitors, tmp); + g_free (monitor); + } + + tmp = next; + } +} + +static void +cached_dir_add_reference (CachedDir *dir) +{ + cached_dir_ref (dir); + + if (dir->parent != NULL) + { + cached_dir_add_reference (dir->parent); + } +} + +static void +cached_dir_remove_reference (CachedDir *dir) +{ + CachedDir *parent; + + parent = dir->parent; + + cached_dir_unref (dir); + + if (parent != NULL) + { + cached_dir_remove_reference (parent); + } +} + +/* + * Entry directories + */ + +static EntryDirectory * +entry_directory_new_full (DesktopEntryType entry_type, + const char *path, + gboolean is_legacy, + const char *legacy_prefix) +{ + EntryDirectory *ed; + char *canonical; + + menu_verbose ("Loading entry directory \"%s\" (legacy %s)\n", + path, + is_legacy ? "" : ""); + + canonical = menu_canonicalize_file_name (path, FALSE); + if (canonical == NULL) + { + menu_verbose ("Failed to canonicalize \"%s\": %s\n", + path, g_strerror (errno)); + return NULL; + } + + ed = g_new0 (EntryDirectory, 1); + + ed->dir = cached_dir_lookup (canonical); + g_assert (ed->dir != NULL); + + cached_dir_add_reference (ed->dir); + cached_dir_load_entries_recursive (ed->dir, canonical); + + ed->legacy_prefix = g_strdup (legacy_prefix); + ed->entry_type = entry_type; + ed->is_legacy = is_legacy != FALSE; + ed->refcount = 1; + + g_free (canonical); + + return ed; +} + +EntryDirectory * +entry_directory_new (DesktopEntryType entry_type, + const char *path) +{ + return entry_directory_new_full (entry_type, path, FALSE, NULL); +} + +EntryDirectory * +entry_directory_new_legacy (DesktopEntryType entry_type, + const char *path, + const char *legacy_prefix) +{ + return entry_directory_new_full (entry_type, path, TRUE, legacy_prefix); +} + +EntryDirectory * +entry_directory_ref (EntryDirectory *ed) +{ + g_return_val_if_fail (ed != NULL, NULL); + g_return_val_if_fail (ed->refcount > 0, NULL); + + g_atomic_int_inc (&ed->refcount); + + return ed; +} + +void +entry_directory_unref (EntryDirectory *ed) +{ + gboolean is_zero; + + g_return_if_fail (ed != NULL); + g_return_if_fail (ed->refcount > 0); + + is_zero = g_atomic_int_dec_and_test (&ed->refcount); + if (is_zero) + { + cached_dir_remove_reference (ed->dir); + + ed->dir = NULL; + ed->entry_type = DESKTOP_ENTRY_INVALID; + ed->is_legacy = FALSE; + + g_free (ed->legacy_prefix); + ed->legacy_prefix = NULL; + + g_free (ed); + } +} + +static void +entry_directory_add_monitor (EntryDirectory *ed, + EntryDirectoryChangedFunc callback, + gpointer user_data) +{ + cached_dir_add_monitor (ed->dir, ed, callback, user_data); +} + +static void +entry_directory_remove_monitor (EntryDirectory *ed, + EntryDirectoryChangedFunc callback, + gpointer user_data) +{ + cached_dir_remove_monitor (ed->dir, ed, callback, user_data); +} + +static DesktopEntry * +entry_directory_get_directory (EntryDirectory *ed, + const char *relative_path) +{ + DesktopEntry *entry; + + if (ed->entry_type != DESKTOP_ENTRY_DIRECTORY) + return NULL; + + entry = cached_dir_find_relative_path (ed->dir, relative_path); + if (entry == NULL || desktop_entry_get_type (entry) != DESKTOP_ENTRY_DIRECTORY) + return NULL; + + return desktop_entry_ref (entry); +} + +static char * +get_desktop_file_id_from_path (EntryDirectory *ed, + DesktopEntryType entry_type, + const char *relative_path) +{ + char *retval; + + retval = NULL; + + if (entry_type == DESKTOP_ENTRY_DESKTOP) + { + if (!ed->is_legacy) + { + retval = g_strdelimit (g_strdup (relative_path), "/", '-'); + } + else + { + char *basename; + + basename = g_path_get_basename (relative_path); + + if (ed->legacy_prefix) + { + retval = g_strjoin ("-", ed->legacy_prefix, basename, NULL); + g_free (basename); + } + else + { + retval = basename; + } + } + } + else + { + retval = g_strdup (relative_path); + } + + return retval; +} + +typedef gboolean (* EntryDirectoryForeachFunc) (EntryDirectory *ed, + DesktopEntry *entry, + const char *file_id, + DesktopEntrySet *set, + gpointer user_data); + +static gboolean +entry_directory_foreach_recursive (EntryDirectory *ed, + CachedDir *cd, + GString *relative_path, + EntryDirectoryForeachFunc func, + DesktopEntrySet *set, + gpointer user_data) +{ + GSList *tmp; + int relative_path_len; + + if (cd->deleted) + return TRUE; + + relative_path_len = relative_path->len; + + tmp = cd->entries; + while (tmp != NULL) + { + DesktopEntry *entry = tmp->data; + + if (desktop_entry_get_type (entry) == ed->entry_type) + { + gboolean ret; + char *file_id; + const char *basename; + + basename = desktop_entry_get_basename (entry); + g_string_append (relative_path, basename); + + file_id = get_desktop_file_id_from_path (ed, + ed->entry_type, + relative_path->str); + + ret = func (ed, entry, file_id, set, user_data); + + g_free (file_id); + + g_string_truncate (relative_path, relative_path_len); + + if (!ret) + return FALSE; + } + + tmp = tmp->next; + } + + tmp = cd->subdirs; + while (tmp != NULL) + { + CachedDir *subdir = tmp->data; + + g_string_append (relative_path, subdir->name); + g_string_append_c (relative_path, G_DIR_SEPARATOR); + + if (!entry_directory_foreach_recursive (ed, + subdir, + relative_path, + func, + set, + user_data)) + return FALSE; + + g_string_truncate (relative_path, relative_path_len); + + tmp = tmp->next; + } + + return TRUE; +} + +static void +entry_directory_foreach (EntryDirectory *ed, + EntryDirectoryForeachFunc func, + DesktopEntrySet *set, + gpointer user_data) +{ + GString *path; + + path = g_string_new (NULL); + + entry_directory_foreach_recursive (ed, + ed->dir, + path, + func, + set, + user_data); + + g_string_free (path, TRUE); +} + +void +entry_directory_get_flat_contents (EntryDirectory *ed, + DesktopEntrySet *desktop_entries, + DesktopEntrySet *directory_entries, + GSList **subdirs) +{ + GSList *tmp; + + if (subdirs) + *subdirs = NULL; + + tmp = ed->dir->entries; + while (tmp != NULL) + { + DesktopEntry *entry = tmp->data; + const char *basename; + + basename = desktop_entry_get_path (entry); + + if (desktop_entries && + desktop_entry_get_type (entry) == DESKTOP_ENTRY_DESKTOP) + { + char *file_id; + + file_id = get_desktop_file_id_from_path (ed, + DESKTOP_ENTRY_DESKTOP, + basename); + + desktop_entry_set_add_entry (desktop_entries, + entry, + file_id); + + g_free (file_id); + } + + if (directory_entries && + desktop_entry_get_type (entry) == DESKTOP_ENTRY_DIRECTORY) + { + desktop_entry_set_add_entry (directory_entries, + entry, + basename); + } + + tmp = tmp->next; + } + + if (subdirs) + { + tmp = ed->dir->subdirs; + while (tmp != NULL) + { + CachedDir *cd = tmp->data; + + if (!cd->deleted) + { + *subdirs = g_slist_prepend (*subdirs, g_strdup (cd->name)); + } + + tmp = tmp->next; + } + } + + if (subdirs) + *subdirs = g_slist_reverse (*subdirs); +} + +/* + * Entry directory lists + */ + +EntryDirectoryList * +entry_directory_list_new (void) +{ + EntryDirectoryList *list; + + list = g_new0 (EntryDirectoryList, 1); + + list->refcount = 1; + list->dirs = NULL; + list->length = 0; + + return list; +} + +EntryDirectoryList * +entry_directory_list_ref (EntryDirectoryList *list) +{ + g_return_val_if_fail (list != NULL, NULL); + g_return_val_if_fail (list->refcount > 0, NULL); + + g_atomic_int_inc (&list->refcount); + + return list; +} + +void +entry_directory_list_unref (EntryDirectoryList *list) +{ + gboolean is_zero; + + g_return_if_fail (list != NULL); + g_return_if_fail (list->refcount > 0); + + is_zero = g_atomic_int_dec_and_test (&list->refcount); + if (is_zero) + { + g_list_foreach (list->dirs, (GFunc) entry_directory_unref, NULL); + g_list_free (list->dirs); + list->dirs = NULL; + list->length = 0; + g_free (list); + } +} + +void +entry_directory_list_prepend (EntryDirectoryList *list, + EntryDirectory *ed) +{ + list->length += 1; + list->dirs = g_list_prepend (list->dirs, + entry_directory_ref (ed)); +} + +int +entry_directory_list_get_length (EntryDirectoryList *list) +{ + return list->length; +} + +void +entry_directory_list_append_list (EntryDirectoryList *list, + EntryDirectoryList *to_append) +{ + GList *tmp; + GList *new_dirs = NULL; + + if (to_append->length == 0) + return; + + tmp = to_append->dirs; + while (tmp != NULL) + { + list->length += 1; + new_dirs = g_list_prepend (new_dirs, + entry_directory_ref (tmp->data)); + + tmp = tmp->next; + } + + new_dirs = g_list_reverse (new_dirs); + list->dirs = g_list_concat (list->dirs, new_dirs); +} + +DesktopEntry * +entry_directory_list_get_directory (EntryDirectoryList *list, + const char *relative_path) +{ + DesktopEntry *retval = NULL; + GList *tmp; + + tmp = list->dirs; + while (tmp != NULL) + { + if ((retval = entry_directory_get_directory (tmp->data, relative_path)) != NULL) + break; + + tmp = tmp->next; + } + + return retval; +} + +gboolean +_entry_directory_list_compare (const EntryDirectoryList *a, + const EntryDirectoryList *b) +{ + GList *al, *bl; + + if (a == NULL && b == NULL) + return TRUE; + + if ((a == NULL || b == NULL)) + return FALSE; + + if (a->length != b->length) + return FALSE; + + al = a->dirs; bl = b->dirs; + while (al && bl && al->data == bl->data) + { + al = al->next; + bl = bl->next; + } + + return (al == NULL && bl == NULL); +} + +static gboolean +get_all_func (EntryDirectory *ed, + DesktopEntry *entry, + const char *file_id, + DesktopEntrySet *set, + gpointer user_data) +{ + if (ed->is_legacy && !desktop_entry_has_categories (entry)) + { + entry = desktop_entry_copy (entry); + desktop_entry_add_legacy_category (entry); + } + else + { + entry = desktop_entry_ref (entry); + } + + desktop_entry_set_add_entry (set, entry, file_id); + desktop_entry_unref (entry); + + return TRUE; +} + +static DesktopEntrySet *entry_directory_last_set = NULL; +static EntryDirectoryList *entry_directory_last_list = NULL; + +void +_entry_directory_list_empty_desktop_cache (void) +{ + if (entry_directory_last_set != NULL) + desktop_entry_set_unref (entry_directory_last_set); + entry_directory_last_set = NULL; + + if (entry_directory_last_list != NULL) + entry_directory_list_unref (entry_directory_last_list); + entry_directory_last_list = NULL; +} + +DesktopEntrySet * +_entry_directory_list_get_all_desktops (EntryDirectoryList *list) +{ + GList *tmp; + DesktopEntrySet *set; + + /* The only tricky thing here is that desktop files later + * in the search list with the same relative path + * are "hidden" by desktop files earlier in the path, + * so we have to do the earlier files first causing + * the later files to replace the earlier files + * in the DesktopEntrySet + * + * We go from the end of the list so we can just + * g_hash_table_replace and not have to do two + * hash lookups (check for existing entry, then insert new + * entry) + */ + + /* This method is -extremely- slow, so we have a simple + one-entry cache here */ + if (_entry_directory_list_compare (list, entry_directory_last_list)) + { + menu_verbose (" Hit desktop list (%p) cache\n", list); + return desktop_entry_set_ref (entry_directory_last_set); + } + + if (entry_directory_last_set != NULL) + desktop_entry_set_unref (entry_directory_last_set); + if (entry_directory_last_list != NULL) + entry_directory_list_unref (entry_directory_last_list); + + set = desktop_entry_set_new (); + menu_verbose (" Storing all of list %p in set %p\n", + list, set); + + tmp = g_list_last (list->dirs); + while (tmp != NULL) + { + entry_directory_foreach (tmp->data, get_all_func, set, NULL); + + tmp = tmp->prev; + } + + entry_directory_last_list = entry_directory_list_ref (list); + entry_directory_last_set = desktop_entry_set_ref (set); + + return set; +} + +void +entry_directory_list_add_monitors (EntryDirectoryList *list, + EntryDirectoryChangedFunc callback, + gpointer user_data) +{ + GList *tmp; + + tmp = list->dirs; + while (tmp != NULL) + { + entry_directory_add_monitor (tmp->data, callback, user_data); + tmp = tmp->next; + } +} + +void +entry_directory_list_remove_monitors (EntryDirectoryList *list, + EntryDirectoryChangedFunc callback, + gpointer user_data) +{ + GList *tmp; + + tmp = list->dirs; + while (tmp != NULL) + { + entry_directory_remove_monitor (tmp->data, callback, user_data); + tmp = tmp->next; + } +} diff --git a/libmenu/entry-directories.h b/libmenu/entry-directories.h new file mode 100644 index 0000000..4b1f5fb --- /dev/null +++ b/libmenu/entry-directories.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2002 - 2004 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ENTRY_DIRECTORIES_H__ +#define __ENTRY_DIRECTORIES_H__ + +#include +#include "desktop-entries.h" + +G_BEGIN_DECLS + +typedef struct EntryDirectory EntryDirectory; + +typedef void (*EntryDirectoryChangedFunc) (EntryDirectory *ed, + gpointer user_data); + +EntryDirectory *entry_directory_new (DesktopEntryType entry_type, + const char *path); +EntryDirectory *entry_directory_new_legacy (DesktopEntryType entry_type, + const char *path, + const char *legacy_prefix); + +EntryDirectory *entry_directory_ref (EntryDirectory *ed); +void entry_directory_unref (EntryDirectory *ed); + +void entry_directory_get_flat_contents (EntryDirectory *ed, + DesktopEntrySet *desktop_entries, + DesktopEntrySet *directory_entries, + GSList **subdirs); + + +typedef struct EntryDirectoryList EntryDirectoryList; + +EntryDirectoryList *entry_directory_list_new (void); +EntryDirectoryList *entry_directory_list_ref (EntryDirectoryList *list); +void entry_directory_list_unref (EntryDirectoryList *list); + +int entry_directory_list_get_length (EntryDirectoryList *list); +gboolean _entry_directory_list_compare (const EntryDirectoryList *a, + const EntryDirectoryList *b); + +void entry_directory_list_prepend (EntryDirectoryList *list, + EntryDirectory *ed); +void entry_directory_list_append_list (EntryDirectoryList *list, + EntryDirectoryList *to_append); + +void entry_directory_list_add_monitors (EntryDirectoryList *list, + EntryDirectoryChangedFunc callback, + gpointer user_data); +void entry_directory_list_remove_monitors (EntryDirectoryList *list, + EntryDirectoryChangedFunc callback, + gpointer user_data); + +DesktopEntry* entry_directory_list_get_directory (EntryDirectoryList *list, + const char *relative_path); + +DesktopEntrySet *_entry_directory_list_get_all_desktops (EntryDirectoryList *list); +void _entry_directory_list_empty_desktop_cache (void); + +G_END_DECLS + +#endif /* __ENTRY_DIRECTORIES_H__ */ diff --git a/libmenu/gmenu-tree.c b/libmenu/gmenu-tree.c new file mode 100644 index 0000000..c8c31e9 --- /dev/null +++ b/libmenu/gmenu-tree.c @@ -0,0 +1,4941 @@ +/* -*- mode:c; c-file-style: "gnu"; indent-tabs-mode: nil -*- + * Copyright (C) 2003, 2004, 2011 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#include "gmenu-tree.h" + +#include +#include + +#include "menu-layout.h" +#include "menu-monitor.h" +#include "menu-util.h" +#include "canonicalize.h" + +/* private */ +typedef struct GMenuTreeItem GMenuTreeItem; +#define GMENU_TREE_ITEM(i) ((GMenuTreeItem *)(i)) +#define GMENU_TREE_DIRECTORY(i) ((GMenuTreeDirectory *)(i)) +#define GMENU_TREE_ENTRY(i) ((GMenuTreeEntry *)(i)) +#define GMENU_TREE_SEPARATOR(i) ((GMenuTreeSeparator *)(i)) +#define GMENU_TREE_HEADER(i) ((GMenuTreeHeader *)(i)) +#define GMENU_TREE_ALIAS(i) ((GMenuTreeAlias *)(i)) + +enum { + PROP_0, + + PROP_MENU_BASENAME, + PROP_MENU_PATH, + PROP_FLAGS +}; + +/* Signals */ +enum +{ + CHANGED, + LAST_SIGNAL +}; + +static guint gmenu_tree_signals [LAST_SIGNAL] = { 0 }; + +struct _GMenuTree +{ + GObject parent_instance; + + char *basename; + char *non_prefixed_basename; + char *path; + char *canonical_path; + + GMenuTreeFlags flags; + + GSList *menu_file_monitors; + + MenuLayoutNode *layout; + GMenuTreeDirectory *root; + GHashTable *entries_by_id; + + guint canonical : 1; + guint loaded : 1; +}; + +G_DEFINE_TYPE (GMenuTree, gmenu_tree, G_TYPE_OBJECT) + +struct GMenuTreeItem +{ + volatile gint refcount; + + GMenuTreeItemType type; + + GMenuTreeDirectory *parent; + GMenuTree *tree; +}; + +struct GMenuTreeIter +{ + volatile gint refcount; + + GMenuTreeItem *item; + GSList *contents; + GSList *contents_iter; +}; + +struct GMenuTreeDirectory +{ + GMenuTreeItem item; + + DesktopEntry *directory_entry; + char *name; + + GSList *entries; + GSList *subdirs; + + MenuLayoutValues default_layout_values; + GSList *default_layout_info; + GSList *layout_info; + GSList *contents; + + guint only_unallocated : 1; + guint is_nodisplay : 1; + guint layout_pending_separator : 1; + guint preprocessed : 1; + + /* 16 bits should be more than enough; G_MAXUINT16 means no inline header */ + guint will_inline_header : 16; +}; + +struct GMenuTreeEntry +{ + GMenuTreeItem item; + + DesktopEntry *desktop_entry; + char *desktop_file_id; + + guint is_excluded : 1; + guint is_unallocated : 1; +}; + +struct GMenuTreeSeparator +{ + GMenuTreeItem item; +}; + +struct GMenuTreeHeader +{ + GMenuTreeItem item; + + GMenuTreeDirectory *directory; +}; + +struct GMenuTreeAlias +{ + GMenuTreeItem item; + + GMenuTreeDirectory *directory; + GMenuTreeItem *aliased_item; +}; + +static gboolean gmenu_tree_load_layout (GMenuTree *tree, + GError **error); +static void gmenu_tree_force_reload (GMenuTree *tree); +static gboolean gmenu_tree_build_from_layout (GMenuTree *tree, + GError **error); +static void gmenu_tree_force_rebuild (GMenuTree *tree); +static void gmenu_tree_resolve_files (GMenuTree *tree, + GHashTable *loaded_menu_files, + MenuLayoutNode *layout); +static void gmenu_tree_force_recanonicalize (GMenuTree *tree); +static void gmenu_tree_invoke_monitors (GMenuTree *tree); + +static void gmenu_tree_item_unref_and_unset_parent (gpointer itemp); + +typedef enum +{ + MENU_FILE_MONITOR_INVALID = 0, + MENU_FILE_MONITOR_FILE, + MENU_FILE_MONITOR_NONEXISTENT_FILE, + MENU_FILE_MONITOR_DIRECTORY +} MenuFileMonitorType; + +typedef struct +{ + MenuFileMonitorType type; + MenuMonitor *monitor; +} MenuFileMonitor; + +static void +handle_nonexistent_menu_file_changed (MenuMonitor *monitor, + MenuMonitorEvent event, + const char *path, + GMenuTree *tree) +{ + if (event == MENU_MONITOR_EVENT_CHANGED || + event == MENU_MONITOR_EVENT_CREATED) + { + menu_verbose ("\"%s\" %s, marking tree for recanonicalization\n", + path, + event == MENU_MONITOR_EVENT_CREATED ? "created" : "changed"); + + gmenu_tree_force_recanonicalize (tree); + gmenu_tree_invoke_monitors (tree); + } +} + +static void +handle_menu_file_changed (MenuMonitor *monitor, + MenuMonitorEvent event, + const char *path, + GMenuTree *tree) +{ + menu_verbose ("\"%s\" %s, marking tree for recanicalization\n", + path, + event == MENU_MONITOR_EVENT_CREATED ? "created" : + event == MENU_MONITOR_EVENT_CHANGED ? "changed" : "deleted"); + + gmenu_tree_force_recanonicalize (tree); + gmenu_tree_invoke_monitors (tree); +} + +static void +handle_menu_file_directory_changed (MenuMonitor *monitor, + MenuMonitorEvent event, + const char *path, + GMenuTree *tree) +{ + if (!g_str_has_suffix (path, ".menu")) + return; + + menu_verbose ("\"%s\" %s, marking tree for recanicalization\n", + path, + event == MENU_MONITOR_EVENT_CREATED ? "created" : + event == MENU_MONITOR_EVENT_CHANGED ? "changed" : "deleted"); + + gmenu_tree_force_recanonicalize (tree); + gmenu_tree_invoke_monitors (tree); +} + +static void +gmenu_tree_add_menu_file_monitor (GMenuTree *tree, + const char *path, + MenuFileMonitorType type) +{ + MenuFileMonitor *monitor; + + monitor = g_slice_new0 (MenuFileMonitor); + + monitor->type = type; + + switch (type) + { + case MENU_FILE_MONITOR_FILE: + menu_verbose ("Adding a menu file monitor for \"%s\"\n", path); + + monitor->monitor = menu_get_file_monitor (path); + menu_monitor_add_notify (monitor->monitor, + (MenuMonitorNotifyFunc) handle_menu_file_changed, + tree); + break; + + case MENU_FILE_MONITOR_NONEXISTENT_FILE: + menu_verbose ("Adding a menu file monitor for non-existent \"%s\"\n", path); + + monitor->monitor = menu_get_file_monitor (path); + menu_monitor_add_notify (monitor->monitor, + (MenuMonitorNotifyFunc) handle_nonexistent_menu_file_changed, + tree); + break; + + case MENU_FILE_MONITOR_DIRECTORY: + menu_verbose ("Adding a menu directory monitor for \"%s\"\n", path); + + monitor->monitor = menu_get_directory_monitor (path); + menu_monitor_add_notify (monitor->monitor, + (MenuMonitorNotifyFunc) handle_menu_file_directory_changed, + tree); + break; + + default: + g_assert_not_reached (); + break; + } + + tree->menu_file_monitors = g_slist_prepend (tree->menu_file_monitors, monitor); +} + +static void +remove_menu_file_monitor (MenuFileMonitor *monitor, + GMenuTree *tree) +{ + switch (monitor->type) + { + case MENU_FILE_MONITOR_FILE: + menu_monitor_remove_notify (monitor->monitor, + (MenuMonitorNotifyFunc) handle_menu_file_changed, + tree); + break; + + case MENU_FILE_MONITOR_NONEXISTENT_FILE: + menu_monitor_remove_notify (monitor->monitor, + (MenuMonitorNotifyFunc) handle_nonexistent_menu_file_changed, + tree); + break; + + case MENU_FILE_MONITOR_DIRECTORY: + menu_monitor_remove_notify (monitor->monitor, + (MenuMonitorNotifyFunc) handle_menu_file_directory_changed, + tree); + break; + + default: + g_assert_not_reached (); + break; + } + + menu_monitor_unref (monitor->monitor); + monitor->monitor = NULL; + + monitor->type = MENU_FILE_MONITOR_INVALID; + + g_slice_free (MenuFileMonitor, monitor); +} + +static void +gmenu_tree_remove_menu_file_monitors (GMenuTree *tree) +{ + menu_verbose ("Removing all menu file monitors\n"); + + g_slist_foreach (tree->menu_file_monitors, + (GFunc) remove_menu_file_monitor, + tree); + g_slist_free (tree->menu_file_monitors); + tree->menu_file_monitors = NULL; +} + +static gboolean +canonicalize_path (GMenuTree *tree, + const char *path) +{ + tree->canonical_path = menu_canonicalize_file_name (path, FALSE); + if (tree->canonical_path) + { + tree->canonical = TRUE; + gmenu_tree_add_menu_file_monitor (tree, + tree->canonical_path, + MENU_FILE_MONITOR_FILE); + } + else + { + gmenu_tree_add_menu_file_monitor (tree, + path, + MENU_FILE_MONITOR_NONEXISTENT_FILE); + } + + return tree->canonical; +} + +static gboolean +canonicalize_basename_with_config_dir (GMenuTree *tree, + const char *basename, + const char *config_dir) +{ + gboolean ret; + char *path; + + path = g_build_filename (config_dir, "menus", basename, NULL); + ret = canonicalize_path (tree, path); + g_free (path); + + return ret; +} + +static void +canonicalize_basename (GMenuTree *tree, + const char *basename) +{ + if (!canonicalize_basename_with_config_dir (tree, + basename, + g_get_user_config_dir ())) + { + const char * const *system_config_dirs; + int i; + + system_config_dirs = g_get_system_config_dirs (); + + i = 0; + while (system_config_dirs[i] != NULL) + { + if (canonicalize_basename_with_config_dir (tree, + basename, + system_config_dirs[i])) + break; + + ++i; + } + } +} + +static char * +prefix_menu_name (const char *orig_name) +{ + char *prefix; + prefix = (char *) g_getenv ("XDG_MENU_PREFIX"); + if (prefix == NULL) + prefix = "gnome-"; + return g_strconcat (prefix, orig_name, NULL); +} + +static gboolean +gmenu_tree_canonicalize_path (GMenuTree *tree, + GError **error) +{ + const char *menu_file = NULL; + + if (tree->canonical) + return TRUE; + + g_assert (tree->canonical_path == NULL); + + gmenu_tree_remove_menu_file_monitors (tree); + + if (tree->path) + { + menu_file = tree->path; + canonicalize_path (tree, tree->path); + } + else + { + const gchar *xdg_menu_prefix; + + menu_file = tree->basename; + xdg_menu_prefix = g_getenv ("XDG_MENU_PREFIX"); + + if (xdg_menu_prefix == NULL) + xdg_menu_prefix = "gnome-"; + + if (xdg_menu_prefix != NULL) + { + gchar *prefixed_basename; + + prefixed_basename = g_strdup_printf ("%sapplications.menu", + xdg_menu_prefix); + + /* Some gnome-menus using applications just use "applications.menu" + * as the basename and expect gnome-menus to prefix it. Others (e.g. + * Alacarte) explicitly use "${XDG_MENU_PREFIX}applications.menu" as + * the basename, because they want to save changes to the right files + * in ~. In both cases, we want to use "applications-merged" as the + * merge directory (as required by the fd.o menu spec), so we save + * the non-prefixed basename and use it later when calling + * menu_layout_load(). + */ + if (!g_strcmp0 (tree->basename, "applications.menu") || + !g_strcmp0 (tree->basename, prefixed_basename)) + { + canonicalize_basename (tree, prefixed_basename); + g_free (tree->non_prefixed_basename); + tree->non_prefixed_basename = g_strdup ("applications.menu"); + } + g_free (prefixed_basename); + } + + if (!tree->canonical) + canonicalize_basename (tree, tree->basename); + } + + if (tree->canonical) + { + menu_verbose ("Successfully looked up menu_file for \"%s\": %s\n", + menu_file, tree->canonical_path); + return TRUE; + } + else + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Failed to look up menu_file for \"%s\"\n", + menu_file); + return FALSE; + } +} + +static void +gmenu_tree_force_recanonicalize (GMenuTree *tree) +{ + gmenu_tree_remove_menu_file_monitors (tree); + + if (tree->canonical) + { + gmenu_tree_force_reload (tree); + + g_free (tree->canonical_path); + tree->canonical_path = NULL; + + tree->canonical = FALSE; + } +} + +/** + * gmenu_tree_new: + * @menu_basename: Basename of menu file + * @flags: Flags controlling menu content + * + * Returns: (transfer full): A new #GMenuTree instance + */ +GMenuTree * +gmenu_tree_new (const char *menu_basename, + GMenuTreeFlags flags) +{ + g_return_val_if_fail (menu_basename != NULL, NULL); + + return g_object_new (GMENU_TYPE_TREE, + "menu-basename", menu_basename, + "flags", flags, + NULL); +} + +/** + * gmenu_tree_new_fo_path: + * @menu_path: Path of menu file + * @flags: Flags controlling menu content + * + * Returns: (transfer full): A new #GMenuTree instance + */ +GMenuTree * +gmenu_tree_new_for_path (const char *menu_path, + GMenuTreeFlags flags) +{ + g_return_val_if_fail (menu_path != NULL, NULL); + + return g_object_new (GMENU_TYPE_TREE, + "menu-path", menu_path, + "flags", flags, + NULL); +} + +static GObject * +gmenu_tree_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GObject *obj; + GMenuTree *self; + + obj = G_OBJECT_CLASS (gmenu_tree_parent_class)->constructor (type, + n_construct_properties, + construct_properties); + + /* If GMenuTree:menu-path is set, then we should make sure that + * GMenuTree:menu-basename is unset (especially as it has a default + * value). This has to be done here, in the constructor, since the + * properties are construct-only. */ + + self = GMENU_TREE (obj); + + if (self->path != NULL) + g_object_set (self, "menu-basename", NULL, NULL); + + return obj; +} + +static void +gmenu_tree_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GMenuTree *self = GMENU_TREE (object); + + switch (prop_id) + { + case PROP_MENU_BASENAME: + self->basename = g_value_dup_string (value); + break; + + case PROP_MENU_PATH: + self->path = g_value_dup_string (value); + break; + + case PROP_FLAGS: + self->flags = g_value_get_flags (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gmenu_tree_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GMenuTree *self = GMENU_TREE (object); + + switch (prop_id) + { + case PROP_MENU_BASENAME: + g_value_set_string (value, self->basename); + break; + case PROP_MENU_PATH: + g_value_set_string (value, self->path); + break; + case PROP_FLAGS: + g_value_set_flags (value, self->flags); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gmenu_tree_finalize (GObject *object) +{ + GMenuTree *tree = GMENU_TREE (object); + + gmenu_tree_force_recanonicalize (tree); + + if (tree->basename != NULL) + g_free (tree->basename); + tree->basename = NULL; + + g_free (tree->non_prefixed_basename); + tree->non_prefixed_basename = NULL; + + if (tree->path != NULL) + g_free (tree->path); + tree->path = NULL; + + if (tree->canonical_path != NULL) + g_free (tree->canonical_path); + tree->canonical_path = NULL; + + g_hash_table_destroy (tree->entries_by_id); + tree->entries_by_id = NULL; + + G_OBJECT_CLASS (gmenu_tree_parent_class)->finalize (object); +} + +static void +gmenu_tree_init (GMenuTree *self) +{ + self->entries_by_id = g_hash_table_new (g_str_hash, g_str_equal); +} + +static void +gmenu_tree_class_init (GMenuTreeClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->constructor = gmenu_tree_constructor; + gobject_class->get_property = gmenu_tree_get_property; + gobject_class->set_property = gmenu_tree_set_property; + gobject_class->finalize = gmenu_tree_finalize; + + /** + * GMenuTree:menu-basename: + * + * The name of the menu file; must be a basename or a relative path. The file + * will be looked up in $XDG_CONFIG_DIRS/menus/. See the Desktop Menu + * specification. + */ + g_object_class_install_property (gobject_class, + PROP_MENU_BASENAME, + g_param_spec_string ("menu-basename", "", "", + "applications.menu", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + /** + * GMenuTree:menu-path: + * + * The full path of the menu file. If set, GMenuTree:menu-basename will get + * ignored. + */ + g_object_class_install_property (gobject_class, + PROP_MENU_PATH, + g_param_spec_string ("menu-path", "", "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + /** + * GMenuTree:flags: + * + * Flags controlling the content of the menu. + */ + g_object_class_install_property (gobject_class, + PROP_FLAGS, + g_param_spec_flags ("flags", "", "", + GMENU_TYPE_TREE_FLAGS, + GMENU_TREE_FLAGS_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + /** + * GMenuTree:changed: + * + * This signal is emitted when applications are added, removed, or + * upgraded. But note the new data will only be visible after + * gmenu_tree_load_sync() or a variant thereof is invoked. + */ + gmenu_tree_signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +/** + * gmenu_tree_get_canonical_menu_path: + * @tree: a #GMenuTree + * + * This function is only available if the tree has been loaded via + * gmenu_tree_load_sync() or a variant thereof. + * + * Returns: The absolute and canonicalized path to the loaded menu file + */ +const char * +gmenu_tree_get_canonical_menu_path (GMenuTree *tree) +{ + g_return_val_if_fail (GMENU_IS_TREE (tree), NULL); + g_return_val_if_fail (tree->loaded, NULL); + + return tree->canonical_path; +} + +/** + * gmenu_tree_load_sync: + * @tree: a #GMenuTree + * @error: a #GError + * + * Synchronously load the menu contents. This function + * performs a significant amount of blocking I/O if the + * tree has not been loaded yet. + * + * Returns: %TRUE on success, %FALSE on error + */ +gboolean +gmenu_tree_load_sync (GMenuTree *tree, + GError **error) +{ + GError *local_error = NULL; + + if (tree->loaded) + return TRUE; + + if (!gmenu_tree_build_from_layout (tree, &local_error)) + { + if (local_error) + g_propagate_error (error, local_error); + return FALSE; + } + + tree->loaded = TRUE; + + return TRUE; +} + +/** + * gmenu_tree_get_root_directory: + * @tree: a #GMenuTree + * + * Get the root directory; you must have loaded the tree first (at + * least once) via gmenu_tree_load_sync() or a variant thereof. + * + * Returns: (transfer full): Root of the tree + */ +GMenuTreeDirectory * +gmenu_tree_get_root_directory (GMenuTree *tree) +{ + g_return_val_if_fail (tree != NULL, NULL); + g_return_val_if_fail (tree->loaded, NULL); + + return gmenu_tree_item_ref (tree->root); +} + +static GMenuTreeDirectory * +find_path (GMenuTreeDirectory *directory, + const char *path) +{ + const char *name; + char *slash; + char *freeme; + GSList *tmp; + + while (path[0] == G_DIR_SEPARATOR) path++; + + if (path[0] == '\0') + return directory; + + freeme = NULL; + slash = strchr (path, G_DIR_SEPARATOR); + if (slash) + { + name = freeme = g_strndup (path, slash - path); + path = slash + 1; + } + else + { + name = path; + path = NULL; + } + + tmp = directory->contents; + while (tmp != NULL) + { + GMenuTreeItem *item = tmp->data; + + if (item->type != GMENU_TREE_ITEM_DIRECTORY) + { + tmp = tmp->next; + continue; + } + + if (!strcmp (name, GMENU_TREE_DIRECTORY (item)->name)) + { + g_free (freeme); + + if (path) + return find_path (GMENU_TREE_DIRECTORY (item), path); + else + return GMENU_TREE_DIRECTORY (item); + } + + tmp = tmp->next; + } + + g_free (freeme); + + return NULL; +} + +GMenuTreeDirectory * +gmenu_tree_get_directory_from_path (GMenuTree *tree, + const char *path) +{ + GMenuTreeDirectory *root; + GMenuTreeDirectory *directory; + + g_return_val_if_fail (tree != NULL, NULL); + g_return_val_if_fail (path != NULL, NULL); + + if (path[0] != G_DIR_SEPARATOR) + return NULL; + + if (!(root = gmenu_tree_get_root_directory (tree))) + return NULL; + + directory = find_path (root, path); + + gmenu_tree_item_unref (root); + + return directory ? gmenu_tree_item_ref (directory) : NULL; +} + +/** + * gmenu_tree_get_entry_by_id: + * @tree: a #GMenuTree + * @id: a desktop file ID + * + * Look up the entry corresponding to the given "desktop file id". + * + * Returns: (transfer full): A newly referenced #GMenuTreeEntry, or %NULL if none + */ +GMenuTreeEntry * +gmenu_tree_get_entry_by_id (GMenuTree *tree, + const char *id) +{ + GMenuTreeEntry *entry; + + g_return_val_if_fail (tree->loaded, NULL); + + entry = g_hash_table_lookup (tree->entries_by_id, id); + if (entry != NULL) + gmenu_tree_item_ref (entry); + + return entry; +} + +static void +gmenu_tree_invoke_monitors (GMenuTree *tree) +{ + g_signal_emit (tree, gmenu_tree_signals[CHANGED], 0); +} + +static GMenuTreeDirectory * +get_parent (GMenuTreeItem *item) +{ + g_return_val_if_fail (item != NULL, NULL); + return item->parent ? gmenu_tree_item_ref (item->parent) : NULL; +} + +/** + * gmenu_tree_directory_get_parent: + * @directory: a #GMenuTreeDirectory + * + * Returns: (transfer full): The parent directory, or %NULL if none + */ +GMenuTreeDirectory * +gmenu_tree_directory_get_parent (GMenuTreeDirectory *directory) +{ + return get_parent ((GMenuTreeItem *)directory); +} + +/** + * gmenu_tree_entry_get_parent: + * @entry: a #GMenuTreeEntry + * + * Returns: (transfer full): The parent directory, or %NULL if none + */ +GMenuTreeDirectory * +gmenu_tree_entry_get_parent (GMenuTreeEntry *entry) +{ + return get_parent ((GMenuTreeItem *)entry); +} + +/** + * gmenu_tree_alias_get_parent: + * @alias: a #GMenuTreeAlias + * + * Returns: (transfer full): The parent directory, or %NULL if none + */ +GMenuTreeDirectory * +gmenu_tree_alias_get_parent (GMenuTreeAlias *alias) +{ + return get_parent ((GMenuTreeItem *)alias); +} + +/** + * gmenu_tree_header_get_parent: + * @header: a #GMenuTreeHeader + * + * Returns: (transfer full): The parent directory, or %NULL if none + */ +GMenuTreeDirectory * +gmenu_tree_header_get_parent (GMenuTreeHeader *header) +{ + return get_parent ((GMenuTreeItem *)header); +} + +/** + * gmenu_tree_separator_get_parent: + * @separator: a #GMenuTreeSeparator + * + * Returns: (transfer full): The parent directory, or %NULL if none + */ +GMenuTreeDirectory * +gmenu_tree_separator_get_parent (GMenuTreeSeparator *separator) +{ + return get_parent ((GMenuTreeItem *)separator); +} + +static void +gmenu_tree_item_set_parent (GMenuTreeItem *item, + GMenuTreeDirectory *parent) +{ + g_return_if_fail (item != NULL); + + item->parent = parent; +} + +/** + * gmenu_tree_iter_ref: (skip) + * @iter: iter + * + * Increment the reference count of @iter + */ +GMenuTreeIter * +gmenu_tree_iter_ref (GMenuTreeIter *iter) +{ + g_atomic_int_inc (&iter->refcount); + return iter; +} + +/** + * gmenu_tree_iter_unref: (skip) + * @iter: iter + * + * Decrement the reference count of @iter + */ +void +gmenu_tree_iter_unref (GMenuTreeIter *iter) +{ + if (!g_atomic_int_dec_and_test (&iter->refcount)) + return; + + g_slist_foreach (iter->contents, (GFunc)gmenu_tree_item_unref, NULL); + g_slist_free (iter->contents); + + g_slice_free (GMenuTreeIter, iter); +} + +/** + * gmenu_tree_directory_iter: + * @directory: directory + * + * Returns: (transfer full): A new iterator over the directory contents + */ +GMenuTreeIter * +gmenu_tree_directory_iter (GMenuTreeDirectory *directory) +{ + GMenuTreeIter *iter; + + g_return_val_if_fail (directory != NULL, NULL); + + iter = g_slice_new0 (GMenuTreeIter); + iter->refcount = 1; + + iter->contents = g_slist_copy (directory->contents); + iter->contents_iter = iter->contents; + g_slist_foreach (iter->contents, (GFunc) gmenu_tree_item_ref, NULL); + + return iter; +} + +/** + * gmenu_tree_iter_next: + * @iter: iter + * + * Change the iterator to the next item, and return its type. If + * there are no more items, %GMENU_TREE_ITEM_INVALID is returned. + * + * Returns: The type of the next item that can be retrived from the iterator + */ +GMenuTreeItemType +gmenu_tree_iter_next (GMenuTreeIter *iter) +{ + g_return_val_if_fail (iter != NULL, GMENU_TREE_ITEM_INVALID); + + if (iter->contents_iter) + { + iter->item = iter->contents_iter->data; + iter->contents_iter = iter->contents_iter->next; + return iter->item->type; + } + else + return GMENU_TREE_ITEM_INVALID; +} + +/** + * gmenu_tree_iter_get_directory: + * @iter: iter + * + * This method may only be called if gmenu_tree_iter_next() + * returned GMENU_TREE_ITEM_DIRECTORY. + * + * Returns: (transfer full): A directory + */ +GMenuTreeDirectory * +gmenu_tree_iter_get_directory (GMenuTreeIter *iter) +{ + g_return_val_if_fail (iter != NULL, NULL); + g_return_val_if_fail (iter->item != NULL, NULL); + g_return_val_if_fail (iter->item->type == GMENU_TREE_ITEM_DIRECTORY, NULL); + + return (GMenuTreeDirectory*)gmenu_tree_item_ref (iter->item); +} + +/** + * gmenu_tree_iter_get_entry: + * @iter: iter + * + * This method may only be called if gmenu_tree_iter_next() + * returned GMENU_TREE_ITEM_ENTRY. + * + * Returns: (transfer full): An entry + */ +GMenuTreeEntry * +gmenu_tree_iter_get_entry (GMenuTreeIter *iter) +{ + g_return_val_if_fail (iter != NULL, NULL); + g_return_val_if_fail (iter->item != NULL, NULL); + g_return_val_if_fail (iter->item->type == GMENU_TREE_ITEM_ENTRY, NULL); + + return (GMenuTreeEntry*)gmenu_tree_item_ref (iter->item); +} + +/** + * gmenu_tree_iter_get_header: + * @iter: iter + * + * This method may only be called if gmenu_tree_iter_next() + * returned GMENU_TREE_ITEM_HEADER. + * + * Returns: (transfer full): A header + */ +GMenuTreeHeader * +gmenu_tree_iter_get_header (GMenuTreeIter *iter) +{ + g_return_val_if_fail (iter != NULL, NULL); + g_return_val_if_fail (iter->item != NULL, NULL); + g_return_val_if_fail (iter->item->type == GMENU_TREE_ITEM_HEADER, NULL); + + return (GMenuTreeHeader*)gmenu_tree_item_ref (iter->item); +} + +/** + * gmenu_tree_iter_get_alias: + * @iter: iter + * + * This method may only be called if gmenu_tree_iter_next() + * returned GMENU_TREE_ITEM_ALIAS. + * + * Returns: (transfer full): An alias + */ +GMenuTreeAlias * +gmenu_tree_iter_get_alias (GMenuTreeIter *iter) +{ + g_return_val_if_fail (iter != NULL, NULL); + g_return_val_if_fail (iter->item != NULL, NULL); + g_return_val_if_fail (iter->item->type == GMENU_TREE_ITEM_ALIAS, NULL); + + return (GMenuTreeAlias*)gmenu_tree_item_ref (iter->item); +} + +/** + * gmenu_tree_iter_get_separator: + * @iter: iter + * + * This method may only be called if gmenu_tree_iter_next() + * returned #GMENU_TREE_ITEM_SEPARATOR. + * + * Returns: (transfer full): A separator + */ +GMenuTreeSeparator * +gmenu_tree_iter_get_separator (GMenuTreeIter *iter) +{ + g_return_val_if_fail (iter != NULL, NULL); + g_return_val_if_fail (iter->item != NULL, NULL); + g_return_val_if_fail (iter->item->type == GMENU_TREE_ITEM_SEPARATOR, NULL); + + return (GMenuTreeSeparator*)gmenu_tree_item_ref (iter->item); +} + +const char * +gmenu_tree_directory_get_name (GMenuTreeDirectory *directory) +{ + g_return_val_if_fail (directory != NULL, NULL); + + if (!directory->directory_entry) + return directory->name; + + return desktop_entry_get_name (directory->directory_entry); +} + +const char * +gmenu_tree_directory_get_generic_name (GMenuTreeDirectory *directory) +{ + g_return_val_if_fail (directory != NULL, NULL); + + if (!directory->directory_entry) + return NULL; + + return desktop_entry_get_generic_name (directory->directory_entry); +} + +const char * +gmenu_tree_directory_get_comment (GMenuTreeDirectory *directory) +{ + g_return_val_if_fail (directory != NULL, NULL); + + if (!directory->directory_entry) + return NULL; + + return desktop_entry_get_comment (directory->directory_entry); +} + +/** + * gmenu_tree_directory_get_icon: + * @directory: a #GMenuTreeDirectory + * + * Gets the icon for the directory. + * + * Returns: (transfer none): The #GIcon for this directory + */ +GIcon * +gmenu_tree_directory_get_icon (GMenuTreeDirectory *directory) +{ + g_return_val_if_fail (directory != NULL, NULL); + + if (!directory->directory_entry) + return NULL; + + return desktop_entry_get_icon (directory->directory_entry); +} + +const char * +gmenu_tree_directory_get_desktop_file_path (GMenuTreeDirectory *directory) +{ + g_return_val_if_fail (directory != NULL, NULL); + + if (!directory->directory_entry) + return NULL; + + return desktop_entry_get_path (directory->directory_entry); +} + +const char * +gmenu_tree_directory_get_menu_id (GMenuTreeDirectory *directory) +{ + g_return_val_if_fail (directory != NULL, NULL); + + return directory->name; +} + +gboolean +gmenu_tree_directory_get_is_nodisplay (GMenuTreeDirectory *directory) +{ + g_return_val_if_fail (directory != NULL, FALSE); + + return directory->is_nodisplay; +} + +/** + * gmenu_tree_directory_get_tree: + * @directory: A #GMenuTreeDirectory + * + * Grab the tree associated with a #GMenuTreeItem. + * + * Returns: (transfer full): The #GMenuTree + */ +GMenuTree * +gmenu_tree_directory_get_tree (GMenuTreeDirectory *directory) +{ + g_return_val_if_fail (directory != NULL, NULL); + + return g_object_ref (directory->item.tree); +} + +static void +append_directory_path (GMenuTreeDirectory *directory, + GString *path) +{ + + if (!directory->item.parent) + { + g_string_append_c (path, G_DIR_SEPARATOR); + return; + } + + append_directory_path (directory->item.parent, path); + + g_string_append (path, directory->name); + g_string_append_c (path, G_DIR_SEPARATOR); +} + +char * +gmenu_tree_directory_make_path (GMenuTreeDirectory *directory, + GMenuTreeEntry *entry) +{ + GString *path; + + g_return_val_if_fail (directory != NULL, NULL); + + path = g_string_new (NULL); + + append_directory_path (directory, path); + + if (entry != NULL) + { + const char *basename; + + basename = desktop_entry_get_basename (entry->desktop_entry); + g_string_append (path, basename); + } + + return g_string_free (path, FALSE); +} + +/** + * gmenu_tree_entry_get_app_info: + * @entry: a #GMenuTreeEntry + * + * Returns: (transfer none): The #GDesktopAppInfo for this entry + */ +GDesktopAppInfo * +gmenu_tree_entry_get_app_info (GMenuTreeEntry *entry) +{ + g_return_val_if_fail (entry != NULL, NULL); + + return desktop_entry_get_app_info (entry->desktop_entry); +} + +const char * +gmenu_tree_entry_get_desktop_file_path (GMenuTreeEntry *entry) +{ + g_return_val_if_fail (entry != NULL, NULL); + + return desktop_entry_get_path (entry->desktop_entry); +} + +const char * +gmenu_tree_entry_get_desktop_file_id (GMenuTreeEntry *entry) +{ + g_return_val_if_fail (entry != NULL, FALSE); + + return entry->desktop_file_id; +} + +gboolean +gmenu_tree_entry_get_is_nodisplay_recurse (GMenuTreeEntry *entry) +{ + GMenuTreeDirectory *directory; + GDesktopAppInfo *app_info; + + g_return_val_if_fail (entry != NULL, FALSE); + + app_info = gmenu_tree_entry_get_app_info (entry); + + if (g_desktop_app_info_get_nodisplay (app_info)) + return TRUE; + + directory = entry->item.parent; + while (directory != NULL) + { + if (directory->is_nodisplay) + return TRUE; + + directory = directory->item.parent; + } + + return FALSE; +} + +gboolean +gmenu_tree_entry_get_is_excluded (GMenuTreeEntry *entry) +{ + g_return_val_if_fail (entry != NULL, FALSE); + + return entry->is_excluded; +} + +gboolean +gmenu_tree_entry_get_is_unallocated (GMenuTreeEntry *entry) +{ + g_return_val_if_fail (entry != NULL, FALSE); + + return entry->is_unallocated; +} + +/** + * gmenu_tree_entry_get_tree: + * @entry: A #GMenuTreeEntry + * + * Grab the tree associated with a #GMenuTreeEntry. + * + * Returns: (transfer full): The #GMenuTree + */ +GMenuTree * +gmenu_tree_entry_get_tree (GMenuTreeEntry *entry) +{ + g_return_val_if_fail (entry != NULL, NULL); + + return g_object_ref (entry->item.tree); +} + +GMenuTreeDirectory * +gmenu_tree_header_get_directory (GMenuTreeHeader *header) +{ + g_return_val_if_fail (header != NULL, NULL); + + return gmenu_tree_item_ref (header->directory); +} + +/** + * gmenu_tree_header_get_tree: + * @header: A #GMenuTreeHeader + * + * Grab the tree associated with a #GMenuTreeHeader. + * + * Returns: (transfer full): The #GMenuTree + */ +GMenuTree * +gmenu_tree_header_get_tree (GMenuTreeHeader *header) +{ + g_return_val_if_fail (header != NULL, NULL); + + return g_object_ref (header->item.tree); +} + +GMenuTreeItemType +gmenu_tree_alias_get_aliased_item_type (GMenuTreeAlias *alias) +{ + g_return_val_if_fail (alias != NULL, GMENU_TREE_ITEM_INVALID); + + g_assert (alias->aliased_item != NULL); + return alias->aliased_item->type; +} + +GMenuTreeDirectory * +gmenu_tree_alias_get_directory (GMenuTreeAlias *alias) +{ + g_return_val_if_fail (alias != NULL, NULL); + + return gmenu_tree_item_ref (alias->directory); +} + +/** + * gmenu_tree_alias_get_tree: + * @alias: A #GMenuTreeAlias + * + * Grab the tree associated with a #GMenuTreeAlias. + * + * Returns: (transfer full): The #GMenuTree + */ +GMenuTree * +gmenu_tree_alias_get_tree (GMenuTreeAlias *alias) +{ + g_return_val_if_fail (alias != NULL, NULL); + + return g_object_ref (alias->item.tree); +} + +/** + * gmenu_tree_separator_get_tree: + * @separator: A #GMenuTreeSeparator + * + * Grab the tree associated with a #GMenuTreeSeparator. + * + * Returns: (transfer full): The #GMenuTree + */ +GMenuTree * +gmenu_tree_separator_get_tree (GMenuTreeSeparator *separator) +{ + g_return_val_if_fail (separator != NULL, NULL); + + return g_object_ref (separator->item.tree); +} + +/** + * gmenu_tree_alias_get_aliased_directory: + * @alias: alias + * + * Returns: (transfer full): The aliased directory entry + */ +GMenuTreeDirectory * +gmenu_tree_alias_get_aliased_directory (GMenuTreeAlias *alias) +{ + g_return_val_if_fail (alias != NULL, NULL); + g_return_val_if_fail (alias->aliased_item->type == GMENU_TREE_ITEM_DIRECTORY, NULL); + + return (GMenuTreeDirectory *) gmenu_tree_item_ref (alias->aliased_item); +} + +/** + * gmenu_tree_alias_get_aliased_entry: + * @alias: alias + * + * Returns: (transfer full): The aliased entry + */ +GMenuTreeEntry * +gmenu_tree_alias_get_aliased_entry (GMenuTreeAlias *alias) +{ + g_return_val_if_fail (alias != NULL, NULL); + g_return_val_if_fail (alias->aliased_item->type == GMENU_TREE_ITEM_ENTRY, NULL); + + return (GMenuTreeEntry *) gmenu_tree_item_ref (alias->aliased_item); +} + +static GMenuTreeDirectory * +gmenu_tree_directory_new (GMenuTree *tree, + GMenuTreeDirectory *parent, + const char *name) +{ + GMenuTreeDirectory *retval; + + retval = g_slice_new0 (GMenuTreeDirectory); + + retval->item.type = GMENU_TREE_ITEM_DIRECTORY; + retval->item.parent = parent; + retval->item.refcount = 1; + retval->item.tree = tree; + + retval->name = g_strdup (name); + retval->directory_entry = NULL; + retval->entries = NULL; + retval->subdirs = NULL; + retval->default_layout_info = NULL; + retval->layout_info = NULL; + retval->contents = NULL; + retval->only_unallocated = FALSE; + retval->is_nodisplay = FALSE; + retval->layout_pending_separator = FALSE; + retval->preprocessed = FALSE; + retval->will_inline_header = G_MAXUINT16; + + retval->default_layout_values.mask = MENU_LAYOUT_VALUES_NONE; + retval->default_layout_values.show_empty = FALSE; + retval->default_layout_values.inline_menus = FALSE; + retval->default_layout_values.inline_limit = 4; + retval->default_layout_values.inline_header = FALSE; + retval->default_layout_values.inline_alias = FALSE; + + return retval; +} + +static void +gmenu_tree_directory_finalize (GMenuTreeDirectory *directory) +{ + g_assert (directory->item.refcount == 0); + + g_slist_foreach (directory->contents, + (GFunc) gmenu_tree_item_unref_and_unset_parent, + NULL); + g_slist_free (directory->contents); + directory->contents = NULL; + + g_slist_foreach (directory->default_layout_info, + (GFunc) menu_layout_node_unref, + NULL); + g_slist_free (directory->default_layout_info); + directory->default_layout_info = NULL; + + g_slist_foreach (directory->layout_info, + (GFunc) menu_layout_node_unref, + NULL); + g_slist_free (directory->layout_info); + directory->layout_info = NULL; + + g_slist_foreach (directory->subdirs, + (GFunc) gmenu_tree_item_unref_and_unset_parent, + NULL); + g_slist_free (directory->subdirs); + directory->subdirs = NULL; + + g_slist_foreach (directory->entries, + (GFunc) gmenu_tree_item_unref_and_unset_parent, + NULL); + g_slist_free (directory->entries); + directory->entries = NULL; + + if (directory->directory_entry) + desktop_entry_unref (directory->directory_entry); + directory->directory_entry = NULL; + + g_free (directory->name); + directory->name = NULL; + + g_slice_free (GMenuTreeDirectory, directory); +} + +static GMenuTreeSeparator * +gmenu_tree_separator_new (GMenuTreeDirectory *parent) +{ + GMenuTreeSeparator *retval; + + retval = g_slice_new0 (GMenuTreeSeparator); + + retval->item.type = GMENU_TREE_ITEM_SEPARATOR; + retval->item.parent = parent; + retval->item.refcount = 1; + retval->item.tree = parent->item.tree; + + return retval; +} + +static void +gmenu_tree_separator_finalize (GMenuTreeSeparator *separator) +{ + g_assert (separator->item.refcount == 0); + + g_slice_free (GMenuTreeSeparator, separator); +} + +static GMenuTreeHeader * +gmenu_tree_header_new (GMenuTreeDirectory *parent, + GMenuTreeDirectory *directory) +{ + GMenuTreeHeader *retval; + + retval = g_slice_new0 (GMenuTreeHeader); + + retval->item.type = GMENU_TREE_ITEM_HEADER; + retval->item.parent = parent; + retval->item.refcount = 1; + retval->item.tree = parent->item.tree; + + retval->directory = gmenu_tree_item_ref (directory); + + gmenu_tree_item_set_parent (GMENU_TREE_ITEM (retval->directory), NULL); + + return retval; +} + +static void +gmenu_tree_header_finalize (GMenuTreeHeader *header) +{ + g_assert (header->item.refcount == 0); + + if (header->directory != NULL) + gmenu_tree_item_unref (header->directory); + header->directory = NULL; + + g_slice_free (GMenuTreeHeader, header); +} + +static GMenuTreeAlias * +gmenu_tree_alias_new (GMenuTreeDirectory *parent, + GMenuTreeDirectory *directory, + GMenuTreeItem *item) +{ + GMenuTreeAlias *retval; + + retval = g_slice_new0 (GMenuTreeAlias); + + retval->item.type = GMENU_TREE_ITEM_ALIAS; + retval->item.parent = parent; + retval->item.refcount = 1; + retval->item.tree = parent->item.tree; + + retval->directory = gmenu_tree_item_ref (directory); + if (item->type != GMENU_TREE_ITEM_ALIAS) + retval->aliased_item = gmenu_tree_item_ref (item); + else + { + GMenuTreeAlias *alias = GMENU_TREE_ALIAS (item); + retval->aliased_item = gmenu_tree_item_ref (alias->aliased_item); + } + + gmenu_tree_item_set_parent (GMENU_TREE_ITEM (retval->directory), NULL); + gmenu_tree_item_set_parent (retval->aliased_item, NULL); + + return retval; +} + +static void +gmenu_tree_alias_finalize (GMenuTreeAlias *alias) +{ + g_assert (alias->item.refcount == 0); + + if (alias->directory != NULL) + gmenu_tree_item_unref (alias->directory); + alias->directory = NULL; + + if (alias->aliased_item != NULL) + gmenu_tree_item_unref (alias->aliased_item); + alias->aliased_item = NULL; + + g_slice_free (GMenuTreeAlias, alias); +} + +static GMenuTreeEntry * +gmenu_tree_entry_new (GMenuTreeDirectory *parent, + DesktopEntry *desktop_entry, + const char *desktop_file_id, + gboolean is_excluded, + gboolean is_unallocated) +{ + GMenuTreeEntry *retval; + + retval = g_slice_new0 (GMenuTreeEntry); + + retval->item.type = GMENU_TREE_ITEM_ENTRY; + retval->item.parent = parent; + retval->item.refcount = 1; + retval->item.tree = parent->item.tree; + + retval->desktop_entry = desktop_entry_ref (desktop_entry); + retval->desktop_file_id = g_strdup (desktop_file_id); + retval->is_excluded = is_excluded != FALSE; + retval->is_unallocated = is_unallocated != FALSE; + + return retval; +} + +static void +gmenu_tree_entry_finalize (GMenuTreeEntry *entry) +{ + g_assert (entry->item.refcount == 0); + + g_free (entry->desktop_file_id); + entry->desktop_file_id = NULL; + + if (entry->desktop_entry) + desktop_entry_unref (entry->desktop_entry); + entry->desktop_entry = NULL; + + g_slice_free (GMenuTreeEntry, entry); +} + +static int +gmenu_tree_entry_compare_by_id (GMenuTreeItem *a, + GMenuTreeItem *b) +{ + if (a->type == GMENU_TREE_ITEM_ALIAS) + a = GMENU_TREE_ALIAS (a)->aliased_item; + + if (b->type == GMENU_TREE_ITEM_ALIAS) + b = GMENU_TREE_ALIAS (b)->aliased_item; + + return strcmp (GMENU_TREE_ENTRY (a)->desktop_file_id, + GMENU_TREE_ENTRY (b)->desktop_file_id); +} + +/** + * gmenu_tree_item_ref: + * @item: a #GMenuTreeItem + * + * Returns: (transfer full): The same @item, or %NULL if @item is not a valid #GMenuTreeItem + */ +gpointer +gmenu_tree_item_ref (gpointer itemp) +{ + GMenuTreeItem *item; + + item = (GMenuTreeItem *) itemp; + + g_return_val_if_fail (item != NULL, NULL); + g_return_val_if_fail (item->refcount > 0, NULL); + + g_atomic_int_inc (&item->refcount); + + return item; +} + +void +gmenu_tree_item_unref (gpointer itemp) +{ + GMenuTreeItem *item; + + item = (GMenuTreeItem *) itemp; + + g_return_if_fail (item != NULL); + g_return_if_fail (item->refcount > 0); + + if (g_atomic_int_dec_and_test (&(item->refcount))) + { + switch (item->type) + { + case GMENU_TREE_ITEM_DIRECTORY: + gmenu_tree_directory_finalize (GMENU_TREE_DIRECTORY (item)); + break; + + case GMENU_TREE_ITEM_ENTRY: + gmenu_tree_entry_finalize (GMENU_TREE_ENTRY (item)); + break; + + case GMENU_TREE_ITEM_SEPARATOR: + gmenu_tree_separator_finalize (GMENU_TREE_SEPARATOR (item)); + break; + + case GMENU_TREE_ITEM_HEADER: + gmenu_tree_header_finalize (GMENU_TREE_HEADER (item)); + break; + + case GMENU_TREE_ITEM_ALIAS: + gmenu_tree_alias_finalize (GMENU_TREE_ALIAS (item)); + break; + + default: + g_assert_not_reached (); + break; + } + } +} + +static void +gmenu_tree_item_unref_and_unset_parent (gpointer itemp) +{ + GMenuTreeItem *item; + + item = (GMenuTreeItem *) itemp; + + g_return_if_fail (item != NULL); + + gmenu_tree_item_set_parent (item, NULL); + gmenu_tree_item_unref (item); +} + +static inline const char * +gmenu_tree_item_compare_get_name_helper (GMenuTreeItem *item, + GMenuTreeFlags flags) +{ + const char *name; + + name = NULL; + + switch (item->type) + { + case GMENU_TREE_ITEM_DIRECTORY: + if (GMENU_TREE_DIRECTORY (item)->directory_entry) + name = desktop_entry_get_name (GMENU_TREE_DIRECTORY (item)->directory_entry); + else + name = GMENU_TREE_DIRECTORY (item)->name; + break; + + case GMENU_TREE_ITEM_ENTRY: + if (flags & GMENU_TREE_FLAGS_SORT_DISPLAY_NAME) + name = g_app_info_get_display_name (G_APP_INFO (gmenu_tree_entry_get_app_info (GMENU_TREE_ENTRY (item)))); + else + name = desktop_entry_get_name (GMENU_TREE_ENTRY (item)->desktop_entry); + break; + + case GMENU_TREE_ITEM_ALIAS: + { + GMenuTreeItem *dir; + dir = GMENU_TREE_ITEM (GMENU_TREE_ALIAS (item)->directory); + name = gmenu_tree_item_compare_get_name_helper (dir, flags); + } + break; + + case GMENU_TREE_ITEM_SEPARATOR: + case GMENU_TREE_ITEM_HEADER: + default: + g_assert_not_reached (); + break; + } + + return name; +} + +static int +gmenu_tree_item_compare (GMenuTreeItem *a, + GMenuTreeItem *b, + gpointer flags_p) +{ + const char *name_a; + const char *name_b; + GMenuTreeFlags flags; + + flags = GPOINTER_TO_INT (flags_p); + + name_a = gmenu_tree_item_compare_get_name_helper (a, flags); + name_b = gmenu_tree_item_compare_get_name_helper (b, flags); + + return g_utf8_collate (name_a, name_b); +} + +static MenuLayoutNode * +find_menu_child (MenuLayoutNode *layout) +{ + MenuLayoutNode *child; + + child = menu_layout_node_get_children (layout); + while (child && menu_layout_node_get_type (child) != MENU_LAYOUT_NODE_MENU) + child = menu_layout_node_get_next (child); + + return child; +} + +static void +merge_resolved_children (GMenuTree *tree, + GHashTable *loaded_menu_files, + MenuLayoutNode *where, + MenuLayoutNode *from) +{ + MenuLayoutNode *insert_after; + MenuLayoutNode *menu_child; + MenuLayoutNode *from_child; + + gmenu_tree_resolve_files (tree, loaded_menu_files, from); + + insert_after = where; + g_assert (menu_layout_node_get_type (insert_after) != MENU_LAYOUT_NODE_ROOT); + g_assert (menu_layout_node_get_parent (insert_after) != NULL); + + /* skip root node */ + menu_child = find_menu_child (from); + g_assert (menu_child != NULL); + g_assert (menu_layout_node_get_type (menu_child) == MENU_LAYOUT_NODE_MENU); + + /* merge children of toplevel */ + from_child = menu_layout_node_get_children (menu_child); + while (from_child != NULL) + { + MenuLayoutNode *next; + + next = menu_layout_node_get_next (from_child); + + menu_verbose ("Merging "); + menu_debug_print_layout (from_child, FALSE); + menu_verbose (" after "); + menu_debug_print_layout (insert_after, FALSE); + + switch (menu_layout_node_get_type (from_child)) + { + case MENU_LAYOUT_NODE_NAME: + menu_layout_node_unlink (from_child); /* delete this */ + break; + + default: + menu_layout_node_steal (from_child); + menu_layout_node_insert_after (insert_after, from_child); + menu_layout_node_unref (from_child); + + insert_after = from_child; + break; + } + + from_child = next; + } +} + +static gboolean +load_merge_file (GMenuTree *tree, + GHashTable *loaded_menu_files, + const char *filename, + gboolean is_canonical, + gboolean add_monitor, + MenuLayoutNode *where) +{ + MenuLayoutNode *to_merge; + const char *canonical; + char *freeme; + gboolean retval; + + freeme = NULL; + retval = FALSE; + + if (!is_canonical) + { + canonical = freeme = menu_canonicalize_file_name (filename, FALSE); + if (canonical == NULL) + { + if (add_monitor) + gmenu_tree_add_menu_file_monitor (tree, + filename, + MENU_FILE_MONITOR_NONEXISTENT_FILE); + + menu_verbose ("Failed to canonicalize merge file path \"%s\": %s\n", + filename, g_strerror (errno)); + goto out; + } + } + else + { + canonical = filename; + } + + if (g_hash_table_lookup (loaded_menu_files, canonical) != NULL) + { + g_warning ("Not loading \"%s\": recursive loop detected in .menu files", + canonical); + retval = TRUE; + goto out; + } + + menu_verbose ("Merging file \"%s\"\n", canonical); + + to_merge = menu_layout_load (canonical, tree->non_prefixed_basename, NULL); + if (to_merge == NULL) + { + menu_verbose ("No menu for file \"%s\" found when merging\n", + canonical); + goto out; + } + + retval = TRUE; + + g_hash_table_insert (loaded_menu_files, (char *) canonical, GUINT_TO_POINTER (TRUE)); + + if (add_monitor) + gmenu_tree_add_menu_file_monitor (tree, + canonical, + MENU_FILE_MONITOR_FILE); + + merge_resolved_children (tree, loaded_menu_files, where, to_merge); + + g_hash_table_remove (loaded_menu_files, canonical); + + menu_layout_node_unref (to_merge); + + out: + if (freeme) + g_free (freeme); + + return retval; +} + +static gboolean +load_merge_file_with_config_dir (GMenuTree *tree, + GHashTable *loaded_menu_files, + const char *menu_file, + const char *config_dir, + MenuLayoutNode *where) +{ + char *merge_file; + gboolean loaded; + + loaded = FALSE; + + merge_file = g_build_filename (config_dir, "menus", menu_file, NULL); + + if (load_merge_file (tree, loaded_menu_files, merge_file, FALSE, TRUE, where)) + loaded = TRUE; + + g_free (merge_file); + + return loaded; +} + +static gboolean +compare_basedir_to_config_dir (const char *canonical_basedir, + const char *config_dir) +{ + char *dirname; + char *canonical_menus_dir; + gboolean retval; + + menu_verbose ("Checking to see if basedir '%s' is in '%s'\n", + canonical_basedir, config_dir); + + dirname = g_build_filename (config_dir, "menus", NULL); + + retval = FALSE; + + canonical_menus_dir = menu_canonicalize_file_name (dirname, FALSE); + if (canonical_menus_dir != NULL && + strcmp (canonical_basedir, canonical_menus_dir) == 0) + { + retval = TRUE; + } + + g_free (canonical_menus_dir); + g_free (dirname); + + return retval; +} + +static gboolean +load_parent_merge_file_from_basename (GMenuTree *tree, + GHashTable *loaded_menu_files, + MenuLayoutNode *layout, + const char *menu_file, + const char *canonical_basedir) +{ + gboolean found_basedir; + const char * const *system_config_dirs; + int i; + + /* We're not interested in menu files that are in directories which are not a + * parent of the base directory of this menu file */ + found_basedir = compare_basedir_to_config_dir (canonical_basedir, + g_get_user_config_dir ()); + + system_config_dirs = g_get_system_config_dirs (); + + i = 0; + while (system_config_dirs[i] != NULL) + { + if (!found_basedir) + { + found_basedir = compare_basedir_to_config_dir (canonical_basedir, + system_config_dirs[i]); + } + else + { + menu_verbose ("Looking for parent menu file '%s' in '%s'\n", + menu_file, system_config_dirs[i]); + + if (load_merge_file_with_config_dir (tree, + loaded_menu_files, + menu_file, + system_config_dirs[i], + layout)) + { + break; + } + } + + ++i; + } + + return system_config_dirs[i] != NULL; +} + +static gboolean +load_parent_merge_file (GMenuTree *tree, + GHashTable *loaded_menu_files, + MenuLayoutNode *layout) +{ + MenuLayoutNode *root; + const char *basedir; + const char *menu_name; + char *canonical_basedir; + char *menu_file; + gboolean found; + + root = menu_layout_node_get_root (layout); + + basedir = menu_layout_node_root_get_basedir (root); + menu_name = menu_layout_node_root_get_name (root); + + canonical_basedir = menu_canonicalize_file_name (basedir, FALSE); + if (canonical_basedir == NULL) + { + menu_verbose ("Menu basedir '%s' no longer exists, not merging parent\n", + basedir); + return FALSE; + } + + found = FALSE; + menu_file = g_strconcat (menu_name, ".menu", NULL); + + if (strcmp (menu_file, "applications.menu") == 0) + { + char *prefixed_basename; + prefixed_basename = prefix_menu_name (menu_file); + found = load_parent_merge_file_from_basename (tree, loaded_menu_files, + layout, prefixed_basename, + canonical_basedir); + g_free (prefixed_basename); + } + + if (!found) + { + found = load_parent_merge_file_from_basename (tree, loaded_menu_files, + layout, menu_file, + canonical_basedir); + } + + g_free (menu_file); + g_free (canonical_basedir); + + return found; +} + +static void +load_merge_dir (GMenuTree *tree, + GHashTable *loaded_menu_files, + const char *dirname, + MenuLayoutNode *where) +{ + GDir *dir; + const char *menu_file; + + menu_verbose ("Loading merge dir \"%s\"\n", dirname); + + gmenu_tree_add_menu_file_monitor (tree, + dirname, + MENU_FILE_MONITOR_DIRECTORY); + + if ((dir = g_dir_open (dirname, 0, NULL)) == NULL) + return; + + while ((menu_file = g_dir_read_name (dir))) + { + if (g_str_has_suffix (menu_file, ".menu")) + { + char *full_path; + + full_path = g_build_filename (dirname, menu_file, NULL); + + load_merge_file (tree, loaded_menu_files, full_path, TRUE, FALSE, where); + + g_free (full_path); + } + } + + g_dir_close (dir); +} + +static void +load_merge_dir_with_config_dir (GMenuTree *tree, + GHashTable *loaded_menu_files, + const char *config_dir, + const char *dirname, + MenuLayoutNode *where) +{ + char *path; + + path = g_build_filename (config_dir, "menus", dirname, NULL); + + load_merge_dir (tree, loaded_menu_files, path, where); + + g_free (path); +} + +static void +resolve_merge_file (GMenuTree *tree, + GHashTable *loaded_menu_files, + MenuLayoutNode *layout) +{ + char *filename; + + if (menu_layout_node_merge_file_get_type (layout) == MENU_MERGE_FILE_TYPE_PARENT) + { + if (load_parent_merge_file (tree, loaded_menu_files, layout)) + return; + } + + filename = menu_layout_node_get_content_as_path (layout); + if (filename == NULL) + { + menu_verbose ("didn't get node content as a path, not merging file\n"); + } + else + { + load_merge_file (tree, loaded_menu_files, filename, FALSE, TRUE, layout); + + g_free (filename); + } + + /* remove the now-replaced node */ + menu_layout_node_unlink (layout); +} + +static void +resolve_merge_dir (GMenuTree *tree, + GHashTable *loaded_menu_files, + MenuLayoutNode *layout) +{ + char *path; + + path = menu_layout_node_get_content_as_path (layout); + if (path == NULL) + { + menu_verbose ("didn't get layout node content as a path, not merging dir\n"); + } + else + { + load_merge_dir (tree, loaded_menu_files, path, layout); + + g_free (path); + } + + /* remove the now-replaced node */ + menu_layout_node_unlink (layout); +} + +static MenuLayoutNode * +add_app_dir (GMenuTree *tree, + MenuLayoutNode *before, + const char *data_dir) +{ + MenuLayoutNode *tmp; + char *dirname; + + tmp = menu_layout_node_new (MENU_LAYOUT_NODE_APP_DIR); + dirname = g_build_filename (data_dir, "applications", NULL); + menu_layout_node_set_content (tmp, dirname); + menu_layout_node_insert_before (before, tmp); + menu_layout_node_unref (before); + + menu_verbose ("Adding %s in \n", + dirname); + + g_free (dirname); + + return tmp; +} + +static void +resolve_default_app_dirs (GMenuTree *tree, + MenuLayoutNode *layout) +{ + MenuLayoutNode *before; + const char * const *system_data_dirs; + int i; + + system_data_dirs = g_get_system_data_dirs (); + + before = add_app_dir (tree, + menu_layout_node_ref (layout), + g_get_user_data_dir ()); + + i = 0; + while (system_data_dirs[i] != NULL) + { + before = add_app_dir (tree, before, system_data_dirs[i]); + + ++i; + } + + menu_layout_node_unref (before); + + /* remove the now-replaced node */ + menu_layout_node_unlink (layout); +} + +static MenuLayoutNode * +add_directory_dir (GMenuTree *tree, + MenuLayoutNode *before, + const char *data_dir) +{ + MenuLayoutNode *tmp; + char *dirname; + + tmp = menu_layout_node_new (MENU_LAYOUT_NODE_DIRECTORY_DIR); + dirname = g_build_filename (data_dir, "desktop-directories", NULL); + menu_layout_node_set_content (tmp, dirname); + menu_layout_node_insert_before (before, tmp); + menu_layout_node_unref (before); + + menu_verbose ("Adding %s in \n", + dirname); + + g_free (dirname); + + return tmp; +} + +static void +resolve_default_directory_dirs (GMenuTree *tree, + MenuLayoutNode *layout) +{ + MenuLayoutNode *before; + const char * const *system_data_dirs; + int i; + + system_data_dirs = g_get_system_data_dirs (); + + before = add_directory_dir (tree, + menu_layout_node_ref (layout), + g_get_user_data_dir ()); + + i = 0; + while (system_data_dirs[i] != NULL) + { + before = add_directory_dir (tree, before, system_data_dirs[i]); + + ++i; + } + + menu_layout_node_unref (before); + + /* remove the now-replaced node */ + menu_layout_node_unlink (layout); +} + +static void +resolve_default_merge_dirs (GMenuTree *tree, + GHashTable *loaded_menu_files, + MenuLayoutNode *layout) +{ + MenuLayoutNode *root; + const char *menu_name; + char *merge_name; + const char * const *system_config_dirs; + int i; + + root = menu_layout_node_get_root (layout); + menu_name = menu_layout_node_root_get_name (root); + + merge_name = g_strconcat (menu_name, "-merged", NULL); + + system_config_dirs = g_get_system_config_dirs (); + + /* Merge in reverse order */ + i = 0; + while (system_config_dirs[i] != NULL) i++; + while (i > 0) + { + i--; + load_merge_dir_with_config_dir (tree, + loaded_menu_files, + system_config_dirs[i], + merge_name, + layout); + } + + load_merge_dir_with_config_dir (tree, + loaded_menu_files, + g_get_user_config_dir (), + merge_name, + layout); + + g_free (merge_name); + + /* remove the now-replaced node */ + menu_layout_node_unlink (layout); +} + +static void +add_filename_include (const char *desktop_file_id, + DesktopEntry *entry, + MenuLayoutNode *include) +{ + if (!desktop_entry_has_categories (entry)) + { + MenuLayoutNode *node; + + node = menu_layout_node_new (MENU_LAYOUT_NODE_FILENAME); + menu_layout_node_set_content (node, desktop_file_id); + + menu_layout_node_append_child (include, node); + menu_layout_node_unref (node); + } +} + +static void +is_dot_directory (const char *basename, + DesktopEntry *entry, + gboolean *has_dot_directory) +{ + if (!strcmp (basename, ".directory")) + *has_dot_directory = TRUE; +} + +static gboolean +add_menu_for_legacy_dir (MenuLayoutNode *parent, + const char *legacy_dir, + const char *relative_path, + const char *legacy_prefix, + const char *menu_name) +{ + EntryDirectory *ed; + DesktopEntrySet *desktop_entries; + DesktopEntrySet *directory_entries; + GSList *subdirs; + gboolean menu_added; + gboolean has_dot_directory; + + ed = entry_directory_new_legacy (DESKTOP_ENTRY_INVALID, legacy_dir, legacy_prefix); + if (!ed) + return FALSE; + + subdirs = NULL; + desktop_entries = desktop_entry_set_new (); + directory_entries = desktop_entry_set_new (); + + entry_directory_get_flat_contents (ed, + desktop_entries, + directory_entries, + &subdirs); + entry_directory_unref (ed); + + has_dot_directory = FALSE; + desktop_entry_set_foreach (directory_entries, + (DesktopEntrySetForeachFunc) is_dot_directory, + &has_dot_directory); + desktop_entry_set_unref (directory_entries); + + menu_added = FALSE; + if (desktop_entry_set_get_count (desktop_entries) > 0 || subdirs) + { + MenuLayoutNode *menu; + MenuLayoutNode *node; + GString *subdir_path; + GString *subdir_relative; + GSList *tmp; + int legacy_dir_len; + int relative_path_len; + + menu = menu_layout_node_new (MENU_LAYOUT_NODE_MENU); + menu_layout_node_append_child (parent, menu); + + menu_added = TRUE; + + g_assert (menu_name != NULL); + + node = menu_layout_node_new (MENU_LAYOUT_NODE_NAME); + menu_layout_node_set_content (node, menu_name); + menu_layout_node_append_child (menu, node); + menu_layout_node_unref (node); + + if (has_dot_directory) + { + node = menu_layout_node_new (MENU_LAYOUT_NODE_DIRECTORY); + if (relative_path != NULL) + { + char *directory_entry_path; + + directory_entry_path = g_strdup_printf ("%s/.directory", relative_path); + menu_layout_node_set_content (node, directory_entry_path); + g_free (directory_entry_path); + } + else + { + menu_layout_node_set_content (node, ".directory"); + } + menu_layout_node_append_child (menu, node); + menu_layout_node_unref (node); + } + + if (desktop_entry_set_get_count (desktop_entries) > 0) + { + MenuLayoutNode *include; + + include = menu_layout_node_new (MENU_LAYOUT_NODE_INCLUDE); + menu_layout_node_append_child (menu, include); + + desktop_entry_set_foreach (desktop_entries, + (DesktopEntrySetForeachFunc) add_filename_include, + include); + + menu_layout_node_unref (include); + } + + subdir_path = g_string_new (legacy_dir); + legacy_dir_len = strlen (legacy_dir); + + subdir_relative = g_string_new (relative_path); + relative_path_len = relative_path ? strlen (relative_path) : 0; + + tmp = subdirs; + while (tmp != NULL) + { + const char *subdir = tmp->data; + + g_string_append_c (subdir_path, G_DIR_SEPARATOR); + g_string_append (subdir_path, subdir); + + if (relative_path_len) + { + g_string_append_c (subdir_relative, G_DIR_SEPARATOR); + } + g_string_append (subdir_relative, subdir); + + add_menu_for_legacy_dir (menu, + subdir_path->str, + subdir_relative->str, + legacy_prefix, + subdir); + + g_string_truncate (subdir_relative, relative_path_len); + g_string_truncate (subdir_path, legacy_dir_len); + + tmp = tmp->next; + } + + g_string_free (subdir_path, TRUE); + g_string_free (subdir_relative, TRUE); + + menu_layout_node_unref (menu); + } + + desktop_entry_set_unref (desktop_entries); + + g_slist_foreach (subdirs, (GFunc) g_free, NULL); + g_slist_free (subdirs); + + return menu_added; +} + +static void +resolve_legacy_dir (GMenuTree *tree, + GHashTable *loaded_menu_files, + MenuLayoutNode *legacy) +{ + MenuLayoutNode *to_merge; + MenuLayoutNode *menu; + + to_merge = menu_layout_node_new (MENU_LAYOUT_NODE_ROOT); + + menu = menu_layout_node_get_parent (legacy); + g_assert (menu_layout_node_get_type (menu) == MENU_LAYOUT_NODE_MENU); + + if (add_menu_for_legacy_dir (to_merge, + menu_layout_node_get_content (legacy), + NULL, + menu_layout_node_legacy_dir_get_prefix (legacy), + menu_layout_node_menu_get_name (menu))) + { + merge_resolved_children (tree, loaded_menu_files, legacy, to_merge); + } + + menu_layout_node_unref (to_merge); +} + +static MenuLayoutNode * +add_legacy_dir (GMenuTree *tree, + GHashTable *loaded_menu_files, + MenuLayoutNode *before, + const char *data_dir) +{ + MenuLayoutNode *legacy; + char *dirname; + + dirname = g_build_filename (data_dir, "applnk", NULL); + + legacy = menu_layout_node_new (MENU_LAYOUT_NODE_LEGACY_DIR); + menu_layout_node_set_content (legacy, dirname); + menu_layout_node_legacy_dir_set_prefix (legacy, "kde"); + menu_layout_node_insert_before (before, legacy); + menu_layout_node_unref (before); + + menu_verbose ("Adding %s in \n", + dirname); + + resolve_legacy_dir (tree, loaded_menu_files, legacy); + + g_free (dirname); + + return legacy; +} + +static void +resolve_kde_legacy_dirs (GMenuTree *tree, + GHashTable *loaded_menu_files, + MenuLayoutNode *layout) +{ + MenuLayoutNode *before; + const char * const *system_data_dirs; + int i; + + system_data_dirs = g_get_system_data_dirs (); + + before = add_legacy_dir (tree, + loaded_menu_files, + menu_layout_node_ref (layout), + g_get_user_data_dir ()); + + i = 0; + while (system_data_dirs[i] != NULL) + { + before = add_legacy_dir (tree, loaded_menu_files, before, system_data_dirs[i]); + + ++i; + } + + menu_layout_node_unref (before); + + /* remove the now-replaced node */ + menu_layout_node_unlink (layout); +} + +static void +gmenu_tree_resolve_files (GMenuTree *tree, + GHashTable *loaded_menu_files, + MenuLayoutNode *layout) +{ + MenuLayoutNode *child; + + menu_verbose ("Resolving files in: "); + menu_debug_print_layout (layout, TRUE); + + switch (menu_layout_node_get_type (layout)) + { + case MENU_LAYOUT_NODE_MERGE_FILE: + resolve_merge_file (tree, loaded_menu_files, layout); + break; + + case MENU_LAYOUT_NODE_MERGE_DIR: + resolve_merge_dir (tree, loaded_menu_files, layout); + break; + + case MENU_LAYOUT_NODE_DEFAULT_APP_DIRS: + resolve_default_app_dirs (tree, layout); + break; + + case MENU_LAYOUT_NODE_DEFAULT_DIRECTORY_DIRS: + resolve_default_directory_dirs (tree, layout); + break; + + case MENU_LAYOUT_NODE_DEFAULT_MERGE_DIRS: + resolve_default_merge_dirs (tree, loaded_menu_files, layout); + break; + + case MENU_LAYOUT_NODE_LEGACY_DIR: + resolve_legacy_dir (tree, loaded_menu_files, layout); + break; + + case MENU_LAYOUT_NODE_KDE_LEGACY_DIRS: + resolve_kde_legacy_dirs (tree, loaded_menu_files, layout); + break; + + case MENU_LAYOUT_NODE_PASSTHROUGH: + /* Just get rid of these, we don't need the memory usage */ + menu_layout_node_unlink (layout); + break; + + default: + /* Recurse */ + child = menu_layout_node_get_children (layout); + while (child != NULL) + { + MenuLayoutNode *next = menu_layout_node_get_next (child); + + gmenu_tree_resolve_files (tree, loaded_menu_files, child); + + child = next; + } + break; + } +} + +static void +move_children (MenuLayoutNode *from, + MenuLayoutNode *to) +{ + MenuLayoutNode *from_child; + MenuLayoutNode *insert_before; + + insert_before = menu_layout_node_get_children (to); + from_child = menu_layout_node_get_children (from); + + while (from_child != NULL) + { + MenuLayoutNode *next; + + next = menu_layout_node_get_next (from_child); + + menu_layout_node_steal (from_child); + + if (menu_layout_node_get_type (from_child) == MENU_LAYOUT_NODE_NAME) + { + ; /* just drop the Name in the old */ + } + else if (insert_before) + { + menu_layout_node_insert_before (insert_before, from_child); + g_assert (menu_layout_node_get_next (from_child) == insert_before); + } + else + { + menu_layout_node_append_child (to, from_child); + } + + menu_layout_node_unref (from_child); + + from_child = next; + } +} + +static int +null_safe_strcmp (const char *a, + const char *b) +{ + if (a == NULL && b == NULL) + return 0; + else if (a == NULL) + return -1; + else if (b == NULL) + return 1; + else + return strcmp (a, b); +} + +static int +node_compare_func (const void *a, + const void *b) +{ + MenuLayoutNode *node_a = (MenuLayoutNode*) a; + MenuLayoutNode *node_b = (MenuLayoutNode*) b; + MenuLayoutNodeType t_a = menu_layout_node_get_type (node_a); + MenuLayoutNodeType t_b = menu_layout_node_get_type (node_b); + + if (t_a < t_b) + return -1; + else if (t_a > t_b) + return 1; + else + { + const char *c_a = menu_layout_node_get_content (node_a); + const char *c_b = menu_layout_node_get_content (node_b); + + return null_safe_strcmp (c_a, c_b); + } +} + +static int +node_menu_compare_func (const void *a, + const void *b) +{ + MenuLayoutNode *node_a = (MenuLayoutNode*) a; + MenuLayoutNode *node_b = (MenuLayoutNode*) b; + MenuLayoutNode *parent_a = menu_layout_node_get_parent (node_a); + MenuLayoutNode *parent_b = menu_layout_node_get_parent (node_b); + + if (parent_a < parent_b) + return -1; + else if (parent_a > parent_b) + return 1; + else + return null_safe_strcmp (menu_layout_node_menu_get_name (node_a), + menu_layout_node_menu_get_name (node_b)); +} + +static void +gmenu_tree_strip_duplicate_children (GMenuTree *tree, + MenuLayoutNode *layout) +{ + MenuLayoutNode *child; + GSList *simple_nodes; + GSList *menu_layout_nodes; + GSList *prev; + GSList *tmp; + + /* to strip dups, we find all the child nodes where + * we want to kill dups, sort them, + * then nuke the adjacent nodes that are equal + */ + + simple_nodes = NULL; + menu_layout_nodes = NULL; + + child = menu_layout_node_get_children (layout); + while (child != NULL) + { + switch (menu_layout_node_get_type (child)) + { + /* These are dups if their content is the same */ + case MENU_LAYOUT_NODE_APP_DIR: + case MENU_LAYOUT_NODE_DIRECTORY_DIR: + case MENU_LAYOUT_NODE_DIRECTORY: + simple_nodes = g_slist_prepend (simple_nodes, child); + break; + + /* These have to be merged in a more complicated way, + * and then recursed + */ + case MENU_LAYOUT_NODE_MENU: + menu_layout_nodes = g_slist_prepend (menu_layout_nodes, child); + break; + + default: + break; + } + + child = menu_layout_node_get_next (child); + } + + /* Note that the lists are all backward. So we want to keep + * the items that are earlier in the list, because they were + * later in the file + */ + + /* stable sort the simple nodes */ + simple_nodes = g_slist_sort (simple_nodes, + node_compare_func); + + prev = NULL; + tmp = simple_nodes; + while (tmp != NULL) + { + GSList *next = tmp->next; + + if (prev) + { + MenuLayoutNode *p = prev->data; + MenuLayoutNode *n = tmp->data; + + if (node_compare_func (p, n) == 0) + { + /* nuke it! */ + menu_layout_node_unlink (n); + simple_nodes = g_slist_delete_link (simple_nodes, tmp); + tmp = prev; + } + } + + prev = tmp; + tmp = next; + } + + g_slist_free (simple_nodes); + simple_nodes = NULL; + + /* stable sort the menu nodes (the sort includes the + * parents of the nodes in the comparison). Remember + * the list is backward. + */ + menu_layout_nodes = g_slist_sort (menu_layout_nodes, + node_menu_compare_func); + + prev = NULL; + tmp = menu_layout_nodes; + while (tmp != NULL) + { + GSList *next = tmp->next; + + if (prev) + { + MenuLayoutNode *p = prev->data; + MenuLayoutNode *n = tmp->data; + + if (node_menu_compare_func (p, n) == 0) + { + /* Move children of first menu to the start of second + * menu and nuke the first menu + */ + move_children (n, p); + menu_layout_node_unlink (n); + menu_layout_nodes = g_slist_delete_link (menu_layout_nodes, tmp); + tmp = prev; + } + } + + prev = tmp; + tmp = next; + } + + g_slist_free (menu_layout_nodes); + menu_layout_nodes = NULL; + + /* Recursively clean up all children */ + child = menu_layout_node_get_children (layout); + while (child != NULL) + { + if (menu_layout_node_get_type (child) == MENU_LAYOUT_NODE_MENU) + gmenu_tree_strip_duplicate_children (tree, child); + + child = menu_layout_node_get_next (child); + } +} + +static MenuLayoutNode * +find_submenu (MenuLayoutNode *layout, + const char *path, + gboolean create_if_not_found) +{ + MenuLayoutNode *child; + const char *slash; + const char *next_path; + char *name; + + menu_verbose (" (splitting \"%s\")\n", path); + + if (path[0] == '\0' || path[0] == G_DIR_SEPARATOR) + return NULL; + + slash = strchr (path, G_DIR_SEPARATOR); + if (slash != NULL) + { + name = g_strndup (path, slash - path); + next_path = slash + 1; + if (*next_path == '\0') + next_path = NULL; + } + else + { + name = g_strdup (path); + next_path = NULL; + } + + child = menu_layout_node_get_children (layout); + while (child != NULL) + { + switch (menu_layout_node_get_type (child)) + { + case MENU_LAYOUT_NODE_MENU: + { + if (strcmp (name, menu_layout_node_menu_get_name (child)) == 0) + { + menu_verbose ("MenuNode %p found for path component \"%s\"\n", + child, name); + + g_free (name); + + if (!next_path) + { + menu_verbose (" Found menu node %p parent is %p\n", + child, layout); + return child; + } + + return find_submenu (child, next_path, create_if_not_found); + } + } + break; + + default: + break; + } + + child = menu_layout_node_get_next (child); + } + + if (create_if_not_found) + { + MenuLayoutNode *name_node; + + child = menu_layout_node_new (MENU_LAYOUT_NODE_MENU); + menu_layout_node_append_child (layout, child); + + name_node = menu_layout_node_new (MENU_LAYOUT_NODE_NAME); + menu_layout_node_set_content (name_node, name); + menu_layout_node_append_child (child, name_node); + menu_layout_node_unref (name_node); + + menu_verbose (" Created menu node %p parent is %p\n", + child, layout); + + menu_layout_node_unref (child); + g_free (name); + + if (!next_path) + return child; + + return find_submenu (child, next_path, create_if_not_found); + } + else + { + g_free (name); + return NULL; + } +} + +/* To call this you first have to strip duplicate children once, + * otherwise when you move a menu Foo to Bar then you may only + * move one of Foo, not all the merged Foo. + */ +static void +gmenu_tree_execute_moves (GMenuTree *tree, + MenuLayoutNode *layout, + gboolean *need_remove_dups_p) +{ + MenuLayoutNode *child; + gboolean need_remove_dups; + GSList *move_nodes; + GSList *tmp; + + need_remove_dups = FALSE; + + move_nodes = NULL; + + child = menu_layout_node_get_children (layout); + while (child != NULL) + { + switch (menu_layout_node_get_type (child)) + { + case MENU_LAYOUT_NODE_MENU: + /* Recurse - we recurse first and process the current node + * second, as the spec dictates. + */ + gmenu_tree_execute_moves (tree, child, &need_remove_dups); + break; + + case MENU_LAYOUT_NODE_MOVE: + move_nodes = g_slist_prepend (move_nodes, child); + break; + + default: + break; + } + + child = menu_layout_node_get_next (child); + } + + /* We need to execute the move operations in the order that they appear */ + move_nodes = g_slist_reverse (move_nodes); + + tmp = move_nodes; + while (tmp != NULL) + { + MenuLayoutNode *move_node = tmp->data; + MenuLayoutNode *old_node; + GSList *next = tmp->next; + const char *old; + const char *new; + + old = menu_layout_node_move_get_old (move_node); + new = menu_layout_node_move_get_new (move_node); + g_assert (old != NULL && new != NULL); + + menu_verbose ("executing old = \"%s\" new = \"%s\"\n", + old, new); + + old_node = find_submenu (layout, old, FALSE); + if (old_node != NULL) + { + MenuLayoutNode *new_node; + + /* here we can create duplicates anywhere below the + * node + */ + need_remove_dups = TRUE; + + /* look up new node creating it and its parents if + * required + */ + new_node = find_submenu (layout, new, TRUE); + g_assert (new_node != NULL); + + move_children (old_node, new_node); + + menu_layout_node_unlink (old_node); + } + + menu_layout_node_unlink (move_node); + + tmp = next; + } + + g_slist_free (move_nodes); + + /* This oddness is to ensure we only remove dups once, + * at the root, instead of recursing the tree over + * and over. + */ + if (need_remove_dups_p) + *need_remove_dups_p = need_remove_dups; + else if (need_remove_dups) + gmenu_tree_strip_duplicate_children (tree, layout); +} + +static gboolean +gmenu_tree_load_layout (GMenuTree *tree, + GError **error) +{ + GHashTable *loaded_menu_files; + + if (tree->layout) + return TRUE; + + if (!gmenu_tree_canonicalize_path (tree, error)) + return FALSE; + + menu_verbose ("Loading menu layout from \"%s\"\n", + tree->canonical_path); + + tree->layout = menu_layout_load (tree->canonical_path, + tree->non_prefixed_basename, + error); + if (!tree->layout) + return FALSE; + + loaded_menu_files = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (loaded_menu_files, tree->canonical_path, GUINT_TO_POINTER (TRUE)); + gmenu_tree_resolve_files (tree, loaded_menu_files, tree->layout); + g_hash_table_destroy (loaded_menu_files); + + gmenu_tree_strip_duplicate_children (tree, tree->layout); + gmenu_tree_execute_moves (tree, tree->layout, NULL); + + return TRUE; +} + +static void +gmenu_tree_force_reload (GMenuTree *tree) +{ + gmenu_tree_force_rebuild (tree); + + if (tree->layout) + menu_layout_node_unref (tree->layout); + tree->layout = NULL; +} + +typedef struct +{ + DesktopEntrySet *set; + const char *category; +} GetByCategoryForeachData; + +static void +get_by_category_foreach (const char *file_id, + DesktopEntry *entry, + GetByCategoryForeachData *data) +{ + if (desktop_entry_has_category (entry, data->category)) + desktop_entry_set_add_entry (data->set, entry, file_id); +} + +static void +get_by_category (DesktopEntrySet *entry_pool, + DesktopEntrySet *set, + const char *category) +{ + GetByCategoryForeachData data; + + data.set = set; + data.category = category; + + desktop_entry_set_foreach (entry_pool, + (DesktopEntrySetForeachFunc) get_by_category_foreach, + &data); +} + +static DesktopEntrySet * +process_include_rules (MenuLayoutNode *layout, + DesktopEntrySet *entry_pool) +{ + DesktopEntrySet *set = NULL; + + switch (menu_layout_node_get_type (layout)) + { + case MENU_LAYOUT_NODE_AND: + { + MenuLayoutNode *child; + + menu_verbose ("Processing \n"); + + child = menu_layout_node_get_children (layout); + while (child != NULL) + { + DesktopEntrySet *child_set; + + child_set = process_include_rules (child, entry_pool); + + if (set == NULL) + { + set = child_set; + } + else + { + desktop_entry_set_intersection (set, child_set); + desktop_entry_set_unref (child_set); + } + + /* as soon as we get empty results, we can bail, + * because it's an AND + */ + if (desktop_entry_set_get_count (set) == 0) + break; + + child = menu_layout_node_get_next (child); + } + menu_verbose ("Processed \n"); + } + break; + + case MENU_LAYOUT_NODE_OR: + { + MenuLayoutNode *child; + + menu_verbose ("Processing \n"); + + child = menu_layout_node_get_children (layout); + while (child != NULL) + { + DesktopEntrySet *child_set; + + child_set = process_include_rules (child, entry_pool); + + if (set == NULL) + { + set = child_set; + } + else + { + desktop_entry_set_union (set, child_set); + desktop_entry_set_unref (child_set); + } + + child = menu_layout_node_get_next (child); + } + menu_verbose ("Processed \n"); + } + break; + + case MENU_LAYOUT_NODE_NOT: + { + /* First get the OR of all the rules */ + MenuLayoutNode *child; + + menu_verbose ("Processing \n"); + + child = menu_layout_node_get_children (layout); + while (child != NULL) + { + DesktopEntrySet *child_set; + + child_set = process_include_rules (child, entry_pool); + + if (set == NULL) + { + set = child_set; + } + else + { + desktop_entry_set_union (set, child_set); + desktop_entry_set_unref (child_set); + } + + child = menu_layout_node_get_next (child); + } + + if (set != NULL) + { + DesktopEntrySet *inverted; + + /* Now invert the result */ + inverted = desktop_entry_set_new (); + desktop_entry_set_union (inverted, entry_pool); + desktop_entry_set_subtract (inverted, set); + desktop_entry_set_unref (set); + set = inverted; + } + menu_verbose ("Processed \n"); + } + break; + + case MENU_LAYOUT_NODE_ALL: + menu_verbose ("Processing \n"); + set = desktop_entry_set_new (); + desktop_entry_set_union (set, entry_pool); + menu_verbose ("Processed \n"); + break; + + case MENU_LAYOUT_NODE_FILENAME: + { + DesktopEntry *entry; + + menu_verbose ("Processing %s\n", + menu_layout_node_get_content (layout)); + + entry = desktop_entry_set_lookup (entry_pool, + menu_layout_node_get_content (layout)); + if (entry != NULL) + { + set = desktop_entry_set_new (); + desktop_entry_set_add_entry (set, + entry, + menu_layout_node_get_content (layout)); + } + menu_verbose ("Processed %s\n", + menu_layout_node_get_content (layout)); + } + break; + + case MENU_LAYOUT_NODE_CATEGORY: + menu_verbose ("Processing %s\n", + menu_layout_node_get_content (layout)); + set = desktop_entry_set_new (); + get_by_category (entry_pool, set, menu_layout_node_get_content (layout)); + menu_verbose ("Processed %s\n", + menu_layout_node_get_content (layout)); + break; + + default: + break; + } + + if (set == NULL) + set = desktop_entry_set_new (); /* create an empty set */ + + menu_verbose ("Matched %d entries\n", desktop_entry_set_get_count (set)); + + return set; +} + +static void +collect_layout_info (MenuLayoutNode *layout, + GSList **layout_info) +{ + MenuLayoutNode *iter; + + g_slist_foreach (*layout_info, + (GFunc) menu_layout_node_unref, + NULL); + g_slist_free (*layout_info); + *layout_info = NULL; + + iter = menu_layout_node_get_children (layout); + while (iter != NULL) + { + switch (menu_layout_node_get_type (iter)) + { + case MENU_LAYOUT_NODE_MENUNAME: + case MENU_LAYOUT_NODE_FILENAME: + case MENU_LAYOUT_NODE_SEPARATOR: + case MENU_LAYOUT_NODE_MERGE: + *layout_info = g_slist_prepend (*layout_info, + menu_layout_node_ref (iter)); + break; + + default: + break; + } + + iter = menu_layout_node_get_next (iter); + } + + *layout_info = g_slist_reverse (*layout_info); +} + +static void +entries_listify_foreach (const char *desktop_file_id, + DesktopEntry *desktop_entry, + GMenuTreeDirectory *directory) +{ + directory->entries = + g_slist_prepend (directory->entries, + gmenu_tree_entry_new (directory, + desktop_entry, + desktop_file_id, + FALSE, + FALSE)); +} + +static void +excluded_entries_listify_foreach (const char *desktop_file_id, + DesktopEntry *desktop_entry, + GMenuTreeDirectory *directory) +{ + directory->entries = + g_slist_prepend (directory->entries, + gmenu_tree_entry_new (directory, + desktop_entry, + desktop_file_id, + TRUE, + FALSE)); +} + +static void +unallocated_entries_listify_foreach (const char *desktop_file_id, + DesktopEntry *desktop_entry, + GMenuTreeDirectory *directory) +{ + directory->entries = + g_slist_prepend (directory->entries, + gmenu_tree_entry_new (directory, + desktop_entry, + desktop_file_id, + FALSE, + TRUE)); +} + +static void +set_default_layout_values (GMenuTreeDirectory *parent, + GMenuTreeDirectory *child) +{ + GSList *tmp; + + /* if the child has a defined default layout, we don't want to override its + * values. The parent might have a non-defined layout info (ie, no child of + * the DefaultLayout node) but it doesn't meant the default layout values + * (ie, DefaultLayout attributes) aren't different from the global defaults. + */ + if (child->default_layout_info != NULL || + child->default_layout_values.mask != MENU_LAYOUT_VALUES_NONE) + return; + + child->default_layout_values = parent->default_layout_values; + + tmp = child->subdirs; + while (tmp != NULL) + { + GMenuTreeDirectory *subdir = tmp->data; + + set_default_layout_values (child, subdir); + + tmp = tmp->next; + } +} + +static GMenuTreeDirectory * +process_layout (GMenuTree *tree, + GMenuTreeDirectory *parent, + MenuLayoutNode *layout, + DesktopEntrySet *allocated) +{ + MenuLayoutNode *layout_iter; + GMenuTreeDirectory *directory; + DesktopEntrySet *entry_pool; + DesktopEntrySet *entries; + DesktopEntrySet *allocated_set; + DesktopEntrySet *excluded_set; + gboolean deleted; + gboolean only_unallocated; + GSList *tmp; + + g_assert (menu_layout_node_get_type (layout) == MENU_LAYOUT_NODE_MENU); + g_assert (menu_layout_node_menu_get_name (layout) != NULL); + + directory = gmenu_tree_directory_new (tree, parent, + menu_layout_node_menu_get_name (layout)); + + menu_verbose ("=== Menu name = %s ===\n", directory->name); + + + deleted = FALSE; + only_unallocated = FALSE; + + entries = desktop_entry_set_new (); + allocated_set = desktop_entry_set_new (); + + if (tree->flags & GMENU_TREE_FLAGS_INCLUDE_EXCLUDED) + excluded_set = desktop_entry_set_new (); + else + excluded_set = NULL; + + entry_pool = _entry_directory_list_get_all_desktops (menu_layout_node_menu_get_app_dirs (layout)); + + layout_iter = menu_layout_node_get_children (layout); + while (layout_iter != NULL) + { + switch (menu_layout_node_get_type (layout_iter)) + { + case MENU_LAYOUT_NODE_MENU: + /* recurse */ + { + GMenuTreeDirectory *child_dir; + + menu_verbose ("Processing \n"); + + child_dir = process_layout (tree, + directory, + layout_iter, + allocated); + if (child_dir) + directory->subdirs = g_slist_prepend (directory->subdirs, + child_dir); + + menu_verbose ("Processed \n"); + } + break; + + case MENU_LAYOUT_NODE_INCLUDE: + { + /* The match rule children of the are + * independent (logical OR) so we can process each one by + * itself + */ + MenuLayoutNode *rule; + + menu_verbose ("Processing (%d entries)\n", + desktop_entry_set_get_count (entries)); + + rule = menu_layout_node_get_children (layout_iter); + while (rule != NULL) + { + DesktopEntrySet *rule_set; + + rule_set = process_include_rules (rule, entry_pool); + if (rule_set != NULL) + { + desktop_entry_set_union (entries, rule_set); + desktop_entry_set_union (allocated_set, rule_set); + if (excluded_set != NULL) + desktop_entry_set_subtract (excluded_set, rule_set); + desktop_entry_set_unref (rule_set); + } + + rule = menu_layout_node_get_next (rule); + } + + menu_verbose ("Processed (%d entries)\n", + desktop_entry_set_get_count (entries)); + } + break; + + case MENU_LAYOUT_NODE_EXCLUDE: + { + /* The match rule children of the are + * independent (logical OR) so we can process each one by + * itself + */ + MenuLayoutNode *rule; + + menu_verbose ("Processing (%d entries)\n", + desktop_entry_set_get_count (entries)); + + rule = menu_layout_node_get_children (layout_iter); + while (rule != NULL) + { + DesktopEntrySet *rule_set; + + rule_set = process_include_rules (rule, entry_pool); + if (rule_set != NULL) + { + if (excluded_set != NULL) + desktop_entry_set_union (excluded_set, rule_set); + desktop_entry_set_subtract (entries, rule_set); + desktop_entry_set_unref (rule_set); + } + + rule = menu_layout_node_get_next (rule); + } + + menu_verbose ("Processed (%d entries)\n", + desktop_entry_set_get_count (entries)); + } + break; + + case MENU_LAYOUT_NODE_DIRECTORY: + { + DesktopEntry *entry; + + menu_verbose ("Processing %s\n", + menu_layout_node_get_content (layout_iter)); + + /* + * The last to exist wins, so we always try overwriting + */ + entry = entry_directory_list_get_directory (menu_layout_node_menu_get_directory_dirs (layout), + menu_layout_node_get_content (layout_iter)); + + if (entry != NULL) + { + if (!desktop_entry_get_hidden (entry)) + { + if (directory->directory_entry) + desktop_entry_unref (directory->directory_entry); + directory->directory_entry = entry; /* pass ref ownership */ + } + else + { + desktop_entry_unref (entry); + } + } + + menu_verbose ("Processed new directory entry = %p (%s)\n", + directory->directory_entry, + directory->directory_entry? desktop_entry_get_path (directory->directory_entry) : "null"); + } + break; + + case MENU_LAYOUT_NODE_DELETED: + menu_verbose ("Processed \n"); + deleted = TRUE; + break; + + case MENU_LAYOUT_NODE_NOT_DELETED: + menu_verbose ("Processed \n"); + deleted = FALSE; + break; + + case MENU_LAYOUT_NODE_ONLY_UNALLOCATED: + menu_verbose ("Processed \n"); + only_unallocated = TRUE; + break; + + case MENU_LAYOUT_NODE_NOT_ONLY_UNALLOCATED: + menu_verbose ("Processed \n"); + only_unallocated = FALSE; + break; + + case MENU_LAYOUT_NODE_DEFAULT_LAYOUT: + menu_layout_node_default_layout_get_values (layout_iter, + &directory->default_layout_values); + collect_layout_info (layout_iter, &directory->default_layout_info); + menu_verbose ("Processed \n"); + break; + + case MENU_LAYOUT_NODE_LAYOUT: + collect_layout_info (layout_iter, &directory->layout_info); + menu_verbose ("Processed \n"); + break; + + default: + break; + } + + layout_iter = menu_layout_node_get_next (layout_iter); + } + + desktop_entry_set_unref (entry_pool); + + directory->only_unallocated = only_unallocated; + + if (!directory->only_unallocated) + desktop_entry_set_union (allocated, allocated_set); + + desktop_entry_set_unref (allocated_set); + + if (directory->directory_entry) + { + if (desktop_entry_get_no_display (directory->directory_entry)) + { + directory->is_nodisplay = TRUE; + + if (!(tree->flags & GMENU_TREE_FLAGS_INCLUDE_NODISPLAY)) + { + menu_verbose ("Not showing menu %s because NoDisplay=true\n", + desktop_entry_get_name (directory->directory_entry)); + deleted = TRUE; + } + } + + if (!desktop_entry_get_show_in (directory->directory_entry)) + { + menu_verbose ("Not showing menu %s because OnlyShowIn!=$DESKTOP or NotShowIn=$DESKTOP (with $DESKTOP=${XDG_CURRENT_DESKTOP:-GNOME})\n", + desktop_entry_get_name (directory->directory_entry)); + deleted = TRUE; + } + } + + if (deleted) + { + if (excluded_set != NULL) + desktop_entry_set_unref (excluded_set); + desktop_entry_set_unref (entries); + gmenu_tree_item_unref (directory); + return NULL; + } + + desktop_entry_set_foreach (entries, + (DesktopEntrySetForeachFunc) entries_listify_foreach, + directory); + desktop_entry_set_unref (entries); + + if (excluded_set != NULL) + { + desktop_entry_set_foreach (excluded_set, + (DesktopEntrySetForeachFunc) excluded_entries_listify_foreach, + directory); + desktop_entry_set_unref (excluded_set); + } + + tmp = directory->subdirs; + while (tmp != NULL) + { + GMenuTreeDirectory *subdir = tmp->data; + + set_default_layout_values (directory, subdir); + + tmp = tmp->next; + } + + tmp = directory->entries; + while (tmp != NULL) + { + GMenuTreeEntry *entry = tmp->data; + GSList *next = tmp->next; + gboolean delete = FALSE; + + /* If adding a new condition to delete here, it has to be added to + * get_still_unallocated_foreach() too */ + + if (desktop_entry_get_hidden (entry->desktop_entry)) + { + menu_verbose ("Deleting %s because Hidden=true\n", + desktop_entry_get_name (entry->desktop_entry)); + delete = TRUE; + } + + if (!(tree->flags & GMENU_TREE_FLAGS_INCLUDE_NODISPLAY) && + desktop_entry_get_no_display (entry->desktop_entry)) + { + menu_verbose ("Deleting %s because NoDisplay=true\n", + desktop_entry_get_name (entry->desktop_entry)); + delete = TRUE; + } + + if (!desktop_entry_get_show_in (entry->desktop_entry)) + { + menu_verbose ("Deleting %s because OnlyShowIn!=$DESKTOP or NotShowIn=$DESKTOP (with $DESKTOP=${XDG_CURRENT_DESKTOP:-GNOME})\n", + desktop_entry_get_name (entry->desktop_entry)); + delete = TRUE; + } + + /* No need to filter out based on TryExec since GDesktopAppInfo cannot + * deal with .desktop files with a failed TryExec. */ + + if (delete) + { + directory->entries = g_slist_delete_link (directory->entries, + tmp); + gmenu_tree_item_unref_and_unset_parent (entry); + } + + tmp = next; + } + + g_assert (directory->name != NULL); + + return directory; +} + +static void +process_only_unallocated (GMenuTree *tree, + GMenuTreeDirectory *directory, + DesktopEntrySet *allocated, + DesktopEntrySet *unallocated_used) +{ + GSList *tmp; + + /* For any directory marked only_unallocated, we have to remove any + * entries that were in fact allocated. + */ + + if (directory->only_unallocated) + { + tmp = directory->entries; + while (tmp != NULL) + { + GMenuTreeEntry *entry = tmp->data; + GSList *next = tmp->next; + + if (desktop_entry_set_lookup (allocated, entry->desktop_file_id)) + { + directory->entries = g_slist_delete_link (directory->entries, + tmp); + gmenu_tree_item_unref_and_unset_parent (entry); + } + else + { + desktop_entry_set_add_entry (unallocated_used, entry->desktop_entry, entry->desktop_file_id); + } + + tmp = next; + } + } + + tmp = directory->subdirs; + while (tmp != NULL) + { + GMenuTreeDirectory *subdir = tmp->data; + + process_only_unallocated (tree, subdir, allocated, unallocated_used); + + tmp = tmp->next; + } +} + +typedef struct +{ + GMenuTree *tree; + DesktopEntrySet *allocated; + DesktopEntrySet *unallocated_used; + DesktopEntrySet *still_unallocated; +} GetStillUnallocatedForeachData; + +static void +get_still_unallocated_foreach (const char *file_id, + DesktopEntry *entry, + GetStillUnallocatedForeachData *data) +{ + if (desktop_entry_set_lookup (data->allocated, file_id)) + return; + + if (desktop_entry_set_lookup (data->unallocated_used, file_id)) + return; + + /* Same rules than at the end of process_layout() */ + if (desktop_entry_get_hidden (entry)) + return; + + if (!(data->tree->flags & GMENU_TREE_FLAGS_INCLUDE_NODISPLAY) && + desktop_entry_get_no_display (entry)) + return; + + if (!desktop_entry_get_show_in (entry)) + return; + + desktop_entry_set_add_entry (data->still_unallocated, entry, file_id); +} + +static void preprocess_layout_info (GMenuTree *tree, + GMenuTreeDirectory *directory); + +static GSList * +get_layout_info (GMenuTreeDirectory *directory, + gboolean *is_default_layout) +{ + GMenuTreeDirectory *iter; + + if (directory->layout_info != NULL) + { + if (is_default_layout) + { + *is_default_layout = FALSE; + } + return directory->layout_info; + } + + /* Even if there's no layout information at all, the result will be an + * implicit default layout */ + if (is_default_layout) + { + *is_default_layout = TRUE; + } + + iter = directory; + while (iter != NULL) + { + /* FIXME: this is broken: we might skip real parent in the + * XML structure, that are hidden because of inlining. */ + if (iter->default_layout_info != NULL) + { + return iter->default_layout_info; + } + + iter = GMENU_TREE_ITEM (iter)->parent; + } + + return NULL; +} + +static void +get_values_with_defaults (MenuLayoutNode *node, + MenuLayoutValues *layout_values, + MenuLayoutValues *default_layout_values) +{ + menu_layout_node_menuname_get_values (node, layout_values); + + if (!(layout_values->mask & MENU_LAYOUT_VALUES_SHOW_EMPTY)) + layout_values->show_empty = default_layout_values->show_empty; + + if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_MENUS)) + layout_values->inline_menus = default_layout_values->inline_menus; + + if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_LIMIT)) + layout_values->inline_limit = default_layout_values->inline_limit; + + if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_HEADER)) + layout_values->inline_header = default_layout_values->inline_header; + + if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_ALIAS)) + layout_values->inline_alias = default_layout_values->inline_alias; +} + +static guint +get_real_subdirs_len (GMenuTreeDirectory *directory) +{ + guint len; + GSList *tmp; + + len = 0; + + tmp = directory->subdirs; + while (tmp != NULL) + { + GMenuTreeDirectory *subdir = tmp->data; + + tmp = tmp->next; + + if (subdir->will_inline_header != G_MAXUINT16) + { + len += get_real_subdirs_len (subdir) + g_slist_length (subdir->entries) + 1; + } + else + len += 1; + } + + return len; +} + +static void +preprocess_layout_info_subdir_helper (GMenuTree *tree, + GMenuTreeDirectory *directory, + GMenuTreeDirectory *subdir, + MenuLayoutValues *layout_values, + gboolean *contents_added, + gboolean *should_remove) +{ + preprocess_layout_info (tree, subdir); + + *should_remove = FALSE; + *contents_added = FALSE; + + if (subdir->subdirs == NULL && subdir->entries == NULL) + { + if (!(tree->flags & GMENU_TREE_FLAGS_SHOW_EMPTY) && + !layout_values->show_empty) + { + menu_verbose ("Not showing empty menu '%s'\n", subdir->name); + *should_remove = TRUE; + } + } + + else if (layout_values->inline_menus) + { + guint real_subdirs_len; + + real_subdirs_len = get_real_subdirs_len (subdir); + + if (layout_values->inline_alias && + real_subdirs_len + g_slist_length (subdir->entries) == 1) + { + GMenuTreeAlias *alias; + GMenuTreeItem *item; + GSList *list; + + if (subdir->subdirs != NULL) + list = subdir->subdirs; + else + list = subdir->entries; + + item = GMENU_TREE_ITEM (list->data); + + menu_verbose ("Inline aliasing '%s' to '%s'\n", + item->type == GMENU_TREE_ITEM_ENTRY ? + g_app_info_get_name (G_APP_INFO (gmenu_tree_entry_get_app_info (GMENU_TREE_ENTRY (item)))) : + (item->type == GMENU_TREE_ITEM_DIRECTORY ? + gmenu_tree_directory_get_name (GMENU_TREE_DIRECTORY (item)) : + gmenu_tree_directory_get_name (GMENU_TREE_ALIAS (item)->directory)), + subdir->name); + + alias = gmenu_tree_alias_new (directory, subdir, item); + + g_slist_foreach (list, + (GFunc) gmenu_tree_item_unref_and_unset_parent, + NULL); + g_slist_free (list); + subdir->subdirs = NULL; + subdir->entries = NULL; + + if (item->type == GMENU_TREE_ITEM_DIRECTORY) + directory->subdirs = g_slist_append (directory->subdirs, alias); + else + directory->entries = g_slist_append (directory->entries, alias); + + *contents_added = TRUE; + *should_remove = TRUE; + } + + else if (layout_values->inline_limit == 0 || + layout_values->inline_limit >= real_subdirs_len + g_slist_length (subdir->entries)) + { + if (layout_values->inline_header) + { + menu_verbose ("Creating inline header with name '%s'\n", subdir->name); + /* we're limited to 16-bits to spare some memory; if the limit is + * higher than that (would be crazy), we just consider it's + * unlimited */ + if (layout_values->inline_limit < G_MAXUINT16) + subdir->will_inline_header = layout_values->inline_limit; + else + subdir->will_inline_header = 0; + } + else + { + g_slist_foreach (subdir->subdirs, + (GFunc) gmenu_tree_item_set_parent, + directory); + directory->subdirs = g_slist_concat (directory->subdirs, + subdir->subdirs); + subdir->subdirs = NULL; + + g_slist_foreach (subdir->entries, + (GFunc) gmenu_tree_item_set_parent, + directory); + directory->entries = g_slist_concat (directory->entries, + subdir->entries); + subdir->entries = NULL; + + *contents_added = TRUE; + *should_remove = TRUE; + } + + menu_verbose ("Inlining directory contents of '%s' to '%s'\n", + subdir->name, directory->name); + } + } +} + +static void +preprocess_layout_info (GMenuTree *tree, + GMenuTreeDirectory *directory) +{ + GSList *tmp; + GSList *layout_info; + gboolean using_default_layout; + GSList *last_subdir; + gboolean strip_duplicates; + gboolean contents_added; + gboolean should_remove; + GSList *subdirs_sentinel; + + /* Note: we need to preprocess all menus, even if the layout mask for a menu + * is MENU_LAYOUT_VALUES_NONE: in this case, we need to remove empty menus; + * and the layout mask can be different for a submenu anyway */ + + menu_verbose ("Processing menu layout inline hints for %s\n", directory->name); + g_assert (!directory->preprocessed); + + strip_duplicates = FALSE; + /* we use last_subdir to track the last non-inlined subdirectory */ + last_subdir = g_slist_last (directory->subdirs); + + /* + * First process subdirectories with explicit layout + */ + layout_info = get_layout_info (directory, &using_default_layout); + tmp = layout_info; + /* see comment below about Menuname to understand why we leave the loop if + * last_subdir is NULL */ + while (tmp != NULL && last_subdir != NULL) + { + MenuLayoutNode *node = tmp->data; + MenuLayoutValues layout_values; + const char *name; + GMenuTreeDirectory *subdir; + GSList *subdir_l; + + tmp = tmp->next; + + /* only Menuname nodes are relevant here */ + if (menu_layout_node_get_type (node) != MENU_LAYOUT_NODE_MENUNAME) + continue; + + get_values_with_defaults (node, + &layout_values, + &directory->default_layout_values); + + /* find the subdirectory that is affected by those attributes */ + name = menu_layout_node_get_content (node); + subdir = NULL; + subdir_l = directory->subdirs; + while (subdir_l != NULL) + { + subdir = subdir_l->data; + + if (!strcmp (subdir->name, name)) + break; + + subdir = NULL; + subdir_l = subdir_l->next; + + /* We do not want to use Menuname on a menu that appeared via + * inlining: without inlining, the Menuname wouldn't have matched + * anything, and we want to keep the same behavior. + * Unless the layout is a default layout, in which case the Menuname + * does match the subdirectory. */ + if (!using_default_layout && subdir_l == last_subdir) + { + subdir_l = NULL; + break; + } + } + + if (subdir == NULL) + continue; + + preprocess_layout_info_subdir_helper (tree, directory, + subdir, &layout_values, + &contents_added, &should_remove); + strip_duplicates = strip_duplicates || contents_added; + if (should_remove) + { + if (last_subdir == subdir_l) + { + /* we need to recompute last_subdir since we'll remove it from + * the list */ + GSList *buf; + + if (subdir_l == directory->subdirs) + last_subdir = NULL; + else + { + buf = directory->subdirs; + while (buf != NULL && buf->next != subdir_l) + buf = buf->next; + last_subdir = buf; + } + } + + directory->subdirs = g_slist_remove (directory->subdirs, subdir); + gmenu_tree_item_unref_and_unset_parent (GMENU_TREE_ITEM (subdir)); + } + } + + /* + * Now process the subdirectories with no explicit layout + */ + /* this is bogus data, but we just need the pointer anyway */ + subdirs_sentinel = g_slist_prepend (directory->subdirs, PACKAGE); + directory->subdirs = subdirs_sentinel; + + tmp = directory->subdirs; + while (tmp->next != NULL) + { + GMenuTreeDirectory *subdir = tmp->next->data; + + if (subdir->preprocessed) + { + tmp = tmp->next; + continue; + } + + preprocess_layout_info_subdir_helper (tree, directory, + subdir, &directory->default_layout_values, + &contents_added, &should_remove); + strip_duplicates = strip_duplicates || contents_added; + if (should_remove) + { + tmp = g_slist_delete_link (tmp, tmp->next); + gmenu_tree_item_unref_and_unset_parent (GMENU_TREE_ITEM (subdir)); + } + else + tmp = tmp->next; + } + + /* remove the sentinel */ + directory->subdirs = g_slist_delete_link (directory->subdirs, + directory->subdirs); + + /* + * Finally, remove duplicates if needed + */ + if (strip_duplicates) + { + /* strip duplicate entries; there should be no duplicate directories */ + directory->entries = g_slist_sort (directory->entries, + (GCompareFunc) gmenu_tree_entry_compare_by_id); + tmp = directory->entries; + while (tmp != NULL && tmp->next != NULL) + { + GMenuTreeItem *a = tmp->data; + GMenuTreeItem *b = tmp->next->data; + + if (a->type == GMENU_TREE_ITEM_ALIAS) + a = GMENU_TREE_ALIAS (a)->aliased_item; + + if (b->type == GMENU_TREE_ITEM_ALIAS) + b = GMENU_TREE_ALIAS (b)->aliased_item; + + if (strcmp (GMENU_TREE_ENTRY (a)->desktop_file_id, + GMENU_TREE_ENTRY (b)->desktop_file_id) == 0) + { + tmp = g_slist_delete_link (tmp, tmp->next); + gmenu_tree_item_unref (b); + } + else + tmp = tmp->next; + } + } + + directory->preprocessed = TRUE; +} + +static void process_layout_info (GMenuTree *tree, + GMenuTreeDirectory *directory); + +static void +check_pending_separator (GMenuTreeDirectory *directory) +{ + if (directory->layout_pending_separator) + { + menu_verbose ("Adding pending separator in '%s'\n", directory->name); + + directory->contents = g_slist_append (directory->contents, + gmenu_tree_separator_new (directory)); + directory->layout_pending_separator = FALSE; + } +} + +static void +merge_alias (GMenuTree *tree, + GMenuTreeDirectory *directory, + GMenuTreeAlias *alias) +{ + menu_verbose ("Merging alias '%s' in directory '%s'\n", + alias->directory->name, directory->name); + + if (alias->aliased_item->type == GMENU_TREE_ITEM_DIRECTORY) + { + process_layout_info (tree, GMENU_TREE_DIRECTORY (alias->aliased_item)); + } + + check_pending_separator (directory); + + directory->contents = g_slist_append (directory->contents, + gmenu_tree_item_ref (alias)); +} + +static void +merge_subdir (GMenuTree *tree, + GMenuTreeDirectory *directory, + GMenuTreeDirectory *subdir) +{ + menu_verbose ("Merging subdir '%s' in directory '%s'\n", + subdir->name, directory->name); + + process_layout_info (tree, subdir); + + check_pending_separator (directory); + + if (subdir->will_inline_header == 0 || + (subdir->will_inline_header != G_MAXUINT16 && + g_slist_length (subdir->contents) <= subdir->will_inline_header)) + { + GMenuTreeHeader *header; + + header = gmenu_tree_header_new (directory, subdir); + directory->contents = g_slist_append (directory->contents, header); + + g_slist_foreach (subdir->contents, + (GFunc) gmenu_tree_item_set_parent, + directory); + directory->contents = g_slist_concat (directory->contents, + subdir->contents); + subdir->contents = NULL; + subdir->will_inline_header = G_MAXUINT16; + + gmenu_tree_item_set_parent (GMENU_TREE_ITEM (subdir), NULL); + } + else + { + directory->contents = g_slist_append (directory->contents, + gmenu_tree_item_ref (subdir)); + } +} + +static void +merge_subdir_by_name (GMenuTree *tree, + GMenuTreeDirectory *directory, + const char *subdir_name) +{ + GSList *tmp; + + menu_verbose ("Attempting to merge subdir '%s' in directory '%s'\n", + subdir_name, directory->name); + + tmp = directory->subdirs; + while (tmp != NULL) + { + GMenuTreeDirectory *subdir = tmp->data; + GSList *next = tmp->next; + + /* if it's an alias, then it cannot be affected by + * the Merge nodes in the layout */ + if (GMENU_TREE_ITEM (subdir)->type == GMENU_TREE_ITEM_ALIAS) + continue; + + if (!strcmp (subdir->name, subdir_name)) + { + directory->subdirs = g_slist_delete_link (directory->subdirs, tmp); + merge_subdir (tree, directory, subdir); + gmenu_tree_item_unref (subdir); + } + + tmp = next; + } +} + +static void +merge_entry (GMenuTree *tree, + GMenuTreeDirectory *directory, + GMenuTreeEntry *entry) +{ + menu_verbose ("Merging entry '%s' in directory '%s'\n", + entry->desktop_file_id, directory->name); + + check_pending_separator (directory); + directory->contents = g_slist_append (directory->contents, + gmenu_tree_item_ref (entry)); +} + +static void +merge_entry_by_id (GMenuTree *tree, + GMenuTreeDirectory *directory, + const char *file_id) +{ + GSList *tmp; + + menu_verbose ("Attempting to merge entry '%s' in directory '%s'\n", + file_id, directory->name); + + tmp = directory->entries; + while (tmp != NULL) + { + GMenuTreeEntry *entry = tmp->data; + GSList *next = tmp->next; + + /* if it's an alias, then it cannot be affected by + * the Merge nodes in the layout */ + if (GMENU_TREE_ITEM (entry)->type == GMENU_TREE_ITEM_ALIAS) + continue; + + if (!strcmp (entry->desktop_file_id, file_id)) + { + directory->entries = g_slist_delete_link (directory->entries, tmp); + merge_entry (tree, directory, entry); + gmenu_tree_item_unref (entry); + } + + tmp = next; + } +} + +static inline gboolean +find_name_in_list (const char *name, + GSList *list) +{ + while (list != NULL) + { + if (!strcmp (name, list->data)) + return TRUE; + + list = list->next; + } + + return FALSE; +} + +static void +merge_subdirs (GMenuTree *tree, + GMenuTreeDirectory *directory, + GSList *except) +{ + GSList *subdirs; + GSList *tmp; + + menu_verbose ("Merging subdirs in directory '%s'\n", directory->name); + + subdirs = directory->subdirs; + directory->subdirs = NULL; + + subdirs = g_slist_sort_with_data (subdirs, + (GCompareDataFunc) gmenu_tree_item_compare, + GINT_TO_POINTER (GMENU_TREE_FLAGS_NONE)); + + tmp = subdirs; + while (tmp != NULL) + { + GMenuTreeDirectory *subdir = tmp->data; + + if (GMENU_TREE_ITEM (subdir)->type == GMENU_TREE_ITEM_ALIAS) + { + merge_alias (tree, directory, GMENU_TREE_ALIAS (subdir)); + gmenu_tree_item_unref (subdir); + } + else if (!find_name_in_list (subdir->name, except)) + { + merge_subdir (tree, directory, subdir); + gmenu_tree_item_unref (subdir); + } + else + { + menu_verbose ("Not merging directory '%s' yet\n", subdir->name); + directory->subdirs = g_slist_append (directory->subdirs, subdir); + } + + tmp = tmp->next; + } + + g_slist_free (subdirs); + g_slist_free (except); +} + +static void +merge_entries (GMenuTree *tree, + GMenuTreeDirectory *directory, + GSList *except) +{ + GSList *entries; + GSList *tmp; + + menu_verbose ("Merging entries in directory '%s'\n", directory->name); + + entries = directory->entries; + directory->entries = NULL; + + entries = g_slist_sort_with_data (entries, + (GCompareDataFunc) gmenu_tree_item_compare, + GINT_TO_POINTER (tree->flags)); + + tmp = entries; + while (tmp != NULL) + { + GMenuTreeEntry *entry = tmp->data; + + if (GMENU_TREE_ITEM (entry)->type == GMENU_TREE_ITEM_ALIAS) + { + merge_alias (tree, directory, GMENU_TREE_ALIAS (entry)); + gmenu_tree_item_unref (entry); + } + else if (!find_name_in_list (entry->desktop_file_id, except)) + { + merge_entry (tree, directory, entry); + gmenu_tree_item_unref (entry); + } + else + { + menu_verbose ("Not merging entry '%s' yet\n", entry->desktop_file_id); + directory->entries = g_slist_append (directory->entries, entry); + } + + tmp = tmp->next; + } + + g_slist_free (entries); + g_slist_free (except); +} + +static void +merge_subdirs_and_entries (GMenuTree *tree, + GMenuTreeDirectory *directory, + GSList *except_subdirs, + GSList *except_entries) +{ + GSList *items; + GSList *tmp; + + menu_verbose ("Merging subdirs and entries together in directory %s\n", + directory->name); + + items = g_slist_concat (directory->subdirs, directory->entries); + + directory->subdirs = NULL; + directory->entries = NULL; + + items = g_slist_sort_with_data (items, + (GCompareDataFunc) gmenu_tree_item_compare, + GINT_TO_POINTER (tree->flags)); + + tmp = items; + while (tmp != NULL) + { + GMenuTreeItem *item = tmp->data; + GMenuTreeItemType type; + + type = item->type; + + if (type == GMENU_TREE_ITEM_ALIAS) + { + merge_alias (tree, directory, GMENU_TREE_ALIAS (item)); + gmenu_tree_item_unref (item); + } + else if (type == GMENU_TREE_ITEM_DIRECTORY) + { + if (!find_name_in_list (GMENU_TREE_DIRECTORY (item)->name, except_subdirs)) + { + merge_subdir (tree, + directory, + GMENU_TREE_DIRECTORY (item)); + gmenu_tree_item_unref (item); + } + else + { + menu_verbose ("Not merging directory '%s' yet\n", + GMENU_TREE_DIRECTORY (item)->name); + directory->subdirs = g_slist_append (directory->subdirs, item); + } + } + else if (type == GMENU_TREE_ITEM_ENTRY) + { + if (!find_name_in_list (GMENU_TREE_ENTRY (item)->desktop_file_id, except_entries)) + { + merge_entry (tree, directory, GMENU_TREE_ENTRY (item)); + gmenu_tree_item_unref (item); + } + else + { + menu_verbose ("Not merging entry '%s' yet\n", + GMENU_TREE_ENTRY (item)->desktop_file_id); + directory->entries = g_slist_append (directory->entries, item); + } + } + else + { + g_assert_not_reached (); + } + + tmp = tmp->next; + } + + g_slist_free (items); + g_slist_free (except_subdirs); + g_slist_free (except_entries); +} + +static GSList * +get_subdirs_from_layout_info (GSList *layout_info) +{ + GSList *subdirs; + GSList *tmp; + + subdirs = NULL; + + tmp = layout_info; + while (tmp != NULL) + { + MenuLayoutNode *node = tmp->data; + + if (menu_layout_node_get_type (node) == MENU_LAYOUT_NODE_MENUNAME) + { + subdirs = g_slist_append (subdirs, + (char *) menu_layout_node_get_content (node)); + } + + tmp = tmp->next; + } + + return subdirs; +} + +static GSList * +get_entries_from_layout_info (GSList *layout_info) +{ + GSList *entries; + GSList *tmp; + + entries = NULL; + + tmp = layout_info; + while (tmp != NULL) + { + MenuLayoutNode *node = tmp->data; + + if (menu_layout_node_get_type (node) == MENU_LAYOUT_NODE_FILENAME) + { + entries = g_slist_append (entries, + (char *) menu_layout_node_get_content (node)); + } + + tmp = tmp->next; + } + + return entries; +} + +static void +process_layout_info (GMenuTree *tree, + GMenuTreeDirectory *directory) +{ + GSList *layout_info; + + menu_verbose ("Processing menu layout hints for %s\n", directory->name); + + g_slist_foreach (directory->contents, + (GFunc) gmenu_tree_item_unref_and_unset_parent, + NULL); + g_slist_free (directory->contents); + directory->contents = NULL; + directory->layout_pending_separator = FALSE; + + layout_info = get_layout_info (directory, NULL); + + if (layout_info == NULL) + { + merge_subdirs (tree, directory, NULL); + merge_entries (tree, directory, NULL); + } + else + { + GSList *tmp; + + tmp = layout_info; + while (tmp != NULL) + { + MenuLayoutNode *node = tmp->data; + + switch (menu_layout_node_get_type (node)) + { + case MENU_LAYOUT_NODE_MENUNAME: + merge_subdir_by_name (tree, + directory, + menu_layout_node_get_content (node)); + break; + + case MENU_LAYOUT_NODE_FILENAME: + merge_entry_by_id (tree, + directory, + menu_layout_node_get_content (node)); + break; + + case MENU_LAYOUT_NODE_SEPARATOR: + /* Unless explicitly told to show all separators, do not show a + * separator at the beginning of a menu. Note that we don't add + * the separators now, and instead make it pending. This way, we + * won't show two consecutive separators nor will we show a + * separator at the end of a menu. */ + if (tree->flags & GMENU_TREE_FLAGS_SHOW_ALL_SEPARATORS) + { + directory->layout_pending_separator = TRUE; + check_pending_separator (directory); + } + else if (directory->contents) + { + menu_verbose ("Adding a potential separator in '%s'\n", + directory->name); + + directory->layout_pending_separator = TRUE; + } + else + { + menu_verbose ("Skipping separator at the beginning of '%s'\n", + directory->name); + } + break; + + case MENU_LAYOUT_NODE_MERGE: + switch (menu_layout_node_merge_get_type (node)) + { + case MENU_LAYOUT_MERGE_NONE: + break; + + case MENU_LAYOUT_MERGE_MENUS: + merge_subdirs (tree, + directory, + get_subdirs_from_layout_info (tmp->next)); + break; + + case MENU_LAYOUT_MERGE_FILES: + merge_entries (tree, + directory, + get_entries_from_layout_info (tmp->next)); + break; + + case MENU_LAYOUT_MERGE_ALL: + merge_subdirs_and_entries (tree, + directory, + get_subdirs_from_layout_info (tmp->next), + get_entries_from_layout_info (tmp->next)); + break; + + default: + g_assert_not_reached (); + break; + } + break; + + default: + g_assert_not_reached (); + break; + } + + tmp = tmp->next; + } + } + + g_slist_foreach (directory->subdirs, + (GFunc) gmenu_tree_item_unref, + NULL); + g_slist_free (directory->subdirs); + directory->subdirs = NULL; + + g_slist_foreach (directory->entries, + (GFunc) gmenu_tree_item_unref, + NULL); + g_slist_free (directory->entries); + directory->entries = NULL; + + g_slist_foreach (directory->default_layout_info, + (GFunc) menu_layout_node_unref, + NULL); + g_slist_free (directory->default_layout_info); + directory->default_layout_info = NULL; + + g_slist_foreach (directory->layout_info, + (GFunc) menu_layout_node_unref, + NULL); + g_slist_free (directory->layout_info); + directory->layout_info = NULL; +} + +static void +handle_entries_changed (MenuLayoutNode *layout, + GMenuTree *tree) +{ + if (tree->layout == layout) + { + gmenu_tree_force_rebuild (tree); + gmenu_tree_invoke_monitors (tree); + } +} + +static void +update_entry_index (GMenuTree *tree, + GMenuTreeDirectory *dir) +{ + GMenuTreeIter *iter = gmenu_tree_directory_iter (dir); + GMenuTreeItemType next_type; + + while ((next_type = gmenu_tree_iter_next (iter)) != GMENU_TREE_ITEM_INVALID) + { + gpointer item = NULL; + + switch (next_type) + { + case GMENU_TREE_ITEM_ENTRY: + { + const char *id; + + item = gmenu_tree_iter_get_entry (iter); + id = gmenu_tree_entry_get_desktop_file_id (item); + if (id != NULL) + g_hash_table_insert (tree->entries_by_id, (char*)id, item); + } + break; + case GMENU_TREE_ITEM_DIRECTORY: + { + item = gmenu_tree_iter_get_directory (iter); + update_entry_index (tree, (GMenuTreeDirectory*)item); + } + break; + default: + break; + } + if (item != NULL) + gmenu_tree_item_unref (item); + } + + gmenu_tree_iter_unref (iter); +} + +static gboolean +gmenu_tree_build_from_layout (GMenuTree *tree, + GError **error) +{ + DesktopEntrySet *allocated; + + if (tree->root) + return TRUE; + + if (!gmenu_tree_load_layout (tree, error)) + return FALSE; + + menu_verbose ("Building menu tree from layout\n"); + + allocated = desktop_entry_set_new (); + + /* create the menu structure */ + tree->root = process_layout (tree, + NULL, + find_menu_child (tree->layout), + allocated); + if (tree->root) + { + DesktopEntrySet *unallocated_used; + + unallocated_used = desktop_entry_set_new (); + + process_only_unallocated (tree, tree->root, allocated, unallocated_used); + if (tree->flags & GMENU_TREE_FLAGS_INCLUDE_UNALLOCATED) + { + DesktopEntrySet *entry_pool; + DesktopEntrySet *still_unallocated; + GetStillUnallocatedForeachData data; + + entry_pool = _entry_directory_list_get_all_desktops (menu_layout_node_menu_get_app_dirs (find_menu_child (tree->layout))); + still_unallocated = desktop_entry_set_new (); + + data.tree = tree; + data.allocated = allocated; + data.unallocated_used = unallocated_used; + data.still_unallocated = still_unallocated; + + desktop_entry_set_foreach (entry_pool, + (DesktopEntrySetForeachFunc) get_still_unallocated_foreach, + &data); + + desktop_entry_set_unref (entry_pool); + + desktop_entry_set_foreach (still_unallocated, + (DesktopEntrySetForeachFunc) unallocated_entries_listify_foreach, + tree->root); + + desktop_entry_set_unref (still_unallocated); + } + + desktop_entry_set_unref (unallocated_used); + + /* process the layout info part that can move/remove items: + * inline, show_empty, etc. */ + preprocess_layout_info (tree, tree->root); + /* populate the menu structure that we got with the items, and order it + * according to the layout info */ + process_layout_info (tree, tree->root); + + update_entry_index (tree, tree->root); + + menu_layout_node_root_add_entries_monitor (tree->layout, + (MenuLayoutNodeEntriesChangedFunc) handle_entries_changed, + tree); + } + + desktop_entry_set_unref (allocated); + + return TRUE; +} + +static void +gmenu_tree_force_rebuild (GMenuTree *tree) +{ + if (tree->root) + { + g_hash_table_remove_all (tree->entries_by_id); + gmenu_tree_item_unref (tree->root); + tree->root = NULL; + tree->loaded = FALSE; + + g_assert (tree->layout != NULL); + + menu_layout_node_root_remove_entries_monitor (tree->layout, + (MenuLayoutNodeEntriesChangedFunc) handle_entries_changed, + tree); + } +} + +GType +gmenu_tree_iter_get_type (void) +{ + static GType gtype = G_TYPE_INVALID; + if (gtype == G_TYPE_INVALID) + { + gtype = g_boxed_type_register_static ("GMenuTreeIter", + (GBoxedCopyFunc)gmenu_tree_iter_ref, + (GBoxedFreeFunc)gmenu_tree_iter_unref); + } + return gtype; +} + +GType +gmenu_tree_directory_get_type (void) +{ + static GType gtype = G_TYPE_INVALID; + if (gtype == G_TYPE_INVALID) + { + gtype = g_boxed_type_register_static ("GMenuTreeDirectory", + (GBoxedCopyFunc)gmenu_tree_item_ref, + (GBoxedFreeFunc)gmenu_tree_item_unref); + } + return gtype; +} + +GType +gmenu_tree_entry_get_type (void) +{ + static GType gtype = G_TYPE_INVALID; + if (gtype == G_TYPE_INVALID) + { + gtype = g_boxed_type_register_static ("GMenuTreeEntry", + (GBoxedCopyFunc)gmenu_tree_item_ref, + (GBoxedFreeFunc)gmenu_tree_item_unref); + } + return gtype; +} + +GType +gmenu_tree_separator_get_type (void) +{ + static GType gtype = G_TYPE_INVALID; + if (gtype == G_TYPE_INVALID) + { + gtype = g_boxed_type_register_static ("GMenuTreeSeparator", + (GBoxedCopyFunc)gmenu_tree_item_ref, + (GBoxedFreeFunc)gmenu_tree_item_unref); + } + return gtype; +} + +GType +gmenu_tree_header_get_type (void) +{ + static GType gtype = G_TYPE_INVALID; + if (gtype == G_TYPE_INVALID) + { + gtype = g_boxed_type_register_static ("GMenuTreeHeader", + (GBoxedCopyFunc)gmenu_tree_item_ref, + (GBoxedFreeFunc)gmenu_tree_item_unref); + } + return gtype; +} + +GType +gmenu_tree_alias_get_type (void) +{ + static GType gtype = G_TYPE_INVALID; + if (gtype == G_TYPE_INVALID) + { + gtype = g_boxed_type_register_static ("GMenuTreeAlias", + (GBoxedCopyFunc)gmenu_tree_item_ref, + (GBoxedFreeFunc)gmenu_tree_item_unref); + } + return gtype; +} + +GType +gmenu_tree_flags_get_type (void) +{ + static GType enum_type_id = 0; + if (G_UNLIKELY (!enum_type_id)) + { + static const GFlagsValue values[] = { + { GMENU_TREE_FLAGS_NONE, "GMENU_TREE_FLAGS_NONE", "none" }, + { GMENU_TREE_FLAGS_INCLUDE_EXCLUDED, "GMENU_TREE_FLAGS_INCLUDE_EXCLUDED", "include-excluded" }, + { GMENU_TREE_FLAGS_SHOW_EMPTY, "GMENU_TREE_FLAGS_SHOW_EMPTY", "show-empty" }, + { GMENU_TREE_FLAGS_INCLUDE_NODISPLAY, "GMENU_TREE_FLAGS_INCLUDE_NODISPLAY", "include-nodisplay" }, + { GMENU_TREE_FLAGS_SHOW_ALL_SEPARATORS, "GMENU_TREE_FLAGS_SHOW_ALL_SEPARATORS", "show-all-separators" }, + { GMENU_TREE_FLAGS_SORT_DISPLAY_NAME, "GMENU_TREE_FLAGS_SORT_DISPLAY_NAME", "sort-display-name" }, + { GMENU_TREE_FLAGS_INCLUDE_UNALLOCATED, "GMENU_TREE_FLAGS_INCLUDE_UNALLOCATED,", "include-unallocated" }, + { 0, NULL, NULL } + }; + enum_type_id = g_flags_register_static ("GMenuTreeFlags", values); + } + return enum_type_id; +} diff --git a/libmenu/gmenu-tree.h b/libmenu/gmenu-tree.h new file mode 100644 index 0000000..4ccdb2b --- /dev/null +++ b/libmenu/gmenu-tree.h @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2004, 2011 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GMENU_TREE_H__ +#define __GMENU_TREE_H__ + +#ifndef GMENU_I_KNOW_THIS_IS_UNSTABLE +#error "libgnome-menu should only be used if you understand that it's subject to frequent change, and is not supported as a fixed API/ABI or as part of the platform" +#endif + +#include + +G_BEGIN_DECLS + +#define GMENU_TYPE_TREE (gmenu_tree_get_type ()) +#define GMENU_TREE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GMENU_TYPE_TREE, GMenuTree)) +#define GMENU_TREE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GMENU_TYPE_TREE, GMenuTreeClass)) +#define GMENU_IS_TREE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GMENU_TYPE_TREE)) +#define GMENU_IS_TREE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GMENU_TYPE_TREE)) +#define GMENU_TREE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DESKTOP_APP_INFO, GMenuTreeClass)) + +typedef struct _GMenuTree GMenuTree; +typedef struct _GMenuTreeClass GMenuTreeClass; + +struct _GMenuTreeClass +{ + GObjectClass parent_class; +}; + +GType gmenu_tree_get_type (void) G_GNUC_CONST; + +typedef struct GMenuTreeIter GMenuTreeIter; +typedef struct GMenuTreeDirectory GMenuTreeDirectory; +typedef struct GMenuTreeEntry GMenuTreeEntry; +typedef struct GMenuTreeSeparator GMenuTreeSeparator; +typedef struct GMenuTreeHeader GMenuTreeHeader; +typedef struct GMenuTreeAlias GMenuTreeAlias; + +typedef enum +{ + GMENU_TREE_ITEM_INVALID = 0, + GMENU_TREE_ITEM_DIRECTORY, + GMENU_TREE_ITEM_ENTRY, + GMENU_TREE_ITEM_SEPARATOR, + GMENU_TREE_ITEM_HEADER, + GMENU_TREE_ITEM_ALIAS +} GMenuTreeItemType; + +GType gmenu_tree_iter_get_type (void); + +/* Explicitly skip item, it's a "hidden" base class */ +GType gmenu_tree_directory_get_type (void); +GType gmenu_tree_entry_get_type (void); +GType gmenu_tree_separator_get_type (void); +GType gmenu_tree_header_get_type (void); +GType gmenu_tree_alias_get_type (void); + +typedef enum +{ + GMENU_TREE_FLAGS_NONE = 0, + GMENU_TREE_FLAGS_INCLUDE_EXCLUDED = 1 << 0, + GMENU_TREE_FLAGS_INCLUDE_NODISPLAY = 1 << 1, + GMENU_TREE_FLAGS_INCLUDE_UNALLOCATED = 1 << 2, + /* leave some space for more include flags */ + GMENU_TREE_FLAGS_SHOW_EMPTY = 1 << 8, + GMENU_TREE_FLAGS_SHOW_ALL_SEPARATORS = 1 << 9, + /* leave some space for more show flags */ + GMENU_TREE_FLAGS_SORT_DISPLAY_NAME = 1 << 16 +} GMenuTreeFlags; +GType gmenu_tree_flags_get_type (void); +#define GMENU_TYPE_TREE_FLAGS (gmenu_tree_flags_get_type ()) + +GMenuTree *gmenu_tree_new (const char *menu_basename, + GMenuTreeFlags flags); + +GMenuTree *gmenu_tree_new_for_path (const char *menu_path, + GMenuTreeFlags flags); + +gboolean gmenu_tree_load_sync (GMenuTree *tree, + GError **error); + +const char *gmenu_tree_get_canonical_menu_path (GMenuTree *tree); +GMenuTreeDirectory *gmenu_tree_get_root_directory (GMenuTree *tree); +GMenuTreeDirectory *gmenu_tree_get_directory_from_path (GMenuTree *tree, + const char *path); +GMenuTreeEntry *gmenu_tree_get_entry_by_id (GMenuTree *tree, + const char *id); + +gpointer gmenu_tree_item_ref (gpointer item); +void gmenu_tree_item_unref (gpointer item); + +GMenuTreeDirectory *gmenu_tree_directory_get_parent (GMenuTreeDirectory *directory); +const char *gmenu_tree_directory_get_name (GMenuTreeDirectory *directory); +const char *gmenu_tree_directory_get_generic_name (GMenuTreeDirectory *directory); +const char *gmenu_tree_directory_get_comment (GMenuTreeDirectory *directory); +GIcon *gmenu_tree_directory_get_icon (GMenuTreeDirectory *directory); +const char *gmenu_tree_directory_get_desktop_file_path (GMenuTreeDirectory *directory); +const char *gmenu_tree_directory_get_menu_id (GMenuTreeDirectory *directory); +GMenuTree *gmenu_tree_directory_get_tree (GMenuTreeDirectory *directory); + +gboolean gmenu_tree_directory_get_is_nodisplay (GMenuTreeDirectory *directory); + +GMenuTreeIter *gmenu_tree_directory_iter (GMenuTreeDirectory *directory); + +GMenuTreeIter *gmenu_tree_iter_ref (GMenuTreeIter *iter); +void gmenu_tree_iter_unref (GMenuTreeIter *iter); + +GMenuTreeItemType gmenu_tree_iter_next (GMenuTreeIter *iter); +GMenuTreeDirectory *gmenu_tree_iter_get_directory (GMenuTreeIter *iter); +GMenuTreeEntry *gmenu_tree_iter_get_entry (GMenuTreeIter *iter); +GMenuTreeHeader *gmenu_tree_iter_get_header (GMenuTreeIter *iter); +GMenuTreeAlias *gmenu_tree_iter_get_alias (GMenuTreeIter *iter); +GMenuTreeSeparator *gmenu_tree_iter_get_separator (GMenuTreeIter *iter); + +char *gmenu_tree_directory_make_path (GMenuTreeDirectory *directory, + GMenuTreeEntry *entry); + + +GDesktopAppInfo *gmenu_tree_entry_get_app_info (GMenuTreeEntry *entry); +GMenuTreeDirectory *gmenu_tree_entry_get_parent (GMenuTreeEntry *entry); +GMenuTree *gmenu_tree_entry_get_tree (GMenuTreeEntry *entry); + +const char *gmenu_tree_entry_get_desktop_file_path (GMenuTreeEntry *entry); +const char *gmenu_tree_entry_get_desktop_file_id (GMenuTreeEntry *entry); + +gboolean gmenu_tree_entry_get_is_nodisplay_recurse (GMenuTreeEntry *entry); +gboolean gmenu_tree_entry_get_is_excluded (GMenuTreeEntry *entry); +gboolean gmenu_tree_entry_get_is_unallocated (GMenuTreeEntry *entry); + +GMenuTreeDirectory *gmenu_tree_header_get_directory (GMenuTreeHeader *header); +GMenuTree *gmenu_tree_header_get_tree (GMenuTreeHeader *header); +GMenuTreeDirectory *gmenu_tree_header_get_parent (GMenuTreeHeader *header); + +GMenuTreeDirectory *gmenu_tree_alias_get_directory (GMenuTreeAlias *alias); +GMenuTreeItemType gmenu_tree_alias_get_aliased_item_type (GMenuTreeAlias *alias); +GMenuTreeDirectory *gmenu_tree_alias_get_aliased_directory (GMenuTreeAlias *alias); +GMenuTreeEntry *gmenu_tree_alias_get_aliased_entry (GMenuTreeAlias *alias); +GMenuTree *gmenu_tree_alias_get_tree (GMenuTreeAlias *alias); +GMenuTreeDirectory *gmenu_tree_alias_get_parent (GMenuTreeAlias *alias); + +GMenuTree *gmenu_tree_separator_get_tree (GMenuTreeSeparator *separator); +GMenuTreeDirectory *gmenu_tree_separator_get_parent (GMenuTreeSeparator *separator); + +G_END_DECLS + +#endif /* __GMENU_TREE_H__ */ diff --git a/libmenu/libcinnamon-menu-3.0-uninstalled.pc.in b/libmenu/libcinnamon-menu-3.0-uninstalled.pc.in new file mode 100644 index 0000000..a777484 --- /dev/null +++ b/libmenu/libcinnamon-menu-3.0-uninstalled.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libcinnamon-menu +Description: Desktop Menu Specification Implementation +Requires: gio-unix-2.0 +Version: @VERSION@ +Libs: ${pc_top_builddir}/${pcfiledir}/libcinnamon-menu-3.la +Cflags: -I${pc_top_builddir}/${pcfiledir} diff --git a/libmenu/libcinnamon-menu-3.0.pc.in b/libmenu/libcinnamon-menu-3.0.pc.in new file mode 100644 index 0000000..a3a1d8e --- /dev/null +++ b/libmenu/libcinnamon-menu-3.0.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libcinnamon-menu +Description: Desktop Menu Specification Implementation +Requires: gio-unix-2.0 +Version: @VERSION@ +Libs: -L${libdir} -lcinnamon-menu-3 +Cflags: -I${includedir}/cinnamon-menus-3.0 diff --git a/libmenu/menu-layout.c b/libmenu/menu-layout.c new file mode 100644 index 0000000..4b2a02c --- /dev/null +++ b/libmenu/menu-layout.c @@ -0,0 +1,2395 @@ +/* Menu layout in-memory data structure (a custom "DOM tree") */ + +/* + * Copyright (C) 2002 - 2004 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#include "menu-layout.h" + +#include +#include +#include +#include +#include + +#include "canonicalize.h" +#include "entry-directories.h" +#include "menu-util.h" + +typedef struct MenuLayoutNodeMenu MenuLayoutNodeMenu; +typedef struct MenuLayoutNodeRoot MenuLayoutNodeRoot; +typedef struct MenuLayoutNodeLegacyDir MenuLayoutNodeLegacyDir; +typedef struct MenuLayoutNodeMergeFile MenuLayoutNodeMergeFile; +typedef struct MenuLayoutNodeDefaultLayout MenuLayoutNodeDefaultLayout; +typedef struct MenuLayoutNodeMenuname MenuLayoutNodeMenuname; +typedef struct MenuLayoutNodeMerge MenuLayoutNodeMerge; + +struct MenuLayoutNode +{ + /* Node lists are circular, for length-one lists + * prev/next point back to the node itself. + */ + MenuLayoutNode *prev; + MenuLayoutNode *next; + MenuLayoutNode *parent; + MenuLayoutNode *children; + + char *content; + + guint refcount : 20; + guint type : 7; +}; + +struct MenuLayoutNodeRoot +{ + MenuLayoutNode node; + + char *basedir; + char *name; + + GMainContext *main_context; + + GSList *monitors; + GSource *monitors_idle_handler; +}; + +struct MenuLayoutNodeMenu +{ + MenuLayoutNode node; + + MenuLayoutNode *name_node; /* cache of the node */ + + EntryDirectoryList *app_dirs; + EntryDirectoryList *dir_dirs; +}; + +struct MenuLayoutNodeLegacyDir +{ + MenuLayoutNode node; + + char *prefix; +}; + +struct MenuLayoutNodeMergeFile +{ + MenuLayoutNode node; + + MenuMergeFileType type; +}; + +struct MenuLayoutNodeDefaultLayout +{ + MenuLayoutNode node; + + MenuLayoutValues layout_values; +}; + +struct MenuLayoutNodeMenuname +{ + MenuLayoutNode node; + + MenuLayoutValues layout_values; +}; + +struct MenuLayoutNodeMerge +{ + MenuLayoutNode node; + + MenuLayoutMergeType merge_type; +}; + +typedef struct +{ + MenuLayoutNodeEntriesChangedFunc callback; + gpointer user_data; +} MenuLayoutNodeEntriesMonitor; + + +static inline MenuLayoutNode * +node_next (MenuLayoutNode *node) +{ + /* root nodes (no parent) never have siblings */ + if (node->parent == NULL) + return NULL; + + /* circular list */ + if (node->next == node->parent->children) + return NULL; + + return node->next; +} + +static gboolean +menu_layout_invoke_monitors (MenuLayoutNodeRoot *nr) +{ + GSList *tmp; + + g_assert (nr->node.type == MENU_LAYOUT_NODE_ROOT); + + nr->monitors_idle_handler = NULL; + + tmp = nr->monitors; + while (tmp != NULL) + { + MenuLayoutNodeEntriesMonitor *monitor = tmp->data; + GSList *next = tmp->next; + + monitor->callback ((MenuLayoutNode *) nr, monitor->user_data); + + tmp = next; + } + + return FALSE; +} + +static void +handle_entry_directory_changed (EntryDirectory *dir, + MenuLayoutNode *node) +{ + MenuLayoutNodeRoot *nr; + + g_assert (node->type == MENU_LAYOUT_NODE_MENU); + + nr = (MenuLayoutNodeRoot *) menu_layout_node_get_root (node); + + if (nr->monitors_idle_handler == NULL) + { + nr->monitors_idle_handler = g_idle_source_new (); + g_source_set_callback (nr->monitors_idle_handler, + (GSourceFunc) menu_layout_invoke_monitors, nr, NULL); + g_source_attach (nr->monitors_idle_handler, nr->main_context); + g_source_unref (nr->monitors_idle_handler); + } +} + +static void +remove_entry_directory_list (MenuLayoutNodeMenu *nm, + EntryDirectoryList **dirs) +{ + if (*dirs) + { + entry_directory_list_remove_monitors (*dirs, + (EntryDirectoryChangedFunc) handle_entry_directory_changed, + nm); + entry_directory_list_unref (*dirs); + *dirs = NULL; + } +} + +MenuLayoutNode * +menu_layout_node_ref (MenuLayoutNode *node) +{ + g_return_val_if_fail (node != NULL, NULL); + + node->refcount += 1; + + return node; +} + +void +menu_layout_node_unref (MenuLayoutNode *node) +{ + g_return_if_fail (node != NULL); + g_return_if_fail (node->refcount > 0); + + node->refcount -= 1; + if (node->refcount == 0) + { + MenuLayoutNode *iter; + + iter = node->children; + while (iter != NULL) + { + MenuLayoutNode *next = node_next (iter); + + menu_layout_node_unref (iter); + + iter = next; + } + + if (node->type == MENU_LAYOUT_NODE_MENU) + { + MenuLayoutNodeMenu *nm = (MenuLayoutNodeMenu *) node; + + if (nm->name_node) + menu_layout_node_unref (nm->name_node); + + remove_entry_directory_list (nm, &nm->app_dirs); + remove_entry_directory_list (nm, &nm->dir_dirs); + } + else if (node->type == MENU_LAYOUT_NODE_LEGACY_DIR) + { + MenuLayoutNodeLegacyDir *legacy = (MenuLayoutNodeLegacyDir *) node; + + g_free (legacy->prefix); + } + else if (node->type == MENU_LAYOUT_NODE_ROOT) + { + MenuLayoutNodeRoot *nr = (MenuLayoutNodeRoot*) node; + + g_slist_foreach (nr->monitors, (GFunc) g_free, NULL); + g_slist_free (nr->monitors); + + if (nr->monitors_idle_handler != NULL) + g_source_destroy (nr->monitors_idle_handler); + nr->monitors_idle_handler = NULL; + + if (nr->main_context != NULL) + g_main_context_unref (nr->main_context); + nr->main_context = NULL; + + g_free (nr->basedir); + g_free (nr->name); + } + + g_free (node->content); + g_free (node); + } +} + +MenuLayoutNode * +menu_layout_node_new (MenuLayoutNodeType type) +{ + MenuLayoutNode *node; + + switch (type) + { + case MENU_LAYOUT_NODE_MENU: + node = (MenuLayoutNode *) g_new0 (MenuLayoutNodeMenu, 1); + break; + + case MENU_LAYOUT_NODE_LEGACY_DIR: + node = (MenuLayoutNode *) g_new0 (MenuLayoutNodeLegacyDir, 1); + break; + + case MENU_LAYOUT_NODE_ROOT: + node = (MenuLayoutNode*) g_new0 (MenuLayoutNodeRoot, 1); + break; + + case MENU_LAYOUT_NODE_MERGE_FILE: + node = (MenuLayoutNode *) g_new0 (MenuLayoutNodeMergeFile, 1); + break; + + case MENU_LAYOUT_NODE_DEFAULT_LAYOUT: + node = (MenuLayoutNode *) g_new0 (MenuLayoutNodeDefaultLayout, 1); + break; + + case MENU_LAYOUT_NODE_MENUNAME: + node = (MenuLayoutNode *) g_new0 (MenuLayoutNodeMenuname, 1); + break; + + case MENU_LAYOUT_NODE_MERGE: + node = (MenuLayoutNode *) g_new0 (MenuLayoutNodeMerge, 1); + break; + + default: + node = g_new0 (MenuLayoutNode, 1); + break; + } + + node->type = type; + + node->refcount = 1; + + /* we're in a list of one node */ + node->next = node; + node->prev = node; + + return node; +} + +MenuLayoutNode * +menu_layout_node_get_next (MenuLayoutNode *node) +{ + return node_next (node); +} + +MenuLayoutNode * +menu_layout_node_get_parent (MenuLayoutNode *node) +{ + return node->parent; +} + +MenuLayoutNode * +menu_layout_node_get_children (MenuLayoutNode *node) +{ + return node->children; +} + +MenuLayoutNode * +menu_layout_node_get_root (MenuLayoutNode *node) +{ + MenuLayoutNode *parent; + + parent = node; + while (parent->parent != NULL) + parent = parent->parent; + + g_assert (parent->type == MENU_LAYOUT_NODE_ROOT); + + return parent; +} + +char * +menu_layout_node_get_content_as_path (MenuLayoutNode *node) +{ + if (node->content == NULL) + { + menu_verbose (" (node has no content to get as a path)\n"); + return NULL; + } + + if (g_path_is_absolute (node->content)) + { + return g_strdup (node->content); + } + else + { + MenuLayoutNodeRoot *root; + + root = (MenuLayoutNodeRoot *) menu_layout_node_get_root (node); + + if (root->basedir == NULL) + { + menu_verbose ("No basedir available, using \"%s\" as-is\n", + node->content); + return g_strdup (node->content); + } + else + { + menu_verbose ("Using basedir \"%s\" filename \"%s\"\n", + root->basedir, node->content); + return g_build_filename (root->basedir, node->content, NULL); + } + } +} + +#define RETURN_IF_NO_PARENT(node) G_STMT_START { \ + if ((node)->parent == NULL) \ + { \ + g_warning ("To add siblings to a menu node, " \ + "it must not be the root node, " \ + "and must be linked in below some root node\n" \ + "node parent = %p and type = %d", \ + (node)->parent, (node)->type); \ + return; \ + } \ + } G_STMT_END + +#define RETURN_IF_HAS_ENTRY_DIRS(node) G_STMT_START { \ + if ((node)->type == MENU_LAYOUT_NODE_MENU && \ + (((MenuLayoutNodeMenu*)(node))->app_dirs != NULL || \ + ((MenuLayoutNodeMenu*)(node))->dir_dirs != NULL)) \ + { \ + g_warning ("node acquired ->app_dirs or ->dir_dirs " \ + "while not rooted in a tree\n"); \ + return; \ + } \ + } G_STMT_END \ + +void +menu_layout_node_insert_before (MenuLayoutNode *node, + MenuLayoutNode *new_sibling) +{ + g_return_if_fail (new_sibling != NULL); + g_return_if_fail (new_sibling->parent == NULL); + + RETURN_IF_NO_PARENT (node); + RETURN_IF_HAS_ENTRY_DIRS (new_sibling); + + new_sibling->next = node; + new_sibling->prev = node->prev; + + node->prev = new_sibling; + new_sibling->prev->next = new_sibling; + + new_sibling->parent = node->parent; + + if (node == node->parent->children) + node->parent->children = new_sibling; + + menu_layout_node_ref (new_sibling); +} + +void +menu_layout_node_insert_after (MenuLayoutNode *node, + MenuLayoutNode *new_sibling) +{ + g_return_if_fail (new_sibling != NULL); + g_return_if_fail (new_sibling->parent == NULL); + + RETURN_IF_NO_PARENT (node); + RETURN_IF_HAS_ENTRY_DIRS (new_sibling); + + new_sibling->prev = node; + new_sibling->next = node->next; + + node->next = new_sibling; + new_sibling->next->prev = new_sibling; + + new_sibling->parent = node->parent; + + menu_layout_node_ref (new_sibling); +} + +void +menu_layout_node_prepend_child (MenuLayoutNode *parent, + MenuLayoutNode *new_child) +{ + RETURN_IF_HAS_ENTRY_DIRS (new_child); + + if (parent->children) + { + menu_layout_node_insert_before (parent->children, new_child); + } + else + { + parent->children = menu_layout_node_ref (new_child); + new_child->parent = parent; + } +} + +void +menu_layout_node_append_child (MenuLayoutNode *parent, + MenuLayoutNode *new_child) +{ + RETURN_IF_HAS_ENTRY_DIRS (new_child); + + if (parent->children) + { + menu_layout_node_insert_after (parent->children->prev, new_child); + } + else + { + parent->children = menu_layout_node_ref (new_child); + new_child->parent = parent; + } +} + +void +menu_layout_node_unlink (MenuLayoutNode *node) +{ + g_return_if_fail (node != NULL); + g_return_if_fail (node->parent != NULL); + + menu_layout_node_steal (node); + menu_layout_node_unref (node); +} + +static void +recursive_clean_entry_directory_lists (MenuLayoutNode *node, + gboolean apps) +{ + EntryDirectoryList **dirs; + MenuLayoutNodeMenu *nm; + MenuLayoutNode *iter; + + if (node->type != MENU_LAYOUT_NODE_MENU) + return; + + nm = (MenuLayoutNodeMenu *) node; + + dirs = apps ? &nm->app_dirs : &nm->dir_dirs; + + if (*dirs == NULL || entry_directory_list_get_length (*dirs) == 0) + return; /* child menus continue to have valid lists */ + + remove_entry_directory_list (nm, dirs); + + iter = node->children; + while (iter != NULL) + { + if (iter->type == MENU_LAYOUT_NODE_MENU) + recursive_clean_entry_directory_lists (iter, apps); + + iter = node_next (iter); + } +} + +void +menu_layout_node_steal (MenuLayoutNode *node) +{ + g_return_if_fail (node != NULL); + g_return_if_fail (node->parent != NULL); + + switch (node->type) + { + case MENU_LAYOUT_NODE_NAME: + { + MenuLayoutNodeMenu *nm = (MenuLayoutNodeMenu *) node->parent; + + if (nm->name_node == node) + { + menu_layout_node_unref (nm->name_node); + nm->name_node = NULL; + } + } + break; + + case MENU_LAYOUT_NODE_APP_DIR: + recursive_clean_entry_directory_lists (node->parent, TRUE); + break; + + case MENU_LAYOUT_NODE_DIRECTORY_DIR: + recursive_clean_entry_directory_lists (node->parent, FALSE); + break; + + default: + break; + } + + if (node->parent && node->parent->children == node) + { + if (node->next != node) + node->parent->children = node->next; + else + node->parent->children = NULL; + } + + /* these are no-ops for length-one node lists */ + node->prev->next = node->next; + node->next->prev = node->prev; + + node->parent = NULL; + + /* point to ourselves, now we're length one */ + node->next = node; + node->prev = node; +} + +MenuLayoutNodeType +menu_layout_node_get_type (MenuLayoutNode *node) +{ + return node->type; +} + +const char * +menu_layout_node_get_content (MenuLayoutNode *node) +{ + return node->content; +} + +void +menu_layout_node_set_content (MenuLayoutNode *node, + const char *content) +{ + if (node->content == content) + return; + + g_free (node->content); + node->content = g_strdup (content); +} + +const char * +menu_layout_node_root_get_name (MenuLayoutNode *node) +{ + MenuLayoutNodeRoot *nr; + + g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_ROOT, NULL); + + nr = (MenuLayoutNodeRoot*) node; + + return nr->name; +} + +const char * +menu_layout_node_root_get_basedir (MenuLayoutNode *node) +{ + MenuLayoutNodeRoot *nr; + + g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_ROOT, NULL); + + nr = (MenuLayoutNodeRoot*) node; + + return nr->basedir; +} + +const char * +menu_layout_node_menu_get_name (MenuLayoutNode *node) +{ + MenuLayoutNodeMenu *nm; + + g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_MENU, NULL); + + nm = (MenuLayoutNodeMenu*) node; + + if (nm->name_node == NULL) + { + MenuLayoutNode *iter; + + iter = node->children; + while (iter != NULL) + { + if (iter->type == MENU_LAYOUT_NODE_NAME) + { + nm->name_node = menu_layout_node_ref (iter); + break; + } + + iter = node_next (iter); + } + } + + if (nm->name_node == NULL) + return NULL; + + return menu_layout_node_get_content (nm->name_node); +} + +static void +ensure_dir_lists (MenuLayoutNodeMenu *nm) +{ + MenuLayoutNode *node; + MenuLayoutNode *iter; + EntryDirectoryList *app_dirs; + EntryDirectoryList *dir_dirs; + + node = (MenuLayoutNode *) nm; + + if (nm->app_dirs && nm->dir_dirs) + return; + + app_dirs = NULL; + dir_dirs = NULL; + + if (nm->app_dirs == NULL) + { + app_dirs = entry_directory_list_new (); + + if (node->parent && node->parent->type == MENU_LAYOUT_NODE_MENU) + { + EntryDirectoryList *dirs; + + if ((dirs = menu_layout_node_menu_get_app_dirs (node->parent))) + entry_directory_list_append_list (app_dirs, dirs); + } + } + + if (nm->dir_dirs == NULL) + { + dir_dirs = entry_directory_list_new (); + + if (node->parent && node->parent->type == MENU_LAYOUT_NODE_MENU) + { + EntryDirectoryList *dirs; + + if ((dirs = menu_layout_node_menu_get_directory_dirs (node->parent))) + entry_directory_list_append_list (dir_dirs, dirs); + } + } + + iter = node->children; + while (iter != NULL) + { + EntryDirectory *ed; + + if (app_dirs != NULL && iter->type == MENU_LAYOUT_NODE_APP_DIR) + { + char *path; + + path = menu_layout_node_get_content_as_path (iter); + + ed = entry_directory_new (DESKTOP_ENTRY_DESKTOP, path); + if (ed != NULL) + { + entry_directory_list_prepend (app_dirs, ed); + entry_directory_unref (ed); + } + + g_free (path); + } + + if (dir_dirs != NULL && iter->type == MENU_LAYOUT_NODE_DIRECTORY_DIR) + { + char *path; + + path = menu_layout_node_get_content_as_path (iter); + + ed = entry_directory_new (DESKTOP_ENTRY_DIRECTORY, path); + if (ed != NULL) + { + entry_directory_list_prepend (dir_dirs, ed); + entry_directory_unref (ed); + } + + g_free (path); + } + + if (iter->type == MENU_LAYOUT_NODE_LEGACY_DIR) + { + MenuLayoutNodeLegacyDir *legacy = (MenuLayoutNodeLegacyDir *) iter; + char *path; + + path = menu_layout_node_get_content_as_path (iter); + + if (app_dirs != NULL) /* we're loading app dirs */ + { + ed = entry_directory_new_legacy (DESKTOP_ENTRY_DESKTOP, + path, + legacy->prefix); + if (ed != NULL) + { + entry_directory_list_prepend (app_dirs, ed); + entry_directory_unref (ed); + } + } + + if (dir_dirs != NULL) /* we're loading dir dirs */ + { + ed = entry_directory_new_legacy (DESKTOP_ENTRY_DIRECTORY, + path, + legacy->prefix); + if (ed != NULL) + { + entry_directory_list_prepend (dir_dirs, ed); + entry_directory_unref (ed); + } + } + + g_free (path); + } + + iter = node_next (iter); + } + + if (app_dirs) + { + g_assert (nm->app_dirs == NULL); + + nm->app_dirs = app_dirs; + entry_directory_list_add_monitors (nm->app_dirs, + (EntryDirectoryChangedFunc) handle_entry_directory_changed, + nm); + } + + if (dir_dirs) + { + g_assert (nm->dir_dirs == NULL); + + nm->dir_dirs = dir_dirs; + entry_directory_list_add_monitors (nm->dir_dirs, + (EntryDirectoryChangedFunc) handle_entry_directory_changed, + nm); + } +} + +EntryDirectoryList * +menu_layout_node_menu_get_app_dirs (MenuLayoutNode *node) +{ + MenuLayoutNodeMenu *nm; + + g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_MENU, NULL); + + nm = (MenuLayoutNodeMenu *) node; + + ensure_dir_lists (nm); + + return nm->app_dirs; +} + +EntryDirectoryList * +menu_layout_node_menu_get_directory_dirs (MenuLayoutNode *node) +{ + MenuLayoutNodeMenu *nm; + + g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_MENU, NULL); + + nm = (MenuLayoutNodeMenu *) node; + + ensure_dir_lists (nm); + + return nm->dir_dirs; +} + +const char * +menu_layout_node_move_get_old (MenuLayoutNode *node) +{ + MenuLayoutNode *iter; + + iter = node->children; + while (iter != NULL) + { + if (iter->type == MENU_LAYOUT_NODE_OLD) + return iter->content; + + iter = node_next (iter); + } + + return NULL; +} + +const char * +menu_layout_node_move_get_new (MenuLayoutNode *node) +{ + MenuLayoutNode *iter; + + iter = node->children; + while (iter != NULL) + { + if (iter->type == MENU_LAYOUT_NODE_NEW) + return iter->content; + + iter = node_next (iter); + } + + return NULL; +} + +const char * +menu_layout_node_legacy_dir_get_prefix (MenuLayoutNode *node) +{ + MenuLayoutNodeLegacyDir *legacy; + + g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_LEGACY_DIR, NULL); + + legacy = (MenuLayoutNodeLegacyDir *) node; + + return legacy->prefix; +} + +void +menu_layout_node_legacy_dir_set_prefix (MenuLayoutNode *node, + const char *prefix) +{ + MenuLayoutNodeLegacyDir *legacy; + + g_return_if_fail (node->type == MENU_LAYOUT_NODE_LEGACY_DIR); + + legacy = (MenuLayoutNodeLegacyDir *) node; + + g_free (legacy->prefix); + legacy->prefix = g_strdup (prefix); +} + +MenuMergeFileType +menu_layout_node_merge_file_get_type (MenuLayoutNode *node) +{ + MenuLayoutNodeMergeFile *merge_file; + + g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_MERGE_FILE, FALSE); + + merge_file = (MenuLayoutNodeMergeFile *) node; + + return merge_file->type; +} + +void +menu_layout_node_merge_file_set_type (MenuLayoutNode *node, + MenuMergeFileType type) +{ + MenuLayoutNodeMergeFile *merge_file; + + g_return_if_fail (node->type == MENU_LAYOUT_NODE_MERGE_FILE); + + merge_file = (MenuLayoutNodeMergeFile *) node; + + merge_file->type = type; +} + +MenuLayoutMergeType +menu_layout_node_merge_get_type (MenuLayoutNode *node) +{ + MenuLayoutNodeMerge *merge; + + g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_MERGE, 0); + + merge = (MenuLayoutNodeMerge *) node; + + return merge->merge_type; +} + +static void +menu_layout_node_merge_set_type (MenuLayoutNode *node, + const char *merge_type) +{ + MenuLayoutNodeMerge *merge; + + g_return_if_fail (node->type == MENU_LAYOUT_NODE_MERGE); + + merge = (MenuLayoutNodeMerge *) node; + + merge->merge_type = MENU_LAYOUT_MERGE_NONE; + + if (strcmp (merge_type, "menus") == 0) + { + merge->merge_type = MENU_LAYOUT_MERGE_MENUS; + } + else if (strcmp (merge_type, "files") == 0) + { + merge->merge_type = MENU_LAYOUT_MERGE_FILES; + } + else if (strcmp (merge_type, "all") == 0) + { + merge->merge_type = MENU_LAYOUT_MERGE_ALL; + } +} + +void +menu_layout_node_default_layout_get_values (MenuLayoutNode *node, + MenuLayoutValues *values) +{ + MenuLayoutNodeDefaultLayout *default_layout; + + g_return_if_fail (node->type == MENU_LAYOUT_NODE_DEFAULT_LAYOUT); + g_return_if_fail (values != NULL); + + default_layout = (MenuLayoutNodeDefaultLayout *) node; + + *values = default_layout->layout_values; +} + +void +menu_layout_node_menuname_get_values (MenuLayoutNode *node, + MenuLayoutValues *values) +{ + MenuLayoutNodeMenuname *menuname; + + g_return_if_fail (node->type == MENU_LAYOUT_NODE_MENUNAME); + g_return_if_fail (values != NULL); + + menuname = (MenuLayoutNodeMenuname *) node; + + *values = menuname->layout_values; +} + +static void +menu_layout_values_set (MenuLayoutValues *values, + const char *show_empty, + const char *inline_menus, + const char *inline_limit, + const char *inline_header, + const char *inline_alias) +{ + values->mask = MENU_LAYOUT_VALUES_NONE; + values->show_empty = FALSE; + values->inline_menus = FALSE; + values->inline_limit = 4; + values->inline_header = FALSE; + values->inline_alias = FALSE; + + if (show_empty != NULL) + { + values->show_empty = strcmp (show_empty, "true") == 0; + values->mask |= MENU_LAYOUT_VALUES_SHOW_EMPTY; + } + + if (inline_menus != NULL) + { + values->inline_menus = strcmp (inline_menus, "true") == 0; + values->mask |= MENU_LAYOUT_VALUES_INLINE_MENUS; + } + + if (inline_limit != NULL) + { + char *end; + int limit; + + limit = strtol (inline_limit, &end, 10); + if (*end == '\0') + { + values->inline_limit = limit; + values->mask |= MENU_LAYOUT_VALUES_INLINE_LIMIT; + } + } + + if (inline_header != NULL) + { + values->inline_header = strcmp (inline_header, "true") == 0; + values->mask |= MENU_LAYOUT_VALUES_INLINE_HEADER; + } + + if (inline_alias != NULL) + { + values->inline_alias = strcmp (inline_alias, "true") == 0; + values->mask |= MENU_LAYOUT_VALUES_INLINE_ALIAS; + } +} + +static void +menu_layout_node_default_layout_set_values (MenuLayoutNode *node, + const char *show_empty, + const char *inline_menus, + const char *inline_limit, + const char *inline_header, + const char *inline_alias) +{ + MenuLayoutNodeDefaultLayout *default_layout; + + g_return_if_fail (node->type == MENU_LAYOUT_NODE_DEFAULT_LAYOUT); + + default_layout = (MenuLayoutNodeDefaultLayout *) node; + + menu_layout_values_set (&default_layout->layout_values, + show_empty, + inline_menus, + inline_limit, + inline_header, + inline_alias); +} + +static void +menu_layout_node_menuname_set_values (MenuLayoutNode *node, + const char *show_empty, + const char *inline_menus, + const char *inline_limit, + const char *inline_header, + const char *inline_alias) +{ + MenuLayoutNodeMenuname *menuname; + + g_return_if_fail (node->type == MENU_LAYOUT_NODE_MENUNAME); + + menuname = (MenuLayoutNodeMenuname *) node; + + menu_layout_values_set (&menuname->layout_values, + show_empty, + inline_menus, + inline_limit, + inline_header, + inline_alias); +} + +void +menu_layout_node_root_add_entries_monitor (MenuLayoutNode *node, + MenuLayoutNodeEntriesChangedFunc callback, + gpointer user_data) +{ + MenuLayoutNodeEntriesMonitor *monitor; + MenuLayoutNodeRoot *nr; + GSList *tmp; + + g_return_if_fail (node->type == MENU_LAYOUT_NODE_ROOT); + + nr = (MenuLayoutNodeRoot *) node; + + tmp = nr->monitors; + while (tmp != NULL) + { + monitor = tmp->data; + + if (monitor->callback == callback && + monitor->user_data == user_data) + break; + + tmp = tmp->next; + } + + if (tmp == NULL) + { + monitor = g_new0 (MenuLayoutNodeEntriesMonitor, 1); + monitor->callback = callback; + monitor->user_data = user_data; + + nr->monitors = g_slist_append (nr->monitors, monitor); + } +} + +void +menu_layout_node_root_remove_entries_monitor (MenuLayoutNode *node, + MenuLayoutNodeEntriesChangedFunc callback, + gpointer user_data) +{ + MenuLayoutNodeRoot *nr; + GSList *tmp; + + g_return_if_fail (node->type == MENU_LAYOUT_NODE_ROOT); + + nr = (MenuLayoutNodeRoot *) node; + + tmp = nr->monitors; + while (tmp != NULL) + { + MenuLayoutNodeEntriesMonitor *monitor = tmp->data; + GSList *next = tmp->next; + + if (monitor->callback == callback && + monitor->user_data == user_data) + { + nr->monitors = g_slist_delete_link (nr->monitors, tmp); + g_free (monitor); + } + + tmp = next; + } +} + + +/* + * Menu file parsing + */ + +typedef struct +{ + MenuLayoutNode *root; + MenuLayoutNode *stack_top; +} MenuParser; + +static void set_error (GError **err, + GMarkupParseContext *context, + int error_domain, + int error_code, + const char *format, + ...) G_GNUC_PRINTF (5, 6); + +static void add_context_to_error (GError **err, + GMarkupParseContext *context); + +static void start_element_handler (GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + gpointer user_data, + GError **error); +static void end_element_handler (GMarkupParseContext *context, + const char *element_name, + gpointer user_data, + GError **error); +static void text_handler (GMarkupParseContext *context, + const char *text, + gsize text_len, + gpointer user_data, + GError **error); +static void passthrough_handler (GMarkupParseContext *context, + const char *passthrough_text, + gsize text_len, + gpointer user_data, + GError **error); + + +static GMarkupParser menu_funcs = { + start_element_handler, + end_element_handler, + text_handler, + passthrough_handler, + NULL +}; + +static void +set_error (GError **err, + GMarkupParseContext *context, + int error_domain, + int error_code, + const char *format, + ...) +{ + int line, ch; + va_list args; + char *str; + + g_markup_parse_context_get_position (context, &line, &ch); + + va_start (args, format); + str = g_strdup_vprintf (format, args); + va_end (args); + + g_set_error (err, error_domain, error_code, + "Line %d character %d: %s", + line, ch, str); + + g_free (str); +} + +static void +add_context_to_error (GError **err, + GMarkupParseContext *context) +{ + int line, ch; + char *str; + + if (err == NULL || *err == NULL) + return; + + g_markup_parse_context_get_position (context, &line, &ch); + + str = g_strdup_printf ("Line %d character %d: %s", + line, ch, (*err)->message); + g_free ((*err)->message); + (*err)->message = str; +} + +#define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0) + +typedef struct +{ + const char *name; + const char **retloc; +} LocateAttr; + +static gboolean +locate_attributes (GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + GError **error, + const char *first_attribute_name, + const char **first_attribute_retloc, + ...) +{ +#define MAX_ATTRS 24 + LocateAttr attrs[MAX_ATTRS]; + int n_attrs; + va_list args; + const char *name; + const char **retloc; + gboolean retval; + int i; + + g_return_val_if_fail (first_attribute_name != NULL, FALSE); + g_return_val_if_fail (first_attribute_retloc != NULL, FALSE); + + retval = TRUE; + + n_attrs = 1; + attrs[0].name = first_attribute_name; + attrs[0].retloc = first_attribute_retloc; + *first_attribute_retloc = NULL; + + va_start (args, first_attribute_retloc); + + name = va_arg (args, const char *); + retloc = va_arg (args, const char **); + + while (name != NULL) + { + g_return_val_if_fail (retloc != NULL, FALSE); + + g_assert (n_attrs < MAX_ATTRS); + + attrs[n_attrs].name = name; + attrs[n_attrs].retloc = retloc; + n_attrs += 1; + *retloc = NULL; + + name = va_arg (args, const char *); + retloc = va_arg (args, const char **); + } + + va_end (args); + + i = 0; + while (attribute_names[i]) + { + int j; + + j = 0; + while (j < n_attrs) + { + if (strcmp (attrs[j].name, attribute_names[i]) == 0) + { + retloc = attrs[j].retloc; + + if (*retloc != NULL) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + "Attribute \"%s\" repeated twice on the same <%s> element", + attrs[j].name, element_name); + retval = FALSE; + goto out; + } + + *retloc = attribute_values[i]; + break; + } + + ++j; + } + + if (j == n_attrs) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + "Attribute \"%s\" is invalid on <%s> element in this context", + attribute_names[i], element_name); + retval = FALSE; + goto out; + } + + ++i; + } + + out: + return retval; + +#undef MAX_ATTRS +} + +static gboolean +check_no_attributes (GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + GError **error) +{ + if (attribute_names[0] != NULL) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + "Attribute \"%s\" is invalid on <%s> element in this context", + attribute_names[0], element_name); + return FALSE; + } + + return TRUE; +} + +static int +has_child_of_type (MenuLayoutNode *node, + MenuLayoutNodeType type) +{ + MenuLayoutNode *iter; + + iter = node->children; + while (iter) + { + if (iter->type == type) + return TRUE; + + iter = node_next (iter); + } + + return FALSE; +} + +static void +push_node (MenuParser *parser, + MenuLayoutNodeType type) +{ + MenuLayoutNode *node; + + node = menu_layout_node_new (type); + menu_layout_node_append_child (parser->stack_top, node); + menu_layout_node_unref (node); + + parser->stack_top = node; +} + +static void +start_menu_element (MenuParser *parser, + GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + GError **error) +{ + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + if (!(parser->stack_top->type == MENU_LAYOUT_NODE_ROOT || + parser->stack_top->type == MENU_LAYOUT_NODE_MENU)) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + " element can only appear below other elements or at toplevel\n"); + } + else + { + push_node (parser, MENU_LAYOUT_NODE_MENU); + } +} + +static void +start_menu_child_element (MenuParser *parser, + GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + GError **error) +{ + if (ELEMENT_IS ("LegacyDir")) + { + const char *prefix; + + push_node (parser, MENU_LAYOUT_NODE_LEGACY_DIR); + + if (!locate_attributes (context, element_name, + attribute_names, attribute_values, + error, + "prefix", &prefix, + NULL)) + return; + + menu_layout_node_legacy_dir_set_prefix (parser->stack_top, prefix); + } + else if (ELEMENT_IS ("MergeFile")) + { + const char *type; + + push_node (parser, MENU_LAYOUT_NODE_MERGE_FILE); + + if (!locate_attributes (context, element_name, + attribute_names, attribute_values, + error, + "type", &type, + NULL)) + return; + + if (type != NULL && strcmp (type, "parent") == 0) + { + menu_layout_node_merge_file_set_type (parser->stack_top, + MENU_MERGE_FILE_TYPE_PARENT); + } + } + else if (ELEMENT_IS ("DefaultLayout")) + { + const char *show_empty; + const char *inline_menus; + const char *inline_limit; + const char *inline_header; + const char *inline_alias; + + push_node (parser, MENU_LAYOUT_NODE_DEFAULT_LAYOUT); + + locate_attributes (context, element_name, + attribute_names, attribute_values, + error, + "show_empty", &show_empty, + "inline", &inline_menus, + "inline_limit", &inline_limit, + "inline_header", &inline_header, + "inline_alias", &inline_alias, + NULL); + + menu_layout_node_default_layout_set_values (parser->stack_top, + show_empty, + inline_menus, + inline_limit, + inline_header, + inline_alias); + } + else + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + if (ELEMENT_IS ("AppDir")) + { + push_node (parser, MENU_LAYOUT_NODE_APP_DIR); + } + else if (ELEMENT_IS ("DefaultAppDirs")) + { + push_node (parser, MENU_LAYOUT_NODE_DEFAULT_APP_DIRS); + } + else if (ELEMENT_IS ("DirectoryDir")) + { + push_node (parser, MENU_LAYOUT_NODE_DIRECTORY_DIR); + } + else if (ELEMENT_IS ("DefaultDirectoryDirs")) + { + push_node (parser, MENU_LAYOUT_NODE_DEFAULT_DIRECTORY_DIRS); + } + else if (ELEMENT_IS ("DefaultMergeDirs")) + { + push_node (parser, MENU_LAYOUT_NODE_DEFAULT_MERGE_DIRS); + } + else if (ELEMENT_IS ("Name")) + { + if (has_child_of_type (parser->stack_top, MENU_LAYOUT_NODE_NAME)) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + "Multiple elements in a element is not allowed\n"); + return; + } + + push_node (parser, MENU_LAYOUT_NODE_NAME); + } + else if (ELEMENT_IS ("Directory")) + { + push_node (parser, MENU_LAYOUT_NODE_DIRECTORY); + } + else if (ELEMENT_IS ("OnlyUnallocated")) + { + push_node (parser, MENU_LAYOUT_NODE_ONLY_UNALLOCATED); + } + else if (ELEMENT_IS ("NotOnlyUnallocated")) + { + push_node (parser, MENU_LAYOUT_NODE_NOT_ONLY_UNALLOCATED); + } + else if (ELEMENT_IS ("Include")) + { + push_node (parser, MENU_LAYOUT_NODE_INCLUDE); + } + else if (ELEMENT_IS ("Exclude")) + { + push_node (parser, MENU_LAYOUT_NODE_EXCLUDE); + } + else if (ELEMENT_IS ("MergeDir")) + { + push_node (parser, MENU_LAYOUT_NODE_MERGE_DIR); + } + else if (ELEMENT_IS ("KDELegacyDirs")) + { + push_node (parser, MENU_LAYOUT_NODE_KDE_LEGACY_DIRS); + } + else if (ELEMENT_IS ("Move")) + { + push_node (parser, MENU_LAYOUT_NODE_MOVE); + } + else if (ELEMENT_IS ("Deleted")) + { + push_node (parser, MENU_LAYOUT_NODE_DELETED); + + } + else if (ELEMENT_IS ("NotDeleted")) + { + push_node (parser, MENU_LAYOUT_NODE_NOT_DELETED); + } + else if (ELEMENT_IS ("Layout")) + { + push_node (parser, MENU_LAYOUT_NODE_LAYOUT); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Element <%s> may not appear below <%s>\n", + element_name, "Menu"); + } + } +} + +static void +start_matching_rule_element (MenuParser *parser, + GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + GError **error) +{ + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + + if (ELEMENT_IS ("Filename")) + { + push_node (parser, MENU_LAYOUT_NODE_FILENAME); + } + else if (ELEMENT_IS ("Category")) + { + push_node (parser, MENU_LAYOUT_NODE_CATEGORY); + } + else if (ELEMENT_IS ("All")) + { + push_node (parser, MENU_LAYOUT_NODE_ALL); + } + else if (ELEMENT_IS ("And")) + { + push_node (parser, MENU_LAYOUT_NODE_AND); + } + else if (ELEMENT_IS ("Or")) + { + push_node (parser, MENU_LAYOUT_NODE_OR); + } + else if (ELEMENT_IS ("Not")) + { + push_node (parser, MENU_LAYOUT_NODE_NOT); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Element <%s> may not appear in this context\n", + element_name); + } +} + +static void +start_move_child_element (MenuParser *parser, + GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + GError **error) +{ + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + if (ELEMENT_IS ("Old")) + { + push_node (parser, MENU_LAYOUT_NODE_OLD); + } + else if (ELEMENT_IS ("New")) + { + push_node (parser, MENU_LAYOUT_NODE_NEW); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Element <%s> may not appear below <%s>\n", + element_name, "Move"); + } +} + +static void +start_layout_child_element (MenuParser *parser, + GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + GError **error) +{ + if (ELEMENT_IS ("Menuname")) + { + const char *show_empty; + const char *inline_menus; + const char *inline_limit; + const char *inline_header; + const char *inline_alias; + + push_node (parser, MENU_LAYOUT_NODE_MENUNAME); + + locate_attributes (context, element_name, + attribute_names, attribute_values, + error, + "show_empty", &show_empty, + "inline", &inline_menus, + "inline_limit", &inline_limit, + "inline_header", &inline_header, + "inline_alias", &inline_alias, + NULL); + + menu_layout_node_menuname_set_values (parser->stack_top, + show_empty, + inline_menus, + inline_limit, + inline_header, + inline_alias); + } + else if (ELEMENT_IS ("Merge")) + { + const char *type; + + push_node (parser, MENU_LAYOUT_NODE_MERGE); + + locate_attributes (context, element_name, + attribute_names, attribute_values, + error, + "type", &type, + NULL); + + menu_layout_node_merge_set_type (parser->stack_top, type); + } + else + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + if (ELEMENT_IS ("Filename")) + { + push_node (parser, MENU_LAYOUT_NODE_FILENAME); + } + else if (ELEMENT_IS ("Separator")) + { + push_node (parser, MENU_LAYOUT_NODE_SEPARATOR); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Element <%s> may not appear below <%s>\n", + element_name, "Move"); + } + } +} + +static void +start_element_handler (GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + gpointer user_data, + GError **error) +{ + MenuParser *parser = user_data; + + if (ELEMENT_IS ("Menu")) + { + if (parser->stack_top == parser->root && + has_child_of_type (parser->root, MENU_LAYOUT_NODE_MENU)) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + "Multiple root elements in menu file, only one toplevel is allowed\n"); + return; + } + + start_menu_element (parser, context, element_name, + attribute_names, attribute_values, + error); + } + else if (parser->stack_top == parser->root) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + "Root element in a menu file must be , not <%s>\n", + element_name); + } + else if (parser->stack_top->type == MENU_LAYOUT_NODE_MENU) + { + start_menu_child_element (parser, context, element_name, + attribute_names, attribute_values, + error); + } + else if (parser->stack_top->type == MENU_LAYOUT_NODE_INCLUDE || + parser->stack_top->type == MENU_LAYOUT_NODE_EXCLUDE || + parser->stack_top->type == MENU_LAYOUT_NODE_AND || + parser->stack_top->type == MENU_LAYOUT_NODE_OR || + parser->stack_top->type == MENU_LAYOUT_NODE_NOT) + { + start_matching_rule_element (parser, context, element_name, + attribute_names, attribute_values, + error); + } + else if (parser->stack_top->type == MENU_LAYOUT_NODE_MOVE) + { + start_move_child_element (parser, context, element_name, + attribute_names, attribute_values, + error); + } + else if (parser->stack_top->type == MENU_LAYOUT_NODE_LAYOUT || + parser->stack_top->type == MENU_LAYOUT_NODE_DEFAULT_LAYOUT) + { + start_layout_child_element (parser, context, element_name, + attribute_names, attribute_values, + error); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Element <%s> may not appear in this context\n", + element_name); + } + + add_context_to_error (error, context); +} + +/* we want to make sure that the or is either empty, + * or contain one of type "all", or contain one of type "menus" + * and one of type "files". If this is not the case, we try to clean up + * things: + * + if there is at least one of type "all", then we only keep the + * last of type "all" and remove all others + * + if there is no with type "all", we keep only the last of + * type "menus" and the last of type "files". If there's no + * of type "menus" we append one, and then if there's no of type + * "files", we append one. (So menus are before files) + */ +static gboolean +fixup_layout_node (GMarkupParseContext *context, + MenuParser *parser, + MenuLayoutNode *node, + GError **error) +{ + MenuLayoutNode *child; + MenuLayoutNode *last_all; + MenuLayoutNode *last_menus; + MenuLayoutNode *last_files; + int n_all; + int n_menus; + int n_files; + + if (!node->children) + { + return TRUE; + } + + last_all = NULL; + last_menus = NULL; + last_files = NULL; + n_all = 0; + n_menus = 0; + n_files = 0; + + child = node->children; + while (child != NULL) + { + switch (child->type) + { + case MENU_LAYOUT_NODE_MERGE: + switch (menu_layout_node_merge_get_type (child)) + { + case MENU_LAYOUT_MERGE_NONE: + break; + + case MENU_LAYOUT_MERGE_MENUS: + last_menus = child; + n_menus++; + break; + + case MENU_LAYOUT_MERGE_FILES: + last_files = child; + n_files++; + break; + + case MENU_LAYOUT_MERGE_ALL: + last_all = child; + n_all++; + break; + + default: + g_assert_not_reached (); + break; + } + break; + + default: + break; + } + + child = node_next (child); + } + + if ((n_all == 1 && n_menus == 0 && n_files == 0) || + (n_all == 0 && n_menus == 1 && n_files == 1)) + { + return TRUE; + } + else if (n_all > 1 || n_menus > 1 || n_files > 1 || + (n_all == 1 && (n_menus != 0 || n_files != 0))) + { + child = node->children; + while (child != NULL) + { + MenuLayoutNode *next; + + next = node_next (child); + + switch (child->type) + { + case MENU_LAYOUT_NODE_MERGE: + switch (menu_layout_node_merge_get_type (child)) + { + case MENU_LAYOUT_MERGE_NONE: + break; + + case MENU_LAYOUT_MERGE_MENUS: + if (n_all || last_menus != child) + { + menu_verbose ("removing duplicated merge menus element\n"); + menu_layout_node_unlink (child); + } + break; + + case MENU_LAYOUT_MERGE_FILES: + if (n_all || last_files != child) + { + menu_verbose ("removing duplicated merge files element\n"); + menu_layout_node_unlink (child); + } + break; + + case MENU_LAYOUT_MERGE_ALL: + if (last_all != child) + { + menu_verbose ("removing duplicated merge all element\n"); + menu_layout_node_unlink (child); + } + break; + + default: + g_assert_not_reached (); + break; + } + break; + + default: + break; + } + + child = next; + } + } + + if (n_all == 0 && n_menus == 0) + { + last_menus = menu_layout_node_new (MENU_LAYOUT_NODE_MERGE); + menu_layout_node_merge_set_type (last_menus, "menus"); + menu_verbose ("appending missing merge menus element\n"); + menu_layout_node_append_child (node, last_menus); + } + + if (n_all == 0 && n_files == 0) + { + last_files = menu_layout_node_new (MENU_LAYOUT_NODE_MERGE); + menu_layout_node_merge_set_type (last_files, "files"); + menu_verbose ("appending missing merge files element\n"); + menu_layout_node_append_child (node, last_files); + } + + return TRUE; +} + +/* we want to a) check that we have old-new pairs and b) canonicalize + * such that each has exactly one old-new pair + */ +static gboolean +fixup_move_node (GMarkupParseContext *context, + MenuParser *parser, + MenuLayoutNode *node, + GError **error) +{ + MenuLayoutNode *child; + int n_old; + int n_new; + + n_old = 0; + n_new = 0; + + child = node->children; + while (child != NULL) + { + switch (child->type) + { + case MENU_LAYOUT_NODE_OLD: + if (n_new != n_old) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + "/ elements not paired properly\n"); + return FALSE; + } + + n_old += 1; + + break; + + case MENU_LAYOUT_NODE_NEW: + n_new += 1; + + if (n_new != n_old) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + "/ elements not paired properly\n"); + return FALSE; + } + + break; + + default: + g_assert_not_reached (); + break; + } + + child = node_next (child); + } + + if (n_new == 0 || n_old == 0) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + "/ elements missing under \n"); + return FALSE; + } + + g_assert (n_new == n_old); + g_assert ((n_new + n_old) % 2 == 0); + + if (n_new > 1) + { + MenuLayoutNode *prev; + MenuLayoutNode *append_after; + + /* Need to split the into multiple */ + + n_old = 0; + n_new = 0; + prev = NULL; + append_after = node; + + child = node->children; + while (child != NULL) + { + MenuLayoutNode *next; + + next = node_next (child); + + switch (child->type) + { + case MENU_LAYOUT_NODE_OLD: + n_old += 1; + break; + + case MENU_LAYOUT_NODE_NEW: + n_new += 1; + break; + + default: + g_assert_not_reached (); + break; + } + + if (n_old == n_new && + n_old > 1) + { + /* Move the just-completed pair */ + MenuLayoutNode *new_move; + + g_assert (prev != NULL); + + new_move = menu_layout_node_new (MENU_LAYOUT_NODE_MOVE); + menu_verbose ("inserting new_move after append_after\n"); + menu_layout_node_insert_after (append_after, new_move); + append_after = new_move; + + menu_layout_node_steal (prev); + menu_layout_node_steal (child); + + menu_verbose ("appending prev to new_move\n"); + menu_layout_node_append_child (new_move, prev); + menu_verbose ("appending child to new_move\n"); + menu_layout_node_append_child (new_move, child); + + menu_verbose ("Created new move element old = %s new = %s\n", + menu_layout_node_move_get_old (new_move), + menu_layout_node_move_get_new (new_move)); + + menu_layout_node_unref (new_move); + menu_layout_node_unref (prev); + menu_layout_node_unref (child); + + prev = NULL; + } + else + { + prev = child; + } + + prev = child; + child = next; + } + } + + return TRUE; +} + +static void +end_element_handler (GMarkupParseContext *context, + const char *element_name, + gpointer user_data, + GError **error) +{ + MenuParser *parser = user_data; + + g_assert (parser->stack_top != NULL); + + switch (parser->stack_top->type) + { + case MENU_LAYOUT_NODE_APP_DIR: + case MENU_LAYOUT_NODE_DIRECTORY_DIR: + case MENU_LAYOUT_NODE_NAME: + case MENU_LAYOUT_NODE_DIRECTORY: + case MENU_LAYOUT_NODE_FILENAME: + case MENU_LAYOUT_NODE_CATEGORY: + case MENU_LAYOUT_NODE_MERGE_DIR: + case MENU_LAYOUT_NODE_LEGACY_DIR: + case MENU_LAYOUT_NODE_OLD: + case MENU_LAYOUT_NODE_NEW: + case MENU_LAYOUT_NODE_MENUNAME: + if (menu_layout_node_get_content (parser->stack_top) == NULL) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Element <%s> is required to contain text and was empty\n", + element_name); + goto out; + } + break; + + case MENU_LAYOUT_NODE_MENU: + if (!has_child_of_type (parser->stack_top, MENU_LAYOUT_NODE_NAME)) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + " elements are required to contain a element\n"); + goto out; + } + break; + + case MENU_LAYOUT_NODE_ROOT: + case MENU_LAYOUT_NODE_PASSTHROUGH: + case MENU_LAYOUT_NODE_DEFAULT_APP_DIRS: + case MENU_LAYOUT_NODE_DEFAULT_DIRECTORY_DIRS: + case MENU_LAYOUT_NODE_DEFAULT_MERGE_DIRS: + case MENU_LAYOUT_NODE_ONLY_UNALLOCATED: + case MENU_LAYOUT_NODE_NOT_ONLY_UNALLOCATED: + case MENU_LAYOUT_NODE_INCLUDE: + case MENU_LAYOUT_NODE_EXCLUDE: + case MENU_LAYOUT_NODE_ALL: + case MENU_LAYOUT_NODE_AND: + case MENU_LAYOUT_NODE_OR: + case MENU_LAYOUT_NODE_NOT: + case MENU_LAYOUT_NODE_KDE_LEGACY_DIRS: + case MENU_LAYOUT_NODE_DELETED: + case MENU_LAYOUT_NODE_NOT_DELETED: + case MENU_LAYOUT_NODE_SEPARATOR: + case MENU_LAYOUT_NODE_MERGE: + case MENU_LAYOUT_NODE_MERGE_FILE: + break; + + case MENU_LAYOUT_NODE_LAYOUT: + case MENU_LAYOUT_NODE_DEFAULT_LAYOUT: + if (!fixup_layout_node (context, parser, parser->stack_top, error)) + goto out; + break; + + case MENU_LAYOUT_NODE_MOVE: + if (!fixup_move_node (context, parser, parser->stack_top, error)) + goto out; + break; + } + + out: + parser->stack_top = parser->stack_top->parent; +} + +static gboolean +all_whitespace (const char *text, + int text_len) +{ + const char *p; + const char *end; + + p = text; + end = text + text_len; + + while (p != end) + { + if (!g_ascii_isspace (*p)) + return FALSE; + + p = g_utf8_next_char (p); + } + + return TRUE; +} + +static void +text_handler (GMarkupParseContext *context, + const char *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + MenuParser *parser = user_data; + + switch (parser->stack_top->type) + { + case MENU_LAYOUT_NODE_APP_DIR: + case MENU_LAYOUT_NODE_DIRECTORY_DIR: + case MENU_LAYOUT_NODE_NAME: + case MENU_LAYOUT_NODE_DIRECTORY: + case MENU_LAYOUT_NODE_FILENAME: + case MENU_LAYOUT_NODE_CATEGORY: + case MENU_LAYOUT_NODE_MERGE_FILE: + case MENU_LAYOUT_NODE_MERGE_DIR: + case MENU_LAYOUT_NODE_LEGACY_DIR: + case MENU_LAYOUT_NODE_OLD: + case MENU_LAYOUT_NODE_NEW: + case MENU_LAYOUT_NODE_MENUNAME: + g_assert (menu_layout_node_get_content (parser->stack_top) == NULL); + + menu_layout_node_set_content (parser->stack_top, text); + break; + + case MENU_LAYOUT_NODE_ROOT: + case MENU_LAYOUT_NODE_PASSTHROUGH: + case MENU_LAYOUT_NODE_MENU: + case MENU_LAYOUT_NODE_DEFAULT_APP_DIRS: + case MENU_LAYOUT_NODE_DEFAULT_DIRECTORY_DIRS: + case MENU_LAYOUT_NODE_DEFAULT_MERGE_DIRS: + case MENU_LAYOUT_NODE_ONLY_UNALLOCATED: + case MENU_LAYOUT_NODE_NOT_ONLY_UNALLOCATED: + case MENU_LAYOUT_NODE_INCLUDE: + case MENU_LAYOUT_NODE_EXCLUDE: + case MENU_LAYOUT_NODE_ALL: + case MENU_LAYOUT_NODE_AND: + case MENU_LAYOUT_NODE_OR: + case MENU_LAYOUT_NODE_NOT: + case MENU_LAYOUT_NODE_KDE_LEGACY_DIRS: + case MENU_LAYOUT_NODE_MOVE: + case MENU_LAYOUT_NODE_DELETED: + case MENU_LAYOUT_NODE_NOT_DELETED: + case MENU_LAYOUT_NODE_LAYOUT: + case MENU_LAYOUT_NODE_DEFAULT_LAYOUT: + case MENU_LAYOUT_NODE_SEPARATOR: + case MENU_LAYOUT_NODE_MERGE: + if (!all_whitespace (text, text_len)) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + "No text is allowed inside element <%s>", + g_markup_parse_context_get_element (context)); + } + break; + } + + add_context_to_error (error, context); +} + +static void +passthrough_handler (GMarkupParseContext *context, + const char *passthrough_text, + gsize text_len, + gpointer user_data, + GError **error) +{ + MenuParser *parser = user_data; + MenuLayoutNode *node; + + /* don't push passthrough on the stack, it's not an element */ + + node = menu_layout_node_new (MENU_LAYOUT_NODE_PASSTHROUGH); + menu_layout_node_set_content (node, passthrough_text); + + menu_layout_node_append_child (parser->stack_top, node); + menu_layout_node_unref (node); + + add_context_to_error (error, context); +} + +static void +menu_parser_init (MenuParser *parser) +{ + parser->root = menu_layout_node_new (MENU_LAYOUT_NODE_ROOT); + parser->stack_top = parser->root; +} + +static void +menu_parser_free (MenuParser *parser) +{ + if (parser->root) + menu_layout_node_unref (parser->root); +} + +MenuLayoutNode * +menu_layout_load (const char *filename, + const char *non_prefixed_basename, + GError **err) +{ + GMainContext *main_context; + GMarkupParseContext *context; + MenuLayoutNodeRoot *root; + MenuLayoutNode *retval; + MenuParser parser; + GError *error; + GString *str; + char *text; + char *s; + gsize length; + + text = NULL; + length = 0; + retval = NULL; + context = NULL; + + main_context = g_main_context_get_thread_default (); + + menu_verbose ("Loading \"%s\" from disk\n", filename); + + if (!g_file_get_contents (filename, + &text, + &length, + err)) + { + menu_verbose ("Failed to load \"%s\"\n", + filename); + return NULL; + } + + g_assert (text != NULL); + + menu_parser_init (&parser); + + root = (MenuLayoutNodeRoot *) parser.root; + + root->basedir = g_path_get_dirname (filename); + menu_verbose ("Set basedir \"%s\"\n", root->basedir); + + if (non_prefixed_basename) + s = g_strdup (non_prefixed_basename); + else + s = g_path_get_basename (filename); + str = g_string_new (s); + if (g_str_has_suffix (str->str, ".menu")) + g_string_truncate (str, str->len - strlen (".menu")); + + root->name = str->str; + menu_verbose ("Set menu name \"%s\"\n", root->name); + + g_string_free (str, FALSE); + g_free (s); + + context = g_markup_parse_context_new (&menu_funcs, 0, &parser, NULL); + + error = NULL; + if (!g_markup_parse_context_parse (context, + text, + length, + &error)) + goto out; + + error = NULL; + g_markup_parse_context_end_parse (context, &error); + + root->main_context = main_context ? g_main_context_ref (main_context) : NULL; + + out: + if (context) + g_markup_parse_context_free (context); + g_free (text); + + if (error) + { + menu_verbose ("Error \"%s\" loading \"%s\"\n", + error->message, filename); + g_propagate_error (err, error); + } + else if (has_child_of_type (parser.root, MENU_LAYOUT_NODE_MENU)) + { + menu_verbose ("File loaded OK\n"); + retval = parser.root; + parser.root = NULL; + } + else + { + menu_verbose ("Did not have a root element in file\n"); + g_set_error (err, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + "Menu file %s did not contain a root element", + filename); + } + + menu_parser_free (&parser); + + return retval; +} diff --git a/libmenu/menu-layout.h b/libmenu/menu-layout.h new file mode 100644 index 0000000..1a6af13 --- /dev/null +++ b/libmenu/menu-layout.h @@ -0,0 +1,178 @@ +/* Menu layout in-memory data structure (a custom "DOM tree") */ + +/* + * Copyright (C) 2002 - 2004 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __MENU_LAYOUT_H__ +#define __MENU_LAYOUT_H__ + +#include + +#include "entry-directories.h" + +G_BEGIN_DECLS + +typedef struct MenuLayoutNode MenuLayoutNode; + +typedef enum +{ + MENU_LAYOUT_NODE_ROOT, + MENU_LAYOUT_NODE_PASSTHROUGH, + MENU_LAYOUT_NODE_MENU, + MENU_LAYOUT_NODE_APP_DIR, + MENU_LAYOUT_NODE_DEFAULT_APP_DIRS, + MENU_LAYOUT_NODE_DIRECTORY_DIR, + MENU_LAYOUT_NODE_DEFAULT_DIRECTORY_DIRS, + MENU_LAYOUT_NODE_DEFAULT_MERGE_DIRS, + MENU_LAYOUT_NODE_NAME, + MENU_LAYOUT_NODE_DIRECTORY, + MENU_LAYOUT_NODE_ONLY_UNALLOCATED, + MENU_LAYOUT_NODE_NOT_ONLY_UNALLOCATED, + MENU_LAYOUT_NODE_INCLUDE, + MENU_LAYOUT_NODE_EXCLUDE, + MENU_LAYOUT_NODE_FILENAME, + MENU_LAYOUT_NODE_CATEGORY, + MENU_LAYOUT_NODE_ALL, + MENU_LAYOUT_NODE_AND, + MENU_LAYOUT_NODE_OR, + MENU_LAYOUT_NODE_NOT, + MENU_LAYOUT_NODE_MERGE_FILE, + MENU_LAYOUT_NODE_MERGE_DIR, + MENU_LAYOUT_NODE_LEGACY_DIR, + MENU_LAYOUT_NODE_KDE_LEGACY_DIRS, + MENU_LAYOUT_NODE_MOVE, + MENU_LAYOUT_NODE_OLD, + MENU_LAYOUT_NODE_NEW, + MENU_LAYOUT_NODE_DELETED, + MENU_LAYOUT_NODE_NOT_DELETED, + MENU_LAYOUT_NODE_LAYOUT, + MENU_LAYOUT_NODE_DEFAULT_LAYOUT, + MENU_LAYOUT_NODE_MENUNAME, + MENU_LAYOUT_NODE_SEPARATOR, + MENU_LAYOUT_NODE_MERGE +} MenuLayoutNodeType; + +typedef enum +{ + MENU_MERGE_FILE_TYPE_PATH = 0, + MENU_MERGE_FILE_TYPE_PARENT +} MenuMergeFileType; + +typedef enum +{ + MENU_LAYOUT_MERGE_NONE, + MENU_LAYOUT_MERGE_MENUS, + MENU_LAYOUT_MERGE_FILES, + MENU_LAYOUT_MERGE_ALL +} MenuLayoutMergeType; + +typedef enum +{ + MENU_LAYOUT_VALUES_NONE = 0, + MENU_LAYOUT_VALUES_SHOW_EMPTY = 1 << 0, + MENU_LAYOUT_VALUES_INLINE_MENUS = 1 << 1, + MENU_LAYOUT_VALUES_INLINE_LIMIT = 1 << 2, + MENU_LAYOUT_VALUES_INLINE_HEADER = 1 << 3, + MENU_LAYOUT_VALUES_INLINE_ALIAS = 1 << 4 +} MenuLayoutValuesMask; + +typedef struct +{ + MenuLayoutValuesMask mask; + + guint show_empty : 1; + guint inline_menus : 1; + guint inline_header : 1; + guint inline_alias : 1; + + guint inline_limit; +} MenuLayoutValues; + + +MenuLayoutNode *menu_layout_load (const char *filename, + const char *non_prefixed_basename, + GError **error); + +MenuLayoutNode *menu_layout_node_new (MenuLayoutNodeType type); +MenuLayoutNode *menu_layout_node_ref (MenuLayoutNode *node); +void menu_layout_node_unref (MenuLayoutNode *node); + +MenuLayoutNodeType menu_layout_node_get_type (MenuLayoutNode *node); + +MenuLayoutNode *menu_layout_node_get_root (MenuLayoutNode *node); +MenuLayoutNode *menu_layout_node_get_parent (MenuLayoutNode *node); +MenuLayoutNode *menu_layout_node_get_children (MenuLayoutNode *node); +MenuLayoutNode *menu_layout_node_get_next (MenuLayoutNode *node); + +void menu_layout_node_insert_before (MenuLayoutNode *node, + MenuLayoutNode *new_sibling); +void menu_layout_node_insert_after (MenuLayoutNode *node, + MenuLayoutNode *new_sibling); +void menu_layout_node_prepend_child (MenuLayoutNode *parent, + MenuLayoutNode *new_child); +void menu_layout_node_append_child (MenuLayoutNode *parent, + MenuLayoutNode *new_child); + +void menu_layout_node_unlink (MenuLayoutNode *node); +void menu_layout_node_steal (MenuLayoutNode *node); + +const char *menu_layout_node_get_content (MenuLayoutNode *node); +void menu_layout_node_set_content (MenuLayoutNode *node, + const char *content); + +char *menu_layout_node_get_content_as_path (MenuLayoutNode *node); + +const char *menu_layout_node_root_get_name (MenuLayoutNode *node); +const char *menu_layout_node_root_get_basedir (MenuLayoutNode *node); + +const char *menu_layout_node_menu_get_name (MenuLayoutNode *node); +EntryDirectoryList *menu_layout_node_menu_get_app_dirs (MenuLayoutNode *node); +EntryDirectoryList *menu_layout_node_menu_get_directory_dirs (MenuLayoutNode *node); + +const char *menu_layout_node_move_get_old (MenuLayoutNode *node); +const char *menu_layout_node_move_get_new (MenuLayoutNode *node); + +const char *menu_layout_node_legacy_dir_get_prefix (MenuLayoutNode *node); +void menu_layout_node_legacy_dir_set_prefix (MenuLayoutNode *node, + const char *prefix); + +MenuMergeFileType menu_layout_node_merge_file_get_type (MenuLayoutNode *node); +void menu_layout_node_merge_file_set_type (MenuLayoutNode *node, + MenuMergeFileType type); + +MenuLayoutMergeType menu_layout_node_merge_get_type (MenuLayoutNode *node); + +void menu_layout_node_default_layout_get_values (MenuLayoutNode *node, + MenuLayoutValues *values); +void menu_layout_node_menuname_get_values (MenuLayoutNode *node, + MenuLayoutValues *values); + +typedef void (* MenuLayoutNodeEntriesChangedFunc) (MenuLayoutNode *node, + gpointer user_data); + +void menu_layout_node_root_add_entries_monitor (MenuLayoutNode *node, + MenuLayoutNodeEntriesChangedFunc callback, + gpointer user_data); +void menu_layout_node_root_remove_entries_monitor (MenuLayoutNode *node, + MenuLayoutNodeEntriesChangedFunc callback, + gpointer user_data); + +G_END_DECLS + +#endif /* __MENU_LAYOUT_H__ */ diff --git a/libmenu/menu-monitor.c b/libmenu/menu-monitor.c new file mode 100644 index 0000000..8895b49 --- /dev/null +++ b/libmenu/menu-monitor.c @@ -0,0 +1,461 @@ +/* + * Copyright (C) 2005 Red Hat, Inc. + * Copyright (C) 2006 Mark McLoughlin + * Copyright (C) 2007 Sebastian Dröge + * Copyright (C) 2008 Vincent Untz + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#include "menu-monitor.h" + +#include + +#include "menu-util.h" + +struct MenuMonitor +{ + char *path; + guint refcount; + + GSList *notifies; + + GFileMonitor *monitor; + + guint is_directory : 1; +}; + +typedef struct +{ + MenuMonitor *monitor; + MenuMonitorEvent event; + char *path; +} MenuMonitorEventInfo; + +typedef struct +{ + MenuMonitorNotifyFunc notify_func; + gpointer user_data; + guint refcount; +} MenuMonitorNotify; + +static MenuMonitorNotify *menu_monitor_notify_ref (MenuMonitorNotify *notify); +static void menu_monitor_notify_unref (MenuMonitorNotify *notify); + +static GHashTable *monitors_registry = NULL; +static guint events_idle_handler = 0; +static GSList *pending_events = NULL; + +static void +invoke_notifies (MenuMonitor *monitor, + MenuMonitorEvent event, + const char *path) +{ + GSList *copy; + GSList *tmp; + + copy = g_slist_copy (monitor->notifies); + g_slist_foreach (copy, + (GFunc) menu_monitor_notify_ref, + NULL); + + tmp = copy; + while (tmp != NULL) + { + MenuMonitorNotify *notify = tmp->data; + GSList *next = tmp->next; + + if (notify->notify_func) + { + notify->notify_func (monitor, event, path, notify->user_data); + } + + menu_monitor_notify_unref (notify); + + tmp = next; + } + + g_slist_free (copy); +} + +static gboolean +emit_events_in_idle (void) +{ + GSList *events_to_emit; + GSList *tmp; + + events_to_emit = pending_events; + + pending_events = NULL; + events_idle_handler = 0; + + tmp = events_to_emit; + while (tmp != NULL) + { + MenuMonitorEventInfo *event_info = tmp->data; + + menu_monitor_ref (event_info->monitor); + + tmp = tmp->next; + } + + tmp = events_to_emit; + while (tmp != NULL) + { + MenuMonitorEventInfo *event_info = tmp->data; + + invoke_notifies (event_info->monitor, + event_info->event, + event_info->path); + + menu_monitor_unref (event_info->monitor); + event_info->monitor = NULL; + + g_free (event_info->path); + event_info->path = NULL; + + event_info->event = MENU_MONITOR_EVENT_INVALID; + + g_free (event_info); + + tmp = tmp->next; + } + + g_slist_free (events_to_emit); + + return FALSE; +} + +static void +menu_monitor_queue_event (MenuMonitorEventInfo *event_info) +{ + pending_events = g_slist_append (pending_events, event_info); + + if (events_idle_handler == 0) + { + events_idle_handler = g_idle_add ((GSourceFunc) emit_events_in_idle, NULL); + } +} + +static inline char * +get_registry_key (const char *path, + gboolean is_directory) +{ + return g_strdup_printf ("%s:%s", + path, + is_directory ? "" : ""); +} + +static gboolean +monitor_callback (GFileMonitor *monitor, + GFile *child, + GFile *other_file, + GFileMonitorEvent eflags, + gpointer user_data) +{ + MenuMonitorEventInfo *event_info; + MenuMonitorEvent event; + MenuMonitor *menu_monitor = (MenuMonitor *) user_data; + + event = MENU_MONITOR_EVENT_INVALID; + switch (eflags) + { + case G_FILE_MONITOR_EVENT_CHANGED: + event = MENU_MONITOR_EVENT_CHANGED; + break; + case G_FILE_MONITOR_EVENT_CREATED: + event = MENU_MONITOR_EVENT_CREATED; + break; + case G_FILE_MONITOR_EVENT_DELETED: + event = MENU_MONITOR_EVENT_DELETED; + break; + default: + return TRUE; + } + + event_info = g_new0 (MenuMonitorEventInfo, 1); + + event_info->path = g_file_get_path (child); + event_info->event = event; + event_info->monitor = menu_monitor; + + menu_monitor_queue_event (event_info); + + return TRUE; +} + +static MenuMonitor * +register_monitor (const char *path, + gboolean is_directory) +{ + MenuMonitor *retval; + GFile *file; + + retval = g_new0 (MenuMonitor, 1); + + retval->path = g_strdup (path); + retval->refcount = 1; + retval->is_directory = is_directory != FALSE; + + file = g_file_new_for_path (retval->path); + + if (file == NULL) + { + menu_verbose ("Not adding monitor on '%s', failed to create GFile\n", + retval->path); + return retval; + } + + if (retval->is_directory) + retval->monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, + NULL, NULL); + else + retval->monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, + NULL, NULL); + + g_object_unref (G_OBJECT (file)); + + if (retval->monitor == NULL) + { + menu_verbose ("Not adding monitor on '%s', failed to create monitor\n", + retval->path); + return retval; + } + + g_signal_connect (retval->monitor, "changed", + G_CALLBACK (monitor_callback), retval); + + return retval; +} + +static MenuMonitor * +lookup_monitor (const char *path, + gboolean is_directory) +{ + MenuMonitor *retval; + char *registry_key; + + retval = NULL; + + registry_key = get_registry_key (path, is_directory); + + if (monitors_registry == NULL) + { + monitors_registry = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); + } + else + { + retval = g_hash_table_lookup (monitors_registry, registry_key); + } + + if (retval == NULL) + { + retval = register_monitor (path, is_directory); + g_hash_table_insert (monitors_registry, registry_key, retval); + + return retval; + } + else + { + g_free (registry_key); + + return menu_monitor_ref (retval); + } +} + +MenuMonitor * +menu_get_file_monitor (const char *path) +{ + g_return_val_if_fail (path != NULL, NULL); + + return lookup_monitor (path, FALSE); +} + +MenuMonitor * +menu_get_directory_monitor (const char *path) +{ + g_return_val_if_fail (path != NULL, NULL); + + return lookup_monitor (path, TRUE); +} + +MenuMonitor * +menu_monitor_ref (MenuMonitor *monitor) +{ + g_return_val_if_fail (monitor != NULL, NULL); + g_return_val_if_fail (monitor->refcount > 0, NULL); + + monitor->refcount++; + + return monitor; +} + +static void +menu_monitor_clear_pending_events (MenuMonitor *monitor) +{ + GSList *tmp; + + tmp = pending_events; + while (tmp != NULL) + { + MenuMonitorEventInfo *event_info = tmp->data; + GSList *next = tmp->next; + + if (event_info->monitor == monitor) + { + pending_events = g_slist_delete_link (pending_events, tmp); + + g_free (event_info->path); + event_info->path = NULL; + + event_info->monitor = NULL; + event_info->event = MENU_MONITOR_EVENT_INVALID; + + g_free (event_info); + } + + tmp = next; + } +} + +void +menu_monitor_unref (MenuMonitor *monitor) +{ + char *registry_key; + + g_return_if_fail (monitor != NULL); + g_return_if_fail (monitor->refcount > 0); + + if (--monitor->refcount > 0) + return; + + registry_key = get_registry_key (monitor->path, monitor->is_directory); + g_hash_table_remove (monitors_registry, registry_key); + g_free (registry_key); + + if (g_hash_table_size (monitors_registry) == 0) + { + g_hash_table_destroy (monitors_registry); + monitors_registry = NULL; + } + + if (monitor->monitor) + { + g_file_monitor_cancel (monitor->monitor); + g_object_unref (monitor->monitor); + monitor->monitor = NULL; + } + + g_slist_foreach (monitor->notifies, (GFunc) menu_monitor_notify_unref, NULL); + g_slist_free (monitor->notifies); + monitor->notifies = NULL; + + menu_monitor_clear_pending_events (monitor); + + g_free (monitor->path); + monitor->path = NULL; + + g_free (monitor); +} + +static MenuMonitorNotify * +menu_monitor_notify_ref (MenuMonitorNotify *notify) +{ + g_return_val_if_fail (notify != NULL, NULL); + g_return_val_if_fail (notify->refcount > 0, NULL); + + notify->refcount++; + + return notify; +} + +static void +menu_monitor_notify_unref (MenuMonitorNotify *notify) +{ + g_return_if_fail (notify != NULL); + g_return_if_fail (notify->refcount > 0); + + if (--notify->refcount > 0) + return; + + g_free (notify); +} + +void +menu_monitor_add_notify (MenuMonitor *monitor, + MenuMonitorNotifyFunc notify_func, + gpointer user_data) +{ + MenuMonitorNotify *notify; + GSList *tmp; + + g_return_if_fail (monitor != NULL); + g_return_if_fail (notify_func != NULL); + + tmp = monitor->notifies; + while (tmp != NULL) + { + notify = tmp->data; + + if (notify->notify_func == notify_func && + notify->user_data == user_data) + break; + + tmp = tmp->next; + } + + if (tmp == NULL) + { + notify = g_new0 (MenuMonitorNotify, 1); + notify->notify_func = notify_func; + notify->user_data = user_data; + notify->refcount = 1; + + monitor->notifies = g_slist_append (monitor->notifies, notify); + } +} + +void +menu_monitor_remove_notify (MenuMonitor *monitor, + MenuMonitorNotifyFunc notify_func, + gpointer user_data) +{ + GSList *tmp; + + tmp = monitor->notifies; + while (tmp != NULL) + { + MenuMonitorNotify *notify = tmp->data; + GSList *next = tmp->next; + + if (notify->notify_func == notify_func && + notify->user_data == user_data) + { + notify->notify_func = NULL; + notify->user_data = NULL; + menu_monitor_notify_unref (notify); + + monitor->notifies = g_slist_delete_link (monitor->notifies, tmp); + } + + tmp = next; + } +} diff --git a/libmenu/menu-monitor.h b/libmenu/menu-monitor.h new file mode 100644 index 0000000..4a37ce2 --- /dev/null +++ b/libmenu/menu-monitor.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __MENU_MONITOR_H__ +#define __MENU_MONITOR_H__ + +#include + +G_BEGIN_DECLS + +typedef struct MenuMonitor MenuMonitor; + +typedef enum +{ + MENU_MONITOR_EVENT_INVALID = 0, + MENU_MONITOR_EVENT_CREATED = 1, + MENU_MONITOR_EVENT_DELETED = 2, + MENU_MONITOR_EVENT_CHANGED = 3 +} MenuMonitorEvent; + +typedef void (*MenuMonitorNotifyFunc) (MenuMonitor *monitor, + MenuMonitorEvent event, + const char *path, + gpointer user_data); + + +MenuMonitor *menu_get_file_monitor (const char *path); +MenuMonitor *menu_get_directory_monitor (const char *path); + +MenuMonitor *menu_monitor_ref (MenuMonitor *monitor); +void menu_monitor_unref (MenuMonitor *monitor); + +void menu_monitor_add_notify (MenuMonitor *monitor, + MenuMonitorNotifyFunc notify_func, + gpointer user_data); +void menu_monitor_remove_notify (MenuMonitor *monitor, + MenuMonitorNotifyFunc notify_func, + gpointer user_data); + +G_END_DECLS + +#endif /* __MENU_MONITOR_H__ */ diff --git a/libmenu/menu-util.c b/libmenu/menu-util.c new file mode 100644 index 0000000..7bc6fd1 --- /dev/null +++ b/libmenu/menu-util.c @@ -0,0 +1,496 @@ +/* Random utility functions for menu code */ + +/* + * Copyright (C) 2003 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#include "menu-util.h" + +#include +#include + + +#ifdef G_ENABLE_DEBUG + +static gboolean verbose = FALSE; +static gboolean initted = FALSE; + +static inline gboolean +menu_verbose_enabled (void) +{ + if (!initted) + { + verbose = g_getenv ("MENU_VERBOSE") != NULL; + initted = TRUE; + } + + return verbose; +} + +static int +utf8_fputs (const char *str, + FILE *f) +{ + char *l; + int ret; + + l = g_locale_from_utf8 (str, -1, NULL, NULL, NULL); + + if (l == NULL) + ret = fputs (str, f); /* just print it anyway, better than nothing */ + else + ret = fputs (l, f); + + g_free (l); + + return ret; +} + +void +menu_verbose (const char *format, + ...) +{ + va_list args; + char *str; + + if (!menu_verbose_enabled ()) + return; + + va_start (args, format); + str = g_strdup_vprintf (format, args); + va_end (args); + + utf8_fputs (str, stderr); + fflush (stderr); + + g_free (str); +} + +static void append_to_string (MenuLayoutNode *node, + gboolean onelevel, + int depth, + GString *str); + +static void +append_spaces (GString *str, + int depth) +{ + while (depth > 0) + { + g_string_append_c (str, ' '); + --depth; + } +} + +static void +append_children (MenuLayoutNode *node, + int depth, + GString *str) +{ + MenuLayoutNode *iter; + + iter = menu_layout_node_get_children (node); + while (iter != NULL) + { + append_to_string (iter, FALSE, depth, str); + + iter = menu_layout_node_get_next (iter); + } +} + +static void +append_simple_with_attr (MenuLayoutNode *node, + int depth, + const char *node_name, + const char *attr_name, + const char *attr_value, + GString *str) +{ + const char *content; + + append_spaces (str, depth); + + if ((content = menu_layout_node_get_content (node))) + { + char *escaped; + + escaped = g_markup_escape_text (content, -1); + + if (attr_name && attr_value) + { + char *attr_escaped; + + attr_escaped = g_markup_escape_text (attr_value, -1); + + g_string_append_printf (str, "<%s %s=\"%s\">%s\n", + node_name, attr_name, + attr_escaped, escaped, node_name); + + g_free (attr_escaped); + } + else + { + g_string_append_printf (str, "<%s>%s\n", + node_name, escaped, node_name); + } + + g_free (escaped); + } + else + { + if (attr_name && attr_value) + { + char *attr_escaped; + + attr_escaped = g_markup_escape_text (attr_value, -1); + + g_string_append_printf (str, "<%s %s=\"%s\"/>\n", + node_name, attr_name, + attr_escaped); + + g_free (attr_escaped); + } + else + { + g_string_append_printf (str, "<%s/>\n", node_name); + } + } +} + +static void +append_layout (MenuLayoutNode *node, + int depth, + const char *node_name, + MenuLayoutValues *layout_values, + GString *str) +{ + const char *content; + + append_spaces (str, depth); + + if ((content = menu_layout_node_get_content (node))) + { + char *escaped; + + escaped = g_markup_escape_text (content, -1); + + g_string_append_printf (str, + "<%s show_empty=\"%s\" inline=\"%s\" inline_header=\"%s\"" + " inline_alias=\"%s\" inline_limit=\"%d\">%s\n", + node_name, + layout_values->show_empty ? "true" : "false", + layout_values->inline_menus ? "true" : "false", + layout_values->inline_header ? "true" : "false", + layout_values->inline_alias ? "true" : "false", + layout_values->inline_limit, + escaped, + node_name); + + g_free (escaped); + } + else + { + g_string_append_printf (str, + "<%s show_empty=\"%s\" inline=\"%s\" inline_header=\"%s\"" + " inline_alias=\"%s\" inline_limit=\"%d\"/>\n", + node_name, + layout_values->show_empty ? "true" : "false", + layout_values->inline_menus ? "true" : "false", + layout_values->inline_header ? "true" : "false", + layout_values->inline_alias ? "true" : "false", + layout_values->inline_limit); + } +} + +static void +append_merge (MenuLayoutNode *node, + int depth, + const char *node_name, + MenuLayoutMergeType merge_type, + GString *str) +{ + const char *merge_type_str; + + merge_type_str = NULL; + + switch (merge_type) + { + case MENU_LAYOUT_MERGE_NONE: + merge_type_str = "none"; + break; + + case MENU_LAYOUT_MERGE_MENUS: + merge_type_str = "menus"; + break; + + case MENU_LAYOUT_MERGE_FILES: + merge_type_str = "files"; + break; + + case MENU_LAYOUT_MERGE_ALL: + merge_type_str = "all"; + break; + + default: + g_assert_not_reached (); + break; + } + + append_simple_with_attr (node, depth, node_name, "type", merge_type_str, str); +} + +static void +append_simple (MenuLayoutNode *node, + int depth, + const char *node_name, + GString *str) +{ + append_simple_with_attr (node, depth, node_name, NULL, NULL, str); +} + +static void +append_start (MenuLayoutNode *node, + int depth, + const char *node_name, + GString *str) +{ + append_spaces (str, depth); + + g_string_append_printf (str, "<%s>\n", node_name); +} + +static void +append_end (MenuLayoutNode *node, + int depth, + const char *node_name, + GString *str) +{ + append_spaces (str, depth); + + g_string_append_printf (str, "\n", node_name); +} + +static void +append_container (MenuLayoutNode *node, + gboolean onelevel, + int depth, + const char *node_name, + GString *str) +{ + append_start (node, depth, node_name, str); + if (!onelevel) + { + append_children (node, depth + 2, str); + append_end (node, depth, node_name, str); + } +} + +static void +append_to_string (MenuLayoutNode *node, + gboolean onelevel, + int depth, + GString *str) +{ + MenuLayoutValues layout_values; + + switch (menu_layout_node_get_type (node)) + { + case MENU_LAYOUT_NODE_ROOT: + if (!onelevel) + append_children (node, depth - 1, str); /* -1 to ignore depth of root */ + else + append_start (node, depth - 1, "Root", str); + break; + + case MENU_LAYOUT_NODE_PASSTHROUGH: + g_string_append (str, + menu_layout_node_get_content (node)); + g_string_append_c (str, '\n'); + break; + + case MENU_LAYOUT_NODE_MENU: + append_container (node, onelevel, depth, "Menu", str); + break; + + case MENU_LAYOUT_NODE_APP_DIR: + append_simple (node, depth, "AppDir", str); + break; + + case MENU_LAYOUT_NODE_DEFAULT_APP_DIRS: + append_simple (node, depth, "DefaultAppDirs", str); + break; + + case MENU_LAYOUT_NODE_DIRECTORY_DIR: + append_simple (node, depth, "DirectoryDir", str); + break; + + case MENU_LAYOUT_NODE_DEFAULT_DIRECTORY_DIRS: + append_simple (node, depth, "DefaultDirectoryDirs", str); + break; + + case MENU_LAYOUT_NODE_DEFAULT_MERGE_DIRS: + append_simple (node, depth, "DefaultMergeDirs", str); + break; + + case MENU_LAYOUT_NODE_NAME: + append_simple (node, depth, "Name", str); + break; + + case MENU_LAYOUT_NODE_DIRECTORY: + append_simple (node, depth, "Directory", str); + break; + + case MENU_LAYOUT_NODE_ONLY_UNALLOCATED: + append_simple (node, depth, "OnlyUnallocated", str); + break; + + case MENU_LAYOUT_NODE_NOT_ONLY_UNALLOCATED: + append_simple (node, depth, "NotOnlyUnallocated", str); + break; + + case MENU_LAYOUT_NODE_INCLUDE: + append_container (node, onelevel, depth, "Include", str); + break; + + case MENU_LAYOUT_NODE_EXCLUDE: + append_container (node, onelevel, depth, "Exclude", str); + break; + + case MENU_LAYOUT_NODE_FILENAME: + append_simple (node, depth, "Filename", str); + break; + + case MENU_LAYOUT_NODE_CATEGORY: + append_simple (node, depth, "Category", str); + break; + + case MENU_LAYOUT_NODE_ALL: + append_simple (node, depth, "All", str); + break; + + case MENU_LAYOUT_NODE_AND: + append_container (node, onelevel, depth, "And", str); + break; + + case MENU_LAYOUT_NODE_OR: + append_container (node, onelevel, depth, "Or", str); + break; + + case MENU_LAYOUT_NODE_NOT: + append_container (node, onelevel, depth, "Not", str); + break; + + case MENU_LAYOUT_NODE_MERGE_FILE: + { + MenuMergeFileType type; + + type = menu_layout_node_merge_file_get_type (node); + + append_simple_with_attr (node, depth, "MergeFile", + "type", type == MENU_MERGE_FILE_TYPE_PARENT ? "parent" : "path", + str); + break; + } + + case MENU_LAYOUT_NODE_MERGE_DIR: + append_simple (node, depth, "MergeDir", str); + break; + + case MENU_LAYOUT_NODE_LEGACY_DIR: + append_simple_with_attr (node, depth, "LegacyDir", + "prefix", menu_layout_node_legacy_dir_get_prefix (node), + str); + break; + + case MENU_LAYOUT_NODE_KDE_LEGACY_DIRS: + append_simple (node, depth, "KDELegacyDirs", str); + break; + + case MENU_LAYOUT_NODE_MOVE: + append_container (node, onelevel, depth, "Move", str); + break; + + case MENU_LAYOUT_NODE_OLD: + append_simple (node, depth, "Old", str); + break; + + case MENU_LAYOUT_NODE_NEW: + append_simple (node, depth, "New", str); + break; + + case MENU_LAYOUT_NODE_DELETED: + append_simple (node, depth, "Deleted", str); + break; + + case MENU_LAYOUT_NODE_NOT_DELETED: + append_simple (node, depth, "NotDeleted", str); + break; + + case MENU_LAYOUT_NODE_LAYOUT: + append_container (node, onelevel, depth, "Layout", str); + break; + + case MENU_LAYOUT_NODE_DEFAULT_LAYOUT: + menu_layout_node_default_layout_get_values (node, &layout_values); + append_layout (node, depth, "DefaultLayout", &layout_values, str); + break; + + case MENU_LAYOUT_NODE_MENUNAME: + menu_layout_node_menuname_get_values (node, &layout_values); + append_layout (node, depth, "MenuName", &layout_values, str); + break; + + case MENU_LAYOUT_NODE_SEPARATOR: + append_simple (node, depth, "Name", str); + break; + + case MENU_LAYOUT_NODE_MERGE: + append_merge (node, + depth, + "Merge", + menu_layout_node_merge_get_type (node), + str); + break; + + default: + g_assert_not_reached (); + break; + } +} + +void +menu_debug_print_layout (MenuLayoutNode *node, + gboolean onelevel) +{ + if (menu_verbose_enabled ()) + { + GString *str; + + str = g_string_new (NULL); + append_to_string (node, onelevel, 0, str); + + utf8_fputs (str->str, stderr); + fflush (stderr); + + g_string_free (str, TRUE); + } +} + +#endif /* G_ENABLE_DEBUG */ diff --git a/libmenu/menu-util.h b/libmenu/menu-util.h new file mode 100644 index 0000000..5b0a3f4 --- /dev/null +++ b/libmenu/menu-util.h @@ -0,0 +1,54 @@ +/* Random utility functions for menu code */ + +/* + * Copyright (C) 2003 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __MENU_UTIL_H__ +#define __MENU_UTIL_H__ + +#include + +#include "menu-layout.h" + +G_BEGIN_DECLS + +#ifdef G_ENABLE_DEBUG + +void menu_verbose (const char *format, ...) G_GNUC_PRINTF (1, 2); + +void menu_debug_print_layout (MenuLayoutNode *node, + gboolean onelevel); + +#else /* !defined(G_ENABLE_DEBUG) */ + +#ifdef G_HAVE_ISO_VARARGS +#define menu_verbose(...) +#elif defined(G_HAVE_GNUC_VARARGS) +#define menu_verbose(format...) +#else +#error "Cannot disable verbose mode due to lack of varargs macros" +#endif + +#define menu_debug_print_layout(n,o) + +#endif /* G_ENABLE_DEBUG */ + +G_END_DECLS + +#endif /* __MENU_UTIL_H__ */ diff --git a/omf.make b/omf.make new file mode 100644 index 0000000..35dec24 --- /dev/null +++ b/omf.make @@ -0,0 +1,65 @@ +# +# No modifications of this Makefile should be necessary. +# +# This file contains the build instructions for installing OMF files. It is +# generally called from the makefiles for particular formats of documentation. +# +# Note that you must configure your package with --localstatedir=/var +# so that the scrollkeeper-update command below will update the database +# in the standard scrollkeeper directory. +# +# If it is impossible to configure with --localstatedir=/var, then +# modify the definition of scrollkeeper_localstate_dir so that +# it points to the correct location. Note that you must still use +# $(localstatedir) in this or when people build RPMs it will update +# the real database on their system instead of the one under RPM_BUILD_ROOT. +# +# Note: This make file is not incorporated into xmldocs.make because, in +# general, there will be other documents install besides XML documents +# and the makefiles for these formats should also include this file. +# +# About this file: +# This file was derived from scrollkeeper_example2, a package +# illustrating how to install documentation and OMF files for use with +# ScrollKeeper 0.3.x and 0.4.x. For more information, see: +# http://scrollkeeper.sourceforge.net/ +# Version: 0.1.3 (last updated: March 20, 2002) +# + +omf_dest_dir=$(datadir)/omf/@PACKAGE@ +scrollkeeper_localstate_dir = $(localstatedir)/scrollkeeper + +# At some point, it may be wise to change to something like this: +# scrollkeeper_localstate_dir = @SCROLLKEEPER_STATEDIR@ + +omf: omf_timestamp + +omf_timestamp: $(omffile) + -for file in $(omffile); do \ + absfile=$(srcdir)/$$file; \ + test -r $$file && absfile=$$file; \ + scrollkeeper-preinstall $(docdir)/$(docname).xml $$absfile $$file.out; \ + done; \ + touch omf_timestamp + +install-data-hook-omf: + $(mkinstalldirs) $(DESTDIR)$(omf_dest_dir) + for file in $(omffile); do \ + absfile=$(srcdir)/$$file.out; \ + test -r $$file.out && absfile=$$file.out; \ + $(INSTALL_DATA) $$absfile $(DESTDIR)$(omf_dest_dir)/$$file; \ + done + -scrollkeeper-update -p $(DESTDIR)$(scrollkeeper_localstate_dir) -o $(DESTDIR)$(omf_dest_dir) + +uninstall-local-omf: + -for file in $(omffile); do \ + basefile=`basename $$file`; \ + rm -f $(DESTDIR)$(omf_dest_dir)/$$basefile; \ + done + -rmdir $(DESTDIR)$(omf_dest_dir) + -scrollkeeper-update -p $(DESTDIR)$(scrollkeeper_localstate_dir) + +clean-local-omf: + -for file in $(omffile); do \ + rm -f $$file.out; \ + done diff --git a/xmldocs.make b/xmldocs.make new file mode 100644 index 0000000..b93e3f3 --- /dev/null +++ b/xmldocs.make @@ -0,0 +1,101 @@ +# +# No modifications of this Makefile should be necessary. +# +# To use this template: +# 1) Define: figdir, docname, lang, omffile, and entities in +# your Makefile.am file for each document directory, +# although figdir, omffile, and entities may be empty +# 2) Make sure the Makefile in (1) also includes +# "include $(top_srcdir)/xmldocs.make" and +# "dist-hook: app-dist-hook". +# 3) Optionally define 'entities' to hold xml entities which +# you would also like installed +# 4) Figures must go under $(figdir)/ and be in PNG format +# 5) You should only have one document per directory +# 6) Note that the figure directory, $(figdir)/, should not have its +# own Makefile since this Makefile installs those figures. +# +# example Makefile.am: +# figdir = figures +# docname = scrollkeeper-manual +# lang = C +# omffile=scrollkeeper-manual-C.omf +# entities = fdl.xml +# include $(top_srcdir)/xmldocs.make +# dist-hook: app-dist-hook +# +# About this file: +# This file was taken from scrollkeeper_example2, a package illustrating +# how to install documentation and OMF files for use with ScrollKeeper +# 0.3.x and 0.4.x. For more information, see: +# http://scrollkeeper.sourceforge.net/ +# Version: 0.1.2 (last updated: March 20, 2002) +# + + +# ********** Begin of section some packagers may need to modify ********** +# This variable (docdir) specifies where the documents should be installed. +# This default value should work for most packages. +docdir = $(datadir)/gnome/help/$(docname)/$(lang) + +# ********** You should not have to edit below this line ********** +xml_files = $(entities) $(docname).xml + +EXTRA_DIST = $(xml_files) $(omffile) +CLEANFILES = omf_timestamp + +include $(top_srcdir)/omf.make + +all: omf + +$(docname).xml: $(entities) + -ourdir=`pwd`; \ + cd $(srcdir); \ + cp $(entities) $$ourdir + +app-dist-hook: + if test "$(figdir)"; then \ + $(mkinstalldirs) $(distdir)/$(figdir); \ + for file in $(srcdir)/$(figdir)/*.png; do \ + basefile=`echo $$file | sed -e 's,^.*/,,'`; \ + $(INSTALL_DATA) $$file $(distdir)/$(figdir)/$$basefile; \ + done \ + fi + +install-data-local: omf + $(mkinstalldirs) $(DESTDIR)$(docdir) + for file in $(xml_files); do \ + cp $(srcdir)/$$file $(DESTDIR)$(docdir); \ + done + if test "$(figdir)"; then \ + $(mkinstalldirs) $(DESTDIR)$(docdir)/$(figdir); \ + for file in $(srcdir)/$(figdir)/*.png; do \ + basefile=`echo $$file | sed -e 's,^.*/,,'`; \ + $(INSTALL_DATA) $$file $(DESTDIR)$(docdir)/$(figdir)/$$basefile; \ + done \ + fi + +install-data-hook: install-data-hook-omf + +uninstall-local: uninstall-local-doc uninstall-local-omf + +uninstall-local-doc: + -if test "$(figdir)"; then \ + for file in $(srcdir)/$(figdir)/*.png; do \ + basefile=`echo $$file | sed -e 's,^.*/,,'`; \ + rm -f $(DESTDIR)$(docdir)/$(figdir)/$$basefile; \ + done; \ + rmdir $(DESTDIR)$(docdir)/$(figdir); \ + fi + -for file in $(xml_files); do \ + rm -f $(DESTDIR)$(docdir)/$$file; \ + done + -rmdir $(DESTDIR)$(docdir) + +clean-local: clean-local-doc clean-local-omf + +# for non-srcdir builds, remove the copied entities. +clean-local-doc: + if test $(srcdir) != .; then \ + rm -f $(entities); \ + fi