+static GHashTable *menu_hash = NULL;
+static ObtParseInst *menu_parse_inst;
+static ObMenuParseState menu_parse_state;
+static gboolean menu_can_hide = FALSE;
+
+static void menu_destroy_hash_value(ObMenu *self);
+static void parse_menu_item(xmlNodePtr node, gpointer data);
+static void parse_menu_separator(xmlNodePtr node, gpointer data);
+static void parse_menu(xmlNodePtr node, gpointer data);
+static gunichar parse_shortcut(const gchar *label, gboolean allow_shortcut,
+ gchar **strippedlabel, guint *position,
+ gboolean *always_show);
+
+
+static void client_dest(ObClient *client, gpointer data)
+{
+ /* menus can be associated with a client, so close any that are since
+ we are disappearing now */
+ menu_frame_hide_all_client(client);
+}
+
+void menu_startup(gboolean reconfig)
+{
+ gboolean loaded = FALSE;
+ GSList *it;
+
+ menu_hash = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify)menu_destroy_hash_value);
+
+ client_list_menu_startup(reconfig);
+ client_list_combined_menu_startup(reconfig);
+ client_menu_startup();
+
+ menu_parse_inst = obt_parse_instance_new();
+
+ menu_parse_state.parent = NULL;
+ menu_parse_state.pipe_creator = NULL;
+ obt_parse_register(menu_parse_inst, "menu", parse_menu, &menu_parse_state);
+ obt_parse_register(menu_parse_inst, "item", parse_menu_item,
+ &menu_parse_state);
+ obt_parse_register(menu_parse_inst, "separator",
+ parse_menu_separator, &menu_parse_state);
+
+ for (it = config_menu_files; it; it = g_slist_next(it)) {
+ if (obt_parse_load_config_file(menu_parse_inst,
+ "openbox",
+ it->data,
+ "openbox_menu"))
+ {
+ loaded = TRUE;
+ obt_parse_tree_from_root(menu_parse_inst);
+ obt_parse_close(menu_parse_inst);
+ } else
+ g_message(_("Unable to find a valid menu file '%s'"),
+ (const gchar*)it->data);
+ }
+ if (!loaded) {
+ if (obt_parse_load_config_file(menu_parse_inst,
+ "openbox",
+ "menu.xml",
+ "openbox_menu"))
+ {
+ obt_parse_tree_from_root(menu_parse_inst);
+ obt_parse_close(menu_parse_inst);
+ } else
+ g_message(_("Unable to find a valid menu file '%s'"),
+ "menu.xml");
+ }
+
+ g_assert(menu_parse_state.parent == NULL);
+
+ if (!reconfig)
+ client_add_destroy_notify(client_dest, NULL);
+}
+
+void menu_shutdown(gboolean reconfig)
+{
+ if (!reconfig)
+ client_remove_destroy_notify(client_dest);
+
+ obt_parse_instance_unref(menu_parse_inst);
+ menu_parse_inst = NULL;
+
+ client_list_menu_shutdown(reconfig);
+ client_list_combined_menu_shutdown(reconfig);
+
+ menu_frame_hide_all();
+ g_hash_table_destroy(menu_hash);
+ menu_hash = NULL;
+}
+
+static gboolean menu_pipe_submenu(gpointer key, gpointer val, gpointer data)
+{
+ ObMenu *menu = val;
+ return menu->pipe_creator != NULL;
+}
+
+static void clear_cache(gpointer key, gpointer val, gpointer data)
+{
+ ObMenu *menu = val;
+ if (menu->execute)
+ menu_clear_entries(menu);
+}
+
+void menu_clear_pipe_caches(void)
+{
+ /* delete any pipe menus' submenus */
+ g_hash_table_foreach_remove(menu_hash, menu_pipe_submenu, NULL);
+ /* empty the top level pipe menus */
+ g_hash_table_foreach(menu_hash, clear_cache, NULL);
+}
+
+void menu_pipe_execute(ObMenu *self)
+{
+ gchar *output;
+ GError *err = NULL;
+
+ if (!self->execute)
+ return;
+ if (self->entries) /* the entries are already created and cached */
+ return;
+
+ if (!g_spawn_command_line_sync(self->execute, &output, NULL, NULL, &err)) {
+ g_message(_("Failed to execute command for pipe-menu '%s': %s"),
+ self->execute, err->message);
+ g_error_free(err);
+ return;
+ }
+
+ if (obt_parse_load_mem(menu_parse_inst, output, strlen(output),
+ "openbox_pipe_menu"))
+ {
+ menu_parse_state.pipe_creator = self;
+ menu_parse_state.parent = self;
+ obt_parse_tree_from_root(menu_parse_inst);
+ obt_parse_close(menu_parse_inst);
+ } else {
+ g_message(_("Invalid output from pipe-menu '%s'"), self->execute);
+ }
+
+ g_free(output);
+}