I recently extended my website downloader, Crystal, so that the projects it creates have a proper icon on macOS, Windows, and Linux. It was a lot more challenging than I expected!
Crystal organizes a group of downloaded web pages into a project, which is a special folder containing a particular arrangement of files:
$ tree xkcd.crystalproj xkcd.crystalproj ← project ├── database.sqlite ← web page metadata └── revisions ├── 1 ← web page #1 content ├── 2 ← web page #2 content ├── 3 ← web page #3 content ├── 4 ← web page #4 content └── 5 ← web page #5 content
My goal was to make each of these projects appear as a file, rather than as a folder, and to open in Crystal automatically when it is double-clicked on:
Looks and behaves like a file, but is secretly a folder!
On macOS it’s easy to get projects to behave the way I wanted. It’s actually common on macOS for certain types of folders - called bundles - to behave like files. For example apps on macOS are themselves stored as bundles with the
To tell macOS that folders ending with
.crystalproj are bundles I added the following to Crystal’s
Info.plist file, which is included in all macOS applications:
<key>CFBundleDocumentTypes</key> <array> <dict> <key>CFBundleTypeExtensions</key> <array> <string>crystalproj</string> </array> <key>CFBundleTypeIconFile</key> <string>DocIconMac.icns</string> <key>CFBundleTypeName</key> <string>Crystal Project</string> <key>CFBundleTypeRole</key> <string>Editor</string> <key>LSTypeIsPackage</key> <true/> </dict> </array>
The important parts of this
.icnsicon file to use for a project
Setting the app icon requires similar changes to the
On Windows there is no concept of a “bundle”. However it is possible to give a folder a custom icon by putting a specially crafted
desktop.ini file inside of it:
$ cat xkcd.crystalproj/desktop.ini [.ShellClassInfo] DirectoryClass=crystalproj ConfirmFileOp=0 IconFile=icons\docicon.ico IconIndex=0 InfoTip=Crystal Project
It’s even possible to tell Windows to open Crystal when the folder is double-clicked on by setting some registry keys:
However it’s still possible to navigate inside of a such a folder, notably from Open and Save dialogs, so we need another way to open projects in those contexts. My solution was to add a special “opener” file inside of a project folder which could be used to open the enclosing project:
Of course that
.crystalopen file itself needs an icon. Again you can set some registry keys to tell Windows about this new file extension and its associated icon:
And if a
.crystalopen file is double-clicked we want it to open Crystal, which can be configured with more registry keys:
All of these registry keys should be set by the installer for the app.
Oh Linux… Icons for files, folders, and apps are displayed by the desktop environment, and multiple such desktop environments exist for Linux. The most common ones (in 2023) seem to be GNOME 3 and KDE, but there’s also xfce, MATE (GNOME 2), and Budgie, among others. Most of these desktop environments strive to implement the Freedesktop Specifications for defining file types & icons, but with varying degrees of compatibility.
According to the Icon Theme Specification, which is one of the Freedesktop Specifications, an application developer should be able to install icons for file types to a special “hicolor” theme which all other themes must eventually inherit from. But there are some problems:
metadata::custom-icon-namekey references the name of an icon installed to a theme.
metadata::custom-iconkey references a single icon file.
file://URL rather than an absolute or relative file path.
metadata::trustedGIO attribute on it.
In short, my experience in getting icons to work in Linux has been a big poorly-documented mess. But after 2 weeks I did succeed!
When creating a desktop application for end users it’s important to get icons for your app and its documents configured correctly to provide a good user experience, but it getting those configurations correct can be challenging.
I’d like to file bugs on several Linux desktop environments so that future Linux application developers don’t have such a hard time getting icons setup, but right now I just don’t have the energy. Hopefully at least this blog post will help other application developers configure icons more quickly.