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 .app
extension.
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 Info.plist
are:
.icns
icon file to use for a projectSetting the app icon requires similar changes to the Info.plist
:
<key>CFBundleIconFile</key>
<string>AppIconMac.icns</string>
Simple.
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-name
key references the name of an icon installed to a theme.
metadata::custom-icon
key references a single icon file.
file://
URL rather than an absolute or relative file path.metadata::trusted
GIO 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.