exportd
MixOS keeps every desktop setting in settingsd. A desk that runs a foreign desktop (COSMIC, Plasma) or foreign toolkits (GTK, Qt) gets the same look through an exporter: a Mix Bus citizen that follows the settings topics and writes the foreign desktop's own config. The export is one way. settingsd is the source; a change made in the foreign desktop's own settings app is overwritten at the next revision, and nothing is read back.
Every exporter:
- reconciles once when it connects, then writes each revision's changed keys in one burst, with no polling;
- writes only the keys it owns, and only where the contents differ;
- keeps the contents it replaced, so one command undoes it;
- stops writing while its gate is
false:appearance.export_desktopfor the desktops,appearance.export_toolkitsfor GTK and Qt.
Colours and fonts come from settings.palette, the derived palette
settingsd computes; an exporter names them and computes none.
COSMIC
scripts/cosmic.mix writes a COSMIC desktop's cosmic-config tree
($XDG_CONFIG_HOME/cosmic, one RON file per key). cosmic-comp, the panel
and every libcosmic app watch those files, so the desktop follows at once.
mix --serve <prefix>/lib/exportd/cosmic.mix --name exportd-cosmic
The unit exportd-cosmic@.service runs it in a session under the
mixos account. A session using another account needs a User= drop-in;
the template is not enabled or started automatically.
What it writes
| Settings | cosmic-config |
|---|---|
appearance.mode (resolved; auto is settingsd's) | com.system76.CosmicTheme.Mode v1 is_dark; auto_switch always false |
the palette, appearance.contrast, accent, tints, backgrounds | both builders (com.system76.CosmicTheme.Dark.Builder, .Light.Builder v2) and both derived themes (.Dark, .Light v2): every container, component, the 32-colour palette, accent text, shade |
appearance.roundness, appearance.density | both builders' and themes' corner_radii, spacing; com.system76.CosmicTk v1 interface_density, header_size; the panel and dock radius, padding and spacing |
appearance.frosted.* | frosted, alpha_map, frosted_windows, frosted_system_interface, frosted_panel, frosted_applets |
window.active_hint, window.hint_colour, window.gaps | active_hint, window_hint, gaps (the inner gap at least the hint width) |
window.hint_shown, window.clip_*, window.shadow_tiled, window.edge_gravity, focus | com.system76.CosmicComp v1 active_hint, appearance_settings, edge_snap_threshold, focus_follows_cursor, focus_follows_cursor_delay, cursor_follows_focus |
appearance.font_family, mono_family, icon_theme, window.controls.* | CosmicTk v1 interface_font, monospace_font, icon_theme, show_minimize, show_maximize; apply_theme_global always false (MixOS's own GTK and Qt export does that work) |
shell.panels.top, .bottom | com.system76.CosmicPanel.Panel and .Dock v1, and which of them com.system76.CosmicPanel v1 entries lists |
wallpaper.* | com.system76.CosmicBackground v1 all, same-on-all and one key per output |
The fonts are the palette's too: the families MixOS draws, the session's choice or the bundled Inter and JetBrains Mono (COSMIC takes the family alone; it has no size setting). Panel applet lists and the wallpaper are written only once MixOS has its own; until then COSMIC keeps its own.
Each key is replaced atomically (a .atomicwrite temporary, then a
rename), builders before themes and the mode last, so a mode flip lands on
two complete themes.
Commands
| Command | Request and reply |
|---|---|
exportd.cosmic.status | → {binding, root, revision, paused, dry_run, last} |
exportd.cosmic.plan | → {changed: [path], unchanged}: what an export would write; writes nothing |
exportd.cosmic.reconcile | export now; clears a pause |
exportd.cosmic.undo | {to: "previous" | "original"} → {restored, to, paused}: every key back to its contents before the latest write, or before the first; exporting then pauses until reconcile |
exportd.cosmic.owned | → {paths}: every key file it has written |
Environment
| Variable | Default |
|---|---|
MIXOS_SETTINGS_INSTANCE, MIXOS_SETTINGS_PROFILE | the hostname, default (the profile must be default) |
MIXOS_SETTINGS_SERVICE | settingsd |
MIXOS_EXPORTD_COSMIC_ROOT | $XDG_CONFIG_HOME/cosmic |
MIXOS_EXPORTD_STATE | $XDG_STATE_HOME/mixos/exportd/cosmic (undo and pause) |
MIXOS_EXPORTD_DRY_RUN=1 | plan only: log what would change, write nothing |
KDE Plasma
scripts/kde.mix runs in the Plasma user's session as exportd-kde:
mix --serve <prefix>/lib/exportd/kde.mix --name exportd-kde
The user unit exportd-kde.service starts it with the graphical session,
only when the session is Plasma (XDG_SESSION_DESKTOP=KDE).
It reconciles once at start, then once per settings revision that moves
appearance, window, accessibility or shell.panels: a revision that
moves several of them delivers one topic each, and the deliveries of a
revision already reconciled are skipped. A reconcile reads settings.get
and, only when an appearance.* value changed, settings.palette.
What it writes
| settings | KDE |
|---|---|
the resolved mode (resolved.mode of settings.get; never auto) | kdeglobals [General] ColorScheme=MixOS-Dark or MixOS-Light, and the scheme's [Colors:*], [ColorEffects:*] and [WM] groups; both modes also as <data>/color-schemes/MixOS-{Dark,Light}.colors |
| the accent and colour axes, through the palette | [General] AccentColor (accentColorFromWallpaper=false), [Colors:Selection], every scheme colour |
the palette's fonts, accessibility.text_scale | [General] font, menuFont, toolBarFont, fixed, [WM] activeFont |
appearance.icon_theme | [Icons] Theme, the -dark variant in dark mode when installed |
window.hint_colour | [WM] activeBackground, activeForeground |
accessibility.reduced_motion | [KDE] AnimationDurationFactor (0 when reduced, else 1) |
appearance.density | kwinrc [org.kde.kdecoration2] BorderSize (Tiny, Normal, Large), BorderSizeAuto=false |
window.caption_side, window.controls.* | kwinrc [org.kde.kdecoration2] ButtonsOnLeft, ButtonsOnRight |
shell.panels.<panel>.gap (enabled panels) | floating of the Plasma panel on that panel's edge (the Plasma scripting interface); panels on other edges are left alone |
The decoration plugin and theme (library, theme) are the user's and are
never written; the keys above apply to whichever decoration is active.
Scheme colours are the palette's opaque values; a translucent one is
refused. A font size in pixels becomes points at 96 dpi (× 0.75, to the
nearest half point), after the text scale. kdeglobals [MixOS] carries a
stamp with the revision written; the stamp alone never causes a write. A
key the user made immutable (key[$i]) is not written, and the report
names it (immutable: <file>|<group>|<key>).
How it writes
Keys are matched by their raw text, so Icon[de] or font[$i] is never
mistaken for Icon or font. Each file is re-read, edited as text and
replaced atomically while holding KConfig's own lock (<file>.lock), so a
concurrent KConfig writer loses nothing. The last key of each group, and
every font, scheme, accent, icon and animation key, then goes through
kwriteconfig6 --notify, so KConfig's change notification reaches each
group. Then busctl --user sends the KGlobalSettings notifyChange
signal (palette, fonts), KIconLoader iconChanged, and
org.kde.KWin.reconfigure; a call that fails is reported as
<name>_unreachable. What a burst touched is recorded as dirty before the
first write and cleared once every call succeeded, so after a crash or an
unreachable session bus the next run notifies again.
Known limit: running apps follow a colour, icon and decoration change at once but keep their old font until they restart; apps started afterwards get the new font.
Not exported yet (key space §5.3 lists them for kde-export; the next
slice): wallpaper (desktop containments), workspaces (kwinrc [Desktops], RollOverDesktops), input (kxkbrc, kcminputrc), time
(clock use24hFormat, showDate), region, keymaps
(ModifierOnlyShortcuts), the window behaviour rows (focus policy, snap
zones, Super key) and appearance.frosted.* (KWin blur). The behaviour
rows wait for a decision, since they change how the desktop behaves, not
how it looks.
Commands
| Command | Does |
|---|---|
exportd.kde.status | the binding, last revision, gate, pause, the last run's report or error |
exportd.kde.plan | what a reconcile would write now; writes nothing |
exportd.kde.reconcile | reconcile now; clears a pause |
exportd.kde.undo | put back the keys and panels the last burst changed, then pause |
exportd.kde.restore | put back every key as it was before the first write, the scheme files (a user's own MixOS-*.colors gets its saved bytes back; files it created are removed) and the panels, forget the record, then pause |
While paused (undo or restore, kept across restarts) no revision writes;
exportd.kde.reconcile resumes.
Environment
| Variable | Default |
|---|---|
MIXOS_SETTINGS_INSTANCE | this node's name |
MIXOS_SETTINGS_SERVICE | settingsd; the read endpoint (reserved settings topics still come from settingsd) |
MIXOS_KDE_EXPORT_DRY=1 | plan and report, write nothing |
MIXOS_KDE_EXPORT_STATE | $XDG_STATE_HOME/mixos/kde-export (the undo record, saved scheme files, the pause marker) |
MIXOS_KDE_EXPORT_RECONFIGURE=0 | no D-Bus calls |
MIXOS_KDE_EXPORT_KWRITECONFIG, MIXOS_KDE_EXPORT_BUSCTL | kwriteconfig6, busctl; a JSON argv list replaces them (the tests' stand-ins) |
External programs: kwriteconfig6 (KDE Frameworks 6) and busctl.
GTK and Qt
scripts/gtkqt.mix (--name exportd-gtkqt, unit exportd-gtkqt@.service)
writes GTK CSS and settings.ini keys and the qt5ct/qt6ct colour schemes
and config on every desk but a KDE Plasma one, where the KDE export covers
GTK and Qt. Its commands are exportd.gtkqt.status, .reconcile and
.undo. The GTK and Qt page has the details.
Changelog
See the changelog.
Both desktop exporters read all values and the palette at one incarnation and revision, retrying a moving record before writing. Prefix topics are hints, never snapshot fragments. KDE broadcasts use busctl emit OBJECT INTERFACE SIGNAL (no positional service). Wallpaper RGB triples refuse non-opaque alpha; KDE hint colours are composited over each mode\u0027s titlebar.