From e5c0fc3f7dc38f6e826e91b5f6e9a0257bf7da8f Mon Sep 17 00:00:00 2001 From: Sir Blob Date: Thu, 24 Sep 2026 02:23:04 +0000 Subject: [PATCH] Lock the bar to the top of the screen --- bin/blob-bar | 15 +- config/blob/shell.json | 1 - default/blob/blob-menu.jsonc | 5 - shell.json | 1 - shell/plugins/bar/Bar.qml | 214 +----------------------- shell/plugins/bar/BarModel.js | 6 - shell/plugins/notifications/Service.qml | 2 +- shell/shell.qml | 1 - 8 files changed, 6 insertions(+), 239 deletions(-) diff --git a/bin/blob-bar b/bin/blob-bar index 8d912f7..6bd601c 100755 --- a/bin/blob-bar +++ b/bin/blob-bar @@ -2,7 +2,7 @@ # blob:summary=Configure the bar and its widget layout # blob:group=bar -# blob:args=use | reset | defaults | position | transparent | put [placement] | move [placement] | set [--json] [placement] +# blob:args=use | reset | defaults | transparent | put [placement] | move [placement] | set [--json] [placement] # blob:examples=blob bar use local.neon-bar | blob bar put blob.keyboard-layout --after blob.clock | blob bar move blob.clock --section center --index 0 | blob bar set blob.clock format HH:mm set -euo pipefail @@ -16,7 +16,6 @@ Usage: blob bar [args...] use Use a bar option as the active bar reset Return to the built-in Blob bar defaults Restore the default bar and service widgets - position Bar position transparent Bar transparency put [placement] Put a widget on the bar, leaving one that is already there where it is @@ -195,15 +194,6 @@ cmd_defaults() { echo "Restored the default Blob bar" } -cmd_position() { - local position="${1:-}" - [[ -n $position ]] || fail "position is required" - (( $# == 1 )) || fail "position takes a single value" - [[ $position =~ ^(top|bottom|left|right)$ ]] || fail "position must be top, bottom, left, or right" - commit "$NORMALIZE | .bar.position = \$position" --arg position "$position" - echo "Bar position set to $position" -} - cmd_transparent() { local transparent="${1:-}" [[ -n $transparent ]] || fail "transparent is required" @@ -379,9 +369,6 @@ case "$command" in defaults) cmd_defaults "$@" ;; - position) - cmd_position "$@" - ;; transparent) cmd_transparent "$@" ;; diff --git a/config/blob/shell.json b/config/blob/shell.json index 3e6bf43..21d19f0 100644 --- a/config/blob/shell.json +++ b/config/blob/shell.json @@ -5,7 +5,6 @@ "lock": 300 }, "bar": { - "position": "top", "transparent": false, "centerAnchor": "blob.clock", "layout": { diff --git a/default/blob/blob-menu.jsonc b/default/blob/blob-menu.jsonc index 167af96..a4d3655 100644 --- a/default/blob/blob-menu.jsonc +++ b/default/blob/blob-menu.jsonc @@ -185,15 +185,10 @@ "style.background": {"icon":"","label":"Background","aliases":["background","wallpaper"],"action":"background=$(blob-bg-switcher); [[ -n $background ]] && blob-bg-set \"$background\""}, "style.font": {"icon":"","label":"Font","provider":"fonts"}, "style.bar": {"icon":"󰍜","label":"Menu Bar"}, - "style.bar.position": {"icon":"","label":"Position"}, "style.bar.transparency": {"icon":"󰂵","label":"Transparency","action":"blob-bar transparent toggle"}, "style.hyprland": {"icon":"","label":"Hyprland","action":"blob-launch-config-editor \"$HOME/.config/hypr/looknfeel.lua\""}, "style.screensaver": {"icon":"󱄄","label":"Screensaver"}, "style.about": {"icon":"","label":"About"}, - "style.bar.position.top": {"icon":"󰁝","label":"Top","action":"blob-bar position top"}, - "style.bar.position.bottom": {"icon":"󰁅","label":"Bottom","action":"blob-bar position bottom"}, - "style.bar.position.left": {"icon":"󰁍","label":"Left","action":"blob-bar position left"}, - "style.bar.position.right": {"icon":"󰁔","label":"Right","action":"blob-bar position right"}, "style.about.text": {"icon":"","label":"Edit Text","action":"blob-branding-about text"}, "style.about.image": {"icon":"","label":"Set From Image","action":"blob-branding-about image"}, "style.about.default": {"icon":"","label":"Restore Default","action":"blob-branding-about reset"}, diff --git a/shell.json b/shell.json index 443b863..68ee79c 100644 --- a/shell.json +++ b/shell.json @@ -5,7 +5,6 @@ "lock": 900 }, "bar": { - "position": "top", "transparent": false, "centerAnchor": "blob.clock", "layout": { diff --git a/shell/plugins/bar/Bar.qml b/shell/plugins/bar/Bar.qml index f8cec36..a8ba180 100644 --- a/shell/plugins/bar/Bar.qml +++ b/shell/plugins/bar/Bar.qml @@ -19,7 +19,7 @@ Item { // not otherwise need it, but declaring it keeps clone construction atomic. property var pluginRegistry: null // Injected by the host shell every time shell.json is reloaded. Holds the - // `bar:` subtree: position, centerAnchor, layout. The host owns file IO; + // `bar:` subtree: centerAnchor, layout. The host owns file IO; // the bar just renders whatever it's handed. The bar font follows the // OS-level fontconfig monospace binding — it is not stored in shell.json. property var barConfig: ({}) @@ -43,7 +43,6 @@ Item { property string stateHome: home + "/.local/state" property string blobConfigDir: home + "/.config/blob" property var fallbackBarConfig: ({ - position: "top", transparent: false, centerAnchor: "blob.clock", layout: { left: [], center: [], right: [] } @@ -64,7 +63,7 @@ Item { property bool centerSectionRevealHeld: false property bool centerHoverRevealSuppressed: false property int barConfigSerial: 0 - property string position: "top" + readonly property string position: "top" // Resolves through fontconfig at paint time (Style.font.family defaults // to "monospace"), so changing the system font (via `blob-font-set`) // updates the bar without a reload. @@ -103,10 +102,6 @@ Item { property real barDragScreenY: 0 property real barDragOffsetX: 0 property real barDragOffsetY: 0 - property bool barMoveActive: false - property string barMoveCandidate: "" - property var barMoveWindow: null - property var barMoveScreen: null property var clickTargets: [] property var moduleSlots: [] property var pluginBarApis: ({}) @@ -469,63 +464,6 @@ Item { } } - // 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) { - barMoveWindow = window - barMoveScreen = window ? window.screen : null - barMoveCandidate = position - barMoveActive = true - } - - function updateBarMove(screenPoint) { - if (!barMoveActive || !barMoveScreen) return - barMoveCandidate = nearestScreenEdge(screenPoint, barMoveScreen) - } - - function clearBarMove() { - barMoveActive = false - barMoveCandidate = "" - barMoveWindow = null - barMoveScreen = null - } - - function finishBarMove() { - var edge = barMoveCandidate - if (!barMoveActive || !edge || edge === position) { - clearBarMove() - return - } - - clearBarMove() - setBarPosition(edge) - } - - 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 = "" @@ -555,10 +493,6 @@ Item { readonly property bool vertical: position === "left" || position === "right" readonly property int barSize: vertical ? Style.bar.sizeVertical : Style.bar.sizeHorizontal - function normalizePosition(value) { - return BarModel.normalizePosition(value) - } - // Apply tray-pinning on top of the shared layout normalization so the // bar host and scriptable config helpers can't drift on entry shape. function normalizeLayout(layout) { @@ -581,7 +515,6 @@ Item { function applyBarConfig() { var config = Util.isPlainObject(barConfig) ? barConfig : fallbackBarConfig - position = normalizePosition(config.position) setRequestedTransparency(config.transparent === true) centerAnchor = Util.canonicalWidgetId(config.centerAnchor || "") @@ -1218,19 +1151,6 @@ Item { } } - Variants { - model: Quickshell.screens - - delegate: Component { - BarMoveGhostPanel { - required property var modelData - - screen: modelData - ghostScreen: modelData - } - } - } - component BarPanel: PanelWindow { id: barWindow @@ -1462,59 +1382,6 @@ 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 && screenMatches - color: "transparent" - exclusionMode: ExclusionMode.Ignore - WlrLayershell.namespace: "blob-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) @@ -1635,84 +1502,11 @@ Item { } component CenterGestureArea: MouseArea { - id: gestureArea - - 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) { - // A widget above us propagates its composed press-and-hold down here without - // ever handing over the grab, so we'd get no release or cancel to end the move. - if (!gestureArea.pressed) return - 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 - } - } onDoubleClicked: function(mouse) { - if (suppressClick) { - suppressClick = false - return - } - if (mouse.button === Qt.LeftButton) { - root.toggleTransparency() - mouse.accepted = true - } + root.toggleTransparency() + mouse.accepted = true } } diff --git a/shell/plugins/bar/BarModel.js b/shell/plugins/bar/BarModel.js index a09425c..c330034 100644 --- a/shell/plugins/bar/BarModel.js +++ b/shell/plugins/bar/BarModel.js @@ -2,11 +2,6 @@ function isPlainObject(value) { return !!value && typeof value === "object" && !Array.isArray(value) } -function normalizePosition(value) { - var next = String(value || "").trim() - return /^(top|bottom|left|right)$/.test(next) ? next : "top" -} - function entrySettings(entry) { if (!isPlainObject(entry)) return {} var copy = {} @@ -214,7 +209,6 @@ if (typeof module !== "undefined") { pickDrawnSlot: pickDrawnSlot, pickPanelSlot: pickPanelSlot, nearestDropTarget: nearestDropTarget, - normalizePosition: normalizePosition, entrySettings: entrySettings, entryId: entryId, pinTrayToInner: pinTrayToInner, diff --git a/shell/plugins/notifications/Service.qml b/shell/plugins/notifications/Service.qml index 07ec407..314631c 100644 --- a/shell/plugins/notifications/Service.qml +++ b/shell/plugins/notifications/Service.qml @@ -45,7 +45,7 @@ Item { // pull notification popups away from the expected top-right location. // Falls back to the bar's default size (26 horizontal / 28 vertical) when // shell.bar isn't reachable so the popup never lands on top of the bar. - readonly property string barPosition: shell && shell.barConfig ? String(shell.barConfig.position || "top") : "top" + readonly property string barPosition: "top" readonly property bool barVertical: barPosition === "left" || barPosition === "right" readonly property int defaultBarSize: barVertical ? Style.bar.sizeVertical : Style.bar.sizeHorizontal readonly property int liveBarSize: shell && shell.bar && !shell.bar.barHidden ? Math.max(0, shell.bar.barSize) : defaultBarSize diff --git a/shell/shell.qml b/shell/shell.qml index 16e9a45..9646a21 100644 --- a/shell/shell.qml +++ b/shell/shell.qml @@ -41,7 +41,6 @@ ShellRoot { lock: 300 }, bar: { - position: "top", transparent: false, centerAnchor: "blob.clock", layout: {