#!/usr/bin/perl

# Copyright (c) 2003 SuSE Linux AG, Nuernberg, Germany. All rights reserved.
#
# Author: nadvornik@suse.cz
#
#

use strict;
use Getopt::Long;
use Encode;
use I18N::Langinfo qw(langinfo CODESET);
use POSIX qw(locale_h);
use File::Find;

my $Version = "0.2";

my $DefaultAppDirs;
my $DefaultDirectoryDirs;
my @KDELegacyDirs;

my $format = 'WindowMaker';
my $desktop_name;
my $language = '';
my $charset = 'iso-8859-1';
my $root_cmd = 'xdg_menu_su';

my $validate_error = 0;
my $die_on_error = 0;
my $verbose = 0;

my @language_keys;

my @accessed_files;
my @save_ARGV = @ARGV;


my %Desktop_entries;
my %Directory_entries;

# These icons get accepted by default. Newer icon themes should provide them
# in any case. List fetched from icon naming spec (last change from 2009),
# without animation and international icons.
my %standardIconNames = (
# Standard Action Icons
	"address-book-new" => undef,
	"application-exit" => undef,
	"appointment-new" => undef,
	"call-start" => undef,
	"call-stop" => undef,
	"contact-new" => undef,
	"document-new" => undef,
	"document-open" => undef,
	"document-open-recent" => undef,
	"document-page-setup" => undef,
	"document-print" => undef,
	"document-print-preview" => undef,
	"document-properties" => undef,
	"document-revert" => undef,
	"document-save" => undef,
	"document-save-as" => undef,
	"document-send" => undef,
	"edit-clear" => undef,
	"edit-copy" => undef,
	"edit-cut" => undef,
	"edit-delete" => undef,
	"edit-find" => undef,
	"edit-find-replace" => undef,
	"edit-paste" => undef,
	"edit-redo" => undef,
	"edit-select-all" => undef,
	"edit-undo" => undef,
	"folder-new" => undef,
	"format-indent-less" => undef,
	"format-indent-more" => undef,
	"format-justify-center" => undef,
	"format-justify-fill" => undef,
	"format-justify-left" => undef,
	"format-justify-right" => undef,
	"format-text-direction-ltr" => undef,
	"format-text-direction-rtl" => undef,
	"format-text-bold" => undef,
	"format-text-italic" => undef,
	"format-text-underline" => undef,
	"format-text-strikethrough" => undef,
	"go-bottom" => undef,
	"go-down" => undef,
	"go-first" => undef,
	"go-home" => undef,
	"go-jump" => undef,
	"go-last" => undef,
	"go-next" => undef,
	"go-previous" => undef,
	"go-top" => undef,
	"go-up" => undef,
	"help-about" => undef,
	"help-contents" => undef,
	"help-faq" => undef,
	"insert-image" => undef,
	"insert-link" => undef,
	"insert-object" => undef,
	"insert-text" => undef,
	"list-add" => undef,
	"list-remove" => undef,
	"mail-forward" => undef,
	"mail-mark-important" => undef,
	"mail-mark-junk" => undef,
	"mail-mark-notjunk" => undef,
	"mail-mark-read" => undef,
	"mail-mark-unread" => undef,
	"mail-message-new" => undef,
	"mail-reply-all" => undef,
	"mail-reply-sender" => undef,
	"mail-send" => undef,
	"mail-send-receive" => undef,
	"media-eject" => undef,
	"media-playback-pause" => undef,
	"media-playback-start" => undef,
	"media-playback-stop" => undef,
	"media-record" => undef,
	"media-seek-backward" => undef,
	"media-seek-forward" => undef,
	"media-skip-backward" => undef,
	"media-skip-forward" => undef,
	"object-flip-horizontal" => undef,
	"object-flip-vertical" => undef,
	"object-rotate-left" => undef,
	"object-rotate-right" => undef,
	"process-stop" => undef,
	"system-lock-screen" => undef,
	"system-log-out" => undef,
	"system-run" => undef,
	"system-search" => undef,
	"system-reboot" => undef,
	"system-shutdown" => undef,
	"tools-check-spelling" => undef,
	"view-fullscreen" => undef,
	"view-refresh" => undef,
	"view-restore" => undef,
	"view-sort-ascending" => undef,
	"view-sort-descending" => undef,
	"window-close" => undef,
	"window-new" => undef,
	"zoom-fit-best" => undef,
	"zoom-in" => undef,
	"zoom-original" => undef,
	"zoom-out" => undef,

# Standard Application Icons
	"accessories-calculator" => undef,
	"accessories-character-map" => undef,
	"accessories-dictionary" => undef,
	"accessories-text-editor" => undef,
	"help-browser" => undef,
	"multimedia-volume-control" => undef,
	"preferences-desktop-accessibility" => undef,
	"preferences-desktop-font" => undef,
	"preferences-desktop-keyboard" => undef,
	"preferences-desktop-locale" => undef,
	"preferences-desktop-multimedia" => undef,
	"preferences-desktop-screensaver" => undef,
	"preferences-desktop-theme" => undef,
	"preferences-desktop-wallpaper" => undef,
	"system-file-manager" => undef,
	"system-software-install" => undef,
	"system-software-update" => undef,
	"utilities-system-monitor" => undef,
	"utilities-terminal" => undef,

# Standard Category Icons
	"applications-accessories" => undef,
	"applications-development" => undef,
	"applications-engineering" => undef,
	"applications-games" => undef,
	"applications-graphics" => undef,
	"applications-internet" => undef,
	"applications-multimedia" => undef,
	"applications-office" => undef,
	"applications-other" => undef,
	"applications-science" => undef,
	"applications-system" => undef,
	"applications-utilities" => undef,
	"preferences-desktop" => undef,
	"preferences-desktop-peripherals" => undef,
	"preferences-desktop-personal" => undef,
	"preferences-other" => undef,
	"preferences-system" => undef,
	"preferences-system-network" => undef,
	"system-help" => undef,

# Standard Device Icons
	"audio-card" => undef,
	"audio-input-microphone" => undef,
	"battery" => undef,
	"camera-photo" => undef,
	"camera-video" => undef,
	"camera-web" => undef,
	"computer" => undef,
	"drive-harddisk" => undef,
	"drive-optical" => undef,
	"drive-removable-media" => undef,
	"input-gaming" => undef,
	"input-keyboard" => undef,
	"input-mouse" => undef,
	"input-tablet" => undef,
	"media-flash" => undef,
	"media-floppy" => undef,
	"media-optical" => undef,
	"media-tape" => undef,
	"modem" => undef,
	"multimedia-player" => undef,
	"network-wired" => undef,
	"network-wireless" => undef,
	"pda" => undef,
	"phone" => undef,
	"printer" => undef,
	"scanner" => undef,
	"video-display" => undef,

# Standard Emblem Icons
	"emblem-default" => undef,
	"emblem-documents" => undef,
	"emblem-downloads" => undef,
	"emblem-favorite" => undef,
	"emblem-important" => undef,
	"emblem-mail" => undef,
	"emblem-photos" => undef,
	"emblem-readonly" => undef,
	"emblem-shared" => undef,
	"emblem-symbolic-link" => undef,
	"emblem-synchronized" => undef,
	"emblem-system" => undef,
	"emblem-unreadable" => undef,

# Standard Emotion Icons
	"face-angel" => undef,
	"face-angry" => undef,
	"face-cool" => undef,
	"face-crying" => undef,
	"face-devilish" => undef,
	"face-embarrassed" => undef,
	"face-kiss" => undef,
	"face-laugh" => undef,
	"face-monkey" => undef,
	"face-plain" => undef,
	"face-raspberry" => undef,
	"face-sad" => undef,
	"face-sick" => undef,
	"face-smile" => undef,
	"face-smile-big" => undef,
	"face-smirk" => undef,
	"face-surprise" => undef,
	"face-tired" => undef,
	"face-uncertain" => undef,
	"face-wink" => undef,
	"face-worried" => undef,

# Standard MIME Type Icons
	"application-x-executable" => undef,
	"audio-x-generic" => undef,
	"font-x-generic" => undef,
	"image-x-generic" => undef,
	"package-x-generic" => undef,
	"text-html" => undef,
	"text-x-generic" => undef,
	"text-x-generic-template" => undef,
	"text-x-script" => undef,
	"video-x-generic" => undef,
	"x-office-address-book" => undef,
	"x-office-calendar" => undef,
	"x-office-document" => undef,
	"x-office-presentation" => undef,
	"x-office-spreadsheet" => undef,

# Standard Place Icons
	"folder" => undef,
	"folder-remote" => undef,
	"network-server" => undef,
	"network-workgroup" => undef,
	"start-here" => undef,
	"user-bookmarks" => undef,
	"user-desktop" => undef,
	"user-home" => undef,
	"user-trash" => undef,

# Standard Status Icons
	"appointment-missed" => undef,
	"appointment-soon" => undef,
	"audio-volume-high" => undef,
	"audio-volume-low" => undef,
	"audio-volume-medium" => undef,
	"audio-volume-muted" => undef,
	"battery-caution" => undef,
	"battery-low" => undef,
	"dialog-error" => undef,
	"dialog-information" => undef,
	"dialog-password" => undef,
	"dialog-question" => undef,
	"dialog-warning" => undef,
	"folder-drag-accept" => undef,
	"folder-open" => undef,
	"folder-visiting" => undef,
	"image-loading" => undef,
	"image-missing" => undef,
	"mail-attachment" => undef,
	"mail-unread" => undef,
	"mail-read" => undef,
	"mail-replied" => undef,
	"mail-signed" => undef,
	"mail-signed-verified" => undef,
	"media-playlist-repeat" => undef,
	"media-playlist-shuffle" => undef,
	"network-error" => undef,
	"network-idle" => undef,
	"network-offline" => undef,
	"network-receive" => undef,
	"network-transmit" => undef,
	"network-transmit-receive" => undef,
	"printer-error" => undef,
	"printer-printing" => undef,
	"security-high" => undef,
	"security-medium" => undef,
	"security-low" => undef,
	"software-update-available" => undef,
	"software-update-urgent" => undef,
	"sync-error" => undef,
	"sync-synchronizing" => undef,
	"task-due" => undef,
	"task-past-due" => undef,
	"user-available" => undef,
	"user-away" => undef,
	"user-idle" => undef,
	"user-offline" => undef,
	"user-trash-full" => undef,
	"weather-clear" => undef,
	"weather-clear-night" => undef,
	"weather-few-clouds" => undef,
	"weather-few-clouds-night" => undef,
	"weather-fog" => undef,
	"weather-overcast" => undef,
	"weather-severe-alert" => undef,
	"weather-showers" => undef,
	"weather-showers-scattered" => undef,
	"weather-snow" => undef,
	"weather-storm" => undef,
                        );
