DiskPush 0.3.0 adds "Open with" to the right click menu on a local file. The system default is preselected, the applications registered for that file type are listed under it, and Enter opens the file the way double clicking it would. That is the whole feature. It took about an hour, and most of that hour was two lines in a config format I thought I already understood.
Where the list comes from
A Linux desktop keeps this in .desktop files and a mime association database. You do not have to reimplement any of it, because gio is right there and it is what the desktop itself consults:
gio info -a standard::content-type Notes.md -> text/markdown
gio mime text/markdown -> the default, then the rest
gio launch /usr/share/applications/code.desktop Notes.md
Launching through gio launch matters. A desktop entry's Exec line has its own syntax, with field codes like %f and %U that mean single file, list of files, list of URLs. Reconstructing that yourself is a small parser and a large number of ways to hand an application the wrong thing. gio launch already knows.
The first lie
A .desktop file is INI shaped, so the obvious parser scans for Name= and takes the value. Here is a real one from this machine:
[Desktop Entry]
Name=btop++
GenericName=System Monitor
GenericName[it]=Monitor di sistema
Name[zh_TW]=系統監視器
Those bracketed keys are translations. Name[zh_TW] comes after Name in the file, so a scan that keeps the last match names the application 系統監視器 for everybody. A scan that keeps the first match works here and breaks on the next file that happens to order them differently. The key is Name exactly, and anything with a bracket in it is a different key.
The second lie
The same file can carry more than one group:
[Desktop Entry]
Name=Files
[Desktop Action new-window]
Name=Open a New Window
Read the whole file for Name= and the application is called "Open a New Window". Actions are the entries you get from a right click on a launcher, and they have names because they are menu items too. Only [Desktop Entry] is the application.
Both of these are the same mistake: treating a structured format as a pile of lines because the lines look simple. The fix is four lines of state, and the test that pins it is a real file copied out of /usr/share/applications rather than one I wrote to match my parser.
The one I have made twice now
The dialog lists the handlers in a scroll box. I wrote this:
<ScrollArea className="max-h-[240px]">
which does not clip. The scroll viewport inside is height: 100%, and against a parent with no definite height that resolves to auto, so the box grows to fit its contents and the dialog cuts off whatever hangs past the bottom. Not scrolls. Cuts off. The last application in the list was unreachable.
I fixed exactly this bug earlier the same day in a different dialog, in the delete list of the mirror preview, where the rows were printing over the disclosure below them. Knowing it did not stop me writing it again, because max-h on the scrolling thing is what you reach for. The shape that works puts the constraint on a parent and lets the scroll area fill it:
<div className="flex max-h-[240px] flex-col overflow-hidden">
<ScrollArea className="min-h-0 flex-1">
I only caught it because I screenshot the dialog instead of trusting that it rendered. That is the actual lesson here, and it is not about CSS.
What is not tested
Handler enumeration end to end. The machine I build on has gio but no desktop associations at all, so gio mime returns "No default applications" for every type on earth. The parsers are tested against real gio output and real desktop files. The live lookup is not, and I would rather say so than let the passing suite imply otherwise.
DiskPush 0.3.0: https://diskpush.com/download
This post was drafted with AI assistance.
Top comments (0)