This is a small Perl6 script that scans a folder for the .pdf documents and .url files to generate an application menu. This menu can reside in a menu bar such as that offered by Xfce's menubar panels. The script will also mimic the folder's subtree structure to create submenus. This provides immediate access to a large library of documents and links right from your desktop.

Perl6/Rakudo is really great at performing these sorts of fs-tree scan/visit algorithms, especially where it needs to actually parse certain files in order to scoop out the relevant contents.


If you do any kind of technical work, you will have some kind of document library. Some documents you will store as copies (usually as PDF), while others you might retain as bookmarks to web pages in your browser.

Now, you could (and probably do) use tab-complete to find these documents in the filesystem from the command line. But if you're opening and closing these documents often, this can be cumbersome.

Since you probably already store the files in an organized set of folders anyway, wouldn't it be nice if you could generate a menu containing quicklinks to all your documents, with submenus mimicking the folder structure? But wait, could that include hyperlinked documents? Could the links be stored in a file tree as well?

Read on.

FreeDesktop specifications

The FreeDesktop specifications are used by GNOME, KDE, and Xfce, and provide your Linux desktop with a set of standard file formats for organizing desktop application menus, shortcuts, icons, and special directories. The files look like Windows .INI files, and they allow for the creation of Start menu-like shortcut menus on your desktop, like this one, seen in Xfce:

The specifications also provide a way to store a URL shortcut in a file. We can use this method to store hyperlinks along with informative descriptions.

In my case, I keep my documents organized into three piles:

  • ee (for electrical engineering)
  • math
  • eng (for other misc. engineering)

If I created folders in the Applications menu to mimick this structure, it would add bloat to that menu, and require me to re-use that menu for documents, which I'd prefer not to do. If I'm retrieving these documents often, it's faster for me to put them inside their own menus next to Applications on the dockbar itself. Essentially, make them root menus of their own.

The .menu format
Reference: [1] The FreeDesktop Menu Specification.

Each menu is described using the FreeDesktop .menu format. An example of such a menu is given here. Here's an excerpt:

math.menu
<Menu>
  <Name>math</Name>
  <Directory>math.directory</Directory>
  <AppDir>/home/joya/.local/share/applications/mylibrary/math</AppDir>
  <DirectoryDir>/home/joya/.local/share/desktop-directories/mylibrary/math</DirectoryDir>
      
  <Include>
    <Filename>MatrixCookbook.desktop</Filename>
    ...

An entry in this menu sets up base directories <AppDir> and <DirectoryDir> which allow subsequent FreeDesktop files to be named using relative paths. Further down, each <Include> section names a .desktop file which provides all the information you'd need to follow a shortcut, be it a document, hyperlink, or whatever.

The <Menu> items can be nested, implying submenus. The excerpt continues:

  </Include>
            
  <Menu>
    <Name>Feedback Systems: An Introduction</Name>
    <Directory>Feedback Systems: An Introduction.directory</Directory>
    <AppDir>/home/joya/.local/share/applications/mylibrary/math/Feedback Systems: An Introduction</AppDir>
    <DirectoryDir>/home/joya/.local/share/desktop-directories/mylibrary/math/Feedback Systems: An Introduction</DirectoryDir>
    
    <Include>
      <Filename>1st-ed.desktop</Filename>
      <Filename>fbs-frontmatter_30Sep18.desktop</Filename>
      <Filename>fbs-ch7-state-feedback.desktop</Filename>
      <Filename>fbs-ch2-feedback-principles.desktop</Filename>
      ... 

While the .menu format looks like a lot, most of it can be derived from the paths and filenames themselves, or from the simple name of the menu being generated (here: math).

Also, there seems to be links off to other .desktop and .directory files which contain more information. Each menu has a <DirectoryDir> element containing a path and a <Directory> which names a .directory file. Again, all of these files can be generated from the basename and path of some other .pdf file.

The .directory format
Reference: [2] The FreeDesktop Desktop Specification.

The .directory file specification is one of the easier ones -- it just allows you to set a name, comment, icon, and character encoding for these. The .directory format follows a subset of the .desktop format, but is mostly self-explanatory:

math.directory
[Desktop Entry]
Encoding=UTF-8
Icon=folder
Type=Directory
Name=math
Comment=Documentation Directory