sub check_file ($)
{
	my ($file) =@_;
	
	unless (-e $file) {
		push @accessed_files, "X $file";
		return '';
	}
	
	if (-d $file) {
		push @accessed_files, "D $file";
		return 'D';
	} else {
		push @accessed_files, "F $file";
		return 'F';
	}
}

sub scan_AppDir ($$;$)
{
	my ($pool, $dir, $topdir) = @_;
	
	check_file($dir);
	$topdir = $dir unless defined $topdir;

	opendir(DIR, $dir) or return;

	foreach my $entry (readdir(DIR)) {

		if ( -f "$dir/$entry" && $entry =~ /\.desktop$/ ) {
			read_desktop_entry($pool, "$dir/$entry", $topdir);
		}
		elsif ( -d "$dir/$entry" && $entry ne '.' && $entry ne '..' && $entry ne '.hidden') {
			scan_AppDir ($pool, "$dir/$entry", $topdir);
		}
	}
	closedir DIR;
}

sub scan_DirectoryDir ($$;$)
{
	my ($pool, $dir, $topdir) = @_;

	check_file($dir);
	$topdir = $dir unless defined $topdir;

	opendir(DIR, $dir) or return;

	foreach my $entry (readdir(DIR)) {

		if ( -f "$dir/$entry" && $entry =~ /\.directory$/ ) {
			read_directory_entry($pool, "$dir/$entry", $topdir);
		}
		elsif ( -d "$dir/$entry" && $entry ne '.' && $entry ne '..' && $entry ne '.hidden') {
			scan_DirectoryDir ($pool, "$dir/$entry", $topdir);
		}
	}
	
	closedir DIR;
}

