Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Bower components Debian packages RPM packages NuGet packages

beebox / crossover   deb

Repository URL to install this package:

Version: 18.5.0-1 

/ opt / cxoffice / lib / perl / CXMenuXDG.pm

# (c) Copyright 2005-2014. CodeWeavers, Inc.
package CXMenuXDG;
use warnings;
use strict;

use CXLog;
use CXUtils;
use CXXMLDOM;
use CXMenu;
use base "CXMenu";



#####
#
# Folder helper functions
#
#####

sub get_root_menu($$)
{
    my ($self, $component)=@_;

    if (!$self->{root_subname} or
        $self->{root_allowed}->{$component->{_xname}})
    {
        $component->{_xpath}="/$component->{_xname}";
        return $self->{root_menu};
    }

    if (!$self->{root_submenu})
    {
        $self->{root_submenu}=get_child_by_name($self->{root_menu}, "Menu", $self->{root_subname});
        if (!$self->{root_submenu})
        {
            $self->{root_submenu}=add_new_element($self->{root_menu}, "Menu");
            add_new_element($self->{root_submenu}, "Name", $self->{root_subname});
        }
    }
    $component->{_xpath}="/$self->{root_subname}/$component->{_xname}";
    return $self->{root_submenu};
}

sub get_xpath($$)
{
    my ($self, $path)=@_;

    my $xpath=$path;
    if (defined $self->{root_subname})
    {
        my $topmenu=$xpath;
        $topmenu=~ s!^/([^/]+)(?:/.*)?$!$1!;
        if (!$self->{root_allowed}->{$topmenu})
        {
            $xpath="/$self->{root_subname}$xpath";
        }
    }
    cxlog("xpath='$xpath'\n");

    return $xpath;
}

sub get_folder_by_xpath($$)
{
    my ($self, $xpath)=@_;

    my $folder=$self->{folders}->{$xpath};
    # Check the parent node to verify that the folder has not been deleted
    return $folder if ($folder and $folder->getParentNode());

    $folder=get_tag_by_path($self->{root_menu}, "Menu", $xpath);
    # $self->{folders} also serves as a negative cache
    $self->{folders}->{$xpath}=$folder;
    return $folder;
}

sub get_xdg_basename($$)
{
    my ($self, $path)=@_;

    $path =~ s!/$!!;
    return "cxmenu-$self->{tag}-" . CXUtils::base32(CXUtils::hash_string($path), 7);
}


sub create_folder($$$)
{
    my ($self, $parent, $component)=@_;
    cxlog("create_folder($component->{_xpath})\n");

    my $folder=$self->{folders}->{$component->{_xpath}};
    if (!defined $folder)
    {
        $folder=get_child_by_name($parent, "Menu", $component->{_xname});
    }

    # Update the XML tree
    my $basename=$self->get_xdg_basename($component->{path});
    my $new_folder;
    if (!defined $folder)
    {
        # Add a new <Menu> tag
        my $pos=get_child($parent, "Menu");
        $folder=add_new_element($parent, "Menu", undef, $pos);
        # gnome-panel < 2.10 (e.g. Fedora Core 2 and 3) is confused if it
        # finds a '%' in the XML file. Using the XML character entity
        # instead does not help. Furthermore the Name field in the
        # .directory file will override the XML <Name> tag anyway, so let's
        # just use the mangled folder name.
        add_new_element($folder, "Name", $component->{_xname});
        add_new_element($folder, "Directory", "$basename.directory");

        if ($self->{hidden_hack})
        {
            # KDE 3.5.1 is buggy and won't show folders that have no menu entry
            # and a single sub-folder. So we always add an empty '#Hidden'
            # folder:
            # - The '#' guarantees there won't be a name collision with a real
            #   mangled folder name.
            # - Because that folder is empty it will not be shown.
            # - Folders will always have at least two entries and thus will
            #   always be shown.
            # - However this also disables KDE's 'collapsing' feature for
            #   single-entry folders and thus we only enable this hack when
            #   needed.
            my $hidden=add_new_element($folder, "Menu");
            add_new_element($hidden, "Name", "#Hidden");
        }
        my $inctag=add_new_element($folder, "Include");
        add_new_element($inctag, "Category", "X-$basename");

        $self->{folders}->{$component->{_xpath}}=$folder;
        $self->{modified}=1;
        $new_folder=1;
    }

    # Update or create the .directory file
    my $folder_dir="$self->{xdg_dir}/desktop-directories";
    my $filename="$folder_dir/$basename.directory";
    if ($new_folder or !$component->{intermediate} or !-f $filename)
    {
        if (!cxmkpath($folder_dir))
        {
            cxerr("unable to create the '$folder_dir' directory: $@\n");
            return 0;
        }
        # (Re)Create the corresponding directory file (from scratch)
        # xdg_create_directory() takes care of everything, we just need
        # to figure out the filename
        CXMenu::xdg_create_directory($filename, $component, 1);
    }

    return $folder;
}

