--- /usr/bin/fvwm-menu-desktop 2011-10-02 15:26:46.000000000 +0200 +++ fvwm-menu-desktop 2011-10-20 11:28:52.662681622 +0200 @@ -2,6 +2,22 @@ # Modification History +# Changed on 10/19/11 by TF: +# - add new root_cmd 'gksu' +# - change creating xdg menu flow - if the user has its own +# ${XDG_MENU_PREFIX}applications.menu, it replaces the system wide one. +# Also, if more applications.menus exist they are merged into the whole +# Fvwm-menu. +# +# - add new option "content". There exist other menus than applications +# -> settings, preferences, etc. To get them use this option +# - add new option "global". By default first in $XDG_CONFIG_HOME and than +# in $XDG_CONFIG_DIRS will be searched for menus. Is it set the system +# wide menu(s) in $XDG_CONFIG_DIRS will used only. +# - reanimate option "desktop". If different menus exist and the user wants +# them as seperate menus "desktop" can use like gnome, kde, xfce +# - updated help page. Add new and remove unsupported options + # Changed on 05/07/11 by (dane): # - new option kde_config for alternate name of optional # kde-config command. @@ -31,10 +47,6 @@ # - No testing for wm_icons has been done (is that fvwm-themes?). # - Docs need to be updated. # - fvwm-icons: okay, maybe the cat isn't a good default for Exec... -# - Running alacarte, I can see there are Application menus, and System menus. -# Figure out the options to get both. -# - There are good programs like alacarte that are marked GNOME-ONLY. -# Figure out how to get them in menus. # - I think this should default to mini-icons enabled, but which # icon package should be assumed? # - Need a way to select the /usr/share/icons icon sets with themes like @@ -52,7 +64,15 @@ # - The "check" functions are useless. Since this module doesn't get the # built in path, or the users path, it can't do the check. # - Looks like there is no such thing as fvwm_toptitle, must have been a gtk thing? - +# +# DONE: +# - Running alacarte, I can see there are Application menus, and System menus. +# Figure out the options to get both. Done - use option "content" (TF) +# - There are good programs like alacarte that are marked GNOME-ONLY. +# Figure out how to get them in menus. Done - using a loop to get all +# applications.menus in one. Thanks Dan for great programming the xdg stuff +# :-) (TF) +# # --------------------------------------------------------------------------- # fvwm-menu-desktop # See the man page fvwm-menu-desktop.1 for instructions. @@ -62,6 +82,8 @@ # Updated on 15/08/1999 by Mikhael Goikhman # # Updated on 24/12/2010 by Dan Espen for xdg menus (see copyright below) +# +# Updated on 10/19/2011 by Thomas Funk for xdg menus # --------------------------------------------------------------------------- # COPYING # @@ -103,9 +125,15 @@ # it. eval "use XML::Parser; 1" or die "fvwm-menu-desktop: XML::Parser not installed.\n"; -my $xdg_data_dirs = $ENV{XDG_DATA_DIRS} || ''; +my $xdg_config_dirs = $ENV{XDG_CONFIG_DIRS} || '/etc/xdg'; +my $xdg_config_home = $ENV{XDG_CONFIG_HOME} || "$ENV{HOME}/.config"; +my $xdg_data_dirs = $ENV{XDG_DATA_DIRS} || '/usr/local/share/:/usr/share/'; +my $xdg_data_home = $ENV{XDG_DATA_HOME} || "$ENV{HOME}/.local/share"; +my $xdg_menu_prefix = $ENV{XDG_MENU_PREFIX} || ''; +my %xdg_menus; my @PATH_DIRS = split(':',$ENV{PATH}); # for checking if applications exist + my $version = '2.6.4'; my $menu_prefix='FvwmMenu'; my $DefaultAppDirs; @@ -116,19 +144,22 @@ my $charset = 'iso-8859-1'; my $root_cmd; my $kde_cmd; +my $menu_content = 'applications'; +my $global = 0; my $die_on_error = 0; my $verbose = 0; +my $desktop; my @language_keys; #my @accessed_files; my $TERM_CMD = "xterm -e"; - my %Desktop_entries; my %Directory_entries; my $root_menu; +my $user_menu; my $help; # Default for the mini-icons is mini/ (relatively to the ImagePath) @@ -202,14 +233,20 @@ "check-app!" => \&obsolete, "time-limit=s" => \&obsolete, "merge-user-menu" => \&obsolete, - "desktop=s" => \&obsolete, + "desktop=s" => \$desktop, "su_gui" => \$root_cmd, "kde_config" => \$kde_cmd, - "verbose" => \$verbose + "content=s" => \$menu_content, + "global" => \$global, + "verbose" => \$verbose ); icon_init(); +if (defined $desktop) { + $xdg_menu_prefix = "$desktop-"; +} + # kde-config helps us find things. # sometimes it has funny names. # we can get by without it. @@ -225,7 +262,25 @@ $DefaultAppDirs = get_app_dirs(); $DefaultDirectoryDirs = get_desktop_dirs(); -$root_menu = get_root_menu(); +# get menus +unless ($global) { + # first check user home + foreach my $dir (split(/:/, $xdg_config_home)) { + my $user_file_list = get_file_list("$dir/menus", 0); + if (keys %{$user_file_list}) { + get_prefixes_and_menus($user_file_list); + } + } +} +# if user home is empty or global set check root +unless (keys %xdg_menus) { + foreach my $dir (split(/:/, $xdg_config_dirs)) { + my $root_file_list = get_file_list("$dir/menus", 0); + if (keys %{$root_file_list}) { + get_prefixes_and_menus($root_file_list); + } + } +} @KDELegacyDirs = get_KDE_legacy_dirs(); @@ -233,49 +288,72 @@ $language = setlocale(LC_MESSAGES); if (! defined $root_cmd ) { - foreach (qw(gnomesu kdesu xdg_menu_su)) { + foreach (qw(gnomesu gksu kdesu xdg_menu_su)) { if (check_app($_)) { $root_cmd = $_; last; } } + if (! defined $root_cmd ) { + $root_cmd = '' + } } if ($verbose) { + print "\n"; + foreach my $prefix (sort (keys %xdg_menus)) { + warn "\tDEBUG: ${prefix}menu is \'$xdg_menus{$prefix}\'."; + } warn qq| - DEBUG: root menu is $root_menu. - DEBUG: charset is $charset. - DEBUG: language is $language. - DEBUG: root-cmd is $root_cmd.|; + DEBUG: charset is \'$charset\'. + DEBUG: language is \'$language\'. + DEBUG: root-cmd is \'$root_cmd\'.|; if (defined $kde_cmd) { - warn "\tDEBUG: kde-config command is $kde_cmd."; + warn "\tDEBUG: kde-config command is \'$kde_cmd\'."; } else { warn "\tDEBUG: No kde-config command found, using defaults."; } } + @language_keys = prepare_language_keys($language); -unless (-f $root_menu) +unless (keys %xdg_menus) { - warn "ERROR: Can't find root menu file.\n" if $verbose; + warn "ERROR: Can't find any $desktop $menu_content.menu files.\n" if $verbose; exit 1; } -my $tree = read_menu($root_menu); - -merge_menus($tree); -move_menus($tree); - -my $menu = interpret_root($tree, ''); +# collect all found menus +my %fvwm2_menus = (); -remove_allocated($menu); -preprocess_menu($menu); -remove_empty_menus($menu); +foreach my $prefix (sort (keys %xdg_menus)) { + my $tree; + my $menu; + my $output; + + $tree = read_menu($xdg_menus{$prefix}); + + merge_menus($tree); + move_menus($tree); + $menu = interpret_root($tree, ''); + remove_allocated($menu); + preprocess_menu($menu); + remove_empty_menus($menu); + + $output = output_fvwm2_menu($menu); + if (scalar keys %xdg_menus > 1) { + $fvwm2_menus{$prefix} = create_fvwm2_menu_hash($output); + } else { + print $output; + } +} -my $output = output_fvwm2_menu($menu); -print $output; +unless (scalar keys %fvwm2_menus == 0) { + merge_fvwm2_menus(); + printout_fvwm2_menu(); +} # output the menu style if ($MENU_STYLE ne "") @@ -1118,16 +1196,20 @@ { my ($file, $basedir) = @_; - if ($file !~ /^\// and defined $basedir) - { - $file = "$basedir/$file"; - } - unless (defined $basedir) { $basedir = $file; $basedir =~ s/\/[^\/]*$//; + } else { + if ($file !~ /^\// and defined $basedir) + { + $file = "$basedir/$file"; + } else { + $basedir = $file; + $basedir =~ s/\/[^\/]*$//; + } } + unless (check_file($file)) { @@ -1136,7 +1218,8 @@ } warn "reading menu '$file'\n" if $verbose; - + warn "basedir: $basedir\n" if $verbose; + my $parser = XML::Parser->new(Style => 'Tree'); my $tree = $parser->parsefile($file); @@ -1924,18 +2007,37 @@ return $output; } -sub get_root_menu -{ - my $xdg_config_dirs = $ENV{XDG_CONFIG_DIRS} || ''; - my $xdg_menu_prefix = $ENV{XDG_MENU_PREFIX} || ''; - foreach my $dir (split(/:/, $xdg_config_dirs), "/etc/xdg") - { - my $menu_file="$dir/menus/${xdg_menu_prefix}applications.menu"; # - warn "looking for root menu $menu_file\n" if $verbose; - return "$menu_file" if -f "$menu_file"; +sub get_file_list +{ + my ($path, $subdirs) = @_; + $subdirs = defined($subdirs)?$subdirs:0; + + my %files = (); + push (my @all_directories, $path); + foreach my $dir (@all_directories) { + opendir (my $IN, $dir) || return {}; + my @all = readdir($IN); + closedir $IN; + + foreach my $file (@all) { + next if ($file eq '..' || $file eq '.'); + if (-d "$dir/$file") { + if ($subdirs == 1) { + push (@all_directories, "$dir/$file"); + $files{"$dir/$file"} = $file; + next; + } + else { + $files{"$dir/$file"} = $file; + } + } + else { + $files{"$dir/$file"} = $file; + } + } } - return ""; + return \%files; } sub get_app_dirs @@ -2058,7 +2160,188 @@ return @keys; } -# Fixme, remove unsupported options. +sub get_prefixes_and_menus { + my $file_list = shift; + my $verbosecheck = 0; + foreach my $file (sort (keys %{$file_list})) { + next if (-d $file); + if ($file_list->{$file} =~ m/(.*)$menu_content.menu(.*)/) { + my $front_prefix = $1; + my $back_prefix = $2; + if ($back_prefix ne '') { + $back_prefix =~ s/\.//; + $back_prefix =~ s/(\s*$)/-/; + } + if ($xdg_menu_prefix ne '') { + if ($xdg_menu_prefix eq $front_prefix || $xdg_menu_prefix eq $back_prefix) { + $xdg_menus{$xdg_menu_prefix} = $file; + } else { + if ($verbose) { + warn "wrong desktop prefix: \'$desktop\'\n" unless ($verbosecheck); + $verbosecheck = 1; + warn "found file: $file\n"; + } + } + } else { + if ($back_prefix ne '') { + $xdg_menus{$back_prefix} = $file; + } else { + $xdg_menus{$front_prefix} = $file; + } + } + } + } +} + +sub create_fvwm2_menu_hash { + my $menu = shift; + my %new_menu = (); + my @reference_menu = split("\n", $menu); + # first create menu hashes + foreach my $line (@reference_menu) { + if ($line =~ m/^DestroyMenu \"(.*)\"/) { + $new_menu{$1} = (); + } + } + # add menu entries + foreach my $key (keys %new_menu) { + my $start = 0; + foreach my $line (@reference_menu) { + if ($line =~ m/^DestroyMenu \"$key\"/) { + push @{ $new_menu{$key} }, $line; + $start = 1; + } else { + if ($start == 1) { + last if ($line eq ""); + push @{ $new_menu{$key} }, $line; + } + } + } + } + return \%new_menu; +} + +sub merge_fvwm2_menus { + my $weight = 0; + my $reference_menu; + # check first if xdg_menu_prefix is set + if ($xdg_menu_prefix ne '') { + $reference_menu = $xdg_menu_prefix; + } else { + # check if applications.menu exist + if (exists $fvwm2_menus{''}) { + $reference_menu = ''; + } else { + # get the biggest menu as reference + foreach my $prefix (keys %fvwm2_menus) { + my $size = scalar keys %{$fvwm2_menus{$prefix}}; + if ($size > $weight) { + $reference_menu = $prefix; + $weight = $size; + } + } + } + } + # compare the other menus with reference and delete same entries + %{$fvwm2_menus{'fvwm-'}} = %{$fvwm2_menus{$reference_menu}}; + foreach my $compare_menu (keys %fvwm2_menus) { + next if ($compare_menu eq $reference_menu || $compare_menu eq 'fvwm-'); + foreach my $compare_menu (keys %fvwm2_menus) { + next if ($compare_menu eq $reference_menu || $compare_menu eq 'fvwm-'); + foreach my $refkey (keys %{$fvwm2_menus{$reference_menu}}) { + foreach my $compkey (keys %{$fvwm2_menus{$compare_menu}}) { + my @rest = grep { my $x = $_; not grep { $x =~ /\Q$_/i and $x !~ /DestroyMenu|AddToMenu/} @{$fvwm2_menus{$reference_menu}{$refkey}} } @{$fvwm2_menus{$compare_menu}{$compkey}}; + undef @{ $fvwm2_menus{$compare_menu}{$compkey} }; + push @{ $fvwm2_menus{$compare_menu}{$compkey} }, @rest; + if (scalar @{ $fvwm2_menus{$compare_menu}{$compkey} } == 2) { + delete $fvwm2_menus{$compare_menu}{$compkey}; + } + } + } + # check if double entries in the compare menu and delete them + foreach my $refkey (keys %{$fvwm2_menus{$compare_menu}}) { + foreach my $compkey (keys %{$fvwm2_menus{$compare_menu}}) { + next if ($compkey eq $refkey); + my @rest = grep { my $x = $_; not grep { $x =~ /\Q$_/i and $x !~ /DestroyMenu|AddToMenu/} @{$fvwm2_menus{$compare_menu}{$refkey}} } @{$fvwm2_menus{$compare_menu}{$compkey}}; + undef @{ $fvwm2_menus{$compare_menu}{$compkey} }; + push @{ $fvwm2_menus{$compare_menu}{$compkey} }, @rest; + } + if (scalar @{ $fvwm2_menus{$compare_menu}{$refkey} } == 2) { + delete $fvwm2_menus{$compare_menu}{$refkey}; + } + } + # merge all '+' entries in existing ref menus + foreach my $key (keys %{$fvwm2_menus{$compare_menu}}) { + next if ($key eq 'FvwmMenu'); + if (exists $fvwm2_menus{$reference_menu}{$key}) { + my @entry = (grep (/\+/, @{$fvwm2_menus{$compare_menu}{$key}}) and grep (!/Popup|DestroyMenu|AddToMenu/, @{$fvwm2_menus{$compare_menu}{$key}})); + if (@entry) { + push @{ $fvwm2_menus{$reference_menu}{$key} }, @entry; + my @rest = grep (/Popup|DestroyMenu|AddToMenu/, @{$fvwm2_menus{$compare_menu}{$key}}); + undef @{ $fvwm2_menus{$compare_menu}{$key} }; + push @{ $fvwm2_menus{$compare_menu}{$key} }, @rest; + if (scalar @{ $fvwm2_menus{$compare_menu}{$key} } == 2) { + delete $fvwm2_menus{$compare_menu}{$key}; + } + } + } + } + # check, if non existing Popup entries available and delete them + foreach my $key (keys %{$fvwm2_menus{$compare_menu}}) { + my @rest; + foreach my $entry (@{$fvwm2_menus{$compare_menu}{$key}}) { + if ($entry =~ m/DestroyMenu|AddToMenu/) { + push @rest, $entry; + } else { + foreach (keys %{$fvwm2_menus{$compare_menu}}) { + next if ($_ eq $key || $_ eq 'FvwmMenu'); + if ($entry =~ m/$_\b/) { + push @rest, $entry; + } + } + } + } + #foreach (@rest) { print "$_\n"; } + undef @{ $fvwm2_menus{$compare_menu}{$key} }; + push @{ $fvwm2_menus{$compare_menu}{$key} }, @rest; + if (scalar @{ $fvwm2_menus{$compare_menu}{$key} } == 2) { + delete $fvwm2_menus{$compare_menu}{$key}; + } + } + # check if menus available which not exist in the reference menu + # and merge them into it. + foreach my $key (keys %{$fvwm2_menus{$compare_menu}}) { + my @entry; + if ($key eq 'FvwmMenu') { + @entry = (grep (/\+/, @{$fvwm2_menus{$compare_menu}{$key}}) and grep (!/DestroyMenu|AddToMenu/, @{$fvwm2_menus{$compare_menu}{$key}})); + } else { + @entry = @{$fvwm2_menus{$compare_menu}{$key}}; + } + push @{ $fvwm2_menus{$reference_menu}{$key} }, @entry; + delete $fvwm2_menus{$compare_menu}{$key}; + } + } + } +} + +sub printout_fvwm2_menu { + foreach my $key (keys %{$fvwm2_menus{'fvwm-'}}) { + if ($key eq 'FvwmMenu') { + foreach my $entry (@{$fvwm2_menus{'fvwm-'}{$key}}) { + if ($entry =~ m/AddToMenu/) { + $entry =~ s/\"(.*)[\- ]Men/\"FvwmMenu\" \"Fvwm\-Men/; + } + print "$entry\n"; + } + } else { + foreach (@{$fvwm2_menus{'fvwm-'}{$key}}) { + print "$_\n"; + } + } + print "\n"; + } +} + sub show_help { print <