Merging upstream version 1.0.2.
[debian/menu-cache.git] / menu-cache-gen / menu-merge.c
CommitLineData
2f7ba096
AG
1/*
2 * menu-file.c : parses <name>.menu file and merges all XML tags.
3 *
b94f3144 4 * Copyright 2013-2016 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
2f7ba096
AG
5 *
6 * This file is a part of libmenu-cache package and created program
7 * should be not used without the library.
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 */
23
24#ifdef HAVE_CONFIG_H
25#include <config.h>
26#endif
27
28#include "menu-tags.h"
29
30#include <string.h>
31#include <stdlib.h>
32#include <gio/gio.h>
33
34#define _(...) __VA_ARGS__
35
36/* ---- applications.menu manipulations ---- */
37typedef struct _MenuTreeData MenuTreeData;
38
39struct _MenuTreeData
40{
41 FmXmlFile *menu; /* composite tree to analyze */
42 const char *file_path; /* current file */
43 gint line, pos; /* we remember position in deepest file */
44};
45
46FmXmlFileTag menuTag_Menu = 0; /* tags that are supported */
47FmXmlFileTag menuTag_Include = 0;
48FmXmlFileTag menuTag_Exclude = 0;
49FmXmlFileTag menuTag_Filename = 0;
50FmXmlFileTag menuTag_Or = 0;
51FmXmlFileTag menuTag_And = 0;
52FmXmlFileTag menuTag_Not = 0;
53FmXmlFileTag menuTag_Category = 0;
54FmXmlFileTag menuTag_MergeFile = 0;
55FmXmlFileTag menuTag_MergeDir = 0;
56FmXmlFileTag menuTag_DefaultMergeDirs = 0;
57FmXmlFileTag menuTag_Directory = 0;
58FmXmlFileTag menuTag_Name = 0;
59FmXmlFileTag menuTag_Deleted = 0;
60FmXmlFileTag menuTag_NotDeleted = 0;
61FmXmlFileTag menuTag_AppDir = 0;
62FmXmlFileTag menuTag_DefaultAppDirs = 0;
63FmXmlFileTag menuTag_DirectoryDir = 0;
64FmXmlFileTag menuTag_DefaultDirectoryDirs = 0;
65FmXmlFileTag menuTag_OnlyUnallocated = 0;
66FmXmlFileTag menuTag_NotOnlyUnallocated = 0;
67FmXmlFileTag menuTag_All = 0;
68FmXmlFileTag menuTag_LegacyDir = 0;
69FmXmlFileTag menuTag_KDELegacyDirs = 0;
70FmXmlFileTag menuTag_Move = 0;
71FmXmlFileTag menuTag_Old = 0;
72FmXmlFileTag menuTag_New = 0;
73FmXmlFileTag menuTag_Layout = 0;
74FmXmlFileTag menuTag_DefaultLayout = 0;
75FmXmlFileTag menuTag_Menuname = 0;
76FmXmlFileTag menuTag_Separator = 0;
77FmXmlFileTag menuTag_Merge = 0;
78
79/* list of available app dirs */
80GSList *AppDirs = NULL;
81
82/* list of available dir dirs */
83GSList *DirDirs = NULL;
84
85/* list of menu dirs to monitor */
86GSList *MenuDirs = NULL;
87
88/* we keep all the unfinished items in the hash */
89static GHashTable *layout_hash = NULL;
90
91#define RETURN_TRUE_AND_DESTROY_IF_QUIET(a) do { \
92 if (verbose == 0) { \
93 fm_xml_file_item_destroy(a); \
94 return TRUE; \
95 } } while (0)
96
97static gboolean _fail_if_in_layout(FmXmlFileItem *item, GError **error)
98{
99 FmXmlFileItem *parent;
100
101 parent = fm_xml_file_item_get_parent(item);
102 if (parent && (fm_xml_file_item_get_tag(parent) == menuTag_Layout ||
103 fm_xml_file_item_get_tag(parent) == menuTag_DefaultLayout))
104 {
105 g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
106 _("Tag <%s> is invalid below <%s>"),
107 fm_xml_file_item_get_tag_name(item),
108 fm_xml_file_item_get_tag_name(parent));
109 return TRUE;
110 }
111 return FALSE;
112}
113
114#define RETURN_IF_IN_LAYOUT(i,e) do { \
115 if (_fail_if_in_layout(i, e)) { \
116 if (verbose > 0) \
117 return FALSE; \
118 fm_xml_file_item_destroy(i); \
119 g_clear_error(e); \
120 return TRUE; \
121 } } while (0)
122
123/* this handler does nothing, used just to remember its id */
124static gboolean _menu_xml_handler_pass(FmXmlFileItem *item, GList *children,
125 char * const *attribute_names,
126 char * const *attribute_values,
127 guint n_attributes, gint line, gint pos,
128 GError **error, gpointer user_data)
129{
130 RETURN_IF_IN_LAYOUT(item, error);
131 return TRUE;
132}
133
134/* checks the tag */
135static gboolean _menu_xml_handler_Name(FmXmlFileItem *item, GList *children,
136 char * const *attribute_names,
137 char * const *attribute_values,
138 guint n_attributes, gint line, gint pos,
139 GError **error, gpointer user_data)
140{
141 const char *name;
142
143 RETURN_IF_IN_LAYOUT(item, error);
144 item = fm_xml_file_item_find_child(item, FM_XML_FILE_TEXT);
145 if (item == NULL || (name = fm_xml_file_item_get_data(item, NULL)) == NULL ||
146 strchr(name, '/') != NULL) /* empty or invalid tag */
147 {
148 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
149 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
150 _("Invalid <Name> tag"));
151 return FALSE;
152 }
153 return TRUE;
154}
155
156static gboolean _menu_xml_handler_Not(FmXmlFileItem *item, GList *children,
157 char * const *attribute_names,
158 char * const *attribute_values,
159 guint n_attributes, gint line, gint pos,
160 GError **error, gpointer user_data)
161{
162 FmXmlFileTag tag;
163 GList *child;
164
165 RETURN_IF_IN_LAYOUT(item, error);
166 if (verbose > 0) for (child = children; child; child = child->next)
167 {
168 tag = fm_xml_file_item_get_tag(child->data);
169 if (tag != menuTag_And && tag == menuTag_Or && tag == menuTag_Filename &&
170 tag != menuTag_Category && tag != menuTag_All)
171 {
172 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
173 _("Tag <Not> may contain only <And>, <Or>,"
174 " <Filename>, <Category> or <All> child"));
175 return FALSE;
176 }
177 }
178 return TRUE;
179}
180
181static void _add_app_dir(const char *app_dir)
182{
183 const char *str = g_intern_string(app_dir);
184 GSList *l;
185 GDir *dir;
186 char *path;
187
188 for (l = AppDirs; l; l = l->next)
189 if (l->data == str)
190 break;
191 if (l == NULL)
192 AppDirs = g_slist_append(AppDirs, (gpointer)str);
193 /* recursively scan the directory now */
194 dir = g_dir_open(app_dir, 0, NULL);
195 if (dir)
196 {
197 while ((str = g_dir_read_name(dir)) != NULL) /* reuse pointer */
198 {
199 path = g_build_filename(app_dir, str, NULL);
200 if (g_file_test(path, G_FILE_TEST_IS_DIR))
201 _add_app_dir(path);
202 g_free(path);
203 }
204 g_dir_close(dir);
205 }
206}
207
208static gboolean _menu_xml_handler_AppDir(FmXmlFileItem *item, GList *children,
209 char * const *attribute_names,
210 char * const *attribute_values,
211 guint n_attributes, gint line, gint pos,
212 GError **error, gpointer user_data)
213{
214 MenuTreeData *data = user_data;
215 FmXmlFileItem *parent, *name;
216 const char *path;
217 char *_path;
218
219 parent = fm_xml_file_item_get_parent(item);
220 if (parent == NULL || fm_xml_file_item_get_tag(parent) != menuTag_Menu)
221 {
222 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
223 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
224 _("Tag <AppDir> can appear only below <Menu>"));
225 return FALSE;
226 }
227 if (children == NULL ||
228 fm_xml_file_item_get_tag((name = children->data)) != FM_XML_FILE_TEXT ||
229 (path = fm_xml_file_item_get_data(name, NULL)) == NULL)
230 {
231 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
232 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
233 _("Invalid <AppDir> tag"));
234 return FALSE;
235 }
236 if (g_path_is_absolute(path))
237 _path = NULL;
238 else
239 {
240 char *_dir = g_path_get_dirname(data->file_path);
241 path = _path = g_build_filename(_dir, path, NULL);
242 g_free(_dir);
243 /* FIXME: canonicalize path */
244 fm_xml_file_item_destroy(name);
245 fm_xml_file_item_append_text(item, _path, -1, FALSE);
246 }
247 _add_app_dir(path);
248 g_free(_path);
249 /* contents of the directory will be parsed later */
250 return TRUE;
251}
252
253static gboolean _menu_xml_handler_DefaultAppDirs(FmXmlFileItem *item, GList *children,
254 char * const *attribute_names,
255 char * const *attribute_values,
256 guint n_attributes, gint line, gint pos,
257 GError **error, gpointer user_data)
258{
259 FmXmlFileItem *parent;
260 static gboolean added = FALSE;
261
262 parent = fm_xml_file_item_get_parent(item);
263 if (parent == NULL || fm_xml_file_item_get_tag(parent) != menuTag_Menu)
264 {
265 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
266 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
267 _("Tag <DefaultAppDirs> can appear only below <Menu>"));
268 return FALSE;
269 }
270 if (!added)
271 {
272 const gchar* const * dirs = g_get_system_data_dirs();
273 char *dir = g_build_filename(g_get_user_data_dir(), "applications", NULL);
274 _add_app_dir(dir);
275 g_free(dir);
276 if (dirs) while (dirs[0] != NULL)
277 {
278 dir = g_build_filename(*dirs++, "applications", NULL);
279 _add_app_dir(dir);
280 g_free(dir);
281 }
282 added = TRUE;
283 }
284 /* contents of the directories will be parsed later */
285 return TRUE;
286}
287
288static void _add_dir_dir(const char *dir_dir)
289{
290 const char *str = g_intern_string(dir_dir);
291 GSList *l;
292
293 for (l = DirDirs; l; l = l->next)
294 if (l->data == str)
295 return;
296 DirDirs = g_slist_append(DirDirs, (gpointer)str);
297}
298
299static gboolean _menu_xml_handler_DirectoryDir(FmXmlFileItem *item, GList *children,
300 char * const *attribute_names,
301 char * const *attribute_values,
302 guint n_attributes, gint line, gint pos,
303 GError **error, gpointer user_data)
304{
305 MenuTreeData *data = user_data;
306 FmXmlFileItem *parent, *name;
307 const char *path;
308
309 parent = fm_xml_file_item_get_parent(item);
310 if (parent == NULL || fm_xml_file_item_get_tag(parent) != menuTag_Menu)
311 {
312 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
313 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
314 _("Tag <DirectoryDir> can appear only below <Menu>"));
315 return FALSE;
316 }
317 if (children == NULL ||
318 fm_xml_file_item_get_tag((name = children->data)) != FM_XML_FILE_TEXT ||
319 (path = fm_xml_file_item_get_data(name, NULL)) == NULL)
320 {
321 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
322 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
323 _("Invalid <DirectoryDir> tag"));
324 return FALSE;
325 }
326 if (g_path_is_absolute(path))
327 _add_dir_dir(path);
328 else
329 {
330 char *_dir = g_path_get_dirname(data->file_path);
331 char *_path = g_build_filename(_dir, path, NULL);
332
333 g_free(_dir);
334 fm_xml_file_item_destroy(name);
335 fm_xml_file_item_append_text(item, _path, -1, FALSE);
336 _add_dir_dir(_path);
337 g_free(_path);
338 }
339 /* contents of the directory will be parsed later */
340 return TRUE;
341}
342
343static gboolean _menu_xml_handler_DefaultDirectoryDirs(FmXmlFileItem *item, GList *children,
344 char * const *attribute_names,
345 char * const *attribute_values,
346 guint n_attributes, gint line, gint pos,
347 GError **error, gpointer user_data)
348{
349 FmXmlFileItem *parent;
350 static gboolean added = FALSE;
351
352 parent = fm_xml_file_item_get_parent(item);
353 if (parent == NULL || fm_xml_file_item_get_tag(parent) != menuTag_Menu)
354 {
355 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
356 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
357 _("Tag <DefaultDirectoryDirs> can appear only below <Menu>"));
358 return FALSE;
359 }
360 if (!added)
361 {
362 const gchar* const * dirs = g_get_system_data_dirs();
363 char *dir = g_build_filename(g_get_user_data_dir(), "desktop-directories", NULL);
364 _add_dir_dir(dir);
365 g_free(dir);
366 if (dirs) while (dirs[0] != NULL)
367 {
368 dir = g_build_filename(*dirs++, "desktop-directories", NULL);
369 _add_dir_dir(dir);
370 g_free(dir);
371 }
372 added = TRUE;
373 }
374 /* contents of the directories will be parsed later */
375 return TRUE;
376}
377
378/* adds .menu file contents next to current item */
379static gboolean _menu_xml_handler_MergeFile(FmXmlFileItem *item, GList *children,
380 char * const *attribute_names,
381 char * const *attribute_values,
382 guint n_attributes, gint line, gint pos,
383 GError **error, gpointer user_data)
384{
385 MenuTreeData *data = user_data;
386 FmXmlFileItem *name;
387 const char *path;
388
389 name = fm_xml_file_item_get_parent(item);
390 if (name == NULL || fm_xml_file_item_get_tag(name) != menuTag_Menu)
391 {
392 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
393 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
394 _("Tag <MergeFile> can appear only below <Menu>"));
395 return FALSE;
396 }
397 if (children == NULL ||
398 fm_xml_file_item_get_tag((name = children->data)) != FM_XML_FILE_TEXT ||
399 (path = fm_xml_file_item_get_data(name, NULL)) == NULL)
400 {
401 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
402 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
403 _("Invalid <MergeFile> tag"));
404 return FALSE;
405 }
406 if (attribute_names) while (attribute_names[0])
407 {
408 if (strcmp(attribute_names[0], "type") == 0)
409 {
410 if (strcmp(attribute_values[0], "parent") == 0)
411 {
412 const gchar* const *dirs = g_get_system_config_dirs();
413 const gchar* const *dir;
414 const char *rel_path;
415 char *file;
416
417 /* scan whole config dirs for matching, ignoring current path */
418 for (dir = dirs; dir[0]; dir++)
419 if (g_str_has_prefix(data->file_path, dir[0]))
420 {
421 rel_path = data->file_path + strlen(dir[0]);
422 goto replace_from_system_config_dirs;
423 }
424 /* not found in XDG_CONFIG_DIRS, test for XDG_CONFIG_HOME */
425 if (g_str_has_prefix(data->file_path, g_get_user_config_dir()))
426 {
427 rel_path = data->file_path + strlen(g_get_user_config_dir());
428replace_from_system_config_dirs:
429 fm_xml_file_item_destroy(name);
430 while (*rel_path == G_DIR_SEPARATOR) rel_path++;
431 while (dirs[0] != NULL)
432 {
433 if (dirs[0] == dir[0])
434 continue;
435 file = g_build_filename(dirs[0], rel_path, NULL);
436 if (g_file_test(file, G_FILE_TEST_IS_REGULAR))
437 {
438 fm_xml_file_item_append_text(item, file, -1, FALSE);
439 g_free(file);
440 break;
441 }
442 g_free(file);
443 dirs++;
444 }
b94f3144
AG
445 if (dirs[0] != NULL) /* a file for merge was found */
446 return TRUE;
2f7ba096
AG
447 }
448 /* FIXME: what to do if parsed file is not in some config dirs? */
b94f3144
AG
449 VDBG("No file for <MergeFile type=\"parent\"/> found, ignoring");
450 fm_xml_file_item_destroy(item);
451 return TRUE;
2f7ba096
AG
452 }
453 break;
454 }
455 attribute_names++;
456 attribute_values++;
457 }
458 if (!g_path_is_absolute(path))
459 {
460 char *_dir = g_path_get_dirname(data->file_path);
461 char *_path = g_build_filename(_dir, path, NULL);
462
463 g_free(_dir);
464 fm_xml_file_item_destroy(name);
465 fm_xml_file_item_append_text(item, _path, -1, FALSE);
466 g_free(_path);
467 }
468 /* actual merge will be done in next stage */
469 return TRUE;
470}
471
472/* adds all .menu files in directory */
473static gboolean _menu_xml_handler_MergeDir(FmXmlFileItem *item, GList *children,
474 char * const *attribute_names,
475 char * const *attribute_values,
476 guint n_attributes, gint line, gint pos,
477 GError **error, gpointer user_data)
478{
479 MenuTreeData *data = user_data;
480 FmXmlFileItem *name;
481 const char *path;
482
483 name = fm_xml_file_item_get_parent(item);
484 if (name == NULL || fm_xml_file_item_get_tag(name) != menuTag_Menu)
485 {
486 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
487 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
488 _("Tag <MergeDir> can appear only below <Menu>"));
489 return FALSE;
490 }
491 if (children == NULL ||
492 fm_xml_file_item_get_tag((name = children->data)) != FM_XML_FILE_TEXT ||
493 (path = fm_xml_file_item_get_data(name, NULL)) == NULL)
494 {
495 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
496 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
497 _("Invalid <MergeDir> tag"));
498 return FALSE;
499 }
500 if (!g_path_is_absolute(path))
501 {
502 char *_dir = g_path_get_dirname(data->file_path);
503 char *_path = g_build_filename(_dir, path, NULL);
504
505 g_free(_dir);
506 fm_xml_file_item_destroy(name);
507 fm_xml_file_item_append_text(item, _path, -1, FALSE);
508 g_free(_path);
509 }
510 /* actual merge will be done in next stage */
511 return TRUE;
512}
513
514/* used for validating DefaultMergeDirs and KDELegacyDirs */
515static gboolean _menu_xml_handler_DefaultMergeDirs(FmXmlFileItem *item, GList *children,
516 char * const *attribute_names,
517 char * const *attribute_values,
518 guint n_attributes, gint line, gint pos,
519 GError **error, gpointer user_data)
520{
521 FmXmlFileItem *parent = fm_xml_file_item_get_parent(item);
522
523 if (parent == NULL || fm_xml_file_item_get_tag(parent) != menuTag_Menu)
524 {
525 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
526 g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
527 _("Tag <%s> can appear only below <Menu>"),
528 fm_xml_file_item_get_tag_name(item));
529 return FALSE;
530 }
531 /* actual merge will be done in next stage */
532 return TRUE;
533}
534
535static gboolean _menu_xml_handler_LegacyDir(FmXmlFileItem *item, GList *children,
536 char * const *attribute_names,
537 char * const *attribute_values,
538 guint n_attributes, gint line, gint pos,
539 GError **error, gpointer user_data)
540{
541 MenuTreeData *data = user_data;
542 FmXmlFileItem *name;
543 const char *path;
544
545 name = fm_xml_file_item_get_parent(item);
546 if (name == NULL || fm_xml_file_item_get_tag(name) != menuTag_Menu)
547 {
548 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
549 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
550 _("Tag <LegacyDir> can appear only below <Menu>"));
551 return FALSE;
552 }
553 if (children == NULL ||
554 fm_xml_file_item_get_tag((name = children->data)) != FM_XML_FILE_TEXT ||
555 (path = fm_xml_file_item_get_data(name, NULL)) == NULL)
556 {
557 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
558 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
559 _("Invalid <LegacyDir> tag"));
560 return FALSE;
561 }
562 if (!g_path_is_absolute(path))
563 {
564 char *_dir = g_path_get_dirname(data->file_path);
565 char *_path = g_build_filename(_dir, path, NULL);
566
567 g_free(_dir);
568 fm_xml_file_item_destroy(name);
569 fm_xml_file_item_append_text(item, _path, -1, FALSE);
570 g_free(_path);
571 }
572 /* handle "prefix" attribute! */
573 path = 0;
574 if (attribute_names) while (attribute_names[0])
575 {
576 if (strcmp(attribute_names[0], "prefix") == 0)
577 path = attribute_values[0];
578 attribute_names++;
579 attribute_values++;
580 }
581 fm_xml_file_item_set_comment(item, path);
582 /* actual merge will be done in next stage */
583 return TRUE;
584}
585
586static MenuLayout *_find_layout(FmXmlFileItem *item, gboolean create)
587{
588 MenuLayout *layout = g_hash_table_lookup(layout_hash, item);
589
590 if (layout == NULL && create)
591 {
592 layout = g_slice_new0(MenuLayout);
593 /* set defaults */
594 layout->inline_header = TRUE;
595 layout->inline_limit = 4;
596 g_hash_table_insert(layout_hash, item, layout);
597 }
598 return layout;
599}
600
601static gboolean _menu_xml_handler_Filename(FmXmlFileItem *item, GList *children,
602 char * const *attribute_names,
603 char * const *attribute_values,
604 guint n_attributes, gint line, gint pos,
605 GError **error, gpointer user_data)
606{
607 FmXmlFileItem *parent;
608 const char *id;
609 MenuLayout *layout;
610 MenuFilename *app;
611 FmXmlFileTag tag = 0;
612
613 if (children == NULL ||
614 fm_xml_file_item_get_tag(children->data) != FM_XML_FILE_TEXT ||
615 (id = fm_xml_file_item_get_data(children->data, NULL)) == NULL)
616 {
617 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
618 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
619 _("Empty <Filename> tag"));
620 return FALSE;
621 }
622 parent = fm_xml_file_item_get_parent(item);
623 if (parent)
624 tag = fm_xml_file_item_get_tag(parent);
625 if (tag == menuTag_Layout || tag == menuTag_DefaultLayout)
626 {
627 layout = _find_layout(parent, TRUE);
628 app = g_slice_new0(MenuFilename);
629 app->type = MENU_CACHE_TYPE_APP;
630 app->id = g_strdup(id);
631 layout->items = g_list_append(layout->items, app);
632 }
633 return TRUE;
634}
635
636static gboolean _menu_xml_handler_Menuname(FmXmlFileItem *item, GList *children,
637 char * const *attribute_names,
638 char * const *attribute_values,
639 guint n_attributes, gint line, gint pos,
640 GError **error, gpointer user_data)
641{
642 FmXmlFileItem *parent;
643 const char *name;
644 MenuLayout *layout;
645 MenuMenuname *menu;
646 FmXmlFileTag tag = 0;
647
648 if (children == NULL ||
649 fm_xml_file_item_get_tag(children->data) != FM_XML_FILE_TEXT ||
650 (name = fm_xml_file_item_get_data(children->data, NULL)) == NULL)
651 {
652 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
653 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
654 _("Empty <Menuname> tag"));
655 return FALSE;
656 }
657 parent = fm_xml_file_item_get_parent(item);
658 if (parent)
659 tag = fm_xml_file_item_get_tag(parent);
660 if (tag != menuTag_Layout && tag != menuTag_DefaultLayout)
661 {
662 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
663 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
664 _("Tag <Menuname> may only appear below <Layout> or"
665 " <DefaultLayout>"));
666 return FALSE;
667 }
668 layout = _find_layout(parent, TRUE);
669 menu = g_slice_new0(MenuMenuname);
670 menu->layout.type = MENU_CACHE_TYPE_DIR;
671 menu->name = g_strdup(name);
672 if (attribute_names) while (attribute_names[0])
673 {
674 if (strcmp(attribute_names[0], "show_empty") == 0)
675 {
676 menu->layout.show_empty = (g_ascii_strcasecmp(attribute_values[0],
677 "true") == 0);
678 menu->layout.only_unallocated = TRUE;
679 }
680 else if (strcmp(attribute_names[0], "inline") == 0)
681 {
682 menu->layout.allow_inline = (g_ascii_strcasecmp(attribute_values[0],
683 "true") == 0);
684 menu->layout.is_set = TRUE;
685 }
686 else if (strcmp(attribute_names[0], "inline_header") == 0)
687 {
688 menu->layout.inline_header = (g_ascii_strcasecmp(attribute_values[0],
689 "true") == 0);
690 menu->layout.inline_header_is_set = TRUE;
691 }
692 else if (strcmp(attribute_names[0], "inline_alias") == 0)
693 {
694 menu->layout.inline_alias = (g_ascii_strcasecmp(attribute_values[0],
695 "true") == 0);
696 menu->layout.inline_alias_is_set = TRUE;
697 }
698 else if (strcmp(attribute_names[0], "inline_limit") == 0)
699 {
700 menu->layout.inline_limit = atoi(attribute_values[0]);
701 menu->layout.inline_limit_is_set = TRUE;
702 }
703 attribute_names++;
704 attribute_values++;
705 }
706 layout->items = g_list_append(layout->items, menu);
707 return TRUE;
708}
709
710static gboolean _menu_xml_handler_Separator(FmXmlFileItem *item, GList *children,
711 char * const *attribute_names,
712 char * const *attribute_values,
713 guint n_attributes, gint line, gint pos,
714 GError **error, gpointer user_data)
715{
716 FmXmlFileItem *parent;
717 MenuLayout *layout;
718 MenuSep *sep;
719 FmXmlFileTag tag = 0;
720
721 parent = fm_xml_file_item_get_parent(item);
722 if (parent)
723 tag = fm_xml_file_item_get_tag(parent);
724 if (tag != menuTag_Layout && tag != menuTag_DefaultLayout)
725 {
726 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
727 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
728 _("Tag <Separator> may only appear below <Layout> or"
729 " <DefaultLayout>"));
730 return FALSE;
731 }
732 layout = _find_layout(parent, TRUE);
733 sep = g_slice_new0(MenuSep);
734 sep->type = MENU_CACHE_TYPE_SEP;
735 layout->items = g_list_append(layout->items, sep);
736 return TRUE;
737}
738
739static gboolean _menu_xml_handler_Merge(FmXmlFileItem *item, GList *children,
740 char * const *attribute_names,
741 char * const *attribute_values,
742 guint n_attributes, gint line, gint pos,
743 GError **error, gpointer user_data)
744{
745 FmXmlFileItem *parent;
746 MenuLayout *layout;
747 MenuMerge *mm;
748 FmXmlFileTag tag = 0;
749 MenuMergeType type = MERGE_NONE;
750
751 parent = fm_xml_file_item_get_parent(item);
752 if (parent)
753 tag = fm_xml_file_item_get_tag(parent);
754 if (tag != menuTag_Layout && tag != menuTag_DefaultLayout)
755 {
756 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
757 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
758 _("Tag <Merge> may only appear below <Layout> or"
759 " <DefaultLayout>"));
760 return FALSE;
761 }
762 if (attribute_names) while (attribute_names[0])
763 {
764 if (strcmp(attribute_names[0], "type") == 0)
765 {
766 if (strcmp(attribute_values[0], "menus") == 0)
767 type = MERGE_MENUS;
768 else if (strcmp(attribute_values[0], "files") == 0)
769 type = MERGE_FILES;
770 else if (strcmp(attribute_values[0], "all") == 0)
771 type = MERGE_ALL;
772 break;
773 }
774 attribute_names++;
775 attribute_values++;
776 }
777 if (type == MERGE_NONE)
778 {
779 RETURN_TRUE_AND_DESTROY_IF_QUIET(item);
780 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
781 _("Tag <Merge> should have attribute 'type' as"
782 " \"menus\", \"files\", or \"all\""));
783 return FALSE;
784 }
785 layout = _find_layout(parent, TRUE);
786 mm = g_slice_new0(MenuMerge);
787 mm->type = MENU_CACHE_TYPE_NONE;
788 mm->merge_type = type;
789 layout->items = g_list_append(layout->items, mm);
790 return TRUE;
791}
792
793static gboolean _menu_xml_handler_Layout(FmXmlFileItem *item, GList *children,
794 char * const *attribute_names,
795 char * const *attribute_values,
796 guint n_attributes, gint line, gint pos,
797 GError **error, gpointer user_data)
798{
799 /* ignore empty layout */
800 return TRUE;
801}
802
803static gboolean _menu_xml_handler_DefaultLayout(FmXmlFileItem *item, GList *children,
804 char * const *attribute_names,
805 char * const *attribute_values,
806 guint n_attributes, gint line, gint pos,
807 GError **error, gpointer user_data)
808{
809 MenuLayout *layout;
810
811 layout = _find_layout(item, TRUE);
812 if (attribute_names) while (attribute_names[0])
813 {
814 if (strcmp(attribute_names[0], "show_empty") == 0)
815 layout->show_empty = (g_ascii_strcasecmp(attribute_values[0],
816 "true") == 0);
817 else if (strcmp(attribute_names[0], "inline") == 0)
818 layout->allow_inline = (g_ascii_strcasecmp(attribute_values[0],
819 "true") == 0);
820 else if (strcmp(attribute_names[0], "inline_header") == 0)
821 layout->inline_header = (g_ascii_strcasecmp(attribute_values[0],
822 "true") == 0);
823 else if (strcmp(attribute_names[0], "inline_alias") == 0)
824 layout->inline_alias = (g_ascii_strcasecmp(attribute_values[0],
825 "true") == 0);
826 else if (strcmp(attribute_names[0], "inline_limit") == 0)
827 layout->inline_limit = atoi(attribute_values[0]);
828 attribute_names++;
829 attribute_values++;
830 }
831 return TRUE;
832}
833
834static gboolean _merge_xml_file(MenuTreeData *data, FmXmlFileItem *item,
835 const char *path, GList **m, GError **error,
836 gboolean add_to_list)
837{
838 FmXmlFile *menu = NULL;
839 GList *xml = NULL, *it; /* loaded list */
840 GFile *gf;
841 GError *err = NULL;
842 const char *save_path;
843 char *contents;
844 gsize len;
845 gboolean ok;
846
847 /* check for loops! */
848 path = g_intern_string(path);
849 it = *m;
850 if (g_list_find(it, path) != NULL)
851 {
852 g_critical("merging loop detected for file '%s'", path);
853 return TRUE;
854 }
855 *m = g_list_prepend(it, (gpointer)path);
856 if (add_to_list && g_slist_find(MenuFiles, path) == NULL)
857 MenuFiles = g_slist_append(MenuFiles, (gpointer)path);
858 save_path = data->file_path;
859 data->file_path = path;
860 DBG("merging the XML file '%s'", data->file_path);
861 gf = g_file_new_for_path(data->file_path);
862 ok = g_file_load_contents(gf, NULL, &contents, &len, NULL, error);
863 g_object_unref(gf);
864 if (!ok)
865 {
866 /* replace the path with failed one */
867 return FALSE;
868 }
869 menu = fm_xml_file_new(data->menu);
870 /* g_debug("merging FmXmlFile %p into %p", menu, data->menu); */
871 ok = fm_xml_file_parse_data(menu, contents, len, error, data);
872 g_free(contents);
873 if (ok)
874 {
875 xml = fm_xml_file_finish_parse(menu, &err);
876 if (err && err->domain == G_MARKUP_ERROR &&
877 err->code == G_MARKUP_ERROR_EMPTY)
878 {
879 /* NOTE: it should be legal case to have empty menu file.
880 it may be not generally this but let it be */
881 g_error_free(err);
882 data->file_path = save_path;
883 g_object_unref(menu);
884 return TRUE;
885 }
886 if (err)
887 g_propagate_error(error, err);
888 }
889 if (xml == NULL) /* error is set by failed function */
890 {
891 /* g_debug("freeing FmXmlFile %p (failed)", menu); */
892 /* only this handler does recursion, therefore it is safe to set and
893 and do check of data->line here */
894 if (data->line == -1)
895 data->line = fm_xml_file_get_current_line(menu, &data->pos);
896 /* we do a little trick here - we don't restore previous fule but
897 leave data->file_path for diagnostics in _update_categories() */
898 g_object_unref(menu);
899 return FALSE;
900 }
901 data->file_path = save_path;
902 /* insert all children but Name before item */
903 for (it = xml; it; it = it->next)
904 {
905 GList *xml_sub, *it_sub;
906
907 if (fm_xml_file_item_get_tag(it->data) != menuTag_Menu)
908 {
909 if (verbose == 0)
910 continue; /* just skip it in quiet mode */
911 g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
912 _("Merging file may contain only <Menu> top level tag,"
913 " got <%s>"), fm_xml_file_item_get_tag_name(it->data));
914 /* FIXME: it will show error not for merged file but current */
915 break;
916 }
917 xml_sub = fm_xml_file_item_get_children(it->data);
918 for (it_sub = xml_sub; it_sub; it_sub = it_sub->next)
919 {
920 /* g_debug("merge: trying to insert %p into %p", it_sub->data,
921 fm_xml_file_item_get_parent(item)); */
922 if (fm_xml_file_item_get_tag(it_sub->data) != menuTag_Name &&
923 !fm_xml_file_insert_before(item, it_sub->data))
924 {
925 g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
926 _("Failed to insert tag <%s> from merging file"),
927 fm_xml_file_item_get_tag_name(it_sub->data));
928 /* FIXME: it will show error not for merged file but current */
929 break;
930 }
931 }
932 g_list_free(xml_sub);
933 if (it_sub) /* failed above */
934 break;
935 }
936 g_list_free(xml);
937 ok = (it == NULL);
938 /* g_debug("freeing FmXmlFile %p (success=%d)", menu, (int)ok); */
939 g_object_unref(menu);
940 return ok;
941}
942
943static gboolean _merge_menu_directory(MenuTreeData *data, FmXmlFileItem *item,
944 const char *path, GList **m, GError **error,
945 gboolean ignore_not_exist)
946{
947 char *child;
948 GDir *dir;
949 GError *err = NULL;
950 const char *name;
951 gboolean ok = TRUE;
952
953 DBG("merging the XML directory '%s'", path);
954 path = g_intern_string(path);
955 if (g_slist_find(MenuDirs, path) == NULL)
956 MenuDirs = g_slist_append(MenuDirs, (gpointer)path);
957 dir = g_dir_open(path, 0, &err);
958 if (dir)
959 {
960 while ((name = g_dir_read_name(dir)))
961 {
962 if (strlen(name) <= 5 || !g_str_has_suffix(name, ".menu"))
963 {
964 /* skip files that aren't *.menu */
965 continue;
966 }
967 child = g_build_filename(path, name, NULL);
968 ok = _merge_xml_file(data, item, child, m, &err, FALSE);
969 if (!ok)
970 {
971 /*
972 if (err->domain == G_IO_ERROR && err->code == G_IO_ERROR_PERMISSION_DENIED)
973 {
974 g_warning("cannot merge XML file %s: no access", child);
975 g_clear_error(&err);
976 g_free(child);
977 ok = TRUE;
978 continue;
979 }
980 */
981 g_free(child);
982 if (ignore_not_exist && err->domain == G_IO_ERROR &&
983 (err->code == G_IO_ERROR_NOT_FOUND))
984 {
985 g_clear_error(&err);
986 continue;
987 }
988 g_propagate_error(error, err);
989 err = NULL;
990 break;
991 }
992 g_free(child);
993 }
994 g_dir_close(dir);
995 }
996 else if (ignore_not_exist && err->domain == G_FILE_ERROR &&
997 (err->code == G_FILE_ERROR_NOENT))
998 {
999 VDBG("_merge_menu_directory: dir %s does not exist", path);
1000 g_error_free(err);
1001 }
1002 else
1003 {
1004 g_propagate_error(error, err);
1005 ok = FALSE;
1006 }
1007 return ok;
1008}
1009
1010static inline const char *_get_menu_name(FmXmlFileItem *item)
1011{
1012 if (fm_xml_file_item_get_tag(item) != menuTag_Menu) /* skip not menu */
1013 return NULL;
1014 item = fm_xml_file_item_find_child(item, menuTag_Name);
1015 if (item == NULL) /* no Name tag? */
1016 return NULL;
1017 item = fm_xml_file_item_find_child(item, FM_XML_FILE_TEXT);
1018 if (item == NULL) /* empty Name tag? */
1019 return NULL;
1020 return fm_xml_file_item_get_data(item, NULL);
1021}
1022
1023/* merges subitems - consumes list */
1024/* NOTE: it will not delete duplicate elements other than Menu or Name */
1025static void _merge_level(GList *first)
1026{
1027 while (first)
1028 {
1029 if (first->data) /* we might merge this one already */
1030 {
1031 GList *next;
1032
1033 if (first->next)
1034 {
1035 /* merge this item with identical ones */
1036 const char *name = _get_menu_name(first->data);
1037
1038 if (name) /* not a menu tag */
1039 {
1040 for (next = first->next; next; next = next->next)
1041 {
1042 if (next->data == NULL) /* already merged */
1043 continue;
1044 if (g_strcmp0(name, _get_menu_name(next->data)) == 0)
1045 {
1046 GList *children = fm_xml_file_item_get_children(next->data);
1047 GList *l;
1048
1049 DBG("found two identical Menu '%s', merge them", name);
1050 for (l = children; l; l = l->next) /* merge all but Name */
1051 if (fm_xml_file_item_get_tag(l->data) != menuTag_Name)
1052 fm_xml_file_item_append_child(first->data, l->data);
1053 g_list_free(children);
1054 fm_xml_file_item_destroy(next->data);
1055 next->data = NULL; /* we merged it so no data */
1056 }
1057 }
1058 }
1059 }
1060 }
1061 /* go to next item */
1062 first = g_list_delete_link(first, first);
1063 }
1064}
1065
1066static FmXmlFileItem *_walk_path(GList *child, const char *path,
1067 FmXmlFileItem *parent, gboolean create)
1068{
1069 FmXmlFileItem *item = NULL;
1070 char *subpath = strchr(path, '/');
1071
1072 if (subpath)
1073 {
1074 char *next = &subpath[1];
1075 subpath = g_strndup(path, subpath - path);
1076 path = next;
1077 }
1078 for (; child != NULL; child = child->next)
1079 {
1080 item = child->data;
1081 if (item == NULL)
1082 continue;
1083 if (g_strcmp0(subpath ? subpath : path, _get_menu_name(item)) == 0)
1084 break;
1085 item = NULL;
1086 }
1087 g_free(subpath); /* free but still use as marker */
1088 if (subpath != NULL && item != NULL)
1089 {
1090 child = fm_xml_file_item_get_children(item);
1091 item = _walk_path(child, path, item, create);
1092 g_list_free(child);
1093 }
1094 else if (subpath == NULL && item == NULL && create)
1095 {
1096 /* create new <Menu><Name>path</Name></Menu> and append it to parent */
1097 item = fm_xml_file_item_new(menuTag_Menu);
1098 if (!fm_xml_file_item_append_child(parent, item))
1099 fm_xml_file_item_destroy(item); /* FIXME: is it possible? */
1100 else
1101 {
1102 parent = fm_xml_file_item_new(menuTag_Name); /* reuse pointer */
1103 fm_xml_file_item_append_text(parent, path, -1, FALSE);
1104 fm_xml_file_item_append_child(item, parent);
1105 }
1106 }
1107 return item;
1108}
1109
1110static FmXmlFileItem *_walk_children(GList *children, FmXmlFileItem *list,
1111 FmXmlFileTag tag, gboolean create)
1112{
1113 GList *sub, *l;
1114 FmXmlFileItem *item = NULL;
1115
1116 sub = fm_xml_file_item_get_children(list);
1117 for (l = sub; l; l = l->next)
1118 if (fm_xml_file_item_get_tag((item = l->data)) == tag)
1119 break;
1120 else
1121 item = NULL;
1122 g_list_free(sub);
1123 if (item == NULL) /* no tag found */
1124 return NULL;
1125 item = fm_xml_file_item_find_child(item, FM_XML_FILE_TEXT);
1126 list = fm_xml_file_item_get_parent(list); /* it contains parent of <Move> now */
1127 if (item == NULL) /* empty tag, assume we are here */
1128 return list;
1129 return _walk_path(children, fm_xml_file_item_get_data(item, NULL), list, create);
1130}
1131
1132static gboolean _activate_merges(MenuTreeData *data, FmXmlFileItem *item,
1133 GError **error)
1134{
1135 GList *children, *l, *l2, *merged = NULL;
1136 const char *path, *path2;
1137 FmXmlFileItem *sub;
1138 FmXmlFileTag tag;
1139 gboolean ok;
1140
1141restart:
1142 children = fm_xml_file_item_get_children(item);
1143 /* expand DefaultMergeDirs first */
1144 for (l = children, sub = NULL; l; l = l->next)
1145 {
1146 if (fm_xml_file_item_get_tag(l->data) == menuTag_DefaultMergeDirs)
1147 sub = l->data;
1148 }
1149 if (sub != NULL)
1150 {
1151 const gchar * const *dirs = g_get_system_config_dirs();
1152 char *merged;
1153 FmXmlFileItem *it_sub;
1154 int i = g_strv_length((char **)dirs);
1155
1156 /* insert in reverse order - see XDG menu specification */
1157 while (i > 0)
1158 {
1159 merged = g_build_filename(dirs[--i], "menus", "applications-merged", NULL);
1160 it_sub = fm_xml_file_item_new(menuTag_MergeDir);
1161 fm_xml_file_item_append_text(it_sub, merged, -1, FALSE);
1162 if (!fm_xml_file_insert_before(sub, it_sub) && verbose > 0)
1163 {
1164 g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1165 _("Failed to insert tag <MergeDir>%s</MergeDir>"),
1166 merged);
1167 g_free(merged);
1168 goto failed; /* failed to merge */
1169 }
1170 g_free(merged);
1171 }
1172 merged = g_build_filename(g_get_user_config_dir(), "menus", "applications-merged", NULL);
1173 it_sub = fm_xml_file_item_new(menuTag_MergeDir);
1174 fm_xml_file_item_append_text(it_sub, merged, -1, FALSE);
1175 if (!fm_xml_file_insert_before(sub, it_sub) && verbose > 0)
1176 {
1177 g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1178 _("Failed to insert tag <MergeDir>%s</MergeDir>"),
1179 merged);
1180 g_free(merged);
1181 goto failed; /* failed to merge */
1182 }
1183 g_free(merged);
1184 /* destroy all DefaultMergeDirs -- we replaced it already */
1185 for (l = children; l; l = l->next)
1186 if (fm_xml_file_item_get_tag(l->data) == menuTag_DefaultMergeDirs)
1187 fm_xml_file_item_destroy(l->data);
1188 /* restart merge again, we changed the list */
1189 g_list_free(children);
1190 goto restart;
1191 }
1192 /* do with MergeFile and MergeDir now */
1193 for (l = children; l; l = l->next)
1194 {
1195 tag = fm_xml_file_item_get_tag(l->data);
1196 if (tag == menuTag_MergeFile || tag == menuTag_MergeDir)
1197 {
1198 path = fm_xml_file_item_get_data(fm_xml_file_item_find_child(l->data,
1199 FM_XML_FILE_TEXT), NULL);
1200 /* find duplicate - only last one should be used */
1201 for (l2 = l->next; l2; l2 = l2->next)
1202 {
1203 if (fm_xml_file_item_get_tag(l2->data) == tag)
1204 {
1205 path2 = fm_xml_file_item_get_data(fm_xml_file_item_find_child(l2->data,
1206 FM_XML_FILE_TEXT), NULL);
1207 if (strcmp(path2, path) == 0)
1208 break;
1209 }
1210 }
1211 if (l2 == NULL)
1212 {
1213 if (tag == menuTag_MergeFile)
1214 {
1215 GError *err = NULL;
1216 ok = _merge_xml_file(data, l->data, path, &merged, &err, TRUE);
1217 if (ok) ;
1218 else if (err->domain == G_IO_ERROR && err->code == G_IO_ERROR_NOT_FOUND)
1219 {
1220 g_error_free(err);
1221 ok = TRUE;
1222 }
1223 else
1224 g_propagate_error(error, err);
1225 }
1226 else
1227 ok = _merge_menu_directory(data, l->data, path, &merged, error, TRUE);
1228 if (!ok)
1229 {
1230 if (verbose > 0)
1231 {
1232 g_prefix_error(error, "failed on '%s': ", path);
1233 goto failed; /* failed to merge */
1234 }
1235 g_clear_error(error);
1236 }
1237 }
1238 /* destroy item -- we replaced it already */
1239 fm_xml_file_item_destroy(l->data);
1240 if (l2 != NULL) /* it was a duplicate */
1241 continue;
1242 /* restart merge again, we could get new merges */
1243 g_list_free(children);
1244 goto restart;
1245 }
1246 }
1247 g_list_free(merged); /* we don't need it anymore */
1248 /* merge this level */
1249 _merge_level(children);
1250 children = fm_xml_file_item_get_children(item);
1251 /* expand DefaultAppDirs then supress duplicates on AppDir */
1252 for (l = children, sub = NULL; l; l = l->next)
1253 {
1254 if (fm_xml_file_item_get_tag(l->data) == menuTag_DefaultAppDirs)
1255 sub = l->data;
1256 }
1257 if (sub != NULL)
1258 {
1259 const gchar * const *dirs = g_get_system_data_dirs();
1260 char *merged;
1261 FmXmlFileItem *it_sub;
1262 int i = g_strv_length((char **)dirs);
1263
1264 /* insert in reverse order - see XDG menu specification */
1265 while (i > 0)
1266 {
1267 merged = g_build_filename(dirs[--i], "applications", NULL);
1268 it_sub = fm_xml_file_item_new(menuTag_AppDir);
1269 fm_xml_file_item_append_text(it_sub, merged, -1, FALSE);
1270 if (!fm_xml_file_insert_before(sub, it_sub) && verbose > 0)
1271 {
1272 g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1273 _("Failed to insert tag <AppDir>%s</AppDir>"),
1274 merged);
1275 g_free(merged);
1276 goto failed; /* failed to merge */
1277 }
1278 g_free(merged);
1279 }
1280 merged = g_build_filename(g_get_user_data_dir(), "applications", NULL);
1281 it_sub = fm_xml_file_item_new(menuTag_AppDir);
1282 fm_xml_file_item_append_text(it_sub, merged, -1, FALSE);
1283 if (!fm_xml_file_insert_before(sub, it_sub) && verbose > 0)
1284 {
1285 g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1286 _("Failed to insert tag <AppDir>%s</AppDir>"),
1287 merged);
1288 g_free(merged);
1289 goto failed; /* failed to merge */
1290 }
1291 g_free(merged);
1292 /* destroy all DefaultAppDirs -- we replaced it already */
1293 for (l = children; l; l = l->next)
1294 if (fm_xml_file_item_get_tag(l->data) == menuTag_DefaultAppDirs)
1295 {
1296 fm_xml_file_item_destroy(l->data);
1297 l->data = NULL;
1298 }
1299 }
1300 for (l = children; l; l = l->next)
1301 {
1302 sub = l->data;
1303 if (sub == NULL || fm_xml_file_item_get_tag(sub) != menuTag_AppDir)
1304 continue;
1305 for (l2 = l->next; l2; l2 = l2->next)
1306 if (l2->data != NULL && fm_xml_file_item_get_tag(l2->data) == menuTag_AppDir)
1307 if (strcmp(fm_xml_file_item_get_data(fm_xml_file_item_find_child(l2->data, FM_XML_FILE_TEXT), NULL),
1308 fm_xml_file_item_get_data(fm_xml_file_item_find_child(l->data, FM_XML_FILE_TEXT), NULL)) == 0)
1309 break;
1310 if (l2 == NULL) /* no duplicates */
1311 continue;
1312 fm_xml_file_item_destroy(sub);
1313 l->data = NULL;
1314 }
1315 /* expand KDELegacyDirs then supress duplicates on LegacyDir */
1316 for (l = children, sub = NULL; l; l = l->next)
1317 {
1318 if (l->data == NULL)
1319 continue;
1320 if (fm_xml_file_item_get_tag(l->data) == menuTag_KDELegacyDirs)
1321 sub = l->data;
1322 }
1323 if (sub != NULL)
1324 {
1325 const gchar * const *dirs = g_get_system_data_dirs();
1326 char *merged;
1327 FmXmlFileItem *it_sub;
1328 int i = g_strv_length((char **)dirs);
1329
1330 /* insert in reverse order - see XDG menu specification */
1331 while (i > 0)
1332 {
1333 merged = g_build_filename(dirs[--i], "applnk", NULL);
1334 it_sub = fm_xml_file_item_new(menuTag_LegacyDir);
1335 fm_xml_file_item_set_comment(it_sub, "kde-");
1336 fm_xml_file_item_append_text(it_sub, merged, -1, FALSE);
1337 if (!fm_xml_file_insert_before(sub, it_sub) && verbose > 0)
1338 {
1339 g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1340 _("Failed to insert tag <LegacyDir>%s</LegacyDir>"),
1341 merged);
1342 g_free(merged);
1343 goto failed; /* failed to merge */
1344 }
1345 g_free(merged);
1346 }
1347 merged = g_build_filename(g_get_user_data_dir(), "applnk", NULL);
1348 it_sub = fm_xml_file_item_new(menuTag_LegacyDir);
1349 fm_xml_file_item_set_comment(it_sub, "kde-");
1350 fm_xml_file_item_append_text(it_sub, merged, -1, FALSE);
1351 if (!fm_xml_file_insert_before(sub, it_sub) && verbose > 0)
1352 {
1353 g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1354 _("Failed to insert tag <LegacyDir>%s</LegacyDir>"),
1355 merged);
1356 g_free(merged);
1357 goto failed; /* failed to merge */
1358 }
1359 g_free(merged);
1360 /* destroy all KDELegacyDirs */
1361 for (l = children; l; l = l->next)
1a26c599 1362 if (l->data && fm_xml_file_item_get_tag(l->data) == menuTag_KDELegacyDirs)
2f7ba096
AG
1363 {
1364 fm_xml_file_item_destroy(l->data);
1365 l->data = NULL;
1366 }
1367 }
1368 for (l = children; l; l = l->next)
1369 {
1370 sub = l->data;
1371 if (sub == NULL || fm_xml_file_item_get_tag(sub) != menuTag_LegacyDir)
1372 continue;
1373 VDBG("check LegacyDir %s", fm_xml_file_item_get_data(fm_xml_file_item_find_child(l->data, FM_XML_FILE_TEXT), NULL));
1374 for (l2 = l->next; l2; l2 = l2->next)
1375 if (l2->data != NULL && fm_xml_file_item_get_tag(l2->data) == menuTag_LegacyDir)
1376 if (strcmp(fm_xml_file_item_get_data(fm_xml_file_item_find_child(l2->data, FM_XML_FILE_TEXT), NULL),
1377 fm_xml_file_item_get_data(fm_xml_file_item_find_child(l->data, FM_XML_FILE_TEXT), NULL)) == 0)
1378 break;
1379 if (l2 == NULL) /* no duplicates */
1380 continue;
1381 fm_xml_file_item_destroy(sub);
1382 l->data = NULL;
1383 }
1384 /* expand DefaultDirectoryDirs then supress duplicates on DirectoryDir */
1385 for (l = children, sub = NULL; l; l = l->next)
1386 {
1387 if (l->data == NULL)
1388 continue;
1389 if (fm_xml_file_item_get_tag(l->data) == menuTag_DefaultDirectoryDirs)
1390 sub = l->data;
1391 }
1392 if (sub != NULL)
1393 {
1394 const gchar * const *dirs = g_get_system_data_dirs();
1395 char *merged;
1396 FmXmlFileItem *it_sub;
1397 int i = g_strv_length((char **)dirs);
1398
1399 /* insert in reverse order - see XDG menu specification */
1400 while (i > 0)
1401 {
1402 merged = g_build_filename(dirs[--i], "desktop-directories", NULL);
1403 it_sub = fm_xml_file_item_new(menuTag_DirectoryDir);
1404 fm_xml_file_item_append_text(it_sub, merged, -1, FALSE);
1405 if (!fm_xml_file_insert_before(sub, it_sub) && verbose > 0)
1406 {
1407 g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1408 _("Failed to insert tag <DirectoryDir>%s</DirectoryDir>"),
1409 merged);
1410 g_free(merged);
1411 goto failed; /* failed to merge */
1412 }
1413 g_free(merged);
1414 }
1415 merged = g_build_filename(g_get_user_data_dir(), "desktop-directories", NULL);
1416 it_sub = fm_xml_file_item_new(menuTag_DirectoryDir);
1417 fm_xml_file_item_append_text(it_sub, merged, -1, FALSE);
1418 if (!fm_xml_file_insert_before(sub, it_sub) && verbose > 0)
1419 {
1420 g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1421 _("Failed to insert tag <DirectoryDir>%s</DirectoryDir>"),
1422 merged);
1423 g_free(merged);
1424 goto failed; /* failed to merge */
1425 }
1426 g_free(merged);
1427 /* destroy all DefaultDirectoryDirs -- we replaced it already */
1428 for (l = children; l; l = l->next)
1429 if (l->data && fm_xml_file_item_get_tag(l->data) == menuTag_DefaultDirectoryDirs)
1430 {
1431 fm_xml_file_item_destroy(l->data);
1432 l->data = NULL;
1433 }
1434 }
1435 for (l = children; l; l = l->next)
1436 {
1437 sub = l->data;
1438 if (sub == NULL || fm_xml_file_item_get_tag(sub) != menuTag_DirectoryDir)
1439 continue;
1440 for (l2 = l->next; l2; l2 = l2->next)
1441 if (l2->data != NULL && fm_xml_file_item_get_tag(l2->data) == menuTag_DirectoryDir)
1442 if (strcmp(fm_xml_file_item_get_data(fm_xml_file_item_find_child(l2->data, FM_XML_FILE_TEXT), NULL),
1443 fm_xml_file_item_get_data(fm_xml_file_item_find_child(l->data, FM_XML_FILE_TEXT), NULL)) == 0)
1444 break;
1445 if (l2 == NULL) /* no duplicates */
1446 continue;
1447 fm_xml_file_item_destroy(sub);
1448 l->data = NULL;
1449 }
1450 /* support <Move><New>...</New><Old>...</Old></Move> for menus */
1451 for (l = children; l; l = l2)
1452 {
1453 l2 = l->next;
1454 sub = l->data;
1455 if (sub && fm_xml_file_item_get_tag(sub) == menuTag_Move)
1456 {
1457 FmXmlFileItem *old = _walk_children(children, sub, menuTag_Old, FALSE);
1458 sub = _walk_children(children, sub, menuTag_New, TRUE);
1459 if (old != NULL && sub != NULL)
1460 {
1461 GList *child = fm_xml_file_item_get_children(old);
1462
1463 while (child != NULL)
1464 {
1465 if (fm_xml_file_item_get_tag(child->data) != menuTag_Name)
1466 fm_xml_file_item_append_child(sub, child->data);
1467 child = g_list_delete_link(child, child);
1468 }
1469 fm_xml_file_item_destroy(old);
1470 }
1471 else
1472 DBG("invalid <Move> tag ignored");
1473 }
1474 }
1475 /* reload children, they might be changed after movements */
1476 g_list_free(children);
1477 children = fm_xml_file_item_get_children(item);
1478 /* do recursion for children Menu now */
1479 for (l = children; l; l = l->next)
1480 {
1481 sub = l->data;
1482 if (fm_xml_file_item_get_tag(sub) == menuTag_Menu &&
1483 !_activate_merges(data, sub, error))
1484 goto failed; /* failed to merge */
1485 }
1486 g_list_free(children);
1487 return TRUE;
1488
1489failed:
1490 g_list_free(children);
1491 return FALSE;
1492}
1493
1494static GList *_layout_items_copy(GList *orig)
1495{
1496 GList *copy = NULL;
1497 gpointer item;
1498
1499 while (orig)
1500 {
1501 MenuSep *sep = orig->data;
1502
1503 switch (sep->type) {
1504 case MENU_CACHE_TYPE_NONE:
1505 item = g_slice_new(MenuMerge);
1506 memcpy(item, sep, sizeof(MenuMerge));
b94f3144 1507 VVDBG("*** new menu layout: MenuMerge");
2f7ba096
AG
1508 break;
1509 case MENU_CACHE_TYPE_SEP:
1510 item = g_slice_new(MenuSep);
1511 memcpy(item, sep, sizeof(MenuSep));
b94f3144 1512 VVDBG("*** new menu layout: MenuSeparator");
2f7ba096
AG
1513 break;
1514 case MENU_CACHE_TYPE_APP:
1515 item = g_slice_new(MenuFilename);
1516 memcpy(item, sep, sizeof(MenuFilename));
1517 ((MenuFilename *)item)->id = g_strdup(((MenuFilename *)sep)->id);
b94f3144 1518 VVDBG("*** new menu layout: MenuFilename %s", ((MenuFilename *)item)->id);
2f7ba096
AG
1519 break;
1520 case MENU_CACHE_TYPE_DIR:
1521 item = g_slice_new(MenuMenuname);
1522 memcpy(item, sep, sizeof(MenuMenuname));
1523 ((MenuMenuname *)item)->name = g_strdup(((MenuMenuname *)sep)->name);
b94f3144 1524 VVDBG("*** new menu layout: MenuMenuname %s", ((MenuMenuname *)item)->name);
2f7ba096
AG
1525 }
1526 copy = g_list_prepend(copy, item);
1527 orig = orig->next;
1528 }
1529 return g_list_reverse(copy);
1530}
1531
1532static MenuMenu *_make_menu_node(FmXmlFileItem *node, MenuLayout *def)
1533{
1534 FmXmlFileItem *item = NULL;
1535 MenuLayout *layout = NULL;
1536 GList *children, *l;
1537 MenuMenu *menu;
1538 FmXmlFileTag tag;
1539 gboolean ok = TRUE;
1540
1541 if (fm_xml_file_item_find_child(node, menuTag_Name) == NULL)
1542 {
1543 g_warning("got a <Menu> without <Name>, ignored");
1544 return NULL;
1545 }
1546 children = fm_xml_file_item_get_children(node);
1547 /* check if it's deleted first */
1548 for (l = children; l; l = l->next)
1549 {
1550 tag = fm_xml_file_item_get_tag(l->data);
1551 if (tag == menuTag_Layout)
1552 item = l->data;
1553 else if (tag == menuTag_DefaultLayout)
1554 layout = _find_layout(l->data, FALSE);
1555 else if (tag == menuTag_Deleted)
1556 ok = FALSE;
1557 else if (tag == menuTag_NotDeleted)
1558 ok = TRUE;
1559 }
1560 if (!ok)
1561 {
1562 /* menu was disabled, ignore it */
1563 g_list_free(children);
1564 return NULL;
1565 }
1566 /* find default layout if any and subst */
1567 if (layout != NULL)
b94f3144
AG
1568 {
1569 /* new DefaultLayout might be empty, ignore it then */
1570 if (layout->items == NULL)
1571 layout = NULL;
1572 else
1573 def = layout;
1574 }
2f7ba096
AG
1575 /* find layout, if not found then fill from default */
1576 if (item != NULL)
1577 layout = _find_layout(item, FALSE);
1578 if (layout == NULL)
1579 layout = def;
1580 menu = g_slice_new0(MenuMenu);
1581 menu->layout.type = MENU_CACHE_TYPE_DIR;
1582 menu->layout.show_empty = layout->show_empty;
1583 menu->layout.allow_inline = layout->allow_inline;
1584 menu->layout.inline_header = layout->inline_header;
1585 menu->layout.inline_alias = layout->inline_alias;
1586 menu->layout.inline_limit = layout->inline_limit;
1587 menu->layout.items = _layout_items_copy(layout->items);
1588 VDBG("*** starting new menu");
1589 /* gather all explicit data from XML */
1590 for (l = children; l; l = l->next)
1591 {
1592 tag = fm_xml_file_item_get_tag(l->data);
1593 /* we don't do any matching now, i.e. those tags will be processed later
1594 * Include Exclude Filename Category All And Not Or
1595 directory scannings will be processed later as well:
1596 * AppDir LegacyDir KDELegacyDirs */
1597 if (tag == menuTag_Menu)
1598 {
1599 MenuMenu *child = _make_menu_node(l->data, def);
1600 if (child != NULL)
1601 {
1602 VDBG("*** added submenu %s", child->name);
1603 menu->children = g_list_prepend(menu->children, child);
1604 }
1605 }
1606 else if (tag == menuTag_Directory)
1607 {
1608 item = fm_xml_file_item_find_child(l->data, FM_XML_FILE_TEXT);
1609 if (item != NULL)
1610 menu->id = g_list_prepend(menu->id,
1611 g_strdup(fm_xml_file_item_get_data(item, NULL)));
1612 }
1613 else if (tag == menuTag_Name)
1614 {
1615 if (menu->name == NULL)
1616 menu->name = g_strdup(fm_xml_file_item_get_data(fm_xml_file_item_find_child(l->data,
1617 FM_XML_FILE_TEXT), NULL));
1618 }
1619 else if (tag == menuTag_OnlyUnallocated)
1620 menu->layout.only_unallocated = TRUE;
1621 else if (tag == menuTag_NotOnlyUnallocated)
1622 menu->layout.only_unallocated = FALSE;
1623 else if (tag == menuTag_Include || tag == menuTag_Exclude ||
1624 tag == menuTag_DirectoryDir || tag == menuTag_AppDir ||
1625 tag == menuTag_LegacyDir)
1626 /* FIXME: can those be here? Filename Category All And Not Or */
1627 {
1628 MenuRule *child = g_slice_new0(MenuRule);
1629 child->type = MENU_CACHE_TYPE_NONE;
1630 child->rule = l->data;
1631 VDBG("*** adding rule %s", fm_xml_file_item_get_tag_name(l->data));
1632 menu->children = g_list_prepend(menu->children, child);
1633 }
1634 }
1635 g_list_free(children);
1636 menu->children = g_list_reverse(menu->children);
1637 VDBG("*** done menu %s",menu->name);
1638 return menu;
1639}
1640
1641static FmXmlFileItem *_find_in_children(GList *list, const char *name)
1642{
1643 while (list)
1644 {
1645 const char *elem_name = _get_menu_name(list->data);
1646 /* g_debug("got child %d: %s", fm_xml_file_item_get_tag(list->data), elem_name); */
1647 if (g_strcmp0(elem_name, name) == 0)
1648 return list->data;
1649 else
1650 list = list->next;
1651 }
1652 return NULL;
1653}
1654
1655void _free_layout_items(GList *data)
1656{
1657 union {
1658 MenuMenuname *menu;
1659 MenuFilename *file;
1660 MenuSep *sep;
1661 MenuMerge *merge;
1662 } a = { NULL };
1663
1664 while (data != NULL)
1665 {
1666 a.menu = data->data;
1667 switch (a.menu->layout.type) {
1668 case MENU_CACHE_TYPE_DIR:
1669 g_free(a.menu->name);
1670 g_slice_free(MenuMenuname, a.menu);
1671 break;
1672 case MENU_CACHE_TYPE_APP:
1673 g_free(a.file->id);
1674 g_slice_free(MenuFilename, a.file);
1675 break;
1676 case MENU_CACHE_TYPE_SEP:
1677 g_slice_free(MenuSep, a.sep);
1678 break;
1679 case MENU_CACHE_TYPE_NONE:
1680 g_slice_free(MenuMerge, a.merge);
1681 }
1682 data = g_list_delete_link(data, data);
1683 }
1684}
1685
1686static void _free_layout(gpointer data)
1687{
1688 MenuLayout *layout = data;
1689
1690 _free_layout_items(layout->items);
1691 g_slice_free(MenuLayout, data);
1692}
1693
1694MenuMenu *get_merged_menu(const char *file, FmXmlFile **xmlfile, GError **error)
1695{
1696 GFile *gf;
1697 char *contents;
1698 gsize len;
1699 MenuTreeData data;
1700 GList *xml = NULL;
1701 FmXmlFileItem *apps;
1702 MenuMenu *menu = NULL;
1703 MenuLayout default_layout;
1704 MenuMerge def_files = { .type = MENU_CACHE_TYPE_NONE, .merge_type = MERGE_FILES };
1705 MenuMerge def_menus = { .type = MENU_CACHE_TYPE_NONE, .merge_type = MERGE_MENUS };
1706 gboolean ok;
1707
1708 /* Load the file */
1709 data.file_path = file;
1710 gf = g_file_new_for_path(file);
1711 contents = NULL;
1712 ok = g_file_load_contents(gf, NULL, &contents, &len, NULL, error);
1713 g_object_unref(gf);
1714 if (!ok)
1715 return NULL;
1716 /* Init layouts hash and all the data */
1717 layout_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
1718 _free_layout);
1719 data.menu = fm_xml_file_new(NULL);
1720 data.line = data.pos = -1;
1721 /* g_debug("new FmXmlFile %p", data.menu); */
1722 menuTag_Menu = fm_xml_file_set_handler(data.menu, "Menu",
1723 &_menu_xml_handler_pass, FALSE, NULL);
1724 menuTag_Include = fm_xml_file_set_handler(data.menu, "Include",
1725 &_menu_xml_handler_pass, FALSE, NULL);
1726 menuTag_Exclude = fm_xml_file_set_handler(data.menu, "Exclude",
1727 &_menu_xml_handler_pass, FALSE, NULL);
1728 menuTag_Filename = fm_xml_file_set_handler(data.menu, "Filename",
1729 &_menu_xml_handler_Filename, FALSE, NULL);
1730 menuTag_Or = fm_xml_file_set_handler(data.menu, "Or",
1731 &_menu_xml_handler_pass, FALSE, NULL);
1732 menuTag_And = fm_xml_file_set_handler(data.menu, "And",
1733 &_menu_xml_handler_pass, FALSE, NULL);
1734 menuTag_Not = fm_xml_file_set_handler(data.menu, "Not",
1735 &_menu_xml_handler_Not, FALSE, NULL);
1736 menuTag_Category = fm_xml_file_set_handler(data.menu, "Category",
1737 &_menu_xml_handler_pass, FALSE, NULL);
1738 menuTag_MergeFile = fm_xml_file_set_handler(data.menu, "MergeFile",
1739 &_menu_xml_handler_MergeFile, FALSE, NULL);
1740 menuTag_MergeDir = fm_xml_file_set_handler(data.menu, "MergeDir",
1741 &_menu_xml_handler_MergeDir, FALSE, NULL);
1742 menuTag_DefaultMergeDirs = fm_xml_file_set_handler(data.menu, "DefaultMergeDirs",
1743 &_menu_xml_handler_DefaultMergeDirs, FALSE, NULL);
1744 menuTag_KDELegacyDirs = fm_xml_file_set_handler(data.menu, "KDELegacyDirs",
1745 &_menu_xml_handler_DefaultMergeDirs, FALSE, NULL);
1746 menuTag_Name = fm_xml_file_set_handler(data.menu, "Name",
1747 &_menu_xml_handler_Name, FALSE, NULL);
1748 menuTag_Deleted = fm_xml_file_set_handler(data.menu, "Deleted",
1749 &_menu_xml_handler_pass, FALSE, NULL);
1750 menuTag_NotDeleted = fm_xml_file_set_handler(data.menu, "NotDeleted",
1751 &_menu_xml_handler_pass, FALSE, NULL);
1752 menuTag_Directory = fm_xml_file_set_handler(data.menu, "Directory",
1753 &_menu_xml_handler_pass, FALSE, NULL);
1754 menuTag_AppDir = fm_xml_file_set_handler(data.menu, "AppDir",
1755 &_menu_xml_handler_AppDir, FALSE, NULL);
1756 menuTag_DefaultAppDirs = fm_xml_file_set_handler(data.menu, "DefaultAppDirs",
1757 &_menu_xml_handler_DefaultAppDirs, FALSE, NULL);
1758 menuTag_DirectoryDir = fm_xml_file_set_handler(data.menu, "DirectoryDir",
1759 &_menu_xml_handler_DirectoryDir, FALSE, NULL);
1760 menuTag_DefaultDirectoryDirs = fm_xml_file_set_handler(data.menu, "DefaultDirectoryDirs",
1761 &_menu_xml_handler_DefaultDirectoryDirs, FALSE, NULL);
1762 menuTag_OnlyUnallocated = fm_xml_file_set_handler(data.menu, "OnlyUnallocated",
1763 &_menu_xml_handler_pass, FALSE, NULL);
1764 menuTag_NotOnlyUnallocated = fm_xml_file_set_handler(data.menu, "NotOnlyUnallocated",
1765 &_menu_xml_handler_pass, FALSE, NULL);
1766 menuTag_All = fm_xml_file_set_handler(data.menu, "All",
1767 &_menu_xml_handler_pass, FALSE, NULL);
1768 menuTag_LegacyDir = fm_xml_file_set_handler(data.menu, "LegacyDir",
1769 &_menu_xml_handler_LegacyDir, FALSE, NULL);
1770 menuTag_Move = fm_xml_file_set_handler(data.menu, "Move",
1771 &_menu_xml_handler_pass, FALSE, NULL);
1772 menuTag_Old = fm_xml_file_set_handler(data.menu, "Old",
1773 &_menu_xml_handler_pass, FALSE, NULL);
1774 menuTag_New = fm_xml_file_set_handler(data.menu, "New",
1775 &_menu_xml_handler_pass, FALSE, NULL);
1776 menuTag_Layout = fm_xml_file_set_handler(data.menu, "Layout",
1777 &_menu_xml_handler_Layout, FALSE, NULL);
1778 menuTag_DefaultLayout = fm_xml_file_set_handler(data.menu, "DefaultLayout",
1779 &_menu_xml_handler_DefaultLayout, FALSE, NULL);
1780 menuTag_Menuname = fm_xml_file_set_handler(data.menu, "Menuname",
1781 &_menu_xml_handler_Menuname, FALSE, NULL);
1782 menuTag_Separator = fm_xml_file_set_handler(data.menu, "Separator",
1783 &_menu_xml_handler_Separator, FALSE, NULL);
1784 menuTag_Merge = fm_xml_file_set_handler(data.menu, "Merge",
1785 &_menu_xml_handler_Merge, FALSE, NULL);
1786 /* Do parsing */
1787 ok = fm_xml_file_parse_data(data.menu, contents, len, error, &data);
1788 g_free(contents);
1789 if (ok)
1790 xml = fm_xml_file_finish_parse(data.menu, error);
1791 if (xml == NULL) /* error is set by failed function */
1792 {
1793 if (data.line == -1)
1794 data.line = fm_xml_file_get_current_line(data.menu, &data.pos);
1795 g_prefix_error(error, _("XML file '%s' error (%d:%d): "), data.file_path,
1796 data.line, data.pos);
1797 goto _return_error;
1798 }
1799 /* Merge other files */
1800 apps = _find_in_children(xml, "Applications");
1801 g_list_free(xml);
1802 if (apps == NULL)
1803 {
1804 g_set_error_literal(error, G_FILE_ERROR, G_FILE_ERROR_NOENT,
1805 _("XML file doesn't contain Applications root"));
1806 goto _return_error;
1807 }
1808 if (!_activate_merges(&data, apps, error))
1809 goto _return_error;
1810 /* FIXME: validate <Merge> tags */
1811 /* Create our menu tree -- no failures anymore! */
1812 memset(&default_layout, 0, sizeof(default_layout));
1813 default_layout.inline_header = TRUE;
1814 default_layout.inline_limit = 4;
1815 default_layout.items = g_list_prepend(g_list_prepend(NULL, &def_files), &def_menus);
1816 menu = _make_menu_node(apps, &default_layout);
1817 g_list_free(default_layout.items);
1818 if (verbose > 2)
1819 {
1820 char *dump = fm_xml_file_to_data(data.menu, NULL, NULL);
1821 g_print("%s", dump);
1822 g_free(dump);
1823 }
1824 /* Free layouts hash */
1825_return_error:
1826 if (menu == NULL)
1827 g_object_unref(data.menu);
1828 else
1829 /* keep XML file still since MenuRule elements use items in it */
1830 *xmlfile = data.menu;
1831 g_hash_table_destroy(layout_hash);
1832 return menu;
1833}