The real purpose of this file is to supplement a bit of metadata for the menus and submenus. In our project, there is a one-to-one relationship between .directory and .menu files.

The .desktop format
Reference: [2] The FreeDesktop Desktop Specification.
Reference: [3] The entry keys for this file type.

The first math book has an entry for a file called MatrixCookbook.desktop. This is a file that our script will generate that describes the shortcut.

Most of the action is explained in [3], where the keys are explained. Here's the listing for the MatrixCookbook entry given in the menu above:

MatrixCookbook.desktop
[Desktop Entry]
GenericName=File link
Icon=evince
URL=file:///home/joya/read/math/MatrixCookbook.pdf
Type=Link
Name=MatrixCookbook.pdf]

The important part is that the entry is stored as a local file:// URL. You can use hypertext references here too.

The Type field in our shortcuts will always be Link. This invokes the loading-by-mime-type behaviour of the desktop shell.


The insight here is that all of this information can be derived from the filename via its base and extension, as long as we are working with a small set of file types like .pdf.

If our library folder contained .pdf files alongside .desktop files which are "bookmarked" hypertext links, the latter could be copied over directly into our menu system file tree alongside derived .desktop files that we generate for PDF.

And that's exactly what we'll do next with the script.

Generating menus with Perl6 / Rakudo
Reference: [4] GitHub/mjml. The build-mylibrary.pl repository.

Before looking at the script [4] itself, there's a few directories that FreeDesktop reads that you'll have to know about. We'll be creating .menu and .directory files for each library file, and .desktop files for each library file.

So running build-mylibrary.pl ~/read/ee will create ee.menu and ee.directory and the same for all its subfolders and files. Here's a breakdown of what and where each file will be created.

What Where
Why
ee.menu $HOME/.config/menus/ee
Provides for top-level menu creation
ee.directory $HOME/.local/share/desktop-directories/mylibrary/ee
Provides locale-specific descriptions and icons for a top-level folder.
subfolder.directory $HOME/.local/share/desktop-directories/mylibrary/ee/subfolder
Provides locale-specific descriptions and icons for a sub-folder (and hence a sub-menu).
some-document.desktop $HOME/.local/share/applications/mylibrary/ee/subfolder
Provides everything needed to access a file like: $HOME/read/ee/subfolder/some-document.pdf. Using the .desktop format, we can also store URLs underneath e.g. $HOME/read/ee/subfolder with PDFs, and have these copied straight over into our FreeDesktop trees for immediate use as shortcuts alongside PDFs and other local files.

The structure of the script is fairly straightforward (as Perl6 goes). We define a Menu class to retain all the meta information associated with a folder/menu. Then there are two visitor functions: one for folders and one for files. The main flow of execution is then to simply visit every file and folder under some named library folder.

My own library folders exist at $HOME/read/<name> with my largest being the ee library that contains all of my electrical component datasheets.

The only files you work with after the script has been run are the .menu files: you just need to tell your desktop environment where they are in order to have them appear. Everything else is internally cross-referenced with these .menu files describing the top level.

If that isn't enough, the rest of this article provides a little walk-through of making that part work.

Configuration with Xfce

Right-click on blank area of any desktop panel or on one of its existing launchers to get a floating menu. From that menu, choose Panel ▶ ➕Add New Items... then pick Applications Menu and the button Add.

You'll then notice a familiar prefab Applications Menu appear at the far right of your panel. Right-click on it and select Properties.

Here you can decide on the "look and feel" of your new Applications Menu. Since it's going to be a top level library folder and you might have several of these, I choose to leave out the button title and to only Show icons in menu.

Finally, near the bottom, you'll want to choose Use custom menu file and use the file chooser to select the .menu file that was generated under $HOME/.config/menus. Then hit close.

That's it! An entire library folder is now accessible from your desktop.

One small caveat: whenever you call the script to regenerate the file, you'll get a little message from the panel app saying that it failed to load the menu. This is because when you regenerate, it loses track of the old file. Simply go into the panel preferences, briefly select the Menu File radio button for Use the default menu, and then click it back to Use custom menu file again. Now your menu file will be reloaded by the panel applet.

© 2020 michaeljoya.com