In this article we will explain how to define a custom MIME Type with an associated application; the purpose is to automatically launch the specified application from the Firefox browser, when the user clicks over an URL pointing to a specified content type. The reference system is a GNU/Linux workstation running Debian 10.
We want to execute a local program when the user click over an URL pointing to a playlist file. The playlist is a simple text file, containing the filename of the images to be displayed along with geometry data. Geometry data allow to zoom and pan the image on the fly. The program is photo-reframe Slideshow and the playlists are saved as .m3u files.
The ultimate goal of this project is to build a GNU/Linux based kiosk, with a web based interface to select a folder containing images and start a slideshow on it.
A GNU/Linux box with a modern desktop environment already knows several content types; info about most of them are provided by the shared-mime-info package. The GNOME, KDE and Xfce desktop environments use that database.
You can use the xdg-mime command line tool (from the xdg-utils package) to query the database. Here it is an example against a JPEG image:
xdg-mime query filetype myphoto.jpg image/jpeg
The xdg-mime tool uses actually a very complex procedure to determine the content type of the file. The extension of the file (glob matching) is part of this procedure, but also the actual content of the file is generally inspected (magic detection). On our system it turned out also that different binaries are invoked if you are in a console only (textual) environment or in a graphical desktop.
By defining the XDG_UTILS_DEBUG_LEVEL environment variable, you can see what binary program is actually used by xdg-mime:
XDG_UTILS_DEBUG_LEVEL=10 xdg-mime query filetype myphoto.jpg Running gio info "/home/niccolo/myphoto.jpg" image/jpeg
The above example is taken from a graphic environment: the program executed is actually gio.
XDG_UTILS_DEBUG_LEVEL=10 xdg-mime query filetype myphoto.jpg Running mimetype --brief --dereference "/home/niccolo/myphoto.jpg" image/jpeg
In the second example, executed into a text-only console, the program mimetype is executed instead. Fortunately the result is the same: the file is an image/jpeg content type.
We will create a custom MIME type/subtype to suit our needs. For the type we could use image one, to mean that each line of the playlist will point to an image file, but we prefer the application type, because we want to mean that data will require a specific application to be interpreted. For the subtype we will use the x- prefix, because the extension is not considered a standard type, so our choice is x-image-playlist.
We create a playlist to experiment with; this is a playlist.m3u example:
IMG_6602.JPG|4000x2250+0+332 IMG_6605.JPG|2971x1671+796+628 IMG_6606.JPG|4000x2250+0+442 IMG_6610.JPG|3810x2143+90+387 IMG_6615.JPG|2828x1590+547+681
This is the first version of the MIME Type definition, in XML format:
<!-- WARNING! This file is not optimal, see below for the final version. --> <?xml version="1.0" encoding="UTF-8"?> <mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info"> <mime-type type="application/x-image-playlist"> <comment>M3U Images Playlist</comment> <icon name="image"/> <glob pattern="*.m3u"/> </mime-type> </mime-info>
We save the file as photo-reframe.xml into the /usr/local/share/mime/packages/ directory, then we update the database of MIME Types with the command:
Several files will be created/updated in that directory, most notably: globs, globs2, magic and mime.cache.
Here we face the first problem: asking the system the type of the playlist.m3u we get audio/x-mpegurl instead of application/x-image-playlist:
xdg-mime query filetype playlist.m3u audio/x-mpegurl
This is because the extension .m3u is already used by the MIME definition contained into the system file /usr/share/mime/packages/freedesktop.org.xml (provided by the shared-mime-info package).
We can solve the problem by choosing a different filename extension and add another <glob pattern="..."/> line into the XML definition, but here we want to show how to override the default preference order. So we add the weight="60" attribute to the glob tag, knowing that the default weight is 50 (the value must be between 0 and 100).
Executing the update-mime-database tool, the globs and globs2 files are updated. They contain the mime type and the associated extension; if globs2 exists, it is preferred because it contains the weight data, otherwise globs is used (where only the listing order matters).
WARNING! Not all the MIME query tools behave in the same way: gio (from the libglib2.0-bin package) and mimetype (from the libfile-mimeinfo-perl package) are known to return different results for the same file; one reason is that the files in /usr/local/share/mime/ and /usr/share/mime/ are not treated in the same way by the two programs.
Here we face the second problem, despite the .m3u extension is associated to our custom MIME Type with more weigth than the default audio/x-mpegurl type, xdg-mime returns the same result as above.
To get some insight we run the underlying program, which is gio (see above about the XDG_UTILS_DEBUG_LEVEL):
XDG_UTILS_DEBUG_LEVEL=10 xdg-mime query filetype playlist.m3u Running gio info "/home/niccolo/playlist.m3u" audio/x-mpegurl gio info "/home/niccolo/playlist.m3u" | grep content-type standard::content-type: audio/x-mpegurl standard::fast-content-type: application/x-image-playlist
Here it is the clue: the fast-content-type is the one we want (application/x-image-playlist), but the content-type is the one which prevails. The fast-content-type is determined by just inspecting the filename extension (the glob), but the more important content-type is determined by inspecting the actual content of the file (the magic).
There is no choiche: we need to add some info to let the magic work. Taking a cue from the Extended M3U, we will use the pseudo comment #EXTM3U-PHOTO-REFRAME as the first line of the playlist.
So this is the final version of th XML file to define the MIME type:
<?xml version="1.0" encoding="UTF-8"?> <mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info"> <mime-type type="application/x-image-playlist"> <comment>M3U Images Playlist</comment> <icon name="image"/> <magic priority="60"> <match offset="0" type="string" value="#EXTM3U-PHOTO-REFRAME"/> </magic> <glob pattern="*.m3u" weight="60"/> </mime-type> </mime-info>
The <magic> tag uses the match method: the file will be inspected starting at offset zero, searching for that magic string. The priority value of 60, assures that this match will prevail over the defaults. To update the MIME database another run of update-mime-database is required.
This is the new version of the playlist:
#EXTM3U-PHOTO-REFRAME IMG_6602.JPG|4000x2250+0+332 IMG_6605.JPG|2971x1671+796+628 IMG_6606.JPG|4000x2250+0+442 IMG_6610.JPG|3810x2143+90+387 IMG_6615.JPG|2828x1590+547+681
Finally the MIME type detection works as expected:
xdg-mime query filetype playlist.m3u application/x-image-playlist
The MIME related tools search for the MIME database in several directories. On a Debian system there is the directory where offical packages install their definitions, the directory where the system administrator can install local configuration valid for all the users, and the user's personal directory:
To view if there is an application associated with our custom MIME Type, we can run the following command. No result is returned, as we can expect:
xdg-mime query default application/x-image-playlist
We check also the behaviour of the Firefox browser, we prepare an HTML file containing this (notice the anchor tag uses the type attribute to declare the proper MIME type):
<a href="playlist.m3u" type="application/x-image-playlist">Image Slideshow</a>
Once clicked the link, Firefox displays the Open with… dialog box suggesting to use VLC to open the MPEG Audio URL. If you click over the Other… option, Firefox says No applications found for “M3U Images Playlist” files. The MIME Type is recognized, but the application to handle it is unknown.
Now we prepare a file and save it as /usr/local/share/applications/photo-reframe.desktop:
[Desktop Entry] # The format of this file is specified at # http://standards.freedesktop.org/desktop-entry-spec/1.0/ # The entries are in the order they are listed in version 1.0 Type=Application # This is the version of the spec for this file, not the application version. Version=1.0 Name=Photo-Reframe GenericName=Image Viewer Comment=Slideshow with on-the-fly image cropping Icon=image TryExec=photo-reframe Exec=photo-reframe --fullscreen --play %f Terminal=false MimeType=application/x-image-playlist; # Category entry according to: # http://standards.freedesktop.org/menu-spec/1.0/ Categories=Graphics;2DGraphics;RasterGraphics;Qt;
then we run the update-desktop-database over that directory (it will update the mimeinfo.cache file in the same directory):
Something good has happened; the system knows how to handle that MIME type:
xdg-mime query default application/x-image-playlist photo-reframe.desktop
After this step, Firefox suggests to use Photo-Reframe (default) to handle the link, you can check the box Do this automatically for files like this from now on and the associated application will be launched automatically whenever you will click over a link like this. Only after this association, you can open the Firefox Preferences ⇒ Applications and browse for the M3U Images Playlist and assign the preferred action.
NOTICE: You may need to use Shift-Click to force the reloading of the link, otherwise Firefox may get the object from the cache, where the MIME type is cached too.
There are many recipes to add a new MIME Type and an associated applications, many of them suggest to edit some system files. In our experience it is absolutely useless to modify the following files:
For security reasons a web browser does not allow to open a file:// link from a page loaded via a network protocolo, like http://. This is obvious because you don't want to allow a remote sito to fiddle with objects stored on your local disk. But for our application we need this indeed: a page loaded from http://localhost/gallery.php must execute a local program passing a local file path to it, something like /home/data/directory/playlist.m3u.
The link on the HTML page will look something like this:
To bypass the Firefox restriction, you have to create an user.js file in your profile directory, something like $HOME/.mozilla/firefox/3e4ghyw2.default/user.js (tested with Firefox 68.4):
user_pref("capability.policy.policynames", "localfilelinks"); user_pref("capability.policy.localfilelinks.sites", "http://localhost"); user_pref("capability.policy.localfilelinks.checkloaduri.enabled", "allAccess");