diff --git a/default/hypr/bindings/utilities.lua b/default/hypr/bindings/utilities.lua index 046fbfb6..dee63d09 100644 --- a/default/hypr/bindings/utilities.lua +++ b/default/hypr/bindings/utilities.lua @@ -53,11 +53,11 @@ o.bind("SUPER + CTRL + ALT + T", "Show time", "omarchy-notification-time") o.bind("SUPER + CTRL + ALT + B", "Show battery remaining", "omarchy-notification-battery") o.bind("SUPER + CTRL + ALT + W", "Show weather", "omarchy-notification-weather") -o.bind("SUPER + CTRL + A", "Audio panel", "omarchy-shell audioPanel toggle") -o.bind("SUPER + CTRL + B", "Bluetooth panel", "omarchy-shell bluetoothPanel toggle") -o.bind("SUPER + CTRL + D", "Display panel", "omarchy-shell monitorPanel toggle") -o.bind("SUPER + CTRL + W", "Network panel", "omarchy-shell networkPanel toggle") -o.bind("SUPER + CTRL + P", "Power panel", "omarchy-shell powerPanel toggle") +o.bind("SUPER + CTRL + A", "Audio panel", "omarchy-shell panels.audio toggle") +o.bind("SUPER + CTRL + B", "Bluetooth panel", "omarchy-shell panels.bluetooth toggle") +o.bind("SUPER + CTRL + D", "Display panel", "omarchy-shell panels.monitor toggle") +o.bind("SUPER + CTRL + W", "Network panel", "omarchy-shell panels.network toggle") +o.bind("SUPER + CTRL + P", "Power panel", "omarchy-shell panels.power toggle") o.bind("SUPER + CTRL + T", "Activity", { tui = "btop" }) o.bind("SUPER + CTRL + X", "Toggle dictation", "voxtype record toggle") diff --git a/install/config/hardware/bluetooth.sh b/install/config/hardware/bluetooth.sh index e76d03de..1e5d0c30 100644 --- a/install/config/hardware/bluetooth.sh +++ b/install/config/hardware/bluetooth.sh @@ -7,7 +7,7 @@ sudo sed -i 's/^#\?AutoEnable=.*/AutoEnable=false/' /etc/bluetooth/main.conf mkdir -p ~/.config/wireplumber/wireplumber.conf.d/ cp "$OMARCHY_PATH/default/wireplumber/wireplumber.conf.d/bluetooth-a2dp-autoconnect.conf" ~/.config/wireplumber/wireplumber.conf.d/ -# Quickshell.Bluetooth has no Agent API, so the omarchy-shell bluetoothPanel +# Quickshell.Bluetooth has no Agent API, so the omarchy-shell panels.bluetooth # can't answer the auth prompts bluez issues during pair(). bt-agent registers # a NoInputNoOutput agent on the system bus so pair() actually completes. mkdir -p ~/.config/systemd/user/ diff --git a/install/first-run/wifi.sh b/install/first-run/wifi.sh index ff26872c..f184eba5 100644 --- a/install/first-run/wifi.sh +++ b/install/first-run/wifi.sh @@ -8,7 +8,7 @@ notify_update() { notify_wifi() { ( action=$(notify-send -a omarchy-action -u critical --hint=string:omarchy-glyph:󰖩 "Click to Setup Wi-Fi" -A "default=Setup") - [[ $action == "default" ]] && omarchy-shell networkPanel toggle + [[ $action == "default" ]] && omarchy-shell panels.network toggle ) >/dev/null 2>&1 & } diff --git a/shell/Ui/KeyboardPanel.qml b/shell/Ui/KeyboardPanel.qml index 684666c7..3fbdc9ee 100644 --- a/shell/Ui/KeyboardPanel.qml +++ b/shell/Ui/KeyboardPanel.qml @@ -59,8 +59,8 @@ PanelWindow { readonly property var anchorWindow: anchorItem ? anchorItem.QsWindow.window : null readonly property string barPos: bar ? bar.position : "top" - function closePopout() { - if (owner && "closePopout" in owner) owner.closePopout() + function close() { + if (owner && "close" in owner) owner.close() else root.open = false } @@ -255,7 +255,7 @@ PanelWindow { onExited: hoveringBar = false onClicked: function(mouse) { if (inBarRegion(mouse.x, mouse.y) && forwardBarClick(mouse.x, mouse.y, mouse.button)) return - root.closePopout() + root.close() } } diff --git a/shell/Ui/Panel.qml b/shell/Ui/Panel.qml new file mode 100644 index 00000000..3bc776df --- /dev/null +++ b/shell/Ui/Panel.qml @@ -0,0 +1,40 @@ +import QtQuick +import Quickshell.Io + +// Base item for shell panels. Panels are not bar widgets, but the bar may host +// or toggle them and injects the same ambient context while doing so. The base +// owns the shared IPC-backed open/close lifecycle; panel implementations own +// their button behavior, keyboard navigation, and content. +Item { + id: root + + property QtObject bar: null + property string moduleName: "" + property var settings: ({}) + property string ipcTarget: "" + property bool manageIpc: true + property alias controller: panelController + + readonly property bool opened: panelController.open + + function open() { panelController.show() } + function close() { panelController.hide() } + function toggle() { opened ? close() : open() } + + PanelController { + id: panelController + } + + property IpcHandler _ipc: manageIpc ? ipcComponent.createObject(root) : null + + property Component ipcComponent: Component { + IpcHandler { + target: root.ipcTarget + function open(): void { root.open() } + function close(): void { root.close() } + function show(): void { root.open() } + function hide(): void { root.close() } + function toggle(): void { root.toggle() } + } + } +} diff --git a/shell/Ui/PanelController.qml b/shell/Ui/PanelController.qml index 408183a4..c386640b 100644 --- a/shell/Ui/PanelController.qml +++ b/shell/Ui/PanelController.qml @@ -1,49 +1,16 @@ import QtQuick -import Quickshell -import Quickshell.Io - -// Owns the open/close lifecycle for a bar panel widget. Wraps the -// repetitive popupOpen + closePopout + toggle/show/hide IpcHandler triplet -// so each panel just declares one of these and binds its WidgetButton + -// KeyboardPanel to the exposed `open` property. +// Stores the open state for a shell panel. Panel owns the public lifecycle +// methods and IPC wiring; this object only keeps state separate from the +// panel implementation's own properties. // // Usage: -// PanelController { id: ctrl; ipcTarget: "audioPanel" } -// -// WidgetButton { onPressed: ctrl.toggle() } -// KeyboardPanel { open: ctrl.open; owner: ctrl; focusTarget: keyCatcher } -// -// The bar popout coordinator uses `owner` as a registry key, so each -// PanelController instance doubles as that key. KeyboardPanel calls -// `owner.closePopout()` when another panel grabs the slot. -// -// Set `manageIpc: false` when the panel needs to declare its own IpcHandler -// for additional methods on the same target (monitorPanel adds brightness + -// state). Quickshell only honors one IpcHandler per target, so the panel's -// handler must then also delegate toggle/show/hide to this controller. +// PanelController { id: panelController } QtObject { id: root - // IPC target name. The bar pairs this with the bar widget's filename so a - // Hyprland keybind (`omarchy-shell toggle`) summons the panel. - property string ipcTarget: "" - property bool manageIpc: true - property bool open: false function toggle() { open = !open } function show() { if (!open) open = true } function hide() { open = false } - function closePopout() { open = false } - - property IpcHandler _ipc: manageIpc ? ipcComponent.createObject(root) : null - - property Component ipcComponent: Component { - IpcHandler { - target: root.ipcTarget - function toggle(): void { root.toggle() } - function show(): void { root.show() } - function hide(): void { root.hide() } - } - } } diff --git a/shell/Ui/PanelKeyCatcher.qml b/shell/Ui/PanelKeyCatcher.qml index fd9b87f1..27e17d1f 100644 --- a/shell/Ui/PanelKeyCatcher.qml +++ b/shell/Ui/PanelKeyCatcher.qml @@ -12,7 +12,7 @@ import QtQuick // anchors.fill: parent // onMoveRequested: function(dx, dy) { root.moveCursor(dx, dy) } // onActivateRequested: root.activateCursor() -// onCloseRequested: root.closePopout() +// onCloseRequested: root.close() // onDeleteRequested: root.deleteSelected() // onTextKey: function(t) { if (t === "r") root.refresh() } // diff --git a/shell/Ui/PopupCard.qml b/shell/Ui/PopupCard.qml index e40c877b..9a4fc9c2 100644 --- a/shell/Ui/PopupCard.qml +++ b/shell/Ui/PopupCard.qml @@ -55,8 +55,8 @@ PopupWindow { return Math.round(Math.min(desired, maxHeight)) } - function closePopout() { - if (owner && "closePopout" in owner) owner.closePopout() + function close() { + if (owner && "close" in owner) owner.close() else root.open = false } @@ -80,7 +80,7 @@ PopupWindow { HyprlandFocusGrab { active: root.open && root.triggerMode === "click" windows: root.anchorWindow ? [root, root.anchorWindow] : [root] - onCleared: root.closePopout() + onCleared: root.close() } anchor { diff --git a/shell/Ui/qmldir b/shell/Ui/qmldir index 63cf7782..69940561 100644 --- a/shell/Ui/qmldir +++ b/shell/Ui/qmldir @@ -8,6 +8,7 @@ CursorSurface 1.0 CursorSurface.qml Dropdown 1.0 Dropdown.qml KeyboardPanel 1.0 KeyboardPanel.qml NumberField 1.0 NumberField.qml +Panel 1.0 Panel.qml PanelActionButton 1.0 PanelActionButton.qml PanelController 1.0 PanelController.qml PanelKeyCatcher 1.0 PanelKeyCatcher.qml diff --git a/shell/plugins/bar/Bar.qml b/shell/plugins/bar/Bar.qml index 452dfa27..e013f72e 100644 --- a/shell/plugins/bar/Bar.qml +++ b/shell/plugins/bar/Bar.qml @@ -95,7 +95,7 @@ Item { function requestPopout(owner) { if (activePopout === owner) return - if (activePopout && "closePopout" in activePopout) activePopout.closePopout() + if (activePopout && "close" in activePopout) activePopout.close() activePopout = owner } @@ -277,16 +277,16 @@ Item { return source ? Util.fileUrl(source) : "" } - // First-party widgets are registered with the BarWidgetRegistry at startup. - // Each entry maps a widget id to its display metadata; the QML source lives - // at widgets/.qml and is loaded asynchronously via Qt.createComponent. + // First-party modules are registered with the BarWidgetRegistry at startup. + // Each entry maps a module id to its display metadata; the QML source is + // loaded asynchronously via Qt.createComponent. readonly property var firstPartyWidgetMetadata: ({ "media": { displayName: "Media", description: "MPRIS now-playing with playback controls", category: "Media", allowMultiple: false }, - "audioPanel": { displayName: "Audio", description: "Volume slider, output picker, per-app mixer", category: "Audio", allowMultiple: false }, - "monitorPanel": { displayName: "Display", description: "Brightness slider and laptop display controls", category: "System", allowMultiple: false }, - "networkPanel": { displayName: "Network", description: "Wi-Fi list and connection state", category: "Network", allowMultiple: false }, - "powerPanel": { displayName: "Power", description: "Battery, power profile, and system stats", category: "System", allowMultiple: false }, - "bluetoothPanel": { displayName: "Bluetooth", description: "Bluetooth device list with connect/disconnect", category: "Network", allowMultiple: false }, + "audioPanel": { displayName: "Audio", description: "Volume slider, output picker, per-app mixer", category: "Audio", allowMultiple: false, sourceDir: "../panels", sourceName: "Audio" }, + "monitorPanel": { displayName: "Display", description: "Brightness slider and laptop display controls", category: "System", allowMultiple: false, sourceDir: "../panels", sourceName: "Monitor" }, + "networkPanel": { displayName: "Network", description: "Wi-Fi list and connection state", category: "Network", allowMultiple: false, sourceDir: "../panels", sourceName: "Network" }, + "powerPanel": { displayName: "Power", description: "Battery, power profile, and system stats", category: "System", allowMultiple: false, sourceDir: "../panels", sourceName: "Power" }, + "bluetoothPanel": { displayName: "Bluetooth", description: "Bluetooth device list with connect/disconnect", category: "Network", allowMultiple: false, sourceDir: "../panels", sourceName: "Bluetooth" }, "calendar": { displayName: "Calendar", description: "Clock with month-grid popup", category: "Time", allowMultiple: false, settingsForm: "calendarSettings" }, "notificationCenter": { displayName: "Notification center", description: "Recent notifications + DND", category: "Status", allowMultiple: false }, "systemStats": { displayName: "System stats", description: "CPU icon — hover for graphs, click to open btop", category: "System", allowMultiple: false }, @@ -316,8 +316,10 @@ Item { } function registerOneFirstPartyWidget(id) { - var url = Qt.resolvedUrl("widgets/" + id + ".qml") var meta = firstPartyWidgetMetadata[id] || {} + var sourceDir = meta.sourceDir || "widgets" + var sourceName = meta.sourceName || id + var url = Qt.resolvedUrl(sourceDir + "/" + sourceName + ".qml") var enrichedMeta = { displayName: meta.displayName || id, description: meta.description || "", @@ -1285,7 +1287,7 @@ Item { property bool expanded: false property bool managePopupOpen: false - function closePopout() { managePopupOpen = false } + function close() { managePopupOpen = false } // Re-resolve the tray's own entry settings whenever the bar layout reloads. readonly property var trayEntry: { diff --git a/shell/plugins/bar/README.md b/shell/plugins/bar/README.md index 2af1a8d8..1199dd16 100644 --- a/shell/plugins/bar/README.md +++ b/shell/plugins/bar/README.md @@ -7,7 +7,8 @@ the shell for its whole session. - `manifest.json` declares the plugin (`id: omarchy.bar`, `kind: bar`) and points at `Bar.qml` as the entry point. - `Bar.qml` is Omarchy-owned bar engine code, loaded by the omarchy-shell host. Users should not edit it directly. -- `widgets/` holds first-party widgets — modular, interactive components shipped with Omarchy. +- `widgets/` holds first-party bar widgets. +- `../panels/` holds first-party panels that the bar can toggle by id. - The bar receives its config from the host shell as a `barConfig` property; the host loads it from `~/.config/omarchy/shell.json` (or `shell-defaults.json` when the user has no file). - `omarchy-style-bar-position` updates only the user shell.json file. @@ -49,15 +50,11 @@ Example `shell.json` (bar subtree only shown): ## Module catalogue -### First-party interactive widgets (in `widgets/`) +### First-party interactive widgets | Name | What it does | Interactions | |---|---|---| | `media` | MPRIS now-playing — scrolling track + artist, cover-art popup | left = play/pause · middle = next · scroll = prev/next · right = popup | -| `audioPanel` | Volume icon + popup with master slider, output-device picker, per-app mixer | left = popup · right = mute · middle = popup · scroll = volume | -| `networkPanel` | Wi-Fi/Ethernet icon + popup with Wi-Fi scan, signal, connect, DNS provider selection | left = popup · right = nmtui | -| `powerPanel` | Battery/AC icon + popup with battery stats, power profiles, and system info | left = popup | -| `bluetoothPanel` | Bluetooth icon + popup with device list, connect/disconnect, battery | left = popup · right = toggle radio · middle = bluetoothctl TUI | | `calendar` | Clock + popup with month-grid calendar | left = popup · right = tz selector | | `notificationCenter` | Bell with badge + popup with recent notifications, DND toggle | left = popup · right = toggle DND | | `systemStats` | Inline CPU + memory sparklines, popup with detail | left = popup · right = terminal | @@ -65,11 +62,21 @@ Example `shell.json` (bar subtree only shown): | `idleInhibitor` | Coffee-cup that toggles `omarchy-toggle-idle` | left = toggle | | `microphone` | Mic icon + scroll volume | left = mute toggle · middle = audio panel · scroll = source volume | +### First-party panels (in `../panels/`) + +| Name | What it does | Interactions | +|---|---|---| +| `panels.audio` | Volume icon + popup with master slider, output-device picker, per-app mixer | left = popup · right = mute · middle = popup · scroll = volume | +| `panels.network` | Wi-Fi/Ethernet icon + popup with Wi-Fi scan, signal, connect, DNS provider selection | left = popup · right = nmtui | +| `panels.power` | Battery/AC icon + popup with battery stats, power profiles, and system info | left = popup | +| `panels.bluetooth` | Bluetooth icon + popup with device list, connect/disconnect, battery | left = popup · right = toggle radio · middle = bluetoothctl TUI | +| `panels.monitor` | Brightness and laptop display controls | left = popup | + ### Built-in base modules (in `Bar.qml`) `omarchy`, `workspaces`, `clock`, `update`, `indicators`, `tray`. -The `indicators` module loads individual bar indicators from `indicators/`, ordered by its `items` array in `shell.json`. Rich panels such as `powerPanel`, `networkPanel`, and `audioPanel` live in `widgets/` above. +The `indicators` module loads individual bar indicators from `indicators/`, ordered by its `items` array in `shell.json`. Rich panels such as `powerPanel`, `networkPanel`, and `audioPanel` live in `../panels/` above. ## Orientation @@ -162,9 +169,11 @@ Widgets receive `bar` (the shell root), `moduleName` (string), and `settings` (o - `bar.showTooltip(target, text)` / `bar.hideTooltip(target)` — shared tooltip popup - `bar.requestPopout(owner)` / `bar.releasePopout(owner)` — one-popup-at-a-time coordinator -First-party widgets live in `widgets/.qml` and are picked up by the -shell's `BarWidgetRegistry` at startup; reference one by `id` in any -layout list. +First-party bar widgets live in `widgets/.qml`; first-party panels +live in `../panels/.qml` and expose IPC targets such as +`panels.audio`. The compatibility bar layout ids remain `audioPanel`, +`networkPanel`, and so on, and are picked up by the shell's +`BarWidgetRegistry` at startup; reference one by `id` in any layout list. Third-party widgets ship as separate plugins under `~/.config/omarchy/plugins//` with their own `manifest.json` diff --git a/shell/plugins/bar/widgets/calendar.qml b/shell/plugins/bar/widgets/calendar.qml index c3282daf..067893ae 100644 --- a/shell/plugins/bar/widgets/calendar.qml +++ b/shell/plugins/bar/widgets/calendar.qml @@ -13,7 +13,7 @@ BarWidget { property date viewMonth: new Date() property bool popupOpen: false - function closePopout() { popupOpen = false } + function close() { popupOpen = false } function setting(name, fallback) { var value = settings ? settings[name] : undefined diff --git a/shell/plugins/bar/widgets/media.qml b/shell/plugins/bar/widgets/media.qml index b0ec1b9f..3a79c26a 100644 --- a/shell/plugins/bar/widgets/media.qml +++ b/shell/plugins/bar/widgets/media.qml @@ -33,7 +33,7 @@ BarWidget { property bool popupOpen: false - function closePopout() { popupOpen = false } + function close() { popupOpen = false } property real maxLabelWidth: 180 visible: hasMedia diff --git a/shell/plugins/bar/widgets/microphone.qml b/shell/plugins/bar/widgets/microphone.qml index c137f3be..33e84ddf 100644 --- a/shell/plugins/bar/widgets/microphone.qml +++ b/shell/plugins/bar/widgets/microphone.qml @@ -42,7 +42,7 @@ BarWidget { active: root.inUse tooltipText: root.muted ? "Microphone muted" : (root.inUse ? "Microphone in use" : "Microphone live") onPressed: function(b) { - if (b === Qt.MiddleButton) root.bar.run("omarchy-shell audioPanel toggle") + if (b === Qt.MiddleButton) root.bar.run("omarchy-shell panels.audio toggle") else root.toggleMute() } onWheelMoved: function(delta) { diff --git a/shell/plugins/bar/widgets/notificationCenter.qml b/shell/plugins/bar/widgets/notificationCenter.qml index c21bc40a..4c9202e2 100644 --- a/shell/plugins/bar/widgets/notificationCenter.qml +++ b/shell/plugins/bar/widgets/notificationCenter.qml @@ -10,10 +10,10 @@ BarWidget { property bool popupOpen: false - function closePopout() { popupOpen = false } + function close() { popupOpen = false } // Always default to the pending tab when there's anything unseen, no - // matter how the popup was opened (click, keybind/IPC, or the closePopout + // matter how the popup was opened (click, keybind/IPC, or the close // path). Keeps the spec from drifting based on the user's last manual // tab selection. onPopupOpenChanged: { diff --git a/shell/plugins/bar/widgets/systemStats.qml b/shell/plugins/bar/widgets/systemStats.qml index 2939b83b..520f07f4 100644 --- a/shell/plugins/bar/widgets/systemStats.qml +++ b/shell/plugins/bar/widgets/systemStats.qml @@ -19,7 +19,7 @@ BarWidget { property bool popupOpen: false - function closePopout() { popupOpen = false } + function close() { popupOpen = false } readonly property int historyLimit: 30 diff --git a/shell/plugins/bar/widgets/weather.qml b/shell/plugins/bar/widgets/weather.qml index 2b5284cf..0517652a 100644 --- a/shell/plugins/bar/widgets/weather.qml +++ b/shell/plugins/bar/widgets/weather.qml @@ -10,7 +10,7 @@ BarWidget { property bool popupOpen: false - function closePopout() { popupOpen = false } + function close() { popupOpen = false } function showPopup() { root.popupOpen = !root.popupOpen diff --git a/shell/plugins/dev-gallery/GalleryPanel.qml b/shell/plugins/dev-gallery/GalleryPanel.qml index 86d188d3..6e380059 100644 --- a/shell/plugins/dev-gallery/GalleryPanel.qml +++ b/shell/plugins/dev-gallery/GalleryPanel.qml @@ -407,7 +407,7 @@ Item { color: Qt.darker(root.foreground, 1.4) font.family: root.fontFamily font.pixelSize: Style.font.bodySmall - text: "Single cursor. Most reusable panel primitives expose hasCursor: bool and emit hovered(bool); composed rows (including sliders) wrap their content in CursorSurface. The panel root owns cursorActive + focusSection + selectedIndex; each element binds hasCursor: root.cursorActive && root.focusSection === 'X' && root.selectedIndex === N, and onHovered flips cursorActive on while updating the same state. No initial highlight, then one highlight on screen once the keyboard or mouse enters. See plugins/bar/widgets/audioPanel.qml for the canonical recipe." + text: "Single cursor. Most reusable panel primitives expose hasCursor: bool and emit hovered(bool); composed rows (including sliders) wrap their content in CursorSurface. The panel root owns cursorActive + focusSection + selectedIndex; each element binds hasCursor: root.cursorActive && root.focusSection === 'X' && root.selectedIndex === N, and onHovered flips cursorActive on while updating the same state. No initial highlight, then one highlight on screen once the keyboard or mouse enters. See plugins/panels/Audio.qml for the canonical recipe." } Text { width: parent.width diff --git a/shell/plugins/bar/widgets/audioPanel.qml b/shell/plugins/panels/Audio.qml similarity index 99% rename from shell/plugins/bar/widgets/audioPanel.qml rename to shell/plugins/panels/Audio.qml index 49eb662e..a4a84c65 100644 --- a/shell/plugins/bar/widgets/audioPanel.qml +++ b/shell/plugins/panels/Audio.qml @@ -7,14 +7,10 @@ import Quickshell.Services.Pipewire import qs.Ui import qs.Commons -BarWidget { +Panel { id: root moduleName: "audioPanel" - - - PanelController { id: ctrl; ipcTarget: "audioPanel" } - readonly property bool popupOpen: ctrl.open - function closePopout() { ctrl.hide() } + ipcTarget: "panels.audio" readonly property var sink: Pipewire.defaultAudioSink readonly property var source: Pipewire.defaultAudioSource @@ -214,8 +210,8 @@ BarWidget { } } - onPopupOpenChanged: { - if (popupOpen) { + onOpenedChanged: { + if (opened) { focusSection = "output" selectedIndex = -1 // first keyboard cursor reveal starts on the output slider cursorActive = false @@ -546,7 +542,7 @@ for block in re.split(r"(?m)^Sink #", sys.stdin.read())[1:]: Timer { interval: 5000 - running: root.popupOpen + running: root.opened repeat: true triggeredOnStart: true onTriggered: if (!sinkAvailabilityProc.running) sinkAvailabilityProc.running = true @@ -560,7 +556,7 @@ for block in re.split(r"(?m)^Sink #", sys.stdin.read())[1:]: fontSize: Style.font.body onPressed: function(b) { if (b === Qt.RightButton) root.toggleOutputMute() - else ctrl.toggle() + else root.toggle() } onWheelMoved: function(delta) { @@ -574,7 +570,7 @@ for block in re.split(r"(?m)^Sink #", sys.stdin.read())[1:]: anchorItem: button owner: ctrl bar: root.bar - open: ctrl.open + open: root.opened focusTarget: keyCatcher contentWidth: panel.fittedContentWidth(Style.space(370)) contentHeight: panel.fittedContentHeight(panelColumn.implicitHeight, Style.space(560)) @@ -588,7 +584,7 @@ for block in re.split(r"(?m)^Sink #", sys.stdin.read())[1:]: else if (dx !== 0) root.adjustVolume(dx * 0.05) } onActivateRequested: if (root.cursorActive) root.activateCursor() - onCloseRequested: root.closePopout() + onCloseRequested: root.close() onTextKey: function(t) { // 'm' mutes whatever the cursor is on: focused section's slider // for output/input, the focused stream for streams. diff --git a/shell/plugins/bar/widgets/bluetoothPanel.qml b/shell/plugins/panels/Bluetooth.qml similarity index 98% rename from shell/plugins/bar/widgets/bluetoothPanel.qml rename to shell/plugins/panels/Bluetooth.qml index f114ae54..7fd7c74d 100644 --- a/shell/plugins/bar/widgets/bluetoothPanel.qml +++ b/shell/plugins/panels/Bluetooth.qml @@ -6,13 +6,10 @@ import Quickshell.Bluetooth import qs.Ui import qs.Commons -BarWidget { +Panel { id: root moduleName: "bluetoothPanel" - - - PanelController { id: ctrl; ipcTarget: "bluetoothPanel" } - readonly property bool popupOpen: ctrl.open + ipcTarget: "panels.bluetooth" // Address -> true while we are waiting for a click-initiated pair to land // so we can chain trust + connect at root scope. Doing this in the row's @@ -20,8 +17,6 @@ BarWidget { // moment `paired` flips, before the row's handler reliably fires. property var pendingPairAddresses: ({}) - function closePopout() { ctrl.hide() } - readonly property var adapter: Bluetooth.defaultAdapter readonly property var devices: Bluetooth.devices ? Bluetooth.devices.values : [] @@ -219,8 +214,8 @@ BarWidget { else if (dev.forget) dev.forget() } - onPopupOpenChanged: { - if (popupOpen) { + onOpenedChanged: { + if (opened) { if (adapter && adapter.enabled && !adapter.discovering) adapter.discovering = true if (knownDevices.length > 0) { focusSection = "known"; selectedIndex = 0 } else { focusSection = "header"; selectedIndex = 1 } @@ -303,7 +298,7 @@ BarWidget { Connections { target: root.adapter || null function onEnabledChanged() { - if (root.popupOpen && root.adapter && root.adapter.enabled && !root.adapter.discovering) + if (root.opened && root.adapter && root.adapter.enabled && !root.adapter.discovering) root.adapter.discovering = true } } @@ -341,7 +336,7 @@ BarWidget { onPressed: function(b) { if (b === Qt.RightButton && root.adapter) root.adapter.enabled = !root.adapter.enabled else if (b === Qt.MiddleButton) root.bar.run("omarchy-launch-bluetooth") - else ctrl.toggle() + else root.toggle() } } @@ -350,7 +345,7 @@ BarWidget { anchorItem: button owner: ctrl bar: root.bar - open: ctrl.open + open: root.opened focusTarget: keyCatcher contentWidth: panel.fittedContentWidth(Style.space(320)) contentHeight: panel.fittedContentHeight(column.implicitHeight) @@ -364,7 +359,7 @@ BarWidget { else if (dx !== 0) root.moveCursorH(dx) } onActivateRequested: if (root.cursorActive) root.activateCursor() - onCloseRequested: root.closePopout() + onCloseRequested: root.close() onDeleteRequested: if (root.cursorActive) root.deleteSelected() Column { diff --git a/shell/plugins/bar/widgets/monitorPanel.qml b/shell/plugins/panels/Monitor.qml similarity index 97% rename from shell/plugins/bar/widgets/monitorPanel.qml rename to shell/plugins/panels/Monitor.qml index d8d85e54..5ea5ebc4 100644 --- a/shell/plugins/bar/widgets/monitorPanel.qml +++ b/shell/plugins/panels/Monitor.qml @@ -5,15 +5,14 @@ import Quickshell.Io import qs.Ui import qs.Commons -BarWidget { +Panel { id: root moduleName: "monitorPanel" - + ipcTarget: "panels.monitor" + manageIpc: false // manageIpc: false so this panel can own the single IpcHandler the target // permits — needed for the brightness + state methods below. - PanelController { id: ctrl; ipcTarget: "monitorPanel"; manageIpc: false } - readonly property bool popupOpen: ctrl.open property int brightnessPercent: 0 property int pendingBrightnessPercent: 0 property bool brightnessSetQueued: false @@ -29,7 +28,7 @@ BarWidget { // Cursor model shared by keyboard and mouse. Sections: // "brightness" - single slider row, selectedIndex = -1 sentinel - // (mirrors audioPanel's slider rows). Only present if a + // (mirrors Audio's slider rows). Only present if a // controllable backlight was detected. // "scale" - 6 Button scale presets; treated as a single // horizontal row from j/k's perspective. h/l moves @@ -170,10 +169,8 @@ BarWidget { flick.contentY = bottom + margin - flick.height } - function closePopout() { ctrl.hide() } - IpcHandler { - target: "monitorPanel" + target: "panels.monitor" function brightness(percent: string): string { var value = Number(percent) @@ -191,9 +188,11 @@ BarWidget { }) } - function toggle(): void { ctrl.toggle() } - function show(): void { ctrl.show() } - function hide(): void { ctrl.hide() } + function open(): void { root.open() } + function close(): void { root.close() } + function toggle(): void { root.toggle() } + function show(): void { root.open() } + function hide(): void { root.close() } } function refresh() { @@ -260,8 +259,8 @@ BarWidget { // KeyboardPanel takes Exclusive focus at map-time, so SUPER-bound IPC // summons land with j/k ready to navigate. Keep a default landing point, // but don't paint the cursor until hover or the first navigation key. - onPopupOpenChanged: { - if (popupOpen) { + onOpenedChanged: { + if (opened) { refresh() if (brightnessAvailable) { focusSection = "brightness" @@ -343,7 +342,7 @@ BarWidget { bar: root.bar text: root.displays.length > 1 ? "󰍺" : "󰍹" fontSize: Style.font.subtitle - onPressed: function(b) { ctrl.toggle() } + onPressed: function(b) { root.toggle() } onWheelMoved: function(delta) { if (root.brightnessAvailable) root.setBrightness(root.brightnessPercent + (delta > 0 ? 5 : -5)) } @@ -354,7 +353,7 @@ BarWidget { anchorItem: button owner: ctrl bar: root.bar - open: ctrl.open + open: root.opened focusTarget: keyCatcher contentWidth: panel.fittedContentWidth(Style.space(320)) contentHeight: panel.fittedContentHeight(panelColumn.implicitHeight, Style.space(560)) @@ -371,7 +370,7 @@ BarWidget { } } onActivateRequested: if (root.cursorActive) root.activateCursor() - onCloseRequested: root.closePopout() + onCloseRequested: root.close() ScrollView { id: scrollArea diff --git a/shell/plugins/bar/widgets/networkPanel.qml b/shell/plugins/panels/Network.qml similarity index 98% rename from shell/plugins/bar/widgets/networkPanel.qml rename to shell/plugins/panels/Network.qml index e6f3228a..f5718bfd 100644 --- a/shell/plugins/bar/widgets/networkPanel.qml +++ b/shell/plugins/panels/Network.qml @@ -5,16 +5,14 @@ import Quickshell.Io import qs.Ui import qs.Commons -BarWidget { +Panel { id: root moduleName: "networkPanel" + ipcTarget: "panels.network" - - PanelController { id: ctrl; ipcTarget: "networkPanel" } - readonly property bool popupOpen: ctrl.open // Centralized close so callers can't forget to drop the passphrase prompt. - function closePopout() { - ctrl.hide() + function close() { + root.controller.hide() passwordSsid = "" } @@ -96,11 +94,11 @@ BarWidget { readonly property color selectedFill: bar ? Style.selectedFillFor(bar.foreground, Color.accent) : "transparent" // The panel below is its own layer-shell with Exclusive keyboard focus, - // so Hyprland grants focus when the surface is mapped (popupOpen flips + // so Hyprland grants focus when the surface is mapped (opened flips // to true). That's what makes the SUPER+CTRL+W keybind actually work // — OnDemand only grants focus on click/hover. - onPopupOpenChanged: { - if (popupOpen) { + onOpenedChanged: { + if (opened) { refresh(true) selectedIndex = wifiNetworks.length > 0 ? 0 : -1 focusSection = wifiNetworks.length > 0 ? "wifi" : "dns" @@ -115,7 +113,7 @@ BarWidget { // The KeyboardPanel's focusTarget covers initial popup-open; this handles // the inline-editor case where focus was handed off to a child. onPasswordSsidChanged: { - if (passwordSsid === "" && popupOpen) { + if (passwordSsid === "" && opened) { Qt.callLater(function() { if (keyCatcher) keyCatcher.forceActiveFocus() }) } } @@ -129,7 +127,7 @@ BarWidget { if (focusSection === "wifi") focusSection = "dns" } else if (selectedIndex >= wifiNetworks.length) { selectedIndex = wifiNetworks.length - 1 - } else if (selectedIndex < 0 && popupOpen) { + } else if (selectedIndex < 0 && opened) { selectedIndex = 0 } } @@ -385,14 +383,14 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\ if (provider === "Custom") { var launcher = Util.shellQuote(root.bar.omarchyPath + "/bin/omarchy-launch-floating-terminal-with-presentation") root.bar.run(launcher + " " + Util.shellQuote(root.dnsCommand(provider))) - root.closePopout() + root.close() return } root.pendingDnsProvider = provider actionProc.command = ["bash", "-lc", root.dnsCommand(provider)] actionProc.running = true - root.closePopout() + root.close() } function isProtected(security) { @@ -597,7 +595,7 @@ fi id: detailsPoll interval: 1500 repeat: true - running: root.popupOpen + running: root.opened onTriggered: if (!detailsProc.running) detailsProc.running = true } @@ -631,8 +629,8 @@ fi rightExtraMargin: 2 onPressed: function(b) { - if (ctrl.open) root.closePopout() - else { ctrl.show(); root.refresh() } + if (root.opened) root.close() + else { root.open(); root.refresh() } } } @@ -647,7 +645,7 @@ fi anchorItem: button owner: ctrl bar: root.bar - open: ctrl.open + open: root.opened focusTarget: keyCatcher contentWidth: panel.fittedContentWidth(Style.space(340)) contentHeight: panel.fittedContentHeight(column.implicitHeight) @@ -699,7 +697,7 @@ fi else root.activateSelected() } } - onCloseRequested: root.closePopout() + onCloseRequested: root.close() onDeleteRequested: { if (root.cursorActive && root.focusSection === "wifi") root.forgetSelected() } diff --git a/shell/plugins/bar/widgets/powerPanel.qml b/shell/plugins/panels/Power.qml similarity index 96% rename from shell/plugins/bar/widgets/powerPanel.qml rename to shell/plugins/panels/Power.qml index a5667dad..13d23bb0 100644 --- a/shell/plugins/bar/widgets/powerPanel.qml +++ b/shell/plugins/panels/Power.qml @@ -5,13 +5,10 @@ import Quickshell.Services.UPower import qs.Commons import qs.Ui -BarWidget { +Panel { id: root moduleName: "powerPanel" - - - PanelController { id: ctrl; ipcTarget: "powerPanel" } - readonly property bool popupOpen: ctrl.open + ipcTarget: "panels.power" property var batteryInfo: ({}) property var systemInfo: ({}) property var profiles: [] @@ -19,8 +16,6 @@ BarWidget { property int profileIndex: 0 property bool cursorActive: false - function closePopout() { ctrl.hide() } - function selectProfileByDelta(delta) { if (profiles.length === 0) { profileIndex = 0; return } profileIndex = Math.max(0, Math.min(profiles.length - 1, profileIndex + delta)) @@ -95,7 +90,7 @@ BarWidget { profiles = list activeProfile = active if (profileIndex >= profiles.length) profileIndex = Math.max(0, profiles.length - 1) - if (popupOpen && activeProfile !== "") { + if (opened && activeProfile !== "") { var idx = profiles.indexOf(activeProfile) if (idx >= 0) profileIndex = idx } @@ -107,8 +102,8 @@ BarWidget { actionProc.running = true } - onPopupOpenChanged: { - if (popupOpen) { + onOpenedChanged: { + if (opened) { refresh() var idx = profiles.indexOf(activeProfile) profileIndex = idx >= 0 ? idx : 0 @@ -164,7 +159,7 @@ printf 'time\t%s\n' "$($OMARCHY_PATH/bin/omarchy-battery-remaining-time 2>/dev/n rightExtraMargin: 2 active: UPower.displayDevice && UPower.displayDevice.percentage <= 0.2 && UPower.onBattery tooltipText: "" - onPressed: function(b) { ctrl.toggle() } + onPressed: function(b) { root.toggle() } } KeyboardPanel { @@ -172,7 +167,7 @@ printf 'time\t%s\n' "$($OMARCHY_PATH/bin/omarchy-battery-remaining-time 2>/dev/n anchorItem: button owner: ctrl bar: root.bar - open: ctrl.open + open: root.opened focusTarget: keyCatcher contentWidth: panel.fittedContentWidth(Style.space(340)) contentHeight: panel.fittedContentHeight(column.implicitHeight) @@ -186,7 +181,7 @@ printf 'time\t%s\n' "$($OMARCHY_PATH/bin/omarchy-battery-remaining-time 2>/dev/n else if (dy !== 0) root.selectProfileByDelta(dy) } onActivateRequested: if (root.cursorActive) root.activateSelectedProfile() - onCloseRequested: root.closePopout() + onCloseRequested: root.close() Column { id: column