From 9aa1dcd664e597e17f27231564cfb51173fe02d4 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 18 Jul 2026 11:44:35 -0700 Subject: [PATCH] Drag the bar to move it, drop the config panel Position the bar by dragging (or click-and-holding) empty bar space toward a screen edge, with a ghost slab previewing the target edge. With drag for position and double-click for transparency, the bar config panel, its inline gear button, and the omarchy-launch-bar-settings CLI are no longer needed. Co-Authored-By: Claude Fable 5 --- bin/omarchy-bar | 7 +- bin/omarchy-launch-bar-settings | 5 - bin/omarchy-shell | 1 - default/omarchy-skill/SKILL.md | 2 +- default/omarchy/omarchy-menu.jsonc | 1 - shell/README.md | 3 +- shell/plugins/bar/Bar.qml | 341 +++++++++++++++++---------- shell/plugins/bar/BarConfigPanel.qml | 246 ------------------- shell/plugins/bar/README.md | 4 +- shell/shell.qml | 4 - test/shell.d/runtime-smoke-test.sh | 1 - 11 files changed, 221 insertions(+), 394 deletions(-) delete mode 100755 bin/omarchy-launch-bar-settings delete mode 100644 shell/plugins/bar/BarConfigPanel.qml diff --git a/bin/omarchy-bar b/bin/omarchy-bar index 17c0a099..3ce9dd25 100755 --- a/bin/omarchy-bar +++ b/bin/omarchy-bar @@ -2,7 +2,7 @@ # omarchy:summary=Set the active bar option, position, and transparency # omarchy:group=bar -# omarchy:args=use | reset | defaults | position | transparent | settings +# omarchy:args=use | reset | defaults | position | transparent # omarchy:examples=omarchy bar use local.neon-bar | omarchy bar reset | omarchy bar defaults | omarchy bar position top | omarchy bar transparent true set -euo pipefail @@ -20,7 +20,6 @@ Usage: omarchy bar [args...] defaults Restore the default bar and service widgets position Bar position transparent Bar transparency - settings Open the inline bar config panel Bar widgets are added, moved, removed, and configured with 'omarchy bar plugin'. @@ -168,10 +167,6 @@ case "$command" in transparent) cmd_transparent "$@" ;; - settings) - (( $# == 0 )) || fail "settings does not take arguments" - exec omarchy-launch-bar-settings - ;; plugin) exec omarchy-bar-plugin "$@" ;; diff --git a/bin/omarchy-launch-bar-settings b/bin/omarchy-launch-bar-settings deleted file mode 100755 index 23a557c9..00000000 --- a/bin/omarchy-launch-bar-settings +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# omarchy:summary=Open the Omarchy bar config panel - -exec omarchy-shell shell openBarConfig diff --git a/bin/omarchy-shell b/bin/omarchy-shell index 583beee1..ed7729b2 100755 --- a/bin/omarchy-shell +++ b/bin/omarchy-shell @@ -29,7 +29,6 @@ Options: Examples: omarchy-shell shell ping - omarchy-shell shell openBarConfig omarchy-shell -q Indicators refresh omarchy-shell shell listPlugins omarchy-shell shell toggle omarchy.menu '{"menu":"root"}' diff --git a/default/omarchy-skill/SKILL.md b/default/omarchy-skill/SKILL.md index 21d7083b..312b4b35 100644 --- a/default/omarchy-skill/SKILL.md +++ b/default/omarchy-skill/SKILL.md @@ -174,7 +174,7 @@ $OMARCHY_PATH/config/omarchy/shell.json # Canonical defaults The shell hot-reloads `shell.json` on save — no restart needed for layout changes. For more invasive changes (new plugin, packaged update): -**Commands:** `omarchy-restart-shell`, `omarchy refresh shell`, `omarchy launch bar settings` +**Commands:** `omarchy-restart-shell`, `omarchy refresh shell` ### Terminals diff --git a/default/omarchy/omarchy-menu.jsonc b/default/omarchy/omarchy-menu.jsonc index cea78914..a3bb097f 100644 --- a/default/omarchy/omarchy-menu.jsonc +++ b/default/omarchy/omarchy-menu.jsonc @@ -148,7 +148,6 @@ "setup.security.fido2": {"icon":"","label":"Fido2","keywords":"key","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fido2"}, "setup.config.hyprland": {"icon":"","label":"Hyprland","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/hyprland.lua\""}, "setup.config.hyprsunset": {"icon":"","label":"Hyprsunset","keywords":"night light","action":"omarchy-launch-config-editor ~/.config/hypr/hyprsunset.conf && omarchy-restart-hyprsunset"}, - "setup.config.bar": {"icon":"󰍜","label":"Bar","keywords":"quickshell shell config settings widgets position","action":"omarchy-launch-bar-settings"}, "setup.config.xcompose": {"icon":"󰞅","label":"XCompose","keywords":"compose key","action":"omarchy-launch-config-editor ~/.XCompose && omarchy-restart-xcompose"}, // Install diff --git a/shell/README.md b/shell/README.md index b2c76d17..d57c681f 100644 --- a/shell/README.md +++ b/shell/README.md @@ -188,7 +188,6 @@ calls to the running shell. It does not start the shell. ``` omarchy-shell shell ping -omarchy-shell shell openBarConfig omarchy-shell shell toggle omarchy.menu '{"menu":"root"}' omarchy-shell shell listPlugins omarchy-shell shell rescanPlugins @@ -256,7 +255,7 @@ becomes the authoritative file — we do **not** deep-merge defaults back in. like `Clock` and `AudioPanel` forward. 5. **Third-party enabled ⇔ present.** A third-party plugin is enabled iff its id appears somewhere in shell.json. For full bar options, that means - `bar.id`; for bar widgets, the bar settings UI adds/removes layout entries; + `bar.id`; for bar widgets, `omarchy bar plugin` adds/removes layout entries; other plugin kinds are enabled with the shell IPC. First-party non-bar plugins are always enabled. 6. **Multiple instances** are allowed when a manifest sets diff --git a/shell/plugins/bar/Bar.qml b/shell/plugins/bar/Bar.qml index 9becf9b5..a9277b93 100644 --- a/shell/plugins/bar/Bar.qml +++ b/shell/plugins/bar/Bar.qml @@ -85,7 +85,11 @@ Item { property real barDragScreenY: 0 property real barDragOffsetX: 0 property real barDragOffsetY: 0 - property var configControls: [] + property bool barMoveActive: false + property bool barMoveSettling: false + property string barMoveCandidate: "" + property var barMoveWindow: null + property var barMoveScreen: null property var clickTargets: [] property var moduleSlots: [] @@ -113,18 +117,6 @@ Item { moduleSlots = next } - function registerConfigControl(control) { - if (!control || configControls.indexOf(control) !== -1) return - var next = configControls.slice() - next.push(control) - configControls = next - } - - function unregisterConfigControl(control) { - var next = configControls.filter(function(item) { return item !== control }) - configControls = next - } - function debugBarGeometry() { var out = [] for (var i = 0; i < moduleSlots.length; i++) { @@ -198,10 +190,9 @@ Item { barDragOffsetY = 0 } - function barDragScreenPoint(scenePoint) { + function windowScreenPoint(scenePoint, window) { var x = scenePoint ? scenePoint.x : 0 var y = scenePoint ? scenePoint.y : 0 - var window = barDragWindow if (!window || !window.screen) return { x: x, y: y } if (root.position === "bottom") @@ -212,6 +203,81 @@ Item { return { x: x, y: y } } + function barDragScreenPoint(scenePoint) { + return windowScreenPoint(scenePoint, barDragWindow) + } + + // Split the screen along its diagonals (in normalized space, so widescreens + // don't bias toward left/right): whichever triangle holds the cursor names + // the candidate edge. + function nearestScreenEdge(point, screen) { + var nx = screen.width > 0 ? Util.clamp(point.x / screen.width, 0, 1) : 0.5 + var ny = screen.height > 0 ? Util.clamp(point.y / screen.height, 0, 1) : 0.5 + + var edge = "top" + var best = ny + if (1 - ny < best) { edge = "bottom"; best = 1 - ny } + if (nx < best) { edge = "left"; best = nx } + if (1 - nx < best) { edge = "right"; best = 1 - nx } + return edge + } + + function beginBarMove(window) { + barMoveSettleTimer.stop() + barMoveSettling = false + barMoveWindow = window + barMoveScreen = window ? window.screen : null + barMoveCandidate = position + barMoveActive = true + } + + function updateBarMove(screenPoint) { + if (!barMoveActive || !barMoveScreen) return + barMoveCandidate = nearestScreenEdge(screenPoint, barMoveScreen) + } + + function clearBarMove() { + barMoveSettleTimer.stop() + barMoveActive = false + barMoveSettling = false + barMoveCandidate = "" + barMoveWindow = null + barMoveScreen = null + } + + function finishBarMove() { + var edge = barMoveCandidate + if (!barMoveActive || !edge || edge === position) { + clearBarMove() + return + } + + // Hold the ghost on the target edge while the config round-trips and the + // bar re-anchors, so the handoff doesn't flash an empty edge. + barMoveActive = false + barMoveSettling = true + barMoveSettleTimer.restart() + setBarPosition(edge) + } + + Timer { + id: barMoveSettleTimer + interval: 450 + onTriggered: root.clearBarMove() + } + + function setBarPosition(value) { + var next = normalizePosition(value) + if (root.shell && typeof root.shell.mutateShellConfig === "function") { + root.shell.mutateShellConfig(function(config) { + if (!Util.isPlainObject(config.bar)) config.bar = {} + config.bar.position = next + }) + } else { + root.position = next + } + } + function captureBarDragGhost(slot) { var item = slot && slot.activeItem ? slot.activeItem : null barDragImageUrl = "" @@ -441,16 +507,6 @@ Item { launcher.startDetached() } - function openConfigPanel() { - for (var i = 0; i < configControls.length; i++) { - var control = configControls[i] - if (!control || control.visible !== true || typeof control.openPanel !== "function") continue - control.openPanel() - return true - } - return false - } - function toggleTransparency() { var nextTransparent = !(root.requestedTransparent === true) if (root.shell && typeof root.shell.mutateShellConfig === "function") { @@ -804,6 +860,19 @@ Item { } } + Variants { + model: Quickshell.screens + + delegate: Component { + BarMoveGhostPanel { + required property var modelData + + screen: modelData + ghostScreen: modelData + } + } + } + component BarPanel: PanelWindow { id: barWindow @@ -994,6 +1063,59 @@ Item { } } + component BarMoveGhostPanel: PanelWindow { + id: moveGhostWindow + + required property var ghostScreen + readonly property bool screenMatches: root.barMoveScreen === ghostScreen || + (root.barMoveScreen && ghostScreen && root.barMoveScreen.name && ghostScreen.name && root.barMoveScreen.name === ghostScreen.name) + visible: (root.barMoveActive || root.barMoveSettling) && screenMatches + color: "transparent" + exclusionMode: ExclusionMode.Ignore + WlrLayershell.namespace: "omarchy-bar-move-ghost" + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + + anchors { + top: true + bottom: true + left: true + right: true + } + + // Visual-only preview of the candidate edge. Keep the input region empty + // so the overlay never steals the gesture area's active pointer grab. + mask: Region {} + + // One fixed-geometry slab per edge, crossfaded on candidate changes. + // Resizing a single slab between edges repaints mid-transition and + // flickers; fading between static ones does not. + Repeater { + model: ["top", "bottom", "left", "right"] + + BorderSurface { + id: edgeSlab + + required property string modelData + readonly property bool edgeVertical: modelData === "left" || modelData === "right" + readonly property int edgeSize: edgeVertical ? Style.bar.sizeVertical : Style.bar.sizeHorizontal + + x: modelData === "right" ? parent.width - edgeSize : 0 + y: modelData === "bottom" ? parent.height - edgeSize : 0 + width: edgeVertical ? edgeSize : parent.width + height: edgeVertical ? parent.height : edgeSize + color: root.transparent ? "transparent" : root.background + borderSpec: Border.flat(root.barForeground, 1) + visible: opacity > 0 + opacity: root.barMoveCandidate === modelData ? (root.transparent ? 0.45 : 0.7) : 0 + + Behavior on opacity { + NumberAnimation { duration: 140; easing.type: Easing.OutCubic } + } + } + } + } + function findCenterAnchorEntry() { var entries = root.layoutEntries("center") var idx = root.entryIndex(entries, root.centerAnchor) @@ -1045,7 +1167,7 @@ Item { visible: centerRoot.hasAnchor entries: root.entriesBefore(centerRoot.entries, root.centerAnchor) region: "center" - anchors.right: centerConfigControl.visible ? centerConfigControl.left : centerAnchorModule.left + anchors.right: centerAnchorModule.left anchors.verticalCenter: centerAnchorModule.verticalCenter } @@ -1057,16 +1179,6 @@ Item { anchors.centerIn: parent } - BarConfigControl { - id: centerConfigControl - - visible: centerRoot.hasAnchor && centerAnchorModule.moduleName === "omarchy.clock" - clockHovered: centerAnchorModule.hovered - centerHovered: root.centerSectionRevealHeld && !root.centerHoverRevealSuppressed - anchors.right: centerAnchorModule.left - anchors.verticalCenter: centerAnchorModule.verticalCenter - } - ModuleList { visible: centerRoot.hasAnchor entries: root.entriesAfter(centerRoot.entries, root.centerAnchor) @@ -1100,7 +1212,7 @@ Item { visible: centerRoot.hasAnchor entries: root.entriesBefore(centerRoot.entries, root.centerAnchor) region: "center" - anchors.bottom: centerConfigControl.visible ? centerConfigControl.top : centerAnchorModule.top + anchors.bottom: centerAnchorModule.top anchors.horizontalCenter: centerAnchorModule.horizontalCenter } @@ -1112,16 +1224,6 @@ Item { anchors.centerIn: parent } - BarConfigControl { - id: centerConfigControl - - visible: centerRoot.hasAnchor && centerAnchorModule.moduleName === "omarchy.clock" - clockHovered: centerAnchorModule.hovered - centerHovered: root.centerSectionRevealHeld && !root.centerHoverRevealSuppressed - anchors.bottom: centerAnchorModule.top - anchors.horizontalCenter: centerAnchorModule.horizontalCenter - } - ModuleList { visible: centerRoot.hasAnchor entries: root.entriesAfter(centerRoot.entries, root.centerAnchor) @@ -1134,91 +1236,80 @@ Item { } component CenterGestureArea: MouseArea { - acceptedButtons: Qt.LeftButton + id: gestureArea - onDoubleClicked: function(mouse) { - if (mouse.button === Qt.LeftButton) { - root.toggleTransparency() + property bool dragging: false + property bool suppressClick: false + property real pressedX: 0 + property real pressedY: 0 + readonly property real dragThreshold: Style.space(4) + + acceptedButtons: Qt.LeftButton + cursorShape: dragging ? Qt.ClosedHandCursor : Qt.ArrowCursor + pressAndHoldInterval: 200 + + function startDrag(x, y) { + if (dragging) return + dragging = true + root.beginBarMove(root.targetWindow(gestureArea)) + var scenePoint = gestureArea.mapToItem(null, x, y) + root.updateBarMove(root.windowScreenPoint(scenePoint, root.barMoveWindow)) + } + + onPressed: function(mouse) { + dragging = false + suppressClick = false + pressedX = mouse.x + pressedY = mouse.y + } + + onPressAndHold: function(mouse) { + startDrag(mouse.x, mouse.y) + } + + onPositionChanged: function(mouse) { + if (!(mouse.buttons & Qt.LeftButton)) return + + if (!dragging) { + var distance = Math.abs(mouse.x - pressedX) + Math.abs(mouse.y - pressedY) + if (distance < dragThreshold) return + startDrag(mouse.x, mouse.y) + return + } + + var scenePoint = gestureArea.mapToItem(null, mouse.x, mouse.y) + root.updateBarMove(root.windowScreenPoint(scenePoint, root.barMoveWindow)) + } + + onReleased: function(mouse) { + if (!dragging) return + dragging = false + suppressClick = true + root.finishBarMove() + mouse.accepted = true + } + + onCanceled: { + dragging = false + suppressClick = false + root.clearBarMove() + } + + onClicked: function(mouse) { + if (suppressClick) { + suppressClick = false mouse.accepted = true } } - } - component BarConfigControl: Item { - id: configControl - - property bool clockHovered: false - property bool centerHovered: false - property bool openWhenReady: false - - readonly property var panelItem: configPanelLoader.item - readonly property bool panelOpen: panelItem ? panelItem.opened === true : false - readonly property bool revealed: visible && (clockHovered || centerHovered || controlHover.hovered || panelOpen) - - implicitWidth: button.implicitWidth - implicitHeight: button.implicitHeight - width: implicitWidth - height: implicitHeight - z: 500 - - HoverHandler { id: controlHover } - - Component.onCompleted: root.registerConfigControl(configControl) - Component.onDestruction: root.unregisterConfigControl(configControl) - - function configurePanel(panel) { - if (!panel) return - panel.bar = root - panel.anchorItem = button - } - - function openPanel() { - if (!panelItem) { - openWhenReady = true + onDoubleClicked: function(mouse) { + if (suppressClick) { + suppressClick = false return } - panelItem.open() - } - - function togglePanel() { - if (!panelItem) { - openPanel() - return - } - panelItem.toggle() - } - - WidgetButton { - id: button - - anchors.fill: parent - bar: root - text: "" - keepSpace: true - concealed: !configControl.revealed - dimmed: configControl.revealed && !controlHover.hovered && !configControl.panelOpen - interactive: configControl.revealed - horizontalMargin: 6.5 - verticalPadding: 6 - fixedWidth: vertical ? -1 : Style.bar.statusSlot - fixedHeight: vertical ? Style.bar.statusSlot : -1 - tooltipText: "Bar config" - onPressed: function(b) { - if (b === Qt.LeftButton) configControl.togglePanel() - } - } - - Loader { - id: configPanelLoader - - active: true - source: Qt.resolvedUrl("BarConfigPanel.qml") - onLoaded: { - configControl.configurePanel(item) - if (configControl.openWhenReady) { - configControl.openWhenReady = false - item.open() - } + if (mouse.button === Qt.LeftButton) { + root.toggleTransparency() + mouse.accepted = true } } } diff --git a/shell/plugins/bar/BarConfigPanel.qml b/shell/plugins/bar/BarConfigPanel.qml deleted file mode 100644 index 4e52ec16..00000000 --- a/shell/plugins/bar/BarConfigPanel.qml +++ /dev/null @@ -1,246 +0,0 @@ -import QtQuick -import qs.Commons -import qs.Ui - -Panel { - id: root - - moduleName: "omarchy.bar-config" - manageIpc: false - - property Item anchorItem: null - property string focusSection: "transparency" - property int positionIndex: 0 - property bool cursorActive: false - property int phraseIndex: 0 - - readonly property string currentPosition: bar ? bar.position : "top" - readonly property bool transparent: bar ? bar.requestedTransparent === true : false - readonly property color foreground: bar ? bar.foreground : Color.foreground - readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family - readonly property var heroPhrases: [ - "Picking Sides", - "Choose Transparency", - "Edge Decisions", - "Bar Exam", - "Top Shelf Thinking", - "Side Quest Settings" - ] - readonly property string heroPhraseText: heroPhrases[phraseIndex % heroPhrases.length] - readonly property var positionOptions: [ - { value: "top", label: "Top" }, - { value: "bottom", label: "Bottom" }, - { value: "left", label: "Left" }, - { value: "right", label: "Right" } - ] - - function normalizePosition(value) { - var next = String(value || "") - return /^(top|bottom|left|right)$/.test(next) ? next : "top" - } - - function positionLabel(value) { - var next = normalizePosition(value) - return next.charAt(0).toUpperCase() + next.slice(1) - } - - function currentPositionIndex() { - var current = normalizePosition(currentPosition) - for (var i = 0; i < positionOptions.length; i++) - if (positionOptions[i].value === current) return i - return 0 - } - - function mutateBarConfig(mutator) { - if (!bar || !bar.shell || typeof bar.shell.mutateShellConfig !== "function") return false - bar.shell.mutateShellConfig(function(config) { - if (!Util.isPlainObject(config.bar)) config.bar = {} - mutator(config.bar) - }) - return true - } - - function setTransparency(value) { - var next = value === true - if (mutateBarConfig(function(barConfig) { barConfig.transparent = next })) return - if (bar && typeof bar.setRequestedTransparency === "function") bar.setRequestedTransparency(next) - } - - function setPosition(value) { - var next = normalizePosition(value) - if (mutateBarConfig(function(barConfig) { barConfig.position = next })) return - if (bar) bar.position = next - } - - function moveCursor(dx, dy) { - if (!cursorActive) { - cursorActive = true - return - } - - if (dy !== 0) { - focusSection = focusSection === "transparency" ? "position" : "transparency" - if (focusSection === "position") positionIndex = currentPositionIndex() - return - } - - if (dx !== 0 && focusSection === "position") { - var count = positionOptions.length - positionIndex = (positionIndex + (dx > 0 ? 1 : -1) + count) % count - } - } - - function activateCursor() { - if (focusSection === "transparency") { - setTransparency(!transparent) - return - } - - var option = positionOptions[positionIndex] - if (option) setPosition(option.value) - } - - onOpenedChanged: { - if (!opened) return - focusSection = "transparency" - positionIndex = currentPositionIndex() - cursorActive = false - } - - onCurrentPositionChanged: if (!cursorActive || focusSection === "position") positionIndex = currentPositionIndex() - - Timer { - id: phraseTimer - interval: 2200 - running: root.opened - repeat: true - onTriggered: phraseSwap.restart() - } - - SequentialAnimation { - id: phraseSwap - PropertyAnimation { - target: hero; property: "metaOpacity" - to: 0.0; duration: 180; easing.type: Easing.OutQuad - } - ScriptAction { - script: root.phraseIndex = (root.phraseIndex + 1) % root.heroPhrases.length - } - PropertyAnimation { - target: hero; property: "metaOpacity" - to: 1.0; duration: 260; easing.type: Easing.InQuad - } - } - - Component { - id: heroIconComponent - - Text { - text: "" - color: root.foreground - font.family: root.fontFamily - font.pixelSize: Style.font.display - } - } - - KeyboardPanel { - id: panel - - anchorItem: root.anchorItem - owner: root - bar: root.bar - open: root.opened && root.anchorItem !== null - focusTarget: keyCatcher - contentWidth: panel.fittedContentWidth(Style.space(380)) - contentHeight: panel.fittedContentHeight(contentColumn.implicitHeight) - - PanelKeyCatcher { - id: keyCatcher - - anchors.fill: parent - onMoveRequested: function(dx, dy) { root.moveCursor(dx, dy) } - onActivateRequested: root.activateCursor() - onCloseRequested: root.close() - - Column { - id: contentColumn - - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - spacing: Style.space(14) - - PanelHero { - id: hero - - width: parent.width - iconComponent: heroIconComponent - title: "Bar" - meta: root.heroPhraseText - foreground: root.foreground - fontFamily: root.fontFamily - } - - Column { - width: parent.width - spacing: Style.space(8) - - PanelSectionHeader { - text: "APPEARANCE" - foreground: root.foreground - fontFamily: root.fontFamily - } - - Toggle { - width: parent.width - label: "Transparency" - description: root.transparent ? "Wallpaper visible" : "Solid background" - checked: root.transparent - hasCursor: root.cursorActive && root.focusSection === "transparency" - foreground: root.foreground - accent: Color.accent - fontFamily: root.fontFamily - onClicked: root.setTransparency(!root.transparent) - onHovered: function(h) { - if (!h) return - root.cursorActive = true - root.focusSection = "transparency" - } - } - } - - Column { - width: parent.width - spacing: Style.space(8) - - PanelSectionHeader { - text: "POSITION" - foreground: root.foreground - fontFamily: root.fontFamily - } - - ButtonGroup { - id: positionGroup - - width: parent.width - options: root.positionOptions - value: root.currentPosition - foreground: root.foreground - background: "transparent" - accent: Color.accent - fontFamily: root.fontFamily - focusable: false - cursorIndex: root.cursorActive && root.focusSection === "position" ? root.positionIndex : -1 - onChanged: function(value) { root.setPosition(value) } - onHovered: function(index, hovered) { - if (!hovered) return - root.cursorActive = true - root.focusSection = "position" - root.positionIndex = index - } - } - } - } - } - } -} diff --git a/shell/plugins/bar/README.md b/shell/plugins/bar/README.md index df26874c..3606e9ab 100644 --- a/shell/plugins/bar/README.md +++ b/shell/plugins/bar/README.md @@ -14,9 +14,9 @@ the shell for its whole session. ## Customizing -The bar config lives under the `bar:` key of [`~/.config/omarchy/shell.json`](../../README.md#shelljson-shape). Out of the box the shell uses [`config/omarchy/shell.json`](../../../config/omarchy/shell.json). Once you customize anything via the inline bar config panel, `omarchy bar ...`, or by editing shell.json directly, your file is canonical — there is no deep-merge. +The bar config lives under the `bar:` key of [`~/.config/omarchy/shell.json`](../../README.md#shelljson-shape). Out of the box the shell uses [`config/omarchy/shell.json`](../../../config/omarchy/shell.json). Once you customize anything via the bar gestures, `omarchy bar ...`, or by editing shell.json directly, your file is canonical — there is no deep-merge. -Open quick position and transparency controls with `omarchy bar settings` (or run `omarchy-launch-bar-settings`). You can also hover the centered clock module to reveal the inline bar config button. For scriptable widget changes, use `omarchy bar plugin add`, `omarchy bar plugin move`, `omarchy bar plugin remove`, and `omarchy bar plugin set` (widget ids come from `omarchy plugin list`). Double-left-click empty center-bar space to toggle bar transparency. +The bar is configured directly on the bar itself: drag empty bar space (or click-and-hold) to move the bar to another screen edge, double-left-click empty center-bar space to toggle transparency, and drag widgets to reorder them. The `omarchy bar position` and `omarchy bar transparent` commands do the same from scripts. For scriptable widget changes, use `omarchy bar plugin add`, `omarchy bar plugin move`, `omarchy bar plugin remove`, and `omarchy bar plugin set` (widget ids come from `omarchy plugin list`). Example `shell.json` (bar subtree only shown): diff --git a/shell/shell.qml b/shell/shell.qml index 96778ec6..5c4a6ce3 100644 --- a/shell/shell.qml +++ b/shell/shell.qml @@ -909,10 +909,6 @@ ShellRoot { return JSON.stringify(shell.bar && shell.bar.debugBarGeometry ? shell.bar.debugBarGeometry() : []) } - function openBarConfig(): string { - return shell.bar && shell.bar.openConfigPanel && shell.bar.openConfigPanel() ? "ok" : "unknown" - } - function summon(id: string, payloadJson: string): string { return shell.summon(id, payloadJson) ? "ok" : "unknown" } diff --git a/test/shell.d/runtime-smoke-test.sh b/test/shell.d/runtime-smoke-test.sh index d2b38c4b..e474c3f1 100755 --- a/test/shell.d/runtime-smoke-test.sh +++ b/test/shell.d/runtime-smoke-test.sh @@ -116,7 +116,6 @@ jq -e ' } pass "shell IPC returns effective shell config" -[[ $(shell_ipc shell openBarConfig) == "ok" ]] || fail_with_log "shell IPC opens bar config panel" [[ $(shell_ipc shell summon omarchy.launcher '{"query":"term"}') == "ok" ]] || fail_with_log "shell IPC summons launcher overlay" shell_ipc_quiet shell hide omarchy.launcher >/dev/null [[ $(shell_ipc shell summon missing.plugin "{}") == "unknown" ]] || fail_with_log "shell IPC rejects unknown plugin"