sub delete_folder($$$;$)
{
    my ($self, $folder, $xpath, $detach)=@_;

    # Delete that folder's directory and desktop files
    my $directory=get_child($folder, "Directory");
    if ($directory)
    {
        # Computing the path from the xpath is tricky. Fortunately for our menus
        # the <Directory> tag contains the basename so we don't need the path.
        # If <Directory> does not look like a basename then it must be the
        # root_menu or root_submenu and they don't have desktop or directory
        # files anyway.
        my $basename=get_cdata($directory);
        $basename =~ s/\.directory$//;
        if ($basename =~ /^cxmenu-/)
        {
            for my $rawdir (@{$self->{xdg_dirs}})
            {
                my $dir="$self->{destdir}$rawdir";
                # The desktop files
                CXUtils::delete_files("$dir/applications", "$basename-.*\\.desktop\$");
                # And the directory file
                my $filename="$dir/desktop-directories/$basename.directory";
                if (-f $filename)
                {
                    cxlog("Deleting '$filename'\n");
                    cxwarn("unable to delete '$filename': $!\n") if (!unlink $filename);
                }
            }
        }
    }

    # Then recursively delete the subfolders. We have to do that to get rid of
    # their .desktop and .directory files.
    my $child=$folder->getFirstChild();
    while (defined $child)
    {
        my $tag=$child->getNodeName();
        if ($tag eq "Menu")
        {
            my $name=get_cdata(get_child($child, "Name"));
            $self->delete_folder($child, "$xpath/$name");
        }
        $child=$child->getNextSibling();
    }

    # Delete the <Menu> tag
    remove_element($folder->getParentNode(), $folder) if ($detach);
    delete $self->{folders}->{$xpath};
    $self->{modified}=1;
}


#####
#
# XDG XML file creation and parsing
#
#####

sub setup_default_merge_dir($)
{
    my ($self)=@_;
    my $merge_dir=$self->{menu};
    $merge_dir =~ s/\.menu$/-merged/;
    # Override the existing merge_dir if any
    $self->{merge_dir}=$merge_dir;
    push @{$self->{merge_dirs}}, $merge_dir;
}

sub get_xdg_config($$)
{
    my ($self)=@_;
    return if (defined $self->{root_name});

    # Read the master XML file to extract configuration information. Because
    # this is a configuration file we must read the real file, even if it is
    # outside destdir.
    my $filename=$self->{menu};
    $filename=$self->{master_menu} if (!-f $filename);
    if (-f $filename)
    {
        my $xml=CXXMLDOM::parse_xml_file($filename);
        if ($xml)
        {
            my $root_menu=find_tag($xml, "Menu");
            my $child=$root_menu->getFirstChild();
            while (defined $child)
            {
                my $tag=$child->getNodeName();
                if ($tag eq "Name")
                {
                    $self->{root_name}=get_cdata($child);
                }
                elsif ($tag eq "DefaultMergeDirs")
                {
                    cxlog("found <DefaultMergeDirs>\n");
                    $self->setup_default_merge_dir();
                }
                elsif ($tag eq "MergeDir")
                {
                    my $merge_dir=get_cdata($child);
                    if ($merge_dir)
                    {
                        if ($merge_dir !~ m%^/%)
                        {
                            my $dir=cxdirname($self->{menu});
                            $merge_dir="$dir/$merge_dir";
                        }
                        # Must check the real directory here so we know what to
                        # create in destdir
                        if (!-d $merge_dir)
                        {
                            cxlog("the '$merge_dir' MergeDir does not exist. Ignoring it.\n");
                        }
                        elsif (!-w $merge_dir)
                        {
                            cxlog("the '$merge_dir' MergeDir is not writable. Ignoring it.\n");
                        }
                        else
                        {
                            if (!defined $self->{merge_dir})
                            {
                                $self->{merge_dir}=1;
                            }
                            push @{$self->{merge_dirs}}, $merge_dir;
                        }
                    }
                }
                elsif ($tag eq "Layout")
                {
                    cxlog("found <Layout>\n");
                    my $lspec=$child->getFirstChild();
                    while (defined $lspec)
                    {
                        $tag=$lspec->getNodeName();
                        if ($tag eq "Merge")
                        {
                            my $type=$lspec->getAttribute("type") || "";
                            cxlog("type=$type\n");
                            if ($type =~ /^(menus|all)$/i)
                            {
                                # We won't have trouble adding our menus
                                delete $self->{root_subname};
                                delete $self->{root_allowed};
                                last;
                            }
                        }
                        elsif ($tag eq "Menuname")
                        {
                            my $name=get_cdata($lspec);
                            if ($name)
                            {
                                if (!defined $self->{root_subname})
                                {
                                    $self->{root_subname}=$name;
                                }
                                $self->{root_allowed}->{$name}=1;
                            }
                        }
                        $lspec=$lspec->getNextSibling();
                    }
                    if ($self->{root_subname})
                    {
                        cxlog("sub root='$self->{root_subname}'\n");
                        cxlog("allowed root directories:\n  ",
                              join("\n  ", keys %{$self->{root_allowed}}), "\n");
                    }
                }
                $child=$child->getNextSibling();
            }
        }
    }
    $self->{root_name} ||= "Applications";
    $self->setup_default_merge_dir() if (!defined $self->{merge_dir});
}


