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 <noreply@anthropic.com>
This commit is contained in:
David Heinemeier Hansson
2026-07-18 11:44:35 -07:00
co-authored by Claude Fable 5
parent 66cdec78c2
commit 9aa1dcd664
11 changed files with 221 additions and 394 deletions
+1 -6
View File
@@ -2,7 +2,7 @@
# omarchy:summary=Set the active bar option, position, and transparency # omarchy:summary=Set the active bar option, position, and transparency
# omarchy:group=bar # omarchy:group=bar
# omarchy:args=use <id> | reset | defaults | position <top|bottom|left|right> | transparent <true|false> | settings # omarchy:args=use <id> | reset | defaults | position <top|bottom|left|right> | transparent <true|false>
# omarchy:examples=omarchy bar use local.neon-bar | omarchy bar reset | omarchy bar defaults | omarchy bar position top | omarchy bar transparent true # 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 set -euo pipefail
@@ -20,7 +20,6 @@ Usage: omarchy bar <command> [args...]
defaults Restore the default bar and service widgets defaults Restore the default bar and service widgets
position <top|bottom|left|right> Bar position position <top|bottom|left|right> Bar position
transparent <true|false> Bar transparency transparent <true|false> Bar transparency
settings Open the inline bar config panel
Bar widgets are added, moved, removed, and configured with 'omarchy bar plugin'. Bar widgets are added, moved, removed, and configured with 'omarchy bar plugin'.
@@ -168,10 +167,6 @@ case "$command" in
transparent) transparent)
cmd_transparent "$@" cmd_transparent "$@"
;; ;;
settings)
(( $# == 0 )) || fail "settings does not take arguments"
exec omarchy-launch-bar-settings
;;
plugin) plugin)
exec omarchy-bar-plugin "$@" exec omarchy-bar-plugin "$@"
;; ;;
-5
View File
@@ -1,5 +0,0 @@
#!/bin/bash
# omarchy:summary=Open the Omarchy bar config panel
exec omarchy-shell shell openBarConfig
-1
View File
@@ -29,7 +29,6 @@ Options:
Examples: Examples:
omarchy-shell shell ping omarchy-shell shell ping
omarchy-shell shell openBarConfig
omarchy-shell -q Indicators refresh omarchy-shell -q Indicators refresh
omarchy-shell shell listPlugins omarchy-shell shell listPlugins
omarchy-shell shell toggle omarchy.menu '{"menu":"root"}' omarchy-shell shell toggle omarchy.menu '{"menu":"root"}'
+1 -1
View File
@@ -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 The shell hot-reloads `shell.json` on save — no restart needed for layout
changes. For more invasive changes (new plugin, packaged update): 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 ### Terminals
-1
View File
@@ -148,7 +148,6 @@
"setup.security.fido2": {"icon":"","label":"Fido2","keywords":"key","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fido2"}, "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.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.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"}, "setup.config.xcompose": {"icon":"󰞅","label":"XCompose","keywords":"compose key","action":"omarchy-launch-config-editor ~/.XCompose && omarchy-restart-xcompose"},
// Install // Install
+1 -2
View File
@@ -188,7 +188,6 @@ calls to the running shell. It does not start the shell.
``` ```
omarchy-shell shell ping omarchy-shell shell ping
omarchy-shell shell openBarConfig
omarchy-shell shell toggle omarchy.menu '{"menu":"root"}' omarchy-shell shell toggle omarchy.menu '{"menu":"root"}'
omarchy-shell shell listPlugins omarchy-shell shell listPlugins
omarchy-shell shell rescanPlugins 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. like `Clock` and `AudioPanel` forward.
5. **Third-party enabled ⇔ present.** A third-party plugin is enabled iff 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 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 other plugin kinds are enabled with the shell IPC. First-party non-bar
plugins are always enabled. plugins are always enabled.
6. **Multiple instances** are allowed when a manifest sets 6. **Multiple instances** are allowed when a manifest sets
+216 -125
View File
@@ -85,7 +85,11 @@ Item {
property real barDragScreenY: 0 property real barDragScreenY: 0
property real barDragOffsetX: 0 property real barDragOffsetX: 0
property real barDragOffsetY: 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 clickTargets: []
property var moduleSlots: [] property var moduleSlots: []
@@ -113,18 +117,6 @@ Item {
moduleSlots = next 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() { function debugBarGeometry() {
var out = [] var out = []
for (var i = 0; i < moduleSlots.length; i++) { for (var i = 0; i < moduleSlots.length; i++) {
@@ -198,10 +190,9 @@ Item {
barDragOffsetY = 0 barDragOffsetY = 0
} }
function barDragScreenPoint(scenePoint) { function windowScreenPoint(scenePoint, window) {
var x = scenePoint ? scenePoint.x : 0 var x = scenePoint ? scenePoint.x : 0
var y = scenePoint ? scenePoint.y : 0 var y = scenePoint ? scenePoint.y : 0
var window = barDragWindow
if (!window || !window.screen) return { x: x, y: y } if (!window || !window.screen) return { x: x, y: y }
if (root.position === "bottom") if (root.position === "bottom")
@@ -212,6 +203,81 @@ Item {
return { x: x, y: y } 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) { function captureBarDragGhost(slot) {
var item = slot && slot.activeItem ? slot.activeItem : null var item = slot && slot.activeItem ? slot.activeItem : null
barDragImageUrl = "" barDragImageUrl = ""
@@ -441,16 +507,6 @@ Item {
launcher.startDetached() 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() { function toggleTransparency() {
var nextTransparent = !(root.requestedTransparent === true) var nextTransparent = !(root.requestedTransparent === true)
if (root.shell && typeof root.shell.mutateShellConfig === "function") { 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 { component BarPanel: PanelWindow {
id: barWindow 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() { function findCenterAnchorEntry() {
var entries = root.layoutEntries("center") var entries = root.layoutEntries("center")
var idx = root.entryIndex(entries, root.centerAnchor) var idx = root.entryIndex(entries, root.centerAnchor)
@@ -1045,7 +1167,7 @@ Item {
visible: centerRoot.hasAnchor visible: centerRoot.hasAnchor
entries: root.entriesBefore(centerRoot.entries, root.centerAnchor) entries: root.entriesBefore(centerRoot.entries, root.centerAnchor)
region: "center" region: "center"
anchors.right: centerConfigControl.visible ? centerConfigControl.left : centerAnchorModule.left anchors.right: centerAnchorModule.left
anchors.verticalCenter: centerAnchorModule.verticalCenter anchors.verticalCenter: centerAnchorModule.verticalCenter
} }
@@ -1057,16 +1179,6 @@ Item {
anchors.centerIn: parent 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 { ModuleList {
visible: centerRoot.hasAnchor visible: centerRoot.hasAnchor
entries: root.entriesAfter(centerRoot.entries, root.centerAnchor) entries: root.entriesAfter(centerRoot.entries, root.centerAnchor)
@@ -1100,7 +1212,7 @@ Item {
visible: centerRoot.hasAnchor visible: centerRoot.hasAnchor
entries: root.entriesBefore(centerRoot.entries, root.centerAnchor) entries: root.entriesBefore(centerRoot.entries, root.centerAnchor)
region: "center" region: "center"
anchors.bottom: centerConfigControl.visible ? centerConfigControl.top : centerAnchorModule.top anchors.bottom: centerAnchorModule.top
anchors.horizontalCenter: centerAnchorModule.horizontalCenter anchors.horizontalCenter: centerAnchorModule.horizontalCenter
} }
@@ -1112,16 +1224,6 @@ Item {
anchors.centerIn: parent 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 { ModuleList {
visible: centerRoot.hasAnchor visible: centerRoot.hasAnchor
entries: root.entriesAfter(centerRoot.entries, root.centerAnchor) entries: root.entriesAfter(centerRoot.entries, root.centerAnchor)
@@ -1134,91 +1236,80 @@ Item {
} }
component CenterGestureArea: MouseArea { component CenterGestureArea: MouseArea {
acceptedButtons: Qt.LeftButton id: gestureArea
onDoubleClicked: function(mouse) { property bool dragging: false
if (mouse.button === Qt.LeftButton) { property bool suppressClick: false
root.toggleTransparency() 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 mouse.accepted = true
} }
} }
}
component BarConfigControl: Item { onDoubleClicked: function(mouse) {
id: configControl if (suppressClick) {
suppressClick = false
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
return return
} }
panelItem.open() if (mouse.button === Qt.LeftButton) {
} root.toggleTransparency()
mouse.accepted = true
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()
}
} }
} }
} }
-246
View File
@@ -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
}
}
}
}
}
}
}
+2 -2
View File
@@ -14,9 +14,9 @@ the shell for its whole session.
## Customizing ## 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): Example `shell.json` (bar subtree only shown):
-4
View File
@@ -909,10 +909,6 @@ ShellRoot {
return JSON.stringify(shell.bar && shell.bar.debugBarGeometry ? shell.bar.debugBarGeometry() : []) 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 { function summon(id: string, payloadJson: string): string {
return shell.summon(id, payloadJson) ? "ok" : "unknown" return shell.summon(id, payloadJson) ? "ok" : "unknown"
} }
-1
View File
@@ -116,7 +116,6 @@ jq -e '
} }
pass "shell IPC returns effective shell config" 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 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_quiet shell hide omarchy.launcher >/dev/null
[[ $(shell_ipc shell summon missing.plugin "{}") == "unknown" ]] || fail_with_log "shell IPC rejects unknown plugin" [[ $(shell_ipc shell summon missing.plugin "{}") == "unknown" ]] || fail_with_log "shell IPC rejects unknown plugin"