diff --git a/bin/omarchy-launch-bar-settings b/bin/omarchy-launch-bar-settings new file mode 100755 index 00000000..ffc10c1a --- /dev/null +++ b/bin/omarchy-launch-bar-settings @@ -0,0 +1,18 @@ +#!/bin/bash + +# omarchy:summary=Launch the Omarchy bar settings panel +# omarchy:group=launch +# omarchy:name=settings +# omarchy:args=[bar] +# omarchy:examples=omarchy launch settings | omarchy launch settings bar + +case "${1:-}" in + ""|bar) + ;; + *) + echo "Usage: omarchy-launch-settings [bar]" >&2 + exit 1 + ;; +esac + +exec omarchy-shell-ipc shell summon omarchy.settings "{}" diff --git a/bin/omarchy-launch-settings b/bin/omarchy-launch-settings deleted file mode 100755 index 00887875..00000000 --- a/bin/omarchy-launch-settings +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -# omarchy:summary=Launch the Omarchy settings panel -# omarchy:group=launch -# omarchy:name=settings -# omarchy:args=[bar|plugins|defaults|style] -# omarchy:examples=omarchy launch settings | omarchy launch settings defaults | omarchy launch settings style - -category="${1:-}" -payload="{}" -case "$category" in - bar|plugins|defaults|style) - payload=$(printf '{"category":"%s"}' "$category") - ;; - "") - ;; - *) - echo "Usage: omarchy-launch-settings [bar|plugins|defaults|style]" >&2 - exit 1 - ;; -esac - -exec omarchy-shell-ipc shell summon omarchy.settings "$payload" diff --git a/bin/omarchy-style-corners-quickshell b/bin/omarchy-style-corners-quickshell index 248307a6..8023bdbc 100755 --- a/bin/omarchy-style-corners-quickshell +++ b/bin/omarchy-style-corners-quickshell @@ -1,6 +1,6 @@ #!/bin/bash -# omarchy:summary=Set or toggle corner radius for the omarchy-shell menu and settings panel +# omarchy:summary=Set or toggle corner radius for the omarchy-shell menu and bar settings panel # omarchy:args=[toggle|sharp|round] # omarchy:examples=omarchy style corners quickshell toggle | omarchy style corners quickshell round | omarchy style corners quickshell sharp diff --git a/default/hypr/apps/omarchy-shell.lua b/default/hypr/apps/omarchy-shell.lua index 76576db6..465430f0 100644 --- a/default/hypr/apps/omarchy-shell.lua +++ b/default/hypr/apps/omarchy-shell.lua @@ -1,6 +1,6 @@ -- Window and layer rules for the Omarchy Quickshell surfaces. The --- shell-wide bar / menu / popouts are layer-shell; the settings panel is --- a regular Hyprland window via Quickshell's FloatingWindow. +-- shell-wide bar / menu / popouts are layer-shell; the bar settings panel +-- is a regular Hyprland window via Quickshell's FloatingWindow. -- Keep the menu instant: no layer-shell fade/slide animation. hl.layer_rule({ match = { namespace = "omarchy-menu" }, no_anim = true, animation = "none" }) @@ -8,13 +8,13 @@ hl.layer_rule({ match = { namespace = "omarchy-menu" }, no_anim = true, animatio -- Image selector overlay should also pop without animation. hl.layer_rule({ match = { namespace = "omarchy-image-selector" }, no_anim = true, animation = "none" }) --- Settings panel floats centered with a sensible default size instead of +-- Bar settings floats centered with a sensible default size instead of -- tiling — it's a transient dialog, not a workspace surface. -hl.window_rule({ match = { class = "^org.quickshell$", title = "^Omarchy Settings$" }, float = true }) -hl.window_rule({ match = { class = "^org.quickshell$", title = "^Omarchy Settings$" }, center = true }) -hl.window_rule({ match = { class = "^org.quickshell$", title = "^Omarchy Settings$" }, size = { 960, 700 } }) +hl.window_rule({ match = { class = "^org.quickshell$", title = "^Omarchy Bar Settings$" }, float = true }) +hl.window_rule({ match = { class = "^org.quickshell$", title = "^Omarchy Bar Settings$" }, center = true }) +hl.window_rule({ match = { class = "^org.quickshell$", title = "^Omarchy Bar Settings$" }, size = { 760, 620 } }) -- Per-widget settings dialog opens as a smaller FloatingWindow off the --- settings panel; keep it floating with its own default size. +-- bar settings panel; keep it floating with its own default size. hl.window_rule({ match = { class = "^org.quickshell$", title = "^Widget settings " }, float = true }) hl.window_rule({ match = { class = "^org.quickshell$", title = "^Widget settings " }, size = { 380, 320 } }) diff --git a/default/omarchy/omarchy-menu.jsonc b/default/omarchy/omarchy-menu.jsonc index cae68e52..477f0d48 100644 --- a/default/omarchy/omarchy-menu.jsonc +++ b/default/omarchy/omarchy-menu.jsonc @@ -164,9 +164,8 @@ "setup.config.hypridle": {"icon":"","label":"Hypridle","keywords":"idle","action":"omarchy-launch-config-editor ~/.config/hypr/hypridle.conf && omarchy-restart-hypridle"}, "setup.config.hyprlock": {"icon":"","label":"Hyprlock","keywords":"lock","action":"omarchy-launch-config-editor ~/.config/hypr/hyprlock.conf"}, "setup.config.hyprsunset": {"icon":"","label":"Hyprsunset","keywords":"night light","action":"omarchy-launch-config-editor ~/.config/hypr/hyprsunset.conf && omarchy-restart-hyprsunset"}, - "setup.config.osd": {"icon":"","label":"OSD","action":"omarchy launch settings"}, "setup.config.walker": {"icon":"󰌧","label":"Walker","keywords":"launcher","action":"omarchy-launch-config-editor ~/.config/walker/config.toml && omarchy-restart-walker"}, - "setup.config.bar": {"icon":"󰍜","label":"Bar","keywords":"quickshell shell config","action":"omarchy-launch-settings bar"}, + "setup.config.bar": {"icon":"󰍜","label":"Bar","keywords":"quickshell shell config settings widgets position","action":"omarchy-launch-settings bar"}, "setup.config.xcompose": {"icon":"󰞅","label":"XCompose","keywords":"compose key","action":"omarchy-launch-config-editor ~/.XCompose && omarchy-restart-xcompose"}, // Install diff --git a/default/quickshell/omarchy-shell/plugins/README.md b/default/quickshell/omarchy-shell/plugins/README.md index 0184f91e..eb2e8515 100644 --- a/default/quickshell/omarchy-shell/plugins/README.md +++ b/default/quickshell/omarchy-shell/plugins/README.md @@ -8,12 +8,15 @@ so they cannot be disabled. User-installed plugins live alongside these conceptually but on disk under `~/.config/omarchy/plugins//` rather than in this directory. -| Plugin | id | kinds | activation | entry point | -|------------------|--------------------------|--------------|-------------|----------------------------------------------| -| Bar | `omarchy.bar` | `bar` | persistent | `bar/Bar.qml` | -| Settings | `omarchy.settings` | `panel` | on-demand | `settings/SettingsPanel.qml` | -| Image picker | `omarchy.image-picker` | `overlay` | on-demand | `image-picker/ImagePicker.qml` | -| Omarchy menu | `omarchy.menu` | `menu` | on-demand | `menu/Menu.qml` | +| Plugin | id | kinds | activation | entry point | +|---------------|-------------------------|-----------|------------|-------------------------------------| +| Bar | `omarchy.bar` | `bar` | persistent | `bar/Bar.qml` | +| Bar settings | `omarchy.settings` | `panel` | on-demand | `settings/SettingsPanel.qml` | +| Image picker | `omarchy.image-picker` | `overlay` | on-demand | `image-picker/ImagePicker.qml` | +| Omarchy menu | `omarchy.menu` | `menu` | on-demand | `menu/Menu.qml` | +| Notifications | `omarchy.notifications` | `service` | persistent | `notifications/Service.qml` | +| OSD | `omarchy.osd` | `panel` | persistent | `osd/Osd.qml` | +| Polkit agent | `omarchy.polkit` | `service` | persistent | `polkit/PolkitAgent.qml` | ## Bar @@ -26,16 +29,14 @@ and customization schema. ## Bar settings -Visual editor for the entire shell config. Summoned by +Visual editor for the bar layout. Summoned by `omarchy-shell-ipc shell summon omarchy.settings "{}"` (which is what `omarchy launch settings` ultimately calls). Provides: +- bar position and center-anchor controls - per-section add/move/remove/edit of bar widget entries -- a separate "Other plugins" section for panels, overlays, services, - and menus (entries that live in `plugins[]` rather than the bar layout) -- a Plugin Manager tab listing every discovered plugin with its manifest -- a dynamic settings form driven by each widget's manifest schema, that - writes inline back to the corresponding shell.json entry +- dynamic per-widget settings forms that write inline back to the + corresponding shell.json entry ## Image picker @@ -64,6 +65,13 @@ clears it without writing a selection. The plugin has `keepLoaded: true` so the layer-shell window survives between summons within a single shell session. +## Polkit agent + +Theme-aware authentication dialog for privileged actions. It uses +Quickshell's native `Quickshell.Services.Polkit.PolkitAgent` backend and +runs inside the long-lived `omarchy-shell` process, replacing the old +`polkit-gnome-authentication-agent-1` autostart. + ## Omarchy menu Quickshell-powered replacement for the legacy Walker-driven `omarchy-menu`. diff --git a/default/quickshell/omarchy-shell/plugins/bar/Bar.qml b/default/quickshell/omarchy-shell/plugins/bar/Bar.qml index f847638c..f19aa764 100644 --- a/default/quickshell/omarchy-shell/plugins/bar/Bar.qml +++ b/default/quickshell/omarchy-shell/plugins/bar/Bar.qml @@ -18,7 +18,7 @@ Item { // The omarchy-shell host injects omarchyPath when it instantiates this Bar. // Default fallback keeps the file loadable in isolation (e.g. for QML tooling). required property string omarchyPath - // Injected by the host shell. Shared with the settings panel so both + // Injected by the host shell. Shared with the bar settings panel so both // see the same widget catalogue. required property var barWidgetRegistry // Injected by the host shell every time shell.json is reloaded. Holds the diff --git a/default/quickshell/omarchy-shell/plugins/bar/README.md b/default/quickshell/omarchy-shell/plugins/bar/README.md index aeb41951..a082c94a 100644 --- a/default/quickshell/omarchy-shell/plugins/bar/README.md +++ b/default/quickshell/omarchy-shell/plugins/bar/README.md @@ -169,6 +169,6 @@ layout list. Third-party widgets ship as separate plugins under `~/.config/omarchy/plugins//` with their own `manifest.json` declaring `kinds: ["bar-widget"]` and a `barWidget` entry point. See -[../../README.md](../../README.md) for the manifest schema and the -Plugin Manager tab in `omarchy launch settings` for enable/disable -controls. +[../../README.md](../../README.md) for the manifest schema. Enable or +rescan third-party plugins with `omarchy-shell-ipc shell setPluginEnabled` +and `omarchy-shell-ipc shell rescanPlugins`. diff --git a/default/quickshell/omarchy-shell/plugins/notifications/Service.qml b/default/quickshell/omarchy-shell/plugins/notifications/Service.qml index 3a773d3d..eb6b9cee 100644 --- a/default/quickshell/omarchy-shell/plugins/notifications/Service.qml +++ b/default/quickshell/omarchy-shell/plugins/notifications/Service.qml @@ -34,7 +34,7 @@ Item { readonly property string imageCacheDir: cacheDir + "notification-images/" readonly property string styleStatePath: home + "/.local/state/omarchy/toggles/quickshell-menu.json" - // Corner radius is shared with omarchy-shell menu and settings panel — + // Corner radius is shared with omarchy-shell menu and bar settings panel — // `omarchy style corners ` writes this file once and every // surface reads it. Default 0 for sharp corners. property int cornerRadius: 0 diff --git a/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml b/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml index e8a3fe5f..7668a4ca 100644 --- a/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml +++ b/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml @@ -16,22 +16,8 @@ Item { function open(payloadJson) { closingFromHost = false - var payload = ({}) - try { payload = JSON.parse(payloadJson || "{}") } catch (e) { payload = ({}) } - - if (payload && typeof payload.category === "string" && root.categoryIds.indexOf(payload.category) !== -1) { - root.activeCategory = payload.category - } else if (payload && (payload.focusWidgetId || payload.section || payload.focusPluginId)) { - // Noctalia-compat callers ask us to focus a specific widget/plugin — - // route them to the relevant tab. Widgets live under Bar; otherwise - // open the Plugins tab. - root.activeCategory = payload.focusPluginId ? "plugins" : "bar" - } - - root.syncSidebarIndexFromCategory() - root.focusZone = "sidebar" window.visible = true - Qt.callLater(parkFocusOnSink) + Qt.callLater(focusFirstBodyItem) } function close() { @@ -60,8 +46,8 @@ Item { readonly property string styleStatePath: home + "/.local/state/omarchy/toggles/quickshell-menu.json" // ---------------- theme -------------------------------------------------- - // Settings panel deliberately isn't a themable surface in shell.toml — - // it tracks the foundational palette so every theme renders consistently. + // Bar settings deliberately isn't a themable surface in shell.toml — it + // tracks the foundational palette so every theme renders consistently. property color foreground: Color.foreground property color background: Color.background property color accent: Color.accent @@ -73,15 +59,9 @@ Item { // flips both surfaces together. property int cornerRadius: 0 - // ---------------- navigation --------------------------------------------- - readonly property var categoryIds: ["defaults", "style", "bar", "system", "plugins"] - property string activeCategory: "defaults" - - // Keyboard nav state. Two zones: "sidebar" (j/k cycles category) and - // "body" (j/k walks through visible focusable controls). Tab/Enter/l/Right - // dive from sidebar into body; h/Left/Esc backs out. - property string focusZone: "sidebar" - property int sidebarIndex: 0 + // ---------------- keyboard focus ----------------------------------------- + // Keyboard navigation walks the bar form directly; this panel no longer has + // side navigation because it only edits the bar. // Focus visuals — deliberately *different* from selected styling so the // keyboard cursor never gets confused with the current value/choice. A @@ -91,29 +71,6 @@ Item { readonly property color focusFillColor: Qt.rgba(accent.r, accent.g, accent.b, 0.22) readonly property int focusBorderWidth: 3 - function syncSidebarIndexFromCategory() { - var idx = categoryIds.indexOf(activeCategory) - if (idx >= 0) sidebarIndex = idx - } - - function setSidebarIndex(i) { - if (categoryIds.length === 0) return - if (i < 0) i = categoryIds.length - 1 - if (i >= categoryIds.length) i = 0 - sidebarIndex = i - activeCategory = categoryIds[i] - } - - function enterBodyZone() { - focusZone = "body" - Qt.callLater(focusFirstBodyItem) - } - - function exitBodyZone() { - focusZone = "sidebar" - parkFocusOnSink() - } - // Move activeFocus to a dedicated sink Item that lives outside the body // tree. Just clearing focus on the previously focused descendant isn't // enough — controls like ComboBox keep an internal focused child that @@ -125,8 +82,7 @@ Item { } // Walk the visible body subtree and collect any item with - // `activeFocusOnTab: true`. Filters out invisible categories so j/k stays - // within the active tab. + // `activeFocusOnTab: true` so j/k can move through the form. function gatherBodyFocusables() { var arr = [] function walk(item) { @@ -153,7 +109,7 @@ Item { function focusBodyDelta(delta) { var items = gatherBodyFocusables() - if (items.length === 0) { exitBodyZone(); return } + if (items.length === 0) { parkFocusOnSink(); return } var current = -1 for (var i = 0; i < items.length; i++) { if (items[i].activeFocus) { current = i; break } @@ -181,12 +137,6 @@ Item { } } - onActiveCategoryChanged: { - syncSidebarIndexFromCategory() - if (focusZone === "body") Qt.callLater(focusFirstBodyItem) - else parkFocusOnSink() - } - // ---------------- bundled defaults --------------------------------------- readonly property var builtinShellConfig: ({ version: 1, @@ -298,7 +248,7 @@ Item { userFile.setText(JSON.stringify(draft, null, 2) + "\n") } - function resetToDefaults() { + function defaultBarDraft() { var source = defaultConfig if (!isPlainObject(source) || !isPlainObject(source.bar) || !isPlainObject(source.bar.layout)) { source = builtinShellConfig @@ -307,11 +257,15 @@ Item { var anyEntries = (l.left && l.left.length) || (l.center && l.center.length) || (l.right && l.right.length) if (!anyEntries) source = builtinShellConfig } - var payload = normalizeDraft(source) - draft = payload + return normalizeDraft(source).bar + } + + function resetBarToDefaults() { + var next = cloneJson(draft) + next.bar = cloneJson(defaultBarDraft()) + draft = next draftRevision++ - suppressReload = true - userFile.setText(JSON.stringify(payload, null, 2) + "\n") + persistDraft() } function markDirty() { @@ -389,7 +343,7 @@ Item { onBarWidgetRegistryChanged: { catalogRevision++ if (!root.barWidgetRegistry) return - console.log("settings panel open. omarchyPath=" + root.omarchyPath, + console.log("bar settings panel open. omarchyPath=" + root.omarchyPath, "defaultsPath=" + root.defaultsPath, "userConfigPath=" + root.userConfigPath, "registry has", @@ -484,7 +438,6 @@ Item { function availableToAdd(section) { var rev = catalogRevision - var isBarSection = section === "left" || section === "center" || section === "right" var barSections = ["left", "center", "right"] var existingInBar = {} @@ -492,9 +445,6 @@ Item { var list = sectionArray(barSections[s]) for (var i = 0; i < list.length; i++) existingInBar[list[i].id] = true } - var existingInPlugins = {} - var pluginList = sectionArray("plugins") - for (var p = 0; p < pluginList.length; p++) existingInPlugins[pluginList[p].id] = true var ids = catalogIds().sort(function(a, b) { return widgetName(a).localeCompare(widgetName(b)) }) @@ -505,23 +455,16 @@ Item { var manifest = root.pluginRegistry ? root.pluginRegistry.installedPlugins[id] : null var manifestIsBarWidget = manifest && Array.isArray(manifest.kinds) && manifest.kinds.indexOf("bar-widget") !== -1 var isBarWidget = !!(meta && meta.source !== "plugin") || manifestIsBarWidget - if (isBarSection) { - if (!isBarWidget && !legacyWidgetMeta[id]) continue - var inSection = sectionArray(section) - var existsHere = false - for (var x = 0; x < inSection.length; x++) if (inSection[x].id === id) { existsHere = true; break } - var allowsMultiple = widgetAllowsMultiple(id) - if (!allowsMultiple && existingInBar[id]) continue - result.push({ id: id, name: widgetName(id), description: widgetDescription(id), - elsewhere: allowsMultiple && !!existingInBar[id] && !existsHere, - isNoctalia: widgetIsNoctaliaPlugin(id) }) - } else { - if (!manifest) continue - if (manifest.__isFirstParty) continue - if (existingInPlugins[id]) continue - result.push({ id: id, name: widgetName(id), description: widgetDescription(id), elsewhere: false, - isNoctalia: widgetIsNoctaliaPlugin(id) }) - } + if (!isBarWidget && !legacyWidgetMeta[id]) continue + + var inSection = sectionArray(section) + var existsHere = false + for (var x = 0; x < inSection.length; x++) if (inSection[x].id === id) { existsHere = true; break } + var allowsMultiple = widgetAllowsMultiple(id) + if (!allowsMultiple && existingInBar[id]) continue + result.push({ id: id, name: widgetName(id), description: widgetDescription(id), + elsewhere: allowsMultiple && !!existingInBar[id] && !existsHere, + isNoctalia: widgetIsNoctaliaPlugin(id) }) } return result } @@ -561,16 +504,16 @@ Item { // ---------------- window ------------------------------------------------- FloatingWindow { id: window - title: "Omarchy Settings" + title: "Omarchy Bar Settings" color: root.background - implicitWidth: 880 + implicitWidth: 760 implicitHeight: 620 - minimumSize: Qt.size(700, 480) + minimumSize: Qt.size(620, 480) onVisibleChanged: { if (!visible && !root.closingFromHost && root.shell && typeof root.shell.hide === "function") root.shell.hide("omarchy.settings") - if (visible) Qt.callLater(root.parkFocusOnSink) + if (visible) Qt.callLater(root.focusFirstBodyItem) } FocusScope { @@ -580,9 +523,9 @@ Item { Component.onCompleted: navFocusSink.forceActiveFocus() - // Invisible focus sink. When focus belongs to the sidebar (no - // specific body item focused), activeFocus lives on this 1px Item so - // body controls render their unfocused state cleanly. + // Invisible focus sink. When no specific body item is focused, + // activeFocus lives on this 1px Item so body controls render their + // unfocused state cleanly. Item { id: navFocusSink width: 1 @@ -592,43 +535,17 @@ Item { Keys.priority: Keys.BeforeItem Keys.onPressed: function(event) { - var ctrl = (event.modifiers & Qt.ControlModifier) !== 0 - - if (root.focusZone === "sidebar") { - switch (event.key) { - case Qt.Key_J: - case Qt.Key_Down: - root.setSidebarIndex(root.sidebarIndex + 1); event.accepted = true; return - case Qt.Key_K: - case Qt.Key_Up: - root.setSidebarIndex(root.sidebarIndex - 1); event.accepted = true; return - case Qt.Key_L: - case Qt.Key_Right: - case Qt.Key_Tab: - case Qt.Key_Return: - case Qt.Key_Enter: - root.enterBodyZone(); event.accepted = true; return - case Qt.Key_Escape: - root.close(); event.accepted = true; return - } - if (ctrl && event.key === Qt.Key_L) { root.enterBodyZone(); event.accepted = true; return } - } else { - // body - switch (event.key) { - case Qt.Key_Escape: - case Qt.Key_H: - case Qt.Key_Left: - case Qt.Key_Backtab: - root.exitBodyZone(); event.accepted = true; return - case Qt.Key_J: - case Qt.Key_Down: - case Qt.Key_Tab: - root.focusBodyDelta(+1); event.accepted = true; return - case Qt.Key_K: - case Qt.Key_Up: - root.focusBodyDelta(-1); event.accepted = true; return - } - if (ctrl && event.key === Qt.Key_H) { root.exitBodyZone(); event.accepted = true; return } + switch (event.key) { + case Qt.Key_Escape: + root.close(); event.accepted = true; return + case Qt.Key_J: + case Qt.Key_Down: + case Qt.Key_Tab: + root.focusBodyDelta(+1); event.accepted = true; return + case Qt.Key_K: + case Qt.Key_Up: + case Qt.Key_Backtab: + root.focusBodyDelta(-1); event.accepted = true; return } } @@ -647,7 +564,7 @@ Item { Layout.preferredHeight: 48 Text { - text: "Omarchy Settings" + text: "Omarchy Bar Settings" color: root.foreground font.family: root.fontFamily font.pixelSize: 16 @@ -674,70 +591,24 @@ Item { color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18) } - // Sidebar + content - RowLayout { + // Content + Flickable { + id: bodyScroll Layout.fillWidth: true Layout.fillHeight: true - spacing: 0 + Layout.margins: 18 + clip: true + contentWidth: width + contentHeight: contentColumn.implicitHeight + boundsBehavior: Flickable.StopAtBounds + flickableDirection: Flickable.VerticalFlick - // Sidebar - Item { - Layout.preferredWidth: 180 - Layout.fillHeight: true + ColumnLayout { + id: contentColumn + width: bodyScroll.width + spacing: 14 - Rectangle { - anchors.fill: parent - color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.03) - } - - ColumnLayout { - anchors.fill: parent - anchors.margins: 8 - spacing: 2 - - SidebarRow { categoryId: "defaults"; label: "Defaults"; glyph: "󰀻" } - SidebarRow { categoryId: "style"; label: "Style"; glyph: "󰏘" } - SidebarRow { categoryId: "bar"; label: "Bar"; glyph: "󰛼" } - SidebarRow { categoryId: "system"; label: "System"; glyph: "󰒓" } - SidebarRow { categoryId: "plugins"; label: "Plugins"; glyph: "󰐱" } - - Item { Layout.fillHeight: true } - } - } - - Rectangle { - Layout.fillHeight: true - Layout.preferredWidth: 1 - color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18) - } - - // Content - Item { - Layout.fillWidth: true - Layout.fillHeight: true - - Flickable { - id: bodyScroll - anchors.fill: parent - anchors.margins: 18 - clip: true - contentWidth: width - contentHeight: contentColumn.implicitHeight - boundsBehavior: Flickable.StopAtBounds - flickableDirection: Flickable.VerticalFlick - - ColumnLayout { - id: contentColumn - width: bodyScroll.width - spacing: 14 - - BarCategory { visible: root.activeCategory === "bar" ; Layout.fillWidth: true } - PluginManager { visible: root.activeCategory === "plugins" ; Layout.fillWidth: true } - DefaultsCategory{ visible: root.activeCategory === "defaults" ; Layout.fillWidth: true } - StyleCategory { visible: root.activeCategory === "style" ; Layout.fillWidth: true } - SystemCategory { visible: root.activeCategory === "system" ; Layout.fillWidth: true } - } - } + BarCategory { Layout.fillWidth: true } } } } @@ -745,66 +616,6 @@ Item { } } - // ===================== sidebar row ======================================= - component SidebarRow: Rectangle { - id: sb - property string categoryId: "" - property string label: "" - property string glyph: "" - readonly property bool active: root.activeCategory === categoryId - readonly property bool sidebarFocused: root.focusZone === "sidebar" - - Layout.fillWidth: true - Layout.preferredHeight: 30 - radius: root.cornerRadius - color: sb.active - ? (sb.sidebarFocused - ? Qt.rgba(root.accent.r, root.accent.g, root.accent.b, 0.20) - : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.14)) - : (sbArea.containsMouse ? Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.07) : "transparent") - border.color: sb.active - ? (sb.sidebarFocused ? root.accent : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.25)) - : "transparent" - border.width: sb.active && sb.sidebarFocused ? 2 : 1 - - Behavior on color { ColorAnimation { duration: 100 } } - - Row { - anchors.left: parent.left - anchors.leftMargin: 10 - anchors.verticalCenter: parent.verticalCenter - spacing: 10 - - Text { - text: sb.glyph - color: sb.active ? root.accent : Qt.darker(root.foreground, 1.4) - font.family: root.fontFamily - font.pixelSize: 13 - anchors.verticalCenter: parent.verticalCenter - } - Text { - text: sb.label - color: sb.active ? root.foreground : Qt.darker(root.foreground, 1.2) - font.family: root.fontFamily - font.pixelSize: 12 - font.bold: sb.active - anchors.verticalCenter: parent.verticalCenter - } - } - - MouseArea { - id: sbArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - root.activeCategory = sb.categoryId - root.focusZone = "sidebar" - if (navRoot) navRoot.forceActiveFocus() - } - } - } - // ===================== bar category ====================================== component BarCategory: ColumnLayout { spacing: 14 @@ -883,1000 +694,13 @@ Item { Row { Layout.alignment: Qt.AlignRight ActionPill { - text: "Reset to defaults" + text: "Reset bar to defaults" foreground: root.urgent - onClicked: root.resetToDefaults() + onClicked: root.resetBarToDefaults() } } } - // ===================== defaults category ================================= - component DefaultsCategory: ColumnLayout { - spacing: 14 - - property string terminalCurrent: "" - property string browserCurrent: "" - property string editorCurrent: "" - property int refreshTick: 0 - - function refresh() { - refreshTick++ - readTerminalProc.running = true - readBrowserProc.running = true - readEditorProc.running = true - } - - Process { - id: readTerminalProc - command: ["omarchy-default-terminal"] - stdout: SplitParser { onRead: function(line) { terminalCurrent = String(line).trim() } } - } - Process { - id: readBrowserProc - command: ["omarchy-default-browser"] - stdout: SplitParser { onRead: function(line) { browserCurrent = String(line).trim() } } - } - Process { - id: readEditorProc - command: ["omarchy-default-editor"] - stdout: SplitParser { onRead: function(line) { editorCurrent = String(line).trim() } } - } - // Refresh after the write has actually finished — kicking off a read - // synchronously after .running = true races the bash apply and ends up - // displaying the *previous* default. - Process { - id: applyDefaultsProc - onExited: refresh() - } - - function applyDefault(group, value) { - var cmd = "" - if (group === "terminal") cmd = "omarchy-default-terminal " + value - else if (group === "browser") cmd = "omarchy-default-browser " + value - else if (group === "editor") cmd = "omarchy-default-editor " + value - if (!cmd) return - applyDefaultsProc.command = ["bash", "-lc", cmd] - applyDefaultsProc.running = true - } - - Component.onCompleted: refresh() - onVisibleChanged: if (visible) refresh() - - Text { - text: "Defaults" - color: root.foreground - font.family: root.fontFamily - font.pixelSize: 18 - font.bold: true - } - - Text { - text: "Pick the terminal, browser, and editor Omarchy hands off to when launching apps." - color: Qt.darker(root.foreground, 1.6) - font.family: root.fontFamily - font.pixelSize: 11 - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - - DefaultsGroup { - title: "Terminal" - description: "Used by Super+Return and xdg-terminal-exec." - options: [ - { id: "alacritty", label: "Alacritty", cmd: "alacritty" }, - { id: "foot", label: "Foot", cmd: "foot" }, - { id: "ghostty", label: "Ghostty", cmd: "ghostty" }, - { id: "kitty", label: "Kitty", cmd: "kitty" } - ] - currentId: terminalCurrent - onPicked: function(id) { applyDefault("terminal", id) } - } - - DefaultsGroup { - title: "Browser" - description: "Used for x-scheme-handler/http and HTML files." - options: [ - { id: "chromium", label: "Chromium", cmd: "chromium" }, - { id: "chrome", label: "Chrome", cmd: "google-chrome-stable" }, - { id: "brave", label: "Brave", cmd: "brave" }, - { id: "brave-origin", label: "Brave Origin", cmd: "brave-origin-beta" }, - { id: "edge", label: "Edge", cmd: "microsoft-edge-stable" }, - { id: "firefox", label: "Firefox", cmd: "firefox" }, - { id: "zen", label: "Zen", cmd: "zen-browser" } - ] - currentId: browserCurrent - onPicked: function(id) { applyDefault("browser", id) } - } - - DefaultsGroup { - title: "Editor" - description: "Sets $EDITOR. Takes effect after the next login." - options: [ - { id: "nvim", label: "Neovim", cmd: "nvim" }, - { id: "code", label: "VSCode", cmd: "code" }, - { id: "cursor", label: "Cursor", cmd: "cursor" }, - { id: "zeditor", label: "Zed", cmd: "zeditor" }, - { id: "sublime_text", label: "Sublime Text", cmd: "sublime_text" }, - { id: "helix", label: "Helix", cmd: "helix" }, - { id: "vim", label: "Vim", cmd: "vim" }, - { id: "emacs", label: "Emacs", cmd: "emacs" } - ] - currentId: editorCurrent - onPicked: function(id) { applyDefault("editor", id) } - } - } - - component DefaultsGroup: Column { - id: dg - property string title: "" - property string description: "" - property var options: [] - property string currentId: "" - signal picked(string id) - - Layout.fillWidth: true - Layout.topMargin: 8 - spacing: 6 - - Row { - width: dg.width - spacing: 8 - Text { - text: dg.title - color: root.foreground - font.family: root.fontFamily - font.pixelSize: 13 - font.bold: true - anchors.verticalCenter: parent.verticalCenter - } - Text { - text: "· " + dg.description - color: Qt.darker(root.foreground, 1.5) - font.family: root.fontFamily - font.pixelSize: 10 - anchors.verticalCenter: parent.verticalCenter - } - } - - Column { - width: dg.width - spacing: 4 - - Repeater { - model: dg.options - delegate: DefaultsRow { - required property var modelData - width: dg.width - optionId: modelData.id - optionLabel: modelData.label - checkCmd: modelData.cmd - selected: dg.currentId === modelData.id - onClicked: dg.picked(optionId) - } - } - } - } - - component DefaultsRow: Rectangle { - id: dr - property string optionId: "" - property string optionLabel: "" - property string checkCmd: "" - property bool selected: false - property bool available: false - signal clicked() - - activeFocusOnTab: dr.available - Keys.onReturnPressed: if (dr.available && !dr.selected) dr.clicked() - Keys.onEnterPressed: if (dr.available && !dr.selected) dr.clicked() - Keys.onSpacePressed: if (dr.available && !dr.selected) dr.clicked() - - implicitHeight: 38 - radius: root.cornerRadius - color: dr.activeFocus - ? root.focusFillColor - : (drArea.containsMouse - ? Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.08) - : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.03)) - border.color: dr.activeFocus - ? root.focusBorderColor - : (dr.selected ? root.accent : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12)) - border.width: dr.activeFocus ? root.focusBorderWidth : 1 - opacity: dr.available ? 1 : 0.45 - - Behavior on color { ColorAnimation { duration: 100 } } - - // Probe availability via `command -v`. Cached for the lifetime of the row. - Process { - id: probeProc - command: ["bash", "-lc", "command -v " + dr.checkCmd + " >/dev/null && echo yes || echo no"] - stdout: SplitParser { onRead: function(line) { dr.available = String(line).trim() === "yes" } } - Component.onCompleted: running = true - } - - Row { - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.right: trailRow.left - anchors.rightMargin: 8 - anchors.verticalCenter: parent.verticalCenter - spacing: 10 - - Text { - text: dr.selected ? "●" : "○" - color: dr.selected ? root.accent : Qt.darker(root.foreground, 1.3) - font.family: root.fontFamily - font.pixelSize: 12 - anchors.verticalCenter: parent.verticalCenter - } - Text { - text: dr.optionLabel - color: dr.selected ? root.foreground : Qt.darker(root.foreground, 1.05) - font.family: root.fontFamily - font.pixelSize: 12 - font.bold: dr.selected - anchors.verticalCenter: parent.verticalCenter - } - Text { - text: dr.checkCmd - color: Qt.darker(root.foreground, 1.7) - font.family: root.fontFamily - font.pixelSize: 10 - anchors.verticalCenter: parent.verticalCenter - } - } - - Row { - id: trailRow - anchors.right: parent.right - anchors.rightMargin: 10 - anchors.verticalCenter: parent.verticalCenter - spacing: 6 - - Text { - visible: !dr.available - text: "not installed" - color: Qt.darker(root.foreground, 1.7) - font.family: root.fontFamily - font.pixelSize: 10 - } - Text { - visible: dr.selected - text: "default" - color: root.accent - font.family: root.fontFamily - font.pixelSize: 10 - } - } - - MouseArea { - id: drArea - anchors.fill: parent - hoverEnabled: true - cursorShape: dr.available ? Qt.PointingHandCursor : Qt.ForbiddenCursor - onClicked: if (dr.available && !dr.selected) dr.clicked() - } - } - - // ===================== style category ==================================== - component StyleCategory: ColumnLayout { - spacing: 14 - - property string currentCorners: root.cornerRadius > 0 ? "round" : "sharp" - property bool barOn: true - property bool gapsOn: true - property bool oneWinSquare: false - property string monitorName: "" - property string monitorScale: "" - property string fontName: "" - property var fontsList: [] - property int refreshTick: 0 - - function refresh() { - refreshTick++ - readBarProc.running = true - readGapsProc.running = true - readOneWinSqProc.running = true - readMonitorProc.running = true - readFontProc.running = true - readFontsListProc.running = true - } - - Process { id: applyStyleProc; onExited: refresh() } - - Process { - id: readFontProc - command: ["omarchy-font-current"] - stdout: SplitParser { onRead: function(line) { fontName = String(line).trim() } } - } - Process { - id: readFontsListProc - command: ["omarchy-font-list"] - stdout: StdioCollector { - waitForEnd: true - onStreamFinished: fontsList = String(text || "").trim().split("\n").filter(function(x) { return x.length > 0 }) - } - } - - // Pick up theme/background changes that happen via the menu / CLI without - // going through the Style category's own buttons. - FileView { - path: root.home + "/.config/omarchy/current/theme.name" - watchChanges: true - printErrors: false - onFileChanged: refresh() - onLoaded: refresh() - } - FileView { - path: root.home + "/.config/alacritty/alacritty.toml" - watchChanges: true - printErrors: false - onFileChanged: refresh() - } - - // Re-read when any toggle flag changes on disk — covers CLI/menu paths - // that mutate `~/.local/state/omarchy/toggles/*` without going through us. - FileView { - path: root.home + "/.local/state/omarchy/toggles" - watchChanges: true - printErrors: false - onFileChanged: refresh() - } - FileView { - path: root.home + "/.local/state/omarchy/toggles/hypr" - watchChanges: true - printErrors: false - onFileChanged: refresh() - } - - Process { - id: readBarProc - command: ["bash", "-lc", "[[ -f $HOME/.local/state/omarchy/toggles/bar-off ]] && echo no || echo yes"] - stdout: SplitParser { onRead: function(line) { barOn = String(line).trim() === "yes" } } - } - Process { - id: readGapsProc - command: ["bash", "-lc", "[[ -f $HOME/.local/state/omarchy/toggles/hypr/window-no-gaps.lua ]] && echo no || echo yes"] - stdout: SplitParser { onRead: function(line) { gapsOn = String(line).trim() === "yes" } } - } - Process { - id: readOneWinSqProc - command: ["bash", "-lc", "[[ -f $HOME/.local/state/omarchy/toggles/hypr/single-window-aspect-ratio.lua ]] && echo yes || echo no"] - stdout: SplitParser { onRead: function(line) { oneWinSquare = String(line).trim() === "yes" } } - } - // Snaps the scale Hyprland reports (which may be fractional / drifted) to - // the nearest value in the canonical list. Used by the scale picker to - // know which chip to highlight. - function snapScale(raw) { - var n = parseFloat(raw) - if (!isFinite(n)) return "" - var scales = ["1", "1.25", "1.6", "2", "3", "4"] - var best = scales[0], bestDiff = Infinity - for (var i = 0; i < scales.length; i++) { - var d = Math.abs(n - parseFloat(scales[i])) - if (d < bestDiff) { bestDiff = d; best = scales[i] } - } - return best - } - - Process { - id: readMonitorProc - command: ["bash", "-lc", "hyprctl monitors -j 2>/dev/null | jq -r '.[] | select(.focused == true) | \"\\(.name)\\t\\(.scale)\"'"] - stdout: SplitParser { onRead: function(line) { - var parts = String(line).trim().split("\t") - if (parts.length >= 2) { - monitorName = parts[0] - monitorScale = snapScale(parts[1]) - } - } } - } - - function runStyle(cmd) { - applyStyleProc.command = ["bash", "-lc", cmd] - applyStyleProc.running = true - } - - Component.onCompleted: refresh() - onVisibleChanged: if (visible) refresh() - - Text { - text: "Style" - color: root.foreground - font.family: root.fontFamily - font.pixelSize: 18 - font.bold: true - } - - Text { - text: "Look-and-feel of the shell, lock screen, and windows." - color: Qt.darker(root.foreground, 1.6) - font.family: root.fontFamily - font.pixelSize: 11 - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - - - StyleToggleRow { - label: "Window gaps" - description: "Tile windows with the default gap between them." - isOn: gapsOn - onToggle: runStyle("omarchy-hyprland-window-gaps-toggle") - } - - StyleToggleRow { - label: "1-window square ratio" - description: "Constrain a solo tiled window to a square aspect." - isOn: oneWinSquare - onToggle: runStyle("omarchy-hyprland-window-single-square-aspect-toggle") - } - - StyleToggleRow { - label: "Bar" - description: "Show the omarchy bar. The shell keeps running either way, so menus and this panel stay reachable." - isOn: barOn - onToggle: runStyle("omarchy-toggle-bar") - } - - // Corner style — a real picker since this changes the whole shell's look. - ColumnLayout { - Layout.fillWidth: true - Layout.topMargin: 8 - spacing: 6 - - Row { - spacing: 8 - Text { - text: "Corners" - color: root.foreground - font.family: root.fontFamily - font.pixelSize: 13 - font.bold: true - anchors.verticalCenter: parent.verticalCenter - } - Text { - text: "· Sharp matches the retro TUI look; round softens windows, menus, and notifications." - color: Qt.darker(root.foreground, 1.5) - font.family: root.fontFamily - font.pixelSize: 10 - anchors.verticalCenter: parent.verticalCenter - } - } - - Row { - spacing: 8 - - StyleOptionTile { - tileLabel: "Sharp" - tileHint: "0px radius" - selected: currentCorners === "sharp" - onClicked: runStyle("omarchy-style-corners sharp") - } - StyleOptionTile { - tileLabel: "Round" - tileHint: "6px radius" - selected: currentCorners === "round" - onClicked: runStyle("omarchy-style-corners round") - } - } - } - - // Monitor scaling — explicit picker. The Super+Plus/Minus shortcuts still - // call `omarchy-hyprland-monitor-scaling-cycle` for keyboard cycling. - ColumnLayout { - Layout.fillWidth: true - Layout.topMargin: 8 - spacing: 6 - - Row { - spacing: 8 - Text { - text: "Monitor scaling" - color: root.foreground - font.family: root.fontFamily - font.pixelSize: 13 - font.bold: true - anchors.verticalCenter: parent.verticalCenter - } - Text { - text: "· Pick a scale for " + (monitorName || "the focused monitor") + ". Keyboard shortcut still cycles." - color: Qt.darker(root.foreground, 1.5) - font.family: root.fontFamily - font.pixelSize: 10 - anchors.verticalCenter: parent.verticalCenter - } - } - - Row { - spacing: 8 - - Repeater { - model: ["1", "1.25", "1.6", "2", "3", "4"] - delegate: StyleChip { - required property var modelData - label: modelData + "×" - selected: monitorScale === modelData - onClicked: runStyle("omarchy-hyprland-monitor-scaling-set " + modelData) - } - } - } - } - - // Font picker — each row rendered in its own typeface. Sits at the - // bottom because it's long; everything else above is one-line tall. - ColumnLayout { - Layout.fillWidth: true - Layout.topMargin: 8 - spacing: 6 - - Row { - spacing: 8 - Text { - text: "Font" - color: root.foreground - font.family: root.fontFamily - font.pixelSize: 13 - font.bold: true - anchors.verticalCenter: parent.verticalCenter - } - Text { - text: "· " + (fontName || "—") - color: Qt.darker(root.foreground, 1.5) - font.family: root.fontFamily - font.pixelSize: 10 - anchors.verticalCenter: parent.verticalCenter - } - } - - ColumnLayout { - Layout.fillWidth: true - spacing: 4 - - Repeater { - model: fontsList - delegate: FontRow { - required property var modelData - Layout.fillWidth: true - fontFamilyName: modelData - selected: modelData === fontName - onPicked: runStyle("omarchy-font-set \"" + modelData + "\"") - } - } - } - } - } - - // ===================== system category =================================== - component SystemCategory: ColumnLayout { - spacing: 14 - - property string powerProfile: "" - property var powerProfiles: [] - property bool nightlightOn: false - property bool dndOn: false - property bool idleOn: false - property bool screensaverOn: false - property bool suspendAvailable: true - property int refreshTick: 0 - - function refresh() { - refreshTick++ - readPowerProc.running = true - readPowerListProc.running = true - readNightlightProc.running = true - readDndProc.running = true - readIdleProc.running = true - readScreensaverProc.running = true - readSuspendProc.running = true - } - - Process { id: applySystemProc; onExited: refresh() } - - Process { - id: readPowerProc - command: ["bash", "-lc", "powerprofilesctl get 2>/dev/null"] - stdout: SplitParser { onRead: function(line) { powerProfile = String(line).trim() } } - } - Process { - id: readPowerListProc - command: ["bash", "-lc", "omarchy-powerprofiles-list 2>/dev/null"] - stdout: StdioCollector { - waitForEnd: true - onStreamFinished: powerProfiles = String(text || "").trim().split("\n").filter(function(x) { return x.length > 0 }) - } - } - Process { - id: readNightlightProc - command: ["bash", "-lc", "hyprctl hyprsunset temperature 2>/dev/null | grep -oE '[0-9]+' | head -n1 || echo 6000"] - stdout: SplitParser { onRead: function(line) { - var n = parseInt(String(line).trim(), 10) - nightlightOn = isFinite(n) && n < 5500 - } } - } - Process { - id: readDndProc - command: ["bash", "-lc", "omarchy-shell-ipc notifications isDnd 2>/dev/null || echo off"] - stdout: SplitParser { onRead: function(line) { dndOn = String(line).trim() === "on" } } - } - Process { - id: readIdleProc - command: ["bash", "-lc", "pgrep -x hypridle >/dev/null && echo yes || echo no"] - stdout: SplitParser { onRead: function(line) { idleOn = String(line).trim() === "yes" } } - } - Process { - id: readScreensaverProc - command: ["bash", "-lc", "[[ -f $HOME/.local/state/omarchy/toggles/screensaver-off ]] && echo no || echo yes"] - stdout: SplitParser { onRead: function(line) { screensaverOn = String(line).trim() === "yes" } } - } - Process { - id: readSuspendProc - command: ["bash", "-lc", "[[ -f $HOME/.local/state/omarchy/toggles/suspend-off ]] && echo no || echo yes"] - stdout: SplitParser { onRead: function(line) { suspendAvailable = String(line).trim() === "yes" } } - } - - function runSystem(cmd) { - applySystemProc.command = ["bash", "-lc", cmd] - applySystemProc.running = true - } - - // Catch external state changes (CLI / menu / power-profile daemon). - FileView { - path: root.home + "/.local/state/omarchy/toggles" - watchChanges: true - printErrors: false - onFileChanged: refresh() - } - - Component.onCompleted: refresh() - onVisibleChanged: if (visible) refresh() - - Text { - text: "System" - color: root.foreground - font.family: root.fontFamily - font.pixelSize: 18 - font.bold: true - } - - Text { - text: "System-level behavior — power, notifications, idle, screensaver, suspend." - color: Qt.darker(root.foreground, 1.6) - font.family: root.fontFamily - font.pixelSize: 11 - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - - ColumnLayout { - Layout.fillWidth: true - Layout.topMargin: 8 - spacing: 6 - - Row { - spacing: 8 - Text { - text: "Power profile" - color: root.foreground - font.family: root.fontFamily - font.pixelSize: 13 - font.bold: true - anchors.verticalCenter: parent.verticalCenter - } - Text { - text: "· Pick how aggressively the CPU clocks down. Reads via power-profiles-daemon." - color: Qt.darker(root.foreground, 1.5) - font.family: root.fontFamily - font.pixelSize: 10 - anchors.verticalCenter: parent.verticalCenter - } - } - - Row { - spacing: 8 - - Repeater { - model: powerProfiles - delegate: StyleChip { - required property var modelData - label: modelData.charAt(0).toUpperCase() + modelData.slice(1).replace("-", " ") - selected: powerProfile === modelData - onClicked: runSystem("powerprofilesctl set " + modelData) - } - } - } - } - - StyleToggleRow { - label: "Nightlight" - description: "Lower screen colour temperature in the evening." - isOn: nightlightOn - onToggle: runSystem("omarchy-toggle-nightlight") - } - - StyleToggleRow { - label: "Notifications" - description: dndOn ? "Do-not-disturb is on — notifications are silenced." : "Notifications post normally." - isOn: !dndOn - onToggle: runSystem("omarchy-toggle-notification-silencing") - } - - StyleToggleRow { - label: "Idle locking" - description: "Lock the screen when idle (hypridle)." - isOn: idleOn - onToggle: runSystem("omarchy-toggle-idle") - } - - StyleToggleRow { - label: "Screensaver" - description: "Allow the screensaver to engage during idle." - isOn: screensaverOn - onToggle: runSystem("omarchy-toggle-screensaver") - } - - StyleToggleRow { - label: "Suspend in system menu" - description: "Show 'Suspend' in the system power menu." - isOn: suspendAvailable - onToggle: runSystem("omarchy-toggle-suspend") - } - } - - component StyleOptionTile: Rectangle { - id: tile - property string tileLabel: "" - property string tileHint: "" - property bool selected: false - signal clicked() - - activeFocusOnTab: true - Keys.onReturnPressed: tile.clicked() - Keys.onEnterPressed: tile.clicked() - Keys.onSpacePressed: tile.clicked() - - implicitWidth: 140 - implicitHeight: 52 - radius: root.cornerRadius - color: tile.activeFocus - ? root.focusFillColor - : (tileArea.containsMouse - ? Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) - : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.03)) - border.color: tile.activeFocus - ? root.focusBorderColor - : (tile.selected ? root.accent : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18)) - border.width: tile.activeFocus ? root.focusBorderWidth : (tile.selected ? 2 : 1) - - Behavior on color { ColorAnimation { duration: 100 } } - - Text { - id: tileTitle - text: tile.tileLabel - color: tile.selected ? root.foreground : Qt.darker(root.foreground, 1.1) - font.family: root.fontFamily - font.pixelSize: 13 - font.bold: tile.selected - horizontalAlignment: Text.AlignHCenter - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - anchors.topMargin: 10 - } - Text { - text: tile.tileHint - color: Qt.darker(root.foreground, 1.6) - font.family: root.fontFamily - font.pixelSize: 10 - horizontalAlignment: Text.AlignHCenter - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: tileTitle.bottom - anchors.topMargin: 2 - } - - MouseArea { - id: tileArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: tile.clicked() - } - } - - // Compact, single-line variant of StyleOptionTile — used for the monitor - // scaling chip row where 6 options need to fit on one line. - component StyleChip: Rectangle { - id: chip - property string label: "" - property bool selected: false - signal clicked() - - activeFocusOnTab: true - Keys.onReturnPressed: chip.clicked() - Keys.onEnterPressed: chip.clicked() - Keys.onSpacePressed: chip.clicked() - - implicitWidth: chipText.implicitWidth + 22 - implicitHeight: 30 - radius: root.cornerRadius - color: chip.activeFocus - ? root.focusFillColor - : (chipArea.containsMouse - ? Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) - : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.03)) - border.color: chip.activeFocus - ? root.focusBorderColor - : (chip.selected ? root.accent : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18)) - border.width: chip.activeFocus ? root.focusBorderWidth : (chip.selected ? 2 : 1) - - Behavior on color { ColorAnimation { duration: 100 } } - - Text { - id: chipText - anchors.centerIn: parent - text: chip.label - color: chip.selected ? root.foreground : Qt.darker(root.foreground, 1.1) - font.family: root.fontFamily - font.pixelSize: 12 - font.bold: chip.selected - } - - MouseArea { - id: chipArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: chip.clicked() - } - } - - // Row showing a font name rendered in its own typeface, plus a short sample - // string in the same font. Selected = accent border + bold. - component FontRow: Rectangle { - id: fr - property string fontFamilyName: "" - property bool selected: false - signal picked() - - activeFocusOnTab: true - Keys.onReturnPressed: if (!fr.selected) fr.picked() - Keys.onEnterPressed: if (!fr.selected) fr.picked() - Keys.onSpacePressed: if (!fr.selected) fr.picked() - - implicitHeight: 44 - radius: root.cornerRadius - color: fr.activeFocus - ? root.focusFillColor - : (frArea.containsMouse - ? Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.08) - : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.03)) - border.color: fr.activeFocus - ? root.focusBorderColor - : (fr.selected ? root.accent : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12)) - border.width: fr.activeFocus ? root.focusBorderWidth : (fr.selected ? 2 : 1) - - Behavior on color { ColorAnimation { duration: 100 } } - - Row { - anchors.fill: parent - anchors.leftMargin: 14 - anchors.rightMargin: 14 - spacing: 14 - - Text { - text: fr.fontFamilyName - color: fr.selected ? root.accent : root.foreground - font.family: fr.fontFamilyName - font.pixelSize: 13 - font.bold: fr.selected - anchors.verticalCenter: parent.verticalCenter - width: 220 - elide: Text.ElideRight - } - Text { - text: "The quick brown fox jumps over the lazy dog 0123" - color: Qt.darker(root.foreground, 1.3) - font.family: fr.fontFamilyName - font.pixelSize: 12 - anchors.verticalCenter: parent.verticalCenter - elide: Text.ElideRight - width: parent.width - 234 - } - } - - MouseArea { - id: frArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: if (!fr.selected) fr.picked() - } - } - - - component StyleToggleRow: Rectangle { - id: tr - property string label: "" - property string description: "" - property bool isOn: false - signal toggle() - - activeFocusOnTab: true - Keys.onReturnPressed: tr.toggle() - Keys.onEnterPressed: tr.toggle() - Keys.onSpacePressed: tr.toggle() - - Layout.fillWidth: true - implicitHeight: 46 - radius: root.cornerRadius - color: tr.activeFocus - ? root.focusFillColor - : (trArea.containsMouse - ? Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.08) - : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.03)) - border.color: tr.activeFocus ? root.focusBorderColor : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12) - border.width: tr.activeFocus ? root.focusBorderWidth : 1 - - Behavior on color { ColorAnimation { duration: 100 } } - - Column { - anchors.left: parent.left - anchors.leftMargin: 14 - anchors.right: trSwitch.left - anchors.rightMargin: 14 - anchors.verticalCenter: parent.verticalCenter - spacing: 2 - - Text { - text: tr.label - color: root.foreground - font.family: root.fontFamily - font.pixelSize: 12 - font.bold: true - } - Text { - text: tr.description - color: Qt.darker(root.foreground, 1.5) - font.family: root.fontFamily - font.pixelSize: 10 - elide: Text.ElideRight - width: parent.width - } - } - - Rectangle { - id: trSwitch - anchors.right: parent.right - anchors.rightMargin: 14 - anchors.verticalCenter: parent.verticalCenter - width: 48 - height: 22 - radius: root.cornerRadius - color: tr.isOn - ? root.accent - : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) - border.color: tr.isOn ? root.accent : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.4) - border.width: 1 - - Rectangle { - width: 16 - height: 16 - radius: root.cornerRadius - color: tr.isOn ? root.background : root.foreground - anchors.verticalCenter: parent.verticalCenter - x: tr.isOn ? parent.width - width - 3 : 3 - - Behavior on x { NumberAnimation { duration: 120 } } - } - } - - MouseArea { - id: trArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: tr.toggle() - } - } - // ===================== shared chrome ===================================== component ActionPill: Rectangle { id: pill @@ -2500,237 +1324,5 @@ Item { } } - // ===================== plugins category ================================== - component PluginManager: Column { - id: pm - property int registryRevision: root.pluginRegistry ? root.pluginRegistry.registryRevision : 0 - Connections { - target: root.pluginRegistry - function onPluginsChanged() { pm.registryRevision = root.pluginRegistry.registryRevision } - } - - function pluginList() { - var rev = pm.registryRevision - if (!root.pluginRegistry) return [] - var plugins = root.pluginRegistry.installedPlugins - var ids = Object.keys(plugins).sort(function(a, b) { - var fa = !!plugins[a].__isFirstParty, fb = !!plugins[b].__isFirstParty - if (fa !== fb) return fa ? -1 : 1 - return String(plugins[a].name || a).localeCompare(String(plugins[b].name || b)) - }) - var rows = [] - for (var i = 0; i < ids.length; i++) { - var id = ids[i] - var m = plugins[id] - rows.push({ - id: id, - manifest: m, - enabled: root.pluginRegistry.isEnabled(id), - firstParty: !!m.__isFirstParty - }) - } - return rows - } - - spacing: 14 - Layout.fillWidth: true - width: parent ? parent.width : 0 - - Text { - text: "Plugins" - color: root.foreground - font.family: root.fontFamily - font.pixelSize: 18 - font.bold: true - } - - Text { - text: "Drop plugins at ~/.config/omarchy/plugins//, then click Rescan. First-party plugins are always enabled." - color: Qt.darker(root.foreground, 1.6) - font.family: root.fontFamily - font.pixelSize: 11 - wrapMode: Text.WordWrap - width: pm.width - } - - Row { - spacing: 8 - width: pm.width - - Text { - text: (root.pluginRegistry ? Object.keys(root.pluginRegistry.installedPlugins).length : 0) + " installed" - color: Qt.darker(root.foreground, 1.3) - font.family: root.fontFamily - font.pixelSize: 11 - anchors.verticalCenter: parent.verticalCenter - } - - Item { width: Math.max(0, pm.width - 200); height: 1 } - - ActionPill { - text: "Rescan" - onClicked: root.pluginRegistry.rescan() - } - } - - Repeater { - model: pm.pluginList() - delegate: PluginRow { - required property var modelData - width: pm.width - manifest: modelData.manifest - pluginId: modelData.id - pluginEnabled: modelData.enabled - firstParty: modelData.firstParty - } - } - - Text { - visible: pm.pluginList().length === 0 - text: "No plugins discovered yet." - color: Qt.darker(root.foreground, 1.5) - font.family: root.fontFamily - font.pixelSize: 11 - } - } - - component PluginRow: Rectangle { - id: row - property var manifest: ({}) - property string pluginId: "" - property bool pluginEnabled: false - property bool firstParty: false - property bool expanded: false - - activeFocusOnTab: !row.firstParty - Keys.onReturnPressed: if (!row.firstParty) root.pluginRegistry.setEnabled(row.pluginId, !row.pluginEnabled) - Keys.onEnterPressed: if (!row.firstParty) root.pluginRegistry.setEnabled(row.pluginId, !row.pluginEnabled) - Keys.onSpacePressed: if (!row.firstParty) root.pluginRegistry.setEnabled(row.pluginId, !row.pluginEnabled) - - radius: root.cornerRadius - color: row.activeFocus - ? root.focusFillColor - : (rowArea.containsMouse ? Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.08) : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.03)) - border.color: row.activeFocus ? root.focusBorderColor : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12) - border.width: row.activeFocus ? root.focusBorderWidth : 1 - implicitHeight: rowContent.implicitHeight + 16 - - Behavior on color { ColorAnimation { duration: 100 } } - - Column { - id: rowContent - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.margins: 8 - spacing: 4 - - Row { - spacing: 8 - width: parent.width - - Column { - spacing: 2 - width: parent.width - 110 - - Text { - text: row.manifest && row.manifest.name ? row.manifest.name : row.pluginId - color: root.foreground - font.family: root.fontFamily - font.pixelSize: 12 - font.bold: true - elide: Text.ElideRight - width: parent.width - } - Text { - text: { - var bits = [] - if (row.manifest && row.manifest.version) bits.push("v" + row.manifest.version) - if (row.manifest && row.manifest.author) bits.push(row.manifest.author) - bits.push(row.firstParty ? "first-party" : "third-party") - return bits.join(" · ") - } - color: Qt.darker(root.foreground, 1.5) - font.family: root.fontFamily - font.pixelSize: 10 - } - Text { - visible: !!(row.manifest && row.manifest.description) - text: row.manifest ? (row.manifest.description || "") : "" - color: Qt.darker(root.foreground, 1.3) - font.family: root.fontFamily - font.pixelSize: 10 - wrapMode: Text.WordWrap - width: parent.width - } - } - - Item { width: 8; height: 1 } - - Item { - implicitWidth: enabledSwitch.implicitWidth - implicitHeight: enabledSwitch.implicitHeight - anchors.verticalCenter: parent.verticalCenter - - Switch { - id: enabledSwitch - checked: row.pluginEnabled - enabled: !row.firstParty - opacity: row.firstParty ? 0.45 : 1 - ToolTip.visible: row.firstParty && hoverArea.containsMouse - ToolTip.delay: 300 - ToolTip.text: "First-party plugin — always enabled" - onToggled: root.pluginRegistry.setEnabled(row.pluginId, checked) - } - - MouseArea { - id: hoverArea - anchors.fill: parent - hoverEnabled: true - acceptedButtons: Qt.NoButton - visible: row.firstParty - cursorShape: Qt.ForbiddenCursor - } - } - } - - Row { - visible: !!(row.manifest && row.manifest.barWidget && Array.isArray(row.manifest.barWidget.schema) && row.manifest.barWidget.schema.length > 0) - spacing: 6 - - Text { - text: row.expanded ? "▾ Options" : "▸ Options" - color: Qt.darker(root.foreground, 1.4) - font.family: root.fontFamily - font.pixelSize: 10 - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: row.expanded = !row.expanded - } - } - } - - Repeater { - model: row.expanded && row.manifest && row.manifest.barWidget && Array.isArray(row.manifest.barWidget.schema) ? row.manifest.barWidget.schema : [] - delegate: Text { - required property var modelData - text: "• " + (modelData.label || modelData.key) + " (" + (modelData.type || "string") + ")" - color: Qt.darker(root.foreground, 1.3) - font.family: root.fontFamily - font.pixelSize: 10 - leftPadding: 12 - } - } - } - - MouseArea { - id: rowArea - anchors.fill: parent - hoverEnabled: true - acceptedButtons: Qt.NoButton - } - } } diff --git a/default/quickshell/omarchy-shell/plugins/settings/manifest.json b/default/quickshell/omarchy-shell/plugins/settings/manifest.json index d27ad8b6..8c9a9ffa 100644 --- a/default/quickshell/omarchy-shell/plugins/settings/manifest.json +++ b/default/quickshell/omarchy-shell/plugins/settings/manifest.json @@ -1,10 +1,10 @@ { "schemaVersion": 1, "id": "omarchy.settings", - "name": "Omarchy Settings", + "name": "Omarchy Bar Settings", "version": "1.0.0", "author": "Omarchy", - "description": "Customize the bar, plugins, defaults, and look-and-feel of Omarchy", + "description": "Customize the Omarchy bar position and widgets", "kinds": ["panel"], "activation": "on-demand", "entryPoints": { "panel": "SettingsPanel.qml" } diff --git a/default/quickshell/omarchy-shell/services/BarWidgetRegistry.qml b/default/quickshell/omarchy-shell/services/BarWidgetRegistry.qml index bee81680..049a379f 100644 --- a/default/quickshell/omarchy-shell/services/BarWidgetRegistry.qml +++ b/default/quickshell/omarchy-shell/services/BarWidgetRegistry.qml @@ -3,7 +3,7 @@ import QtQuick // Instance, not a singleton — instantiated once by shell.qml and injected into // plugins that need to read or extend the widget catalogue. Relative-path // singleton imports were creating per-importer instances which prevented the -// settings panel from seeing what the bar registered. +// bar settings panel from seeing what the bar registered. QtObject { id: registry