Live-save bar settings as you edit

Every layout mutation (add/remove/reorder, settings dialog Apply,
position or centerAnchor change) now writes ~/.config/omarchy/bar.json
immediately. No more Save button — replaced with an inline 'Auto-saving
to ...' status line next to Reset to defaults.

To stop the FileView feedback loop (write → inotify → reload → set
draft → write again) the shell keeps a one-shot suppressReload flag
that swallows the userFile.onLoaded triggered by our own setText.
This commit is contained in:
Ryan Hughes
2026-05-14 02:21:47 -04:00
parent e9806b093b
commit 34c645af72
+54 -24
View File
@@ -27,11 +27,35 @@ ShellRoot {
property string fontFamily: "JetBrainsMono Nerd Font" property string fontFamily: "JetBrainsMono Nerd Font"
property var defaultConfig: ({}) // Bundled fallback so 'Reset to defaults' never produces an empty bar even
// if bar-defaults.json fails to load. Keep in rough sync with the layout
// shipped in default/quickshell/bar/bar-defaults.json.
readonly property var builtinBarConfig: ({
position: "top",
fontFamily: "JetBrainsMono Nerd Font",
centerAnchor: "calendar",
layout: {
left: [{ id: "omarchy" }, { id: "workspacesPro" }, { id: "activeWindow" }],
center: [
{ id: "media" },
{ id: "calendar", format: "dddd HH:mm", formatAlt: "dd MMMM 'W'ww yyyy", verticalFormat: "HH\n—\nmm" },
{ id: "weatherFlyout" }, { id: "update" }, { id: "voxtype" },
{ id: "screenRecording" }, { id: "idle" }, { id: "notifications" }
],
right: [
{ id: "tray" }, { id: "systemStats" }, { id: "microphone" },
{ id: "bluetoothPanel" }, { id: "networkPanel" }, { id: "audioPanel" },
{ id: "nightLight" }, { id: "brightness" }, { id: "powerProfile" },
{ id: "battery" }, { id: "controlCenter" }, { id: "powerMenu" }
]
}
})
property var defaultConfig: builtinBarConfig
property var draft: ({ position: "top", centerAnchor: "calendar", layout: { left: [], center: [], right: [] }, fontFamily: "JetBrainsMono Nerd Font" }) property var draft: ({ position: "top", centerAnchor: "calendar", layout: { left: [], center: [], right: [] }, fontFamily: "JetBrainsMono Nerd Font" })
property var registry: ({}) property var registry: ({})
property bool dirty: false
property int draftRevision: 0 property int draftRevision: 0
property bool suppressReload: false
function cloneJson(value) { function cloneJson(value) {
return JSON.parse(JSON.stringify(value || null)) return JSON.parse(JSON.stringify(value || null))
@@ -76,13 +100,17 @@ ShellRoot {
} }
function loadConfig() { function loadConfig() {
try { var defaults = builtinBarConfig
var defaults = defaultsFile.text() ? JSON.parse(defaultsFile.text()) : {} var diskText = defaultsFile.text()
defaultConfig = defaults if (diskText) {
} catch (e) { try {
console.warn("Bad defaults JSON:", e) defaults = JSON.parse(diskText)
defaultConfig = {} } catch (e) {
console.warn("Bad defaults JSON, falling back to builtin:", e)
defaults = builtinBarConfig
}
} }
defaultConfig = defaults
var userText = userFile.text() || "{}" var userText = userFile.text() || "{}"
var user = {} var user = {}
@@ -95,17 +123,14 @@ ShellRoot {
fontFamily: String(merged.fontFamily || "JetBrainsMono Nerd Font"), fontFamily: String(merged.fontFamily || "JetBrainsMono Nerd Font"),
layout: normalizeLayout(merged.layout || {}) layout: normalizeLayout(merged.layout || {})
} }
dirty = false
draftRevision++ draftRevision++
} }
function saveConfig() { function persistDraft() {
// Persist the full picture rather than a diff so what the user sees in the // Suppress the inotify callback that this write triggers so the FileView
// GUI is what lives in bar.json. This means Omarchy defaults changes won't // reload doesn't race with rapid edits and clobber them.
// propagate after a user has saved at least once, which is the right suppressReload = true
// tradeoff for a customizer: predictable layouts > silent migrations.
userFile.setText(JSON.stringify(draft, null, 2) + "\n") userFile.setText(JSON.stringify(draft, null, 2) + "\n")
dirty = false
} }
function resetToDefaults() { function resetToDefaults() {
@@ -126,12 +151,13 @@ ShellRoot {
fontFamily: String(source.fontFamily || "JetBrainsMono Nerd Font"), fontFamily: String(source.fontFamily || "JetBrainsMono Nerd Font"),
layout: normalizeLayout(source.layout || {}) layout: normalizeLayout(source.layout || {})
} }
suppressReload = true
userFile.setText(JSON.stringify(payload, null, 2) + "\n") userFile.setText(JSON.stringify(payload, null, 2) + "\n")
} }
function markDirty() { function markDirty() {
dirty = true
draftRevision++ draftRevision++
persistDraft()
} }
// Replace the whole `layout` object so any binding that reads `draft.layout` // Replace the whole `layout` object so any binding that reads `draft.layout`
@@ -289,7 +315,10 @@ ShellRoot {
atomicWrites: true atomicWrites: true
printErrors: false printErrors: false
onLoaded: { onLoaded: {
if (root.dirty) return if (root.suppressReload) {
root.suppressReload = false
return
}
root.loadConfig() root.loadConfig()
} }
onFileChanged: reload() onFileChanged: reload()
@@ -339,18 +368,19 @@ ShellRoot {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Text {
text: "Auto-saving to ~/.config/omarchy/bar.json"
color: Qt.darker(root.foreground, 1.5)
font.family: root.fontFamily
font.pixelSize: 11
anchors.verticalCenter: parent.verticalCenter
}
ActionPill { ActionPill {
text: "Reset to defaults" text: "Reset to defaults"
foreground: root.urgent foreground: root.urgent
onClicked: root.resetToDefaults() onClicked: root.resetToDefaults()
} }
ActionPill {
text: root.dirty ? "Save" : "Saved"
foreground: root.dirty ? root.accent : Qt.darker(root.foreground, 1.5)
bordered: root.dirty
onClicked: if (root.dirty) root.saveConfig()
}
} }
} }