DEV Community

Cover image for I had 27 AppImages and none of them showed up in my launcher
pvojnisek
pvojnisek

Posted on

I had 27 AppImages and none of them showed up in my launcher

I use Pop!_OS with the new COSMIC desktop. I also use a lot of AppImages — Obsidian, Cursor, WezTerm, Krita, FreeTube, about 27 of them. And every single time I downloaded one, the same ritual:

  1. Make it executable (chmod +x)
  2. Create a .desktop file by hand
  3. Hunt for the icon (where does this AppImage keep it?)
  4. Figure out the right Categories= so it shows up in the right menu
  5. Repeat for the next app

After a while my ~/apps/ directory looked like a graveyard of versioned files with no way to tell what was current:

Cursor-0.47.9-x86_64.AppImage
cursor-0.45.14-build-250219jnihavxsz-x86_64.AppImage
Joplin-3.0.15.AppImage
Joplin-3.3.13.AppImage
WezTerm-nightly-Ubuntu20.04.AppImage
WezTerm-20240203-110809-5046fc22-Ubuntu20.04.AppImage
Enter fullscreen mode Exit fullscreen mode

So I wrote a tool to fix this. One Python file, zero dependencies.

What it does

You point it at a directory full of AppImages, and it:

  1. Scans for .AppImage files and groups them by app name
  2. Picks the latest version (by version number, not file date — so 0.47.9 beats 0.45.14 even if the older file was touched more recently)
  3. Creates a clean symlinkobsidian.AppImageObsidian-1.9.10.AppImage
  4. Extracts the icon from inside the AppImage and installs it to the XDG hicolor theme
  5. Reads categories and StartupWMClass from the embedded .desktop file
  6. Generates a proper .desktop launcher with all the metadata

The result: hit Super, type the app name, there it is with its real icon. Pin it to the dock — the icon matches. Done.

Why not just use AppImageLauncher?

It's archived. Plus it runs as a system daemon that intercepts all AppImage launches. I wanted something simpler.

Gear Lever is nice but requires Flatpak. AM/AppMan is a full package manager. I didn't need any of that.

I just wanted one thing: make my existing AppImages visible in the launcher. One job, done well.

The tricky parts

Name extraction

AppImage filenames are chaos. There's no standard. I had to write a regex that handles all of these:

Obsidian-1.9.10.AppImage            → obsidian
cursor-0.45.14-build-250219...-x86_64.AppImage → cursor
WezTerm-nightly-Ubuntu20.04.AppImage → wezterm
LM+Studio-0.2.8-beta-v1.AppImage   → lm-studio
FreeCAD_1.0.2-conda-Linux-x86_64-py311.AppImage → freecad
Enter fullscreen mode Exit fullscreen mode

The trick: strip everything from the first version/architecture/platform token onwards. One regex handles 30+ naming patterns.

Icon extraction

Each AppImage has a .DirIcon file that's usually a symlink into usr/share/icons/hicolor/. The challenge is:

  • Some have .DirIcon as a real PNG file
  • Some have it as a symlink (that resolves inside the archive)
  • Some put the icon deep in hicolor/512x512/apps/
  • Some only have SVGs
  • Some have broken symlinks

The script tries each strategy in order, picks the largest resolution available, detects SVG vs PNG, and installs to the right hicolor directory.

COSMIC dock icons

This was the hardest bug to find. The COSMIC launcher (Super key search) worked fine with absolute icon paths. But the dock showed generic icons. Turns out COSMIC's panel doesn't render icons from absolute paths — it only looks in the XDG icon theme.

The fix: install icons to ~/.local/share/icons/hicolor/<size>/apps/ and use bare icon names (Icon=appimage-obsidian) in the .desktop file. Then rebuild the GTK icon cache.

Another missing piece: StartupWMClass. Without it, the dock can't match a running window to its .desktop entry. The script extracts this from the embedded .desktop file inside each AppImage.

Install once, forget it

This is the part I'm most happy with. The --install-watch command creates a systemd user path unit that monitors the AppImage directory. After that, you never touch the script again:

  • Drop a new AppImage → it appears in the launcher with its icon
  • Delete an AppImage → the launcher entry, symlink, and icon get cleaned up
  • Drop a newer version → the symlink re-links to the latest
./appimage-manager.py --install-watch
# That's it. From now on, just drop AppImages in the directory.
Enter fullscreen mode Exit fullscreen mode

No daemon running in the background — systemd only triggers the script when a file actually changes.

The registry

Everything is tracked in a CSV file (appimages.csv). You can edit it with any text editor — change labels, categories, set an app to ignored. Run ./appimage-manager.py sync to apply.

id,label,filename,categories,startup_wm_class,status
obsidian,Obsidian,Obsidian-1.9.10.AppImage,Office;X-AppImage;,obsidian,active
cursor,Cursor,Cursor-0.47.9-x86_64.AppImage,TextEditor;Development;IDE;X-AppImage;,Cursor,active
zen,Zen browser,zen-x86_64.AppImage,Network;WebBrowser;X-AppImage;,zen,active
Enter fullscreen mode Exit fullscreen mode

Try it

curl -LO https://raw.githubusercontent.com/pvojnisek/appimage-manager/main/appimage-manager.py
chmod +x appimage-manager.py
mv appimage-manager.py ~/apps/  # or wherever you keep AppImages
./appimage-manager.py
Enter fullscreen mode Exit fullscreen mode

~850 lines of Python 3.9+, zero dependencies, MIT licensed.

GitHub: pvojnisek/appimage-manager

I'd love feedback — especially if the name extraction regex doesn't handle your AppImage naming convention.

Top comments (0)