sub init_xml($$)
{
    my ($self)=@_;

    return $self->{xml} if (defined $self->{xml_file});
    $self->get_xdg_config();
    $self->{xml_file}="$self->{destdir}$self->{merge_dir}/cxmenu-$self->{tag}.menu";

    my $xml;
    if (-f $self->{xml_file})
    {
        $xml=CXXMLDOM::parse_xml_file($self->{xml_file});
    }
    else
    {
        cxlog("Creating XDG menu file\n");
        my $xdg="<Menu>\n" .
                "  <Name>$self->{root_name}</Name>\n" .
                "</Menu>\n";
        $xml=CXXMLDOM::parse_xml_string($xdg);
    }
    return 0 if (!$xml);
    $self->{xml}=$xml;

    # Make sure we have a root menu
    $self->{root_menu}=find_tag($xml, "Menu");
    if (!$self->{root_menu})
    {
        $self->{root_menu}=add_new_element($xml, "Menu");
        add_new_element($self->{root_menu}, "Name", $self->{root_name});
    }

    return 1;
}


#####
#
# Main
#
#####

sub new($$$$$)
{
    my ($class, $cxoptions, $gui_info, $menu, $master_menu)=@_;

    my $self={
        tag              => $cxoptions->{tag},
        destdir         => $cxoptions->{destdir},
        desktopdata      => $cxoptions->{desktopdata},
        ro_desktopdata   => $cxoptions->{ro_desktopdata},
        menu             => $menu,
        master_menu      => $master_menu,
        xdg_dir          => $gui_info->{xdg_preferred_data},
    };
    if ($gui_info->{preferred_scope} eq "private")
    {
        $self->{xdg_dirs}=[$gui_info->{xdg_preferred_data}];
    }
    else
    {
        $self->{xdg_dirs}=[grep {$_ ne ""} split /:+/, $gui_info->{xdg_data_dirs}];
    }

    if (($gui_info->{kde_version} || "") =~ /^3\.5\./)
    {
        $self->{hidden_hack}=1;
    }

    bless $self, $class;
    return $self;
}

sub detect($$$$)
{
    my ($class, $cxoptions, $cxconfig, $gui_info)=@_;
    return () if (!$gui_info->{xdg_menu_on});

    # Check for XML::DOM support
    my $err=CXXMLDOM::get_xml_load_error();
    if ($err)
    {
        cxerr("unable to use XML::DOM:\n$err\n");
        return  ();
    }

    # Notes:
    # - CXMenuXDG does not handle the XDG desktop icons as these
    #   have nothing in common with the regular XDG menus.
    # - Also, each XDG menu instance could in theory share its files
    #   including the XML file with a simple symbolic link in the right place.
    #   However, each instance must also be independent. That is, it should be
    #   possible to disable an instance while still creating the others,
    #   and uninstalling a given menu in an instance should not delete the
    #   corresponding menu in the others. This means either introducing
    #   complex usage tracking (slowing things down too), or duplicating the
    #   menus and keeping the current relatively simple code at the cost of
    #   doing the same work twice when multiple instances are present.
    #   The latter solution was chosen.
    # - We may also have independent sets of global XDG menus that all
    #   correspond to a single set of user XDG menus. So detect duplicate
    #   xdg_preferred_menu* values and only create one CXMenuXDG instance for
    #   them. Note that this means we will only read the settings of one of
    #   the global .menu files. Hopefully they will all be interchangeable.
    my %seen;
    my @selves;
    for (my $i=1; exists $gui_info->{"xdg_preferred_menu$i"}; $i++)
    {
        my $preferred=$gui_info->{"xdg_preferred_menu$i"};
        next if ($seen{$preferred});
        $seen{$preferred}=1;
        push @selves, new($class, $cxoptions, $gui_info,
                          $preferred, $gui_info->{"xdg_global_menu$i"});
    }
    return @selves;
}

