You are not logged in.
Pages: 1
In my spare time, I am fooling around putting fluxbox over an xfce4 base, with the user selecting fb from the login screen. I am using xfce components which makes for a very nice package.
I would like to be able to add an entry to the desktop menu called "Applications" that pops up the Xfce Applications menu the way a right-click on the Xfce desktop can do. Is there a command-line method of launching it?
Thanks for any help.
MX-23 (based on Debian Stable) with our flagship Xfce 4.18.
Offline
OK, I see this isn't a thriller...
I went through /usr/bin/ and found the file that does it: xfce4-popup-applicationsmenu. It is set for xfce4panel and xfdesktop, so I will try to see if it works as is or needs some redirection.
#!/bin/sh
#
# Copyright (C) 2010 Nick Schermer <nick@xfce.org>
#
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
export TEXTDOMAIN="xfce4-panel"
export TEXTDOMAINDIR="/usr/share/locale"
ATPOINTER="false"
case "$1" in
-h|--help)
echo "$(gettext "Usage:")"
echo " $(basename $0) [$(gettext "OPTION")...]"
echo
echo "$(gettext "Options:")"
echo " -p, --pointer $(gettext "Popup menu at current mouse position")"
echo " -h, --help $(gettext "Show help options")"
echo " -V, --version $(gettext "Print version information and exit")"
exit 0
;;
-V|--version)
exec /usr/bin/xfce4-panel -V "$(basename $0)"
exit 0
;;
-p|--pointer)
ATPOINTER="true"
;;
esac
# try to open panel menu, if this fails try xfdesktop
/usr/bin/xfce4-panel --plugin-event=applicationsmenu:popup:bool:$ATPOINTER || xfdesktop --menu
# vim:set ts=2 sw=2 et ai:
MX-23 (based on Debian Stable) with our flagship Xfce 4.18.
Offline
There is no easy way to call that menu. It's all done programmatically using garcon.
You can use xfce4-popup-applicationsmenu with the following constraints:
- if the applications menu is added to the panel, then it will popup the menu
- if desktop menus are enabled, it will popup the desktop menu - but that will include the desktop entries and the applications menu (if enabled in desktop settings). There is no way to just show the applications menu
- if the applications menu is not added to the panel, and desktop menus are disabled, then nothing will happen.
Perhaps you could use jgmenu to popup an xdg-compliant menu (it was mentioned on this previous post.
Please remember to mark your thread [SOLVED] to make it easier for others to find
--- How To Ask For Help | FAQ | Developer Wiki | Community | Contribute ---
Offline
Thanks, I will take a look at those.
I have created a workaround for now that is pretty neat: add a fluxbox menu item that calls a local panel (/home/$USER/.local/bin/fb-xfce4-panel) and reduce that panel to the Applications or Whisker icon. ATM the icon is small and stuck in the upper left corner, but I can change all that later.
Last edited by Jerry3904 (2019-11-09 14:52:06)
MX-23 (based on Debian Stable) with our flagship Xfce 4.18.
Offline
According to this, you can generate an XDG-compliant custom menu and add it to the fluxbox menu.
xdg_menu --format fluxbox --root-menu /etc/xdg/menus/xfce-applications.menu >~/.fluxbox/my-menu
...and then insert into the fluxbox menu:
[include] (~/.fluxbox/my-menu)
To keep the menu up to date, you can cron the first command to run at regular intervals.
Please remember to mark your thread [SOLVED] to make it easier for others to find
--- How To Ask For Help | FAQ | Developer Wiki | Community | Contribute ---
Offline
I tried that earlier (along with just about everything else in the Arch wiki), thanks, but the command xdg_menu is "unknown" and is not found in the Debian 10 repos. Maybe it is in Arch...
MX-23 (based on Debian Stable) with our flagship Xfce 4.18.
Offline
Ah, yes it is. It's just a simple script. Requires perl and perl-xml-parser. Here is the script:
#!/usr/bin/perl -w
#
# xdg-menu for archlinux. based on suse xdg-menu written by <nadvornik@suse.cz>
# Sergej Pupykin <pupykin.s@gmail.com>
#
# >> Copyright (c) 2003 SuSE Linux AG, Nuernberg, Germany. All rights reserved.
# >>
# >> Author: nadvornik@suse.cz
# >>
use strict;
no warnings qw(uninitialized prototype);
use Getopt::Long;
use Encode;
use I18N::Langinfo qw(langinfo CODESET);
use POSIX qw(locale_h);
use Digest::MD5 qw(md5_hex);
my $Version = "0.7.6.3";
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 $die_on_error = 0;
my $verbose = 0;
my $fullmenu = 0;
my @language_keys;
my @accessed_files;
my @save_ARGV = @ARGV;
my $kdeconfig = get_kde_config();
my %Desktop_entries;
my %Directory_entries;
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 findicon
{
# my $theme = "hicolor";
my $iconname = shift;
my $home = $ENV{"HOME"};
my $xdg_data_dirs = $ENV{"XDG_DATA_DIRS"} || "/usr/local/share:/usr/share";
my (@xdg_data_dirs);
@xdg_data_dirs = split(":",$xdg_data_dirs);
my (@icon_search_path);
push @icon_search_path, "/";
my (@categorylist);
@categorylist = ("apps", "places", "devices", "actions", "animations", "categories", "emblems", "emotes", "filesystems", "intl", "mimetypes", "status", "stock");
my (@sizelist);
@sizelist = (16, 22, 24, 26, 32, 36, 48, 64, 72, 128, 192, 256);
my $thisdir = $home."/.icons";
( -d $thisdir ) && push @icon_search_path, $thisdir;
foreach my $dir (@xdg_data_dirs)
{
my $theme = "hicolor";
my $size;
my $category;
foreach $size (@sizelist)
{
foreach $category (@categorylist)
{
$thisdir = $dir."/icons/".$theme."/".$size."x".$size."/".$category;
( -d $thisdir ) && push @icon_search_path, $thisdir;
}
}
$thisdir = $dir."/icons/mini";
( -d $thisdir ) && push @icon_search_path, $thisdir;
$thisdir = $dir."/icons";
( -d $thisdir ) && push @icon_search_path, $thisdir;
$thisdir = $dir."/icons/large";
( -d $thisdir ) && push @icon_search_path, $thisdir;
$theme = "gnome";
foreach $size (@sizelist)
{
foreach $category (@categorylist)
{
$thisdir = $dir."/icons/".$theme."/".$size."x".$size."/".$category;
( -d $thisdir ) && push @icon_search_path, $thisdir;
}
}
}
push @icon_search_path, "/usr/share/pixmaps";
my $filename;
#if ( -f $iconname )
#{
# return $iconname;
#}
#else
{
foreach my $dir (@icon_search_path)
{
foreach my $ext ('','.png','.xpm','.svg')
{
$filename = $dir."/".$iconname.$ext;
( -f $filename ) && return $filename;
}
}
}
}
# sub add_png_extension_if_needed ($)
# {
# my ($f) = @_;
# return $f =~ /\.(png|xpm|svg)$/ ? $f : "$f.png";
# }
sub scan_AppDir ($$;$)
{
my ($pool, $dir, $topdir) = @_;
check_file($dir);
$topdir = $dir unless defined $topdir;
return if(check_file($dir) ne 'D');
my $DIR;
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{'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->{'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 '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";
}
if(check_file($dir) ne 'D')
{
return [];
}
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/&/&/g;
$txt =~ s/</</g;
$txt =~ s/>/>/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') {
if( -d $tree->[$i + 1][2])
{
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 "legacy directory '" . $tree->[$i + 1][2] . "' not found\n" if $verbose;
splice @$tree, $i, 2, ();
}
}
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;
$menu->{'PrepIcon'} = $menu->{"Directory"}->{"Icon"};
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'}} ];
$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, $icon_needed, $indent) = @_;
my $output = '';
$output .= "[begin] (Menu)\n";
$output .= "[exec] (xterm) {xterm}\n[separator]\n";
$output .= output_blackbox_inner_menu ($menu, $icon_needed, $indent);
$output .= "[separator]\n";
$output .= '[config] (Configuration)
[workspaces] (Workspace)
[submenu] (System Styles) {Choose a style...}
[stylesdir] (/usr/share/blackbox/styles)
[stylesdir] (/usr/share/fluxbox/styles)
[stylesdir] (/usr/share/openbox/styles)
[end]
[submenu] (User Styles) {Choose a style...}
[stylesdir] (~/.blackbox/styles)
[stylesdir] (~/.fluxbox/styles)
[stylesdir] (~/.openbox/styles)
[end]
[separator]
[exec] (Run Command) {bbrun -a -w}
[exec] (Lock Screen) {xlock}
[restart] (Restart) {}
[exit] (Logout)
[end]
';
$output .= "[end]\n";
return $output;
}
sub output_blackbox_inner_menu ($;$;$)
{
my ($menu, $icon_needed, $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;
if($icon_needed)
{
my $icon = findicon($desktop->{'Icon'}) || "-";
$output .= " [exec] ($name) {$exec} <$icon>\n";
}
else
{
$output .= " [exec] ($name) {$exec}\n";
}
}
elsif ($entry->{type} eq 'menu')
{
$output .= output_blackbox_inner_menu ($entry->{'menu'}, $icon_needed, $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'};
#my $menu_icon = $menu->{'PrepIcon'} || "folder" ;
my $menu_icon = findicon($menu->{'PrepIcon'}) || "folder";
if($indent)
{
$output .= ' ' x $indent;
$output .= "menu \"$menu_name\" $menu_icon {\n";
}
foreach my $entry (@{$menu->{'entries'}})
{
if ($entry->{type} eq 'desktop')
{
my $desktop = $entry->{desktop};
my $name = $desktop->{'PrepName'};
my $exec = $desktop->{'PrepExec'};
my $icon = findicon($desktop->{'Icon'}) || "-";
$output .= ' ' x $indent;
$output .= " prog \"$name\" $icon $exec\n";
}
elsif ($entry->{type} eq 'menu')
{
$output .= output_icewm_menu ($entry->{'menu'}, $indent + 1);
}
else
{
print STDERR "wrong menu entry type: $entry->{type}";
}
}
if($indent)
{
$output .= ' ' x $indent;
$output .= "}\n";
}
return $output;
}
sub output_ion3_menu ($;$)
{
my ($menu, $indent) = @_;
my $output = '';
my @pending_list = ();
$indent = 0 unless defined $indent;
my $menu_name = $menu->{'PrepName'};
$output .= ' ' x $indent;
$output = "defmenu(\"".$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'};
$exec =~ s/"/\\"/g;
$output .= ' ' x $indent;
$output .= " menuentry(\"$name\", \"ioncore.exec_on(_, '$exec')\"),\n";
}
elsif ($entry->{type} eq 'menu')
{
push @pending_list, $entry;
$output .= " submenu(\"".$entry->{'menu'}->{'PrepName'}."\", \"".$entry->{'menu'}->{'PrepName'}."\"),\n";
}
else
{
print STDERR "wrong menu entry type: $entry->{type}";
}
}
$output .= ' ' x $indent;
$output .= "})\n";
foreach my $entry (@pending_list)
{
$output .= output_ion3_menu ($entry->{'menu'}, $indent + 1);
}
return $output;
}
sub output_awesome_menu ($;$)
{
my ($menu, $indent) = @_;
my $output = '';
my @pending_list = ();
$indent = 0 unless defined $indent;
my $menu_name = "menu".md5_hex($menu->{'PrepName'});
if($indent == 0)
{
$menu_name = "xdgmenu";
}
#my $menu_icon = findicon($menu->{'PrepIcon'}) || "folder";
foreach my $entry (@{$menu->{'entries'}})
{
if ($entry->{type} eq 'menu')
{
push @pending_list, $entry;
}
}
foreach my $entry (@pending_list)
{
$output .= output_awesome_menu ($entry->{'menu'}, $indent + 1);
}
$output .= ' ' x $indent;
if($indent == 1)
{
$output .= 'local ';
}
$output .= $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'};
my $icon = findicon($desktop->{'Icon'}) || "-";
$exec =~ s/"/\\"/g;
$output .= ' ' x $indent;
if ($icon eq "-") {
$output .= " {\"$name\", \"$exec\"},\n";
} else {
$output .= " {\"$name\", \"$exec\", \"$icon\" },\n";
}
}
elsif ($entry->{type} eq 'menu')
{
$output .= " {\"".$entry->{'menu'}->{'PrepName'}."\", "."menu".md5_hex($entry->{'menu'}->{'PrepName'})."},\n";
}
else
{
print STDERR "wrong menu entry type: $entry->{type}";
}
}
$output .= ' ' x $indent;
$output .= "}\n\n";
return $output;
}
sub output_twm_menu ($;$)
{
my ($menu, $indent) = @_;
my $output = '';
my @pending_list = ();
$indent = 0 unless defined $indent;
my $menu_name = $menu->{'PrepName'};
$output .= ' ' x $indent;
$output = "menu \"".$menu_name."\"\n{\n";
foreach my $entry (@{$menu->{'entries'}})
{
if ($entry->{type} eq 'desktop')
{
my $desktop = $entry->{desktop};
my $name = $desktop->{'PrepName'};
my $exec = $desktop->{'PrepExec'};
$exec =~ s/"/\\"/g;
$output .= ' ' x $indent;
$output .= " \"$name\" f.exec \"exec $exec &\"\n";
}
elsif ($entry->{type} eq 'menu')
{
push @pending_list, $entry;
$output .= " \"".$entry->{'menu'}->{'PrepName'}."\" f.menu \"".$entry->{'menu'}->{'PrepName'}."\"\n";
}
else
{
print STDERR "wrong menu entry type: $entry->{type}";
}
}
$output .= ' ' x $indent;
$output .= "}\n";
foreach my $entry (@pending_list)
{
$output .= output_twm_menu ($entry->{'menu'}, $indent + 1);
}
return $output;
}
sub prepare_exec_xfce4 ($$)
{
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 =~ s/\"/"/g;
$exec = "$root_cmd $exec" if $desktop->{'X-KDE-SubstituteUID'} eq '1' || $desktop->{'X-KDE-SubstituteUID'} eq 'true';
return $exec;
}
sub output_xfce4_menu ($;$)
{
my ($menu, $indent) = @_;
my $output = '';
$indent = 0 unless defined $indent;
if ($indent == 0)
{
$output .= '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
$output .= '<!DOCTYPE xfdesktop-menu [' . "\n";
$output .= ' <!ENTITY menu2 SYSTEM "menu2.xml">' . "\n";
$output .= ']>' . "\n\n";
}
my $menu_name = $menu->{'PrepName'};
$output .= ' ' x $indent;
if ($indent == 0)
{
$output .= "<xfdesktop-menu>\n"
}
else
{
$output .= "<menu name=\"" . quote_xml($menu_name) ."\" visible=\"yes\">\n";
}
foreach my $entry (@{$menu->{'entries'}})
{
if ($entry->{type} eq 'desktop')
{
my $desktop = $entry->{desktop};
my $name = $desktop->{'PrepName'};
my $exec = prepare_exec_xfce4($desktop->{Exec}, $desktop);
my $term = ($desktop->{Terminal} eq '1' || $desktop->{Terminal} eq 'true') ? "yes" : "no";
$output .= ' ' x $indent;
$output .= " <app name=\"" . quote_xml($name) ."\" cmd=\"$exec\" term=\"$term\"/>\n";
}
elsif ($entry->{type} eq 'menu')
{
$output .= output_xfce4_menu ($entry->{'menu'}, $indent + 1);
}
else
{
print STDERR "wrong menu entry type: $entry->{type}";
}
}
$output .= ' ' x $indent;
if ($indent == 0)
{
$output .= "</xfdesktop-menu>\n";
}
else
{
$output .= "</menu>\n";
}
return $output;
}
sub output_openbox3_menu ($;$)
{
my ($menu, $indent) = @_;
my $output = '';
$output .= '<?xml version="1.0" encoding="UTF-8"?>
<openbox_menu xmlns="http://openbox.org/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://openbox.org/
file:///usr/share/openbox/menu.xsd">';
$output .= "<menu id=\"xdg\" label=\"xdg\">\n";
$output .= output_openbox3_inner_menu ($menu, $indent);
$output .= "</menu>\n";
$output .= ' <menu id="root-menu" label="Openbox 3">
<item label="xterm">
<action name="Execute"><execute>xterm</execute></action> </item>
<separator />
<menu id="KDE Menu" label="KDE Menu" />
<separator />
<menu id="client-list-menu" />
<separator />
<menu id="ob-menu" label="openbox3">
<item label="ob conf"><action name="Execute"><execute>obconf</execute></action></item>
<item label="reconfigure"><action name="Reconfigure" /></item>
</menu>
<separator />
<item label="lock screen"><action name="Execute"><execute>xlock -remote -nice 19 -mode blank -geometry 1x1 -enablesaver</execute></action></item>
<separator />
<item label="Exit"><action name="Exit" /></item>
</menu>';
$output .= "</openbox_menu>\n";
return $output;
}
sub output_openbox3_pipe_menu ($;$)
{
my ($menu, $indent) = @_;
my $output = '';
$output .= "<openbox_pipe_menu>\n";
$output .= output_openbox3_inner_menu ($menu, $indent);
$output .= "</openbox_pipe_menu>\n";
return $output;
}
sub output_openbox3_inner_menu ($;$)
{
my ($menu, $indent) = @_;
my $output = '';
$indent = 0 unless defined $indent;
my $menu_name = $menu->{'PrepName'};
my $menu_icon = findicon($menu->{'PrepIcon'});
if($indent != 0)
{
$output .= ' ' x $indent;
if ($menu_icon eq "")
{
$output .= "<menu id=\"" . quote_xml($menu_name) . "\" label=\"".quote_xml($menu_name) . "\">\n";
}
else
{
$output .= "<menu id=\"" . quote_xml($menu_name) . "\" label=\"".quote_xml($menu_name) . "\" icon=\"" . quote_xml($menu_icon) . "\">\n";
}
# $output .= "<menu label=\"".quote_xml($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'};
my $icon = findicon($desktop->{'Icon'});
$output .= ' ' x $indent;
if ($icon eq "")
{
$output .= " <item label=\"". quote_xml($name)."\">\n";
}
else
{
$output .= " <item label=\"". quote_xml($name)."\" icon=\"" . quote_xml($icon) . "\">\n";
}
$output .= " <action name=\"Execute\"><execute>$exec</execute></action>\n";
$output .= " </item>\n";
}
elsif ($entry->{type} eq 'menu')
{
$output .= output_openbox3_inner_menu ($entry->{'menu'}, $indent + 1);
}
else
{
print STDERR "wrong menu entry type: $entry->{type}";
}
}
if($indent != 0)
{
$output .= ' ' x $indent;
$output .= "</menu> <!-- $menu_name -->\n";
}
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 output_jwm_inner_menu ($;$)
{
my ($menu, $indent) = @_;
my $output = '';
$indent = 0 unless defined $indent;
my $menu_name = $menu->{'PrepName'};
#my $menu_icon = $menu->{'PrepIcon'} || "folder" ;
my $menu_icon = findicon($menu->{'PrepIcon'}) || "folder";
if($indent)
{
$output .= ' ' x $indent;
$output .= "<Menu icon=\"$menu_icon\" label=\"$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'};
my $icon = findicon($desktop->{'Icon'}) || "";
$output .= ' ' x $indent;
$output .= " <Program icon=\"$icon\" label=\"$name\">$exec</Program>\n";
}
elsif ($entry->{type} eq 'menu')
{
$output .= output_jwm_inner_menu ($entry->{'menu'}, $indent + 1);
}
else
{
print STDERR "wrong menu entry type: $entry->{type}";
}
}
if($indent)
{
$output .= ' ' x $indent;
$output .= "</Menu>\n";
}
return $output;
}
sub output_jwm_menu ($;$)
{
my ($menu, $indent) = @_;
my $output = '';
$output .= "<JWM>\n";
$output .= output_jwm_inner_menu ($menu, $indent);
$output .= "</JWM>\n";
return $output;
}
sub get_root_menu
{
foreach my $dir (split(/:/, $ENV{XDG_CONFIG_DIRS}), "/etc/xdg")
{
check_file("$dir/menus/arch-applications.menu");
return "$dir/menus/arch-applications.menu" if -f "$dir/menus/arch-applications.menu";
}
return "";
}
sub get_kde_config
{
my $ret = 'true';
if(-x '/usr/bin/kde4-config')
{
$ret = '/usr/bin/kde4-config';
}
elsif(-x '/opt/kde/bin/kde-config')
{
$ret = '/opt/kde/bin/kde-config';
}
return $ret;
}
sub get_app_dirs
{
my %used;
my $ret = '';
my @kde_xdgdata = split(/:/, `$kdeconfig --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 = $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(/:/, `$kdeconfig --path apps`)))
{
my $dir = $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 get_cache_dir
{
my $ret;
if($ENV{XDG_CACHE_HOME})
{
$ret = "$ENV{XDG_CACHE_HOME}/xdg_menu";
}
else
{
$ret = "$ENV{HOME}/.xdg_menu_cache";
}
return $ret;
}
sub check_cache
{
my $cachedir = get_cache_dir;
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 = get_cache_dir;
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 = "/usr/bin/gnomesu" if -x '/usr/bin/gnomesu';
$root_cmd = "/usr/bin/kdesu" if -x '/usr/bin/kdesu';
my $help;
GetOptions("format=s" => \$format,
"fullmenu" => \$fullmenu,
"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]
[--fullmenu] [--help]
format - output format
possible formats: twm, WindowMaker, fvwm2, icewm, ion3,
blackbox, fluxbox, openbox,
xfce4, openbox3, openbox3-pipe, awesome
jwm, readable
default: WindowMaker
fullmenu - output a full menu and not only a submenu
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 'icewm')
{
$output = output_icewm_menu($menu);
}
elsif ($format eq 'ion3')
{
$output = output_ion3_menu($menu);
}
elsif ($format eq 'awesome')
{
$output = output_awesome_menu($menu);
}
elsif ($format eq 'twm')
{
$output = output_twm_menu($menu);
}
elsif ($format eq 'xfce4')
{
$output = output_xfce4_menu($menu);
}
elsif ( ($format eq 'blackbox') || ($format eq 'openbox') || ($format eq 'fluxbox') )
{
if ($fullmenu)
{
$output = output_blackbox_menu($menu, $format eq 'fluxbox');
}
else
{
$output = output_blackbox_inner_menu($menu, $format eq 'fluxbox');
}
}
elsif ($format eq 'openbox3')
{
if ($fullmenu)
{
$output = output_openbox3_menu($menu);
}
else
{
$output = output_openbox3_inner_menu($menu);
}
}
elsif ($format eq 'openbox3-pipe')
{
$output = output_openbox3_pipe_menu($menu);
}
elsif ($format eq 'jwm')
{
if ($fullmenu)
{
$output = output_jwm_menu($menu);
}
else
{
$output = output_jwm_inner_menu($menu);
}
}
elsif ($format eq 'readable')
{
$output = output_readable($menu);
}
else
{
print STDERR "Unknown format $format\n";
exit 1;
}
print $output;
write_cache($output);
exit 0;
Please remember to mark your thread [SOLVED] to make it easier for others to find
--- How To Ask For Help | FAQ | Developer Wiki | Community | Contribute ---
Offline
Simple, huh?!
Thanks, I will see what that does.
MX-23 (based on Debian Stable) with our flagship Xfce 4.18.
Offline
In case anyone is tempted to try that xdg_menu command, I found it unusable in its present form on a Debian Stable base. The output file contained 174 lines, and when running it only 4 items showed in the menu itself.
MX-23 (based on Debian Stable) with our flagship Xfce 4.18.
Offline
Pages: 1
[ Generated in 0.012 seconds, 8 queries executed - Memory usage: 902.04 KiB (Peak: 1006.89 KiB) ]