cinnamon-menus-6.2.0/0000775000175000017500000000000014632057634013404 5ustar fabiofabiocinnamon-menus-6.2.0/debian/0000775000175000017500000000000014632057634014626 5ustar fabiofabiocinnamon-menus-6.2.0/meson.build0000664000175000017500000000235114632057634015547 0ustar fabiofabioproject('cinnamon-menus', 'c', version : '6.2.0', meson_version : '>=0.56.0') gnome = import('gnome') version = meson.project_version() binary_version = '0.0.1' binary_major_version = binary_version.split('.')[0] cmenu_conf = configuration_data() cmenu_conf.set_quoted('PACKAGE', meson.project_name()) # directories prefix = get_option('prefix') datadir = get_option('datadir') libdir = get_option('libdir') includedir = get_option('includedir') # generate config.h config_h_file = configure_file( output : 'config.h', configuration : cmenu_conf ) config_h = declare_dependency( sources: config_h_file ) include_root = include_directories('.') c_args = [ '-DGMENU_I_KNOW_THIS_IS_UNSTABLE', ] if get_option('enable_debug') c_args += '-DG_ENABLE_DEBUG' else c_args += '-DG_DISABLE_ASSERT' c_args += '-DG_DISABLE_CHECKS' c_args += '-DG_DISABLE_CAST_CHECKS' endif if not get_option('deprecated_warnings') c_args += '-Wno-deprecated-declarations' c_args += '-Wno-deprecated' c_args += '-Wno-declaration-after-statement' endif add_global_arguments(c_args, language: 'c') gio = dependency('gio-unix-2.0', version: '>= 2.29.15') subdir('libmenu') if get_option('enable_docs') subdir('docs/reference') endif cinnamon-menus-6.2.0/docs/0000775000175000017500000000000014632057634014334 5ustar fabiofabiocinnamon-menus-6.2.0/docs/reference/0000775000175000017500000000000014632057634016272 5ustar fabiofabiocinnamon-menus-6.2.0/docs/reference/meson.build0000664000175000017500000000066514632057634020443 0ustar fabiofabioversion_conf = configuration_data() version_conf.set('VERSION', version) configure_file( input: 'version.xml.in', output: 'version.xml', configuration: version_conf, ) gnome.gtkdoc( 'cmenu', src_dir: join_paths(meson.project_source_root(), 'libmenu'), main_xml: 'cmenu-docs.xml', scan_args: ['--rebuild-types'], ignore_headers: libmenu_private_headers, dependencies: cmenu_dep, install: true, ) cinnamon-menus-6.2.0/docs/reference/version.xml.in0000664000175000017500000000001214632057634021077 0ustar fabiofabio@VERSION@ cinnamon-menus-6.2.0/docs/reference/cmenu-docs.xml0000664000175000017500000000164414632057634021056 0ustar fabiofabio ]> Cinnamon Menus Reference Manual For Cinnamon Menus &version; API Reference Classes API Index Index cinnamon-menus-6.2.0/.gitignore0000664000175000017500000000034214632057634015373 0ustar fabiofabio*.debhelper.log debian/*.debhelper debian/*.substvars debian/debhelper-build-stamp debian/files debian/gir1.2-cmenu-3.0/ debian/libcinnamon-menu-3-0-dbg/ debian/libcinnamon-menu-3-0/ debian/libcinnamon-menu-3-dev/ debian/tmp/ cinnamon-menus-6.2.0/README0000664000175000017500000000230514632057634014264 0ustar fabiofabiocinnamon-menus =========== cinnamon-menus contains the libcinnamon-menu library, the layout configuration files for the Cinnamon menu, as well as a simple menu editor. The libcinnamon-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: https://github.com/linuxmint/cinnamon-menus/releases Installation ============ 1) Run meson with options you like. The following configuration installs all binaries, libs, and shared files into /usr/local, and enables all available options: meson debian/build \ --prefix=/usr/local \ --buildtype=plain \ -D deprecated_warnings=false 2) Compile and install (sudo is needed for install) ninja -C debian/build ninja -C debian/build install 3) You can uninstall the installed files with ninja -C debian/build uninstall How to report bugs ================== Bugs should be reported to the Cinnamon bug tracking system: https://github.com/linuxmint/cinnamon-menus/issues You will need to create an account for yourself. cinnamon-menus-6.2.0/AUTHORS0000664000175000017500000000014114632057634014450 0ustar fabiofabioMark McLoughlin Havoc Pennington Vincent Untz cinnamon-menus-6.2.0/COPYING0000664000175000017500000004325414632057634014447 0ustar fabiofabio 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. cinnamon-menus-6.2.0/.github/0000775000175000017500000000000014632057634014744 5ustar fabiofabiocinnamon-menus-6.2.0/.github/workflows/0000775000175000017500000000000014632057634017001 5ustar fabiofabiocinnamon-menus-6.2.0/.github/workflows/build.yml0000664000175000017500000000107114632057634020622 0ustar fabiofabioname: Build on: push: branches: - master pull_request: branches: - master workflow_dispatch: inputs: debug_enabled: type: boolean description: 'Start an SSH server on failure.' required: false default: false jobs: build: uses: linuxmint/github-actions/.github/workflows/do-builds.yml@master with: commit_id: master ############################## Comma separated list - like 'linuxmint/xapp, linuxmint/cinnamon-desktop' dependencies: ############################## cinnamon-menus-6.2.0/libmenu/0000775000175000017500000000000014632057634015037 5ustar fabiofabiocinnamon-menus-6.2.0/libmenu/menu-monitor.h0000664000175000017500000000345614632057634017651 0ustar fabiofabio/* * 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__ */ cinnamon-menus-6.2.0/libmenu/desktop-entries.c0000664000175000017500000006053714632057634020336 0ustar fabiofabio/* * 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 "desktop-entries.h" #include "gmenu-desktopappinfo.h" #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; GMenuDesktopAppInfo *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 = gmenu_desktopappinfo_new_from_filename (entry->path); if (!GMENU_IS_DESKTOPAPPINFO (((DesktopEntryDesktop*)entry)->appinfo)) { menu_verbose ("Failed to load \"%s\"\n", entry->path); return DESKTOP_ENTRY_LOAD_FAIL_APPINFO; } categories_str = gmenu_desktopappinfo_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); entry->refcount += 1; 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); desktop_entry->appinfo = 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; 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) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (((DesktopEntryDesktop*)entry)->appinfo), NULL); 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) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (((DesktopEntryDesktop*)entry)->appinfo), NULL); return gmenu_desktopappinfo_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) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (((DesktopEntryDesktop*)entry)->appinfo), NULL); 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) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (((DesktopEntryDesktop*)entry)->appinfo), NULL); 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) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (((DesktopEntryDesktop*)entry)->appinfo), FALSE); return gmenu_desktopappinfo_get_nodisplay (((DesktopEntryDesktop*)entry)->appinfo); } return ((DesktopEntryDirectory*)entry)->nodisplay; } gboolean desktop_entry_get_hidden (DesktopEntry *entry) { if (entry->type == DESKTOP_ENTRY_DESKTOP) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (((DesktopEntryDesktop*)entry)->appinfo), FALSE); return gmenu_desktopappinfo_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; } const char * desktop_entry_get_id (DesktopEntry *entry) { if (entry->type == DESKTOP_ENTRY_DESKTOP) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (((DesktopEntryDesktop*)entry)->appinfo), NULL); return g_app_info_get_id (G_APP_INFO (((DesktopEntryDesktop*)entry)->appinfo)); } // this only applies to non-desktop entries return entry->basename; } GMenuDesktopAppInfo * 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; } /* * 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); set->refcount += 1; return set; } void desktop_entry_set_unref (DesktopEntrySet *set) { g_return_if_fail (set != NULL); g_return_if_fail (set->refcount > 0); set->refcount -= 1; if (set->refcount == 0) { 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; } cinnamon-menus-6.2.0/libmenu/entry-directories.c0000664000175000017500000007660514632057634020674 0ustar fabiofabio/* * 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 "entry-directories.h" #include #include #include #include #include #include "menu-util.h" #include "menu-monitor.h" typedef struct CachedDir CachedDir; typedef struct CachedDirMonitor CachedDirMonitor; struct EntryDirectory { CachedDir *dir; guint entry_type : 2; guint refcount : 24; }; struct EntryDirectoryList { 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; guint references; GFunc notify; gpointer notify_data; }; 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 void cached_dir_unref_noparent (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_noparent, 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) { dir->references++; return dir; } static void cached_dir_unref (CachedDir *dir) { if (--dir->references == 0) { 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 void cached_dir_unref_noparent (CachedDir *dir) { if (--dir->references == 0) { 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) { if (strcmp (desktop_entry_get_basename (tmp->data), 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) { if (strcmp (desktop_entry_get_basename (tmp->data), 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) { if (strcmp (desktop_entry_get_basename (tmp->data), 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; 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) { g_source_remove (monitors_idle_handler); } monitors_idle_handler = g_timeout_add (100, (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; dirname = g_path_get_dirname (path); basename = g_path_get_basename (path); dir = cached_dir_lookup (dirname); cached_dir_add_reference (dir); 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 (gmenu_desktopappinfo_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 gmenu_desktopappinfo_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) { 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")); /* 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); } cached_dir_remove_reference (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 */ EntryDirectory * entry_directory_new (DesktopEntryType entry_type, const char *path) { EntryDirectory *ed; char *canonical; menu_verbose ("Loading entry directory \"%s\"\n", path); canonical = realpath (path, NULL); 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->entry_type = entry_type; ed->refcount = 1; g_free (canonical); return ed; } EntryDirectory * entry_directory_ref (EntryDirectory *ed) { g_return_val_if_fail (ed != NULL, NULL); g_return_val_if_fail (ed->refcount > 0, NULL); ed->refcount++; return ed; } void entry_directory_unref (EntryDirectory *ed) { g_return_if_fail (ed != NULL); g_return_if_fail (ed->refcount > 0); if (--ed->refcount == 0) { cached_dir_remove_reference (ed->dir); ed->dir = NULL; ed->entry_type = DESKTOP_ENTRY_INVALID; 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, DesktopEntry *entry) { char *retval; retval = NULL; if (entry_type == DESKTOP_ENTRY_DESKTOP) { GMenuDesktopAppInfo *appinfo; appinfo = desktop_entry_get_app_info (entry); retval = g_strdelimit (g_strdup (relative_path), "/", '-'); if (gmenu_desktopappinfo_get_is_flatpak (appinfo)) { char* tmp; tmp = retval; retval = g_strconcat (retval, GMENU_DESKTOPAPPINFO_FLATPAK_SUFFIX, NULL); g_free (tmp); } } 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; g_string_append (relative_path, desktop_entry_get_basename (entry)); file_id = get_desktop_file_id_from_path (ed, ed->entry_type, relative_path->str, entry); 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_basename (entry); if (desktop_entries && desktop_entry_get_type (entry) == DESKTOP_ENTRY_DESKTOP) { desktop_entry_set_add_entry (desktop_entries, entry, NULL); } 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); list->refcount += 1; return list; } void entry_directory_list_unref (EntryDirectoryList *list) { g_return_if_fail (list != NULL); g_return_if_fail (list->refcount > 0); list->refcount -= 1; if (list->refcount == 0) { 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) { 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; } } cinnamon-menus-6.2.0/libmenu/meson.build0000664000175000017500000000331114632057634017177 0ustar fabiofabiopublic_headers = [ 'gmenu-tree.h', 'gmenu-desktopappinfo.h' ] public_sources = [ 'gmenu-tree.c', 'gmenu-desktopappinfo.c', public_headers, ] libmenu_private_headers = [ 'desktop-entries.h', 'entry-directories.h', 'menu-layout.h', 'menu-monitor.h', 'menu-util.h', ] libmenu_sources = [ 'desktop-entries.c', 'entry-directories.c', 'menu-layout.c', 'menu-monitor.c', 'menu-util.c', public_sources, libmenu_private_headers, ] libmenu_deps = [ gio, config_h, ] libcinnamon_menus = library( 'cinnamon-menu-3', libmenu_sources, soversion: binary_major_version, version: binary_version, include_directories: include_root, dependencies: libmenu_deps, install: true, build_by_default: false, ) cmenu_dep = declare_dependency( include_directories: include_directories('.'), link_with: libcinnamon_menus, dependencies: libmenu_deps, link_args: ['-Wl,-Bsymbolic', '-Wl,-z,relro', '-Wl,-z,now'], ) install_headers( public_headers, subdir: 'cinnamon-menus-3.0' ) pkgconfig = import('pkgconfig') # meson 0.46.0 can drop the version keyword and move libraries to a # positional argument pkgconfig.generate( name: 'libcinnamon-menu-3.0', description: 'Desktop Menu Specification Implementation', version: version, libraries: libcinnamon_menus, subdirs: 'cinnamon-menus-3.0' ) gnome.generate_gir( libcinnamon_menus, namespace: 'CMenu', nsversion: '3.0', sources: public_sources, identifier_prefix: 'GMenu', symbol_prefix: 'gmenu', includes: 'Gio-2.0', install: true, install_dir_gir: join_paths(datadir, 'gir-1.0'), export_packages: 'libcinnamon-menu-3.0', ) cinnamon-menus-6.2.0/libmenu/gmenu-desktopappinfo.h0000664000175000017500000001117314632057634021352 0ustar fabiofabio/* * gmenu-desktopappinfo.h * Copyright (C) 2020 Lars Mueller * * 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, 51 Franklin Street, Fifth Floor, * Boston, MA 02110, USA. */ #ifndef _GMENU_DESKTOPAPPINFO_H_ #define _GMENU_DESKTOPAPPINFO_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_DESKTOPAPPINFO (gmenu_desktopappinfo_get_type ()) #define GMENU_DESKTOPAPPINFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GMENU_TYPE_DESKTOPAPPINFO, GMenuDesktopAppInfo)) #define GMENU_DESKTOPAPPINFO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GMENU_TYPE_DESKTOPAPPINFO, GMenuDesktopAppInfoClass)) #define GMENU_IS_DESKTOPAPPINFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GMENU_TYPE_DESKTOPAPPINFO)) #define GMENU_IS_DESKTOPAPPINFO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GMENU_TYPE_DESKTOPAPPINFO)) #define GMENU_DESKTOPAPPINFO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GMENU_TYPE_DESKTOPAPPINFO, GMenuDesktopAppInfoClass)) typedef struct _GMenuDesktopAppInfoClass GMenuDesktopAppInfoClass; typedef struct _GMenuDesktopAppInfo GMenuDesktopAppInfo; struct _GMenuDesktopAppInfoClass { GObjectClass parent_class; }; #define GMENU_DESKTOPAPPINFO_FLATPAK_SUFFIX ":flatpak" GType gmenu_desktopappinfo_get_type (void) G_GNUC_CONST; GMenuDesktopAppInfo* gmenu_desktopappinfo_new_from_filename (gchar *filename); GMenuDesktopAppInfo *gmenu_desktopappinfo_new_from_keyfile (GKeyFile *key_file); const char * gmenu_desktopappinfo_get_filename (GMenuDesktopAppInfo *appinfo); const char * gmenu_desktopappinfo_get_generic_name (GMenuDesktopAppInfo *appinfo); const char * gmenu_desktopappinfo_get_categories (GMenuDesktopAppInfo *appinfo); const char * const *gmenu_desktopappinfo_get_keywords (GMenuDesktopAppInfo *appinfo); gboolean gmenu_desktopappinfo_get_nodisplay (GMenuDesktopAppInfo *appinfo); gboolean gmenu_desktopappinfo_get_show_in (GMenuDesktopAppInfo *appinfo, const gchar *desktop_env); const char * gmenu_desktopappinfo_get_startup_wm_class (GMenuDesktopAppInfo *appinfo); GMenuDesktopAppInfo *gmenu_desktopappinfo_new (const char *desktop_id); gboolean gmenu_desktopappinfo_get_is_hidden (GMenuDesktopAppInfo *appinfo); gboolean gmenu_desktopappinfo_has_key (GMenuDesktopAppInfo *appinfo, const char *key); char * gmenu_desktopappinfo_get_string (GMenuDesktopAppInfo *appinfo, const char *key); char * gmenu_desktopappinfo_get_locale_string (GMenuDesktopAppInfo *appinfo, const char *key); gboolean gmenu_desktopappinfo_get_boolean (GMenuDesktopAppInfo *appinfo, const char *key); const gchar * const * gmenu_desktopappinfo_list_actions (GMenuDesktopAppInfo *appinfo); void gmenu_desktopappinfo_launch_action (GMenuDesktopAppInfo *appinfo, const gchar *action_name, GAppLaunchContext *launch_context); gchar * gmenu_desktopappinfo_get_action_name (GMenuDesktopAppInfo *appinfo, const gchar *action_name); gboolean gmenu_desktopappinfo_launch_uris_as_manager (GMenuDesktopAppInfo *appinfo, GList *uris, GAppLaunchContext *launch_context, GSpawnFlags spawn_flags, GSpawnChildSetupFunc user_setup, gpointer user_setup_data, GDesktopAppLaunchCallback pid_callback, gpointer pid_callback_data, GError **error); gboolean gmenu_desktopappinfo_get_is_flatpak (GMenuDesktopAppInfo *appinfo); const char * gmenu_desktopappinfo_get_flatpak_app_id (GMenuDesktopAppInfo *appinfo); G_END_DECLS #endif /* _GMENU_DESKTOPAPPINFO_H_ */ cinnamon-menus-6.2.0/libmenu/menu-layout.c0000664000175000017500000017205514632057634017474 0ustar fabiofabio/* 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 "menu-layout.h" #include #include #include #include #include #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); } 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; } cinnamon-menus-6.2.0/libmenu/gmenu-tree.c0000664000175000017500000036405314632057634017266 0ustar fabiofabio/* -*- 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 #include "menu-layout.h" #include "menu-monitor.h" #include "menu-util.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; guint is_flatpak : 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 = realpath (path, NULL); 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) g_string_append (path, desktop_entry_get_basename (entry->desktop_entry)); return g_string_free (path, FALSE); } /** * gmenu_tree_entry_get_app_info: * @entry: a #GMenuTreeEntry * * Returns: (transfer none): The #GMenuDesktopAppInfo for this entry */ GMenuDesktopAppInfo * 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, NULL); return entry->desktop_file_id; } gboolean gmenu_tree_entry_get_is_nodisplay_recurse (GMenuTreeEntry *entry) { GMenuTreeDirectory *directory; GMenuDesktopAppInfo *app_info; g_return_val_if_fail (entry != NULL, FALSE); app_info = gmenu_tree_entry_get_app_info (entry); if (gmenu_desktopappinfo_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_flatpak (GMenuTreeEntry *entry) { GMenuDesktopAppInfo *app_info; g_return_val_if_fail (entry != NULL, FALSE); app_info = gmenu_tree_entry_get_app_info (entry); return gmenu_desktopappinfo_get_is_flatpak (app_info); } 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 (gmenu_tree_entry_get_app_info (GMENU_TREE_ENTRY (item)) == NULL) { return desktop_entry_get_basename (GMENU_TREE_ENTRY (item)->desktop_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 = realpath (filename, NULL); 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 = realpath (dirname, NULL); 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 = realpath (basedir, NULL); 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 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: menu_verbose ("Ignoring obsolete legacy dir"); break; case MENU_LAYOUT_NODE_KDE_LEGACY_DIRS: menu_verbose ("Ignoring obsolete KDE legacy dirs"); 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 GMenuDesktopAppInfo 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; } cinnamon-menus-6.2.0/libmenu/desktop-entries.h0000664000175000017500000001013514632057634020330 0ustar fabiofabio/* * 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 "gmenu-desktopappinfo.h" 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 */ GMenuDesktopAppInfo *desktop_entry_get_app_info (DesktopEntry *entry); gboolean desktop_entry_has_categories (DesktopEntry *entry); gboolean desktop_entry_has_category (DesktopEntry *entry, const char *category); 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); const char * desktop_entry_get_id (DesktopEntry *entry); 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__ */ cinnamon-menus-6.2.0/libmenu/menu-util.h0000664000175000017500000000271714632057634017136 0ustar fabiofabio/* 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__ */ cinnamon-menus-6.2.0/libmenu/gmenu-tree.h0000664000175000017500000001613214632057634017263 0ustar fabiofabio/* * 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 "gmenu-desktopappinfo.h" 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); GMenuDesktopAppInfo *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); gboolean gmenu_tree_entry_get_is_flatpak (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__ */ cinnamon-menus-6.2.0/libmenu/gmenu-desktopappinfo.c0000664000175000017500000006551014632057634021351 0ustar fabiofabio/* * gmenu-desktopappinfo.c * Copyright (C) 2020 Lars Mueller * * 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, 51 Franklin Street, Fifth Floor, * Boston, MA 02110, USA. */ #include "gmenu-desktopappinfo.h" #include struct _GMenuDesktopAppInfo { GObject parent_instance; GDesktopAppInfo *super_appinfo; gchar *desktop_id; gboolean is_flatpak; gchar *startup_wm_class; gchar *flatpak_app_id; }; // This function sets desktop id and startup wm class and adds ":flatpak" for flatpak apps static void update_app_data (GMenuDesktopAppInfo *info) { gchar *exec = NULL; gchar *id = NULL; gchar *startup_wm_class = NULL; g_free (info->desktop_id); info->desktop_id = NULL; g_free (info->startup_wm_class); info->startup_wm_class = NULL; g_free (info->flatpak_app_id); info->flatpak_app_id = NULL; if (info->super_appinfo != NULL) { exec = (gchar *) g_app_info_get_executable (G_APP_INFO (info->super_appinfo)); id = (gchar *) g_app_info_get_id (G_APP_INFO (info->super_appinfo)); startup_wm_class = (gchar *) g_desktop_app_info_get_startup_wm_class (info->super_appinfo); if (exec && (strstr (exec, "flatpak") || strstr (exec, "bwrap"))) { info->desktop_id = g_strconcat (id, GMENU_DESKTOPAPPINFO_FLATPAK_SUFFIX, NULL); if (startup_wm_class) { info->startup_wm_class = g_strconcat (startup_wm_class, GMENU_DESKTOPAPPINFO_FLATPAK_SUFFIX, NULL); } // if (g_desktop_app_info_has_key (info->super_appinfo, "X-Flatpak")) // { info->flatpak_app_id = g_desktop_app_info_get_string (info->super_appinfo, "X-Flatpak"); // } info->is_flatpak = TRUE; } else { info->desktop_id = g_strdup (id); info->is_flatpak = FALSE; info->startup_wm_class = g_strdup (startup_wm_class); } } } static GAppInfo * gmenu_desktopappinfo_dup (GAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), NULL); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); GMenuDesktopAppInfo *new_info; new_info = g_object_new (GMENU_TYPE_DESKTOPAPPINFO, NULL); new_info->super_appinfo = G_DESKTOP_APP_INFO (g_app_info_dup(G_APP_INFO(info->super_appinfo))); update_app_data (new_info); return G_APP_INFO (new_info); } static gboolean gmenu_desktopappinfo_equal (GAppInfo *appinfo1, GAppInfo *appinfo2) { GMenuDesktopAppInfo *info1 = GMENU_DESKTOPAPPINFO (appinfo1); GMenuDesktopAppInfo *info2 = GMENU_DESKTOPAPPINFO (appinfo2); if (info1->desktop_id == NULL || info2->desktop_id == NULL) return info1 == info2; return strcmp (info1->desktop_id, info2->desktop_id) == 0; } static const char * gmenu_desktopappinfo_get_id (GAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), NULL); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return info->desktop_id; } static const char * gmenu_desktopappinfo_get_name (GAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), NULL); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_get_name (G_APP_INFO(info->super_appinfo)); } static const char * gmenu_desktopappinfo_get_description (GAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), NULL); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_get_description (G_APP_INFO(info->super_appinfo)); } static const char * gmenu_desktopappinfo_get_executable (GAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), NULL); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_get_executable (G_APP_INFO(info->super_appinfo)); } static GIcon * gmenu_desktopappinfo_get_icon (GAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), NULL); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_get_icon (G_APP_INFO(info->super_appinfo)); } static gboolean gmenu_desktopappinfo_launch (GAppInfo *appinfo, GList *files, GAppLaunchContext *launch_context, GError **error) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), FALSE); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_launch (G_APP_INFO(info->super_appinfo), files, launch_context, error); } static gboolean gmenu_desktopappinfo_supports_uris (GAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), FALSE); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_supports_uris (G_APP_INFO(info->super_appinfo)); } static gboolean gmenu_desktopappinfo_supports_files (GAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), FALSE); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_supports_files (G_APP_INFO(info->super_appinfo)); } static gboolean gmenu_desktopappinfo_launch_uris (GAppInfo *appinfo, GList *uris, GAppLaunchContext *launch_context, GError **error) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), FALSE); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_launch_uris (G_APP_INFO(info->super_appinfo), uris, launch_context, error); } static gboolean gmenu_desktopappinfo_should_show (GAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), FALSE); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_should_show (G_APP_INFO(info->super_appinfo)); } static gboolean gmenu_desktopappinfo_set_as_default_for_type (GAppInfo *appinfo, const char *content_type, GError **error) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), FALSE); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_set_as_default_for_type (G_APP_INFO(info->super_appinfo), content_type, error); } static gboolean gmenu_desktopappinfo_set_as_default_for_extension (GAppInfo *appinfo, const char *extension, GError **error) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), FALSE); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_set_as_default_for_extension (G_APP_INFO(info->super_appinfo), extension, error); } static gboolean gmenu_desktopappinfo_add_supports_type (GAppInfo *appinfo, const char *content_type, GError **error) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), FALSE); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_add_supports_type (G_APP_INFO(info->super_appinfo), content_type, error); } static gboolean gmenu_desktopappinfo_can_remove_supports_type (GAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), FALSE); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_can_remove_supports_type (G_APP_INFO(info->super_appinfo)); } static gboolean gmenu_desktopappinfo_remove_supports_type (GAppInfo *appinfo, const char *content_type, GError **error) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), FALSE); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_remove_supports_type (G_APP_INFO(info->super_appinfo), content_type, error); } static gboolean gmenu_desktopappinfo_can_delete (GAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), FALSE); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_can_delete (G_APP_INFO(info->super_appinfo)); } static gboolean gmenu_desktopappinfo_delete (GAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), FALSE); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_delete (G_APP_INFO(info->super_appinfo)); } static const char * gmenu_desktopappinfo_get_commandline (GAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), NULL); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_get_commandline (G_APP_INFO(info->super_appinfo)); } static const char * gmenu_desktopappinfo_get_display_name (GAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), NULL); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_get_display_name (G_APP_INFO(info->super_appinfo)); } static gboolean gmenu_desktopappinfo_set_as_last_used_for_type (GAppInfo *appinfo, const char *content_type, GError **error) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), FALSE); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_set_as_last_used_for_type (G_APP_INFO(info->super_appinfo), content_type, error); } static const char ** gmenu_desktopappinfo_get_supported_types (GAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), NULL); GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (appinfo); return g_app_info_get_supported_types (G_APP_INFO(info->super_appinfo)); } static void gmenu_desktopappinfo_interface_init (GAppInfoIface *iface) { iface->dup = gmenu_desktopappinfo_dup; iface->equal = gmenu_desktopappinfo_equal; iface->get_id = gmenu_desktopappinfo_get_id; iface->get_name = gmenu_desktopappinfo_get_name; iface->get_description = gmenu_desktopappinfo_get_description; iface->get_executable = gmenu_desktopappinfo_get_executable; iface->get_icon = gmenu_desktopappinfo_get_icon; iface->launch = gmenu_desktopappinfo_launch; iface->supports_uris = gmenu_desktopappinfo_supports_uris; iface->supports_files = gmenu_desktopappinfo_supports_files; iface->launch_uris = gmenu_desktopappinfo_launch_uris; iface->should_show = gmenu_desktopappinfo_should_show; iface->set_as_default_for_type = gmenu_desktopappinfo_set_as_default_for_type; iface->set_as_default_for_extension = gmenu_desktopappinfo_set_as_default_for_extension; iface->add_supports_type = gmenu_desktopappinfo_add_supports_type; iface->can_remove_supports_type = gmenu_desktopappinfo_can_remove_supports_type; iface->remove_supports_type = gmenu_desktopappinfo_remove_supports_type; iface->can_delete = gmenu_desktopappinfo_can_delete; iface->do_delete = gmenu_desktopappinfo_delete; iface->get_commandline = gmenu_desktopappinfo_get_commandline; iface->get_display_name = gmenu_desktopappinfo_get_display_name; iface->set_as_last_used_for_type = gmenu_desktopappinfo_set_as_last_used_for_type; iface->get_supported_types = gmenu_desktopappinfo_get_supported_types; } G_DEFINE_TYPE_WITH_CODE (GMenuDesktopAppInfo, gmenu_desktopappinfo, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_APP_INFO, gmenu_desktopappinfo_interface_init)) static void gmenu_desktopappinfo_init (GMenuDesktopAppInfo *info) { info->super_appinfo = NULL; info->desktop_id = NULL; info->flatpak_app_id = NULL; info->startup_wm_class = NULL; } static void gmenu_desktopappinfo_finalize (GObject *object) { /* TODO: Add deinitalization code here */ GMenuDesktopAppInfo *info = GMENU_DESKTOPAPPINFO (object); g_free (info->desktop_id); g_free (info->startup_wm_class); g_free (info->flatpak_app_id); if (info->super_appinfo) g_object_unref (info->super_appinfo); } static void gmenu_desktopappinfo_class_init (GMenuDesktopAppInfoClass *klass) { GObjectClass* object_class = G_OBJECT_CLASS (klass); object_class->finalize = gmenu_desktopappinfo_finalize; } /** * gmenu_desktopappinfo_new: * @desktop_id: the desktop file id * * This is currently unused in Cinnamon and does not make sense here * because the desktop id as used here is not necessarily unique * * Returns: (nullable): %NULL */ GMenuDesktopAppInfo* gmenu_desktopappinfo_new (const char *desktop_id) { return NULL; } /** * gmenu_desktopappinfo_new_from_filename: * @filename: (type filename): the path of a desktop file, in the GLib * filename encoding * * Creates a new #GMenuDesktopAppInfo. * * Returns: (nullable): a new #GMenuDesktopAppInfo or %NULL on error. **/ GMenuDesktopAppInfo* gmenu_desktopappinfo_new_from_filename (gchar *filename) { GMenuDesktopAppInfo *info = NULL; info = g_object_new (GMENU_TYPE_DESKTOPAPPINFO, NULL); info->super_appinfo = g_desktop_app_info_new_from_filename (filename); if (info->super_appinfo) { update_app_data (info); return info; } else { g_object_unref (info); return NULL; } } /** * gmenu_desktopappinfo_new_from_keyfile: * @key_file: an opened #GKeyFile * * Creates a new #GMenuDesktopAppInfo. * * Returns: (nullable): a new #GMenuDesktopAppInfo or %NULL on error. **/ GMenuDesktopAppInfo* gmenu_desktopappinfo_new_from_keyfile (GKeyFile *keyfile) { GMenuDesktopAppInfo *info = NULL; info = g_object_new (GMENU_TYPE_DESKTOPAPPINFO, NULL); info->super_appinfo = g_desktop_app_info_new_from_keyfile (keyfile); if (info->super_appinfo) { update_app_data (info); return info; } else { g_object_unref (info); return NULL; } } /** * gmenu_desktopappinfo_get_filename: * @appinfo: a #MenuGDesktopAppInfo * * When @info was created from a known filename, return it. In some * situations such as the #GMenuDesktopAppInfo returned from * gmenu_desktopappinfo_new_from_keyfile(), this function will return %NULL. * * Returns: (type filename): The full path to the file for @info, * or %NULL if not known. * Since: 2.24 */ const char * gmenu_desktopappinfo_get_filename (GMenuDesktopAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), NULL); return g_desktop_app_info_get_filename (appinfo->super_appinfo); } /** * gmenu_desktopappinfo_get_generic_name: * @appinfo: a #MenuGDesktopAppInfo * * Gets the generic name from the destkop file. * * Returns: The value of the GenericName key */ const char * gmenu_desktopappinfo_get_generic_name (GMenuDesktopAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), NULL); return g_desktop_app_info_get_generic_name (appinfo->super_appinfo); } /** * gmenu_desktopappinfo_get_categories: * @appinfo: a #GMenuDesktopAppInfo * * Gets the categories from the desktop file. * * Returns: The unparsed Categories key from the desktop file; * i.e. no attempt is made to split it by ';' or validate it. */ const char * gmenu_desktopappinfo_get_categories (GMenuDesktopAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), NULL); return g_desktop_app_info_get_categories (appinfo->super_appinfo); } /** * gmenu_desktopappinfo_get_keywords: * @appinfo: a #GMenuDesktopAppInfo * * Gets the keywords from the desktop file. * * Returns: (transfer none): The value of the Keywords key */ const char * const *gmenu_desktopappinfo_get_keywords (GMenuDesktopAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), NULL); return g_desktop_app_info_get_keywords (appinfo->super_appinfo); } /** * gmenu_desktopappinfo_get_nodisplay: * @appinfo: a #GMenuDesktopAppInfo * * Gets the value of the NoDisplay key, which helps determine if the * application info should be shown in menus. See * #G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY and g_app_info_should_show(). * * Returns: The value of the NoDisplay key */ gboolean gmenu_desktopappinfo_get_nodisplay (GMenuDesktopAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), FALSE); return g_desktop_app_info_get_nodisplay (appinfo->super_appinfo); } /** * gmenu_desktopappinfo_get_show_in: * @appinfo: a #GMenuDesktopAppInfo * @desktop_env: (nullable): a string specifying a desktop name * * Checks if the application info should be shown in menus that list available * applications for a specific name of the desktop, based on the * `OnlyShowIn` and `NotShowIn` keys. * * @desktop_env should typically be given as %NULL, in which case the * `XDG_CURRENT_DESKTOP` environment variable is consulted. If you want * to override the default mechanism then you may specify @desktop_env, * but this is not recommended. * * Note that g_app_info_should_show() for @info will include this check (with * %NULL for @desktop_env) as well as additional checks. * * Returns: %TRUE if the @info should be shown in @desktop_env according to the * `OnlyShowIn` and `NotShowIn` keys, %FALSE * otherwise. */ gboolean gmenu_desktopappinfo_get_show_in (GMenuDesktopAppInfo *appinfo, const gchar *desktop_env) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), FALSE); return g_desktop_app_info_get_show_in (appinfo->super_appinfo, desktop_env); } /** * gmenu_desktopappinfo_get_startup_wm_class: * @appinfo: a #GMenuDesktopAppInfo that supports startup notify * * Retrieves the StartupWMClass field from @info. This represents the * WM_CLASS property of the main window of the application, if launched * through @info. * * Note: The returned value contain the suffix ":flatpak" if @info specifies a flatpak app * and if the desktop file has a StartupWMClass * * Returns: (transfer none): the startup WM class, or %NULL if none is set * in the desktop file. */ const char * gmenu_desktopappinfo_get_startup_wm_class (GMenuDesktopAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), NULL); return appinfo->startup_wm_class; } /** * gmenu_desktopappinfo_get_is_hidden: * @appinfo: a #GMenuDesktopAppInfo. * * A desktop file is hidden if the Hidden key in it is * set to True. * * Returns: %TRUE if hidden, %FALSE otherwise. **/ gboolean gmenu_desktopappinfo_get_is_hidden (GMenuDesktopAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), FALSE); return g_desktop_app_info_get_is_hidden (appinfo->super_appinfo); } /** * gmenu_desktopappinfo_has_key: * @appinfo: a #GMenuDesktopAppInfo * @key: the key to look up * * Returns whether @key exists in the "Desktop Entry" group * of the keyfile backing @info. * * Returns: %TRUE if the @key exists */ gboolean gmenu_desktopappinfo_has_key (GMenuDesktopAppInfo *appinfo, const char *key) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), FALSE); return g_desktop_app_info_has_key (appinfo->super_appinfo, key); } /** * gmenu_desktopappinfo_get_string: * @appinfo: a #GMenuDesktopAppInfo * @key: the key to look up * * Looks up a string value in the keyfile backing @info. * * The @key is looked up in the "Desktop Entry" group. * * Returns: a newly allocated string, or %NULL if the key * is not found */ char * gmenu_desktopappinfo_get_string (GMenuDesktopAppInfo *appinfo, const char *key) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), NULL); return g_desktop_app_info_get_string (appinfo->super_appinfo, key); } /** * gmenu_desktopappinfo_get_locale_string: * @appinfo: a #GMenuDesktopAppInfo * @key: the key to look up * * Looks up a localized string value in the keyfile backing @info * translated to the current locale. * * The @key is looked up in the "Desktop Entry" group. * * Returns: (nullable): a newly allocated string, or %NULL if the key * is not found */ char * gmenu_desktopappinfo_get_locale_string (GMenuDesktopAppInfo *appinfo, const char *key) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), NULL); return g_desktop_app_info_get_locale_string (appinfo->super_appinfo, key); } /** * gmenu_desktopappinfo_get_boolean: * @appinfo: a #GMenuDesktopAppInfo * @key: the key to look up * * Looks up a boolean value in the keyfile backing @info. * * The @key is looked up in the "Desktop Entry" group. * * Returns: the boolean value, or %FALSE if the key * is not found */ gboolean gmenu_desktopappinfo_get_boolean (GMenuDesktopAppInfo *appinfo, const char *key) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), FALSE); return g_desktop_app_info_get_boolean (appinfo->super_appinfo, key); } /** * gmenu_desktopappinfo_list_actions: * @appinfo: a #GMenuDesktopAppInfo * * Returns the list of "additional application actions" supported on the * desktop file, as per the desktop file specification. * * As per the specification, this is the list of actions that are * explicitly listed in the "Actions" key of the [Desktop Entry] group. * * Returns: (array zero-terminated=1) (element-type utf8) (transfer none): a list of strings, always non-%NULL **/ const gchar * const * gmenu_desktopappinfo_list_actions (GMenuDesktopAppInfo *appinfo) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), NULL); return g_desktop_app_info_list_actions (appinfo->super_appinfo); } /** * gmenu_desktopappinfo_launch_action: * @appinfo: a #GMenuDesktopAppInfo * @action_name: the name of the action as from * g_desktop_app_info_list_actions() * @launch_context: (nullable): a #GAppLaunchContext * * Activates the named application action. * * You may only call this function on action names that were * returned from g_desktop_app_info_list_actions(). * * Note that if the main entry of the desktop file indicates that the * application supports startup notification, and @launch_context is * non-%NULL, then startup notification will be used when activating the * action (and as such, invocation of the action on the receiving side * must signal the end of startup notification when it is completed). * This is the expected behaviour of applications declaring additional * actions, as per the desktop file specification. * * As with g_app_info_launch() there is no way to detect failures that * occur while using this function. */ void gmenu_desktopappinfo_launch_action (GMenuDesktopAppInfo *appinfo, const gchar *action_name, GAppLaunchContext *launch_context) { g_return_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo)); g_desktop_app_info_launch_action (appinfo->super_appinfo, action_name, launch_context); } /** * gmenu_desktopappinfo_get_action_name: * @appinfo: a #GMenuDesktopAppInfo * @action_name: the name of the action as from * gmenu_desktopappinfo_list_actions() * * Gets the user-visible display name of the "additional application * action" specified by @action_name. * * This corresponds to the "Name" key within the keyfile group for the * action. * * Returns: (transfer full): the locale-specific action name * * Since: 2.38 */ gchar * gmenu_desktopappinfo_get_action_name (GMenuDesktopAppInfo *appinfo, const gchar *action_name) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), NULL); return g_desktop_app_info_get_action_name (appinfo->super_appinfo, action_name); } /** * gmenu_desktopappinfo_launch_uris_as_manager: * @appinfo: a #GMenuDesktopAppInfo * @uris: (element-type utf8): List of URIs * @launch_context: (nullable): a #GAppLaunchContext * @spawn_flags: #GSpawnFlags, used for each process * @user_setup: (scope async) (nullable): a #GSpawnChildSetupFunc, used once * for each process. * @user_setup_data: (closure user_setup) (nullable): User data for @user_setup * @pid_callback: (scope call) (nullable): Callback for child processes * @pid_callback_data: (closure pid_callback) (nullable): User data for @callback * @error: return location for a #GError, or %NULL * * This function performs the equivalent of g_app_info_launch_uris(), * but is intended primarily for operating system components that * launch applications. Ordinary applications should use * g_app_info_launch_uris(). * * If the application is launched via GSpawn, then @spawn_flags, @user_setup * and @user_setup_data are used for the call to g_spawn_async(). * Additionally, @pid_callback (with @pid_callback_data) will be called to * inform about the PID of the created process. See g_spawn_async_with_pipes() * for information on certain parameter conditions that can enable an * optimized posix_spawn() codepath to be used. * * If application launching occurs via some other mechanism (eg: D-Bus * activation) then @spawn_flags, @user_setup, @user_setup_data, * @pid_callback and @pid_callback_data are ignored. * * Returns: %TRUE on successful launch, %FALSE otherwise. */ gboolean gmenu_desktopappinfo_launch_uris_as_manager (GMenuDesktopAppInfo *appinfo, GList *uris, GAppLaunchContext *launch_context, GSpawnFlags spawn_flags, GSpawnChildSetupFunc user_setup, gpointer user_setup_data, GDesktopAppLaunchCallback pid_callback, gpointer pid_callback_data, GError **error) { g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (appinfo), FALSE); return g_desktop_app_info_launch_uris_as_manager (appinfo->super_appinfo, uris, launch_context, spawn_flags, user_setup, user_setup_data, pid_callback, pid_callback_data, error); } /** * gmenu_desktopappinfo_get_is_flatpak: * @appinfo: a #GMenuMenuDesktopAppInfo * * Returns: %TRUE if @info specifies a flatpak app, %FALSE otherwise */ gboolean gmenu_desktopappinfo_get_is_flatpak (GMenuDesktopAppInfo *appinfo) { return appinfo->is_flatpak; } /** * gmenu_desktopappinfo_get_flatpak_app_id: * @appinfo: a #GMenuMenuDesktopAppInfo * * This function looks up the "X-Flatpak" key of the [Desktop Entry] group, * which contains the Flatpak App ID * * Returns: (nullable): the flatpak app id or %NULL */ const char * gmenu_desktopappinfo_get_flatpak_app_id (GMenuDesktopAppInfo *appinfo) { return appinfo->flatpak_app_id; } cinnamon-menus-6.2.0/libmenu/entry-directories.h0000664000175000017500000000623214632057634020666 0ustar fabiofabio/* * 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_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__ */ cinnamon-menus-6.2.0/libmenu/menu-monitor.c0000664000175000017500000002413714632057634017643 0ustar fabiofabio/* * 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 "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) { g_source_remove (events_idle_handler); } events_idle_handler = g_timeout_add (100, (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; } } cinnamon-menus-6.2.0/libmenu/menu-util.c0000664000175000017500000003000514632057634017120 0ustar fabiofabio/* 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 "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 */ cinnamon-menus-6.2.0/libmenu/menu-layout.h0000664000175000017500000001445014632057634017473 0ustar fabiofabio/* 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__ */ cinnamon-menus-6.2.0/NEWS0000664000175000017500000011023314632057634014103 0ustar fabiofabio============= 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) cinnamon-menus-6.2.0/COPYING.LIB0000664000175000017500000006144714632057634015060 0ustar fabiofabio 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! cinnamon-menus-6.2.0/meson_options.txt0000664000175000017500000000051514632057634017042 0ustar fabiofabiooption('deprecated_warnings', type: 'boolean', value: true, description: 'Show build warnings for deprecations' ) option('enable_debug', type: 'boolean', value: true, description: 'Enable debugging' ) option('enable_docs', type: 'boolean', value: false, description: 'Build the API references (requires gtk-doc)' )