sub read_directory_entry
{
	my ($pool, $file, $topdir) = @_;
	

	unless (defined $Directory_entries{$file}) {

		check_file($file);

		open(FILE, "<$file") or return;

		my $in_desktop_entry = 0;
		my %entry;
		while (<FILE>) {
			if (/^\[/) {
				if (/^\[Desktop Entry\]/) {
					$in_desktop_entry = 1;
				}
				elsif (/^\[.*\]/) {
					$in_desktop_entry = 0;
				}
			}
			elsif ($in_desktop_entry && /^([^=]*)=([^[:cntrl:]]*)/) {
				$entry{$1} = $2;
			}
		}
		close(FILE);

		my $id = $file;
		$id =~ s/^$topdir//;
		$id =~ s/^\/*//;
		$id =~ s/\//-/g;
		$entry{'id'} = $id;

		$Directory_entries{$file} = \%entry;
	}

	my $entry = $Directory_entries{$file};
	
	$pool->{'Directory_entries'}{$entry->{'id'}} = $entry;
}

sub check_show_in ($)
{
	my ($entry) = @_;

	return 1 unless defined $entry;

	my %OnlyShowIn;
	my %NotShowIn;
	
	if (defined $entry->{'OnlyShowIn'}) {
		foreach my $showin (split /;/, $entry->{'OnlyShowIn'}) {
			$OnlyShowIn{$showin} = 1;
		}
		return 0 unless defined $OnlyShowIn{$desktop_name};
	}
	if (defined $entry->{'NotShowIn'}) {
		foreach my $showin (split /;/, $entry->{'NotShowIn'}) {
			$NotShowIn{$showin} = 1;
		}
		return 0 if defined $NotShowIn{$desktop_name} ;
	}
	
	return 1;
}

sub read_desktop_entry
{
	my ($pool, $file, $topdir) = @_;


	unless (defined $Desktop_entries{$file}) {

		check_file($file);
	
		open(FILE, "<$file") or return;

		my $in_desktop_entry = 0;
		my %entry;
		while (<FILE>) {
			if (/^\[/) {
				if (/^\[Desktop Entry\]/) {
					$in_desktop_entry = 1;
				}
				elsif (/^\[.*\]/) {
					$in_desktop_entry = 0;
				}
			}
			elsif ($in_desktop_entry && /^([^=]*)=([^[:cntrl:]]*)/) {
				$entry{$1} = $2;
			}
		}
		close(FILE);

		my $id = $file;
		$id =~ s/^$topdir//;
		$id =~ s/^\/*//;
		$id =~ s/\//-/g;
		$entry{'id'} = $id;
		$entry{'file'} = $file;

		$entry{'refcount'} = 0;
		
		$Desktop_entries{$file} = \%entry;
	}

	my $entry = $Desktop_entries{$file};

	return unless defined $entry->{'Name'};	
	return unless defined $entry->{'Exec'};	
	return if $entry->{'Hidden'} eq 'true';	
	return if $entry->{'X-SuSE-Unimportant'} eq 'true';	
	return if $entry->{'NoDisplay'} eq 'true';	

	return unless check_show_in($entry);

	return if defined $entry->{'NotShowIn'} && $entry->{'NotShowIn'} eq $desktop_name;	
	
	
	if (defined $pool) {
	
		foreach my $category (split /;/, $entry->{'Categories'}) {
		        $pool->{'Categories'}{$category} = [] unless defined $pool->{'Categories'}{$category};
			push @{$pool->{'Categories'}{$category}}, $entry;
		}

		$pool->{'Desktop_entries'}{$entry->{'id'}} = $entry;
	}

	return $entry;
}

my $cached_pool;

sub read_desktop_entries ($$)
{
	my ($directory_paths, $desktop_paths) = @_;

	if ($cached_pool->{'Directory_paths'} eq $directory_paths &&
	    $cached_pool->{'Desktop_paths'} eq $desktop_paths) {
	
		return $cached_pool;
	}

	
	my $pool = {'Desktop_entries' => {},
		       'Categories' => {},
		       'Directory_entries' => {},
		       'Directory_paths' => $directory_paths,
		       'Desktop_paths' => $desktop_paths
		       };
		       
	foreach my $dir (split /:/, $directory_paths) {
		next if $dir =~ /^\s*$/;
		scan_DirectoryDir($pool, $dir);
	}
	
	foreach my $dir (split /:/, $desktop_paths) {
		next if $dir =~ /^\s*$/;
		scan_AppDir($pool, $dir);
	}

	$cached_pool = $pool;
	
	return $pool;
}

sub dump_entry_list ($)
{
	my ($list) = @_;

	print "list: ";	
	foreach my $entry (@$list) {
		print "$entry->{id} ";
	}
	print "\n";

}

sub get_directory_entry ($$)
{
	my ($entry, $pool) = @_;
	
	return $pool->{'Directory_entries'}{$entry};
}

sub interpret_Include
{
	my ($tree, $entries, $pool) = @_;
	my %exist;
	
	my $i = 0;


	my @list = interpret_entry_node($tree, 'Or', $pool);

	foreach my $e (@$entries) {
		if ($e->{type} eq 'desktop') {
			$exist{$e->{desktop}} = 1;
		}
	}


#	dump_entry_list(\@list);

	foreach my $entry (@list) {
		
		next if $exist{$entry};
		
		push @$entries, {type => 'desktop', desktop => $entry};
		$entry->{'refcount'}++;
		
		$exist{$entry} = 1;
			
	}
}

sub interpret_Exclude
{
	my ($tree, $entries, $pool) = @_;
	
	my $i = 0;

	my @list = interpret_entry_node($tree, 'Or', $pool);


	foreach my $entry (@list) {
		
		my $i = 0;
		while (defined $entries->[$i]) {
			my $exist = $entries->[$i];
			if ($exist->{type} eq 'desktop' &&
			    $exist->{desktop} eq $entry) {
				splice @$entries, $i, 1;
				$entry->{'refcount'}--;
			}
			else {
				$i++;
			}
		}
	}
}


sub interpret_entry_node ($$$)
{
	my ($tree, $node, $pool) = @_;

	my $i = 0;
	$i++ if (ref($tree->[$i]) eq 'HASH');
	
	my @subtree;
	
	while (defined $tree->[$i]) {
		if ($tree->[$i] eq 'Filename') {
			$i++;
			if (ref($tree->[$i][0]) eq 'HASH' and $tree->[$i][1] eq '0') {
				my $entry = $tree->[$i][2];
				if (defined $pool->{'Desktop_entries'}{$entry}) {
					push @subtree, [$pool->{'Desktop_entries'}{$entry}];
				}
				else {
					push @subtree, [];
				}
			}
			else {
				 print STDERR "Filename\n";
				 exit 1 if $die_on_error;
			}
			$i++;
		}
		elsif ($tree->[$i] eq 'Category') {
			$i++;
			if (ref($tree->[$i][0]) eq 'HASH' and $tree->[$i][1] eq '0') {
				my $category = $tree->[$i][2];
				if (defined $pool->{'Categories'}{$category}) {
					push @subtree, $pool->{'Categories'}{$category};
				}
				else {
					push @subtree, [];
				}
			}
			else {
				 print STDERR "Category\n";
				 exit 1 if $die_on_error;
			}
			$i++;
		}
		elsif ($tree->[$i] eq 'All') {
			$i++;
			if (values %{$pool->{'Desktop_entries'}} > 0) {
				push @subtree, [values %{$pool->{'Desktop_entries'}}];
			}
			else {
				push @subtree, [];
			}
			$i++;
		}
		elsif ($tree->[$i] eq '0') {
			$i++;
			$i++;
		}
		else {
			my @res = interpret_entry_node($tree->[$i+1], $tree->[$i], $pool);
			push @subtree, \@res;
			$i++; $i++;
		}
	}

	if ($node eq 'Or')
	{
#		print "or - \n";

		my %used;
		my @res;
		foreach my $st (@subtree) {
#			print "  st: ";
#			dump_entry_list($st);
			foreach my $entry (@$st) {
				if (! defined $used{$entry}) {
					push @res, $entry;
					$used{$entry} = 1;
				}
			}
		}
#		print " res: ";
#		dump_entry_list(\@res);
		return @res;
	} elsif ($node eq 'And')
	{
		my %used;
		my @res;
#		print "and - \n";
		my $cnt = @subtree;
		my $min = @{$subtree[0]};
		my $min_idx = 0;
		my $idx = 0;
		
		foreach my $st (@subtree) {
#			print "  st: ";
#			dump_entry_list($st);

			my $num = @$st;
			if ($num < $min) {
				$min = $num;
				$min_idx = $idx;
			}
			
			my %dupes;
			foreach my $entry (@$st) {
				next if $dupes{$entry};
				$dupes{$entry} = 1;
				
				if (! defined $used{$entry}) {
					$used{$entry} = 1;
				}
				else {
					$used{$entry} ++
				}
			}
			
			$idx ++;
		}
		return () if $cnt == 0;
		foreach my $entry (@{$subtree[$min_idx]}) {
			push @res, $entry if $used{$entry} == $cnt;
		}
		
#		print " res: ";
#		dump_entry_list(\@res);
		return @res;
	} elsif ($node eq 'Not')
	{
		my %used;
		my @res;
#		print "not - \n";
		my $cnt = @subtree;
		foreach my $st (@subtree) {
#			print "  st: ";
#			dump_entry_list($st);
			foreach my $entry (@$st) {
				$used{$entry} = 1;
			}
		}
		return if $cnt == 0;
		foreach my $entry (values %{$pool->{'Desktop_entries'}}) {
			push @res, $entry if !defined $used{$entry};
		}
		
#		print " res: ";
#		dump_entry_list(\@res);
		return @res;
	} else {
		print STDERR "Can't use '$node' inside <Include> or <Exclude>\n";
		exit 1 if $die_on_error;
		return ();
	}
}

sub interpret_root ($$)
{
	my ($tree, $topdir) = @_;
	if ($tree->[0] eq 'Menu') {
		return interpret_menu($tree->[1]);
	}
	else {
		print STDERR "No toplevel Menu\n";
		exit 1 if $die_on_error;
		return;
	}
}


sub interpret_menu ($;$$)
{
	my ($tree, $directory_paths, $desktop_paths) = @_;
	
	$directory_paths = '' unless defined $directory_paths;
	$desktop_paths = '' unless defined $desktop_paths;
	
	my %menu = ('entries' => [], 
	            'OnlyUnallocated' => 0,
		    'DontShowIfEmpty' => 0,
		    'Deleted' => 0);

	my $i = 0;

	$i++ if (ref($tree->[$i]) eq 'HASH');

	while (defined $tree->[$i]) {
		if ($tree->[$i] eq 'AppDir') {
			if (ref($tree->[$i + 1][0]) eq 'HASH' and $tree->[$i + 1][1] eq '0') {
				$desktop_paths .= ':' . $tree->[$i + 1][2];
				splice @$tree, $i, 2;
			}
			else {
				print STDERR "wrong AppDir\n";
				exit 1 if $die_on_error;
				$i++;
				$i++;
			}
		}
		elsif ($tree->[$i] eq 'DefaultAppDirs') {
			$desktop_paths .= ':' . $DefaultAppDirs;
			splice @$tree, $i, 2;
		}
		elsif ($tree->[$i] eq 'DirectoryDir') {
			if (ref($tree->[$i + 1][0]) eq 'HASH' and $tree->[$i + 1][1] eq '0') {
				$directory_paths .= ':' . $tree->[$i + 1][2];
				splice @$tree, $i, 2;
			}
			else {
				print STDERR "wrong DirectoryDir\n";
				exit 1 if $die_on_error;
				$i++;
				$i++;
			}
		}
		elsif ($tree->[$i] eq 'DefaultDirectoryDirs') {
			$directory_paths .= ':' . $DefaultDirectoryDirs;
			splice @$tree, $i, 2;
		}
		else {
			$i++;
			$i++;
		}
	}

	
	$menu{directory_paths} = $directory_paths;
	$menu{desktop_paths} = $desktop_paths;
	
	my $pool = read_desktop_entries($directory_paths, $desktop_paths);


	$i = 0;

	$i++ if (ref($tree->[$i]) eq 'HASH');

	while (defined $tree->[$i]) {
		if ($tree->[$i] eq 'Menu') {
			$i++;
			my $submenu = interpret_menu($tree->[$i], $directory_paths, $desktop_paths);
			push @{$menu{'entries'}}, {type => 'menu', menu => $submenu};
			$i++;
		}
		elsif ($tree->[$i] eq 'Name') {
			$i++;
			if (ref($tree->[$i][0]) eq 'HASH' and $tree->[$i][1] eq '0') {
				$menu{'Name'} = $tree->[$i][2];
			}
			else {
				 print STDERR "wrong Name\n";
				 exit 1 if $die_on_error;
			}
			$i++;
		}
		elsif ($tree->[$i] eq 'Directory') {
			$i++;
			if (ref($tree->[$i][0]) eq 'HASH' and $tree->[$i][1] eq '0') {
				$menu{'Directory'} = get_directory_entry($tree->[$i][2], $pool);
#				print "Directory " . $tree->[$i][2] . "\n"; 
			}
			else {
				 print STDERR "wrong Directory\n";
				 exit 1 if $die_on_error;
			}
			$i++;
		}
		elsif ($tree->[$i] eq 'OnlyUnallocated') {
			$menu{'OnlyUnallocated'} = 1;
			$i++;
			$i++;
		}
		elsif ($tree->[$i] eq 'DontShowIfEmpty') {
			$menu{'DontShowIfEmpty'} = 1;
			$i++;
			$i++;
		}
		elsif ($tree->[$i] eq 'Deleted') {
			$menu{'Deleted'} = 1;
			$i++;
			$i++;
		}
		elsif ($tree->[$i] eq 'NotDeleted') {
			$menu{'Deleted'} = 0;
			$i++;
			$i++;
		}
		elsif ($tree->[$i] eq 'Include') {
			$i++;
			interpret_Include($tree->[$i], $menu{'entries'}, $pool);
			$i++;
		}
		elsif ($tree->[$i] eq 'Exclude') {
			$i++;
			interpret_Exclude($tree->[$i], $menu{'entries'}, $pool);
			$i++;
		}
                elsif ($tree->[$i] eq 'DefaultLayout') {
                        $i++;
			# just ignore, not important for this test
                        $i++;
                }
		elsif ($tree->[$i] eq '0') {
			$i++;
			if ($tree->[$i] !~ /^\s*$/) {
				print STDERR "skip '$tree->[$i]'\n" ;
				exit 1 if $die_on_error;
			}
			$i++;
		}
		else {
			print STDERR "Unknown '$tree->[$i]':\n";
			$i++;
			print STDERR "        '@{$tree->[$i]}'\n";
			$i++;
			exit 1 if $die_on_error;
		}
	}
	
	return \%menu;
}

		
sub read_menu ($;$)
{
	my ($file, $basedir) = @_;

	
	if ($file !~ /^\// && defined $basedir) {
		$file = "$basedir/$file";
	}

	unless (defined $basedir) {
		$basedir = $file;
		$basedir =~ s/\/[^\/]*$//;
	}

	unless (check_file($file)) {
		print STDERR "WARNING: '$file' does not exist\n";
		return ['Menu', [{}]];
	}	

	print STDERR "reading '$file'\n" if $verbose; 

	my $parser = new XML::Parser(Style => 'Tree');
	my $tree = $parser->parsefile($file);
	
	my $DefaultMergeDir = $file;
	$DefaultMergeDir =~ s/^.*\///;
	$DefaultMergeDir =~ s/\.menu$/-merged/;
	
	read_includes($tree, $basedir, $DefaultMergeDir);
	
	return $tree
}

sub read_menu_dir ($;$)
{
	my ($dir, $basedir) = @_;

	my @out;
	
	if ($dir !~ /^\// && defined $basedir) {
		$dir = "$basedir/$dir";
	}


	check_file($dir);
	
	opendir(DIR, $dir);

	foreach my $entry (readdir(DIR)) {

		if ( -f "$dir/$entry" && $entry =~ /\.menu$/ ) {
			my $menu = read_menu("$dir/$entry");
			$menu = remove_toplevel_Menu($menu);
			push @out, @$menu;
		}
	}
	closedir DIR;

	return \@out;
}

sub quote_xml ($)
{
	my ($txt) = @_;
	
	$txt =~ s/&/&amp;/g;
	$txt =~ s/</&lt;/g;
	$txt =~ s/>/&gt;/g;
	return $txt;
}

sub read_legacy_dir ($;$)
{
	my ($dir,$basedir) = @_;
	my $out;

	$dir =~ s/\/*$//;
	
	$basedir = $dir unless defined $basedir;
	
	return "" if check_file($dir) ne 'D';
	
	$out = "<Menu>\n";
	
	if ($dir eq $basedir) {
		my $xmldir = quote_xml($dir);
	
		$out .= "<AppDir>$xmldir</AppDir>\n";
		$out .= "<DirectoryDir>$xmldir</DirectoryDir>\n";
	}
	else {
		my $name = $dir;
		$name =~ s/\/*$//;
		$name =~ s/^.*\///;
		
		$name = quote_xml($name); 
		
		$out .= "<Name>$name</Name>\n"; 
	}
	
	
	if (-f "$dir/.directory") {

		my $dir_id = "$dir/.directory";
		$dir_id =~ s/^$basedir//;
		$dir_id =~ s/^\///;
		$dir_id = quote_xml($dir_id);
		
		$out .= "<Directory>$dir_id</Directory>\n";
	}

	opendir(DIR, $dir);

	foreach my $entry (readdir(DIR)) {

		if ( -f "$dir/$entry" && $entry =~ /\.desktop$/ ) {
			my $id = "$dir/$entry";
			$id =~ s/^$basedir//;
			$id =~ s/^\///;
			$id =~ s/\//-/g;
			$id = quote_xml($id);
			
			my $desktop = read_desktop_entry(undef, "$dir/$entry", $basedir);
			$out .= "<Include><Filename>$id</Filename></Include>\n" unless defined $desktop->{'Categories'}
		}
		elsif ( -d "$dir/$entry" && $entry ne '.' && $entry ne '..' && $entry ne '.hidden') {
			$out .= read_legacy_dir("$dir/$entry", $basedir);
		}
	}
	closedir DIR;
	$out .= "</Menu>\n";
	return $out;
}

sub remove_toplevel_Menu ($)
{
	my ($tree) = @_;
	if ($tree->[0] eq 'Menu') {
		shift @{$tree->[1]} if (ref($tree->[1][0]) eq 'HASH');
		return $tree->[1];
	}
	else {
		print STDERR "No toplevel Menu\n";
		exit 1 if $die_on_error;
		return;
	}
}

sub read_includes ($$$)
{
	my ($tree, $basedir, $DefaultMergeDir) = @_;

	my $i = 0;

	$i++ if (ref($tree->[$i]) eq 'HASH');

	while (defined $tree->[$i]) {
		if ($tree->[$i] eq 'MergeFile') {
			if (ref($tree->[$i + 1][0]) eq 'HASH' and $tree->[$i + 1][1] eq '0') {
				my $add_tree = read_menu($tree->[$i + 1][2], $basedir);
				$add_tree = remove_toplevel_Menu($add_tree);
				
				splice @$tree, $i, 2, @$add_tree;
				
			}
			else {
				print STDERR "wrong MergeFile\n";
				exit 1 if $die_on_error;
				$i++;
				$i++;
			}
			
		}
		elsif ($tree->[$i] eq 'MergeDir') {
			if (ref($tree->[$i + 1][0]) eq 'HASH' and $tree->[$i + 1][1] eq '0') {
				
				my $add_tree = read_menu_dir($tree->[$i + 1][2], $basedir);
				
				splice @$tree, $i, 2, @$add_tree;
				
			}
			else {
				print STDERR "wrong MergeFile\n";
				exit 1 if $die_on_error;
				$i++;
				$i++;
			}
			
		}
		elsif ($tree->[$i] eq 'DefaultMergeDirs') {
			my $add_tree = read_menu_dir($DefaultMergeDir, $basedir);
			splice @$tree, $i, 2, @$add_tree;
		}
		elsif ($tree->[$i] eq 'LegacyDir') {
			if (ref($tree->[$i + 1][0]) eq 'HASH' and $tree->[$i + 1][1] eq '0') {
				
				my $xml = read_legacy_dir($tree->[$i + 1][2]);
				print STDERR "reading legacy directory '" . $tree->[$i + 1][2] . "'\n" if $verbose; 

				my $parser = new XML::Parser(Style => 'Tree');
				my $add_tree = $parser->parse($xml);
				$add_tree = remove_toplevel_Menu($add_tree);
				splice @$tree, $i, 2, @$add_tree;
				
			}
			else {
				print STDERR "wrong LegacyDir\n";
				exit 1 if $die_on_error;
				$i++;
				$i++;
			}
			
		}
		elsif ($tree->[$i] eq 'KDELegacyDirs') {
			my @out;
			foreach my $dir (@KDELegacyDirs) {
				my $xml = read_legacy_dir($dir);
				print STDERR "reading legacy directory '$dir'\n" if $verbose; 
				
				my $parser = new XML::Parser(Style => 'Tree');
				my $add_tree = $parser->parse($xml);
				$add_tree = remove_toplevel_Menu($add_tree);
				push @out, @$add_tree
			}
			splice @$tree, $i, 2, @out;
		}
		elsif ($tree->[$i] eq 'Menu') {
			$i++;
			read_includes($tree->[$i], $basedir, $DefaultMergeDir);
			$i++;
		}
		else {
			$i++;
			$i++;
		}
	}
}

sub get_menu_name ($)
{
	my ($tree) = @_;
	my $name;
	
	my $i = 0;

	$i++ if (ref($tree->[$i]) eq 'HASH');

	while (defined $tree->[$i]) {
		if ($tree->[$i] eq 'Name') {
			$i++;
			if (ref($tree->[$i][0]) eq 'HASH' and $tree->[$i][1] eq '0') {
				$name = $tree->[$i][2];
				last;
			}
			else {
				 print STDERR "wrong Name\n";
			}
			$i++;
		}
		else {
			$i++;
			$i++;
		}
	}
	
	unless (defined $name) {
		print STDERR "Menu has no name element\n";
	}
	return $name;
}


sub append_menu ($$)
{
	my ($target, $source) = @_;
	
	my $i = 0;
	
	$i++ if (ref($source->[$i]) eq 'HASH');
	
	while (defined $source->[$i]) {
		if ($source->[$i] ne 'Name') {
			push @$target, $source->[$i];
			push @$target, $source->[$i + 1];
		}
		
		$i++;
		$i++;
	}
}

			
sub merge_menus ($)
{
	my ($tree) = @_;
	
	my %used; #menu name already used

	my $i = 0;

	$i++ if (ref($tree->[$i]) eq 'HASH');

	while (defined $tree->[$i]) {
		if ($tree->[$i] eq 'Menu') {
			my $name = get_menu_name($tree->[$i + 1]);
			if (defined $used{$name}) { #second menu with the same name
				my $target = $used{$name};
				
				append_menu($tree->[$target], $tree->[$i + 1]);
				
				splice @$tree, $i, 2;
			}
			else { # first appearance
				$used{$name} = $i + 1;
				$i++;
				$i++;
			}
		}
		else {
			$i++;
			$i++;
		}
	}


	$i = 0;
	$i++ if (ref($tree->[$i]) eq 'HASH');
	
	while (defined $tree->[$i]) {
		if ($tree->[$i] eq 'Menu') {
			merge_menus($tree->[$i + 1]);
		}
		$i++;
		$i++;
	}
}

sub read_Move ($$)
{
	my ($tree, $hash) = @_;
	
	my $i = 0;
	
	my $old = '';
	

	$i++ if (ref($tree->[$i]) eq 'HASH');

	while (defined $tree->[$i]) {
		if ($tree->[$i] eq 'Old') {
			$i++;
			if (ref($tree->[$i][0]) eq 'HASH' and $tree->[$i][1] eq '0') {
				$old = $tree->[$i][2];
			}
			else {
				print STDERR "wrong Old\n";
				exit 1 if $die_on_error;
			}
			$i++;
		}
		if ($tree->[$i] eq 'New') {
			$i++;
			if (ref($tree->[$i][0]) eq 'HASH' and $tree->[$i][1] eq '0') {
				$hash->{$old} = $tree->[$i][2];
			}
			else {
				print STDERR "wrong New\n";
				exit 1 if $die_on_error;
			}
			$i++;
		}
		else {
			$i++;
			$i++;
		}
	}
}

sub find_menu_in_tree ($$)
{
	my ($path, $tree) = @_;
	
	my $root = $path;
	$root =~ s/\/.*$//;
	
	my $subpath = $path;
	$subpath =~ s/^[^\/]*\/*//; 	
	
	my $i = 0;

	$i++ if (ref($tree->[$i]) eq 'HASH');

	while (defined $tree->[$i]) {
		if ($tree->[$i] eq 'Menu') {
			if ($root eq get_menu_name($tree->[$i + 1])) {
			
				if ($subpath eq '') {
					return { 'parent' => $tree, 'index' => $i, 'menu' => $tree->[$i + 1]}; 
				}
				return find_menu_in_tree($subpath, $tree->[$i + 1]);
			}
		}
		
		$i++;
		$i++;
	}
	
	return undef;
}

sub copy_menu ($$)
{
	my ($path, $tree) = @_;
	
	my $tail;
	my $child;
	
	foreach my $elem (reverse split(/\//, $path)) {
		next if $elem eq '';
		
		my $menu = [{}, 'Name', [{}, 0, $elem]];
		push @$menu, ('Menu', $child) if defined $child;
		
		$tail = $menu unless defined $tail;
		$child = $menu;
	}
	
	append_menu($tail, $tree);
	
	return $child;
}

sub move_menus ($)
{
	my ($tree) = @_;

#	print "@$tree\n";	
	my %move;

	my $i = 0;

	$i++ if (ref($tree->[$i]) eq 'HASH');

	while (defined $tree->[$i]) {
		if ($tree->[$i] eq 'Move') {
		
			read_Move($tree->[$i + 1], \%move);
			splice @$tree, $i, 2;		
		}
		else {
			$i++;
			$i++;
		}
	}

	foreach my $source (keys %move) {
		my $sourceinfo = find_menu_in_tree($source, $tree);
		
		if (defined $sourceinfo) {
			my $target = copy_menu($move{$source}, $sourceinfo->{'menu'});
			splice @{$sourceinfo->{'parent'}}, $sourceinfo->{'index'}, 2; 
			push @$tree, ('Menu', $target);
			merge_menus($tree);
		}			
	}

	$i = 0;
	$i++ if (ref($tree->[$i]) eq 'HASH');
	
	while (defined $tree->[$i]) {
		if ($tree->[$i] eq 'Menu') {
			move_menus($tree->[$i + 1]);
		}
		$i++;
		$i++;
	}
}

sub remove_allocated ($)
{
	my ($menu) = @_;


	my $i = 0;
	while ($i < @{$menu->{'entries'}}) {
		my $entry = $menu->{'entries'}[$i];
	
		if ($entry->{type} eq 'menu') {
			 remove_allocated($entry->{menu});
			 $i++;
		} 
		elsif ($entry->{type} eq 'desktop' && 
		       $menu->{'OnlyUnallocated'} &&
		       $entry->{desktop}{'refcount'} > 1) {
		       
		        $entry->{desktop}{'refcount'}--;
			splice @{$menu->{'entries'}}, $i, 1;
		}
		else {
			$i++;
		}
		
		
	}
	return 0;
}


sub remove_empty_menus ($)
{
	my ($menu) = @_;


	my $i = 0;
	while ($i < @{$menu->{'entries'}}) {
		my $entry = $menu->{'entries'}[$i];
	
		if ($entry->{type} eq 'menu' && remove_empty_menus($entry->{menu})) {
			splice @{$menu->{'entries'}}, $i, 1;
		}
		else {
			$i++;
		}
		
		
	}
	
	return 1 if @{$menu->{'entries'}} == 0; # && $menu->{'DontShowIfEmpty'}; #menu is empty
	
	return 0;
}


sub prepare_exec ($$)
{
	my ($exec, $desktop) = @_;
	
	$exec =~ s/%f//g;
	$exec =~ s/%F//g;
	$exec =~ s/%u//g;
	$exec =~ s/%U//g;
	$exec =~ s/%d//g;
	$exec =~ s/%D//g;
	$exec =~ s/%n//g;
	$exec =~ s/%N//g;
	$exec =~ s/%i//g;
	$exec =~ s/%k//g;
	$exec =~ s/%v//g;
	$exec =~ s/%m//g;
	
	my $caption = $desktop->{Name};
	
	$exec =~ s/%c/$caption/g;

	$exec =~ s/%%/%/g;

	$exec = "xterm -e $exec" if $desktop->{Terminal} eq '1' || $desktop->{Terminal} eq 'true';

	$exec = "$root_cmd $exec" if $desktop->{'X-KDE-SubstituteUID'} eq '1' || $desktop->{'X-KDE-SubstituteUID'} eq 'true';
	return $exec;
}

sub get_loc_entry ($$)
{
	my ($desktop, $entry) = @_;
	
	foreach my $key (@language_keys) {
		my $loc_entry = $entry . "[$key]";
		return $desktop->{$loc_entry} if defined $desktop->{$loc_entry} && $desktop->{$loc_entry} !~ /^\s*$/;
	}
	
	return $desktop->{$entry};
}

sub preprocess_menu ($)
{
	# localize, sort, prepare_exec
	my ($menu) = @_;
	
	return 0 if $menu->{'Deleted'};
	return 0 unless check_show_in($menu->{'Directory'});
	return 0 if defined $menu->{'Directory'} && $menu->{'Directory'}->{'NoDisplay'} eq 'true';
	
	my $menu_name = $menu->{'Name'};
	
	if (defined $menu->{'Directory'}) {
		my $directory = $menu->{'Directory'};
		
		my $directory_name = get_loc_entry($directory, 'Name');
		
		if (defined $directory_name) {
			Encode::from_to($directory_name, "utf8", $charset) 
				if !defined $directory->{"Encoding"} || $directory->{"Encoding"} eq 'UTF-8';
				
			$menu_name = $directory_name;
		}
	}

	$menu->{'PrepName'} = $menu_name;

	my $i = 0;
	while (defined $menu->{'entries'}[$i]) {
		my $entry = $menu->{'entries'}[$i];
		if ($entry->{'type'} eq 'desktop') {
			my $desktop = $entry->{desktop};
			
			my $name = $desktop->{'id'};
			
			my $desktop_name = get_loc_entry($desktop, 'Name');
			
			if (defined $desktop_name) {
				Encode::from_to($desktop_name, "utf8", $charset) 
					if !defined $desktop->{"Encoding"} || $desktop->{"Encoding"} eq 'UTF-8';
				
				$name = $desktop_name;
			} 
			
			$desktop->{'PrepName'} = $name;
			$entry->{'Name'} = $name;
			$entry->{'PrepName'} = $name;
			
			$desktop->{'PrepExec'} = prepare_exec($desktop->{Exec}, $desktop);
			$i++;
		} 
		elsif ($entry->{type} eq 'menu') {
			if (preprocess_menu ($entry->{'menu'})) {
				$entry->{'Name'} = $entry->{'menu'}{'Name'};
				$entry->{'PrepName'} = $entry->{'menu'}{'PrepName'};
				$i++;
			}
			else {
				splice @{$menu->{'entries'}}, $i, 1;
			}
		}
		else {
			print STDERR "wrong menu entry type: $entry->{type}";
			exit 1 if $die_on_error;
			splice @{$menu->{'entries'}}, $i, 1;			
		}
	}
	
	$menu->{'entries'} = [ sort {$b->{'type'} cmp $a->{'type'} || $a->{'PrepName'} cmp $b->{'PrepName'}} @{$menu->{'entries'}} ];

	my $i = 0;
	my $prev_entry;
	while (defined $menu->{'entries'}[$i]) {
		my $entry = $menu->{'entries'}[$i];
		if (defined $prev_entry && 
		    $entry->{'type'} eq 'desktop' &&
		    $prev_entry->{'type'} eq 'desktop' &&
		    $prev_entry->{'PrepName'} eq $entry->{'PrepName'} &&
		    $prev_entry->{'desktop'}->{'PrepExec'} eq $entry->{'desktop'}->{'PrepExec'}) {
			splice @{$menu->{'entries'}}, $i, 1;
		}
		else {
			$prev_entry = $entry;
			$i++;
		}
	}
	
	return 1;
}


sub output_wmaker_menu ($;$)
{
	my ($menu, $indent) = @_;
	
	my $output = '';
	
	$indent = 0 unless defined $indent;

	my $menu_name = $menu->{'PrepName'};
	
	$output .= ' ' x $indent;
	$output .= "\"$menu_name\" MENU\n";
	
	foreach my $entry (@{$menu->{'entries'}}) {
		if ($entry->{type} eq 'desktop') {
			my $desktop = $entry->{desktop};
			
			my $name = $desktop->{'PrepName'};
			my $exec = $desktop->{'PrepExec'};
			
			$output .= ' ' x $indent;
			$output .= " \"$name\" EXEC $exec\n";
		} 
		elsif ($entry->{type} eq 'menu') {
			$output .= output_wmaker_menu ($entry->{'menu'}, $indent + 1);
		}
		else {
			print STDERR "wrong menu entry type: $entry->{type}";
		}
		
	}
	$output .= ' ' x $indent;
	$output .= "\"$menu_name\" END\n";
	
	return $output;
}

sub output_fvwm2_menu ($;$$)
{
	my ($menu, $toplevel, $path) = @_;
	
	my $output = '';
	
	$path = '' unless defined $path;
	
	$toplevel = 1 unless defined $toplevel;

	my $menu_name = $menu->{'PrepName'};
	my $menu_id = "$path-" . $menu->{'Name'};
	$menu_id =~ s/\s/_/g;
	
	$menu_id = 'xdg_menu' if $toplevel;
	
	foreach my $entry (@{$menu->{'entries'}}) {
		if ($entry->{type} eq 'menu') {
			$output .= output_fvwm2_menu($entry->{'menu'}, 0, $menu_id);
		}
	}

	$output .= "DestroyMenu \"$menu_id\"\n";
	$output .= "AddToMenu \"$menu_id\" \"$menu_name\" Title\n";
	
	foreach my $entry (@{$menu->{'entries'}}) {
		if ($entry->{type} eq 'desktop') {
			my $desktop = $entry->{desktop};
			
			my $name = $desktop->{'PrepName'};
			my $exec = $desktop->{'PrepExec'};
			
			$output .= "+		 \"$name\" Exec $exec\n";
		} 
		elsif ($entry->{type} eq 'menu') {
			my $name = $entry->{'menu'}{'PrepName'};
			my $id = "$menu_id-" . $entry->{'menu'}{'Name'};
			$id =~ s/\s/_/g;

			$output .= "+		 \"$name\" Popup \"$id\"\n";
		}
		else {
			print STDERR "wrong menu entry type: $entry->{type}";
		}
		
	}
	$output .= "\n";
	
	return $output;
}

sub output_blackbox_menu ($;$)
{
	my ($menu, $indent) = @_;
	
	my $output = '';
	
	$indent = 0 unless defined $indent;

	my $menu_name = $menu->{'PrepName'};
	
	$output .= ' ' x $indent;
	$output .= "[submenu] ($menu_name)\n";
	
	foreach my $entry (@{$menu->{'entries'}}) {
		if ($entry->{type} eq 'desktop') {
			my $desktop = $entry->{desktop};
			
			my $name = $desktop->{'PrepName'};
			my $exec = $desktop->{'PrepExec'};
			
			$output .= ' ' x $indent;
			$output .= "    [exec] ($name) {$exec}\n";
		} 
		elsif ($entry->{type} eq 'menu') {
			$output .= output_blackbox_menu ($entry->{'menu'}, $indent + 1);
		}
		else {
			print STDERR "wrong menu entry type: $entry->{type}";
		}
		
	}
	$output .= ' ' x $indent;
	$output .= "[end] # ($menu_name)\n";
	
	return $output;
}

sub output_icewm_menu ($;$)
{
	my ($menu, $indent) = @_;
	
	my $output = '';
	
	$indent = 0 unless defined $indent;

	my $menu_name = $menu->{'PrepName'};
	
	$output .= ' ' x $indent;
	$output .= "menu \"$menu_name\" folder {\n";
	
	foreach my $entry (@{$menu->{'entries'}}) {
		if ($entry->{type} eq 'desktop') {
			my $desktop = $entry->{desktop};
			
			my $name = $desktop->{'PrepName'};
			my $exec = $desktop->{'PrepExec'};
			
			$output .= ' ' x $indent;
			$output .= " prog \"$name\" none $exec\n";
		} 
		elsif ($entry->{type} eq 'menu') {
			$output .= output_icewm_menu ($entry->{'menu'}, $indent + 1);
		}
		else {
			print STDERR "wrong menu entry type: $entry->{type}";
		}
		
	}
	$output .= ' ' x $indent;
	$output .= "}\n";
	
	return $output;
}

sub find_icon ($@)
{
	my $icon = shift;
	my @icon_dirs = ();
	my @themes = ('pixmaps','icons/hicolor','icons/Adwaita');
	push @themes, @_;
	my @sizes = ('16x16','22x22','24x24','32x32','36x36','48x48',
		     '64x64','72x72','96x96','128x128','192x192','scalable');
	my @types = ('actions','animations','apps','categories','devices',
		     'emblems','emotes','filesystems','intl','mimetypes',
		     'places','status','stock');

	for my $d1 ('/usr','/opt/kde3', '/opt/gnome') {
		for my $d2 (@themes) {
			push @icon_dirs, "$d1/share/$d2";
			for my $d3 (@sizes) {
				push @icon_dirs, map { "$d1/share/$d2/$d3/$_" } @types;
			}
		}
	}
	my $found = 0;
	find(sub { $found = 1 if
		       $_ eq "$icon" ||
		       $_ eq "$icon.png" ||
		       $_ eq "$icon.jpg" ||
		       $_ eq "$icon.xpm" ||
		       $_ eq "$icon.svg" ||
		       $_ eq "$icon.svgz"; },
	     map { "$ENV{RPM_BUILD_ROOT}$_" } @icon_dirs);
	find(sub { $found = 1 if
		       $_ eq "$icon" ||
		       $_ eq "$icon.png" ||
		       $_ eq "$icon.jpg" ||
		       $_ eq "$icon.xpm" ||
		       $_ eq "$icon.svg" ||
		       $_ eq "$icon.svgz"; }, @icon_dirs)
	    unless $found;
	return $found;
}

sub output_validate ($;$)
{
	my ($menu, $indent) = @_;
	my $output = '';
	
	foreach my $entry (@{$menu->{'entries'}}) {
		if ($entry->{type} eq 'desktop') {
			my $desktop = $entry->{desktop};
			
			if ($desktop->{Name}  eq '') {
				$output .= "ERROR: Empty Name in $desktop->{file}\n";
				$validate_error = 1;
			}
			if ( $menu->{Name} eq 'Applications' or $menu->{Name} eq 'More Programs' ) {
				$output .= "ERROR: No sufficient Category definition: $desktop->{file} \n";
				$output .= "Please refer to https://en.opensuse.org/openSUSE:Packaging_desktop_menu_categories\n";
				$validate_error = 1;
			}

			if ($desktop->{Icon}  eq '') {
				$output .= "WARNING: Empty Icon in $desktop->{file}\n";
			}
			else {
				if ( substr($desktop->{Icon}, 0, 1) eq '/' ) {
					$output .= "WARNING: absolute path in Icon line: $desktop->{file}\n" if substr($desktop->{Icon}, 0, 1) eq '/';
					if ( ! -e "$ENV{RPM_BUILD_ROOT}/$desktop->{Icon}" ) {
						$output .= "ERROR: Icon file not installed: $desktop->{file} ($desktop->{Icon})\n";
						$validate_error = 1;
					}

				 } elsif ( exists $standardIconNames{$desktop->{Icon}} ) {
                                        # standard name, accept it without any action
				 } else {
					$output .= "WARNING: file extension in Icon line: $desktop->{file}\n" if substr($desktop->{Icon}, -4, 1) eq '.';
					my @xtrapath = ();
					push @xtrapath, 'icons/crystalsvg'
					    if $desktop->{'OnlyShowIn'} eq "KDE";
					push @xtrapath, 'icons/gnome'
					    if $desktop->{'OnlyShowIn'} eq "GNOME";
					if (!find_icon($desktop->{Icon}, @xtrapath) and $desktop->{'X-SuSE-YaST-Call'} eq '') {
						$output .= "ERROR: Icon file not installed: $desktop->{file} ($desktop->{Icon})\n";
						$validate_error = 1;
					}
				}


			}

			$output .= "WARNING: Empty GenericName: $desktop->{file}\n" if $desktop->{GenericName}  eq '';
		} 
		elsif ($entry->{type} eq 'menu') {
			$output .= output_validate ($entry->{menu});
		}
		else {
			$output .= "ERROR: wrong menu entry type: $entry->{type} $entry->{desktop}->{file}\n";
			$validate_error = 1;
		}
		
	}
	
	return $output;
}

sub output_readable ($;$)
{
	my ($menu, $indent) = @_;
	
	my $output = '';
	
	$indent = 0 unless defined $indent;

	my $menu_name = $menu->{'Name'};
	
	$output .= "\t" x $indent;
	$output .= "\"$menu_name\" MENU\n";
	

	foreach my $entry (@{$menu->{'entries'}}) {
		if ($entry->{type} eq 'desktop') {
			my $desktop = $entry->{desktop};
			
			my $name = $desktop->{Name};

			$output .= "\t" x $indent;
			$output .= "\t\"$name\"\n";
			
			
			my @v = %$desktop;
			$output .= "@v\n" if $name  eq '';
		} 
		elsif ($entry->{type} eq 'menu') {
			$output .= output_readable ($entry->{menu}, $indent + 1);
		}
		else {
			print STDERR "wrong menu entry type: $entry->{type}";
		}
		
	}
	
	return $output;
}

sub get_root_menu
{
	foreach my $dir (split(/:/, $ENV{XDG_CONFIG_DIRS}), "/etc/xdg") {
		check_file("$dir/menus/applications.menu");
		return "$dir/menus/applications.menu" if -f "$dir/menus/applications.menu";
	}
	return "";
}

sub get_app_dirs 
{
	my %used;
	my $ret = '';
	my @kde_xdgdata = split(/:/, `kde-config --path xdgdata-apps`);
	
	foreach $_ (@kde_xdgdata) {
	    s/\/applications\/*\s*$//;
	};
	
	foreach my $d (split(/:/, $ENV{XDG_DATA_DIRS}), @kde_xdgdata, "/usr/share", "/opt/gnome/share") {
		my $dir = "$ENV{RPM_BUILD_ROOT}/$d";
		$dir =~ s/\/*$//;
		next if defined $used{$dir};
		next if check_file("$dir/applications") ne 'D';
		$ret .= ':' if $ret ne '';
		$ret .= "$dir/applications";
		$used{$dir} = 1;
	}
	return $ret;
}

sub get_desktop_dirs 
{
	my %used;
	my $ret = '';
	foreach my $dir (split(/:/, $ENV{XDG_DATA_DIRS}), "/usr/share", "/opt/kde3/share", "/opt/gnome/share") {
		next if defined $used{$dir};
		next if check_file("$dir/desktop-directories") ne 'D';
		$ret .= ':' if $ret ne '';
		$ret .= "$dir/desktop-directories";
		$used{$dir} = 1;
	}
	return $ret;
}

sub get_KDE_legacy_dirs 
{
	my %used;
	my @ret;
	foreach my $d ("/etc/opt/kde3/share/applnk", "/opt/kde3/share/applnk", reverse(split(/:/, `kde-config --path apps`))) {
		my $dir = "$ENV{RPM_BUILD_ROOT}/$d";
		chomp $dir;
		$dir =~ s/\/*$//;
		next if defined $used{$dir};
		next if check_file("$dir") ne 'D';
		$used{$dir} = 1;
		push @ret, $dir;
	}
	return @ret;
}

sub prepare_language_keys ($)
{
	my ($language) = @_;
	
	my @keys;

	$language =~ s/\.[^@]*//;  # remove .ENCODING

	if ($language =~ /^([^_]*)_([^@]*)@(.*)$/) { # LANG_COUNTRY@MODIFIER
		push @keys, $1 . '_' . $2 . '@' . $3;
		push @keys, $1 . '_' . $2;
		push @keys, $1 . '@' . $3;
		push @keys, $1;
	}
	elsif ($language =~ /^([^_]*)_([^@]*)$/) { # LANG_COUNTRY
		push @keys, $1 . '_' . $2;
		push @keys, $1;
	}
	elsif ($language =~ /^([^_]*)@(.*)$/) { # LANG@MODIFIER
		push @keys, $1 . '@' . $2;
		push @keys, $1;
	}
	elsif ($language =~ /^([^_@]*)$/) { # LANG
		push @keys, $1;
	}
	
	return @keys;
}

sub check_cache
{
	my $cachedir = $ENV{HOME};
	
	return unless -d $cachedir;
	
	$cachedir .= "/.xdg_menu_cache";

	return unless -f "$cachedir/inputs" && -f "$cachedir/output";
	
	my @st = stat "$cachedir/output";
	my $ref_time = $st[10]; #ctime

	open(FILE, "<$cachedir/inputs");
	
	my $num_opts = 0;
	
	while (<FILE>) {
		chomp;
		next if /^\s*$/;
		next if /^#/;
		
		if (/^[FD] (.*)$/) {
			my $file = $1;
			my @st = stat $file;
			my $time = $st[10]; #ctime
			
			if (!defined $time || $time >= $ref_time) {
#				print STDERR "$file: is newer\n";
				return;
			}
		}
		elsif (/^X (.*)$/) {
			my $file = $1;
			
			if (-e $file) {
#				print STDERR "$file: exists\n";
				return;
			}
		}
		elsif (/^ENV ([^ ]+) (.*)$/) {
			my $var = $1;
			my $val = $2;
			
			if ($ENV{$var} ne $val) {
#				print STDERR "$var: differs\n";
				return;
			}
		}
		elsif (/^OPT ([0-9]+) (.*)$/) {
			my $optidx = $1;
			my $val = $2;
			
			$num_opts ++;
			if ($save_ARGV[$optidx] ne $val) {
#				print STDERR "$optidx: differs\n";
				return;
			}
		}
		elsif (/^CHARSET (.*)$/) {
			my $charset = $1;
			
			if ($charset ne langinfo(CODESET)) {
#				print STDERR "charset $charset differs\n";
				return;
			}
		}
		elsif (/^LANGUAGE (.*)$/) {
			my $language = $1;
			
			if ($language ne setlocale(LC_MESSAGES)) {
#				print STDERR "language $language differs\n";
				return;
			}
		}
		elsif (/^VERSION (.*)$/) {
			my $v = $1;
			
			if ($v ne $Version) {
#				print STDERR "Version differs\n";
				return;
			}
		}
		else {
			print STDERR "Wrong cache inputs list\n";
			return;
		}
		
		
	}
	
	return if $num_opts != @save_ARGV;
	
	open(FILE, "<$cachedir/output") or return;

	print STDERR "Using cached output\n" if $verbose;

	my $buf;
	while(read(FILE, $buf, 4096)) {
		print $buf;
	}
	close(FILE);	
	
	exit 0;
}

sub write_cache ($)
{
	my ($output) = @_;

	my $cachedir = $ENV{HOME};
	
	return unless -d $cachedir;
	
	$cachedir .= "/.xdg_menu_cache";

	mkdir $cachedir;
	unlink "$cachedir/output";
	
	open(FILE, ">$cachedir/inputs") or return;
	print FILE "# this file contains list of inputs xdg_menu\n";
	print FILE "VERSION $Version\n";
	print FILE "\n\n";
	print FILE join("\n",  @accessed_files);
	print FILE "\n\n";
	
	for (my $i = 0; $i < @save_ARGV; $i++) {
		print FILE "OPT $i $save_ARGV[$i]\n";
	}

	print FILE "ENV XDG_CONFIG_DIRS $ENV{XDG_CONFIG_DIRS}\n";
	print FILE "ENV XDG_DATA_DIRS $ENV{XDG_DATA_DIRS}\n";

	print FILE "CHARSET " . langinfo(CODESET) . "\n";
	print FILE "LANGUAGE " . setlocale(LC_MESSAGES) . "\n";
	
	close(FILE);
	open(FILE, ">$cachedir/output") or return;
	print FILE $output;
	close(FILE);
}


#check_cache();

use XML::Parser;

$DefaultAppDirs = get_app_dirs();
$DefaultDirectoryDirs = get_desktop_dirs();

my $root_menu = get_root_menu();

@KDELegacyDirs = get_KDE_legacy_dirs();

$charset = langinfo(CODESET);
$language = setlocale(LC_MESSAGES);

$root_cmd = "/opt/gnome/bin/gnomesu" if -x '/opt/gnome/bin/gnomesu';
$root_cmd = "/opt/kde3/bin/kdesu" if -x '/opt/kde3/bin/kdesu';

my $help;

GetOptions("format=s" => \$format,
	   "desktop=s" => \$desktop_name,
	   "charset=s" => \$charset,
	   "language=s" => \$language,
	   "root-menu=s" => \$root_menu,
	   "die-on-error" => \$die_on_error,
	   "verbose" => \$verbose,
	   "help" => \$help
	   );

@language_keys = prepare_language_keys($language);

$desktop_name = $format unless defined $desktop_name;

if ($help) {
	print <<"EOF"; 

xdg-menu - XDG menus for WindowMaker and other window managers
           http://freedesktop.org/Standards/menu-spec


Usage:
	xdg_menu [--format <format>] [--desktop <desktop>] 
	         [--charset <charset>] [--language <language>]  
		 [--root-menu <root-menu>] [--die-on-error] [--help]
		 
		format - output format
		         possible formats: WindowMaker, fvwm2, blackbox,
		                           icewm, readable
			 default: WindowMaker
			 
		desktop - desktop name for NotShowIn and OnlyShowIn
			 default: the same as format
			 
		charset - output charset
			 default: $charset
			 
		language - output language
			 default: $language
			 
		root-menu - location of root menu file
			 default: $root_menu
			 
		die-on-error - abort execution on any error, 
			 default: try to continue

		verbose - print debugging information
			 
		help - print this text

EOF
	exit 0;
}


unless ( -f $root_menu) {
	print STDERR "Can't find root menu file.\n";
	exit 1;
}
	      
my $tree = read_menu($root_menu);

merge_menus($tree);
move_menus($tree);

my $menu = interpret_root($tree, '');

remove_allocated($menu);
preprocess_menu($menu);
remove_empty_menus($menu);

my $output;

if ($format eq 'WindowMaker') {

	$output = output_wmaker_menu($menu) 
}
elsif ($format eq 'fvwm2') {

	$output = output_fvwm2_menu($menu) 
}
elsif ($format eq 'blackbox') {
  
        $output = output_blackbox_menu($menu) 
}
elsif ($format eq 'icewm') {

	$output = output_icewm_menu($menu)
}
elsif ($format eq 'readable') {

	$output = output_readable($menu) 
}
elsif ($format eq 'validate') {

	$output = output_validate($menu);
	print $output;
	if ( $validate_error eq 1 ) {
		print "Errors in installed desktop file detected. Please refer to http://en.opensuse.org/SUSE_Package_Conventions/RPM_Macros\n";
		exit 1;
	}
	exit 0;
}
else
{
	print STDERR "Unknown format $format\n";
	exit 1;
}

print $output;
write_cache($output);

exit 0;

