Files
mesh-catalog/modules/xdg/README.md
T
jochen 785d32407b xdg: own the account's folders and the machine's default applications
The workstations' XDG conventions had no owner: xdg-user-dirs-update rewrote
the folders file at every login, both machines' default-application lists
named an editor neither has, and the laptop kept two dead links of the
retired predecessor. The module adopts the operator's folders (three as
settings, as the machines differ), disables the update so it cannot undo the
mesh's file, and writes the machine's own mimeapps list, the last one read,
so the person's choices in their own list always win. Autostart is listed,
not owned: each entry is its application's module's.
2026-10-05 11:50:34 +02:00

183 lines
12 KiB
Markdown

# xdg
The workstations' XDG desktop conventions as a module (novox/hq ADR 0208, ADR 0182): the account's
folders, the default applications, and a view of what starts at login. It claims no seat: nothing
calls "the conventions" as a role. It needs no display; every tool reads files, asks xdg-mime and gio,
or reads `/proc`. It is for the two workstations only.
- **Packages:** `xdg-user-dirs` and `xdg-utils` (`xdg-open`, `xdg-mime`, which the tools use).
`dex`, which runs the autostart entries, is `i3`'s. `desktop-file-utils` and `shared-mime-info` come
as other packages' dependencies and are held as found.
- **No contribution, no session code.** The window manager's `dex --autostart --environment i3` line
is `i3`'s, and stays there.
## What it owns
| path | class (ADR 0182) | from |
|---|---|---|
| `~/.config/user-dirs.dirs` | owned, the found file kept once | [`config/user-dirs.dirs`](config/user-dirs.dirs), three folders from settings |
| `~/.config/user-dirs.locale` | owned, the found file kept once | [`config/user-dirs.locale`](config/user-dirs.locale) |
| `~/.config/user-dirs.conf` | owned, new | [`config/user-dirs.conf`](config/user-dirs.conf): `enabled=False` |
| `/etc/xdg/mimeapps.list` | owned, new | [`config/mimeapps.list`](config/mimeapps.list): the machine's default applications |
## What it does not own, and why
| | whose | why |
|---|---|---|
| `~/.config/mimeapps.list` | the person's, held as found | every program offering "always open with" rewrites it, and so does `xdg-mime default` (below) |
| the folders themselves (`~/Downloads`, …) | the person's, held as found | they hold the person's files. Declaring a directory would make its mode the mesh's (ADR 0182), and on one workstation `~/Desktop` is a file, not a folder |
| `~/.config/autostart/*`, `/etc/xdg/autostart/*` | each application's | an autostart entry belongs to the module of the application it starts (below) |
| `~/.config/xdg-desktop-portal/portals.conf` | `adwaita` | the portal's backends are the theme's business |
| `~/.local/share/applications/*.desktop` | the person's and their installers' | JetBrains Toolbox, Steam, Telegram and the agent write their own launchers and handlers there |
## The folders
`user-dirs.dirs` is owned whole, with the operator's own values. Three folders differ between the
workstations, so they are settings; the rest are the same on both.
| setting | the mesh's layer | the laptop | the desktop machine |
|---|---|---|---|
| `desktop-dir` | `$HOME/Desktop` | as the mesh's | `$HOME/` |
| `publicshare-dir` | `$HOME/Public` | as the mesh's | `$HOME/` |
| `videos-dir` | `$HOME/Videos` | as the mesh's | `$HOME/` |
A value is written into the file as it is, in xdg-user-dirs' format: `"$HOME/yyy"` or `"/yyy"`. `$HOME/`
is the home itself, xdg-user-dirs' way of saying the folder is not wanted. The settings have no
default, so the mesh's layer is set before the first assignment (as `power`'s are). They land in this
one file: the module has no merged file and no contribution, so issue 168 cannot carry them anywhere
else.
**Why `xdg-user-dirs-update` is disabled.** It runs at every login, from its XDG autostart entry, which
`dex` starts. Measured on xdg-user-dirs 0.20 against a copy of the folders:
- enabled, it reassigns a folder it cannot find to the home (`Videos was removed, reassigning VIDEOS to
homedir`), adds a line and creates the folder for every default it knows that the file lacks
(`PROJECTS` arrived this way), and rewrites the file with its own header. Every login would undo
the mesh's file, and every push would undo the login;
- disabled by `enabled=False` in the account's `user-dirs.conf`, it does nothing, even with `--force`.
Only an explicit `--set` still writes.
The package's systemd user unit `xdg-user-dirs.service` is enabled by preset but never runs: it is
wanted by `graphical-session-pre.target`, which the xinit session does not reach. Disabled, it would
do nothing either way, so it is left as found.
`user-dirs.locale` (`en_US` on both) only tells xdg-user-dirs-gtk, which neither workstation has,
which language the folders were named in. It is owned so the account's xdg-user-dirs files are one
set.
## Default applications: the mesh's list is the last one read
**Decision.** The mesh owns `/etc/xdg/mimeapps.list`, the system's list, and never writes the
person's `~/.config/mimeapps.list`. An application reads the person's list first and the system's
after it (the XDG MIME Applications specification), so every choice the person makes wins, and the
mesh's list answers only what the person has not chosen. `xdg_default` gets and sets a default, and
setting writes the person's list through `xdg-mime default`, as any program would.
**Why not own the person's file, or a block in it.** Measured on 2026-10-05 with xdg-utils 1.2.1 and
GLib's `gio`, against copies:
1. **Owned whole** fights the person: Firefox's "make default", a file manager's "always open with" and
`xdg-mime default` all rewrite that file, and the next push would take each choice back.
2. **A mesh block at its start** (`into: block`, as `zsh` and `xorg` do) breaks three ways, because a
mimeapps list is a key file and not a script:
- the block's `[Default Applications]` and the person's are one group written twice. **xdg-mime reads
the first and GLib the last**, so `xdg-open` and every GTK or Electron program answered differently
for the same type;
- `gio mime` (what GTK programs do on "always open with") **rewrote the file as one merged group and
dropped the block's markers**: the next push would find no block and add a second;
- `xdg-mime default` **wrote the person's choice into the first group, inside the mesh's block**,
where the next push removes it.
3. **The system's list** has none of this: nothing but the mesh writes it, each reader reads one group
per file, and a person's choice in their own file is never touched. A default naming an
application that is not installed is passed over, to the next list.
**What the mesh's list holds.** The operator's own defaults, adopted from both workstations' lists of
2026-10-05 (the two were byte-identical), except entries naming an application installed on neither:
- dropped: 55 types for `code-oss.desktop` (no Code-OSS on either machine; plain text opens in
Fleet, from its own declaration), `jetbrains-rider.desktop`, `org.kde.korganizer.desktop`,
`Postman.desktop`, and a Fleet launcher whose name is the id one machine's JetBrains Toolbox
generated (a different one on the other machine);
- kept: Firefox for the web, HTML and PDF; Thunar for folders; ghostwriter for Markdown; Plexamp, Slack,
Telegram and JetBrains Toolbox for their own links; and mpv, mplayer, vlc for MP4.
The person's list keeps every line as found, the dropped ones included. `xdg_check` names them.
## Autostart: listed, not owned
An XDG autostart entry belongs to the module of the application it starts, as `picom` does: picom's
package entry is its one starter. A future `nextcloud-client` module owns `Nextcloud.desktop` (the
client writes it itself), a blueman module beside `bluetooth` owns blueman's, and Slack's and JetBrains
Toolbox's entries would be their modules'.
This module only says what is there, and what the session does with it.
The session's starter is `i3`'s `dex --autostart --environment i3`. `xdg_autostart` decides each
entry by dex's own rules (dex 0.10):
- a user entry hides the system entry of the same name;
- `Hidden=true` keeps an entry from starting;
- an `OnlyShowIn` without `i3` keeps it from starting, and so does a `NotShowIn` with it;
- a `TryExec` that is not installed keeps it from starting;
- `X-GNOME-Autostart-enabled=false` and `AutostartCondition` are GNOME's, and **dex ignores them**: the
entry still starts.
## Tools
| tool | | what |
|---|---|---|
| `xdg_user_dirs` | r | each folder as written, the path it resolves to, whether it exists as a directory or points at the home, the folder xdg-user-dirs would have made; whether the update may rewrite the file, and from which file that is decided; the locale |
| `xdg_default` | r/a | the default for a MIME type or a URL scheme (`https`, `mailto`): what `xdg-open` uses (xdg-mime) and what GTK programs use (gio), flagged when they differ; which list decided it and which named applications were passed over as not installed; the application and whether its program is installed. `set` makes an installed desktop file the default through `xdg-mime default`, in the person's list |
| `xdg_open_handlers` | r | the same for http, https, mailto, HTML, folders, plain text, Markdown, PDF, PNG, JPEG, MP4, MP3 and any types given; every mimeapps list in reading order and whose it is; every default naming an application that is not installed; every installed handler whose program is missing |
| `xdg_autostart` | r | every autostart entry, the account's and the system's: what it runs and whether that is installed, Hidden, OnlyShowIn/NotShowIn, whether dex starts it (and if not, why), whether the window manager's configuration starts the same program too, and how many of the account's processes run it now |
| `xdg_check` | r | the folders file is the mesh's, every folder it names exists, the update cannot rewrite it; the mesh's list is in place, every list parses with no group or key written twice, no default names a missing application, no handler's program is missing; no program has two starters at login, every program dex starts is installed |
From `/proc` only the account's own processes are read, and of each only its command line and name.
## What it leaves found
The person's `~/.config/mimeapps.list`; the folders; every autostart entry; every desktop file under
`~/.local/share/applications`, including its `mimeinfo.cache`; `/etc/xdg/user-dirs.conf` and
`user-dirs.defaults` (the package's); and the `xdg-user-dirs.service` user unit.
## The one-off migration (ADR 0182)
**Before the first assignment,** set the three settings on the mesh's layer (`$HOME/Desktop`,
`$HOME/Public`, `$HOME/Videos`) and on the desktop machine's layer (`$HOME/` for all three).
**Once `xdg` is assigned, on the laptop:**
1. Delete `~/.local/share/applications/mimeapps.list` and `~/.local/share/applications/defaults.list`.
Both are links the retired predecessor made into its own tree, which is gone; they point at nothing.
2. `~/.config/autostart/slack.desktop` is the predecessor's too (its comment says so). It still starts
Slack, and is left until a Slack module owns it.
**On the desktop machine:** nothing to remove. `~/Desktop` is a text file, not a folder, which is why
`desktop-dir` is the home there; move it aside and change the setting if a Desktop folder is wanted.
## What changes when it is assigned
| | the laptop | the desktop machine |
|---|---|---|
| packages | none (both present) | the same |
| `user-dirs.dirs` | the found file kept once, then the module's: the same eight folders, and `PROJECTS` becomes `~/projects` instead of the home | the found file kept once, then the module's: the same eight folders, and a `PROJECTS` line (`~/projects`) where there was none |
| `user-dirs.conf` | new: the update stops rewriting the file at login | new: the update, which `dex` starts here since `i3` installed it, would otherwise have added `PROJECTS` and created `~/Projects` at the next login |
| `/etc/xdg/mimeapps.list` | new; changes no answer today, because the person's list says the same | the same |
| running programs, the person's list, autostart | nothing | nothing |
**The one visible change is `PROJECTS`:** `xdg-user-dir PROJECTS` answers `~/projects`, the folder
where the operator's repositories are, on both machines. GLib knows only the eight older folders, so
GTK programs see no change at all.
## What `xdg_check` will say at first
Measured on the laptop before assignment, and expected on both: the defaults naming applications that
are not installed (the 55 Code-OSS types and the four others above) fail "every default names an
installed application", until the person removes them from their list or installs the editor. On the
laptop, the predecessor's two dead links fail "the mimeapps lists are sane" until migration step 1. It
also showed that `audio/mpeg` opens in Audacity from `xdg-open` and in mpv from GTK programs, because
no list names a default for it: one `xdg_default` with `set` settles it.
## Blockers
- The three settings must be set before the first assignment (above).