sub id($)
{
    my ($self)=@_;
    my $id="CXMenuXDG/$self->{menu}";
    $id =~ s%/+%/%g;
    return $id;
}

sub install($$)
{
    my ($self, $components)=@_;

    my $menu=@$components[-1];
    return 1 if ($menu->{is_desktop});
    return 0 if (!$self->init_xml());

    if (!defined $menu->{xdgcategories})
    {
        # Create the folder the menu entry is supposed to be in
        my ($folder, $xpath);
        foreach my $component (@$components)
        {
            $component->{_xname}=$component->{name};
            if (!$folder)
            {
                $folder=$self->get_root_menu($component);
                $xpath=$component->{_xpath};
            }
            else
            {
                $xpath="$xpath/$component->{_xname}";
                $component->{_xpath}=$xpath;
            }
            $component->{_creator}="";
            last if (!$component->{is_dir});
            CXMenu::xdg_install_icons($self->{xdg_dir}, $self->{tag}, $component);
            $folder=$self->create_folder($folder, $component);
            return 0 if (!defined $folder);
        }
    }
    else
    {
        # No need to create a folder. The menuing system will place the menu
        # entry according to its XDG category.
        $menu->{_xpath}=$menu->{path};
        $menu->{_xname}=$menu->{name};
        $menu->{_creator}="";
    }

    CXMenu::xdg_install_icons($self->{xdg_dir}, $self->{tag}, $menu);
    if ($menu->{is_dir})
    {
        # Make sure our brand new (maybe empty) folder will not be
        # 'garbage collected' by finalize()
        $self->{status}->{$menu->{_xpath}}="in-use";
    }
    else
    {
        my $xdir=cxdirname($menu->{_xpath});
        $self->{status}->{$xdir}="in-use";

        # Create the desktop file
        my $apps_dir="$self->{xdg_dir}/applications";
        if (!cxmkpath($apps_dir))
        {
            cxerr("unable to create the '$apps_dir' directory: $@\n");
            return 0;
        }
        my $basename=$self->get_xdg_basename($menu->{dir});
        $menu->{_categories}=$menu->{xdgcategories} || "X-$basename;";
        $menu->{_creator}=" (" . $self->id() . ")" if ($ENV{CX_TAGALL});
        CXMenu::xdg_create_desktop_file("$apps_dir/$basename-$menu->{_xname}.desktop", $menu);
    }

    return 1;
}

sub query($$)
{
    my ($self, $components)=@_;
    return ($self->id(), "") if (!defined $components);

    my $menu=@$components[-1];
    return "" if ($menu->{is_desktop});
    return "0" if (!$self->init_xml());

    # Check that everything is in place in the XML file
    my $dir=$menu->{is_dir} ? $menu->{path} : $menu->{dir};
    $dir =~ s%/+$%%;
    my $basename=$self->get_xdg_basename($dir);
    if (!defined $menu->{xdgcategories})
    {
        my $xdir=$self->get_xpath($dir);
        my $folder=$self->get_folder_by_xpath($xdir);
        return "" if (!$folder);
        my $include=get_child($folder, "Include");
        return "" if (!$include);
        my $category=get_child($include, "Category");
        return "" if (!$category or get_cdata($category) ne "X-$basename");
    }

    # Then check the presence of the .desktop or .directory file
    my $filename=$menu->{is_dir} ?
        "desktop-directories/$basename.directory" :
        "applications/$basename-$menu->{name}.desktop";
    for my $dir (@{$self->{xdg_dirs}})
    {
        return $self->id() if (-f "$self->{destdir}$dir/$filename");
    }
    return "";
}

