GTK and Qt export

MixOS apps follow settingsd natively. GTK and Qt apps (Firefox, Thunderbird, GNOME and KDE apps) read their own files. exportd-gtkqt is the desk's one writer of those files: a per-session Mix citizen that turns each settings revision into GTK CSS, GTK settings.ini keys and a qt5ct/qt6ct colour scheme and config. The portal values (color-scheme, accent-color, contrast) come from portald, which writes no files.

It runs on every desk except one that already has its own bridge: on a KDE Plasma host, Plasma's GTK bridge and Qt platform theme read kdeglobals, so the KDE exporter covers them and exportd-gtkqt is not started there.

What it writes

Everything goes under the session's $XDG_CONFIG_HOME:

FileWhat
gtk-4.0/mixos/dark.css, light.csslibadwaita's named colours (window_bg_color, accent_bg_color, blue_1 … purple_5, light_1 … light_5, dark_1 … dark_5) as @define-color lines; the light and dark greys are one ramp of the palette's neutral_5 from white to black, with libadwaita's names and meaning (COSMIC writes 0-based names no stylesheet reads)
gtk-4.0/gtk.css, gtk-3.0/gtk.csssymlinks to the current mode's file, so a mode switch is one rename
gtk-3.0/settings.ini, gtk-4.0/settings.ini[Settings]: gtk-application-prefer-dark-theme, gtk-font-name, gtk-decoration-layout, gtk-icon-theme-name, and in GTK 3 gtk-theme-name (adw-gtk3/adw-gtk3-dark, only when installed)
qt5ct/colors/MixOSDark.conf, MixOSLight.conf (and qt6ct/)the 21 Qt palette roles for the active, inactive and disabled groups, opaque, #AARRGGBB
qt5ct/qt5ct.conf, qt6ct/qt6ct.conf[Appearance]: color_scheme_path, custom_palette, icon_theme, standard_dialogs=xdgdesktopportal, mixos_qt_version; [Fonts]: general, fixed

When the desk has gsettings, it also sets org.gnome.desktop.interface color-scheme, font-name, monospace-font-name, icon-theme, gtk-theme, org.gnome.desktop.a11y.interface high-contrast and org.gnome.desktop.wm.preferences button-layout.

Qt apps read the qt5ct/qt6ct files only through that platform theme, so the desk's session environment sets QT_QPA_PLATFORMTHEME=qt6ct (a Qt 5 app looks for qt5ct under the same variable and falls back to its default theme without it).

The colours are not computed here. settings.palette carries a toolkit block per mode (gtk, the named colours, and qt, the three palette groups), derived from the same theme the COSMIC export uses. The fonts are the palette's too (fonts.ui, fonts.mono: the family, size and weight MixOS draws, the session's choices over the design's), written even when no font key is set. Sizes go to points as every exporter does: px × accessibility.text_scale × 0.75, to the nearest half point; the text scale is in the point size, so text-scaling-factor is not written. A weight other than Regular becomes a Pango style word in GTK (Inter Light 9) and Qt 5's weight scale in the qt conf. The icon theme is appearance.icon_theme, its -dark variant under dark when installed. A desk without the theme keeps the mixos icon theme, which Craft title bars draw their buttons from, and exportd.gtkqt.status reports icon_theme_missing: true. The button layout comes from window.caption_side and window.controls.minimize / .maximize.

How it follows settings

It subscribes to the appearance, window and accessibility keys topics of the default profile. Each delivery, the retained replay at start included, is only a hint: it reads one record (settings.get {view: values} and settings.palette, both at one revision) and writes what differs, so a desk that was off catches up at start. Files land with an atomic rename; the colour files land before the links flip to them.

appearance.export_toolkits = false stops all writes. It undoes nothing.

Your own edits

The CSS and colour-scheme files are exportd-gtkqt's own. In settings.ini and the qt conf files it owns only the keys above: every other key, comment and section is left as it was. A key whose value is no longer what exportd-gtkqt last wrote was changed by hand; it is kept from then on and listed under kept in the status. A foreign gtk.css that was a regular file is copied once to gtk.css.bak before the link replaces it.

It refuses to write through a symlink, and it does not run when XDG_CONFIG_HOME is unset, relative, or the home directory's own .config, so a nested desk never writes into its host desktop. A standalone install that wants exactly that sets MIXOS_EXPORTD_GTKQT_HOME=1.

Undo

The first time it touches a file it records the file's original state, and the first time it sets a key it records the key's original value (under $MIXOS_VAR/exportd/gtkqt/records.json, else $XDG_STATE_HOME/mixos/exportd/gtkqt). exportd.gtkqt.undo restores every file and key that still holds what exportd-gtkqt wrote, lists the ones changed since under left, and pauses the export. exportd.gtkqt.reconcile resumes it. GSettings values are not undone.

Commands

CommandAnswer
exportd.gtkqt.statusoutcome (waiting, written, unchanged, gated, paused, skipped, error), the revision, written, kept, icon_theme_missing, gsettings, last_error
exportd.gtkqt.reconcileresumes after an undo and writes the current record now
exportd.gtkqt.undo{restored, left}
send exportd-gtkqt exportd.gtkqt.status
print($result)

Running it

The unit is exportd-gtkqt@.service, one per session, with the session's environment file. Tests:

mix services/exportd/tests/gtkqt_test.mix
mix services/exportd/tests/gtkqt_export_test.mix

The source is services/exportd.

Each file mutation has a durable write-ahead intent. Restart recovers an interrupted operation without losing the original. Undo persists its pause first and can be repeated after interruption. Hand-edit records survive undo and reconcile; generated empty INI files and owned backup copies are removed. An unreadable records.json is preserved and reported through status; writes remain refused until it is repaired. Every config ancestor must be a plain directory, and HOME/.config is also checked against the canonical HOME path.

This page as Markdown · This page describes the current main branch of MixOS.