sub get_files($$)
{
    my ($self, $components)=@_;
    return [] if (!defined $components);

    my $menu=@$components[-1];
    return [] if ($menu->{is_desktop});

    my @files;
    $self->get_xdg_config();
    foreach my $mdir (@{$self->{merge_dirs}})
    {
        my $filename="$self->{destdir}$mdir/cxmenu-$self->{tag}.menu";
        push @files, $filename if (-f $filename);
    }
    push @files, @{CXMenu::xdg_get_icon_files($self->{destdir}, $self->{xdg_dirs}, $self->{tag}, $menu)};

    my $dir=$menu->{is_dir} ? $menu->{path} : $menu->{dir};
    $dir =~ s%/+$%%;
    my $basename=$self->get_xdg_basename($dir);
    my $filename=$menu->{is_dir} ?
        "desktop-directories/$basename.directory" :
        "applications/$basename-$menu->{name}.desktop";
    for my $dir (@{$self->{xdg_dirs}})
    {
        my $fullpath="$self->{destdir}$dir/$filename";
        push @files, $fullpath if (-f $fullpath);
    }
    return \@files;
}

sub uninstall($$)
{
    my ($self, $components)=@_;

    my $menu=@$components[-1];
    return 1 if ($menu->{is_desktop});
    return 0 if (!$self->init_xml());

    my $dir=$menu->{is_dir} ? $menu->{path} : $menu->{dir};
    $dir =~ s%/+$%%;

    # Always mark the parent folder for garbage collection
    # Note that it has to be an xpath in order to take into account the
    # potential 'root_submenu' (see get_root_menu()).
    my $xdir=$self->get_xpath($dir);
    my $xparent=($menu->{is_dir} ? cxdirname($xdir) : $xdir);
    $self->{garbage_collect}->{$xparent}=1;

    if ($menu->{is_dir})
    {
        my $folder=$self->get_folder_by_xpath($xdir);
        return 1 if (!$folder);
        $self->delete_folder($folder, $xdir, 1);
    }
    else
    {
        my $basename=$self->get_xdg_basename($dir);
        for my $dir (@{$self->{xdg_dirs}})
        {
            my $filename="$self->{destdir}$dir/applications/$basename-$menu->{name}.desktop";
            if (-f $filename)
            {
                cxlog("Deleting '$filename'\n");
                if (!unlink $filename)
                {
                    cxerr("unable to delete '$filename': $!\n");
                    return 0;
                }
            }
        }
    }

    return 1;
}

sub removeall($$)
{
    my ($self, $pattern)=@_;
    $self->get_xdg_config();

    my $xdg_pattern;
    if ($pattern eq "legacy")
    {
        my $legacy_dir="$ENV{CX_ROOT}/support/xdg-legacy-menus";
        if (-d $legacy_dir)
        {
            cxlog("Deleting the '$legacy_dir' directory\n");
            require File::Path;
            if (!File::Path::rmtree($legacy_dir))
            {
                cxerr("unable to delete the '$legacy_dir' directory: $!\n");
            }
        }
        $xdg_pattern="^cxlegacy";
    }
    else
    {
        $xdg_pattern="^cxmenu-$pattern";
        $xdg_pattern.=".*" if ($xdg_pattern !~ s/\$$//);
    }

    foreach my $mdir (@{$self->{merge_dirs}})
    {
        my $dir="$self->{destdir}$mdir";
        next if (!-d $dir);
        if (!-w $dir)
        {
            cxlog("skipping read-only '$dir' directory\n");
            next;
        }
        CXUtils::delete_files($dir, "$xdg_pattern\\.menu");
    }

    for my $rawdir (@{$self->{xdg_dirs}})
    {
        my $dir="$self->{destdir}$rawdir";
        if (-d "$dir/applications")
        {
            CXUtils::delete_files("$dir/applications", "$xdg_pattern-.*\\.desktop\$");
        }
        if (-d "$dir/desktop-directories")
        {
            CXUtils::delete_files("$dir/desktop-directories", "$xdg_pattern-.*\\.directory\$");
        }
        if (-d "$dir/icons")
        {
            # Linux distributions ship empty icon directories so don't garbage
            # collect them.
            CXUtils::delete_files("$dir/icons", "$xdg_pattern-.*\\.(?:png|xpm)\$", 1, 0);
        }
    }

    return 1 if ($self->{ro_desktopdata});

    # Remove the directory trees where earlier CXMenuXDG versions
    # were storing the .desktop and .directory files
    my $cxdata_dir="xdg-" . cxbasename($self->{menu});
    $cxdata_dir =~ s/\.menu$//;

    if ($self->{tag} and "cxmenu-$self->{tag}" =~ /$xdg_pattern$/)
    {
        my $dir="$self->{desktopdata}/cxmenu/$cxdata_dir";
        if (-d $dir)
        {
            cxlog("Deleting the '$dir' directory\n");
            require File::Path;
            if (!File::Path::rmtree($dir))
            {
                cxerr("unable to delete the '$dir' directory: $!\n");
            }
        }
        CXUtils::garbage_collect_subdirs($self->{desktopdata}, "/cxmenu", 1);
    }
    else
    {
        require CXBottle;
        CXBottle::removeall_desktopdata_dirs($pattern, "/cxmenu/$cxdata_dir");
    }

    return 1;
}

sub gc_get_folder_status($$)
{
    my ($self, $xpath)=@_;
    my $folder=$self->get_folder_by_xpath($xpath);
    if (!$folder)
    {
        cxlog("folder '$xpath' has already been deleted\n");
        return undef;
    }

    # Check for menu entries and sub-folders
    my $basename="";
    my $child=$folder->getFirstChild();
    while (defined $child)
    {
        my $tag=$child->getNodeName();
        if ($tag eq "Menu")
        {
            my $text=get_child($child, "Name");
            if (get_cdata($text) ne "#Hidden")
            {
                cxlog(" -> found a sub-folder\n");
                return ("in-use", $folder);
            }
        }
        elsif ($tag eq "Directory")
        {
            $basename=get_cdata($child);
            $basename =~ s/\.directory$//;
        }
        $child=$child->getNextSibling();
    }

    # Check if there are .desktop files in this folder
    if ($basename =~ /^cxmenu-/)
    {
        for my $dir (@{$self->{xdg_dirs}})
        {
            if (opendir(my $dh, "$self->{destdir}$dir/applications"))
            {
                foreach my $dentry (readdir $dh)
                {
                    if ($dentry =~ /^\Q$basename\E-.*\.desktop$/)
                    {
                        # Although this desktop file corresponds to a menu
                        # entry which originally was in this folder, it may
                        # have an XDG category that puts it elsewhere. This
                        # will just delay, not prevent the deletion of this
                        # folder a bit so we'll ignore this.
                        closedir($dh);
                        cxlog(" -> found a menu entry\n");
                        return ("in-use", $folder);
                    }
                }
                closedir($dh);
            }
        }
    }

    cxlog(" -> empty\n");
    return ("empty", $folder);
}

sub gc_delete_folder($$$)
{
    my ($self, $xpath, $folder)=@_;
    $self->delete_folder($folder, $xpath, 1);
}

sub finalize($)
{
    my ($self)=@_;
    return 1 if (!$self->{xml});

    $self->SUPER::finalize();

    if ($self->{modified})
    {
        if (find_tag($self->{xml}, "Menu"))
        {
            my $dir=cxdirname($self->{xml_file});
            my $target=readlink($dir);
            if (defined $target and $target !~ m%/%)
            {
                # On Mint 17 Cinnamon's '-merged' directory is a symbolic link
                # to the GNOME one which may not exist yet. So try to create it,
                # just in case.
                $target=cxdirname($dir) . "/$target";
                mkdir($target);
            }

            # GNOME is buggy and won't detect our menu file if it is
            # created at the same time as the directory. So rather than
            # introducing delays, we schedule a 'delete and recreate' of
            # the file for a bit later (a simple touch is not enough).
            $dir=shquote_string($dir);
            cxsystem("sleep 10 && touch $dir/cxmenu-gnome-bug-482845.menu && sleep 5 && rm $dir/cxmenu-gnome-bug-482845.menu 2>/dev/null &");

            return CXXMLDOM::save_xml_file($self->{xml_file}, $self->{xml},
                "<!DOCTYPE Menu PUBLIC \"-//freedesktop//DTD Menu 1.0//EN\" \"http://www.freedesktop.org/standards/menu-spec/1.0/menu.dtd\">\n");
        }
        else
        {
            # The file is empty so let's just delete it
            if (-f $self->{xml_file})
            {
                cxlog("Deleting '$self->{xml_file}'\n");
                if (!unlink "$self->{xml_file}")
                {
                    cxerr("unable to delete '$self->{xml_file}': $!\n");
                    return 0;
                }
            }
        }
    }
    else
    {
        cxlog("XML file '$self->{xml_file}' not modified\n");
    }
    return 1;
}

return 1;